Completed
Push — master ( 0d4f0a...c8c073 )
by Andreas
17:46
created

net_nehmer_comments_comment::report_abuse()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5.5069

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 5
nop 0
dl 0
loc 20
ccs 8
cts 11
cp 0.7272
crap 5.5069
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package net.nehmer.comments
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
/**
10
 * Comments main comment class
11
 *
12
 * Comments link up to the object they refer to.
13
 *
14
 * @property string $author
15
 * @property string $objectguid
16
 * @property string $title
17
 * @property longstring $content
18
 * @property integer $rating
19
 * @property string $remoteid Remote ID for comments fetched from external source (rss …)
20
 * @property string $ip
21
 * @property integer $status
22
 * @package net.nehmer.comments
23
 */
24
class net_nehmer_comments_comment extends midcom_core_dbaobject
25
{
26
    public $__midcom_class_name__ = __CLASS__;
27
    public $__mgdschema_class_name__ = 'net_nehmer_comments_comment_db';
28
29
    // New messages enter at 4, and can be lowered or raised
30
    const JUNK = 1;
31
    const ABUSE = 2;
32
    const REPORTED_ABUSE = 3;
33
    const NEW_ANONYMOUS = 4;
34
    const NEW_USER = 5;
35
    const MODERATED = 6;
36
37
    var $_send_notification = false;
38
39
    public $_sudo_requested = false;
40
41
    /**
42
     * DBA magic defaults which assign write privileges for all USERS, so that they can
43
     * add new comments at will.
44
     */
45
    public function get_class_magic_default_privileges()
46
    {
47
        return [
48
            'EVERYONE' => [],
49
            'ANONYMOUS' => [],
50
            'USERS' => [
51
                'midgard:create' => MIDCOM_PRIVILEGE_ALLOW
52
            ],
53
        ];
54
    }
55
56
    /**
57
     * Link to the parent object specified in the objectguid field.
58
     */
59 2
    public function get_parent_guid_uncached() : string
60
    {
61 2
        return $this->objectguid;
62
    }
63
64
    /**
65
     * Returns a list of comments applicable to a given object, ordered by creation
66
     * date.
67
     *
68
     * @param string $guid The GUID of the object to bind to.
69
     * @return net_nehmer_comments_comment[] List of applicable comments.
70
     */
71 208
    public static function list_by_objectguid($guid, $limit = false, $order = 'ASC', $paging = false)
72
    {
73 208
        $qb = self::_prepare_query($guid, $paging, $limit);
74 208
        $qb->add_order('metadata.published', $order);
75
76 208
        if ($paging !== false) {
77
            return $qb;
78
        }
79
80 208
        return $qb->execute();
81
    }
82
83
    /**
84
     * Returns a list of comments applicable to a given object
85
     * not displaying empty comments or anonymous posts,
86
     * ordered by creation date.
87
     *
88
     * @param string $guid The GUID of the object to bind to.
89
     * @return net_nehmer_comments_comment[] List of applicable comments.
90
     */
91 1
    public static function list_by_objectguid_filter_anonymous($guid, $limit = false, $order = 'ASC', $paging = false)
92
    {
93 1
        $qb = self::_prepare_query($guid, $paging, $limit);
94 1
        $qb->add_order('metadata.published', $order);
95 1
        $qb->add_constraint('author', '<>', '');
96 1
        $qb->add_constraint('content', '<>', '');
97
98 1
        if ($paging !== false) {
99
            return $qb;
100
        }
101
102 1
        return $qb->execute();
103
    }
104
105
    /**
106
     * Returns the number of comments associated with a given object. This is intended for
107
     * outside usage to render stuff like "15 comments". The count is executed unchecked.
108
     *
109
     * @return int Number of comments matching a given result.
110
     */
111 5
    public static function count_by_objectguid($guid) : int
112
    {
113 5
        $qb = self::_prepare_query($guid);
114 5
        return $qb->count_unchecked();
115
    }
116
117
    /**
118
     * Returns the number of comments associated with a given object by actual registered users.
119
     * This is intended for outside usage to render stuff like "15 comments". The count is
120
     * executed unchecked.
121
     *
122
     * @return int Number of comments matching a given result.
123
     */
124
    public static function count_by_objectguid_filter_anonymous($guid) : int
125
    {
126
        $qb = self::_prepare_query($guid);
127
        $qb->add_constraint('author', '<>', '');
128
        $qb->add_constraint('content', '<>', '');
129
        return $qb->count_unchecked();
130
    }
131
132 214
    private static function _prepare_query($guid, $paging = false, $limit = false)
133
    {
134 214
        if ($paging !== false) {
135
            $qb = new org_openpsa_qbpager(self::class, 'net_nehmer_comments_comment');
136
            $qb->results_per_page = $paging;
137
        } else {
138 214
            $qb = self::new_query_builder();
139 214
            if ($limit) {
140 2
                $qb->set_limit($limit);
141
            }
142
        }
143
144 214
        $qb->add_constraint('status', 'IN', self::get_default_status());
145 214
        $qb->add_constraint('objectguid', '=', $guid);
146
147 214
        return $qb;
148
    }
149
150
    /**
151
     * Check the post against possible spam filters.
152
     *
153
     * This will update post status on the background and log the information.
154
     */
155
    public function check_spam($config)
156
    {
157
        if (!$config->get('enable_spam_check')) {
158
            // Spam checker is not enabled, skip check
159
            return;
160
        }
161
162
        $ret = net_nehmer_comments_spamchecker::check_linksleeve($this->title . ' ' . $this->content . ' ' . $this->author);
163
164
        if ($ret == net_nehmer_comments_spamchecker::HAM) {
165
            // Quality content
166
            debug_add("Linksleeve noted comment \"{$this->title}\" ({$this->guid}) as ham");
167
168
            $this->status = self::MODERATED;
169
            $this->update();
170
            $this->_log_moderation('reported_not_junk', 'linksleeve');
171
        } elseif ($ret == net_nehmer_comments_spamchecker::SPAM) {
172
            // Spam
173
            debug_add("Linksleeve noted comment \"{$this->title}\" ({$this->guid}) as spam");
174
175
            $this->status = self::JUNK;
176
            $this->update();
177
            $this->_log_moderation('confirmed_junk', 'linksleeve');
178
        }
179
    }
180
181 1
    public function report_abuse() : bool
182
    {
183 1
        if ($this->status == self::MODERATED) {
184
            return false;
185
        }
186
187
        // Set the status
188 1
        if (   $this->can_do('net.nehmer.comments:moderation')
189 1
            && !$this->_sudo_requested) {
190 1
            $this->status = self::ABUSE;
191
        } else {
192
            $this->status = self::REPORTED_ABUSE;
193
        }
194
195 1
        if ($this->update()) {
196
            // Log who reported it
197 1
            $this->_log_moderation('reported_abuse');
198 1
            return true;
199
        }
200
        return false;
201
    }
202
203
    /**
204
     * Marks the message as confirmed abuse
205
     */
206
    public function confirm_abuse() : bool
207
    {
208
        if ($this->status == self::MODERATED) {
209
            return false;
210
        }
211
        // Set the status
212
        if (   !$this->can_do('net.nehmer.comments:moderation')
213
            || $this->_sudo_requested) {
214
            return false;
215
        }
216
217
        $this->status = self::ABUSE;
218
        if ($this->update()) {
219
            // Log who reported it
220
            $this->_log_moderation('confirmed_abuse');
221
            return true;
222
        }
223
        return false;
224
    }
225
226
    /**
227
     * Marks the message as confirmed junk (spam)
228
     */
229
    public function confirm_junk() : bool
230
    {
231
        if ($this->status == self::MODERATED) {
232
            return false;
233
        }
234
235
        // Set the status
236
        if (   !$this->can_do('net.nehmer.comments:moderation')
237
            || $this->_sudo_requested) {
238
            return false;
239
        }
240
241
        $this->status = self::JUNK;
242
        if ($this->update()) {
243
            // Log who reported it
244
            $this->_log_moderation('confirmed_junk');
245
            return true;
246
        }
247
        return false;
248
    }
249
250
    /**
251
     * Marks the message as not abuse
252
     */
253
    public function report_not_abuse() : bool
254
    {
255
        if (   !$this->can_do('net.nehmer.comments:moderation')
256
            || $this->_sudo_requested) {
257
            return false;
258
        }
259
260
        // Set the status
261
        $this->status = self::MODERATED;
262
263
        if ($this->update()) {
264
            // Log who reported it
265
            $this->_log_moderation('reported_not_abuse');
266
            return true;
267
        }
268
        return false;
269
    }
270
271
    public function get_logs() : array
272
    {
273
        $log_entries = [];
274
        $logs = $this->list_parameters('net.nehmer.comments:moderation_log');
275
        foreach ($logs as $action => $details) {
276
            // TODO: Show everything only to moderators
277
            $log_action  = explode(':', $action);
278
            $log_details = explode(':', $details);
279
280
            if (count($log_action) == 2) {
281
                switch ($log_details[0]) {
282
                    case 'anonymous':
283
                        $reporter = 'anonymous';
284
                        break;
285
                    case 'linksleeve':
286
                        $reporter = 'linksleeve';
287
                        break;
288
                    default:
289
                        $user = midcom::get()->auth->get_user($log_details[0]);
290
                        $reporter = $user->name;
291
                        break;
292
                }
293
294
                $log_entries[$log_action[1]] = [
295
                    'action'   => $log_action[0],
296
                    'reporter' => $reporter,
297
                    'ip'       => $log_details[1],
298
                    'browser'  => $log_details[2],
299
                ];
300
            }
301
        }
302
        return $log_entries;
303
    }
304
305 1
    private function _log_moderation($action, $reporter = null)
306
    {
307 1
        if ($reporter === null) {
308 1
            if (midcom::get()->auth->user) {
309 1
                $reporter = midcom::get()->auth->user->guid;
310
            } else {
311
                $reporter = 'anonymous';
312
            }
313
        }
314 1
        $browser = str_replace(':', '_', $_SERVER['HTTP_USER_AGENT']);
315 1
        $date_string = gmdate('Ymd\This');
316
317
        $log_action = [
318 1
            0 => $action,
319 1
            1 => $date_string
320
        ];
321
322
        $log_details = [
323 1
            0 => $reporter,
324 1
            1 => str_replace(':', '_', $_SERVER['REMOTE_ADDR']),
325 1
            2 => $browser
326
        ];
327
328 1
        $this->set_parameter('net.nehmer.comments:moderation_log', implode(':', $log_action), implode(':', $log_details));
329 1
    }
330
331 214
    public static function get_default_status() : array
332
    {
333
        $view_status = [
334 214
            self::NEW_ANONYMOUS,
335
            self::NEW_USER,
336
            self::MODERATED,
337
        ];
338
339 214
        $config = midcom_baseclasses_components_configuration::get('net.nehmer.comments', 'config');
340 214
        if ($config->get('show_reported_abuse_as_normal')) {
341 214
            $view_status[] = self::REPORTED_ABUSE;
342
        }
343
344 214
        return $view_status;
345
    }
346
347
    /**
348
     * Update possible ratings cache as requested in configuration
349
     */
350 2
    private function _cache_ratings()
351
    {
352 2
        $config = midcom_baseclasses_components_configuration::get('net.nehmer.comments', 'config');
353
354 2
        if (   $config->get('ratings_enable')
355
            && (    $config->get('ratings_cache_to_object')
356 2
                 || $config->get('comment_count_cache_to_object'))) {
357
            // Handle ratings
358
            $comments = self::list_by_objectguid($this->objectguid);
359
            $ratings_total = 0;
360
            $rating_comments = 0;
361
            $value = 0;
362
            foreach ($comments as $comment) {
363
                if (!empty($comment->rating)) {
364
                    $rating_comments++;
365
                    $ratings_total += $comment->rating;
366
                }
367
            }
368
369
            // Get parent object
370
            $parent_property = $config->get('ratings_cache_to_object_property');
371
            midcom::get()->auth->request_sudo('net.nehmer.comments');
372
            $parent_object = midcom::get()->dbfactory->get_object_by_guid($this->objectguid);
373
374
            if ($config->get('ratings_cache_total')) {
375
                $value = $ratings_total;
376
            } elseif ($rating_comments != 0) {
377
                $value = $ratings_total / $rating_comments;
378
            }
379
380
            if ($config->get('ratings_cache_to_object_property_metadata')) {
381
                $parent_object->metadata->set($parent_property, round($value));
382
            } else {
383
                $orig_rcs = $parent_object->_use_rcs;
384
                $parent_object->_use_rcs = (boolean) $config->get('ratings_cache_to_object_use_rcs');
385
                // TODO: Figure out whether to round
386
                $parent_object->$parent_property = $value;
387
                $parent_object->update();
388
                $parent_object->_use_rcs = $orig_rcs;
389
            }
390
391
            $parent_property = $config->get('comment_count_cache_to_object_property');
392
            $orig_rcs = $parent_object->_use_rcs;
393
            $parent_object->_use_rcs = (boolean) $config->get('comment_count_cache_to_object_use_rcs');
394
            $parent_object->$parent_property = count($comments);
395
            $parent_object->update();
396
            $parent_object->_use_rcs = $orig_rcs;
397
398
            midcom::get()->auth->drop_sudo();
399
        }
400 2
    }
401
402
    private function _send_notifications()
403
    {
404
        if (   empty($this->title)
405
            && empty($this->content)) {
406
            // No need to send notifications about empty rating entries
407
            return;
408
        }
409
        //Get the parent object
410
        try {
411
            $parent = midcom::get()->dbfactory->get_object_by_guid($this->objectguid);
412
        } catch (midcom_error $e) {
413
            $e->log();
414
            return;
415
        }
416
417
        // Construct the message
418
        $message = $this->_construct_message();
419
420
        $authors = explode('|', substr($parent->metadata->authors, 1, -1));
421
        if (empty($authors)) {
422
            // Fall back to original creator if authors are not set for some reason
423
            $authors = [$parent->metadata->creator];
424
        }
425
426
        //Go through all the authors
427
        foreach ($authors as $author) {
428
            // Send the notification to each author of the original document
429
            org_openpsa_notifications::notify('net.nehmer.comments:comment_posted', $author, $message);
430
        }
431
432
        $subscriptions = $parent->list_parameters('net.nehmer.comments:subscription');
433
        //Go through each subscription
434
        foreach (array_keys($subscriptions) as $user_guid) {
435
            // Send notice
436
            org_openpsa_notifications::notify('net.nehmer.comments:subscription', $user_guid, $message);
437
        }
438
    }
439
440
    /**
441
     * Construct the message
442
     */
443
    private function _construct_message() : array
444
    {
445
        // Construct the message
446
        $message = [];
447
448
        // Resolve parent title
449
        $parent_object = midcom::get()->dbfactory->get_object_by_guid($this->objectguid);
450
        $ref = midcom_helper_reflector::get($parent_object);
451
        $parent_title = $ref->get_object_label($parent_object);
452
453
        // Resolve commenting user
454
        $auth = midcom::get()->auth;
455
        if ($auth->user) {
456
            $user_string = "{$auth->user->name} ({$auth->user->username})";
457
        } else {
458
            $user_string = "{$this->author} (" . midcom::get()->i18n->get_string('anonymous', 'midcom') . ")";
459
        }
460
461
        $message['title'] = sprintf(midcom::get()->i18n->get_string('page %s has been commented by %s', 'net.nehmer.comments'), $parent_title, $user_string);
462
463
        $message['content']  = "{$this->title}\n";
464
        $message['content'] .= "{$this->content}\n\n";
465
        $message['content'] .= midcom::get()->i18n->get_string('link to page', 'net.nemein.wiki') . ":\n";
466
        $message['content'] .= midcom::get()->permalinks->create_permalink($this->objectguid);
467
468
        $message['abstract'] = $message['title'];
469
470
        return $message;
471
    }
472
473 2
    public function _on_updated()
474
    {
475 2
        $this->_cache_ratings();
476
477 2
        if ($this->_send_notification) {
478
            //Notify authors and subscribers about the new comment
479
            $this->_send_notifications();
480
        }
481 2
    }
482
}
483