PluginProbe
wpForo Forum / 2.4.2
wpForo Forum v2.4.2
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 1.4.2 All 137 releases
wpforo / classes / Activity.php

Activity.php in wpForo Forum 2.4.2, at classes/Activity.php

746 lines 32.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace wpforo\classes;
4
5 use stdClass;
6
7 // Exit if accessed directly
8 if( ! defined( 'ABSPATH' ) ) exit;
9
10 class Activity {
11 private $default;
12 public $activity;
13 private $actions;
14 public $notifications = [];
15
16 public function __construct() {
17 add_action( 'wpforo_after_init', [ $this, 'init' ] );
18 }
19
20 public function init() {
21 $this->init_defaults();
22 $this->activity = $this->default->activity;
23 $this->init_hooks();
24 $this->init_actions();
25 if( is_user_logged_in() && wpforo_setting( 'notifications', 'notifications' ) ) {
26 $this->notifications = $this->get_notifications();
27 }
28 }
29
30 private function init_actions() {
31 $this->actions = [
32 'edit_topic' => [
33 'title' => wpforo_phrase( 'Edit Topic', false ),
34 'icon' => '',
35 'description' => wpforo_phrase( 'This topic was modified %s by %s', false ),
36 'before' => '<div class="wpf-post-edited"><i class="far fa-edit"></i>',
37 'after' => '</div>',
38 ],
39 'edit_post' => [
40 'title' => wpforo_phrase( 'Edit Post', false ),
41 'icon' => '',
42 'description' => wpforo_phrase( 'This post was modified %s by %s', false ),
43 'before' => '<div class="wpf-post-edited"><i class="far fa-edit"></i>',
44 'after' => '</div>',
45 ],
46 'new_reply' => [
47 'title' => wpforo_phrase( 'New Reply', false ),
48 'icon' => '<svg style="transform: rotate(180deg);" height="12" width="12" viewBox="0 0 512 512"><path fill="currentColor" d="M8.309 189.836L184.313 37.851C199.719 24.546 224 35.347 224 56.015v80.053c160.629 1.839 288 34.032 288 186.258c0 61.441-39.581 122.309-83.333 154.132c-13.653 9.931-33.111-2.533-28.077-18.631c45.344-145.012-21.507-183.51-176.59-185.742V360c0 20.7-24.3 31.453-39.687 18.164l-176.004-152c-11.071-9.562-11.086-26.753 0-36.328"></path></svg>',
49 'description' => wpforo_phrase( 'New reply from %1$s, %2$s', false ),
50 'before' => '<li class="wpf-new_reply">',
51 'after' => '</li>',
52 ],
53 'new_like' => [
54 'title' => wpforo_phrase( 'New Like', false ),
55 'icon' => '<svg height="12" width="12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M313.4 32.9c26 5.2 42.9 30.5 37.7 56.5l-2.3 11.4c-5.3 26.7-15.1 52.1-28.8 75.2H464c26.5 0 48 21.5 48 48c0 18.5-10.5 34.6-25.9 42.6C497 275.4 504 288.9 504 304c0 23.4-16.8 42.9-38.9 47.1c4.4 7.3 6.9 15.8 6.9 24.9c0 21.3-13.9 39.4-33.1 45.6c.7 3.3 1.1 6.8 1.1 10.4c0 26.5-21.5 48-48 48H294.5c-19 0-37.5-5.6-53.3-16.1l-38.5-25.7C176 420.4 160 390.4 160 358.3V320 272 247.1c0-29.2 13.3-56.7 36-75l7.4-5.9c26.5-21.2 44.6-51 51.2-84.2l2.3-11.4c5.2-26 30.5-42.9 56.5-37.7zM32 192H96c17.7 0 32 14.3 32 32V448c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32V224c0-17.7 14.3-32 32-32z"/></svg>',
56 'description' => wpforo_phrase( 'New like from %1$s, %2$s', false ),
57 'before' => '<li class="wpf-new_like">',
58 'after' => '</li>',
59 ],
60 'new_dislike' => [
61 'title' => wpforo_phrase( 'New Dislike', false ),
62 'icon' => '<svg height="12" width="12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M313.4 479.1c26-5.2 42.9-30.5 37.7-56.5l-2.3-11.4c-5.3-26.7-15.1-52.1-28.8-75.2H464c26.5 0 48-21.5 48-48c0-18.5-10.5-34.6-25.9-42.6C497 236.6 504 223.1 504 208c0-23.4-16.8-42.9-38.9-47.1c4.4-7.3 6.9-15.8 6.9-24.9c0-21.3-13.9-39.4-33.1-45.6c.7-3.3 1.1-6.8 1.1-10.4c0-26.5-21.5-48-48-48H294.5c-19 0-37.5 5.6-53.3 16.1L202.7 73.8C176 91.6 160 121.6 160 153.7V192v48 24.9c0 29.2 13.3 56.7 36 75l7.4 5.9c26.5 21.2 44.6 51 51.2 84.2l2.3 11.4c5.2 26 30.5 42.9 56.5 37.7zM32 384H96c17.7 0 32-14.3 32-32V128c0-17.7-14.3-32-32-32H32C14.3 96 0 110.3 0 128V352c0 17.7 14.3 32 32 32z"/></svg>',
63 'description' => wpforo_phrase( 'New dislike from %1$s, %2$s', false ),
64 'before' => '<li class="wpf-new_dislike">',
65 'after' => '</li>',
66 ],
67 'new_up_vote' => [
68 'title' => wpforo_phrase( 'New Up Vote', false ),
69 'icon' => '<svg height="12" width="12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M8 256C8 119 119 8 256 8s248 111 248 248-111 248-248 248S8 393 8 256zm292 116V256h70.9c10.7 0 16.1-13 8.5-20.5L264.5 121.2c-4.7-4.7-12.2-4.7-16.9 0l-115 114.3c-7.6 7.6-2.2 20.5 8.5 20.5H212v116c0 6.6 5.4 12 12 12h64c6.6 0 12-5.4 12-12z"/></svg>',
70 'description' => wpforo_phrase( 'New up vote from %1$s, %2$s', false ),
71 'before' => '<li class="wpf-new_up_vote">',
72 'after' => '</li>',
73 ],
74 'new_down_vote' => [
75 'title' => wpforo_phrase( 'New Down Vote', false ),
76 'icon' => '<svg height="12" width="12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 137-111 248-248 248S8 393 8 256 119 8 256 8s248 111 248 248zM212 140v116h-70.9c-10.7 0-16.1 13-8.5 20.5l114.9 114.3c4.7 4.7 12.2 4.7 16.9 0l114.9-114.3c7.6-7.6 2.2-20.5-8.5-20.5H300V140c0-6.6-5.4-12-12-12h-64c-6.6 0-12 5.4-12 12z"/></svg>',
77 'description' => wpforo_phrase( 'New down vote from %1$s, %2$s', false ),
78 'before' => '<li class="wpf-new_down_vote">',
79 'after' => '</li>',
80 ],
81 'new_reaction' => [
82 'title' => wpforo_phrase( 'New Reaction', false ),
83 'icon' => '<svg height="12" width="12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M349.4 44.6c5.9-13.7 1.5-29.7-10.6-38.5s-28.6-8-39.9 1.8l-256 224c-10 8.8-13.6 22.9-8.9 35.3S50.7 288 64 288H175.5L98.6 467.4c-5.9 13.7-1.5 29.7 10.6 38.5s28.6 8 39.9-1.8l256-224c10-8.8 13.6-22.9 8.9-35.3s-16.6-20.7-30-20.7H272.5L349.4 44.6z"/></svg>',
84 'description' => wpforo_phrase( 'New Reaction from %1$s, %2$s', false ),
85 'before' => '<li class="wpf-new_reaction">',
86 'after' => '</li>',
87 ],
88 'new_mention' => [
89 'title' => wpforo_phrase( 'New User Mentioning', false ),
90 'icon' => '<svg height="12" width="12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C118.9 8 8 118.9 8 256c0 137.1 110.9 248 248 248 48.2 0 95.3-14.1 135.4-40.2 12-7.8 14.6-24.3 5.6-35.4l-10.2-12.4c-7.7-9.4-21.2-11.7-31.4-5.1C325.9 429.8 291.3 440 256 440c-101.5 0-184-82.5-184-184S154.5 72 256 72c100.1 0 184 57.6 184 160 0 38.8-21.1 79.7-58.2 83.7-17.3-.5-16.9-12.9-13.5-30l23.4-121.1C394.7 149.8 383.3 136 368.2 136h-45a13.5 13.5 0 0 0 -13.4 12l0 .1c-14.7-17.9-40.4-21.8-60-21.8-74.6 0-137.8 62.2-137.8 151.5 0 65.3 36.8 105.9 96 105.9 27 0 57.4-15.6 75-38.3 9.5 34.1 40.6 34.1 70.7 34.1C462.6 379.4 504 307.8 504 232 504 95.7 394 8 256 8zm-21.7 304.4c-22.2 0-36.1-15.6-36.1-40.8 0-45 30.8-72.7 58.6-72.7 22.3 0 35.6 15.2 35.6 40.8 0 45.1-33.9 72.7-58.2 72.7z"/></svg>',
91 'description' => wpforo_phrase( '%1$s has mentioned you, %2$s', false ),
92 'before' => '<li class="wpf-new_mention">',
93 'after' => '</li>',
94 ],
95 'default' => [
96 'title' => wpforo_phrase( 'New Notification', false ),
97 'icon' => '<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32V51.2C119 66 64 130.6 64 208v18.8c0 47-17.3 92.4-48.5 127.6l-7.4 8.3c-8.4 9.4-10.4 22.9-5.3 34.4S19.4 416 32 416H416c12.6 0 24-7.4 29.2-18.9s3.1-25-5.3-34.4l-7.4-8.3C401.3 319.2 384 273.9 384 226.8V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32zm45.3 493.3c12-12 18.7-28.3 18.7-45.3H224 160c0 17 6.7 33.3 18.7 45.3s28.3 18.7 45.3 18.7s33.3-6.7 45.3-18.7z"/></svg>',
98 'description' => wpforo_phrase( 'New notification from %1$s, %2$s', false ),
99 'before' => '<li class="wpf-new_note">',
100 'after' => '</li>',
101 ],
102 ];
103
104 $this->actions = apply_filters( 'wpforo_register_actions', $this->actions );
105 }
106
107 private function init_defaults() {
108 $this->default = new stdClass();
109 $this->default->activity = [
110 'id' => 0,
111 'type' => '',
112 'itemid' => 0,
113 'itemtype' => '',
114 'itemid_second' => 0,
115 'userid' => 0,
116 'name' => '',
117 'email' => '',
118 'date' => 0,
119 'content' => '',
120 'permalink' => '',
121 'new' => 0,
122 ];
123 $this->default->activity_format = [
124 'id' => '%d',
125 'type' => '%s',
126 'itemid' => '%d',
127 'itemtype' => '%s',
128 'itemid_second' => '%d',
129 'userid' => '%d',
130 'name' => '%s',
131 'email' => '%s',
132 'date' => '%d',
133 'content' => '%s',
134 'permalink' => '%s',
135 'new' => '%d',
136 ];
137 $this->default->sql_select_args = [
138 'type' => null,
139 'userid' => null,
140 'itemtype' => null,
141 'new' => null,
142 'include' => [],
143 'exclude' => [],
144 'userids_include' => [],
145 'userids_exclude' => [],
146 'types_include' => [],
147 'types_exclude' => [],
148 'itemids_include' => [],
149 'itemids_exclude' => [],
150 'itemtypes_include' => [],
151 'itemtypes_exclude' => [],
152 'emails_include' => [],
153 'emails_exclude' => [],
154 'orderby' => 'id',
155 'order' => 'ASC',
156 'offset' => null,
157 'row_count' => null,
158 ];
159
160 $this->default = apply_filters( 'wpforo_activity_after_init_defaults', $this->default );
161 }
162
163 private function init_hooks() {
164 if( wpforo_setting( 'posting', 'edit_topic' ) ) {
165 add_action( 'wpforo_after_edit_topic', [ $this, 'after_edit_topic' ] );
166 }
167 if( wpforo_setting( 'posting', 'edit_post' ) ) {
168 add_action( 'wpforo_after_edit_post', [ $this, 'after_edit_post' ] );
169 }
170
171 if( WPF()->current_userid && wpforo_setting( 'notifications', 'notifications' ) ) {
172 if( wpforo_setting( 'notifications', 'notifications_bar' ) ) {
173 add_action( 'wpforo_before_search_toggle', [ $this, 'bell' ] );
174 }
175 add_action( 'wpforo_after_add_post', [ $this, 'after_add_post' ], 10, 2 );
176 add_action( 'wpforo_post_status_update', [ $this, 'update_notification' ], 10, 2 );
177 add_action( 'wpforo_vote', [ $this, 'after_vote' ], 10, 2 );
178 add_action( 'wpforo_react_post', [ &$this, 'after_react' ], 10, 2 );
179 add_action( 'wpforo_unreact_post', [ &$this, 'after_unreact' ] );
180 }
181 }
182
183 private function filter_built_html_rows( $rows ) {
184 $_rows = [];
185 foreach( $rows as $row_key => $row ) {
186 $in_array = false;
187 if( $_rows ) {
188 foreach( $_rows as $_row_key => $_row ) {
189 if( in_array( $row, $_row ) ) {
190 $in_array = true;
191 $match_key = $_row_key;
192 break;
193 }
194 }
195 }
196 if( $in_array && isset( $match_key ) ) {
197 $_rows[ $match_key ]['times'] ++;
198 } else {
199 $_rows[ $row_key ]['html'] = $row;
200 $_rows[ $row_key ]['times'] = 1;
201 }
202 }
203
204 $rows = [];
205 foreach( $_rows as $_row ) {
206 $times = '';
207 if( $_row['times'] > 1 ) {
208 $times = ' ' . sprintf(
209 wpforo_phrase( '%d times', false ),
210 $_row['times']
211 );
212 }
213
214 $rows[] = sprintf( $_row['html'], $times );
215 }
216
217 $limit = wpforo_setting( 'posting', 'edit_log_display_limit' );
218 if( $limit ) $rows = array_slice( $rows, ( - 1 * $limit ), $limit );
219
220 return $rows;
221 }
222
223 private function parse_activity( $data ) {
224 return apply_filters( 'wpforo_activty_parse_activity', array_merge( $this->default->activity, $data ) );
225 }
226
227 private function parse_args( $args ) {
228 $args = wpforo_parse_args( $args, $this->default->sql_select_args );
229
230 $args['include'] = wpforo_parse_args( $args['include'] );
231 $args['exclude'] = wpforo_parse_args( $args['exclude'] );
232
233 $args['userids_include'] = wpforo_parse_args( $args['userids_include'] );
234 $args['userids_exclude'] = wpforo_parse_args( $args['userids_exclude'] );
235
236 $args['types_include'] = wpforo_parse_args( $args['types_include'] );
237 $args['types_exclude'] = wpforo_parse_args( $args['types_exclude'] );
238
239 $args['itemids_include'] = wpforo_parse_args( $args['itemids_include'] );
240 $args['itemids_exclude'] = wpforo_parse_args( $args['itemids_exclude'] );
241
242 $args['itemtypes_include'] = wpforo_parse_args( $args['itemtypes_include'] );
243 $args['itemtypes_exclude'] = wpforo_parse_args( $args['itemtypes_exclude'] );
244
245 $args['emails_include'] = wpforo_parse_args( $args['emails_include'] );
246 $args['emails_exclude'] = wpforo_parse_args( $args['emails_exclude'] );
247
248 return $args;
249 }
250
251 private function build_sql_select( $args ) {
252 $args = $this->parse_args( $args );
253
254 $wheres = [];
255
256 if( ! is_null( $args['type'] ) ) $wheres[] = "`type` = '" . esc_sql( $args['type'] ) . "'";
257 if( ! is_null( $args['itemtype'] ) ) $wheres[] = "`itemtype` = '" . esc_sql( $args['itemtype'] ) . "'";
258 if( ! is_null( $args['userid'] ) ) $wheres[] = "`userid` = " . intval( $args['userid'] );
259 if( ! is_null( $args['new'] ) ) $wheres[] = "`new` = " . intval( $args['new'] );
260
261 if( ! empty( $args['include'] ) ) $wheres[] = "`id` IN(" . implode( ',', array_map( 'wpforo_bigintval', $args['include'] ) ) . ")";
262 if( ! empty( $args['exclude'] ) ) $wheres[] = "`id` NOT IN(" . implode( ',', array_map( 'wpforo_bigintval', $args['exclude'] ) ) . ")";
263
264 if( ! empty( $args['userids_include'] ) ) $wheres[] = "`userid` IN(" . implode( ',', array_map( 'wpforo_bigintval', $args['userids_include'] ) ) . ")";
265 if( ! empty( $args['userids_exclude'] ) ) $wheres[] = "`userid` NOT IN(" . implode( ',', array_map( 'wpforo_bigintval', $args['userids_exclude'] ) ) . ")";
266
267 if( ! empty( $args['types_include'] ) ) $wheres[] = "`type` IN('" . implode( "','", array_map( 'trim', $args['types_include'] ) ) . "')";
268 if( ! empty( $args['types_exclude'] ) ) $wheres[] = "`type` NOT IN('" . implode( "','", array_map( 'trim', $args['types_exclude'] ) ) . "')";
269
270 if( ! empty( $args['itemids_include'] ) ) $wheres[] = "`itemid` IN(" . implode( ',', array_map( 'wpforo_bigintval', $args['itemids_include'] ) ) . ")";
271 if( ! empty( $args['itemids_exclude'] ) ) $wheres[] = "`itemid` NOT IN(" . implode( ',', array_map( 'wpforo_bigintval', $args['itemids_exclude'] ) ) . ")";
272
273 if( ! empty( $args['itemtypes_include'] ) ) $wheres[] = "`itemtype` IN('" . implode( "','", array_map( 'trim', $args['itemtypes_include'] ) ) . "')";
274 if( ! empty( $args['itemtypes_exclude'] ) ) $wheres[] = "`itemtype` NOT IN('" . implode( "','", array_map( 'trim', $args['itemtypes_exclude'] ) ) . "')";
275
276 if( ! empty( $args['emails_include'] ) ) $wheres[] = "`email` IN('" . implode( "','", array_map( 'trim', $args['emails_include'] ) ) . "')";
277 if( ! empty( $args['emails_exclude'] ) ) $wheres[] = "`email` NOT IN('" . implode( "','", array_map( 'trim', $args['emails_exclude'] ) ) . "')";
278
279 $sql = "SELECT * FROM " . WPF()->tables->activity;
280 if( $wheres ) $sql .= " WHERE " . implode( " AND ", $wheres );
281 $sql .= " ORDER BY " . $args['orderby'] . " " . $args['order'];
282 if( $args['row_count'] ) {
283 if( ! empty( $args['offset'] ) ) {
284 $sql .= " LIMIT " . wpforo_bigintval( $args['offset'] ) . "," . wpforo_bigintval( $args['row_count'] );
285 } else {
286 $sql .= " LIMIT " . wpforo_bigintval( $args['row_count'] );
287 }
288 }
289
290 return $sql;
291 }
292
293 public function get_activity( $args ) {
294 if( ! $args ) return false;
295
296 return $this->parse_activity( (array) WPF()->db->get_row( $this->build_sql_select( $args ), ARRAY_A ) );
297 }
298
299 public function get_activities( $args ) {
300 if( ! $args ) return [];
301
302 return array_map( [ $this, 'parse_activity' ], (array) WPF()->db->get_results( $this->build_sql_select( $args ), ARRAY_A ) );
303 }
304
305 public function after_edit_topic( $topic ) {
306 $data = [
307 'type' => 'edit_topic',
308 'itemid' => $topic['topicid'],
309 'itemtype' => 'topic',
310 'userid' => WPF()->current_userid,
311 'name' => WPF()->current_user_display_name,
312 'email' => WPF()->current_user_email,
313 'permalink' => wpforo_topic( $topic['topicid'], 'url' ),
314 ];
315
316 $this->add( $data );
317 }
318
319 public function after_edit_post( $post ) {
320 $data = [
321 'type' => 'edit_post',
322 'itemid' => $post['postid'],
323 'itemtype' => 'post',
324 'userid' => WPF()->current_userid,
325 'name' => WPF()->current_user_display_name,
326 'email' => WPF()->current_user_email,
327 'permalink' => wpforo_post( $post['postid'], 'url' ),
328 ];
329
330 $this->add( $data );
331 }
332
333 public function after_add_post( $post, $topic ) {
334 $this->add_notification_new_reply( 'new_reply', $post, $topic );
335 }
336
337 private function add( $data ) {
338 if( empty( $data ) ) return false;
339 $activity = array_merge( $this->default->activity, $data );
340 unset( $activity['id'] );
341
342 if( ! $activity['type'] || ! $activity['itemid'] || ! $activity['itemtype'] ) return false;
343 if( ! $activity['date'] ) $activity['date'] = time();
344
345 $activity = apply_filters( 'wpforo_add_activity_data_filter', $activity );
346 do_action( 'wpforo_before_add_activity', $activity );
347
348 $activity = wpforo_array_ordered_intersect_key( $activity, $this->default->activity_format );
349 if( WPF()->db->insert(
350 WPF()->tables->activity,
351 $activity,
352 wpforo_array_ordered_intersect_key( $this->default->activity_format, $activity )
353 ) ) {
354 $activity['id'] = WPF()->db->insert_id;
355 do_action( 'wpforo_after_add_activity', $activity );
356
357 return $activity['id'];
358 }
359
360 return false;
361 }
362
363 private function edit( $data, $where ) {
364 if( empty( $data ) || empty( $where ) ) return false;
365 if( is_numeric( $where ) ) $where = [ 'id' => $where ];
366 $data = (array) $data;
367 $where = (array) $where;
368
369 $data = apply_filters( 'wpforo_activity_edit_data_filter', $data );
370 $where = apply_filters( 'wpforo_activity_edit_where_filter', $where );
371 do_action( 'wpforo_before_edit_activity', $data, $where );
372
373 $data = wpforo_array_ordered_intersect_key( $data, $this->default->activity_format );
374 $where = wpforo_array_ordered_intersect_key( $where, $this->default->activity_format );
375 if( false !== WPF()->db->update(
376 WPF()->tables->activity,
377 $data,
378 $where,
379 wpforo_array_ordered_intersect_key( $this->default->activity_format, $data ),
380 wpforo_array_ordered_intersect_key( $this->default->activity_format, $where )
381 ) ) {
382 do_action( 'wpforo_after_edit_activity', $data, $where );
383
384 return true;
385 }
386
387 return false;
388 }
389
390 private function delete( $where ): bool {
391 if( empty( $where ) ) return false;
392 if( wpforo_is_id( $where ) ) $where = [ 'id' => $where ];
393 $where = (array) $where;
394
395 $where = apply_filters( 'wpforo_activity_delete_where_filter', $where );
396 do_action( 'wpforo_before_delete_activity', $where );
397
398 $where = wpforo_array_ordered_intersect_key( $where, $this->default->activity_format );
399 if( false !== WPF()->db->delete(
400 WPF()->tables->activity,
401 $where,
402 wpforo_array_ordered_intersect_key( $this->default->activity_format, $where )
403 ) ) {
404 do_action( 'wpforo_after_delete_activity', $where );
405
406 return true;
407 }
408
409 return false;
410 }
411
412 public function build( $itemtype, $itemid, $type, $echo = false ): string {
413 $rows = [];
414 $args = [
415 'itemtypes_include' => $itemtype,
416 'itemids_include' => $itemid,
417 'types_include' => $type,
418 ];
419 if( $activities = $this->get_activities( $args ) ) {
420 foreach( $activities as $activity ) {
421 switch( $activity['type'] ) {
422 case 'edit_topic':
423 case 'edit_post':
424 $rows[] = $this->_build_edit_topic_edit_post( $activity );
425 break;
426 }
427 }
428 }
429
430 $rows = $this->filter_built_html_rows( $rows );
431
432 $html = ( $rows ? implode( '', $rows ) : '' );
433 if( $echo ) echo $html;
434
435 return $html;
436 }
437
438 private function _build_edit_topic_edit_post( $activity ) {
439 $html = '';
440 $type = $activity['type'];
441 $userid = $activity['userid'];
442 $date = wpforo_date( $activity['date'], 'ago', false ) . '%s';
443
444 if( $userid ) {
445 $profile_url = wpforo_member( $userid, 'profile_url' );
446 $display_name = wpforo_member( $userid, 'display_name' );
447 $user = sprintf( '<a href="%s">%s</a>', $profile_url, $display_name );
448 } else {
449 $user = $activity['name'] ?: wpforo_phrase( 'Guest', false );
450 }
451
452 if( wpfval( $this->actions, $type, 'before' ) ) {
453 $html = $this->actions[ $type ]['before'];
454 $html = apply_filters( 'wpforo_activity_action_html_before', $html, $activity );
455 }
456 if( wpfval( $this->actions, $type, 'description' ) ) {
457 $html .= sprintf( $this->actions[ $activity['type'] ]['description'], $date, str_replace( '%', '%%', $user ) );
458 $html = apply_filters( 'wpforo_activity_action_html', $html, $activity );
459 }
460 if( wpfval( $this->actions, $type, 'after' ) ) {
461 $html .= $this->actions[ $type ]['after'];
462 $html = apply_filters( 'wpforo_activity_action_html_after', $html, $activity );
463 }
464
465 return $html;
466 }
467
468 public function bell( $class = 'wpf-alerts' ) {
469 wp_enqueue_script( 'wpforo-widgets-js' );
470
471 $class = ( ! $class ) ? 'wpf-alerts' : $class;
472 $count = ( ! empty( $this->notifications ) ) ? count( $this->notifications ) : 0;
473 $phrase = ( $count > 1 ) ? wpforo_phrase( 'You have new notifications', false ) : wpforo_phrase( 'You have a new notification', false );
474 $tooltip = ' wpf-tooltip="' . esc_attr( $phrase ) . '" wpf-tooltip-size="middle"';
475 ?>
476 <div class="<?php echo esc_attr( $class ) ?> <?php echo ( $count ) ? 'wpf-new' : ''; ?>">
477 <?php if( $count ): ?>
478 <div class="wpf-bell" <?php echo $tooltip ?>>
479 <svg height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
480 <path fill="currentColor"
481 d="M224 0c-17.7 0-32 14.3-32 32V51.2C119 66 64 130.6 64 208v18.8c0 47-17.3 92.4-48.5 127.6l-7.4 8.3c-8.4 9.4-10.4 22.9-5.3 34.4S19.4 416 32 416H416c12.6 0 24-7.4 29.2-18.9s3.1-25-5.3-34.4l-7.4-8.3C401.3 319.2 384 273.9 384 226.8V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32zm45.3 493.3c12-12 18.7-28.3 18.7-45.3H224 160c0 17 6.7 33.3 18.7 45.3s28.3 18.7 45.3 18.7s33.3-6.7 45.3-18.7z"/>
482 </svg>
483 <span class="wpf-alerts-count"><?php echo $count ?></span>
484 </div>
485 <?php else: ?>
486 <div class="wpf-bell">
487 <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
488 <path fill="currentColor"
489 d="M224 0c-17.7 0-32 14.3-32 32V51.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416H424c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6C399.5 322.9 384 278.8 384 233.4V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32zm0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3C98.1 328 112 281.3 112 233.4V208c0-61.9 50.1-112 112-112zm64 352H224 160c0 17 6.7 33.3 18.7 45.3s28.3 18.7 45.3 18.7s33.3-6.7 45.3-18.7s18.7-28.3 18.7-45.3z"/>
490 </svg>
491 </div>
492 <?php endif; ?>
493 </div>
494 <?php
495 }
496
497 public function notifications() {
498 ?>
499 <div class="wpf-notifications">
500 <div class="wpf-notification-head">
501 <svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
502 <path fill="currentColor"
503 d="M224 0c-17.7 0-32 14.3-32 32V51.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416H424c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6C399.5 322.9 384 278.8 384 233.4V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32zm0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3C98.1 328 112 281.3 112 233.4V208c0-61.9 50.1-112 112-112zm64 352H224 160c0 17 6.7 33.3 18.7 45.3s28.3 18.7 45.3 18.7s33.3-6.7 45.3-18.7s18.7-28.3 18.7-45.3z"/>
504 </svg> <?php wpforo_phrase( 'Notifications' ) ?>
505 </div>
506 <div class="wpf-notification-content">
507 <div class="wpf-nspin">
508 <svg width="24" height="24" viewBox="0 0 24 24">
509 <g stroke="currentColor">
510 <circle cx="12" cy="12" r="9.5" fill="none" stroke-linecap="round" stroke-width="3">
511 <animate attributeName="stroke-dasharray" calcMode="spline" dur="1.5s" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" keyTimes="0;0.475;0.95;1"
512 repeatCount="indefinite" values="0 150;42 150;42 150;42 150"></animate>
513 <animate attributeName="stroke-dashoffset" calcMode="spline" dur="1.5s" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" keyTimes="0;0.475;0.95;1"
514 repeatCount="indefinite" values="0;-16;-59;-59"></animate>
515 </circle>
516 <animateTransform attributeName="transform" dur="2s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"></animateTransform>
517 </g>
518 </svg>
519 </div>
520 </div>
521 <div class="wpf-notification-actions">
522 <span class="wpf-action wpf-notification-action-clear-all" data-foro_n="<?php echo wp_create_nonce( 'wpforo_clear_notifications' ) ?>"><?php wpforo_phrase( 'Clear all' ) ?></span>
523 </div>
524 </div>
525 <?php
526 }
527
528 public function notifications_list( $echo = true ) {
529 $items = [];
530 $list_html = '';
531 if( ! empty( $this->notifications ) && is_array( $this->notifications ) ) {
532 $list_html .= '<ul>';
533 foreach( $this->notifications as $n ) {
534 if( $type = wpfval( $n, 'type' ) ) {
535 $html = wpfval( $this->actions, $type ) ? $this->actions[ $type ] : $this->actions['default'];
536 $items[ $n['id'] ] = $html['before'];
537 if( wpfval( $n, 'itemid_second' ) ) {
538 $member = wpforo_member( $n['itemid_second'] );
539 $member_name = wpfval( $member, 'display_name' ) ? $member['display_name'] : wpforo_phrase( 'Guest', false );
540 } else {
541 $member_name = wpfval( $n, 'name' ) ? $n['name'] : wpforo_phrase( 'Guest', false );
542 }
543 if( strpos( (string) $n['permalink'], '#' ) === false ) {
544 $n['permalink'] = wp_nonce_url( $n['permalink'] . '?_nread=' . $n['id'], 'wpforo_mark_notification_read', 'foro_n' );
545 } else {
546 $n['permalink'] = str_replace( '#', '?_nread=' . $n['id'] . '#', $n['permalink'] );
547 $n['permalink'] = wp_nonce_url( $n['permalink'], 'wpforo_mark_notification_read', 'foro_n' );
548 }
549 $date = wpforo_date( $n['date'], 'ago', false );
550 $length = apply_filters( 'wpforo_notification_description_length', 40 );
551 $items[ $n['id'] ] .= '<div class="wpf-nleft">' . $html['icon'] . '</div>';
552 $items[ $n['id'] ] .= '<div class="wpf-nright">';
553 $items[ $n['id'] ] .= '<a href="' . esc_url_raw( $n['permalink'] ) . '">';
554 $items[ $n['id'] ] .= sprintf( $html['description'], '<strong>' . $member_name . '</strong>', $date );
555 $items[ $n['id'] ] .= '</a>';
556 $items[ $n['id'] ] .= '<div class="wpf-ndesc">' . stripslashes( wpforo_text( (string) $n['content'], $length, false ) ) . '</div>';
557 $items[ $n['id'] ] .= '</div>';
558 $items[ $n['id'] ] .= $html['after'];
559 }
560 }
561 $items = apply_filters( 'wpforo_notifications_list', $items );
562 $list_html .= implode( "\r\n", $items );
563 $list_html .= '</ul>';
564 } else {
565 $list_html = $this->get_no_notifications_html();
566 }
567 if( $echo ) echo $list_html;
568
569 return $list_html;
570 }
571
572 public function get_no_notifications_html() {
573 return '<div class="wpf-no-notification">' . wpforo_phrase( 'You have no new notifications', false ) . '</div>';
574 }
575
576 public function get_notifications() {
577 $args = [ 'itemtype' => 'alert', 'userid' => WPF()->current_userid, 'row_count' => 100, 'orderby' => 'date', 'order' => 'DESC' ];
578 $args = apply_filters( 'wpforo_get_notifications_args', $args );
579
580 return $this->get_activities( $args );
581 }
582
583 public function add_notification_new_reply( $type, $post, $topic = [] ): void {
584 if( ! wpfval( $post, 'status' ) ) {
585 $notification = [
586 'type' => $type,
587 'itemid' => $post['postid'],
588 'itemtype' => 'alert',
589 'itemid_second' => $post['userid'],
590 'name' => $post['name'],
591 'email' => $post['email'],
592 'content' => $post['title'],
593 'permalink' => $post['posturl'],
594 'new' => 1,
595 ];
596 // Notify replied person
597 $replied_post = wpforo_post( $post['parentid'] );
598 if( ! empty( $replied_post ) && wpfval( $replied_post, 'userid' ) != wpfval( $post, 'userid' ) ) {
599 $notification['userid'] = $replied_post['userid'];
600 $notification = apply_filters( 'wpforo_add_notification_new_reply_data', $notification, $type, $post, $topic, $replied_post );
601 $this->add( $notification );
602 }
603 // Notify the topic author
604 if( ! empty( $topic ) && $topic['userid'] != $post['userid'] && ! ( ! empty( $replied_post ) && $topic['userid'] == $replied_post['userid'] ) ) {
605 $notification['userid'] = $topic['userid'];
606 $notification = apply_filters( 'wpforo_add_notification_new_reply_data', $notification, $type, $post, $topic, $replied_post );
607 $this->add( $notification );
608 }
609 }
610 }
611
612 public function add_notification( $type, $args ): void {
613 if( $args['userid'] != WPF()->current_userid ) {
614 $length = apply_filters( 'wpforo_notification_saved_description_length', 50 );
615 $notification = [
616 'type' => $type,
617 'itemid' => $args['itemid'],
618 'itemtype' => 'alert',
619 'itemid_second' => WPF()->current_userid,
620 'userid' => $args['userid'],
621 'name' => WPF()->current_user_display_name,
622 'email' => WPF()->current_user_email,
623 'content' => wpforo_text( $args['content'], $length, false ),
624 'permalink' => ( wpfval( $args, 'permalink' ) ? $args['permalink'] : '#' ),
625 'new' => 1,
626 ];
627 $notification = apply_filters( 'wpforo_add_notification_data', $notification, $type, $args );
628 $this->add( $notification );
629 }
630 }
631
632 public function clear_all_reaction_notifications( $postid ) {
633 if( $postid = wpforo_bigintval( $postid ) ) {
634 $sql = "DELETE FROM `" . WPF()->tables->activity . "` WHERE `itemtype` = 'alert' AND `type` IN('new_like', 'new_dislike', 'new_reaction') AND `itemid` = %d AND `itemid_second` = %d";
635 $sql = WPF()->db->prepare( $sql, $postid, WPF()->current_userid );
636 WPF()->db->query( $sql );
637 }
638 }
639
640 public function after_react( $reaction, $post ): void {
641 if( $post ) {
642 $this->clear_all_reaction_notifications( wpfval( $reaction, 'postid' ) );
643 $args = [
644 'itemid' => $post['postid'],
645 'userid' => $post['userid'],
646 'content' => $post['body'],
647 'permalink' => WPF()->post->get_url( $post['postid'] ),
648 ];
649 switch( wpfval( $reaction, 'type' ) ) {
650 case 'up':
651 $ntype = 'new_like';
652 break;
653 case 'down':
654 $ntype = 'new_dislike';
655 break;
656 default:
657 $ntype = 'new_reaction';
658 break;
659 }
660 $this->add_notification( $ntype, $args );
661 }
662 }
663
664 public function after_unreact( $args ): void {
665 if( $postid = wpforo_bigintval( wpfval( $args, 'postid' ) ) ) {
666 $this->clear_all_reaction_notifications( $postid );
667 }
668 }
669
670 public function after_vote( $reaction, $post ) {
671 if( $post ) {
672 if( $reaction == 1 ) {
673 $args = [
674 'itemid' => $post['postid'],
675 'userid' => $post['userid'],
676 'content' => $post['body'],
677 'permalink' => WPF()->post->get_url( $post['postid'] ),
678 ];
679 $this->add_notification( 'new_up_vote', $args );
680 $args = [
681 'type' => 'new_down_vote',
682 'itemid' => $post['postid'],
683 'itemtype' => 'alert',
684 'itemid_second' => WPF()->current_userid,
685 ];
686 $this->delete_notification( $args );
687 } elseif( $reaction == - 1 ) {
688 $args = [
689 'itemid' => $post['postid'],
690 'userid' => $post['userid'],
691 'content' => $post['body'],
692 'permalink' => WPF()->post->get_url( $post['postid'] ),
693 ];
694 $this->add_notification( 'new_down_vote', $args );
695 $args = [
696 'type' => 'new_up_vote',
697 'itemid' => $post['postid'],
698 'itemtype' => 'alert',
699 'itemid_second' => WPF()->current_userid,
700 ];
701 $this->delete_notification( $args );
702 }
703 }
704 }
705
706 public function delete_notification( $args ) {
707 $this->delete( $args );
708 }
709
710 public function update_notification( $post, $status ) {
711 $post['status'] = $status = intval( $status );
712 $post['posturl'] = WPF()->post->get_url( $post['postid'] );
713 if( wpfval( $post, 'topicid' ) ) {
714 $topic = WPF()->topic->get_topic( $post['topicid'] );
715 if( $status ) {
716 $args = [
717 'type' => 'new_reply',
718 'itemid' => $post['postid'],
719 'itemtype' => 'alert',
720 ];
721 $this->delete_notification( $args );
722 } else {
723 $this->add_notification_new_reply( 'new_reply', $post, $topic );
724 }
725 }
726 }
727
728 public function read_notification( $id, $userid = null ) {
729 $userid = is_null( $userid ) ? WPF()->current_userid : $userid;
730 $args = [
731 'id' => $id,
732 'userid' => $userid,
733 ];
734 $this->delete_notification( $args );
735 }
736
737 public function clear_notifications( $userid = null ) {
738 $userid = is_null( $userid ) ? WPF()->current_userid : $userid;
739 $args = [
740 'userid' => $userid,
741 ];
742 $this->delete_notification( $args );
743 }
744
745 }
746