/**
* smd_wrap
*
* A Textpattern CMS plugin for wrapping content with HTML tags, labels and attributes.
* -> Adds wraptag / class / html_id / label support round any tag
* -> Permits a range of formatting options for manipulating the item
* (e.g. trim, escape, sanitize, change case, linkify, format date,
* strip tags, split / combine, process with textile, etc).
* -> If the content is empty, nothing is displayed.
* -> Supports
*
* @author Stef Dawson
* @link http://stefdawson.com/
*/
// TODO:
// * Allow hidden pref to determine default transforms?
function smd_wrap($atts, $thing=NULL) {
global $prefs;
extract(lAtts(array(
'item' => '',
'wraptag' => '',
'class' => '',
'html_id' => '',
'label' => '',
'labeltag' => '',
'attr' => '',
'prefix' => '',
'suffix' => '',
'format' => '', // convenience only: same as transform
'transform' => '',
'delim' => ',',
'param_delim' => '|',
'trim' => 1,
'debug' => 0,
),$atts));
// item attribute trumps container
$thing = ($item) ? $item : $thing;
$out = '';
if ($format) {
trigger_error("smd_wrap: format attribute deprecated: use transform attribute instead.", E_USER_NOTICE);
$transform = $format;
}
// Grab the true portion of any container
$truePart = EvalElse($thing, 1);
if ($debug) {
echo '++ TO WRAP ++';
dmp($item);
}
if ($thing) {
// Handle custom attributes
if ($attr) {
$custom_atts = array();
$attribs = do_list($attr);
foreach($attribs as $attdef) {
list($key, $val) = do_list($attdef, $param_delim);
$custom_atts[] = $key . '="' . $val . '"';
}
$attr = ' ' . join(' ', $custom_atts);
}
// Run the Txp parser first
$out = parse($truePart);
$out = $trim ? trim($out) : $out;
if ($out) {
// Top and tail the output
$out = $prefix.$out.$suffix;
// Reformat the item with any of the following transformations, in the supplied order
if ($transform) {
$formats = do_list($transform, $delim);
foreach ($formats as $xformlist) {
// Use explode() because do_list() performs a trim() that we don't want
$xform = explode($param_delim, $xformlist);
$xtype = array_shift($xform);
switch ($xtype) {
case 'add':
$pos = array_shift($xform);
$val = $xform[0];
$out = (($pos == 'before' || $pos == 'both') ? $val : '') . $out . (($pos == 'after' || $pos == 'both') ? $val : '');
break;
case 'case':
foreach ($xform as $arg) {
if ($arg == "upper") {
$out = strtoupper($out);
} else if ($arg == "lower") {
$out = strtolower($out);
} else if ($arg == "ucfirst") {
$out = ucfirst($out);
} else if ($arg == "ucwords") {
$out = ucwords($out);
} else if ($arg == "title") {
// Inelegantly ported + extended for Unicode from David Gouch's JS title case script: thanks
// http://individed.com/code/to-title-case/js/to-title-case.js
$has_unicode = @preg_match('/\pL/u', 'a');
$az = ($has_unicode) ? '\p{Lu}' : 'A-Z';
$wrd = ($has_unicode) ? '(?:\p{L}|\p{M}|\p{N}|\p{Pc})' : '\w';
$capsre = '/[' . $az . ']+|&|[' . $wrd . ']+[._][' . $wrd . ']+/';
$smalls = get_pref('smd_wrap_small_words', 'a(nd?|s|t)?|b(ut|y)|en|for|i[fn]|o[fnr]|t(he|o)|vs?\.?|via');
$smallre = '/^(' . $smalls . ')[ \-]/i';
$pat = '/([' . $wrd . '&`\'‘’"“.@:\/\{\(\[<>_]+-? *)/';
$ret = array();
preg_match_all($pat, $out, $matches, PREG_PATTERN_ORDER|PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $it) {
$match = $it[0];
$index = $it[1];
$idxm2 = $index - 2;
$length = strlen($match);
$title = $out; // Copy the original since we're working on $out directly
// Fudge because substr with negative start counts from end of string in PHP
$idxm1 = (($index-1) < 0) ? 0 : $index - 1;
$offset = (($index-1) < 0) ? 1 : 2;
if ($index > 0
&& ( $title{$idxm2} !== ":" )
&& ( preg_match($smallre, $match) > 0 )
) {
$out = substr($out, 0, $index) . strtolower($match) . substr($out, $index+$length);
continue;
}
if (preg_match('/[\'\"_{(\[]/', substr($title, $idxm1, $offset)) > 0) {
$out = substr($out, 0, $index) . $match{0} . @strtoupper($match{1}) . substr($match, 2). substr($out, $index+$length);
continue;
}
if (
( preg_match($capsre, substr($match, 1)) > 0 )
|| ( preg_match('/[\])}]/', substr($title, $idxm1, $offset)) > 0 )
) {
$out = substr($out, 0, $index) . $match . substr($out, $index+$length);
continue;
}
$out = substr($out, 0, $index) . strtoupper($match{0}) . substr($match, 1) . substr($out, $index+$length);
}
}
}
break;
case 'date':
$nd = (is_numeric($out)) ? $out : strtotime($out);
if ($nd !== false) {
$out = strftime($xform[0], $nd);
}
break;
case 'escape':
$flags = 0;
foreach ($xform as $arg) {
switch ($arg) {
case 'no_quotes':
$flags |= ENT_NOQUOTES;
break;
case 'all_quotes':
$flags |= ENT_QUOTES;
break;
case 'double_quotes':
$flags |= ENT_COMPAT;
break;
default:
$flags |= $arg;
break;
}
}
$out = htmlspecialchars($out, $flags);
break;
case 'fordb':
$out = doSlash($out);
break;
case 'form':
foreach ($xform as $arg) {
$content = fetch_form($arg);
$reps = array(
'{smd_wrap_it}' => $out,
);
$out = parse(strtr($content, $reps));
}
break;
case 'link':
// From http://codesnippets.joyent.com/posts/show/2104
$pat = "@\b(https?://)?(([0-9a-zA-Z_!~*'().&=+$%-]+:)?[0-9a-zA-Z_!~*'().&=+$%-]+\@)?(([0-9]{1,3}\.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+\.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z]\.[a-zA-Z]{2,6})(:[0-9]{1,4})?((/[0-9a-zA-Z_!~*'().;?:\@&=+$,%#-]+)*/?)@";
$text = (isset($xform[0]) && $xform[0] != '') ? $xform[0] : '$0';
$out = preg_replace($pat, ''.$text.'', $out);
break;
case 'no_widow':
$no_widow = isset($xform[0]) ? $xform[0] : @$prefs['title_no_widow'];
$out = ($no_widow) ? noWidow($out) : $out;
break;
case 'replace':
$type = $xform[0] ? $xform[0] : 'string'; // string / regex
$from = $xform[1];
$to = isset($xform[2]) ? $xform[2] : '';
$out = ($type=='regex') ? preg_replace($from, $to, $out) : str_replace($from, $to, $out);
break;
case 'sanitize':
if ($xform[0] == "url") {
$out = sanitizeForUrl($out);
} else if ($xform[0] == "file") {
$out = sanitizeForFile($out);
} else if ($xform[0] == "url_title") {
$out = stripSpace($out, 1);
}
break;
case 'split':
$parts = explode($xform[0], $out);
array_shift($xform); // Throw away the split character
$joinchar = array_shift($xform);
// Grab the specified parts to return
$retstr = array();
$numParts = count($parts);
foreach ($xform as $idx) {
$addit = true;
if ($idx == 'all') {
$retstr = array_merge($retstr, $parts);
$addit = false;
} else if ($idx == 'last') {
$idx = $numParts;
} else if ($idx == 'rand') {
$idx = mt_rand(1, $numParts);
} else if (strpos($idx, '-') === 0) {
// Negative offset: count from the end: -1 = last, -2 = penultimate, etc
// The +1 is to counter the fact we subtract one in a moment. Damn zero indices
$idx = $numParts - substr($idx, 1) + 1;
} else if (strpos($idx, '>') === 0) {
$retstr = array_merge($retstr, array_slice($parts, substr($idx, 1)));
$addit = false;
} else if (strpos($idx, '<') === 0) {
$retstr = array_merge($retstr, array_slice($parts, 0, substr($idx, 1) - 1 ));
$addit = false;
}
// Subtract one because the input is 'human'
// e.g. split|.|+|1|3 == return the 1st and 3rd parts == $parts[0] + $parts[2]
if ($addit) {
$retstr[] = $parts[$idx-1];
}
}
$out = join($joinchar, $retstr);
break;
case 'strip_tags':
$out = strip_tags($out);
break;
case 'textile':
include_once txpath.'/lib/classTextile.php';
$textile = new Textile();
$out = $textile->TextileThis($out);
break;
case 'trim':
$charlist = isset($xform[0]) ? $xform[0] : '';
$out = ($charlist) ? trim($out, $charlist) : trim($out);
break;
}
}
}
} else {
return parse(EvalElse($thing, 0));
}
}
return ($out) ? doLabel($label, $labeltag).doTag($out, $wraptag, $class, $attr, $html_id) : '';
}