You are here

shortcode.module in Shortcode 7

Same filename and directory in other branches
  1. 6 shortcode.module
  2. 7.2 shortcode.module

File

shortcode.module
View source
<?php

/*
 * TODO
 *
 * strip_shortcodes - remove all shortcodes from the processed text
 *
 * correct element cascading
 *
 *
 *
 */

/**
 * Build a list of all shortcodes (for filter).
 * Calls the shortcode hook with the list parameter on all module
 *
 * From filter.module
 */
function shortcode_list_all() {
  $shortcodes = array();
  foreach (module_list() as $module) {
    $list = module_invoke($module, 'shortcodes', 'list');
    if (isset($list) && is_array($list)) {
      foreach ($list as $name => $descr) {
        $shortcodes[$name] = (object) array(
          'module' => $module,
          'descr' => $descr,
        );
      }
    }
  }
  return $shortcodes;
}

/**
 * Build a list of all shortcodes (for filter).
 * Calls the shortcode hook with the tips parameter on all module
 * From filter.module
 */
function shortcode_tips_all($format, $long = FALSE) {

  // calls the shortcode hook with the tips parameter on all module
  $tips = '';
  foreach (module_list() as $module) {
    $tips .= module_invoke($module, 'shortcodes', 'tips', $format, $long);
  }
  return $tips;
}

/**
* Implementation of hook_filter().
*/
function shortcode_filter($op, $delta = 0, $format = -1, $text = '') {
  switch ($op) {
    case 'list':
      return array(
        0 => t('Shortcodes'),
        1 => t('Shortcodes - html corrector'),
      );
      break;
    case 'description':
      switch ($delta) {
        case 1:
          return t('Shortcodes html corrector');
          break;
        case 0:
        default:
          return t('Shortcodes like in WP');
          break;
      }
      break;
    case 'settings':
      switch ($delta) {
        case 1:
          return '';
          break;
        case 0:
        default:

          // list all shortcodes, enable/disable, and cache value.
          return _shortcode_settings_form($format);
          break;
      }
      break;
    case 'no cache':

      // Do not cache the output.
      // TODO: build a shortcode list with cache options
      return TRUE;
      break;
    case 'prepare':

      // We're a simple filter and have no preparatory needs.
      return $text;
      break;
    case 'process':
      switch ($delta) {
        case 1:

          // run shortcodes html corrector
          return _shortcode_postprocess_text($text);
          break;

        // runs shortcodes
        case 0:
        default:
          return _shortcode_process($text, $format);
          break;
      }
      break;
    default:
      return $text;
  }
}

// shortcodes filter

/**
 * Implementation of hook_filter_tips().
 */
function shortcode_filter_tips($delta, $format, $long = FALSE) {
  static $sent = array();
  if (isset($sent[$format])) {

    // do not send on the html corrector
    return '';
  }
  $tips = shortcode_tips_all($format, $long);
  if ($tips) {
    $tips = '<fieldset><legend>' . t('Shortcodes') . '</legend><div>' . $tips . '</div></fieldset>';
  }
  $sent[$format] = TRUE;
  return $tips;
}

/**
 *
 * Base form elements of the all shortcode options
 *
 */
function _shortcode_settings_form($format) {
  $form['shortcodes_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Shortcodes'),
    '#collapsible' => TRUE,
  );
  $shortcodes = shortcode_list_all();
  foreach ($shortcodes as $name => $item) {
    $n = 'shortcode_enabled_' . $name . '_in_' . $format;
    $form['shortcodes_settings'][$n] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable %name shortcode', array(
        '%name' => $name,
      )),
      '#default_value' => variable_get($n, 1),
      '#description' => 'Enable or disable this shortcode in this input format',
    );
  }
  return $form;
}

/**
 *
 * shortcodes hook implementation
 * @param $op string - the operator - one of list, settings or tips
 * @param $format - the filter format index
 * @param $long
 */
function shortcode_shortcodes($op, $format = 1, $long = FALSE) {
  switch ($op) {
    case 'list':

      // array indexed by the shortcode name
      return array(
        'random' => 'Code replacey by random text',
      );
      break;

    // TODO: special settings
    case 'settings':
      break;
    case 'tips':
      $output = shortcode_shortcode_random_tip($format, $long);
      return $output;
      break;
    default:
      break;
  }
}

// shortcode_shortcodes()

/**
 * Tags cache
 * @param $tags
 *
 * @access private
 */
function _shortcode_tags($tags = NULL) {
  static $shortcodes = array();
  if ($tags) {
    $shortcodes = $tags;
    return TRUE;
  }
  return $shortcodes;
}
function shortcode_shortcode_is_enabled($name, $format) {
  $n = 'shortcode_enabled_' . $name . '_in_' . $format;
  return variable_get($n, 1);
}

