friendica/include/text.php

2112 lines
54 KiB
PHP
Raw Normal View History

<?php
/**
* @file include/text.php
*/
use Friendica\App;
use Friendica\Content\ContactSelector;
use Friendica\Content\Feature;
use Friendica\Content\Smilies;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Addon;
use Friendica\Core\Config;
2018-01-21 19:33:59 +01:00
use Friendica\Core\L10n;
use Friendica\Core\PConfig;
2017-08-26 08:04:21 +02:00
use Friendica\Core\System;
2017-11-08 04:57:46 +01:00
use Friendica\Database\DBM;
use Friendica\Model\Contact;
use Friendica\Model\Event;
2018-02-07 21:22:40 +01:00
use Friendica\Model\Item;
use Friendica\Model\Profile;
use Friendica\Render\FriendicaSmarty;
use Friendica\Util\DateTimeFormat;
2017-12-13 22:37:34 +01:00
use Friendica\Util\Map;
require_once "mod/proxy.php";
require_once "include/conversation.php";
2013-03-27 10:11:40 +01:00
/**
* This is our template processor
*
* @param string|FriendicaSmarty $s the string requiring macro substitution,
* or an instance of FriendicaSmarty
2013-03-27 10:11:40 +01:00
* @param array $r key value pairs (search => replace)
* @return string substituted string
*/
function replace_macros($s, $r) {
$stamp1 = microtime(true);
2012-12-22 20:57:29 +01:00
$a = get_app();
2015-11-13 10:56:37 +01:00
// pass $baseurl to all templates
$r['$baseurl'] = System::baseUrl();
$t = $a->template_engine();
try {
$output = $t->replaceMacros($s, $r);
} catch (Exception $e) {
echo "<pre><b>" . __FUNCTION__ . "</b>: " . $e->getMessage() . "</pre>";
killme();
}
2013-01-27 13:57:44 +01:00
$a->save_timestamp($stamp1, "rendering");
2012-12-22 20:57:29 +01:00
return $output;
}
/**
* @brief Generates a pseudo-random string of hexadecimal characters
*
* @param int $size
* @return string
*/
function random_string($size = 64)
{
$byte_size = ceil($size / 2);
$bytes = random_bytes($byte_size);
$return = substr(bin2hex($bytes), 0, $size);
return $return;
}
/**
* This is our primary input filter.
*
* The high bit hack only involved some old IE browser, forget which (IE5/Mac?)
* that had an XSS attack vector due to stripping the high-bit on an 8-bit character
* after cleansing, and angle chars with the high bit set could get through as markup.
*
* This is now disabled because it was interfering with some legitimate unicode sequences
* and hopefully there aren't a lot of those browsers left.
*
* Use this on any text input where angle chars are not valid or permitted
* They will be replaced with safer brackets. This may be filtered further
* if these are not allowed either.
*
2013-03-27 10:11:40 +01:00
* @param string $string Input string
* @return string Filtered string
*/
function notags($string) {
return str_replace(["<", ">"], ['[', ']'], $string);
// High-bit filter no longer used
// return str_replace(array("<",">","\xBA","\xBC","\xBE"), array('[',']','','',''), $string);
}
2013-03-27 10:11:40 +01:00
2013-03-27 10:11:40 +01:00
/**
* use this on "body" or "content" input where angle chars shouldn't be removed,
* and allow them to be safely displayed.
* @param string $string
* @return string
*/
function escape_tags($string) {
return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false);
}
2013-03-27 10:11:40 +01:00
/**
* generate a string that's random, but usually pronounceable.
2013-03-27 10:11:40 +01:00
* used to generate initial passwords
* @param int $len
* @return string
*/
function autoname($len) {
if ($len <= 0) {
2012-04-06 03:44:36 +02:00
return '';
}
2012-04-06 03:44:36 +02:00
$vowels = ['a','a','ai','au','e','e','e','ee','ea','i','ie','o','ou','u'];
if (mt_rand(0, 5) == 4) {
$vowels[] = 'y';
}
$cons = [
'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 = ['ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp',
'nd','ng','nk','nt','rn','rp','rt'];
$noend = ['bl', 'br', 'cl','cr','dr','fl','fr','gl','gr',
2018-05-27 10:11:51 +02:00
'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh','q'];
$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) {
2018-05-27 10:11:51 +02:00
$noelen = strlen($noe);
if ((strlen($word) > $noelen) && (substr($word, -$noelen) == $noe)) {
$word = autoname($len);
break;
}
}
return $word;
}
2013-03-27 10:11:40 +01:00
/**
* escape text ($str) for XML transport
* @param string $str
* @return string Escaped text.
*/
function xmlify($str) {
/// @TODO deprecated code found?
/* $buffer = '';
2013-02-06 08:37:15 +01:00
$len = mb_strlen($str);
for ($x = 0; $x < $len; $x ++) {
2013-02-06 08:37:15 +01:00
$char = mb_substr($str,$x,1);
switch($char) {
case "\r" :
break;
case "&" :
$buffer .= '&amp;';
break;
case "'" :
$buffer .= '&apos;';
break;
case "\"" :
$buffer .= '&quot;';
break;
case '<' :
$buffer .= '&lt;';
break;
case '>' :
$buffer .= '&gt;';
break;
case "\n" :
$buffer .= "\n";
break;
default :
$buffer .= $char;
break;
}
}*/
/*
$buffer = mb_ereg_replace("&", "&amp;", $str);
$buffer = mb_ereg_replace("'", "&apos;", $buffer);
$buffer = mb_ereg_replace('"', "&quot;", $buffer);
$buffer = mb_ereg_replace("<", "&lt;", $buffer);
$buffer = mb_ereg_replace(">", "&gt;", $buffer);
*/
2015-06-22 23:47:08 +02:00
$buffer = htmlspecialchars($str, ENT_QUOTES, "UTF-8");
$buffer = trim($buffer);
return $buffer;
}
2013-03-27 10:11:40 +01:00
/**
* undo an xmlify
* @param string $s xml escaped text
* @return string unescaped text
*/
function unxmlify($s) {
/// @TODO deprecated code found?
// $ret = str_replace('&amp;','&', $s);
// $ret = str_replace(array('&lt;','&gt;','&quot;','&apos;'),array('<','>','"',"'"),$ret);
/*$ret = mb_ereg_replace('&amp;', '&', $s);
$ret = mb_ereg_replace('&apos;', "'", $ret);
$ret = mb_ereg_replace('&quot;', '"', $ret);
$ret = mb_ereg_replace('&lt;', "<", $ret);
$ret = mb_ereg_replace('&gt;', ">", $ret);
*/
2014-01-20 08:59:20 +01:00
$ret = htmlspecialchars_decode($s, ENT_QUOTES);
return $ret;
}
2013-03-27 10:11:40 +01:00
/**
* @brief Paginator function. Pushes relevant links in a pager array structure.
*
* Links are generated depending on the current page and the total number of items.
* Inactive links (like "first" and "prev" on page 1) are given the "disabled" class.
* Current page link is given the "active" CSS class
2013-03-27 10:11:40 +01:00
*
* @param App $a App instance
* @param int $count [optional] item count (used with minimal pager)
* @return Array data for pagination template
2013-03-27 10:11:40 +01:00
*/
function paginate_data(App $a, $count = null) {
$stripped = preg_replace('/([&?]page=[0-9]*)/', '', $a->query_string);
2012-04-02 09:45:45 +02:00
$stripped = str_replace('q=', '', $stripped);
$stripped = trim($stripped, '/');
$pagenum = $a->pager['page'];
if (($a->page_offset != '') && !preg_match('/[?&].offset=/', $stripped)) {
$stripped .= '&offset=' . urlencode($a->page_offset);
}
$url = $stripped;
$data = [];
function _l(&$d, $name, $url, $text, $class = '') {
if (strpos($url, '?') === false && ($pos = strpos($url, '&')) !== false) {
$url = substr($url, 0, $pos) . '?' . substr($url, $pos + 1);
2015-02-06 16:56:09 +01:00
}
$d[$name] = ['url' => $url, 'text' => $text, 'class' => $class];
}
if (!is_null($count)) {
// minimal pager (newer / older)
$data['class'] = 'pager';
2018-01-21 19:33:59 +01:00
_l($data, 'prev', $url . '&page=' . ($a->pager['page'] - 1), L10n::t('newer'), 'previous' . ($a->pager['page'] == 1 ? ' disabled' : ''));
_l($data, 'next', $url . '&page=' . ($a->pager['page'] + 1), L10n::t('older'), 'next' . ($count <= 0 ? ' disabled' : ''));
} else {
// full pager (first / prev / 1 / 2 / ... / 14 / 15 / next / last)
$data['class'] = 'pagination';
if ($a->pager['total'] > $a->pager['itemspage']) {
2018-01-21 19:33:59 +01:00
_l($data, 'first', $url . '&page=1', L10n::t('first'), $a->pager['page'] == 1 ? 'disabled' : '');
_l($data, 'prev', $url . '&page=' . ($a->pager['page'] - 1), L10n::t('prev'), $a->pager['page'] == 1 ? 'disabled' : '');
$numpages = $a->pager['total'] / $a->pager['itemspage'];
$numstart = 1;
$numstop = $numpages;
// Limit the number of displayed page number buttons.
if ($numpages > 8) {
$numstart = (($pagenum > 4) ? ($pagenum - 4) : 1);
$numstop = (($pagenum > ($numpages - 7)) ? $numpages : ($numstart + 8));
}
$pages = [];
for ($i = $numstart; $i <= $numstop; $i++) {
if ($i == $a->pager['page']) {
_l($pages, $i, '#', $i, 'current active');
} else {
_l($pages, $i, $url . '&page='. $i, $i, 'n');
}
}
if (($a->pager['total'] % $a->pager['itemspage']) != 0) {
if ($i == $a->pager['page']) {
_l($pages, $i, '#', $i, 'current active');
} else {
_l($pages, $i, $url . '&page=' . $i, $i, 'n');
}
}
$data['pages'] = $pages;
$lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages);
2018-01-21 19:33:59 +01:00
_l($data, 'next', $url . '&page=' . ($a->pager['page'] + 1), L10n::t('next'), $a->pager['page'] == $lastpage ? 'disabled' : '');
_l($data, 'last', $url . '&page=' . $lastpage, L10n::t('last'), $a->pager['page'] == $lastpage ? 'disabled' : '');
}
}
return $data;
}
/**
* Automatic pagination.
*
* To use, get the count of total items.
* Then call $a->set_pager_total($number_items);
* Optionally call $a->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 => $a->pager['start'],$a->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 App instance
* @return string html for pagination #FIXME remove html
*/
function paginate(App $a) {
$data = paginate_data($a);
$tpl = get_markup_template("paginate.tpl");
return replace_macros($tpl, ["pager" => $data]);
}
2013-03-27 10:11:40 +01:00
/**
* Alternative pager
* @param App $a App instance
* @param int $i
* @return string html for pagination #FIXME remove html
*/
function alt_pager(App $a, $i) {
$data = paginate_data($a, $i);
$tpl = get_markup_template("paginate.tpl");
return replace_macros($tpl, ['pager' => $data]);
}
2015-02-05 11:29:14 +01:00
/**
* Loader for infinite scrolling
* @return string html for loader
*/
function scroll_loader() {
$tpl = get_markup_template("scroll_loader.tpl");
return replace_macros($tpl, [
2018-01-21 19:33:59 +01:00
'wait' => L10n::t('Loading more entries...'),
'end' => L10n::t('The end')
]);
}
2013-03-27 10:11:40 +01:00
/**
* Turn user/group ACLs stored as angle bracketed text into arrays
*
2013-03-27 10:11:40 +01:00
* @param string $s
* @return array
*/
function expand_acl($s) {
// turn string array of angle-bracketed elements into numeric array
// e.g. "<1><2><3>" => array(1,2,3);
$ret = [];
if (strlen($s)) {
$t = str_replace('<', '', $s);
$a = explode('>', $t);
foreach ($a as $aa) {
if (intval($aa)) {
$ret[] = intval($aa);
}
}
}
return $ret;
}
2013-03-27 10:11:40 +01:00
/**
* Wrap ACL elements in angle brackets for storage
2013-03-27 10:11:40 +01:00
* @param string $item
*/
function sanitise_acl(&$item) {
if (intval($item)) {
$item = '<' . intval(notags(trim($item))) . '>';
} else {
unset($item);
}
}
2013-03-27 10:11:40 +01:00
/**
* Convert an ACL array to a storable string
*
2013-03-27 10:11:40 +01:00
* Normally ACL permissions will be an array.
* We'll also allow a comma-separated string.
*
2013-03-27 10:11:40 +01:00
* @param string|array $p
* @return string
*/
function perms2str($p) {
$ret = '';
if (is_array($p)) {
$tmp = $p;
} else {
$tmp = explode(',', $p);
}
if (is_array($tmp)) {
array_walk($tmp, 'sanitise_acl');
$ret = implode('', $tmp);
}
return $ret;
}
2013-03-27 10:11:40 +01:00
/**
* generate a guaranteed unique (for this domain) item ID for ATOM
* safe from birthday paradox
*
2013-03-27 10:11:40 +01:00
* @param string $hostname
* @param int $uid
* @return string
*/
function item_new_uri($hostname, $uid, $guid = "") {
do {
if ($guid == "") {
$hash = get_guid(32);
} else {
$hash = $guid;
$guid = "";
}
$uri = "urn:X-dfrn:" . $hostname . ':' . $uid . ':' . $hash;
$dups = dba::exists('item', ['uri' => $uri]);
} while ($dups == true);
return $uri;
}
2013-03-27 10:11:40 +01:00
/**
* @deprecated
* wrapper to load a view template, checking for alternate
* languages before falling back to the default
*
2013-03-27 10:11:40 +01:00
* @global string $lang
* @global App $a
* @param string $s view name
* @return string
*/
function load_view_file($s) {
global $lang, $a;
if (! isset($lang)) {
$lang = 'en';
}
$b = basename($s);
$d = dirname($s);
if (file_exists("$d/$lang/$b")) {
$stamp1 = microtime(true);
$content = file_get_contents("$d/$lang/$b");
$a->save_timestamp($stamp1, "file");
return $content;
}
$theme = $a->getCurrentTheme();
2012-07-24 04:37:00 +02:00
if (file_exists("$d/theme/$theme/$b")) {
$stamp1 = microtime(true);
$content = file_get_contents("$d/theme/$theme/$b");
$a->save_timestamp($stamp1, "file");
return $content;
}
$stamp1 = microtime(true);
$content = file_get_contents($s);
$a->save_timestamp($stamp1, "file");
return $content;
}
2013-03-27 10:11:40 +01:00
/**
* load a view template, checking for alternate
* languages before falling back to the default
*
2013-03-27 10:11:40 +01:00
* @global string $lang
* @param string $s view path
* @return string
*/
function get_intltext_template($s) {
global $lang;
2012-12-22 20:57:29 +01:00
$a = get_app();
$engine = '';
if ($a->theme['template_engine'] === 'smarty3') {
2012-12-22 20:57:29 +01:00
$engine = "/smarty3";
}
2012-12-22 20:57:29 +01:00
if (! isset($lang)) {
$lang = 'en';
}
if (file_exists("view/lang/$lang$engine/$s")) {
$stamp1 = microtime(true);
$content = file_get_contents("view/lang/$lang$engine/$s");
$a->save_timestamp($stamp1, "file");
return $content;
} elseif (file_exists("view/lang/en$engine/$s")) {
$stamp1 = microtime(true);
$content = file_get_contents("view/lang/en$engine/$s");
$a->save_timestamp($stamp1, "file");
return $content;
} else {
$stamp1 = microtime(true);
$content = file_get_contents("view$engine/$s");
$a->save_timestamp($stamp1, "file");
return $content;
}
}
2013-03-27 10:11:40 +01:00
/**
* load template $s
*
2013-03-27 10:11:40 +01:00
* @param string $s
* @param string $root
* @return string
*/
2012-12-22 20:57:29 +01:00
function get_markup_template($s, $root = '') {
$stamp1 = microtime(true);
2012-12-22 20:57:29 +01:00
$a = get_app();
$t = $a->template_engine();
try {
$template = $t->getTemplateFile($s, $root);
} catch (Exception $e) {
echo "<pre><b>" . __FUNCTION__ . "</b>: " . $e->getMessage() . "</pre>";
killme();
}
$a->save_timestamp($stamp1, "file");
return $template;
}
2013-03-27 10:11:40 +01:00
/**
* for html,xml parsing - let's say you've got
* an attribute foobar="class1 class2 class3"
* and you want to find out if it contains 'class3'.
* you can't use a normal sub string search because you
* might match 'notclass3' and a regex to do the job is
* possible but a bit complicated.
* pass the attribute string as $attr and the attribute you
2013-03-27 10:11:40 +01:00
* are looking for as $s - returns true if found, otherwise false
*
2013-03-27 10:11:40 +01:00
* @param string $attr attribute value
* @param string $s string to search
* @return boolean True if found, False otherwise
*/
function attribute_contains($attr, $s) {