1 | <?php |
||
2 | /** |
||
3 | * This file is part of the phpBB Topic Solved extension package. |
||
4 | * |
||
5 | * @copyright (c) Bryan Petty |
||
6 | * @license GNU General Public License, version 2 (GPL-2.0) |
||
7 | * |
||
8 | * @package tierra/topicsolved |
||
9 | */ |
||
10 | |||
11 | namespace tierra\topicsolved; |
||
12 | |||
13 | /** |
||
14 | * Core topic solved functionality used throughout the extension. |
||
15 | * |
||
16 | * @package tierra/topicsolved |
||
17 | */ |
||
18 | class topicsolved |
||
19 | { |
||
20 | /** No-one can mark topics as solved. */ |
||
21 | const TOPIC_SOLVED_NO = 0; |
||
22 | |||
23 | /** Topic starter and moderators can mark topics as solved. */ |
||
24 | const TOPIC_SOLVED_YES = 1; |
||
25 | |||
26 | /** Only moderators can mark topics as solved. */ |
||
27 | const TOPIC_SOLVED_MOD = 2; |
||
28 | |||
29 | /** @var \phpbb\db\driver\driver_interface */ |
||
30 | protected $db; |
||
31 | |||
32 | /** @var \phpbb\user */ |
||
33 | protected $user; |
||
34 | |||
35 | /** @var \phpbb\auth\auth */ |
||
36 | protected $auth; |
||
37 | |||
38 | /** @var \phpbb\event\dispatcher_interface */ |
||
39 | protected $dispatcher; |
||
40 | |||
41 | /** @var string core.root_path */ |
||
42 | protected $root_path; |
||
43 | |||
44 | /** @var string core.php_ext */ |
||
45 | protected $php_ext; |
||
46 | |||
47 | /** |
||
48 | * Constructor |
||
49 | * |
||
50 | * @param \phpbb\db\driver\driver_interface $db Database object |
||
51 | * @param \phpbb\user $user |
||
52 | * @param \phpbb\auth\auth $auth |
||
53 | * @param \phpbb\event\dispatcher_interface $dispatcher |
||
54 | * @param string $root_path core.root_path |
||
55 | * @param string $php_ext core.php_ext |
||
56 | */ |
||
57 | 17 | public function __construct( |
|
58 | \phpbb\db\driver\driver_interface $db, |
||
59 | \phpbb\user $user, |
||
60 | \phpbb\auth\auth $auth, |
||
61 | \phpbb\event\dispatcher_interface $dispatcher, |
||
62 | $root_path, $php_ext) |
||
63 | { |
||
64 | 17 | $this->db = $db; |
|
65 | 17 | $this->user = $user; |
|
66 | 17 | $this->auth = $auth; |
|
67 | 17 | $this->dispatcher = $dispatcher; |
|
68 | 17 | $this->root_path = $root_path; |
|
69 | 17 | $this->php_ext = $php_ext; |
|
70 | |||
71 | 17 | $this->user->add_lang_ext('tierra/topicsolved', 'common'); |
|
72 | 17 | } |
|
73 | |||
74 | /** |
||
75 | * Determine if user is allowed to mark a post as solved or unsolved. |
||
76 | * |
||
77 | * @param string $solved Either "solved" or "unsolved". |
||
78 | * @param array $topic_data Topic to be solved or unsolved. |
||
79 | * |
||
80 | * @throws \phpbb\exception\runtime_exception |
||
81 | * if an invalid solved parameter is specified. |
||
82 | * |
||
83 | * @return bool User is authorized to (un)solve topic. |
||
84 | */ |
||
85 | 1 | public function user_can_solve_post($solved, $topic_data) |
|
86 | { |
||
87 | // Disallow (un)solving topic if post is global. |
||
88 | 1 | if ($topic_data['topic_type'] == POST_GLOBAL) |
|
89 | 1 | { |
|
90 | return false; |
||
91 | } |
||
92 | |||
93 | $forum_permission = array( |
||
94 | 1 | 'solved' => 'forum_allow_solve', |
|
95 | 1 | 'unsolved' => 'forum_allow_unsolve', |
|
96 | 1 | ); |
|
97 | |||
98 | 1 | if (!array_key_exists($solved, $forum_permission)) |
|
99 | 1 | { |
|
100 | throw new \phpbb\exception\runtime_exception( |
||
101 | 'BAD_METHOD_CALL', array('user_can_solve_post')); |
||
102 | } |
||
103 | |||
104 | 1 | if (($topic_data[$forum_permission[$solved]] == topicsolved::TOPIC_SOLVED_MOD || |
|
105 | 1 | $topic_data[$forum_permission[$solved]] == topicsolved::TOPIC_SOLVED_YES) && |
|
106 | 1 | $this->auth->acl_get('m_', $topic_data['forum_id'])) |
|
107 | 1 | { |
|
108 | return true; |
||
109 | } |
||
110 | 1 | else if ($topic_data[$forum_permission[$solved]] == topicsolved::TOPIC_SOLVED_YES && |
|
111 | 1 | $topic_data['topic_poster'] == $this->user->data['user_id'] && |
|
112 | 1 | $topic_data['topic_status'] == ITEM_UNLOCKED) |
|
113 | 1 | { |
|
114 | 1 | return true; |
|
115 | } |
||
116 | |||
117 | return false; |
||
118 | } |
||
119 | |||
120 | /** |
||
121 | * Fetches all topic solved data related to the given post. |
||
122 | * |
||
123 | * @param int $post_id ID of post to fetch topic/forum data for. |
||
124 | * |
||
125 | * @return mixed topic data, or false if not found |
||
126 | */ |
||
127 | 1 | public function get_topic_data($post_id) |
|
128 | { |
||
129 | $select_sql_array = array( |
||
130 | 'SELECT' => |
||
131 | 't.topic_id, t.topic_poster, t.topic_status, t.topic_type, t.topic_solved, ' . |
||
132 | 1 | 'f.forum_id, f.forum_allow_solve, f.forum_allow_unsolve, f.forum_lock_solved, ' . |
|
133 | 1 | 'p.post_id', |
|
134 | 'FROM' => array( |
||
135 | 1 | FORUMS_TABLE => 'f', |
|
136 | 1 | POSTS_TABLE => 'p', |
|
137 | 1 | TOPICS_TABLE => 't', |
|
138 | 1 | ), |
|
139 | 'WHERE' => |
||
140 | 1 | 'p.post_id = ' . (int) $post_id . |
|
141 | 1 | ' AND t.topic_id = p.topic_id AND f.forum_id = t.forum_id', |
|
142 | 1 | ); |
|
143 | 1 | $select_sql = $this->db->sql_build_query('SELECT', $select_sql_array); |
|
144 | 1 | $result = $this->db->sql_query($select_sql); |
|
145 | 1 | $topic_data = $this->db->sql_fetchrow($result); |
|
146 | 1 | $this->db->sql_freeresult($result); |
|
147 | |||
148 | 1 | return $topic_data; |
|
149 | } |
||
150 | |||
151 | /** |
||
152 | * Update topic with the given data. |
||
153 | * |
||
154 | * @param int $topic_id Topic to update. |
||
155 | * @param array $data Topic data to update. |
||
156 | * |
||
157 | * @return mixed true if successful |
||
158 | */ |
||
159 | 3 | public function update_topic($topic_id, $data) |
|
160 | { |
||
161 | 3 | $update_sql = $this->db->sql_build_array('UPDATE', $data); |
|
162 | 3 | $result = $this->db->sql_query(' |
|
163 | 3 | UPDATE ' . TOPICS_TABLE . ' |
|
164 | 3 | SET ' . $update_sql . ' |
|
165 | 3 | WHERE topic_id = ' . (int) $topic_id |
|
166 | 3 | ); |
|
167 | |||
168 | 3 | return $result; |
|
169 | } |
||
170 | |||
171 | /** |
||
172 | * Marks a topic as solved. |
||
173 | * |
||
174 | * @param array $topic_data Topic to be marked as solved. |
||
175 | * @param int $post_id Post to mark as the solution. |
||
176 | */ |
||
177 | 2 | public function mark_solved($topic_data, $post_id) |
|
178 | { |
||
179 | // Database column values to set. |
||
180 | 2 | $column_data = array('topic_solved' => $post_id); |
|
181 | |||
182 | 2 | if ($topic_data['forum_lock_solved'] && |
|
183 | 1 | $this->user_can_lock_post($topic_data['forum_id'])) |
|
184 | 2 | { |
|
185 | 1 | $column_data['topic_status'] = ITEM_LOCKED; |
|
186 | 1 | } |
|
187 | |||
188 | 2 | $this->update_topic($topic_data['topic_id'], $column_data); |
|
189 | |||
190 | /** |
||
191 | * This event allows you to perform additional actions after a topic has been marked as solved. |
||
192 | * |
||
193 | * @event tierra.topicsolved.mark_solved_after |
||
194 | * @var array topic_data Array with general topic data |
||
195 | * @var array column_data Array with topic data that the database has been updated with |
||
196 | * @since 2.2.0 |
||
197 | */ |
||
198 | $vars = array( |
||
199 | 2 | 'topic_data', |
|
200 | 2 | 'column_data', |
|
201 | 2 | ); |
|
202 | 2 | extract($this->dispatcher->trigger_event('tierra.topicsolved.mark_solved_after', compact($vars))); |
|
203 | 2 | } |
|
204 | |||
205 | /** |
||
206 | * Marks a topic as unsolved. |
||
207 | * |
||
208 | * @param array $topic_data Topic to be marked as unsolved. |
||
209 | */ |
||
210 | 1 | public function mark_unsolved($topic_data) |
|
211 | { |
||
212 | // Database column values to set. |
||
213 | 1 | $column_data = array('topic_solved' => 0); |
|
214 | |||
215 | 1 | if ($topic_data['forum_lock_solved'] && |
|
216 | 1 | $this->auth->acl_get('m_lock', $topic_data['forum_id'])) |
|
217 | 1 | { |
|
218 | 1 | $column_data['topic_status'] = ITEM_UNLOCKED; |
|
219 | 1 | } |
|
220 | |||
221 | 1 | $this->update_topic($topic_data['topic_id'], $column_data); |
|
222 | |||
223 | /** |
||
224 | * This event allows you to perform additional actions after a topic has been marked as unsolved. |
||
225 | * |
||
226 | * @event tierra.topicsolved.mark_unsolved_after |
||
227 | * @var array topic_data Array with general topic data |
||
228 | * @var array column_data Array with topic data that the database has been updated with |
||
229 | * @since 2.2.0 |
||
230 | */ |
||
231 | $vars = array( |
||
232 | 1 | 'topic_data', |
|
233 | 1 | 'column_data', |
|
234 | 1 | ); |
|
235 | 1 | extract($this->dispatcher->trigger_event('tierra.topicsolved.mark_unsolved_after', compact($vars))); |
|
236 | 1 | } |
|
237 | |||
238 | /** |
||
239 | * Checks if the currently logged in user has permission to lock a post. |
||
240 | * |
||
241 | * Regular users won't have permission to solve any topics other than their |
||
242 | * own, and moderator permissions are forum based, so we only need to know |
||
243 | * the forum, not the post. |
||
244 | * |
||
245 | * @param int $forum_id Forum to check permissions on. |
||
246 | * |
||
247 | * @return bool true if user has permission to lock a post. |
||
248 | */ |
||
249 | 4 | public function user_can_lock_post($forum_id) |
|
250 | { |
||
251 | // Check if user is moderator with appropriate lock permission |
||
252 | 4 | if ($this->auth->acl_get('m_lock', $forum_id)) |
|
253 | 4 | { |
|
254 | 2 | return true; |
|
255 | } |
||
256 | |||
257 | // Check if user has "lock own posts" permission |
||
258 | 2 | if ($this->auth->acl_get('f_user_lock', $forum_id)) |
|
259 | 2 | { |
|
260 | 1 | return true; |
|
261 | } |
||
262 | |||
263 | 1 | return false; |
|
264 | } |
||
265 | |||
266 | /** |
||
267 | * Generate markup for the given solved indicator image. |
||
268 | * |
||
269 | * @param string $type One of "head", "list", or "post". |
||
270 | * @param string $alt Language code for title and alternative text. |
||
271 | * @param string $url Optional link to solved post. |
||
272 | * |
||
273 | * @return string HTML markup for image. |
||
274 | */ |
||
275 | 6 | public function image($type, $alt = '', $url = '') |
|
276 | { |
||
277 | 6 | $title = ''; |
|
278 | 6 | $markup = $this->user->img('icon_solved_' . $type, $alt); |
|
279 | |||
280 | 6 | if (!empty($alt)) |
|
281 | 6 | { |
|
282 | 4 | $alt = $this->user->lang($alt); |
|
283 | 4 | $title = ' title="' . htmlspecialchars($alt, ENT_QUOTES, 'UTF-8') . '"'; |
|
284 | 4 | } |
|
285 | |||
286 | 6 | if (!empty($url)) |
|
287 | 6 | { |
|
288 | 6 | $markup = sprintf('<a href="%s"%s>%s</a>', |
|
289 | 6 | htmlspecialchars($url, ENT_QUOTES, 'UTF-8'), $title, $markup); |
|
290 | 6 | } |
|
291 | |||
292 | 6 | return $markup; |
|
293 | } |
||
294 | |||
295 | /** |
||
296 | * Generate markup for the given solved indicator icon. |
||
297 | * |
||
298 | * @param string $color Color to use for the icon. |
||
299 | * @param string $alt Language code for title and alternative text. |
||
300 | * @param string $url Optional link to solved post. |
||
301 | * |
||
302 | * @return string HTML markup for icon. |
||
303 | */ |
||
304 | 5 | public function icon($color = '', $alt = '', $url = '') |
|
305 | { |
||
306 | 5 | $title = ''; |
|
307 | 5 | if (empty($color)) |
|
308 | 5 | { |
|
309 | 3 | $color = '00BF00'; |
|
310 | 3 | } |
|
311 | 5 | $classes = 'fa fa-check-circle fa-fw'; |
|
312 | |||
313 | /** |
||
314 | * This event makes it possible to customize the solved icon. |
||
315 | * |
||
316 | * @event tierra.topicsolved.render_icon |
||
317 | * @var string alt Alternative text label for link if a URL was provided. |
||
318 | * @var string classes CSS classes used for icon. |
||
319 | * @var string color Color applied to the icon. |
||
320 | * @var string url Link to the solved post. |
||
321 | * @since 2.3.0 |
||
322 | */ |
||
323 | 5 | $vars = array('alt', 'classes', 'color', 'url'); |
|
324 | 5 | extract($this->dispatcher->trigger_event('tierra.topicsolved.render_icon', compact($vars))); |
|
325 | |||
326 | 5 | $markup = sprintf( |
|
327 | 5 | '<i class="%1s" style="color: #%2s" aria-hidden="true"></i>', |
|
328 | 5 | $classes, $color |
|
329 | 5 | ); |
|
330 | |||
331 | 5 | if (!empty($alt)) |
|
332 | 5 | { |
|
333 | 3 | $alt = $this->user->lang($alt); |
|
334 | 3 | $title = ' title="' . htmlspecialchars($alt, ENT_QUOTES, 'UTF-8') . '"'; |
|
335 | 3 | } |
|
336 | |||
337 | 5 | if (!empty($url)) |
|
338 | 5 | { |
|
339 | 5 | $markup = sprintf('<a href="%s"%s>%s</a>', |
|
340 | 5 | htmlspecialchars($url, ENT_QUOTES, 'UTF-8'), $title, $markup); |
|
341 | 5 | } |
|
342 | |||
343 | 5 | return $markup; |
|
344 | } |
||
345 | |||
346 | /** |
||
347 | * Generate link to specific post (usually solution post). |
||
348 | * |
||
349 | * @param int $forum_id |
||
350 | * @param int $topic_id |
||
351 | * @param int $post_id |
||
352 | * |
||
353 | * @return string Relative URL to post |
||
354 | */ |
||
355 | 1 | public function get_link_to_post($forum_id, $topic_id, $post_id) |
|
356 | { |
||
357 | 1 | return append_sid("{$this->root_path}viewtopic.{$this->php_ext}", |
|
358 | 1 | "f=$forum_id&t=$topic_id&p=$post_id") . '#p' . $post_id; |
|
359 | } |
||
360 | } |
||
361 |