popsu-d7/sites/all/modules/shortcode/shortcode.module
Bachir Soussi Chiadmi 1bc61b12ad first import
2015-04-08 11:40:19 +02:00

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>(&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) {
// 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;
}