503 lines
13 KiB
Plaintext
503 lines
13 KiB
Plaintext
<?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
|
|
*/
|
|
function shortcode_list_all($reset = FALSE) {
|
|
$shortcodes = &drupal_static(__FUNCTION__);
|
|
if(!isset($shortcodes) || $reset) {
|
|
$shortcodes = array();
|
|
$shortcodes += module_invoke_all('shortcode_info');
|
|
}
|
|
|
|
return $shortcodes;
|
|
}
|
|
|
|
/**
|
|
* Returns only enabled shortcodes for a format
|
|
*/
|
|
function shortcode_list_all_enabled($format, $reset = FALSE) {
|
|
if (is_string($format)) {
|
|
$format = filter_format_load($format);
|
|
}
|
|
|
|
$shortcodes_enabled = &drupal_static(__FUNCTION__, array());
|
|
|
|
if (isset($shortcodes_enabled[$format->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',
|
|
'!<p>( |\s)*(<\/*div>)!is',
|
|
'!<p>( |\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) {
|
|
// 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;
|
|
}
|