[ 'html' => '<', 'nonHtml' => '<' ], '≤REAL_GT≥' => [ 'html' => '>', 'nonHtml' => '>' ], '≤REAL_NBSP≥' => [ 'html' => ' ', 'nonHtml' => ' ' ], /*on post back the page is parsed, which turns & into & this is done to prevent that from happening, we are just protecting some chars from letting the parser nab them*/ '≤REAL_AMP≥' => [ 'html' => '& ', 'nonHtml' => '& ' ], ]; /* * Parsing "options". Some of these are real parsing parameters, such as protect_email and security options. * Others (like is_html) define the markup's semantic. * * TODO: Separate real parsing parameters from properties of the parsable markup * TO DO: To ease usage tracking, it may be best to replace $option with individual properties. * Or replace setOptions() with individual setters? */ public $option = []; // An associative array of (most) parameters (despite the singular) public function setOptions($option = []) { global $page, $prefs; $this->option = array_merge( [ 'is_html' => false, 'is_markdown' => (isset($prefs['markdown_enabled']) && $prefs['markdown_enabled'] === 'y' && $prefs['markdown_default'] === 'markdown'), /* Determines if "Tiki syntax" is parsed in some circumstances. Currently, when is_html is true, but that is probably wrong. Overriden by the HTML plugin to force wiki parsing */ 'parse_wiki' => ! isset($prefs['wysiwyg_wiki_parsed']) || $prefs['wysiwyg_wiki_parsed'] === 'y', 'absolute_links' => false, 'language' => '', 'noparseplugins' => false, 'stripplugins' => false, 'noheaderinc' => false, 'page' => $page, 'print' => false, 'parseimgonly' => false, 'preview_mode' => false, 'suppress_icons' => false, 'parsetoc' => true, 'inside_pretty' => false, 'process_wiki_paragraphs' => true, 'min_one_paragraph' => false, 'skipvalidation' => false, 'ck_editor' => false, 'namespace' => false, 'protect_email' => true, 'exclude_plugins' => [], 'exclude_all_plugins' => false, 'include_plugins' => [], 'typography' => true, 'autotoc' => false, ], empty($option) ? [] : (array) $this->option, (array)$option ); $this->option['include_plugins'] = array_map('strtolower', $this->option['include_plugins']); $this->option['exclude_plugins'] = array_map('strtolower', $this->option['exclude_plugins']); } public function __construct() { $this->setOptions(); } //* public function parse_data_raw($data) { $data = $this->parse_data($data); $data = str_replace("tiki-index", "tiki-index_raw", $data); return $data; } // This function handles wiki codes for those special HTML characters // that textarea won't leave alone. //* protected function parse_htmlchar(&$data) { // cleaning some user input // ckeditor parses several times and messes things up, we should only let it parse once if (! $this->option['ck_editor']) { $data = str_replace('&', '&', $data); } // oft-used characters (case insensitive) $data = preg_replace("/~bs~/i", "\", $data); $data = preg_replace("/~hs~/i", " ", $data); $data = preg_replace("/~amp~/i", "&", $data); $data = preg_replace("/~ldq~/i", "“", $data); $data = preg_replace("/~rdq~/i", "”", $data); $data = preg_replace("/~lsq~/i", "‘", $data); $data = preg_replace("/~rsq~/i", "’", $data); $data = preg_replace("/~c~/i", "©", $data); $data = preg_replace("/~--~/", "—", $data); $data = preg_replace("/ -- /", " — ", $data); $data = preg_replace("/~lt~/i", "<", $data); $data = preg_replace("/~gt~/i", ">", $data); // HTML numeric character entities $data = preg_replace("/~([0-9]+)~/", "&#$1;", $data); } // This function handles the protection of html entities so that they are not mangled when // parse_htmlchar runs, and as well so they can be properly seen, be it html or non-html public function protectSpecialChars($data, $is_html = false) { if ((isset($this->option['is_html']) && $this->option['is_html'] != true) || ! empty($this->option['ck_editor'])) { foreach ($this->specialChars as $key => $specialChar) { if ($this->option['is_markdown'] && $specialChar['html'] == '>') { // markdown uses greater-than character for blockquotes and links, so we need to keep it continue; } $data = str_replace($specialChar['html'], $key, $data); } } return $data; } // This function removed the protection of html entities so that they are rendered as expected by the viewer public function unprotectSpecialChars($data, $is_html = false) { if ( ( $is_html != false || ( isset($this->option['is_html']) && $this->option['is_html'])) || $this->option['ck_editor'] ) { foreach ($this->specialChars as $key => $specialChar) { $data = str_replace($key, $specialChar['html'], $data); } } else { foreach ($this->specialChars as $key => $specialChar) { $data = str_replace($key, $specialChar['nonHtml'], $data); } } return $data; } // Reverses parse_first. //* public function replace_preparse(&$data, &$preparsed, &$noparsed, $is_html = false) { $data1 = $data; $data2 = ""; // Cook until done. Handles nested cases. while ($data1 != $data2) { $data1 = $data; if (isset($noparsed["key"]) and count($noparsed["key"]) and count($noparsed["key"]) == count($noparsed["data"])) { $data = str_replace($noparsed["key"], $noparsed["data"], $data); } if (isset($preparsed["key"]) and count($preparsed["key"]) and count($preparsed["key"]) == count($preparsed["data"])) { $data = str_replace($preparsed["key"], $preparsed["data"], $data); } // nested keys in plugins like {CODE} get the section chars encoded $data = preg_replace('/§(\w{32})§/', '§$1§', $data); $data2 = $data; } $data = $this->unprotectSpecialChars($data, $is_html); } /** * Replace plugins with guid keys and store them in an array * * @param $data string data to be cleaned of plugins * @param $noparsed array output array * @see parserLib::plugins_replace() */ public function plugins_remove(&$data, &$noparsed, $removeCb = null) { $tikilib = TikiLib::lib('tiki'); if (isset($removeCb) && ! is_callable($removeCb)) { throw new Exception('Invalid callback'); } $matches = WikiParser_PluginMatcher::match($data); // find the plugins foreach ($matches as $match) { // each plugin if (isset($removeCb) && ! $removeCb($match)) { continue; } $plugin = (string) $match; $key = '§' . md5($tikilib->genPass()) . '§'; // by replace whole plugin with a guid $noparsed['key'][] = $key; $noparsed['data'][] = $plugin; } $data = isset($noparsed['data']) ? str_replace($noparsed['data'], $noparsed['key'], $data) : $data; } /** * Restore plugins from array * * @param $data string data previously processed with plugins_remove() * @param $noparsed array input array */ public function plugins_replace(&$data, $noparsed, $is_html = false) { $preparsed = []; // unused $noparsed['data'] = isset($noparsed['data']) ? str_replace('', '', $noparsed['data']) : ''; $this->replace_preparse($data, $preparsed, $noparsed, $is_html); } //* private function plugin_match(&$data, &$plugins) { global $pluginskiplist; if (! is_array($pluginskiplist)) { $pluginskiplist = []; } $matcher_fake = ["~pp~","~np~","<pre>"]; $matcher = "/\{([A-Z0-9_]+) *\(|\{([a-z]+)(\s|\})|~pp~|~np~|<[pP][rR][eE]>/"; $plugins = []; preg_match_all($matcher, $data, $tmp, PREG_SET_ORDER); foreach ($tmp as $p) { if ( in_array(TikiLib::strtolower($p[0]), $matcher_fake) || ( isset($p[1]) && ( in_array($p[1], $matcher_fake) || $this->plugin_exists($p[1]) ) ) || ( isset($p[2]) && ( in_array($p[2], $matcher_fake) || $this->plugin_exists($p[2]) ) ) ) { $plugins = $p; break; } } // Check to make sure there was a match. if (count($plugins) > 0 && strlen($plugins[0]) > 0) { $pos = 0; while (in_array($plugins[0], $pluginskiplist)) { $pos = strpos($data, $plugins[0], $pos) + 1; if (! preg_match($matcher, substr($data, $pos), $plugins)) { return; } } // If it is a true plugin if ($plugins[0][0] == "{") { $pos = strpos($data, $plugins[0]); // where plugin starts $pos_end = $pos + strlen($plugins[0]); // where character after ( is // Here we're going to look for the end of the arguments for the plugin. $i = $pos_end; $last_data = strlen($data); // We start with one open curly brace, and one open paren. $curlies = 1; // If model with ( if (strlen($plugins[1])) { $parens = 1; $plugins['type'] = 'long'; } else { $parens = 0; $plugins[1] = $plugins[2]; unset($plugins[3]); $plugins['type'] = 'short'; } // While we're not at the end of the string, and we still haven't found both closers while ($i < $last_data) { $char = substr($data, $i, 1); //print "
Data char: $i, $char, $curlies, $parens\n.
\n"; if ($char == "{") { $curlies++; } elseif ($char == "(" && $plugins['type'] == 'long') { $parens++; } elseif ($char == "}") { $curlies--; if ($plugins['type'] == 'short') { $lastParens = $i; } } elseif ($char == ")" && $plugins['type'] == 'long') { $parens--; $lastParens = $i; } // If we found the end of the match... if ($curlies == 0 && $parens == 0) { break; } $i++; } if ($curlies == 0 && $parens == 0) { $plugins[2] = (string) substr($data, $pos_end, $lastParens - $pos_end); $plugins[0] = $plugins[0] . (string) substr($data, $pos_end, $i - $pos_end + 1); /* print "
Match found: ";
                         print( $plugins[2] );
                         print "
