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
14class ObjectLib extends TikiLib
15{
16	const SecondsPerDay = 86400;
17
18	/**
19	 *	Create an object record for the given Tiki object if one doesn't already exist.
20	 * Returns the object record OID. If the designated object does not exist, may return NULL.
21	 * If the object type is not handled and $checkHandled is TRUE, fail and return FALSE.
22	 * $checkHandled A boolean indicating whether only handled object types should be accepted when the object has no object record (legacy).
23	 * When creating, if $description is given, use the description, name and URL given as information.
24	 * Otherwise retrieve it from the object (if $checkHandled is FALSE, fill with empty strings if the object type is not handled).
25	 * Handled object types: "article", "blog", "calendar", "directory", "faq",
26	 * "file", "file gallery", "forum", "image gallery", "poll", "quiz", "tracker", "trackeritem", "wiki page" and "template".
27	 *
28	 * Remember to update get_supported_types if this changes
29	 */
30	function add_object($type, $itemId, $checkHandled = true, $description = null, $name = null, $href = null)
31	{
32		$objectId = $this->get_object_id($type, $itemId);
33
34		if ($objectId) {
35			if (! empty($description) || ! empty($name) || ! empty($href)) {
36				$query = "update `tiki_objects` set `description`=?,`name`=?,`href`=? where `objectId`=?";
37				$this->query($query, [$description, $name, $href, $objectId]);
38			}
39		} else {
40			if (is_null($description)) {
41				switch ($type) {
42					case 'article':
43						$artlib = TikiLib::lib('art');
44						$info = $artlib->get_article($itemId);
45
46						$description = $info['heading'];
47						$name = $info['title'];
48						$href = 'tiki-read_article.php?articleId=' . $itemId;
49						break;
50
51					case 'blog':
52						$bloglib = TikiLib::lib('blog');
53						$info = $bloglib->get_blog($itemId);
54
55						$description = $info['description'];
56						$name = $info['title'];
57						$href = 'tiki-view_blog.php?blogId=' . $itemId;
58						break;
59
60					case 'calendar':
61						$calendarlib = TikiLib::lib('calendar');
62						$info = $calendarlib->get_calendar($itemId);
63
64						$description = $info['description'];
65						$name = $info['name'];
66						$href = 'tiki-calendar.php?calId=' . $itemId;
67						break;
68
69					case 'directory':
70						$info = $this->get_directory($itemId);
71
72						$description = $info['description'];
73						$name = $info['name'];
74						$href = 'tiki-directory_browse.php?parent=' . $itemId;
75						break;
76
77					case 'faq':
78						$info = TikiLib::lib('faq')->get_faq($itemId);
79
80						$description = $info['description'];
81						$name = $info['title'];
82						$href = 'tiki-view_faq.php?faqId=' . $itemId;
83						break;
84
85					case 'file':
86						$filegallib = TikiLib::lib('filegal');
87						$info = $filegallib->get_file_info($itemId, false, false, false);
88
89						$description = $info['description'];
90						$name = $info['name'];
91						$href = 'tiki-upload_file.php?fileId=' . $itemId;
92						break;
93
94					case 'file gallery':
95						$filegallib = TikiLib::lib('filegal');
96						$info = $filegallib->get_file_gallery($itemId);
97
98						$description = $info['description'];
99						$name = $info['name'];
100						$href = 'tiki-list_file_gallery.php?galleryId=' . $itemId;
101						break;
102
103					case 'forum':
104						$commentslib = TikiLib::lib('comments');
105						$info = $commentslib->get_forum($itemId);
106
107						$description = $info['description'];
108						$name = $info['name'];
109						$href = 'tiki-view_forum.php?forumId=' . $itemId;
110						break;
111
112					case 'image gallery':
113						$info = $this->get_gallery($itemId);
114
115						$description = $info['description'];
116						$name = $info['name'];
117						$href = 'tiki-browse_gallery.php?galleryId=' . $itemId;
118						break;
119
120					case 'perspective':
121						$info = TikiLib::lib('perspective')->get_perspective($itemId);
122						$name = $info['name'];
123						$href = 'tiki-switch_perspective.php?perspective=' . $itemId;
124						break;
125
126					case 'poll':
127						$polllib = TikiLib::lib('poll');
128						$info = $polllib->get_poll($itemId);
129
130						$description = $info['title'];
131						$name = $info['title'];
132						$href = 'tiki-poll_form.php?pollId=' . $itemId;
133						break;
134
135					case 'quiz':
136						$info = TikiLib::lib('quiz')->get_quiz($itemId);
137
138						$description = $info['description'];
139						$name = $info['name'];
140						$href = 'tiki-take_quiz.php?quizId=' . $itemId;
141						break;
142
143					case 'tracker':
144						$trklib = TikiLib::lib('trk');
145						$info = $trklib->get_tracker($itemId);
146
147						$description = $info['description'];
148						$name = $info['name'];
149						$href = 'tiki-view_tracker.php?trackerId=' . $itemId;
150						break;
151
152					case 'trackeritem':
153						$trklib = TikiLib::lib('trk');
154						$info = $trklib->get_tracker_item($itemId);
155
156						$description = '';
157						$name = $trklib->get_isMain_value($info['trackerId'], $itemId);
158						$href = "tiki-view_tracker_item.php?itemId=$itemId&trackerId=" . $info['trackerId'];
159						break;
160
161					case 'wiki page':
162						if (! ($info = $this->get_page_info($itemId))) {
163							return;
164						}
165						$description = $info["description"];
166						$name = $itemId;
167						$href = 'tiki-index.php?page=' . urlencode($itemId);
168						break;
169
170					case 'template':
171						$info = TikiLib::lib('template')->get_template($itemId);
172
173						$description = '';
174						$name = $info['name'];
175						$href = "tiki-admin_content_templates.php?templateId=$itemId";
176						break;
177
178					default:
179						if ($checkHandled) {
180							return false;
181						} else {
182							$description = '';
183							$name = '';
184							$href = '';
185						}
186				}
187			}
188			$objectId = $this->insert_object($type, $itemId, $description, $name, $href);
189		}
190
191		return $objectId;
192	}
193
194	/**
195	 * Returns an array of object types supported (and therefore can be categorised etc)
196	 *
197	 * @return array
198	 */
199	static function get_supported_types()
200	{
201		return [
202			'article',
203			'blog',
204			'calendar',
205			'directory',
206			'faq',
207			'file',
208			'file gallery',
209			'forum',
210			'image gallery',
211			'perspective',
212			'poll',
213			'quiz',
214			'tracker',
215			'trackeritem',
216			'wiki page',
217			'template',
218		];
219	}
220
221	function getSelectorType($type)
222	{
223		$supported = [
224			'category' => 'category',
225			'file_gallery' => 'file gallery',
226			'forum' => 'forum',
227			'group' => 'group',
228			'tracker' => 'tracker',
229			'tracker_field' => 'trackerfield',
230			'trackerfield' => 'trackerfield',
231			'wiki_page' => 'wiki page',
232			'wiki page' => 'wiki page',
233			'template' => 'template',
234		];
235
236		if (isset($supported[$type])) {
237			return $supported[$type];
238		} else {
239			return false;
240		}
241	}
242
243	function insert_object($type, $itemId, $description = '', $name = '', $href = '')
244	{
245		if (! $itemId) {
246			// When called with a blank page name or any other empty value, no insertion should be made
247			return false;
248		}
249
250		$tikilib = TikiLib::lib('tiki');
251		$table = $this->table('tiki_objects');
252		return $table->insert(
253			[
254				'type' => $type,
255				'itemId' => (string) $itemId,
256				'description' => $description,
257				'name' => $name,
258				'href' => $href,
259				'created' => (int) $tikilib->now,
260				'hits' => 0,
261				'comments_locked' => 'n',
262			]
263		);
264	}
265
266	function get_object_id($type, $itemId)
267	{
268		$query = "select `objectId` from `tiki_objects` where `type`=? and `itemId`=?";
269		return $this->getOne($query, [$type, $itemId]);
270	}
271
272	// Returns an array containing the object ids of objects of the same type.
273	// Each entry uses the item id as key and the object id as key. Items with no object id are ignored.
274	function get_object_ids($type, $itemIds)
275	{
276		if (empty($itemIds)) {
277			return [];
278		}
279
280		$query = 'select `objectId`, `itemId` from `tiki_objects` where `type`=? and `itemId` IN (' .
281						implode(',', array_fill(0, count($itemIds), '?')) . ')';
282
283		$result = $this->query($query, array_merge([$type], $itemIds));
284		$objectIds = [];
285
286		while ($res = $result->fetchRow()) {
287			$objectIds[$res['itemId']] = $res['objectId'];
288		}
289		return $objectIds;
290	}
291
292	function get_needed_perm($objectType, $action)
293	{
294		switch ($objectType) {
295			case 'wiki page':
296			case 'wiki':
297				switch ($action) {
298					case 'view':
299					case 'read':
300						return 'tiki_p_view';
301
302					case 'edit':
303						return 'tiki_p_edit';
304				}
305			case 'article':
306				switch ($action) {
307					case 'view':
308					case 'read':
309						return 'tiki_p_read_article';
310
311					case 'edit':
312						return 'tiki_p_edit_article';
313				}
314			case 'post':
315				switch ($action) {
316					case 'view':
317					case 'read':
318						return 'tiki_p_read_blog';
319
320					case 'edit':
321						return 'tiki_p_create_blog';
322				}
323
324			case 'blog':
325				switch ($action) {
326					case 'view':
327					case 'read':
328						return 'tiki_p_read_blog';
329
330					case 'edit':
331						return 'tiki_p_create_blog';
332				}
333
334			case 'faq':
335				switch ($action) {
336					case 'view':
337					case 'read':
338						return 'tiki_p_view_faqs';
339
340					case 'edit':
341						return 'tiki_p_admin_faqs';
342				}
343
344			case 'file gallery':
345				switch ($action) {
346					case 'view':
347					case 'read':
348						return 'tiki_p_view_file_gallery';
349
350					case 'edit':
351						return 'tiki-admin_file_galleries';
352				}
353
354			case 'image gallery':
355				switch ($action) {
356					case 'view':
357					case 'read':
358						return 'tiki_p_view_image_gallery';
359
360					case 'edit':
361						return 'tiki_p_admin_galleries';
362				}
363
364			case 'poll':
365				switch ($action) {
366					case 'view':
367					case 'read':
368						return 'tiki_p_vote_poll';
369
370					case 'edit':
371						return 'tiki_p_admin';
372				}
373
374			case 'comment':
375			case 'comments':
376				switch ($action) {
377					case 'view':
378					case 'read':
379						return 'tiki_p_read_comments';
380
381					case 'edit':
382						return 'tiki_p_edit_comments';
383				}
384
385			case 'trackeritem':
386				switch ($action) {
387					case 'view':
388					case 'read':
389						return 'tiki_p_view_trackers';
390
391					case 'edit':
392						return 'tiki_p_modify_tracker_items';
393				}
394
395			case 'trackeritem_closed':
396				switch ($action) {
397					case 'view':
398					case 'read':
399						return 'tiki_p_view_trackers';
400
401					case 'edit':
402						return 'tiki_p_modify_tracker_items_closed';
403				}
404
405			case 'trackeritem_pending':
406				switch ($action) {
407					case 'view':
408					case 'read':
409						return 'tiki_p_view_trackers';
410
411					case 'edit':
412						return 'tiki_p_modify_tracker_items_pending';
413				}
414
415			case 'tracker':
416				switch ($action) {
417					case 'view':
418					case 'read':
419						return 'tiki_p_list_trackers';
420
421					case 'edit':
422						return 'tiki_p_admin_trackers';
423				}
424
425			case 'template':
426				switch ($action) {
427					case 'view':
428					case 'read':
429						return 'tiki_p_use_content_templates';
430
431					case 'edit':
432						return 'tiki_p_edit_content_templates';
433				}
434			default:
435				return '';
436		}
437	}
438
439	function get_info($objectType, $object)
440	{
441		switch ($objectType) {
442			case 'wiki':
443			case 'wiki page':
444				$tikilib = TikiLib::lib('tiki');
445				$info = $tikilib->get_page_info($object);
446				return ['title' => $object, 'data' => $info['data'], 'is_html' => $info['is_html']];
447
448			case 'article':
449				$artlib = TikiLib::lib('art');
450				$info = $artlib->get_article($object);
451				return ['title' => $info['title'], 'data' => $info['body']];
452
453			case 'file gallery':
454				$info = TikiLib::lib('filegal')->get_file_gallery_info($object);
455				return ['title' => $info['name']];
456
457			case 'blog':
458				$info = TikiLib::lib('blog')->get_blog($object);
459				return ['title' => $info['title']];
460
461			case 'post':
462			case 'blog post':
463			case 'blogpost':
464				$info = TikiLib::lib('blog')->get_post($object);
465				return ['title' => $info['title']];
466
467			case 'forum':
468				$info = TikiLib::lib('comments')->get_forum($object);
469				return ['title' => $info['name']];
470
471			case 'forum post':
472				$info = TikiLib::lib('comments')->get_comment($object);
473				return ['title' => $info['title']];
474
475			case 'tracker':
476				$info = TikiLib::lib('trk')->get_tracker($object);
477				return ['title' => $info['name']];
478
479			case 'trackerfield':
480				$info = TikiLib::lib('trk')->get_tracker_field($object);
481				return ['title' => $info['name']];
482
483			case 'goal':
484				return TikiLib::lib('goal')->fetchGoal($object);
485
486			case 'template':
487				$info = TikiLib::lib('template')->get_template($object);
488				return ['title' => $info['name']];
489		}
490		return (['error' => 'true']);
491	}
492
493	function set_data($objectType, $object, $data)
494	{
495		switch ($objectType) {
496			case 'wiki':
497			case 'wiki page':
498				global $user;
499				$tikilib = TikiLib::lib('tiki');
500				$tikilib->update_page($object, $data, tra('section edit'), $user, $tikilib->get_ip_address());
501				break;
502		}
503	}
504
505	function delete_object($type, $itemId)
506	{
507		$query = 'delete from `tiki_objects` where `itemId`=? and `type`=?';
508		$this->query($query, [$itemId, $type]);
509	}
510
511	function delete_object_via_objectid($objectId)
512	{
513		$query = 'delete from `tiki_objects` where `objectId`=?';
514		$this->query($query, [(int) $objectId]);
515	}
516
517	function get_object($type, $itemId)
518	{
519		$query = 'select * from `tiki_objects` where `itemId`=? and `type`=?';
520		$result = $this->query($query, [$itemId, $type]);
521		return $result->fetchRow();
522	}
523
524	function get_object_via_objectid($objectId)
525	{
526		$query = 'select * from `tiki_objects` where `objectId`=?';
527		$result = $this->query($query, [(int) $objectId]);
528		return $result->fetchRow();
529	}
530
531	/**
532	 * @param string $type
533	 * @param $id
534	 * @param string $format - trackeritem format coming from ItemLink field or null by default
535	 * @return void|string
536	 */
537	function get_title($type, $id, $format = null)
538	{
539		$detail = '';
540		switch ($type) {
541			case 'trackeritemfield':
542				$type = 'trackeritem';
543				$ids = explode(':', $id);
544				$id = (int)$ids[0];
545				$fieldId = (int)$ids[1];
546				$trackerlib = TikiLib::lib('trk');
547				$info = $trackerlib->get_field_info($fieldId);
548				$extra = $info['name'];
549			case 'trackeritem':
550				if ($format) {
551					$lib = TikiLib::lib('unifiedsearch');
552					$query = $lib->buildQuery([
553						'object_type' => 'trackeritem',
554						'object_id' => $id
555					]);
556					$result = $query->search($lib->getIndex());
557					$result->applyTransform(function ($item) use ($format) {
558						return preg_replace_callback('/\{(\w+)\}/', function ($matches) use ($item, $format) {
559							$key = $matches[1];
560							if (isset($item[$key])) {
561								return $item[$key];
562							} elseif (! $format || $format == '{title}') {
563								return tr('empty');
564							} else {
565								return '';
566							}
567						}, $format);
568					});
569					$titles = $result->getArrayCopy();
570					$title = array_shift($titles);
571				} else {
572					$title = TikiLib::lib('trk')->get_isMain_value(null, $id);
573				}
574				if (empty($title)) {
575					$title = "$type:$id";
576				}
577				if (isset($extra) && $extra) {
578					$title .= ' (' . $extra . ')';
579				}
580				return $title;
581			case 'category':
582				return TikiLib::lib('categ')->get_category_name($id);
583			case 'file':
584				return TikiLib::lib('filegal')->get_file_label($id);
585			case 'topic':
586				$meta = TikiLib::lib('art')->get_topic($id);
587				return $meta['name'];
588			case 'group':
589				return $id;
590			case 'user':
591				if (is_int($id)) {
592					$id = TikiLib::lib('tiki')->get_user_login($id);
593				}
594				return TikiLib::lib('user')->clean_user($id);
595			case 'calendar':
596				$info = TikiLib::lib('calendar')->get_calendar($id);
597				return $info['name'];
598			case 'calendar event':
599				$info = TikiLib::lib('calendar')->get_item($id);
600				return $info['name'];
601		}
602
603		$title = $this->table('tiki_objects')->fetchOne(
604			'name',
605			[
606				'type' => $type,
607				'itemId' => $id,
608			]
609		);
610
611		if ($title) {
612			return $title;
613		}
614
615		$info = $this->get_info($type, $id);
616
617		if (isset($info['title'])) {
618			return $info['title'];
619		}
620
621		if (isset($info['name'])) {
622			return $info['name'];
623		}
624	}
625
626	/**
627	 * Gets a wiki parsed content for an object. This is used in case an object can have wiki parsed
628	 * content that generates relations (ex: Plugin Include).
629	 *
630	 * This content can be used to find elements, but displaying to user might not be a good idea, since
631	 * text from different fields can be concatenated.
632	 *
633	 * @param string $type
634	 * @param $id
635	 * @return void|string
636	 */
637	function get_wiki_content($type, $objectId)
638	{
639		if (substr($type, -7) == 'comment') {
640			$comment_info = TikiLib::lib('comments')->get_comment((int)$objectId);
641			return $comment_info['data'];
642		}
643
644		switch ($type) {
645			case 'wiki':
646				$type = 'wiki page';
647			case 'wiki page':
648				$info = $this->get_page_info($objectId);
649				return $info['data'];
650			case 'forum post':
651				$comment_info = TikiLib::lib('comments')->get_comment((int)$objectId);
652				return $comment_info['data'];
653			case 'article':
654				$info = TikiLib::lib('art')->get_article((int)$objectId);
655				return $info['heading'] . "\n" . $info['body'];
656			case 'tracker':
657				$tracker_info = TikiLib::lib('trk')->get_tracker((int)$objectId);
658				return $tracker_info['description'];
659			case 'trackerfield':
660				$field_info = TikiLib::lib('trk')->get_field_info((int)$objectId);
661				return $field_info['description'];
662			case 'trackeritemfield':
663				$objectId = explode(':', $objectId);
664				$itemId = (int)$objectId[0];
665				$fieldId = (int)$objectId[1];
666				$trackerlib = TikiLib::lib('trk');
667				$item_info = $trackerlib->get_tracker_item($itemId);
668				return $item_info[$fieldId];
669		}
670	}
671
672	/**
673	 * @param string $type
674	 * @return string
675	 */
676	function get_verbose_type($type)
677	{
678		if (substr($type, -7) == 'comment') {
679			$isComment = true;
680			$type = substr($type, 0, strlen($type) - 8);
681		} else {
682			$isComment = false;
683		}
684
685		switch ($type) {
686			case 'trackeritem':
687				$type = 'tracker item';
688				break;
689			case 'trackeritemfield':
690				$type = 'tracker item field';
691				break;
692			case 'trackerfield':
693				$type = 'tracker field';
694				break;
695			case 'wiki':
696				$type = 'wiki page';
697				break;
698		}
699
700		if ($isComment) {
701			$type .= " comment";
702		}
703
704		return tra(ucwords($type));
705	}
706
707	/**
708	 * Returns a hash indicating which permission is needed for viewing an object of desired type.
709	 *
710	 * @param boolean $comment - indicate if returned permission must be comment-related, e.g.
711	 * am I allowed to see comments on a tracker item if I have or don't have tiki_p_tracker_view_comments.
712	 * This allows search index to properly update comment permissions not basing them on viewing
713	 * parent tracker item or wiki page but the comment itself.
714	 */
715	static function map_object_type_to_permission($comment = false)
716	{
717		return [
718			'wiki page' => $comment ? 'tiki_p_wiki_view_comments' : 'tiki_p_view',
719			'wiki' => $comment ? 'tiki_p_wiki_view_comments' : 'tiki_p_view',
720			'forum' => 'tiki_p_forum_read',
721			'forum post' => 'tiki_p_forum_read',
722			'image gallery' => 'tiki_p_view_image_gallery',
723			'file gallery' => 'tiki_p_view_file_gallery',
724			'tracker' => 'tiki_p_view_trackers',
725			'blog' => 'tiki_p_read_blog',
726			'quiz' => 'tiki_p_take_quiz',
727			'template' => 'tiki_p_use_content_templates',
728
729			// overhead - we are checking individual permission on types below, but they
730			// can't have individual permissions, although they can be categorized.
731			// should they have permissions too?
732			'poll' => 'tiki_p_vote_poll',
733			'survey' => 'tiki_p_take_survey',
734			'directory' => 'tiki_p_view_directory',
735			'faq' => 'tiki_p_view_faqs',
736			'sheet' => 'tiki_p_view_sheet',
737
738			// these ones are tricky, because permission type is for container, not object itself.
739			// I think we need to refactor permission schemes for them to be wysiwyca - lfagundes
740			//
741			// by now they're not showing, list_category_objects needs support for ignoring permissions
742			// for a type.
743			'blog post' => 'tiki_p_read_blog',
744			'article' => 'tiki_p_read_article',
745			'submission' => 'tiki_p_approve_submission',
746			'image' => 'tiki_p_view_image_gallery',
747			'calendar' => 'tiki_p_view_calendar',
748			'file' => 'tiki_p_download_files',
749			'trackeritem' => $comment ? 'tiki_p_tracker_view_comments' : 'tiki_p_view_trackers',
750
751			// newsletters can't be categorized, although there's some code in tiki-admin_newsletters.php
752			// 'newsletter' => ?,
753			// 'events' => ?,
754		];
755	}
756
757	function get_metadata($type, $object, & $classList)
758	{
759		$smarty = TikiLib::lib('smarty');
760		$smarty->loadPlugin('smarty_modifier_escape');
761
762		$escapedType = smarty_modifier_escape($type);
763		$escapedObject = smarty_modifier_escape($object);
764		$metadata = ' data-type="' . $escapedType . '" data-object="' . $escapedObject . '"';
765
766		if ($coordinates = TikiLib::lib('geo')->get_coordinates($type, $object)) {
767			$classList[] = 'geolocated';
768			$metadata .= " data-geo-lat=\"{$coordinates['lat']}\" data-geo-lon=\"{$coordinates['lon']}\"";
769
770			if (isset($coordinates['zoom'])) {
771				$metadata .= " data-geo-zoom=\"{$coordinates['zoom']}\"";
772			}
773		}
774
775		$attributelib = TikiLib::lib('attribute');
776		$attributes = $attributelib->get_attributes($type, $object);
777
778		if (isset($attributes['tiki.icon.src'])) {
779			$escapedIcon = smarty_modifier_escape($attributes['tiki.icon.src']);
780			$metadata .= " data-icon-src=\"$escapedIcon\"";
781		}
782
783		return $metadata;
784	}
785
786	function get_typeItemsInfo($type) // Returns information on all items of an object type (eg: menu, article, etc) from tiki_objects table
787	{
788		//get objects
789		$queryObjectInfo = 'select * from `tiki_objects` where `type`=?';
790		$resultObjectInfo = $this->fetchAll($queryObjectInfo, [$type]);
791
792		//get object attributes
793		foreach ($resultObjectInfo as &$tempInfo) {
794			$objectAttributes = TikiLib::lib('attribute')->get_attributes($tempInfo['type'], $tempInfo['objectId']);
795			$tempInfo = array_merge($tempInfo, $objectAttributes);
796		}
797		unset($tempInfo);
798
799		//return information
800		return $resultObjectInfo;
801	}
802
803	public function get_maintainers() // get all objects that have maintainers ??? GET_MAINTAINED_OBJECTS
804	{
805		$relationlib = TikiLib::lib('relation');
806		return $relationlib->get_related_objects('tiki.object.maintainer');
807	}
808
809	public function set_maintainers($objectId, array $maintainers, $type = 'wiki page')
810	{
811		$relationlib = TikiLib::lib('relation');
812
813		foreach ($maintainers as $maintainer) {
814			$relationlib->add_relation('tiki.object.maintainer', $type, $objectId, 'user', $maintainer);
815		}
816	}
817
818	public function get_freshness($objectId, $type = 'wiki page')
819	{
820		if ($type === 'wiki page') {
821			$info = TikiLib::lib('tiki')->get_page_info($objectId, false);
822			if (isset($info)) {
823				$lastModif = $info['lastModif'];
824				$freshness = (int) ((time() - $lastModif) / self::SecondsPerDay);
825				return $freshness;
826			}
827		} else {
828			Feedback::error(tr('Object freshness not supported yet for object type %0', $type));
829		}
830
831		return false;
832	}
833}
834