1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * This file is mainly meant for controlling the actions related to personal |
||||
5 | * messages. It allows viewing, sending, deleting, and marking. |
||||
6 | * For compatibility reasons, they are often called "instant messages". |
||||
7 | * |
||||
8 | * @package ElkArte Forum |
||||
9 | * @copyright ElkArte Forum contributors |
||||
10 | * @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
||||
11 | * |
||||
12 | * This file contains code covered by: |
||||
13 | * copyright: 2011 Simple Machines (http://www.simplemachines.org) |
||||
14 | * |
||||
15 | * @version 2.0 dev |
||||
16 | * |
||||
17 | */ |
||||
18 | |||||
19 | namespace ElkArte\Controller; |
||||
20 | |||||
21 | use BBC\ParserWrapper; |
||||
22 | use ElkArte\AbstractController; |
||||
23 | use ElkArte\Action; |
||||
24 | use ElkArte\Cache\Cache; |
||||
25 | use ElkArte\Errors\ErrorContext; |
||||
26 | use ElkArte\EventManager; |
||||
27 | use ElkArte\Exceptions\ControllerRedirectException; |
||||
28 | use ElkArte\Exceptions\Exception; |
||||
29 | use ElkArte\Exceptions\PmErrorException; |
||||
30 | use ElkArte\Helper\Util; |
||||
31 | use ElkArte\Helper\ValuesContainer; |
||||
32 | use ElkArte\Languages\Loader; |
||||
33 | use ElkArte\Languages\Txt; |
||||
34 | use ElkArte\MembersList; |
||||
35 | use ElkArte\MessagesCallback\BodyParser\Normal; |
||||
36 | use ElkArte\MessagesCallback\PmRenderer; |
||||
37 | use ElkArte\Profile\Profile; |
||||
38 | use ElkArte\Profile\ProfileFields; |
||||
39 | use ElkArte\Profile\ProfileOptions; |
||||
40 | use ElkArte\User; |
||||
41 | use ElkArte\VerificationControls\VerificationControlsIntegrate; |
||||
42 | |||||
43 | /** |
||||
44 | * Class PersonalMessage |
||||
45 | * It allows viewing, sending, deleting, and marking personal messages |
||||
46 | * |
||||
47 | * @package ElkArte\Controller |
||||
48 | */ |
||||
49 | class PersonalMessage extends AbstractController |
||||
50 | { |
||||
51 | /** |
||||
52 | * @var array $_search_params will carry all settings that differ from the default |
||||
53 | * search parameters. That way, the URLs involved in a search page will |
||||
54 | * be kept as short as possible. |
||||
55 | */ |
||||
56 | private $_search_params = []; |
||||
57 | |||||
58 | /** @var array $_searchq_parameters will carry all the values needed by S_search_params */ |
||||
59 | private $_searchq_parameters = []; |
||||
60 | |||||
61 | /** @var int display_mode key is as follows */ |
||||
62 | private const DISPLAY_ALL_AT_ONCE = 0; |
||||
63 | private const DISPLAY_ONE_AT_TIME = 1; |
||||
64 | private const DISPLAY_AS_CONVERSATION = 2; |
||||
65 | |||||
66 | /** |
||||
67 | * This method is executed before any other in this file (when the class is |
||||
68 | * loaded by the dispatcher). |
||||
69 | * |
||||
70 | * What it does: |
||||
71 | 4 | * |
|||
72 | * - It sets the context, load templates and language file(s), as necessary |
||||
73 | 4 | * for the function that will be called. |
|||
74 | */ |
||||
75 | public function pre_dispatch() |
||||
76 | 4 | { |
|||
77 | global $txt, $context, $modSettings; |
||||
78 | |||||
79 | 4 | // No guests! |
|||
80 | is_not_guest(); |
||||
81 | |||||
82 | 4 | // You're not supposed to be here at all, if you can't even read PMs. |
|||
83 | isAllowedTo('pm_read'); |
||||
84 | |||||
85 | 4 | // This file contains PM functions such as mark, send, delete |
|||
86 | 4 | require_once(SUBSDIR . '/PersonalMessage.subs.php'); |
|||
87 | |||||
88 | 4 | // Templates, language, javascripts |
|||
89 | Txt::load('PersonalMessage'); |
||||
90 | loadJavascriptFile(['suggest.js', 'PersonalMessage.js']); |
||||
91 | |||||
92 | if ($this->getApi() === false) |
||||
93 | 4 | { |
|||
94 | theme()->getTemplates()->load('PersonalMessage'); |
||||
95 | } |
||||
96 | 4 | ||||
97 | $this->_events->trigger('pre_dispatch', ['xml' => $this->getApi() !== false]); |
||||
98 | |||||
99 | 4 | // Load up the members maximum message capacity. |
|||
100 | $this->_loadMessageLimit(); |
||||
101 | |||||
102 | // A previous message was sent successfully? show a small indication. |
||||
103 | if ($this->_req->getQuery('done') === 'sent') |
||||
104 | { |
||||
105 | 4 | $context['pm_sent'] = true; |
|||
106 | } |
||||
107 | 4 | ||||
108 | // Load the label counts data. |
||||
109 | if (User::$settings['new_pm'] || !Cache::instance()->getVar($context['labels'], 'labelCounts:' . $this->user->id, 720)) |
||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||
110 | 4 | { |
|||
111 | $this->_loadLabels(); |
||||
112 | |||||
113 | // Get the message count for each label |
||||
114 | 4 | $context['labels'] = loadPMLabels($context['labels']); |
|||
115 | } |
||||
116 | |||||
117 | // Now we have the labels, and assuming we have unsorted mail, apply our rules! |
||||
118 | if (User::$settings['new_pm']) |
||||
119 | { |
||||
120 | // Apply our rules to the new PM's |
||||
121 | applyRules(); |
||||
122 | |||||
123 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
124 | updateMemberData($this->user->id, array('new_pm' => 0)); |
||||
125 | |||||
126 | // Turn the new PM's status off, for the popup alert, since they have entered the PM area |
||||
127 | 4 | toggleNewPM($this->user->id); |
|||
128 | } |
||||
129 | |||||
130 | 4 | // This determines if we have more labels than just the standard inbox. |
|||
131 | 4 | $context['currently_using_labels'] = count($context['labels']) > 1 ? 1 : 0; |
|||
132 | 4 | ||||
133 | // Some stuff for the labels... |
||||
134 | $label = $this->_req->getQuery('l', 'intval'); |
||||
135 | 4 | $folder = $this->_req->getQuery('f', 'trim', ''); |
|||
136 | 4 | $start = $this->_req->getQuery('start', 'trim'); |
|||
137 | $context['current_label_id'] = isset($label, $context['labels'][$label]) ? (int) $label : -1; |
||||
138 | $context['current_label'] = &$context['labels'][$context['current_label_id']]['name']; |
||||
139 | 4 | $context['folder'] = $folder !== 'sent' ? 'inbox' : 'sent'; |
|||
140 | 4 | ||||
141 | 4 | // This is convenient. Do you know how annoying it is to do this every time?! |
|||
142 | $context['current_label_redirect'] = 'action=pm;f=' . $context['folder'] . (isset($start) ? ';start=' . $start : '') . (empty($label) ? '' : ';l=' . $label); |
||||
143 | $context['can_issue_warning'] = featureEnabled('w') && allowedTo('issue_warning') && !empty($modSettings['warning_enable']); |
||||
144 | |||||
145 | 4 | // Build the breadcrumbs for all the actions... |
|||
146 | 4 | $context['breadcrumbs'][] = [ |
|||
147 | 'url' => getUrl('action', ['action' => 'pm']), |
||||
148 | 'name' => $txt['personal_messages'] |
||||
149 | ]; |
||||
150 | |||||
151 | 4 | // Preferences... |
|||
152 | $context['display_mode'] = (int) User::$settings['pm_prefs'] & 3; |
||||
153 | 4 | } |
|||
154 | |||||
155 | 4 | /** |
|||
156 | * Load a members message limit and prepares the limit bar |
||||
157 | */ |
||||
158 | 4 | private function _loadMessageLimit() |
|||
159 | { |
||||
160 | global $context, $txt; |
||||
161 | |||||
162 | $context['message_limit'] = loadMessageLimit(); |
||||
163 | |||||
164 | // Prepare the context for the capacity bar. |
||||
165 | if (!empty($context['message_limit'])) |
||||
166 | { |
||||
167 | $bar = ($this->user->messages * 100) / $context['message_limit']; |
||||
0 ignored issues
–
show
The property
messages does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
168 | |||||
169 | $context['limit_bar'] = array( |
||||
170 | 4 | 'messages' => $this->user->messages, |
|||
171 | 'allowed' => $context['message_limit'], |
||||
172 | 'percent' => $bar, |
||||
173 | 'bar' => min(100, (int) $bar), |
||||
174 | 'text' => sprintf($txt['pm_currently_using'], $this->user->messages, round($bar, 1)), |
||||
175 | 4 | ); |
|||
176 | } |
||||
177 | 4 | } |
|||
178 | |||||
179 | 4 | /** |
|||
180 | * Loads the user defined label's for use in the template etc. |
||||
181 | 4 | */ |
|||
182 | private function _loadLabels() |
||||
183 | 4 | { |
|||
184 | global $context, $txt; |
||||
185 | 4 | ||||
186 | $userLabels = explode(',', User::$settings['message_labels'] ?? ''); |
||||
187 | |||||
188 | foreach ($userLabels as $id_label => $label_name) |
||||
189 | { |
||||
190 | if (empty($label_name)) |
||||
191 | { |
||||
192 | continue; |
||||
193 | } |
||||
194 | |||||
195 | $context['labels'][$id_label] = [ |
||||
196 | 'id' => $id_label, |
||||
197 | 4 | 'name' => trim($label_name), |
|||
198 | 4 | 'messages' => 0, |
|||
199 | 4 | 'unread_messages' => 0, |
|||
200 | 4 | ]; |
|||
201 | 4 | } |
|||
202 | |||||
203 | 4 | // The default inbox is always available |
|||
204 | $context['labels'][-1] = [ |
||||
205 | 'id' => -1, |
||||
206 | 'name' => $txt['pm_msg_label_inbox'], |
||||
207 | 'messages' => 0, |
||||
208 | 'unread_messages' => 0, |
||||
209 | ]; |
||||
210 | } |
||||
211 | |||||
212 | /** |
||||
213 | * This is the main function of personal messages, called before the action handler. |
||||
214 | * |
||||
215 | * What it does: |
||||
216 | 4 | * |
|||
217 | * - PersonalMessages is a menu-based controller. |
||||
218 | 4 | * - It sets up the menu. |
|||
219 | * - Calls from the menu the appropriate method/function for the current area. |
||||
220 | * |
||||
221 | * @see AbstractController::action_index |
||||
222 | 4 | */ |
|||
223 | 4 | public function action_index() |
|||
224 | 4 | { |
|||
225 | 4 | global $context; |
|||
226 | 4 | ||||
227 | 4 | // Finally all the things we know how to do |
|||
228 | 4 | $subActions = array( |
|||
229 | 4 | 'manlabels' => array($this, 'action_manlabels', 'permission' => 'pm_read'), |
|||
230 | 4 | 'manrules' => array($this, 'action_manrules', 'permission' => 'pm_read'), |
|||
231 | 4 | 'markunread' => array($this, 'action_markunread', 'permission' => 'pm_read'), |
|||
232 | 4 | 'pmactions' => array($this, 'action_pmactions', 'permission' => 'pm_read'), |
|||
233 | 4 | 'prune' => array($this, 'action_prune', 'permission' => 'pm_read'), |
|||
234 | 4 | 'removeall' => array($this, 'action_removeall', 'permission' => 'pm_read'), |
|||
235 | 4 | 'removeall2' => array($this, 'action_removeall2', 'permission' => 'pm_read'), |
|||
236 | 'report' => array($this, 'action_report', 'permission' => 'pm_read'), |
||||
237 | 'search' => array($this, 'action_search', 'permission' => 'pm_read'), |
||||
238 | 'search2' => array($this, 'action_search2', 'permission' => 'pm_read'), |
||||
239 | 4 | 'send' => array($this, 'action_send', 'permission' => 'pm_read'), |
|||
240 | 'send2' => array($this, 'action_send2', 'permission' => 'pm_read'), |
||||
241 | 'settings' => array($this, 'action_settings', 'permission' => 'pm_read'), |
||||
242 | 4 | 'inbox' => array($this, 'action_folder', 'permission' => 'pm_read'), |
|||
243 | ); |
||||
244 | |||||
245 | 4 | // Set up our action array |
|||
246 | $action = new Action('pm_index'); |
||||
247 | 2 | ||||
248 | // Known action, go to it, otherwise the inbox for you |
||||
249 | 2 | $subAction = $action->initialize($subActions, 'inbox'); |
|||
250 | |||||
251 | // Set the right index bar for the action |
||||
252 | if ($subAction === 'inbox') |
||||
253 | { |
||||
254 | $this->_messageIndexBar($context['current_label_id'] === -1 ? $context['folder'] : 'label' . $context['current_label_id']); |
||||
255 | 4 | } |
|||
256 | 4 | elseif ($this->getApi() === false) |
|||
257 | { |
||||
258 | $this->_messageIndexBar($subAction); |
||||
259 | } |
||||
260 | |||||
261 | // And off we go! |
||||
262 | $action->dispatch($subAction); |
||||
263 | } |
||||
264 | |||||
265 | 2 | /** |
|||
266 | * A menu to easily access different areas of the PM section |
||||
267 | 2 | * |
|||
268 | * @param string $area |
||||
269 | 2 | */ |
|||
270 | private function _messageIndexBar($area) |
||||
271 | { |
||||
272 | 1 | global $txt, $context; |
|||
273 | 2 | ||||
274 | 2 | require_once(SUBSDIR . '/Menu.subs.php'); |
|||
275 | |||||
276 | $pm_areas = array( |
||||
277 | 2 | 'folders' => array( |
|||
278 | 2 | 'title' => $txt['pm_messages'], |
|||
279 | 2 | 'counter' => 'unread_messages', |
|||
280 | 'areas' => array( |
||||
281 | 'inbox' => array( |
||||
282 | 2 | 'label' => $txt['inbox'], |
|||
283 | 2 | 'custom_url' => getUrl('action', ['action' => 'pm']), |
|||
284 | 2 | 'counter' => 'unread_messages', |
|||
285 | ), |
||||
286 | 'send' => array( |
||||
287 | 2 | 'label' => $txt['new_message'], |
|||
288 | 2 | 'custom_url' => getUrl('action', ['action' => 'pm', 'sa' => 'send']), |
|||
289 | 'permission' => 'pm_send', |
||||
290 | ), |
||||
291 | 'sent' => array( |
||||
292 | 'label' => $txt['sent_items'], |
||||
293 | 2 | 'custom_url' => getUrl('action', ['action' => 'pm', 'f' => 'sent']), |
|||
294 | 2 | ), |
|||
295 | ), |
||||
296 | ), |
||||
297 | 'labels' => array( |
||||
298 | 2 | 'title' => $txt['pm_labels'], |
|||
299 | 'counter' => 'labels_unread_total', |
||||
300 | 'areas' => array(), |
||||
301 | 2 | ), |
|||
302 | 2 | 'actions' => array( |
|||
303 | 'title' => $txt['pm_actions'], |
||||
304 | 'areas' => array( |
||||
305 | 2 | 'search' => array( |
|||
306 | 2 | 'label' => $txt['pm_search_bar_title'], |
|||
307 | 'custom_url' => getUrl('action', ['action' => 'pm', 'sa' => 'search']), |
||||
308 | ), |
||||
309 | 'prune' => array( |
||||
310 | 'label' => $txt['pm_prune'], |
||||
311 | 2 | 'custom_url' => getUrl('action', ['action' => 'pm', 'sa' => 'prune']), |
|||
312 | ), |
||||
313 | ), |
||||
314 | 2 | ), |
|||
315 | 2 | 'pref' => array( |
|||
316 | 'title' => $txt['pm_preferences'], |
||||
317 | 'areas' => array( |
||||
318 | 2 | 'manlabels' => array( |
|||
319 | 2 | 'label' => $txt['pm_manage_labels'], |
|||
320 | 'custom_url' => getUrl('action', ['action' => 'pm', 'sa' => 'manlabels']), |
||||
321 | ), |
||||
322 | 2 | 'manrules' => array( |
|||
323 | 2 | 'label' => $txt['pm_manage_rules'], |
|||
324 | 'custom_url' => getUrl('action', ['action' => 'pm', 'sa' => 'manrules']), |
||||
325 | ), |
||||
326 | 'settings' => array( |
||||
327 | 'label' => $txt['pm_settings'], |
||||
328 | 'custom_url' => getUrl('action', ['action' => 'pm', 'sa' => 'settings']), |
||||
329 | ), |
||||
330 | 2 | ), |
|||
331 | 2 | ), |
|||
332 | ); |
||||
333 | 2 | ||||
334 | // Handle labels. |
||||
335 | $label_counters = array('unread_messages' => $context['labels'][-1]['unread_messages']); |
||||
336 | if (empty($context['currently_using_labels'])) |
||||
337 | { |
||||
338 | unset($pm_areas['labels']); |
||||
339 | } |
||||
340 | else |
||||
341 | { |
||||
342 | // Note we send labels by id as it will have fewer problems in the query string. |
||||
343 | $label_counters['labels_unread_total'] = 0; |
||||
344 | foreach ($context['labels'] as $label) |
||||
345 | { |
||||
346 | if ($label['id'] === -1) |
||||
347 | { |
||||
348 | continue; |
||||
349 | } |
||||
350 | |||||
351 | // Count the amount of unread items in labels. |
||||
352 | $label_counters['labels_unread_total'] += $label['unread_messages']; |
||||
353 | |||||
354 | // Add the label to the menu. |
||||
355 | $pm_areas['labels']['areas']['label' . $label['id']] = array( |
||||
356 | 'label' => $label['name'], |
||||
357 | 'custom_url' => getUrl('action', ['action' => 'pm', 'l' => $label['id']]), |
||||
358 | 'counter' => 'label' . $label['id'], |
||||
359 | 'messages' => $label['messages'], |
||||
360 | ); |
||||
361 | |||||
362 | 2 | $label_counters['label' . $label['id']] = $label['unread_messages']; |
|||
363 | } |
||||
364 | } |
||||
365 | |||||
366 | // Do we have a limit on the amount of messages we can keep? |
||||
367 | if (!empty($context['message_limit'])) |
||||
368 | { |
||||
369 | $bar = round(($this->user->messages * 100) / $context['message_limit'], 1); |
||||
0 ignored issues
–
show
The property
messages does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
370 | |||||
371 | $context['limit_bar'] = array( |
||||
372 | 'messages' => $this->user->messages, |
||||
373 | 'allowed' => $context['message_limit'], |
||||
374 | 'percent' => $bar, |
||||
375 | 'bar' => $bar > 100 ? 100 : (int) $bar, |
||||
376 | 'text' => sprintf($txt['pm_currently_using'], $this->user->messages, $bar) |
||||
377 | 2 | ); |
|||
378 | 2 | } |
|||
379 | |||||
380 | 2 | // Set a few options for the menu. |
|||
381 | $menuOptions = array( |
||||
382 | 'current_area' => $area, |
||||
383 | 'hook' => 'pm', |
||||
384 | 2 | 'disable_url_session_check' => true, |
|||
385 | 2 | 'counters' => empty($label_counters) ? 0 : $label_counters, |
|||
386 | ); |
||||
387 | |||||
388 | 2 | // Actually create the menu! |
|||
389 | 2 | $pm_include_data = createMenu($pm_areas, $menuOptions); |
|||
390 | unset($pm_areas); |
||||
391 | |||||
392 | 2 | // Make a note of the Unique ID for this menu. |
|||
393 | $context['pm_menu_id'] = $context['max_menu_id']; |
||||
394 | $context['pm_menu_name'] = 'menu_data_' . $context['pm_menu_id']; |
||||
395 | 2 | ||||
396 | // Set the selected item. |
||||
397 | $context['menu_item_selected'] = $pm_include_data['current_area']; |
||||
398 | |||||
399 | // Set the template for this area and add the profile layer. |
||||
400 | 2 | if ($this->getApi() === false) |
|||
401 | { |
||||
402 | $template_layers = theme()->getLayers(); |
||||
403 | $template_layers->add('pm'); |
||||
404 | } |
||||
405 | } |
||||
406 | |||||
407 | /** |
||||
408 | * Display a folder, i.e. inbox/sent etc. |
||||
409 | * |
||||
410 | 2 | * Display mode: 0 = all at once, 1 = one at a time, 2 = as a conversation |
|||
411 | * |
||||
412 | 2 | * @throws Exception |
|||
413 | 2 | * @uses subject_list, pm template layers |
|||
414 | * @uses folder sub template |
||||
415 | */ |
||||
416 | 2 | public function action_folder() |
|||
417 | { |
||||
418 | global $txt, $scripturl, $modSettings, $context, $subjects_request, $messages_request, $options; |
||||
419 | |||||
420 | // Changing view? |
||||
421 | if ($this->_req->isSet('view')) |
||||
422 | { |
||||
423 | $context['display_mode'] = (int) $context['display_mode'] > 1 ? 0 : (int) $context['display_mode'] + 1; |
||||
424 | 2 | require_once(SUBSDIR . '/Members.subs.php'); |
|||
425 | updateMemberData($this->user->id, ['pm_prefs' => (User::$settings['pm_prefs'] & 252) | $context['display_mode']]); |
||||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
426 | 2 | } |
|||
427 | |||||
428 | // Make sure the starting location is valid. |
||||
429 | $start = $this->_req->getQuery('start', 'trim'); |
||||
430 | if (isset($start) && $start !== 'new') |
||||
431 | { |
||||
432 | $start = (int) $this->_req->query->start; |
||||
433 | } |
||||
434 | elseif (!isset($start) && !empty($options['view_newest_pm_first'])) |
||||
435 | { |
||||
436 | $start = 0; |
||||
437 | } |
||||
438 | 2 | else |
|||
439 | 2 | { |
|||
440 | 2 | $start = 'new'; |
|||
441 | } |
||||
442 | |||||
443 | 2 | // Set up some basic template stuff. |
|||
444 | 2 | $context['from_or_to'] = $context['folder'] !== 'sent' ? 'from' : 'to'; |
|||
445 | $context['signature_enabled'] = strpos($modSettings['signature_settings'], '1') === 0; |
||||
446 | 2 | $context['disabled_fields'] = isset($modSettings['disabled_profile_fields']) ? array_flip(explode(',', $modSettings['disabled_profile_fields'])) : array(); |
|||
447 | 2 | ||||
448 | // Set the template layers we need |
||||
449 | $template_layers = theme()->getLayers(); |
||||
450 | 2 | $template_layers->addAfter('subject_list', 'pm'); |
|||
451 | 2 | ||||
452 | $labelQuery = $context['folder'] !== 'sent' ? ' |
||||
453 | AND FIND_IN_SET(' . $context['current_label_id'] . ', pmr.labels) != 0' : ''; |
||||
454 | 1 | ||||
455 | // They didn't pick a sort, so we use the forum default. |
||||
456 | 2 | $sort_by = $this->_req->getQuery('sort', 'trim', 'date'); |
|||
457 | 2 | $descending = isset($this->_req->query->desc); |
|||
458 | 2 | ||||
459 | // Set our sort by query |
||||
460 | 2 | switch ($sort_by) |
|||
461 | { |
||||
462 | 2 | case 'date': |
|||
463 | $sort_by_query = 'pm.id_pm'; |
||||
464 | if (!empty($options['view_newest_pm_first']) && !isset($this->_req->query->desc) && !isset($this->_req->query->asc)) |
||||
465 | { |
||||
466 | $descending = true; |
||||
467 | } |
||||
468 | |||||
469 | break; |
||||
470 | case 'name': |
||||
471 | $sort_by_query = "COALESCE(mem.real_name, '')"; |
||||
472 | break; |
||||
473 | case 'subject': |
||||
474 | 2 | $sort_by_query = 'pm.subject'; |
|||
475 | 2 | break; |
|||
476 | default: |
||||
477 | $sort_by_query = 'pm.id_pm'; |
||||
478 | 2 | } |
|||
479 | |||||
480 | 2 | // Set the text to resemble the current folder. |
|||
481 | 2 | $pmbox = $context['folder'] !== 'sent' ? $txt['inbox'] : $txt['sent_items']; |
|||
482 | 2 | $txt['delete_all'] = str_replace('PMBOX', $pmbox, $txt['delete_all']); |
|||
483 | |||||
484 | // Now, build the link tree! |
||||
485 | if ($context['current_label_id'] === -1) |
||||
486 | { |
||||
487 | 2 | $context['breadcrumbs'][] = [ |
|||
488 | 'url' => getUrl('action', ['action' => 'pm', 'f' => $context['folder']]), |
||||
489 | 'name' => $pmbox |
||||
490 | ]; |
||||
491 | } |
||||
492 | |||||
493 | // Build it further if we also have a label. |
||||
494 | if ($context['current_label_id'] !== -1) |
||||
495 | { |
||||
496 | 2 | $context['breadcrumbs'][] = array( |
|||
497 | 'url' => getUrl('action', ['action' => 'pm', 'f' => $context['folder'], 'l' => $context['current_label_id']]), |
||||
498 | 'name' => $txt['pm_current_label'] . ': ' . $context['current_label'] |
||||
499 | 2 | ); |
|||
500 | } |
||||
501 | |||||
502 | 2 | // Figure out how many messages there are. |
|||
503 | $max_messages = getPMCount(false, null, $labelQuery); |
||||
504 | 2 | ||||
505 | // Only show the button if there are messages to delete. |
||||
506 | $context['show_delete'] = $max_messages > 0; |
||||
507 | |||||
508 | // Start on the last page. |
||||
509 | if (!is_numeric($start) || $start >= $max_messages) |
||||
510 | { |
||||
511 | $start = ($max_messages - 1) - (($max_messages - 1) % $modSettings['defaultMaxMessages']); |
||||
512 | 2 | } |
|||
513 | elseif ($start < 0) |
||||
514 | { |
||||
515 | $start = 0; |
||||
516 | } |
||||
517 | |||||
518 | // ... but wait - what if we want to start from a specific message? |
||||
519 | if ($this->_req->isSet('pmid')) |
||||
520 | { |
||||
521 | $pmID = $this->_req->getQuery('pmid', 'intval', 0); |
||||
522 | |||||
523 | // Make sure you have access to this PM. |
||||
524 | if (!isAccessiblePM($pmID, $context['folder'] === 'sent' ? 'outbox' : 'inbox')) |
||||
525 | { |
||||
526 | throw new Exception('no_access', false); |
||||
527 | } |
||||
528 | |||||
529 | $context['current_pm'] = $pmID; |
||||
530 | |||||
531 | // With only one page of PM's we're gonna want page 1. |
||||
532 | if ($max_messages <= $modSettings['defaultMaxMessages']) |
||||
533 | { |
||||
534 | $start = 0; |
||||
535 | } |
||||
536 | // If we pass kstart we assume we're in the right place. |
||||
537 | elseif (!$this->_req->isSet('kstart')) |
||||
538 | { |
||||
539 | $start = getPMCount($descending, $pmID, $labelQuery); |
||||
540 | |||||
541 | 2 | // To stop the page index's being abnormal, start the page on the page the message |
|||
542 | // would normally be located on... |
||||
543 | $start = $modSettings['defaultMaxMessages'] * (int) ($start / $modSettings['defaultMaxMessages']); |
||||
544 | } |
||||
545 | } |
||||
546 | |||||
547 | // Sanitize and validate pmsg variable if set. |
||||
548 | if ($this->_req->isSet('pmsg')) |
||||
549 | { |
||||
550 | $pmsg = $this->_req->getQuery('pmsg', 'intval', 0); |
||||
551 | |||||
552 | if (!isAccessiblePM($pmsg, $context['folder'] === 'sent' ? 'outbox' : 'inbox')) |
||||
553 | 2 | { |
|||
554 | 2 | throw new Exception('no_access', false); |
|||
555 | } |
||||
556 | 2 | } |
|||
557 | 2 | ||||
558 | 2 | // Determine the navigation context |
|||
559 | $context['links'] += [ |
||||
560 | 'prev' => $start >= $modSettings['defaultMaxMessages'] ? $scripturl . '?action=pm;start=' . ($start - $modSettings['defaultMaxMessages']) : '', |
||||
561 | 'next' => $start + $modSettings['defaultMaxMessages'] < $max_messages ? $scripturl . '?action=pm;start=' . ($start + $modSettings['defaultMaxMessages']) : '', |
||||
562 | 2 | ]; |
|||
563 | 2 | ||||
564 | 2 | // We now know what they want, so lets fetch those PM's |
|||
565 | 2 | [$pms, $posters, $recipients, $lastData] = loadPMs([ |
|||
566 | 2 | 'sort_by_query' => $sort_by_query, |
|||
567 | 2 | 'display_mode' => $context['display_mode'], |
|||
568 | 2 | 'sort_by' => $sort_by, |
|||
569 | 2 | 'label_query' => $labelQuery, |
|||
570 | 2 | 'pmsg' => isset($pmsg) ? (int) $pmsg : 0, |
|||
571 | 2 | 'descending' => $descending, |
|||
572 | 2 | 'start' => $start, |
|||
573 | 2 | 'limit' => $modSettings['defaultMaxMessages'], |
|||
574 | 'folder' => $context['folder'], |
||||
575 | 'pmid' => $pmID ?? 0, |
||||
576 | 2 | ], $this->user->id); |
|||
577 | |||||
578 | // Make sure that we have been given a correct head pm id if we are in conversation mode |
||||
579 | if ($context['display_mode'] === self::DISPLAY_AS_CONVERSATION && !empty($pmID) && $pmID != $lastData['id']) |
||||
580 | { |
||||
581 | throw new Exception('no_access', false); |
||||
582 | 2 | } |
|||
583 | |||||
584 | // If loadPMs returned results, lets show the pm subject list |
||||
585 | if (!empty($pms)) |
||||
586 | { |
||||
587 | // Tell the template if no pm has specifically been selected |
||||
588 | if (empty($pmID)) |
||||
589 | { |
||||
590 | $context['current_pm'] = 0; |
||||
591 | } |
||||
592 | |||||
593 | $display_pms = $context['display_mode'] === self::DISPLAY_ALL_AT_ONCE ? $pms : array($lastData['id']); |
||||
594 | |||||
595 | // At this point we know the main id_pm's. But if we are looking at conversations we need |
||||
596 | // the PMs that make up the conversation |
||||
597 | if ($context['display_mode'] === self::DISPLAY_AS_CONVERSATION) |
||||
598 | { |
||||
599 | [$display_pms, $posters] = loadConversationList($lastData['head'], $recipients, $context['folder']); |
||||
600 | |||||
601 | // Conversation list may expose additional PM's being displayed |
||||
602 | $all_pms = array_unique(array_merge($pms, $display_pms)); |
||||
603 | |||||
604 | // See if any of these 'listing' PMs are in a conversation thread that has unread entries |
||||
605 | $context['conversation_unread'] = loadConversationUnreadStatus($all_pms); |
||||
606 | } |
||||
607 | // This is pretty much EVERY pm! |
||||
608 | else |
||||
609 | { |
||||
610 | $all_pms = array_unique(array_merge($pms, $display_pms)); |
||||
611 | } |
||||
612 | |||||
613 | // Get recipients (don't include bcc-recipients for your inbox, you're not supposed to know :P). |
||||
614 | [$context['message_labels'], $context['message_replied'], $context['message_unread']] = loadPMRecipientInfo($all_pms, $recipients, $context['folder']); |
||||
615 | |||||
616 | // Make sure we don't load any unnecessary data for one at a time mode |
||||
617 | if ($context['display_mode'] === self::DISPLAY_ONE_AT_TIME) |
||||
618 | { |
||||
619 | foreach ($posters as $pm_key => $sender) |
||||
620 | { |
||||
621 | if (!in_array($pm_key, $display_pms)) |
||||
622 | { |
||||
623 | unset($posters[$pm_key]); |
||||
624 | } |
||||
625 | } |
||||
626 | } |
||||
627 | |||||
628 | // Load some information about the message sender |
||||
629 | $posters = array_unique($posters); |
||||
630 | if (!empty($posters)) |
||||
631 | { |
||||
632 | MembersList::load($posters); |
||||
633 | } |
||||
634 | |||||
635 | // If we're on grouped/restricted view get a restricted list of messages. |
||||
636 | if ($context['display_mode'] !== self::DISPLAY_ALL_AT_ONCE) |
||||
637 | { |
||||
638 | // Get the order right. |
||||
639 | $orderBy = []; |
||||
640 | foreach (array_reverse($pms) as $pm) |
||||
641 | { |
||||
642 | $orderBy[] = 'pm.id_pm = ' . $pm; |
||||
643 | } |
||||
644 | |||||
645 | // Separate query for these bits, the callback will use it as required |
||||
646 | $subjects_request = loadPMSubjectRequest($pms, $orderBy); |
||||
647 | } |
||||
648 | |||||
649 | // Execute the load message query if a message has been chosen and let |
||||
650 | // the callback fetch the results. Otherwise, just show the pm selection list |
||||
651 | if (empty($pmsg) && empty($pmID) && $context['display_mode'] !== self::DISPLAY_ALL_AT_ONCE) |
||||
652 | { |
||||
653 | $messages_request = false; |
||||
654 | } |
||||
655 | else |
||||
656 | { |
||||
657 | $messages_request = loadPMMessageRequest($display_pms, $sort_by_query, $sort_by, $descending, $context['display_mode'], $context['folder']); |
||||
658 | } |
||||
659 | 2 | } |
|||
660 | else |
||||
661 | { |
||||
662 | $messages_request = false; |
||||
663 | 2 | } |
|||
664 | 2 | ||||
665 | 2 | // Initialize the subject and message render callbacks |
|||
666 | 2 | $bodyParser = new Normal(array(), false); |
|||
667 | $opt = new ValuesContainer(['recipients' => $recipients]); |
||||
668 | $renderer = new PmRenderer($messages_request, $this->user, $bodyParser, $opt); |
||||
669 | 2 | $subject_renderer = new PmRenderer($subjects_request ?? $messages_request, $this->user, $bodyParser, $opt); |
|||
670 | 2 | ||||
671 | // Subject and Message |
||||
672 | $context['get_pmessage'] = [$renderer, 'getContext']; |
||||
673 | 2 | $context['get_psubject'] = [$subject_renderer, 'getContext']; |
|||
674 | 2 | ||||
675 | 2 | // Prepare some items for the template |
|||
676 | 2 | $context['topic_starter_id'] = 0; |
|||
677 | 2 | $context['can_send_pm'] = allowedTo('pm_send'); |
|||
678 | 2 | $context['can_send_email'] = allowedTo('send_email_to_members'); |
|||
679 | 2 | $context['sub_template'] = 'folder'; |
|||
680 | $context['page_title'] = $txt['pm_inbox']; |
||||
681 | 2 | $context['sort_direction'] = $descending ? 'down' : 'up'; |
|||
682 | $context['sort_by'] = $sort_by; |
||||
683 | |||||
684 | if ($messages_request !== false && !empty($context['show_delete']) && $messages_request->hasResults()) |
||||
685 | { |
||||
686 | theme()->getLayers()->addEnd('pm_pages_and_buttons'); |
||||
687 | } |
||||
688 | |||||
689 | // Set up the page index. |
||||
690 | $context['page_index'] = constructPageIndex('{scripturl}?action=pm;f=' . $context['folder'] . (isset($this->_req->query->l) ? ';l=' . (int) $this->_req->query->l : '') . ';sort=' . $context['sort_by'] . ($descending ? ';desc' : ''), $start, $max_messages, $modSettings['defaultMaxMessages']); |
||||
691 | $context['start'] = $start; |
||||
692 | |||||
693 | $context['pm_form_url'] = $scripturl . '?action=pm;sa=pmactions;' . ($context['display_mode'] === self::DISPLAY_AS_CONVERSATION ? 'conversation;' : '') . 'f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] !== -1 ? ';l=' . $context['current_label_id'] : ''); |
||||
694 | |||||
695 | // Finally, mark the relevant messages as read. |
||||
696 | if ($context['folder'] !== 'sent' && !empty($context['labels'][(int) $context['current_label_id']]['unread_messages'])) |
||||
697 | { |
||||
698 | // If the display mode is "old sk00l" do them all... |
||||
699 | if ($context['display_mode'] === self::DISPLAY_ALL_AT_ONCE) |
||||
700 | 2 | { |
|||
701 | 2 | markMessages(null, $context['current_label_id']); |
|||
702 | } |
||||
703 | 2 | // Otherwise do just the currently displayed ones! |
|||
704 | elseif (!empty($context['current_pm'])) |
||||
705 | { |
||||
706 | 2 | markMessages($display_pms, $context['current_label_id']); |
|||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
707 | } |
||||
708 | } |
||||
709 | |||||
710 | // Build the conversation button array. |
||||
711 | if ($context['display_mode'] === self::DISPLAY_AS_CONVERSATION && !empty($context['current_pm'])) |
||||
712 | { |
||||
713 | $context['conversation_buttons'] = array( |
||||
714 | 'delete' => array( |
||||
715 | 'text' => 'delete_conversation', |
||||
716 | 'lang' => true, |
||||
717 | 'url' => $scripturl . '?action=pm;sa=pmactions;pm_actions%5B' . $context['current_pm'] . '%5D=delete;conversation;f=' . $context['folder'] . ';start=' . $context['start'] . ($context['current_label_id'] !== -1 ? ';l=' . $context['current_label_id'] : '') . ';' . $context['session_var'] . '=' . $context['session_id'], |
||||
718 | 'custom' => 'onclick="return confirm(\'' . addslashes($txt['remove_message']) . '?\');"' |
||||
719 | ), |
||||
720 | ); |
||||
721 | 2 | ||||
722 | // Allow mods to add additional buttons here |
||||
723 | call_integration_hook('integrate_conversation_buttons'); |
||||
724 | } |
||||
725 | } |
||||
726 | |||||
727 | /** |
||||
728 | * Send a new personal message? |
||||
729 | * |
||||
730 | * @throws Exception pm_not_yours |
||||
731 | */ |
||||
732 | public function action_send() |
||||
733 | { |
||||
734 | global $txt, $modSettings, $context; |
||||
735 | |||||
736 | 2 | // Load in some text and template dependencies |
|||
737 | Txt::load('PersonalMessage'); |
||||
738 | theme()->getTemplates()->load('PersonalMessage'); |
||||
739 | |||||
740 | // Set the template we will use |
||||
741 | 2 | $context['sub_template'] = 'send'; |
|||
742 | |||||
743 | 2 | // Extract out the spam settings - cause it's neat. |
|||
744 | [$modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']] = explode(',', $modSettings['pm_spam_settings']); |
||||
745 | |||||
746 | 2 | // Set up some items for the template |
|||
747 | 2 | $context['page_title'] = $txt['send_message']; |
|||
748 | $context['reply'] = isset($this->_req->query->pmsg) || isset($this->_req->query->quote); |
||||
749 | |||||
750 | 2 | // Check whether we've gone over the limit of messages we can send per hour. |
|||
751 | if (!empty($modSettings['pm_posts_per_hour']) |
||||
752 | && !allowedTo(array('admin_forum', 'moderate_forum', 'send_mail')) |
||||
753 | 2 | && $this->user->mod_cache['bq'] === '0=1' |
|||
0 ignored issues
–
show
The property
mod_cache does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
754 | && $this->user->mod_cache['gq'] === '0=1') |
||||
755 | { |
||||
756 | 2 | // How many messages have they sent this last hour? |
|||
757 | 2 | $pmCount = pmCount($this->user->id, 3600); |
|||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
758 | |||||
759 | if (!empty($pmCount) && $pmCount >= $modSettings['pm_posts_per_hour']) |
||||
760 | 2 | { |
|||
761 | throw new Exception('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour'])); |
||||
762 | } |
||||
763 | } |
||||
764 | |||||
765 | try |
||||
766 | { |
||||
767 | $this->_events->trigger('before_set_context', array('pmsg' => $this->_req->query->pmsg ?? ($this->_req->query->quote ?? 0))); |
||||
768 | } |
||||
769 | catch (PmErrorException $pmErrorException) |
||||
770 | { |
||||
771 | $this->messagePostError($pmErrorException->namedRecipientList, $pmErrorException->recipientList, $pmErrorException->msgOptions); |
||||
0 ignored issues
–
show
$pmErrorException->msgOptions of type ElkArte\Helper\ValuesContainer is incompatible with the type array expected by parameter $msg_options of ElkArte\Controller\Perso...age::messagePostError() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
772 | return; |
||||
773 | 2 | } |
|||
774 | |||||
775 | // Quoting / Replying to a message? |
||||
776 | if (!empty($this->_req->query->pmsg)) |
||||
777 | { |
||||
778 | $pmsg = $this->_req->getQuery('pmsg', 'intval'); |
||||
779 | |||||
780 | // Make sure this is accessible (not deleted) |
||||
781 | 2 | if (!isAccessiblePM($pmsg)) |
|||
782 | { |
||||
783 | throw new Exception('no_access', false); |
||||
784 | } |
||||
785 | |||||
786 | // Validate that this is one has been received? |
||||
787 | $isReceived = checkPMReceived($pmsg); |
||||
788 | |||||
789 | // Get the quoted message (and make sure you're allowed to see this quote!). |
||||
790 | $row_quoted = loadPMQuote($pmsg, $isReceived); |
||||
791 | if ($row_quoted === false) |
||||
0 ignored issues
–
show
|
|||||
792 | { |
||||
793 | throw new Exception('pm_not_yours', false); |
||||
794 | } |
||||
795 | |||||
796 | // Censor the message. |
||||
797 | $row_quoted['subject'] = censor($row_quoted['subject']); |
||||
798 | $row_quoted['body'] = censor($row_quoted['body']); |
||||
799 | |||||
800 | // Let's make sure we mark this one as read |
||||
801 | markMessages($pmsg); |
||||
802 | |||||
803 | // Figure out which flavor or 'Re: ' to use |
||||
804 | $context['response_prefix'] = response_prefix(); |
||||
805 | |||||
806 | $form_subject = $row_quoted['subject']; |
||||
807 | |||||
808 | // Add 'Re: ' to it.... |
||||
809 | if ($context['reply'] && trim($context['response_prefix']) !== '' && Util::strpos($form_subject, trim($context['response_prefix'])) !== 0) |
||||
810 | { |
||||
811 | $form_subject = $context['response_prefix'] . $form_subject; |
||||
812 | } |
||||
813 | |||||
814 | // If quoting, lets clean up some things and set the quote header for the pm body |
||||
815 | if (isset($this->_req->query->quote)) |
||||
816 | { |
||||
817 | // Remove any nested quotes and <br />... |
||||
818 | $form_message = preg_replace('~<br ?/?>~i', "\n", $row_quoted['body']); |
||||
819 | $form_message = removeNestedQuotes($form_message); |
||||
820 | |||||
821 | if (empty($row_quoted['id_member'])) |
||||
822 | { |
||||
823 | $form_message = '[quote author="' . $row_quoted['real_name'] . '"]' . "\n" . $form_message . "\n" . '[/quote]'; |
||||
824 | } |
||||
825 | else |
||||
826 | { |
||||
827 | $form_message = '[quote author=' . $row_quoted['real_name'] . ' link=action=profile;u=' . $row_quoted['id_member'] . ' date=' . $row_quoted['msgtime'] . ']' . "\n" . $form_message . "\n" . '[/quote]'; |
||||
828 | } |
||||
829 | } |
||||
830 | else |
||||
831 | { |
||||
832 | $form_message = ''; |
||||
833 | } |
||||
834 | |||||
835 | // Allow them to QQ the message they are replying to |
||||
836 | loadJavascriptFile('quickQuote.js', ['defer' => true]); |
||||
837 | theme()->addInlineJavascript(" |
||||
838 | document.addEventListener('DOMContentLoaded', () => new Elk_QuickQuote(), false);", true |
||||
839 | ); |
||||
840 | |||||
841 | // Do the BBC thang on the message. |
||||
842 | $bbc_parser = ParserWrapper::instance(); |
||||
843 | $row_quoted['body'] = $bbc_parser->parsePM($row_quoted['body']); |
||||
844 | |||||
845 | // Set up the quoted message array. |
||||
846 | $context['quoted_message'] = array( |
||||
847 | 'id' => $row_quoted['id_pm'], |
||||
848 | 'pm_head' => $row_quoted['pm_head'], |
||||
849 | 'member' => array( |
||||
850 | 'name' => $row_quoted['real_name'], |
||||
851 | 'username' => $row_quoted['member_name'], |
||||
852 | 'id' => $row_quoted['id_member'], |
||||
853 | 'href' => empty($row_quoted['id_member']) ? '' : getUrl('profile', ['action' => 'profile', 'u' => $row_quoted['id_member']]), |
||||
854 | 'link' => empty($row_quoted['id_member']) ? $row_quoted['real_name'] : '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row_quoted['id_member']]) . '">' . $row_quoted['real_name'] . '</a>', |
||||
855 | ), |
||||
856 | 'subject' => $row_quoted['subject'], |
||||
857 | 'time' => standardTime($row_quoted['msgtime']), |
||||
858 | 'html_time' => htmlTime($row_quoted['msgtime']), |
||||
859 | 'timestamp' => forum_time(true, $row_quoted['msgtime']), |
||||
860 | 'body' => $row_quoted['body'] |
||||
861 | ); |
||||
862 | } |
||||
863 | // A new message it is then |
||||
864 | else |
||||
865 | 2 | { |
|||
866 | 2 | $context['quoted_message'] = false; |
|||
867 | 2 | $form_subject = ''; |
|||
868 | $form_message = ''; |
||||
869 | } |
||||
870 | |||||
871 | 2 | // Start of like we don't know where this is going |
|||
872 | $context['recipients'] = array( |
||||
873 | 'to' => array(), |
||||
874 | 'bcc' => array(), |
||||
875 | ); |
||||
876 | |||||
877 | 2 | // Sending by ID? Replying to all? Fetch the real_name(s). |
|||
878 | if (isset($this->_req->query->u)) |
||||
879 | { |
||||
880 | // If the user is replying to all, get all the other members this was sent to.. |
||||
881 | if ($this->_req->query->u === 'all' && isset($row_quoted)) |
||||
882 | { |
||||
883 | // Firstly, to reply to all we clearly already have $row_quoted - so have the original member from. |
||||
884 | if ($row_quoted['id_member'] != $this->user->id) |
||||
885 | { |
||||
886 | $context['recipients']['to'][] = array( |
||||
887 | 'id' => $row_quoted['id_member'], |
||||
888 | 'name' => htmlspecialchars($row_quoted['real_name'], ENT_COMPAT), |
||||
889 | ); |
||||
890 | } |
||||
891 | |||||
892 | // Now to get all the others. |
||||
893 | $context['recipients']['to'] = array_merge($context['recipients']['to'], isset($pmsg) ? loadPMRecipientsAll($pmsg) : array()); |
||||
894 | } |
||||
895 | else |
||||
896 | { |
||||
897 | $users = array_map('intval', explode(',', $this->_req->query->u)); |
||||
898 | $users = array_unique($users); |
||||
899 | |||||
900 | // For all the member's this is going to, get their display name. |
||||
901 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
902 | $result = getBasicMemberData($users); |
||||
903 | |||||
904 | foreach ($result as $row) |
||||
905 | { |
||||
906 | $context['recipients']['to'][] = array( |
||||
907 | 'id' => $row['id_member'], |
||||
908 | 'name' => $row['real_name'], |
||||
909 | ); |
||||
910 | } |
||||
911 | } |
||||
912 | |||||
913 | // Get a literal name list in case the user has JavaScript disabled. |
||||
914 | $names = array(); |
||||
915 | foreach ($context['recipients']['to'] as $to) |
||||
916 | { |
||||
917 | $names[] = $to['name']; |
||||
918 | } |
||||
919 | |||||
920 | $context['to_value'] = empty($names) ? '' : '"' . implode('", "', $names) . '"'; |
||||
921 | } |
||||
922 | 2 | else |
|||
923 | { |
||||
924 | $context['to_value'] = ''; |
||||
925 | } |
||||
926 | 2 | ||||
927 | 2 | // Set the defaults... |
|||
928 | $context['subject'] = $form_subject; |
||||
929 | $context['message'] = str_replace(array('"', '<', '>', ' '), array('"', '<', '>', ' '), $form_message); |
||||
930 | 2 | ||||
931 | 2 | // And build the link tree. |
|||
932 | 2 | $context['breadcrumbs'][] = [ |
|||
933 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'send']), |
||||
934 | 'name' => $txt['new_message'] |
||||
935 | ]; |
||||
936 | 2 | ||||
937 | // Needed for the editor. |
||||
938 | require_once(SUBSDIR . '/Editor.subs.php'); |
||||
939 | |||||
940 | 2 | // Now create the editor. |
|||
941 | 2 | $editorOptions = array( |
|||
942 | 2 | 'id' => 'message', |
|||
943 | 2 | 'value' => $context['message'], |
|||
944 | 'height' => '250px', |
||||
945 | 2 | 'width' => '100%', |
|||
946 | 'labels' => array( |
||||
947 | 2 | 'post_button' => $txt['send_message'], |
|||
948 | ), |
||||
949 | 'smiley_container' => 'smileyBox_message', |
||||
950 | 'bbc_container' => 'bbcBox_message', |
||||
951 | 2 | 'preview_type' => 2, |
|||
952 | ); |
||||
953 | 2 | ||||
954 | // Trigger the prepare_send_context PM event |
||||
955 | $this->_events->trigger('prepare_send_context', array('editorOptions' => &$editorOptions)); |
||||
956 | 2 | ||||
957 | create_control_richedit($editorOptions); |
||||
958 | |||||
959 | 2 | // No one is bcc'ed just yet |
|||
960 | 2 | $context['bcc_value'] = ''; |
|||
961 | |||||
962 | // Register this form and get a sequence number in $context. |
||||
963 | checkSubmitOnce('register'); |
||||
964 | } |
||||
965 | |||||
966 | /** |
||||
967 | * An error in the message... |
||||
968 | * |
||||
969 | * @param array $named_recipients |
||||
970 | * @param array $recipient_ids array keys of [bbc] => int[] and [to] => int[] |
||||
971 | * @param array $msg_options body, subject and reply values |
||||
972 | * |
||||
973 | * @throws Exception pm_not_yours |
||||
974 | */ |
||||
975 | public function messagePostError($named_recipients, $recipient_ids = array(), $msg_options = null) |
||||
976 | { |
||||
977 | global $txt, $context, $modSettings; |
||||
978 | |||||
979 | if ($this->getApi() !== false) |
||||
980 | { |
||||
981 | $context['sub_template'] = 'generic_preview'; |
||||
982 | } |
||||
983 | else |
||||
984 | { |
||||
985 | $context['sub_template'] = 'send'; |
||||
986 | $context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'send'; |
||||
987 | } |
||||
988 | |||||
989 | $context['page_title'] = $txt['send_message']; |
||||
990 | $error_types = ErrorContext::context('pm', 1); |
||||
991 | |||||
992 | // Got some known members? |
||||
993 | $context['recipients'] = array( |
||||
994 | 'to' => array(), |
||||
995 | 'bcc' => array(), |
||||
996 | ); |
||||
997 | |||||
998 | if (!empty($recipient_ids['to']) || !empty($recipient_ids['bcc'])) |
||||
999 | { |
||||
1000 | $allRecipients = array_merge($recipient_ids['to'], $recipient_ids['bcc']); |
||||
1001 | |||||
1002 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
1003 | |||||
1004 | // Get the latest activated member's display name. |
||||
1005 | $result = getBasicMemberData($allRecipients); |
||||
1006 | foreach ($result as $row) |
||||
1007 | { |
||||
1008 | $recipientType = in_array($row['id_member'], $recipient_ids['bcc']) ? 'bcc' : 'to'; |
||||
1009 | $context['recipients'][$recipientType][] = array( |
||||
1010 | 'id' => $row['id_member'], |
||||
1011 | 'name' => $row['real_name'], |
||||
1012 | ); |
||||
1013 | } |
||||
1014 | } |
||||
1015 | |||||
1016 | // Set everything up like before.... |
||||
1017 | if (!empty($msg_options)) |
||||
1018 | { |
||||
1019 | $context['subject'] = $msg_options->subject; |
||||
1020 | $context['message'] = $msg_options->body; |
||||
1021 | $context['reply'] = $msg_options->reply_to; |
||||
1022 | } |
||||
1023 | else |
||||
1024 | { |
||||
1025 | $context['subject'] = isset($this->_req->post->subject) ? Util::htmlspecialchars($this->_req->post->subject) : ''; |
||||
1026 | $context['message'] = isset($this->_req->post->message) ? str_replace(array(' '), array(' '), Util::htmlspecialchars($this->_req->post->message, ENT_QUOTES, 'UTF-8', true)) : ''; |
||||
1027 | $context['reply'] = !empty($this->_req->post->replied_to); |
||||
1028 | } |
||||
1029 | |||||
1030 | // If this is a reply to message, we need to reload the quote |
||||
1031 | if ($context['reply']) |
||||
1032 | { |
||||
1033 | $pmsg = (int) $this->_req->post->replied_to; |
||||
1034 | $isReceived = $context['folder'] !== 'sent'; |
||||
1035 | $row_quoted = loadPMQuote($pmsg, $isReceived); |
||||
1036 | if ($row_quoted === false) |
||||
0 ignored issues
–
show
|
|||||
1037 | { |
||||
1038 | if ($this->getApi() === false) |
||||
1039 | { |
||||
1040 | throw new Exception('pm_not_yours', false); |
||||
1041 | } |
||||
1042 | |||||
1043 | $error_types->addError('pm_not_yours'); |
||||
1044 | } |
||||
1045 | else |
||||
1046 | { |
||||
1047 | $row_quoted['subject'] = censor($row_quoted['subject']); |
||||
1048 | $row_quoted['body'] = censor($row_quoted['body']); |
||||
1049 | $bbc_parser = ParserWrapper::instance(); |
||||
1050 | |||||
1051 | $context['quoted_message'] = array( |
||||
1052 | 'id' => $row_quoted['id_pm'], |
||||
1053 | 'pm_head' => $row_quoted['pm_head'], |
||||
1054 | 'member' => array( |
||||
1055 | 'name' => $row_quoted['real_name'], |
||||
1056 | 'username' => $row_quoted['member_name'], |
||||
1057 | 'id' => $row_quoted['id_member'], |
||||
1058 | 'href' => empty($row_quoted['id_member']) ? '' : getUrl('profile', ['action' => 'profile', 'u' => $row_quoted['id_member']]), |
||||
1059 | 'link' => empty($row_quoted['id_member']) ? $row_quoted['real_name'] : '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row_quoted['id_member']]) . '">' . $row_quoted['real_name'] . '</a>', |
||||
1060 | ), |
||||
1061 | 'subject' => $row_quoted['subject'], |
||||
1062 | 'time' => standardTime($row_quoted['msgtime']), |
||||
1063 | 'html_time' => htmlTime($row_quoted['msgtime']), |
||||
1064 | 'timestamp' => forum_time(true, $row_quoted['msgtime']), |
||||
1065 | 'body' => $bbc_parser->parsePM($row_quoted['body']), |
||||
1066 | ); |
||||
1067 | } |
||||
1068 | } |
||||
1069 | |||||
1070 | // Build the link tree.... |
||||
1071 | $context['breadcrumbs'][] = [ |
||||
1072 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'send']), |
||||
1073 | 'name' => $txt['new_message'] |
||||
1074 | ]; |
||||
1075 | |||||
1076 | // Set each of the errors for the template. |
||||
1077 | $context['post_error'] = array( |
||||
1078 | 'errors' => $error_types->prepareErrors(), |
||||
1079 | 'type' => $error_types->getErrorType() == 0 ? 'minor' : 'serious', |
||||
1080 | 'title' => $txt['error_while_submitting'], |
||||
1081 | ); |
||||
1082 | |||||
1083 | // We need to load the editor once more. |
||||
1084 | require_once(SUBSDIR . '/Editor.subs.php'); |
||||
1085 | |||||
1086 | // Create it... |
||||
1087 | $editorOptions = array( |
||||
1088 | 'id' => 'message', |
||||
1089 | 'value' => $context['message'], |
||||
1090 | 'width' => '100%', |
||||
1091 | 'height' => '250px', |
||||
1092 | 'labels' => array( |
||||
1093 | 'post_button' => $txt['send_message'], |
||||
1094 | ), |
||||
1095 | 'smiley_container' => 'smileyBox_message', |
||||
1096 | 'bbc_container' => 'bbcBox_message', |
||||
1097 | 'preview_type' => 2, |
||||
1098 | ); |
||||
1099 | |||||
1100 | // Trigger the prepare_send_context PM event |
||||
1101 | $this->_events->trigger('prepare_send_context', array('editorOptions' => &$editorOptions)); |
||||
1102 | |||||
1103 | create_control_richedit($editorOptions); |
||||
1104 | |||||
1105 | // Check whether we need to show the code again. |
||||
1106 | $context['require_verification'] = $this->user->is_admin === false && !empty($modSettings['pm_posts_verification']) && $this->user->posts < $modSettings['pm_posts_verification']; |
||||
0 ignored issues
–
show
The property
is_admin does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() The property
posts does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
1107 | if ($context['require_verification'] && $this->getApi() === false) |
||||
1108 | { |
||||
1109 | $verificationOptions = array( |
||||
1110 | 'id' => 'pm', |
||||
1111 | ); |
||||
1112 | $context['require_verification'] = VerificationControlsIntegrate::create($verificationOptions); |
||||
1113 | $context['visual_verification_id'] = $verificationOptions['id']; |
||||
1114 | } |
||||
1115 | |||||
1116 | $context['to_value'] = empty($named_recipients['to']) ? '' : '"' . implode('", "', $named_recipients['to']) . '"'; |
||||
1117 | $context['bcc_value'] = empty($named_recipients['bcc']) ? '' : '"' . implode('", "', $named_recipients['bcc']) . '"'; |
||||
1118 | |||||
1119 | // No check for the previous submission is needed. |
||||
1120 | checkSubmitOnce('free'); |
||||
1121 | |||||
1122 | // Acquire a new form sequence number. |
||||
1123 | checkSubmitOnce('register'); |
||||
1124 | } |
||||
1125 | 2 | ||||
1126 | /** |
||||
1127 | 2 | * Send a personal message. |
|||
1128 | */ |
||||
1129 | public function action_send2() |
||||
1130 | 2 | { |
|||
1131 | 2 | global $txt, $context, $modSettings; |
|||
1132 | |||||
1133 | 2 | // All the helpers we need |
|||
1134 | require_once(SUBSDIR . '/Auth.subs.php'); |
||||
1135 | require_once(SUBSDIR . '/Post.subs.php'); |
||||
1136 | 2 | ||||
1137 | Txt::load('PersonalMessage', false); |
||||
1138 | |||||
1139 | 2 | // Extract out the spam settings - it saves database space! |
|||
1140 | [$modSettings['max_pm_recipients'], $modSettings['pm_posts_verification'], $modSettings['pm_posts_per_hour']] = explode(',', $modSettings['pm_spam_settings']); |
||||
1141 | |||||
1142 | 2 | // Initialize the errors we're about to make. |
|||
1143 | 2 | $post_errors = ErrorContext::context('pm', 1); |
|||
1144 | 2 | ||||
1145 | 2 | // Check whether we've gone over the limit of messages we can send per hour - fatal error if fails! |
|||
1146 | if (!empty($modSettings['pm_posts_per_hour']) |
||||
1147 | && !allowedTo(array('admin_forum', 'moderate_forum', 'send_mail')) |
||||
1148 | && $this->user->mod_cache['bq'] === '0=1' |
||||
0 ignored issues
–
show
The property
mod_cache does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
1149 | && $this->user->mod_cache['gq'] === '0=1') |
||||
1150 | { |
||||
1151 | // How many have they sent this last hour? |
||||
1152 | $pmCount = pmCount($this->user->id, 3600); |
||||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
1153 | |||||
1154 | if (!empty($pmCount) && $pmCount >= $modSettings['pm_posts_per_hour']) |
||||
1155 | { |
||||
1156 | if ($this->getApi() === false) |
||||
1157 | { |
||||
1158 | throw new Exception('pm_too_many_per_hour', true, array($modSettings['pm_posts_per_hour'])); |
||||
1159 | } |
||||
1160 | |||||
1161 | $post_errors->addError('pm_too_many_per_hour'); |
||||
1162 | } |
||||
1163 | } |
||||
1164 | |||||
1165 | 2 | // If your session timed out, show an error, but do allow to re-submit. |
|||
1166 | if ($this->getApi() === false && checkSession('post', '', false) !== '') |
||||
1167 | { |
||||
1168 | $post_errors->addError('session_timeout'); |
||||
1169 | } |
||||
1170 | 2 | ||||
1171 | 2 | $this->_req->post->subject = isset($this->_req->post->subject) ? strtr(Util::htmltrim($this->_req->post->subject), array("\r" => '', "\n" => '', "\t" => '')) : ''; |
|||
1172 | 2 | $this->_req->post->to = $this->_req->getPost('to', 'trim', empty($this->_req->query->to) ? '' : $this->_req->query->to); |
|||
1173 | $this->_req->post->bcc = $this->_req->getPost('bcc', 'trim', empty($this->_req->query->bcc) ? '' : $this->_req->query->bcc); |
||||
1174 | |||||
1175 | 2 | // Route the input from the 'u' parameter to the 'to'-list. |
|||
1176 | if (!empty($this->_req->post->u)) |
||||
1177 | 2 | { |
|||
1178 | $this->_req->post->recipient_to = explode(',', $this->_req->post->u); |
||||
1179 | } |
||||
1180 | 2 | ||||
1181 | $bbc_parser = ParserWrapper::instance(); |
||||
1182 | |||||
1183 | 2 | // Construct the list of recipients. |
|||
1184 | 2 | $recipientList = []; |
|||
1185 | 2 | $namedRecipientList = []; |
|||
1186 | 2 | $namesNotFound = []; |
|||
1187 | foreach (['to', 'bcc'] as $recipientType) |
||||
1188 | { |
||||
1189 | 2 | // First, let's see if there's user ID's given. |
|||
1190 | 2 | $recipientList[$recipientType] = []; |
|||
1191 | 2 | $type = 'recipient_' . $recipientType; |
|||
1192 | if (!empty($this->_req->post->{$type}) && is_array($this->_req->post->{$type})) |
||||
1193 | 2 | { |
|||
1194 | $recipientList[$recipientType] = array_map('intval', $this->_req->post->{$type}); |
||||
1195 | } |
||||
1196 | |||||
1197 | 2 | // Are there also literal names set? |
|||
1198 | if (!empty($this->_req->post->{$recipientType})) |
||||
1199 | { |
||||
1200 | 2 | // We're going to take out the "s anyway ;). |
|||
1201 | $recipientString = strtr($this->_req->post->{$recipientType}, array('\\"' => '"')); |
||||
1202 | 2 | ||||
1203 | 2 | preg_match_all('~"([^"]+)"~', $recipientString, $matches); |
|||
1204 | $namedRecipientList[$recipientType] = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $recipientString)))); |
||||
1205 | |||||
1206 | 2 | // Clean any literal names entered |
|||
1207 | foreach ($namedRecipientList[$recipientType] as $index => $recipient) |
||||
1208 | 2 | { |
|||
1209 | if (trim($recipient) !== '') |
||||
1210 | 2 | { |
|||
1211 | $namedRecipientList[$recipientType][$index] = Util::htmlspecialchars(Util::strtolower(trim($recipient))); |
||||
1212 | } |
||||
1213 | else |
||||
1214 | 1 | { |
|||
1215 | unset($namedRecipientList[$recipientType][$index]); |
||||
1216 | } |
||||
1217 | } |
||||
1218 | |||||
1219 | 2 | // Now see if we can resolve any entered name (not suggest selected) to an actual user |
|||
1220 | if (!empty($namedRecipientList[$recipientType])) |
||||
1221 | 2 | { |
|||
1222 | $foundMembers = findMembers($namedRecipientList[$recipientType]); |
||||
1223 | |||||
1224 | 2 | // Assume all are not found, until proven otherwise. |
|||
1225 | $namesNotFound[$recipientType] = $namedRecipientList[$recipientType]; |
||||
1226 | |||||
1227 | 2 | // Make sure we only have each member listed once, in case they did not use the select list |
|||
1228 | foreach ($foundMembers as $member) |
||||
1229 | { |
||||
1230 | 2 | $testNames = array( |
|||
1231 | 2 | Util::strtolower($member['username']), |
|||
1232 | 2 | Util::strtolower($member['name']), |
|||
1233 | Util::strtolower($member['email']), |
||||
1234 | ); |
||||
1235 | 2 | ||||
1236 | if (array_intersect($testNames, $namedRecipientList[$recipientType]) !== []) |
||||
1237 | 2 | { |
|||
1238 | $recipientList[$recipientType][] = $member['id']; |
||||
1239 | |||||
1240 | 2 | // Get rid of this username, since we found it. |
|||
1241 | $namesNotFound[$recipientType] = array_diff($namesNotFound[$recipientType], $testNames); |
||||
1242 | } |
||||
1243 | } |
||||
1244 | } |
||||
1245 | } |
||||
1246 | |||||
1247 | 2 | // Selected a recipient to be deleted? Remove them now. |
|||
1248 | if (!empty($this->_req->post->delete_recipient)) |
||||
1249 | { |
||||
1250 | $recipientList[$recipientType] = array_diff($recipientList[$recipientType], array((int) $this->_req->post->delete_recipient)); |
||||
1251 | } |
||||
1252 | |||||
1253 | 2 | // Make sure we don't include the same name twice |
|||
1254 | $recipientList[$recipientType] = array_unique($recipientList[$recipientType]); |
||||
1255 | } |
||||
1256 | |||||
1257 | 2 | // Are we changing the recipients somehow? |
|||
1258 | $is_recipient_change = !empty($this->_req->post->delete_recipient) || !empty($this->_req->post->to_submit) || !empty($this->_req->post->bcc_submit); |
||||
1259 | |||||
1260 | 2 | // Check if there's at least one recipient. |
|||
1261 | if (empty($recipientList['to']) && empty($recipientList['bcc'])) |
||||
1262 | { |
||||
1263 | $post_errors->addError('no_to'); |
||||
1264 | } |
||||
1265 | |||||
1266 | 2 | // Make sure that we remove the members who did get it from the screen. |
|||
1267 | if (!$is_recipient_change) |
||||
1268 | 2 | { |
|||
1269 | foreach (array_keys($recipientList) as $recipientType) |
||||
1270 | 2 | { |
|||
1271 | if (!empty($namesNotFound[$recipientType])) |
||||
1272 | { |
||||
1273 | $post_errors->addError('bad_' . $recipientType); |
||||
1274 | |||||
1275 | // Since we already have a post error, remove the previous one. |
||||
1276 | $post_errors->removeError('no_to'); |
||||
1277 | |||||
1278 | foreach ($namesNotFound[$recipientType] as $name) |
||||
1279 | 1 | { |
|||
1280 | $context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name); |
||||
1281 | } |
||||
1282 | } |
||||
1283 | } |
||||
1284 | } |
||||
1285 | |||||
1286 | 2 | // Did they make any mistakes like no subject or message? |
|||
1287 | if ($this->_req->post->subject === '') |
||||
1288 | { |
||||
1289 | $post_errors->addError('no_subject'); |
||||
1290 | } |
||||
1291 | 2 | ||||
1292 | if ($this->_req->getPost('message', 'trim', '') === '') |
||||
1293 | { |
||||
1294 | $post_errors->addError('no_message'); |
||||
1295 | 2 | } |
|||
1296 | elseif (!empty($modSettings['max_messageLength']) && Util::strlen($this->_req->post->message) > $modSettings['max_messageLength']) |
||||
1297 | { |
||||
1298 | $post_errors->addError('long_message'); |
||||
1299 | } |
||||
1300 | else |
||||
1301 | { |
||||
1302 | 2 | // Preparse the message. |
|||
1303 | 2 | $message = $this->_req->getPost('message', 'trim', ''); |
|||
1304 | preparsecode($message); |
||||
1305 | |||||
1306 | 2 | // Make sure there's still some content left without the tags. |
|||
1307 | if (Util::htmltrim(strip_tags($bbc_parser->parsePM(Util::htmlspecialchars($message, ENT_QUOTES)), '<img>')) === '' |
||||
1308 | && (!allowedTo('admin_forum') || strpos($message, '[html]') === false)) |
||||
1309 | { |
||||
1310 | $post_errors->addError('no_message'); |
||||
1311 | } |
||||
1312 | } |
||||
1313 | 2 | ||||
1314 | // If they made any errors, give them a chance to make amends. |
||||
1315 | if ($post_errors->hasErrors() |
||||
1316 | && !$is_recipient_change |
||||
1317 | && !$this->_req->isSet('preview') |
||||
1318 | && $this->getApi() === false) |
||||
1319 | { |
||||
1320 | $this->messagePostError($namedRecipientList, $recipientList); |
||||
1321 | 2 | ||||
1322 | return false; |
||||
1323 | } |
||||
1324 | |||||
1325 | // Want to take a second glance before you send? |
||||
1326 | if ($this->_req->isSet('preview')) |
||||
1327 | { |
||||
1328 | // Set everything up to be displayed. |
||||
1329 | $context['preview_subject'] = Util::htmlspecialchars($this->_req->post->subject); |
||||
1330 | $context['preview_message'] = Util::htmlspecialchars($this->_req->post->message, ENT_QUOTES, 'UTF-8', true); |
||||
1331 | preparsecode($context['preview_message'], true); |
||||
1332 | |||||
1333 | // Parse out the BBC if it is enabled. |
||||
1334 | $context['preview_message'] = $bbc_parser->parsePM($context['preview_message']); |
||||
1335 | |||||
1336 | // Censor, as always. |
||||
1337 | $context['preview_subject'] = censor($context['preview_subject']); |
||||
1338 | $context['preview_message'] = censor($context['preview_message']); |
||||
1339 | |||||
1340 | // Set a descriptive title. |
||||
1341 | $context['page_title'] = $txt['preview'] . ' - ' . $context['preview_subject']; |
||||
1342 | |||||
1343 | // Pretend they messed up but don't ignore if they really did :P. |
||||
1344 | 2 | $this->messagePostError($namedRecipientList, $recipientList); |
|||
1345 | |||||
1346 | return false; |
||||
1347 | } |
||||
1348 | |||||
1349 | if ($is_recipient_change) |
||||
1350 | { |
||||
1351 | // Maybe we couldn't find one? |
||||
1352 | foreach ($namesNotFound as $recipientType => $names) |
||||
1353 | { |
||||
1354 | $post_errors->addError('bad_' . $recipientType); |
||||
1355 | foreach ($names as $name) |
||||
1356 | { |
||||
1357 | $context['send_log']['failed'][] = sprintf($txt['pm_error_user_not_found'], $name); |
||||
1358 | } |
||||
1359 | } |
||||
1360 | |||||
1361 | $this->messagePostError($namedRecipientList, $recipientList); |
||||
1362 | |||||
1363 | 2 | return true; |
|||
1364 | } |
||||
1365 | |||||
1366 | // Adding a recipient cause javascript ain't working? |
||||
1367 | try |
||||
1368 | { |
||||
1369 | $this->_events->trigger('before_sending', array('namedRecipientList' => $namedRecipientList, 'recipientList' => $recipientList, 'namesNotFound' => $namesNotFound, 'post_errors' => $post_errors)); |
||||
1370 | } |
||||
1371 | 2 | catch (ControllerRedirectException) |
|||
1372 | { |
||||
1373 | $this->messagePostError($namedRecipientList, $recipientList); |
||||
1374 | |||||
1375 | return true; |
||||
1376 | } |
||||
1377 | |||||
1378 | // Safety net, it may be a module may just add to the list of errors without actually throw the error |
||||
1379 | 2 | if ($post_errors->hasErrors() && !$this->_req->isSet('preview') && $this->getApi() === false) |
|||
1380 | { |
||||
1381 | $this->messagePostError($namedRecipientList, $recipientList); |
||||
1382 | |||||
1383 | return false; |
||||
1384 | } |
||||
1385 | |||||
1386 | // Before we send the PM, let's make sure we don't have an abuse of numbers. |
||||
1387 | if (!empty($modSettings['max_pm_recipients']) && count($recipientList['to']) + count($recipientList['bcc']) > $modSettings['max_pm_recipients'] && !allowedTo(array('moderate_forum', 'send_mail', 'admin_forum'))) |
||||
1388 | { |
||||
1389 | $context['send_log'] = array( |
||||
1390 | 'sent' => array(), |
||||
1391 | 'failed' => array(sprintf($txt['pm_too_many_recipients'], $modSettings['max_pm_recipients'])), |
||||
1392 | 2 | ); |
|||
1393 | |||||
1394 | $this->messagePostError($namedRecipientList, $recipientList); |
||||
1395 | 2 | ||||
1396 | return false; |
||||
1397 | } |
||||
1398 | 2 | ||||
1399 | // Protect from message spamming. |
||||
1400 | 2 | spamProtection('pm'); |
|||
1401 | |||||
1402 | // Prevent double submission of this form. |
||||
1403 | checkSubmitOnce('check'); |
||||
1404 | |||||
1405 | // Finally do the actual sending of the PM. |
||||
1406 | if (!empty($recipientList['to']) || !empty($recipientList['bcc'])) |
||||
1407 | { |
||||
1408 | $context['send_log'] = sendpm($recipientList, $this->_req->post->subject, $this->_req->post->message, true, null, empty($this->_req->post->pm_head) ? 0 : (int) $this->_req->post->pm_head); |
||||
1409 | } |
||||
1410 | else |
||||
1411 | 2 | { |
|||
1412 | $context['send_log'] = array( |
||||
1413 | 'sent' => array(), |
||||
1414 | 'failed' => array() |
||||
1415 | ); |
||||
1416 | } |
||||
1417 | 2 | ||||
1418 | 2 | // Mark the message as "replied to". |
|||
1419 | $replied_to = $this->_req->getPost('replied_to', 'intval', 0); |
||||
1420 | $box = $this->_req->getPost('f', 'trim', ''); |
||||
1421 | 2 | if (!empty($context['send_log']['sent']) && !empty($replied_to) && $box === 'inbox') |
|||
1422 | { |
||||
1423 | require_once(SUBSDIR . '/PersonalMessage.subs.php'); |
||||
1424 | setPMRepliedStatus($this->user->id, $replied_to); |
||||
1425 | } |
||||
1426 | |||||
1427 | $failed = !empty($context['send_log']['failed']); |
||||
1428 | $this->_events->trigger('message_sent', array('failed' => $failed)); |
||||
1429 | |||||
1430 | // If one or more of the recipients were invalid, go back to the post screen with the failed usernames. |
||||
1431 | if ($failed) |
||||
1432 | { |
||||
1433 | 2 | $this->messagePostError($namesNotFound, array( |
|||
1434 | 'to' => array_intersect($recipientList['to'], $context['send_log']['failed']), |
||||
1435 | 'bcc' => array_intersect($recipientList['bcc'], $context['send_log']['failed']) |
||||
1436 | )); |
||||
1437 | 2 | ||||
1438 | return false; |
||||
1439 | 2 | } |
|||
1440 | |||||
1441 | // Message sent successfully |
||||
1442 | $context['current_label_redirect'] .= ';done=sent'; |
||||
1443 | |||||
1444 | // Go back to the where they sent from, if possible... |
||||
1445 | redirectexit($context['current_label_redirect']); |
||||
1446 | |||||
1447 | return true; |
||||
1448 | } |
||||
1449 | |||||
1450 | /** |
||||
1451 | * This function performs all additional actions including the deleting |
||||
1452 | * and labeling of PM's |
||||
1453 | */ |
||||
1454 | public function action_pmactions() |
||||
1455 | { |
||||
1456 | global $context; |
||||
1457 | |||||
1458 | checkSession('request'); |
||||
1459 | |||||
1460 | // Sending in the single pm choice via GET |
||||
1461 | $pm_actions = $this->_req->getQuery('pm_actions', null, ''); |
||||
1462 | |||||
1463 | // Set the action to apply to the PMs defined by pm_actions (yes it is that brilliant) |
||||
1464 | $pm_action = $this->_req->getPost('pm_action', 'trim', ''); |
||||
1465 | $pm_action = empty($pm_action) && isset($this->_req->post->del_selected) ? 'delete' : $pm_action; |
||||
1466 | |||||
1467 | // Create a list of PMs that we need to work on |
||||
1468 | if ($pm_action !== '' |
||||
1469 | && !empty($this->_req->post->pms) |
||||
1470 | && is_array($this->_req->post->pms)) |
||||
1471 | { |
||||
1472 | $pm_actions = array(); |
||||
1473 | foreach ($this->_req->post->pms as $pm) |
||||
1474 | { |
||||
1475 | $pm_actions[(int) $pm] = $pm_action; |
||||
1476 | } |
||||
1477 | } |
||||
1478 | |||||
1479 | // No messages to action then bug out |
||||
1480 | if (empty($pm_actions)) |
||||
1481 | { |
||||
1482 | redirectexit($context['current_label_redirect']); |
||||
1483 | } |
||||
1484 | |||||
1485 | // If we are in conversation, we may need to apply this to every message in that conversation. |
||||
1486 | if ($context['display_mode'] === self::DISPLAY_AS_CONVERSATION && isset($this->_req->query->conversation)) |
||||
1487 | { |
||||
1488 | $id_pms = array_map('intval', array_keys($pm_actions)); |
||||
1489 | $pm_heads = getDiscussions($id_pms); |
||||
1490 | $pms = getPmsFromDiscussion(array_keys($pm_heads)); |
||||
1491 | |||||
1492 | // Copy the action from the single to PM to the others in the conversation. |
||||
1493 | foreach ($pms as $id_pm => $id_head) |
||||
1494 | { |
||||
1495 | if (isset($pm_heads[$id_head], $pm_actions[$pm_heads[$id_head]])) |
||||
1496 | { |
||||
1497 | $pm_actions[$id_pm] = $pm_actions[$pm_heads[$id_head]]; |
||||
1498 | } |
||||
1499 | } |
||||
1500 | } |
||||
1501 | |||||
1502 | // Get to doing what we've been told |
||||
1503 | $to_delete = array(); |
||||
1504 | $to_label = array(); |
||||
1505 | $label_type = array(); |
||||
1506 | foreach ($pm_actions as $pm => $action) |
||||
1507 | { |
||||
1508 | // What are we doing with the selected messages, adding a label, removing, other? |
||||
1509 | switch (substr($action, 0, 4)) |
||||
1510 | { |
||||
1511 | case 'dele': |
||||
1512 | $to_delete[] = (int) $pm; |
||||
1513 | break; |
||||
1514 | case 'add_': |
||||
1515 | $type = 'add'; |
||||
1516 | $action = substr($action, 4); |
||||
1517 | break; |
||||
1518 | case 'rem_': |
||||
1519 | $type = 'rem'; |
||||
1520 | $action = substr($action, 4); |
||||
1521 | break; |
||||
1522 | default: |
||||
1523 | $type = 'unk'; |
||||
1524 | } |
||||
1525 | |||||
1526 | if ((int) $action === -1 || (int) $action === 0 || (int) $action > 0) |
||||
1527 | { |
||||
1528 | $to_label[(int) $pm] = (int) $action; |
||||
1529 | $label_type[(int) $pm] = $type ?? ''; |
||||
1530 | } |
||||
1531 | } |
||||
1532 | |||||
1533 | // Deleting, it looks like? |
||||
1534 | if (!empty($to_delete)) |
||||
1535 | { |
||||
1536 | deleteMessages($to_delete, $context['display_mode'] === self::DISPLAY_AS_CONVERSATION ? null : $context['folder']); |
||||
1537 | } |
||||
1538 | |||||
1539 | // Are we labelling anything? |
||||
1540 | if (!empty($to_label) && $context['folder'] === 'inbox') |
||||
1541 | { |
||||
1542 | $updateErrors = changePMLabels($to_label, $label_type, $this->user->id); |
||||
1543 | |||||
1544 | // Any errors? |
||||
1545 | if (!empty($updateErrors)) |
||||
1546 | { |
||||
1547 | throw new Exception('labels_too_many', true, array($updateErrors)); |
||||
1548 | } |
||||
1549 | } |
||||
1550 | |||||
1551 | // Back to the folder. |
||||
1552 | $_SESSION['pm_selected'] = array_keys($to_label); |
||||
1553 | redirectexit($context['current_label_redirect'] . (count($to_label) === 1 ? '#msg_' . $_SESSION['pm_selected'][0] : '')); |
||||
1554 | } |
||||
1555 | |||||
1556 | /** |
||||
1557 | * Are you sure you want to PERMANENTLY (mostly) delete ALL your messages? |
||||
1558 | */ |
||||
1559 | public function action_removeall() |
||||
1560 | { |
||||
1561 | global $txt, $context; |
||||
1562 | |||||
1563 | // Only have to set up the template.... |
||||
1564 | $context['sub_template'] = 'ask_delete'; |
||||
1565 | $context['page_title'] = $txt['delete_all']; |
||||
1566 | $context['delete_all'] = $this->_req->query->f === 'all'; |
||||
1567 | |||||
1568 | // And set the folder name... |
||||
1569 | $txt['delete_all'] = str_replace('PMBOX', $context['folder'] != 'sent' ? $txt['inbox'] : $txt['sent_items'], $txt['delete_all']); |
||||
1570 | } |
||||
1571 | |||||
1572 | /** |
||||
1573 | * Delete ALL the messages! |
||||
1574 | */ |
||||
1575 | public function action_removeall2() |
||||
1576 | { |
||||
1577 | global $context; |
||||
1578 | |||||
1579 | checkSession('get'); |
||||
1580 | |||||
1581 | // If all then delete all messages the user has. |
||||
1582 | if ($this->_req->query->f === 'all') |
||||
1583 | { |
||||
1584 | deleteMessages(null); |
||||
1585 | } |
||||
1586 | // Otherwise just the selected folder. |
||||
1587 | else |
||||
1588 | { |
||||
1589 | deleteMessages(null, $this->_req->query->f != 'sent' ? 'inbox' : 'sent'); |
||||
1590 | } |
||||
1591 | |||||
1592 | // Done... all gone. |
||||
1593 | redirectexit($context['current_label_redirect']); |
||||
1594 | } |
||||
1595 | |||||
1596 | /** |
||||
1597 | * This function allows the user to prune (delete) all messages older than a supplied duration. |
||||
1598 | */ |
||||
1599 | public function action_prune() |
||||
1600 | { |
||||
1601 | global $txt, $context; |
||||
1602 | |||||
1603 | // Actually delete the messages. |
||||
1604 | if (isset($this->_req->post->age)) |
||||
1605 | { |
||||
1606 | checkSession(); |
||||
1607 | |||||
1608 | // Calculate the time to delete before. |
||||
1609 | $deleteTime = max(0, time() - (86400 * (int) $this->_req->post->age)); |
||||
1610 | |||||
1611 | // Select all the messages older than $deleteTime. |
||||
1612 | $toDelete = getPMsOlderThan($this->user->id, $deleteTime); |
||||
1613 | |||||
1614 | // Delete the actual messages. |
||||
1615 | deleteMessages($toDelete); |
||||
1616 | |||||
1617 | // Go back to their inbox. |
||||
1618 | redirectexit($context['current_label_redirect']); |
||||
1619 | } |
||||
1620 | |||||
1621 | // Build the link tree elements. |
||||
1622 | $context['breadcrumbs'][] = array( |
||||
1623 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'prune']), |
||||
1624 | 'name' => $txt['pm_prune'] |
||||
1625 | ); |
||||
1626 | $context['sub_template'] = 'prune'; |
||||
1627 | $context['page_title'] = $txt['pm_prune']; |
||||
1628 | } |
||||
1629 | |||||
1630 | /** |
||||
1631 | * This function handles adding, deleting and editing labels on messages. |
||||
1632 | */ |
||||
1633 | public function action_manlabels() |
||||
1634 | { |
||||
1635 | global $txt, $context; |
||||
1636 | |||||
1637 | require_once(SUBSDIR . '/PersonalMessage.subs.php'); |
||||
1638 | |||||
1639 | // Build the link tree elements... |
||||
1640 | $context['breadcrumbs'][] = array( |
||||
1641 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'manlabels']), |
||||
1642 | 'name' => $txt['pm_manage_labels'] |
||||
1643 | ); |
||||
1644 | |||||
1645 | // Some things for the template |
||||
1646 | $context['page_title'] = $txt['pm_manage_labels']; |
||||
1647 | $context['sub_template'] = 'labels'; |
||||
1648 | |||||
1649 | // Add all existing labels to the array to save, slashing them as necessary... |
||||
1650 | $the_labels = array(); |
||||
1651 | foreach ($context['labels'] as $label) |
||||
1652 | { |
||||
1653 | if ($label['id'] !== -1) |
||||
1654 | { |
||||
1655 | $the_labels[$label['id']] = $label['name']; |
||||
1656 | } |
||||
1657 | } |
||||
1658 | |||||
1659 | // Submitting changes? |
||||
1660 | if (isset($this->_req->post->add) || isset($this->_req->post->delete) || isset($this->_req->post->save)) |
||||
1661 | { |
||||
1662 | checkSession(); |
||||
1663 | |||||
1664 | // This will be for updating messages. |
||||
1665 | $message_changes = array(); |
||||
1666 | $new_labels = array(); |
||||
1667 | $rule_changes = array(); |
||||
1668 | |||||
1669 | // Will most likely need this. |
||||
1670 | loadRules(); |
||||
1671 | |||||
1672 | // Adding a new label? |
||||
1673 | if (isset($this->_req->post->add)) |
||||
1674 | { |
||||
1675 | $this->_req->post->label = strtr(Util::htmlspecialchars(trim($this->_req->post->label)), array(',' => ',')); |
||||
1676 | |||||
1677 | if (Util::strlen($this->_req->post->label) > 30) |
||||
1678 | { |
||||
1679 | $this->_req->post->label = Util::substr($this->_req->post->label, 0, 30); |
||||
1680 | } |
||||
1681 | |||||
1682 | if ($this->_req->post->label !== '') |
||||
1683 | { |
||||
1684 | $the_labels[] = $this->_req->post->label; |
||||
1685 | } |
||||
1686 | } |
||||
1687 | // Deleting an existing label? |
||||
1688 | elseif (isset($this->_req->post->delete, $this->_req->post->delete_label)) |
||||
1689 | { |
||||
1690 | $i = 0; |
||||
1691 | foreach (array_keys($the_labels) as $id) |
||||
1692 | { |
||||
1693 | if (isset($this->_req->post->delete_label[$id])) |
||||
1694 | { |
||||
1695 | unset($the_labels[$id]); |
||||
1696 | $message_changes[$id] = true; |
||||
1697 | } |
||||
1698 | else |
||||
1699 | { |
||||
1700 | $new_labels[$id] = $i++; |
||||
1701 | } |
||||
1702 | } |
||||
1703 | } |
||||
1704 | // The hardest one to deal with... changes. |
||||
1705 | elseif (isset($this->_req->post->save) && !empty($this->_req->post->label_name)) |
||||
1706 | { |
||||
1707 | $i = 0; |
||||
1708 | foreach (array_keys($the_labels) as $id) |
||||
1709 | { |
||||
1710 | if ($id === -1) |
||||
1711 | { |
||||
1712 | continue; |
||||
1713 | } |
||||
1714 | |||||
1715 | if (isset($this->_req->post->label_name[$id])) |
||||
1716 | { |
||||
1717 | // Prepare the label name |
||||
1718 | $this->_req->post->label_name[$id] = trim(strtr(Util::htmlspecialchars($this->_req->post->label_name[$id]), array(',' => ','))); |
||||
1719 | |||||
1720 | // Has to fit in the database as well |
||||
1721 | if (Util::strlen($this->_req->post->label_name[$id]) > 30) |
||||
1722 | { |
||||
1723 | $this->_req->post->label_name[$id] = Util::substr($this->_req->post->label_name[$id], 0, 30); |
||||
1724 | } |
||||
1725 | |||||
1726 | if ($this->_req->post->label_name[$id] != '') |
||||
1727 | { |
||||
1728 | $the_labels[(int) $id] = $this->_req->post->label_name[$id]; |
||||
1729 | $new_labels[$id] = $i++; |
||||
1730 | } |
||||
1731 | else |
||||
1732 | { |
||||
1733 | unset($the_labels[(int) $id]); |
||||
1734 | $message_changes[(int) $id] = true; |
||||
1735 | } |
||||
1736 | } |
||||
1737 | else |
||||
1738 | { |
||||
1739 | $new_labels[$id] = $i++; |
||||
1740 | } |
||||
1741 | } |
||||
1742 | } |
||||
1743 | |||||
1744 | // Save the label status. |
||||
1745 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
1746 | updateMemberData($this->user->id, array('message_labels' => implode(',', $the_labels))); |
||||
1747 | |||||
1748 | // Update all the messages currently with any label changes in them! |
||||
1749 | if (!empty($message_changes)) |
||||
1750 | { |
||||
1751 | $searchArray = array_keys($message_changes); |
||||
1752 | |||||
1753 | if (!empty($new_labels)) |
||||
1754 | { |
||||
1755 | for ($i = max($searchArray) + 1, $n = max(array_keys($new_labels)); $i <= $n; $i++) |
||||
1756 | { |
||||
1757 | $searchArray[] = $i; |
||||
1758 | } |
||||
1759 | } |
||||
1760 | |||||
1761 | updateLabelsToPM($searchArray, $new_labels, $this->user->id); |
||||
1762 | |||||
1763 | // Now do the same the rules - check through each rule. |
||||
1764 | foreach ($context['rules'] as $k => $rule) |
||||
1765 | { |
||||
1766 | // Each action... |
||||
1767 | foreach ($rule['actions'] as $k2 => $action) |
||||
1768 | { |
||||
1769 | if ($action['t'] !== 'lab' || !in_array($action['v'], $searchArray)) |
||||
1770 | { |
||||
1771 | continue; |
||||
1772 | } |
||||
1773 | |||||
1774 | $rule_changes[] = $rule['id']; |
||||
1775 | |||||
1776 | // If we're here we have a label which is either changed or gone... |
||||
1777 | if (isset($new_labels[$action['v']])) |
||||
1778 | { |
||||
1779 | $context['rules'][$k]['actions'][$k2]['v'] = $new_labels[$action['v']]; |
||||
1780 | } |
||||
1781 | else |
||||
1782 | { |
||||
1783 | unset($context['rules'][$k]['actions'][$k2]); |
||||
1784 | } |
||||
1785 | } |
||||
1786 | } |
||||
1787 | } |
||||
1788 | |||||
1789 | // If we have rules to change do so now. |
||||
1790 | if (!empty($rule_changes)) |
||||
1791 | { |
||||
1792 | $rule_changes = array_unique($rule_changes); |
||||
1793 | |||||
1794 | // Update/delete as appropriate. |
||||
1795 | foreach ($rule_changes as $k => $id) |
||||
1796 | { |
||||
1797 | if (!empty($context['rules'][$id]['actions'])) |
||||
1798 | { |
||||
1799 | updatePMRuleAction($id, $this->user->id, $context['rules'][$id]['actions']); |
||||
1800 | unset($rule_changes[$k]); |
||||
1801 | } |
||||
1802 | } |
||||
1803 | |||||
1804 | // Anything left here means it's lost all actions... |
||||
1805 | if (!empty($rule_changes)) |
||||
1806 | { |
||||
1807 | deletePMRules($this->user->id, $rule_changes); |
||||
1808 | } |
||||
1809 | } |
||||
1810 | |||||
1811 | // Make sure we're not caching this! |
||||
1812 | Cache::instance()->remove('labelCounts:' . $this->user->id); |
||||
1813 | |||||
1814 | // To make the changes appear right away, redirect. |
||||
1815 | redirectexit('action=pm;sa=manlabels'); |
||||
1816 | } |
||||
1817 | } |
||||
1818 | |||||
1819 | /** |
||||
1820 | * Allows to edit Personal Message Settings. |
||||
1821 | * |
||||
1822 | * @uses ProfileOptions controller. (@todo refactor this.) |
||||
1823 | * @uses Profile template. |
||||
1824 | * @uses Profile language file. |
||||
1825 | */ |
||||
1826 | public function action_settings() |
||||
1827 | { |
||||
1828 | global $txt, $context, $profile_vars, $cur_profile; |
||||
1829 | |||||
1830 | require_once(SUBSDIR . '/Profile.subs.php'); |
||||
1831 | |||||
1832 | // Load the member data for editing |
||||
1833 | MembersList::load($this->user->id, false, 'profile'); |
||||
0 ignored issues
–
show
The property
id does not exist on ElkArte\Helper\ValuesContainer . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||
1834 | $cur_profile = MembersList::get($this->user->id); |
||||
1835 | |||||
1836 | // Load up the profile template, its where PM settings are located |
||||
1837 | Txt::load('Profile'); |
||||
1838 | theme()->getTemplates()->load('Profile'); |
||||
1839 | |||||
1840 | // We want them to submit back to here. |
||||
1841 | $context['profile_custom_submit_url'] = getUrl('action', ['action' => 'pm', 'sa' => 'settings', 'save']); |
||||
1842 | |||||
1843 | $context['page_title'] = $txt['pm_settings']; |
||||
1844 | $context['user']['is_owner'] = true; |
||||
1845 | $context['id_member'] = $this->user->id; |
||||
1846 | $context['require_password'] = false; |
||||
1847 | $context['menu_item_selected'] = 'settings'; |
||||
1848 | $context['submit_button_text'] = $txt['pm_settings']; |
||||
1849 | |||||
1850 | // Add our position to the breadcrumbs. |
||||
1851 | $context['breadcrumbs'][] = [ |
||||
1852 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'settings']), |
||||
1853 | 'name' => $txt['pm_settings'] |
||||
1854 | ]; |
||||
1855 | |||||
1856 | // Are they saving? |
||||
1857 | if (isset($this->_req->post->save)) |
||||
1858 | { |
||||
1859 | checkSession(); |
||||
1860 | |||||
1861 | // Mimic what profile would do. |
||||
1862 | // @todo fix this when Profile.subs is not dependant on this behavior |
||||
1863 | $_POST = Util::htmltrim__recursive((array) $this->_req->post); |
||||
1864 | $_POST = Util::htmlspecialchars__recursive($_POST); |
||||
1865 | |||||
1866 | // Save the fields. |
||||
1867 | $profileFields = new ProfileFields(); |
||||
1868 | $fields = ProfileOptions::getFields('contactprefs'); |
||||
1869 | $profileFields->saveProfileFields($fields['fields'], $fields['hook']); |
||||
1870 | |||||
1871 | if (!empty($profile_vars)) |
||||
1872 | { |
||||
1873 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
1874 | updateMemberData($this->user->id, $profile_vars); |
||||
1875 | } |
||||
1876 | |||||
1877 | // Invalidate any cached data and reload so we show the saved values |
||||
1878 | Cache::instance()->remove('member_data-profile-' . $this->user->id); |
||||
1879 | MembersList::load($this->user->id, false, 'profile'); |
||||
1880 | $cur_profile = MembersList::get($this->user->id); |
||||
1881 | } |
||||
1882 | |||||
1883 | // Load up the fields. |
||||
1884 | $controller = new ProfileOptions(new EventManager()); |
||||
1885 | $controller->setUser(User::$info); |
||||
1886 | $controller->pre_dispatch(); |
||||
1887 | $controller->action_pmprefs(); |
||||
1888 | } |
||||
1889 | |||||
1890 | /** |
||||
1891 | * Allows the user to report a personal message to an administrator. |
||||
1892 | * |
||||
1893 | * What it does: |
||||
1894 | * |
||||
1895 | * - In the first instance requires that the ID of the message to report is passed through $_GET. |
||||
1896 | * - It allows the user to report to either a particular administrator - or the whole admin team. |
||||
1897 | * - It will forward on a copy of the original message without allowing the reporter to make changes. |
||||
1898 | * |
||||
1899 | * @uses report_message sub-template. |
||||
1900 | */ |
||||
1901 | public function action_report() |
||||
1902 | { |
||||
1903 | global $txt, $context, $language, $modSettings; |
||||
1904 | |||||
1905 | // Check that this feature is even enabled! |
||||
1906 | if (empty($modSettings['enableReportPM']) || empty($this->_req->getPost('pmsg', 'intval', $this->_req->getQuery('pmsg', 'intval', 0)))) |
||||
1907 | { |
||||
1908 | throw new Exception('no_access', false); |
||||
1909 | } |
||||
1910 | |||||
1911 | $pmsg = $this->_req->getQuery('pmsg', 'intval', $this->_req->getPost('pmsg', 'intval', 0)); |
||||
1912 | |||||
1913 | if (!isAccessiblePM($pmsg, 'inbox')) |
||||
1914 | { |
||||
1915 | throw new Exception('no_access', false); |
||||
1916 | } |
||||
1917 | |||||
1918 | $context['pm_id'] = $pmsg; |
||||
1919 | $context['page_title'] = $txt['pm_report_title']; |
||||
1920 | $context['sub_template'] = 'report_message'; |
||||
1921 | |||||
1922 | // We'll query some members, we will. |
||||
1923 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
1924 | |||||
1925 | // If we're here, just send the user to the template, with a few useful context bits. |
||||
1926 | if (isset($this->_req->post->report)) |
||||
1927 | { |
||||
1928 | $poster_comment = strtr(Util::htmlspecialchars($this->_req->post->reason), array("\r" => '', "\t" => '')); |
||||
1929 | |||||
1930 | if (Util::strlen($poster_comment) > 254) |
||||
1931 | { |
||||
1932 | throw new Exception('post_too_long', false); |
||||
1933 | } |
||||
1934 | |||||
1935 | // Check the session before proceeding any further! |
||||
1936 | checkSession(); |
||||
1937 | |||||
1938 | // First, load up the message they want to file a complaint against, and verify it actually went to them! |
||||
1939 | [$subject, $body, $time, $memberFromID, $memberFromName, $poster_name, $time_message] = loadPersonalMessage($pmsg); |
||||
1940 | |||||
1941 | require_once(SUBSDIR . '/Messages.subs.php'); |
||||
1942 | |||||
1943 | recordReport(array( |
||||
1944 | 'id_msg' => $pmsg, |
||||
1945 | 'id_topic' => 0, |
||||
1946 | 'id_board' => 0, |
||||
1947 | 'type' => 'pm', |
||||
1948 | 'id_poster' => $memberFromID, |
||||
1949 | 'real_name' => $memberFromName, |
||||
1950 | 'poster_name' => $poster_name, |
||||
1951 | 'subject' => $subject, |
||||
1952 | 'body' => $body, |
||||
1953 | 'time_message' => $time_message, |
||||
1954 | ), $poster_comment); |
||||
1955 | |||||
1956 | // Remove the line breaks... |
||||
1957 | $body = preg_replace('~<br ?/?>~i', "\n", $body); |
||||
1958 | |||||
1959 | $recipients = array(); |
||||
1960 | $temp = loadPMRecipientsAll($context['pm_id'], true); |
||||
1961 | foreach ($temp as $recipient) |
||||
1962 | { |
||||
1963 | $recipients[] = $recipient['link']; |
||||
1964 | } |
||||
1965 | |||||
1966 | // Now let's get out and loop through the admins. |
||||
1967 | $admins = admins(isset($this->_req->post->id_admin) ? (int) $this->_req->post->id_admin : 0); |
||||
1968 | |||||
1969 | // Maybe we shouldn't advertise this? |
||||
1970 | if (empty($admins)) |
||||
1971 | { |
||||
1972 | throw new Exception('no_access', false); |
||||
1973 | } |
||||
1974 | |||||
1975 | $memberFromName = un_htmlspecialchars($memberFromName); |
||||
1976 | |||||
1977 | // Prepare the message storage array. |
||||
1978 | $messagesToSend = array(); |
||||
1979 | |||||
1980 | // Loop through each admin, and add them to the right language pile... |
||||
1981 | foreach ($admins as $id_admin => $admin_info) |
||||
1982 | { |
||||
1983 | // Need to send in the correct language! |
||||
1984 | $cur_language = empty($admin_info['lngfile']) || empty($modSettings['userLanguage']) ? $language : $admin_info['lngfile']; |
||||
1985 | |||||
1986 | if (!isset($messagesToSend[$cur_language])) |
||||
1987 | { |
||||
1988 | $mtxt = []; |
||||
1989 | $lang = new Loader($cur_language, $mtxt, database()); |
||||
1990 | $lang->load('PersonalMessage', false); |
||||
1991 | |||||
1992 | // Make the body. |
||||
1993 | $report_body = str_replace(array('{REPORTER}', '{SENDER}'), array(un_htmlspecialchars($this->user->name), $memberFromName), $mtxt['pm_report_pm_user_sent']); |
||||
1994 | $report_body .= "\n" . '[b]' . $this->_req->post->reason . '[/b]' . "\n\n"; |
||||
1995 | if (!empty($recipients)) |
||||
1996 | { |
||||
1997 | $report_body .= $mtxt['pm_report_pm_other_recipients'] . ' ' . implode(', ', $recipients) . "\n\n"; |
||||
1998 | } |
||||
1999 | |||||
2000 | $report_body .= $mtxt['pm_report_pm_unedited_below'] . "\n" . '[quote author=' . (empty($memberFromID) ? '"' . $memberFromName . '"' : $memberFromName . ' link=action=profile;u=' . $memberFromID . ' date=' . $time) . ']' . "\n" . un_htmlspecialchars($body) . '[/quote]'; |
||||
2001 | |||||
2002 | // Plonk it in the array ;) |
||||
2003 | $messagesToSend[$cur_language] = array( |
||||
2004 | 'subject' => (Util::strpos($subject, $mtxt['pm_report_pm_subject']) === false ? $mtxt['pm_report_pm_subject'] : '') . un_htmlspecialchars($subject), |
||||
2005 | 'body' => $report_body, |
||||
2006 | 'recipients' => array( |
||||
2007 | 'to' => array(), |
||||
2008 | 'bcc' => array() |
||||
2009 | ), |
||||
2010 | ); |
||||
2011 | } |
||||
2012 | |||||
2013 | // Add them to the list. |
||||
2014 | $messagesToSend[$cur_language]['recipients']['to'][$id_admin] = $id_admin; |
||||
2015 | } |
||||
2016 | |||||
2017 | // Send a different email for each language. |
||||
2018 | foreach ($messagesToSend as $message) |
||||
2019 | { |
||||
2020 | sendpm($message['recipients'], $message['subject'], $message['body']); |
||||
2021 | } |
||||
2022 | |||||
2023 | // Leave them with a template. |
||||
2024 | $context['sub_template'] = 'report_message_complete'; |
||||
2025 | } |
||||
2026 | } |
||||
2027 | |||||
2028 | /** |
||||
2029 | * List and allow adding/entering all man rules, such as |
||||
2030 | * |
||||
2031 | * What it does: |
||||
2032 | * |
||||
2033 | * - If it itches, it will be scratched. |
||||
2034 | * - Yes or No are perfectly acceptable answers to almost every question. |
||||
2035 | * - Men see in only 16 colors, Peach, for example, is a fruit, not a color. |
||||
2036 | * |
||||
2037 | * @uses sub template rules |
||||
2038 | */ |
||||
2039 | public function action_manrules() |
||||
2040 | { |
||||
2041 | global $txt, $context; |
||||
2042 | |||||
2043 | require_once(SUBSDIR . '/PersonalMessage.subs.php'); |
||||
2044 | |||||
2045 | // The link tree - gotta have this :o |
||||
2046 | $context['breadcrumbs'][] = [ |
||||
2047 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'manrules']), |
||||
2048 | 'name' => $txt['pm_manage_rules'] |
||||
2049 | ]; |
||||
2050 | |||||
2051 | $context['page_title'] = $txt['pm_manage_rules']; |
||||
2052 | $context['sub_template'] = 'rules'; |
||||
2053 | |||||
2054 | // Load them... load them!! |
||||
2055 | loadRules(); |
||||
2056 | |||||
2057 | // Likely to need all the groups! |
||||
2058 | require_once(SUBSDIR . '/Membergroups.subs.php'); |
||||
2059 | $context['groups'] = accessibleGroups(); |
||||
2060 | |||||
2061 | // Applying all rules? |
||||
2062 | if (isset($this->_req->query->apply)) |
||||
2063 | { |
||||
2064 | checkSession('get'); |
||||
2065 | |||||
2066 | applyRules(true); |
||||
2067 | redirectexit('action=pm;sa=manrules'); |
||||
2068 | } |
||||
2069 | |||||
2070 | // Editing a specific rule? |
||||
2071 | if (isset($this->_req->query->add)) |
||||
2072 | { |
||||
2073 | $rid = $this->_req->getQuery('rid', 'intval', 0); |
||||
2074 | $context['rid'] = isset($context['rules'][$rid]) ? $rid : 0; |
||||
2075 | $context['sub_template'] = 'add_rule'; |
||||
2076 | |||||
2077 | // Any known rule |
||||
2078 | $js_rules = []; |
||||
2079 | foreach ($context['known_rules'] as $rule) |
||||
2080 | { |
||||
2081 | $js_rules[$rule] = $txt['pm_rule_' . $rule]; |
||||
2082 | } |
||||
2083 | |||||
2084 | $js_rules = json_encode($js_rules); |
||||
2085 | |||||
2086 | // Any known label |
||||
2087 | $js_labels = []; |
||||
2088 | foreach ($context['labels'] as $label) |
||||
2089 | { |
||||
2090 | if ($label['id'] !== -1) |
||||
2091 | { |
||||
2092 | $js_labels[$label['id'] + 1] = $label['name']; |
||||
2093 | } |
||||
2094 | } |
||||
2095 | |||||
2096 | $js_labels = json_encode($js_labels); |
||||
2097 | |||||
2098 | // And all the groups as well |
||||
2099 | $js_groups = json_encode($context['groups']); |
||||
2100 | |||||
2101 | theme()->addJavascriptVar([ |
||||
2102 | 'criteriaNum' => 0, |
||||
2103 | 'actionNum' => 0, |
||||
2104 | ] |
||||
2105 | ); |
||||
2106 | |||||
2107 | // Oh my, we have a lot of text strings for this |
||||
2108 | theme()->addJavascriptVar(array( |
||||
2109 | 'groups' => $js_groups, |
||||
2110 | 'labels' => $js_labels, |
||||
2111 | 'rules' => $js_rules, |
||||
2112 | 'txt_pm_readable_and' => $txt['pm_readable_and'], |
||||
2113 | 'txt_pm_readable_or' => $txt['pm_readable_or'], |
||||
2114 | 'txt_pm_readable_member' => $txt['pm_readable_member'], |
||||
2115 | 'txt_pm_readable_group' => $txt['pm_readable_group'], |
||||
2116 | 'txt_pm_readable_subject ' => $txt['pm_readable_subject'], |
||||
2117 | 'txt_pm_readable_body' => $txt['pm_readable_body'], |
||||
2118 | 'txt_pm_readable_buddy' => $txt['pm_readable_buddy'], |
||||
2119 | 'txt_pm_readable_label' => $txt['pm_readable_label'], |
||||
2120 | 'txt_pm_readable_delete' => $txt['pm_readable_delete'], |
||||
2121 | 'txt_pm_readable_start' => $txt['pm_readable_start'], |
||||
2122 | 'txt_pm_readable_end' => $txt['pm_readable_end'], |
||||
2123 | 'txt_pm_readable_then' => $txt['pm_readable_then'], |
||||
2124 | 'txt_pm_rule_not_defined' => $txt['pm_rule_not_defined'], |
||||
2125 | 'txt_pm_rule_criteria_pick' => $txt['pm_rule_criteria_pick'], |
||||
2126 | 'txt_pm_rule_sel_group' => $txt['pm_rule_sel_group'], |
||||
2127 | 'txt_pm_rule_sel_action' => $txt['pm_rule_sel_action'], |
||||
2128 | 'txt_pm_rule_label' => $txt['pm_rule_label'], |
||||
2129 | 'txt_pm_rule_delete' => $txt['pm_rule_delete'], |
||||
2130 | 'txt_pm_rule_sel_label' => $txt['pm_rule_sel_label'], |
||||
2131 | ), true); |
||||
2132 | |||||
2133 | // Current rule information... |
||||
2134 | if ($context['rid']) |
||||
2135 | { |
||||
2136 | $context['rule'] = $context['rules'][$context['rid']]; |
||||
2137 | $members = array(); |
||||
2138 | |||||
2139 | // Need to get member names! |
||||
2140 | foreach ($context['rule']['criteria'] as $k => $criteria) |
||||
2141 | { |
||||
2142 | if ($criteria['t'] !== 'mid') |
||||
2143 | { |
||||
2144 | continue; |
||||
2145 | } |
||||
2146 | if (empty($criteria['v'])) |
||||
2147 | { |
||||
2148 | continue; |
||||
2149 | } |
||||
2150 | $members[(int) $criteria['v']] = $k; |
||||
2151 | } |
||||
2152 | |||||
2153 | if (!empty($members)) |
||||
2154 | { |
||||
2155 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
2156 | $result = getBasicMemberData(array_keys($members)); |
||||
2157 | foreach ($result as $row) |
||||
2158 | { |
||||
2159 | $context['rule']['criteria'][$members[$row['id_member']]]['v'] = $row['member_name']; |
||||
2160 | } |
||||
2161 | } |
||||
2162 | } |
||||
2163 | else |
||||
2164 | { |
||||
2165 | $context['rule'] = array( |
||||
2166 | 'id' => '', |
||||
2167 | 'name' => '', |
||||
2168 | 'criteria' => array(), |
||||
2169 | 'actions' => array(), |
||||
2170 | 'logic' => 'and', |
||||
2171 | ); |
||||
2172 | } |
||||
2173 | |||||
2174 | // Add a dummy criteria to allow expansion for none js users. |
||||
2175 | $context['rule']['criteria'][] = array('t' => '', 'v' => ''); |
||||
2176 | } |
||||
2177 | // Saving? |
||||
2178 | elseif (isset($this->_req->query->save)) |
||||
2179 | { |
||||
2180 | checkSession(); |
||||
2181 | $rid = $this->_req->getQuery('rid', 'intval', 0); |
||||
2182 | $context['rid'] = isset($context['rules'][$rid]) ? $rid : 0; |
||||
2183 | |||||
2184 | // Name is easy! |
||||
2185 | $ruleName = Util::htmlspecialchars(trim($this->_req->post->rule_name)); |
||||
2186 | if (empty($ruleName)) |
||||
2187 | { |
||||
2188 | throw new Exception('pm_rule_no_name', false); |
||||
2189 | } |
||||
2190 | |||||
2191 | // Sanity check... |
||||
2192 | if (empty($this->_req->post->ruletype) || empty($this->_req->post->acttype)) |
||||
2193 | { |
||||
2194 | throw new Exception('pm_rule_no_criteria', false); |
||||
2195 | } |
||||
2196 | |||||
2197 | // Let's do the criteria first - it's also hardest! |
||||
2198 | $criteria = array(); |
||||
2199 | foreach ($this->_req->post->ruletype as $ind => $type) |
||||
2200 | { |
||||
2201 | // Check everything is here... |
||||
2202 | if ($type === 'gid' && (!isset($this->_req->post->ruledefgroup[$ind], $context['groups'][$this->_req->post->ruledefgroup[$ind]]))) |
||||
2203 | { |
||||
2204 | continue; |
||||
2205 | } |
||||
2206 | |||||
2207 | if ($type !== 'bud' && !isset($this->_req->post->ruledef[$ind])) |
||||
2208 | { |
||||
2209 | continue; |
||||
2210 | } |
||||
2211 | |||||
2212 | // Members need to be found. |
||||
2213 | if ($type === 'mid') |
||||
2214 | { |
||||
2215 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
2216 | $name = trim($this->_req->post->ruledef[$ind]); |
||||
2217 | $member = getMemberByName($name, true); |
||||
2218 | if (empty($member)) |
||||
2219 | { |
||||
2220 | continue; |
||||
2221 | } |
||||
2222 | |||||
2223 | $criteria[] = array('t' => 'mid', 'v' => $member['id_member']); |
||||
2224 | } |
||||
2225 | elseif ($type === 'bud') |
||||
2226 | { |
||||
2227 | $criteria[] = array('t' => 'bud', 'v' => 1); |
||||
2228 | } |
||||
2229 | elseif ($type === 'gid') |
||||
2230 | { |
||||
2231 | $criteria[] = array('t' => 'gid', 'v' => (int) $this->_req->post->ruledefgroup[$ind]); |
||||
2232 | } |
||||
2233 | elseif (in_array($type, array('sub', 'msg')) && trim($this->_req->post->ruledef[$ind]) !== '') |
||||
2234 | { |
||||
2235 | $criteria[] = array('t' => $type, 'v' => Util::htmlspecialchars(trim($this->_req->post->ruledef[$ind]))); |
||||
2236 | } |
||||
2237 | } |
||||
2238 | |||||
2239 | // Also do the actions! |
||||
2240 | $actions = array(); |
||||
2241 | $doDelete = 0; |
||||
2242 | $isOr = $this->_req->post->rule_logic === 'or' ? 1 : 0; |
||||
2243 | foreach ($this->_req->post->acttype as $ind => $type) |
||||
2244 | { |
||||
2245 | // Picking a valid label? |
||||
2246 | if ($type === 'lab' && (!isset($this->_req->post->labdef[$ind], $context['labels'][(int) $this->_req->post->labdef[$ind] - 1]))) |
||||
2247 | { |
||||
2248 | continue; |
||||
2249 | } |
||||
2250 | |||||
2251 | // Record what we're doing. |
||||
2252 | if ($type === 'del') |
||||
2253 | { |
||||
2254 | $doDelete = 1; |
||||
2255 | } |
||||
2256 | elseif ($type === 'lab') |
||||
2257 | { |
||||
2258 | $actions[] = array('t' => 'lab', 'v' => (int) $this->_req->post->labdef[$ind] - 1); |
||||
2259 | } |
||||
2260 | } |
||||
2261 | |||||
2262 | if (empty($criteria) || (empty($actions) && !$doDelete)) |
||||
2263 | { |
||||
2264 | throw new Exception('pm_rule_no_criteria', false); |
||||
2265 | } |
||||
2266 | |||||
2267 | // What are we storing? |
||||
2268 | $criteria = serialize($criteria); |
||||
2269 | $actions = serialize($actions); |
||||
2270 | |||||
2271 | // Create the rule? |
||||
2272 | if (empty($context['rid'])) |
||||
2273 | { |
||||
2274 | addPMRule($this->user->id, $ruleName, $criteria, $actions, $doDelete, $isOr); |
||||
2275 | } |
||||
2276 | else |
||||
2277 | { |
||||
2278 | updatePMRule($this->user->id, $context['rid'], $ruleName, $criteria, $actions, $doDelete, $isOr); |
||||
2279 | } |
||||
2280 | |||||
2281 | redirectexit('action=pm;sa=manrules'); |
||||
2282 | } |
||||
2283 | // Deleting? |
||||
2284 | elseif (isset($this->_req->post->delselected) && !empty($this->_req->post->delrule)) |
||||
2285 | { |
||||
2286 | checkSession(); |
||||
2287 | $toDelete = array(); |
||||
2288 | foreach ($this->_req->post->delrule as $k => $v) |
||||
2289 | { |
||||
2290 | $toDelete[] = (int) $k; |
||||
2291 | } |
||||
2292 | |||||
2293 | if (!empty($toDelete)) |
||||
2294 | { |
||||
2295 | deletePMRules($this->user->id, $toDelete); |
||||
2296 | } |
||||
2297 | |||||
2298 | redirectexit('action=pm;sa=manrules'); |
||||
2299 | } |
||||
2300 | } |
||||
2301 | |||||
2302 | /** |
||||
2303 | * Actually do the search of personal messages and show the results |
||||
2304 | * |
||||
2305 | * What it does: |
||||
2306 | * |
||||
2307 | * - accessed with ?action=pm;sa=search2 |
||||
2308 | * - checks user input and searches the pm table for messages matching the query. |
||||
2309 | * - uses the search_results sub template of the PersonalMessage template. |
||||
2310 | * - show the results of the search query. |
||||
2311 | */ |
||||
2312 | public function action_search2() |
||||
2313 | { |
||||
2314 | global $scripturl, $modSettings, $context, $txt; |
||||
2315 | |||||
2316 | // Make sure the server is able to do this right now |
||||
2317 | if (!empty($modSettings['loadavg_search']) && $modSettings['current_load'] >= $modSettings['loadavg_search']) |
||||
2318 | { |
||||
2319 | throw new Exception('loadavg_search_disabled', false); |
||||
2320 | } |
||||
2321 | |||||
2322 | // Some useful general permissions. |
||||
2323 | $context['can_send_pm'] = allowedTo('pm_send'); |
||||
2324 | |||||
2325 | // Extract all the search parameters if coming in from pagination, etc |
||||
2326 | $this->_searchParamsFromString(); |
||||
2327 | |||||
2328 | // Set a start for pagination |
||||
2329 | $context['start'] = $this->_req->getQuery('start', 'intval', 0); |
||||
2330 | |||||
2331 | // Set/clean search criteria |
||||
2332 | $this->_prepareSearchParams(); |
||||
2333 | |||||
2334 | $context['folder'] = empty($this->_search_params['sent_only']) ? 'inbox' : 'sent'; |
||||
2335 | |||||
2336 | // Searching for specific members |
||||
2337 | $userQuery = $this->_setUserQuery(); |
||||
2338 | |||||
2339 | // Set up the sorting variables... |
||||
2340 | $this->_setSortParams(); |
||||
2341 | |||||
2342 | // Sort out any labels we may be searching by. |
||||
2343 | $labelQuery = $this->_setLabelQuery(); |
||||
2344 | |||||
2345 | // Unfortunately, searching for words like this is going to be slow, so we're blocking them. |
||||
2346 | $blocklist_words = array('quote', 'the', 'is', 'it', 'are', 'if', 'in'); |
||||
2347 | |||||
2348 | // What are we actually searching for? |
||||
2349 | $this->_search_params['search'] = empty($this->_search_params['search']) ? $this->_req->post->search ?? '' : ($this->_search_params['search']); |
||||
2350 | |||||
2351 | // If nothing is left to search on - we set an error! |
||||
2352 | if (!isset($this->_search_params['search']) || $this->_search_params['search'] === '') |
||||
2353 | { |
||||
2354 | $context['search_errors']['invalid_search_string'] = true; |
||||
2355 | } |
||||
2356 | |||||
2357 | // Change non-word characters into spaces. |
||||
2358 | $stripped_query = preg_replace('~(?:[\x0B\0\x{A0}\t\r\s\n(){}\\[\\]<>!@$%^*.,:+=`\~\?/\\\\]+|&(?:amp|lt|gt|quot);)+~u', ' ', $this->_search_params['search']); |
||||
2359 | |||||
2360 | // Make the query lower case since it will case-insensitive anyway. |
||||
2361 | $stripped_query = un_htmlspecialchars(Util::strtolower($stripped_query)); |
||||
2362 | |||||
2363 | // Extract phrase parts first (e.g. some words "this is a phrase" some more words.) |
||||
2364 | preg_match_all('/(?:^|\s)([-]?)"([^"]+)"(?:$|\s)/', $stripped_query, $matches, PREG_PATTERN_ORDER); |
||||
2365 | $phraseArray = $matches[2]; |
||||
2366 | |||||
2367 | // Remove the phrase parts and extract the words. |
||||
2368 | $wordArray = preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~u', ' ', $this->_search_params['search']); |
||||
2369 | $wordArray = explode(' ', Util::htmlspecialchars(un_htmlspecialchars($wordArray), ENT_QUOTES)); |
||||
2370 | |||||
2371 | // A minus sign in front of a word excludes the word.... so... |
||||
2372 | $excludedWords = array(); |
||||
2373 | |||||
2374 | // Check for things like -"some words", but not "-some words". |
||||
2375 | foreach ($matches[1] as $index => $word) |
||||
2376 | { |
||||
2377 | if ($word === '-') |
||||
2378 | { |
||||
2379 | if (($word = trim($phraseArray[$index], "-_' ")) !== '' && !in_array($word, $blocklist_words)) |
||||
2380 | { |
||||
2381 | $excludedWords[] = $word; |
||||
2382 | } |
||||
2383 | |||||
2384 | unset($phraseArray[$index]); |
||||
2385 | } |
||||
2386 | } |
||||
2387 | |||||
2388 | // Now we look for -test, etc |
||||
2389 | foreach ($wordArray as $index => $word) |
||||
2390 | { |
||||
2391 | if (strpos(trim($word), '-') === 0) |
||||
2392 | { |
||||
2393 | if (($word = trim($word, "-_' ")) !== '' && !in_array($word, $blocklist_words)) |
||||
2394 | { |
||||
2395 | $excludedWords[] = $word; |
||||
2396 | } |
||||
2397 | |||||
2398 | unset($wordArray[$index]); |
||||
2399 | } |
||||
2400 | } |
||||
2401 | |||||
2402 | // The remaining words and phrases are all included. |
||||
2403 | $searchArray = array_merge($phraseArray, $wordArray); |
||||
2404 | |||||
2405 | // Trim everything and make sure there are no words that are the same. |
||||
2406 | foreach ($searchArray as $index => $value) |
||||
2407 | { |
||||
2408 | // Skip anything that's close to empty. |
||||
2409 | if (($searchArray[$index] = trim($value, "-_' ")) === '') |
||||
2410 | { |
||||
2411 | unset($searchArray[$index]); |
||||
2412 | } |
||||
2413 | // Skip blocked words. Make sure to note we skipped them as well |
||||
2414 | elseif (in_array($searchArray[$index], $blocklist_words)) |
||||
2415 | { |
||||
2416 | $foundBlockListedWords = true; |
||||
2417 | unset($searchArray[$index]); |
||||
2418 | |||||
2419 | } |
||||
2420 | |||||
2421 | if (isset($searchArray[$index])) |
||||
2422 | { |
||||
2423 | $searchArray[$index] = Util::strtolower(trim($value)); |
||||
2424 | |||||
2425 | if ($searchArray[$index] === '') |
||||
2426 | { |
||||
2427 | unset($searchArray[$index]); |
||||
2428 | } |
||||
2429 | else |
||||
2430 | { |
||||
2431 | // Sort out entities first. |
||||
2432 | $searchArray[$index] = Util::htmlspecialchars($searchArray[$index]); |
||||
2433 | } |
||||
2434 | } |
||||
2435 | } |
||||
2436 | |||||
2437 | $searchArray = array_slice(array_unique($searchArray), 0, 10); |
||||
2438 | |||||
2439 | // This contains *everything* |
||||
2440 | $searchWords = array_merge($searchArray, $excludedWords); |
||||
2441 | |||||
2442 | // Make sure at least one word is being searched for. |
||||
2443 | if (empty($searchArray)) |
||||
2444 | { |
||||
2445 | $context['search_errors']['invalid_search_string' . (empty($foundBlockListedWords) ? '' : '_blocklist')] = true; |
||||
2446 | } |
||||
2447 | |||||
2448 | // Sort out the search query so the user can edit it - if they want. |
||||
2449 | $context['search_params'] = $this->_search_params; |
||||
2450 | if (isset($context['search_params']['search'])) |
||||
2451 | { |
||||
2452 | $context['search_params']['search'] = Util::htmlspecialchars($context['search_params']['search']); |
||||
2453 | } |
||||
2454 | |||||
2455 | if (isset($context['search_params']['userspec'])) |
||||
2456 | { |
||||
2457 | $context['search_params']['userspec'] = Util::htmlspecialchars($context['search_params']['userspec']); |
||||
2458 | } |
||||
2459 | |||||
2460 | // Now we have all the parameters, combine them together for pagination and the like... |
||||
2461 | $context['params'] = $this->_compileURLparams(); |
||||
2462 | |||||
2463 | // Compile the subject query part. |
||||
2464 | $andQueryParts = array(); |
||||
2465 | foreach ($searchWords as $index => $word) |
||||
2466 | { |
||||
2467 | if ($word === '') |
||||
2468 | { |
||||
2469 | continue; |
||||
2470 | } |
||||
2471 | |||||
2472 | if ($this->_search_params['subject_only']) |
||||
2473 | { |
||||
2474 | $andQueryParts[] = 'pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '}'; |
||||
2475 | } |
||||
2476 | else |
||||
2477 | { |
||||
2478 | $andQueryParts[] = '(pm.subject' . (in_array($word, $excludedWords) ? ' NOT' : '') . ' LIKE {string:search_' . $index . '} ' . (in_array($word, $excludedWords) ? 'AND pm.body NOT' : 'OR pm.body') . ' LIKE {string:search_' . $index . '})'; |
||||
2479 | } |
||||
2480 | |||||
2481 | $this->_searchq_parameters ['search_' . $index] = '%' . strtr($word, array('_' => '\\_', '%' => '\\%')) . '%'; |
||||
2482 | } |
||||
2483 | |||||
2484 | $searchQuery = ' 1=1'; |
||||
2485 | if (!empty($andQueryParts)) |
||||
2486 | { |
||||
2487 | $searchQuery = implode(!empty($this->_search_params['searchtype']) && $this->_search_params['searchtype'] == 2 ? ' OR ' : ' AND ', $andQueryParts); |
||||
2488 | } |
||||
2489 | |||||
2490 | // Age limits? |
||||
2491 | $timeQuery = ''; |
||||
2492 | if (!empty($this->_search_params['minage'])) |
||||
2493 | { |
||||
2494 | $timeQuery .= ' AND pm.msgtime < ' . (time() - $this->_search_params['minage'] * 86400); |
||||
2495 | } |
||||
2496 | |||||
2497 | if (!empty($this->_search_params['maxage'])) |
||||
2498 | { |
||||
2499 | $timeQuery .= ' AND pm.msgtime > ' . (time() - $this->_search_params['maxage'] * 86400); |
||||
2500 | } |
||||
2501 | |||||
2502 | // If we have errors - return back to the first screen... |
||||
2503 | if (!empty($context['search_errors'])) |
||||
2504 | { |
||||
2505 | $this->_req->post->params = $context['params']; |
||||
2506 | |||||
2507 | $this->action_search(); |
||||
2508 | |||||
2509 | return false; |
||||
2510 | } |
||||
2511 | |||||
2512 | // Get the number of results. |
||||
2513 | $numResults = numPMSeachResults($userQuery, $labelQuery, $timeQuery, $searchQuery, $this->_searchq_parameters); |
||||
2514 | |||||
2515 | // Get all the matching message ids, senders and head pm nodes |
||||
2516 | [$foundMessages, $posters, $head_pms] = loadPMSearchMessages($userQuery, $labelQuery, $timeQuery, $searchQuery, $this->_searchq_parameters, $this->_search_params); |
||||
2517 | |||||
2518 | // Find the real head pm when in conversation view |
||||
2519 | if ($context['display_mode'] === self::DISPLAY_AS_CONVERSATION && !empty($head_pms)) |
||||
2520 | { |
||||
2521 | $real_pm_ids = loadPMSearchHeads($head_pms); |
||||
2522 | } |
||||
2523 | |||||
2524 | // Load the found user data |
||||
2525 | $posters = array_unique($posters); |
||||
2526 | if (!empty($posters)) |
||||
2527 | { |
||||
2528 | MembersList::load($posters); |
||||
2529 | } |
||||
2530 | |||||
2531 | // Sort out the page index. |
||||
2532 | $context['page_index'] = constructPageIndex('{scripturl}?action=pm;sa=search2;params=' . $context['params'], $this->_req->query->start, $numResults, $modSettings['search_results_per_page'], false); |
||||
2533 | |||||
2534 | $context['message_labels'] = array(); |
||||
2535 | $context['message_replied'] = array(); |
||||
2536 | $context['personal_messages'] = array(); |
||||
2537 | $context['first_label'] = array(); |
||||
2538 | |||||
2539 | // If we have results, we have work to do! |
||||
2540 | if (!empty($foundMessages)) |
||||
2541 | { |
||||
2542 | $recipients = array(); |
||||
2543 | [$context['message_labels'], $context['message_replied'], $context['message_unread'], $context['first_label']] = loadPMRecipientInfo($foundMessages, $recipients, $context['folder'], true); |
||||
2544 | |||||
2545 | // Prepare for the callback! |
||||
2546 | $search_results = loadPMSearchResults($foundMessages, $this->_search_params); |
||||
2547 | $counter = 0; |
||||
2548 | $bbc_parser = ParserWrapper::instance(); |
||||
2549 | foreach ($search_results as $row) |
||||
2550 | { |
||||
2551 | // If there's no subject, use the default. |
||||
2552 | $row['subject'] = $row['subject'] === '' ? $txt['no_subject'] : $row['subject']; |
||||
2553 | |||||
2554 | // Load this poster context info, if not there, then fill in the essentials... |
||||
2555 | $member = MembersList::get($row['id_member_from']); |
||||
2556 | $member->loadContext(true); |
||||
2557 | if ($member->isEmpty()) |
||||
2558 | { |
||||
2559 | $member['name'] = $row['from_name']; |
||||
2560 | $member['id'] = 0; |
||||
2561 | $member['group'] = $txt['guest_title']; |
||||
2562 | $member['link'] = $row['from_name']; |
||||
2563 | $member['email'] = ''; |
||||
2564 | $member['show_email'] = showEmailAddress(0); |
||||
2565 | $member['is_guest'] = true; |
||||
2566 | } |
||||
2567 | |||||
2568 | // Censor anything we don't want to see... |
||||
2569 | $row['body'] = censor($row['body']); |
||||
2570 | $row['subject'] = censor($row['subject']); |
||||
2571 | |||||
2572 | // Parse out any BBC... |
||||
2573 | $row['body'] = $bbc_parser->parsePM($row['body']); |
||||
2574 | |||||
2575 | // Highlight the hits |
||||
2576 | $body_highlighted = ''; |
||||
2577 | $subject_highlighted = ''; |
||||
2578 | foreach ($searchArray as $query) |
||||
2579 | { |
||||
2580 | // Fix the international characters in the keyword too. |
||||
2581 | $query = un_htmlspecialchars($query); |
||||
2582 | $query = trim($query, '\*+'); |
||||
2583 | $query = strtr(Util::htmlspecialchars($query), array('\\\'' => "'")); |
||||
2584 | |||||
2585 | $body_highlighted = preg_replace_callback('/((<[^>]*)|' . preg_quote(strtr($query, array("'" => ''')), '/') . ')/iu', |
||||
2586 | fn($matches) => $this->_highlighted_callback($matches), $row['body']); |
||||
2587 | $subject_highlighted = preg_replace('/(' . preg_quote($query, '/') . ')/iu', '<strong class="highlight">$1</strong>', $row['subject']); |
||||
2588 | } |
||||
2589 | |||||
2590 | // Set a link using the first label information |
||||
2591 | $href = $scripturl . '?action=pm;f=' . $context['folder'] . (isset($context['first_label'][$row['id_pm']]) ? ';l=' . $context['first_label'][$row['id_pm']] : '') . ';pmid=' . ($context['display_mode'] === self::DISPLAY_AS_CONVERSATION && isset($real_pm_ids[$head_pms[$row['id_pm']]]) && $context['folder'] === 'inbox' ? $real_pm_ids[$head_pms[$row['id_pm']]] : $row['id_pm']) . '#msg_' . $row['id_pm']; |
||||
2592 | |||||
2593 | $context['personal_messages'][] = [ |
||||
2594 | 'id' => $row['id_pm'], |
||||
2595 | 'member' => $member, |
||||
2596 | 'subject' => $subject_highlighted, |
||||
2597 | 'body' => $body_highlighted, |
||||
2598 | 'time' => standardTime($row['msgtime']), |
||||
2599 | 'html_time' => htmlTime($row['msgtime']), |
||||
2600 | 'timestamp' => forum_time(true, $row['msgtime']), |
||||
2601 | 'recipients' => &$recipients[$row['id_pm']], |
||||
2602 | 'labels' => &$context['message_labels'][$row['id_pm']], |
||||
2603 | 'fully_labeled' => (empty($context['message_labels'][$row['id_pm']]) ? 0 : count($context['message_labels'][$row['id_pm']])) === count($context['labels']), |
||||
2604 | 'is_replied_to' => &$context['message_replied'][$row['id_pm']], |
||||
2605 | 'href' => $href, |
||||
2606 | 'link' => '<a href="' . $href . '">' . $subject_highlighted . '</a>', |
||||
2607 | 'counter' => ++$counter, |
||||
2608 | 'pmbuttons' => $this->_setSearchPmButtons($row['id_pm'], $member), |
||||
2609 | ]; |
||||
2610 | } |
||||
2611 | } |
||||
2612 | |||||
2613 | // Finish off the context. |
||||
2614 | $context['page_title'] = $txt['pm_search_title']; |
||||
2615 | $context['sub_template'] = 'search_results'; |
||||
2616 | $context['menu_data_' . $context['pm_menu_id']]['current_area'] = 'search'; |
||||
2617 | $context['breadcrumbs'][] = [ |
||||
2618 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'search']), |
||||
2619 | 'name' => $txt['pm_search_bar_title'], |
||||
2620 | ]; |
||||
2621 | } |
||||
2622 | |||||
2623 | /** |
||||
2624 | * Return buttons for search results, used when viewing full message as result |
||||
2625 | * |
||||
2626 | * @param int $id of the PM |
||||
2627 | * @param ValuesContainer $member member information |
||||
2628 | * @return array[] |
||||
2629 | */ |
||||
2630 | private function _setSearchPmButtons($id, $member) |
||||
2631 | { |
||||
2632 | global $context; |
||||
2633 | |||||
2634 | $pmButtons = [ |
||||
2635 | // Reply, Quote |
||||
2636 | 'reply_button' => [ |
||||
2637 | 'text' => 'reply', |
||||
2638 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'send', 'f' => $context['folder'], 'pmsg' => $id, 'u' => $member['id']]) . ($context['current_label_id'] !== "-1" ? ';l=' . $context['current_label_id'] : ''), |
||||
2639 | 'class' => 'reply_button', |
||||
2640 | 'icon' => 'modify', |
||||
2641 | 'enabled' => !$member['is_guest'] && $context['can_send_pm'], |
||||
2642 | ], |
||||
2643 | 'quote_button' => [ |
||||
2644 | 'text' => 'quote', |
||||
2645 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'send', 'f' => $context['folder'], 'pmsg' => $id, 'quote' => '']) . ($context['current_label_id'] !== "-1" ? ';l=' . $context['current_label_id'] : '') . ($context['folder'] === 'sent' ? '' : ';u=' . $member['id']), |
||||
2646 | 'class' => 'quote_button', |
||||
2647 | 'icon' => 'quote', |
||||
2648 | 'enabled' => !$member['is_guest'] && $context['can_send_pm'], |
||||
2649 | ], |
||||
2650 | // This is for "forwarding" - even if the member is gone. |
||||
2651 | 'reply_quote_button' => [ |
||||
2652 | 'text' => 'reply_quote', |
||||
2653 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'send', 'f' => $context['folder'], 'pmsg' => $id, 'quote' => '']) . ($context['current_label_id'] !== "-1" ? ';l=' . $context['current_label_id'] : ''), |
||||
2654 | 'class' => 'reply_button', |
||||
2655 | 'icon' => 'modify', |
||||
2656 | 'enabled' => $member['is_guest'] && $context['can_send_pm'], |
||||
2657 | ] |
||||
2658 | ]; |
||||
2659 | |||||
2660 | // Drop any non-enabled ones |
||||
2661 | return array_filter($pmButtons, static fn($button) => !isset($button['enabled']) || (bool) $button['enabled']); |
||||
2662 | } |
||||
2663 | |||||
2664 | /** |
||||
2665 | * Extract search params from a string |
||||
2666 | * |
||||
2667 | * What it does: |
||||
2668 | * |
||||
2669 | * - When paging search results, reads and decodes the passed parameters |
||||
2670 | * - Places what it finds back in search_params |
||||
2671 | */ |
||||
2672 | private function _searchParamsFromString() |
||||
2673 | { |
||||
2674 | $this->_search_params = array(); |
||||
2675 | |||||
2676 | if (isset($this->_req->query->params) || isset($this->_req->post->params)) |
||||
2677 | { |
||||
2678 | // Feed it |
||||
2679 | $temp_params = $this->_req->query->params ?? $this->_req->post->params; |
||||
2680 | |||||
2681 | // Decode and replace the uri safe characters we added |
||||
2682 | $temp_params = base64_decode(str_replace(array('-', '_', '.'), array('+', '/', '='), $temp_params)); |
||||
2683 | |||||
2684 | $temp_params = explode('|"|', $temp_params); |
||||
2685 | foreach ($temp_params as $data) |
||||
2686 | { |
||||
2687 | [$k, $v] = array_pad(explode("|'|", $data), 2, ''); |
||||
2688 | $this->_search_params[$k] = $v; |
||||
2689 | } |
||||
2690 | } |
||||
2691 | |||||
2692 | return $this->_search_params; |
||||
2693 | } |
||||
2694 | |||||
2695 | /** |
||||
2696 | * Sets the search params for the query |
||||
2697 | * |
||||
2698 | * What it does: |
||||
2699 | * |
||||
2700 | * - Uses existing ones if coming from pagination or uses those passed from the search pm form |
||||
2701 | * - Validates passed params are valid |
||||
2702 | */ |
||||
2703 | private function _prepareSearchParams() |
||||
2704 | { |
||||
2705 | // Store whether simple search was used (needed if the user wants to do another query). |
||||
2706 | if (!isset($this->_search_params['advanced'])) |
||||
2707 | { |
||||
2708 | $this->_search_params['advanced'] = empty($this->_req->post->advanced) ? 0 : 1; |
||||
2709 | } |
||||
2710 | |||||
2711 | // 1 => 'allwords' (default, don't set as param), 2 => 'anywords'. |
||||
2712 | if (!empty($this->_search_params['searchtype']) || (!empty($this->_req->post->searchtype) && $this->_req->post->searchtype == 2)) |
||||
2713 | { |
||||
2714 | $this->_search_params['searchtype'] = 2; |
||||
2715 | } |
||||
2716 | |||||
2717 | // Minimum age of messages. Default to zero (don't set param in that case). |
||||
2718 | if (!empty($this->_search_params['minage']) || (!empty($this->_req->post->minage) && $this->_req->post->minage > 0)) |
||||
2719 | { |
||||
2720 | $this->_search_params['minage'] = empty($this->_search_params['minage']) ? (int) $this->_req->post->minage : (int) $this->_search_params['minage']; |
||||
2721 | } |
||||
2722 | |||||
2723 | // Maximum age of messages. Default to infinite (9999 days: param not set). |
||||
2724 | if (!empty($this->_search_params['maxage']) || (!empty($this->_req->post->maxage) && $this->_req->post->maxage < 9999)) |
||||
2725 | { |
||||
2726 | $this->_search_params['maxage'] = empty($this->_search_params['maxage']) ? (int) $this->_req->post->maxage : (int) $this->_search_params['maxage']; |
||||
2727 | } |
||||
2728 | |||||
2729 | // Default the username to a wildcard matching every user (*). |
||||
2730 | if (!empty($this->_search_params['userspec']) || (!empty($this->_req->post->userspec) && $this->_req->post->userspec !== '*')) |
||||
2731 | { |
||||
2732 | $this->_search_params['userspec'] = $this->_search_params['userspec'] ?? $this->_req->post->userspec; |
||||
2733 | } |
||||
2734 | |||||
2735 | // Search modifiers |
||||
2736 | $this->_search_params['subject_only'] = !empty($this->_search_params['subject_only']) || !empty($this->_req->post->subject_only); |
||||
2737 | $this->_search_params['show_complete'] = !empty($this->_search_params['show_complete']) || !empty($this->_req->post->show_complete); |
||||
2738 | $this->_search_params['sent_only'] = !empty($this->_search_params['sent_only']) || !empty($this->_req->post->sent_only); |
||||
2739 | } |
||||
2740 | |||||
2741 | /** |
||||
2742 | * Handles the parameters when searching on specific users |
||||
2743 | * |
||||
2744 | * What it does: |
||||
2745 | * |
||||
2746 | * - Returns the user query for use in the main search query |
||||
2747 | * - Sets the parameters for use in the query |
||||
2748 | * |
||||
2749 | * @return string |
||||
2750 | */ |
||||
2751 | private function _setUserQuery() |
||||
2752 | { |
||||
2753 | global $context; |
||||
2754 | |||||
2755 | // Hardcoded variables that can be tweaked if required. |
||||
2756 | $maxMembersToSearch = 500; |
||||
2757 | |||||
2758 | // Init to not be searching based on members |
||||
2759 | $userQuery = ''; |
||||
2760 | |||||
2761 | // If there's no specific user, then don't mention it in the main query. |
||||
2762 | if (!empty($this->_search_params['userspec'])) |
||||
2763 | { |
||||
2764 | // Set up, so we can search by username, wildcards, like, etc |
||||
2765 | $userString = strtr(Util::htmlspecialchars($this->_search_params['userspec'], ENT_QUOTES), array('"' => '"')); |
||||
2766 | $userString = strtr($userString, array('%' => '\%', '_' => '\_', '*' => '%', '?' => '_')); |
||||
2767 | |||||
2768 | preg_match_all('~"([^"]+)"~', $userString, $matches); |
||||
2769 | $possible_users = array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $userString))); |
||||
2770 | |||||
2771 | // Who matches those criteria? |
||||
2772 | require_once(SUBSDIR . '/Members.subs.php'); |
||||
2773 | $members = membersBy('member_names', array('member_names' => $possible_users)); |
||||
2774 | |||||
2775 | foreach ($possible_users as $key => $possible_user) |
||||
2776 | { |
||||
2777 | $this->_searchq_parameters['guest_user_name_implode_' . $key] = '{string_case_insensitive:' . $possible_user . '}'; |
||||
2778 | } |
||||
2779 | |||||
2780 | // Simply do nothing if there are too many members matching the criteria. |
||||
2781 | if (count($members) > $maxMembersToSearch) |
||||
2782 | { |
||||
2783 | $userQuery = ''; |
||||
2784 | } |
||||
2785 | elseif (count($members) === 0) |
||||
2786 | { |
||||
2787 | if ($context['folder'] === 'inbox') |
||||
2788 | { |
||||
2789 | $uq = array(); |
||||
2790 | $name = '{column_case_insensitive:pm.from_name}'; |
||||
2791 | foreach (array_keys($possible_users) as $key) |
||||
2792 | { |
||||
2793 | $uq[] = 'AND pm.id_member_from = 0 AND (' . $name . ' LIKE {string:guest_user_name_implode_' . $key . '})'; |
||||
2794 | } |
||||
2795 | |||||
2796 | $userQuery = implode(' ', $uq); |
||||
2797 | $this->_searchq_parameters['pm_from_name'] = $name; |
||||
2798 | } |
||||
2799 | else |
||||
2800 | { |
||||
2801 | $userQuery = ''; |
||||
2802 | } |
||||
2803 | } |
||||
2804 | else |
||||
2805 | { |
||||
2806 | $memberlist = array(); |
||||
2807 | foreach ($members as $id) |
||||
2808 | { |
||||
2809 | $memberlist[] = $id; |
||||
2810 | } |
||||
2811 | |||||
2812 | // Use the name as sent from or sent to |
||||
2813 | if ($context['folder'] === 'inbox') |
||||
2814 | { |
||||
2815 | $uq = array(); |
||||
2816 | $name = '{column_case_insensitive:pm.from_name}'; |
||||
2817 | |||||
2818 | foreach (array_keys($possible_users) as $key) |
||||
2819 | { |
||||
2820 | $uq[] = 'AND (pm.id_member_from IN ({array_int:member_list}) OR (pm.id_member_from = 0 AND (' . $name . ' LIKE {string:guest_user_name_implode_' . $key . '})))'; |
||||
2821 | } |
||||
2822 | |||||
2823 | $userQuery = implode(' ', $uq); |
||||
2824 | } |
||||
2825 | else |
||||
2826 | { |
||||
2827 | $userQuery = 'AND (pmr.id_member IN ({array_int:member_list}))'; |
||||
2828 | } |
||||
2829 | |||||
2830 | $this->_searchq_parameters['pm_from_name'] = '{column_case_insensitive:pm.from_name}'; |
||||
2831 | $this->_searchq_parameters['member_list'] = $memberlist; |
||||
2832 | } |
||||
2833 | } |
||||
2834 | |||||
2835 | return $userQuery; |
||||
2836 | } |
||||
2837 | |||||
2838 | /** |
||||
2839 | * Read / Set the sort parameters for the results listing |
||||
2840 | */ |
||||
2841 | private function _setSortParams() |
||||
2842 | { |
||||
2843 | $sort_columns = array( |
||||
2844 | 'pm.id_pm', |
||||
2845 | ); |
||||
2846 | |||||
2847 | if (empty($this->_search_params['sort']) && !empty($this->_req->post->sort)) |
||||
2848 | { |
||||
2849 | [$this->_search_params['sort'], $this->_search_params['sort_dir']] = array_pad(explode('|', $this->_req->post->sort), 2, ''); |
||||
2850 | } |
||||
2851 | |||||
2852 | $this->_search_params['sort'] = !empty($this->_search_params['sort']) && in_array($this->_search_params['sort'], $sort_columns) ? $this->_search_params['sort'] : 'pm.id_pm'; |
||||
2853 | $this->_search_params['sort_dir'] = !empty($this->_search_params['sort_dir']) && $this->_search_params['sort_dir'] === 'asc' ? 'asc' : 'desc'; |
||||
2854 | } |
||||
2855 | |||||
2856 | /** |
||||
2857 | * Handles the parameters when searching on specific labels |
||||
2858 | * |
||||
2859 | * What it does: |
||||
2860 | * |
||||
2861 | * - Returns the label query for use in the main search query |
||||
2862 | * - Sets the parameters for use in the query |
||||
2863 | * |
||||
2864 | * @return string |
||||
2865 | * @throws \Exception |
||||
2866 | */ |
||||
2867 | private function _setLabelQuery() |
||||
2868 | { |
||||
2869 | global $context; |
||||
2870 | |||||
2871 | $db = database(); |
||||
2872 | |||||
2873 | $labelQuery = ''; |
||||
2874 | |||||
2875 | if ($context['folder'] === 'inbox' && !empty($this->_search_params['advanced']) && $context['currently_using_labels']) |
||||
2876 | { |
||||
2877 | // Came here from pagination? Put them back into $_REQUEST for sanitation. |
||||
2878 | if (isset($this->_search_params['labels'])) |
||||
2879 | { |
||||
2880 | $this->_req->post->searchlabel = explode(',', $this->_search_params['labels']); |
||||
2881 | } |
||||
2882 | |||||
2883 | // Assuming we have some labels - make them all integers. |
||||
2884 | if (!empty($this->_req->post->searchlabel) && is_array($this->_req->post->searchlabel)) |
||||
2885 | { |
||||
2886 | $this->_req->post->searchlabel = array_map('intval', $this->_req->post->searchlabel); |
||||
2887 | } |
||||
2888 | else |
||||
2889 | { |
||||
2890 | $this->_req->post->searchlabel = array(); |
||||
2891 | } |
||||
2892 | |||||
2893 | // Now that everything is cleaned up a bit, make the labels a param. |
||||
2894 | $this->_search_params['labels'] = implode(',', $this->_req->post->searchlabel); |
||||
2895 | |||||
2896 | // No labels selected? That must be an error! |
||||
2897 | if (empty($this->_req->post->searchlabel)) |
||||
2898 | { |
||||
2899 | $context['search_errors']['no_labels_selected'] = true; |
||||
2900 | } |
||||
2901 | // Otherwise prepare the query! |
||||
2902 | elseif (count($this->_req->post->searchlabel) !== count($context['labels'])) |
||||
2903 | { |
||||
2904 | $labelQuery = ' |
||||
2905 | AND {raw:label_implode}'; |
||||
2906 | |||||
2907 | $labelStatements = array(); |
||||
2908 | foreach ($this->_req->post->searchlabel as $label) |
||||
2909 | { |
||||
2910 | $labelStatements[] = $db->quote('FIND_IN_SET({string:label}, pmr.labels) != 0', array('label' => $label,)); |
||||
2911 | } |
||||
2912 | |||||
2913 | $this->_searchq_parameters ['label_implode'] = '(' . implode(' OR ', $labelStatements) . ')'; |
||||
2914 | } |
||||
2915 | } |
||||
2916 | |||||
2917 | return $labelQuery; |
||||
2918 | } |
||||
2919 | |||||
2920 | /** |
||||
2921 | * Encodes search params in a URL-compatible way |
||||
2922 | * |
||||
2923 | * @return string - the encoded string to be appended to the URL |
||||
2924 | */ |
||||
2925 | private function _compileURLparams() |
||||
2926 | { |
||||
2927 | $encoded = array(); |
||||
2928 | |||||
2929 | // Now we have all the parameters, combine them together for pagination and the like... |
||||
2930 | foreach ($this->_search_params as $k => $v) |
||||
2931 | { |
||||
2932 | $encoded[] = $k . "|'|" . $v; |
||||
2933 | } |
||||
2934 | |||||
2935 | // Base64 encode, then replace +/= with uri safe ones that can be reverted |
||||
2936 | return str_replace(array('+', '/', '='), array('-', '_', '.'), base64_encode(implode('|"|', $encoded))); |
||||
2937 | } |
||||
2938 | |||||
2939 | /** |
||||
2940 | * Allows searching personal messages. |
||||
2941 | * |
||||
2942 | * What it does: |
||||
2943 | * |
||||
2944 | * - accessed with ?action=pm;sa=search |
||||
2945 | * - shows the screen to search PMs (?action=pm;sa=search) |
||||
2946 | * - uses the search sub template of the PersonalMessage template. |
||||
2947 | * - decodes and loads search parameters given in the URL (if any). |
||||
2948 | * - the form redirects to index.php?action=pm;sa=search2. |
||||
2949 | * |
||||
2950 | * @uses search sub template |
||||
2951 | */ |
||||
2952 | public function action_search() |
||||
2953 | { |
||||
2954 | global $context, $txt; |
||||
2955 | |||||
2956 | // If they provided some search parameters, we need to extract them |
||||
2957 | if (isset($this->_req->post->params)) |
||||
2958 | { |
||||
2959 | $context['search_params'] = $this->_searchParamsFromString(); |
||||
2960 | } |
||||
2961 | |||||
2962 | // Set up the search criteria, type, what, age, etc |
||||
2963 | if (isset($this->_req->post->search)) |
||||
2964 | { |
||||
2965 | $context['search_params']['search'] = un_htmlspecialchars($this->_req->post->search); |
||||
2966 | $context['search_params']['search'] = htmlspecialchars($context['search_params']['search'], ENT_COMPAT); |
||||
2967 | } |
||||
2968 | |||||
2969 | if (isset($context['search_params']['userspec'])) |
||||
2970 | { |
||||
2971 | $context['search_params']['userspec'] = htmlspecialchars($context['search_params']['userspec'], ENT_COMPAT); |
||||
2972 | } |
||||
2973 | |||||
2974 | // 1 => 'allwords' / 2 => 'anywords'. |
||||
2975 | if (!empty($context['search_params']['searchtype'])) |
||||
2976 | { |
||||
2977 | $context['search_params']['searchtype'] = 2; |
||||
2978 | } |
||||
2979 | |||||
2980 | // Minimum and Maximum age of the message |
||||
2981 | if (!empty($context['search_params']['minage'])) |
||||
2982 | { |
||||
2983 | $context['search_params']['minage'] = (int) $context['search_params']['minage']; |
||||
2984 | } |
||||
2985 | |||||
2986 | if (!empty($context['search_params']['maxage'])) |
||||
2987 | { |
||||
2988 | $context['search_params']['maxage'] = (int) $context['search_params']['maxage']; |
||||
2989 | } |
||||
2990 | |||||
2991 | $context['search_params']['show_complete'] = !empty($context['search_params']['show_complete']); |
||||
2992 | $context['search_params']['subject_only'] = !empty($context['search_params']['subject_only']); |
||||
2993 | |||||
2994 | // Create the array of labels to be searched. |
||||
2995 | $context['search_labels'] = array(); |
||||
2996 | $searchedLabels = isset($context['search_params']['labels']) && $context['search_params']['labels'] != '' ? explode(',', $context['search_params']['labels']) : array(); |
||||
2997 | foreach ($context['labels'] as $label) |
||||
2998 | { |
||||
2999 | $context['search_labels'][] = array( |
||||
3000 | 'id' => $label['id'], |
||||
3001 | 'name' => $label['name'], |
||||
3002 | 'checked' => empty($searchedLabels) || in_array($label['id'], $searchedLabels), |
||||
3003 | ); |
||||
3004 | } |
||||
3005 | |||||
3006 | // Are all the labels checked? |
||||
3007 | $context['check_all'] = empty($searchedLabels) || count($context['search_labels']) === count($searchedLabels); |
||||
3008 | |||||
3009 | // Load the error text strings if there were errors in the search. |
||||
3010 | if (!empty($context['search_errors'])) |
||||
3011 | { |
||||
3012 | Txt::load('Errors'); |
||||
3013 | $context['search_errors']['messages'] = array(); |
||||
3014 | foreach ($context['search_errors'] as $search_error => $dummy) |
||||
3015 | { |
||||
3016 | if ($search_error === 'messages') |
||||
3017 | { |
||||
3018 | continue; |
||||
3019 | } |
||||
3020 | |||||
3021 | $context['search_errors']['messages'][] = $txt['error_' . $search_error]; |
||||
3022 | } |
||||
3023 | } |
||||
3024 | |||||
3025 | $context['page_title'] = $txt['pm_search_title']; |
||||
3026 | $context['sub_template'] = 'search'; |
||||
3027 | $context['breadcrumbs'][] = [ |
||||
3028 | 'url' => getUrl('action', ['action' => 'pm', 'sa' => 'search']), |
||||
3029 | 'name' => $txt['pm_search_bar_title'], |
||||
3030 | ]; |
||||
3031 | } |
||||
3032 | |||||
3033 | /** |
||||
3034 | * Used to highlight body text with strings that match the search term |
||||
3035 | * |
||||
3036 | * - Callback function used in $body_highlighted |
||||
3037 | * |
||||
3038 | * @param string[] $matches |
||||
3039 | * |
||||
3040 | * @return string |
||||
3041 | */ |
||||
3042 | private function _highlighted_callback($matches) |
||||
3043 | { |
||||
3044 | return isset($matches[2]) && $matches[2] === $matches[1] ? stripslashes($matches[1]) : '<strong class="highlight">' . $matches[1] . '</strong>'; |
||||
3045 | } |
||||
3046 | |||||
3047 | /** |
||||
3048 | * Allows the user to mark a personal message as unread, so they remember to come back to it |
||||
3049 | */ |
||||
3050 | public function action_markunread() |
||||
3051 | { |
||||
3052 | global $context; |
||||
3053 | |||||
3054 | checkSession('request'); |
||||
3055 | |||||
3056 | $pmsg = $this->_req->getQuery('pmsg', 'intval', null); |
||||
3057 | |||||
3058 | // Marking a message as unread, we need a message that was sent to them |
||||
3059 | // Can't mark your own reply as unread, that would be weird |
||||
3060 | if (!is_null($pmsg) && checkPMReceived($pmsg)) |
||||
3061 | { |
||||
3062 | // Make sure this is accessible, should be of course |
||||
3063 | if (!isAccessiblePM($pmsg, 'inbox')) |
||||
3064 | { |
||||
3065 | throw new Exception('no_access', false); |
||||
3066 | } |
||||
3067 | |||||
3068 | // Well then, you get to hear about it all over again |
||||
3069 | markMessagesUnread($pmsg); |
||||
3070 | } |
||||
3071 | |||||
3072 | // Back to the folder. |
||||
3073 | redirectexit($context['current_label_redirect']); |
||||
3074 | } |
||||
3075 | } |
||||
3076 |