/**
 * Process the shortcodes according to the text and the text format.
 *
 */
function _shortcode_process($text, $format) {
  $shortcodes = shortcode_list_all();
  $enabled_names = array();
  foreach ($shortcodes as $name => $item) {
    $shortcodes[$name]->enabled = shortcode_shortcode_is_enabled($name, $format);
    if ($shortcodes[$name]->enabled) {
      $enabled_names[] = $name;
      $shortcodes[$name]->function = $shortcodes[$name]->module . '_shortcode_' . $name;
    }
  }
  if (!$enabled_names) {
    return $text;
  }

  // remove the p tags around the lines whitch contains shortcodes only

  //$text = preg_replace('!<p>(\[.*?\])<\/p>!is', '\\1', $text);

  // we have to insert a special chars between the shortcode elements because
  // without it it is not works korrectly

  //$text = preg_replace('!<p>\s*(\[.*?\])\s*<\/p>!is', '#!#\\1', $text);

  //$text = preg_replace('!(\[.*?\])!is', '#!#\\1', $text);

  // save the shortcodes
  _shortcode_tags($shortcodes);

  //dpr($shortcodes);

  // old, wp way

  //$tag_regexp = _shortcode_get_shortcode_regex($enabled_names);

  //$text = preg_replace_callback('/' . $tag_regexp . '/s', '_shortcode_process_tag', $text);

  //dpr('after process:');

  //dpr($text);

  //$text = preg_replace('/#!#/is', '', $text);

  // imroved version - recursive processing - embed tags within tags supported!
  $chunks = preg_split('!(\\[.*?\\])!', $text, -1, PREG_SPLIT_DELIM_CAPTURE);

  //dpr($chunks);
  $heap = array();
  $heap_index = array();

  // array_shift — Shift an element off the beginning of array
  // mixed array_shift  ( array &$array  )
  // array_unshift — Prepend one or more elements to the beginning of an array
  // int array_unshift  ( array &$array  , mixed $var  [, mixed $...  ] )
  foreach ($chunks as $c) {
    if (!$c) {
      continue;
    }

    // shortcode or not
    if ($c[0] == '[' && substr($c, -1, 1) == ']') {

      // $c contains shortcode
      // self-closing tag or not
      $c = substr($c, 1, -1);

      //dpr('process: ' . $c);
      if (substr($c, -1, 1) == '/') {

        // process a self closing tag - it has / at the end!

        //dpr('self closing: ' . $c);

        /*
         * 0 - the full tag text?
         * 1/5 - An extra [ or ] to allow for escaping shortcodes with double [[]]
         * 2 - The shortcode name
         * 3 - The shortcode argument list
         * 4 - The content of a shortcode when it wraps some content.
         * */
        $ts = explode(' ', trim($c));
        $tag = array_shift($ts);
        $m = array(
          $c,
          '',
          $tag,
          implode(' ', $ts),
          NULL,
          '',
        );
        array_unshift($heap_index, '_string_');
        array_unshift($heap, _shortcode_process_tag($m));
      }
      elseif ($c[0] == '/') {

        // closing tag - process the heap
        $closing_tag = substr($c, 1);

        //dpr('closing tag: ' . $closing_tag );
        $process_heap = array();
        $process_heap_index = array();
        $found = FALSE;

        // get elements from heap and process
        do {
          $tag = array_shift($heap_index);
          $heap_text = array_shift($heap);
          if ($closing_tag == $tag) {

            // process the whole tag
            $m = array(
              $tag . ' ' . $heap_text,
              '',
              $tag,
              $heap_text,
              implode('', $process_heap),
              '',
            );
            $str = _shortcode_process_tag($m);

            //dpr($str);
            array_unshift($heap_index, '_string_');
            array_unshift($heap, $str);
            $found = TRUE;
          }
          else {
            array_unshift($process_heap, $heap_text);
            array_unshift($process_heap_index, $tag);
          }
        } while (!$found && $heap);
        if (!$found) {
          foreach ($process_heap as $val) {
            array_unshift($heap, $val);
          }
          foreach ($process_heap_index as $val) {
            array_unshift($heap_index, $val);
          }

          //array_unshift($heap_index, '_string_');

          //array_unshift($heap, 'notfound: ' . $closing_tag . ':' . implode('', $process_heap));
        }
      }
      else {

        // starting tag. put to the heap

        //dpr('tag pattern: ' . $c);
        $ts = explode(' ', trim($c));
        $tag = array_shift($ts);

        // dpr('start tag: ' . $tag);
        array_unshift($heap_index, $tag);
        array_unshift($heap, implode(' ', $ts));
      }
    }
    else {

      // not found a pair?
      array_unshift($heap_index, '_string_');
      array_unshift($heap, $c);
    }
  }
  return implode('', array_reverse($heap));

  //d(array_reverse($heap));

  //return $text;
}

