Fixed the security issue with AI
-
It replaces the code in accordion-shortcodes.php, use at your own risk ofcourse.
<?php
/**
* Plugin Name: Accordion Shortcodes
* Description: Shortcodes for creating responsive accordion drop-downs.
* Version: 2.5
* Author: AI
*/
require_once('tinymce/tinymce.php');
// Make sure to not redeclare the class
if (!class_exists('Accordion_Shortcodes')) :
class Accordion_Shortcodes {
/**
* Current plugin version number
*/
private $plugin_version = '2.4.2';
/**
* Should the accordion JavaScript file be loaded the on the current page
* False by default
*/
private $load_script = false;
/**
* Holds all the accordion shortcodes group settings
*/
private $script_data = array();
/**
* Count of each accordion group on a page
*/
private $group_count = 0;
/**
* Count for each accordion item within an accordion group
*/
private $item_count = 0;
/**
* Holds the accordion group container HTML tag
*/
private $wrapper_tag = 'div';
/**
* Holds the accordion item title HTML tag
*/
private $title_tag = 'h3';
/**
* Holds the accordion item content container HTML tag
*/
private $content_tag = 'div';
/**
* Whether to include a<button>tag wrapper on each title
*/
private $use_buttons = false;
/**
* Class constructor
* Sets up the plugin, including: textdomain, adding shortcodes, registering
* scripts and adding buttons.
*/
function __construct() {
$basename = plugin_basename(__FILE__);
// Load text domain
load_plugin_textdomain('accordion_shortcodes', false, dirname($basename) . '/languages/');
// Register JavaScript
add_action('wp_enqueue_scripts', array($this, 'register_script'));
// Add shortcodes
$prefix = $this->get_compatibility_prefix();
add_shortcode($prefix . 'accordion', array($this, 'accordion_shortcode'));
add_shortcode($prefix . 'accordion-item', array($this, 'accordion_item_shortcode'));
// Print script in wp_footer
add_action('wp_footer', array($this, 'print_script'));
if (is_admin()) {
// Add link to documentation on plugin page
add_filter("plugin_action_links_$basename", array($this, 'add_documentation_link'));
// Add buttons to MCE editor
if (!defined('AS_TINYMCE') || AS_TINYMCE != false) {
// Check if the file exists before requiring it
if (file_exists(plugin_dir_path(__FILE__) . 'tinymce/tinymce.php')) {
$Accordion_Shortcode_Tinymce_Extensions = new Accordion_Shortcode_Tinymce_Extensions;
}
}
}
}
/**
* Get the compatibility mode prefix
*
* return string The compatibility mode prefix
*/
private function get_compatibility_prefix() {
return defined('AS_COMPATIBILITY') && AS_COMPATIBILITY ? 'as-' : '';
}
/**
* Registers the JavaScript file
* If SCRIPT_DEBUG is set to true in the config file, the un-minified
* version of the JavaScript file will be used.
*/
public function register_script() {
$min = (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min';
wp_register_script('accordion-shortcodes-script', plugins_url('accordion' . $min . '.js', __FILE__), array('jquery'), $this->plugin_version, true);
}
/**
* Prints the accordion JavaScript in the footer
* This inlcludes both the accordion jQuery plugin file registered by
* 'register_script()' and the accordion settings JavaScript variable.
*/
public function print_script() {
// Check to see if shortcodes are used on page
if (!$this->load_script) return;
wp_enqueue_script('accordion-shortcodes-script');
// Sanitize script data before output
$sanitized_script_data = array();
foreach ($this->script_data as $data) {
$sanitized_data = array(
'id' => sanitize_key($data['id']),
'autoClose' => (bool) $data['autoClose'],
'openFirst' => (bool) $data['openFirst'],
'openAll' => (bool) $data['openAll'],
'clickToClose' => (bool) $data['clickToClose'],
'scroll' => is_bool($data['scroll']) ? $data['scroll'] : intval($data['scroll']),
'usebuttons' => (bool) $data['usebuttons'],
);
$sanitized_script_data[] = $sanitized_data;
}
// Output accordions settings JavaScript variable
wp_localize_script('accordion-shortcodes-script', 'accordionShortcodesSettings', $sanitized_script_data);
}
/**
* Checks if a value is boolean
*
* @param string $value The value to test
* return bool
*/
private function is_boolean($value) {
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
/**
* Sanitize an ID to only allow safe characters
*
* @param string $id The ID to sanitize
* @return string Sanitized ID
*/
private function sanitize_id($id) {
return preg_replace('/[^a-zA-Z0-9-_]/', '', $id);
}
/**
* Check for valid HTML tag
* Checks the supplied HTML tag against a list of approved tags.
*
* @param string $tag The HTML tag to test
* return string A valid HTML tag
*/
private function check_html_tag($tag) {
// Remove any whitespace and HTML special characters
$tag = preg_replace('/\s/', '', $tag);
$tag = sanitize_html_class($tag);
$tags = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'button');
if (in_array($tag, $tags, true)) {
return $tag;
}
else {
return $this->title_tag;
}
}
/**
* Check for valid scroll value
* Scroll value must be either an int or bool
*
* @param int/bool $scroll The scroll offset integer or true/false
* return int/bool The scroll offset integer else true/false
*/
private function check_scroll_value($scroll) {
$int = intval($scroll);
if (is_int($int) && $int != 0) {
return $int;
}
else {
return $this->is_boolean($scroll);
}
}
/**
* Get's the ID for an accordion item
*
* @param string $id If the user set an ID
* return array The IDs for the accordion title and item
*/
private function get_accordion_id($id) {
// Sanitize the provided ID
$clean_id = $this->sanitize_id($id);
$title_id = $clean_id ? $clean_id : "accordion-$this->group_count-t$this->item_count";
$content_id = $clean_id ? "content-$clean_id" : "accordion-$this->group_count-c$this->item_count";
return array(
'title' => $title_id,
'content' => $content_id
);
}
/**
* Accordion group shortcode
*/
public function accordion_shortcode($atts, $content = null) {
// The shortcode is used on the page, so load the JavaScript
$this->load_script = true;
// Set accordion counters
$this->group_count++;
$this->item_count = 0;
// Sanitize all attributes
$atts = shortcode_atts(array(
'tag' => '',
'autoclose' => true,
'openfirst' => false,
'openall' => false,
'clicktoclose' => false,
'scroll' => false,
'usebuttons' => false,
'semantics' => '',
'class' => '',
), $atts, 'accordion');
// Sanitize individual attributes
$tag = sanitize_html_class($atts['tag']);
$autoclose = $atts['autoclose'];
$openfirst = $atts['openfirst'];
$openall = $atts['openall'];
$clicktoclose = $atts['clicktoclose'];
$scroll = $atts['scroll'];
$usebuttons = $atts['usebuttons'];
$semantics = sanitize_key($atts['semantics']);
$class = sanitize_html_class($atts['class']);
// Set global HTML tag names
// Set title HTML tag
if ($tag) $this->title_tag = $this->check_html_tag($tag);
else $this->title_tag = 'h3';
// Set wrapper HTML tags
if ($semantics == 'dl') {
$this->wrapper_tag = 'dl';
$this->title_tag = 'dt';
$this->content_tag = 'dd';
}
else {
$this->wrapper_tag = 'div';
$this->content_tag = 'div';
}
if ($usebuttons && $this->is_boolean($usebuttons)) {
$this->use_buttons = true;
}
// Set settings object (for use in JavaScript)
$script_data = array(
'id' => "accordion-$this->group_count",
'autoClose' => $this->is_boolean($autoclose),
'openFirst' => $this->is_boolean($openfirst),
'openAll' => $this->is_boolean($openall),
'clickToClose' => $this->is_boolean($clicktoclose),
'scroll' => $this->check_scroll_value($scroll),
'usebuttons' => $this->is_boolean($usebuttons),
);
// Add this shortcodes settings instance to the global script data array
$this->script_data[] = $script_data;
// Build class attribute
$class_attr = '';
if (!empty($class)) {
$class_attr = ' ' . esc_attr($class);
}
// Ensure content is processed safely
$processed_content = do_shortcode($content);
return sprintf(
'<%2$s id="%3$s" class="accordion no-js%4$s">%1$s</%2$s>',
$processed_content,
esc_attr($this->wrapper_tag),
esc_attr("accordion-$this->group_count"),
$class_attr
);
}
/**
* Accordion item shortcode
*/
public function accordion_item_shortcode($atts, $content = null) {
// Sanitize all attributes
$atts = shortcode_atts(array(
'title' => '',
'id' => '',
'tag' => '',
'class' => '',
'state' => ''
), $atts, 'accordion-item');
// Sanitize individual attributes
$title = sanitize_text_field($atts['title']);
$id = sanitize_key($atts['id']);
$tag = sanitize_html_class($atts['tag']);
$class = sanitize_html_class($atts['class']);
$state = sanitize_key($atts['state']);
// Increment accordion item count
$this->item_count++;
$ids = $this->get_accordion_id($id);
$html_tag = $tag ? $this->check_html_tag($tag) : $this->title_tag;
// Build class attribute
$class_attr = '';
if (!empty($class)) {
$class_attr = ' ' . esc_attr($class);
}
// Check if output buffering is already active
if (ob_get_level() == 0) {
ob_start();
} else {
// If output buffering is already active, we'll capture output differently
return $this->generate_accordion_item_html($html_tag, $ids, $title, $class_attr, $state, $content);
}
?>
<?php if ($this->use_buttons) { ?>
<<?php echo esc_attr($html_tag); ?> class="accordion-title<?php echo $class_attr; ?>">
<button id="<?php echo esc_attr($ids['title']); ?>" class="js-accordion-controller" aria-controls="<?php echo esc_attr($ids['content']); ?>" aria-expanded="false" <?php echo !empty($state) ? ' data-initialstate="' . esc_attr($state) . '"' : ''; ?>>
<?php echo !empty($title) ? esc_html($title) : '<span style="color:red;">' . esc_html__('Please enter a title attribute', 'accordion_shortcodes') . '</span>'; ?>
</button>
</<?php echo esc_attr($html_tag); ?>>
<?php } else { ?>
<<?php echo esc_attr($html_tag); ?> role="button" id="<?php echo esc_attr($ids['title']); ?>" class="accordion-title<?php echo !$this->use_buttons ? ' js-accordion-controller' : ''; echo $class_attr; ?>" aria-controls="<?php echo esc_attr($ids['content']); ?>" aria-expanded="false" tabindex="0"<?php echo !empty($state) ? ' data-initialstate="' . esc_attr($state) . '"' : ''; ?>>
<?php echo !empty($title) ? esc_html($title) : '<span style="color:red;">' . esc_html__('Please enter a title attribute', 'accordion_shortcodes') . '</span>'; ?>
</<?php echo esc_attr($html_tag); ?>>
<?php } ?>
<<?php echo esc_attr($this->content_tag); ?> id="<?php echo esc_attr($ids['content']); ?>" class="accordion-content" aria-hidden="true">
<?php
// Process content but ensure it's safe
$processed_content = do_shortcode($content);
// Allow only safe HTML in content
echo wp_kses_post($processed_content);
?>
</<?php echo esc_attr($this->content_tag); ?>>
<?php return ob_get_clean();
}
/**
* Alternative method to generate accordion item HTML when output buffering is already active
*/
private function generate_accordion_item_html($html_tag, $ids, $title, $class_attr, $state, $content) {
$output = '';
if ($this->use_buttons) {
$output .= '<' . esc_attr($html_tag) . ' class="accordion-title' . $class_attr . '">';
$output .= '<button id="' . esc_attr($ids['title']) . '" class="js-accordion-controller" aria-controls="' . esc_attr($ids['content']) . '" aria-expanded="false"' . (!empty($state) ? ' data-initialstate="' . esc_attr($state) . '"' : '') . '>';
$output .= !empty($title) ? esc_html($title) : '<span style="color:red;">' . esc_html__('Please enter a title attribute', 'accordion_shortcodes') . '</span>';
$output .= '</button>';
$output .= '</' . esc_attr($html_tag) . '>';
} else {
$output .= '<' . esc_attr($html_tag) . ' role="button" id="' . esc_attr($ids['title']) . '" class="accordion-title' . (!$this->use_buttons ? ' js-accordion-controller' : '') . $class_attr . '" aria-controls="' . esc_attr($ids['content']) . '" aria-expanded="false" tabindex="0"' . (!empty($state) ? ' data-initialstate="' . esc_attr($state) . '"' : '') . '>';
$output .= !empty($title) ? esc_html($title) : '<span style="color:red;">' . esc_html__('Please enter a title attribute', 'accordion_shortcodes') . '</span>';
$output .= '</' . esc_attr($html_tag) . '>';
}
$output .= '<' . esc_attr($this->content_tag) . ' id="' . esc_attr($ids['content']) . '" class="accordion-content" aria-hidden="true">';
$output .= wp_kses_post(do_shortcode($content));
$output .= '</' . esc_attr($this->content_tag) . '>';
return $output;
}
/**
* Add documentation link on plugin page
*/
public function add_documentation_link($links) {
// Verify user has permission to see plugin links
if (!current_user_can('manage_options')) {
return $links;
}
array_push($links, sprintf('<a href="%s">%s</a>',
esc_url('http://wordpress.org/plugins/accordion-shortcodes/'),
esc_html_x('Documentation', 'link to documentation on wordpress.org site', 'accordion_shortcodes')
));
return $links;
}
}
// Check if class exists before instantiating
if (class_exists('Accordion_Shortcodes')) {
$Accordion_Shortcodes = new Accordion_Shortcodes;
}
endif;
You must be logged in to reply to this topic.