"; */ } $plugins['arguments'] = isset($plugins[2]) ? $this->plugin_split_args($plugins[2]) : []; } else { $plugins[1] = $plugins[0]; $plugins[2] = ""; } } /* print "
Plugin match end:";
             print_r( $plugins );
             print "
"; */ } //* public function plugin_split_args($params_string) { $parser = new WikiParser_PluginArgumentParser(); return $parser->parse($params_string); } // get all the plugins of a text- can be limitted only to some //* public function getPlugins($data, $only = null) { $plugins = []; for (;;) { $this->plugin_match($data, $plugin); if (empty($plugin)) { break; } if (empty($only) || in_array($plugin[1], $only) || in_array(TikiLib::strtoupper($plugin[1]), $only) || in_array(TikiLib::strtolower($plugin[1]), $only)) { $plugins[] = $plugin; } $pos = strpos($data, $plugin[0]); $data = substr_replace($data, '', $pos, strlen($plugin[0])); } return $plugins; } // Transitional wrapper over WikiParser_Parsable::parse_first() // This recursive function handles pre- and no-parse sections and plugins public function parse_first(&$data, &$preparsed, &$noparsed, $real_start_diff = '0') { return WikiParser_Parsable::instantiate('')->parse_first($data, $preparsed, $noparsed, $real_start_diff); } protected function strip_unparsed_block(&$data, &$noparsed, $protect = false) { $tikilib = TikiLib::lib('tiki'); $start = -1; while (false !== $start = strpos($data, '~np~', $start + 1)) { if (false !== $end = strpos($data, '~/np~', $start)) { $content = substr($data, $start + 4, $end - $start - 4); if ($protect) { $content = $this->protectSpecialChars($content, $this->option['is_html']); } // ~pp~ type "plugins" $key = "§" . md5($tikilib->genPass()) . "§"; $noparsed["key"][] = preg_quote($key); $noparsed["data"][] = $content; $data = substr($data, 0, $start) . $key . substr($data, $end + 5); } } } // // Call 'wikiplugin_.*_description()' from given file // public function get_plugin_description($name, &$enabled, $area_id = 'editwiki') { if (( ! $info = $this->plugin_info($name) ) && $this->plugin_exists($name, true)) { $enabled = true; $func_name = "wikiplugin_{$name}_help"; if (! function_exists($func_name)) { return false; } $ret = $func_name(); return $this->parse_data($ret); } else { $smarty = TikiLib::lib('smarty'); $enabled = true; $ret = $info; if (isset($ret['prefs'])) { global $prefs; // If the plugin defines required preferences, they should all be to 'y' foreach ($ret['prefs'] as $pref) { if (! isset($prefs[$pref]) || $prefs[$pref] != 'y') { $enabled = false; return; } } } if (isset($ret['documentation']) && ctype_alnum($ret['documentation'])) { $ret['documentation'] = "http://doc.tiki.org/{$ret['documentation']}"; } $smarty->assign('area_id', $area_id); $smarty->assign('plugin', $ret); $smarty->assign('plugin_name', TikiLib::strtoupper($name)); return $smarty->fetch('tiki-plugin_help.tpl'); } } //* public function plugin_get_list($includeReal = true, $includeAlias = true) { return WikiPlugin_Negotiator_Wiki::getList($includeReal, $includeAlias); } //* public function plugin_exists($name, $include = false) { $php_name = 'lib/wiki-plugins/wikiplugin_'; $php_name .= TikiLib::strtolower($name) . '.php'; $exists = file_exists($php_name); if ($include && $exists) { include_once $php_name; } if ($exists) { return true; } elseif ($info = WikiPlugin_Negotiator_Wiki_Alias::info($name)) { // Make sure the underlying implementation exists return $this->plugin_exists($info['implementation'], $include); } return false; } //* public function plugin_info($name, $args = []) { static $known = []; if (isset($known[$name]) && $name != 'package') { return $known[$name]; } if (! $this->plugin_exists($name, true)) { return $known[$name] = false; } $func_name_info = "wikiplugin_{$name}_info"; if (! function_exists($func_name_info)) { if ($info = WikiPlugin_Negotiator_Wiki_Alias::info($name)) { return $known[$name] = $info['description']; } else { return $known[$name] = false; } } // Support Tiki Packages param overrides for Package plugin if ($name == 'package' && ! empty($args['package']) && ! empty($args['plugin'])) { $info = $func_name_info(); $parts = explode('/', $args['package']); if ($extensionPackage = \Tiki\Package\ExtensionManager::get($args['package'])) { $path = $extensionPackage->getPath() . '/lib/wiki-plugins/' . $args['plugin'] . '.php'; } else { $path = ''; } if (! file_exists($path)) { return $known[$name] = $info; } require_once($path); $namespace = $extensionPackage->getBaseNamespace(); if (! empty($namespace)) { $namespace .= '\\PackagePlugins\\'; } $functionname = $namespace . $args['plugin'] . "_info"; if (! function_exists($functionname)) { return $known[$name] = $info; } $viewinfo = $functionname(); if (isset($viewinfo['params'])) { $combinedparams = $viewinfo['params'] + $info['params']; } else { $combinedparams = $info['params']; } $info = $viewinfo + $info; $info['params'] = $combinedparams; return $known[$name] = $info; } return $known[$name] = $func_name_info(); } //* public function plugin_alias_info($name) { return WikiPlugin_Negotiator_Wiki_Alias::info($name); } //* public function plugin_alias_store($name, $data) { return WikiPlugin_Negotiator_Wiki_Alias::store($name, $data); } //* public function plugin_alias_delete($name) { return WikiPlugin_Negotiator_Wiki_Alias::delete($name); } //* public function plugin_enabled($name, &$output) { if (! $meta = $this->plugin_info($name)) { return true; // Legacy plugins always execute } global $prefs; $missing = []; if (isset($meta['prefs'])) { foreach ($meta['prefs'] as $pref) { if ($prefs[$pref] != 'y') { $missing[] = $pref; } } } if (count($missing) > 0) { $output = WikiParser_PluginOutput::disabled($name, $missing); return false; } return true; } //* public function plugin_is_inline($name) { if (! $meta = $this->plugin_info($name)) { return true; // Legacy plugins always inline } global $prefs; $inline = false; if (isset($meta['inline']) && $meta['inline']) { return true; } $inline_pref = 'wikiplugininline_' . $name; if (isset($prefs[ $inline_pref ]) && $prefs[ $inline_pref ] == 'y') { return true; } return false; } /** * Check if possible to execute a plugin * * @param string $name * @param string $data * @param array $args * @param bool $dont_modify * @return bool|string Boolean true if can execute, string 'rejected' if can't execute and plugin fingerprint if pending */ //* public function plugin_can_execute($name, $data = '', $args = [], $dont_modify = false) { global $prefs; // If validation is disabled, anything can execute if ($prefs['wiki_validate_plugin'] != 'y') { return true; } $meta = $this->plugin_info($name, $args); if (! isset($meta['validate'])) { return true; } $fingerprint = $this->plugin_fingerprint($name, $meta, $data, $args); if ($fingerprint === '') { // only args or body were being validated and they're empty or safe return true; } $val = $this->plugin_fingerprint_check($fingerprint, $dont_modify); if (strpos($val, 'accept') === 0) { return true; } elseif (strpos($val, 'reject') === 0) { return 'rejected'; } else { global $tiki_p_plugin_approve, $tiki_p_plugin_preview, $user; if ( isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['plugin_fingerprint']) && $_POST['plugin_fingerprint'] == $fingerprint ) { if ($tiki_p_plugin_approve == 'y') { if (isset($_POST['plugin_accept'])) { $tikilib = TikiLib::lib('tiki'); $this->plugin_fingerprint_store($fingerprint, 'accept'); $tikilib->invalidate_cache($this->option['page']); return true; } elseif (isset($_POST['plugin_reject'])) { $tikilib = TikiLib::lib('tiki'); $this->plugin_fingerprint_store($fingerprint, 'reject'); $tikilib->invalidate_cache($this->option['page']); return 'rejected'; } } if ( $tiki_p_plugin_preview == 'y' && isset($_POST['plugin_preview']) ) { return true; } } return $fingerprint; } } //* public function plugin_fingerprint_check($fp, $dont_modify = false) { global $user; $tikilib = TikiLib::lib('tiki'); $limit = date('Y-m-d H:i:s', time() - 15 * 24 * 3600); $result = $this->query("SELECT `status`, if (`status`='pending' AND `last_update` < ?, 'old', '') flag FROM `tiki_plugin_security` WHERE `fingerprint` = ?", [ $limit, $fp ]); $needUpdate = false; if ($row = $result->fetchRow()) { $status = $row['status']; $flag = $row['flag']; if ($status == 'accept' || $status == 'reject') { return $status; } if ($flag == 'old') { $needUpdate = true; } } else { $needUpdate = true; } if ($needUpdate && ! $dont_modify) { if ($this->option['page']) { $objectType = 'wiki page'; $objectId = $this->option['page']; } else { $objectType = ''; $objectId = ''; } if (! $user) { $user = tra('Anonymous'); } $pluginSecurity = $tikilib->table('tiki_plugin_security'); $pluginSecurity->delete(['fingerprint' => $fp]); $pluginSecurity->insert( ['fingerprint' => $fp, 'status' => 'pending', 'added_by' => $user, 'last_objectType' => $objectType, 'last_objectId' => $objectId] ); } return ''; } //* public function plugin_fingerprint_store($fp, $type) { global $prefs, $user; $tikilib = TikiLib::lib('tiki'); if ($this->option['page']) { $objectType = 'wiki page'; $objectId = $this->option['page']; } else { $objectType = ''; $objectId = ''; } $pluginSecurity = $tikilib->table('tiki_plugin_security'); $pluginSecurity->delete(['fingerprint' => $fp]); $pluginSecurity->insert( ['fingerprint' => $fp,'status' => $type,'added_by' => $user,'last_objectType' => $objectType,'last_objectId' => $objectId] ); } //* public function plugin_clear_fingerprint($fp) { $tikilib = TikiLib::lib('tiki'); $pluginSecurity = $tikilib->table('tiki_plugin_security'); $pluginSecurity->delete(['fingerprint' => $fp]); } //* public function list_plugins_pending_approval() { $tikilib = TikiLib::lib('tiki'); return $tikilib->fetchAll("SELECT `fingerprint`, `added_by`, `last_update`, `last_objectType`, `last_objectId` FROM `tiki_plugin_security` WHERE `status` = 'pending' ORDER BY `last_update` DESC"); } /** * Return a list of plugins by status * * @param string|array $statuses * @return array */ public function listPluginsByStatus($statuses) { if (! empty($statuses) && ! is_array($statuses)) { $statuses = [$statuses]; } $tikiLib = TikiLib::lib('tiki'); $pluginSecurity = $tikiLib->table('tiki_plugin_security'); return $pluginSecurity->fetchAll( ['fingerprint', 'added_by', 'last_update', 'last_objectType', 'last_objectId', 'status'], ['status' => $pluginSecurity->in($statuses)], -1, -1, ['last_update' => 'DESC'] ); } //* public function approve_all_pending_plugins() { global $user; $tikilib = TikiLib::lib('tiki'); $pluginSecurity = $tikilib->table('tiki_plugin_security'); $pluginSecurity->updateMultiple(['status' => 'accept', 'approval_by' => $user], ['status' => 'pending',]); } //* public function approve_selected_pending_plugings($fp) { global $user; $tikilib = TikiLib::lib('tiki'); $pluginSecurity = $tikilib->table('tiki_plugin_security'); $pluginSecurity->update(['status' => 'accept', 'approval_by' => $user], ['fingerprint' => $fp]); } //* public function plugin_fingerprint($name, $meta, $data, $args) { $validate = (isset($meta['validate']) ? $meta['validate'] : ''); $data = $this->unprotectSpecialChars($data, true); if ($validate == 'all' || $validate == 'body') { // Tiki 6 and ulterior may insert sequences in plugin body to break XSS exploits. The replacement works around removing them to keep fingerprints identical for upgrades from previous versions. $validateBody = str_replace('', '', $data); } else { $validateBody = ''; } if ($validate === 'body' && empty($validateBody)) { return ''; } if ($validate == 'all' || $validate == 'arguments') { $validateArgs = $args; // Remove arguments marked as safe from the fingerprint foreach ($meta['params'] as $key => $info) { if ( isset($validateArgs[$key]) && isset($info['safe']) && $info['safe'] ) { unset($validateArgs[$key]); } } // Parameter order needs to be stable ksort($validateArgs); if (empty($validateArgs)) { if ($validate === 'arguments') { return ''; } $validateArgs = [ '' => '' ]; // maintain compatibility with pre-Tiki 7 fingerprints } } else { $validateArgs = []; } $bodyLen = str_pad(strlen($validateBody), 6, '0', STR_PAD_RIGHT); $serialized = serialize($validateArgs); $argsLen = str_pad(strlen($serialized), 6, '0', STR_PAD_RIGHT); $bodyHash = md5($validateBody); $argsHash = md5($serialized); return "$name-$bodyHash-$argsHash-$bodyLen-$argsLen"; } // Transitional wrapper over WikiParser_Parsable::pluginExecute() public function pluginExecute($name, $data = '', $args = [], $offset = 0, $validationPerformed = false, $option = []) { return WikiParser_Parsable::instantiate('', $option)->pluginExecute($name, $data, $args, $offset, $validationPerformed, $option); } //* protected function convert_plugin_for_ckeditor($name, $args, $plugin_result, $data, $info = []) { $ck_editor_plugin = '{' . (empty($data) ? $name : TikiLib::strtoupper($name) . '(') . ' '; $arg_str = ''; // not using http_build_query() as it converts spaces into + if (! empty($args)) { foreach ($args as $argKey => $argValue) { if (is_array($argValue)) { if (isset($info['params'][$argKey]['separator'])) { $sep = $info['params'][$argKey]['separator']; } else { $sep = ','; } $ck_editor_plugin .= $argKey . '="' . implode($sep, $argValue) . '" '; // process array $arg_str .= $argKey . '=' . implode($sep, $argValue) . '&'; } else { // even though args are now decoded we still need to escape double quotes $argValue = addcslashes($argValue, '"'); $ck_editor_plugin .= $argKey . '="' . $argValue . '" '; $arg_str .= $argKey . '=' . $argValue . '&'; } } } if (substr($ck_editor_plugin, -1) === ' ') { $ck_editor_plugin = substr($ck_editor_plugin, 0, -1); } if (! empty($data)) { $ck_editor_plugin .= ')}' . $data . '{' . TikiLib::strtoupper($name) . '}'; } else { $ck_editor_plugin .= '}'; } // work out if I'm a nested plugin and return empty if so $stack = debug_backtrace(); $plugin_nest_level = 0; foreach ($stack as $st) { if ($st['function'] === 'parse_first') { $plugin_nest_level++; if ($plugin_nest_level > 1) { return ''; } } } $arg_str = rtrim($arg_str, '&'); $icon = isset($info['icon']) ? $info['icon'] : 'img/icons/wiki_plugin_edit.png'; // some plugins are just too fragile to do wysiwyg, so show the "source" for them ;( $excluded = ['tracker', 'trackerlist', 'trackerfilter', 'kaltura', 'toc', 'freetagged', 'draw', 'googlemap', 'include', 'module', 'list', 'custom_search', 'iframe', 'map', 'calendar', 'file', 'files', 'mouseover', 'sort', 'slideshow', 'convene', 'redirect', 'galleriffic', 'sign']; $ignore = null; $enabled = $this->plugin_enabled($name, $ignore); if (in_array($name, $excluded) || ! $enabled) { $plugin_result = '    ' . $ck_editor_plugin; } else { if (! isset($info['format']) || $info['format'] !== 'html') { $oldOptions = $this->option; $plugin_result = $this->parse_data($plugin_result, ['is_html' => false, 'suppress_icons' => true, 'ck_editor' => true, 'noparseplugins' => true]); $this->setOptions($oldOptions); // reset the noparseplugins option, to allow for proper display in CkEditor $this->option['noparseplugins'] = false; } else { $plugin_result = preg_replace('/~[\/]?np~/ms', '', $plugin_result); // remove no parse tags otherwise they get nested later (bad) } if (! getCookie('wysiwyg_inline_edit', 'preview', false)) { // remove hrefs and onclicks $plugin_result = preg_replace('/\shref\=/i', ' tiki_href=', $plugin_result); $plugin_result = preg_replace('/\sonclick\=/i', ' tiki_onclick=', $plugin_result); $plugin_result = preg_replace('//mi', '', $plugin_result); // remove hidden inputs $plugin_result = preg_replace('//mi', '', $plugin_result); } } if (! in_array($name, ['html'])) { // remove

