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 .= '&amp;';
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 .= '&amp;';
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 &lt; and &gt; 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(['<', '>'], ['&lt;', '&gt;'], $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 &quot; 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("/&amp;&/i", $argsRaw) || preg_match("/&quot;/i", $argsRaw) || preg_match("/&gt;/i", $argsRaw)) {
2068				$argsRaw = htmlspecialchars_decode($argsRaw);				// decode entities in the plugin args (usually &quot;)
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("/&amp;&/i", $plugin) || preg_match("/&quot;/i", $plugin) || preg_match("/&gt;/i", $plugin)) {
2084				$plugin = htmlspecialchars_decode($plugin);				// decode entities in the plugin args (usually &quot;)
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