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}
13class StructLib extends TikiLib
14{
15	public $displayLanguageOrder;
16
17	public function __construct()
18	{
19		global $prefs;
20		parent::__construct();
21
22		$this->displayLanguageOrder = [];
23	}
24	public function s_export_structure($structure_id)
25	{
26		global $exportlib, $tikidomain;
27		global $dbTiki;
28		include_once('lib/wiki/exportlib.php');
29		include_once('lib/tar.class.php');
30		$page_info = $this->s_get_structure_info($structure_id);
31		$page_name = $page_info['pageName'];
32		$zipname   = $page_name . '.zip';
33		$tar = new tar();
34		$pages = $this->s_get_structure_pages($page_info['page_ref_id']);
35		foreach ($pages as $page) {
36			$data = $exportlib->export_wiki_page($page['pageName'], 0);
37			$tar->addData($page['pageName'], $data, $this->now);
38		}
39		$dump = 'dump';
40		if ($tikidomain) {
41			$dump .= "/$tikidomain";
42		}
43		$tar->toTar("$dump/$page_name.tar", false);
44		header("location: $dump/$page_name.tar");
45		return '';
46	}
47	public function s_export_structure_tree($structure_id, $level = 0)
48	{
49		$structure_tree = $this->get_subtree($structure_id);
50		$level = 0;
51		$first = true;
52		header('Content-type: text/plain; charset=utf-8');
53		foreach ($structure_tree as $node) {
54			//This special case indicates head of structure
55			if ($node['first'] and $node['last']) {
56				print (tra('Use this tree to copy the structure') . ': ' . $node['pageName'] . "\n\n");
57			} elseif ($node['first'] or ! $node['last']) {
58				if ($node['first'] and ! $first) {
59					$level++;
60				}
61				$first = false;
62				for ($i = 0; $i < $level; $i++) {
63					print (' ');
64				}
65				print ($node['pageName']);
66				if (! empty($node['page_alias'])) {
67					print("->" . $node['page_alias']);
68				}
69				print("\n");
70			} else {
71				//node is a place holder for last in level
72				$level--;
73			}
74		}
75	}
76	public function s_remove_page($page_ref_id, $delete, $name = '')
77	{
78		// Now recursively remove
79		global $user, $prefs, $tiki_p_remove;
80		if ($prefs['feature_user_watches'] == 'y') {
81			include_once('lib/notifications/notificationemaillib.php');
82			sendStructureEmailNotification(['action' => 'remove', 'page_ref_id' => $page_ref_id, 'name' => $name]);
83		}
84		$query = 'select `page_ref_id`, ts.`page_id`, `pageName` ';
85		$query .= 'from `tiki_structures` ts, `tiki_pages` tp ';
86		$query .= 'where tp.`page_id`=ts.`page_id` and `parent_id`=?';
87		$result = $this->query($query, [(int) $page_ref_id]);
88		//Iterate down through the child nodes
89		while ($res = $result->fetchRow()) {
90			$this->s_remove_page($res['page_ref_id'], $delete);
91		}
92		//Only delete a page if other structures arent referencing it
93		if ($delete && $tiki_p_remove == 'y') {
94			$page_info = $this->s_get_page_info($page_ref_id);
95			  $query = 'select count(*) from `tiki_structures` where `page_id`=?';
96			  $count = $this->getOne($query, [(int) $page_info['page_id']]);
97			$wikilib = TikiLib::lib('wiki');
98			if ($count == 1 && $wikilib->is_editable($page_info['pageName'], $user)) {
99				$this->remove_all_versions($page_info['pageName']);
100			}
101		}
102		// Remove the space created by the removal
103		$page_info = $this->s_get_page_info($page_ref_id);
104		if (isset($page_info["parent_id"])) {
105			$query = "update `tiki_structures` set `pos`=`pos`-1 where `pos`>? and `parent_id`=?";
106			$this->query($query, [(int) $page_info["pos"], (int) $page_info["parent_id"]]);
107		}
108		//Remove the structure node
109		$query = 'delete from `tiki_structures` where `page_ref_id`=?';
110		$result = $this->query($query, [(int) $page_ref_id]);
111		return true;
112	}
113	public function promote_node($page_ref_id)
114	{
115		global $prefs;
116		$page_info = $this->s_get_page_info($page_ref_id);
117		$parent_info = $this->s_get_parent_info($page_ref_id);
118		//If there is a parent and the parent isnt the structure root node.
119		if (isset($parent_info) && $parent_info["parent_id"]) {
120			//Make a space for the node after its parent
121			$query = 'update `tiki_structures` set `pos`=`pos`+1 where `pos`>? and `parent_id`=?';
122			$this->query($query, [(int) $parent_info['pos'], (int) $parent_info['parent_id']]);
123			//Move the node up one level
124			$query = 'update `tiki_structures` set `parent_id`=?, `pos`=(? + 1) where `page_ref_id`=?';
125			$this->query($query, [(int) $parent_info['parent_id'], (int) $parent_info['pos'], (int) $page_ref_id]);
126			//Remove the space that was created by the promotion
127			  $query = "update `tiki_structures` set `pos`=`pos`-1 where `pos`>? and `parent_id`=?";
128			  $this->query($query, [(int) $page_info["pos"], (int) $page_info["parent_id"]]);
129			if ($prefs['feature_user_watches'] == 'y') {
130				include_once('lib/notifications/notificationemaillib.php');
131				sendStructureEmailNotification(['action' => 'move_up', 'page_ref_id' => $page_ref_id, 'parent_id' => $page_info['parent_id']]);
132			}
133		}
134	}
135	public function demote_node($page_ref_id)
136	{
137		$page_info = $this->s_get_page_info($page_ref_id);
138		$parent_info = $this->s_get_parent_info($page_ref_id);
139		$query = 'select `page_ref_id`, `pos` from `tiki_structures` where `pos`<? and `parent_id`=? order by `pos` desc';
140		$result = $this->query($query, [(int) $page_info['pos'], (int) $page_info['parent_id']]);
141		if ($previous = $result->fetchRow()) {
142			//Get last child nodes for previous sibling
143			$query = 'select `pos` from `tiki_structures` where `parent_id`=? order by `pos` desc';
144			$result = $this->query($query, [(int) $previous['page_ref_id']]);
145			if ($res = $result->fetchRow()) {
146				$pos = $res['pos'];
147			} else {
148				$pos = 0;
149			}
150			$query = 'update `tiki_structures` set `parent_id`=?, `pos`=(? + 1) where `page_ref_id`=?';
151			$this->query($query, [(int) $previous['page_ref_id'], (int) $pos, (int) $page_ref_id]);
152			//Remove the space created by the demotion
153			$query = "update `tiki_structures` set `pos`=`pos`-1 where `pos`>? and `parent_id`=?";
154			$this->query($query, [(int) $page_info["pos"], (int) $page_info["parent_id"]]);
155			global $prefs;
156			if ($prefs['feature_user_watches'] == 'y') {
157				include_once('lib/notifications/notificationemaillib.php');
158				sendStructureEmailNotification(['action' => 'move_down', 'page_ref_id' => $page_ref_id, 'parent_id' => $previous['page_ref_id']]);
159			}
160		}
161	}
162	public function move_after_next_node($page_ref_id)
163	{
164		$page_info = $this->s_get_page_info($page_ref_id);
165		$query = 'select `page_ref_id`, `pos` from `tiki_structures` where `pos`>? and `parent_id`=? order by `pos` asc';
166		$result = $this->query($query, [(int) $page_info['pos'], (int) $page_info['parent_id']]);
167		$res = $result->fetchRow();
168		if ($res) {
169			//Swap position values
170			$query = 'update `tiki_structures` set `pos`=? where `page_ref_id`=?';
171			$this->query($query, [(int) $page_info['pos'], (int) $res['page_ref_id']]);
172			$this->query($query, [(int) $res['pos'], (int) $page_info['page_ref_id']]);
173		}
174	}
175	public function move_before_previous_node($page_ref_id)
176	{
177		$page_info = $this->s_get_page_info($page_ref_id);
178		$query = 'select `page_ref_id`, `pos` from `tiki_structures` where `pos`<? and `parent_id`=? order by `pos` desc';
179		$result = $this->query($query, [(int) $page_info['pos'], (int) $page_info['parent_id']]);
180		$res = $result->fetchRow();
181		if ($res) {
182			//Swap position values
183			$query = 'update `tiki_structures` set `pos`=? where `page_ref_id`=?';
184			$this->query($query, [(int) $res['pos'], (int) $page_info['page_ref_id']]);
185			$this->query($query, [(int) $page_info['pos'], (int) $res['page_ref_id']]);
186		} elseif ($page_info['pos'] > 1) { // a bug occurred - try to fix
187			$query = 'update `tiki_structures` set `pos`=? where `page_ref_id`=?';
188			$this->query($query, [$page_info['pos'] - 1, (int) $page_info['page_ref_id']]);
189		}
190	}
191
192	/**
193	 * @param $data array - from from nestedSortable('toHierarchy')
194	 */
195
196	public function reorder_structure($data)
197	{
198		global $user;
199
200		if (! empty($data)) {
201			$parent_ref_id = $data[0]->structure_id;
202			$structure_info = $this->s_get_structure_info($parent_ref_id);	// "root"
203
204			if (TikiLib::lib('tiki')->user_has_perm_on_object($user, $structure_info['pageName'], 'wiki page', 'tiki_p_edit_structures')) {
205				$structure_id = $structure_info['structure_id'];
206				$tiki_structures = TikiDb::get()->table('tiki_structures');
207				$orders = [];
208				$conditions = ['structure_id' => (int) $structure_id];
209
210				foreach ($data as $node) {
211					if ($node->item_id != 'root') {
212						if (! isset($orders[$node->depth])) {
213							$orders[$node->depth] = 1;
214						} else {
215							$orders[$node->depth]++;
216						}
217						$node->parent_id = $node->parent_id == 'root' || empty($node->parent_id) ? $parent_ref_id : $node->parent_id;
218						$fields = [
219							'parent_id' => $node->parent_id,
220							'pos' => $orders[$node->depth],
221							'page_alias' => $node->page_alias,
222						];
223						if ($node->item_id < 1000000) {
224							$conditions['page_ref_id'] = (int) $node->item_id;
225							$tiki_structures->update(
226								$fields,
227								$conditions
228							);
229						} else {
230							// new nodes with id > 1000000
231							$fields['page_id'] = TikiLib::lib('tiki')->get_page_id_from_name($node->page_name);
232							$fields['structure_id'] = $structure_id;
233							$tiki_structures->insert($fields);
234						}
235					}
236				}
237
238				return $structure_info;
239			}
240		}
241		return false;
242	}
243
244	/** \brief Create a structure entry with the given name
245	  \param parent_id The parent entry to add this to.
246		   If NULL, create new structure.
247	  \param after_ref_id The entry to add this one after.
248		   If NULL, put it in position 0.
249	  \param name The wiki page to reference
250	  \param alias An alias for the wiki page name.
251	  \return the new entries page_ref_id or null if not created.
252	*/
253	public function s_create_page($parent_id, $after_ref_id, $name, $alias = '', $structure_id = null, $options = [])
254	{
255		global $prefs;
256		$ret = null;
257
258		$hide_toc = isset($options['hide_toc']) ? $options['hide_toc'] : 'n';
259		$creator = isset($options['creator']) ? $options['creator'] : tra('system');
260		$creator_msg = isset($options['creator_msg']) ? $options['creator_msg'] : tra('created from structure');
261		$ip_source = isset($options['ip_source']) ? $options['ip_source'] : '0.0.0.0';
262
263		// If the page doesn't exist then create a new wiki page!
264		$newpagebody = '';
265		if ($hide_toc !== 'y') {
266			$newpagebody = tra("Table of contents") . ":" . "{toc}";
267		}
268		$created = $this->create_page($name, 0, $newpagebody, $this->now, $creator_msg, $creator, $ip_source, '', false, '', ['parent_id' => $parent_id]);
269
270		if (! empty($parent_id) || $created || ! $this->page_is_in_structure($name)) {
271			// if were not trying to add a duplicate structure head
272			$query = 'select `page_id` from `tiki_pages` where `pageName`=?';
273			$page_id = $this->getOne($query, [$name]);
274			if (! empty($after_ref_id)) {
275				$max = $this->getOne('select `pos` from `tiki_structures` where `page_ref_id`=?', [(int) $after_ref_id]);
276			} else {
277				$max = 0;
278			}
279			if (! isset($after_ref_id)) {
280				// after_ref_id		The entry to add this one after. If NULL, put it in position 0.
281				$max = 0;
282				$query = 'update `tiki_structures` set `pos`=`pos`+1 where `pos`>? and `parent_id`=?';
283				$this->query($query, [(int) $max, (int) $parent_id]);
284			} elseif ($after_ref_id != 0) {
285				if ($max > 0) {
286					//If max is 5 then we are inserting after position 5 so we'll insert 5 and move all
287					// the others
288					$query = 'update `tiki_structures` set `pos`=`pos`+1 where `pos`>? and `parent_id`=?';
289					$result = $this->query($query, [(int) $max, (int) $parent_id]);
290				}
291			} elseif (! $created) {
292				$max = $this->getOne('select max(`pos`) from `tiki_structures` where `parent_id`=?', [(int) $parent_id]);
293			}
294			//
295			//Create a new structure entry
296			$max++;
297			$query = 'insert into `tiki_structures`(`parent_id`,`page_id`,`page_alias`,`pos`, `structure_id`) values(?,?,?,?,?)';
298			$result = $this->query($query, [(int) $parent_id, (int) $page_id, $alias, (int) $max, (int) $structure_id]);
299			//Get the page_ref_id just created
300			if (isset($parent_id)) {
301				$parent_check = ' and `parent_id`=?';
302				$attributes = [(int) $page_id,$alias,(int) $max, (int) $parent_id];
303			} else {
304				$parent_check = ' and (`parent_id` is null or `parent_id`=0)';
305				$attributes = [(int) $page_id,$alias,(int) $max];
306			}
307			$query  = 'select `page_ref_id` from `tiki_structures` ';
308			$query .= 'where `page_id`=? and `page_alias`=? and `pos`=?';
309			$query .= $parent_check;
310			$ret = $this->getOne($query, $attributes);
311			if (empty($parent_id)) {
312				$query = 'update `tiki_structures` set `structure_id`=? where `page_ref_id`=?';
313				$this->query($query, [$ret, $ret]);
314			}
315
316			if ($prefs['feature_wiki_categorize_structure'] == 'y') {
317				$this->categorizeNewStructurePage($name, $this->s_get_structure_info($parent_id));
318			}
319
320			if ($prefs['feature_user_watches'] == 'y') {
321				include_once('lib/notifications/notificationemaillib.php');
322				sendStructureEmailNotification(['action' => 'add', 'page_ref_id' => $ret, 'name' => $name]);
323			}
324		}
325		return $ret;
326	}
327
328	/**
329	 * Categorizes a (new) page the same as the parent structure
330	 * Called from s_create_page if feature_wiki_categorize_structure = y
331	 *
332	 * @param string $page				name of new page
333	 * @param array $structure_info		structure info
334	 */
335	public function categorizeNewStructurePage($page, $structure_info)
336	{
337		$categlib = TikiLib::lib('categ');
338
339		$cat_type = 'wiki page';
340		$cat_href = "tiki-index.php?page=" . urlencode($page);
341
342		$structObjectId = $categlib->is_categorized($cat_type, $structure_info["pageName"]);
343		if ($structObjectId) {
344			// structure is categorized
345			$pageObjectId = $categlib->is_categorized($cat_type, $page);
346			$structure_cats = $categlib->get_object_categories($cat_type, $structure_info["pageName"]);
347			if (! $pageObjectId) {
348				// added page is not categorized
349				$pageObjectId = $categlib->add_categorized_object($cat_type, $page, '', $page, $cat_href);
350				foreach ($structure_cats as $cat_acat) {
351					$categlib->categorize($pageObjectId, $cat_acat);
352				}
353			} else {
354				// added page is already categorized (somehow?)
355				$cats = $categlib->get_object_categories($cat_type, $page);
356				foreach ($structure_cats as $cat_acat) {
357					if (! in_array($cat_acat, $cats, true)) {
358						$categlib->categorize($pageObjectId, $cat_acat);
359					}
360				}
361			}
362		}
363	}
364
365	public function get_subtree($page_ref_id, $level = 0, $parent_pos = '')
366	{
367		$tikilib = TikiLib::lib('tiki');
368		$ret = [];
369		$pos = 1;
370		//The structure page is used as a title
371		if ($level == 0) {
372			$struct_info = $this->s_get_page_info($page_ref_id);
373			$aux['first']       = true;
374			$aux['last']        = true;
375			$aux['pos']         = '';
376			$aux['page_ref_id'] = $struct_info['page_ref_id'];
377			$aux['pageName']    = $struct_info['pageName'];
378			$aux['page_alias']  = $struct_info['page_alias'];
379			$wikilib = TikiLib::lib('wiki');
380			$is_locked = $wikilib->is_locked($struct_info['pageName']);
381			if ($is_locked) {
382				$aux['flag'] = 'L';
383				$aux['user'] = $is_locked;
384			}
385			$perms = $tikilib->get_perm_object($struct_info['pageName'], 'wiki page', '', false);
386			$aux['editable'] = $perms['tiki_p_edit'];
387			$aux['viewable'] = $perms['tiki_p_view'];
388			$ret[] = $aux;
389			$level++;
390		}
391		//Get all child nodes for this page_ref_id
392		$query = 'select `page_ref_id`, `page_alias`, `pageName`, `flag`, `user`, `pos` as db_pos ';
393		$query .= 'from `tiki_structures` ts, `tiki_pages` tp ';
394		$query .= 'where ts.`page_id` = tp.`page_id` and `parent_id`=? order by `pos` asc';
395		$result = $this->query($query, [(int) $page_ref_id]);
396		$subs = [];
397		$row_max = $result->numRows();
398		while ($res = $result->fetchRow()) {
399			//Add
400			$aux['first']       = ($pos == 1);
401			$aux['db_pos'] = $res['db_pos'];
402			$aux['last']        = false;
403			$aux['page_ref_id'] = $res['page_ref_id'];
404			$aux['pageName']    = $res['pageName'];
405			$aux['page_alias']  = $res['page_alias'];
406			$aux["flag"]  = $res["flag"];
407			$aux["user"]  = $res["user"];
408			global $user;
409			if ($this->user_has_perm_on_object($user, $res['pageName'], 'wiki page', 'tiki_p_edit')) {
410				$aux['editable'] = 'y';
411				$aux['viewable'] = 'y';
412			} else {
413				$aux['editable'] = 'n';
414				if ($this->user_has_perm_on_object($user, $res['pageName'], 'wiki page', 'tiki_p_view')) {
415					$aux['viewable'] = 'y';
416				} else {
417					$aux['viewable'] = 'n';
418				}
419			}
420			if (strlen($parent_pos) == 0) {
421				$aux['pos'] = "$pos";
422			} else {
423				$aux['pos'] = $parent_pos . '.' . "$pos";
424			}
425			$ret[] = $aux;
426			//Recursively add any child nodes
427			$subs = $this->get_subtree($res['page_ref_id'], ($level + 1), $aux['pos']);
428			if (isset($subs)) {
429				$ret = array_merge($ret, $subs);
430			}
431			// Insert a dummy entry to close table/list
432			if ($pos == $row_max) {
433				$aux['first'] = false;
434				$aux['last']  = true;
435				$ret[] = $aux;
436			}
437			$pos++;
438		}
439		return $ret;
440	}
441	/**Returns an array of page_info arrays
442		This can be used to construct a path from the
443		structure head to the requested page.
444	*/
445	public function get_structure_path($page_ref_id)
446	{
447		global $prefs;
448		$structure_path = [];
449		$page_info = $this->s_get_page_info($page_ref_id);
450		if ($page_info['parent_id']) {
451			$structure_path = $this->get_structure_path($page_info['parent_id']);
452		}
453		$structure_path[] = $page_info;
454		foreach ($structure_path as $key => $value) {
455			if ($prefs['namespace_indicator_in_structure'] === 'y' && ! empty($prefs['namespace_separator'])
456				&& strpos($value['pageName'], $prefs['namespace_separator']) !== false) {
457					$arr = explode($prefs['namespace_separator'], $value['pageName']);
458					$structure_path[$key]['stripped_pageName'] = end($arr);
459			} else {
460				$structure_path[$key]['stripped_pageName'] = $value['pageName'];
461			}
462		}
463		return $structure_path;
464	}
465	/* get all the users that watches a page or a page above */
466	public function get_watches($pageName = '', $page_ref_id = 0, $recurs = true)
467	{
468		global $tiki_p_watch_structure;
469		if ($tiki_p_watch_structure != 'y') {
470			return [];
471		}
472		$query = "SELECT ts.`parent_id`,tuw.`email`,tuw.`user`, tuw.`event`";
473		$query .= " FROM `tiki_structures` ts";
474		$query .= " LEFT JOIN (
475			SELECT watchId, user, event, object, title, type, url, email FROM `tiki_user_watches`
476			UNION DISTINCT
477				SELECT watchId, uu.login as user, event, object, title, type, url, uu.email
478				FROM
479					`tiki_group_watches` tgw
480					INNER JOIN users_usergroups ug ON tgw.`group` = ug.groupName
481					INNER JOIN users_users uu ON ug.userId = uu.userId AND uu.email IS NOT NULL AND uu.email <> ''
482			) tuw ON (tuw.`object`=ts.`page_ref_id` AND tuw.`event`=?)";
483		if (empty($page_ref_id)) {
484			$query .= " LEFT JOIN `tiki_pages` tp ON ( tp.`page_id`=ts.`page_id`)";
485			$query .= " WHERE tp.`pageName`=?";
486			$result = $this->query($query, ['structure_changed', $pageName]);
487		} else {
488			$query .= " WHERE ts.`page_ref_id`=?";
489			$result = $this->query($query, ['structure_changed', $page_ref_id]);
490		}
491		$ret = [];
492		while ($res = $result->fetchRow()) {
493			$parent_id = $res['parent_id'];
494			unset($res['parent_id']);
495			if (! empty($res['email']) || ! empty($res['user'])) {
496				$ret[] = $res;
497			}
498		}
499		if (! empty($parent_id) && $recurs) {
500			$ret2 = $this->get_watches('', $parent_id);
501			if (! empty($ret2)) {
502				$ret = array_merge($ret2, $ret);
503			}
504		}
505		return $ret;
506	}
507	/**Returns a structure_info array
508		See get_page_info for details of array
509	*/
510	public function s_get_structure_info($page_ref_id)
511	{
512		$parent_id = $this->getOne('select `parent_id` from `tiki_structures` where `page_ref_id`=?', [(int) $page_ref_id]);
513		if (! $parent_id) {
514			return $this->s_get_page_info($page_ref_id);
515		}
516		return $this->s_get_structure_info($parent_id);
517	}
518	/**Returns an array of info about the parent
519	   page_ref_id
520	   See get_page_info for details of array
521	*/
522	public function s_get_parent_info($page_ref_id)
523	{
524		// Try to get the parent of this page
525		$parent_id = $this->getOne('select `parent_id` from `tiki_structures` where `page_ref_id`=?', [(int) $page_ref_id]);
526		if (! $parent_id) {
527			return null;
528		}
529		return ($this->s_get_page_info($parent_id));
530	}
531
532	public function use_user_language_preferences($langContext = null)
533	{
534		global $prefs;
535		if ($prefs['feature_multilingual'] != 'y') {
536			return;
537		}
538		if ($prefs['feature_multilingual_structures'] != 'y') {
539			return;
540		}
541
542		$multilinguallib = TikiLib::lib('multilingual');
543
544		$this->displayLanguageOrder = $multilinguallib->preferredLangs($langContext);
545	}
546
547	public function build_language_order_clause(&$args, $pageTable = 'tp', $structTable = 'ts')
548	{
549		$query = " CASE\n";
550
551
552		// Languages in preferences go first
553		foreach ($this->displayLanguageOrder as $key => $lang) {
554			$query .= "\tWHEN $pageTable.lang = ? THEN ?\n";
555			$args[] = $lang;
556			$args[] = $key;
557		}
558
559		// If nothing in preferences, use structure default
560		$query .= "\tWHEN $structTable.page_id = $pageTable.page_id THEN ?\n";
561		$args[] = count($this->displayLanguageOrder);
562
563		// Else should never be required
564		$query .= "\tELSE ?\nEND\n";
565		$args[] = count($this->displayLanguageOrder) + 1;
566
567		return $query;
568	}
569
570	/** Return an array of page info
571	*/
572	public function s_get_page_info($page_ref_id)
573	{
574		if (empty($this->displayLanguageOrder)) {
575			$query = 'select `pos`, `page_ref_id`, `parent_id`, ts.`page_id`, `pageName`, `page_alias`, `structure_id` ';
576			$query .= 'from `tiki_structures` ts, `tiki_pages` tp ';
577			$query .= 'where ts.`page_id`=tp.`page_id` and `page_ref_id`=?';
578			$result = $this->query($query, [(int) $page_ref_id]);
579		} else {
580			$args = [ (int) $page_ref_id ];
581
582			$query = "
583				SELECT
584					`pos`,
585					`page_ref_id`,
586					`parent_id`,
587					ts.`page_id`,
588					`pageName`,
589					`page_alias`,
590					`structure_id`
591				FROM
592					`tiki_structures` ts
593					LEFT JOIN tiki_translated_objects a ON a.type = 'wiki page' AND a.objId = ts.page_id
594					LEFT JOIN tiki_translated_objects b ON b.type = 'wiki page' AND a.traId = b.traId
595					LEFT JOIN `tiki_pages` tp ON b.`objId` = tp.`page_id` OR ts.page_id = tp.page_id
596				WHERE
597					`page_ref_id` = ?
598				ORDER BY " . $this->build_language_order_clause($args)
599				. " LIMIT 1";
600
601			$result = $this->query($query, $args);
602		}
603
604		if ($res = $result->fetchRow()) {
605			return $res;
606		} else {
607			return null;
608		}
609	}
610	// that is intended to replace the get_subtree_toc and get_subtree_toc_slide
611	// it's used only in {toc} thing hardcoded in parse tikilib->parse -- (mose)
612	// the $tocPrefix can be used to Prefix a subtree as it would start from a given number (e.g. 2.1.3)
613	public function build_subtree_toc($id, $slide = false, $order = 'asc', $tocPrefix = '')
614	{
615		global $user, $tikilib, $prefs;
616		$ret = [];
617		$cant = $this->getOne('select count(*) from `tiki_structures` where `parent_id`=?', [(int) $id]);
618		if ($cant) {
619			// TODO : FIX
620			$args = [];
621			if (! $this->displayLanguageOrder) {
622				$query = 'select `page_ref_id`, `pageName`, `page_alias`, tp.`description` from `tiki_structures` ts, `tiki_pages` tp ';
623				$query .= 'where ts.`page_id`=tp.`page_id` and `parent_id`=? order by ' . $this->convertSortMode('pos_' . $order);
624				$args[] = (int) $id;
625			} else {
626				$query = "
627				SELECT
628					`page_ref_id`,
629					`pageName`,
630					`page_alias`,
631					tp.`description`
632				FROM
633					`tiki_structures` ts
634					INNER JOIN tiki_pages tp ON tp.page_id = (
635						SELECT tp.page_id
636						FROM
637							`tiki_pages` tr
638							LEFT JOIN tiki_translated_objects a ON tr.page_id = a.objId AND a.type = 'wiki page'
639							LEFT JOIN tiki_translated_objects b ON b.type = 'wiki page' AND a.traId = b.traId
640							LEFT JOIN tiki_pages tp ON b.objId = tp.page_id OR tr.page_id = tp.page_id
641						WHERE
642							tr.page_id = ts.page_id
643						ORDER BY " . $this->build_language_order_clause($args) . "
644						LIMIT 1
645					)
646				WHERE
647					parent_id = ?
648				order by " . $this->convertSortMode('pos_' . $order);
649				$args[] = (int) $id;
650			}
651			$result = $this->query($query, $args);
652			$prefix = 1;
653			while ($res = $result->fetchRow()) {
654				if (! $tikilib->user_has_perm_on_object($user, $res['pageName'], 'wiki page', 'tiki_p_view')) {
655					continue;
656				}
657
658				if ($prefs['namespace_indicator_in_structure'] === 'y'
659					&& ! empty($prefs['namespace_separator'])
660					&& ! empty($res['pageName'])
661					&& strpos($res['pageName'], $prefs['namespace_separator']) !== false) {
662					$arr = explode($prefs['namespace_separator'], $res['pageName']);
663					$res['short_pageName'] = end($arr);
664				} else {
665					$res['short_pageName'] = $res['pageName'];
666				}
667				$res['prefix'] = ($tocPrefix == '') ? '' : "$tocPrefix.";
668				$res['prefix'] .= $prefix;
669				$prefix++;
670				if ($res['page_ref_id'] != $id) {
671					$sub = $this->build_subtree_toc($res['page_ref_id'], $slide, $order, $res['prefix']);
672					if (is_array($sub)) {
673						$res['sub'] = $sub;
674					}
675				}
676				//if ($res['page_alias']<>'') $res['pageName']=$res['page_alias'];
677				$back[] = $res;
678			}
679		} else {
680			return false;
681		}
682		return $back;
683	}
684	public function get_toc($page_ref_id, $order = 'asc', $showdesc = false, $numbering = true, $numberPrefix = '', $type = 'plain', $page = '', $maxdepth = 0, $mindepth = 0, $sortalpha = 0, $structurePageName = '')
685	{
686		global $user, $prefs;
687
688		$structure_tree = $this->build_subtree_toc($page_ref_id, false, $order, $numberPrefix);
689
690		if ($type === 'admin') {
691			// check perms here as we still have $page_ref_id
692			$structure_info = $this->s_get_structure_info($page_ref_id);
693
694			$perms = Perms::get('wiki page', $structure_info["pageName"]);
695
696			if ($prefs['lock_wiki_structures'] === 'y') {
697				$lockedby = TikiLib::lib('attribute')->get_attribute('wiki structure', $structure_info['pageName'], 'tiki.object.lock');
698				if ($lockedby && $lockedby === $user && $perms->lock_structures || ! $lockedby || $perms->admin_structures) {
699					$editable = $perms->edit_structures;
700				} else {
701					$editable = false;
702				}
703			} else {
704				$editable = $perms->edit_structures;
705			}
706
707			if (! $editable) {
708				$type = 'plain';
709			} else {
710				TikiLib::lib('smarty')->assign('structure_name', $structure_info["pageName"]);
711				$json_params = json_encode(
712					[
713						'page_ref_id' => $page_ref_id,
714						'order' => $order,
715						'showdesc' => $showdesc,
716						'numbering' => $numbering,
717						'numberPrefix' => $numberPrefix,
718						'type' => $type,
719						'page' => $page,
720						'maxdepth' => $maxdepth,
721						'mindepth' => $mindepth,
722						'sortalpha' => $sortalpha,
723						'structurePageName' => $structurePageName
724					]
725				);
726				TikiLib::lib('smarty')->assign('json_params', $json_params);
727			}
728		}
729
730		if ($structure_tree != '') {
731			if ($mindepth > 0) {
732				$currentLevel = $structure_tree;
733				for ($i = 0; $i < $mindepth; $i++) {
734					$deeperLevel = [];
735					if ($currentLevel != '') {
736						foreach ($currentLevel as $leaf) {
737							if (isset($leaf['sub']) && is_array($leaf['sub'])) {
738								foreach ($leaf['sub'] as $sub) {
739									$deeperLevel[] = $sub;
740								}
741							}
742						}
743					}
744					$currentLevel = $deeperLevel;
745				}
746				if ($maxdepth > 0) {
747					$maxdepth = $maxdepth - $mindepth;
748					if ($maxdepth <= 0) {
749						$maxdepth = 1;
750					}
751				}
752				$structure_tree = $currentLevel;
753			}
754			if ($sortalpha == 'alpha') {
755				if ($order == 'asc') {
756					usort($structure_tree, [$this, 'compareByPageName']);
757				} else {
758					usort($structure_tree, [$this, 'compareByPageNameDesc']);
759				}
760			}
761		}
762
763		$nodelist = $this->fetch_toc($structure_tree, $showdesc, $numbering, $type, $page, $maxdepth, 0, $structurePageName);
764		if ($type === 'admin' && empty($nodelist)) {
765			$nodelist = "<ol class='admintoc' style='min-height: 4em;' data-params='$json_params'></ol>";
766		}
767		return $nodelist . "\n";
768	}
769
770	public function compareByPageName($a, $b)
771	{
772		return strcasecmp($a['pageName'], $b['pageName']);
773	}
774
775	public function compareByPageNameDesc($a, $b)
776	{
777		return strcasecmp($b['pageName'], $a['pageName']);
778	}
779
780	public function fetch_toc($structure_tree, $showdesc, $numbering, $type = 'plain', $page = '', $maxdepth = 0, $cur_depth = 0, $structurePageName = '')
781	{
782		$smarty = TikiLib::lib('smarty');
783		global $user;
784		$ret = '';
785		if ($structure_tree != '') {
786			if (($maxdepth <= 0) || ($cur_depth < $maxdepth)) {
787				$smarty->assign('toc_type', $type);
788				$ret .= $smarty->fetch('structures_toc-startul.tpl') . "\n";
789
790				foreach ($structure_tree as $leaf) {
791					if (is_numeric($page)) {
792						$smarty->assign('hilite', $leaf["page_ref_id"] == $page);
793					} else {
794						$smarty->assign('hilite', $leaf["pageName"] == $page);
795					}
796
797					if ($type === 'admin') {
798						if ($this->user_has_perm_on_object($user, $leaf["pageName"], 'wiki page', 'tiki_p_edit')) {
799							$leaf['editable'] = true;
800						} else {
801							$leaf['editable'] = false;
802						}
803						if (TikiLib::lib('tiki')->user_watches($user, 'structure_changed', $leaf['page_ref_id'], 'structure')) {
804							$leaf['event'] = true;
805						} else {
806							$leaf['event'] = false;
807						}
808					}
809
810					$smarty->assign('structurePageName', $structurePageName);
811					$smarty->assign_by_ref('structure_tree', $leaf);
812					$smarty->assign('showdesc', $showdesc);
813					$smarty->assign('numbering', $numbering);
814					$ret .= $smarty->fetch('structures_toc-leaf.tpl');
815					if (isset($leaf['sub']) && is_array($leaf['sub'])) {
816						$ret .= $this->fetch_toc($leaf['sub'], $showdesc, $numbering, $type, $page, $maxdepth, $cur_depth + 1, $structurePageName) . "</li>\n";
817					} else {
818						$ret .= str_repeat("\t", ($cur_depth * 2) + 1) . "</li>\n";
819					}
820				}
821				$ret .= $smarty->fetch('structures_toc-endul.tpl') . "\n";
822			}
823		}
824		return $ret;
825	}
826	// end of replacement
827	public function page_is_in_structure($pageName)
828	{
829		$query  = 'select count(*) ';
830		$query .= 'from `tiki_structures` ts, `tiki_pages` tp ';
831		$query .= 'where ts.`page_id`=tp.`page_id` and `pageName`=?';
832		$cant = $this->getOne($query, [$pageName]);
833		return $cant;
834	}
835	public function page_id_is_in_structure($pageId)
836	{
837		$query  = 'select count(*) ';
838		$query .= 'from `tiki_structures` ts, `tiki_pages` tp ';
839		$query .= 'where ts.`page_id`=tp.`page_id` and `page_id`=?';
840		$cant = $this->getOne($query, [$pageId]);
841		return $cant;
842	}
843	//Is this page the head page for a structure?
844	public function get_struct_ref_if_head($pageName)
845	{
846		$query = 'select `page_ref_id` ';
847		$query .= 'from `tiki_structures` ts, `tiki_pages` tp ';
848		$query .= 'where ts.`page_id`=tp.`page_id` and (`parent_id` is null or `parent_id`=0) and `pageName`=?';
849		$page_ref_id = $this->getOne($query, [$pageName]);
850		if ($page_ref_id) {
851			return $page_ref_id;
852		}
853
854		if (! $this->displayLanguageOrder) {
855			return null;
856		}
857
858		$query = "
859			SELECT
860				page_ref_id
861			FROM
862				tiki_structures ts
863				INNER JOIN tiki_translated_objects a ON ts.page_id = a.objId AND a.type = 'wiki page'
864				INNER JOIN tiki_translated_objects b ON a.traId = b.traId AND b.type = 'wiki page'
865				INNER JOIN tiki_pages tp ON b.objId = tp.page_id
866			WHERE
867				(parent_id IS NULL or parent_id = 0)
868				AND pageName = ?";
869
870		$page_ref_id = $this->getOne($query, [$pageName]);
871		return $page_ref_id;
872	}
873	//Get reference id for a page
874	public function get_struct_ref_id($pageName)
875	{
876		$query = 'select `page_ref_id` ';
877		$query .= 'from `tiki_structures` ts, `tiki_pages` tp ';
878		$query .= 'where ts.`page_id`=tp.`page_id` and `pageName`=?';
879		$page_ref_id = $this->getOne($query, [$pageName]);
880		return $page_ref_id;
881	}
882	public function get_next_page($page_ref_id, $deep = true)
883	{
884		// If we have children then get the first child
885		if ($deep) {
886			$query  = 'select `page_ref_id` ';
887			$query .= 'from `tiki_structures` ts ';
888			$query .= 'where `parent_id`=? ';
889			$query .= 'order by ' . $this->convertSortMode('pos_asc');
890			$result1 = $this->query($query, [(int) $page_ref_id]);
891			if ($result1->numRows()) {
892				$res = $result1->fetchRow();
893				return $res['page_ref_id'];
894			}
895		}
896		// Try to get the next page with the same parent as this
897		$page_info = $this->s_get_page_info($page_ref_id);
898		$parent_id = $page_info['parent_id'];
899		$page_pos = $page_info['pos'];
900		if (! $parent_id) {
901			return null;
902		}
903		$query  = 'select `page_ref_id` ';
904		$query .= 'from `tiki_structures` ts ';
905		$query .= 'where `parent_id`=? and `pos`>? ';
906		$query .= 'order by ' . $this->convertSortMode('pos_asc');
907		$result2 = $this->query($query, [(int) $parent_id, (int) $page_pos]);
908		if ($result2->numRows()) {
909			$res = $result2->fetchRow();
910			return $res['page_ref_id'];
911		} else {
912			return $this->get_next_page($parent_id, false);
913		}
914	}
915	public function get_prev_page($page_ref_id, $deep = false)
916	{
917		//Drill down to last child for this tree node
918		if ($deep) {
919			$query  = 'select `page_ref_id` ';
920			$query .= 'from `tiki_structures` ts ';
921			$query .= 'where `parent_id`=? ';
922			$query .= 'order by ' . $this->convertSortMode('pos_desc');
923			$result = $this->query($query, [$page_ref_id]);
924			if ($result->numRows()) {
925				//There are more children
926				$res = $result->fetchRow();
927				$page_ref_id = $this->get_prev_page($res['page_ref_id'], true);
928			}
929			return $page_ref_id;
930		}
931		// Try to get the previous page with the same parent as this
932		$page_info = $this->s_get_page_info($page_ref_id);
933		$parent_id = $page_info['parent_id'];
934		$pos       = $page_info['pos'];
935		//At the top of the tree
936		if (empty($parent_id)) {
937			return null;
938		}
939		$query  = 'select `page_ref_id` ';
940		$query .= 'from `tiki_structures` ts ';
941		$query .= 'where `parent_id`=? and `pos`<? ';
942		$query .= 'order by ' . $this->convertSortMode('pos_desc');
943		$result = $this->query($query, [(int) $parent_id, (int) $pos]);
944		if ($result->numRows()) {
945			//There is a previous sibling
946			$res = $result->fetchRow();
947			$page_ref_id = $this->get_prev_page($res['page_ref_id'], true);
948		} else {
949			//No previous siblings, just the parent
950			$page_ref_id = $parent_id;
951		}
952		return $page_ref_id;
953	}
954	public function get_navigation_info($page_ref_id)
955	{
956		$struct_nav_pages = [
957			'prev'   => $this->get_neighbor_info($page_ref_id, 'get_prev_page'),
958			'next'   => $this->get_neighbor_info($page_ref_id, 'get_next_page'),
959			'parent' => $this->get_neighbor_info($page_ref_id, 's_get_parent_info'),
960			'home'   => $this->s_get_structure_info($page_ref_id),
961		];
962
963		 return $struct_nav_pages;
964	}
965
966	/**
967	 * Get structure info for a page's neighbour respecting view perms
968	 * @param int $page_ref_id
969	 * @param string $fn		function to find neighbour (get_prev_page|get_next_page|s_get_parent_info)
970	 * @return null | array		neighbour page info
971	 */
972	private function get_neighbor_info($page_ref_id, $fn)
973	{
974		if (method_exists($this, $fn)) {
975			$neighbor = $this->$fn($page_ref_id);
976			if ($neighbor) {
977				if (is_array($neighbor)) {	// s_get_parent_info() returns the info array
978					$info = $neighbor;
979				} else {
980					$info = $this->s_get_page_info($neighbor);
981				}
982				if ($info && Perms::get([ 'type' => 'wiki page', 'object' => $info['pageName'] ])->view) {
983					return $info;
984				} else {
985					return $this->get_neighbor_info($neighbor, $fn);
986				}
987			} else {
988				return null;
989			}
990		} else {
991			trigger_error('No structlib method found: ' . $fn);
992			return null;
993		}
994	}
995	/** Return an array of subpages
996	  Used by the 'After Page' select box
997	*/
998	public function s_get_pages($parent_id)
999	{
1000		$ret = [];
1001		$query = 'select `pos`, `page_ref_id`, `parent_id`, ts.`page_id`, `pageName`, `page_alias` ';
1002		$query .= 'from `tiki_structures` ts, `tiki_pages` tp ';
1003		$query .= 'where ts.`page_id`=tp.`page_id` and `parent_id`=? ';
1004		$query .= 'order by ' . $this->convertSortMode('pos_asc');
1005		$result = $this->query($query, [(int) $parent_id]);
1006		while ($res = $result->fetchRow()) {
1007			//$ret[] = $this->populate_page_info($res);
1008			$ret[] = $res;
1009		}
1010		return $ret;
1011	}
1012	/** Get a list of all structures this page is a member of
1013	*/
1014	public function get_page_structures($pageName, $structure = '')
1015	{
1016		$ret = [];
1017		$structures_added = [];
1018		if (empty($this->displayLanguageOrder)) {
1019			$query = 'select `page_ref_id` ';
1020			$query .= 'from `tiki_structures` ts, `tiki_pages` tp ';
1021			$query .= 'where ts.`page_id`=tp.`page_id` and (tp.`pageName`=? OR tp.`pageSlug`=?)';
1022		} else {
1023			$query = "
1024				SELECT DISTINCT
1025					`page_ref_id`
1026				FROM
1027					tiki_structures ts
1028					LEFT JOIN tiki_translated_objects a ON a.objId = ts.page_id AND a.type = 'wiki page'
1029					LEFT JOIN tiki_translated_objects b ON a.traId = b.traId AND b.type = 'wiki page'
1030					LEFT JOIN tiki_pages tp ON ts.page_id = tp.page_id OR b.objId = tp.page_id
1031				WHERE
1032					tp.`pageName`=? OR tp.`pageSlug`=?";
1033		}
1034
1035		$result = $this->query($query, [$pageName,$pageName]);
1036		while ($res = $result->fetchRow()) {
1037			$next_page = $this->s_get_structure_info($res['page_ref_id']);
1038			//Add each structure head only once
1039			if (! in_array($next_page['page_ref_id'], $structures_added)) {
1040				if (empty($structure) || $structure == $next_page['pageName']) {
1041					$structures_added[] = $next_page['page_ref_id'];
1042					$next_page['req_page_ref_id'] = $res['page_ref_id'];
1043					$ret[] = $next_page;
1044				}
1045			}
1046		}
1047		return $ret;
1048	}
1049	public function get_max_children($page_ref_id)
1050	{
1051		$query = 'select `page_ref_id` from `tiki_structures` where `parent_id`=?';
1052		$result = $this->query($query, [(int) $page_ref_id]);
1053		if (! $result->numRows()) {
1054			return '';
1055		}
1056		$res = $result->fetchRow();
1057		return $res;
1058	}
1059	/** Return a unique list of pages belonging to the structure
1060	  \return An array of page_info arrays
1061	*/
1062	public function s_get_structure_pages_unique($page_ref_id)
1063	{
1064		$ret = [];
1065		// Add the structure page as well
1066		$ret[] = $this->s_get_page_info($page_ref_id);
1067		$ret2  = $this->s_get_structure_pages($page_ref_id);
1068		return array_unique(array_merge($ret, $ret2));
1069	}
1070	/** Return all the pages belonging to a structure
1071	 \param  Page reference ID in struct table
1072	 \return An array of page_info arrays
1073	*/
1074	public function s_get_structure_pages($page_ref_id)
1075	{
1076		$ret = [];
1077		if ($page_ref_id) {
1078				$ret[0] = $this->s_get_page_info($page_ref_id);
1079			 $query = 'select `pos`, `page_ref_id`, `parent_id`, ts.`page_id`, `pageName`, `page_alias` ';
1080			$query .= 'from `tiki_structures` ts, `tiki_pages` tp ';
1081				$query .= 'where ts.`page_id`=tp.`page_id` and `parent_id`=? ';
1082			$query .= 'order by ' . $this->convertSortMode('pos_asc');
1083			   $result = $this->query($query, [(int) $page_ref_id]);
1084			while ($res = $result->fetchRow()) {
1085				$ret = array_merge($ret, $this->s_get_structure_pages($res['page_ref_id']));
1086			}
1087		}
1088		return $ret;
1089	}
1090	public function list_structures($offset, $maxRecords, $sort_mode, $find = '', $exact_match = true, $filter = [])
1091	{
1092		global $prefs;
1093
1094		if ($find) {
1095			if (! $exact_match && $find) {
1096				$find = preg_replace("/(\w+)/", "%\\1%", $find);
1097				$find = preg_split("/[\s]+/", $find, -1, PREG_SPLIT_NO_EMPTY);
1098				$mid = " where (`parent_id` is null or `parent_id`=0) and (tp.`pageName` like " . implode(' or tp.`pageName` like ', array_fill(0, count($find), '?')) . ")";
1099				$bindvars = $find;
1100			} else {
1101				$mid = ' where (`parent_id` is null or `parent_id`=0) and (tp.`pageName` like ?)';
1102				$findesc = '%' . $find . '%';
1103				$bindvars = [$findesc];
1104			}
1105		} else {
1106			$mid = ' where (`parent_id` is null or `parent_id`=0) ';
1107			$bindvars = [];
1108		}
1109
1110		// If language is set to '', assume that no language filtering should be done.
1111		if (isset($filter['lang']) && $filter['lang'] == '') {
1112			unset($filter['lang']);
1113		}
1114
1115		if ($prefs['feature_wiki_categorize_structure'] == 'y') {
1116			$category_jails = TikiLib::lib('categ')->get_jail();
1117			if (! isset($filter['andCategId']) && ! isset($filter['categId']) && empty($filter['noCateg']) && ! empty($category_jails)) {
1118				$filter['categId'] = $category_jails;
1119			}
1120		}
1121
1122		$join_tables = ' inner join `tiki_pages` tp on (tp.`page_id`= ts.`page_id`)';
1123		$join_bindvars = [];
1124		$distinct = '';
1125		if (! empty($filter)) {
1126			foreach ($filter as $type => $val) {
1127				if ($type == 'categId') {
1128					$categories = TikiLib::lib('categ')->get_jailed((array) $val);
1129					$categories[] = -1;
1130
1131					$cat_count = count($categories);
1132					$join_tables .= " inner join `tiki_objects` as tob on (tob.`itemId`= tp.`pageName` and tob.`type`= ?) inner join `tiki_category_objects` as tc on (tc.`catObjectId`=tob.`objectId` and tc.`categId` IN(" . implode(', ', array_fill(0, $cat_count, '?')) . ")) ";
1133
1134					if ($cat_count > 1) {
1135						$distinct = ' DISTINCT ';
1136					}
1137
1138					$join_bindvars = array_merge(['wiki page'], $categories);
1139				} elseif ($type == 'lang') {
1140					$mid .= empty($mid) ? ' where ' : ' and ';
1141					$mid .= '`lang`=? ';
1142					$bindvars[] = $val;
1143				}
1144			}
1145		}
1146
1147		if (! empty($join_bindvars)) {
1148			$bindvars = empty($bindvars) ? $join_bindvars : array_merge($join_bindvars, $bindvars);
1149		}
1150
1151		$query = "select $distinct `page_ref_id`,`structure_id`,`parent_id`,ts.`page_id`,`page_alias`,`pos`,
1152			`pageName`,tp.`hits`,`data`,tp.`description`,`lastModif`,`comment`,`version`,
1153			`user`,`ip`,`flag`,`points`,`votes`,`cache`,`wiki_cache`,`cache_timestamp`,
1154			`pageRank`,`creator`,`page_size` from `tiki_structures` as ts $join_tables $mid order by " . $this->convertSortMode($sort_mode);
1155		$query_cant = "select count(*) from `tiki_structures` ts $join_tables $mid";
1156		$result = $this->query($query, $bindvars, $maxRecords, $offset);
1157		$cant = $this->getOne($query_cant, $bindvars);
1158		$ret = [];
1159		while ($res = $result->fetchRow()) {
1160			global $user;
1161			if ($this->user_has_perm_on_object($user, $res['pageName'], 'wiki page', 'tiki_p_view')) {
1162				if (file_exists('whelp/' . $res['pageName'] . '/index.html')) {
1163					$res['webhelp'] = 'y';
1164				} else {
1165					$res['webhelp'] = 'n';
1166				}
1167				if ($this->user_has_perm_on_object($user, $res['pageName'], 'wiki page', 'tiki_p_edit')) {
1168					$res['editable'] = 'y';
1169				} else {
1170					$res['editable'] = 'n';
1171				}
1172				if ($this->user_has_perm_on_object($user, $res['pageName'], 'wiki page', 'tiki_p_edit_structures')) {
1173					$res['edit_structure'] = 'y';
1174				} else {
1175					$res['edit_structure'] = 'n';
1176				}
1177				if ($this->user_has_perm_on_object($user, $res['pageName'], 'wiki page', 'tiki_p_admin_structures')) {
1178					$res['admin_structure'] = 'y';
1179				} else {
1180					$res['admin_structure'] = 'n';
1181				}
1182				$ret[] = $res;
1183			} // end check for perm if
1184		}
1185		$retval = [];
1186		$retval['data'] = $ret;
1187		$retval['cant'] = $cant;
1188		return $retval;
1189	}
1190	public function get_page_alias($page_ref_id)
1191	{
1192		$query = 'select `page_alias` from `tiki_structures` where `page_ref_id`=?';
1193		$res = $this->getOne($query, [(int) $page_ref_id]);
1194		return $res;
1195	}
1196	public function set_page_alias($page_ref_id, $pageAlias)
1197	{
1198		$query = 'update `tiki_structures` set `page_alias`=? where `page_ref_id`=?';
1199		$this->query($query, [$pageAlias, (int) $page_ref_id]);
1200	}
1201	//This nifty function creates a static WebHelp version using a TikiStructure as
1202	//the base.
1203	public function structure_to_webhelp($page_ref_id, $dir, $top)
1204	{
1205		  global $style_base;
1206		global $prefs;
1207		//The first task is to convert the structure into an array with the
1208		//proper format to produce a WebHelp project.
1209		//We have to create something in the form
1210		//$pages=Array('root'=>Array('pag1'=>'','pag2'=>'','page3'=>Array(...)));
1211		//Where the name is the pageName|description and the other side is either ''
1212		//when the page is a leaf or an Array of pages when the page is a folder
1213		//Folders that are not TikiPages are known for having only a name instead
1214		//of name|description
1215		$tree = '$tree=Array(' . $this->structure_to_tree($page_ref_id) . ');';
1216		eval($tree);
1217		//Now we have the tree in $tree!
1218		$menucode = "foldersTree = gFld(\"Contents\", \"content.html\")\n";
1219		$menucode .= $this->traverse($tree);
1220		$base = "whelp/$dir";
1221		copy("$base/menu/options.cfg", "$base/menu/menuNodes.js");
1222		$fw = fopen("$base/menu/menuNodes.js", 'a+');
1223		fwrite($fw, $menucode);
1224		fclose($fw);
1225		$docs = [];
1226		$words = [];
1227		$index = [];
1228		$first = true;
1229		$pages = $this->traverse2($tree);
1230		// Now loop the pages
1231		foreach ($pages as $page) {
1232			$query = 'select * from `tiki_pages` where `pageName`=?';
1233			  $result = $this->query($query, [$page]);
1234			$res = $result->fetchRow();
1235			  $docs[] = $res['pageName'];
1236			if (empty($res['description'])) {
1237				$res['description'] = $res['pageName'];
1238			}
1239			  $pageName = $res['pageName'] . '|' . $res['description'];
1240			  $dat = TikiLib::lib('parser')->parse_data($res['data']);
1241			  //Now dump the page
1242			  $dat = preg_replace("/tiki-index.php\?page=([^\'\" ]+)/", "$1.html", $dat);
1243			  $dat = str_replace('?nocache=1', '', $dat);
1244			  $cs = '';
1245			  $data = "<html><head><script src=\"../js/highlight.js\"></script><link rel=\"StyleSheet\"  href=\"../../../styles/$style_base.css\" type=\"text/css\" /><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> <title>" . $res["pageName"] . "</title></head><body style=\"padding:10px\" onload=\"doProc();\">$cs<div id='tiki-center'><div class='wikitext'>" . $dat . '</div></div></body></html>';
1246			  $fw = fopen("$base/pages/" . $res['pageName'] . '.html', 'wb+');
1247			  fwrite($fw, $data);
1248			  fclose($fw);
1249			  unset($dat);
1250			  $page_words = preg_split("/[^A-Za-z0-9\-_]/", $res['data']);
1251			foreach ($page_words as $word) {
1252				$word = strtolower($word);
1253				if (strlen($word) > 3 && preg_match("/^[A-Za-z][A-Za-z0-9\_\-]*[A-Za-z0-9]$/", $word)) {
1254					if (! in_array($word, $words)) {
1255						$words[] = $word;
1256						$index[$word] = [];
1257					}
1258					if (! in_array($res['pageName'] . '|' . $res['description'], $index[$word])) {
1259						$index[$word][] = $res['pageName'] . '|' . $res['description'];
1260					}
1261				}
1262			}
1263		}
1264		sort($words);
1265		$i = 0;
1266		$fw = fopen("$base/js/searchdata.js", 'w');
1267		fwrite($fw, "keywords = new Array();\n");
1268		foreach ($words as $word) {
1269			fwrite($fw, "keywords[$i] = Array(\"$word\",Array(");
1270			$first = true;
1271			foreach ($index[$word] as $doc) {
1272				if (! $first) {
1273					fwrite($fw, ',');
1274				} else {
1275					$first = false;
1276				}
1277				fwrite($fw, '"' . $doc . '"');
1278			}
1279			  fwrite($fw, "));\n");
1280			  $i++;
1281		}
1282		fclose($fw);
1283
1284		// write the title page, using:
1285		// Browser Title, Logo, Site title, Site subtitle
1286		$fw = fopen("$base/content.html", 'w+');
1287		$titlepage = "<h1>" . $prefs['browsertitle'] . "</h1><p><img src='../../" . $prefs['sitelogo_src'] . "' alt='" . $prefs['sitelogo_alt'] . "' style=\"text-align: center;\" /></p><h2>" . $prefs['sitetitle'] . "</h2><h3>" . $prefs['sitesubtitle'] . "</h3>";
1288		fwrite($fw, $titlepage);
1289		fclose($fw);
1290	}
1291
1292	public function structure_to_tree($page_ref_id)
1293	{
1294		$query = 'select * from `tiki_structures` ts,`tiki_pages` tp where tp.`page_id`=ts.`page_id` and `page_ref_id`=?';
1295		$result = $this->query($query, [(int) $page_ref_id]);
1296		$res = $result->fetchRow();
1297		if (empty($res['description'])) {
1298			$res['description'] = $res['pageName'];
1299		}
1300		$name = str_replace("'", "\'", $res['description'] . '|' . $res['pageName']);
1301		$code = '';
1302		$code .= "'$name'=>";
1303		$query = 'select * from `tiki_structures` ts, `tiki_pages` tp  where tp.`page_id`=ts.`page_id` and `parent_id`=?';
1304		$result = $this->query($query, [(int) $page_ref_id]);
1305		if ($result->numRows()) {
1306			$code .= 'Array(';
1307			$first = true;
1308			while ($res = $result->fetchRow()) {
1309				if (! $first) {
1310					$code .= ',';
1311				} else {
1312					$first = false;
1313				}
1314				$code .= $this->structure_to_tree($res['page_ref_id']);
1315			}
1316			$code .= ')';
1317		} else {
1318			$code .= "''";
1319		}
1320		return $code;
1321	}
1322	public function traverse($tree, $parent = '')
1323	{
1324		$code = '';
1325		foreach ($tree as $name => $node) {
1326			list($name,$link) = explode('|', $name);
1327			if (is_array($node)) {
1328				//New folder node is parent++ folder parent is paren
1329				$new = $parent . 'A';
1330				$code .= 'foldersTree' . $new . "=insFld(foldersTree$parent,gFld(\"$name\",\"pages/$link.html\"));\n";
1331				$code .= $this->traverse($node, $new);
1332			} else {
1333				$code .= "insDoc(foldersTree$parent,gLnk(\"R\",\"$name\",\"pages/$link.html\"));\n";
1334			}
1335		}
1336		return $code;
1337	}
1338	public function traverse2($tree)
1339	{
1340		$pages = [];
1341		foreach ($tree as $name => $node) {
1342			list($name,$link) = explode('|', $name);
1343			if (is_array($node)) {
1344				if (isset($name) && isset($link)) {
1345					$pageName = $link;
1346					$pages[] = $pageName;
1347				}
1348				$pages2 = $this->traverse2($node);
1349				foreach ($pages2 as $elem) {
1350					$pages[] = $elem;
1351				}
1352			} else {
1353				$pages[] = $link;
1354			}
1355		}
1356		return $pages;
1357	}
1358	public function move_to_structure($page_ref_id, $structure_id, $begin = true)
1359	{
1360		$page_info = $this->s_get_page_info($page_ref_id);
1361		$query = "update `tiki_structures` set `pos`=`pos`-1 where `pos`>? and `parent_id`=?";
1362		$this->query($query, [(int) $page_info["pos"], (int) $page_info["parent_id"]]);
1363		if ($begin) {
1364			$query = "update `tiki_structures` set `pos`=`pos`+1 where `parent_id`=?";
1365			$this->query($query, [$structure_id]);
1366			$pos = 1;
1367			$query = "update `tiki_structures` set `structure_id`=?, `parent_id`=?, `pos`=? where `page_ref_id`=?";
1368			$this->query($query, [$structure_id, $structure_id, $pos + 1, $page_ref_id]);
1369		} else {
1370			$query = "select max(`pos`) from `tiki_structures` where `parent_id`=?";
1371			$pos = $this->getOne($query, [$structure_id]);
1372			$query = "update `tiki_structures` set `structure_id`=?, `parent_id`=?, `pos`=? where `page_ref_id`=?";
1373			$this->query($query, [$structure_id, $structure_id, $pos + 1, $page_ref_id]);
1374		}
1375	}
1376
1377	/* transform a structure into a menu */
1378	public function to_menu($channels, $structure, $sectionLevel = 0, $cumul = 0, $params = [])
1379	{
1380		$smarty = TikiLib::lib('smarty');
1381		include_once('lib/smarty_tiki/function.sefurl.php');
1382		$options = [];
1383		$cant = 0;
1384		if (empty($channels)) {
1385			return ['cant' => 0, 'data' => []];
1386		}
1387		foreach ($channels as $channel) {
1388			if (empty($channel['sub'])) {
1389				if (isset($options[$cant - 1]['sectionLevel'])) {
1390					$level = $options[$cant - 1]['sectionLevel'];
1391					while ($level-- > $sectionLevel) {
1392						$options[] = ['type' => '-', 'sectionLevel' => $level];
1393						++$cant;
1394					}
1395				}
1396			}
1397			$pageName = $channel['pageName'];
1398			if (isset($params['show_namespace']) && $params['show_namespace'] === 'n') {
1399				$pageName = ! empty($channel['short_pageName']) ? $channel['short_pageName'] : $channel['pageName'];
1400			}
1401			$option['name'] = empty($channel['page_alias']) ? $pageName : $channel['page_alias'];
1402			$option['type'] = empty($channel['sub']) ? 'o' : ($sectionLevel ? $sectionLevel : 's');
1403			$option['url'] = smarty_function_sefurl(['page' => $channel['pageName'], 'structure' => $structure, 'page_ref_id' => $channel['page_ref_id'], 'sefurl' => 'n'], $smarty->getEmptyInternalTemplate());
1404			$option['canonic'] = '((' . $channel['pageName'] . '))';
1405			$option['sefurl'] = smarty_function_sefurl(['page' => $channel['pageName'], 'structure' => $structure, 'page_ref_id' => $channel['page_ref_id']], $smarty->getEmptyInternalTemplate());
1406			$option['position'] = $cant + $cumul;
1407			$option['sectionLevel'] = $sectionLevel;
1408
1409			$option['url'] = str_replace('&amp;', '&', $option['url']);			// as of Tiki 7 menu items get encoded later
1410			$option['sefurl'] = str_replace('&amp;', '&', $option['sefurl']);
1411			$option['optionId'] = $channel['page_ref_id'];
1412
1413			++$cant;
1414			$options[] = $option;
1415			if (! empty($channel['sub'])) {
1416				$oSub = $this->to_menu($channel['sub'], $structure, $sectionLevel + 1, $cant + $cumul, $params);
1417				$cant += $oSub['cant'];
1418				$options = array_merge($options, $oSub['data']);
1419			}
1420		}
1421		return ['data' => $options, 'cant' => $cant];
1422	}
1423}
1424