1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8/** 9 * Parser Library 10 * 11 * \wiki syntax parser for tiki 12 * 13 * NB: Needs to be kept in utf-8 14 * 15 * @package Tiki 16 * @subpackage Parser 17 * @author Robert Plummer 18 * @copyright (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 19 * See copyright.txt for details and a complete list of authors. 20 * @licence Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 21 * @version SVN $Rev$ 22 * @filesource 23 * @link http://dev.tiki.org/Parser 24 * @since 8 25 * @see WikiParser_Parsable 26 */ 27class ParserLib extends TikiDb_Bridge 28{ 29 // private $makeTocCount = 0; Unused since Tiki 12 or earlier 30 31 //This var is used in both protectSpecialChars and unprotectSpecialChars to simplify the html ouput process. Can be replaced with a const starting with PHP 7 32 public $specialChars = [ 33 '≤REAL_LT≥' => [ 34 'html' => '<', 35 'nonHtml' => '<' 36 ], 37 '≤REAL_GT≥' => [ 38 'html' => '>', 39 'nonHtml' => '>' 40 ], 41 '≤REAL_NBSP≥' => [ 42 'html' => ' ', 43 'nonHtml' => ' ' 44 ], 45 /*on post back the page is parsed, which turns & into & 46 this is done to prevent that from happening, we are just 47 protecting some chars from letting the parser nab them*/ 48 '≤REAL_AMP≥' => [ 49 'html' => '& ', 50 'nonHtml' => '& ' 51 ], 52 ]; 53 54 /* 55 * Parsing "options". Some of these are real parsing parameters, such as protect_email and security options. 56 * Others (like is_html) define the markup's semantic. 57 * 58 * TODO: Separate real parsing parameters from properties of the parsable markup 59 * TO DO: To ease usage tracking, it may be best to replace $option with individual properties. 60 * Or replace setOptions() with individual setters? 61 */ 62 public $option = []; // An associative array of (most) parameters (despite the singular) 63 64 function setOptions($option = []) 65 { 66 global $page, $prefs; 67 68 $this->option = array_merge( 69 [ 70 'is_html' => false, 71 72 /* Determines if "Tiki syntax" is parsed in some circumstances. 73 Currently, when is_html is true, but that is probably wrong. 74 Overriden by the HTML plugin to force wiki parsing */ 75 'parse_wiki' => ! isset($prefs['wysiwyg_wiki_parsed']) || $prefs['wysiwyg_wiki_parsed'] === 'y', 76 77 'absolute_links' => false, 78 'language' => '', 79 'noparseplugins' => false, 80 'stripplugins' => false, 81 'noheaderinc' => false, 82 'page' => $page, 83 'print' => false, 84 'parseimgonly' => false, 85 'preview_mode' => false, 86 'suppress_icons' => false, 87 'parsetoc' => true, 88 'inside_pretty' => false, 89 'process_wiki_paragraphs' => true, 90 'min_one_paragraph' => false, 91 'skipvalidation' => false, 92 'ck_editor' => false, 93 'namespace' => false, 94 'protect_email' => true, 95 'exclude_plugins' => [], 96 'exclude_all_plugins' => false, 97 'include_plugins' => [], 98 'typography' => true, 99 ], 100 empty($option) ? [] : (array) $this->option, 101 (array)$option 102 ); 103 $this->option['include_plugins'] = array_map('strtolower', $this->option['include_plugins']); 104 $this->option['exclude_plugins'] = array_map('strtolower', $this->option['exclude_plugins']); 105 } 106 107 function __construct() 108 { 109 $this->setOptions(); 110 } 111 //* 112 function parse_data_raw($data) 113 { 114 $data = $this->parse_data($data); 115 $data = str_replace("tiki-index", "tiki-index_raw", $data); 116 return $data; 117 } 118 119 // This function handles wiki codes for those special HTML characters 120 // that textarea won't leave alone. 121 //* 122 protected function parse_htmlchar(&$data) 123 { 124 // cleaning some user input 125 // ckeditor parses several times and messes things up, we should only let it parse once 126 if (! $this->option['ck_editor']) { 127 $data = str_replace('&', '&', $data); 128 } 129 130 // oft-used characters (case insensitive) 131 $data = preg_replace("/~bs~/i", "\", $data); 132 $data = preg_replace("/~hs~/i", " ", $data); 133 $data = preg_replace("/~amp~/i", "&", $data); 134 $data = preg_replace("/~ldq~/i", "“", $data); 135 $data = preg_replace("/~rdq~/i", "”", $data); 136 $data = preg_replace("/~lsq~/i", "‘", $data); 137 $data = preg_replace("/~rsq~/i", "’", $data); 138 $data = preg_replace("/~c~/i", "©", $data); 139 $data = preg_replace("/~--~/", "—", $data); 140 $data = preg_replace("/ -- /", " — ", $data); 141 $data = preg_replace("/~lt~/i", "<", $data); 142 $data = preg_replace("/~gt~/i", ">", $data); 143 144 // HTML numeric character entities 145 $data = preg_replace("/~([0-9]+)~/", "&#$1;", $data); 146 } 147 148 // This function handles the protection of html entities so that they are not mangled when 149 // parse_htmlchar runs, and as well so they can be properly seen, be it html or non-html 150 function protectSpecialChars($data, $is_html = false) 151 { 152 if ((isset($this->option['is_html']) && $this->option['is_html'] != true) || ! empty($this->option['ck_editor'])) { 153 foreach ($this->specialChars as $key => $specialChar) { 154 $data = str_replace($specialChar['html'], $key, $data); 155 } 156 } 157 return $data; 158 } 159 160 // This function removed the protection of html entities so that they are rendered as expected by the viewer 161 function unprotectSpecialChars($data, $is_html = false) 162 { 163 if (( $is_html != false || ( isset($this->option['is_html']) && $this->option['is_html'])) 164 || $this->option['ck_editor']) { 165 foreach ($this->specialChars as $key => $specialChar) { 166 $data = str_replace($key, $specialChar['html'], $data); 167 } 168 } else { 169 foreach ($this->specialChars as $key => $specialChar) { 170 $data = str_replace($key, $specialChar['nonHtml'], $data); 171 } 172 } 173 174 return $data; 175 } 176 177 // Reverses parse_first. 178 //* 179 function replace_preparse(&$data, &$preparsed, &$noparsed, $is_html = false) 180 { 181 $data1 = $data; 182 $data2 = ""; 183 184 // Cook until done. Handles nested cases. 185 while ($data1 != $data2) { 186 $data1 = $data; 187 if (isset($noparsed["key"]) and count($noparsed["key"]) and count($noparsed["key"]) == count($noparsed["data"])) { 188 $data = str_replace($noparsed["key"], $noparsed["data"], $data); 189 } 190 191 if (isset($preparsed["key"]) and count($preparsed["key"]) and count($preparsed["key"]) == count($preparsed["data"])) { 192 $data = str_replace($preparsed["key"], $preparsed["data"], $data); 193 } 194 $data2 = $data; 195 } 196 197 $data = $this->unprotectSpecialChars($data, $is_html); 198 } 199 200 /** 201 * Replace plugins with guid keys and store them in an array 202 * 203 * @param $data string data to be cleaned of plugins 204 * @param $noparsed array output array 205 * @see parserLib::plugins_replace() 206 */ 207 function plugins_remove(&$data, &$noparsed, $removeCb = null) 208 { 209 $tikilib = TikiLib::lib('tiki'); 210 if (isset($removeCb) && ! is_callable($removeCb)) { 211 throw new Exception('Invalid callback'); 212 } 213 $matches = WikiParser_PluginMatcher::match($data); // find the plugins 214 foreach ($matches as $match) { // each plugin 215 if (isset($removeCb) && ! $removeCb($match)) { 216 continue; 217 } 218 $plugin = (string) $match; 219 $key = '§' . md5($tikilib->genPass()) . '§'; // by replace whole plugin with a guid 220 221 $noparsed['key'][] = $key; 222 $noparsed['data'][] = $plugin; 223 } 224 $data = isset($noparsed['data']) ? str_replace($noparsed['data'], $noparsed['key'], $data) : $data; 225 } 226 227 /** 228 * Restore plugins from array 229 * 230 * @param $data string data previously processed with plugins_remove() 231 * @param $noparsed array input array 232 */ 233 234 function plugins_replace(&$data, $noparsed, $is_html = false) 235 { 236 $preparsed = []; // unused 237 $noparsed['data'] = isset($noparsed['data']) ? str_replace('<x>', '', $noparsed['data']) : ''; 238 $this->replace_preparse($data, $preparsed, $noparsed, $is_html); 239 } 240 241 //* 242 private function plugin_match(&$data, &$plugins) 243 { 244 global $pluginskiplist; 245 if (! is_array($pluginskiplist)) { 246 $pluginskiplist = []; 247 } 248 249 $matcher_fake = ["~pp~","~np~","<pre>"]; 250 $matcher = "/\{([A-Z0-9_]+) *\(|\{([a-z]+)(\s|\})|~pp~|~np~|<[pP][rR][eE]>/"; 251 252 $plugins = []; 253 preg_match_all($matcher, $data, $tmp, PREG_SET_ORDER); 254 foreach ($tmp as $p) { 255 if (in_array(TikiLib::strtolower($p[0]), $matcher_fake) 256 || ( isset($p[1]) && ( in_array($p[1], $matcher_fake) || $this->plugin_exists($p[1]) ) ) 257 || ( isset($p[2]) && ( in_array($p[2], $matcher_fake) || $this->plugin_exists($p[2]) ) ) 258 ) { 259 $plugins = $p; 260 break; 261 } 262 } 263 264 // Check to make sure there was a match. 265 if (count($plugins) > 0 && strlen($plugins[0]) > 0) { 266 $pos = 0; 267 while (in_array($plugins[0], $pluginskiplist)) { 268 $pos = strpos($data, $plugins[0], $pos) + 1; 269 if (! preg_match($matcher, substr($data, $pos), $plugins)) { 270 return; 271 } 272 } 273 274 // If it is a true plugin 275 if ($plugins[0]{0} == "{") { 276 $pos = strpos($data, $plugins[0]); // where plugin starts 277 $pos_end = $pos + strlen($plugins[0]); // where character after ( is 278 279 // Here we're going to look for the end of the arguments for the plugin. 280 281 $i = $pos_end; 282 $last_data = strlen($data); 283 284 // We start with one open curly brace, and one open paren. 285 $curlies = 1; 286 287 // If model with ( 288 if (strlen($plugins[1])) { 289 $parens = 1; 290 $plugins['type'] = 'long'; 291 } else { 292 $parens = 0; 293 $plugins[1] = $plugins[2]; 294 unset($plugins[3]); 295 $plugins['type'] = 'short'; 296 } 297 298 // While we're not at the end of the string, and we still haven't found both closers 299 while ($i < $last_data) { 300 $char = substr($data, $i, 1); 301 //print "<pre>Data char: $i, $char, $curlies, $parens\n.</pre>\n"; 302 if ($char == "{") { 303 $curlies++; 304 } elseif ($char == "(" && $plugins['type'] == 'long') { 305 $parens++; 306 } elseif ($char == "}") { 307 $curlies--; 308 if ($plugins['type'] == 'short') { 309 $lastParens = $i; 310 } 311 } elseif ($char == ")" && $plugins['type'] == 'long') { 312 $parens--; 313 $lastParens = $i; 314 } 315 316 // If we found the end of the match... 317 if ($curlies == 0 && $parens == 0) { 318 break; 319 } 320 321 $i++; 322 } 323 324 if ($curlies == 0 && $parens == 0) { 325 $plugins[2] = (string) substr($data, $pos_end, $lastParens - $pos_end); 326 $plugins[0] = $plugins[0] . (string) substr($data, $pos_end, $i - $pos_end + 1); 327 /* 328 print "<pre>Match found: "; 329 print( $plugins[2] ); 330 print "</pre>"; 331 */ 332 } 333 334 $plugins['arguments'] = isset($plugins[2]) ? $this->plugin_split_args($plugins[2]) : []; 335 } else { 336 $plugins[1] = $plugins[0]; 337 $plugins[2] = ""; 338 } 339 } 340 341 /* 342 print "<pre>Plugin match end:"; 343 print_r( $plugins ); 344 print "</pre>"; 345 */ 346 } 347 348 //* 349 function plugin_split_args($params_string) 350 { 351 $parser = new WikiParser_PluginArgumentParser; 352 353 return $parser->parse($params_string); 354 } 355 356 // get all the plugins of a text- can be limitted only to some 357 //* 358 function getPlugins($data, $only = null) 359 { 360 $plugins = []; 361 for (;;) { 362 $this->plugin_match($data, $plugin); 363 if (empty($plugin)) { 364 break; 365 } 366 if (empty($only) || in_array($plugin[1], $only) || in_array(TikiLib::strtoupper($plugin[1]), $only) || in_array(TikiLib::strtolower($plugin[1]), $only)) { 367 $plugins[] = $plugin; 368 } 369 $pos = strpos($data, $plugin[0]); 370 $data = substr_replace($data, '', $pos, strlen($plugin[0])); 371 } 372 return $plugins; 373 } 374 375 // Transitional wrapper over WikiParser_Parsable::parse_first() 376 // This recursive function handles pre- and no-parse sections and plugins 377 function parse_first(&$data, &$preparsed, &$noparsed, $real_start_diff = '0') 378 { 379 return (new WikiParser_Parsable(''))->parse_first($data, $preparsed, $noparsed, $real_start_diff); 380 } 381 382 protected function strip_unparsed_block(& $data, & $noparsed, $protect = false) 383 { 384 $tikilib = TikiLib::lib('tiki'); 385 386 $start = -1; 387 while (false !== $start = strpos($data, '~np~', $start + 1)) { 388 if (false !== $end = strpos($data, '~/np~', $start)) { 389 $content = substr($data, $start + 4, $end - $start - 4); 390 if ($protect) { 391 $content = $this->protectSpecialChars($content, $this->option['is_html']); 392 } 393 // ~pp~ type "plugins" 394 $key = "§" . md5($tikilib->genPass()) . "§"; 395 $noparsed["key"][] = preg_quote($key); 396 $noparsed["data"][] = $content; 397 398 $data = substr($data, 0, $start) . $key . substr($data, $end + 5); 399 } 400 } 401 } 402 403 // 404 // Call 'wikiplugin_.*_description()' from given file 405 // 406 public function get_plugin_description($name, &$enabled, $area_id = 'editwiki') 407 { 408 if (( ! $info = $this->plugin_info($name) ) && $this->plugin_exists($name, true)) { 409 $enabled = true; 410 411 $func_name = "wikiplugin_{$name}_help"; 412 if (! function_exists($func_name)) { 413 return false; 414 } 415 416 $ret = $func_name(); 417 return $this->parse_data($ret); 418 } else { 419 $smarty = TikiLib::lib('smarty'); 420 $enabled = true; 421 422 $ret = $info; 423 424 if (isset($ret['prefs'])) { 425 global $prefs; 426 427 // If the plugin defines required preferences, they should all be to 'y' 428 foreach ($ret['prefs'] as $pref) { 429 if (! isset($prefs[$pref]) || $prefs[$pref] != 'y') { 430 $enabled = false; 431 return; 432 } 433 } 434 } 435 436 if (isset($ret['documentation']) && ctype_alnum($ret['documentation'])) { 437 $ret['documentation'] = "http://doc.tiki.org/{$ret['documentation']}"; 438 } 439 440 $smarty->assign('area_id', $area_id); 441 $smarty->assign('plugin', $ret); 442 $smarty->assign('plugin_name', TikiLib::strtoupper($name)); 443 return $smarty->fetch('tiki-plugin_help.tpl'); 444 } 445 } 446 447 //* 448 function plugin_get_list($includeReal = true, $includeAlias = true) 449 { 450 return WikiPlugin_Negotiator_Wiki::getList($includeReal, $includeAlias); 451 } 452 453 //* 454 function plugin_exists($name, $include = false) 455 { 456 $php_name = 'lib/wiki-plugins/wikiplugin_'; 457 $php_name .= TikiLib::strtolower($name) . '.php'; 458 459 $exists = file_exists($php_name); 460 461 if ($include && $exists) { 462 include_once $php_name; 463 } 464 465 if ($exists) { 466 return true; 467 } elseif ($info = WikiPlugin_Negotiator_Wiki_Alias::info($name)) { 468 // Make sure the underlying implementation exists 469 470 return $this->plugin_exists($info['implementation'], $include); 471 } 472 return false; 473 } 474 475 //* 476 function plugin_info($name, $args = []) 477 { 478 static $known = []; 479 480 if (isset($known[$name]) && $name != 'package') { 481 return $known[$name]; 482 } 483 484 if (! $this->plugin_exists($name, true)) { 485 return $known[$name] = false; 486 } 487 488 $func_name_info = "wikiplugin_{$name}_info"; 489 490 if (! function_exists($func_name_info)) { 491 if ($info = WikiPlugin_Negotiator_Wiki_Alias::info($name)) { 492 return $known[$name] = $info['description']; 493 } else { 494 return $known[$name] = false; 495 } 496 } 497 498 // Support Tiki Packages param overrides for Package plugin 499 if ($name == 'package' && ! empty($args['package']) && ! empty($args['plugin'])) { 500 $info = $func_name_info(); 501 502 $parts = explode('/', $args['package']); 503 if ($extensionPackage = \Tiki\Package\ExtensionManager::get($args['package'])) { 504 $path = $extensionPackage->getPath() . '/lib/wiki-plugins/' . $args['plugin'] . '.php'; 505 } else { 506 $path = ''; 507 } 508 509 if (! file_exists($path)) { 510 return $known[$name] = $info; 511 } 512 513 require_once($path); 514 515 $namespace = $extensionPackage->getBaseNamespace(); 516 if (!empty($namespace)) { 517 $namespace .= '\\PackagePlugins\\'; 518 } 519 $functionname = $namespace . $args['plugin'] . "_info"; 520 521 if (! function_exists($functionname)) { 522 return $known[$name] = $info; 523 } 524 525 $viewinfo = $functionname(); 526 if (isset($viewinfo['params'])) { 527 $combinedparams = $viewinfo['params'] + $info['params']; 528 } else { 529 $combinedparams = $info['params']; 530 } 531 532 $info = $viewinfo + $info; 533 $info['params'] = $combinedparams; 534 535 return $known[$name] = $info; 536 } 537 538 return $known[$name] = $func_name_info(); 539 } 540 541 //* 542 function plugin_alias_info($name) 543 { 544 return WikiPlugin_Negotiator_Wiki_Alias::info($name); 545 } 546 547 //* 548 function plugin_alias_store($name, $data) 549 { 550 return WikiPlugin_Negotiator_Wiki_Alias::store($name, $data); 551 } 552 553 //* 554 function plugin_alias_delete($name) 555 { 556 return WikiPlugin_Negotiator_Wiki_Alias::delete($name); 557 } 558 559 //* 560 function plugin_enabled($name, & $output) 561 { 562 if (! $meta = $this->plugin_info($name)) { 563 return true; // Legacy plugins always execute 564 } 565 566 global $prefs; 567 568 $missing = []; 569 570 if (isset($meta['prefs'])) { 571 foreach ($meta['prefs'] as $pref) { 572 if ($prefs[$pref] != 'y') { 573 $missing[] = $pref; 574 } 575 } 576 } 577 578 if (count($missing) > 0) { 579 $output = WikiParser_PluginOutput::disabled($name, $missing); 580 return false; 581 } 582 583 return true; 584 } 585 586 //* 587 function plugin_is_inline($name) 588 { 589 if (! $meta = $this->plugin_info($name)) { 590 return true; // Legacy plugins always inline 591 } 592 593 global $prefs; 594 595 $inline = false; 596 if (isset($meta['inline']) && $meta['inline']) { 597 return true; 598 } 599 600 $inline_pref = 'wikiplugininline_' . $name; 601 if (isset($prefs[ $inline_pref ]) && $prefs[ $inline_pref ] == 'y') { 602 return true; 603 } 604 605 return false; 606 } 607 608 /** 609 * Check if possible to execute a plugin 610 * 611 * @param string $name 612 * @param string $data 613 * @param array $args 614 * @param bool $dont_modify 615 * @return bool|string Boolean true if can execute, string 'rejected' if can't execute and plugin fingerprint if pending 616 */ 617 //* 618 function plugin_can_execute($name, $data = '', $args = [], $dont_modify = false) 619 { 620 global $prefs; 621 622 // If validation is disabled, anything can execute 623 if ($prefs['wiki_validate_plugin'] != 'y') { 624 return true; 625 } 626 627 $meta = $this->plugin_info($name, $args); 628 629 if (! isset($meta['validate'])) { 630 return true; 631 } 632 633 $fingerprint = $this->plugin_fingerprint($name, $meta, $data, $args); 634 635 if ($fingerprint === '') { // only args or body were being validated and they're empty or safe 636 return true; 637 } 638 639 $val = $this->plugin_fingerprint_check($fingerprint, $dont_modify); 640 if (strpos($val, 'accept') === 0) { 641 return true; 642 } elseif (strpos($val, 'reject') === 0) { 643 return 'rejected'; 644 } else { 645 global $tiki_p_plugin_approve, $tiki_p_plugin_preview, $user; 646 if (isset($_SERVER['REQUEST_METHOD']) 647 && $_SERVER['REQUEST_METHOD'] == 'POST' 648 && isset($_POST['plugin_fingerprint']) 649 && $_POST['plugin_fingerprint'] == $fingerprint 650 ) { 651 if ($tiki_p_plugin_approve == 'y') { 652 if (isset($_POST['plugin_accept'])) { 653 $tikilib = TikiLib::lib('tiki'); 654 $this->plugin_fingerprint_store($fingerprint, 'accept'); 655 $tikilib->invalidate_cache($this->option['page']); 656 return true; 657 } elseif (isset($_POST['plugin_reject'])) { 658 $tikilib = TikiLib::lib('tiki'); 659 $this->plugin_fingerprint_store($fingerprint, 'reject'); 660 $tikilib->invalidate_cache($this->option['page']); 661 return 'rejected'; 662 } 663 } 664 665 if ($tiki_p_plugin_preview == 'y' 666 && isset($_POST['plugin_preview']) ) { 667 return true; 668 } 669 } 670 671 return $fingerprint; 672 } 673 } 674 675 //* 676 function plugin_fingerprint_check($fp, $dont_modify = false) 677 { 678 global $user; 679 $tikilib = TikiLib::lib('tiki'); 680 $limit = date('Y-m-d H:i:s', time() - 15 * 24 * 3600); 681 $result = $this->query("SELECT `status`, if (`status`='pending' AND `last_update` < ?, 'old', '') flag FROM `tiki_plugin_security` WHERE `fingerprint` = ?", [ $limit, $fp ]); 682 683 $needUpdate = false; 684 685 if ($row = $result->fetchRow()) { 686 $status = $row['status']; 687 $flag = $row['flag']; 688 689 if ($status == 'accept' || $status == 'reject') { 690 return $status; 691 } 692 693 if ($flag == 'old') { 694 $needUpdate = true; 695 } 696 } else { 697 $needUpdate = true; 698 } 699 700 if ($needUpdate && ! $dont_modify) { 701 if ($this->option['page']) { 702 $objectType = 'wiki page'; 703 $objectId = $this->option['page']; 704 } else { 705 $objectType = ''; 706 $objectId = ''; 707 } 708 709 if (! $user) { 710 $user = tra('Anonymous'); 711 } 712 713 $pluginSecurity = $tikilib->table('tiki_plugin_security'); 714 $pluginSecurity->delete(['fingerprint' => $fp]); 715 $pluginSecurity->insert( 716 ['fingerprint' => $fp, 'status' => 'pending', 'added_by' => $user, 'last_objectType' => $objectType, 'last_objectId' => $objectId] 717 ); 718 } 719 720 return ''; 721 } 722 723 //* 724 function plugin_fingerprint_store($fp, $type) 725 { 726 global $prefs, $user; 727 $tikilib = TikiLib::lib('tiki'); 728 if ($this->option['page']) { 729 $objectType = 'wiki page'; 730 $objectId = $this->option['page']; 731 } else { 732 $objectType = ''; 733 $objectId = ''; 734 } 735 736 $pluginSecurity = $tikilib->table('tiki_plugin_security'); 737 $pluginSecurity->delete(['fingerprint' => $fp]); 738 $pluginSecurity->insert( 739 ['fingerprint' => $fp,'status' => $type,'added_by' => $user,'last_objectType' => $objectType,'last_objectId' => $objectId] 740 ); 741 } 742 743 //* 744 function plugin_clear_fingerprint($fp) 745 { 746 $tikilib = TikiLib::lib('tiki'); 747 $pluginSecurity = $tikilib->table('tiki_plugin_security'); 748 $pluginSecurity->delete(['fingerprint' => $fp]); 749 } 750 751 //* 752 function list_plugins_pending_approval() 753 { 754 $tikilib = TikiLib::lib('tiki'); 755 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"); 756 } 757 758 /** 759 * Return a list of plugins by status 760 * 761 * @param string|array $statuses 762 * @return array 763 */ 764 public function listPluginsByStatus($statuses) 765 { 766 if (! empty($statuses) && ! is_array($statuses)) { 767 $statuses = [$statuses]; 768 } 769 770 $tikiLib = TikiLib::lib('tiki'); 771 $pluginSecurity = $tikiLib->table('tiki_plugin_security'); 772 return $pluginSecurity->fetchAll( 773 ['fingerprint', 'added_by', 'last_update', 'last_objectType', 'last_objectId', 'status'], 774 ['status' => $pluginSecurity->in($statuses)], 775 -1, 776 -1, 777 ['last_update' => 'DESC'] 778 ); 779 } 780 781 //* 782 function approve_all_pending_plugins() 783 { 784 global $user; 785 $tikilib = TikiLib::lib('tiki'); 786 787 $pluginSecurity = $tikilib->table('tiki_plugin_security'); 788 $pluginSecurity->updateMultiple(['status' => 'accept', 'approval_by' => $user], ['status' => 'pending',]); 789 } 790 791 //* 792 function approve_selected_pending_plugings($fp) 793 { 794 global $user; 795 $tikilib = TikiLib::lib('tiki'); 796 797 $pluginSecurity = $tikilib->table('tiki_plugin_security'); 798 $pluginSecurity->update(['status' => 'accept', 'approval_by' => $user], ['fingerprint' => $fp]); 799 } 800 801 //* 802 function plugin_fingerprint($name, $meta, $data, $args) 803 { 804 $validate = (isset($meta['validate']) ? $meta['validate'] : ''); 805 806 $data = $this->unprotectSpecialChars($data, true); 807 808 if ($validate == 'all' || $validate == 'body') { 809 // 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. 810 $validateBody = str_replace('<x>', '', $data); 811 } else { 812 $validateBody = ''; 813 } 814 815 if ($validate === 'body' && empty($validateBody)) { 816 return ''; 817 } 818 819 if ($validate == 'all' || $validate == 'arguments') { 820 $validateArgs = $args; 821 822 // Remove arguments marked as safe from the fingerprint 823 foreach ($meta['params'] as $key => $info) { 824 if (isset($validateArgs[$key]) 825 && isset($info['safe']) 826 && $info['safe'] 827 ) { 828 unset($validateArgs[$key]); 829 } 830 } 831 // Parameter order needs to be stable 832 ksort($validateArgs); 833 834 if (empty($validateArgs)) { 835 if ($validate === 'arguments') { 836 return ''; 837 } 838 $validateArgs = [ '' => '' ]; // maintain compatibility with pre-Tiki 7 fingerprints 839 } 840 } else { 841 $validateArgs = []; 842 } 843 844 $bodyLen = str_pad(strlen($validateBody), 6, '0', STR_PAD_RIGHT); 845 $serialized = serialize($validateArgs); 846 $argsLen = str_pad(strlen($serialized), 6, '0', STR_PAD_RIGHT); 847 848 $bodyHash = md5($validateBody); 849 $argsHash = md5($serialized); 850 851 return "$name-$bodyHash-$argsHash-$bodyLen-$argsLen"; 852 } 853 854 // Transitional wrapper over WikiParser_Parsable::plugin_execute() 855 function plugin_execute($name, $data = '', $args = [], $offset = 0, $validationPerformed = false, $option = []) 856 { 857 return (new WikiParser_Parsable(''))->plugin_execute($name, $data, $args, $offset, $validationPerformed, $option); 858 } 859 860 //* 861 protected function convert_plugin_for_ckeditor($name, $args, $plugin_result, $data, $info = []) 862 { 863 $ck_editor_plugin = '{' . (empty($data) ? $name : TikiLib::strtoupper($name) . '(') . ' '; 864 $arg_str = ''; // not using http_build_query() as it converts spaces into + 865 if (! empty($args)) { 866 foreach ($args as $argKey => $argValue) { 867 if (is_array($argValue)) { 868 if (isset($info['params'][$argKey]['separator'])) { 869 $sep = $info['params'][$argKey]['separator']; 870 } else { 871 $sep = ','; 872 } 873 $ck_editor_plugin .= $argKey . '="' . implode($sep, $argValue) . '" '; // process array 874 $arg_str .= $argKey . '=' . implode($sep, $argValue) . '&'; 875 } else { 876 // even though args are now decoded we still need to escape double quotes 877 $argValue = addcslashes($argValue, '"'); 878 879 $ck_editor_plugin .= $argKey . '="' . $argValue . '" '; 880 $arg_str .= $argKey . '=' . $argValue . '&'; 881 } 882 } 883 } 884 if (substr($ck_editor_plugin, -1) === ' ') { 885 $ck_editor_plugin = substr($ck_editor_plugin, 0, -1); 886 } 887 if (! empty($data)) { 888 $ck_editor_plugin .= ')}' . $data . '{' . TikiLib::strtoupper($name) . '}'; 889 } else { 890 $ck_editor_plugin .= '}'; 891 } 892 // work out if I'm a nested plugin and return empty if so 893 $stack = debug_backtrace(); 894 $plugin_nest_level = 0; 895 foreach ($stack as $st) { 896 if ($st['function'] === 'parse_first') { 897 $plugin_nest_level ++; 898 if ($plugin_nest_level > 1) { 899 return ''; 900 } 901 } 902 } 903 $arg_str = rtrim($arg_str, '&'); 904 $icon = isset($info['icon']) ? $info['icon'] : 'img/icons/wiki_plugin_edit.png'; 905 906 // some plugins are just too fragile to do wysiwyg, so show the "source" for them ;( 907 $excluded = ['tracker', 'trackerlist', 'trackerfilter', 'kaltura', 'toc', 'freetagged', 'draw', 'googlemap', 908 'include', 'module', 'list', 'custom_search', 'iframe', 'map', 'calendar', 'file', 'files', 'mouseover', 'sort', 909 'slideshow', 'convene', 'redirect', 'galleriffic']; 910 911 $ignore = null; 912 $enabled = $this->plugin_enabled($name, $ignore); 913 if (in_array($name, $excluded) || ! $enabled) { 914 $plugin_result = ' ' . $ck_editor_plugin; 915 } else { 916 if (! isset($info['format']) || $info['format'] !== 'html') { 917 $oldOptions = $this->option; 918 $plugin_result = $this->parse_data($plugin_result, ['is_html' => false, 'suppress_icons' => true, 'ck_editor' => true, 'noparseplugins' => true]); 919 $this->setOptions($oldOptions); 920 // reset the noparseplugins option, to allow for proper display in CkEditor 921 $this->option['noparseplugins'] = false; 922 } else { 923 $plugin_result = preg_replace('/~[\/]?np~/ms', '', $plugin_result); // remove no parse tags otherwise they get nested later (bad) 924 } 925 926 if (! getCookie('wysiwyg_inline_edit', 'preview', false)) { // remove hrefs and onclicks 927 $plugin_result = preg_replace('/\shref\=/i', ' tiki_href=', $plugin_result); 928 $plugin_result = preg_replace('/\sonclick\=/i', ' tiki_onclick=', $plugin_result); 929 $plugin_result = preg_replace('/<script.*?<\/script>/mi', '', $plugin_result); 930 // remove hidden inputs 931 $plugin_result = preg_replace('/<input.*?type=[\'"]?hidden[\'"]?.*>/mi', '', $plugin_result); 932 } 933 } 934 if (! in_array($name, ['html'])) { // remove <p> and <br>s from non-html 935 $data = str_replace(['<p>', '</p>', "\t"], '', $data); 936 $data = str_replace('<br />', "\n", $data); 937 } 938 939 if ($this->contains_html_block($plugin_result)) { 940 $elem = 'div'; 941 } else { 942 $elem = 'span'; 943 } 944 $elem_style = 'position:relative;display:inline-block;'; 945 if (! $enabled) { 946 $elem_style .= 'opacity:0.3;'; 947 } 948 if (in_array($name, ['img', 'div']) && preg_match('/<' . $name . '[^>]*style="(.*?)"/i', $plugin_result, $m)) { 949 if (count($m)) { 950 $elem_style .= $m[1]; 951 } 952 } 953 954 $ret = '~np~<' . $elem . ' contenteditable="false" unselectable="on" class="tiki_plugin" data-plugin="' . $name . '" style="' . $elem_style . '"' . 955 ' data-syntax="' . htmlentities($ck_editor_plugin, ENT_QUOTES, 'UTF-8') . '"' . 956 ' data-args="' . htmlentities($arg_str, ENT_QUOTES, 'UTF-8') . '"' . 957 ' data-body="' . htmlentities($data, ENT_QUOTES, 'UTF-8') . '">' . // not <!--{cke_protected} 958 '<img src="' . $icon . '" width="16" height="16" class="plugin_icon" />' . 959 $plugin_result . '<!-- end tiki_plugin --></' . $elem . '>~/np~'; 960 961 return $ret; 962 } 963 964 function find_plugins($data, $name = null) 965 { 966 $parserlib = TikiLib::lib('parser'); 967 $argumentParser = new WikiParser_PluginArgumentParser; 968 $matches = WikiParser_PluginMatcher::match($data); 969 $occurrences = []; 970 foreach ($matches as $match) { 971 $plugin = [ 972 'name' => $match->getName(), 973 'arguments' => $argumentParser->parse($match->getArguments()), 974 'body' => $match->getBody(), 975 ]; 976 977 $dummy_output = ''; 978 if ($parserlib->plugin_enabled($plugin['name'], $dummy_output)) { 979 if ($name === null || $plugin['name'] == $name) { 980 $occurrences[] = $plugin; 981 } 982 } 983 } 984 return $occurrences; 985 } 986 987 function process_save_plugins($data, array $context) 988 { 989 $parserlib = TikiLib::lib('parser'); 990 991 $argumentParser = new WikiParser_PluginArgumentParser; 992 993 $matches = WikiParser_PluginMatcher::match($data); 994 995 foreach ($matches as $match) { 996 $plugin_name = $match->getName(); 997 $body = $match->getBody(); 998 $arguments = $argumentParser->parse($match->getArguments()); 999 1000 $dummy_output = ''; 1001 if ($parserlib->plugin_enabled($plugin_name, $dummy_output)) { 1002 $func_name = 'wikiplugin_' . $plugin_name . '_rewrite'; 1003 1004 if (function_exists($func_name)) { 1005 $parserlib->plugin_apply_filters($plugin_name, $data, $arguments); 1006 $output = $func_name($body, $arguments, $context); 1007 1008 if ($output !== false) { 1009 $match->replaceWith($output); 1010 } 1011 } 1012 1013 if ($plugin_name == 'translationof') { 1014 $this->add_translationof_relation($data, $arguments, $context['itemId']); 1015 } 1016 } 1017 } 1018 1019 $matches_text = $matches->getText(); 1020 1021 return $matches_text; 1022 } 1023 1024 //* 1025 protected function plugin_apply_filters($name, & $data, & $args) 1026 { 1027 $tikilib = TikiLib::lib('tiki'); 1028 1029 $info = $this->plugin_info($name, $args); 1030 1031 $default = TikiFilter::get(isset($info['defaultfilter']) ? $info['defaultfilter'] : 'xss'); 1032 1033 // Apply filters on the body 1034 $filter = isset($info['filter']) ? TikiFilter::get($info['filter']) : $default; 1035 //$data = TikiLib::htmldecode($data); // jb 9.0 commented out in fix for html entitles 1036 $data = $filter->filter($data); 1037 1038 if (isset($this->option) && (! empty($this->option['is_html']) && (! $this->option['is_html']))) { 1039 $noparsed = ['data' => [], 'key' => []]; 1040 $this->strip_unparsed_block($data, $noparsed); 1041 $data = str_replace(['<', '>'], ['<', '>'], $data); 1042 foreach ($noparsed['data'] as &$instance) { 1043 $instance = '~np~' . $instance . '~/np~'; 1044 } 1045 unset($instance); 1046 $data = str_replace($noparsed['key'], $noparsed['data'], $data); 1047 } 1048 1049 // Make sure all arguments are declared 1050 if (isset($info['params'])) { 1051 $params = $info['params']; 1052 } 1053 $argsCopy = $args; 1054 if (! isset($info['extraparams']) && isset($params) && is_array($params)) { 1055 $args = array_intersect_key($args, $params); 1056 } 1057 1058 // Apply filters on values individually 1059 if (! empty($args)) { 1060 foreach ($args as $argKey => &$argValue) { 1061 if (! isset($params[$argKey])) { 1062 continue;// extra params 1063 } 1064 $paramInfo = $params[$argKey]; 1065 $filter = isset($paramInfo['filter']) ? TikiFilter::get($paramInfo['filter']) : $default; 1066 $argValue = TikiLib::htmldecode($argValue); 1067 1068 if (isset($paramInfo['separator'])) { 1069 $vals = []; 1070 1071 $vals = $tikilib->array_apply_filter($tikilib->multi_explode($paramInfo['separator'], $argValue), $filter); 1072 1073 $argValue = array_values($vals); 1074 } else { 1075 $argValue = $filter->filter($argValue); 1076 } 1077 } 1078 } 1079 } 1080 1081 //* 1082 protected function convert_plugin_output($output, $from, $to) 1083 { 1084 if (! $output instanceof WikiParser_PluginOutput) { 1085 if ($from === 'wiki') { 1086 $output = WikiParser_PluginOutput::wiki($output); 1087 } elseif ($from === 'html') { 1088 $output = WikiParser_PluginOutput::html($output); 1089 } 1090 } 1091 1092 if ($to === 'html') { 1093 return $output->toHtml($this->option); 1094 } elseif ($to === 'wiki') { 1095 return $output->toWiki(); 1096 } 1097 } 1098 1099 //* 1100 function plugin_replace_args($content, $rules, $args) 1101 { 1102 $patterns = []; 1103 $replacements = []; 1104 1105 foreach ($rules as $token => $info) { 1106 $patterns[] = "%$token%"; 1107 if (isset($info['input']) && ! empty($info['input'])) { 1108 $token = $info['input']; 1109 } 1110 1111 if (isset($args[$token])) { 1112 $value = $args[$token]; 1113 } else { 1114 $value = isset($info['default']) ? $info['default'] : ''; 1115 } 1116 1117 switch (isset($info['encoding']) ? $info['encoding'] : 'none') { 1118 case 'html': 1119 $replacements[] = htmlentities($value, ENT_QUOTES, 'UTF-8'); 1120 break; 1121 case 'url': 1122 $replacements[] = rawurlencode($value); 1123 break; 1124 default: 1125 $replacements[] = $value; 1126 } 1127 } 1128 1129 return str_replace($patterns, $replacements, $content); 1130 } 1131 1132 //* 1133 function plugin_is_editable($name) 1134 { 1135 global $tiki_p_edit, $prefs, $section; 1136 $info = $this->plugin_info($name); 1137 // note that for 3.0 the plugin editor only works in wiki pages, but could be extended later 1138 return $section == 'wiki page' && $info && $tiki_p_edit == 'y' && $prefs['wiki_edit_plugin'] == 'y' 1139 && ! $this->plugin_is_inline($name); 1140 } 1141 1142 /** 1143 * Gets a wiki parseable content and substitutes links for $oldName by 1144 * links for $newName. 1145 * 1146 * @param string wiki parseable content 1147 * @param string old page name 1148 * @param string new page name 1149 * @return string new wiki parseable content with links replaced 1150 */ 1151 function replace_links($data, $oldName, $newName) 1152 { 1153 global $prefs; 1154 $quotedOldName = preg_quote($oldName, '/'); 1155 $semanticlib = TikiLib::lib('semantic'); 1156 1157 // FIXME: Affects non-parsed sections 1158 foreach ($semanticlib->getAllTokens() as $sem) { 1159 $data = str_replace("($sem($oldName", "($sem($newName", $data); 1160 } 1161 1162 if ($prefs['feature_wikiwords'] == 'y') { 1163 if (strstr($newName, ' ')) { 1164 $data = preg_replace("/(?<= |\n|\t|\r|\,|\;|^)$quotedOldName(?= |\n|\t|\r|\,|\;|$)/", '((' . $newName . '))', $data); 1165 } else { 1166 $data = preg_replace("/(?<= |\n|\t|\r|\,|\;|^)$quotedOldName(?= |\n|\t|\r|\,|\;|$)/", $newName, $data); 1167 } 1168 } 1169 1170 $data = preg_replace("/(?<=\(\()$quotedOldName(?=\)\)|\|)/i", $newName, $data); 1171 1172 $quotedOldHtmlName = preg_quote(urlencode($oldName), '/'); 1173 1174 $htmlSearch = '/<a class="wiki" href="tiki-index\.php\?page=' . $quotedOldHtmlName . '([^"]*)"/i'; 1175 $htmlReplace = '<a class="wiki" href="tiki-index.php?page=' . urlencode($newName) . '\\1"'; 1176 $data = preg_replace($htmlSearch, $htmlReplace, $data); 1177 1178 $htmlSearch = '/<a class="wiki" href="' . $quotedOldHtmlName . '"/i'; 1179 $htmlReplace = '<a class="wiki" href="' . urlencode($newName) . '"'; 1180 $data = preg_replace($htmlSearch, $htmlReplace, $data); 1181 1182 $htmlWantedSearch = '/(' . $quotedOldName . ')?<a class="wiki wikinew" href="tiki-editpage\.php\?page=' . $quotedOldHtmlName . '"[^<]+<\/a>/i'; 1183 $data = preg_replace($htmlWantedSearch, '((' . $newName . '))', $data); 1184 1185 return $data; 1186 } 1187 1188 // Replace hotwords in given line 1189 //* 1190 function replace_hotwords($line, $words = null) 1191 { 1192 global $prefs; 1193 1194 if ($prefs['feature_hotwords'] == 'y') { 1195 $hotw_nw = ($prefs['feature_hotwords_nw'] == 'y') ? "target='_blank'" : ''; 1196 1197 // 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(). 1198 $sep = empty($prefs['feature_hotwords_sep']) ? " \n\t\r\,\;\(\)\.\:\[\]\{\}\!\?\"" : $prefs['feature_hotwords_sep']; 1199 1200 if (is_null($words)) { 1201 $words = $this->get_hotwords(); 1202 } 1203 foreach ($words as $word => $url) { 1204 $escapedWord = preg_quote($word, '/'); 1205 1206 /* 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". 1207 * 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 1208 */ 1209 1210 // Step 1: Insert magic sequences which will neutralize step 2 in some cases. 1211 $line = preg_replace("/(=(\"|')[^\"']*[$sep'])$escapedWord([$sep][^\"']*(\"|'))/i", "$1:::::$word,:::::$3", $line); 1212 1213 // Step 2: Add links where the hotword appears (not neutralized) 1214 $line = preg_replace("/([$sep']|^)$escapedWord($|[$sep])/i", "$1<a class=\"wiki\" href=\"$url\" $hotw_nw>$word</a>$2", $line); 1215 1216 // Step 3: Remove magic sequences inserted in step 1 1217 $line = preg_replace("/:::::$escapedWord,:::::/i", "$word", $line); 1218 } 1219 } 1220 return $line; 1221 } 1222 1223 // Make plain text URIs in text into clickable hyperlinks 1224 // check to see if autolinks is enabled before calling this function ($prefs['feature_autolinks'] == "y") 1225 //* 1226 function autolinks($text) 1227 { 1228 if ($text) { 1229 global $prefs; 1230 static $mail_protect_pattern = ''; 1231 1232 $attrib = ''; 1233 if ($prefs['popupLinks'] == 'y') { 1234 $attrib .= 'target="_blank" '; 1235 } 1236 if ($prefs['feature_wiki_ext_icon'] == 'y') { 1237 $attrib .= 'class="wiki external" '; 1238 include_once(__DIR__ . '/../smarty_tiki/function.icon.php'); 1239 $ext_icon = smarty_function_icon(['name' => 'link-external'], TikiLib::lib('smarty')->getEmptyInternalTemplate()); 1240 } else { 1241 $attrib .= 'class="wiki" '; 1242 $ext_icon = ""; 1243 } 1244 1245 // add a space so we can match links starting at the beginning of the first line 1246 $text = " " . $text; 1247 $patterns = []; 1248 $replacements = []; 1249 1250 // protocol://suffix 1251 $patterns[] = "#([\n ])([a-z0-9]+?)://([^<, \n\r]+)#i"; 1252 $replacements[] = "\\1<a $attrib href=\"\\2://\\3\">\\2://\\3$ext_icon</a>"; 1253 1254 // www.domain.ext/optionalpath 1255 $patterns[] = "#([\n ])www\.([a-z0-9\-]+)\.([a-z0-9\-.\~]+)((?:/[^,< \n\r]*)?)#i"; 1256 $replacements[] = "\\1<a $attrib href=\"http://www.\\2.\\3\\4\">www.\\2.\\3\\4$ext_icon</a>"; 1257 1258 // email address (foo@domain.ext) 1259 $patterns[] = "#([\n ])([a-z0-9\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i"; 1260 if ($this->option['protect_email'] && $this->option['print'] !== 'y' && $prefs['feature_wiki_protect_email'] == 'y') { 1261 if (! $mail_protect_pattern) { 1262 $mail_protect_pattern = "\\1" . TikiLib::protect_email("\\2", "\\3"); 1263 } 1264 $replacements[] = $mail_protect_pattern; 1265 } else { 1266 $replacements[] = "\\1<a class='wiki' href=\"mailto:\\2@\\3\">\\2@\\3</a>"; 1267 } 1268 1269 $patterns[] = "#([\n ])magnet\:\?([^,< \n\r]+)#i"; 1270 $replacements[] = "\\1<a class='wiki' href=\"magnet:?\\2\">magnet:?\\2</a>"; 1271 1272 $text = preg_replace($patterns, $replacements, $text); 1273 // strip the space we added 1274 $text = substr($text, 1); 1275 } 1276 1277 return $text; 1278 1279 // } else { 1280 // return $text; 1281 // } 1282 } 1283 1284 /** 1285 * close_blocks - Close out open paragraph, lists, and div's 1286 * 1287 * During parse_data, information is kept on blocks of text (paragraphs, lists, divs) 1288 * that need to be closed out. This function does that, rather than duplicating the 1289 * code inline. 1290 * 1291 * @param $data - Output data 1292 * @param $in_paragraph - TRUE if there is an open paragraph 1293 * @param $listbeg - array of open list terminators 1294 * @param $divdepth - array indicating how many div's are open 1295 * @param $close_paragraph - TRUE if open paragraph should be closed. 1296 * @param $close_lists - TRUE if open lists should be closed. 1297 * @param $close_divs - TRUE if open div's should be closed. 1298 */ 1299 /* private */ 1300 //* 1301 function close_blocks(&$data, &$in_paragraph, &$listbeg, &$divdepth, $close_paragraph, $close_lists, $close_divs) 1302 { 1303 1304 $closed = 0; // Set to non-zero if something has been closed out 1305 // Close the paragraph if inside one. 1306 if ($close_paragraph && $in_paragraph) { 1307 $data .= "</p>\n"; 1308 $in_paragraph = 0; 1309 $closed++; 1310 } 1311 // Close open lists 1312 if ($close_lists) { 1313 while (count($listbeg)) { 1314 $data .= array_shift($listbeg); 1315 $closed++; 1316 } 1317 } 1318 1319 // Close open divs 1320 if ($close_divs) { 1321 $temp_max = count($divdepth); 1322 for ($i = 1; $i <= $temp_max; $i++) { 1323 $data .= '</div>'; 1324 $closed++; 1325 } 1326 } 1327 1328 return $closed; 1329 } 1330 1331 // Transitional wrapper over WikiParser_Parsable::parse() 1332 //PARSEDATA 1333 // options defaults : is_html => false, absolute_links => false, language => '' 1334 //* 1335 function parse_data($data, $option = []) 1336 { 1337 return (new WikiParser_Parsable($data))->parse($option); 1338 } 1339 1340 /** 1341 * Used as preg_replace_callback methods within in the parse_data() method to escape color style attributes when 1342 * generating HTML for the wiki color syntax - e.g. ~~#909:text~~ 1343 * 1344 * @param $matches 1345 * @return string 1346 */ 1347 protected function colorAttrEscape($matches) 1348 { 1349 $matches[1] = trim($matches[1]); 1350 $matches[3] = trim($matches[3]); 1351 1352 $esc = new Zend\Escaper\Escaper(); 1353 $color = ! empty($matches[1]) ? 'color:' . str_replace('#', '#', $esc->escapeHtmlAttr($matches[1])) : ''; 1354 $background = ! empty($matches[3]) ? 'background-color:' . str_replace('#', '#', $esc->escapeHtmlAttr($matches[3])) : ''; 1355 $semi = ! empty($color) && ! empty($background) ? '; ' : ''; 1356 $text = ! empty($matches[4]) ? $matches[4] : ''; 1357 return '<span style="' . $color . $semi . $background . '">' . $text . '</span>'; 1358 } 1359 1360 /** 1361 * 1362 * intended for use within wiki plugins. This option preserves opening and closing whitespace that renders into 1363 * annoying <p> tags when parsed, and also respects HTML rendering preferences. 1364 * 1365 * @param $data string wiki/html to be parsed 1366 * @param $optionsOverride array options to override the current defaults 1367 * @param $inlineFirstP boolean If the returned data starts with a <p>, this option will force it to display as inline:block 1368 * useful when the returned data is required to display without adding overhead spacing caused by <p> 1369 * 1370 * @return string parsed data 1371 */ 1372 function parse_data_plugin($data, $inlineFirstP = false, $optionsOverride = []) 1373 { 1374 $options['is_html'] = ($GLOBALS['prefs']['feature_wiki_allowhtml'] === 'y' && $GLOBALS['info']['is_html'] == true) ? true : false; 1375 1376 foreach ($optionsOverride as $name => $value) { 1377 $options[$name] = $value; 1378 } 1379 1380 // record initial whitespace 1381 preg_match('(^\s*)', $data, $bwhite); 1382 preg_match('(\s*$)', $data, $ewhite); 1383 1384 // remove all the whitespace 1385 $data = trim($data); 1386 $data = $this->parse_data($data, $options); 1387 // remove whitespace that was added while parsing (yes it does happen) 1388 $data = trim($data); 1389 1390 if ($inlineFirstP) { 1391 $data = preg_replace('/^(\s*?)<p>/', '$1<p style="display:inline-block">', ' ' . $data); 1392 } 1393 1394 // add original whitespace back to preserve spacing 1395 return ($bwhite[0] . $data . $ewhite[0]); 1396 } 1397 1398 /** Simpler and faster parse than parse_data() 1399 * This is only called from the parse Smarty modifier, for preference definitions. 1400 */ 1401 function parse_data_simple($data) 1402 { 1403 $data = $this->parse_data_wikilinks($data, true); 1404 $data = $this->parse_data_externallinks($data, true); 1405 $data = $this->parse_data_inline_syntax($data); 1406 if ($this->option['typography'] && ! $this->option['ck_editor']) { 1407 $data = typography($data, $this->option['language']); 1408 } 1409 1410 return $data; 1411 } 1412 1413 //* 1414 protected function parse_data_wikilinks($data, $simple_wiki, $ck_editor = false) //TODO: need a wikilink handler 1415 { 1416 global $page_regex, $prefs; 1417 1418 // definitively put out the protected words ))protectedWord(( 1419 if ($prefs['feature_wikiwords'] == 'y') { 1420 preg_match_all("/\)\)(\S+?)\(\(/", $data, $matches); 1421 $noParseWikiLinksK = []; 1422 $noParseWikiLinksT = []; 1423 foreach ($matches[0] as $mi => $match) { 1424 do { 1425 $randNum = chr(0xff) . rand(0, 1048576) . chr(0xff); 1426 } while (strstr($data, $randNum)); 1427 $data = str_replace($match, $randNum, $data); 1428 $noParseWikiLinksK[] = $randNum; 1429 $noParseWikiLinksT[] = $matches[1][$mi]; 1430 } 1431 } 1432 1433 // Links with description 1434 preg_match_all("/\(([a-z0-9-]+)?\(($page_regex)\|([^\)]*?)\)\)/", $data, $pages); 1435 1436 $temp_max = count($pages[1]); 1437 for ($i = 0; $i < $temp_max; $i++) { 1438 $exactMatch = $pages[0][$i]; 1439 $description = $pages[6][$i]; 1440 $anchor = null; 1441 1442 if ($description && $description{0} == '#') { 1443 $temp = $description; 1444 $anchor = strtok($temp, '|'); 1445 $description = strtok('|'); 1446 } 1447 1448 $replacement = $this->get_wiki_link_replacement($pages[2][$i], ['description' => $description,'reltype' => $pages[1][$i],'anchor' => $anchor], $ck_editor); 1449 1450 $data = str_replace($exactMatch, $replacement, $data); 1451 } 1452 1453 // Wiki page syntax without description 1454 preg_match_all("/\(([a-z0-9-]+)?\( *($page_regex) *\)\)/", $data, $pages); 1455 1456 foreach ($pages[2] as $idx => $page_parse) { 1457 $exactMatch = $pages[0][$idx]; 1458 $replacement = $this->get_wiki_link_replacement($page_parse, [ 'reltype' => $pages[1][$idx] ], $ck_editor); 1459 1460 $data = str_replace($exactMatch, $replacement, $data); 1461 } 1462 1463 // Links to internal pages 1464 // If they are parenthesized then don't treat as links 1465 // Prevent ))PageName(( from being expanded \"\' 1466 //[A-Z][a-z0-9_\-]+[A-Z][a-z0-9_\-]+[A-Za-z0-9\-_]* 1467 if ($prefs['feature_wiki'] == 'y' && $prefs['feature_wikiwords'] == 'y') { 1468 if (! $simple_wiki) { 1469 // The first part is now mandatory to prevent [Foo|MyPage] from being converted! 1470 if ($prefs['feature_wikiwords_usedash'] == 'y') { 1471 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); 1472 } else { 1473 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); 1474 } 1475 //TODO to have a real utf8 Wikiword where the capitals can be a utf8 capital 1476 $words = ($prefs['feature_hotwords'] == 'y') ? $this->get_hotwords() : []; 1477 foreach (array_unique($pages[1]) as $page_parse) { 1478 if (! array_key_exists($page_parse, $words)) { // If this is not a hotword 1479 $repl = $this->get_wiki_link_replacement($page_parse, ['plural' => $prefs['feature_wiki_plurals'] == 'y'], $ck_editor); 1480 1481 $data = preg_replace("/(?<=[ \n\t\r\,\;]|^)$page_parse(?=$|[ \n\t\r\,\;\.])/", "$1" . $repl . "$2", $data); 1482 } 1483 } 1484 } 1485 1486 // Reinsert ))Words(( 1487 $data = str_replace($noParseWikiLinksK, $noParseWikiLinksT, $data); 1488 } 1489 1490 return $data; 1491 } 1492 1493 protected function parse_data_externallinks($data, $suppress_icons = false) 1494 { 1495 global $prefs; 1496 $tikilib = TikiLib::lib('tiki'); 1497 1498 // ***** 1499 // This section handles external links of the form [url] and such. 1500 // ***** 1501 1502 $links = $tikilib->get_links($data); 1503 $notcachedlinks = $tikilib->get_links_nocache($data); 1504 $cachedlinks = array_diff($links, $notcachedlinks); 1505 $tikilib->cache_links($cachedlinks); 1506 1507 // Note that there're links that are replaced 1508 foreach ($links as $link) { 1509 $target = ''; 1510 $class = 'class="wiki"'; 1511 $ext_icon = ''; 1512 $rel = ''; 1513 1514 if ($prefs['popupLinks'] == 'y') { 1515 $target = 'target="_blank"'; 1516 } 1517 if (! strstr($link, '://')) { 1518 $target = ''; 1519 } else { 1520 $class = 'class="wiki external"'; 1521 if ($prefs['feature_wiki_ext_icon'] == 'y' && ! ($this->option['suppress_icons'] || $suppress_icons)) { 1522 $smarty = TikiLib::lib('smarty'); 1523 include_once('lib/smarty_tiki/function.icon.php'); 1524 $ext_icon = smarty_function_icon(['name' => 'link-external'], $smarty->getEmptyInternalTemplate()); 1525 } 1526 $rel = 'external'; 1527 if ($prefs['feature_wiki_ext_rel_nofollow'] == 'y') { 1528 $rel .= ' nofollow'; 1529 } 1530 } 1531 1532 // The (?<!\[) stuff below is to give users an easy way to 1533 // enter square brackets in their output; things like [[foo] 1534 // get rendered as [foo]. -rlpowell 1535 1536 if ($prefs['cachepages'] == 'y' && $tikilib->is_cached($link)) { 1537 //use of urlencode for using cached versions of dynamic sites 1538 $cosa = "<a class=\"wikicache\" target=\"_blank\" href=\"tiki-view_cache.php?url=" . urlencode($link) . "\">(cache)</a>"; 1539 1540 $link2 = str_replace("/", "\/", preg_quote($link)); 1541 $pattern = "/(?<!\[)\[$link2\|([^\]\|]+)\|([^\]\|]+)\|([^\]]+)\]/"; //< last param expected here is always nocache 1542 $data = preg_replace($pattern, "<a $class $target href=\"$link\" rel=\"$2 $rel\">$1</a>$ext_icon", $data); 1543 $pattern = "/(?<!\[)\[$link2\|([^\]\|]+)\|([^\]]+)\]/";//< last param here ($2) is used for relation (rel) attribute (e.g. shadowbox) or nocache 1544 preg_match($pattern, $data, $matches); 1545 if (isset($matches[2]) && $matches[2] == 'nocache') { 1546 $data = preg_replace($pattern, "<a $class $target href=\"$link\" rel=\"$rel\">$1</a>$ext_icon", $data); 1547 } else { 1548 $data = preg_replace($pattern, "<a $class $target href=\"$link\" rel=\"$rel\" data-box=\"$2\">$1</a>$ext_icon $cosa", $data); 1549 } 1550 $pattern = "/(?<!\[)\[$link2\|([^\]\|]+)\]/"; 1551 $data = preg_replace($pattern, "<a $class $target href=\"$link\" rel=\"$rel\">$1</a>$ext_icon $cosa", $data); 1552 $pattern = "/(?<!\[)\[$link2\]/"; 1553 $data = preg_replace($pattern, "<a $class $target href=\"$link\" rel=\"$rel\">$link</a>$ext_icon $cosa", $data); 1554 } else { 1555 $link2 = str_replace("/", "\/", preg_quote($link)); 1556 $link = trim($link); 1557 $link = str_replace('"', '%22', $link); 1558 $data = str_replace("|nocache", "", $data); 1559 1560 $pattern = "/(?<!\[)\[$link2\|([^\]\|]+)\|([^\]]+)\]/"; 1561 $data = preg_replace_callback($pattern, function ($matches) use ($class, $target, $link, $rel, $ext_icon) { 1562 return "<a $class $target href=\"$link\" rel=\"$rel\" data-box=\"" . str_replace('"', '%22', $matches[1]) . "\">{$matches[1]}</a>$ext_icon"; 1563 }, $data); 1564 $pattern = "/(?<!\[)\[$link2\|([^\]\|]+)([^\]])*\]/"; 1565 $data = preg_replace($pattern, "<a $class $target href=\"$link\" rel=\"$rel\">$1</a>$ext_icon", $data); 1566 $pattern = "/(?<!\[)\[$link2\|?\]/"; 1567 $data = preg_replace($pattern, "<a $class $target href=\"$link\" rel=\"$rel\">$link</a>$ext_icon", $data); 1568 } 1569 } 1570 1571 // Handle double square brackets. to display [foo] use [[foo] -rlpowell. Improved by sylvieg to avoid replacing them in [[code]] cases. 1572 if (empty($this->option['process_double_brackets']) || $this->option['process_double_brackets'] != 'n') { 1573 $data = preg_replace("/\[\[([^\]]*)\](?!\])/", "[$1]", $data); 1574 $data = preg_replace("/\[\[([^\]]*)$/", "[$1", $data); 1575 } 1576 1577 return $data; 1578 } 1579 1580 //* 1581 protected function parse_data_inline_syntax($line, $words = null, $ck_editor = false) 1582 { 1583 global $prefs; 1584 1585 // Replace monospaced text 1586 $line = preg_replace("/(^|\s)-\+(.*?)\+-/", "$1<code>$2</code>", $line); 1587 // Replace bold text 1588 $line = preg_replace("/__(.*?)__/", "<strong>$1</strong>", $line); 1589 // Replace italic text 1590 $line = preg_replace("/\'\'(.*?)\'\'/", "<em>$1</em>", $line); 1591 1592 if (! $ck_editor) { 1593 if ($prefs['feature_hotwords'] == 'y') { 1594 // Replace Hotwords before begin 1595 $line = $this->replace_hotwords($line, $words); 1596 } 1597 1598 // Make plain URLs clickable hyperlinks 1599 if ($prefs['feature_autolinks'] == 'y') { 1600 $line = $this->autolinks($line); 1601 } 1602 } 1603 1604 if (! $ck_editor) { 1605 // Replace definition lists 1606 $line = preg_replace("/^;([^:]*):([^\/\/].*)/", "<dl><dt>$1</dt><dd>$2</dd></dl>", $line); 1607 $line = preg_replace("/^;(<a [^<]*<\/a>):([^\/\/].*)/", "<dl><dt>$1</dt><dd>$2</dd></dl>", $line); 1608 } 1609 1610 return $line; 1611 } 1612 1613 //* 1614 protected function parse_data_tables($data) 1615 { 1616 global $prefs; 1617 1618 // pretty trackers use pipe for output|template specification, so we need to escape 1619 $data = preg_replace('/{\$f_(\w+)\|(output|template:.*?)}/i', '{\$f_$1-escapedpipe-$2}', $data); 1620 1621 /* 1622 * Wiki Tables syntax 1623 */ 1624 // tables in old style 1625 if ($prefs['feature_wiki_tables'] != 'new') { 1626 if (preg_match_all("/\|\|(.*)\|\|/", $data, $tables)) { 1627 $maxcols = 1; 1628 $cols = []; 1629 $temp_max = count($tables[0]); 1630 for ($i = 0; $i < $temp_max; $i++) { 1631 $rows = explode('||', $tables[0][$i]); 1632 $temp_max2 = count($rows); 1633 for ($j = 0; $j < $temp_max2; $j++) { 1634 $cols[$i][$j] = explode('|', $rows[$j]); 1635 if (count($cols[$i][$j]) > $maxcols) { 1636 $maxcols = count($cols[$i][$j]); 1637 } 1638 } 1639 } // for ($i ... 1640 1641 $temp_max3 = count($tables[0]); 1642 for ($i = 0; $i < $temp_max3; $i++) { 1643 $repl = '<table class="wikitable table table-striped table-hover">'; 1644 1645 $temp_max4 = count($cols[$i]); 1646 for ($j = 0; $j < $temp_max4; $j++) { 1647 $ncols = count($cols[$i][$j]); 1648 1649 if ($ncols == 1 && ! $cols[$i][$j][0]) { 1650 continue; 1651 } 1652 1653 $repl .= '<tr>'; 1654 1655 for ($k = 0; $k < $ncols; $k++) { 1656 $repl .= '<td class="wikicell" '; 1657 1658 if ($k == $ncols - 1 && $ncols < $maxcols) { 1659 $repl .= ' colspan="' . ($maxcols - $k) . '"'; 1660 } 1661 1662 $repl .= '>' . $cols[$i][$j][$k] . '</td>'; 1663 } // // for ($k ... 1664 1665 $repl .= '</tr>'; 1666 } // for ($j ... 1667 1668 $repl .= '</table>'; 1669 $data = str_replace($tables[0][$i], $repl, $data); 1670 } // for ($i ... 1671 } // if (preg_match_all("/\|\|(.*)\|\|/", $data, $tables)) 1672 } else { 1673 // New syntax for tables 1674 // REWRITE THIS CODE 1675 if (preg_match_all("/\|\|(.*?)\|\|/s", $data, $tables)) { 1676 $maxcols = 1; 1677 $cols = []; 1678 $temp_max5 = count($tables[0]); 1679 for ($i = 0; $i < $temp_max5; $i++) { 1680 $rows = preg_split("/(\n|\<br\/\>)/", $tables[0][$i]); 1681 $col[$i] = []; 1682 $temp_max6 = count($rows); 1683 for ($j = 0; $j < $temp_max6; $j++) { 1684 $rows[$j] = str_replace('||', '', $rows[$j]); 1685 $cols[$i][$j] = explode('|', $rows[$j]); 1686 if (count($cols[$i][$j]) > $maxcols) { 1687 $maxcols = count($cols[$i][$j]); 1688 } 1689 } 1690 } 1691 1692 $temp_max7 = count($tables[0]); 1693 for ($i = 0; $i < $temp_max7; $i++) { 1694 $repl = '<table class="wikitable table table-striped table-hover">'; 1695 $temp_max8 = count($cols[$i]); 1696 for ($j = 0; $j < $temp_max8; $j++) { 1697 $ncols = count($cols[$i][$j]); 1698 1699 if ($ncols == 1 && ! $cols[$i][$j][0]) { 1700 continue; 1701 } 1702 1703 $repl .= '<tr>'; 1704 1705 for ($k = 0; $k < $ncols; $k++) { 1706 $repl .= '<td class="wikicell" '; 1707 if ($k == $ncols - 1 && $ncols < $maxcols) { 1708 $repl .= ' colspan="' . ($maxcols - $k) . '"'; 1709 } 1710 1711 $repl .= '>' . $cols[$i][$j][$k] . '</td>'; 1712 } 1713 $repl .= '</tr>'; 1714 } 1715 $repl .= '</table>'; 1716 $data = str_replace($tables[0][$i], $repl, $data); 1717 } 1718 } 1719 } 1720 1721 // unescape the pipes for pretty tracker 1722 $data = preg_replace('/{\$f_(\w+)-escapedpipe-(output|template:.*?)}/i', '{\$f_$1|$2}', $data); 1723 1724 return $data; 1725 } 1726 1727 //* 1728 function parse_wiki_argvariable(&$data) 1729 { 1730 global $prefs, $user; 1731 $tikilib = TikiLib::lib('tiki'); 1732 $smarty = TikiLib::lib('smarty'); 1733 1734 if ($prefs['feature_wiki_argvariable'] == 'y' && ! $this->option['ck_editor']) { 1735 if (preg_match_all("/\\{\\{((\w+)(\\|([^\\}]*))?)\\}\\}/", $data, $args, PREG_SET_ORDER)) { 1736 $needles = []; 1737 $replacements = []; 1738 1739 foreach ($args as $arg) { 1740 $value = isset($arg[4]) ? $arg[4] : ''; 1741 $name = $arg[2]; 1742 switch ($name) { 1743 case 'user': 1744 $value = $user; 1745 break; 1746 case 'page': 1747 $value = $this->option['page']; 1748 break; 1749 case 'pageid': 1750 if ($_REQUEST['page'] != null) { 1751 $value = $tikilib->get_page_id_from_name($_REQUEST['page']); 1752 break; 1753 } else { 1754 $value = ''; 1755 break; 1756 } 1757 case 'domain': 1758 if ($smarty->getTemplateVars('url_host') != null) { 1759 $value = $smarty->getTemplateVars('url_host'); 1760 break; 1761 } else { 1762 $value = ''; 1763 break; 1764 } 1765 case 'domainslash': 1766 if ($smarty->getTemplateVars('url_host') != null) { 1767 $value = $smarty->getTemplateVars('url_host') . '/'; 1768 break; 1769 } else { 1770 $value = ''; 1771 break; 1772 } 1773 case 'domainslash_if_multitiki': 1774 if (is_file('db/virtuals.inc')) { 1775 $virtuals = array_map('trim', file('db/virtuals.inc')); 1776 } 1777 if ($virtuals && $smarty->getTemplateVars('url_host') != null) { 1778 $value = $smarty->getTemplateVars('url_host') . '/'; 1779 break; 1780 } else { 1781 $value = ''; 1782 break; 1783 } 1784 case 'lastVersion': 1785 $histlib = TikiLib::lib('hist'); 1786 // 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); 1787 $history = $histlib->get_page_history($this->option['page'], false, 0, 1); 1788 if ($history[0]['version'] != null) { 1789 $value = $history[0]['version']; 1790 break; 1791 } else { 1792 $value = ''; 1793 break; 1794 } 1795 case 'lastAuthor': 1796 $histlib = TikiLib::lib('hist'); 1797 1798 // 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); 1799 $history = $histlib->get_page_history($this->option['page'], false, 0, 1); 1800 if ($history[0]['user'] != null) { 1801 if ($prefs['user_show_realnames'] == 'y') { 1802 $value = TikiLib::lib('user')->clean_user($history[0]['user']); 1803 break; 1804 } else { 1805 $value = $history[0]['user']; 1806 break; 1807 } 1808 } else { 1809 $value = ''; 1810 break; 1811 } 1812 case 'lastModif': 1813 $histlib = TikiLib::lib('hist'); 1814 // 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); 1815 $history = $histlib->get_page_history($this->option['page'], false, 0, 1); 1816 if ($history[0]['lastModif'] != null) { 1817 $value = $tikilib->get_short_datetime($history[0]['lastModif']); 1818 break; 1819 } else { 1820 $value = ''; 1821 break; 1822 } 1823 case 'lastItemVersion': 1824 $trklib = TikiLib::lib('trk'); 1825 $auto_query_args = ['itemId']; 1826 if (! empty($_REQUEST['itemId'])) { 1827 $item_info = $trklib->get_item_info($_REQUEST['itemId']); 1828 $itemObject = Tracker_Item::fromInfo($item_info); 1829 if (! $itemObject->canView()) { 1830 $smarty->assign('errortype', 401); 1831 $smarty->assign('msg', tra('You do not have permission to view this information from this tracker.')); 1832 $smarty->display('error.tpl'); 1833 die; 1834 } 1835 $fieldId = empty($_REQUEST['fieldId']) ? 0 : $_REQUEST['fieldId']; 1836 $filter = []; 1837 if (! empty($_REQUEST['version'])) { 1838 $filter['version'] = $_REQUEST['version']; 1839 } 1840 $offset = empty($_REQUEST['offset']) ? 0 : $_REQUEST['offset']; 1841 $history = $trklib->get_item_history($item_info, $fieldId, $filter, $offset, $prefs['maxRecords']); 1842 1843 $value = $history['data'][0]['version']; 1844 break; 1845 } else { 1846 $value = ''; 1847 break; 1848 } 1849 case 'lastItemAuthor': 1850 $trklib = TikiLib::lib('trk'); 1851 $auto_query_args = ['itemId']; 1852 if (! empty($_REQUEST['itemId'])) { 1853 $item_info = $trklib->get_item_info($_REQUEST['itemId']); 1854 $itemObject = Tracker_Item::fromInfo($item_info); 1855 if (! $itemObject->canView()) { 1856 $smarty->assign('errortype', 401); 1857 $smarty->assign('msg', tra('You do not have permission to view this information from this tracker.')); 1858 $smarty->display('error.tpl'); 1859 die; 1860 } 1861 if ($item_info['lastModifBy'] != null) { 1862 if ($prefs['user_show_realnames'] == 'y') { 1863 $value = TikiLib::lib('user')->clean_user($item_info['lastModifBy']); 1864 break; 1865 } else { 1866 $value = $item_info['lastModifBy']; 1867 break; 1868 } 1869 } 1870 break; 1871 } else { 1872 $value = ''; 1873 break; 1874 } 1875 case 'lastItemModif': 1876 $trklib = TikiLib::lib('trk'); 1877 $auto_query_args = ['itemId']; 1878 if (! empty($_REQUEST['itemId'])) { 1879 $item_info = $trklib->get_item_info($_REQUEST['itemId']); 1880 $itemObject = Tracker_Item::fromInfo($item_info); 1881 if (! $itemObject->canView()) { 1882 $smarty->assign('errortype', 401); 1883 $smarty->assign('msg', tra('You do not have permission to view this information from this tracker.')); 1884 $smarty->display('error.tpl'); 1885 die; 1886 } 1887 $value = $tikilib->get_short_datetime($item_info['lastModif']); 1888 break; 1889 } else { 1890 $value = ''; 1891 break; 1892 } 1893 case 'lastApprover': 1894 global $prefs, $user; 1895 $tikilib = TikiLib::lib('tiki'); 1896 1897 if ($prefs['flaggedrev_approval'] == 'y') { 1898 $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); 1899 1900 if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { 1901 if ($version_info = $flaggedrevisionlib->get_version_with($this->option['page'], 'moderation', 'OK')) { 1902 if ($this->content_to_render === null) { 1903 $revision_displayed = $version_info['version']; 1904 $approval = $flaggedrevisionlib->find_approval_information($this->option['page'], $revision_displayed); 1905 } 1906 } 1907 } 1908 } 1909 1910 if (! empty($approval['user'])) { 1911 if ($prefs['user_show_realnames'] == 'y') { 1912 $value = TikiLib::lib('user')->clean_user($approval['user']); 1913 break; 1914 } else { 1915 $value = $approval['user']; 1916 break; 1917 } 1918 } else { 1919 $value = ''; 1920 break; 1921 } 1922 case 'lastApproval': 1923 global $prefs, $user; 1924 $tikilib = TikiLib::lib('tiki'); 1925 1926 if ($prefs['flaggedrev_approval'] == 'y') { 1927 $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); 1928 1929 if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { 1930 if ($version_info = $flaggedrevisionlib->get_version_with($this->option['page'], 'moderation', 'OK')) { 1931 if ($this->content_to_render === null) { 1932 $revision_displayed = $version_info['version']; 1933 $approval = $flaggedrevisionlib->find_approval_information($this->option['page'], $revision_displayed); 1934 } 1935 } 1936 } 1937 } 1938 1939 if ($approval['lastModif'] != null) { 1940 $value = $tikilib->get_short_datetime($approval['lastModif']); 1941 break; 1942 } else { 1943 $value = ''; 1944 break; 1945 } 1946 case 'lastApprovedVersion': 1947 global $prefs, $user; 1948 $tikilib = TikiLib::lib('tiki'); 1949 1950 if ($prefs['flaggedrev_approval'] == 'y') { 1951 $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); 1952 1953 if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { 1954 $version_info = $flaggedrevisionlib->get_version_with($this->option['page'], 'moderation', 'OK'); 1955 } 1956 } 1957 1958 if ($version_info['version'] != null) { 1959 $value = $version_info['version']; 1960 break; 1961 } else { 1962 $value = ''; 1963 break; 1964 } 1965 case 'currentVersion': 1966 if (isset($_REQUEST['preview'])) { 1967 $value = (int)$_REQUEST["preview"]; 1968 break; 1969 } elseif (isset($_REQUEST['version'])) { 1970 $value = (int)$_REQUEST["version"]; 1971 break; 1972 } elseif ($prefs['flaggedrev_approval'] == 'y' && ! isset($_REQUEST['latest'])) { 1973 $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); 1974 1975 if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { 1976 $version_info = $flaggedrevisionlib->get_version_with($this->option['page'], 'moderation', 'OK'); 1977 } 1978 if ($version_info['version'] != null) { 1979 $value = $version_info['version']; 1980 break; 1981 } 1982 } else { 1983 $histlib = TikiLib::lib('hist'); 1984 // 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); 1985 $history = $histlib->get_page_history($this->option['page'], false, 0, 1); 1986 if ($history[0]['version'] != null) { 1987 $value = $history[0]['version']; 1988 break; 1989 } else { 1990 $value = ''; 1991 break; 1992 } 1993 } 1994 case 'currentVersionApprover': 1995 global $prefs, $user; 1996 $tikilib = TikiLib::lib('tiki'); 1997 1998 if ($prefs['flaggedrev_approval'] == 'y') { 1999 $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); 2000 if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { 2001 if ($versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK')) { 2002 if (isset($_REQUEST['preview'])) { 2003 $revision_displayed = (int)$_REQUEST["preview"]; 2004 } elseif (isset($_REQUEST['version'])) { 2005 $revision_displayed = (int)$_REQUEST["version"]; 2006 } elseif (isset($_REQUEST['latest'])) { 2007 $revision_displayed = null; 2008 } else { 2009 $versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK'); 2010 $revision_displayed = $versions_info[0]; 2011 } 2012 2013 if ($this->content_to_render === null) { 2014 $approval = $flaggedrevisionlib->find_approval_information($this->option['page'], $revision_displayed); 2015 } 2016 } 2017 } 2018 } 2019 2020 if (! empty($approval['user'])) { 2021 if ($prefs['user_show_realnames'] == 'y') { 2022 $value = TikiLib::lib('user')->clean_user($approval['user']); 2023 break; 2024 } else { 2025 $value = $approval['user']; 2026 break; 2027 } 2028 } else { 2029 $value = ''; 2030 break; 2031 } 2032 case 'currentVersionApproval': 2033 global $prefs, $user; 2034 $tikilib = TikiLib::lib('tiki'); 2035 2036 if ($prefs['flaggedrev_approval'] == 'y') { 2037 $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); 2038 2039 if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { 2040 if ($versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK')) { 2041 if (isset($_REQUEST['preview'])) { 2042 $revision_displayed = (int)$_REQUEST["preview"]; 2043 } elseif (isset($_REQUEST['version'])) { 2044 $revision_displayed = (int)$_REQUEST["version"]; 2045 } elseif (isset($_REQUEST['latest'])) { 2046 $revision_displayed = null; 2047 } else { 2048 $versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK'); 2049 $revision_displayed = $versions_info[0]; 2050 } 2051 2052 if ($this->content_to_render === null) { 2053 $approval = $flaggedrevisionlib->find_approval_information($this->option['page'], $revision_displayed); 2054 } 2055 } 2056 } 2057 } 2058 2059 if ($approval['lastModif'] != null) { 2060 $value = $tikilib->get_short_datetime($approval['lastModif']); 2061 break; 2062 } else { 2063 $value = ''; 2064 break; 2065 } 2066 case 'currentVersionApproved': 2067 global $prefs, $user; 2068 $tikilib = TikiLib::lib('tiki'); 2069 2070 if ($prefs['flaggedrev_approval'] == 'y') { 2071 $flaggedrevisionlib = TikiLib::lib('flaggedrevision'); 2072 2073 if ($flaggedrevisionlib->page_requires_approval($this->option['page'])) { 2074 //$versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK'); 2075 if (isset($_REQUEST['preview'])) { 2076 $revision_displayed = (int)$_REQUEST["preview"]; 2077 } elseif (isset($_REQUEST['version'])) { 2078 $revision_displayed = (int)$_REQUEST["version"]; 2079 } elseif (isset($_REQUEST['latest'])) { 2080 $revision_displayed = null; 2081 } else { 2082 $versions_info = $flaggedrevisionlib->get_versions_with($this->option['page'], 'moderation', 'OK'); 2083 $revision_displayed = $versions_info[0]; 2084 } 2085 } 2086 } 2087 2088 if ($revision_displayed != null && $approval = $flaggedrevisionlib->find_approval_information($this->option['page'], $revision_displayed)) { 2089 $value = tr("yes"); 2090 break; 2091 } else { 2092 $value = tr("no"); 2093 break; 2094 } 2095 case 'cat': 2096 if (empty($_GET['cat']) && ! empty($_REQUEST['organicgroup']) && ! empty($this->option['page'])) { 2097 $utilities = new \Tiki\Package\Extension\Utilities(); 2098 if ($folder = $utilities->getFolderFromObject('wiki page', $this->option['page'])) { 2099 $ogname = $folder . '_' . $_REQUEST['organicgroup']; 2100 $cat = TikiLib::lib('categ')->get_category_id($ogname); 2101 $value = $cat; 2102 } else { 2103 $value = ''; 2104 } 2105 } elseif (! empty($_GET['cat'])) { 2106 $value = $_GET['cat']; 2107 } else { 2108 $value = ''; 2109 } 2110 break; 2111 default: 2112 if (isset($_GET[$name])) { 2113 $value = $_GET[$name]; 2114 } else { 2115 $value = ''; 2116 include_once('lib/wiki-plugins/wikiplugin_showpref.php'); 2117 if ($prefs['wikiplugin_showpref'] == 'y' && $showpref = wikiplugin_showpref('', ['pref' => $name])) { 2118 $value = $showpref; 2119 } 2120 } 2121 break; 2122 } 2123 2124 $needles[] = $arg[0]; 2125 $replacements[] = $value; 2126 } 2127 $data = str_replace($needles, $replacements, $data); 2128 } 2129 } 2130 } 2131 2132 //* 2133 protected function parse_data_dynamic_variables($data, $lang = null) 2134 { 2135 global $tiki_p_edit_dynvar, $prefs; 2136 2137 $enclose = '%'; 2138 if ($prefs['wiki_dynvar_style'] == 'disable') { 2139 return $data; 2140 } elseif ($prefs['wiki_dynvar_style'] == 'double') { 2141 $enclose = '%%'; 2142 } 2143 2144 // Replace dynamic variables 2145 // Dynamic variables are similar to dynamic content but they are editable 2146 // from the page directly, intended for short data, not long text but text 2147 // will work too 2148 // Now won't match HTML-style '%nn' letter codes and some special utf8 situations... 2149 if (preg_match_all("/[^%]$enclose([^% 0-9A-Z][^% 0-9A-Z][^% ]*){$enclose}[^%]/", $data, $dvars)) { 2150 // remove repeated elements 2151 $dvars = array_unique($dvars[1]); 2152 // Now replace each dynamic variable by a pair composed of the 2153 // variable value and a text field to edit the variable. Each 2154 foreach ($dvars as $dvar) { 2155 $value = $this->get_dynamic_variable($dvar, $lang); 2156 //replace backslash with html entity to avoid losing backslashes in the preg_replace function below 2157 $value = str_replace('\\', '\', $value); 2158 // Now build 2 divs 2159 $id = 'dyn_' . $dvar; 2160 2161 if (isset($tiki_p_edit_dynvar)&& $tiki_p_edit_dynvar == 'y') { 2162 $span1 = "<span style='display:inline;' id='dyn_" . $dvar . "_display'><a class='dynavar' onclick='javascript:toggle_dynamic_var(\"$dvar\");' title='" . tra('Click to edit dynamic variable', '', true) . ": $dvar'>$value</a></span>"; 2163 $span2 = "<span style='display:none;' id='dyn_" . $dvar . "_edit'><input type='text' class='input-sm' name='dyn_" . $dvar . "' value='" . $value . "' />" . '<input type="submit" class="btn btn-primary btn-sm" name="_dyn_update" value="' . tra('Update variable', '', true) . '"/></span>'; 2164 } else { 2165 $span1 = "<span class='dynavar' style='display:inline;' id='dyn_" . $dvar . "_display'>$value</span>"; 2166 $span2 = ''; 2167 } 2168 $html = $span1 . $span2; 2169 //It's important to replace only once 2170 $dvar_preg = preg_quote($dvar); 2171 $data = preg_replace("+$enclose$dvar_preg$enclose+", $html, $data, 1); 2172 //Further replacements only with the value 2173 $data = str_replace("$enclose$dvar$enclose", $value, $data); 2174 } 2175 } 2176 2177 return $data; 2178 } 2179 2180 //* 2181 private function get_dynamic_variable($name, $lang = null) 2182 { 2183 $tikilib = TikiLib::lib('tiki'); 2184 $result = $tikilib->table('tiki_dynamic_variables')->fetchAll(['data', 'lang'], ['name' => $name]); 2185 2186 $value = tr('No value assigned'); 2187 2188 foreach ($result as $row) { 2189 if ($row['lang'] == $lang) { 2190 // Exact match 2191 return $row['data']; 2192 } elseif (empty($row['lang'])) { 2193 // Universal match, keep in case no exact match 2194 $value = $row['data']; 2195 } 2196 } 2197 2198 return $value; 2199 } 2200 2201 /* This is only called by parse_data(). It does not just deal with TOC-s. */ 2202 protected function parse_data_process_maketoc(&$data, $noparsed) 2203 { 2204 2205 global $prefs; 2206 $tikilib = TikiLib::lib('tiki'); 2207 2208 // $this->makeTocCount++; Unused since Tiki 12 or earlier 2209 2210 if ($this->option['ck_editor']) { 2211 $need_maketoc = false ; 2212 } else { 2213 $need_maketoc = preg_match('/\{maketoc.*\}/', $data); 2214 } 2215 2216 // Wysiwyg or allowhtml mode {maketoc} handling when not in editor mode (i.e. viewing) 2217 if ($need_maketoc && $this->option['is_html']) { 2218 // Header needs to start at beginning of line (wysiwyg does not necessary obey) 2219 $data = $this->unprotectSpecialChars($data, true); 2220 $data = preg_replace('/<\/([a-z]+)><h([1-6])>/im', "</\\1>\n<h\\2>", $data); 2221 $data = preg_replace('/^\s+<h([1-6])>/im', "<h\\1>", $data); // headings with leading spaces 2222 $data = preg_replace('/\/><h([1-6])>/im', "/>\n<h\\1>", $data); // headings after /> tag 2223 $htmlheadersearch = '/<h([1-6])>\s*([^<]+)\s*<\/h[1-6]>/im'; 2224 preg_match_all($htmlheadersearch, $data, $htmlheaders); 2225 $nbhh = count($htmlheaders[1]); 2226 for ($i = 0; $i < $nbhh; $i++) { 2227 $htmlheaderreplace = ''; 2228 for ($j = 0; $j < $htmlheaders[1][$i]; $j++) { 2229 $htmlheaderreplace .= '!'; 2230 } 2231 $htmlheaderreplace .= $htmlheaders[2][$i]; 2232 $data = str_replace($htmlheaders[0][$i], $htmlheaderreplace, $data); 2233 } 2234 $data = $this->protectSpecialChars($data, true); 2235 } 2236 2237 $need_autonumbering = ( preg_match('/^\!+[\-\+]?#/m', $data) > 0 ); 2238 2239 $anch = []; 2240 global $anch; 2241 $pageNum = 1; 2242 2243 // Now tokenize the expression and process the tokens 2244 // Use tab and newline as tokenizing characters as well //// 2245 $lines = explode("\n", $data); 2246 if (empty($lines[count($lines) - 1]) && empty($lines[count($lines) - 2])) { 2247 array_pop($lines); 2248 } 2249 $data = ''; 2250 $listbeg = []; 2251 $divdepth = []; 2252 $hdr_structure = []; 2253 $show_title_level = []; 2254 $last_hdr = []; 2255 $nb_last_hdr = 0; 2256 $nb_hdrs = 0; 2257 $inTable = 0; 2258 $inPre = 0; 2259 $inComment = 0; 2260 $inTOC = 0; 2261 $inScript = 0; 2262 $title_text = ''; 2263 2264 // loop: process all lines 2265 $in_paragraph = 0; 2266 $in_empty_paragraph = 0; 2267 2268 foreach ($lines as $line) { 2269 // Add newlines between lines 2270 if (isset($current_title_num)) { // Exclude the first line 2271 $data .= "\n"; 2272 } 2273 2274 $current_title_num = ''; 2275 $numbering_remove = 0; 2276 2277 $line = rtrim($line); // Trim off trailing white space 2278 // Check for titlebars... 2279 // NOTE: that title bar should start at the beginning of the line and 2280 // be alone on that line to be autoaligned... otherwise, it is an old 2281 // styled title bar... 2282 if (substr(ltrim($line), 0, 2) == '-=' && substr($line, -2, 2) == '=-') { 2283 // Close open paragraph and lists, but not div's 2284 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 1, 0); 2285 // 2286 $align_len = strlen($line) - strlen(ltrim($line)); 2287 2288 // My textarea size is about 120 space chars. 2289 //define('TEXTAREA_SZ', 120); 2290 2291 // NOTE: That strict math formula (split into 3 areas) gives 2292 // bad visual effects... 2293 // $align = ($align_len < (TEXTAREA_SZ / 3)) ? "left" 2294 // : (($align_len > (2 * TEXTAREA_SZ / 3)) ? "right" : "center"); 2295 // 2296 // Going to introduce some heuristic here :) 2297 // Visualy (remember that space char is thin) center starts at 25 pos 2298 // and 'right' from 60 (HALF of full width!) -- thats all :) 2299 // 2300 // NOTE: Guess align only if more than 10 spaces before -=title=- 2301 if ($align_len > 10) { 2302 $align = ($align_len < 25) ? "left" : (($align_len > 60) ? "right" : "center"); 2303 2304 $align = ' style="text-align: ' . $align . ';"'; 2305 } else { 2306 $align = ''; 2307 } 2308 2309 // 2310 $line = trim($line); 2311 $line = '<div class="titlebar"' . $align . '>' . substr($line, 2, strlen($line) - 4) . '</div>'; 2312 $data .= $line . "\n"; 2313 // TODO: Case is handled ... no need to check other conditions 2314 // (it is apriori known that they are all false, moreover sometimes 2315 // check procedure need > O(0) of compexity) 2316 // -- continue to next line... 2317 // MUST replace all remaining parse blocks to the same logic... 2318 continue; 2319 } 2320 2321 // Replace old styled titlebars 2322 if (strlen($line) != strlen($line = preg_replace("/-=(.+?)=-/", "<div class='titlebar'>$1</div>", $line))) { 2323 // Close open paragraph, but not lists (why not?) or div's 2324 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 0, 0); 2325 $data .= $line . "\n"; 2326 2327 continue; 2328 } 2329 2330 // check if we are inside a ~hc~ block and, if so, ignore 2331 // monospaced and do not insert <br /> 2332 $lineInLowerCase = TikiLib::strtolower($this->unprotectSpecialChars($line, true)); 2333 2334 $inComment += substr_count($lineInLowerCase, "<!--"); 2335 $inComment -= substr_count($lineInLowerCase, "-->"); 2336 if ($inComment < 0) { // stop lines containing just --> being detected as comments 2337 $inComment = 0; 2338 } 2339 2340 // check if we are inside a ~pre~ block and, if so, ignore 2341 // monospaced and do not insert <br /> 2342 $inPre += substr_count($lineInLowerCase, "<pre"); 2343 $inPre -= substr_count($lineInLowerCase, "</pre"); 2344 2345 // check if we are inside a table, if so, ignore monospaced and do 2346 // not insert <br /> 2347 2348 $inTable += substr_count($lineInLowerCase, "<table"); 2349 $inTable -= substr_count($lineInLowerCase, "</table"); 2350 2351 // check if we are inside an ul TOC list, if so, ignore monospaced and do 2352 // not insert <br /> 2353 $inTOC += substr_count($lineInLowerCase, "<ul class=\"toc"); 2354 $inTOC -= substr_count($lineInLowerCase, "</ul><!--toc-->"); 2355 2356 // check if we are inside a script not insert <br /> 2357 $inScript += substr_count($lineInLowerCase, "<script "); 2358 $inScript -= substr_count($lineInLowerCase, "</script>"); 2359 2360 // If the first character is ' ' and we are not in pre then we are in pre 2361 if ($prefs['feature_wiki_monosp'] == 'y' && substr($line, 0, 1) == ' ' /* The first character is a space (' '). */ 2362 && $inTable == 0 && $inPre == 0 && $inComment == 0 && ! $this->option['is_html']) { 2363 // Close open paragraph and lists, but not div's 2364 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 1, 0); 2365 2366 // make font monospaced 2367 // For fixed formatting, use ~pp~...~/pp~ 2368 $line = '<tt>' . $line . '</tt>'; 2369 } 2370 2371 // Replace hotwords and more 2372 // 08-Jul-2003, by zaufi 2373 // HotWords will be replace only in ordinal text 2374 // It looks __really__ goofy in Headers or Titles 2375 $line = $this->parse_data_inline_syntax($line, null, $this->option['ck_editor']); 2376 2377 // This line is parseable then we have to see what we have 2378 if (substr($line, 0, 3) == '---') { 2379 // This is not a list item --- close open paragraph and lists, but not div's 2380 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 1, 0); 2381 $line = '<hr />'; 2382 } else { 2383 $litype = substr($line, 0, 1); 2384 if (($litype == '*' || $litype == '#') && ! (strlen($line) - count($listbeg) > 4 && preg_match('/^\*+$/', $line))) { 2385 // Close open paragraph, but not lists or div's 2386 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 0, 0); 2387 $listlevel = $tikilib->how_many_at_start($line, $litype); 2388 $liclose = '</li>'; 2389 $addremove = 0; 2390 if ($listlevel < count($listbeg)) { 2391 while ($listlevel != count($listbeg)) { 2392 $data .= array_shift($listbeg); 2393 } 2394 if (substr(current($listbeg), 0, 5) != '</li>') { 2395 $liclose = ''; 2396 } 2397 } elseif ($listlevel > count($listbeg)) { 2398 $listyle = ''; 2399 while ($listlevel != count($listbeg)) { 2400 array_unshift($listbeg, ($litype == '*' ? '</ul>' : '</ol>')); 2401 if ($listlevel == count($listbeg)) { 2402 $listate = substr($line, $listlevel, 1); 2403 if (($listate == '+' || $listate == '-') && ! ($litype == '*' && ! strstr(current($listbeg), '</ul>') || $litype == '#' && ! strstr(current($listbeg), '</ol>'))) { 2404 $thisid = 'id' . microtime() * 1000000; 2405 if (! $this->option['ck_editor']) { 2406 $data .= '<br /><a id="flipper' . $thisid . '" class="link" href="javascript:flipWithSign(\'' . $thisid . '\')">[' . ($listate == '-' ? '+' : '-') . ']</a>'; 2407 } 2408 $listyle = ' id="' . $thisid . '" style="display:' . ($listate == '+' || $this->option['ck_editor'] ? 'block' : 'none') . ';"'; 2409 $addremove = 1; 2410 } 2411 } 2412 $data .= ($litype == '*' ? "<ul$listyle>" : "<ol$listyle>"); 2413 } 2414 $liclose = ''; 2415 } 2416 if ($litype == '*' && ! strstr(current($listbeg), '</ul>') || $litype == '#' && ! strstr(current($listbeg), '</ol>')) { 2417 $data .= array_shift($listbeg); 2418 $listyle = ''; 2419 $listate = substr($line, $listlevel, 1); 2420 if (($listate == '+' || $listate == '-')) { 2421 $thisid = 'id' . microtime() * 1000000; 2422 if (! $this->option['ck_editor']) { 2423 $data .= '<br /><a id="flipper' . $thisid . '" class="link" href="javascript:flipWithSign(\'' . $thisid . '\')">[' . ($listate == '-' ? '+' : '-') . ']</a>'; 2424 } 2425 $listyle = ' id="' . $thisid . '" style="display:' . ($listate == '+' || $this->option['ck_editor'] ? 'block' : 'none') . ';"'; 2426 $addremove = 1; 2427 } 2428 $data .= ($litype == '*' ? "<ul$listyle>" : "<ol$listyle>"); 2429 $liclose = ''; 2430 array_unshift($listbeg, ($litype == '*' ? '</li></ul>' : '</li></ol>')); 2431 } 2432 $line = $liclose . '<li>' . substr($line, $listlevel + $addremove); 2433 if (substr(current($listbeg), 0, 5) != '</li>') { 2434 array_unshift($listbeg, '</li>' . array_shift($listbeg)); 2435 } 2436 } elseif ($litype == '+') { 2437 // Close open paragraph, but not list or div's 2438 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 0, 0); 2439 $listlevel = $tikilib->how_many_at_start($line, $litype); 2440 // Close lists down to requested level 2441 while ($listlevel < count($listbeg)) { 2442 $data .= array_shift($listbeg); 2443 } 2444 2445 // Must append paragraph for list item of given depth... 2446 $listlevel = $tikilib->how_many_at_start($line, $litype); 2447 if (count($listbeg)) { 2448 if (substr(current($listbeg), 0, 5) != '</li>') { 2449 array_unshift($listbeg, '</li>' . array_shift($listbeg)); 2450 $liclose = '<li>'; 2451 } else { 2452 $liclose = '<br />'; 2453 } 2454 } else { 2455 $liclose = ''; 2456 } 2457 $line = $liclose . substr($line, count($listbeg)); 2458 } else { 2459 // This is not a list item - close open lists, 2460 // but not paragraph or div's. If we are 2461 // closing a list, there really shouldn't be a 2462 // paragraph open anyway. 2463 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 0, 1, 0); 2464 2465 // Get count of (possible) header signs at start 2466 $hdrlevel = $tikilib->how_many_at_start($line, '!'); 2467 2468 if ($litype == '!' && $hdrlevel > 0 && $hdrlevel <= 6 /* HTML has h1 to h6, but no h7 or above */) { // If the line starts with 1 to 6 exclamation marks ("!") 2469 /* 2470 * Handle headings autonumbering syntax (i.e. !#Text, !!#Text, ...) 2471 * Note : 2472 * this needs to be done even if the current header has no '#' 2473 * in order to generate the right numbers when they are not specified for every headers. 2474 * This is the case, for example, when you want to add numbers to headers of level 2 but not to level 1 2475 */ 2476 2477 $line_lenght = strlen($line); 2478 2479 // Generate an array containing the squeleton of maketoc (based on headers levels) 2480 // i.e. hdr_structure will contain something lile this : 2481 // array( 1, 2, 2.1, 2.1.1, 2.1.2, 2.2, ... , X.Y.Z... ) 2482 // 2483 2484 $hdr_structure[$nb_hdrs] = []; 2485 2486 // Generate the number (e.g. 1.2.1.1) of the current title, based on the previous title number : 2487 // - if the current title deepest level is lesser than (or equal to) 2488 // the deepest level of the previous title : then we increment the last level number, 2489 // - else : we simply add new levels with value '1' (only if the previous level number was shown), 2490 // 2491 if ($nb_last_hdr > 0 && $hdrlevel <= $nb_last_hdr) { 2492 $hdr_structure[$nb_hdrs] = array_slice($last_hdr, 0, $hdrlevel); 2493 if (! empty($show_title_level[$hdrlevel]) || ! $need_autonumbering) { 2494 // 2495 // Increment the level number only if : 2496 // - the last title of the same level number has a displayed number 2497 // or - no title has a displayed number (no autonumbering) 2498 // 2499 $hdr_structure[$nb_hdrs][$hdrlevel - 1]++; 2500 } 2501 } else { 2502 if ($nb_last_hdr > 0) { 2503 $hdr_structure[$nb_hdrs] = $last_hdr; 2504 } 2505 for ($h = 0; $h < $hdrlevel - $nb_last_hdr; $h++) { 2506 $hdr_structure[$nb_hdrs][$h + $nb_last_hdr] = '1'; 2507 } 2508 } 2509 $show_title_level[$hdrlevel] = preg_match('/^!+[\+\-]?#/', $line); 2510 2511 // Update last_hdr info for the next header 2512 $last_hdr = $hdr_structure[$nb_hdrs]; 2513 $nb_last_hdr = count($last_hdr); 2514 2515 if (is_array($last_hdr)) { 2516 $current_title_real_num = implode('.', $last_hdr) . '. '; 2517 } else { 2518 $current_title_real_num = $last_hdr . '. '; 2519 } 2520 2521 // Update the current title number to hide all parents levels numbers if the parent has no autonumbering 2522 $hideall = false; 2523 for ($j = $hdrlevel; $j > 0; $j--) { 2524 if ($hideall || empty($show_title_level[$j])) { 2525 unset($hdr_structure[$j - 1]); 2526 $hideall = true; 2527 } 2528 } 2529 2530 // Store the title number to use only if it has to be shown (if the '#' char is used) 2531 $current_title_num = ''; 2532 if (isset($show_title_level[$hdrlevel]) && isset($hdr_structure[$nb_hdrs])) { 2533 $current_title_num = $show_title_level[$hdrlevel] ? implode('.', $hdr_structure[$nb_hdrs]) . '. ' : ''; 2534 } 2535 2536 $nb_hdrs++; 2537 2538 2539 // Close open paragraph (lists already closed above) 2540 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 0, 0); 2541 // Close lower level divs if opened 2542 for (; current($divdepth) >= $hdrlevel; array_shift($divdepth)) { 2543 $data .= '</div>'; 2544 } 2545 2546 // Remove possible hotwords replaced :) 2547 // Umm, *why*? Taking this out lets page 2548 // links in headers work, which can be nice. 2549 // -rlpowell 2550 // $line = strip_tags($line); 2551 2552 // OK. Parse headers here... 2553 $anchor = ''; 2554 $aclose = ''; 2555 $aclose2 = ''; 2556 $addremove = $show_title_level[$hdrlevel] ? 1 : 0; // If needed, also remove '#' sign from title beginning 2557 2558 // May be special signs present after '!'s? 2559 $divstate = substr($line, $hdrlevel, 1); 2560 if (($divstate == '+' || $divstate == '-') && ! $this->option['ck_editor']) { 2561 // OK. Must insert flipper after HEADER, and then open new div... 2562 $thisid = 'id' . preg_replace('/[^a-zA-z0-9]/', '', urlencode($this->option['page'])) . $nb_hdrs; 2563 require_once __DIR__ . '/../setup/cookies.php'; 2564 $state_cookie = getCookie($thisid, "showhide_headings"); 2565 if ($state_cookie === 'o' && $divstate === '-') { 2566 $divstate = '+'; 2567 } elseif ($state_cookie === 'c' && $divstate === '+') { 2568 $divstate = '-'; 2569 } 2570 $aclose = '<a id="flipper' . $thisid . '" class="link" href="#" onclick="flipWithSign(\'' . $thisid . '\');return false;">[' . ($divstate == '-' ? '+' : '-') . ']</a>'; 2571 $aclose2 = '<div id="' . $thisid . '" class="showhide_heading" style="display:' . ($divstate == '+' ? 'block' : 'none') . ';">'; 2572 $headerlib = TikiLib::lib('header'); 2573 $headerlib->add_jq_onready("setheadingstate('$thisid');"); 2574 array_unshift($divdepth, $hdrlevel); 2575 $addremove += 1; 2576 } 2577 2578 // Generate the final title text 2579 $title_text_base = substr($line, $hdrlevel + $addremove); 2580 $title_text = $current_title_num . $title_text_base; 2581 2582 // create stable anchors for all headers 2583 // use header but replace non-word character sequences 2584 // with one underscore (for XHTML 1.0 compliance) 2585 // Workaround pb with plugin replacement and header id 2586 // first we remove hash from title_text for headings beginning 2587 // with images and HTML tags 2588 $thisid = preg_replace('/§[a-z0-9]{32}§/', '', $title_text); 2589 $thisid = preg_replace('#</?[^>]+>#', '', $thisid); 2590 $thisid = preg_replace('/[^a-zA-Z0-9\:\.\-\_]+/', '_', $thisid); 2591 $thisid = preg_replace('/^[^a-zA-Z]*/', '', $thisid); 2592 if (empty($thisid)) { 2593 $thisid = 'a' . md5($title_text); 2594 } 2595 2596 // Add a number to the anchor if it already exists, to avoid duplicated anchors 2597 if (isset($all_anchors[$thisid])) { 2598 $all_anchors[$thisid]++; 2599 $thisid .= '_' . $all_anchors[$thisid]; 2600 } else { 2601 $all_anchors[$thisid] = 1; 2602 } 2603 2604 // Collect TOC entry if any {maketoc} is present on the page 2605 //if ( $need_maketoc !== false ) { 2606 $anch[] = [ 2607 'id' => $thisid, 2608 'hdrlevel' => $hdrlevel, 2609 'pagenum' => $pageNum, 2610 'title' => $title_text_base, 2611 'title_displayed_num' => $current_title_num, 2612 'title_real_num' => $current_title_real_num 2613 ]; 2614 //} 2615 global $tiki_p_edit, $section; 2616 if ($prefs['wiki_edit_section'] === 'y' && $section === 'wiki page' && $tiki_p_edit === 'y' && 2617 ( $prefs['wiki_edit_section_level'] == 0 || $hdrlevel <= $prefs['wiki_edit_section_level']) && 2618 (empty($this->option['print']) || ! $this->option['print']) && ! $this->option['suppress_icons'] ) { 2619 $smarty = TikiLib::lib('smarty'); 2620 include_once('lib/smarty_tiki/function.icon.php'); 2621 2622 if ($prefs['wiki_edit_icons_toggle'] == 'y' && ! isset($_COOKIE['wiki_plugin_edit_view'])) { 2623 $iconDisplayStyle = ' style="display:none;"'; 2624 } else { 2625 $iconDisplayStyle = ''; 2626 } 2627 $button = '<div class="icon_edit_section"' . $iconDisplayStyle . '><a title="' . tra('Edit Section') . '" href="tiki-editpage.php?'; 2628 if (! empty($this->option['page'])) { 2629 $button .= 'page=' . urlencode($this->option['page']) . '&'; 2630 } 2631 $button .= 'hdr=' . $nb_hdrs . '">' . smarty_function_icon(['name' => 'edit'], $smarty->getEmptyInternalTemplate()) . '</a></div>'; 2632 } else { 2633 $button = ''; 2634 } 2635 2636 // replace <div> with <h> style attribute 2637 $do_center = 0; 2638 $title_text = preg_replace('/<div style="text-align: center;">(.*)<\/div>/', '\1', $title_text, 1, $do_center); 2639 2640 // prevent widow words on headings - originally from http://davidwalsh.name/prevent-widows-php-javascript 2641 $title_text = preg_replace('/^\s*(.+\s+\S+)\s+(\S+)\s*$/sumU', '$1 $2', $title_text); 2642 2643 $style = $do_center ? ' style="text-align: center;"' : ''; 2644 2645 if ($prefs['wiki_heading_links'] === 'y') { 2646 $smarty = TikiLib::lib('smarty'); 2647 $smarty->loadPlugin('smarty_function_icon'); 2648 $headingLink = '<a href="#' . $thisid . '" class="heading-link">' . smarty_function_icon(['name' => 'link'], $smarty->getEmptyInternalTemplate()) . '</a>'; 2649 } else { 2650 $headingLink = ''; 2651 } 2652 2653 if ($prefs['feature_wiki_show_hide_before'] == 'y') { 2654 $line = $button . '<h' . ($hdrlevel) . $style . ' class="showhide_heading" id="' . $thisid . '">' . $aclose . ' ' . $title_text . $headingLink . '</h' . ($hdrlevel) . '>' . $aclose2; 2655 } else { 2656 $line = $button . '<h' . ($hdrlevel) . $style . ' class="showhide_heading" id="' . $thisid . '">' . $title_text . $headingLink . '</h' . ($hdrlevel) . '>' . $aclose . $aclose2; 2657 } 2658 } elseif (! strcmp($line, $prefs['wiki_page_separator'])) { 2659 // Close open paragraph, lists, and div's 2660 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 1, 1); 2661 // Leave line unchanged... tiki-index.php will split wiki here 2662 $line = $prefs['wiki_page_separator']; 2663 $pageNum += 1; 2664 } else { 2665 /** Usual paragraph. 2666 * 2667 * If the 2668 * $prefs['feature_wiki_paragraph_formatting'] 2669 * is on, then consecutive lines of 2670 * text will be gathered into a block 2671 * that is surrounded by HTML 2672 * paragraph tags. One or more blank 2673 * lines, or another special Wiki line 2674 * (e.g., heading, titlebar, etc.) 2675 * signifies the end of the 2676 * paragraph. If the paragraph 2677 * formatting feature is off, the 2678 * original Tikiwiki behavior is used, 2679 * in which each line in the source is 2680 * terminated by an explicit line 2681 * break (br tag). 2682 * 2683 * @since Version 1.9 2684 */ 2685 if ($inTable == 0 && $inPre == 0 && $inComment == 0 && $inTOC == 0 && $inScript == 0 2686 // Don't put newlines at comments' end! 2687 && strpos($line, "-->") !== (strlen($line) - 3) 2688 && $this->option['process_wiki_paragraphs']) { 2689 $tline = trim(str_replace(' ', '', $this->unprotectSpecialChars($line, true))); 2690 2691 if ($prefs['feature_wiki_paragraph_formatting'] == 'y') { 2692 if (count($lines) > 1 || $this->option['min_one_paragraph']) { // don't apply wiki para if only single line so you can have inline includes 2693 $contains_block = $this->contains_html_block($tline); 2694 $contains_br = $this->contains_html_br($tline); 2695 2696 if (! $contains_block) { // check inside plugins etc for block elements 2697 preg_match_all('/\xc2\xa7[^\xc2\xa7]+\xc2\xa7/', $tline, $m); // noparse guid for plugins 2698 if (count($m) > 0) { 2699 $m_count = count($m[0]); 2700 $nop_ix = false; 2701 for ($i = 0; $i < $m_count; $i++) { 2702 //$nop_ix = array_search( $m[0][$i], $noparsed['key'] ); // array_search doesn't seem to work here - why? no "keys"? 2703 foreach ($noparsed['key'] as $k => $s) { 2704 if ($m[0][$i] == $s) { 2705 $nop_ix = $k; 2706 break; 2707 } 2708 } 2709 if ($nop_ix !== false) { 2710 $nop_str = $noparsed['data'][$nop_ix]; 2711 $contains_block = $this->contains_html_block($nop_str); 2712 if ($contains_block) { 2713 break; 2714 } 2715 } 2716 } 2717 } 2718 } 2719 2720 $add_brs = $prefs['feature_wiki_paragraph_formatting_add_br'] === 'y' && ! $this->option['is_html']; 2721 if ($in_paragraph && ((empty($tline) && ! $in_empty_paragraph) || $contains_block)) { 2722 // If still in paragraph, on meeting first blank line or end of div or start of div created by plugins; close a paragraph 2723 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 0, 0); 2724 } elseif (! $in_paragraph && ! $contains_block && ! $contains_br && (! empty($tline) || $add_brs)) { 2725 // If not in paragraph, first non-blank line; start a paragraph; if not start of div created by plugins 2726 $data .= "<p>"; 2727 $in_paragraph = 1; 2728 $in_empty_paragraph = empty($tline) && $add_brs; 2729 } elseif ($in_paragraph && $add_brs && ! $contains_block) { 2730 // A normal in-paragraph line if not close of div created by plugins 2731 if (! empty($tline)) { 2732 $in_empty_paragraph = false; 2733 } 2734 $line = "<br />" . $line; 2735 } // else { 2736 // A normal in-paragraph line or a consecutive blank line. 2737 // Leave it as is. 2738 // } 2739 } 2740 } else { 2741 $line .= "<br />"; 2742 } 2743 } 2744 } 2745 } 2746 } 2747 $data .= $line; 2748 } 2749 2750 if ($this->option['is_html']) { 2751 // A paragraph cannot contain paragraphs. 2752 // The following avoids invalid HTML, but could result in formatting different from that intended. Should a replacement here not at least generate a notice? Chealer 2017-08-14 2753 $count = 1; 2754 while ($count == 1) { 2755 $data = preg_replace("#<p>([^(</p>)]*)<p>([^(</p>)]*)</p>#uims", "<p>$1$2", $data, 1, $count); 2756 } 2757 if (is_null($data)) { 2758 trigger_error('Parsing failed (' . array_flip(get_defined_constants(true)['pcre'])[preg_last_error()] . ')', E_USER_WARNING); 2759 } 2760 } 2761 2762 // Close open paragraph, lists, and div's 2763 $this->close_blocks($data, $in_paragraph, $listbeg, $divdepth, 1, 1, 1); 2764 2765 /* 2766 * Replace special "maketoc" plugins 2767 * Valid arguments : 2768 * - type (look of the maketoc), 2769 * - maxdepth (max level displayed), 2770 * - title (replace the default title), 2771 * - showhide (if set to y, add the Show/Hide link) 2772 * - nolinks (if set to y, don't add links on toc entries) 2773 * - nums : 2774 * * 'n' means 'no title autonumbering' in TOC, 2775 * * 'force' means : 2776 * ~ same as 'y' if autonumbering is used in the page, 2777 * ~ 'number each toc entry as if they were all autonumbered' 2778 * * any other value means 'same as page's headings autonumbering', 2779 * 2780 * (Note that title will be translated if a translation is available) 2781 * 2782 * Examples: {maketoc}, {maketoc type=box maxdepth=1 showhide=y}, {maketoc title="Page Content" maxdepth=3}, ... 2783 * Obsolete syntax: {maketoc:box} 2784 */ 2785 $new_data = ''; 2786 $search_start = 0; 2787 if ($need_maketoc) { 2788 while (($maketoc_start = strpos($data, "{maketoc", $search_start)) !== false) { 2789 $maketoc_length = strpos($data, "}", $maketoc_start) + 1 - $maketoc_start; 2790 $maketoc_string = substr($data, $maketoc_start, $maketoc_length); 2791 2792 // Handle old type definition for type "box" (and preserve environment for the title also) 2793 if ($maketoc_length > 12 && TikiLib::strtolower(substr($maketoc_string, 8, 4)) == ':box') { 2794 $maketoc_string = "{maketoc type=box showhide=y title='" . tra('index', $this->option['language'], true) . '"' . substr($maketoc_string, 12); 2795 } 2796 2797 $maketoc_string = str_replace('"', '"', $maketoc_string); 2798 $maketoc_regs = []; 2799 2800 if ($maketoc_length == 9 || preg_match_all("/([^\s=\(]+)=([^\"\s=\)\}]+|\"[^\"]*\")/", $maketoc_string, $maketoc_regs)) { 2801 if ($maketoc_start > 0) { 2802 $new_data .= substr($data, 0, $maketoc_start); 2803 } 2804 2805 // Set maketoc default values 2806 $maketoc_args = [ 2807 'type' => '', 2808 'maxdepth' => 0, // No limit 2809 'title' => tra('Table of contents', $this->option['language'], true), 2810 'showhide' => '', 2811 'nolinks' => '', 2812 'nums' => '', 2813 'levels' => '' 2814 ]; 2815 2816 // Build maketoc arguments list (and remove " chars if they are around the value) 2817 if (isset($maketoc_regs[1])) { 2818 $nb_args = count($maketoc_regs[1]); 2819 for ($a = 0; $a < $nb_args; $a++) { 2820 $maketoc_args[TikiLib::strtolower($maketoc_regs[1][$a])] = trim($maketoc_regs[2][$a], '"'); 2821 } 2822 } 2823 2824 if ($maketoc_args['title'] != '') { 2825 // Translate maketoc title 2826 $maketoc_summary = ' summary="' . tra($maketoc_args['title'], $this->option['language'], true) . '"'; 2827 $maketoc_title = "<div id='toctitle'><h3>" . tra($maketoc_args['title'], $this->option['language']) . '</h3>'; 2828 2829 if (isset($maketoc_args['showhide']) && $maketoc_args['showhide'] == 'y') { 2830 $maketoc_title .= '<a class="link" href="javascript:toggleToc()">' . '[' . tra('Show/Hide') . ']' . '</a>'; 2831 } 2832 $maketoc_title .= '</div>'; 2833 } else { 2834 $maketoc_summary = ''; 2835 $maketoc_title = ''; 2836 } 2837 if (! empty($maketoc_args['levels'])) { 2838 $maketoc_args['levels'] = preg_split('/\s*,\s*/', $maketoc_args['levels']); 2839 } 2840 2841 // Build maketoc 2842 switch ($maketoc_args['type']) { 2843 case 'box': 2844 $maketoc_header = ''; 2845 $maketoc = "<table id='toc' class='toc'$maketoc_summary>\n<tr><td>$maketoc_title<ul>"; 2846 $maketoc_footer = "</ul></td></tr></table>\n"; 2847 $link_class = 'toclink'; 2848 break; 2849 default: 2850 $maketoc = ''; 2851 $maketoc_header = "<div id='toc'>" . $maketoc_title; 2852 $maketoc_footer = '</div>'; 2853 $link_class = 'link'; 2854 } 2855 foreach ($anch as $tocentry) { 2856 if ($maketoc_args['maxdepth'] > 0 && $tocentry['hdrlevel'] > $maketoc_args['maxdepth']) { 2857 continue; 2858 } 2859 if (! empty($maketoc_args['levels']) && ! in_array($tocentry['hdrlevel'], $maketoc_args['levels'])) { 2860 continue; 2861 } 2862 // Generate the toc entry title (with nums) 2863 if ($maketoc_args['nums'] == 'n') { 2864 $tocentry_title = ''; 2865 } elseif ($maketoc_args['nums'] == 'force' && ! $need_autonumbering) { 2866 $tocentry_title = $tocentry['title_real_num']; 2867 } else { 2868 $tocentry_title = $tocentry['title_displayed_num']; 2869 } 2870 $tocentry_title .= $tocentry['title']; 2871 2872 // Generate the toc entry link 2873 $tocentry_link = '#' . $tocentry['id']; 2874 if ($tocentry['pagenum'] > 1) { 2875 $tocentry_link = $_SERVER['PHP_SELF'] . '?page=' . $this->option['page'] . '&pagenum=' . $tocentry['pagenum'] . $tocentry_link; 2876 } 2877 if ($maketoc_args['nolinks'] != 'y') { 2878 $tocentry_title = "<a href='$tocentry_link' class='link'>" . $tocentry_title . '</a>'; 2879 } 2880 2881 if ($maketoc != '') { 2882 $maketoc .= "\n"; 2883 } 2884 $shift = $tocentry['hdrlevel']; 2885 if (! empty($maketoc_args['levels'])) { 2886 for ($i = 1; $i <= $tocentry['hdrlevel']; ++$i) { 2887 if (! in_array($i, $maketoc_args['levels'])) { 2888 --$shift; 2889 } 2890 } 2891 } 2892 switch ($maketoc_args['type']) { 2893 case 'box': 2894 $maketoc .= "<li class='toclevel-" . $shift . "'>" . $tocentry_title . "</li>"; 2895 break; 2896 default: 2897 $maketoc .= str_repeat('*', $shift) . $tocentry_title; 2898 } 2899 } 2900 2901 $maketoc = $this->parse_data($maketoc, ['noparseplugins' => true]); 2902 2903 if (preg_match("/^<ul>/", $maketoc)) { 2904 $maketoc = preg_replace("/^<ul>/", '<ul class="toc">', $maketoc); 2905 $maketoc .= '<!--toc-->'; 2906 } 2907 2908 if ($link_class != 'link') { 2909 $maketoc = preg_replace("/'link'/", "'$link_class'", $maketoc); 2910 } 2911 2912 //patch-ini - Patch taken from http://dev.tiki.org/item5405 2913 global $TOC_newstring, $TOC_oldstring ; 2914 2915 $TOC_newstring = $maketoc ; //===== get a copy of the newest TOC before we do anything to it 2916 if (strpos($maketoc, $TOC_oldstring)) { // larryg - if this MAKETOC contains previous chapter's TOC entries, remove that portion of the string 2917 $maketoc = substr($maketoc, 0, strpos($maketoc, $TOC_oldstring)) . substr($maketoc, strpos($maketoc, $TOC_oldstring) + strlen($TOC_oldstring)) ; 2918 } 2919 2920 //prepare this chapter's TOC entry to be compared with the next chapter's string] 2921 $head_string = '<li><a href=' ; 2922 $tail_string = '<!--toc-->' ; 2923 if (strpos($TOC_newstring, $head_string) && strpos($TOC_newstring, $tail_string)) { 2924 $TOC_newstring = substr($TOC_newstring, strpos($TOC_newstring, $head_string)) ; // trim unwanted stuff from the beginning of the string 2925 $TOC_newstring = substr($TOC_newstring, 0, (strpos($TOC_newstring, $tail_string) - 5)) ; // trim the stuff from the tail of the string </ul></li></ul> 2926 $TOC_oldstring = $TOC_newstring ; 2927 } 2928 //patch-end - Patch taken from http://dev.tiki.org/item5405 2929 2930 if (! empty($maketoc)) { 2931 $maketoc = $maketoc_header . $maketoc . $maketoc_footer; 2932 } 2933 $new_data .= $maketoc; 2934 $data = substr($data, $maketoc_start + $maketoc_length); 2935 $search_start = 0; // Reinitialize search start cursor, since data now begins after the last replaced maketoc 2936 } else { 2937 $search_start = $maketoc_start + $maketoc_length; 2938 } 2939 } 2940 } 2941 $data = $new_data . $data; 2942 // Add icon to edit the text before the first section (if there is some) 2943 if ($prefs['wiki_edit_section'] === 'y' && isset($section) && $section === 'wiki page' && $tiki_p_edit === 'y' && (empty($this->option['print']) || 2944 ! $this->option['print']) && strpos($data, '<div class="icon_edit_section">') != 0 && ! $this->option['suppress_icons']) { 2945 $smarty = TikiLib::lib('smarty'); 2946 include_once('lib/smarty_tiki/function.icon.php'); 2947 $button = '<div class="icon_edit_section"><a title="' . tra('Edit Section') . '" href="tiki-editpage.php?'; 2948 if (! empty($this->option['page'])) { 2949 $button .= 'page=' . urlencode($this->option['page']) . '&'; 2950 } 2951 $button .= 'hdr=0">' . smarty_function_icon(['name' => 'edit'], $smarty->getEmptyInternalTemplate()) . '</a></div>'; 2952 $data = $button . $data; 2953 } 2954 } 2955 2956 //* 2957 function contains_html_block($inHtml) 2958 { 2959 // detect all block elements as defined on http://www.w3.org/2007/07/xhtml-basic-ref.html 2960 $block_detect_regexp = '/<[\/]?(?:address|blockquote|div|dl|fieldset|h\d|hr|li|noscript|ol|p|pre|table|ul)/i'; 2961 return (preg_match($block_detect_regexp, $this->unprotectSpecialChars($inHtml, true)) > 0); 2962 } 2963 2964 //* 2965 function contains_html_br($inHtml) 2966 { 2967 $block_detect_regexp = '/<(?:br)/i'; 2968 return (preg_match($block_detect_regexp, $this->unprotectSpecialChars($inHtml, true)) > 0); 2969 } 2970 2971 //* 2972 function get_wiki_link_replacement($pageLink, $extra = [], $ck_editor = false) 2973 { 2974 global $prefs; 2975 $wikilib = TikiLib::lib('wiki'); 2976 $tikilib = TikiLib::lib('tiki'); 2977 2978 // Fetch all externals once 2979 static $externals = false; 2980 if (false === $externals) { 2981 $externals = $tikilib->fetchMap('SELECT LOWER(`name`), `extwiki` FROM `tiki_extwiki`'); 2982 } 2983 2984 $displayLink = $pageLink; 2985 2986 // HTML entities encoding breaks page lookup 2987 $pageLink = html_entity_decode($pageLink, ENT_COMPAT, 'UTF-8'); 2988 2989 if ($prefs['namespace_enabled'] == 'y' && $prefs['namespace_force_links'] == 'y' 2990 && $wikilib->get_namespace($this->option['page']) 2991 && ! $wikilib->get_namespace($pageLink) ) { 2992 $pageLink = $wikilib->get_namespace($this->option['page']) . $prefs['namespace_separator'] . $pageLink; 2993 } 2994 2995 $description = null; 2996 $reltype = null; 2997 $processPlural = false; 2998 $anchor = null; 2999 3000 if (array_key_exists('description', $extra)) { 3001 $description = $extra['description']; 3002 } 3003 if (array_key_exists('reltype', $extra)) { 3004 $reltype = $extra['reltype']; 3005 } 3006 if (array_key_exists('plural', $extra)) { 3007 $processPlural = (boolean) $extra['plural']; 3008 } 3009 if (array_key_exists('anchor', $extra)) { 3010 $anchor = $extra['anchor']; 3011 } 3012 3013 $link = new WikiParser_OutputLink; 3014 $link->setIdentifier($pageLink); 3015 $link->setNamespace($this->option['namespace'], $prefs['namespace_separator']); 3016 $link->setQualifier($reltype); 3017 $link->setDescription($description); 3018 $link->setWikiLookup([ $this, 'parser_helper_wiki_info_getter' ]); 3019 if ($ck_editor) { // prevent page slug being used in wysiwyg editor, needs the page name 3020 $link->setWikiLinkBuilder( 3021 function ($pageLink) { 3022 return $pageLink; 3023 } 3024 ); 3025 } else { 3026 $link->setWikiLinkBuilder( 3027 function ($pageLink) { 3028 $wikilib = TikiLib::lib('wiki'); 3029 return $wikilib->sefurl($pageLink); 3030 } 3031 ); 3032 } 3033 $link->setExternals($externals); 3034 $link->setHandlePlurals($processPlural); 3035 $link->setAnchor($anchor); 3036 3037 if ($prefs['feature_multilingual'] == 'y' && isset($GLOBALS['pageLang'])) { 3038 $link->setLanguage($GLOBALS['pageLang']); 3039 } 3040 3041 return $link->getHtml($ck_editor); 3042 } 3043 3044 //* 3045 function parser_helper_wiki_info_getter($pageName) 3046 { 3047 global $prefs; 3048 $tikilib = TikiLib::lib('tiki'); 3049 $page_info = $tikilib->get_page_info($pageName, false); 3050 3051 if ($page_info !== false) { 3052 return $page_info; 3053 } 3054 3055 // If page does not exist directly, attempt to find an alias 3056 if ($prefs['feature_wiki_pagealias'] == 'y') { 3057 $semanticlib = TikiLib::lib('semantic'); 3058 3059 $toPage = $pageName; 3060 $tokens = explode(',', $prefs['wiki_pagealias_tokens']); 3061 3062 $prefixes = explode(',', $prefs["wiki_prefixalias_tokens"]); 3063 foreach ($prefixes as $p) { 3064 $p = trim($p); 3065 if (strlen($p) > 0 && TikiLib::strtolower(substr($pageName, 0, strlen($p))) == TikiLib::strtolower($p)) { 3066 $toPage = $p; 3067 $tokens = 'prefixalias'; 3068 } 3069 } 3070 3071 $links = $semanticlib->getLinksUsing( 3072 $tokens, 3073 [ 'toPage' => $toPage ] 3074 ); 3075 3076 if (count($links) > 1) { 3077 // There are multiple aliases for this page. Need to disambiguate. 3078 // 3079 // When feature_likePages is set, trying to display the alias itself will 3080 // display an error page with the list of aliased pages in the "like pages" section. 3081 // This allows the user to pick the appropriate alias. 3082 // So, leave the $pageName to the alias. 3083 // 3084 // If feature_likePages is not set, then the user will only see that the page does not 3085 // exist. So it's better to just pick the first one. 3086 // 3087 if ($prefs['feature_likePages'] == 'y' || $tokens == 'prefixalias') { 3088 // Even if there is more then one match, if prefix is being redirected then better 3089 // to fail than to show possibly wrong page 3090 return true; 3091 } else { 3092 // If feature_likePages is NOT set, then trying to display the first one is fine 3093 // $pageName is by ref so it does get replaced 3094 $pageName = $links[0]['fromPage']; 3095 return $tikilib->get_page_info($pageName); 3096 } 3097 } elseif (count($links)) { 3098 // there is exactly one match 3099 if ($prefs['feature_wiki_1like_redirection'] == 'y') { 3100 return true; 3101 } else { 3102 $pageName = $links[0]['fromPage']; 3103 return $tikilib->get_page_info($pageName); 3104 } 3105 } 3106 } 3107 } 3108 //* 3109 function parse_tagged_users($data) 3110 { 3111 $count = 1; 3112 return preg_replace_callback( 3113 '/(?:^|\s)@(\w+)/i', 3114 function ($matches) use (&$count) { 3115 $myUser = substr($matches[0], strpos($matches[0], "@") + 1); 3116 if ($myUser) { 3117 $u = TikiLib::lib('user')->get_user_info($myUser); 3118 if (is_array($u) && $u['userId'] > 0) { 3119 $v = TikiLib::lib('user')->build_userinfo_tag($myUser, '', 'userlink', 'y', 'mentioned-' . $myUser . '-section-' . $count); 3120 $count++; 3121 if ($v) { 3122 $prefix = ($matches[0][1] == '@') ? $matches[0][0] : ''; 3123 return $prefix . $v; 3124 } 3125 } 3126 3127 return $matches[0]; 3128 } 3129 return $matches[0]; 3130 }, 3131 $data 3132 ); 3133 } 3134 3135 //* 3136 function parse_smileys($data) 3137 { 3138 global $prefs; 3139 static $patterns; 3140 3141 if ($prefs['feature_smileys'] == 'y') { 3142 if (! $patterns) { 3143 $patterns = [ 3144 // Example of all Tiki Smileys (the old syntax) 3145 // (:biggrin:) (:confused:) (:cool:) (:cry:) (:eek:) (:evil:) (:exclaim:) (:frown:) 3146 // (:idea:) (:lol:) (:mad:) (:mrgreen:) (:neutral:) (:question:) (:razz:) (:redface:) 3147 // (:rolleyes:) (:sad:) (:smile:) (:surprised:) (:twisted:) (:wink:) (:arrow:) (:santa:) 3148 3149 "/\(:([^:]+):\)/" => "<img alt=\"$1\" src=\"img/smiles/icon_$1.gif\" />", 3150 3151 // :) :-) 3152 '/(\s|^):-?\)/' => "$1<img alt=\":-)\" title=\"" . tra('smiling') . "\" src=\"img/smiles/icon_smile.gif\" />", 3153 // :( :-( 3154 '/(\s|^):-?\(/' => "$1<img alt=\":-(\" title=\"" . tra('sad') . "\" src=\"img/smiles/icon_sad.gif\" />", 3155 // :D :-D 3156 '/(\s|^):-?D/' => "$1<img alt=\":-D\" title=\"" . tra('grinning') . "\" src=\"img/smiles/icon_biggrin.gif\" />", 3157 // :S :-S :s :-s 3158 '/(\s|^):-?S/i' => "$1<img alt=\":-S\" title=\"" . tra('confused') . "\" src=\"img/smiles/icon_confused.gif\" />", 3159 // B) B-) 8-) 3160 '/(\s|^)(B-?|8-)\)/' => "$1<img alt=\"B-)\" title=\"" . tra('cool') . "\" src=\"img/smiles/icon_cool.gif\" />", 3161 // :'( :_( 3162 '/(\s|^):[\'|_]\(/' => "$1<img alt=\":_(\" title=\"" . tra('crying') . "\" src=\"img/smiles/icon_cry.gif\" />", 3163 // 8-o 8-O =-o =-O 3164 '/(\s|^)[8=]-O/i' => "$1<img alt=\"8-O\" title=\"" . tra('frightened') . "\" src=\"img/smiles/icon_eek.gif\" />", 3165 // }:( }:-( 3166 '/(\s|^)\}:-?\(/' => "$1<img alt=\"}:(\" title=\"" . tra('evil stuff') . "\" src=\"img/smiles/icon_evil.gif\" />", 3167 // !-) !) 3168 '/(\s|^)\!-?\)/' => "$1<img alt=\"(!)\" title=\"" . tra('exclamation mark !') . "\" src=\"img/smiles/icon_exclaim.gif\" />", 3169 // >:( >:-( 3170 '/(\s|^)\>:-?\(/' => "$1<img alt=\"}:(\" title=\"" . tra('frowning') . "\" src=\"img/smiles/icon_frown.gif\" />", 3171 // i-) 3172 '/(\s|^)i-\)/' => "$1<img alt=\"(" . tra('light bulb') . ")\" title=\"" . tra('idea !') . "\" src=\"img/smiles/icon_idea.gif\" />", 3173 // LOL 3174 '/(\s|^)LOL(\s|$)/' => "$1<img alt=\"(" . tra('LOL') . ")\" title=\"" . tra('laughing out loud !') . "\" src=\"img/smiles/icon_lol.gif\" />$2", 3175 // >X( >X[ >:[ >X-( >X-[ >:-[ 3176 '/(\s|^)\>[:X]-?\(/' => "$1<img alt=\">:[\" title=\"" . tra('mad') . "\" src=\"img/smiles/icon_mad.gif\" />", 3177 // =D =-D 3178 '/(\s|^)[=]-?D/' => "$1<img alt=\"=D\" title=\"" . tra('Mr. Green laughing') . "\" src=\"img/smiles/icon_mrgreen.gif\" />", 3179 ]; 3180 } 3181 3182 foreach ($patterns as $p => $r) { 3183 $data = preg_replace($p, $r, $data); 3184 } 3185 } 3186 return $data; 3187 } 3188 3189 //* 3190 function get_pages($data, $withReltype = false) 3191 { 3192 global $page_regex, $prefs; 3193 $tikilib = TikiLib::lib('tiki'); 3194 3195 $matches = WikiParser_PluginMatcher::match($data); 3196 foreach ($matches as $match) { 3197 if ($match->getName() == 'code') { 3198 $match->replaceWith(''); 3199 } 3200 } 3201 3202 $data = $matches->getText(); 3203 3204 $htmlLinks = ["0" => "dummy"]; 3205 $htmlLinksSefurl = ["0" => "dummy"]; 3206 preg_match_all("/\(([a-z0-9-]+)?\( *($page_regex) *\)\)/", $data, $normal); 3207 preg_match_all("/\(([a-z0-9-]+)?\( *($page_regex) *\|(.+?)\)\)/", $data, $withDesc); 3208 preg_match_all('/<a class="wiki[^\"]*" href="tiki-index\.php\?page=([^\?&"]+)[^"]*"/', $data, $htmlLinks1); 3209 preg_match_all('/<a href="tiki-index\.php\?page=([^\?&"]+)[^"]*"/', $data, $htmlLinks2); 3210 $htmlLinks[1] = array_merge($htmlLinks1[1], $htmlLinks2[1]); 3211 $htmlLinks[1] = array_filter($htmlLinks[1]); 3212 preg_match_all('/<a class="wiki[^\"]*" href="([^\?&"]+)[^"]*"/', $data, $htmlLinksSefurl1); 3213 preg_match_all('/<a href="([^\?&"]+)[^"]*"/', $data, $htmlLinksSefurl2); 3214 $htmlLinksSefurl[1] = array_merge($htmlLinksSefurl1[1], $htmlLinksSefurl2[1]); 3215 preg_match_all('/<a class="wiki wikinew" href="tiki-editpage\.php\?page=([^\?&"]+)"/', $data, $htmlWantedLinks); 3216 // TODO: revise the need to call modified urldecode() (shouldn't be needed after r37568). 20110922 3217 foreach ($htmlLinks[1] as &$h) { 3218 $h = $tikilib->urldecode($h); 3219 } 3220 foreach ($htmlLinksSefurl[1] as &$h) { 3221 $h = $tikilib->urldecode($h); 3222 } 3223 foreach ($htmlWantedLinks[1] as &$h) { 3224 $h = $tikilib->urldecode($h); 3225 } 3226 3227 // Post process SEFURL for html wiki pages 3228 if (count($htmlLinksSefurl[1])) { 3229 // Remove any possible "tiki-index.php" in the SEFURL link list. 3230 // Non-sefurl links will be mapped as "tiki-index.php" 3231 $tikiindex = []; 3232 foreach ($htmlLinksSefurl[1] as $pageName) { 3233 if (strpos($pageName, 'tiki-index.php') !== false) { 3234 $tikiindex[] = $pageName; 3235 } 3236 } 3237 $htmlLinksSefurl[1] = array_diff($htmlLinksSefurl[1], $tikiindex); 3238 3239 if (count($htmlLinksSefurl[1])) { 3240 // The case <a href=" ... will catch manually entered links. Only add links to wiki pages 3241 $pages = $tikilib->get_all_pages(); 3242 $tikiindex = []; 3243 foreach ($htmlLinksSefurl[1] as $link) { 3244 // Validate that the link is to a wiki page 3245 if (! in_array($link, $pages)) { 3246 // If it's not referring to a wiki page, add it to the removal list 3247 $tikiindex[] = $link; 3248 } 3249 } 3250 $htmlLinksSefurl[1] = array_diff($htmlLinksSefurl[1], $tikiindex); 3251 } 3252 } 3253 3254 if ($prefs['feature_wikiwords'] == 'y') { 3255 preg_match_all("/([ \n\t\r\,\;]|^)?([A-Z][a-z0-9_\-]+[A-Z][a-z0-9_\-]+[A-Za-z0-9\-_]*)($|[ \n\t\r\,\;\.])/", $data, $wikiLinks); 3256 3257 $pageList = array_merge($normal[2], $withDesc[2], $wikiLinks[2], $htmlLinks[1], $htmlLinksSefurl[1], $htmlWantedLinks[1]); 3258 if ($withReltype) { 3259 $relList = array_merge( 3260 $normal[1], 3261 $withDesc[1], 3262 count($wikiLinks[2]) ? array_fill(0, count($wikiLinks[2]), null) : [], 3263 count($htmlLinks[1]) ? array_fill(0, count($htmlLinks[1]), null) : [], 3264 count($htmlLinksSefurl[1]) ? array_fill(0, count($htmlLinksSefurl[1]), null) : [], 3265 count($htmlWantedLinks[1]) ? array_fill(0, count($htmlWantedLinks[1]), null) : [] 3266 ); 3267 } 3268 } else { 3269 $pageList = array_merge($normal[2], $withDesc[2], $htmlLinks[1], $htmlLinksSefurl[1], $htmlWantedLinks[1]); 3270 if ($withReltype) { 3271 $relList = array_merge( 3272 $normal[1], 3273 $withDesc[1], 3274 count($htmlLinks[1]) ? array_fill(0, count($htmlLinks[1]), null) : [], 3275 count($htmlLinksSefurl[1]) ? array_fill(0, count($htmlLinksSefurl[1]), null) : [], 3276 count($htmlWantedLinks[1]) ? array_fill(0, count($htmlWantedLinks[1]), null) : [] 3277 ); 3278 } 3279 } 3280 3281 if ($withReltype) { 3282 $complete = []; 3283 foreach ($pageList as $idx => $name) { 3284 if (! array_key_exists($name, $complete)) { 3285 $complete[$name] = []; 3286 } 3287 if (! empty($relList[$idx]) && ! in_array($relList[$idx], $complete[$name])) { 3288 $complete[$name][] = $relList[$idx]; 3289 } 3290 } 3291 3292 return $complete; 3293 } else { 3294 return array_unique($pageList); 3295 } 3296 } 3297 3298 private function get_hotwords() 3299 { 3300 $tikilib = TikiLib::lib('tiki'); 3301 static $cache_hotwords; 3302 if (isset($cache_hotwords)) { 3303 return $cache_hotwords; 3304 } 3305 $query = "select * from `tiki_hotwords`"; 3306 $result = $tikilib->fetchAll($query, [], -1, -1, false); 3307 $ret = []; 3308 foreach ($result as $res) { 3309 $ret[$res["word"]] = $res["url"]; 3310 } 3311 $cache_hotwords = $ret; 3312 return $ret; 3313 } 3314 3315 /* 3316 * When we save a page that contains a TranslationOf plugin, we need to remember 3317 * that relation, so we later, when the page referenced by this plugin gets translated, 3318 * we can replace the plugin by a proper link to the translation. 3319 */ 3320 public function add_translationof_relation($data, $arguments, $page_being_parsed) 3321 { 3322 $relationlib = TikiLib::lib('relation'); 3323 3324 $relationlib->add_relation('tiki.wiki.translationof', 'wiki page', $page_being_parsed, 'wiki page', $arguments['translation_page']); 3325 } 3326 3327 /** 3328 * Refresh the list of plugins that might require validation 3329 * 3330 * @param \Psr\Log\LoggerInterface|null $logger 3331 */ 3332 public function pluginRefresh($logger = null) 3333 { 3334 $headerLib = \TikiLib::lib('header'); 3335 $tempHeaderLib = serialize($headerLib); // cache headerlib so we can remove all js etc added by plugins 3336 3337 $access = \TikiLib::lib('access'); 3338 $access->check_feature('wiki_validate_plugin'); 3339 $access->check_permission('tiki_p_plugin_approve'); 3340 3341 $tikiLib = TikiLib::lib('tiki'); 3342 $parserLib = TikiLib::lib('parser'); 3343 3344 // disable redirect plugin etc 3345 $access->preventRedirect(true); 3346 $access->preventDisplayError(true); 3347 3348 $pages = $tikiLib->list_pages(); 3349 foreach ($pages['data'] as $apage) { 3350 if ($logger instanceof Psr\Log\LoggerInterface) { 3351 $logger->debug(tr('Processing page: %0, is_html: %1', $apage['pageName'], $apage['is_html'])); 3352 } 3353 $page = $apage['pageName']; 3354 $parserLib->setOptions( 3355 [ 3356 'page' => $page, 3357 'is_html' => $apage['is_html'], 3358 ] 3359 ); 3360 $parserLib->parse_first($apage['data'], $pre, $no); 3361 } 3362 3363 $access->preventRedirect(false); 3364 $access->preventDisplayError(false); 3365 3366 $headerLib = unserialize($tempHeaderLib); 3367 unset($tempHeaderLib); 3368 } 3369 3370 /** 3371 * Create a popup with thumbnail file preview 3372 * 3373 * @param $content 3374 * @return string 3375 */ 3376 public function searchFilePreview($content) 3377 { 3378 global $prefs, $user, $tikipath, $tikidomain; 3379 if ($prefs['search_file_thumbnail_preview'] !== 'y') { 3380 return $content; 3381 } 3382 3383 preg_match_all('/data-type="file" data-object="(\d+)/', $content, $matchFiles); 3384 if (! empty($matchFiles[1])) { 3385 $fileIds = $matchFiles[1]; 3386 $userLib = TikiLib::lib('user'); 3387 $maxWidthPreview = $prefs['fgal_maximum_image_width_preview']; 3388 3389 foreach ($fileIds as $fileId) { 3390 $file = \Tiki\FileGallery\File::id($fileId); 3391 if (! $file->exists()) { 3392 continue; 3393 } 3394 if (! $userLib->user_has_perm_on_object($user, $file->fileId, 'file', 'tiki_p_download_files')) { 3395 continue; 3396 } 3397 $search = 'data-type="file" data-object="' . $fileId . '"'; 3398 if (strpos($file->filetype, 'image') !== false) { 3399 $appendMaxSize = ''; 3400 3401 if (! empty($maxWidthPreview)) { 3402 $appendMaxSize = "&x=" . $maxWidthPreview; 3403 } 3404 3405 $popup = 'data-type="file" data-object="' . $fileId . '" data-toggle="popover" data-trigger="hover focus" data-content="<img src=\'tiki-download_file.php?fileId=' . $fileId . '&thumbnail' . $appendMaxSize . '\'>" data-html="1" data-width="' . $maxWidthPreview . '"'; 3406 $content = str_replace($search, $popup, $content); 3407 } else { 3408 $filePath = $file->getWrapper()->getReadableFile(); 3409 $fileMd5 = $file->getWrapper()->getChecksum(); 3410 3411 $cacheLib = TikiLib::lib('cache'); 3412 $cacheName = $fileMd5; 3413 $cacheType = 'preview_' . $fileId . '_'; 3414 3415 if (! $cacheLib->isCached($cacheName, $cacheType) && Tiki\Lib\Alchemy\AlchemyLib::isLibraryAvailable()) { 3416 // This will allow apps executed by Alchemy (like when converting doc to pdf) to have a writable home 3417 // save existing ENV 3418 $envHomeDefined = isset($_ENV) && array_key_exists('HOME', $_ENV); 3419 if ($envHomeDefined) { 3420 $envHomeCopy = $_ENV['HOME']; 3421 } 3422 3423 // set a proper home folder 3424 $_ENV['HOME'] = $tikipath . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . $tikidomain; 3425 3426 $targetFile = 'temp/target_' . $fileId . '.png'; 3427 3428 Tiki\Lib\Alchemy\AlchemyLib::hintMimeTypeByFilePath($filePath, $file->filetype); 3429 3430 $alchemy = new Tiki\Lib\Alchemy\AlchemyLib(); 3431 $height = 400; 3432 $width = 200; 3433 3434 if (! empty($prefs['fgal_maximum_image_width_preview'])) { 3435 $originalHeight = $height; 3436 $originalWidth = $width; 3437 $width = $prefs['fgal_maximum_image_width_preview']; 3438 $height = (int) $originalHeight * $width / $originalWidth; 3439 } 3440 3441 $alchemy->convertToImage($filePath, $targetFile, $height, $width, false); 3442 3443 // Restore the environment 3444 if ($envHomeDefined) { 3445 $_ENV['HOME'] = $envHomeCopy; 3446 } else { 3447 unset($_ENV['HOME']); 3448 } 3449 3450 if (file_exists($targetFile)) { 3451 $cacheContent = file_get_contents($targetFile); 3452 $cacheLib->empty_type_cache($cacheType); 3453 $cacheLib->cacheItem($cacheName, $cacheContent, $cacheType); 3454 unlink($targetFile); 3455 } 3456 } 3457 3458 if ($cacheLib->isCached($cacheName, $cacheType)) { 3459 $popup = 'data-type="file" data-object="' . $fileId . '" data-toggle="popover" data-trigger="hover focus" data-content="<img src=\'tiki-download_file.php?fileId=' . $fileId . '&preview\'>" data-html="1" data-width="' . $maxWidthPreview . '"'; 3460 $content = str_replace($search, $popup, $content); 3461 } 3462 } 3463 } 3464 } 3465 3466 return $content; 3467 } 3468} 3469