PluginProbe
Friends / 4.3.2
Friends v4.3.2
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 4.3.2, at includes/class-reactions.php

576 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->ID, '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;
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 $reaction = sanitize_text_field( wp_unslash( $_POST['reaction'] ) );
239 $emoji = self::validate_emoji( $reaction );
240
241 if ( ! $emoji ) {
242 // This emoji is not defined in emoji.json.
243 return new \WP_Error( 'invalid-emoji', 'This emoji is unknown.' );
244 }
245
246 $taxonomy = 'friend-reaction-' . get_current_user_id();
247 $term = false;
248 foreach ( wp_get_object_terms( $post_id, $taxonomy ) as $t ) {
249 if ( $t->slug === $reaction ) {
250 $term = $t;
251 break;
252 }
253 }
254
255 if ( ! $term ) {
256 $ret = apply_filters( 'friends_react', null, $post_id, $reaction );
257 } else {
258 $ret = apply_filters( 'friends_unreact', null, $post_id, $reaction );
259 }
260
261 if ( ! $ret || is_wp_error( $ret ) ) {
262 wp_send_json_error(
263 array(
264 'result' => false,
265 )
266 );
267 }
268
269 wp_send_json_success(
270 array(
271 'result' => true,
272 )
273 );
274 }
275
276 public function react( $ret, $post_id, $reaction, $user_id = null ) {
277 if ( is_null( $user_id ) ) {
278 $user_id = get_current_user_id();
279 }
280 // Ensure the taxonomy is queryable.
281 self::register_user_taxonomy( $user_id );
282
283 $taxonomy = 'friend-reaction-' . $user_id;
284 $term = false;
285 $terms = wp_get_object_terms( $post_id, $taxonomy );
286 if ( is_array( $terms ) ) {
287 foreach ( $terms as $t ) {
288 if ( $t->slug === $reaction ) {
289 $term = $t;
290 break;
291 }
292 }
293 }
294
295 if ( ! $term ) {
296 $terms = wp_set_object_terms( $post_id, $reaction, $taxonomy, true );
297 if ( ! is_wp_error( $terms ) ) {
298 do_action( 'friends_user_post_reaction', $post_id, self::validate_emoji( $reaction ), $reaction, $terms[0] );
299 return $terms[0];
300 }
301 return false;
302 }
303
304 return false;
305 }
306
307 public function unreact( $ret, $post_id, $reaction, $user_id = null ) {
308 if ( is_null( $user_id ) ) {
309 $user_id = get_current_user_id();
310 }
311 // Ensure the taxonomy is queryable.
312 self::register_user_taxonomy( $user_id );
313
314 $taxonomy = 'friend-reaction-' . $user_id;
315 $term = false;
316 $terms = wp_get_object_terms( $post_id, $taxonomy );
317 if ( is_array( $terms ) ) {
318 foreach ( $terms as $t ) {
319 if ( $t->slug === $reaction ) {
320 $term = $t;
321 break;
322 }
323 }
324 }
325
326 if ( $term ) {
327 wp_remove_object_terms( $post_id, $term->term_id, $taxonomy );
328 do_action( 'friends_user_post_undo_reaction', $post_id, self::validate_emoji( $reaction ), $reaction, $term );
329 return true;
330 }
331
332 return false;
333 }
334
335 /**
336 * Fetches the Emojis from the JSON file.
337 *
338 * @return object The emojis.
339 */
340 public static function get_all_emojis() {
341 static $emojis;
342 if ( ! $emojis ) {
343 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
344 $emojis = json_decode( file_get_contents( __DIR__ . '/../emojis.json' ) );
345 }
346 return $emojis;
347 }
348
349 /**
350 * Get the emoji data to be stored.
351 *
352 * @param string $slug The emoji shortname to look up.
353 * @return string|false The data or false if it doesn't exist.
354 */
355 public static function get_emoji_data( $slug ) {
356 $emojis = self::get_all_emojis();
357
358 $slug = strtolower( $slug );
359 if ( ! isset( $emojis->$slug ) ) {
360 return false;
361 }
362
363 return $emojis->$slug;
364 }
365
366 /**
367 * Get the emojis selected to be available.
368 *
369 * @return array The emojis.
370 */
371 public static function get_available_emojis() {
372 static $available_emojis;
373 if ( ! $available_emojis ) {
374 $available_emojis = get_option( 'friends_selected_emojis' );
375 if ( ! is_array( $available_emojis ) ) {
376 $available_emojis = array(
377 '2b50' => (object) array(
378 'char' => '⭐️',
379 'name' => 'WHITE MEDIUM STAR',
380 ),
381 '2764' => (object) array(
382 'char' => '❤️',
383 'name' => 'RED HEART',
384 ),
385 );
386 }
387 }
388
389 $available_emojis['2b50'] = (object) array(
390 'char' => '⭐️',
391 'name' => 'WHITE MEDIUM STAR',
392 );
393 return $available_emojis;
394 }
395
396 /**
397 * Get the emojis selected to be available.
398 *
399 * @return array The emojis.
400 */
401 public static function get_available_emojis_chars() {
402 $emojis = array();
403 foreach ( self::get_available_emojis() as $id => $data ) {
404 $emojis[ $data->char ] = $id;
405 }
406 return $emojis;
407 }
408
409 /**
410 * Get the UTF-8 for an emoji.
411 *
412 * @param string $slug The emoji shortname to look up.
413 * @return string|false The emoji or false if it doesn't exist.
414 */
415 public static function validate_emoji( $slug ) {
416 $emojis = self::get_available_emojis();
417 $slug = strtolower( $slug );
418 if ( ! isset( $emojis[ $slug ] ) ) {
419 return false;
420 }
421
422 return $emojis[ $slug ]->char;
423 }
424
425 /**
426 * Get the id for an emoji UTF-8.
427 *
428 * @param string $emoji The emoji char to look up.
429 * @return string|false The emoji or false if it doesn't exist.
430 */
431 public static function validate_emoji_char( $emoji ) {
432 $emojis = self::get_available_emojis_chars();
433 if ( ! isset( $emojis[ $emoji ] ) ) {
434 return false;
435 }
436
437 return $emojis[ $emoji ];
438 }
439
440 /**
441 * Store remote reactions in post_meta.
442 *
443 * @param int $post_id The post id.
444 * @param array $feed_data The feed data as delivered by SimplePie.
445 * @return array The parsed reactions.
446 */
447 public function update_remote_feed_reactions( $post_id, array $feed_data ) {
448 $reactions = array();
449
450 foreach ( $feed_data as $feed_reaction ) {
451 $attribs = $feed_reaction['attribs'][ Feed::XMLNS ];
452 $slug = $attribs['slug'];
453 if ( ! preg_match( '/^[a-z0-9_-]+$/', $slug ) ) {
454 continue;
455 }
456
457 $reactions[ $slug ] = (object) array(
458 'count' => $attribs['count'],
459 'usernames' => $feed_reaction['data'],
460 'user_reacted' => isset( $attribs['you-reacted'] ) && $attribs['you-reacted'],
461 );
462 }
463
464 return $this->update_remote_reactions( $post_id, $reactions );
465 }
466
467 /**
468 * Store remote reactions in post_meta and update the main user taxonomy.
469 *
470 * @param int $post_id The post id.
471 * @param array $reactions The reactions data to be updated.
472 * @return array The parsed reactions.
473 */
474 public function update_remote_reactions( $post_id, array $reactions ) {
475 $main_user_id = $this->friends->get_main_friend_user_id();
476 $this->register_user_taxonomy( $main_user_id );
477 $main_user_reactions = wp_get_object_terms( $post_id, 'friend-reaction-' . $main_user_id );
478 if ( is_wp_error( $main_user_reactions ) ) {
479 $main_user_reactions = array();
480 }
481 $changed = false;
482
483 foreach ( $reactions as $slug => $reaction ) {
484 if ( is_array( $reaction ) ) {
485 $reaction = (object) $reaction;
486 }
487
488 if (
489 ! preg_match( '/^[a-z0-9_-]+$/', $slug )
490 || ! isset( $reaction->count )
491 || $reaction->count < 0
492 || ! isset( $reaction->usernames )
493 ) {
494 unset( $reactions[ $slug ] );
495 continue;
496 }
497
498 $term = false;
499 foreach ( $main_user_reactions as $k => $t ) {
500 if ( $t->slug === $slug ) {
501 $term = $t;
502 unset( $main_user_reactions[ $k ] );
503 break;
504 }
505 }
506
507 if ( $reaction->user_reacted && ! $term ) {
508 // Someone reacted on the remote site which hasn't been recorded here yet.
509 wp_set_object_terms( $post_id, $slug, 'friend-reaction-' . $main_user_id, true );
510 $changed = true;
511 } elseif ( ! $reaction->user_reacted && $term ) {
512 // Someone removed our reaction on the remote site so we need to delete it here.
513 wp_remove_object_terms( $post_id, $term->term_id, 'friend-reaction-' . $main_user_id );
514 $changed = true;
515 }
516
517 unset( $reaction->user_reacted );
518 $reactions[ $slug ] = $reaction;
519
520 if ( ! $reaction->count ) {
521 unset( $reactions[ $slug ] );
522 }
523 }
524
525 // Remove all remaining reactions as they have not been reported by remote.
526 foreach ( $main_user_reactions as $term ) {
527 wp_remove_object_terms( $post_id, $term->term_id, 'friend-reaction-' . $main_user_id );
528 }
529
530 update_post_meta( $post_id, 'remote_reactions', $reactions );
531 return $reactions;
532 }
533
534 /**
535 * Store reactions of a friend.
536 *
537 * @param int $post_id The post id.
538 * @param int $friend_user_id The friend who reacted.
539 * @param array $reactions The reactions data to be updated.
540 * @return array The parsed reactions.
541 */
542 public function update_friend_reactions( $post_id, $friend_user_id, array $reactions ) {
543 $this->register_user_taxonomy( $friend_user_id );
544 $friend_user_reactions = wp_get_object_terms( $post_id, 'friend-reaction-' . $friend_user_id );
545
546 if ( is_wp_error( $friend_user_reactions ) ) {
547 return false;
548 }
549 foreach ( $reactions as $slug ) {
550 if ( ! preg_match( '/^[a-z0-9_-]+$/', $slug ) ) {
551 continue;
552 }
553
554 $term = false;
555 foreach ( $friend_user_reactions as $k => $t ) {
556 if ( $t->slug === $slug ) {
557 $term = $t;
558 unset( $friend_user_reactions[ $k ] );
559 break;
560 }
561 }
562
563 if ( ! $term ) {
564 wp_set_object_terms( $post_id, $slug, 'friend-reaction-' . $friend_user_id, true );
565 }
566 }
567
568 // Remove all remaining reactions as they have not been reported by remote.
569 foreach ( $friend_user_reactions as $term ) {
570 wp_remove_object_terms( $post_id, $term->term_id, 'friend-reaction-' . $friend_user_id );
571 }
572
573 return $reactions;
574 }
575 }
576