format]) && !$reset) { return $shortcodes_enabled[$format->format]; } $shortcodes_enabled[$format->format] = array(); $shortcodes = shortcode_list_all($reset); $filters = filter_list_format($format->format); if (empty($filters['shortcode'])) { return array(); } foreach($filters['shortcode']->settings as $name => $enabled) { // Run through all shortcodes if ($enabled) { $shortcodes_enabled[$format->format][$name] = $shortcodes[$name]; } } return $shortcodes_enabled[$format->format]; } /** * Implementation of hook_filter_info(). */ function shortcode_filter_info() { $filters['shortcode'] = array( 'title' => t('Shortcodes'), 'description' => t('Provides WP like shortcodes to this text format.'), 'process callback' => '_shortcode_process', 'settings callback' => '_shortcode_settings_form', 'tips callback' => '_shortcode_filter_tips', ); $filters['shortcode_text_corrector'] = array( 'title' => t('Shortcodes - html corrector'), 'description' => t('Trying to correct the html around shortcodes. Enable only if you using wysiwyg editor.'), 'process callback' => '_shortcode_postprocess_text', ); return $filters; } /** * Filter tips callback */ function _shortcode_filter_tips($filter, $format, $long = FALSE) { $shortcodes = shortcode_list_all_enabled($format); $tips = array(); $args = func_get_args(); foreach($filter->settings as $name => $enabled) { // Run through all shortcodes if($enabled && !empty($shortcodes[$name]['tips callback']) && function_exists($shortcodes[$name]['tips callback'])) { $tips[] = call_user_func_array($shortcodes[$name]['tips callback'], array($format, $long)); } } return theme('item_list', array( 'title' => t('Shortcodes usage'), 'items' => $tips, 'type' => 'ol', ) ); } /** * Settings form */ function _shortcode_settings_form($form, &$form_state, $filter, $format, $defaults) { $settings = array(); $filter->settings += $defaults; $shortcodes = shortcode_list_all(); foreach ($shortcodes as $key => $shortcode) { $settings[$key] = array( '#type' => 'checkbox', '#title' => t('Enable %name shortcode', array('%name' => $shortcode['title'])), '#default_value' => array(), '#description' => 'Enable or disable this shortcode in this input format', ); if (!empty($filter->settings[$key])) { $settings[$key]['#default_value'] = $filter->settings[$key]; } elseif( !empty($defaults[$key])) { $settings[$key]['#default_value'] = $defaults[$key]; } } return $settings; } /** * Tags cache * @param $tags * * @access private */ function _shortcode_tags($tags = NULL) { $shortcodes = &drupal_static(__FUNCTION__, array()); if ($tags) { $shortcodes = $tags; return TRUE; } return $shortcodes; } /** * Process the shortcodes according to the text and the text format. */ function _shortcode_process($text, $filter) { // TODO: need cache for list_all for the given filter! $shortcodes = shortcode_list_all(); $shortcodes_enabled = array(); foreach($filter->settings as $name => $value) { // run through all shortcodes if($value && $shortcodes[$name]['process callback']) { $shortcodes_enabled[$name] = array( 'function' => $shortcodes[$name]['process callback'], ); } } if (empty($shortcodes_enabled)) { return $text; } // save the shortcodes _shortcode_tags($shortcodes_enabled); // improved version - recursive processing - embed tags within other tags is supported! $chunks = preg_split('!(\[.*?\])!', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); //dpr($chunks); $heap = array(); $heap_index = array(); 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); 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); } } } 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))); } /* * Html corrector for wysiwyg editors * * Correcting p elements around the divs. No div are allowed in p so remove them. * */ function _shortcode_postprocess_text($text, $filter) { $patterns = array( '|#!#|is', '!

( |\s)*(<\/*div>)!is', '!

( |\s)*()\s*

!is', '!()\s*

!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) { // get tags from static cache $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 (!empty($shortcodes[$tag])) { // tag exists (enabled) $attr = _shortcode_parse_attrs($m[3]); /* * 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]) ) { // 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 ''; } /** * 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; } /** * 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, $attrs) { $attrs = (array)$attrs; $out = array(); foreach ($pairs as $name => $default) { if (array_key_exists($name, $attrs)) { $out[$name] = $attrs[$name]; } else { $out[$name] = $default; } } return $out; } /** * Helper function to decide the given param is a 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; }