--- a/so-widgets-bundle/base/base.php
+++ b/so-widgets-bundle/base/base.php
@@ -55,9 +55,7 @@
* The Ajax handler for getting a list of available icons.
*/
function siteorigin_widget_get_icon_list() {
- if ( empty( $_REQUEST['_widgets_nonce'] ) || ! wp_verify_nonce( $_REQUEST['_widgets_nonce'], 'widgets_action' ) ) {
- wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 403 );
- }
+ siteorigin_verify_request_permissions();
if ( empty( $_GET['family'] ) ) {
wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 400 );
@@ -717,3 +715,33 @@
$json = wp_json_encode( $decoded, JSON_UNESCAPED_SLASHES );
return $json ? $json : '[]';
}
+
+/**
+ * Verify capability and nonce for admin widget actions.
+ *
+ * This function verifies the nonce sent in the request If either check fails, it terminates the request with an generic error message.
+ *
+ * @param string $permission The capability required to perform the action. Default is 'edit_posts'.
+ * @param string $nonce The name of the nonce field to check in the request. Default is '_widgets_nonce'.
+ * @param string $nonce_action The action name to verify the nonce against. Default is 'widgets_action'.
+ *
+ * @return bool True if the nonce and capability checks pass.
+ */
+function siteorigin_verify_request_permissions(
+ $permission = 'edit_posts',
+ $nonce = '_widgets_nonce',
+ $nonce_action = 'widgets_action'
+) : bool {
+ if (
+ empty( $_REQUEST[ $nonce ] ) ||
+ ! wp_verify_nonce( $_REQUEST[ $nonce ], $nonce_action )
+ ) {
+ wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 403 );
+ }
+
+ if ( ! current_user_can( $permission ) ) {
+ wp_die( __( 'You do not have permission to make this action.', 'so-widgets-bundle' ), 403 );
+ }
+
+ return true;
+}
--- a/so-widgets-bundle/base/inc/actions.php
+++ b/so-widgets-bundle/base/inc/actions.php
@@ -4,12 +4,9 @@
* Action for displaying the widget preview.
*/
function siteorigin_widget_preview_widget_action() {
- if (
- empty( $_REQUEST['_widgets_nonce'] ) ||
- ! wp_verify_nonce( $_REQUEST['_widgets_nonce'], 'widgets_action' )
- ) {
- wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 403 );
- } elseif ( empty( $_POST['class'] ) ) {
+ siteorigin_verify_request_permissions();
+
+ if ( empty( $_POST['class'] ) ) {
wp_die( __( 'Invalid widget.', 'so-widgets-bundle' ), 400 );
}
@@ -94,81 +91,97 @@
return $post_type && current_user_can( $post_type->cap->edit_posts );
}
-/**
- * Action to handle searching posts
- */
-function siteorigin_widget_action_search_posts() {
- if ( empty( $_REQUEST['_widgets_nonce'] ) || ! wp_verify_nonce( $_REQUEST['_widgets_nonce'], 'widgets_action' ) ) {
- wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 403 );
- }
+class SiteOrigin_Widgets_Bundle_Actions {
+ /**
+ * Action to handle searching posts.
+ */
+ public static function search_posts() {
+ siteorigin_verify_request_permissions();
+
+ global $wpdb;
+ $query = '';
+ $wpml_query = '';
+
+ // Get all public post types, besides attachments.
+ $post_types = (array) get_post_types(
+ array(
+ 'public' => true,
+ )
+ );
- global $wpdb;
- $query = '';
- $wpml_query = '';
+ if ( ! empty( $_REQUEST['postTypes'] ) ) {
+ $post_types = array_intersect( explode( ',', sanitize_text_field( $_REQUEST['postTypes'] ) ), $post_types );
+ } else {
+ unset( $post_types['attachment'] );
+ }
- // Get all public post types, besides attachments
- $post_types = (array) get_post_types(
- array(
- 'public' => true,
- )
- );
+ // If WPML is installed, only include posts from the currently active language.
+ if ( defined( 'ICL_LANGUAGE_CODE' ) && ! empty( $_REQUEST['language'] ) ) {
+ $query .= $wpdb->prepare( " AND {$wpdb->prefix}icl_translations.language_code = %s ", sanitize_text_field( $_REQUEST['language'] ) );
+ $wpml_query .= " INNER JOIN {$wpdb->prefix}icl_translations ON ($wpdb->posts.ID = {$wpdb->prefix}icl_translations.element_id) ";
+ }
- if ( ! empty( $_REQUEST['postTypes'] ) ) {
- $post_types = array_intersect( explode( ',', sanitize_text_field( $_REQUEST['postTypes'] ) ), $post_types );
- } else {
- unset( $post_types['attachment'] );
- }
+ if ( ! empty( $_GET['query'] ) ) {
+ $search_query = '%' . $wpdb->esc_like( sanitize_text_field( $_GET['query'] ) ) . '%';
+ $query .= $wpdb->prepare( ' AND post_title LIKE %s ', $search_query );
+ }
- // If WPML is installed, only include posts from the currently active language.
- if ( defined( 'ICL_LANGUAGE_CODE' ) && ! empty( $_REQUEST['language'] ) ) {
- $query .= $wpdb->prepare( " AND {$wpdb->prefix}icl_translations.language_code = %s ", sanitize_text_field( $_REQUEST['language'] ) );
- $wpml_query .= " INNER JOIN {$wpdb->prefix}icl_translations ON ($wpdb->posts.ID = {$wpdb->prefix}icl_translations.element_id) ";
- }
+ $post_types = apply_filters( 'siteorigin_widgets_search_posts_post_types', $post_types );
- if ( ! empty( $_GET['query'] ) ) {
- $search_query = '%' . $wpdb->esc_like( sanitize_text_field( $_GET['query'] ) ) . '%';
- $query .= $wpdb->prepare( ' AND post_title LIKE %s ', $search_query );
- }
+ // Ensure the user can edit this post type.
+ foreach ( $post_types as $key => $post_type ) {
+ if ( ! siteorigin_widget_user_can_edit_post_type( $post_type ) ) {
+ unset( $post_types[ $key ] );
+ }
+ }
+ $post_types = "'" . implode( "', '", array_map( 'esc_sql', $post_types ) ) . "'";
- $post_types = apply_filters( 'siteorigin_widgets_search_posts_post_types', $post_types );
+ $ordered_by = self::get_search_posts_order_by();
- // Ensure the user can edit this post type.
- foreach ( $post_types as $key => $post_type ) {
- if ( ! siteorigin_widget_user_can_edit_post_type( $post_type ) ) {
- unset( $post_types[ $key ] );
+ $results = $wpdb->get_results(
+ "
+ SELECT ID AS 'value', post_title AS label, post_type AS 'type'
+ FROM {$wpdb->posts}
+ {$wpml_query}
+ WHERE
+ post_type IN ( {$post_types} ) AND post_status = 'publish' {$query}
+ ORDER BY {$ordered_by}
+ LIMIT 20
+ ",
+ ARRAY_A
+ );
+
+ if ( empty( $results ) ) {
+ wp_send_json( array() );
}
- }
- $post_types = "'" . implode( "', '", array_map( 'esc_sql', $post_types ) ) . "'";
- $ordered_by = esc_sql( apply_filters( 'siteorigin_widgets_search_posts_order_by', 'post_modified DESC' ) );
+ // Filter results to ensure the user can read the post.
+ $results = array_filter(
+ $results,
+ function ( $post ) {
- $results = $wpdb->get_results(
- "
- SELECT ID AS 'value', post_title AS label, post_type AS 'type'
- FROM {$wpdb->posts}
- {$wpml_query}
- WHERE
- post_type IN ( {$post_types} ) AND post_status = 'publish' {$query}
- ORDER BY {$ordered_by}
- LIMIT 20
- ",
- ARRAY_A
- );
+ return current_user_can( 'read_post', $post['value'] );
+ }
+ );
- if ( empty( $results ) ) {
- wp_send_json( array() );
+ wp_send_json( apply_filters( 'siteorigin_widgets_search_posts_results', $results ) );
}
- // Filter results to ensure the user can read the post.
- $results = array_filter(
- $results,
- function ( $post ) {
-
- return current_user_can( 'read_post', $post['value'] );
- }
- );
+ /**
+ * Get the ORDER BY clause for post searches.
+ *
+ * @return string
+ */
+ private static function get_search_posts_order_by() {
+ return esc_sql( apply_filters( 'siteorigin_widgets_search_posts_order_by', 'post_modified DESC' ) );
+ }
+}
- wp_send_json( apply_filters( 'siteorigin_widgets_search_posts_results', $results ) );
+/**
+ * Action to handle searching posts.
+ */
+function siteorigin_widget_action_search_posts() {
+ SiteOrigin_Widgets_Bundle_Actions::search_posts();
}
add_action( 'wp_ajax_so_widgets_search_posts', 'siteorigin_widget_action_search_posts' );
@@ -215,9 +228,7 @@
* Action to handle searching taxonomy terms.
*/
function siteorigin_widget_action_search_terms() {
- if ( empty( $_REQUEST['_widgets_nonce'] ) || ! wp_verify_nonce( $_REQUEST['_widgets_nonce'], 'widgets_action' ) ) {
- wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 403 );
- }
+ siteorigin_verify_request_permissions();
global $wpdb;
$term = ! empty( $_GET['term'] ) ? sanitize_text_field( stripslashes( $_GET['term'] ) ) : '';
@@ -260,9 +271,7 @@
* Action for getting the number of posts returned by a query.
*/
function siteorigin_widget_get_posts_count_action() {
- if ( empty( $_REQUEST['_widgets_nonce'] ) || ! wp_verify_nonce( $_REQUEST['_widgets_nonce'], 'widgets_action' ) ) {
- wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 403 );
- }
+ siteorigin_verify_request_permissions();
$query = stripslashes( $_POST['query'] );
@@ -272,9 +281,7 @@
add_action( 'wp_ajax_sow_get_posts_count', 'siteorigin_widget_get_posts_count_action' );
function siteorigin_widget_remote_image_search() {
- if ( empty( $_GET[ '_sononce' ] ) || ! wp_verify_nonce( $_GET[ '_sononce' ], 'so-image' ) ) {
- wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 403 );
- }
+ siteorigin_verify_request_permissions( 'upload_files', '_sononce', 'so-image' );
if ( empty( $_GET['q'] ) ) {
wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 400 );
@@ -318,12 +325,9 @@
add_action( 'wp_ajax_so_widgets_image_search', 'siteorigin_widget_remote_image_search' );
function siteorigin_widget_image_import() {
- if ( empty( $_GET[ '_sononce' ] ) || ! wp_verify_nonce( $_GET[ '_sononce' ], 'so-image' ) ) {
- $result = array(
- 'error' => true,
- 'message' => __( 'Nonce error', 'so-widgets-bundle' ),
- );
- } elseif (
+ siteorigin_verify_request_permissions( 'upload_files', '_sononce', 'so-image' );
+
+ if (
empty( $_GET['import_signature'] ) ||
empty( $_GET['full_url'] ) ||
md5( $_GET['full_url'] . '::' . NONCE_SALT ) !== $_GET['import_signature']
@@ -370,9 +374,7 @@
* Action to handle a user dismissing a teaser notice.
*/
function siteorigin_widgets_dismiss_widget_action() {
- if ( empty( $_GET[ '_wpnonce' ] ) || ! wp_verify_nonce( $_GET[ '_wpnonce' ], 'dismiss-widget-teaser' ) ) {
- wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 403 );
- }
+ siteorigin_verify_request_permissions( 'edit_posts', '_wpnonce', 'dismiss-widget-teaser' );
if ( empty( $_GET[ 'widget' ] ) ) {
wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 400 );
--- a/so-widgets-bundle/base/inc/fields/autocomplete.class.php
+++ b/so-widgets-bundle/base/inc/fields/autocomplete.class.php
@@ -20,6 +20,13 @@
protected $source;
/**
+ * Additional data to include with the autocomplete Ajax request.
+ *
+ * @var array
+ */
+ protected $ajax_data;
+
+ /**
* Whether to allow multiple items to be selected.
*
* @access protected
@@ -43,6 +50,10 @@
protected function render_after_field( $value, $instance ) {
$post_types = ! empty( $this->post_types ) && is_array( $this->post_types ) ? implode( ',', $this->post_types ) : '';
+ $ajax_params_attribute = '';
+ if ( ! empty( $this->ajax_data ) && is_array( $this->ajax_data ) ) {
+ $ajax_params_attribute = ' data-ajax-params="' . esc_attr( wp_json_encode( $this->ajax_data ) ) . '"';
+ }
?>
<div class="existing-content-selector" data-multiple="<?php echo esc_attr( $this->multiple ); ?>">
@@ -51,6 +62,7 @@
class="content-text-search"
data-post-types="<?php echo esc_attr( $post_types ); ?>"
data-source="<?php echo esc_attr( $this->source ); ?>"
+ <?php echo $ajax_params_attribute; ?>
placeholder="<?php esc_attr_e( 'Search', 'so-widgets-bundle' ); ?>"
tabindex="0"
/>
--- a/so-widgets-bundle/base/inc/fields/media.class.php
+++ b/so-widgets-bundle/base/inc/fields/media.class.php
@@ -41,10 +41,16 @@
*/
protected $fallback;
+ /**
+ * Initializes the media field, adding the image search dialog if necessary.
+ */
protected function initialize() {
static $once;
- if ( empty( $once ) ) {
+ if (
+ empty( $once ) &&
+ $this->user_can_upload_media()
+ ) {
add_action( 'siteorigin_widgets_footer_admin_templates', array( $this, 'image_search_dialog' ) );
}
$once = true;
@@ -59,6 +65,16 @@
);
}
+ /**
+ * Checks if the current user has permissions to upload media.
+ * If they don't, the media search button and dialog will not be rendered.
+ *
+ * @return bool True if the user can upload media, false otherwise.
+ */
+ private function user_can_upload_media() : bool {
+ return current_user_can( 'upload_files' );
+ }
+
protected function render_field( $value, $instance ) {
if ( version_compare( get_bloginfo( 'version' ), '3.5', '<' ) ) {
printf( __( 'You need to <a href="%s">upgrade</a> to WordPress 3.5 to use media fields', 'so-widgets-bundle' ), admin_url( 'update-core.php' ) );
@@ -108,7 +124,7 @@
>
<?php echo esc_html( $this->choose ); ?>
</a>
- <?php if ( $this->library == 'image' ) { ?>
+ <?php if ( $this->library == 'image' && $this->user_can_upload_media() ) { ?>
<a href="#" class="find-image-button">
<?php echo esc_html( $this->image_search ); ?>
</a>
--- a/so-widgets-bundle/compat/visual-composer/visual-composer.php
+++ b/so-widgets-bundle/compat/visual-composer/visual-composer.php
@@ -132,9 +132,7 @@
wp_die();
}
- if ( empty( $_REQUEST['_sowbnonce'] ) || ! wp_verify_nonce( $_REQUEST['_sowbnonce'], 'sowb_vc_widget_render_form' ) ) {
- wp_die();
- }
+ siteorigin_verify_request_permissions( 'edit_posts', '_sowbnonce', 'sowb_vc_widget_render_form' );
$request = array_map( 'stripslashes_deep', $_REQUEST );
$widget_class = $request['widget'];
--- a/so-widgets-bundle/so-widgets-bundle.php
+++ b/so-widgets-bundle/so-widgets-bundle.php
@@ -2,7 +2,7 @@
/*
Plugin Name: SiteOrigin Widgets Bundle
Description: A highly customizable collection of widgets, ready to be used anywhere, neatly bundled into a single plugin.
-Version: 1.70.4
+Version: 1.71.0
Text Domain: so-widgets-bundle
Domain Path: /lang
Author: SiteOrigin
@@ -12,7 +12,7 @@
License URI: https://www.gnu.org/licenses/gpl-3.0.txt
*/
-define( 'SOW_BUNDLE_VERSION', '1.70.4' );
+define( 'SOW_BUNDLE_VERSION', '1.71.0' );
define( 'SOW_BUNDLE_BASE_FILE', __FILE__ );
// Allow JS suffix to be pre-set.
@@ -582,10 +582,7 @@
* Get JavaScript variables for admin.
*/
public function admin_ajax_get_javascript_variables() {
- if ( empty( $_REQUEST['_widgets_nonce'] ) ||
- ! wp_verify_nonce( $_REQUEST['_widgets_nonce'], 'widgets_action' ) ) {
- wp_die( __( 'Invalid request.', 'so-widgets-bundle' ), 403 );
- }
+ siteorigin_verify_request_permissions();
$widget_class = $_POST['widget'];
global $wp_widget_factory;
--- a/so-widgets-bundle/widgets/accordion/accordion.php
+++ b/so-widgets-bundle/widgets/accordion/accordion.php
@@ -134,6 +134,21 @@
'type' => 'color',
'label' => __( 'Title hover color', 'so-widgets-bundle' ),
),
+ 'title_tag' => array(
+ 'type' => 'select',
+ 'label' => __( 'Title HTML Tag', 'so-widgets-bundle' ),
+ 'default' => 'div',
+ 'options' => array(
+ 'h1' => __( 'H1', 'so-widgets-bundle' ),
+ 'h2' => __( 'H2', 'so-widgets-bundle' ),
+ 'h3' => __( 'H3', 'so-widgets-bundle' ),
+ 'h4' => __( 'H4', 'so-widgets-bundle' ),
+ 'h5' => __( 'H5', 'so-widgets-bundle' ),
+ 'h6' => __( 'H6', 'so-widgets-bundle' ),
+ 'p' => __( 'Paragraph', 'so-widgets-bundle' ),
+ 'div' => __( 'Div', 'so-widgets-bundle' ),
+ ),
+ ),
'border_color' => array(
'type' => 'color',
'label' => __( 'Border color', 'so-widgets-bundle' ),
@@ -212,6 +227,17 @@
return array();
}
+ $title_tag = siteorigin_widget_valid_tag(
+ $instance['design']['heading']['title_tag'] ?? '',
+ 'div',
+ array( 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div' )
+ );
+ $title_level = 2;
+ if ( preg_match( '/^h([1-6])$/', $title_tag, $matches ) ) {
+ $title_level = (int) $matches[1];
+ }
+ $title_has_native_heading = preg_match( '/^h[1-6]$/', $title_tag ) === 1;
+
$panels = empty( $instance['panels'] ) ? array() : $instance['panels'];
$anchor_list = array();
@@ -256,13 +282,34 @@
'panels' => $panels,
'icon_open' => $instance['design']['heading']['icon_open'],
'icon_close' => $instance['design']['heading']['icon_close'],
+ 'title_tag' => $title_tag,
+ 'title_level' => $title_level,
+ 'title_has_native_heading' => $title_has_native_heading,
);
}
public function render_panel_content( $panel, $instance ) {
$content = $panel['autop'] ? wpautop( $panel['content_text'] ) : $panel['content_text'];
- echo apply_filters( 'siteorigin_widgets_accordion_render_panel_content', $content, $panel, $instance );
+ $content = apply_filters( 'siteorigin_widgets_accordion_render_panel_content', $content, $panel, $instance );
+
+ $lazy_iframes = apply_filters( 'siteorigin_widgets_accordion_lazy_iframes', true, $panel, $instance );
+ if ( $lazy_iframes ) {
+ // Ensure oEmbed URLs are converted before we swap iframes.
+ if ( class_exists( 'WP_Embed' ) ) {
+ global $wp_embed;
+ if ( $wp_embed instanceof WP_Embed ) {
+ $content = $wp_embed->autoembed( $content );
+ $content = $wp_embed->run_shortcode( $content );
+ }
+ }
+
+ // Replace iframe tags so we can swap them back on panel open for lazy loading.
+ $content = preg_replace( '/<s*iframeb([^>]*)>/i', '<so-iframe$1>', $content );
+ $content = preg_replace( '/</s*iframes*>/i', '</so-iframe>', $content );
+ }
+
+ echo $content;
}
public function get_form_teaser() {
--- a/so-widgets-bundle/widgets/accordion/tpl/default.php
+++ b/so-widgets-bundle/widgets/accordion/tpl/default.php
@@ -4,6 +4,9 @@
* @var array $panels
* @var string $icon_open
* @var string $icon_close
+ * @var string $title_tag
+ * @var int $title_level
+ * @var bool $title_has_native_heading
*/
if ( ! empty( $instance['title'] ) ) {
echo $args['before_title'] . wp_kses_post( $instance['title'] ) . $args['after_title'];
@@ -17,16 +20,16 @@
if ( $panel['initial_state'] == 'open' ) {
echo ' sow-accordion-panel-open';
}
- ?>
- "
- data-anchor-id="<?php echo esc_attr( sanitize_title( $panel['anchor'] ) ); ?>">
- <div class="sow-accordion-panel-header-container" role="heading" aria-level="2">
+ ?>
+ "
+ data-anchor-id="<?php echo esc_attr( sanitize_title( $panel['anchor'] ) ); ?>">
+ <div class="sow-accordion-panel-header-container"<?php if ( ! $title_has_native_heading ) { ?> role="heading" aria-level="<?php echo esc_attr( $title_level ); ?>"<?php } ?>>
<div class="sow-accordion-panel-header" tabindex="0" role="button" id="accordion-label-<?php echo sanitize_title_with_dashes( $panel['anchor'] ); ?>" aria-controls="accordion-content-<?php echo sanitize_title_with_dashes( $panel['anchor'] ); ?>" aria-expanded="<?php echo $panel['initial_state'] == 'open' ? 'true' : 'false'; ?>">
- <div class="sow-accordion-title <?php echo empty( $panel['after_title'] ) ? 'sow-accordion-title-icon-left' : 'sow-accordion-title-icon-right'; ?>">
+ <<?php echo esc_attr( $title_tag ); ?> class="sow-accordion-title <?php echo empty( $panel['after_title'] ) ? 'sow-accordion-title-icon-left' : 'sow-accordion-title-icon-right'; ?>">
<?php echo $panel['before_title']; ?>
<?php echo wp_kses_post( $panel['title'] ); ?>
<?php echo $panel['after_title']; ?>
- </div>
+ </<?php echo esc_attr( $title_tag ); ?>>
<div class="sow-accordion-open-close-button">
<div class="sow-accordion-open-button">
<?php echo siteorigin_widget_get_icon( $icon_open ); ?>
--- a/so-widgets-bundle/widgets/social-media-buttons/data/networks.php
+++ b/so-widgets-bundle/widgets/social-media-buttons/data/networks.php
@@ -172,6 +172,12 @@
'icon_color' => '#ffffff',
'button_color' => '#202021',
),
+ 'gitlab' => array(
+ 'label' => __( 'GitLab', 'so-widgets-bundle' ),
+ 'base_url' => 'https://gitlab.com/',
+ 'icon_color' => '#ffffff',
+ 'button_color' => '#fc6d26',
+ ),
'goodreads-g' => array(
'label' => __( 'Goodreads', 'so-widgets-bundle' ),
'base_url' => 'https://goodreads.com/',
--- a/so-widgets-bundle/widgets/social-media-buttons/social-media-buttons.php
+++ b/so-widgets-bundle/widgets/social-media-buttons/social-media-buttons.php
@@ -267,7 +267,9 @@
}
}
- if (
+ if ( $network['name'] === 'skype' ) {
+ $network['icon_name'] = 'fontawesome-sow-fab-microsoft';
+ } elseif (
$network['name'] != 'envelope' &&
$network['name'] != 'suitcase' &&
$network['name'] != 'rss' &&