PluginProbe
Friends / 2.9.0
Friends v2.9.0
4.3.2 4.3.1 4.3.0 4.2.2 4.2.1 4.2.0 4.1.0 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.0 2.9.1 All 88 releases
friends / includes / class-reactions.php

class-reactions.php in Friends 2.9.0, at includes/class-reactions.php

575 lines 14.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friends Reactions
4 *
5 * This contains the functions for Reactions.
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 /**
13 * This is the class for the Reactions part of the Friends Plugin.
14 *
15 * @since 0.8
16 *
17 * @package Friends
18 * @author Alex Kirk
19 */
20 class Reactions {
21 /**
22 * Contains a reference to the Friends class.
23 *
24 * @var Friends
25 */
26 private $friends;
27
28 /**
29 * Constructor
30 *
31 * @param Friends $friends A reference to the Friends object.
32 */
33 public function __construct( Friends $friends ) {
34 $this->friends = $friends;
35 $this->register_hooks();
36 }
37
38 /**
39 * Register the WordPress hooks
40 */
41 private function register_hooks() {
42 add_action( 'init', array( $this, 'register_taxonomies' ) );
43 add_action( 'wp_ajax_friends-toggle-react', array( $this, 'wp_ajax_toggle_react' ) );
44 add_action( 'friends_react', array( $this, 'react' ), 10, 4 );
45 add_action( 'friends_unreact', array( $this, 'unreact' ), 10, 4 );
46 add_action( 'friends_get_user_reactions', array( $this, 'get_user_reactions' ), 10, 3 );
47 }
48
49 /**
50 * Register the taxonomies necessary
51 */
52 public function register_taxonomies() {
53 self::register_user_taxonomy( get_current_user_id() );
54 }
55
56 /**
57 * Register the taxonomy for a certain user
58 *
59 * @param int $user_id The user id.
60 */
61 public static function register_user_taxonomy( $user_id ) {
62 $taxonomy = 'friend-reaction-' . $user_id;
63 $args = array(
64 'labels' => array(
65 'name' => _x( 'Reactions', 'taxonomy general name', 'friends' ),
66 'singular_name' => _x( 'Reaction', 'taxonomy singular name', 'friends' ),
67 'menu_name' => __( 'Reaction', 'friends' ),
68 ),
69 'hierarchical' => false,
70 'show_ui' => true,
71 'show_admin_column' => true,
72 'query_var' => true,
73 'rewrite' => false,
74 'public' => false,
75 );
76 register_taxonomy( $taxonomy, apply_filters( 'friends_frontend_post_types', array() ), $args );
77
78 return $taxonomy;
79 }
80
81 /**
82 * Gets the user reactions for a post.
83 *
84 * @param array $reactions The reactions.
85 * @param int $post_id The post ID.
86 * @param int $user_id The user ID.
87 * @return array The users' reactions.
88 */
89 public static function get_user_reactions( $reactions, $post_id, $user_id = null ) {
90 if ( is_null( $user_id ) ) {
91 $user_id = get_current_user_id();
92 }
93 // Ensure the taxonomy is queryable.
94 self::register_user_taxonomy( $user_id );
95
96 $my_reactions = wp_get_object_terms( $post_id, 'friend-reaction-' . $user_id );
97 if ( is_wp_error( $my_reactions ) ) {
98 return array();
99 }
100 if ( ! $reactions ) {
101 $reactions = array();
102 }
103 foreach ( $my_reactions as $term ) {
104 $reactions[] = $term->slug;
105 }
106
107 return $reactions;
108 }
109
110 /**
111 * Get the reactions for a post.
112 *
113 * @param int|\WP_Post $post The post.
114 * @param int|false $exclude_user_id Whether to exclude a certain user_id.
115 * @return array The users' reactions.
116 */
117 public static function get_post_reactions( $post = null, $exclude_user_id = false ) {
118 $post = get_post( $post );
119
120 if ( ! is_object( $post ) ) {
121 return false;
122 }
123
124 $reactions = array();
125 $term_query = new \WP_Term_Query(
126 array(
127 'object_ids' => $post->ID,
128 )
129 );
130
131 if ( false !== $exclude_user_id ) {
132 $excluded_user = new \WP_User( $exclude_user_id );
133 } else {
134 $excluded_user = wp_get_current_user();
135 }
136
137 foreach ( $term_query->get_terms() as $term ) {
138 if ( substr( $term->taxonomy, 0, 16 ) !== 'friend-reaction-' ) {
139 continue;
140 }
141 if ( ! isset( $reactions[ $term->slug ] ) ) {
142 $reactions[ $term->slug ] = array();
143 }
144
145 $user_id = intval( substr( $term->taxonomy, 16 ) );
146 if ( $exclude_user_id === $user_id || ( false === $exclude_user_id && get_current_user_id() === $user_id ) ) {
147 $user_reactions[ $term->slug ] = true;
148 continue;
149 }
150
151 $user_display_name = apply_filters( 'friends_get_reaction_display_name', false, $term );
152 if ( ! $user_display_name ) {
153 $user = new \WP_User( $user_id );
154 if ( ! $user || is_wp_error( $user ) ) {
155 continue;
156 }
157 $user_display_name = $user->display_name;
158 }
159
160 if ( ! isset( $reactions[ $term->slug ] ) ) {
161 $reactions[ $term->slug ] = array();
162 }
163 $reactions[ $term->slug ][ $user_id ] = $user_display_name;
164 }
165
166 $remote_reactions = maybe_unserialize( get_post_meta( $post, 'remote_reactions', true ) );
167 foreach ( $reactions as $emoji => $reacting_usernames ) {
168 $user_reacted = isset( $user_reactions[ $emoji ] );
169
170 $count = count( $reacting_usernames );
171 if ( false === $exclude_user_id && $user_reacted ) {
172 $count += 1;
173 }
174
175 $usernames = array_values( $reacting_usernames );
176 if ( false === $exclude_user_id && $user_reacted ) {
177 $usernames[] = $excluded_user->display_name;
178 }
179
180 if ( is_array( $remote_reactions ) && isset( $remote_reactions[ $emoji ] ) ) {
181 $count += $remote_reactions[ $emoji ]->count;
182 $usernames[] = $remote_reactions[ $emoji ]->usernames;
183 unset( $remote_reactions[ $emoji ] );
184 }
185
186 $usernames = array_filter( $usernames );
187 if ( empty( $usernames ) ) {
188 unset( $reactions[ $emoji ] );
189 continue;
190 }
191
192 $reactions[ $emoji ] = (object) array(
193 'count' => intval( $count ),
194 'emoji' => self::validate_emoji( $emoji ),
195 'usernames' => implode( ', ', $usernames ),
196 'user_reacted' => isset( $user_reactions[ $emoji ] ),
197 );
198 }
199 if ( is_array( $remote_reactions ) ) {
200 foreach ( $remote_reactions as $emoji => $reaction ) {
201 $reaction->user_reacted = false;
202 $reaction->emoji = self::validate_emoji( $emoji );
203 $reactions[ $emoji ] = $reaction;
204 }
205 }
206
207 ksort( $reactions );
208
209 return $reactions;
210 }
211
212 /**
213 * Store a reaction.
214 */
215 public function wp_ajax_toggle_react() {
216 check_ajax_referer( 'friends-reaction' );
217
218 if ( ! is_user_logged_in() ) {
219 return new \WP_Error( 'unauthorized', 'You are not authorized to send a reaction.' );
220 }
221
222 if ( ! isset( $_POST['post_id'] ) || ! isset( $_POST['reaction'] ) ) {
223 wp_send_json_error(
224 array(
225 'result' => false,
226 )
227 );
228 }
229 if ( ! is_numeric( $_POST['post_id'] ) || $_POST['post_id'] <= 0 ) {
230 wp_send_json_error(
231 array(
232 'result' => false,
233 )
234 );
235 }
236
237 $post_id = intval( $_POST['post_id'] );
238 $emoji = self::validate_emoji( $_POST['reaction'] );
239
240 if ( ! $emoji ) {
241 // This emoji is not defined in emoji.json.
242 return new \WP_Error( 'invalid-emoji', 'This emoji is unknown.' );
243 }
244
245 $taxonomy = 'friend-reaction-' . get_current_user_id();
246 $term = false;
247 foreach ( wp_get_object_terms( $post_id, $taxonomy ) as $t ) {
248 if ( $t->slug === $_POST['reaction'] ) {
249 $term = $t;
250 break;
251 }
252 }
253
254 if ( ! $term ) {
255 $ret = apply_filters( 'friends_react', null, $post_id, $_POST['reaction'] );
256 } else {
257 $ret = apply_filters( 'friends_unreact', null, $post_id, $_POST['reaction'] );
258 }
259
260 if ( ! $ret || is_wp_error( $ret ) ) {
261 wp_send_json_error(
262 array(
263 'result' => false,
264 )
265 );
266 }
267
268 wp_send_json_success(
269 array(
270 'result' => true,
271 )
272 );
273 }
274
275 public function react( $ret, $post_id, $reaction, $user_id = null ) {
276 if ( is_null( $user_id ) ) {
277 $user_id = get_current_user_id();
278 }
279 // Ensure the taxonomy is queryable.
280 self::register_user_taxonomy( $user_id );
281
282 $taxonomy = 'friend-reaction-' . $user_id;
283 $term = false;
284 $terms = wp_get_object_terms( $post_id, $taxonomy );
285 if ( is_array( $terms ) ) {
286 foreach ( $terms as $t ) {
287 if ( $t->slug === $reaction ) {
288 $term = $t;
289 break;
290 }
291 }
292 }
293
294 if ( ! $term ) {
295 $terms = wp_set_object_terms( $post_id, $reaction, $taxonomy, true );
296 if ( ! is_wp_error( $terms ) ) {
297 do_action( 'friends_user_post_reaction', $post_id, self::validate_emoji( $reaction ), $reaction, $terms[0] );
298 return $terms[0];
299 }
300 return false;
301 }
302
303 return false;
304 }
305
306 public function unreact( $ret, $post_id, $reaction, $user_id = null ) {
307 if ( is_null( $user_id ) ) {
308 $user_id = get_current_user_id();
309 }
310 // Ensure the taxonomy is queryable.
311 self::register_user_taxonomy( $user_id );
312
313 $taxonomy = 'friend-reaction-' . $user_id;
314 $term = false;
315 $terms = wp_get_object_terms( $post_id, $taxonomy );
316 if ( is_array( $terms ) ) {
317 foreach ( $terms as $t ) {
318 if ( $t->slug === $reaction ) {
319 $term = $t;
320 break;
321 }
322 }
323 }
324
325 if ( $term ) {
326 wp_remove_object_terms( $post_id, $term->term_id, $taxonomy );
327 do_action( 'friends_user_post_undo_reaction', $post_id, self::validate_emoji( $reaction ), $reaction, $term );
328 return true;
329 }
330
331 return false;
332 }
333
334 /**
335 * Fetches the Emojis from the JSON file.
336 *
337 * @return object The emojis.
338 */
339 public static function get_all_emojis() {
340 static $emojis;
341 if ( ! $emojis ) {
342 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
343 $emojis = json_decode( file_get_contents( __DIR__ . '/../emojis.json' ) );
344 }
345 return $emojis;
346 }
347
348 /**
349 * Get the emoji data to be stored.
350 *
351 * @param string $slug The emoji shortname to look up.
352 * @return string|false The data or false if it doesn't exist.
353 */
354 public static function get_emoji_data( $slug ) {
355 $emojis = self::get_all_emojis();
356
357 $slug = strtolower( $slug );
358 if ( ! isset( $emojis->$slug ) ) {
359 return false;
360 }
361
362 return $emojis->$slug;
363 }
364
365 /**
366 * Get the emojis selected to be available.
367 *
368 * @return array The emojis.
369 */
370 public static function get_available_emojis() {
371 static $available_emojis;
372 if ( ! $available_emojis ) {
373 $available_emojis = get_option( 'friends_selected_emojis' );
374 if ( ! is_array( $available_emojis ) ) {
375 $available_emojis = array(
376 '2b50' => (object) array(
377 'char' => '⭐️',
378 'name' => 'WHITE MEDIUM STAR',
379 ),
380 '2764' => (object) array(
381 'char' => '❤️',
382 'name' => 'RED HEART',
383 ),
384 );
385 }
386 }
387
388 $available_emojis['2b50'] = (object) array(
389 'char' => '⭐️',
390 'name' => 'WHITE MEDIUM STAR',
391 );
392 return $available_emojis;
393 }
394
395 /**
396 * Get the emojis selected to be available.
397 *
398 * @return array The emojis.
399 */
400 public static function get_available_emojis_chars() {
401 $emojis = array();
402 foreach ( self::get_available_emojis() as $id => $data ) {
403 $emojis[ $data->char ] = $id;
404 }
405 return $emojis;
406 }
407
408 /**
409 * Get the UTF-8 for an emoji.
410 *
411 * @param string $slug The emoji shortname to look up.
412 * @return string|false The emoji or false if it doesn't exist.
413 */
414 public static function validate_emoji( $slug ) {
415 $emojis = self::get_available_emojis();
416 $slug = strtolower( $slug );
417 if ( ! isset( $emojis[ $slug ] ) ) {
418 return false;
419 }
420
421 return $emojis[ $slug ]->char;
422 }
423
424 /**
425 * Get the id for an emoji UTF-8.
426 *
427 * @param string $emoji The emoji char to look up.
428 * @return string|false The emoji or false if it doesn't exist.
429 */
430 public static function validate_emoji_char( $emoji ) {
431 $emojis = self::get_available_emojis_chars();
432 if ( ! isset( $emojis[ $emoji ] ) ) {
433 return false;
434 }
435
436 return $emojis[ $emoji ];
437 }
438
439 /**
440 * Store remote reactions in post_meta.
441 *
442 * @param int $post_id The post id.
443 * @param array $feed_data The feed data as delivered by SimplePie.
444 * @return array The parsed reactions.
445 */
446 public function update_remote_feed_reactions( $post_id, array $feed_data ) {
447 $reactions = array();
448
449 foreach ( $feed_data as $feed_reaction ) {
450 $attribs = $feed_reaction['attribs'][ Feed::XMLNS ];
451 $slug = $attribs['slug'];
452 if ( ! preg_match( '/^[a-z0-9_-]+$/', $slug ) ) {
453 continue;
454 }
455
456 $reactions[ $slug ] = (object) array(
457 'count' => $attribs['count'],
458 'usernames' => $feed_reaction['data'],
459 'user_reacted' => isset( $attribs['you-reacted'] ) && $attribs['you-reacted'],
460 );
461 }
462
463 return $this->update_remote_reactions( $post_id, $reactions );
464 }
465
466 /**
467 * Store remote reactions in post_meta and update the main user taxonomy.
468 *
469 * @param int $post_id The post id.
470 * @param array $reactions The reactions data to be updated.
471 * @return array The parsed reactions.
472 */
473 public function update_remote_reactions( $post_id, array $reactions ) {
474 $main_user_id = $this->friends->get_main_friend_user_id();
475 $this->register_user_taxonomy( $main_user_id );
476 $main_user_reactions = wp_get_object_terms( $post_id, 'friend-reaction-' . $main_user_id );
477 if ( is_wp_error( $main_user_reactions ) ) {
478 $main_user_reactions = array();
479 }
480 $changed = false;
481
482 foreach ( $reactions as $slug => $reaction ) {
483 if ( is_array( $reaction ) ) {
484 $reaction = (object) $reaction;
485 }
486
487 if (
488 ! preg_match( '/^[a-z0-9_-]+$/', $slug )
489 || ! isset( $reaction->count )
490 || $reaction->count < 0
491 || ! isset( $reaction->usernames )
492 ) {
493 unset( $reactions[ $slug ] );
494 continue;
495 }
496
497 $term = false;
498 foreach ( $main_user_reactions as $k => $t ) {
499 if ( $t->slug === $slug ) {
500 $term = $t;
501 unset( $main_user_reactions[ $k ] );
502 break;
503 }
504 }
505
506 if ( $reaction->user_reacted && ! $term ) {
507 // Someone reacted on the remote site which hasn't been recorded here yet.
508 wp_set_object_terms( $post_id, $slug, 'friend-reaction-' . $main_user_id, true );
509 $changed = true;
510 } elseif ( ! $reaction->user_reacted && $term ) {
511 // Someone removed our reaction on the remote site so we need to delete it here.
512 wp_remove_object_terms( $post_id, $term->term_id, 'friend-reaction-' . $main_user_id );
513 $changed = true;
514 }
515
516 unset( $reaction->user_reacted );
517 $reactions[ $slug ] = $reaction;
518
519 if ( ! $reaction->count ) {
520 unset( $reactions[ $slug ] );
521 }
522 }
523
524 // Remove all remaining reactions as they have not been reported by remote.
525 foreach ( $main_user_reactions as $term ) {
526 wp_remove_object_terms( $post_id, $term->term_id, 'friend-reaction-' . $main_user_id );
527 }
528
529 update_post_meta( $post_id, 'remote_reactions', $reactions );
530 return $reactions;
531 }
532
533 /**
534 * Store reactions of a friend.
535 *
536 * @param int $post_id The post id.
537 * @param int $friend_user_id The friend who reacted.
538 * @param array $reactions The reactions data to be updated.
539 * @return array The parsed reactions.
540 */
541 public function update_friend_reactions( $post_id, $friend_user_id, array $reactions ) {
542 $this->register_user_taxonomy( $friend_user_id );
543 $friend_user_reactions = wp_get_object_terms( $post_id, 'friend-reaction-' . $friend_user_id );
544
545 if ( is_wp_error( $friend_user_reactions ) ) {
546 return false;
547 }
548 foreach ( $reactions as $slug ) {
549 if ( ! preg_match( '/^[a-z0-9_-]+$/', $slug ) ) {
550 continue;
551 }
552
553 $term = false;
554 foreach ( $friend_user_reactions as $k => $t ) {
555 if ( $t->slug === $slug ) {
556 $term = $t;
557 unset( $friend_user_reactions[ $k ] );
558 break;
559 }
560 }
561
562 if ( ! $term ) {
563 wp_set_object_terms( $post_id, $slug, 'friend-reaction-' . $friend_user_id, true );
564 }
565 }
566
567 // Remove all remaining reactions as they have not been reported by remote.
568 foreach ( $friend_user_reactions as $term ) {
569 wp_remove_object_terms( $post_id, $term->term_id, 'friend-reaction-' . $friend_user_id );
570 }
571
572 return $reactions;
573 }
574 }
575