PluginProbe
wpForo Forum / 3.1.6
wpForo Forum v3.1.6
3.1.6 3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 All 138 releases
wpforo / modules / reactions / Reactions.php

Reactions.php in wpForo Forum 3.1.6, at modules/reactions/Reactions.php

676 lines 24.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace wpforo\modules\reactions;
4
5 use stdClass;
6 use wpforo\modules\reactions\classes\Actions;
7 use wpforo\modules\reactions\classes\Template;
8
9 // Exit if accessed directly
10 if( ! defined( 'ABSPATH' ) ) exit;
11
12 class Reactions {
13 public $default;
14 /* @var Template */
15 public $Template;
16 /* @var Actions */
17 public $Actions;
18
19 public function __construct() {
20 $this->init_defaults();
21 $this->init_classes();
22 }
23
24 private function init_classes() {
25 $this->Template = new Template();
26 $this->Actions = new Actions();
27 }
28
29 public static function get_types( $status = true ): array {
30 $types = (array) apply_filters( 'wpforo_reactions_set_types', [
31 'up' => [
32 'key' => 'up',
33 'label' => wpforo_phrase( 'Like', false ),
34 'icon' => '',
35 'html' => '<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M323.8 34.8c-38.2-10.9-78.1 11.2-89 49.4l-5.7 20c-3.7 13-10.4 25-19.5 35l-51.3 56.4c-8.9 9.8-8.2 25 1.6 33.9s25 8.2 33.9-1.6l51.3-56.4c14.1-15.5 24.4-34 30.1-54.1l5.7-20c3.6-12.7 16.9-20.1 29.7-16.5s20.1 16.9 16.5 29.7l-5.7 20c-5.7 19.9-14.7 38.7-26.6 55.5c-5.2 7.3-5.8 16.9-1.7 24.9s12.3 13 21.3 13L448 224c8.8 0 16 7.2 16 16c0 6.8-4.3 12.7-10.4 15c-7.4 2.8-13 9-14.9 16.7s.1 15.8 5.3 21.7c2.5 2.8 4 6.5 4 10.6c0 7.8-5.6 14.3-13 15.7c-8.2 1.6-15.1 7.3-18 15.2s-1.6 16.7 3.6 23.3c2.1 2.7 3.4 6.1 3.4 9.9c0 6.7-4.2 12.6-10.2 14.9c-11.5 4.5-17.7 16.9-14.4 28.8c.4 1.3 .6 2.8 .6 4.3c0 8.8-7.2 16-16 16H286.5c-12.6 0-25-3.7-35.5-10.7l-61.7-41.1c-11-7.4-25.9-4.4-33.3 6.7s-4.4 25.9 6.7 33.3l61.7 41.1c18.4 12.3 40 18.8 62.1 18.8H384c34.7 0 62.9-27.6 64-62c14.6-11.7 24-29.7 24-50c0-4.5-.5-8.8-1.3-13c15.4-11.7 25.3-30.2 25.3-51c0-6.5-1-12.8-2.8-18.7C504.8 273.7 512 257.7 512 240c0-35.3-28.6-64-64-64l-92.3 0c4.7-10.4 8.7-21.2 11.8-32.2l5.7-20c10.9-38.2-11.2-78.1-49.4-89zM32 192c-17.7 0-32 14.3-32 32V448c0 17.7 14.3 32 32 32H96c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32H32z"/></svg>',
36 'color' => '#3f7796',
37 'reaction' => 1,
38 'order' => 0,
39 'status' => true,
40 ],
41 'down' => [
42 'key' => 'down',
43 'label' => wpforo_phrase( 'Dislike', false ),
44 'icon' => '',
45 'html' => '<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M323.8 477.2c-38.2 10.9-78.1-11.2-89-49.4l-5.7-20c-3.7-13-10.4-25-19.5-35l-51.3-56.4c-8.9-9.8-8.2-25 1.6-33.9s25-8.2 33.9 1.6l51.3 56.4c14.1 15.5 24.4 34 30.1 54.1l5.7 20c3.6 12.7 16.9 20.1 29.7 16.5s20.1-16.9 16.5-29.7l-5.7-20c-5.7-19.9-14.7-38.7-26.6-55.5c-5.2-7.3-5.8-16.9-1.7-24.9s12.3-13 21.3-13L448 288c8.8 0 16-7.2 16-16c0-6.8-4.3-12.7-10.4-15c-7.4-2.8-13-9-14.9-16.7s.1-15.8 5.3-21.7c2.5-2.8 4-6.5 4-10.6c0-7.8-5.6-14.3-13-15.7c-8.2-1.6-15.1-7.3-18-15.2s-1.6-16.7 3.6-23.3c2.1-2.7 3.4-6.1 3.4-9.9c0-6.7-4.2-12.6-10.2-14.9c-11.5-4.5-17.7-16.9-14.4-28.8c.4-1.3 .6-2.8 .6-4.3c0-8.8-7.2-16-16-16H286.5c-12.6 0-25 3.7-35.5 10.7l-61.7 41.1c-11 7.4-25.9 4.4-33.3-6.7s-4.4-25.9 6.7-33.3l61.7-41.1c18.4-12.3 40-18.8 62.1-18.8H384c34.7 0 62.9 27.6 64 62c14.6 11.7 24 29.7 24 50c0 4.5-.5 8.8-1.3 13c15.4 11.7 25.3 30.2 25.3 51c0 6.5-1 12.8-2.8 18.7C504.8 238.3 512 254.3 512 272c0 35.3-28.6 64-64 64l-92.3 0c4.7 10.4 8.7 21.2 11.8 32.2l5.7 20c10.9 38.2-11.2 78.1-49.4 89zM32 384c-17.7 0-32-14.3-32-32V128c0-17.7 14.3-32 32-32H96c17.7 0 32 14.3 32 32V352c0 17.7-14.3 32-32 32H32z"/></svg>',
46 'color' => '#f42d2c',
47 'reaction' => - 1,
48 'order' => 1,
49 'status' => true,
50 ],
51 ] );
52
53 $types = array_filter( $types, function( $type ) {
54 return self::is_reaction_type( $type );
55 } );
56
57 if( ! is_null( $status ) ) {
58 $types = array_filter( $types, function( $type ) use ( $status ) {
59 return $type['status'] === $status;
60 } );
61 }
62
63 $types = array_map( function( $type ) {
64 $type['icon'] = self::get_icon( $type );
65
66 return $type;
67 }, $types );
68
69 $order = array_column( $types, 'order' );
70 array_multisort( $order, SORT_ASC, $types );
71
72 return $types;
73 }
74
75 public static function get_type_list( $status = true ): array {
76 return array_keys( self::get_types( $status ) );
77 }
78
79 public static function get_icon( $rtype ): string {
80 if( ! ( $icon = trim( (string) wpfval( $rtype, 'html' ) ) ) ) {
81 if( $fa_class = trim( (string) wpfval( $rtype, 'fa_class' ) ) ) {
82 $icon = sprintf(
83 '<i class="wpf-reaction-icon %1$s" title="%2$s" style="color: %3$s;"></i>',
84 $fa_class,
85 $rtype['label'],
86 $rtype['color']
87 );
88 }
89 }
90
91 if( $icon ) {
92 $icon = sprintf(
93 '<div class="wpf-reaction-icon" title="%1$s" style="color: %2$s;">%3$s</div>',
94 $rtype['label'],
95 $rtype['color'],
96 $icon
97 );
98 }
99
100 return $icon;
101 }
102
103 public static function is_reaction_type( $reaction_type ): bool {
104 if( ! wpfkey( $reaction_type, 'key' ) ) return false;
105 if( ! sanitize_title( $reaction_type['key'] ) ) return false;
106 if( ! wpfkey( $reaction_type, 'label' ) ) return false;
107 if( ! trim( $reaction_type['label'] ) ) return false;
108 if( ! self::get_icon( $reaction_type ) ) return false;
109
110 return true;
111 }
112
113 public static function is_reaction_type_exists( $rtype ): bool {
114 return in_array( ( is_scalar( $rtype ) ? $rtype : (string) wpfval( $rtype, 'key' ) ), self::get_type_list( null ) );
115 }
116
117 private function init_defaults() {
118 $this->default = new stdClass();
119 $this->default->reaction = [
120 'reactionid' => 0,
121 'userid' => 0,
122 'postid' => 0,
123 'post_userid' => 0,
124 'reaction' => 1,
125 'type' => 'up',
126 'name' => '',
127 'email' => '',
128 ];
129 $this->default->reaction_format = [
130 'reactionid' => '%d',
131 'userid' => '%d',
132 'postid' => '%d',
133 'post_userid' => '%d',
134 'reaction' => '%d',
135 'type' => '%s',
136 'name' => '%s',
137 'email' => '%s',
138 ];
139 $this->default->sql_select_args = [
140 'reactionid' => null,
141 'userid' => null,
142 'postid' => null,
143 'postid_include' => [],
144 'postid_exclude' => [],
145 'post_userid' => null,
146 'reaction_include' => [],
147 'reaction_exclude' => [],
148 'type_include' => [],
149 'type_exclude' => [],
150 'name' => null,
151 'email' => null,
152 'orderby' => null,
153 'offset' => null,
154 'row_count' => null,
155 ];
156 }
157
158 /**
159 * @param $reaction
160 *
161 * @return array
162 */
163 public function decode( $reaction ) {
164 $reaction = array_merge( $this->default->reaction, (array) $reaction );
165 $reaction['reactionid'] = wpforo_bigintval( $reaction['reactionid'] );
166 $reaction['userid'] = wpforo_bigintval( $reaction['userid'] );
167 $reaction['postid'] = wpforo_bigintval( $reaction['postid'] );
168 $reaction['post_userid'] = wpforo_bigintval( $reaction['post_userid'] );
169 $reaction['reaction'] = intval( $reaction['reaction'] );
170 $reaction['name'] = trim( strip_tags( (string) $reaction['name'] ) );
171 $reaction['email'] = sanitize_email( (string) $reaction['email'] );
172 if( ! ( $reaction['type'] = trim( strip_tags( (string) $reaction['type'] ) ) ) ) $reaction['type'] = 'up';
173
174 return $reaction;
175 }
176
177 public function decode_item( $reaction ) {
178 if( isset( $reaction['reactionid'] ) ) $reaction['reactionid'] = wpforo_bigintval( $reaction['reactionid'] );
179 if( isset( $reaction['userid'] ) ) $reaction['userid'] = wpforo_bigintval( $reaction['userid'] );
180 if( isset( $reaction['postid'] ) ) $reaction['postid'] = wpforo_bigintval( $reaction['postid'] );
181 if( isset( $reaction['post_userid'] ) ) $reaction['post_userid'] = wpforo_bigintval( $reaction['post_userid'] );
182 if( isset( $reaction['reaction'] ) ) $reaction['reaction'] = intval( $reaction['reaction'] );
183 if( isset( $reaction['name'] ) ) $reaction['name'] = trim( strip_tags( (string) $reaction['name'] ) );
184 if( isset( $reaction['email'] ) ) $reaction['email'] = sanitize_email( $reaction['email'] );
185 if( isset( $reaction['type'] ) ) {
186 if( ! ( $reaction['type'] = trim( strip_tags( (string) $reaction['type'] ) ) ) ) $reaction['type'] = 'up';
187 }
188
189 return $reaction;
190 }
191
192 /**
193 * @param $reaction
194 *
195 * @return array
196 */
197 private function encode( $reaction ) {
198 return $this->decode( $reaction );
199 }
200
201 /**
202 * @param $reaction
203 *
204 * @return false|int
205 */
206 public function add( $reaction ) {
207 $reaction = $this->encode( $reaction );
208 unset( $reaction['reactionid'] );
209 $reaction = wpforo_array_ordered_intersect_key( $reaction, $this->default->reaction_format );
210 if( WPF()->db->insert(
211 WPF()->tables->reactions,
212 $reaction,
213 wpforo_array_ordered_intersect_key( $this->default->reaction_format, $reaction )
214 ) ) {
215 $reaction['reactionid'] = WPF()->db->insert_id;
216 do_action( 'wpforo_after_add_reaction', $reaction );
217
218 return $reaction['reactionid'];
219 }
220
221 return false;
222 }
223
224 /**
225 * @param array $fields
226 * @param array|int $where
227 * @param string $table
228 *
229 * @return bool
230 */
231 public function edit( $fields, $where, $table = '' ) {
232 if( is_numeric( $where ) ) $where = [ 'reactionid' => wpforo_bigintval( $where ) ];
233 $fields = wpforo_array_ordered_intersect_key( $fields, $this->default->reaction_format );
234 if( false !== WPF()->db->update(
235 $table ?: WPF()->tables->reactions,
236 $fields = wpforo_array_ordered_intersect_key( $this->encode( $fields ), $fields ),
237 $where = wpforo_array_ordered_intersect_key( $where, $this->default->reaction_format ),
238 wpforo_array_ordered_intersect_key( $this->default->reaction_format, $fields ),
239 wpforo_array_ordered_intersect_key( $this->default->reaction_format, $where )
240 ) ) {
241 do_action( 'wpforo_after_edit_reaction', $fields, $where );
242
243 return true;
244 }
245
246 return false;
247 }
248
249 public function edit_for_all_active_boards( $fields, $where ) {
250 if( $boardids = WPF()->board->get_active_boardids() ) {
251 foreach( $boardids as $boardid ) {
252 WPF()->change_board( $boardid );
253 $this->edit( $fields, $where );
254 }
255 }
256 }
257
258 /**
259 * @param array|int $args
260 * @param string $operator
261 * @param string $table
262 *
263 * @return bool
264 */
265 public function delete( $args, $operator = 'AND', $table = '' ) {
266 if( is_numeric( $args ) ) $args = [ 'reactionid' => wpforo_bigintval( $args ) ];
267 $operator = trim( strtoupper( (string) $operator ) );
268 if( ! in_array( $operator, [ 'AND', 'OR' ], true ) ) $operator = 'AND';
269
270 do_action( 'wpforo_before_delete_reaction', $args, $operator );
271
272 $sql = "DELETE FROM " . ( $table ?: WPF()->tables->reactions );
273 if( $wheres = $this->build_sql_wheres( $args ) ) $sql .= " WHERE " . implode( " $operator ", $wheres );
274
275 $args = $this->parse_args( $args );
276 if( $args['orderby'] ) $sql .= " ORDER BY " . $args['orderby'];
277 if( $args['row_count'] ) $sql .= " LIMIT " . intval( $args['row_count'] );
278
279 $r = WPF()->db->query( $sql );
280
281 do_action( 'wpforo_after_delete_reaction', $args, $operator );
282
283 return false !== $r;
284 }
285
286 public function delete_for_all_active_boards( $args, $operator = 'AND' ) {
287 if( $boardids = WPF()->board->get_active_boardids() ) {
288 foreach( $boardids as $boardid ) {
289 WPF()->change_board( $boardid );
290 $this->delete( $args, $operator );
291 }
292 }
293 }
294
295 private function parse_args( $args ) {
296 $args = wpforo_parse_args( $args, $this->default->sql_select_args );
297 $args = wpforo_array_ordered_intersect_key( $args, $this->default->sql_select_args );
298 $args['postid_include'] = wpforo_parse_args( $args['postid_include'] );
299 $args['postid_exclude'] = wpforo_parse_args( $args['postid_exclude'] );
300 $args['reaction_include'] = wpforo_parse_args( $args['reaction_include'] );
301 $args['reaction_exclude'] = wpforo_parse_args( $args['reaction_exclude'] );
302 $args['type_include'] = wpforo_parse_args( $args['type_include'] );
303 $args['type_exclude'] = wpforo_parse_args( $args['type_exclude'] );
304 $args['orderby'] = sanitize_sql_orderby( (string) $args['orderby'] );
305
306 return $args;
307 }
308
309 private function build_sql_wheres( $args ) {
310 $args = $this->parse_args( $args );
311 $wheres = [];
312
313 if( ! is_null( $args['reactionid'] ) ) $wheres[] = "`reactionid` = '" . wpforo_bigintval( $args['reactionid'] ) . "'";
314 if( ! is_null( $args['userid'] ) ) $wheres[] = "`userid` = '" . wpforo_bigintval( $args['userid'] ) . "'";
315 if( ! is_null( $args['postid'] ) ) $wheres[] = "`postid` = '" . wpforo_bigintval( $args['postid'] ) . "'";
316 if( ! is_null( $args['post_userid'] ) ) $wheres[] = "`post_userid` = '" . wpforo_bigintval( $args['post_userid'] ) . "'";
317
318 if( ! is_null( $args['name'] ) ) $wheres[] = "`name` = '" . esc_sql( $args['name'] ) . "'";
319 if( ! is_null( $args['email'] ) ) $wheres[] = "`email` = '" . esc_sql( $args['email'] ) . "'";
320
321 if( ! empty( $args['postid_include'] ) ) $wheres[] = "`postid` IN(" . implode( ',', array_map( 'wpforo_bigintval', $args['postid_include'] ) ) . ")";
322 if( ! empty( $args['postid_exclude'] ) ) $wheres[] = "`postid` NOT IN(" . implode( ',', array_map( 'wpforo_bigintval', $args['postid_exclude'] ) ) . ")";
323
324 if( ! empty( $args['reaction_include'] ) ) $wheres[] = "`reaction` IN(" . implode( ',', array_map( 'intval', $args['reaction_include'] ) ) . ")";
325 if( ! empty( $args['reaction_exclude'] ) ) $wheres[] = "`reaction` NOT IN(" . implode( ',', array_map( 'intval', $args['reaction_exclude'] ) ) . ")";
326
327 if( ! empty( $args['type_include'] ) ) $wheres[] = "`type` IN('" . implode( "','", array_map( 'trim', $args['type_include'] ) ) . "')";
328 if( ! empty( $args['type_exclude'] ) ) $wheres[] = "`type` NOT IN(" . implode( "','", array_map( 'trim', $args['type_exclude'] ) ) . "')";
329
330 return $wheres;
331 }
332
333 /**
334 * @param $args
335 * @param $select
336 * @param $operator
337 *
338 * @return string
339 */
340 private function build_sql_select( $args, $select = '', $operator = 'AND' ) {
341 if( ! $select ) $select = '*';
342 $operator = trim( strtoupper( (string) $operator ) );
343 if( ! in_array( $operator, [ 'AND', 'OR' ], true ) ) $operator = 'AND';
344
345 $sql = "SELECT $select FROM " . WPF()->tables->reactions;
346 if( $wheres = $this->build_sql_wheres( $args ) ) $sql .= " WHERE " . implode( " $operator ", $wheres );
347
348 $args = $this->parse_args( $args );
349 if( $args['orderby'] ) $sql .= " ORDER BY " . $args['orderby'];
350 if( $args['row_count'] ) $sql .= " LIMIT " . intval( $args['offset'] ) . "," . intval( $args['row_count'] );
351
352 return $sql;
353 }
354
355 /**
356 * @param array|numeric $args
357 *
358 * @return array
359 */
360 public function _get_reaction( $args, $operator = 'AND' ) {
361 if( is_numeric( $args ) ) $args = [ 'reactionid' => wpforo_bigintval( $args ) ];
362
363 if( WPF()->cache->on( 'reaction' ) ) {
364 // If there is no reaction cache it creates a new cache file
365 // The cache is based on postid, each cache item contains all reactions of the postid
366 $reactions = $this->get_post_reactions_and_cache( $args, $operator );
367 if( is_array( $reactions ) ) return (array) array_shift( $reactions );
368
369 }
370
371 // In case there is no postid in the $args, the original SQL query is executed
372 if( ! wpfkey( $args, 'orderby' ) ) $args['orderby'] = '`reactionid` DESC';
373 $reaction = (array) WPF()->db->get_row( $this->build_sql_select( $args, '', $operator ), ARRAY_A );
374 if( $reaction ) $reaction = $this->decode( $reaction );
375
376 return $reaction;
377 }
378
379 public function get_reaction( $args, $operator = 'AND' ) {
380 return wpforo_ram_get( [ $this, '_get_reaction' ], $args, $operator );
381 }
382
383 /**
384 * @param array $args
385 *
386 * @return array
387 */
388 public function _get_reactions( $args = [], $operator = 'AND' ) {
389 return array_map( [ $this, 'decode' ], (array) WPF()->db->get_results( $this->build_sql_select( $args, '', $operator ), ARRAY_A ) );
390 }
391
392 public function get_reactions( $args = [], $operator = 'AND' ) {
393 return wpforo_ram_get( [ $this, '_get_reactions' ], $args, $operator );
394 }
395
396 public function _get_reactions_col( $col, $args = [], $operator = 'AND' ) {
397 $r = WPF()->db->get_col( $this->build_sql_select( $args, "`$col`", $operator ) );
398 if( $this->default->reaction_format[ $col ] === '%d' ) $r = array_map( 'wpforo_bigintval', $r );
399
400 return $r;
401 }
402
403 public function get_reactions_col( $col, $args = [], $operator = 'AND' ) {
404 return wpforo_ram_get( [ $this, '_get_reactions_col' ], $col, $args, $operator );
405 }
406
407 /**
408 * @param array $args
409 *
410 * @return int
411 */
412 public function _get_count( $args = [], $operator = 'AND' ) {
413 // If there is no reaction cache it creates a new cache file
414 // The cache is based on postid, each cache item contains all reactions of the postid
415 $reactions = $this->get_post_reactions_and_cache( $args, $operator );
416 if( is_array( $reactions ) ) return count( $reactions );
417
418 return (int) WPF()->db->get_var( $this->build_sql_select( $args, 'COUNT(*)', $operator ) );
419 }
420
421 public function get_count( $args = [], $operator = 'AND' ) {
422 return wpforo_ram_get( [ $this, '_get_count' ], $args, $operator );
423 }
424
425 /**
426 * @param array|int $args
427 *
428 * @return int
429 */
430 public function get_sum( $args = [], $operator = 'AND' ) {
431 if( is_numeric( $args ) ) $args = [ 'postid' => wpforo_bigintval( $args ) ];
432
433 return (int) WPF()->db->get_var( $this->build_sql_select( $args, 'SUM(`reaction`)', $operator ) );
434 }
435
436 public function get_reacted_count( $userid, $types = [] ) {
437 return $this->get_count( [ 'userid' => $userid, 'type_include' => $types ] );
438 }
439
440 public function get_received_reactions_count( $userid, $types = [] ) {
441 return $this->get_count( [ 'post_userid' => $userid, 'type_include' => $types ] );
442 }
443
444 public function get_post_reactions_count( $postid, $types = [] ) {
445 $filtered = apply_filters( 'wpforo_reactions_get_post_reactions_count', null, $postid, $types );
446 if( ! is_null( $filtered ) ) return (int) $filtered;
447
448 return $this->get_count( [ 'postid' => $postid, 'type_include' => $types ] );
449 }
450
451 public function get_post_reactions_count_grouped_by_type_sql( $postid ): array {
452 $postid = wpforo_bigintval( $postid );
453 $sql = "SELECT `type`, COUNT(`type`) AS `count` FROM `" . WPF()->tables->reactions . "` WHERE `postid` = %d GROUP BY `type` ORDER BY MAX(`reactionid`) DESC";
454 $types_count = WPF()->db->get_results( WPF()->db->prepare( $sql, $postid ), ARRAY_A );
455
456 $r = [];
457 if( $types_count ) {
458 foreach( self::get_type_list() as $type ) {
459 foreach( $types_count as $item ) {
460 if( $item['type'] === $type ) {
461 $item['count'] = intval( $item['count'] );
462 $r[] = $item;
463 break;
464 }
465 }
466 }
467 /*$type_list = self::get_type_list();
468 $r = array_filter( (array) $types_count, function( $item ) use ( $type_list ) {
469 return in_array( $item['type'], $type_list );
470 } );
471 $r = array_map( function( $item ) {
472 $item['count'] = intval( $item['count'] );
473
474 return $item;
475 }, $r );*/
476 }
477
478 return $r;
479 }
480
481 public function get_post_reactions_count_grouped_by_type( $postid ): array {
482 $postid = wpforo_bigintval( $postid );
483 $reactions = $this->get_post_reactions_and_cache( [ 'postid' => $postid ] );
484
485 $r = [];
486 if( $reactions ) {
487 foreach( self::get_type_list() as $type ) {
488 foreach( $reactions as $reaction ) {
489 if( $reaction['type'] === $type ) {
490 if( ! wpfkey( $r, $type ) ) $r[ $type ] = [ 'type' => $type, 'count' => 0 ];
491 $r[ $type ]['count'] ++;
492 }
493 }
494 }
495 }
496
497 return array_values( $r );
498 }
499
500 public function get_post_reactions_user_dnames( $postid ): array {
501 $filtered = apply_filters( 'wpforo_reactions_get_post_reactions_user_dnames', null, $postid );
502 if( ! is_null( $filtered ) ) return (array) $filtered;
503
504 $rows = WPF()->db->get_results(
505 WPF()->db->prepare(
506 "SELECT u.`ID` as userid, u.`display_name`, u.`user_nicename`
507 FROM `" . WPF()->db->users . "` u
508 INNER JOIN `" . WPF()->tables->reactions . "` r ON r.`userid` = u.`ID`
509 WHERE r.`postid` = %d
510 ORDER BY r.`userid` = %d DESC, r.`reactionid` DESC LIMIT 3",
511 $postid,
512 WPF()->current_userid
513 ),
514 ARRAY_A
515 );
516
517 return array_map(
518 function( $row ) {
519 $row['dname'] = wpforo_user_dname( $row );
520
521 return $row;
522 },
523 $rows
524 );
525 }
526
527 public function get_user_reaction( $postid, $userid = 0 ) {
528 if( ! ( $userid = wpforo_bigintval( $userid ) ) ) {
529 $userid = WPF()->current_userid;
530 }
531 $reaction = $this->get_reaction( [ 'postid' => $postid, 'userid' => $userid ] );
532 if( $reaction ) return $reaction;
533
534 return null;
535 }
536
537 public function get_user_reaction_reaction( $postid, $userid = 0 ) {
538 $reaction = $this->get_user_reaction( $postid, $userid );
539
540 return wpfval( $reaction, 'reaction' );
541 }
542
543 public function is_reacted( $postid, $userid = 0, $type = [] ) {
544 if( ! ( $userid = wpforo_bigintval( $userid ) ) ) {
545 $userid = WPF()->current_userid;
546 }
547
548 return (bool) $this->get_reaction( [ 'postid' => $postid, 'userid' => $userid, 'type_include' => (array) $type ] );
549 }
550
551 public function get_likes_for_topic( $topicid ) {
552 if( $postids = WPF()->topic->get_postids( $topicid ) ) {
553 return $this->get_count( [
554 'postid_include' => $postids,
555 'type_include' => self::get_type_list(),
556 ] );
557 }
558
559 return 0;
560 }
561
562 /**
563 * @param int $postid the postid in the array is required
564 *
565 * @return array | NULL: if the cache is not found | []: if there is no reaction | array( array(...), array(...) ): the reaction
566 *
567 * // $reactions can be either empty array, array of arrays or NULL
568 * // 1. empty array: post has no reaction matched to the $args attributes
569 * // 2. array of arrays: post has some reactions and $args matched
570 * // 3. NULL: there is no reaction cache for this post or the cache is disabled
571 */
572 public function get_post_reactions_cache( $postid ) {
573 if( WPF()->cache->on( 'reaction' ) && ( $postid = wpforo_bigintval( $postid ) ) ) {
574 $reactions = WPF()->cache->get_item( $postid, 'reaction' );
575 if( is_array( $reactions ) ) return $reactions;
576 }
577
578 return null;
579 }
580
581 public function filter_reactions( $args, $reactions, $operator = 'AND' ) {
582 $args = $this->decode_item( $args );
583
584 // TODO: currently the cache filter only works by $operator = 'AND' state
585
586 foreach( $reactions as $reaction_key => $reaction ) {
587 $match = true;
588
589 // Filter reactions based on requested arguments
590 foreach( $args as $key => $value ) {
591 if( ! is_array( $value ) ) {
592 // If even one attribute doesn't match to the
593 // corresponding attribute of the reaction,
594 // the filter loop is stopped and $match becomes false
595 if( $value !== $reaction[ $key ] ) {
596 $match = false;
597 break;
598 }
599 } else {
600 // -----------
601 if( ! empty( $value ) ) {
602 if( $key === 'reaction_include' ) {
603 foreach( $value as $v ) {
604 if( $v !== $reaction['reactionid'] ) {
605 $match = false;
606 break 2;
607 }
608 }
609 } elseif( $key === 'reaction_exclude' ) {
610 foreach( $value as $v ) {
611 if( $v === $reaction['reactionid'] ) {
612 $match = false;
613 break 2;
614 }
615 }
616 } elseif( $key === 'type_include' ) {
617 foreach( $value as $v ) {
618 if( $v !== $reaction['type'] ) {
619 $match = false;
620 break 2;
621 }
622 }
623 } elseif( $key === 'type_exclude' ) {
624 foreach( $value as $v ) {
625 if( $v === $reaction['type'] ) {
626 $match = false;
627 break 2;
628 }
629 }
630 } else {
631 foreach( $value as $v ) {
632 if( $v !== $reaction[ $key ] ) {
633 $match = false;
634 break 2;
635 }
636 }
637 }
638 }
639 //------------
640 }
641 }
642
643 if( ! $match ) {
644 unset( $reactions[ $reaction_key ] );
645 }
646 }
647
648 return $reactions;
649 }
650
651 public function create_reaction_cache( $postid, $reactions = [] ) {
652 if( WPF()->cache->on( 'reaction' ) && $postid ) {
653 $reactions = $reactions ?: $this->_get_reactions( [ 'postid' => $postid ] );
654 WPF()->cache->create( 'item', [ $postid => $reactions ], 'reaction' );
655 }
656 }
657
658 public function get_post_reactions_and_cache( $args, $operator = 'AND' ) {
659 // If there is no reaction cache it creates a new cache file
660 // The cache is based on postid, each cache item contains all reactions of the postid
661 if( WPF()->cache->on( 'reaction' ) && wpfval( $args, 'postid' ) ) {
662 $reactions = $this->get_post_reactions_cache( $args['postid'] );
663 if( ! is_array( $reactions ) ) {
664 //If no reaction cache found for current postid, it creates new one.
665 $reactions = $this->get_reactions( [ 'postid' => $args['postid'] ] );
666 $this->create_reaction_cache( $args['postid'], $reactions );
667 }
668
669 //The reactions of current postid are filtered for current $args
670 return $this->filter_reactions( $args, $reactions, $operator );
671 }
672
673 return null;
674 }
675 }
676