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 / model / class-upstream-model-project.php

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

820 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_Model_Project
4 *
5 * WordPress Coding Standart (WCS) note:
6 * All camelCase methods and object properties on this file are not converted to snake_case,
7 * because it being used (heavily) on another add-on plugins.
8 *
9 * @package UpStream
10 */
11
12 // Exit if accessed directly.
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Class UpStream_Model_Project
19 */
20 class UpStream_Model_Project extends UpStream_Model_Post_Object {
21
22 /**
23 * Tasks
24 *
25 * @var array
26 */
27 protected $tasks = array();
28
29 /**
30 * Bugs
31 *
32 * @var array
33 */
34 protected $bugs = array();
35
36 /**
37 * Files
38 *
39 * @var array
40 */
41 protected $files = array();
42
43 /**
44 * Start Date
45 *
46 * @var undefined
47 */
48 protected $startDate = null; // phpcs:ignore
49
50 /**
51 * End Date
52 *
53 * @var undefined
54 */
55 protected $endDate = null; // phpcs:ignore
56
57 /**
58 * Client User Ids
59 *
60 * @var array
61 */
62 protected $clientUserIds = array(); // phpcs:ignore
63
64 /**
65 * Member User Ids
66 *
67 * @var array
68 */
69 protected $memberUserIds = array(); // phpcs:ignore
70
71 /**
72 * Client Id
73 *
74 * @var int
75 */
76 protected $clientId = 0; // phpcs:ignore
77
78 /**
79 * Progress
80 *
81 * @var int
82 */
83 protected $progress = 0;
84
85 /**
86 * Status Code
87 *
88 * @var undefined
89 */
90 protected $statusCode = null; // phpcs:ignore
91
92 /**
93 * Post Type
94 *
95 * @var string
96 */
97 protected $postType = 'project'; // phpcs:ignore
98
99 /**
100 * Type
101 *
102 * @var undefined
103 */
104 protected $type = UPSTREAM_ITEM_TYPE_PROJECT;
105
106 /**
107 * UpStream_Model_Project constructor.
108 *
109 * @param mixed $id id.
110 * @return void
111 */
112 public function __construct( $id ) {
113 if ( $id > 0 ) {
114 parent::__construct(
115 $id,
116 array(
117 'clientUserIds' => function ( $m ) {
118 $arr = isset( $m['_upstream_project_client_users'][0] ) ? unserialize( $m['_upstream_project_client_users'][0] ) : null;
119 $arr = is_array( $arr ) ? $arr : array();
120 $arr = array_filter( $arr );
121 return $arr;
122 },
123 'memberUserIds' => function ( $m ) {
124 $arr = isset( $m['_upstream_project_members'][0] ) ? unserialize( $m['_upstream_project_members'][0] ) : null;
125 $arr = is_array( $arr ) ? $arr : array();
126 $arr = array_filter( $arr );
127 return $arr;
128 },
129 'clientId' => '_upstream_project_client',
130 'progress' => '_upstream_project_progress',
131 'statusCode' => '_upstream_project_status',
132 'description' => '_upstream_project_description',
133 'startDate' => function ( $m ) {
134 if ( ! empty( $m['_upstream_project_start.YMD'][0] ) ) {
135 return $m['_upstream_project_start.YMD'][0];
136 } elseif ( ! empty( $m['_upstream_project_start'][0] ) ) {
137 return UpStream_Model_Object::timestampToYMD( $m['_upstream_project_start'][0] );
138 }
139 },
140 'endDate' => function ( $m ) {
141 if ( ! empty( $m['_upstream_project_end.YMD'][0] ) ) {
142 return $m['_upstream_project_end.YMD'][0];
143 } elseif ( ! empty( $m['_upstream_project_end'][0] ) ) {
144 return UpStream_Model_Object::timestampToYMD( $m['_upstream_project_end'][0] );
145 }
146 },
147 'assignedTo' => function ( $m ) {
148 return ! empty( $m['_upstream_project_owner'][0] ) ? $m['_upstream_project_owner'] : array();
149 },
150 )
151 );
152
153 $this->loadChildren();
154 $this->loadCategories();
155 } else {
156 parent::__construct( 0, array() );
157 }
158
159 $this->type = UPSTREAM_ITEM_TYPE_PROJECT;
160 }
161
162 /**
163 * Load Children
164 *
165 * @return void
166 */
167 protected function loadChildren() {
168 // TODO: check if these are disabled.
169 $itemset = get_post_meta( $this->id, '_upstream_project_tasks' );
170 if ( $itemset && count( $itemset ) === 1 && is_array( $itemset[0] ) ) {
171 foreach ( $itemset[0] as $item ) {
172 $this->tasks[] = new UpStream_Model_Task( $this, $item );
173 }
174 }
175
176 $itemset = get_post_meta( $this->id, '_upstream_project_bugs' );
177 if ( $itemset && count( $itemset ) === 1 && is_array( $itemset[0] ) ) {
178 foreach ( $itemset[0] as $item ) {
179 $this->bugs[] = new UpStream_Model_Bug( $this, $item );
180 }
181 }
182
183 $itemset = get_post_meta( $this->id, '_upstream_project_files' );
184 if ( $itemset && count( $itemset ) === 1 && is_array( $itemset[0] ) ) {
185 foreach ( $itemset[0] as $item ) {
186 $this->files[] = new UpStream_Model_File( $this, $item );
187 }
188 }
189 }
190
191 /**
192 * Load Categories
193 */
194 protected function loadCategories() {
195 if ( upstream_is_project_categorization_disabled() ) {
196 return array();
197 }
198
199 $categories = wp_get_object_terms( $this->id, 'project_category' );
200
201 $category_ids = array();
202 if ( ! isset( $categories->errors ) ) {
203 foreach ( $categories as $category ) {
204 $category_ids[] = $category->term_id;
205 }
206 }
207
208 $this->categoryIds = $category_ids; // phpcs:ignore
209 }
210
211 /**
212 * Calculate Elapsed Time
213 */
214 public function calculateElapsedTime() {
215 $total = 0;
216
217 foreach ( $this->tasks as $task ) {
218 $total += $task->calculateElapsedTime();
219 }
220
221 foreach ( $this->bugs as $bug ) {
222 $total += $bug->calculateElapsedTime();
223 }
224
225 return $total;
226 }
227
228 /**
229 * Calculate Budgeted
230 */
231 public function calculateBudgeted() {
232 $total = 0;
233
234 foreach ( $this->tasks as $task ) {
235 $total += $task->calculateBudgeted();
236 }
237
238 foreach ( $this->bugs as $bug ) {
239 $total += $bug->calculateBudgeted();
240 }
241
242 return $total;
243 }
244
245 /**
246 * Calculate Spent
247 */
248 public function calculateSpent() {
249 $total = 0;
250
251 foreach ( $this->tasks as $task ) {
252 $total += $task->calculateSpent();
253 }
254
255 foreach ( $this->bugs as $bug ) {
256 $total += $bug->calculateSpent();
257 }
258
259 return $total;
260 }
261
262 /**
263 * Store Categories
264 */
265 protected function storeCategories() {
266 if ( upstream_is_project_categorization_disabled() ) {
267 return;
268 }
269
270 $res = wp_set_object_terms( $this->id, $this->categoryIds, 'project_category' ); // phpcs:ignore
271 $is_error = false;
272
273 if ( $res instanceof \WP_Error ) {
274 $is_error = true; // TODO: throw.
275 }
276
277 }
278
279 /**
280 * Store
281 *
282 * @return void
283 */
284 public function store() {
285 parent::store();
286
287 // Phpcs ignore camelCase methods and object properties.
288 // phpcs:disable
289
290 if ( $this->clientId > 0 ) {
291 update_post_meta( $this->id, '_upstream_project_client', $this->clientId );
292 }
293 if ( null !== $this->clientUserIds ) {
294 update_post_meta( $this->id, '_upstream_project_client_users', $this->clientUserIds );
295 }
296 if ( null !== $this->statusCode ) {
297 update_post_meta( $this->id, '_upstream_project_status', $this->statusCode );
298 }
299 if ( null !== $this->description ) {
300 update_post_meta( $this->id, '_upstream_project_description', $this->description );
301 }
302 if ( count( $this->assignedTo ) > 0 ) {
303 update_post_meta( $this->id, '_upstream_project_owner', $this->assignedTo[0] );
304 }
305 if ( null !== $this->startDate ) {
306 update_post_meta( $this->id, '_upstream_project_start.YMD', $this->startDate );
307 }
308 if ( null !== $this->endDate ) {
309 update_post_meta( $this->id, '_upstream_project_end.YMD', $this->endDate );
310 }
311 if ( null !== $this->startDate ) {
312 update_post_meta( $this->id, '_upstream_project_start', UpStream_Model_Object::ymdToTimestamp( $this->startDate ) );
313 }
314 if ( null !== $this->endDate ) {
315 update_post_meta( $this->id, '_upstream_project_end', UpStream_Model_Object::ymdToTimestamp( $this->endDate ) );
316 }
317 if ( null !== $this->progress ) {
318 update_post_meta( $this->id, '_upstream_project_progress', $this->progress );
319 }
320
321 // phpcs:enable
322
323 $items = array();
324 foreach ( $this->tasks as $item ) {
325 $r = array();
326 $item->storeToArray( $r );
327 $items[] = $r;
328 }
329 update_post_meta( $this->id, '_upstream_project_tasks', $items );
330
331 $items = array();
332 foreach ( $this->bugs as $item ) {
333 $r = array();
334 $item->storeToArray( $r );
335 $items[] = $r;
336 }
337 update_post_meta( $this->id, '_upstream_project_bugs', $items );
338
339 $items = array();
340 foreach ( $this->files as $item ) {
341 $r = array();
342 $item->storeToArray( $r );
343 $items[] = $r;
344 }
345 update_post_meta( $this->id, '_upstream_project_files', $items );
346
347 $this->storeCategories();
348
349 $project_object = new UpStream_Project( $this->id );
350 $project_object->update_project_meta();
351 }
352
353 /**
354 * Has Meta Object
355 *
356 * @param mixed $item item.
357 * @throws UpStream_Model_ArgumentException Exception.
358 */
359 public function hasMetaObject( $item ) {
360 if ( ! ( $item instanceof \UpStream_Model_Meta_Object ) ) {
361 throw new UpStream_Model_ArgumentException( __( 'Argument must be of type UpStream_Model_Meta_Object', 'upstream' ) );
362 } elseif ( $item instanceof UpStream_Model_Task ) {
363 foreach ( $this->tasks() as $task ) {
364 if ( $task->id === $item->id ) {
365 return true;
366 }
367 }
368 } elseif ( $item instanceof UpStream_Model_File ) {
369 foreach ( $this->files() as $file ) {
370 if ( $file->id === $item->id ) {
371 return true;
372 }
373 }
374 } elseif ( $item instanceof UpStream_Model_Bug ) {
375 foreach ( $this->bugs() as $bug ) {
376 if ( $bug->id === $item->id ) {
377 return true;
378 }
379 }
380 }
381
382 return false;
383 }
384
385 /**
386 * Add Meta Object
387 *
388 * @param mixed $item item.
389 * @throws UpStream_Model_ArgumentException Exception.
390 * @return void
391 */
392 public function addMetaObject( $item ) {
393 if ( ! ( $item instanceof \UpStream_Model_Meta_Object ) ) {
394 throw new UpStream_Model_ArgumentException( __( 'Can only add objects of type UpStream_Model_Meta_Object', 'upstream' ) );
395 } elseif ( $item instanceof UpStream_Model_Task ) {
396 $this->tasks[] = $item;
397 } elseif ( $item instanceof UpStream_Model_File ) {
398 $this->files[] = $item;
399 } elseif ( $item instanceof UpStream_Model_Bug ) {
400 $this->bugs[] = $item;
401 }
402 }
403
404 /**
405 * Tasks
406 */
407 public function &tasks() {
408 return $this->tasks;
409 }
410
411 /**
412 * Bugs
413 */
414 public function &bugs() {
415 return $this->bugs;
416 }
417
418 /**
419 * Files
420 */
421 public function &files() {
422 return $this->files;
423 }
424
425 /**
426 * Add Task
427 *
428 * @param string $title title.
429 * @param string $created_by created_by.
430 */
431 public function &addTask( $title, $created_by ) {
432 $item = \UpStream_Model_Task::create( $this, $title, $created_by );
433 $this->tasks[] = $item;
434
435 return $item;
436 }
437
438 /**
439 * Add Bug
440 *
441 * @param string $title title.
442 * @param string $created_by created_by.
443 */
444 public function &addBug( $title, $created_by ) {
445 $item = \UpStream_Model_Bug::create( $this, $title, $created_by );
446 $this->bugs[] = $item;
447
448 return $item;
449 }
450
451 /**
452 * Add File
453 *
454 * @param string $title title.
455 * @param string $created_by created_by.
456 */
457 public function &addFile( $title, $created_by ) {
458 $item = \UpStream_Model_File::create( $this, $title, $created_by );
459 $this->files[] = $item;
460
461 return $item;
462 }
463
464 /**
465 * Get
466 *
467 * @param mixed $property property.
468 * @throws UpStream_Model_ArgumentException Exception.
469 */
470 public function __get( $property ) {
471 $property = apply_filters( 'upstream_wcs_model_variable', $property );
472
473 switch ( $property ) {
474
475 case 'status':
476 $s = $this->getStatuses();
477
478 foreach ( $s as $s_key => $s_value ) {
479 if ( $this->statusCode === $s_key ) { // phpcs:ignore
480 return $s_value;
481 }
482 }
483 return '';
484
485 case 'elapsedTime':
486 return $this->calculateElapsedTime();
487 case 'budgeted':
488 return $this->calculateBudgeted();
489 case 'spent':
490 return $this->calculateSpent();
491
492 case 'statusCode':
493 case 'clientId':
494 case 'clientUserIds':
495 case 'memberUserIds':
496 case 'startDate':
497 case 'endDate':
498 case 'categoryIds':
499 return $this->{$property};
500
501 case 'progress':
502 return round( $this->{$property} );
503
504 case 'categories':
505 $categories = array();
506 foreach ( $this->categoryIds as $tid ) { // phpcs:ignore
507 $term = get_term_by( 'id', $tid, 'project_category' );
508 $categories[] = $term;
509 }
510 return $categories;
511
512 case 'tasks':
513 case 'bugs':
514 case 'files':
515 throw new UpStream_Model_ArgumentException( __( 'Not implemented. Use &tasks(), &files(), or &bugs().', 'upstream' ) );
516 default:
517 return parent::__get( $property );
518
519 }
520 }
521
522 /**
523 * Set
524 *
525 * @param mixed $property property.
526 * @param mixed $value value.
527 * @throws UpStream_Model_ArgumentException Exception.
528 * @return void
529 */
530 public function __set( $property, $value ) {
531 $property = apply_filters( 'upstream_wcs_model_variable', $property );
532
533 switch ( $property ) {
534
535 case 'categoryIds':
536 if ( ! is_array( $value ) ) {
537 $value = array( $value );
538 }
539
540 $category_ids = array();
541 foreach ( $value as $tid ) {
542 $term = get_term_by( 'id', $tid, 'project_category' );
543 if ( false === $term ) {
544 throw new UpStream_Model_ArgumentException(
545 sprintf(
546 // translators: %s: term id.
547 __( 'Term ID %s is invalid.', 'upstream' ),
548 $tid
549 )
550 );
551 }
552 $category_ids[] = $term->term_id;
553 }
554
555 $this->categoryIds = $category_ids; // phpcs:ignore
556
557 break;
558
559 case 'status':
560 $s = $this->getStatuses();
561 $sc = null;
562
563 foreach ( $s as $s_key => $s_value ) {
564 if ( $value === $s_value ) {
565 $sc = $s_key;
566 break;
567 }
568 }
569
570 if ( null === $sc ) {
571 throw new UpStream_Model_ArgumentException(
572 sprintf(
573 // translators: %s: status.
574 __( 'Status %s is invalid.', 'upstream' ),
575 $value
576 )
577 );
578 }
579
580 $this->statusCode = $sc; // phpcs:ignore
581
582 break;
583
584 case 'statusCode':
585 $s = $this->getStatuses();
586 $sc = null;
587
588 foreach ( $s as $s_key => $s_value ) {
589 if ( $value === $s_key ) {
590 $sc = $s_key;
591 break;
592 }
593 }
594
595 if ( null === $sc ) {
596 throw new UpStream_Model_ArgumentException(
597 sprintf(
598 // translators: %s: status code.
599 __( 'Status code %s is invalid.', 'upstream' ),
600 $value
601 )
602 );
603 }
604
605 $this->statusCode = $sc; // phpcs:ignore
606
607 break;
608
609 case 'assignedTo':
610 case 'assignedTo:byUsername':
611 case 'assignedTo:byEmail':
612 if ( is_array( $value ) && count( $value ) !== 1 ) {
613 throw new UpStream_Model_ArgumentException( __( 'For projects, assignedTo must be an array of length 1.', 'upstream' ) );
614 }
615
616 parent::__set( $property, $value );
617 break;
618
619 case 'clientId':
620 // this will throw a model exception if the client doesn't exist.
621 $client = \UpStream_Model_Manager::get_instance()->getByID( UPSTREAM_ITEM_TYPE_CLIENT, $value );
622 $this->clientId = $client->id; // phpcs:ignore
623 break;
624
625 case 'clientUserIds':
626 if ( 0 === $this->clientId ) { // phpcs:ignore
627 throw new UpStream_Model_ArgumentException( __( 'Cannot assign client users if the project has no client.', 'upstream' ) );
628 }
629
630 if ( ! is_array( $value ) ) {
631 throw new UpStream_Model_ArgumentException( __( 'Client user IDs must be an array.', 'upstream' ) );
632 }
633
634 if ( count( array_unique( $value ) ) !== count( $value ) ) {
635 throw new UpStream_Model_ArgumentException( __( 'Input cannot contain duplicates.', 'upstream' ) );
636 }
637
638 $client = \UpStream_Model_Manager::get_instance()->getByID( UPSTREAM_ITEM_TYPE_CLIENT, $this->clientId ); // phpcs:ignore
639 $count_value = count( $value );
640 for ( $i = 0; $i < $count_value; $i++ ) {
641 if ( ! $client->includesUser( $value[ $i ] ) ) {
642 throw new UpStream_Model_ArgumentException(
643 sprintf(
644 // translators: %s: user id.
645 __( 'User ID %s does not exist in this client.', 'upstream' ),
646 $value[ $i ]
647 )
648 );
649 }
650 }
651 $this->clientUserIds = $value; // phpcs:ignore
652
653 break;
654
655 case 'startDate':
656 case 'endDate':
657 if ( ! self::isValidDate( $value ) ) {
658 throw new UpStream_Model_ArgumentException( __( 'Argument is not a valid date of the form YYYY-MM-DD.', 'upstream' ) );
659 }
660
661 $this->{$property} = $value;
662 break;
663
664 default:
665 parent::__set( $property, $value );
666 break;
667
668 }
669 }
670
671 /**
672 * Fields
673 */
674 public static function fields() {
675 $fields = parent::fields();
676
677 $fields['statusCode'] = array(
678 'type' => 'select',
679 'title' => __( 'Status' ),
680 'search' => true,
681 'display' => true,
682 'options_cb' => 'UpStream_Model_Project::getStatuses',
683 );
684 $fields['categoryIds'] = array(
685 'type' => 'select',
686 'title' => __( 'Categories' ),
687 'search' => true,
688 'display' => true,
689 'options_cb' => 'UpStream_Model_Project::getCategories',
690 'is_array' => 'true',
691 );
692 $fields['clientUserIds'] = array(
693 'type' => 'user_id',
694 'is_array' => true,
695 'title' => __( 'Selected Client Users' ),
696 'search' => true,
697 'display' => true,
698 );
699 $fields['memberUserIds'] = array(
700 'type' => 'user_id',
701 'is_array' => true,
702 'title' => __( 'Members' ),
703 'search' => true,
704 'display' => true,
705 );
706 $fields['startDate'] = array(
707 'type' => 'date',
708 'title' => __( 'Start Date' ),
709 'search' => true,
710 'display' => true,
711 );
712 $fields['endDate'] = array(
713 'type' => 'date',
714 'title' => __( 'End Date' ),
715 'search' => true,
716 'display' => true,
717 );
718 $fields['progress'] = array(
719 'type' => 'number',
720 'title' => __( 'Progress (%)' ),
721 'search' => true,
722 'display' => true,
723 );
724
725 $fields = self::customFields( $fields, UPSTREAM_ITEM_TYPE_PROJECT );
726
727 return $fields;
728 }
729
730
731 /**
732 * Find Milestones
733 */
734 public function findMilestones() {
735 $posts = get_posts(
736 array(
737 'post_type' => 'upst_milestone',
738 'post_status' => 'publish',
739 'posts_per_page' => -1,
740 'meta_key' => 'upst_project_id',
741 'meta_value' => $this->id,
742 'orderby' => 'menu_order',
743 'order' => 'ASC',
744 )
745 );
746
747 $milestones = array();
748
749 foreach ( $posts as $post ) {
750 $milestone = \UpStream_Model_Manager::get_instance()->getByID(
751 UPSTREAM_ITEM_TYPE_MILESTONE,
752 $post->ID,
753 UPSTREAM_ITEM_TYPE_PROJECT,
754 $this->id
755 );
756 $milestones[] = $milestone;
757 }
758
759 return $milestones;
760 }
761
762 /**
763 * Get Categories
764 */
765 public static function getCategories() {
766 $tid_to_term = array();
767 $terms = get_terms(
768 array(
769 'taxonomy' => 'project_category',
770 'hide_empty' => true,
771 )
772 );
773
774 foreach ( $terms as $term ) {
775 $tid_to_term[ $term->term_id ] = $term->name;
776 }
777
778 return $tid_to_term;
779 }
780
781 /**
782 * Get Statuses
783 */
784 public static function getStatuses() {
785 $option = get_option( 'upstream_projects' );
786 $statuses = isset( $option['statuses'] ) ? $option['statuses'] : '';
787 $array = array();
788 if ( $statuses ) {
789 foreach ( $statuses as $status ) {
790 if ( isset( $status['type'] ) ) {
791 $array[ $status['id'] ] = $status['name'];
792 }
793 }
794 }
795
796 return $array;
797 }
798
799 /**
800 * Create
801 *
802 * @param mixed $title title.
803 * @param mixed $created_by created_by.
804 * @throws UpStream_Model_ArgumentException Exception.
805 */
806 public static function create( $title, $created_by ) {
807 if ( get_userdata( $created_by ) === false ) {
808 throw new UpStream_Model_ArgumentException( __( 'User ID does not exist.', 'upstream' ) );
809 }
810
811 $item = new \UpStream_Model_Project( 0 );
812
813 $item->title = sanitize_text_field( $title );
814 $item->createdBy = $created_by; // phpcs:ignore
815
816 return $item;
817 }
818
819 }
820