and
s from non-html $data = str_replace(['

', '

', "\t"], '', $data); $data = str_replace('
', "\n", $data); } if ($this->contains_html_block($plugin_result)) { $elem = 'div'; } else { $elem = 'span'; } $elem_style = 'position:relative;display:inline-block;'; if (! $enabled) { $elem_style .= 'opacity:0.3;'; } if (in_array($name, ['img', 'div']) && preg_match('/<' . $name . '[^>]*style="(.*?)"/i', $plugin_result, $m)) { if (count($m)) { $elem_style .= $m[1]; } } $ret = '~np~<' . $elem . ' contenteditable="false" unselectable="on" class="tiki_plugin" data-plugin="' . $name . '" style="' . $elem_style . '"' . ' data-syntax="' . htmlentities($ck_editor_plugin, ENT_QUOTES, 'UTF-8') . '"' . ' data-args="' . htmlentities($arg_str, ENT_QUOTES, 'UTF-8') . '"' . ' data-body="' . htmlentities($data, ENT_QUOTES, 'UTF-8') . '">' . // not ~/np~'; return $ret; } public function find_plugins($data, $name = null) { $parserlib = TikiLib::lib('parser'); $argumentParser = new WikiParser_PluginArgumentParser(); $matches = WikiParser_PluginMatcher::match($data); $occurrences = []; foreach ($matches as $match) { $plugin = [ 'name' => $match->getName(), 'arguments' => $argumentParser->parse($match->getArguments()), 'body' => $match->getBody(), ]; $dummy_output = ''; if ($parserlib->plugin_enabled($plugin['name'], $dummy_output)) { if ($name === null || $plugin['name'] == $name) { $occurrences[] = $plugin; } } } return $occurrences; } public function process_save_plugins($data, array $context) { $parserlib = TikiLib::lib('parser'); $argumentParser = new WikiParser_PluginArgumentParser(); $matches = WikiParser_PluginMatcher::match($data); foreach ($matches as $match) { $plugin_name = $match->getName(); $body = $match->getBody(); $arguments = $argumentParser->parse($match->getArguments()); $dummy_output = ''; if ($parserlib->plugin_enabled($plugin_name, $dummy_output)) { $func_name = 'wikiplugin_' . $plugin_name . '_rewrite'; if (function_exists($func_name)) { $parserlib->plugin_apply_filters($plugin_name, $data, $arguments); $output = $func_name($body, $arguments, $context); if ($output !== false) { $match->replaceWith($output); } } if ($plugin_name == 'translationof') { $this->add_translationof_relation($data, $arguments, $context['itemId']); } } } $matches_text = $matches->getText(); return $matches_text; } //* protected function plugin_apply_filters($name, &$data, &$args) { $tikilib = TikiLib::lib('tiki'); $info = $this->plugin_info($name, $args); $default = TikiFilter::get(isset($info['defaultfilter']) ? $info['defaultfilter'] : 'xss'); // Apply filters on the body $filter = isset($info['filter']) ? TikiFilter::get($info['filter']) : $default; //$data = TikiLib::htmldecode($data); // jb 9.0 commented out in fix for html entitles $data = $filter->filter($data); if (isset($this->option) && (! empty($this->option['is_html']) && (! $this->option['is_html']))) { $noparsed = ['data' => [], 'key' => []]; $this->strip_unparsed_block($data, $noparsed); $data = str_replace(['<', '>'], ['<', '>'], $data); foreach ($noparsed['data'] as &$instance) { $instance = '~np~' . $instance . '~/np~'; } unset($instance); $data = str_replace($noparsed['key'], $noparsed['data'], $data); } // Make sure all arguments are declared if (isset($info['params'])) { $params = $info['params']; } $argsCopy = $args; if (! isset($info['extraparams']) && isset($params) && is_array($params)) { $args = array_intersect_key($args, $params); } // Apply filters on values individually if (! empty($args)) { foreach ($args as $argKey => &$argValue) { if (! isset($params[$argKey])) { continue;// extra params } $paramInfo = $params[$argKey]; $filter = isset($paramInfo['filter']) ? TikiFilter::get($paramInfo['filter']) : $default; $argValue = TikiLib::htmldecode($argValue); if (isset($paramInfo['separator'])) { $vals = []; $vals = $tikilib->array_apply_filter($tikilib->multi_explode($paramInfo['separator'], $argValue), $filter); $argValue = array_values($vals); } else { $argValue = $filter->filter($argValue); } } } } //* protected function convert_plugin_output($output, $from, $to) { if (! $output instanceof WikiParser_PluginOutput) { if ($from === 'wiki') { $output = WikiParser_PluginOutput::wiki($output); } elseif ($from === 'html') { $output = WikiParser_PluginOutput::html($output); } } if ($to === 'html') { return $output->toHtml($this->option); } elseif ($to === 'wiki') { return $output->toWiki(); } } //* public function plugin_replace_args($content, $rules, $args) { $patterns = []; $replacements = []; foreach ($rules as $token => $info) { $patterns[] = "%$token%"; if (isset($info['input']) && ! empty($info['input'])) { $token = $info['input']; } if (isset($args[$token])) { $value = $args[$token]; } else { $value = isset($info['default']) ? $info['default'] : ''; } switch (isset($info['encoding']) ? $info['encoding'] : 'none') { case 'html': $replacements[] = htmlentities($value, ENT_QUOTES, 'UTF-8'); break; case 'url': $replacements[] = rawurlencode($value); break; default: $replacements[] = $value; } } return str_replace($patterns, $replacements, $content); } //* public function plugin_is_editable($name, $pluginArgs = null) { global $tiki_p_edit, $prefs, $section; $info = $this->plugin_info($name); $is_allowed = $this->check_permission_from_plugin_params($pluginArgs); // note that for 3.0 the plugin editor only works in wiki pages, but could be extended later return $section == 'wiki page' && $info && ($tiki_p_edit == 'y' || $is_allowed == 'y') && $prefs['wiki_edit_plugin'] == 'y' && ! $this->plugin_is_inline($name); } // Checking permission from plugin params public function check_permission_from_plugin_params($pluginArgs) { global $user; $userlib = TikiLib::lib('user'); $is_allowed = 'n'; if (isset($pluginArgs)) { if (isset($pluginArgs['editable_by_user'])) { $usersAllowed = array_map('trim', explode(',', $pluginArgs['editable_by_user'])); foreach ($usersAllowed as $allowedUser) { if (strtolower($user) == strtolower($allowedUser)) { $is_allowed = 'y'; return $is_allowed; } } } if (isset($pluginArgs['editable_by_groups'])) { $userGroups = array_map('strtolower',$userlib->get_user_info($user)['groups']); $groupsAllowed = array_map('strtolower', explode(',',$pluginArgs['editable_by_groups'])); foreach ($groupsAllowed as $group) { if (in_array($group, $userGroups)) { $is_allowed = 'y'; return $is_allowed; } } } } return $is_allowed; } /** * Gets a wiki parseable content and substitutes links for $oldName by * links for $newName. * * @param string wiki parseable content * @param string old page name * @param string new page name * @return string new wiki parseable content with links replaced */ public function replace_links($data, $oldName, $newName) { global $prefs; $quotedOldName = preg_quote($oldName, '/'); $semanticlib = TikiLib::lib('semantic'); // FIXME: Affects non-parsed sections foreach ($semanticlib->getAllTokens() as $sem) { $data = str_replace("($sem($oldName", "($sem($newName", $data); } if ($prefs['feature_wikiwords'] == 'y') { if (strstr($newName, ' ')) { $data = preg_replace("/(?<= |\n|\t|\r|\,|\;|^)$quotedOldName(?= |\n|\t|\r|\,|\;|$)/", '((' . $newName . '))', $data); } else { $data = preg_replace("/(?<= |\n|\t|\r|\,|\;|^)$quotedOldName(?= |\n|\t|\r|\,|\;|$)/", $newName, $data); } } $data = preg_replace("/(?<=\(\()$quotedOldName(?=\)\)|\|)/i", $newName, $data); $data = preg_replace("/(?<=\]\()$quotedOldName(?=\))/i", $newName, $data); $quotedOldHtmlName = preg_quote(urlencode($oldName), '/'); $htmlSearch = '//i'; $data = preg_replace($htmlWantedSearch, '((' . $newName . '))', $data); return $data; } // Replace hotwords in given line //* public function replace_hotwords($line, $words = null) { global $prefs; if ($prefs['feature_hotwords'] == 'y') { $hotw_nw = ($prefs['feature_hotwords_nw'] == 'y') ? "target='_blank'" : ''; // FIXME: Replacements may fail if the value contains an unescaped metacharacters (which is why the default value contains escape characters). The value should probably be escaped with preg_quote(). $sep = empty($prefs['feature_hotwords_sep']) ? " \n\t\r\,\;\(\)\.\:\[\]\{\}\!\?\"" : $prefs['feature_hotwords_sep']; if (is_null($words)) { $words = $this->get_hotwords(); } foreach ($words as $word => $url) { $escapedWord = preg_quote($word, '/'); /* In CVS revisions 1.429 and 1.373.2.40 of tikilib.php, mose added the following magic steps, commenting "fixed the hotwords autolinking in case it is in a description field". * I do not understand what description fields this refers to. I do not know if this is correct and still needed. Step 1 seems to prevent replacements in an HTML tag. Chealer 2017-03-08 */ // Step 1: Insert magic sequences which will neutralize step 2 in some cases. $line = preg_replace("/(=(\"|')[^\"']*[$sep'])$escapedWord([$sep][^\"']*(\"|'))/i", "$1:::::$word,:::::$3", $line); // Step 2: Add links where the hotword appears (not neutralized) $line = preg_replace("/([$sep']|^)$escapedWord($|[$sep])/i", "$1$word$2", $line); // Step 3: Remove magic sequences inserted in step 1 $line = preg_replace("/:::::$escapedWord,:::::/i", "$word", $line); } } return $line; } // Make plain text URIs in text into clickable hyperlinks // check to see if autolinks is enabled before calling this function ($prefs['feature_autolinks'] == "y") //* public function autolinks($text) { if ($text) { global $prefs; static $mail_protect_pattern = ''; $attrib = ''; if ($prefs['popupLinks'] == 'y') { $attrib .= 'target="_blank" '; } if ($prefs['feature_wiki_ext_icon'] == 'y') { $attrib .= 'class="wiki external" '; include_once(__DIR__ . '/../smarty_tiki/function.icon.php'); $ext_icon = smarty_function_icon(['name' => 'link-external'], TikiLib::lib('smarty')->getEmptyInternalTemplate()); } else { $attrib .= 'class="wiki" '; $ext_icon = ""; } // add a space so we can match links starting at the beginning of the first line $text = " " . $text; $patterns = []; $replacements = []; // protocol://suffix $patterns[] = "#([\n\( ])([a-z0-9]+?)://([^<,\) \n\r]+)#i"; $replacements[] = "\\1\\2://\\3$ext_icon"; // www.domain.ext/optionalpath $patterns[] = "#([\n ])www\.([a-z0-9\-]+)\.([a-z0-9\-.\~]+)((?:/[^,< \n\r]*)?)#i"; $replacements[] = "\\1www.\\2.\\3\\4$ext_icon"; // email address (foo@domain.ext) $patterns[] = "#([\n ])([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i"; if ($this->option['protect_email'] && $this->option['print'] !== 'y' && $prefs['feature_wiki_protect_email'] == 'y') { if (! $mail_protect_pattern) { $mail_protect_pattern = "\\1" . TikiLib::protect_email("\\2", "\\3"); } $replacements[] = $mail_protect_pattern; } else { $replacements[] = "\\1\\2@\\3"; } $patterns[] = "#([\n ])magnet\:\?([^,< \n\r]+)#i"; $replacements[] = "\\1magnet:?\\2"; $text = preg_replace($patterns, $replacements, $text); // strip the space we added $text = substr($text, 1); } return $text; // } else { // return $text; // } } /** * close_blocks - Close out open paragraph, lists, and div's * * During parse_data, information is kept on blocks of text (paragraphs, lists, divs) * that need to be closed out. This function does that, rather than duplicating the * code inline. * * @param $data - Output data * @param $in_paragraph - TRUE if there is an open paragraph * @param $listbeg - array of open list terminators * @param $divdepth - array indicating how many div's are open * @param $close_paragraph - TRUE if open paragraph should be closed. * @param $close_lists - TRUE if open lists should be closed. * @param $close_divs - TRUE if open div's should be closed. */ /* private */ //* public function close_blocks(&$data, &$in_paragraph, &$listbeg, &$divdepth, $close_paragraph, $close_lists, $close_divs) { $closed = 0; // Set to non-zero if something has been closed out // Close the paragraph if inside one. if ($close_paragraph && $in_paragraph) { $data .= "

\n"; $in_paragraph = 0; $closed++; } // Close open lists if ($close_lists) { while (count($listbeg)) { $data .= array_shift($listbeg); $closed++; } } // Close open divs if ($close_divs) { $temp_max = count($divdepth); for ($i = 1; $i <= $temp_max; $i++) { $data .= ''; $closed++; } } return $closed; } // Transitional wrapper over WikiParser_Parsable::parse() //PARSEDATA // options defaults : is_html => false, absolute_links => false, language => '' //* public function parse_data($data, $option = []) { return WikiParser_Parsable::instantiate($data, $option)->parse($option); } /** * Used as preg_replace_callback methods within in the parse_data() method to escape color style attributes when * generating HTML for the wiki color syntax - e.g. ~~#909:text~~ * * @param $matches * @return string */ protected function colorAttrEscape($matches) { $matches[1] = trim($matches[1]); $matches[3] = trim($matches[3]); $esc = new Laminas\Escaper\Escaper(); $color = ! empty($matches[1]) ? 'color:' . str_replace('#', '#', $esc->escapeHtmlAttr($matches[1])) : ''; $background = ! empty($matches[3]) ? 'background-color:' . str_replace('#', '#', $esc->escapeHtmlAttr($matches[3])) : ''; $semi = ! empty($color) && ! empty($background) ? '; ' : ''; $text = ! empty($matches[4]) ? $matches[4] : ''; return '' . $text . ''; } /** * * intended for use within wiki plugins. This option preserves opening and closing whitespace that renders into * annoying

tags when parsed, and also respects HTML rendering preferences. * * @param $data string wiki/html to be parsed * @param $optionsOverride array options to override the current defaults * @param $inlineFirstP boolean If the returned data starts with a

, this option will force it to display as inline:block * useful when the returned data is required to display without adding overhead spacing caused by

* * @return string parsed data */ public function parse_data_plugin($data, $inlineFirstP = false, $optionsOverride = []) { $options['is_html'] = ($GLOBALS['prefs']['feature_wiki_allowhtml'] === 'y' && isset($GLOBALS['info']['is_html']) && $GLOBALS['info']['is_html'] == true) ? true : false; foreach ($optionsOverride as $name => $value) { $options[$name] = $value; } // record initial whitespace preg_match('(^\s*)', $data, $bwhite); preg_match('(\s*$)', $data, $ewhite); // remove all the whitespace $data = trim($data); $data = $this->parse_data($data, $options); // remove whitespace that was added while parsing (yes it does happen) $data = trim($data); if ($inlineFirstP) { $data = preg_replace('/^(\s*?)

/', '$1

', ' ' . $data); } // add original whitespace back to preserve spacing return ($bwhite[0] . $data . $ewhite[0]); } /** Simpler and faster parse than parse_data() * This is only called from the parse Smarty modifier, for preference definitions. */ public function parse_data_simple($data) { $data = $this->parse_data_wikilinks($data, true); $data = $this->parse_data_externallinks($data, true); $data = $this->parse_data_inline_syntax($data); if ($this->option['typography'] && ! $this->option['ck_editor']) { $data = typography($data, $this->option['language']); } return $data; } //* protected function parse_data_wikilinks($data, $simple_wiki, $ck_editor = false) //TODO: need a wikilink handler { global $page_regex, $prefs; // definitively put out the protected words ))protectedWord(( if ($prefs['feature_wikiwords'] == 'y') { preg_match_all("/\)\)(\S+?)\(\(/", $data, $matches); $noParseWikiLinksK = []; $noParseWikiLinksT = []; foreach ($matches[0] as $mi => $match) { do { $randNum = chr(0xff) . rand(0, 1048576) . chr(0xff); } while (strstr($data, $randNum)); $data = str_replace($match, $randNum, $data); $noParseWikiLinksK[] = $randNum; $noParseWikiLinksT[] = $matches[1][$mi]; } } // Links with description preg_match_all("/\(([a-z0-9-]+)?\(($page_regex)\|([^\)]*?)\)\)/", $data, $pages); $temp_max = count($pages[1]); for ($i = 0; $i < $temp_max; $i++) { $exactMatch = $pages[0][$i]; $description = $pages[6][$i]; $anchor = null; if ($description && $description[0] == '#') { $temp = $description; $anchor = strtok($temp, '|'); $description = strtok('|'); } $replacement = $this->get_wiki_link_replacement($pages[2][$i], ['description' => $description,'reltype' => $pages[1][$i],'anchor' => $anchor], $ck_editor); $data = str_replace($exactMatch, $replacement, $data); } // Wiki page syntax without description preg_match_all("/\(([a-z0-9-]+)?\( *($page_regex) *\)\)/", $data, $pages); foreach ($pages[2] as $idx => $page_parse) { $exactMatch = $pages[0][$idx]; $replacement = $this->get_wiki_link_replacement($page_parse, [ 'reltype' => $pages[1][$idx] ], $ck_editor); $data = str_replace($exactMatch, $replacement, $data); } // Links to internal pages // If they are parenthesized then don't treat as links // Prevent ))PageName(( from being expanded \"\' //[A-Z][a-z0-9_\-]+[A-Z][a-z0-9_\-]+[A-Za-z0-9\-_]* if ($prefs['feature_wiki'] == 'y' && $prefs['feature_wikiwords'] == 'y') { if (! $simple_wiki) { // The first part is now mandatory to prevent [Foo|MyPage] from being converted! if ($prefs['feature_wikiwords_usedash'] == 'y') { preg_match_all("/(?<=[ \n\t\r\,\;]|^)([A-Z][a-z0-9_\-\x80-\xFF]+[A-Z][a-z0-9_\-\x80-\xFF]+[A-Za-z0-9\-_\x80-\xFF]*)(?=$|[ \n\t\r\,\;\.])/", $data, $pages); } else { preg_match_all("/(?<=[ \n\t\r\,\;]|^)([A-Z][a-z0-9\x80-\xFF]+[A-Z][a-z0-9\x80-\xFF]+[A-Za-z0-9\x80-\xFF]*)(?=$|[ \n\t\r\,\;\.])/", $data, $pages); } //TODO to have a real utf8 Wikiword where the capitals can be a utf8 capital $words = ($prefs['feature_hotwords'] == 'y') ? $this->get_hotwords() : []; foreach (array_unique($pages[1]) as $page_parse) { if (! array_key_exists($page_parse, $words)) { // If this is not a hotword $repl = $this->get_wiki_link_replacement($page_parse, ['plural' => $prefs['feature_wiki_plurals'] == 'y'], $ck_editor); $data = preg_replace("/(?<=[ \n\t\r\,\;]|^)$page_parse(?=$|[ \n\t\r\,\;\.])/", "$1" . $repl . "$2", $data); } } } // Reinsert ))Words(( $data = str_replace($noParseWikiLinksK, $noParseWikiLinksT, $data); } return $data; } protected function parse_data_externallinks($data, $suppress_icons = false) { global $prefs; $tikilib = TikiLib::lib('tiki'); // ***** // This section handles external links of the form [url] and such. // ***** $links = $tikilib->get_links($data); $notcachedlinks = $tikilib->get_links_nocache($data); $cachedlinks = array_diff($links, $notcachedlinks); $tikilib->cache_links($cachedlinks); // Note that there're links that are replaced foreach ($links as $link) { $target = ''; $class = 'class="wiki"'; $ext_icon = ''; $rel = ''; if ($prefs['popupLinks'] == 'y') { $target = 'target="_blank"'; } if (! strstr($link, '://')) { $target = ''; } else { $class = 'class="wiki external"'; if ($prefs['feature_wiki_ext_icon'] == 'y' && ! ($this->option['suppress_icons'] || $suppress_icons)) { $smarty = TikiLib::lib('smarty'); include_once('lib/smarty_tiki/function.icon.php'); $ext_icon = smarty_function_icon(['name' => 'link-external'], $smarty->getEmptyInternalTemplate()); } $rel = 'external'; if ($prefs['feature_wiki_ext_rel_nofollow'] == 'y') { $rel .= ' nofollow'; } } // The (?is_cached($link)) { //use of urlencode for using cached versions of dynamic sites $cosa = "(cache)"; $link2 = str_replace("/", "\/", preg_quote($link)); $pattern = "/(?$1$ext_icon", $data); $pattern = "/(?$1$ext_icon", $data); } else { $data = preg_replace($pattern, "$1$ext_icon $cosa", $data); } $pattern = "/(?$1$ext_icon $cosa", $data); $pattern = "/(?$link$ext_icon $cosa", $data); } else { $link2 = str_replace("/", "\/", preg_quote($link)); $link = trim($link); $link = str_replace('"', '%22', $link); $data = str_replace("|nocache", "", $data); $pattern = "/(?{$matches[1]}$ext_icon"; }, $data); $pattern = "/(?$1$ext_icon", $data); $pattern = "/(?$link$ext_icon", $data); } } // Handle double square brackets. to display [foo] use [[foo] -rlpowell. Improved by sylvieg to avoid replacing them in [[code]] cases. if (empty($this->option['process_double_brackets']) || $this->option['process_double_brackets'] != 'n') { $data = preg_replace("/\[\[([^\]]*)\](?!\])/", "[$1]", $data); $data = preg_replace("/\[\[([^\]]*)$/", "[$1", $data); } return $data; } //* protected function parse_data_inline_syntax($line, $words = null, $ck_editor = false) { global $prefs; // Replace monospaced text $line = preg_replace("/(^|\s)-\+(.*?)\+-/", "$1$2", $line); // Replace bold text $line = preg_replace("/__(.*?)__/", "$1", $line); // Replace italic text $line = preg_replace("/\'\'(.*?)\'\'/", "$1", $line); // Links that were ignored by convertAbsoluteLinksToRelative (autolinks will highlight) $line = preg_replace("/(^|\s)==(.*?)==(\s|$)/", "$1$2$3", $line); if (! $ck_editor) { if ($prefs['feature_hotwords'] == 'y') { // Replace Hotwords before begin $line = $this->replace_hotwords($line, $words); } // Make plain URLs clickable hyperlinks if ($prefs['feature_autolinks'] == 'y') { $line = $this->autolinks($line); } } if (! $ck_editor) { // Replace definition lists $line = preg_replace("/^;([^:]*):([^\/\/].*)/", "

$1
$2
", $line); $line = preg_replace("/^;():([^\/\/].*)/", "
$1
$2
", $line); } return $line; } //* protected function parse_data_tables($data) { global $prefs; // pretty trackers use pipe for output|template specification, so we need to escape $data = preg_replace('/{\$f_(\w+)\|(output|template:.*?)}/i', '{\$f_$1-escapedpipe-$2}', $data); /* * Wiki Tables syntax */ // tables in old style if ($prefs['feature_wiki_tables'] != 'new') { if (preg_match_all("/\|\|(.*)\|\|/", $data, $tables)) { $maxcols = 1; $cols = []; $temp_max = count($tables[0]); for ($i = 0; $i < $temp_max; $i++) { $rows = explode('||', $tables[0][$i]); $temp_max2 = count($rows); for ($j = 0; $j < $temp_max2; $j++) { $cols[$i][$j] = explode('|', $rows[$j]); if (count($cols[$i][$j]) > $maxcols) { $maxcols = count($cols[$i][$j]); } } } // for ($i ... $temp_max3 = count($tables[0]); for ($i = 0; $i < $temp_max3; $i++) { $repl = ''; $temp_max4 = count($cols[$i]); for ($j = 0; $j < $temp_max4; $j++) { $ncols = count($cols[$i][$j]); if ($ncols == 1 && ! $cols[$i][$j][0]) { continue; } $repl .= ''; for ($k = 0; $k < $ncols; $k++) { $repl .= ''; } // for ($j ... $repl .= '
'; } // // for ($k ... $repl .= '
'; $data = str_replace($tables[0][$i], $repl, $data); } // for ($i ... } // if (preg_match_all("/\|\|(.*)\|\|/", $data, $tables)) } else { // New syntax for tables // REWRITE THIS CODE if (preg_match_all("/\|\|(.*?)\|\|/s", $data, $tables)) { $maxcols = 1; $cols = []; $temp_max5 = count($tables[0]); for ($i = 0; $i < $temp_max5; $i++) { $rows = preg_split("/(\n|\)/", $tables[0][$i]); $col[$i] = []; $temp_max6 = count($rows); for ($j = 0; $j < $temp_max6; $j++) { $rows[$j] = str_replace('||', '', $rows[$j]); $cols[$i][$j] = explode('|', $rows[$j]); if (count($cols[$i][$j]) > $maxcols) { $maxcols = count($cols[$i][$j]); } } } $temp_max7 = count($tables[0]); for ($i = 0; $i < $temp_max7; $i++) { $repl = ''; $temp_max8 = count($cols[$i]); for ($j = 0; $j < $temp_max8; $j++) { $ncols = count($cols[$i][$j]); if ($ncols == 1 && ! $cols[$i][$j][0]) { continue; } $repl .= ''; for ($k = 0; $k < $ncols; $k++) { $repl .= ''; } $repl .= '
'; } $repl .= '
'; $data = str_replace($tables[0][$i], $repl, $data); } } } // unescape the pipes for pretty tracker $data = preg_replace('/{\$f_(\w+)-escapedpipe-(output|template:.*?)}/i', '{\$f_$1|$2}', $data); return $data; } //* public function parse_wiki_argvariable(&$data) { global $prefs, $user; $tikilib = TikiLib::lib('tiki'); $smarty = TikiLib::lib('smarty'); if ($prefs['feature_wiki_argvariable'] == 'y' && ! $this->option['ck_editor']) { if (preg_match_all("/\\{\\{((\w+)(\\|([^\\}]*))?)\\}\\}/", $data, $args, PREG_SET_ORDER)) { $needles = []; $replacements = []; foreach ($args as $arg) { $value = isset($arg[4]) ? $arg[4] : ''; $name = $arg[2]; switch ($name) { case 'user': $value = $user; break; case 'page': $value = $this->option['page']; break; case 'pageid': if ($_REQUEST['page'] != null) { $value = $tikilib->get_page_id_from_name($_REQUEST['page']); break; } else { $value = ''; break; } case 'page_no_namespace': $page = $this->option['page']; $lastNamespaceSeparatorIndex = strrpos($page, $prefs['namespace_separator']); if ($lastNamespaceSeparatorIndex == false) { // There are no namespaces $value = $page; break; } else { // The namespace is cutted out $value = substr($page, ($lastNamespaceSeparatorIndex + strlen($prefs['namespace_separator']))); break; } case 'domain': if ($smarty->getTemplateVars('url_host') != null) { $value = $smarty->getTemplateVars('url_host'); break; } else { $value = ''; break; } case 'domainslash': if ($smarty->getTemplateVars('url_host') != null) { $value = $smarty->getTemplateVars('url_host') . '/'; break; } else { $value = ''; break; } case 'domainslash_if_multitiki': if (is_file('db/virtuals.inc')) { $virtuals = array_map('trim', file('db/virtuals.inc')); } if ($virtuals && $smarty->getTemplateVars('url_host') != null) { $value = $smarty->getTemplateVars('url_host') . '/'; break; } else { $value = ''; break; } case 'lastVersion': $histlib = TikiLib::lib('hist'); // get_page_history arguments: page name, page contents (set to "false" to save memory), history_offset (none, therefore "0"), max. records (just one for this case); $history = $histlib->get_page_history($this->option['page'], false, 0, 1); if ($history[0]['version'] != null) { $value = $history[0]['version']; break; } else { $value = ''; break; } case 'lastAuthor': $histlib = TikiLib::lib('hist'); // get_page_history arguments: page name, page contents (set to "false" to save memory), history_offset (none, therefore "0"), max. records (just one for this case); $history = $histlib->get_page_history($this->option['page'], false, 0, 1); if ($history[0]['user'] != null) { if ($prefs['user_show_realnames'] == 'y') { $value = TikiLib::lib('user')->clean_user($history[0]['user']); break; } else { $value = $history[0]['user']; break; } } else { $value = ''; break; } case 'lastModif': $histlib = TikiLib::lib('hist'); // get_page_history arguments: page name, page contents (set to "false" to save memory), history_offset (none, therefore "0"), max. records (just one for this case); $history = $histlib->get_page_history($this->option['page'], false, 0, 1); if (! empty($history[0]['lastModif'])) { $value = $tikilib->get_short_datetime($history[0]['lastModif']); break; } else { $value = ''; break; } case 'lastItemVersion': $trklib = TikiLib::lib('trk'); $auto_query_args = ['itemId']; if (! empty($_REQUEST['itemId'])) { $item_info = $trklib->get_item_info($_REQUEST['itemId']); $itemObject = Tracker_Item::fromInfo($item_info); if (! $itemObject->canView()) { $smarty->assign('errortype', 401); $smarty->assign('msg', tra('You do not have permission to view this information from this tracker.')); $smarty->display('error.tpl'); die; } $fieldId = empty($_REQUEST['fieldId']) ? 0 : $_REQUEST['fieldId']; $filter = []; if (! empty($_REQUEST['version'])) { $filter['version'] = $_REQUEST['version']; } $offset = empty($_REQUEST['offset']) ? 0 : $_REQUEST['offset']; $history = $trklib->get_item_history($item_info, $fieldId, $filter, $offset, $prefs['maxRecords']); $value = $history['data'][0]['version']; break; } else { $value = ''; break; } case 'lastItemAuthor': $trklib = TikiLib::lib('trk'); $auto_query_args = ['itemId']; if (! empty($_REQUEST['itemId'])) { $item_info = $trklib->get_item_info($_REQUEST['itemId']); $itemObject = Tracker_Item::fromInfo($item_info); if (! $itemObject->canView()) { $smarty->assign('errortype', 401); $smarty->assign('msg', tra('You do not have permission to view this information from this tracker.')); $smarty->display('error.tpl'); die; } if ($item_info['lastModifBy'] != null) { if ($prefs['user_show_realnames'] == 'y') { $value = TikiLib::lib('user')->clean_user($item_info['lastModifBy']); break; } else { $value = $item_info['lastModifBy']; break; } } break; } else { $value = ''; break; } case 'lastItemModif': $trklib = TikiLib::lib('trk'); $auto_query_args = ['itemId']; if (! empty($_REQUEST['itemId'])) { $item_info = $trklib->get_item_info($_REQUEST['itemId']); $itemObject = Tracker_Item::fromInfo($item_info); if (! $itemObject->canView()) { $smarty->assign('errortype', 401); $smarty->assign('msg', tra('You do not have permission to view this information from this tracker.')); $smarty->display('error.tpl'); die; } $value = $tikilib->get_short_datetime($item_info['lastModif']); break; } else { $value = ''; break; } case 'lastApprover': global $prefs, $user; $tikilib = TikiLib::lib('tiki'); if ($prefs['flaggedrev_approval'] == 'y') { $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { if ($version_info = $flaggedrevisionlib->get_version_with($this->option['page'], 'moderation', 'OK')) { if ($this->content_to_render === null) { $revision_displayed = $version_info['version']; $approval = $flaggedrevisionlib->find_approval_information($this->option['page'], $revision_displayed); } } } } if (! empty($approval['user'])) { if ($prefs['user_show_realnames'] == 'y') { $value = TikiLib::lib('user')->clean_user($approval['user']); break; } else { $value = $approval['user']; break; } } else { $value = ''; break; } case 'lastApproval': global $prefs, $user; $tikilib = TikiLib::lib('tiki'); if ($prefs['flaggedrev_approval'] == 'y') { $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { if ($version_info = $flaggedrevisionlib->get_version_with($this->option['page'], 'moderation', 'OK')) { if ($this->content_to_render === null) { $revision_displayed = $version_info['version']; $approval = $flaggedrevisionlib->find_approval_information($this->option['page'], $revision_displayed); } } } } if ($approval['lastModif'] != null) { $value = $tikilib->get_short_datetime($approval['lastModif']); break; } else { $value = ''; break; } case 'lastApprovedVersion': global $prefs, $user; $tikilib = TikiLib::lib('tiki'); if ($prefs['flaggedrev_approval'] == 'y') { $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { $version_info = $flaggedrevisionlib->get_version_with($this->option['page'], 'moderation', 'OK'); } } if ($version_info['version'] != null) { $value = $version_info['version']; break; } else { $value = ''; break; } case 'currentVersion': if (isset($_REQUEST['preview'])) { $value = (int)$_REQUEST["preview"]; break; } elseif (isset($_REQUEST['version'])) { $value = (int)$_REQUEST["version"]; break; } elseif ($prefs['flaggedrev_approval'] == 'y' && ! isset($_REQUEST['latest'])) { $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { $version_info = $flaggedrevisionlib->get_version_with($this->option['page'], 'moderation', 'OK'); } if ($version_info['version'] != null) { $value = $version_info['version']; break; } } else { $histlib = TikiLib::lib('hist'); // get_page_history arguments: page name, page contents (set to "false" to save memory), history_offset (none, therefore "0"), max. records (just one for this case); $history = $histlib->get_page_history($this->option['page'], false, 0, 1); if ($history[0]['version'] != null) { $value = $history[0]['version']; break; } else { $value = ''; break; } } case 'currentVersionApprover': global $prefs, $user; $tikilib = TikiLib::lib('tiki'); if ($prefs['flaggedrev_approval'] == 'y') { $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { if ($versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK')) { if (isset($_REQUEST['preview'])) { $revision_displayed = (int)$_REQUEST["preview"]; } elseif (isset($_REQUEST['version'])) { $revision_displayed = (int)$_REQUEST["version"]; } elseif (isset($_REQUEST['latest'])) { $revision_displayed = null; } else { $versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK'); $revision_displayed = $versions_info[0]; } if ($this->content_to_render === null) { $approval = $flaggedrevisionlib->find_approval_information($this->option['page'], $revision_displayed); } } } } if (! empty($approval['user'])) { if ($prefs['user_show_realnames'] == 'y') { $value = TikiLib::lib('user')->clean_user($approval['user']); break; } else { $value = $approval['user']; break; } } else { $value = ''; break; } case 'currentVersionApproval': global $prefs, $user; $tikilib = TikiLib::lib('tiki'); if ($prefs['flaggedrev_approval'] == 'y') { $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { if ($versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK')) { if (isset($_REQUEST['preview'])) { $revision_displayed = (int)$_REQUEST["preview"]; } elseif (isset($_REQUEST['version'])) { $revision_displayed = (int)$_REQUEST["version"]; } elseif (isset($_REQUEST['latest'])) { $revision_displayed = null; } else { $versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK'); $revision_displayed = $versions_info[0]; } if ($this->content_to_render === null) { $approval = $flaggedrevisionlib->find_approval_information($this->option['page'], $revision_displayed); } } } } if ($approval['lastModif'] != null) { $value = $tikilib->get_short_datetime($approval['lastModif']); break; } else { $value = ''; break; } case 'currentVersionApproved': global $prefs, $user; $tikilib = TikiLib::lib('tiki'); if ($prefs['flaggedrev_approval'] == 'y') { $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { //$versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK'); if (isset($_REQUEST['preview'])) { $revision_displayed = (int)$_REQUEST["preview"]; } elseif (isset($_REQUEST['version'])) { $revision_displayed = (int)$_REQUEST["version"]; } elseif (isset($_REQUEST['latest'])) { $revision_displayed = null; } else { $versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK'); $revision_displayed = $versions_info[0]; } } } if ($revision_displayed != null && $approval = $flaggedrevisionlib->find_approval_information($this->option['page'], $revision_displayed)) { $value = tr("yes"); break; } else { $value = tr("no"); break; } case 'cat': if (empty($_GET['cat']) && ! empty($_REQUEST['organicgroup']) && ! empty($this->option['page'])) { $utilities = new \Tiki\Package\Extension\Utilities(); if ($folder = $utilities->getFolderFromObject('wiki page', $this->option['page'])) { $ogname = $folder . '_' . $_REQUEST['organicgroup']; $cat = TikiLib::lib('categ')->get_category_id($ogname); $value = $cat; } else { $value = ''; } } elseif (! empty($_GET['cat'])) { $value = $_GET['cat']; } else { $value = ''; } break; default: if (isset($_GET[$name])) { $value = $_GET[$name]; } else { $value = ''; include_once('lib/wiki-plugins/wikiplugin_showpref.php'); if ($prefs['wikiplugin_showpref'] == 'y' && $showpref = wikiplugin_showpref('', ['pref' => $name])) { $value = $showpref; } } break; } $needles[] = $arg[0]; $replacements[] = $value; } $data = str_replace($needles, $replacements, $data); } } } //* protected function parse_data_dynamic_variables($data, $lang = null) { global $tiki_p_edit_dynvar, $prefs; $enclose = '%'; if ($prefs['wiki_dynvar_style'] == 'disable') { return $data; } elseif ($prefs['wiki_dynvar_style'] == 'double') { $enclose = '%%'; } // Replace dynamic variables // Dynamic variables are similar to dynamic content but they are editable // from the page directly, intended for short data, not long text but text // will work too // Now won't match HTML-style '%nn' letter codes and some special utf8 situations... if (preg_match_all("/[^%]$enclose([^% 0-9A-Z][^% 0-9A-Z][^% ]*){$enclose}[^%]/", $data, $dvars)) { // remove repeated elements $dvars = array_unique($dvars[1]); // Now replace each dynamic variable by a pair composed of the // variable value and a text field to edit the variable. Each foreach ($dvars as $dvar) { $value = $this->get_dynamic_variable($dvar, $lang); //replace backslash with html entity to avoid losing backslashes in the preg_replace function below $value = str_replace('\\', '\', $value); // Now build 2 divs $id = 'dyn_' . $dvar; if (isset($tiki_p_edit_dynvar) && $tiki_p_edit_dynvar == 'y') { $span1 = "
$value"; $span2 = "'; } else { $span1 = "$value"; $span2 = ''; } $html = $span1 . $span2; //It's important to replace only once $dvar_preg = preg_quote($dvar); $data = preg_replace("+$enclose$dvar_preg$enclose+", $html, $data, 1); //Further replacements only with the value $data = str_replace("$enclose$dvar$enclose", $value, $data); } } return $data; } //* private function get_dynamic_variable($name, $lang = null) { $tikilib = TikiLib::lib('tiki'); $result = $tikilib->table('tiki_dynamic_variables')->fetchAll(['data', 'lang'], ['name' => $name]); $value = tr('No value assigned'); foreach ($result as $row) { if ($row['lang'] == $lang) { // Exact match return $row['data']; } elseif (empty($row['lang'])) { // Universal match, keep in case no exact match $value = $row['data']; } } return $value; } /* This is only called by parse_data(). It does not just deal with TOC-s. */ protected function parse_data_process_maketoc(&$data, $noparsed) { global $prefs; $tikilib = TikiLib::lib('tiki'); // $this->makeTocCount++; Unused since Tiki 12 or earlier if ($this->option['ck_editor']) { $need_maketoc = false ; } else { $need_maketoc = preg_match('/\{maketoc.*\}/', $data); } // Wysiwyg or allowhtml mode {maketoc} handling when not in editor mode (i.e. viewing) if ($need_maketoc && $this->option['is_html']) { // Header needs to start at beginning of line (wysiwyg does not necessary obey) $data = $this->unprotectSpecialChars($data, true); $data = preg_replace('/<\/([a-z]+)>/im', "\n", $data); $data = preg_replace('/^\s+/im', "", $data); // headings with leading spaces $data = preg_replace('/\/>/im', "/>\n", $data); // headings after /> tag $htmlheadersearch = '/\s*([^<]+)\s*<\/h[1-6]>/im'; preg_match_all($htmlheadersearch, $data, $htmlheaders); $nbhh = count($htmlheaders[1]); for ($i = 0; $i < $nbhh; $i++) { $htmlheaderreplace = ''; for ($j = 0; $j < $htmlheaders[1][$i]; $j++) { $htmlheaderreplace .= '!'; } $htmlheaderreplace .= $htmlheaders[2][$i]; $data = str_replace($htmlheaders[0][$i], $htmlheaderreplace, $data); } $data = $this->protectSpecialChars($data, true); } $need_autonumbering = ( preg_match('/^\!+[\-\+]?#/m', $data) > 0 ); $anch = []; global $anch; $pageNum = 1; // Now tokenize the expression and process the tokens // Use tab and newline as tokenizing characters as well //// $lines = explode("\n", $data); if (empty($lines[count($lines) - 1]) && empty($lines[count($lines) - 2])) { array_pop($lines); } $data = ''; $listbeg = []; $divdepth = []; $hdr_structure = []; $show_title_level = []; $last_hdr = []; $nb_last_hdr = 0; $nb_hdrs = 0; $inTable = 0; $inPre = 0; $inComment = 0; $inTOC = 0; $inScript = 0; $title_text = ''; // loop: process all lines $in_paragraph = 0; $in_empty_paragraph = 0; foreach ($lines as $line) { // Add newlines between lines if (isset($current_title_num)) { // Exclude the first line $data .= "\n"; } $current_title_num = ''; $numbering_remove = 0; $line = rtrim($line); // Trim off trailing white space // Check for titlebars... // NOTE: that title bar should start at the beginning of the line and // be alone on that line to be autoaligned... otherwise, it is an old // styled title bar... if (substr(ltrim($line), 0, 2) == '-=' && substr($line, -2, 2) == '=-') { // Close open paragraph and lists, but not div's $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 1, 0); // $align_len = strlen($line) - strlen(ltrim($line)); // My textarea size is about 120 space chars. //define('TEXTAREA_SZ', 120); // NOTE: That strict math formula (split into 3 areas) gives // bad visual effects... // $align = ($align_len < (TEXTAREA_SZ / 3)) ? "left" // : (($align_len > (2 * TEXTAREA_SZ / 3)) ? "right" : "center"); // // Going to introduce some heuristic here :) // Visualy (remember that space char is thin) center starts at 25 pos // and 'right' from 60 (HALF of full width!) -- thats all :) // // NOTE: Guess align only if more than 10 spaces before -=title=- if ($align_len > 10) { $align = ($align_len < 25) ? "left" : (($align_len > 60) ? "right" : "center"); $align = ' style="text-align: ' . $align . ';"'; } else { $align = ''; } // $line = trim($line); $line = '
' . substr($line, 2, strlen($line) - 4) . '
'; $data .= $line . "\n"; // TODO: Case is handled ... no need to check other conditions // (it is apriori known that they are all false, moreover sometimes // check procedure need > O(0) of compexity) // -- continue to next line... // MUST replace all remaining parse blocks to the same logic... continue; } // Replace old styled titlebars if (strlen($line) != strlen($line = preg_replace("/-=(.+?)=-/", "
$1
", $line))) { // Close open paragraph, but not lists (why not?) or div's $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 0, 0); $data .= $line . "\n"; continue; } // check if we are inside a ~hc~ block and, if so, ignore // monospaced and do not insert
$lineInLowerCase = TikiLib::strtolower($this->unprotectSpecialChars($line, true)); $inComment += substr_count($lineInLowerCase, ""); if ($inComment < 0) { // stop lines containing just --> being detected as comments $inComment = 0; } // check if we are inside a ~pre~ block and, if so, ignore // monospaced and do not insert
$inPre += substr_count($lineInLowerCase, " $inTable += substr_count($lineInLowerCase, " $inTOC += substr_count($lineInLowerCase, "
    "); // check if we are inside a script not insert
    $inScript += substr_count($lineInLowerCase, "