1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This class takes care of deleting and restoring messages in boards |
5
|
|
|
* that means posts and topics |
6
|
|
|
* |
7
|
|
|
* @package ElkArte Forum |
8
|
|
|
* @copyright ElkArte Forum contributors |
9
|
|
|
* @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
10
|
|
|
* |
11
|
|
|
* This file contains code covered by: |
12
|
|
|
* copyright: 2011 Simple Machines (http://www.simplemachines.org) |
13
|
|
|
* |
14
|
|
|
* @version 2.0 dev |
15
|
|
|
* |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
namespace ElkArte; |
19
|
|
|
|
20
|
|
|
use ElkArte\Exceptions\Exception; |
21
|
|
|
use ElkArte\Helper\ValuesContainer; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Class MessagesDelete |
25
|
|
|
* |
26
|
|
|
* Methods for deleting and restoring messages in boards |
27
|
|
|
*/ |
28
|
|
|
class MessagesDelete |
29
|
|
|
{ |
30
|
|
|
/** @var ValuesContainer The current user deleting something */ |
31
|
|
|
protected $user; |
32
|
|
|
|
33
|
|
|
/** @var int[] Id of the messages not found. */ |
34
|
|
|
private $_unfound_messages = []; |
35
|
|
|
|
36
|
|
|
/** @var int[] Id of the topics that should be restored */ |
37
|
|
|
private $_topics_to_restore = []; |
38
|
|
|
|
39
|
|
|
/** @var int The board id of the recycle board */ |
40
|
|
|
private $_recycle_board; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Initialize the class! :P |
44
|
|
|
* |
45
|
|
|
* @param int|bool $recycle_enabled if the recycling is enabled. |
46
|
|
|
* @param int|null $recycle_board the id of the recycle board (if any) |
47
|
|
|
* @param User $user the user::info making the request |
48
|
|
|
*/ |
49
|
|
|
public function __construct($recycle_enabled, $recycle_board, $user = null) |
50
|
|
|
{ |
51
|
|
|
$this->_recycle_board = $recycle_enabled ? (int) $recycle_board : null; |
52
|
|
|
|
53
|
|
|
$this->user = $user ?? User::$info; |
|
|
|
|
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Restores a bunch of messages the recycle bin to the appropriate board. |
58
|
|
|
* If any "first message" is within the array, it is added to the list of |
59
|
|
|
* topics to restore (see MessagesDelete::restoreTopics) |
60
|
|
|
* |
61
|
|
|
* @param int[] $msgs_id Messages to restore |
62
|
|
|
* |
63
|
|
|
* @return array|void |
64
|
|
|
*/ |
65
|
|
|
public function restoreMessages($msgs_id) |
66
|
|
|
{ |
67
|
|
|
$msgs = array(); |
68
|
|
|
foreach ($msgs_id as $msg) |
69
|
|
|
{ |
70
|
|
|
$msg = (int) $msg; |
71
|
|
|
if (!empty($msg)) |
72
|
|
|
{ |
73
|
|
|
$msgs[] = $msg; |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
if (empty($msgs)) |
78
|
|
|
{ |
79
|
|
|
return; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
$db = database(); |
83
|
|
|
|
84
|
|
|
// Get the id_previous_board and id_previous_topic. |
85
|
|
|
$actioned_messages = []; |
86
|
|
|
$previous_topics = []; |
87
|
|
|
$db->fetchQuery(' |
88
|
|
|
SELECT |
89
|
|
|
m.id_topic, m.id_msg, m.id_board, m.subject, m.id_member, t.id_previous_board, t.id_previous_topic, |
90
|
|
|
t.id_first_msg, b.count_posts, COALESCE(pt.id_board, 0) AS possible_prev_board |
91
|
|
|
FROM {db_prefix}messages AS m |
92
|
|
|
INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) |
93
|
|
|
INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board) |
94
|
|
|
LEFT JOIN {db_prefix}topics AS pt ON (pt.id_topic = t.id_previous_topic) |
95
|
|
|
WHERE m.id_msg IN ({array_int:messages})', |
96
|
|
|
array( |
97
|
|
|
'messages' => $msgs, |
98
|
|
|
) |
99
|
|
|
)->fetch_callback( |
100
|
|
|
function ($row) use (&$actioned_messages, &$previous_topics) { |
101
|
|
|
// Restoring the first post means topic. |
102
|
|
|
if ((int) $row['id_msg'] === (int) $row['id_first_msg'] && (int) $row['id_previous_topic'] === (int) $row['id_topic']) |
103
|
|
|
{ |
104
|
|
|
$this->_topics_to_restore[] = $row['id_topic']; |
105
|
|
|
return; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
// Don't know where it's going? |
109
|
|
|
if (empty($row['id_previous_topic'])) |
110
|
|
|
{ |
111
|
|
|
$this->_unfound_messages[$row['id_msg']] = $row['subject']; |
112
|
|
|
return; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
$previous_topics[] = $row['id_previous_topic']; |
116
|
|
|
if (empty($actioned_messages[$row['id_previous_topic']])) |
117
|
|
|
{ |
118
|
|
|
$actioned_messages[$row['id_previous_topic']] = array( |
119
|
|
|
'msgs' => array(), |
120
|
|
|
'count_posts' => (int) $row['count_posts'], |
121
|
|
|
'subject' => $row['subject'], |
122
|
|
|
'previous_board' => $row['id_previous_board'], |
123
|
|
|
'possible_prev_board' => $row['possible_prev_board'], |
124
|
|
|
'current_topic' => (int) $row['id_topic'], |
125
|
|
|
'current_board' => (int) $row['id_board'], |
126
|
|
|
'members' => array(), |
127
|
|
|
); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
$actioned_messages[$row['id_previous_topic']]['msgs'][$row['id_msg']] = $row['subject']; |
131
|
|
|
if ($row['id_member']) |
132
|
|
|
{ |
133
|
|
|
$actioned_messages[$row['id_previous_topic']]['members'][] = (int) $row['id_member']; |
134
|
|
|
} |
135
|
|
|
} |
136
|
|
|
); |
137
|
|
|
|
138
|
|
|
// Check for topics we are going to fully restore. |
139
|
|
|
foreach (array_keys($actioned_messages) as $topic) |
140
|
|
|
{ |
141
|
|
|
if (in_array($topic, $this->_topics_to_restore)) |
142
|
|
|
{ |
143
|
|
|
unset($actioned_messages[$topic]); |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
// Load any previous topics to check they exist. |
148
|
|
|
if (!empty($previous_topics)) |
149
|
|
|
{ |
150
|
|
|
$previous_topics = array(); |
151
|
|
|
$db->fetchQuery(' |
152
|
|
|
SELECT |
153
|
|
|
t.id_topic, t.id_board, m.subject |
154
|
|
|
FROM {db_prefix}topics AS t |
155
|
|
|
INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) |
156
|
|
|
WHERE t.id_topic IN ({array_int:previous_topics})', |
157
|
|
|
array( |
158
|
|
|
'previous_topics' => $previous_topics, |
159
|
|
|
) |
160
|
|
|
)->fetch_callback( |
161
|
|
|
static function ($row) use (&$previous_topics) { |
162
|
|
|
$previous_topics[$row['id_topic']] = array( |
163
|
|
|
'board' => $row['id_board'], |
164
|
|
|
'subject' => $row['subject'], |
165
|
|
|
); |
166
|
|
|
} |
167
|
|
|
); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
// Restore each topic. |
171
|
|
|
$messages = array(); |
172
|
|
|
foreach ($actioned_messages as $topic => $data) |
173
|
|
|
{ |
174
|
|
|
// If we have topics we are going to restore the whole lot ignore them. |
175
|
|
|
if (in_array($topic, $this->_topics_to_restore)) |
176
|
|
|
{ |
177
|
|
|
unset($actioned_messages[$topic]); |
178
|
|
|
continue; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
// Move the posts back then! |
182
|
|
|
if (isset($previous_topics[$topic])) |
183
|
|
|
{ |
184
|
|
|
$this->_mergePosts(array_keys($data['msgs']), $data['current_topic'], $topic); |
185
|
|
|
|
186
|
|
|
// Log em. |
187
|
|
|
logAction('restore_posts', array('topic' => $topic, 'subject' => $previous_topics[$topic]['subject'], 'board' => empty($data['previous_board']) ? $data['possible_prev_board'] : $data['previous_board'])); |
188
|
|
|
$messages = array_merge(array_keys($data['msgs']), $messages); |
189
|
|
|
} |
190
|
|
|
else |
191
|
|
|
{ |
192
|
|
|
foreach ($data['msgs'] as $msg) |
193
|
|
|
{ |
194
|
|
|
$this->_unfound_messages[$msg['id']] = $msg['subject']; |
195
|
|
|
} |
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
// Put the icons back. |
200
|
|
|
if (!empty($messages)) |
201
|
|
|
{ |
202
|
|
|
$db->query('', ' |
203
|
|
|
UPDATE {db_prefix}messages |
204
|
|
|
SET icon = {string:icon} |
205
|
|
|
WHERE id_msg IN ({array_int:messages})', |
206
|
|
|
array( |
207
|
|
|
'icon' => 'xx', |
208
|
|
|
'messages' => $messages, |
209
|
|
|
) |
210
|
|
|
); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
return $actioned_messages; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Take a load of messages from one place and stick them in a topic |
218
|
|
|
* |
219
|
|
|
* @param int[] $msgs |
220
|
|
|
* @param int $from_topic |
221
|
|
|
* @param int $target_topic |
222
|
|
|
*/ |
223
|
|
|
private function _mergePosts($msgs, $from_topic, $target_topic) |
224
|
|
|
{ |
225
|
|
|
$db = database(); |
226
|
|
|
|
227
|
|
|
// @todo This really needs to be rewritten to take a load of messages from ANY topic, it's also inefficient. |
228
|
|
|
|
229
|
|
|
// Is it an array? |
230
|
|
|
if (!is_array($msgs)) |
|
|
|
|
231
|
|
|
{ |
232
|
|
|
$msgs = array($msgs); |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
// Lets make sure they are int. |
236
|
|
|
$msgs = array_map(static fn($value) => (int) $value, $msgs); |
237
|
|
|
|
238
|
|
|
// Get the source information. |
239
|
|
|
$request = $db->query('', ' |
240
|
|
|
SELECT |
241
|
|
|
t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts |
242
|
|
|
FROM {db_prefix}topics AS t |
243
|
|
|
INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) |
244
|
|
|
WHERE t.id_topic = {int:from_topic}', |
245
|
|
|
array( |
246
|
|
|
'from_topic' => $from_topic, |
247
|
|
|
) |
248
|
|
|
); |
249
|
|
|
[$from_board, $from_first_msg, $from_replies, $from_unapproved_posts] = $request->fetch_row(); |
250
|
|
|
$request->free_result(); |
251
|
|
|
|
252
|
|
|
// Get some target topic and board stats. |
253
|
|
|
$request = $db->query('', ' |
254
|
|
|
SELECT |
255
|
|
|
t.id_board, t.id_first_msg, t.num_replies, t.unapproved_posts, b.count_posts |
256
|
|
|
FROM {db_prefix}topics AS t |
257
|
|
|
INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) |
258
|
|
|
WHERE t.id_topic = {int:target_topic}', |
259
|
|
|
array( |
260
|
|
|
'target_topic' => $target_topic, |
261
|
|
|
) |
262
|
|
|
); |
263
|
|
|
[$target_board, $target_first_msg, $target_replies, $target_unapproved_posts, $count_posts] = $request->fetch_row(); |
264
|
|
|
$request->free_result(); |
265
|
|
|
|
266
|
|
|
// Lets see if the board that we are returning to has post count enabled. |
267
|
|
|
if (empty($count_posts)) |
268
|
|
|
{ |
269
|
|
|
// Lets get the members that need their post count restored. |
270
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
271
|
|
|
$db->fetchQuery(' |
272
|
|
|
SELECT id_member |
273
|
|
|
FROM {db_prefix}messages |
274
|
|
|
WHERE id_msg IN ({array_int:messages}) |
275
|
|
|
AND approved = {int:is_approved}', |
276
|
|
|
array( |
277
|
|
|
'messages' => $msgs, |
278
|
|
|
'is_approved' => 1, |
279
|
|
|
) |
280
|
|
|
)->fetch_callback( |
281
|
|
|
static function ($row) { |
282
|
|
|
updateMemberData($row['id_member'], array('posts' => '+')); |
283
|
|
|
} |
284
|
|
|
); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
// Time to move the messages. |
288
|
|
|
$db->query('', ' |
289
|
|
|
UPDATE {db_prefix}messages |
290
|
|
|
SET |
291
|
|
|
id_topic = {int:target_topic}, |
292
|
|
|
id_board = {int:target_board}, |
293
|
|
|
icon = {string:icon} |
294
|
|
|
WHERE id_msg IN({array_int:msgs})', |
295
|
|
|
array( |
296
|
|
|
'target_topic' => $target_topic, |
297
|
|
|
'target_board' => $target_board, |
298
|
|
|
'icon' => $target_board == $this->_recycle_board ? 'recycled' : 'xx', |
299
|
|
|
'msgs' => $msgs, |
300
|
|
|
) |
301
|
|
|
); |
302
|
|
|
|
303
|
|
|
// Fix the id_first_msg and id_last_msg for the target topic. |
304
|
|
|
$target_topic_data = array( |
305
|
|
|
'num_replies' => 0, |
306
|
|
|
'unapproved_posts' => 0, |
307
|
|
|
'id_first_msg' => 9999999999, |
308
|
|
|
); |
309
|
|
|
$db->fetchQuery(' |
310
|
|
|
SELECT |
311
|
|
|
MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved |
312
|
|
|
FROM {db_prefix}messages |
313
|
|
|
WHERE id_topic = {int:target_topic} |
314
|
|
|
GROUP BY id_topic, approved |
315
|
|
|
ORDER BY approved ASC |
316
|
|
|
LIMIT 2', |
317
|
|
|
array( |
318
|
|
|
'target_topic' => $target_topic, |
319
|
|
|
) |
320
|
|
|
)->fetch_callback( |
321
|
|
|
static function ($row) use (&$target_topic_data) { |
322
|
|
|
if ($row['id_first_msg'] < $target_topic_data['id_first_msg']) |
323
|
|
|
{ |
324
|
|
|
$target_topic_data['id_first_msg'] = $row['id_first_msg']; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
$target_topic_data['id_last_msg'] = $row['id_last_msg']; |
328
|
|
|
if (!$row['approved']) |
329
|
|
|
{ |
330
|
|
|
$target_topic_data['unapproved_posts'] = $row['message_count']; |
331
|
|
|
} |
332
|
|
|
else |
333
|
|
|
{ |
334
|
|
|
$target_topic_data['num_replies'] = max(0, $row['message_count'] - 1); |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
); |
338
|
|
|
|
339
|
|
|
// We have a new post count for the board. |
340
|
|
|
require_once(SUBSDIR . '/Boards.subs.php'); |
341
|
|
|
incrementBoard($target_board, array( |
342
|
|
|
'num_posts' => $target_topic_data['num_replies'] - $target_replies, // Lets keep in mind that the first message in a topic counts towards num_replies in a board. |
343
|
|
|
'unapproved_posts' => $target_topic_data['unapproved_posts'] - $target_unapproved_posts, |
344
|
|
|
)); |
345
|
|
|
|
346
|
|
|
// In some cases we merged the only post in a topic so the topic data is left behind in the topic table. |
347
|
|
|
$request = $db->query('', ' |
348
|
|
|
SELECT |
349
|
|
|
id_topic |
350
|
|
|
FROM {db_prefix}messages |
351
|
|
|
WHERE id_topic = {int:from_topic}', |
352
|
|
|
array( |
353
|
|
|
'from_topic' => $from_topic, |
354
|
|
|
) |
355
|
|
|
); |
356
|
|
|
|
357
|
|
|
// Remove the topic if it doesn't have any messages. |
358
|
|
|
$topic_exists = true; |
359
|
|
|
if ($request->num_rows() === 0) |
360
|
|
|
{ |
361
|
|
|
require_once(SUBSDIR . '/Topic.subs.php'); |
362
|
|
|
removeTopics($from_topic, false, true); |
363
|
|
|
$topic_exists = false; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
$request->free_result(); |
367
|
|
|
|
368
|
|
|
// Recycled topic. |
369
|
|
|
if ($topic_exists) |
370
|
|
|
{ |
371
|
|
|
// Fix the id_first_msg and id_last_msg for the source topic. |
372
|
|
|
$source_topic_data = array( |
373
|
|
|
'num_replies' => 0, |
374
|
|
|
'unapproved_posts' => 0, |
375
|
|
|
'id_first_msg' => 9999999999, |
376
|
|
|
); |
377
|
|
|
|
378
|
|
|
$db->fetchQuery(' |
379
|
|
|
SELECT |
380
|
|
|
MIN(id_msg) AS id_first_msg, MAX(id_msg) AS id_last_msg, COUNT(*) AS message_count, approved, subject |
381
|
|
|
FROM {db_prefix}messages |
382
|
|
|
WHERE id_topic = {int:from_topic} |
383
|
|
|
GROUP BY id_topic, approved |
384
|
|
|
ORDER BY approved ASC |
385
|
|
|
LIMIT 2', |
386
|
|
|
array( |
387
|
|
|
'from_topic' => $from_topic, |
388
|
|
|
) |
389
|
|
|
)->fetch_callback( |
390
|
|
|
static function ($row) use (&$source_topic_data) { |
391
|
|
|
if ($row['id_first_msg'] < $source_topic_data['id_first_msg']) |
392
|
|
|
{ |
393
|
|
|
$source_topic_data['id_first_msg'] = $row['id_first_msg']; |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
$source_topic_data['id_last_msg'] = $row['id_last_msg']; |
397
|
|
|
if (!$row['approved']) |
398
|
|
|
{ |
399
|
|
|
$source_topic_data['unapproved_posts'] = $row['message_count']; |
400
|
|
|
} |
401
|
|
|
else |
402
|
|
|
{ |
403
|
|
|
$source_topic_data['num_replies'] = max(0, $row['message_count'] - 1); |
404
|
|
|
} |
405
|
|
|
} |
406
|
|
|
); |
407
|
|
|
|
408
|
|
|
// Update the topic details for the source topic. |
409
|
|
|
setTopicAttribute($from_topic, array( |
410
|
|
|
'id_first_msg' => $source_topic_data['id_first_msg'], |
411
|
|
|
'id_last_msg' => $source_topic_data['id_last_msg'], |
412
|
|
|
'num_replies' => $source_topic_data['num_replies'], |
413
|
|
|
'unapproved_posts' => $source_topic_data['unapproved_posts'], |
414
|
|
|
)); |
415
|
|
|
|
416
|
|
|
// We have a new post count for the source board. |
417
|
|
|
incrementBoard($target_board, array( |
418
|
|
|
'num_posts' => $source_topic_data['num_replies'] - $from_replies, // Lets keep in mind that the first message in a topic counts towards num_replies in a board. |
419
|
|
|
'unapproved_posts' => $source_topic_data['unapproved_posts'] - $from_unapproved_posts, |
420
|
|
|
)); |
421
|
|
|
} |
422
|
|
|
|
423
|
|
|
// Finally get around to updating the destination topic, now all indexes etc on the source are fixed. |
424
|
|
|
setTopicAttribute($target_topic, array( |
425
|
|
|
'id_first_msg' => $target_topic_data['id_first_msg'], |
426
|
|
|
'id_last_msg' => $target_topic_data['id_last_msg'], |
427
|
|
|
'num_replies' => $target_topic_data['num_replies'], |
428
|
|
|
'unapproved_posts' => $target_topic_data['unapproved_posts'], |
429
|
|
|
)); |
430
|
|
|
|
431
|
|
|
// Need it to update some stats. |
432
|
|
|
require_once(SUBSDIR . '/Post.subs.php'); |
433
|
|
|
require_once(SUBSDIR . '/Topic.subs.php'); |
434
|
|
|
require_once(SUBSDIR . '/Messages.subs.php'); |
435
|
|
|
|
436
|
|
|
// Update stats. |
437
|
|
|
updateTopicStats(); |
438
|
|
|
updateMessageStats(); |
439
|
|
|
|
440
|
|
|
// Subject cache? |
441
|
|
|
$cache_updates = array(); |
442
|
|
|
if ($target_first_msg != $target_topic_data['id_first_msg']) |
443
|
|
|
{ |
444
|
|
|
$cache_updates[] = $target_topic_data['id_first_msg']; |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
if (!empty($source_topic_data['id_first_msg']) && $from_first_msg != $source_topic_data['id_first_msg']) |
448
|
|
|
{ |
449
|
|
|
$cache_updates[] = $source_topic_data['id_first_msg']; |
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
if (!empty($cache_updates)) |
453
|
|
|
{ |
454
|
|
|
require_once(SUBSDIR . '/Messages.subs.php'); |
455
|
|
|
$db->fetchQuery(' |
456
|
|
|
SELECT |
457
|
|
|
id_topic, subject |
458
|
|
|
FROM {db_prefix}messages |
459
|
|
|
WHERE id_msg IN ({array_int:first_messages})', |
460
|
|
|
array( |
461
|
|
|
'first_messages' => $cache_updates, |
462
|
|
|
) |
463
|
|
|
)->fetch_callback( |
464
|
|
|
static function ($row) { |
465
|
|
|
updateSubjectStats($row['id_topic'], $row['subject']); |
466
|
|
|
} |
467
|
|
|
); |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
updateLastMessages(array($from_board, $target_board)); |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
/** |
474
|
|
|
* Prepares topics to be restored from the recycle bin to the appropriate board |
475
|
|
|
* |
476
|
|
|
* @param int[] $topics_id Topics to restore |
477
|
|
|
*/ |
478
|
|
|
public function restoreTopics($topics_id) |
479
|
|
|
{ |
480
|
|
|
foreach ($topics_id as $topic) |
481
|
|
|
{ |
482
|
|
|
$topic = (int) $topic; |
483
|
|
|
if (!empty($topic)) |
484
|
|
|
{ |
485
|
|
|
$this->_topics_to_restore[] = $topic; |
486
|
|
|
} |
487
|
|
|
} |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
/** |
491
|
|
|
* Actually restore the topics previously "collected" |
492
|
|
|
*/ |
493
|
|
|
public function doRestore() |
494
|
|
|
{ |
495
|
|
|
if (empty($this->_topics_to_restore)) |
496
|
|
|
{ |
497
|
|
|
return; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
$db = database(); |
501
|
|
|
|
502
|
|
|
require_once(SUBSDIR . '/Boards.subs.php'); |
503
|
|
|
|
504
|
|
|
// Lets get the data for these topics. |
505
|
|
|
$request = $db->fetchQuery(' |
506
|
|
|
SELECT |
507
|
|
|
t.id_topic, t.id_previous_board, t.id_board, t.id_first_msg, m.subject |
508
|
|
|
FROM {db_prefix}topics AS t |
509
|
|
|
INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg) |
510
|
|
|
WHERE t.id_topic IN ({array_int:topics})', |
511
|
|
|
array( |
512
|
|
|
'topics' => $this->_topics_to_restore, |
513
|
|
|
) |
514
|
|
|
); |
515
|
|
|
while (($row = $request->fetch_assoc())) |
516
|
|
|
{ |
517
|
|
|
// We can only restore if the previous board is set. |
518
|
|
|
if (empty($row['id_previous_board'])) |
519
|
|
|
{ |
520
|
|
|
$this->_unfound_messages[$row['id_first_msg']] = $row['subject']; |
521
|
|
|
continue; |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
// Ok we got here so me move them from here to there. |
525
|
|
|
moveTopics($row['id_topic'], $row['id_previous_board']); |
526
|
|
|
|
527
|
|
|
// Lets remove the recycled icon. |
528
|
|
|
$db->query('', ' |
529
|
|
|
UPDATE {db_prefix}messages |
530
|
|
|
SET icon = {string:icon} |
531
|
|
|
WHERE id_topic = {int:id_topic}', |
532
|
|
|
array( |
533
|
|
|
'icon' => 'xx', |
534
|
|
|
'id_topic' => $row['id_topic'], |
535
|
|
|
) |
536
|
|
|
); |
537
|
|
|
|
538
|
|
|
// Lets see if the board that we are returning to has post count enabled. |
539
|
|
|
$board_data = boardInfo($row['id_previous_board']); |
540
|
|
|
|
541
|
|
|
if (empty($board_data['count_posts'])) |
542
|
|
|
{ |
543
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
544
|
|
|
|
545
|
|
|
// Lets get the members that need their post count restored. |
546
|
|
|
$db->fetchQuery(' |
547
|
|
|
SELECT |
548
|
|
|
id_member, COUNT(id_msg) AS post_count |
549
|
|
|
FROM {db_prefix}messages |
550
|
|
|
WHERE id_topic = {int:topic} |
551
|
|
|
AND approved = {int:is_approved} |
552
|
|
|
GROUP BY id_member', |
553
|
|
|
array( |
554
|
|
|
'topic' => $row['id_topic'], |
555
|
|
|
'is_approved' => 1, |
556
|
|
|
) |
557
|
|
|
)->fetch_callback( |
558
|
|
|
static function ($member) { |
559
|
|
|
updateMemberData($member['id_member'], array('posts' => 'posts + ' . $member['post_count'])); |
560
|
|
|
} |
561
|
|
|
); |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
// Log it. |
565
|
|
|
logAction('restore_topic', array('topic' => $row['id_topic'], 'board' => $row['id_board'], 'board_to' => $row['id_previous_board'])); |
566
|
|
|
} |
567
|
|
|
|
568
|
|
|
$request->free_result(); |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
/** |
572
|
|
|
* Returns either the list of messages not found, or the number |
573
|
|
|
* |
574
|
|
|
* @param bool $return_msg If true returns the array of not found messages, |
575
|
|
|
* if false their number |
576
|
|
|
* @return bool|int[] |
577
|
|
|
*/ |
578
|
|
|
public function unfoundRestoreMessages($return_msg = false) |
579
|
|
|
{ |
580
|
|
|
if ($return_msg) |
581
|
|
|
{ |
582
|
|
|
return $this->_unfound_messages; |
583
|
|
|
} |
584
|
|
|
|
585
|
|
|
return !empty($this->_unfound_messages); |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
/** |
589
|
|
|
* Remove a message from the forum. |
590
|
|
|
* |
591
|
|
|
* - If $check_permissions is true, and it is the first and only message in a topic, removes the topic |
592
|
|
|
* |
593
|
|
|
* @param int $message The ID of the message to be removed |
594
|
|
|
* @param bool $decreasePostCount Whether to decrease the post count or not (default: true) |
595
|
|
|
* @param bool $check_permissions Whether to check permissions or not (default: true) (may result in fatal |
596
|
|
|
* errors or login screens) |
597
|
|
|
* |
598
|
|
|
* @return bool Whether the message was successfully removed or not |
599
|
|
|
*/ |
600
|
|
|
public function removeMessage($message, $decreasePostCount = true, $check_permissions = true) |
601
|
|
|
{ |
602
|
|
|
global $board, $modSettings; |
603
|
|
|
|
604
|
|
|
$db = database(); |
605
|
|
|
|
606
|
|
|
$message = (int) $message; |
607
|
|
|
|
608
|
|
|
if (empty($message)) |
609
|
|
|
{ |
610
|
|
|
return false; |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
$row = $db->fetchQuery(' |
614
|
|
|
SELECT |
615
|
|
|
m.id_msg, m.id_member, m.icon, m.poster_time, m.subject,' . (empty($modSettings['search_custom_index_config']) ? '' : ' m.body,') . ' |
616
|
|
|
m.approved, t.id_topic, t.id_first_msg, t.id_last_msg, t.num_replies, t.id_board, |
617
|
|
|
t.id_member_started AS id_member_poster, |
618
|
|
|
b.count_posts |
619
|
|
|
FROM {db_prefix}messages AS m |
620
|
|
|
INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic) |
621
|
|
|
INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board) |
622
|
|
|
WHERE m.id_msg = {int:id_msg} |
623
|
|
|
LIMIT 1', |
624
|
|
|
array( |
625
|
|
|
'id_msg' => $message, |
626
|
|
|
) |
627
|
|
|
)->fetch_assoc(); |
628
|
|
|
|
629
|
|
|
if (empty($row)) |
630
|
|
|
{ |
631
|
|
|
return false; |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
if ($check_permissions) |
635
|
|
|
{ |
636
|
|
|
$check = $this->_checkDeletePermissions($row, $board); |
637
|
|
|
if ($check === 'topic') |
638
|
|
|
{ |
639
|
|
|
// This needs to be included for topic functions |
640
|
|
|
require_once(SUBSDIR . '/Topic.subs.php'); |
641
|
|
|
removeTopics($row['id_topic']); |
642
|
|
|
|
643
|
|
|
return true; |
644
|
|
|
} |
645
|
|
|
|
646
|
|
|
if ($check === 'exit') |
647
|
|
|
{ |
648
|
|
|
return false; |
649
|
|
|
} |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
// Deleting a recycled message can not lower anyone's post count. |
653
|
|
|
if ($row['icon'] === 'recycled') |
654
|
|
|
{ |
655
|
|
|
$decreasePostCount = false; |
656
|
|
|
} |
657
|
|
|
|
658
|
|
|
// This is the last post, update the last post on the board. |
659
|
|
|
if ((int) $row['id_last_msg'] === $message) |
660
|
|
|
{ |
661
|
|
|
// Find the last message, set it, and decrease the post count. |
662
|
|
|
$row2 = $db->fetchQuery(' |
663
|
|
|
SELECT |
664
|
|
|
id_msg, id_member |
665
|
|
|
FROM {db_prefix}messages |
666
|
|
|
WHERE id_topic = {int:id_topic} |
667
|
|
|
AND id_msg != {int:id_msg} |
668
|
|
|
ORDER BY ' . ($modSettings['postmod_active'] ? 'approved DESC, ' : '') . 'id_msg DESC |
669
|
|
|
LIMIT 1', |
670
|
|
|
array( |
671
|
|
|
'id_topic' => $row['id_topic'], |
672
|
|
|
'id_msg' => $message, |
673
|
|
|
) |
674
|
|
|
)->fetch_assoc(); |
675
|
|
|
|
676
|
|
|
$db->query('', ' |
677
|
|
|
UPDATE {db_prefix}topics |
678
|
|
|
SET |
679
|
|
|
id_last_msg = {int:id_last_msg}, |
680
|
|
|
id_member_updated = {int:id_member_updated}' . (!$modSettings['postmod_active'] || $row['approved'] ? ', |
681
|
|
|
num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : ', |
682
|
|
|
unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . ' |
683
|
|
|
WHERE id_topic = {int:id_topic}', |
684
|
|
|
array( |
685
|
|
|
'id_last_msg' => (int) $row2['id_msg'], |
686
|
|
|
'id_member_updated' => (int) $row2['id_member'], |
687
|
|
|
'no_replies' => 0, |
688
|
|
|
'no_unapproved' => 0, |
689
|
|
|
'id_topic' => (int) $row['id_topic'], |
690
|
|
|
) |
691
|
|
|
); |
692
|
|
|
} |
693
|
|
|
// Only decrease post counts. |
694
|
|
|
else |
695
|
|
|
{ |
696
|
|
|
$db->query('', ' |
697
|
|
|
UPDATE {db_prefix}topics |
698
|
|
|
SET ' . ($row['approved'] ? ' |
699
|
|
|
num_replies = CASE WHEN num_replies = {int:no_replies} THEN 0 ELSE num_replies - 1 END' : ' |
700
|
|
|
unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . ' |
701
|
|
|
WHERE id_topic = {int:id_topic}', |
702
|
|
|
array( |
703
|
|
|
'no_replies' => 0, |
704
|
|
|
'no_unapproved' => 0, |
705
|
|
|
'id_topic' => (int) $row['id_topic'], |
706
|
|
|
) |
707
|
|
|
); |
708
|
|
|
} |
709
|
|
|
|
710
|
|
|
// Default recycle to false. |
711
|
|
|
$recycle = false; |
712
|
|
|
|
713
|
|
|
// If recycle topics has been set, make a copy of this message in the recycle board. |
714
|
|
|
// Make sure we're not recycling messages that are already on the recycle board. |
715
|
|
|
if (!empty($this->_recycle_board) && $row['id_board'] != $this->_recycle_board && $row['icon'] !== 'recycled') |
716
|
|
|
{ |
717
|
|
|
// Check if the recycle board exists and if so get the read status. |
718
|
|
|
$request = $db->query('', ' |
719
|
|
|
SELECT |
720
|
|
|
(COALESCE(lb.id_msg, 0) >= b.id_msg_updated) AS is_seen, id_last_msg |
721
|
|
|
FROM {db_prefix}boards AS b |
722
|
|
|
LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member}) |
723
|
|
|
WHERE b.id_board = {int:recycle_board}', |
724
|
|
|
array( |
725
|
|
|
'current_member' => $this->user->id, |
|
|
|
|
726
|
|
|
'recycle_board' => $this->_recycle_board, |
727
|
|
|
) |
728
|
|
|
); |
729
|
|
|
|
730
|
|
|
if ($request->num_rows() === 0) |
731
|
|
|
{ |
732
|
|
|
throw new Exceptions\Exception('recycle_no_valid_board'); |
733
|
|
|
} |
734
|
|
|
|
735
|
|
|
[$isRead, $last_board_msg] = $request->fetch_row(); |
736
|
|
|
$request->free_result(); |
737
|
|
|
|
738
|
|
|
// Is there an existing topic in the recycle board to group this post with? |
739
|
|
|
$request = $db->query('', ' |
740
|
|
|
SELECT |
741
|
|
|
id_topic, id_first_msg, id_last_msg |
742
|
|
|
FROM {db_prefix}topics |
743
|
|
|
WHERE id_previous_topic = {int:id_previous_topic} |
744
|
|
|
AND id_board = {int:recycle_board}', |
745
|
|
|
array( |
746
|
|
|
'id_previous_topic' => $row['id_topic'], |
747
|
|
|
'recycle_board' => $this->_recycle_board, |
748
|
|
|
) |
749
|
|
|
); |
750
|
|
|
[$id_recycle_topic, $first_topic_msg, $last_topic_msg] = $request->fetch_row(); |
751
|
|
|
$request->free_result(); |
752
|
|
|
|
753
|
|
|
// Insert a new topic in the recycle board if $id_recycle_topic is empty. |
754
|
|
|
if (empty($id_recycle_topic)) |
755
|
|
|
{ |
756
|
|
|
$insert_res = $db->insert('', |
757
|
|
|
'{db_prefix}topics', |
758
|
|
|
array( |
759
|
|
|
'id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int', |
760
|
|
|
'id_last_msg' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int', 'id_previous_topic' => 'int', |
761
|
|
|
), |
762
|
|
|
array( |
763
|
|
|
$this->_recycle_board, $row['id_member'], $row['id_member'], $message, |
764
|
|
|
$message, 0, 1, $row['id_topic'], |
765
|
|
|
), |
766
|
|
|
array('id_topic') |
767
|
|
|
); |
768
|
|
|
$topicID = $insert_res->insert_id(); |
769
|
|
|
} |
770
|
|
|
else |
771
|
|
|
{ |
772
|
|
|
// Capture the ID of the new topic... |
773
|
|
|
$topicID = $id_recycle_topic; |
774
|
|
|
} |
775
|
|
|
|
776
|
|
|
// If the topic creation went successful, move the message. |
777
|
|
|
if ($topicID > 0) |
778
|
|
|
{ |
779
|
|
|
$db->query('', ' |
780
|
|
|
UPDATE {db_prefix}messages |
781
|
|
|
SET |
782
|
|
|
id_topic = {int:id_topic}, |
783
|
|
|
id_board = {int:recycle_board}, |
784
|
|
|
icon = {string:recycled}, |
785
|
|
|
approved = {int:is_approved} |
786
|
|
|
WHERE id_msg = {int:id_msg}', |
787
|
|
|
array( |
788
|
|
|
'id_topic' => $topicID, |
789
|
|
|
'recycle_board' => $this->_recycle_board, |
790
|
|
|
'id_msg' => $message, |
791
|
|
|
'recycled' => 'recycled', |
792
|
|
|
'is_approved' => 1, |
793
|
|
|
) |
794
|
|
|
); |
795
|
|
|
|
796
|
|
|
// Take any reported posts with us... |
797
|
|
|
$db->query('', ' |
798
|
|
|
UPDATE {db_prefix}log_reported |
799
|
|
|
SET |
800
|
|
|
id_topic = {int:id_topic}, |
801
|
|
|
id_board = {int:recycle_board} |
802
|
|
|
WHERE id_msg = {int:id_msg} |
803
|
|
|
AND type = {string:msg}', |
804
|
|
|
array( |
805
|
|
|
'id_topic' => $topicID, |
806
|
|
|
'recycle_board' => $this->_recycle_board, |
807
|
|
|
'id_msg' => $message, |
808
|
|
|
'msg' => 'msg', |
809
|
|
|
) |
810
|
|
|
); |
811
|
|
|
|
812
|
|
|
// Mark recycled topic as read. |
813
|
|
|
if ($this->user->is_guest === false) |
|
|
|
|
814
|
|
|
{ |
815
|
|
|
require_once(SUBSDIR . '/Topic.subs.php'); |
816
|
|
|
markTopicsRead(array($this->user->id, $topicID, $modSettings['maxMsgID'], 0), true); |
817
|
|
|
} |
818
|
|
|
|
819
|
|
|
// Mark recycle board as seen, if it was marked as seen before. |
820
|
|
|
if (!empty($isRead) && $this->user->is_guest === false) |
821
|
|
|
{ |
822
|
|
|
require_once(SUBSDIR . '/Boards.subs.php'); |
823
|
|
|
markBoardsRead($this->_recycle_board); |
824
|
|
|
} |
825
|
|
|
|
826
|
|
|
// Add one topic and post to the recycle bin board. |
827
|
|
|
$db->query('', ' |
828
|
|
|
UPDATE {db_prefix}boards |
829
|
|
|
SET |
830
|
|
|
num_topics = num_topics + {int:num_topics_inc}, |
831
|
|
|
num_posts = num_posts + 1' . |
832
|
|
|
($message > $last_board_msg ? ', id_last_msg = {int:id_merged_msg}' : '') . ' |
833
|
|
|
WHERE id_board = {int:recycle_board}', |
834
|
|
|
array( |
835
|
|
|
'num_topics_inc' => empty($id_recycle_topic) ? 1 : 0, |
836
|
|
|
'recycle_board' => $this->_recycle_board, |
837
|
|
|
'id_merged_msg' => $message, |
838
|
|
|
) |
839
|
|
|
); |
840
|
|
|
|
841
|
|
|
// Lets increase the num_replies, and the first/last message ID as appropriate. |
842
|
|
|
if (!empty($id_recycle_topic)) |
843
|
|
|
{ |
844
|
|
|
$db->query('', ' |
845
|
|
|
UPDATE {db_prefix}topics |
846
|
|
|
SET num_replies = num_replies + 1' . |
847
|
|
|
($message > $last_topic_msg ? ', id_last_msg = {int:id_merged_msg}' : '') . |
848
|
|
|
($message < $first_topic_msg ? ', id_first_msg = {int:id_merged_msg}' : '') . ' |
849
|
|
|
WHERE id_topic = {int:id_recycle_topic}', |
850
|
|
|
array( |
851
|
|
|
'id_recycle_topic' => $id_recycle_topic, |
852
|
|
|
'id_merged_msg' => $message, |
853
|
|
|
) |
854
|
|
|
); |
855
|
|
|
} |
856
|
|
|
|
857
|
|
|
// Update mentions accessibility |
858
|
|
|
$this->deleteMessageMentions(messagesInTopics($topicID), true); |
859
|
|
|
|
860
|
|
|
// Make sure this message isn't getting deleted later on. |
861
|
|
|
$recycle = true; |
862
|
|
|
|
863
|
|
|
// Make sure we update the search subject index. |
864
|
|
|
require_once(SUBSDIR . '/Messages.subs.php'); |
865
|
|
|
updateSubjectStats($topicID, $row['subject']); |
866
|
|
|
} |
867
|
|
|
|
868
|
|
|
// If it wasn't approved don't keep it in the queue. |
869
|
|
|
if (!$row['approved']) |
870
|
|
|
{ |
871
|
|
|
$db->query('', ' |
872
|
|
|
DELETE FROM {db_prefix}approval_queue |
873
|
|
|
WHERE id_msg = {int:id_msg} |
874
|
|
|
AND id_attach = {int:id_attach}', |
875
|
|
|
array( |
876
|
|
|
'id_msg' => $message, |
877
|
|
|
'id_attach' => 0, |
878
|
|
|
) |
879
|
|
|
); |
880
|
|
|
} |
881
|
|
|
} |
882
|
|
|
|
883
|
|
|
$db->query('', ' |
884
|
|
|
UPDATE {db_prefix}boards |
885
|
|
|
SET ' . ($row['approved'] ? ' |
886
|
|
|
num_posts = CASE WHEN num_posts = {int:no_posts} THEN 0 ELSE num_posts - 1 END' : ' |
887
|
|
|
unapproved_posts = CASE WHEN unapproved_posts = {int:no_unapproved} THEN 0 ELSE unapproved_posts - 1 END') . ' |
888
|
|
|
WHERE id_board = {int:id_board}', |
889
|
|
|
array( |
890
|
|
|
'no_posts' => 0, |
891
|
|
|
'no_unapproved' => 0, |
892
|
|
|
'id_board' => $row['id_board'], |
893
|
|
|
) |
894
|
|
|
); |
895
|
|
|
|
896
|
|
|
// If the poster was registered and the board this message was on incremented |
897
|
|
|
// the member's posts when it was posted, decrease his or her post count. |
898
|
|
|
if (!empty($row['id_member']) && $decreasePostCount && empty($row['count_posts']) && $row['approved']) |
899
|
|
|
{ |
900
|
|
|
require_once(SUBSDIR . '/Members.subs.php'); |
901
|
|
|
updateMemberData($row['id_member'], array('posts' => '-')); |
902
|
|
|
} |
903
|
|
|
|
904
|
|
|
// Only remove posts if they're not recycled. |
905
|
|
|
if (!$recycle) |
906
|
|
|
{ |
907
|
|
|
// Update the like counts |
908
|
|
|
require_once(SUBSDIR . '/Likes.subs.php'); |
909
|
|
|
decreaseLikeCounts($message); |
910
|
|
|
|
911
|
|
|
// Remove the likes! |
912
|
|
|
$db->query('', ' |
913
|
|
|
DELETE FROM {db_prefix}message_likes |
914
|
|
|
WHERE id_msg = {int:id_msg}', |
915
|
|
|
array( |
916
|
|
|
'id_msg' => $message, |
917
|
|
|
) |
918
|
|
|
); |
919
|
|
|
|
920
|
|
|
// Remove the mentions! |
921
|
|
|
$this->deleteMessageMentions($message); |
922
|
|
|
|
923
|
|
|
// Remove the message! |
924
|
|
|
$db->query('', ' |
925
|
|
|
DELETE FROM {db_prefix}messages |
926
|
|
|
WHERE id_msg = {int:id_msg}', |
927
|
|
|
array( |
928
|
|
|
'id_msg' => $message, |
929
|
|
|
) |
930
|
|
|
); |
931
|
|
|
|
932
|
|
|
if (!empty($modSettings['search_custom_index_config'])) |
933
|
|
|
{ |
934
|
|
|
$words = text2words($row['body'], true); |
935
|
|
|
if (!empty($words)) |
936
|
|
|
{ |
937
|
|
|
$db->query('', ' |
938
|
|
|
DELETE FROM {db_prefix}log_search_words |
939
|
|
|
WHERE id_word IN ({array_int:word_list}) |
940
|
|
|
AND id_msg = {int:id_msg}', |
941
|
|
|
array( |
942
|
|
|
'word_list' => $words, |
943
|
|
|
'id_msg' => $message, |
944
|
|
|
) |
945
|
|
|
); |
946
|
|
|
} |
947
|
|
|
} |
948
|
|
|
|
949
|
|
|
// Delete attachment(s) if they exist. |
950
|
|
|
require_once(SUBSDIR . '/ManageAttachments.subs.php'); |
951
|
|
|
$attachmentQuery = array( |
952
|
|
|
'attachment_type' => 0, |
953
|
|
|
'id_msg' => $message, |
954
|
|
|
); |
955
|
|
|
removeAttachments($attachmentQuery); |
956
|
|
|
|
957
|
|
|
// Delete follow-ups too |
958
|
|
|
require_once(SUBSDIR . '/FollowUps.subs.php'); |
959
|
|
|
|
960
|
|
|
// If it is an entire topic |
961
|
|
|
if ((int) $row['id_first_msg'] === (int) $message) |
962
|
|
|
{ |
963
|
|
|
$db->query('', ' |
964
|
|
|
DELETE FROM {db_prefix}follow_ups |
965
|
|
|
WHERE follow_ups IN ({array_int:topics})', |
966
|
|
|
array( |
967
|
|
|
'topics' => $row['id_topic'], |
968
|
|
|
) |
969
|
|
|
); |
970
|
|
|
} |
971
|
|
|
|
972
|
|
|
// Allow mods to remove message related data of their own (likes, maybe?) |
973
|
|
|
call_integration_hook('integrate_remove_message', array($message)); |
974
|
|
|
} |
975
|
|
|
|
976
|
|
|
// Update the pesky statistics. |
977
|
|
|
updateMessageStats(); |
978
|
|
|
require_once(SUBSDIR . '/Topic.subs.php'); |
979
|
|
|
updateTopicStats(); |
980
|
|
|
updateSettings(['calendar_updated' => time(),]); |
981
|
|
|
|
982
|
|
|
// And now to update the last message of each board we messed with. |
983
|
|
|
require_once(SUBSDIR . '/Post.subs.php'); |
984
|
|
|
if ($recycle) |
985
|
|
|
{ |
986
|
|
|
updateLastMessages([$row['id_board'], $this->_recycle_board]); |
987
|
|
|
} |
988
|
|
|
else |
989
|
|
|
{ |
990
|
|
|
updateLastMessages($row['id_board']); |
991
|
|
|
} |
992
|
|
|
|
993
|
|
|
// Close any moderation reports for this message. |
994
|
|
|
require_once(SUBSDIR . '/Moderation.subs.php'); |
995
|
|
|
$updated_reports = updateReportsStatus($message, 'close', 1); |
996
|
|
|
if ($updated_reports != 0) |
997
|
|
|
{ |
998
|
|
|
updateSettings(array('last_mod_report_action' => time())); |
999
|
|
|
recountOpenReports(true); |
1000
|
|
|
} |
1001
|
|
|
|
1002
|
|
|
// Add it to the mod log. |
1003
|
|
|
if (!allowedTo('delete_any')) |
1004
|
|
|
{ |
1005
|
|
|
return false; |
1006
|
|
|
} |
1007
|
|
|
|
1008
|
|
|
if (allowedTo('delete_own') && $row['id_member'] == $this->user->id) |
1009
|
|
|
{ |
1010
|
|
|
return false; |
1011
|
|
|
} |
1012
|
|
|
|
1013
|
|
|
logAction('delete', array('topic' => $row['id_topic'], 'subject' => $row['subject'], 'member' => $row['id_member'], 'board' => $row['id_board'])); |
1014
|
|
|
return false; |
1015
|
|
|
} |
1016
|
|
|
|
1017
|
|
|
/** |
1018
|
|
|
* When a message is removed, we need to remove associated mentions and updated the member |
1019
|
|
|
* mention count for anyone was mentioned in that message (like, quote, @, etc) |
1020
|
|
|
* |
1021
|
|
|
* @param int|int[] $messages |
1022
|
|
|
* @param bool $recycle If recycle board is enabled, sets mentions as in_accessible, otherwise hard delete |
1023
|
|
|
*/ |
1024
|
|
|
public function deleteMessageMentions($messages, $recycle = false) |
1025
|
|
|
{ |
1026
|
|
|
$db = database(); |
1027
|
|
|
|
1028
|
|
|
$mentionTypes = ['mentionmem', 'likemsg', 'rlikemsg', 'quotedmem', 'watchedtopic', 'watchedboard']; |
1029
|
|
|
$messages = is_array($messages) ? $messages : [$messages]; |
1030
|
|
|
$changeMe = []; |
1031
|
|
|
|
1032
|
|
|
// Who was mentioned in these messages |
1033
|
|
|
$db->fetchQuery(' |
1034
|
|
|
SELECT |
1035
|
|
|
DISTINCT(id_member) as id_member |
1036
|
|
|
FROM {db_prefix}log_mentions |
1037
|
|
|
WHERE id_target IN ({array_int:messages}) |
1038
|
|
|
AND mention_type IN ({array_string:mention_types})', |
1039
|
|
|
[ |
1040
|
|
|
'messages' => $messages, |
1041
|
|
|
'mention_types' => $mentionTypes, |
1042
|
|
|
] |
1043
|
|
|
)->fetch_callback( |
1044
|
|
|
function ($row) use (&$changeMe) { |
1045
|
|
|
$changeMe[] = (int) $row['id_member']; |
1046
|
|
|
} |
1047
|
|
|
); |
1048
|
|
|
|
1049
|
|
|
if ($recycle === true) |
1050
|
|
|
{ |
1051
|
|
|
// Set them as not accessible when they are going to the trashcan |
1052
|
|
|
$db->query('', ' |
1053
|
|
|
UPDATE {db_prefix}log_mentions |
1054
|
|
|
SET |
1055
|
|
|
is_accessible = 0 |
1056
|
|
|
WHERE id_target IN ({array_int:messages}) |
1057
|
|
|
AND is_accessible = 1', |
1058
|
|
|
[ |
1059
|
|
|
'messages' => $messages, |
1060
|
|
|
'mention_types' => $mentionTypes, |
1061
|
|
|
] |
1062
|
|
|
); |
1063
|
|
|
} |
1064
|
|
|
else |
1065
|
|
|
{ |
1066
|
|
|
// This message is really gone, so remove the mentions! |
1067
|
|
|
$db->query('', ' |
1068
|
|
|
DELETE FROM {db_prefix}log_mentions |
1069
|
|
|
WHERE id_target IN ({array_int:messages}) |
1070
|
|
|
AND mention_type IN ({array_string:mention_types})', |
1071
|
|
|
[ |
1072
|
|
|
'messages' => $messages, |
1073
|
|
|
'mention_types' => $mentionTypes, |
1074
|
|
|
] |
1075
|
|
|
); |
1076
|
|
|
} |
1077
|
|
|
|
1078
|
|
|
// Update the mention count for this group |
1079
|
|
|
require_once(SUBSDIR . '/Mentions.subs.php'); |
1080
|
|
|
foreach ($changeMe as $member) |
1081
|
|
|
{ |
1082
|
|
|
countUserMentions(false, '', $member); |
1083
|
|
|
} |
1084
|
|
|
} |
1085
|
|
|
|
1086
|
|
|
/** |
1087
|
|
|
* Check if the user has the necessary permissions to delete a post. |
1088
|
|
|
* |
1089
|
|
|
* @param array $row Details on the message |
1090
|
|
|
* @param int $board The board ID |
1091
|
|
|
* |
1092
|
|
|
* @return bool|string 'topic' if the user has the necessary permissions and this is the only message in a topic |
1093
|
|
|
* 'exit' if the user cannot delete an unapproved message, |
1094
|
|
|
* true if they have permission, |
1095
|
|
|
* exception otherwise. |
1096
|
|
|
* @throws Exception cannot_delete_replies, cannot_delete_own, modify_post_time_passed, cannot_delete_any, delFirstPost |
1097
|
|
|
*/ |
1098
|
|
|
protected function _checkDeletePermissions($row, $board) |
1099
|
|
|
{ |
1100
|
|
|
global $modSettings; |
1101
|
|
|
|
1102
|
|
|
$row['id_board'] = (int) $row['id_board']; |
1103
|
|
|
$row['id_member_poster'] = (int) $row['id_member_poster']; |
1104
|
|
|
$row['id_member'] = (int) $row['id_member']; |
1105
|
|
|
$row['id_first_msg'] = (int) $row['id_first_msg']; |
1106
|
|
|
$row['id_msg'] = (int) $row['id_msg']; |
1107
|
|
|
$board = (int) $board; |
1108
|
|
|
|
1109
|
|
|
if (empty($board) || $row['id_board'] !== $board) |
1110
|
|
|
{ |
1111
|
|
|
$delete_any = boardsAllowedTo('delete_any'); |
1112
|
|
|
|
1113
|
|
|
if (!in_array(0, $delete_any) && !in_array($row['id_board'], $delete_any)) |
1114
|
|
|
{ |
1115
|
|
|
$delete_own = boardsAllowedTo('delete_own'); |
1116
|
|
|
$delete_own = in_array(0, $delete_own) || in_array($row['id_board'], $delete_own); |
|
|
|
|
1117
|
|
|
$delete_replies = boardsAllowedTo('delete_replies'); |
1118
|
|
|
$delete_replies = in_array(0, $delete_replies) || in_array($row['id_board'], $delete_replies); |
1119
|
|
|
|
1120
|
|
|
// Their own checks |
1121
|
|
|
if ($row['id_member'] === $this->user->id) |
|
|
|
|
1122
|
|
|
{ |
1123
|
|
|
if (!$delete_own) |
1124
|
|
|
{ |
1125
|
|
|
if ($row['id_member_poster'] === $this->user->id) |
1126
|
|
|
{ |
1127
|
|
|
if (!$delete_replies) |
1128
|
|
|
{ |
1129
|
|
|
throw new Exceptions\Exception('cannot_delete_replies', 'permission'); |
1130
|
|
|
} |
1131
|
|
|
} |
1132
|
|
|
else |
1133
|
|
|
{ |
1134
|
|
|
throw new Exceptions\Exception('cannot_delete_own', 'permission'); |
1135
|
|
|
} |
1136
|
|
|
} |
1137
|
|
|
elseif (($row['id_member_poster'] !== $this->user->id || !$delete_replies) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time()) |
1138
|
|
|
{ |
1139
|
|
|
throw new Exceptions\Exception('modify_post_time_passed', false); |
1140
|
|
|
} |
1141
|
|
|
} |
1142
|
|
|
elseif ($row['id_member_poster'] === $this->user->id) |
1143
|
|
|
{ |
1144
|
|
|
if (!$delete_replies) |
1145
|
|
|
{ |
1146
|
|
|
throw new Exceptions\Exception('cannot_delete_replies', 'permission'); |
1147
|
|
|
} |
1148
|
|
|
} |
1149
|
|
|
else |
1150
|
|
|
{ |
1151
|
|
|
throw new Exceptions\Exception('cannot_delete_any', 'permission'); |
1152
|
|
|
} |
1153
|
|
|
} |
1154
|
|
|
|
1155
|
|
|
// Can't delete an unapproved message, if you can't see it! |
1156
|
|
|
if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] != $this->user->id && (!in_array(0, $delete_any) && !in_array($row['id_board'], $delete_any))) |
1157
|
|
|
{ |
1158
|
|
|
$approve_posts = empty($this->user->mod_cache['ap']) ? boardsAllowedTo('approve_posts') : $this->user->mod_cache['ap']; |
|
|
|
|
1159
|
|
|
if (!in_array(0, $approve_posts, true) && !in_array($row['id_board'], $approve_posts, true)) |
1160
|
|
|
{ |
1161
|
|
|
return 'exit'; |
1162
|
|
|
} |
1163
|
|
|
} |
1164
|
|
|
} |
1165
|
|
|
else |
1166
|
|
|
{ |
1167
|
|
|
// Check permissions to delete this message. |
1168
|
|
|
if ($row['id_member'] === $this->user->id) |
1169
|
|
|
{ |
1170
|
|
|
if (!allowedTo('delete_own')) |
1171
|
|
|
{ |
1172
|
|
|
if ($row['id_member_poster'] === $this->user->id && !allowedTo('delete_any')) |
1173
|
|
|
{ |
1174
|
|
|
isAllowedTo('delete_replies'); |
1175
|
|
|
} |
1176
|
|
|
elseif (!allowedTo('delete_any')) |
1177
|
|
|
{ |
1178
|
|
|
isAllowedTo('delete_own'); |
1179
|
|
|
} |
1180
|
|
|
} |
1181
|
|
|
elseif (!allowedTo('delete_any') && ($row['id_member_poster'] !== $this->user->id || !allowedTo('delete_replies')) && !empty($modSettings['edit_disable_time']) && $row['poster_time'] + $modSettings['edit_disable_time'] * 60 < time()) |
1182
|
|
|
{ |
1183
|
|
|
throw new Exceptions\Exception('modify_post_time_passed', false); |
1184
|
|
|
} |
1185
|
|
|
} |
1186
|
|
|
elseif ($row['id_member_poster'] === $this->user->id && !allowedTo('delete_any')) |
1187
|
|
|
{ |
1188
|
|
|
isAllowedTo('delete_replies'); |
1189
|
|
|
} |
1190
|
|
|
else |
1191
|
|
|
{ |
1192
|
|
|
isAllowedTo('delete_any'); |
1193
|
|
|
} |
1194
|
|
|
|
1195
|
|
|
if ($modSettings['postmod_active'] && !$row['approved'] && $row['id_member'] !== $this->user->id && !allowedTo('delete_own')) |
1196
|
|
|
{ |
1197
|
|
|
isAllowedTo('approve_posts'); |
1198
|
|
|
} |
1199
|
|
|
} |
1200
|
|
|
|
1201
|
|
|
// Delete the *whole* topic, but only if the topic consists of one message. |
1202
|
|
|
if ($row['id_first_msg'] === $row['id_msg']) |
1203
|
|
|
{ |
1204
|
|
|
if (empty($board) || $row['id_board'] !== $board) |
1205
|
|
|
{ |
1206
|
|
|
$remove_own = false; |
1207
|
|
|
$remove_any = boardsAllowedTo('remove_any'); |
1208
|
|
|
$remove_any = in_array(0, $remove_any) || in_array($row['id_board'], $remove_any, true); |
1209
|
|
|
|
1210
|
|
|
if (!$remove_any) |
1211
|
|
|
{ |
1212
|
|
|
$remove_own = boardsAllowedTo('remove_own'); |
1213
|
|
|
$remove_own = in_array(0, $remove_own) || in_array($row['id_board'], $remove_own, true); |
1214
|
|
|
} |
1215
|
|
|
|
1216
|
|
|
if ($row['id_member'] !== $this->user->id && !$remove_any) |
1217
|
|
|
{ |
1218
|
|
|
throw new Exceptions\Exception('cannot_remove_any', 'permission'); |
1219
|
|
|
} |
1220
|
|
|
|
1221
|
|
|
if (!$remove_any && !$remove_own) |
1222
|
|
|
{ |
1223
|
|
|
throw new Exceptions\Exception('cannot_remove_own', 'permission'); |
1224
|
|
|
} |
1225
|
|
|
} |
1226
|
|
|
elseif ($row['id_member'] !== $this->user->id) |
1227
|
|
|
{ |
1228
|
|
|
// Check permissions to delete a whole topic. |
1229
|
|
|
isAllowedTo('remove_any'); |
1230
|
|
|
} |
1231
|
|
|
elseif (!allowedTo('remove_any')) |
1232
|
|
|
{ |
1233
|
|
|
isAllowedTo('remove_own'); |
1234
|
|
|
} |
1235
|
|
|
|
1236
|
|
|
// ...if there is only one post. |
1237
|
|
|
if (!empty($row['num_replies'])) |
1238
|
|
|
{ |
1239
|
|
|
throw new Exceptions\Exception('delFirstPost', false); |
1240
|
|
|
} |
1241
|
|
|
|
1242
|
|
|
return 'topic'; |
1243
|
|
|
} |
1244
|
|
|
|
1245
|
|
|
return true; |
1246
|
|
|
} |
1247
|
|
|
} |
1248
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.