PluginProbe
UpStream: a Project Management Plugin for WordPress / 2.1.0
UpStream: a Project Management Plugin for WordPress v2.1.0
trunk 1.39.0 1.39.1 1.39.2 1.39.3 2.0.7 2.1.0
upstream / includes / class-comment.php

class-comment.php in UpStream: a Project Management Plugin for WordPress 2.1.0, at includes/class-comment.php

579 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Struct that represents an UpStream Comment.
4 *
5 * @package UpStream
6 */
7
8 namespace UpStream;
9
10 // Prevent direct access.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 /**
16 * Struct that represents an UpStream Comment.
17 *
18 * @since 1.13.0
19 */
20 class Comment extends Struct {
21 /**
22 * Comment ID.
23 *
24 * @since 1.13.0
25 *
26 * @var int $id
27 */
28 public $id;
29
30 /**
31 * Project ID where the comments belongs to.
32 *
33 * @since 1.13.0
34 *
35 * @var int $project_id
36 */
37 public $project_id;
38
39 /**
40 * Comment parent ID.
41 *
42 * @since 1.13.0
43 *
44 * @var int $parent_id
45 */
46 public $parent_id;
47
48 /**
49 * Comment content.
50 *
51 * @since 1.13.0
52 *
53 * @var string $content
54 */
55 public $content;
56
57 /**
58 * Comment current status.
59 * Valid values are: -2, -1, 0, 1 ~ representing spam, trash, unapproved, approved.
60 *
61 * @since 1.13.0
62 *
63 * @var int $state
64 */
65 public $state;
66
67 /**
68 * Comment author.
69 *
70 * @since 1.13.0
71 *
72 * @var object $created_by
73 */
74 public $created_by;
75
76 /**
77 * Date info where the comment was added.
78 *
79 * @since 1.13.0
80 *
81 * @var object $created_at
82 */
83 public $created_at;
84
85 /**
86 * Current cached user capabilities related to comments.
87 *
88 * @since 1.13.0
89 *
90 * @var object $current_user_cap
91 */
92 public $current_user_cap;
93
94 /**
95 * Cached author info.
96 *
97 * @since 1.13.0
98 * @access protected
99 *
100 * @var object $author
101 */
102 protected $author;
103
104 /**
105 * Class constructor.
106 *
107 * @since 1.13.0
108 *
109 * @param string $content Comment content.
110 * @param int $project_id Project ID.
111 * @param int $user_id Author ID.
112 */
113 public function __construct( $content = '', $project_id = 0, $user_id = 0 ) {
114 if ( ! empty( $content ) ) {
115 // Make sure the comment content is always filtered.
116 $allowed_tags = apply_filters( 'upstream_allowed_tags_in_comments', array() );
117 $this->content = wp_kses( $content, wp_kses_allowed_html( $allowed_tags ) );
118 }
119
120 if ( (int) $project_id <= 0 ) {
121 $this->project_id = upstream_post_id();
122 } else {
123 $this->project_id = (int) $project_id;
124 }
125
126 if ( (int) $user_id > 0 ) {
127 $author = get_user_by( 'id', $user_id );
128 }
129
130 $user = wp_get_current_user();
131
132 $user_has_admin_capabilities = upstream_is_user_either_manager_or_admin( $user );
133 $user_can_moderate_comments = ! $user_has_admin_capabilities ? user_can( $user, 'moderate_comments' ) : true;
134 $this->current_user_cap = (object) array(
135 'can_reply' => ! $user_has_admin_capabilities ? user_can( $user, 'publish_project_discussion' ) : true,
136 'can_moderate' => $user_can_moderate_comments,
137 'can_delete' => ! $user_has_admin_capabilities ? $user_can_moderate_comments || user_can(
138 $user,
139 'delete_project_discussion'
140 ) : true,
141 );
142
143 $this->author = isset( $author ) ? $author : $user;
144
145 $this->created_by = (object) array(
146 'id' => $author->ID,
147 'name' => $author->display_name,
148 'avatar' => upstream_get_user_avatar_url( $author->ID ),
149 'email' => $author->user_email,
150 );
151
152 $this->created_at = (object) array(
153 'timestamp' => 0,
154 'utc' => '',
155 'localized' => '',
156 'humanized' => '',
157 );
158
159 $this->parent_id = 0;
160 $this->state = 1;
161 }
162
163 /**
164 * Fill missing comment info from a given comment array.
165 *
166 * @since 1.13.0
167 * @static
168 *
169 * @param array $custom_data Associative array with comment info.
170 *
171 * @return array
172 */
173 public static function arrayToWPPatterns( $custom_data ) {
174 $default_data = array(
175 'comment_post_ID' => 0,
176 'comment_author' => '',
177 'comment_author_email' => '',
178 'comment_author_IP' => '',
179 'comment_date' => '',
180 'comment_date_gmt' => '',
181 'comment_content' => null,
182 'comment_agent' => '',
183 'user_id' => 0,
184 'comment_approved' => 1,
185 'comment_type' => '',
186 );
187
188 $data = array_merge( $default_data, (array) $custom_data );
189
190 return $data;
191 }
192
193 /**
194 * Load a given comment data into its own instance based on ID.
195 *
196 * @since 1.13.0
197 * @static
198 *
199 * @param int $comment_id Comment ID to be loaded.
200 *
201 * @return Comment
202 */
203 public static function load( $comment_id ) {
204 $data = get_comment( $comment_id );
205
206 if ( empty( $data ) ) {
207 return null;
208 }
209
210 $comment = new Comment( $data->comment_content, $data->comment_post_ID, $data->user_id );
211 $comment->id = (int) $data->comment_ID;
212 $comment->created_at->timestamp = strtotime( $data->comment_date_gmt );
213 $comment->created_at->utc = $data->comment_date_gmt;
214 $comment->created_at->localized = $data->comment_date;
215 $comment->updateHumanizedDate();
216 $comment->state = self::convertStateToInt( $data->comment_approved );
217 $comment->parent_id = (int) $data->comment_parent;
218
219 return $comment;
220 }
221
222 /**
223 * Convert a given comment state into its equivalent int value.
224 *
225 * @since 1.13.0
226 * @static
227 *
228 * @param string $state State to be converted.
229 *
230 * @return int
231 */
232 public static function convertStateToInt( $state ) {
233 if ( is_numeric( $state ) ) {
234 $state = (int) $state;
235 } elseif ( 'approve' === $state ) {
236 $state = 1;
237 } elseif ( 'hold' === $state ) {
238 $state = 0;
239 } elseif ( 'trash' === $state ) {
240 $state = -1;
241 } elseif ( 'spam' === $state ) {
242 $state = -2;
243 }
244
245 return $state;
246 }
247
248 /**
249 * Either insert/update the comment into DB.
250 *
251 * @since 1.13.0
252 *
253 * @return mixed int if inserted or bool if updated
254 * @throws \Exception Unable to save the data into database.
255 */
256 public function save() {
257 if ( $this->isNew() ) {
258 $this->doFilters();
259
260 $data = $this->toWpPatterns();
261
262 // Commented to avoid comment rejection by WordPress on simmilar comments made across different items on the same project.
263 // if (is_wp_error($integrity_check)) {
264 // throw new \Exception($integrity_check->get_error_message());
265 // }.
266
267 $this->created_at->timestamp = time();
268 $this->created_at->utc = gmdate( 'Y-m-d H:i:s', $this->created_at->timestamp );
269 $data['comment_date_gmt'] = $this->created_at->utc;
270 $integrity_check = wp_allow_comment( $data, true );
271 $this->state = 'spam' !== $integrity_check ? (int) $integrity_check : $integrity_check;
272 $date_format = get_option( 'date_format' );
273 $time_format = get_option( 'time_format' );
274 $the_date_time_format = $date_format . ' ' . $time_format;
275 $date = \DateTime::createFromFormat( 'Y-m-d H:i:s', $this->created_at->utc );
276 $this->created_at->localized = $date->format( $the_date_time_format );
277 $data['comment_date'] = $date->format( 'Y-m-d H:i:s' );
278 $this->created_at->humanized = _x( 'just now', 'Comment was very recently added.', 'upstream' );
279 $allowed_tags = apply_filters( 'upstream_allowed_tags_in_comments', array() );
280 $data['comment_content'] = wp_kses( $data['comment_content'], wp_kses_allowed_html( $allowed_tags ) );
281 $a = $this->html2text( $data['comment_content'] );
282 $comment_id = wp_insert_comment( $data );
283
284 if ( ! $comment_id ) {
285 throw new \Exception( __( 'Unable to save the data into database.', 'upstream' ) );
286 }
287
288 $this->id = $comment_id;
289
290 return $this->id;
291 } else {
292 $data = $this->toWpPatterns();
293 $success = (bool) wp_update_comment( $data );
294
295 if ( ! $success ) {
296 throw new \Exception( __( 'Unable to save the data into database.', 'upstream' ) );
297 }
298
299 return true;
300 }
301 }
302
303 /**
304 * HTML to text
305 *
306 * @since 1.13.0
307 *
308 * @param string $document The document to convert.
309 */
310 public function html2text( $document ) {
311 $rules = array(
312 '@<script[^>]*?>.*?</script>@si',
313 '@<[\/\!]*?[^<>]*?>@si',
314 '@([\r\n])[\s]+@',
315 '@&(quot|#34);@i',
316 '@&(amp|#38);@i',
317 '@&(lt|#60);@i',
318 '@&(gt|#62);@i',
319 '@&(nbsp|#160);@i',
320 '@&(iexcl|#161);@i',
321 '@&(cent|#162);@i',
322 '@&(pound|#163);@i',
323 '@&(copy|#169);@i',
324 '@&(reg|#174);@i',
325 '@&#(d+);@i',
326 );
327
328 $replace = array(
329 '',
330 '',
331 '',
332 '',
333 '&',
334 '<',
335 '>',
336 ' ',
337 chr( 161 ),
338 chr( 162 ),
339 chr( 163 ),
340 chr( 169 ),
341 chr( 174 ),
342 'chr()',
343 );
344
345 return preg_replace( $rules, $replace, $document );
346 }
347
348 /**
349 * Check if instance represents a new or an existent comment.
350 *
351 * @since 1.13.0
352 *
353 * @return bool
354 */
355 public function isNew() {
356 return (int) $this->id <= 0;
357 }
358
359 /**
360 * Apply WordPress comment filters to the instance data.
361 *
362 * @since 1.13.0
363 *
364 * @uses wp_filter_comment
365 */
366 public function doFilters() {
367 $data = $this->toWpPatterns();
368
369 // TODO: why did it filter here and then later elsewhere.
370 $safe_data = $data; // code: wp_filter_comment( $data ).
371
372 $this->created_by->id = (int) $safe_data['user_id'];
373 $this->created_by->agent = $safe_data['comment_agent'];
374 $this->created_by->name = $safe_data['comment_author'];
375 $this->created_by->email = $safe_data['comment_author_email'];
376 $this->created_by->ip = $safe_data['comment_author_IP'];
377 $this->content = $safe_data['comment_content'];
378 }
379
380 /**
381 * Retrieve an associative array following WordPress Comments design pattern with the instance's data.
382 *
383 * @since 1.13.0
384 *
385 * @return array
386 */
387 public function toWpPatterns() {
388 $data = array(
389 'comment_id' => (int) $this->id,
390 'comment_post_ID' => (int) $this->project_id,
391 'comment_author_url' => '',
392 'user_id' => (int) $this->created_by->id,
393 'comment_author' => $this->created_by->name,
394 'comment_author_email' => $this->created_by->email,
395 'comment_content' => $this->content,
396 'comment_approved' => self::convertStateToWpPatterns( $this->state ),
397 'comment_author_IP' => isset( $this->created_by->ip ) ? $this->created_by->ip : '',
398 'comment_agent' => isset( $this->created_by->agent ) ? $this->created_by->agent : '',
399 'comment_parent' => (int) $this->parent_id > 0 ? $this->parent_id : 0,
400 'comment_type' => '',
401 );
402
403 return $data;
404 }
405
406 /**
407 * Convert a given comment state into its equivalent WordPress value.
408 *
409 * @since 1.13.0
410 * @static
411 *
412 * @param mixed $state The state to be translated.
413 *
414 * @return mixed
415 */
416 public static function convertStateToWpPatterns( $state ) {
417 if ( is_numeric( $state ) ) {
418 $state = (int) $state;
419
420 if ( -1 === $state ) {
421 $state = 'trash';
422 }
423 } elseif ( 'approve' === $state ) {
424 $state = 1;
425 } elseif ( 'hold' === $state ) {
426 $state = 0;
427 }
428
429 return $state;
430 }
431
432 /**
433 * Unapprove the comment.
434 *
435 * @since 1.13.0
436 *
437 * @return bool
438 */
439 public function unapprove() {
440 if ( ! $this->isNew() ) {
441 $success = self::updateApprovalState( $this->id, 0 );
442
443 if ( $success ) {
444 $this->state = 0;
445 }
446
447 return $success;
448 }
449
450 return false;
451 }
452
453 /**
454 * Update comment state statically.
455 *
456 * @since 1.13.0
457 * @access protected
458 * @static
459 *
460 * @param int $comment_id Comment ID to be updated.
461 * @param mixed $new_state Comment's new state.
462 *
463 * @return bool
464 */
465 protected static function updateApprovalState( $comment_id, $new_state ) {
466 if ( ! in_array( strtolower( (string) $new_state ), array( '1', '0', 'spam', 'trash' ), true ) ) {
467 return false;
468 }
469
470 $data = array(
471 'comment_ID' => (int) $comment_id,
472 'comment_approved' => $new_state,
473 );
474
475 $success = (bool) wp_update_comment( $data );
476
477 return $success;
478 }
479
480 /**
481 * Approve the comment.
482 *
483 * @since 1.13.0
484 *
485 * @return bool
486 */
487 public function approve() {
488 if ( ! $this->isNew() ) {
489 $success = self::updateApprovalState( $this->id, 1 );
490
491 if ( $success ) {
492 $this->state = 1;
493 }
494
495 return $success;
496 }
497
498 return false;
499 }
500
501 /**
502 * Render the comment as HTML.
503 *
504 * @since 1.13.0
505 *
506 * @param bool $return Either return the HTML or render it instead.
507 * @param bool $use_admin_layout Either use admin/frontend layout.
508 * @param array $comments_cache Array of comments passed to render functions.
509 *
510 * @return string Will only return something if $return is true.
511 */
512 public function render( $return = false, $use_admin_layout = true, $comments_cache = array() ) {
513 if ( empty( $this->current_user_cap ) ) {
514 $user = wp_get_current_user();
515 $user_has_admin_capabilities = upstream_is_user_either_manager_or_admin();
516 $this->current_user_cap->can_reply = ! $user_has_admin_capabilities ? user_can(
517 $user,
518 'publish_project_discussion'
519 ) : true;
520
521 $user_can_moderate = ! $user_has_admin_capabilities ? user_can(
522 $user,
523 'moderate_comments'
524 ) : true;
525 $this->current_user_cap->can_moderate = $user_can_moderate;
526 $this->current_user_cap->can_delete = ! $user_has_admin_capabilities ? ( $user_can_moderate || user_can(
527 $user,
528 'delete_project_discussion'
529 ) || $user->ID === (int) $created_by->id ) : true;
530 }
531
532 $this->updateHumanizedDate();
533
534 if ( true === (bool) $return ) {
535 ob_start();
536
537 if ( true === (bool) $use_admin_layout ) {
538 upstream_admin_display_message_item( $this, $comments_cache );
539 } else {
540 upstream_display_message_item( $this, $comments_cache );
541 }
542
543 $html = ob_get_contents();
544
545 ob_end_clean();
546
547 return $html;
548 } else {
549 if ( true === (bool) $use_admin_layout ) {
550 upstream_admin_display_message_item( $this, $comments_cache );
551 } else {
552 upstream_display_message_item( $this, $comments_cache );
553 }
554 }
555 }
556
557 /**
558 * Update comment's dates to human format.
559 *
560 * @since 1.13.0
561 */
562 public function updateHumanizedDate() {
563 $date_format = get_option( 'date_format' );
564 $time_format = get_option( 'time_format' );
565 $the_date_time_format = $date_format . ' ' . $time_format;
566 $current_timestamp = time();
567
568 $date = \DateTime::createFromFormat( 'Y-m-d H:i:s', $this->created_at->utc );
569 $date_timestamp = $date->getTimestamp();
570
571 $this->created_at->localized = $date->format( $the_date_time_format );
572 $this->created_at->humanized = sprintf(
573 // translators: %s = human-readable time difference.
574 _x( '%s ago', '%s = human-readable time difference', 'upstream' ),
575 human_time_diff( $date_timestamp, $current_timestamp )
576 );
577 }
578 }
579