";
}
return $output;
}
/**
* @brief Generates a random string.
*
* @param number $size
* @param int $type
*
* @return string
*/
function random_string($size = 64, $type = RANDOM_STRING_HEX) {
// generate a bit of entropy and run it through the whirlpool
$s = hash('whirlpool', (string) rand() . uniqid(rand(),true) . (string) rand(),(($type == RANDOM_STRING_TEXT) ? true : false));
$s = (($type == RANDOM_STRING_TEXT) ? str_replace("\n","",base64url_encode($s,true)) : $s);
return(substr($s, 0, $size));
}
/**
* @brief Input filter to replace HTML tag characters with something safe.
*
* @param string $string Input string
*
* @return string Filtered string
*/
function notags($string) {
return(str_replace(array("<",">"), array('[',']'), $string));
}
/**
* use this on input where angle chars shouldn't be removed,
* and allow them to be safely used in HTML.
*
* @param string $string
*
* @return string
*/
function escape_tags($string) {
return(htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false));
}
function z_input_filter($s,$type = 'text/bbcode',$allow_code = false) {
if($type === 'text/bbcode')
return escape_tags($s);
if($type == 'text/plain')
return escape_tags($s);
if($type == 'application/x-pdl')
return escape_tags($s);
if(App::$is_sys) {
return $s;
}
if($allow_code) {
if($type === 'text/markdown')
return htmlspecialchars($s,ENT_QUOTES);
return $s;
}
if($type === 'text/markdown') {
$x = new MarkdownSoap($s);
return $x->clean();
}
if($type === 'text/html')
return purify_html($s);
return escape_tags($s);
}
/**
* @brief Use HTMLPurifier to get standards compliant HTML.
*
* Use the HTMLPurifier
* library to get filtered and standards compliant HTML.
*
* @see HTMLPurifier
*
* @param string $s raw HTML
* @param boolean $allow_position allow CSS position
* @return string standards compliant filtered HTML
*/
function purify_html($s, $allow_position = false) {
/**
* @FIXME this function has html output, not bbcode - so safely purify these
* require_once('include/html2bbcode.php');
* $s = html2bb_video($s);
* $s = oembed_html2bbcode($s);
*/
$config = HTMLPurifier_Config::createDefault();
$config->set('Cache.DefinitionImpl', null);
$config->set('Attr.EnableID', true);
// If enabled, target=blank attributes are added to all links.
//$config->set('HTML.TargetBlank', true);
//$config->set('Attr.AllowedFrameTargets', ['_blank', '_self', '_parent', '_top']);
// restore old behavior of HTMLPurifier < 4.8, only used when targets allowed at all
// do not add rel="noreferrer" to all links with target attributes
//$config->set('HTML.TargetNoreferrer', false);
// do not add noopener rel attributes to links which have a target attribute associated with them
//$config->set('HTML.TargetNoopener', false);
//Allow some custom data- attributes used by built-in libs.
//In this way members which do not have allowcode set can still use the built-in js libs in webpages to some extent.
$def = $config->getHTMLDefinition(true);
//data- attributes used by the foundation library
// f6 navigation
//dropdown menu
$def->info_global_attr['data-dropdown-menu'] = new HTMLPurifier_AttrDef_Text;
//drilldown menu
$def->info_global_attr['data-drilldown'] = new HTMLPurifier_AttrDef_Text;
//accordion menu
$def->info_global_attr['data-accordion-menu'] = new HTMLPurifier_AttrDef_Text;
//responsive navigation
$def->info_global_attr['data-responsive-menu'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-responsive-toggle'] = new HTMLPurifier_AttrDef_Text;
//magellan
$def->info_global_attr['data-magellan'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-magellan-target'] = new HTMLPurifier_AttrDef_Text;
// f6 containers
//accordion
$def->info_global_attr['data-accordion'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-accordion-item'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-tab-content'] = new HTMLPurifier_AttrDef_Text;
//dropdown
$def->info_global_attr['data-dropdown'] = new HTMLPurifier_AttrDef_Text;
//off-canvas
$def->info_global_attr['data-off-canvas-wrapper'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-off-canvas'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-off-canvas-content'] = new HTMLPurifier_AttrDef_Text;
//reveal
$def->info_global_attr['data-reveal'] = new HTMLPurifier_AttrDef_Text;
//tabs
$def->info_global_attr['data-tabs'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-tabs-content'] = new HTMLPurifier_AttrDef_Text;
// f6 media
//orbit
$def->info_global_attr['data-orbit'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-slide'] = new HTMLPurifier_AttrDef_Text;
//tooltip
$def->info_global_attr['data-tooltip'] = new HTMLPurifier_AttrDef_Text;
// f6 plugins
//abide - the use is pointless since we can't do anything with forms
//equalizer
$def->info_global_attr['data-equalizer'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-equalizer-watch'] = new HTMLPurifier_AttrDef_Text;
//interchange - potentially dangerous since it can load content
//toggler
$def->info_global_attr['data-toggler'] = new HTMLPurifier_AttrDef_Text;
//sticky
$def->info_global_attr['data-sticky'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-sticky-container'] = new HTMLPurifier_AttrDef_Text;
// f6 common
$def->info_global_attr['data-options'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-toggle'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-close'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-open'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-position'] = new HTMLPurifier_AttrDef_Text;
//data- attributes used by the bootstrap library
$def->info_global_attr['data-dismiss'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-target'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-toggle'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-backdrop'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-keyboard'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-show'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-spy'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-offset'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-animation'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-container'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-delay'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-placement'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-title'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-trigger'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-content'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-trigger'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-parent'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-ride'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-slide-to'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-slide'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-interval'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-pause'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-wrap'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-offset-top'] = new HTMLPurifier_AttrDef_Text;
$def->info_global_attr['data-offset-bottom'] = new HTMLPurifier_AttrDef_Text;
//some html5 elements
//Block
$def->addElement('section', 'Block', 'Flow', 'Common');
$def->addElement('nav', 'Block', 'Flow', 'Common');
$def->addElement('article', 'Block', 'Flow', 'Common');
$def->addElement('aside', 'Block', 'Flow', 'Common');
$def->addElement('header', 'Block', 'Flow', 'Common');
$def->addElement('footer', 'Block', 'Flow', 'Common');
//Inline
$def->addElement('button', 'Inline', 'Inline', 'Common');
if($allow_position) {
$cssDefinition = $config->getCSSDefinition();
$cssDefinition->info['position'] = new HTMLPurifier_AttrDef_Enum(array('absolute', 'fixed', 'relative', 'static', 'inherit'), false);
$cssDefinition->info['left'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage()
));
$cssDefinition->info['right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage()
));
$cssDefinition->info['top'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage()
));
$cssDefinition->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length(),
new HTMLPurifier_AttrDef_CSS_Percentage()
));
}
$purifier = new HTMLPurifier($config);
return $purifier->purify($s);
}
/**
* @brief Generate a string that's random, but usually pronounceable.
*
* Used to generate initial passwords.
*
* @note In order to create "pronounceable" strings some consonant pairs or
* letters that does not make a very good word ending are chopped off, so that
* the returned string length can be lower than $len.
*
* @param int $len max length of generated string
* @return string Genereated random, but usually pronounceable string
*/
function autoname($len) {
if ($len <= 0)
return '';
$vowels = array('a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u');
if (mt_rand(0, 5) == 4)
$vowels[] = 'y';
$cons = array(
'b','bl','br',
'c','ch','cl','cr',
'd','dr',
'f','fl','fr',
'g','gh','gl','gr',
'h',
'j',
'k','kh','kl','kr',
'l',
'm',
'n',
'p','ph','pl','pr',
'qu',
'r','rh',
's','sc','sh','sm','sp','st',
't','th','tr',
'v',
'w','wh',
'x',
'z','zh'
);
$midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
'nd','ng','nk','nt','rn','rp','rt');
// avoid these consonant pairs at the end of the string
$noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh');
$start = mt_rand(0, 2);
if ($start == 0)
$table = $vowels;
else
$table = $cons;
$word = '';
for ($x = 0; $x < $len; $x ++) {
$r = mt_rand(0, count($table) - 1);
$word .= $table[$r];
if ($table == $vowels)
$table = array_merge($cons, $midcons);
else
$table = $vowels;
}
$word = substr($word, 0, $len);
foreach ($noend as $noe) {
if ((strlen($word) > 2) && (substr($word, -2) == $noe)) {
$word = substr($word, 0, -1);
break;
}
}
// avoid the letter 'q' as it does not make a very good word ending
if (substr($word, -1) == 'q')
$word = substr($word, 0, -1);
return $word;
}
/**
* @brief escape text ($str) for XML transport
*
* @param string $str
* @return string Escaped text.
*/
function xmlify($str) {
$buffer = '';
if(is_array($str)) {
// allow to fall through so we ge a PHP error, as the log statement will
// probably get lost in the noise unless we're specifically looking for it.
btlogger('xmlify called with array: ' . print_r($str,true), LOGGER_NORMAL, LOG_WARNING);
}
$len = mb_strlen($str);
for($x = 0; $x < $len; $x ++) {
$char = mb_substr($str,$x,1);
switch( $char ) {
case "\r" :
break;
case "&" :
$buffer .= '&';
break;
case "'" :
$buffer .= ''';
break;
case "\"" :
$buffer .= '"';
break;
case '<' :
$buffer .= '<';
break;
case '>' :
$buffer .= '>';
break;
case "\n" :
$buffer .= "\n";
break;
default :
$buffer .= $char;
break;
}
}
$buffer = trim($buffer);
return($buffer);
}
/**
* @brief Undo an xmlify.
*
* Pass xml escaped text ($s), returns unescaped text.
*
* @param string $s
*
* @return string
*/
function unxmlify($s) {
$ret = str_replace('&', '&', $s);
$ret = str_replace(array('<', '>', '"', '''), array('<', '>', '"', "'"), $ret);
return $ret;
}
/**
* @brief Automatic pagination.
*
* To use, get the count of total items.
* Then call App::set_pager_total($number_items);
* Optionally call App::set_pager_itemspage($n) to the number of items to display on each page
* Then call paginate($a) after the end of the display loop to insert the pager block on the page
* (assuming there are enough items to paginate).
* When using with SQL, the setting LIMIT %d, %d => App::$pager['start'],App::$pager['itemspage']
* will limit the results to the correct items for the current page.
* The actual page handling is then accomplished at the application layer.
*
* @param App &$a
*/
function paginate(&$a) {
$o = '';
$stripped = preg_replace('/(&page=[0-9]*)/','',App::$query_string);
// $stripped = preg_replace('/&zid=(.*?)([\?&]|$)/ism','',$stripped);
$stripped = str_replace('q=','',$stripped);
$stripped = trim($stripped,'/');
$pagenum = App::$pager['page'];
$url = z_root() . '/' . $stripped;
if(App::$pager['total'] > App::$pager['itemspage']) {
$o .= '
';
}
} else {
$params['string'] = preg_replace_callback('/<(3+)/','preg_heart',$params['string']);
$s = str_replace($params['texts'],$params['icons'],$params['string']);
}
$s = preg_replace_callback('//ism', 'smile_unshield', $s);
return $s;
}
/**
* @brief
*
* @param array $m
* @return string
*/
function smile_shield($m) {
return '';
}
function smile_unshield($m) {
return base64special_decode($m[1]);
}
/**
* @brief Expand <3333 to the correct number of hearts.
*
* @param array $x
*/
function preg_heart($x) {
if (strlen($x[1]) == 1)
return $x[0];
$t = '';
for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
$t .= '';
$r = str_replace($x[0],$t,$x[0]);
return $r;
}
function day_translate($s) {
$ret = str_replace(array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
array( t('Monday'), t('Tuesday'), t('Wednesday'), t('Thursday'), t('Friday'), t('Saturday'), t('Sunday')),
$s);
$ret = str_replace(array('January','February','March','April','May','June','July','August','September','October','November','December'),
array( t('January'), t('February'), t('March'), t('April'), t('May'), t('June'), t('July'), t('August'), t('September'), t('October'), t('November'), t('December')),
$ret);
return $ret;
}
/**
* @brief normalises a string.
*
* @param string $url
* @return string
*/
function normalise_link($url) {
$ret = str_replace(array('https:', '//www.'), array('http:', '//'), $url);
return(rtrim($ret, '/'));
}
/**
* @brief Compare two URLs to see if they are the same.
*
* But ignore slight but hopefully insignificant differences such as if one
* is https and the other isn't, or if one is www.something and the other
* isn't - and also ignore case differences.
*
* @see normalise_link()
*
* @param string $a
* @param string $b
* @return true if the URLs match, otherwise false
*/
function link_compare($a, $b) {
if (strcasecmp(normalise_link($a), normalise_link($b)) === 0)
return true;
return false;
}
// Given an item array, convert the body element from bbcode to html and add smilie icons.
// If attach is true, also add icons for item attachments
function unobscure(&$item) {
return;
}
function unobscure_mail(&$item) {
if(array_key_exists('mail_obscured',$item) && intval($item['mail_obscured'])) {
if($item['title'])
$item['title'] = base64url_decode(str_rot47($item['title']));
if($item['body'])
$item['body'] = base64url_decode(str_rot47($item['body']));
}
}
function theme_attachments(&$item) {
$arr = json_decode($item['attach'],true);
if(is_array($arr) && count($arr)) {
$attaches = array();
foreach($arr as $r) {
$icon = getIconFromType($r['type']);
if($r['title'])
$label = urldecode(htmlspecialchars($r['title'], ENT_COMPAT, 'UTF-8'));
if(! $label && $r['href'])
$label = basename($r['href']);
//some feeds provide an attachment where title an empty space
if(! $label || $label == ' ')
$label = t('Unknown Attachment');
$title = t('Size') . ' ' . (($r['length']) ? userReadableSize($r['length']) : t('unknown'));
require_once('include/channel.php');
if(is_foreigner($item['author_xchan']))
$url = $r['href'];
else
$url = z_root() . '/magic?f=&owa=1&hash=' . $item['author_xchan'] . '&bdest=' . bin2hex($r['href'] . '/' . $r['revision']);
//$s .= '' . $icon . '';
$attaches[] = array('label' => $label, 'url' => $url, 'icon' => $icon, 'title' => $title);
}
$s = replace_macros(get_markup_template('item_attach.tpl'), array(
'$attaches' => $attaches
));
}
return $s;
}
function format_categories(&$item,$writeable) {
$s = '';
$terms = get_terms_oftype($item['term'],TERM_CATEGORY);
if($terms) {
$categories = array();
foreach($terms as $t) {
$term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ;
if(! trim($term))
continue;
$removelink = (($writeable) ? z_root() . '/filerm/' . $item['id'] . '?f=&cat=' . urlencode($t['term']) : '');
$categories[] = array('term' => $term, 'writeable' => $writeable, 'removelink' => $removelink, 'url' => zid($t['url']));
}
$s = replace_macros(get_markup_template('item_categories.tpl'),array(
'$remove' => t('remove category'),
'$categories' => $categories
));
}
return $s;
}
/**
* @brief Add any hashtags which weren't mentioned in the message body, e.g. community tags
*
* @param[in] array &$item
* @return string HTML link of hashtag
*/
function format_hashtags(&$item) {
$s = '';
$terms = get_terms_oftype($item['term'], array(TERM_HASHTAG,TERM_COMMUNITYTAG));
if($terms) {
foreach($terms as $t) {
$term = htmlspecialchars($t['term'], ENT_COMPAT, 'UTF-8', false) ;
if(! trim($term))
continue;
if($t['url'] && strpos($item['body'], $t['url']))
continue;
if($s)
$s .= ' ';
$s .= '' . $term . '';
}
}
return $s;
}
function format_mentions(&$item) {
$s = '';
$terms = get_terms_oftype($item['term'],TERM_MENTION);
if($terms) {
foreach($terms as $t) {
$term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ;
if(! trim($term))
continue;
if(strpos($item['body'], $t['url']))
continue;
if($s)
$s .= ' ';
$s .= '' . $term . '';
}
}
return $s;
}
function format_filer(&$item) {
$s = '';
$terms = get_terms_oftype($item['term'],TERM_FILE);
if($terms) {
$categories = array();
foreach($terms as $t) {
$term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ;
if(! trim($term))
continue;
$removelink = z_root() . '/filerm/' . $item['id'] . '?f=&term=' . urlencode($t['term']);
$categories[] = array('term' => $term, 'removelink' => $removelink);
}
$s = replace_macros(get_markup_template('item_filer.tpl'),array(
'$remove' => t('remove from file'),
'$categories' => $categories
));
}
return $s;
}
function generate_map($coord) {
$coord = trim($coord);
$coord = str_replace(array(',','/',' '),array(' ',' ',' '),$coord);
$arr = [
'lat' => trim(substr($coord, 0, strpos($coord, ' '))),
'lon' => trim(substr($coord, strpos($coord, ' ')+1)),
'html' => ''
];
/**
* @hooks generate_map
* * \e string \b lat
* * \e string \b lon
* * \e string \b html the parsed HTML to return
*/
call_hooks('generate_map', $arr);
return (($arr['html']) ? $arr['html'] : $coord);
}
function generate_named_map($location) {
$arr = [
'location' => $location,
'html' => ''
];
/**
* @hooks generate_named_map
* * \e string \b location
* * \e string \b html the parsed HTML to return
*/
call_hooks('generate_named_map', $arr);
return (($arr['html']) ? $arr['html'] : $location);
}
function prepare_body(&$item,$attach = false,$opts = false) {
call_hooks('prepare_body_init', $item);
$s = '';
$photo = '';
$is_photo = ((($item['verb'] === ACTIVITY_POST) && ($item['obj_type'] === ACTIVITY_OBJ_PHOTO)) ? true : false);
if($is_photo) {
$object = json_decode($item['obj'],true);
if(array_key_exists('url',$object) && is_array($object['url']) && array_key_exists(0,$object['url'])) {
// if original photo width is <= 640px prepend it to item body
if(array_key_exists('width',$object['url'][0]) && $object['url'][0]['width'] <= 640) {
$s .= '
' . $s;
}
// if original photo width is > 640px make it a cover photo
if(array_key_exists('width',$object['url'][0]) && $object['url'][0]['width'] > 640) {
$scale = ((($object['url'][1]['width'] == 1024) || ($object['url'][1]['height'] == 1024)) ? 1 : 0);
$photo = '';
}
}
}
if($item['item_obscured']) {
$s .= prepare_binary($item);
}
else {
if($item['summary']) {
$s .= prepare_text('[summary]' . $item['summary'] . '[/summary]' . $item['body'],$item['mimetype'],$opts);
}
else {
$s .= prepare_text($item['body'],$item['mimetype'], $opts);
}
}
$event = (($item['obj_type'] === ACTIVITY_OBJ_EVENT) ? format_event_obj($item['obj']) : false);
// This is not the most pleasant UI element possible, but this is difficult to add to one of the templates.
// Eventually we may wish to add/remove to/from calendar in the message title area but it will take a chunk
// of code re-factoring to make that happen.
if($event['header'] && $item['resource_id']) {
$event['header'] .= '';
}
$prep_arr = array(
'item' => $item,
'html' => $event ? $event['content'] : $s,
'event' => $event['header'],
'photo' => $photo
);
call_hooks('prepare_body', $prep_arr);
$s = $prep_arr['html'];
$photo = $prep_arr['photo'];
$event = $prep_arr['event'];
if(! $attach) {
return $s;
}
if(strpos($s,'