PluginProbe
Friends / 2.7.4
Friends v2.7.4
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.7.4, at includes/class-reactions.php

574 lines 14.8 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 $emojis = json_decode( file_get_contents( __DIR__ . '/../emojis.json' ) );
343 }
344 return $emojis;
345 }
346
347 /**
348 * Get the emoji data to be stored.
349 *
350 * @param string $slug The emoji shortname to look up.
351 * @return string|false The data or false if it doesn't exist.
352 */
353 public static function get_emoji_data( $slug ) {
354 $emojis = self::get_all_emojis();
355
356 $slug = strtolower( $slug );
357 if ( ! isset( $emojis->$slug ) ) {
358 return false;
359 }
360
361 return $emojis->$slug;
362 }
363
364 /**
365 * Get the emojis selected to be available.
366 *
367 * @return array The emojis.
368 */
369 public static function get_available_emojis() {
370 static $available_emojis;
371 if ( ! $available_emojis ) {
372 $available_emojis = get_option( 'friends_selected_emojis' );
373 if ( ! is_array( $available_emojis ) ) {
374 $available_emojis = array(
375 '2b50' => (object) array(
376 'char' => '⭐️',
377 'name' => 'WHITE MEDIUM STAR',
378 ),
379 '2764' => (object) array(
380 'char' => '❤️',
381 'name' => 'RED HEART',
382 ),
383 );
384 }
385 }
386
387 $available_emojis['2b50'] = (object) array(
388 'char' => '⭐️',
389 'name' => 'WHITE MEDIUM STAR',
390 );
391 return $available_emojis;
392 }
393
394 /**
395 * Get the emojis selected to be available.
396 *
397 * @return array The emojis.
398 */
399 public static function get_available_emojis_chars() {
400 $emojis = array();
401 foreach ( self::get_available_emojis() as $id => $data ) {
402 $emojis[ $data->char ] = $id;
403 }
404 return $emojis;
405 }
406
407 /**
408 * Get the UTF-8 for an emoji.
409 *
410 * @param string $slug The emoji shortname to look up.
411 * @return string|false The emoji or false if it doesn't exist.
412 */
413 public static function validate_emoji( $slug ) {
414 $emojis = self::get_available_emojis();
415 $slug = strtolower( $slug );
416 if ( ! isset( $emojis[ $slug ] ) ) {
417 return false;
418 }
419
420 return $emojis[ $slug ]->char;
421 }
422
423 /**
424 * Get the id for an emoji UTF-8.
425 *
426 * @param string $emoji The emoji char to look up.
427 * @return string|false The emoji or false if it doesn't exist.
428 */
429 public static function validate_emoji_char( $emoji ) {
430 $emojis = self::get_available_emojis_chars();
431 if ( ! isset( $emojis[ $emoji ] ) ) {
432 return false;
433 }
434
435 return $emojis[ $emoji ];
436 }
437
438 /**
439 * Store remote reactions in post_meta.
440 *
441 * @param int $post_id The post id.
442 * @param array $feed_data The feed data as delivered by SimplePie.
443 * @return array The parsed reactions.
444 */
445 public function update_remote_feed_reactions( $post_id, array $feed_data ) {
446 $reactions = array();
447
448 foreach ( $feed_data as $feed_reaction ) {
449 $attribs = $feed_reaction['attribs'][ Feed::XMLNS ];
450 $slug = $attribs['slug'];
451 if ( ! preg_match( '/^[a-z0-9_-]+$/', $slug ) ) {
452 continue;
453 }
454
455 $reactions[ $slug ] = (object) array(
456 'count' => $attribs['count'],
457 'usernames' => $feed_reaction['data'],
458 'user_reacted' => isset( $attribs['you-reacted'] ) && $attribs['you-reacted'],
459 );
460 }
461
462 return $this->update_remote_reactions( $post_id, $reactions );
463 }
464
465 /**
466 * Store remote reactions in post_meta and update the main user taxonomy.
467 *
468 * @param int $post_id The post id.
469 * @param array $reactions The reactions data to be updated.
470 * @return array The parsed reactions.
471 */
472 public function update_remote_reactions( $post_id, array $reactions ) {
473 $main_user_id = $this->friends->get_main_friend_user_id();
474 $this->register_user_taxonomy( $main_user_id );
475 $main_user_reactions = wp_get_object_terms( $post_id, 'friend-reaction-' . $main_user_id );
476 if ( is_wp_error( $main_user_reactions ) ) {
477 $main_user_reactions = array();
478 }
479 $changed = false;
480
481 foreach ( $reactions as $slug => $reaction ) {
482 if ( is_array( $reaction ) ) {
483 $reaction = (object) $reaction;
484 }
485
486 if (
487 ! preg_match( '/^[a-z0-9_-]+$/', $slug )
488 || ! isset( $reaction->count )
489 || $reaction->count < 0
490 || ! isset( $reaction->usernames )
491 ) {
492 unset( $reactions[ $slug ] );
493 continue;
494 }
495
496 $term = false;
497 foreach ( $main_user_reactions as $k => $t ) {
498 if ( $t->slug === $slug ) {
499 $term = $t;
500 unset( $main_user_reactions[ $k ] );
501 break;
502 }
503 }
504
505 if ( $reaction->user_reacted && ! $term ) {
506 // Someone reacted on the remote site which hasn't been recorded here yet.
507 wp_set_object_terms( $post_id, $slug, 'friend-reaction-' . $main_user_id, true );
508 $changed = true;
509 } elseif ( ! $reaction->user_reacted && $term ) {
510 // Someone removed our reaction on the remote site so we need to delete it here.
511 wp_remove_object_terms( $post_id, $term->term_id, 'friend-reaction-' . $main_user_id );
512 $changed = true;
513 }
514
515 unset( $reaction->user_reacted );
516 $reactions[ $slug ] = $reaction;
517
518 if ( ! $reaction->count ) {
519 unset( $reactions[ $slug ] );
520 }
521 }
522
523 // Remove all remaining reactions as they have not been reported by remote.
524 foreach ( $main_user_reactions as $term ) {
525 wp_remove_object_terms( $post_id, $term->term_id, 'friend-reaction-' . $main_user_id );
526 }
527
528 update_post_meta( $post_id, 'remote_reactions', $reactions );
529 return $reactions;
530 }
531
532 /**
533 * Store reactions of a friend.
534 *
535 * @param int $post_id The post id.
536 * @param int $friend_user_id The friend who reacted.
537 * @param array $reactions The reactions data to be updated.
538 * @return array The parsed reactions.
539 */
540 public function update_friend_reactions( $post_id, $friend_user_id, array $reactions ) {
541 $this->register_user_taxonomy( $friend_user_id );
542 $friend_user_reactions = wp_get_object_terms( $post_id, 'friend-reaction-' . $friend_user_id );
543
544 if ( is_wp_error( $friend_user_reactions ) ) {
545 return false;
546 }
547 foreach ( $reactions as $slug ) {
548 if ( ! preg_match( '/^[a-z0-9_-]+$/', $slug ) ) {
549 continue;
550 }
551
552 $term = false;
553 foreach ( $friend_user_reactions as $k => $t ) {
554 if ( $t->slug === $slug ) {
555 $term = $t;
556 unset( $friend_user_reactions[ $k ] );
557 break;
558 }
559 }
560
561 if ( ! $term ) {
562 wp_set_object_terms( $post_id, $slug, 'friend-reaction-' . $friend_user_id, true );
563 }
564 }
565
566 // Remove all remaining reactions as they have not been reported by remote.
567 foreach ( $friend_user_reactions as $term ) {
568 wp_remove_object_terms( $post_id, $term->term_id, 'friend-reaction-' . $friend_user_id );
569 }
570
571 return $reactions;
572 }
573 }
574