// _shortcode_process($text, $format)

/*
 * Html corrector for wysiwyg editors
 *
 * Correncting p elements around the divs. No div are allowed in p so remove them.
 *
 */
function _shortcode_postprocess_text($text) {

  //return $text;
  $patterns = array(
    '|#!#|is',
    '!<p>(&nbsp;|\\s)*(<\\/*div>)!is',
    '!<p>(&nbsp;|\\s)*(<div)!is',
    '!(<\\/div.*?>)\\s*</p>!is',
    '!(<div.*?>)\\s*</p>!is',
  );

  /*
   *
   */

  //$replacements = array('!!\\2', '###\\2', '@@@\\1');
  $replacements = array(
    '',
    '\\2',
    '\\2',
    '\\1',
    '\\1',
  );
  return preg_replace($patterns, $replacements, $text);
}

/**
 * Regular Expression callable for do_shortcode() for calling shortcode hook.
 * @see get_shortcode_regex for details of the match array contents.
 *
 * @since 2.5
 * @access private
 * @uses $shortcode_tags
 *
 * @param array $m Regular expression match array
 * @return mixed False on failure.
 */
function _shortcode_process_tag($m) {
  $shortcodes = _shortcode_tags();

  // allow [[foo]] syntax for escaping a tag
  if ($m[1] == '[' && $m[5] == ']') {
    return substr($m[0], 1, -1);
  }
  $tag = $m[2];
  if ($shortcodes[$tag]->enabled) {

    //dpr('_shortcode_process_tag: ' . $tag);

    // tag exists (enabled)
    $attr = _shortcode_parse_attrs($m[3]);

    //dpr($attr);

    /*
     * 0 - the full tag text?
     * 1/5 - An extra [ or ] to allow for escaping shortcodes with double [[]]
     * 2 - The shortcode name
     * 3 - The shortcode argument list
     * 4 - The content of a shortcode when it wraps some content.
     * */
    if (!is_null($m[4])) {

      //dpr('fv: ' . $shortcodes[$tag]->function);

      // enclosing tag - extra parameter
      return $m[1] . call_user_func($shortcodes[$tag]->function, $attr, $m[4], $m[2]) . $m[5];
    }
    else {

      // self-closing tag

      //dpr('fv self closing: ' . $shortcodes[$tag]->function);
      return $m[1] . call_user_func($shortcodes[$tag]->function, $attr, NULL, $m[2]) . $m[5];
    }
  }
  elseif (is_null($m[4])) {
    return $m[4];
  }
  return '';
}

// _shortcode_process_tag()

/**
 * Retrieve all attributes from the shortcodes tag.
 *
 * The attributes list has the attribute name as the key and the value of the
 * attribute as the value in the key/value pair. This allows for easier
 * retrieval of the attributes, since all attributes have to be known.
 *
 * @since 2.5
 *
 * @param string $text
 * @return array List of attributes and their value.
 */
function _shortcode_parse_attrs($text) {
  $atts = array();
  $pattern = '/(\\w+)\\s*=\\s*"([^"]*)"(?:\\s|$)|(\\w+)\\s*=\\s*\'([^\']*)\'(?:\\s|$)|(\\w+)\\s*=\\s*([^\\s\'"]+)(?:\\s|$)|"([^"]*)"(?:\\s|$)|(\\S+)(?:\\s|$)/';
  $text = preg_replace("/[\\x{00a0}\\x{200b}]+/u", " ", $text);
  if (preg_match_all($pattern, $text, $match, PREG_SET_ORDER)) {
    foreach ($match as $m) {
      if (!empty($m[1])) {
        $atts[strtolower($m[1])] = stripcslashes($m[2]);
      }
      elseif (!empty($m[3])) {
        $atts[strtolower($m[3])] = stripcslashes($m[4]);
      }
      elseif (!empty($m[5])) {
        $atts[strtolower($m[5])] = stripcslashes($m[6]);
      }
      elseif (isset($m[7]) and strlen($m[7])) {
        $atts[] = stripcslashes($m[7]);
      }
      elseif (isset($m[8])) {
        $atts[] = stripcslashes($m[8]);
      }
    }
  }
  else {
    $atts = ltrim($text);
  }
  return $atts;
}

// _shortcode_parse_attrs

