PluginProbe
UpStream: a Project Management Plugin for WordPress / 2.1.0
UpStream: a Project Management Plugin for WordPress v2.1.0
trunk 1.39.0 1.39.1 1.39.2 1.39.3 2.0.7 2.1.0
upstream / includes / class-upstream-project-activity.php

class-upstream-project-activity.php in UpStream: a Project Management Plugin for WordPress 2.1.0, at includes/class-upstream-project-activity.php

696 lines 19.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * UpStream_Project_Activity Class
4 *
5 * @package UpStream
6 */
7
8 // Exit if accessed directly.
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * UpStream_Project_Activity Class
15 *
16 * @since 1.0.0
17 */
18 class UpStream_Project_Activity {
19
20 use \UpStream\Traits\Singleton;
21
22 /**
23 * The project ID
24 *
25 * @var int
26 */
27 public $ID = 0;
28
29 /**
30 * The posted data
31 *
32 * @var array|null
33 */
34 public $posted = null;
35
36 /**
37 * Get things going
38 *
39 * @since 1.0.0
40 */
41 public function __construct() {
42 $this->hooks();
43 }
44
45 /**
46 * Run WordPress hooks
47 */
48 public function hooks() {
49 // Posted in frontend.
50 $this->init_update( null, null );
51
52 // Posted in backend.
53 add_action( 'wp_insert_post_data', array( $this, 'init_update' ), 99, 2 );
54 }
55
56 /**
57 * Update post data
58 *
59 * @param array $data Post data.
60 * @param array $postarr Posted data to insert.
61 */
62 public function init_update( $data, $postarr ) {
63 if ( ! $postarr && ! isset( $_POST ) ) {
64 return;
65 }
66
67 // Get the post data.
68 $this->posted = $postarr ? $postarr : wp_unslash( $_POST );
69
70 /**
71 * Nonce verification.
72 * We have a different nonce verification between frontend and backend.
73 */
74 if ( isset( $this->posted['upstream-nonce'] ) && isset( $this->posted['post_id'] ) ) {
75 // Posted in frontend.
76 $nonce = isset( $this->posted['upstream-nonce'] ) ? $this->posted['upstream-nonce'] : null;
77
78 if ( ! wp_verify_nonce( $nonce, 'upstream_security' ) ) {
79 return $data;
80 }
81 } elseif (
82 isset( $this->posted['action'] ) &&
83 isset( $this->posted['post_type'] ) &&
84 sanitize_text_field( $this->posted['post_type'] ) === 'project'
85 ) {
86 // Posted in backend.
87 $nonce = isset( $this->posted['upstream_admin_project_form_nonce'] ) ? $this->posted['upstream_admin_project_form_nonce'] : null;
88
89 if ( ! wp_verify_nonce( $nonce, 'upstream_admin_project_form' ) ) {
90 return $data;
91 }
92 } else {
93 return $data;
94 }
95
96 // ID may be alphanumeric.
97 $this->ID = isset( $this->posted['post_id'] ) ? absint( $this->posted['post_id'] ) : absint( $this->posted['post_ID'] );
98
99 // Posted in frontend.
100 if ( isset( $this->posted['upstream-nonce'] ) ) {
101 $posted = $this->posted;
102 $group = '_upstream_project_' . sanitize_text_field( $posted['type'] );
103
104 if ( isset( $posted['editing'] ) ) {
105 $posted['id'] = sanitize_text_field( $posted['editing'] );
106 }
107
108 // reset our posted variable.
109 $this->posted = array();
110 $this->posted[ $group ][0] = $posted;
111
112 if ( isset( $posted['action'] ) && sanitize_text_field( $posted['action'] ) === 'upstream_frontend_delete_item' ) {
113 $this->posted['frontend'] = 'delete';
114 }
115
116 // remove keys not required.
117 $remove = array(
118 'upstream-nonce',
119 'upstream_security',
120 'action',
121 '_wp_http_referer',
122 'post_id',
123 'type',
124 'editing',
125 'row',
126 'upstream-files-nonce',
127 );
128
129 foreach ( $remove as $key ) {
130 unset( $this->posted[ $group ][0][ $key ] );
131 }
132 }
133
134 if ( isset( $postarr ) ) {
135 $this->posted['admin'] = true;
136 }
137
138 // If this is an auto draft.
139 if ( isset( $this->posted['post_status'] ) && sanitize_text_field( $this->posted['post_status'] ) === 'auto-draft' ) {
140 return $data;
141 }
142
143 // ignore quick edit.
144 if ( isset( $this->posted['action'] ) && sanitize_text_field( $this->posted['action'] ) === 'inline-save' ) {
145 return $data;
146 }
147
148 $this->update_project();
149
150 return $data;
151 }
152
153
154 /**
155 * Update a project
156 */
157 public function update_project() {
158 $activity = array();
159 $time = current_time( 'timestamp' );
160 $user_id = upstream_current_user_id();
161
162 // start to loop through each POSTED item.
163 foreach ( $this->posted as $key => $new_value ) {
164
165 $key = sanitize_text_field( $key );
166
167 // skip some of WordPress standard fields that we don't need.
168 if ( $this->match( $key, array( 'nonce', 'action', 'refer', 'hidden' ) ) ) {
169 continue;
170 }
171
172 // first check for UpStream fields.
173 if ( $this->match( $key, '_upstream_project' ) ) {
174
175 // get the old value so we can compare.
176 $old_value = $this->get_meta( $key );
177
178 // check the simple string fields first.
179 if ( ! is_array( $new_value ) || '_upstream_project_client_users' === $key ) {
180
181 if ( '_upstream_project_client_users' === $key ) {
182 $nv = array();
183 if ( $new_value && is_array( $new_value ) && isset( $new_value[0] ) ) {
184 $nv = \array_map( 'sanitize_text_field', $new_value );
185 }
186 $new_value = $nv;
187 } else {
188 $new_value = sanitize_text_field( $new_value );
189 }
190
191 // handle date formatting differences first.
192 if ( $this->match( $key, array( 'project_start', 'project_end', 'date' ) ) ) {
193 // $old_value = upstream_format_date( $old_value );
194 $new_value = upstream_timestamp_from_date( $new_value );
195 }
196
197 // if we are adding a new item.
198 if ( ! $old_value && ! empty( $new_value ) ) {
199 $activity['single'][ $key ]['add'] = $new_value;
200 continue;
201 }
202
203 // add the activity to our array.
204 if ( $old_value != $new_value ) {
205 $activity['single'][ $key ]['from'] = $old_value;
206 $activity['single'][ $key ]['to'] = $new_value;
207 }
208 }
209
210 /*
211 * check the array fields
212 */
213 if ( is_array( $new_value ) && '_upstream_project_client_users' !== $key ) {
214
215 // deleted from frontend.
216 if ( isset( $this->posted['frontend'] ) && 'delete' === sanitize_text_field( $this->posted['frontend'] ) ) {
217
218 if ( $new_value && is_array( $new_value ) && isset( $new_value[0] ) ) {
219 // sanitize new value.
220 $nv = array();
221 $nv[] = \array_map( 'sanitize_text_field', $new_value[0] );
222 $new_value = $nv;
223
224 foreach ( $old_value as $old_old => $old_item ) {
225 // if the old id is not in the new items, we have deleted it.
226 if ( isset( $new_value[0]['id'] ) && $new_value[0]['id'] == $old_item['id'] ) {
227 $activity['group'][ $key ]['remove'][] = $old_item;
228 }
229 }
230 }
231 }
232
233 // deleted from admin.
234 if ( isset( $this->posted['admin'] ) && sanitize_text_field( $this->posted['admin'] ) === true ) {
235 if ( $old_value ) {
236 foreach ( $old_value as $old_index => $old_item ) {
237 // if the old id is not in the new items, we have deleted it.
238 if ( isset( $old_item['id'] ) && ! $this->in_array_r( $old_item['id'], $new_value ) ) {
239 $activity['group'][ $key ]['remove'][] = $old_item;
240 }
241 }
242 }
243 }
244
245 // loop through each new item.
246 foreach ( $new_value as $new_index => $new_item ) {
247
248 // see if our new item matches any existing.
249 $item_id = isset( $new_item['id'] ) ? $new_item['id'] : null;
250 $existing_item = upstream_project_item_by_id( $this->ID, $item_id );
251
252 // if we are adding a new item.
253 if ( ! $existing_item ) {
254 // ignore if all fields are empty.
255 if ( array_filter( $new_item ) ) {
256 $activity['group'][ $key ]['add'][] = $new_item;
257 }
258 }
259
260 if ( $existing_item ) {
261
262 // loop through each new item field.
263 foreach ( $new_item as $new_item_field_key => $new_item_field_val ) {
264
265 // check for date fields.
266 if ( $this->match( $new_item_field_key, array( 'date' ) ) ) {
267 // convert date to timestamp.
268 $new_item_field_val = upstream_timestamp_from_date( $new_item_field_val );
269 }
270
271 // we've added a new field.
272 // existing item is NOT set.
273 if (
274 ! isset( $existing_item[ $new_item_field_key ] )
275 && ! empty( $new_item_field_val ) ) {
276 $activity['group'][ $key ][ $existing_item['id'] ][ $new_item_field_key ]['add'] = $new_item_field_val;
277 continue;
278 }
279
280 // we've removed a field.
281 // existing item is set.
282 // new item is NOT set.
283 if (
284 ( isset( $existing_item[ $new_item_field_key ] ) && ! isset( $existing_item[ $new_item_field_key ] ) )
285 && ! empty( $existing_item[ $new_item_field_key ] ) ) {
286 $activity['group'][ $key ][ $existing_item['id'] ][ $new_item_field_key ]['remove'] = $existing_item[ $new_item_field_key ];
287 continue;
288 }
289
290 // we've edited a field.
291 if ( isset( $existing_item[ $new_item_field_key ] ) && $existing_item[ $new_item_field_key ] != $new_item_field_val ) {
292 $activity['group'][ $key ][ $existing_item['id'] ][ $new_item_field_key ]['from'] = $existing_item[ $new_item_field_key ];
293 $activity['group'][ $key ][ $existing_item['id'] ][ $new_item_field_key ]['to'] = $new_item_field_val;
294 }
295 }
296 }
297 }
298 } // end is_array check.
299 }
300 }
301
302 if ( empty( $activity ) ) {
303 return;
304 }
305
306 $data[ $time ]['fields'] = $activity;
307 $data[ $time ]['user_id'] = $user_id;
308
309 $existing = get_post_meta( $this->ID, '_upstream_project_activity', true );
310 if ( $existing ) {
311 $update = ( $existing + $data );
312 } else {
313 $update = $data;
314 }
315
316 $updated = update_post_meta( $this->ID, '_upstream_project_activity', $update );
317 }
318
319 /**
320 * Helper function to find matching strings.
321 * $needle can be a string or an array with multiple strings.
322 *
323 * @param array $haystack The array.
324 * @param string $needle The searched value.
325 */
326 public function match( $haystack, $needle ) {
327 if ( ! $needle ) {
328 return;
329 }
330
331 // push single string into array for simplicity.
332 if ( ! is_array( $needle ) ) {
333 $needle = array( $needle );
334 }
335
336 foreach ( $needle as $string ) {
337 if ( strpos( $haystack, $string ) !== false ) {
338 return true;
339 }
340 }
341 }
342
343 /**
344 * Get a meta value
345 *
346 * @since 1.0.0
347 *
348 * @param string $meta the meta field (without prefix).
349 *
350 * @return mixed
351 */
352 public function get_meta( $meta ) {
353 // to allow frontend use.
354 if ( strpos( $meta, '_upstream_project_' ) !== false ) {
355 $meta = str_replace( '_upstream_project_', '', $meta );
356 }
357
358 $result = get_post_meta( $this->ID, '_upstream_project_' . $meta, true );
359 if ( ! $result ) {
360 $result = null;
361 }
362
363 return $result;
364 }
365
366 /**
367 * In_array_r
368 *
369 * @param string $needle The searched value.
370 * @param array $haystack The array.
371 * @param bool $strict Set to true then the in_array() function will also check the types of the needle in the haystack.
372 *
373 * @return mixed
374 */
375 public function in_array_r( $needle, $haystack, $strict = false ) {
376 foreach ( $haystack as $item ) {
377 if ( ( $strict ? $item === $needle : $item === $needle ) || ( is_array( $item ) && $this->in_array_r(
378 $needle,
379 $item,
380 $strict
381 ) ) ) {
382 return true;
383 }
384 }
385
386 return false;
387 }
388
389 /**
390 * Get activity
391 *
392 * @param int $post_id Post id.
393 */
394 public function get_activity( $post_id ) {
395 // set the post id.
396 $this->ID = $post_id;
397
398 // make the field names readable.
399 $find = array( '_', 'upstream' );
400 $replace = array( ' ', '' );
401
402 // get the activity data.
403 $activity = $this->get_meta( '_upstream_project_activity' );
404
405 if ( ! $activity ) {
406 return;
407 }
408
409 $activity = array_reverse( $activity, true );
410
411 $activity = array_slice( $activity, 0, $this->number_of_items(), true );
412
413 // loop through each timestamp.
414 foreach ( $activity as $time => $update ) {
415
416 // get the day and time there was some activity.
417 echo '<div class="activity-item">';
418 echo '<h4>' . esc_html( upstream_format_date( $time ) . ' ' . upstream_format_time( $time ) ) . '</h4>';
419 echo '<span class="user">' . esc_html( upstream_users_name( $update['user_id'] ) ) . ' (' . esc_html(
420 upstream_user_item(
421 $update['user_id'],
422 'role'
423 )
424 ) . ')</span>';
425
426 /*
427 * single field
428 */
429 if ( isset( $update['fields']['single'] ) ) {
430 foreach ( $update['fields']['single'] as $field_id => $data ) {
431 $single_name = ucwords( str_replace( $find, $replace, $field_id ) );
432
433 if ( isset( $data['add'] ) ) {
434 $the_val = $this->format_fields( $field_id, $data['add'] );
435 $single_output = sprintf(
436 // translators: %s: field name.
437 __( 'New: %s', 'upstream' ),
438 $the_val
439 );
440 }
441
442 if ( isset( $data['from'] ) ) {
443 $from = $this->format_fields( $field_id, $data['from'] );
444 $to = $this->format_fields( $field_id, $data['to'] );
445 $single_output = sprintf(
446 // translators: %1$s: date from.
447 // translators: %2$s: date to.
448 __( 'Edit: %1$s to %2$s', 'upstream' ),
449 $from,
450 $to
451 );
452 }
453 }
454
455 echo '<span class="item-name">' . esc_html( $single_name ) . '</span>';
456 echo wp_kses_post( $single_output );
457 }
458
459 // group item.
460 if ( isset( $update['fields']['group'] ) ) {
461 foreach ( $update['fields']['group'] as $group_id => $data ) {
462 $group_name = ucwords( str_replace( $find, $replace, $group_id ) );
463 echo '<span class="item-name">' . esc_html( $group_name ) . '</span>';
464 foreach ( $data as $item_id => $fields ) {
465
466 // deleted an item.
467 if ( 'remove' === $item_id ) {
468 $item_removed = '';
469 foreach ( $fields as $key => $item ) {
470 // skip empty files.
471 if ( ( isset( $item['file_id'] ) && '0' === $item['file_id'] ) && ( isset( $item['title'] ) && empty( $item['title'] ) ) ) {
472 $group_name = '';
473 continue;
474 }
475
476 if ( '_upstream_project_milestones' === $group_id ) {
477 $title = $item['milestone'];
478 } else {
479 $title = $item['title'];
480 }
481
482 $item_removed .= '<span class="item">' . sprintf(
483 // translators: %s: Deleted item name.
484 __( 'Deleted: %s', 'upstream' ),
485 '<span class="highlight">' . $title . '</span>'
486 ) . '</span>';
487 }
488
489 $group_output = $item_removed;
490 echo wp_kses_post( $group_output );
491 }
492
493 /*
494 * add an item
495 */
496 if ( 'add' === $item_id ) {
497 $item_added = '';
498 foreach ( $fields as $key => $item ) {
499 // skip empty files.
500 if ( ( isset( $item['file_id'] ) && '0' === $item['file_id'] ) && ( isset( $item['title'] ) && empty( $item['title'] ) ) ) {
501 $group_name = '';
502 continue;
503 }
504
505 if ( '_upstream_project_milestones' === $group_id ) {
506 if ( isset( $item['milestone'] ) ) {
507 $title = $item['milestone'];
508 } elseif ( isset( $item['data']['milestone'] ) ) {
509 $title = $item['data']['milestone'];
510 }
511 } else {
512 if ( isset( $item['title'] ) ) {
513 $title = $item['title'];
514 } elseif ( isset( $item['data']['title'] ) ) {
515 $title = $item['data']['title'];
516 }
517 }
518
519 $item_added .= '<span class="item">' . sprintf(
520 // translators: %s: New item name.
521 __( 'New Item: %s', 'upstream' ),
522 '<span class="highlight">' . $title . '</span>'
523 ) . '</span>';
524 }
525
526 $group_output = $item_added;
527 echo wp_kses_post( $group_output );
528 }
529
530 /*
531 * edit an item
532 */
533 if ( strlen( $item_id ) > 5 ) {
534 foreach ( $fields as $field_id => $field_data ) {
535 $field_name = ucwords( str_replace( $find, $replace, $field_id ) );
536 $field_output = '';
537 if ( isset( $field_data['add'] ) ) {
538 $item = upstream_project_item_by_id( $this->ID, $item_id );
539
540 $the_val = $this->format_fields( $field_id, $field_data['add'] );
541 $field_output .= '<span class="item">' . sprintf(
542 // translators: %1$s: Field name.
543 // translators: %2$s: Field value.
544 // translators: %3$s: Item title.
545 __(
546 'New: %1$s - %2$s on %3$s',
547 'upstream'
548 ),
549 $field_name,
550 ( is_array( $the_val ) ? json_encode( $the_val ) : $the_val ),
551 '<span class="highlight">' . ( isset( $item['title'] ) ? $item['title'] : '' ) . '</span>'
552 ) . '</span>';
553 }
554
555 if ( isset( $field_data['from'] ) ) {
556 $item = upstream_project_item_by_id( $this->ID, $item_id );
557 $from = $this->format_fields( $field_id, $field_data['from'] );
558 $to = $this->format_fields( $field_id, $field_data['to'] );
559
560 $field_output .= '<span class="item">' . sprintf(
561 // translators: %1$s: Field name.
562 // translators: %2$s: Data 'from' couter.
563 // translators: %3$s: Data 'to' couter.
564 // translators: %4$s: Item title.
565 __(
566 'Edit: %1$s from %2$s to %3$s on %4$s',
567 'upstream'
568 ),
569 $field_name,
570 is_array( $from ) ? count( $from ) : $from,
571 is_array( $to ) ? count( $to ) : $to,
572 '<span class="highlight">' . ( isset( $item['title'] ) ? $item['title'] : '' ) . '</span>'
573 ) . '</span>';
574 }
575
576 $group_output = $field_output;
577 echo wp_kses_post( $group_output );
578 }
579 }
580 }
581 }
582 }
583
584 echo '</div>';
585 } // end items
586 }
587
588 /**
589 * Add activity
590 *
591 * @param int $project_id Project id.
592 * @param string $meta_name meta_name.
593 * @param array $action action.
594 * @param mixed $item item.
595 */
596 public function add_activity( $project_id, $meta_name, $action, $item ) {
597 // Update Project activity.
598 $activity = (array) get_post_meta( $project_id, '_upstream_project_activity', true );
599
600 $log = array(
601 'fields' => array(
602 'group' => array(
603 $meta_name => array(
604 $action => array( $item ),
605 ),
606 ),
607 ),
608 'user_id' => get_current_user_id(),
609 );
610
611 $now = time();
612 $activity[ $now ] = $log;
613
614 update_post_meta( $project_id, '_upstream_project_activity', $activity );
615 }
616
617 /**
618 * Get activity
619 */
620 public function number_of_items() {
621 $number = isset( $_GET['activity_items'] ) ? intval( $_GET['activity_items'] ) : 5;
622 $number = 'all' === $number ? 99999999 : $number;
623
624 return intval( $number );
625 }
626
627 /**
628 * Format data fields.
629 *
630 * @param string $field_id Field name / id.
631 * @param string $val Field value.
632 */
633 public function format_fields( $field_id, $val ) {
634 $field = str_replace( '_upstream_project_', '', $field_id );
635 $the_val = '';
636
637 if ( strpos( $field, 'date' ) !== false ) {
638 $field = 'date';
639 }
640
641 switch ( $field ) {
642 case 'client_users':
643 $prefix = '';
644 $users = '';
645 foreach ( $val as $index => $user_id ) {
646 $users .= $prefix . '' . upstream_users_name( $user_id ) . '';
647 $prefix = '& ';
648 }
649 $the_val = $users;
650 break;
651
652 case 'client':
653 $the_val = get_the_title( $val );
654 break;
655
656 case 'start':
657 case 'end':
658 case 'date':
659 $the_val = upstream_format_date( $val );
660 break;
661
662 case 'assigned_to':
663 case 'owner':
664 if ( ! is_array( $val ) ) {
665 $val = (array) $val;
666 }
667
668 $val = array_unique( array_filter( $val ) );
669 $the_val = array();
670 foreach ( $val as $user_id ) {
671 $user_id = (int) $user_id;
672 if ( $user_id > 0 ) {
673 $the_val[] = upstream_users_name( $user_id );
674 }
675 }
676
677 $the_val = implode( ', ', $the_val );
678
679 break;
680
681 case 'milestone':
682 $item = upstream_project_item_by_id( $this->ID, $val );
683 $the_val = isset( $item['title'] ) ? $item['title'] : $item['milestone'];
684 break;
685
686 default:
687 $the_val = $val;
688 break;
689 }
690
691 $the_val = empty( $the_val ) ? '(' . __( 'none', 'upstream' ) . ')' : $the_val;
692
693 return $the_val;
694 }
695 }
696