• 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.