/**
 * Retrieve the shortcode regular expression for searching.
 *
 * The regular expression combines the shortcode tags in the regular expression
 * in a regex class.
 *
 * The regular expresion contains 6 different sub matches to help with parsing.
 *
 * 1/6 - An extra [ or ] to allow for escaping shortcodes with double [[]]
 * 2 - The shortcode name
 * 3 - The shortcode argument list
 * 4 - The self closing /
 * 5 - The content of a shortcode when it wraps some content.
 *
 * @return string The shortcode search regular expression
 */
function _shortcode_get_shortcode_regex($names) {
  $tagregexp = join('|', array_map('preg_quote', $names));

  // WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcodes()
  return '(.?)\\[(' . $tagregexp . ')\\b(.*?)(?:(\\/))?\\](?:(.+?)\\[\\/\\2\\])?(.?)';
}

/**
 * Combine user attributes with known attributes and fill in defaults when needed.
 *
 * The pairs should be considered to be all of the attributes which are
 * supported by the caller and given as a list. The returned attributes will
 * only contain the attributes in the $pairs list.
 *
 * If the $atts list has unsupported attributes, then they will be ignored and
 * removed from the final returned list.
 *
 * @since 2.5
 *
 * @param array $pairs Entire list of supported attributes and their defaults.
 * @param array $atts User defined attributes in shortcode tag.
 * @return array Combined and filtered attribute list.
 */
function shortcode_attrs($pairs, $atts) {
  $atts = (array) $atts;
  $out = array();
  foreach ($pairs as $name => $default) {
    if (array_key_exists($name, $atts)) {
      $out[$name] = $atts[$name];
    }
    else {
      $out[$name] = $default;
    }
  }
  return $out;
}

/**
 * Helper function to decide the give param bool value
 * @param mixed $var
 *
 * @return bool
 */
function shortcode_bool($var) {
  switch (strtolower($var)) {
    case false:
    case 'false':
    case 'no':
    case '0':
      $res = FALSE;
      break;
    default:
      $res = TRUE;
      break;
  }
  return $res;
}

/**
 * Class parameter helper function
 * @param $class
 * @param $default
 */
function shortcode_add_class($class = '', $default = '') {
  if ($class) {
    if (!is_array($class)) {
      $class = explode(' ', $class);
    }
    array_unshift($class, $default);
    $class = array_unique($class);
  }
  else {
    $class[] = $default;
  }
  return implode(' ', $class);
}

//shortcode_add_class

// shortcode implementations

/**
 * Generates a random code
 *
 * Calling
 * [random length=X /]
 *
 * Where X is the length of the random text.
 * If the length empty or invalid, between 1-99, the length will be 8
 *
 */
function shortcode_shortcode_random($attrs, $text) {
  extract(shortcode_attrs(array(
    'length' => 8,
  ), $attrs));
  $length = intval($length);
  if ($length < 0 || $length > 99) {
    $length = 8;
  }
  $text = '';
  for ($i = 0; $i < $length; ++$i) {
    $text .= chr(rand(32, 126));
  }
  return $text;
}

// shortcode_shortcode_random()

/**
 * Provides a random tag tip
 *
 * @param $format
 * @param $long
 */
function shortcode_shortcode_random_tip($format, $long) {
  $output = '';
  if (shortcode_shortcode_is_enabled('random', $format)) {
    $output = '<p><strong>[random (length=X) /]</strong>';
    if ($long) {
      $output .= ' insert a random text with length between 0-99 or if length omitted, 8.</p>';
    }
    else {
      $output .= ' inserts a random text with max length 99. Default length is 8.</p>';
    }
  }
  return $output;
}

Functions

Namesort descending Description
shortcode_add_class Class parameter helper function
shortcode_attrs Combine user attributes with known attributes and fill in defaults when needed.
shortcode_bool Helper function to decide the give param bool value
shortcode_filter Implementation of hook_filter().
shortcode_filter_tips Implementation of hook_filter_tips().
shortcode_list_all Build a list of all shortcodes (for filter). Calls the shortcode hook with the list parameter on all module
shortcode_shortcodes shortcodes hook implementation
shortcode_shortcode_is_enabled
shortcode_shortcode_random Generates a random code
shortcode_shortcode_random_tip Provides a random tag tip
shortcode_tips_all Build a list of all shortcodes (for filter). Calls the shortcode hook with the tips parameter on all module From filter.module
_shortcode_get_shortcode_regex Retrieve the shortcode regular expression for searching.
_shortcode_parse_attrs Retrieve all attributes from the shortcodes tag.
_shortcode_postprocess_text
_shortcode_process Process the shortcodes according to the text and the text format.
_shortcode_process_tag Regular Expression callable for do_shortcode() for calling shortcode hook.
_shortcode_settings_form Base form elements of the all shortcode options
_shortcode_tags Tags cache