1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8/**
9 * Class Services_Annotator_Controller
10 *
11 * Linking annotatorjs to inline comments
12 */
13
14const RANGE_ATTRIBUTE = 'tiki.comment.ranges';
15
16class Services_Comment_AnnotationController
17{
18	/** @var  Comments */
19	private $commentslib;
20	/** @var  Services_Comment_Controller */
21	private $commentController;
22
23
24	/**
25	 * @throws Exception
26	 * @throws Services_Exception_Disabled
27	 */
28	function setUp()
29	{
30		Services_Exception_Disabled::check('comments_inline_annotator');
31
32		$this->commentslib = TikiLib::lib('comments');
33		$this->commentController = new Services_Comment_Controller();
34	}
35
36	/**
37	 * Create an inline comment
38	 *
39	 * @param jitFilter $input
40	 *        string    json encoded comment info from annotatorjs
41	 * containing:
42	 *        string    text    comment text
43	 *        string    quote   quoted text on page
44	 *        array     ranges  range info for quoted text
45	 *        string    uri     actually object-type:object-id identifier for the tiki object to be commented
46	 *
47	 * @return array    unused probably
48	 *
49	 * @throws Exception
50	 * @throws Services_Exception_Denied
51	 */
52
53	function action_create($input)
54	{
55		global $user;
56
57		// annotatejs sends the params in the request payload by default, so we use option emulateJSON
58		// but then need to decode the json string here
59		$params = new jitFilter(json_decode($input->json->none(), true));
60
61		$text = $params->text->wikicontent();
62		$quote = $params->quote->text();
63		$ranges = $params->asArray('ranges');
64		$identifier = urldecode($params->uri->url());	// not really the uri but object-type:object-id identifier
65		list($objectType, $objectId) = explode(':', $identifier, 2);
66
67		if (! $this->commentController->canPost($objectType, $objectId)) {
68			throw new Services_Exception_Denied;
69		}
70
71		$comment = $this->formatComment($quote, $text);
72		$title = $this->createTitle($text);
73		$messageId = '';
74
75		// create the comment
76		$threadId = $this->commentslib->post_new_comment(
77			$identifier,
78			0,
79			$user,
80			$title,
81			$comment,
82			$messageId
83		);
84
85		TikiLib::lib('attribute')->set_attribute(
86			'comment',
87			$threadId,
88			RANGE_ATTRIBUTE,
89			json_encode($ranges)
90		);
91
92		return [
93			'id' => $threadId,
94		];
95	}
96
97	/**
98	 * Update an inline comment
99	 *
100	 * @param jitFilter $input
101	 *        string    json encoded comment info from annotatorjs
102	 * containing:
103	 *        int       id      comment threadId
104	 *        string    text    comment text
105	 *        string    quote   quoted text on page
106	 *        array     ranges  range info for quoted text
107	 *        string    uri     actually object-type:object-id identifier for the tiki object to be commented
108	 *        string    created datetime updated (?)
109	 *        string    updated datetime updated
110	 *
111	 * @return array    unused probably
112	 *
113	 * @throws Exception
114	 * @throws Services_Exception_Denied
115	 * @throws Services_Exception_NotFound
116	 */
117
118	function action_update($input)
119	{
120		$threadId = $input->threadId->int();
121		$params = new jitFilter(json_decode($input->json->none(), true));
122
123		$ranges = $params->asArray('ranges');
124		$text = $params->text->wikicontent();
125		$quote = $params->quote->text();
126
127		$input->offsetSet('data', $this->formatComment($quote, $text));
128		$input->offsetSet('title', $this->createTitle($text));
129		$input->offsetSet('edit', 1);
130
131		$ret = $this->commentController->action_edit($input);
132
133		if ($ret) {
134			TikiLib::lib('attribute')->set_attribute(
135				'comment',
136				$threadId,
137				RANGE_ATTRIBUTE,
138				json_encode($ranges)
139			);
140		}
141
142		return $ret;
143	}
144
145	/**
146	 * Remove an inline comment - warning, no confirmation yet
147	 *
148	 * @param jitFilter $input
149	 *        int       threadId  comment id to delete
150	 *
151	 * @return array
152	 * @throws Services_Exception
153	 */
154
155	function action_destroy($input)
156	{
157		$input->offsetSet('confirm', 1);	// TODO but not sure how?
158
159		$ret = $this->commentController->action_remove($input);
160
161		$ret['id'] = $ret['threadId'];
162		return $ret;
163	}
164
165	/**
166	 * List inline comments for a tiki object
167	 *
168	 * @param jitFilter $input
169	 *        int       limit   page size TODO
170	 *        int       offset  page start
171	 *        string    uri     object-type:object-id identifier for the tiki object to search
172	 *
173	 * @return array    [total, rows]
174	 * @throws Exception
175	 * @throws Services_Exception
176	 */
177
178	function action_search($input)
179	{
180		$tikilib = TikiLib::lib('tiki');
181
182		$limit = $input->limit->int();		// unused so far
183		$offset = $input->offset->int();	// TODO pagination
184
185		$identifier = urldecode($input->uri->url());
186		$object = explode(':', $identifier);
187
188		$list = $this->commentController->action_list(new jitFilter([
189			'type' => $object[0],
190			'objectId' => $object[1],
191		]));
192
193		$comments = [];
194
195		foreach ($list['comments'] as $comment) {
196			if (strpos($comment['data'], ';note:') === 0) {			// only the "inline" ones starting ;note: so far
197				$data = explode("\n", $comment['data'], 2);
198				$quote = trim(substr($data[0], 6));
199				$text = trim($data[1]);
200
201				$ranges = json_decode(
202					TikiLib::lib('attribute')->get_attribute(
203						'comment',
204						$comment['threadId'],
205						RANGE_ATTRIBUTE
206					),
207					true
208				);
209
210				$permissions = [
211					'create' => $list['allow_post'],
212					'update' => $comment['can_edit'],
213					'delete' => $list['allow_remove'],
214				];
215
216				$comments[] = [
217					'id' => $comment['threadId'],
218					'text' => $text,
219					'quote' => $quote,
220					'created' => $tikilib->get_iso8601_datetime($comment['commentDate']),
221					'updated' => $tikilib->get_iso8601_datetime($comment['commentDate']),	// we don't have a commentUpdated column?
222					'ranges' => $ranges ? $ranges : [],
223					'permissions' => $permissions,
224
225				];
226			}
227		};
228
229		return [
230			'total' => count($comments),	// TODO pagination
231			'rows' => $comments,
232		];
233	}
234
235	/**
236	 * @param $text
237	 * @return string
238	 * @throws Exception
239	 * @throws SmartyException
240	 */
241	private function createTitle($text)
242	{
243		$smarty = TikiLib::lib('smarty');
244		$smarty->loadPlugin('smarty_modifier_truncate');
245
246		return smarty_modifier_truncate($text, 50);
247	}
248
249	/**
250	 * @param $quote
251	 * @param $text
252	 * @return string
253	 */
254	private function formatComment($quote, $text)
255	{
256		$safeQuote = str_replace(["\n", "\r"], ' ', $quote);
257		$safeQuote = addslashes($safeQuote);
258		return ';note:' . $safeQuote . "\n\n" . $text;
259	}
260}
261