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//this script may only be included - so its better to die if called directly. 9if (strpos($_SERVER['SCRIPT_NAME'], basename(__FILE__)) !== false) { 10 header('location: index.php'); 11 exit; 12} 13 14if (! defined('PLUGINS_DIR')) { 15 define('PLUGINS_DIR', 'lib/wiki-plugins'); 16} 17 18 19class WikiLib extends TikiLib 20{ 21 22 //Special parsing for multipage articles 23 public function get_number_of_pages($data) 24 { 25 global $prefs; 26 // Temporary remove <PRE></PRE> secions to protect 27 // from broke <PRE> tags and leave well known <PRE> 28 // behaviour (i.e. type all text inside AS IS w/o 29 // any interpretation) 30 $preparsed = []; 31 32 preg_match_all("/(<[Pp][Rr][Ee]>)(.*?)(<\/[Pp][Rr][Ee]>)/s", $data, $preparse); 33 $idx = 0; 34 35 foreach (array_unique($preparse[2]) as $pp) { 36 $key = md5($this->genPass()); 37 38 $aux['key'] = $key; 39 $aux['data'] = $pp; 40 $preparsed[] = $aux; 41 $data = str_replace($preparse[1][$idx] . $pp . $preparse[3][$idx], $key, $data); 42 $idx = $idx + 1; 43 } 44 45 $parts = explode($prefs['wiki_page_separator'], $data); 46 return count($parts); 47 } 48 49 public function get_page($data, $i) 50 { 51 // Get slides 52 global $prefs; 53 $parts = explode($prefs['wiki_page_separator'], $data); 54 $ret = $parts[$i - 1]; 55 56 if (substr($parts[$i - 1], 1, 5) == '<br/>') { 57 $ret = substr($parts[$i - 1], 6); 58 } 59 60 if (substr($parts[$i - 1], 1, 6) == '<br />') { 61 $ret = substr($parts[$i - 1], 7); 62 } 63 64 return $ret; 65 } 66 67 function get_page_by_slug($slug) 68 { 69 $pages = TikiDb::get()->table('tiki_pages'); 70 $found = $pages->fetchOne('pageName', ['pageSlug' => $slug]); 71 72 if ($found) { 73 return $found; 74 } 75 76 if (function_exists('utf8_encode')) { 77 $slug_utf8 = utf8_encode($slug); 78 if ($slug != $slug_utf8) { 79 $found = $pages->fetchOne('pageName', ['pageSlug' => $slug_utf8]); 80 if ($found) { 81 return $found; 82 } 83 } 84 } 85 86 return $slug; 87 } 88 89 /** 90 * Return a Slug, if set, or the page name supplied as result 91 * 92 * @param string $page 93 * @return string 94 */ 95 function get_slug_by_page($page) 96 { 97 $pages = TikiDb::get()->table('tiki_pages'); 98 $slug = $pages->fetchOne('pageSlug', ['pageName' => $page]); 99 if ($slug) { 100 return $slug; 101 } 102 return $page; 103 } 104 105 public function get_creator($name) 106 { 107 return $this->getOne('select `creator` from `tiki_pages` where `pageName`=?', [$name]); 108 } 109 110 /** 111 * Get the contributors for page 112 * the returned array does not contain the user $last (usually the current or last user) 113 */ 114 public function get_contributors($page, $last = '') 115 { 116 static $cache_page_contributors; 117 if ($cache_page_contributors['page'] == $page) { 118 if (empty($last)) { 119 return $cache_page_contributors['contributors']; 120 } 121 $ret = []; 122 if (is_array($cache_page_contributors['contributors'])) { 123 foreach ($cache_page_contributors['contributors'] as $res) { 124 if (isset($res['user']) && $res['user'] != $last) { 125 $ret[] = $res; 126 } 127 } 128 } 129 return $ret; 130 } 131 132 $query = 'select `user`, MAX(`version`) as `vsn` from `tiki_history` where `pageName`=? group by `user` order by `vsn` desc'; 133 // jb fixed 110115 - please check intended behaviour remains 134 // was: $query = "select `user` from `tiki_history` where `pageName`=? group by `user` order by MAX(`version`) desc"; 135 $result = $this->query($query, [$page]); 136 $cache_page_contributors = []; 137 $cache_page_contributors['contributors'] = []; 138 $ret = []; 139 140 while ($res = $result->fetchRow()) { 141 if ($res['user'] != $last) { 142 $ret[] = $res['user']; 143 } 144 $cache_page_contributors['contributors'][] = $res['user']; 145 } 146 $cache_page_contributors['page'] = $page; 147 return $ret; 148 } 149 150 // Returns all pages that links from here or to here, without distinction 151 // This is used by wiki mindmap, to make the graph 152 public function wiki_get_neighbours($page) 153 { 154 $neighbours = []; 155 $already = []; 156 157 $query = "select `toPage` from `tiki_links` where `fromPage`=? and `fromPage` not like 'objectlink:%'"; 158 $result = $this->query($query, [$page]); 159 while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) { 160 $neighbour = $row['toPage']; 161 $neighbours[] = $neighbour; 162 $already[$neighbour] = 1; 163 } 164 165 $query = "select `fromPage` from `tiki_links` where `toPage`=? and `fromPage` not like 'objectlink:%'"; 166 $result = $this->query($query, [$page]); 167 while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) { 168 $neighbour = $row['fromPage']; 169 if (! isset($already[$neighbour])) { 170 $neighbours[] = $neighbour; 171 } 172 } 173 174 return $neighbours; 175 } 176 177 // Returns a string containing all characters considered bad in page names 178 public function get_badchars() 179 { 180 return "/?#[]@$&+;=<>"; 181 } 182 183 // Returns a boolean indicating whether the given page name contains "bad characters" 184 // See http://dev.tiki.org/Bad+characters 185 public function contains_badchars($name) 186 { 187 if (preg_match('/^tiki\-(\w+)\-(\w+)$/', $name)) { 188 return true; 189 } 190 191 $badchars = $this->get_badchars(); 192 $badchars = preg_quote($badchars, '/'); 193 return preg_match("/[$badchars]/", $name); 194 } 195 196 public function remove_badchars($page) 197 { 198 if ($this->contains_badchars($page)) { 199 $badChars = $this->get_badchars(); 200 201 // Replace bad characters with a '_' 202 $iStrlenBadChars = strlen($badChars); 203 for ($j = 0; $j < $iStrlenBadChars; $j++) { 204 $char = $badChars[$j]; 205 $page = str_replace($char, "_", $page); 206 } 207 } 208 209 return $page; 210 } 211 212 /** 213 * Duplicate an existing page 214 * 215 * @param string $name 216 * @param string $copyName 217 * @return bool 218 */ 219 public function wiki_duplicate_page($name, $copyName = null, $dupCateg = true, $dupTags = true) 220 { 221 global $user; 222 global $prefs; 223 224 $tikilib = TikiLib::lib('tiki'); 225 $userlib = TikiLib::lib('user'); 226 $globalperms = Perms::get(); 227 228 $info = $tikilib->get_page_info($name); 229 $ip = $tikilib->get_ip_address(); 230 $version = $info['version']; 231 $comment = tr("Initial content copied from version %0 of page %1", $version, $name); 232 233 if (! $info) { 234 return false; 235 } 236 237 if (! $copyName) { 238 $copyName = $name . ' (' . $tikilib->now . ')'; 239 } 240 241 $copyPage = $tikilib->create_page( 242 $copyName, 243 0, 244 $info['data'], 245 $tikilib->now, 246 $comment, 247 $user, 248 $ip, 249 $info['description'], 250 $info['lang'], 251 $info['is_html'] 252 ); 253 254 if ($copyPage) { 255 $warnings = []; 256 if ($dupCateg && $prefs['feature_categories'] === 'y') { 257 if ($globalperms->modify_object_categories) { 258 $categlib = TikiLib::lib('categ'); 259 $categories = $categlib->get_object_categories('wiki page', $name); 260 Perms::bulk([ 'type' => 'category' ], 'object', $categories); 261 262 foreach ($categories as $catId) { 263 $perms = Perms::get([ 'type' => 'category', 'object' => $catId]); 264 265 if ($perms->add_object) { 266 $categlib->categorizePage($copyName, $catId); 267 } else { 268 $warnings[] = tr("You don't have permission to use category '%0'.", $categlib->get_category_name($catId)); 269 } 270 } 271 } else { 272 $warnings[] = tr("You don't have permission to edit categories."); 273 } 274 } 275 276 if ($dupTags && $prefs['feature_freetags'] === 'y') { 277 if ($globalperms->freetags_tag) { 278 $freetaglib = TikiLib::lib('freetag'); 279 $freetags = $freetaglib->get_tags_on_object($name, 'wiki page'); 280 281 foreach ($freetags['data'] as $tag) { 282 $freetaglib->tag_object($user, $copyName, 'wiki page', $tag['tag']); 283 } 284 } else { 285 $warnings[] = tr("You don't have permission to edit tags."); 286 } 287 } 288 289 if (count($warnings) > 0) { 290 Feedback::warning(['mes' => $warnings]); 291 } 292 } 293 294 return $copyPage; 295 } 296 297 // This method renames a wiki page 298 // If you think this is easy you are very very wrong 299 public function wiki_rename_page($oldName, $newName, $renameHomes = true, $user = '') 300 { 301 global $prefs; 302 $tikilib = TikiLib::lib('tiki'); 303 // if page already exists, stop here 304 $newName = trim($newName); 305 if ($this->get_page_info($newName, false, true)) { 306 // if it is a case change of same page: allow it, else stop here 307 if (strcasecmp(trim($oldName), $newName) <> 0) { 308 throw new Exception("Page already exists", 2); 309 } 310 } 311 312 if ($this->contains_badchars($newName) && $prefs['wiki_badchar_prevent'] == 'y') { 313 throw new Exception("Bad characters", 1); 314 } 315 316 // The pre- and post-tags are eating away the max usable page name length 317 // Use ~ instead of Tmp. Shorter 318 // $tmpName = "TmP".$newName."TmP"; 319 $tmpName = "~" . $newName . "~"; 320 321 // 1st rename the page in tiki_pages, using a tmpname inbetween for 322 // rename pages like ThisTestpage to ThisTestPage 323 $query = 'update `tiki_pages` set `pageName`=?, `pageSlug`=NULL where `pageName`=?'; 324 $this->query($query, [ $tmpName, $oldName ]); 325 326 $slug = TikiLib::lib('slugmanager')->generate($prefs['wiki_url_scheme'], $newName, $prefs['url_only_ascii'] === 'y'); 327 $query = 'update `tiki_pages` set `pageName`=?, `pageSlug`=? where `pageName`=?'; 328 $this->query($query, [ $newName, $slug, $tmpName ]); 329 330 // correct pageName in tiki_history, using a tmpname inbetween for 331 // rename pages like ThisTestpage to ThisTestPage 332 $query = 'update `tiki_history` set `pageName`=? where `pageName`=?'; 333 $this->query($query, [ $tmpName, $oldName ]); 334 335 $query = 'update `tiki_history` set `pageName`=? where `pageName`=?'; 336 $this->query($query, [ $newName, $tmpName ]); 337 338 // get pages linking to the old page 339 $query = 'select `fromPage` from `tiki_links` where `toPage`=?'; 340 $result = $this->query($query, [ $oldName ]); 341 342 $linksToOld = []; 343 while ($res = $result->fetchRow()) { 344 $linkOrigin = $res['fromPage']; 345 $linksToOld[] = $linkOrigin; 346 $is_wiki_page = substr($linkOrigin, 0, 11) != 'objectlink:'; 347 if (! $is_wiki_page) { 348 $objectlinkparts = explode(':', $linkOrigin); 349 $type = $objectlinkparts[1]; 350 // $objectId can contain :, so consider the remaining string 351 $objectId = substr($linkOrigin, strlen($type) + 12); 352 } 353 354 $parserlib = TikiLib::lib('parser'); 355 356 if ($is_wiki_page) { 357 $info = $this->get_page_info($linkOrigin); 358 //$data=addslashes(str_replace($oldName,$newName,$info['data'])); 359 $data = $parserlib->replace_links($info['data'], $oldName, $newName); 360 $query = "update `tiki_pages` set `data`=?,`page_size`=? where `pageName`=?"; 361 $this->query($query, [ $data,(int) strlen($data), $linkOrigin]); 362 $this->invalidate_cache($linkOrigin); 363 } elseif ($type == 'forum post' || substr($type, -7) == 'comment') { 364 $comment_info = TikiLib::lib('comments')->get_comment($objectId); 365 $data = $parserlib->replace_links($comment_info['data'], $oldName, $newName); 366 $query = "update `tiki_comments` set `data`=? where `threadId`=?"; 367 $this->query($query, [ $data, $objectId]); 368 } elseif ($type == 'article') { 369 $info = TikiLib::lib('art')->get_article($objectId); 370 $heading = $parserlib->replace_links($info['heading'], $oldName, $newName); 371 $body = $parserlib->replace_links($info['body'], $oldName, $newName); 372 $query = "update `tiki_articles` set `heading`=?, `body`=? where `articleId`=?"; 373 $this->query($query, [ $heading, $body, $objectId]); 374 } elseif ($type == 'post') { 375 $info = TikiLib::lib('blog')->get_post($objectId); 376 $data = $parserlib->replace_links($info['data'], $oldName, $newName); 377 $query = "update `tiki_blog_posts` set `data`=? where `postId`=?"; 378 $this->query($query, [ $data, $objectId]); 379 } elseif ($type == 'tracker') { 380 $tracker_info = TikiLib::lib('trk')->get_tracker($objectId); 381 $data = $parserlib->replace_links($tracker_info['description'], $oldName, $newName); 382 $query = "update `tiki_trackers` set `description`=? where `trackerId`=?"; 383 $this->query($query, [ $data, $objectId]); 384 } elseif ($type == 'trackerfield') { 385 $field_info = TikiLib::lib('trk')->get_field_info($objectId); 386 $data = $parserlib->replace_links($field_info['description'], $oldName, $newName); 387 $query = "update `tiki_tracker_fields` set `description`=? where `fieldId`=?"; 388 $this->query($query, [ $data, $objectId]); 389 } elseif ($type == 'trackeritemfield') { 390 list($itemId, $fieldId) = explode(":", $objectId); 391 $data = TikiLib::lib('trk')->get_item_value(null, (int)$itemId, (int)$fieldId); 392 $data = $parserlib->replace_links($data, $oldName, $newName); 393 $query = "update `tiki_tracker_item_fields` set `value`=? where `itemId`=? and `fieldId`=?"; 394 $this->query($query, [ $data, $itemId, $fieldId]); 395 } elseif ($type == 'calendar event') { 396 $event_info = TikiLib::lib('calendar')->get_item($objectId); 397 $data = $parserlib->replace_links($event_info['description'], $oldName, $newName); 398 $query = "update `tiki_calendar_items` set `description`=? where `calitemId`=?"; 399 $this->query($query, [ $data, $objectId ]); 400 } 401 } 402 403 // correct toPage and fromPage in tiki_links 404 // before update, manage to avoid duplicating index(es) when B is renamed to C while page(s) points to both C (not created yet) and B 405 $query = 'select `fromPage` from `tiki_links` where `toPage`=?'; 406 $result = $this->query($query, [ $newName ]); 407 $linksToNew = []; 408 409 while ($res = $result->fetchRow()) { 410 $linksToNew[] = $res['fromPage']; 411 } 412 413 if ($extra = array_intersect($linksToOld, $linksToNew)) { 414 $query = 'delete from `tiki_links` where `fromPage` in (' . implode(',', array_fill(0, count($extra), '?')) . ') and `toPage`=?'; 415 $this->query($query, array_merge($extra, [$oldName])); 416 } 417 418 $query = 'update `tiki_links` set `fromPage`=? where `fromPage`=?'; 419 $this->query($query, [ $newName, $oldName]); 420 421 $query = 'update `tiki_links` set `toPage`=? where `toPage`=?'; 422 $this->query($query, [ $newName, $oldName]); 423 424 // Modify pages including the old page with Include plugin, 425 // so that they include the new name 426 $relationlib = TikiLib::lib('relation'); 427 $relations = $relationlib->get_relations_to('wiki page', $oldName, 'tiki.wiki.include'); 428 429 foreach ($relations as $relation) { 430 $type = $relation['type']; 431 if ($type == 'wiki page') { 432 $page = $relation['itemId']; 433 $info = $this->get_page_info($page); 434 $data = [ $info['data'] ]; 435 } elseif ($type == 'forum post' || substr($type, -7) == 'comment') { 436 $objectId = (int)$relation['itemId']; 437 $comment_info = TikiLib::lib('comments')->get_comment($objectId); 438 $data = [ $comment_info['data'] ]; 439 } elseif ($type == 'article') { 440 $objectId = (int)$relation['itemId']; 441 $info = TikiLib::lib('art')->get_article($objectId); 442 $data = [ $info['heading'], $info['body'] ]; 443 } elseif ($type == 'post') { 444 $objectId = (int)$relation['itemId']; 445 $info = TikiLib::lib('blog')->get_post($objectId); 446 $data = [ $info['data'] ]; 447 } elseif ($type == 'tracker') { 448 $objectId = (int)$relation['itemId']; 449 $tracker_info = TikiLib::lib('trk')->get_tracker($objectId); 450 $data = [ $tracker_info['description'] ]; 451 } elseif ($type == 'trackerfield') { 452 $objectId = (int)$relation['itemId']; 453 $field_info = TikiLib::lib('trk')->get_field_info($objectId); 454 $data = [ $field_info['description'] ]; 455 } elseif ($type == 'trackeritemfield') { 456 $objectId = explode(":", $relation['itemId']); 457 $data = [ TikiLib::lib('trk')->get_item_value(null, $objectId[0], $objectId[1]) ]; 458 } elseif ($type == 'calendar event') { 459 $objectId = (int)$relation['itemId']; 460 $data = [ TikiLib::lib('calendar')->get_item($objectId)['description'] ]; 461 } else { 462 continue; 463 } 464 465 $modified = false; 466 $matches = []; 467 for ($i = 0; $i < sizeof($data); $i++) { 468 $matches[] = WikiParser_PluginMatcher::match($data[$i]); 469 $argParser = new WikiParser_PluginArgumentParser(); 470 foreach ($matches[$i] as $match) { 471 if ($match->getName() == 'include') { 472 $arguments = $argParser->parse($match->getArguments()); 473 if ($arguments['page'] == $oldName) { 474 $arguments['page'] = $newName; 475 $match->replaceWithPlugin($match->getName(), $arguments, $match->getBody()); 476 $modified = true; 477 } 478 } 479 } 480 } 481 482 if ($modified) { 483 if ($type == 'article') { 484 $heading = $matches[0]->getText(); 485 $body = $matches[1]->getText(); 486 $query = "update `tiki_articles` set `heading`=?, `body`=? where `articleId`=?"; 487 $this->query($query, [ $heading, $body, $objectId]); 488 continue; 489 } else { 490 $data = $matches[0]->getText(); 491 } 492 if ($type == 'wiki page') { 493 $query = "update `tiki_pages` set `data`=?,`page_size`=? where `pageName`=?"; 494 $this->query($query, [ $data,(int) strlen($data), $page]); 495 $this->invalidate_cache($page); 496 } elseif ($type == 'forum post' || substr($type, -7) == 'comment') { 497 $query = "update `tiki_comments` set `data`=? where `threadId`=?"; 498 $this->query($query, [ $data, $objectId]); 499 } elseif ($type == 'post') { 500 $query = "update `tiki_blog_posts` set `data`=? where `postId`=?"; 501 $this->query($query, [ $data, $objectId]); 502 } elseif ($type == 'tracker') { 503 $query = "update `tiki_trackers` set `description`=? where `trackerId`=?"; 504 $this->query($query, [ $data, $objectId]); 505 } elseif ($type == 'trackerfield') { 506 $query = "update `tiki_tracker_fields` set `description`=? where `fieldId`=?"; 507 $this->query($query, [ $data, $objectId]); 508 } elseif ($type == 'trackeritemfield') { 509 $query = "update `tiki_tracker_item_fields` set `value`=? where `itemId`=? and `fieldId`=?"; 510 $this->query($query, [ $data, $objectId[0], $objectId[1]]); 511 } elseif ($type == 'calendar event') { 512 $query = "update `tiki_calendar_items` set `description`=? where `calitemId`=?"; 513 $this->query($query, [ $data, $objectId ]); 514 } 515 } 516 } 517 518 // tiki_footnotes change pageName 519 $query = 'update `tiki_page_footnotes` set `pageName`=? where `pageName`=?'; 520 $this->query($query, [ $newName, $oldName ]); 521 522 // in tiki_categorized_objects update objId 523 $newcathref = 'tiki-index.php?page=' . urlencode($newName); 524 $query = 'update `tiki_objects` set `itemId`=?,`name`=?,`href`=? where `itemId`=? and `type`=?'; 525 $this->query($query, [ $newName, $newName, $newcathref, $oldName, 'wiki page']); 526 527 $this->rename_object('wiki page', $oldName, $newName, $user); 528 529 // update categories if new name has a category default 530 $categlib = TikiLib::lib('categ'); 531 $categories = $categlib->get_object_categories('wiki page', $newName); 532 $info = $this->get_page_info($newName); 533 $categlib->update_object_categories($categories, $newName, 'wiki page', $info['description'], $newName, $newcathref); 534 535 $query = 'update `tiki_wiki_attachments` set `page`=? where `page`=?'; 536 $this->query($query, [ $newName, $oldName ]); 537 538 // group home page 539 if ($renameHomes) { 540 $query = 'update `users_groups` set `groupHome`=? where `groupHome`=?'; 541 $this->query($query, [ $newName, $oldName ]); 542 } 543 544 // copyright 545 $query = 'update tiki_copyrights set `page`=? where `page`=?'; 546 $this->query($query, [ $newName, $oldName ]); 547 548 //breadcrumb 549 if (isset($_SESSION['breadCrumb']) && in_array($oldName, $_SESSION['breadCrumb'])) { 550 $pos = array_search($oldName, $_SESSION["breadCrumb"]); 551 $_SESSION['breadCrumb'][$pos] = $newName; 552 } 553 554 global $prefs; 555 global $user; 556 $tikilib = TikiLib::lib('tiki'); 557 $smarty = TikiLib::lib('smarty'); 558 if ($prefs['feature_use_fgal_for_wiki_attachments'] == 'y') { 559 $query = 'update `tiki_file_galleries` set `name`=? where `name`=?'; 560 $this->query($query, [ $newName, $oldName ]); 561 } 562 563 // first get all watches for this page ... 564 if ($prefs['feature_user_watches'] == 'y') { 565 $nots = $tikilib->get_event_watches('wiki_page_changed', $oldName); 566 } 567 568 // ... then update the watches table 569 // user watches 570 $query = "update `tiki_user_watches` set `object`=?, `title`=?, `url`=? where `object`=? and `type` = 'wiki page'"; 571 $this->query($query, [ $newName, $newName, 'tiki-index.php?page=' . $newName, $oldName ]); 572 $query = "update `tiki_group_watches` set `object`=?, `title`=?, `url`=? where `object`=? and `type` = 'wiki page'"; 573 $this->query($query, [ $newName, $newName, 'tiki-index.php?page=' . $newName, $oldName ]); 574 575 // now send notification email to all on the watchlist: 576 if ($prefs['feature_user_watches'] == 'y') { 577 if (! isset($_SERVER["SERVER_NAME"])) { 578 $_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST']; 579 } 580 581 if (count($nots)) { 582 include_once('lib/notifications/notificationemaillib.php'); 583 $smarty->assign('mail_site', $_SERVER['SERVER_NAME']); 584 $smarty->assign('mail_oldname', $oldName); 585 $smarty->assign('mail_newname', $newName); 586 $smarty->assign('mail_user', $user); 587 sendEmailNotification($nots, 'watch', 'user_watch_wiki_page_renamed_subject.tpl', $_SERVER['SERVER_NAME'], 'user_watch_wiki_page_renamed.tpl'); 588 } 589 } 590 591 require_once('lib/search/refresh-functions.php'); 592 refresh_index('pages', $oldName, false); 593 refresh_index('pages', $newName); 594 595 if ($renameHomes && $prefs['wikiHomePage'] == $oldName) { 596 $tikilib->set_preference('wikiHomePage', $newName); 597 } 598 if ($prefs['feature_trackers'] == 'y') { 599 $trklib = TikiLib::lib('trk'); 600 $trklib->rename_page($oldName, $newName); 601 } 602 603 return true; 604 } 605 606 /** 607 * Gets a wiki content that has just been saved, parses it and stores relations 608 * with wiki pages that will created from this content. 609 * 610 * If content is optionally wiki parsed, this function must be called even if content is not 611 * to be parsed in this case, so that relations can be cleared. 612 * 613 * @param string $data wiki parsed content 614 * @param string $objectType 615 * @param string/int $itemId 616 * @param boolean $wikiParsed Indicates if content is wiki parsed. 617 */ 618 public function update_wikicontent_relations($data, $objectType, $itemId, $wikiParsed = true) 619 { 620 $parserlib = TikiLib::lib('parser'); 621 $relationlib = TikiLib::lib('relation'); 622 623 $relationlib->remove_relations_from($objectType, $itemId, 'tiki.wiki.include'); 624 625 if (! $wikiParsed) { 626 return; 627 } 628 629 // Now create Plugin Include relations 630 $includes = $parserlib->find_plugins($data, 'include'); 631 632 foreach ($includes as $include) { 633 $page = $include['arguments']['page']; 634 if (isset($page)) { 635 $relationlib->add_relation('tiki.wiki.include', $objectType, $itemId, 'wiki page', $page); 636 } 637 } 638 } 639 640 /** 641 * Very similar to update_wikicontent_relations, but for links. 642 * Both should be merged. The only reason they are not is because 643 * wiki pages have their own way of updating content links. 644 * 645 * @param string $data wiki parsed content 646 * @param string $objectType 647 * @param string/int $itemId 648 * @param boolean $wikiParsed Indicates if content is wiki parsed. 649 */ 650 public function update_wikicontent_links($data, $objectType, $itemId, $wikiParsed = true) 651 { 652 $parserlib = TikiLib::lib('parser'); 653 $tikilib = TikiLib::lib('tiki'); 654 655 // First get identifier for tiki_links table 656 if ($objectType == 'wiki page') { 657 $linkhandle = $itemId; 658 } else { 659 $linkhandle = "objectlink:$objectType:$itemId"; 660 } 661 662 $tikilib->clear_links($linkhandle); 663 664 if (! $wikiParsed) { 665 return; 666 } 667 668 // Create tiki_links entries 669 $pages = $parserlib->get_pages($data); 670 foreach ($pages as $a_page) { 671 $tikilib->replace_link($linkhandle, $a_page); 672 } 673 } 674 675 /** 676 * Checks all pages that include $page and parses its contents to 677 * get a list of all Plugin Include calls. 678 * 679 * @param string $page 680 * @return array list of associative arrays with (page, arguments, body) keys 681 */ 682 public function get_external_includes($page) 683 { 684 $relationlib = TikiLib::lib('relation'); 685 $relations = $relationlib->get_relations_to('wiki page', $page, 'tiki.wiki.include'); 686 687 $objectlib = TikiLib::lib('object'); 688 689 $result = []; 690 691 foreach ($relations as $relation) { 692 $data = $objectlib->get_wiki_content($relation['type'], $relation['itemId']); 693 $matches = WikiParser_PluginMatcher::match($data); 694 $argParser = new WikiParser_PluginArgumentParser(); 695 696 $objectlib = TikiLib::lib('object'); 697 698 foreach ($matches as $match) { 699 $arguments = $argParser->parse($match->getArguments()); 700 if ($match->getName() == 'include') { 701 $result[$relation['type'] . ':' . $relation['itemId']] = [ 702 'type' => $relation['type'], 703 'itemId' => $relation['itemId'], 704 'start' => $arguments['start'], 705 'end' => $arguments['end'] 706 ]; 707 } 708 } 709 } 710 711 return array_values($result); 712 } 713 714 public function set_page_cache($page, $cache) 715 { 716 $query = 'update `tiki_pages` set `wiki_cache`=? where `pageName`=?'; 717 $this->query($query, [ $cache, $page]); 718 } 719 720 // TODO: huho why that function is empty ? 721 public function save_notepad($user, $title, $data) 722 { 723 } 724 725 // Methods to cache and handle the cached version of wiki pages 726 // to prevent parsing large pages. 727 public function get_cache_info($page) 728 { 729 $query = 'select `cache`,`cache_timestamp` from `tiki_pages` where `pageName`=?'; 730 731 $result = $this->query($query, [ $page ]); 732 $res = $result->fetchRow(); 733 return $res; 734 } 735 736 public function get_parse($page, &$canBeRefreshed = false, $suppress_icons = false) 737 { 738 global $prefs, $user; 739 $tikilib = TikiLib::lib('tiki'); 740 $headerlib = TikiLib::lib('header'); 741 $content = ''; 742 $canBeRefreshed = false; 743 744 $info = $this->get_page_info($page); 745 if (empty($info)) { 746 return ''; 747 } 748 749 $parse_options = [ 750 'is_html' => $info['is_html'], 751 'language' => $info['lang'], 752 'namespace' => $info['namespace'], 753 ]; 754 755 if ($suppress_icons || (! empty($info['lockedby']) && $info['lockedby'] != $user)) { 756 $parse_options['suppress_icons'] = true; 757 } 758 759 if ($prefs['wysiwyg_inline_editing'] === 'y' && getCookie('wysiwyg_inline_edit', "preview", false)) { 760 $parse_options['ck_editor'] = true; 761 $parse_options['suppress_icons'] = true; 762 } 763 764 $wiki_cache = ($prefs['feature_wiki_icache'] == 'y' && ! is_null($info['wiki_cache'])) ? $info['wiki_cache'] : $prefs['wiki_cache']; 765 766 if ($wiki_cache > 0 && empty($_REQUEST['offset']) && empty($_REQUEST['itemId']) && (empty($user) || $prefs['wiki_cache'] == 0)) { 767 $cache_info = $this->get_cache_info($page); 768 if (! empty($cache_info['cache_timestamp']) && $cache_info['cache_timestamp'] + $wiki_cache >= $this->now) { 769 $content = $cache_info['cache']; 770 // get any cached JS and add to headerlib JS 771 $jsFiles = $headerlib->getJsFromHTML($content, false, true); 772 773 foreach ($jsFiles as $jsFile) { 774 $headerlib->add_jsfile($jsFile); 775 } 776 777 $headerlib->add_js(implode("\n", $headerlib->getJsFromHTML($content))); 778 779 // now remove all the js from the source 780 $content = $headerlib->removeJsFromHtml($content); 781 782 $canBeRefreshed = true; 783 } else { 784 $jsFile1 = $headerlib->getJsFilesWithScriptTags(); 785 $js1 = $headerlib->getJs(); 786 $info['outputType'] = $tikilib->getOne("SELECT `outputType` FROM `tiki_output` WHERE `entityId` = ? AND `objectType` = ? AND `version` = ?", [$info['pageName'], 'wikiPage', $info['version']]); 787 $content = (new WikiLibOutput($info, $info['data'], $parse_options))->parsedValue; 788 789 // get any JS added to headerlib during parse_data and add to the bottom of the data to cache 790 $jsFile2 = $headerlib->getJsFilesWithScriptTags(); 791 $js2 = $headerlib->getJs(); 792 793 $jsFile = array_diff($jsFile2, $jsFile1); 794 $js = array_diff($js2, $js1); 795 796 $jsFile = implode("\n", $jsFile); 797 $js = $headerlib->wrap_js(implode("\n", $js)); 798 799 $this->update_cache($page, $content . $jsFile . $js); 800 } 801 } else { 802 $content = (new WikiLibOutput($info, $info['data'], $parse_options, $info['version']))->parsedValue; 803 } 804 805 return $content; 806 } 807 808 public function update_cache($page, $data) 809 { 810 $query = 'update `tiki_pages` set `cache`=?, `cache_timestamp`=? where `pageName`=?'; 811 $result = $this->query($query, [ $data, $this->now, $page ]); 812 return true; 813 } 814 815 public function get_attachment_owner($attId) 816 { 817 return $this->getOne("select `user` from `tiki_wiki_attachments` where `attId`=$attId"); 818 } 819 820 public function remove_wiki_attachment($attId) 821 { 822 global $prefs; 823 824 $path = $this->getOne("select `path` from `tiki_wiki_attachments` where `attId`=?", [$attId]); 825 826 /* carefull a same file can be attached in different page */ 827 if ($path && $this->getOne("select count(*) from `tiki_wiki_attachments` where `path`=?", [$path]) <= 1) { 828 @unlink($prefs['w_use_dir'] . $path); 829 } 830 831 $query = "delete from `tiki_wiki_attachments` where `attId`=?"; 832 $result = $this->query($query, [$attId]); 833 if ($prefs['feature_actionlog'] == 'y') { 834 $logslib = TikiLib::lib('logs'); 835 $logslib->add_action('Removed', $attId, 'wiki page attachment'); 836 } 837 } 838 839 public function wiki_attach_file($page, $name, $type, $size, $data, $comment, $user, $fhash, $date = '') 840 { 841 $comment = strip_tags($comment); 842 $now = empty($date) ? $this->now : $date; 843 $attId = $this->table('tiki_wiki_attachments')->insert([ 844 'page' => $page, 845 'filename' => $name, 846 'filesize' => (int) $size, 847 'filetype' => $type, 848 'data' => $data, 849 'created' => (int) $now, 850 'hits' => 0, 851 'user' => $user, 852 'comment' => $comment, 853 'path' => $fhash, 854 ]); 855 856 global $prefs; 857 TikiLib::events()->trigger( 858 'tiki.wiki.attachfile', 859 [ 860 'type' => 'file', 861 'object' => $attId, 862 'wiki' => $page, 863 'user' => $user, 864 ] 865 ); 866 if ($prefs['feature_user_watches'] = 'y') { 867 include_once(__DIR__ . '/../notifications/notificationemaillib.php'); 868 sendWikiEmailNotification('wiki_file_attached', $page, $user, $comment, '', $name, '', '', false, '', 0, $attId); 869 } 870 if ($prefs['feature_actionlog'] == 'y') { 871 $logslib = TikiLib::lib('logs'); 872 $logslib->add_action('Created', $attId, 'wiki page attachment', '', $user); 873 } 874 return $attId; 875 } 876 877 public function get_wiki_attach_file($page, $name, $type, $size) 878 { 879 $query = 'select * from `tiki_wiki_attachments` where `page`=? and `filename`=? and `filetype`=? and `filesize`=?'; 880 $result = $this->query($query, [$page, $name, $type, $size]); 881 $res = $result->fetchRow(); 882 return $res; 883 } 884 885 public function list_wiki_attachments($page, $offset = 0, $maxRecords = -1, $sort_mode = 'created_desc', $find = '') 886 { 887 if ($find) { 888 $mid = ' where `page`=? and (`filename` like ?)'; // why braces? 889 $bindvars = [$page,'%' . $find . '%']; 890 } else { 891 $mid = ' where `page`=? '; 892 $bindvars = [$page]; 893 } 894 895 if ($sort_mode !== 'created_desc') { 896 $pos = strrpos($sort_mode, '_'); 897 // check the sort order is valid for attachments 898 if ($pos !== false && $pos > 0) { 899 $shortsort = substr($sort_mode, 0, $pos); 900 } else { 901 $shortsort = $sort_mode; 902 } 903 if (! in_array(['user','attId','page','filename','filesize','filetype','hits','created','comment'], $shortsort)) { 904 $sort_mode = 'created_desc'; 905 } 906 } 907 908 $query = 'select `user`,`attId`,`page`,`filename`,`filesize`,`filetype`,`hits`,`created`,`comment`' . 909 ' from `tiki_wiki_attachments` ' . $mid . ' order by ' . $this->convertSortMode($sort_mode); 910 $query_cant = "select count(*) from `tiki_wiki_attachments` $mid"; 911 $result = $this->query($query, $bindvars, $maxRecords, $offset); 912 $cant = $this->getOne($query_cant, $bindvars); 913 $ret = []; 914 915 while ($res = $result->fetchRow()) { 916 $ret[] = $res; 917 } 918 919 $retval = []; 920 $retval['data'] = $ret; 921 $retval['cant'] = $cant; 922 return $retval; 923 } 924 public function list_all_attachements($offset = 0, $maxRecords = -1, $sort_mode = 'created_desc', $find = '') 925 { 926 if ($find) { 927 $findesc = '%' . $find . '%'; 928 $mid = ' where `filename` like ?'; 929 $bindvars = [$findesc]; 930 } else { 931 $mid = ''; 932 $bindvars = []; 933 } 934 $query = 'select `user`,`attId`,`page`,`filename`,`filesize`,`filetype`,`hits`,`created`,`comment`,`path` '; 935 $query .= ' from `tiki_wiki_attachments` '. $mid .' order by ' . $this->convertSortMode($sort_mode); 936 $query_cant = "select count(*) from `tiki_wiki_attachments` $mid"; 937 $result = $this->query($query, $bindvars, $maxRecords, $offset); 938 $cant = $this->getOne($query_cant, $bindvars); 939 $ret = []; 940 while ($res = $result->fetchRow()) { 941 $ret[] = $res; 942 } 943 $retval = []; 944 $retval['data'] = $ret; 945 $retval['cant'] = $cant; 946 return $retval; 947 } 948 949 public function file_to_db($path, $attId) 950 { 951 if (is_file($path)) { 952 $fp = fopen($path, 'rb'); 953 $data = ''; 954 while (! feof($fp)) { 955 $data .= fread($fp, 8192 * 16); 956 } 957 fclose($fp); 958 $query = 'update `tiki_wiki_attachments` set `data`=?,`path`=? where `attId`=?'; 959 if ($this->query($query, [$data,'',(int) $attId])) { 960 unlink($path); 961 } 962 } 963 } 964 965 public function db_to_file($filename, $attId) 966 { 967 global $prefs; 968 $file_name = md5($filename . date('U') . rand()); 969 $fw = fopen($prefs['w_use_dir'] . $file_name, 'wb'); 970 $data = $this->getOne('select `data` from `tiki_wiki_attachments` where `attId`=?', [(int) $attId]); 971 if ($data) { 972 fwrite($fw, $data); 973 } 974 fclose($fw); 975 if (is_file($prefs['w_use_dir'] . $file_name)) { 976 $query = 'update `tiki_wiki_attachments` set `data`=?,`path`=? where `attId`=?'; 977 $this->query($query, ['',$file_name,(int) $attId]); 978 } 979 } 980 981 public function get_item_attachment($attId) 982 { 983 $query = 'select * from `tiki_wiki_attachments` where `attId`=?'; 984 $result = $this->query($query, [(int) $attId]); 985 if (! $result->numRows()) { 986 return false; 987 } 988 $res = $result->fetchRow(); 989 return $res; 990 } 991 992 public function get_item_attachement_data($att_info) 993 { 994 if ($att_info['path']) { 995 return file_get_contents($att_info['filename']); 996 } else { 997 return $att_info['data']; 998 } 999 } 1000 1001 1002 // Functions for wiki page footnotes 1003 public function get_footnote($user, $page) 1004 { 1005 $count = $this->getOne('select count(*) from `tiki_page_footnotes` where `user`=? and `pageName`=?', [$user,$page]); 1006 1007 if (! $count) { 1008 return ''; 1009 } else { 1010 return $this->getOne('select `data` from `tiki_page_footnotes` where `user`=? and `pageName`=?', [$user,$page]); 1011 } 1012 } 1013 1014 public function replace_footnote($user, $page, $data) 1015 { 1016 $querydel = 'delete from `tiki_page_footnotes` where `user`=? and `pageName`=?'; 1017 $this->query($querydel, [$user, $page], -1, -1, false); 1018 $query = 'insert into `tiki_page_footnotes`(`user`,`pageName`,`data`) values(?,?,?)'; 1019 $this->query($query, [$user,$page,$data]); 1020 } 1021 1022 public function remove_footnote($user, $page) 1023 { 1024 if (empty($user)) { 1025 $query = 'delete from `tiki_page_footnotes` where `pageName`=?'; 1026 $this->query($query, [$page]); 1027 } else { 1028 $query = 'delete from `tiki_page_footnotes` where `user`=? and `pageName`=?'; 1029 $this->query($query, [$user,$page]); 1030 } 1031 } 1032 1033 public function wiki_link_structure() 1034 { 1035 $query = 'select `pageName` from `tiki_pages` order by ' . $this->convertSortMode('pageName_asc'); 1036 1037 $result = $this->query($query); 1038 1039 while ($res = $result->fetchRow()) { 1040 print ($res['pageName'] . ' '); 1041 1042 $page = $res['pageName']; 1043 $query2 = 'select `toPage` from `tiki_links` where `fromPage`=?'; 1044 $result2 = $this->query($query2, [ $page ]); 1045 $pages = []; 1046 1047 while ($res2 = $result2->fetchRow()) { 1048 if (($res2['toPage'] <> $res['pageName']) && (! in_array($res2['toPage'], $pages))) { 1049 $pages[] = $res2['toPage']; 1050 print ($res2['toPage'] . ' '); 1051 } 1052 } 1053 1054 print ("\n"); 1055 } 1056 } 1057 1058 // Removes last version of the page (from pages) if theres some 1059 // version in the tiki_history then the last version becomes the actual version 1060 public function remove_last_version($page, $comment = '') 1061 { 1062 global $prefs; 1063 $this->invalidate_cache($page); 1064 $query = 'select * from `tiki_history` where `pageName`=? order by ' . $this->convertSortMode('lastModif_desc'); 1065 $result = $this->query($query, [ $page ]); 1066 1067 if ($result->numRows()) { 1068 // We have a version 1069 $res = $result->fetchRow(); 1070 1071 $histlib = TikiLib::lib('hist'); 1072 1073 if ($prefs['feature_contribution'] == 'y') { 1074 $contributionlib = TikiLib::lib('contribution'); 1075 $tikilib = TikiLib::lib('tiki'); 1076 $info = $tikilib->get_page_info($res['pageName']); 1077 1078 $contributionlib->change_assigned_contributions( 1079 $res['historyId'], 1080 'history', 1081 $res['pageName'], 1082 'wiki page', 1083 $info['description'], 1084 $res['pageName'], 1085 'tiki-index.php?page' . urlencode($res['pageName']) 1086 ); 1087 } 1088 $ret = $histlib->remove_version($res['pageName'], $res['version']); 1089 $ret2 = $histlib->restore_page_from_history($res['pageName']); 1090 } else { 1091 $ret = $this->remove_all_versions($page); 1092 } 1093 if ($ret) { 1094 $logslib = TikiLib::lib('logs'); 1095 $logslib->add_action('Removed last version', $page, 'wiki page', $comment); 1096 //get_strings tra("Removed last version"); 1097 } 1098 return $ret; 1099 } 1100 1101 /** 1102 * Return the page names for a page alias, if any. 1103 * 1104 * Unfortunately there is no mechanism to prevent two 1105 * different pages from sharing the same alias and that is 1106 * why this method return an array of page names instead of a 1107 * page name string. 1108 * 1109 * @param string $alias 1110 * @return array page names 1111 */ 1112 public function get_pages_by_alias($alias) 1113 { 1114 global $prefs; 1115 $semanticlib = TikiLib::lib('semantic'); 1116 1117 $pages = []; 1118 1119 if ($prefs['feature_wiki_pagealias'] == 'n' && empty($prefs["wiki_prefixalias_tokens"])) { 1120 return $pages; 1121 } 1122 1123 $toPage = $alias; 1124 $tokens = explode(',', $prefs['wiki_pagealias_tokens']); 1125 1126 $prefixes = explode(',', $prefs["wiki_prefixalias_tokens"]); 1127 foreach ($prefixes as $p) { 1128 $p = trim($p); 1129 if (strlen($p) > 0 && TikiLib::strtolower(substr($alias, 0, strlen($p))) == TikiLib::strtolower($p)) { 1130 $toPage = $p; 1131 $tokens = 'prefixalias'; 1132 } 1133 } 1134 1135 $links = $semanticlib->getLinksUsing($tokens, [ 'toPage' => $toPage ]); 1136 1137 if (empty($links)) { // if no linked pages found then the alias may be sefurl "slug" encoded, so try the un-slugged version 1138 global $prefs; 1139 1140 $toPage = TikiLib::lib('slugmanager')->degenerate($prefs['wiki_url_scheme'], $toPage); 1141 $links = $semanticlib->getLinksUsing($tokens, [ 'toPage' => $toPage ]); 1142 } 1143 1144 if (count($links) > 0) { 1145 foreach ($links as $row) { 1146 $pages[] = $row['fromPage']; 1147 } 1148 } 1149 1150 return $pages; 1151 } 1152 1153 // Like pages are pages that share a word in common with the current page 1154 public function get_like_pages($page) 1155 { 1156 global $user, $prefs; 1157 $semanticlib = TikiLib::lib('semantic'); 1158 $tikilib = TikiLib::lib('tiki'); 1159 1160 preg_match_all("/([A-Z])([a-z]+)/", $page, $words); 1161 1162 // Add support to ((x)) in either strict or full modes 1163 preg_match_all("/(([A-Za-z]|[\x80-\xFF])+)/", $page, $words2); 1164 $words = array_unique(array_merge($words[0], $words2[0])); 1165 $exps = []; 1166 $bindvars = []; 1167 foreach ($words as $word) { 1168 $exps[] = ' `pageName` like ?'; 1169 $bindvars[] = "%$word%"; 1170 } 1171 1172 $exp = implode(' or ', $exps); 1173 if ($exp) { 1174 $query = "select `pageName`, `lang` from `tiki_pages` where ($exp)"; 1175 1176 if ($prefs['feature_multilingual'] == 'y') { 1177 $query .= ' ORDER BY CASE WHEN `lang` = ? THEN 0 WHEN `lang` IS NULL OR `lang` = \'\' THEN 1 ELSE 2 END'; 1178 $bindvars[] = $prefs['language']; 1179 } 1180 1181 $result = $this->query($query, $bindvars); 1182 $ret = []; 1183 1184 while ($res = $result->fetchRow()) { 1185 if ($prefs['wiki_likepages_samelang_only'] == 'y' && ! empty($res['lang']) && $res['lang'] != $prefs['language']) { 1186 continue; 1187 } 1188 1189 if ($tikilib->user_has_perm_on_object($user, $res['pageName'], 'wiki page', 'tiki_p_view')) { 1190 $ret[] = $res['pageName']; 1191 } 1192 } 1193 1194 asort($ret); 1195 return $ret; 1196 } else { 1197 return []; 1198 } 1199 } 1200 1201 public function is_locked($page, $info = null) 1202 { 1203 if (! $info) { 1204 $query = "select `flag`, `user` from `tiki_pages` where `pageName`=?"; 1205 $result = $this->query($query, [ $page ]); 1206 $info = $result->fetchRow(); 1207 } 1208 1209 return ($info['flag'] == 'L') ? $info['user'] : null; 1210 } 1211 1212 public function get_locked() { 1213 $locked = []; 1214 $query = "select `pageName`, 'lockedby', 'lastModif' from `tiki_pages` where `flag`='L'"; 1215 return $this->fetchAll($query); 1216 } 1217 1218 public function is_editable($page, $user, $info = null) 1219 { 1220 global $prefs; 1221 $perms = Perms::get([ 'type' => 'wiki page', 'object' => $page ]); 1222 1223 if ($perms->admin_wiki) { 1224 return true; 1225 } 1226 1227 if ($prefs['wiki_creator_admin'] == 'y' && ! empty($user) && $info['creator'] == $user) { 1228 return true; 1229 } 1230 1231 if ($prefs['feature_wiki_userpage'] == 'y' 1232 && ! empty($user) 1233 && strcasecmp($prefs['feature_wiki_userpage_prefix'], substr($page, 0, strlen($prefs['feature_wiki_userpage_prefix']))) == 0 1234 ) { 1235 if (strcasecmp($page, $prefs['feature_wiki_userpage_prefix'] . $user) == 0) { 1236 return true; 1237 } 1238 } 1239 1240 if ($prefs['feature_wiki_userpage'] == 'y' 1241 && strcasecmp(substr($page, 0, strlen($prefs['feature_wiki_userpage_prefix'])), $prefs['feature_wiki_userpage_prefix']) == 0 1242 and strcasecmp($page, $prefs['feature_wiki_userpage_prefix'] . $user) != 0 1243 ) { 1244 return false; 1245 } 1246 if (! $perms->edit) { 1247 return false; 1248 } 1249 1250 return ($this->is_locked($page, $info) == null || $user == $this->is_locked($page, $info)) ? true : false; 1251 } 1252 1253 /** 1254 * Lock a wiki page 1255 * 1256 * @param $page 1257 * 1258 * @return TikiDb_Pdo_Result|TikiDb_Adodb_Result 1259 */ 1260 public function lock_page($page) 1261 { 1262 global $user, $tikilib; 1263 1264 $query = 'update `tiki_pages` set `flag`=?, `lockedby`=? where `pageName`=?'; 1265 $result = $this->query($query, [ 'L', $user, $page ]); 1266 1267 if (! empty($user)) { 1268 $info = $tikilib->get_page_info($page); 1269 1270 $query = 'update `tiki_pages` set `user`=?, `comment`=?, `version`=? where `pageName`=?'; 1271 $this->query($query, [$user, tra('Page locked'), $info['version'] + 1, $page]); 1272 1273 $query = 'insert into `tiki_history`(`pageName`, `version`, `lastModif`, `user`, `ip`, `comment`, `data`, `description`)' . 1274 ' values(?,?,?,?,?,?,?,?)'; 1275 $this->query( 1276 $query, 1277 [ 1278 $page, 1279 (int) $info['version'] + 1, 1280 (int) $info['lastModif'], 1281 $user, 1282 $info['ip'], 1283 tra('Page locked'), 1284 $info['data'], 1285 $info['description'] 1286 ] 1287 ); 1288 } 1289 1290 return $result; 1291 } 1292 1293 public function unlock_page($page) 1294 { 1295 global $user; 1296 $tikilib = TikiLib::lib('tiki'); 1297 1298 $query = "update `tiki_pages` set `flag`='' where `pageName`=?"; 1299 $result = $this->query($query, [$page]); 1300 1301 if (isset($user)) { 1302 $info = $tikilib->get_page_info($page); 1303 1304 $query = "update `tiki_pages` set `user`=?, `comment`=?, `version`=? where `pageName`=?"; 1305 $result = $this->query($query, [$user, tra('Page unlocked'), $info['version'] + 1, $page]); 1306 1307 $query = "insert into `tiki_history`(`pageName`, `version`, `lastModif`, `user`, `ip`, `comment`, `data`, `description`) values(?,?,?,?,?,?,?,?)"; 1308 $result = $this->query( 1309 $query, 1310 [ 1311 $page, 1312 (int) $info['version'] + 1, 1313 (int) $info['lastModif'], 1314 $user, 1315 $info['ip'], 1316 tra('Page unlocked'), 1317 $info['data'], 1318 $info['description'] 1319 ] 1320 ); 1321 } 1322 1323 return true; 1324 } 1325 1326 // Returns backlinks for a given page 1327 public function get_backlinks($page) 1328 { 1329 global $prefs; 1330 $query = "select `fromPage` from `tiki_links` where `toPage` = ?"; 1331 $result = $this->query($query, [ $page ]); 1332 $ret = []; 1333 1334 while ($res = $result->fetchRow()) { 1335 $is_wiki_page = substr($res['fromPage'], 0, 11) != 'objectlink:'; 1336 if ($is_wiki_page) { 1337 $type = 'wiki page'; 1338 $objectId = $res['fromPage']; 1339 } else { 1340 $objectlinkparts = explode(':', $res['fromPage']); 1341 $type = $objectlinkparts[1]; 1342 $objectId = substr($res['fromPage'], strlen($type) + 12); 1343 if ($type == 'trackeritemfield') { 1344 $feature = 'wiki_backlinks_show_trackeritem'; 1345 } elseif (substr($type, -7) == 'comment') { 1346 $feature = 'wiki_backlinks_show_comment'; 1347 } else { 1348 $feature = 'wiki_backlinks_show_' . str_replace(" ", "_", $type); 1349 } 1350 if ($prefs[$feature] !== 'y') { 1351 continue; 1352 } 1353 } 1354 if ($type == 'trackeritemfield') { 1355 list($itemId, $fieldId) = explode(':', $objectId); 1356 $itemObject = Tracker_Item::fromId($itemId); 1357 if (! $itemObject->canView() || ! $itemObject->canViewField($fieldId)) { 1358 continue; 1359 } 1360 } else { 1361 $objectperms = Perms::get(['type' => $type, 'object' => $objectId]); 1362 if (! $objectperms->view) { 1363 continue; 1364 } 1365 } 1366 $aux["type"] = $type; 1367 $aux["objectId"] = $objectId; 1368 $ret[] = $aux; 1369 } 1370 1371 return $ret; 1372 } 1373 1374 public function get_parent_pages($child_page) 1375 { 1376 $parent_pages = []; 1377 $backlinks_info = $this->get_backlinks($child_page); 1378 foreach ($backlinks_info as $index => $backlink) { 1379 $parent_pages[] = $backlink['fromPage']; 1380 } 1381 return $parent_pages; 1382 } 1383 1384 public function list_plugins($with_help = false, $area_id = 'editwiki', $onlyEnabled = true) 1385 { 1386 $parserlib = TikiLib::lib('parser'); 1387 1388 if ($with_help) { 1389 global $prefs; 1390 $cachelib = TikiLib::lib('cache'); 1391 $commonKey = '{{{area-id}}}'; 1392 $cachetag = 'plugindesc' . $this->get_language() . '_js=' . $prefs['javascript_enabled']; 1393 if (! $plugins = $cachelib->getSerialized($cachetag)) { 1394 $list = $parserlib->plugin_get_list(); 1395 1396 $plugins = []; 1397 foreach ($list as $name) { 1398 $pinfo = [ 1399 'help' => $parserlib->get_plugin_description($name, $enabled, $commonKey), 1400 'name' => TikiLib::strtoupper($name), 1401 ]; 1402 1403 if (! $onlyEnabled || $enabled) { 1404 $info = $parserlib->plugin_info($name); 1405 $pinfo['title'] = $info['name']; 1406 unset($info['name']); 1407 $pinfo = array_merge($pinfo, $info); 1408 1409 $plugins[] = $pinfo; 1410 } 1411 } 1412 usort( 1413 $plugins, 1414 function ($ar1, $ar2) { 1415 return strcasecmp($ar1['title'], $ar2['title']); // sort by translated name 1416 } 1417 ); 1418 $cachelib->cacheItem($cachetag, serialize($plugins)); 1419 } 1420 array_walk_recursive( 1421 $plugins, 1422 function (& $item) use ($commonKey, $area_id) { 1423 $item = str_replace($commonKey, $area_id, $item); 1424 } 1425 ); 1426 return $plugins; 1427 } else { 1428 // Only used by WikiPluginPluginManager 1429 $files = []; 1430 1431 if (is_dir(PLUGINS_DIR)) { 1432 if ($dh = opendir(PLUGINS_DIR)) { 1433 while (($file = readdir($dh)) !== false) { 1434 if (preg_match("/^wikiplugin_.*\.php$/", $file)) { 1435 array_push($files, $file); 1436 } 1437 } 1438 closedir($dh); 1439 } 1440 } 1441 sort($files); 1442 1443 return $files; 1444 } 1445 } 1446 1447 // get all modified pages for a user (if actionlog is not clean) 1448 public function get_user_all_pages($user, $sort_mode) 1449 { 1450 $query = "select p.`pageName`, p.`user` as lastEditor, p.`creator`, max(a.`lastModif`) as date" . 1451 " from `tiki_actionlog` as a, `tiki_pages` as p" . 1452 " where a.`object`= p.`pageName` and a.`user`= ? and (a.`action`=? or a.`action`=?)" . 1453 " group by p.`pageName`, p.`user`, p.`creator` order by " . $this->convertSortMode($sort_mode); 1454 1455 $result = $this->query($query, [$user, 'Updated', 'Created']); 1456 $ret = []; 1457 1458 while ($res = $result->fetchRow()) { 1459 if ($this->user_has_perm_on_object($user, $res['pageName'], 'wiki page', 'tiki_p_view')) { 1460 $ret[] = $res; 1461 } 1462 } 1463 return $ret; 1464 } 1465 1466 public function get_default_wiki_page() 1467 { 1468 global $user, $prefs; 1469 if ($prefs['useGroupHome'] == 'y') { 1470 $userlib = TikiLib::lib('user'); 1471 if ($groupHome = $userlib->get_user_default_homepage($user)) { 1472 return $groupHome; 1473 } else { 1474 return $prefs['wikiHomePage']; 1475 } 1476 } 1477 return $prefs['wikiHomePage']; 1478 } 1479 1480 public function sefurl($page, $with_next = '', $all_langs = '') 1481 { 1482 global $prefs, $info; 1483 $smarty = TikiLib::lib('smarty'); 1484 $script_name = 'tiki-index.php'; 1485 1486 if ($prefs['feature_multilingual_one_page'] == 'y') { 1487 // if ( basename($_SERVER['PHP_SELF']) == 'tiki-all_languages.php' ) { 1488 // return 'tiki-all_languages.php?page='.urlencode($page); 1489 // } 1490 1491 if ($all_langs == 'y') { 1492 $script_name = 'tiki-all_languages.php'; 1493 } 1494 } 1495 1496 $pages = TikiDb::get()->table('tiki_pages'); 1497 $page = $pages->fetchOne('pageSlug', ['pageName' => $page]) ?: $page; 1498 $href = "$script_name?page=" . $page; 1499 1500 if (isset($prefs['feature_wiki_use_date_links']) && $prefs['feature_wiki_use_date_links'] == 'y') { 1501 if (isset($_REQUEST['date'])) { 1502 $href .= '&date=' . urlencode($_REQUEST['date']); 1503 } elseif (isset($_REQUEST['version'])) { 1504 $href .= '&date=' . urlencode($info['lastModif']); 1505 } 1506 } 1507 1508 if ($with_next) { 1509 $href .= '&'; 1510 } 1511 1512 if ($prefs['feature_sefurl'] == 'y') { 1513 // escape colon chars so the url doesn't appear to be protocol:address - occurs with user pages and namespaces 1514 $href = str_replace(':', '%3A', $href); 1515 1516 include_once(__DIR__ . '/../../tiki-sefurl.php'); 1517 return filter_out_sefurl($href, 'wiki'); 1518 } else { 1519 return $href; 1520 } 1521 } 1522 1523 public function url_for_operation_on_a_page($script_name, $page, $with_next) 1524 { 1525 $href = "$script_name?page=" . urlencode($page); 1526 if ($with_next) { 1527 $href .= '&'; 1528 } 1529 return $href; 1530 } 1531 1532 public function editpage_url($page, $with_next) 1533 { 1534 return $this->url_for_operation_on_a_page('tiki-editpage.php', $page, $with_next); 1535 } 1536 1537 public function move_attachments($old, $new) 1538 { 1539 $query = 'update `tiki_wiki_attachments` set `page`=? where `page`=?'; 1540 $this->query($query, [$new, $old]); 1541 } 1542 1543 public function duplicate_page($old, $new) 1544 { 1545 $query = 'insert into `tiki_pages`' . 1546 ' (`pageName`,`hits`,`data`,`lastModif`,`comment`,`version`,`user`,`ip`,`description`,' . 1547 ' `creator`,`page_size`,`is_html`,`created`, `flag`,`points`,`votes`,`pageRank`,`lang`,' . 1548 ' `lockedby`) select ?,`hits`,`data`,`lastModif`,`comment`,`version`,`user`,`ip`,' . 1549 ' `description`,`creator`,`page_size`,`is_html`,`created`, `flag`,`points`,`votes`' . 1550 ',`pageRank`,`lang`,`lockedby` from `tiki_pages` where `pageName`=?'; 1551 $this->query($query, [$new, $old]); 1552 } 1553 1554 public function get_pages_contains($searchtext, $offset = 0, $maxRecords = -1, $sort_mode = 'pageName_asc', $categFilter = []) 1555 { 1556 $jail_bind = []; 1557 $jail_join = ''; 1558 $jail_where = ''; 1559 1560 if ($categFilter) { 1561 $categlib = TikiLib::lib('categ'); 1562 $categlib->getSqlJoin($categFilter, 'wiki page', '`tiki_pages`.`pageName`', $jail_join, $jail_where, $jail_bind); 1563 } 1564 1565 $query = "select * from `tiki_pages` $jail_join where `tiki_pages`.`data` like ? $jail_where order by " . $this->convertSortMode($sort_mode); 1566 $bindvars = ['%' . $searchtext . '%']; 1567 $bindvars = array_merge($bindvars, $jail_bind); 1568 $results = $this->fetchAll($query, $bindvars, $maxRecords, $offset); 1569 $ret['data'] = $results; 1570 $query_cant = "select count(*) from (select count(*) from `tiki_pages` $jail_join where `data` like ? $jail_where group by `page_id`) as `temp`"; 1571 $ret['cant'] = $this->getOne($query_cant, $bindvars); 1572 1573 return $ret; 1574 } 1575 1576 /* 1577 * get_page_auto_toc 1578 * Get the auto generated TOC setting for the page 1579 * @return 1580 * +1 page_auto_toc is explicitly set to true 1581 * 0 page_auto_toc is not set for page. Use global setting 1582 * -1 page_auto_toc is explicitly set to false 1583 */ 1584 public function get_page_auto_toc($pageName) 1585 { 1586 $attributes = TikiLib::lib('attribute')->get_attributes('wiki page', $pageName); 1587 $rc = 0; 1588 if (! isset($attributes['tiki.wiki.autotoc'])) { 1589 return 0; 1590 } 1591 $value = (int)$attributes['tiki.wiki.autotoc']; 1592 if ($value > 0) { 1593 return 1; 1594 } else { 1595 return -1; 1596 } 1597 } 1598 1599 public function set_page_auto_toc($pageName, $isAutoToc) 1600 { 1601 TikiLib::lib('attribute')->set_attribute('wiki page', $pageName, 'tiki.wiki.autotoc', $isAutoToc); 1602 } 1603 1604 1605 1606 /* 1607 * get_page_hide_title 1608 * Enable the page title to not be displayed, on a per-page basis. 1609 * @return 1610 * +1 page_hide_title is explicitly set to true 1611 * 0 page_hide_title is not set for page. Use global setting 1612 * -1 page_hide_title is explicitly set to false 1613 */ 1614 public function get_page_hide_title($pageName) 1615 { 1616 $attributes = TikiLib::lib('attribute')->get_attributes('wiki page', $pageName); 1617 $rc = 0; 1618 if (! isset($attributes['tiki.wiki.page_hide_title'])) { 1619 return 0; 1620 } 1621 $value = (int)$attributes['tiki.wiki.page_hide_title']; 1622 if ($value > 0) { 1623 return 1; 1624 } else { 1625 return -1; 1626 } 1627 } 1628 1629 public function set_page_hide_title($pageName, $isHideTitle) 1630 { 1631 TikiLib::lib('attribute')->set_attribute('wiki page', $pageName, 'tiki.wiki.page_hide_title', $isHideTitle); 1632 } 1633 1634 public function get_without_namespace($pageName) 1635 { 1636 global $prefs; 1637 1638 if ((isset($prefs['namespace_enabled']) && $prefs['namespace_enabled'] == 'y') && $prefs['namespace_separator']) { 1639 $pos = strrpos($pageName, $prefs['namespace_separator']); 1640 1641 if (false !== $pos) { 1642 return substr($pageName, $pos + strlen($prefs['namespace_separator'])); 1643 } else { 1644 return $pageName; 1645 } 1646 } else { 1647 return $pageName; 1648 } 1649 } 1650 1651 public function get_explicit_namespace($pageName) 1652 { 1653 $attributes = TikiLib::lib('attribute')->get_attributes('wiki page', $pageName); 1654 return isset($attributes['tiki.wiki.namespace']) ? $attributes['tiki.wiki.namespace'] : ''; 1655 } 1656 1657 public function set_explicit_namespace($pageName, $namespace) 1658 { 1659 TikiLib::lib('attribute')->set_attribute('wiki page', $pageName, 'tiki.wiki.namespace', $namespace); 1660 } 1661 1662 public function get_namespace($pageName) 1663 { 1664 global $prefs; 1665 1666 if ($pageName 1667 && $prefs['namespace_enabled'] == 'y' 1668 && $prefs['namespace_separator'] 1669 ) { 1670 $explicit = $this->get_explicit_namespace($pageName); 1671 1672 if ($explicit) { 1673 return $explicit; 1674 } 1675 1676 $pos = strrpos($pageName, $prefs['namespace_separator']); 1677 1678 if (false !== $pos) { 1679 return substr($pageName, 0, $pos); 1680 } 1681 } 1682 1683 return false; 1684 } 1685 1686 public function get_readable($pageName) 1687 { 1688 global $prefs; 1689 1690 if ($pageName 1691 && $prefs['namespace_enabled'] == 'y' 1692 && $prefs['namespace_separator'] 1693 ) { 1694 return str_replace($prefs['namespace_separator'], ' / ', $pageName); 1695 } 1696 1697 return $pageName; 1698 } 1699 1700 public function include_default_namespace($pageName) 1701 { 1702 global $prefs; 1703 1704 if ($prefs['namespace_enabled'] == 'y' && ! empty($prefs['namespace_default'])) { 1705 return $prefs['namespace_default'] . $prefs['namespace_separator'] . $pageName; 1706 } else { 1707 return $pageName; 1708 } 1709 } 1710 1711 public function include_namespace($pageName, $namespace) 1712 { 1713 global $prefs; 1714 1715 if ($prefs['namespace_enabled'] == 'y' && $namespace) { 1716 return $namespace . $prefs['namespace_separator'] . $pageName; 1717 } else { 1718 return $pageName; 1719 } 1720 } 1721 1722 public function get_namespace_parts($pageName) 1723 { 1724 global $prefs; 1725 1726 if ($namespace = $this->get_namespace($pageName)) { 1727 return explode($prefs['namespace_separator'], $namespace); 1728 } 1729 1730 return []; 1731 } 1732 1733 // Page display options 1734 ////////////////////////// 1735 public function processPageDisplayOptions() 1736 { 1737 global $prefs; 1738 $headerlib = TikiLib::lib('header'); 1739 1740 $currPage = isset($_REQUEST['page']) ? $_REQUEST['page'] : ''; 1741 if (! empty($currPage) && 1742 (strstr($_SERVER["SCRIPT_NAME"], "tiki-editpage.php") === false) && 1743 (strstr($_SERVER["SCRIPT_NAME"], 'tiki-pagehistory.php') === false)) { 1744 // Determine the auto TOC setting 1745 if ($prefs['wiki_auto_toc'] === 'y') { 1746 $isAutoTocActive = $this->get_page_auto_toc($currPage); 1747 // Use page specific setting? 1748 if ($isAutoTocActive > 0) { 1749 $isAutoTocActive = true; 1750 } else if ($isAutoTocActive < 0) { 1751 $isAutoTocActive = false; 1752 } else { 1753 $isAutoTocActive = $prefs['wiki_toc_default'] === 'on'; 1754 } 1755 // Add Auto TOC if enabled 1756 if ($isAutoTocActive) { 1757 // Enable Auto TOC 1758 $headerlib->add_jsfile('lib/jquery_tiki/autoToc.js'); 1759 1760 //Get autoToc offset 1761 $tocOffset = ! empty($prefs['wiki_toc_offset']) ? $prefs['wiki_toc_offset'] : 10; 1762 1763 // Show/Hide the static inline TOC 1764 $isAddInlineToc = isset($prefs['wiki_inline_auto_toc']) ? $prefs['wiki_inline_auto_toc'] === 'y' : false; 1765 if ($isAddInlineToc) { 1766 // Enable static, inline TOC 1767 //$headerlib->add_css('#autotoc {display: block;}'); 1768 1769 //Add top margin 1770 $headerlib->add_css('#autotoc {margin-top:' . $tocOffset . 'px;}'); 1771 1772 // Postion inline TOC top/left/right 1773 $tocPos = ! empty($prefs['wiki_toc_pos']) ? $prefs['wiki_toc_pos'] : 'right'; 1774 switch (strtolower($tocPos)) { 1775 case 'top': 1776 $headerlib->add_css('#autotoc {border: 0px;}'); 1777 break; 1778 case 'left': 1779 $headerlib->add_css('#autotoc {float: left;margin-right:15px;}'); 1780 break; 1781 case 'right': 1782 default: 1783 $headerlib->add_css('#autotoc {float: right;margin-left:15px;}'); 1784 break; 1785 } 1786 } else {//Not inline TOC 1787 //$headerlib->add_css('#autotoc {display: none;}'); 1788 //Adds the offset for the affix 1789 $headerlib->add_css('.affix {top:' . $tocOffset . 'px;}'); 1790 } 1791 } 1792 } 1793 1794 // Hide title per page 1795 $isHideTitlePerPage = isset($prefs['wiki_page_hide_title']) ? $prefs['wiki_page_hide_title'] === 'y' : false; 1796 if ($isHideTitlePerPage) { 1797 $isHideTitle = false; 1798 if (! empty($currPage)) { 1799 $isPageHideTitle = $this->get_page_hide_title($currPage); 1800 if ($isPageHideTitle != 0) { 1801 // Use page specific setting 1802 $isHideTitle = $isPageHideTitle < 0 ? true : false; 1803 } 1804 } 1805 if ($isHideTitle) { 1806 $headerlib->add_css('.pagetitle {display: none;}'); 1807 $headerlib->add_css('.titletop {display: none;}'); 1808 } 1809 } 1810 } 1811 } 1812} 1813 1814class convertToTiki9 1815{ 1816 public $parserlib; 1817 public $argumentParser; 1818 1819 public function __construct() 1820 { 1821 $this->parserlib = TikiLib::lib('parser'); 1822 $this->argumentParser = new WikiParser_PluginArgumentParser(); 1823 } 1824 1825 1826 //<!--below methods are used for converting objects 1827 //<!--Start for converting pages 1828 public function convertPages() 1829 { 1830 $infos = $this->parserlib->fetchAll( 1831 'SELECT data, page_id' . 1832 ' FROM tiki_pages' . 1833 ' LEFT JOIN tiki_db_status ON tiki_db_status.objectId = tiki_pages.page_id' . 1834 ' WHERE tiki_db_status.tableName = "tiki_pages" IS NULL' 1835 ); 1836 1837 foreach ($infos as $info) { 1838 if (! empty($info['data'])) { 1839 $converted = $this->convertData($info['data']); 1840 1841 $this->updatePlugins($converted['fingerPrintsOld'], $converted['fingerPrintsNew']); 1842 1843 $this->savePage($info['page_id'], $converted['data']); 1844 } 1845 } 1846 } 1847 1848 public function savePage($id, $data) 1849 { 1850 $status = $this->checkObjectStatus($id, 'tiki_pages'); 1851 1852 if (empty($status)) { 1853 $this->parserlib->query("UPDATE tiki_pages SET data = ? WHERE page_id = ?", [$data, $id]); 1854 1855 $this->saveObjectStatus($id, 'tiki_pages', 'conv9.0'); 1856 } 1857 } 1858 //end for converting pages--> 1859 1860 1861 //<!--start for converting histories 1862 public function convertPageHistoryFromPageAndVersion($page, $version) 1863 { 1864 $infos = $this->parserlib->fetchAll( 1865 'SELECT data, historyId' . 1866 ' FROM tiki_history' . 1867 ' LEFT JOIN tiki_db_status' . 1868 ' ON tiki_db_status.objectId = tiki_history.historyId' . 1869 ' WHERE tiki_db_status.tableName = "tiki_history" IS NULL' . 1870 ' AND pageName = ? AND version = ?', 1871 [$page, $version] 1872 ); 1873 1874 foreach ($infos as $info) { 1875 if (! empty($info['data'])) { 1876 $converted = $this->convertData($info['data']); 1877 1878 //update plugins first, if it failes, no problems with the page 1879 $this->updatePlugins($converted['fingerPrintsOld'], $converted['fingerPrintsNew']); 1880 1881 $this->savePageHistory($info['historyId'], $converted['data']); 1882 } 1883 } 1884 } 1885 1886 public function convertPageHistories() 1887 { 1888 $infos = $this->parserlib->fetchAll( 1889 'SELECT data, historyId' . 1890 ' FROM tiki_history' . 1891 ' LEFT JOIN tiki_db_status ON tiki_db_status.objectId = tiki_history.historyId' . 1892 ' WHERE tiki_db_status.tableName = "tiki_history" IS NULL' 1893 ); 1894 1895 foreach ($infos as $info) { 1896 if (! empty($info['data'])) { 1897 $converted = $this->convertData($info['data']); 1898 1899 $this->updatePlugins($converted['fingerPrintsOld'], $converted['fingerPrintsNew']); 1900 1901 $this->savePageHistory($info['historyId'], $converted['data']); 1902 } 1903 } 1904 } 1905 1906 public function savePageHistory($id, $data) 1907 { 1908 $status = $this->checkObjectStatus($id, 'tiki_history'); 1909 1910 if (empty($status)) { 1911 $this->parserlib->query( 1912 'UPDATE tiki_history' . 1913 ' SET data = ?' . 1914 ' WHERE historyId = ?', 1915 [$data, $id] 1916 ); 1917 1918 $this->saveObjectStatus($id, 'tiki_history', 'conv9.0'); 1919 } 1920 } 1921 //end for converting histories--> 1922 1923 1924 1925 //<!--start for converting modules 1926 public function convertModules() 1927 { 1928 $infos = $this->parserlib->fetchAll( 1929 'SELECT data, name' . 1930 ' FROM tiki_user_modules' . 1931 ' LEFT JOIN tiki_db_status ON tiki_db_status.objectId = tiki_user_modules.name' . 1932 ' WHERE tiki_db_status.tableName = "tiki_user_modules" IS NULL' 1933 ); 1934 1935 foreach ($infos as $info) { 1936 if (! empty($info['data'])) { 1937 $converted = $this->convertData($info['data']); 1938 1939 $this->updatePlugins($converted['fingerPrintsOld'], $converted['fingerPrintsNew']); 1940 1941 $this->saveModule($info['name'], $converted['data']); 1942 } 1943 } 1944 } 1945 1946 public function saveModule($name, $data) 1947 { 1948 $status = $this->checkObjectStatus($name, 'tiki_user_modules'); 1949 1950 if (empty($status)) { 1951 $this->parserlib->query('UPDATE tiki_user_modules SET data = ? WHERE name = ?', [$data, $name]); 1952 1953 $this->saveObjectStatus($name, 'tiki_user_modules', 'conv9.0'); 1954 } 1955 } 1956 //end for converting modules--> 1957 //end conversion of objects--> 1958 1959 1960 1961 //<!--below methods are used in tracking status of pages 1962 public function saveObjectStatus($objectId, $tableName, $status = 'new9.0+') 1963 { 1964 $currentStatus = $this->parserlib->getOne("SELECT status FROM tiki_db_status WHERE objectId = ? AND tableName = ?", [$objectId, $tableName]); 1965 1966 if (empty($currentStatus)) { 1967 //Insert a status record if one doesn't exist 1968 $this->parserlib->query( 1969 'INSERT INTO tiki_db_status ( objectId, tableName, status )' . 1970 ' VALUES (?, ?, ?)', 1971 [$objectId, $tableName, $status] 1972 ); 1973 } else { 1974 //update a status record, it already exists 1975 $this->parserlib->query( 1976 'UPDATE tiki_db_status' . 1977 ' SET status = ?' . 1978 ' WHERE objectId = ? AND tableName = ?', 1979 [$status, $objectId, $tableName] 1980 ); 1981 } 1982 } 1983 1984 public function checkObjectStatus($objectId, $tableName) 1985 { 1986 return $this->parserlib->getOne( 1987 'SELECT status' . 1988 ' FROM tiki_db_status' . 1989 ' WHERE objectId = ? AND tableName = ?', 1990 [$objectId, $tableName] 1991 ); 1992 } 1993 //end status methods--> 1994 1995 1996 //<!--below methods are used for conversion of plugins and data 1997 public function updatePlugins($fingerPrintsOld, $fingerPrintsNew) 1998 { 1999 //here we find the old fingerprint and replace it with the new one 2000 for ($i = 0, $count_fingerPrintsOld = count($fingerPrintsOld); $i < $count_fingerPrintsOld; $i++) { 2001 if (! empty($fingerPrintsOld[$i]) && $fingerPrintsOld[$i] != $fingerPrintsNew[$i]) { 2002 //Remove any that may conflict with the new fingerprint, not sure how to fix this yet 2003 $this->parserlib->query("DELETE FROM tiki_plugin_security WHERE fingerprint = ?", [$fingerPrintsNew[$i]]); 2004 2005 // Now update fingerprint (if it exists) 2006 $this->parserlib->query("UPDATE tiki_plugin_security SET fingerprint = ? WHERE fingerprint = ?", [$fingerPrintsNew[$i], $fingerPrintsOld[$i]]); 2007 } 2008 } 2009 } 2010 2011 public function convertData($data) 2012 { 2013 //we store the original matches because we are about to change and update them, we need to get their fingerprint 2014 $oldMatches = WikiParser_PluginMatcher::match($data); 2015 2016 // HTML-decode pages 2017 $data = htmlspecialchars_decode($data); 2018 2019 // find the plugins 2020 $matches = WikiParser_PluginMatcher::match($data); 2021 2022 $replaced = []; 2023 2024 $fingerPrintsOld = []; 2025 foreach ($oldMatches as $match) { 2026 $name = $match->getName(); 2027 $meta = $this->parserlib->plugin_info($name); 2028 // only check fingerprints of plugins requiring validation 2029 if (! empty($meta['validate'])) { 2030 $args = $this->argumentParser->parse($match->getArguments()); 2031 2032 //RobertPlummer - pre 9, latest findings from v8 is that the < and > chars are THE ONLY ones converted to < and > everything else seems to be decoded 2033 $body = $match->getBody(); 2034 2035 // jonnyb - pre 9.0, Tiki 6 (?) fingerprints are calculated with the undecoded body 2036 $fingerPrint = $this->parserlib->plugin_fingerprint($name, $meta, $body, $args); 2037 2038 // so check the db for previously recorded plugins 2039 if (! $this->parserlib->getOne('SELECT COUNT(*) FROM tiki_plugin_security WHERE fingerprint = ?', [$fingerPrint])) { 2040 // jb but v 7 & 8 fingerprints may be calculated differently, so check both fully decoded and partially 2041 $body = htmlspecialchars_decode($body); 2042 $fingerPrint = $this->parserlib->plugin_fingerprint($name, $meta, $body, $args); 2043 2044 if (! $this->parserlib->getOne('SELECT COUNT(*) FROM tiki_plugin_security WHERE fingerprint = ?', [$fingerPrint])) { 2045 $body = str_replace(['<', '>'], ['<', '>'], $body); 2046 $fingerPrint = $this->parserlib->plugin_fingerprint($name, $meta, $body, $args); 2047 2048 if (! $this->parserlib->getOne('SELECT COUNT(*) FROM tiki_plugin_security WHERE fingerprint = ?', [$fingerPrint])) { 2049 // old fingerprint not found - what to do? Might be worth trying " chars too... 2050 $fingerPrint = ''; 2051 } 2052 } 2053 } 2054 $fingerPrintsOld[] = $fingerPrint; 2055 } 2056 } 2057 2058 $fingerPrintsNew = []; 2059 // each plugin 2060 foreach ($matches as $match) { 2061 $name = $match->getName(); 2062 $meta = $this->parserlib->plugin_info($name); 2063 $argsRaw = $match->getArguments(); 2064 2065 //Here we detect if a plugin was double encoded and this is the second decode 2066 //try to detect double encoding 2067 if (preg_match("/&&/i", $argsRaw) || preg_match("/"/i", $argsRaw) || preg_match("/>/i", $argsRaw)) { 2068 $argsRaw = htmlspecialchars_decode($argsRaw); // decode entities in the plugin args (usually ") 2069 } 2070 2071 $args = $this->argumentParser->parse($argsRaw); 2072 $plugin = (string) $match; 2073 $key = '§' . md5(TikiLib::genPass()) . '§'; // by replace whole plugin with a guid 2074 2075 $data = str_replace($plugin, $key, $data); 2076 2077 $body = $match->getBody(); // leave the bodies alone 2078 $key2 = '§' . md5(TikiLib::genPass()) . '§'; // by replacing it with a guid 2079 $plugin = str_replace($body, $key2, $plugin); 2080 2081 //Here we detect if a plugin was double encoded and this is the second decode 2082 //try to detect double encoding 2083 if (preg_match("/&&/i", $plugin) || preg_match("/"/i", $plugin) || preg_match("/>/i", $plugin)) { 2084 $plugin = htmlspecialchars_decode($plugin); // decode entities in the plugin args (usually ") 2085 } 2086 2087 $plugin = str_replace($key2, $body, $plugin); // finally put the body back 2088 2089 $replaced['key'][] = $key; 2090 $replaced['data'][] = $plugin; // store the decoded-args plugin for replacement later 2091 2092 // only check fingerprints of plugins requiring validation 2093 if (! empty($meta['validate'])) { 2094 $fingerPrintsNew[] = $this->parserlib->plugin_fingerprint($name, $meta, $body, $args); 2095 } 2096 } 2097 2098 $this->parserlib->plugins_replace($data, $replaced); // put the plugins back into the page 2099 2100 return [ 2101 "data" => $data, 2102 "fingerPrintsOld" => $fingerPrintsOld, 2103 "fingerPrintsNew" => $fingerPrintsNew 2104 ]; 2105 } 2106 2107 //end conversion methods--> 2108} 2109 2110 2111class WikiLibOutput 2112{ 2113 public $info; 2114 public $originalValue; 2115 public $parsedValue; 2116 public $options; 2117 2118 public function __construct($info, $originalValue, $options = []) 2119 { 2120 //TODO: info may have an override, we need to build it in using MYSQL 2121 $this->info = $info; 2122 $this->originalValue = $originalValue; 2123 $this->options = $options; 2124 2125 $this->parsedValue = TikiLib::lib('parser')->parse_data($this->originalValue, $this->options = $options); 2126 } 2127} 2128