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-milestones.php

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

979 lines 29.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * This class will act as a controller handling incoming requests regarding comments on UpStream items.
4 *
5 * @package UpStream
6 */
7
8 namespace UpStream;
9
10 // Prevent direct access.
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 use UpStream\Traits\Singleton;
16
17 /**
18 * Class Milestones
19 *
20 * @since 1.24.0
21 */
22 class Milestones {
23
24 use Singleton;
25
26 /**
27 * Is post_type_created
28 *
29 * @var bool
30 */
31 protected $post_type_created = false;
32
33 /**
34 * Class constructor.
35 *
36 * @since 1.24.0
37 */
38 public function __construct() {
39 $this->attach_hooks();
40 }
41
42 /**
43 * Attach all relevant actions to handle comments.
44 *
45 * @since 1.24.0
46 */
47 private function attach_hooks() {
48 if ( upstream_disable_milestones() ) {
49 return;
50 }
51
52 add_action( 'before_upstream_init', array( $this, 'create_post_type' ) );
53 add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ), 8 );
54 add_action( 'save_post', array( $this, 'save_post' ) );
55
56 $post_type = $this->get_post_type();
57
58 add_filter( 'manage_' . $post_type . '_posts_columns', array( $this, 'manage_posts_columns' ), 10 );
59 add_action( 'manage_' . $post_type . '_posts_custom_column', array( $this, 'render_post_columns' ), 10, 2 );
60 add_filter( 'get_edit_post_link', array( $this, 'get_milestone_edit_post_link' ), 10, 2 );
61 add_action( 'current_screen', array( $this, 'disable_new_milestone_button' ) );
62 }
63
64 /**
65 * Return the post type name.
66 *
67 * @return string
68 * @since 1.24.0
69 */
70 public function get_post_type() {
71 return Milestone::POST_TYPE;
72 }
73
74 /**
75 * MigrateLegacyMilestonesForProject
76 *
77 * @param int $project_id Project ID.
78 *
79 * @return bool
80 * @throws \Exception Exception.
81 */
82 public static function migrate_legacy_milestones_for_project( $project_id ) {
83 // Migrate the milestones.
84 $default_milestones = get_option( 'upstream_milestones', array() );
85
86 if ( ! empty( $default_milestones ) ) {
87 $default_milestones = $default_milestones['milestones'];
88 $legacy_milestones = array();
89
90 // Organize the milestones by id.
91 foreach ( $default_milestones as $milestone_data ) {
92 $legacy_milestones[ $milestone_data['id'] ] = $milestone_data;
93 }
94
95 global $wpdb;
96
97 // Get the project's milestones to convert them into the new post types.
98 $project_milestones = get_post_meta( $project_id, '_upstream_project_milestones', true );
99 $project_tasks = get_post_meta( $project_id, '_upstream_project_tasks', true );
100
101 try {
102 if ( ! empty( $project_milestones ) ) {
103 // Check if the backup register doesn't exist.
104 $legacy_milestones_backup = get_post_meta( $project_id, '_upstream_project_milestones_legacy', true );
105 if ( empty( $legacy_milestones_backup ) ) {
106 // Move the milestones to a backup register, temporarily.
107 update_post_meta( $project_id, '_upstream_project_milestones_legacy', $project_milestones );
108 }
109
110 $wpdb->query( 'START TRANSACTION' );
111
112 $updated_tasks = false;
113
114 foreach ( $project_milestones as $project_milestone ) {
115
116 $data = $legacy_milestones[ $project_milestone['milestone'] ];
117
118 // Check if we already have this milestone in the project.
119 $migrated_milestone = get_posts(
120 array(
121 'post_type' => Milestone::POST_TYPE,
122 'post_parent' => $project_id,
123 'post_status' => 'publish',
124 'meta_key' => Milestone::META_LEGACY_MILESTONE_CODE,
125 'meta_value' => $project_milestone['milestone'],
126 )
127 );
128
129 // If the milestone already exists, abort.
130 if ( ! empty( $migrated_milestone ) ) {
131 continue;
132 }
133
134 // The milestone doesn't exist. Let's create it.
135 $milestone = Factory::create_milestone( $data['title'] )
136 ->setLegacyId( $project_milestone['id'] )
137 ->setLegacyMilestoneCode( $project_milestone['milestone'] )
138 ->setStartDate( $project_milestone['start_date'] )
139 ->setEndDate( $project_milestone['end_date'] )
140 ->setAssignedTo( $project_milestone['assigned_to'] )
141 ->setNotes( $project_milestone['notes'] )
142 ->setCreatedTimeInUtc( 1 === (int) $project_milestone['notes'] )
143 ->setProgress( (float) $project_milestone['progress'] )
144 ->setTaskCount( (int) $project_milestone['task_count'] )
145 ->setTaskOpen( (int) $project_milestone['task_open'] )
146 ->setColor( $data['color'] )
147 // ->setOrder($data['title'])
148 ->setProjectId( $project_id );
149
150 // Look for all the tasks to convert the milestone ID.
151 if ( ! empty( $project_tasks ) ) {
152 foreach ( $project_tasks as &$task ) {
153 if ( $task['milestone'] === $milestone->getLegacyId() ) {
154 $task['milestone'] = $milestone->getId();
155 // Keep the legacy reference for a while.
156 $task['milestone_legacy'] = $milestone->getLegacyId();
157
158 $updated_tasks = true;
159 }
160 }
161 }
162 }
163
164 update_post_meta( $project_id, '_upstream_milestones_migrated', 1 );
165
166 // Remove the legacy Milestones.
167 delete_post_meta( $project_id, '_upstream_project_milestones' );
168
169 // Update the tasks in the project.
170 if ( $updated_tasks ) {
171 update_post_meta( $project_id, '_upstream_project_tasks', $project_tasks );
172 }
173
174 $wpdb->query( 'COMMIT' );
175 } else {
176 update_post_meta( $project_id, '_upstream_project_milestones_legacy', array() );
177 }
178 } catch ( \Exception $e ) {
179 $wpdb->query( 'ROLLBACK' );
180
181 throw new Exception( 'Error found while migrating a milestone. ' . $e->getMessage() );
182 }
183 }
184
185 return true;
186 }
187
188 /**
189 * FixMilestoneOrdersOnProject
190 *
191 * @param int $project_id Project id.
192 *
193 * @return bool
194 * @throws \Exception Exception.
195 */
196 public static function fix_milestone_orders_on_project( $project_id ) {
197 try {
198 $project_milestones = self::getInstance()->get_milestones_from_project( $project_id );
199
200 if ( ! empty( $project_milestones ) ) {
201 global $wpdb;
202
203 $wpdb->query( 'START TRANSACTION' );
204
205 foreach ( $project_milestones as $project_milestone ) {
206 $milestone = Factory::get_milestone( $project_milestone );
207
208 // $milestone->setOrder($milestone->getName());
209 }
210
211 $wpdb->query( 'COMMIT' );
212 }
213 } catch ( \Exception $e ) {
214 $wpdb->query( 'ROLLBACK' );
215
216 throw new Exception( 'Error found while fixing the order on a milestone. ' . $e->getMessage() );
217 }
218
219 return true;
220 }
221
222 /**
223 * Create the post type for milestones.
224 *
225 * @since 1.24.0
226 */
227 public function create_post_type() {
228 if ( $this->post_type_created ) {
229 return;
230 }
231
232 $singular_label = upstream_milestone_label();
233 $plural_label = upstream_milestone_label_plural();
234
235 $labels = array(
236 'name' => $plural_label,
237 'singular_name' => $singular_label,
238 'add_new' => sprintf(
239 // translators: %s: singular_label.
240 _x( 'Add new %s', 'upstream' ),
241 $singular_label
242 ),
243 'edit_item' => sprintf(
244 // translators: %s: singular_label.
245 __( 'Edit %s', 'upstream' ),
246 $singular_label
247 ),
248 'new_item' => sprintf(
249 // translators: %s: singular_label.
250 __( 'New %s', 'upstream' ),
251 $singular_label
252 ),
253 'view_item' => sprintf(
254 // translators: %s: singular_label.
255 __( 'View %s', 'upstream' ),
256 $singular_label
257 ),
258 'view_items' => sprintf(
259 // translators: %s: plural_label.
260 __( 'View %s', 'upstream' ),
261 $plural_label
262 ),
263 'search_items' => sprintf(
264 // translators: %s: plural_label.
265 __( 'Search %s', 'upstream' ),
266 $plural_label
267 ),
268 'not_found' => sprintf(
269 // translators: %s: plural_label.
270 __( 'No %s found', 'upstream' ),
271 $plural_label
272 ),
273 'not_found_in_trash' => sprintf(
274 // translators: %s: singular_label.
275 __( 'No %s found in Trash', 'upstream' ),
276 $singular_label
277 ),
278 'parent_item_colon' => sprintf(
279 // translators: %s: singular_label.
280 __( 'Parent %s:', 'upstream' ),
281 $singular_label
282 ),
283 'all_items' => $plural_label,
284 'archives' => sprintf(
285 // translators: %s: singular_label.
286 __( '%s Archives', 'upstream' ),
287 $singular_label
288 ),
289 'attributes' => sprintf(
290 // translators: %s: singular_label.
291 __( '%s Attributes', 'upstream' ),
292 $singular_label
293 ),
294 'insert_into_item' => sprintf(
295 // translators: %s: singular_label.
296 __( 'Insert into %s', 'upstream' ),
297 $singular_label
298 ),
299 'uploaded_to_this_item' => sprintf(
300 // translators: %s: singular_label.
301 __( 'Uploaded to this %s', 'upstream' ),
302 $singular_label
303 ),
304 'featured_image' => __( 'Featured Image', 'upstream' ),
305 'set_featured_image' => __( 'Set featured image', 'upstream' ),
306 'remove_featured_image' => __( 'Remove featured image', 'upstream' ),
307 'use_featured_image' => __( 'Use as featured image', 'upstream' ),
308 'menu_name' => $plural_label,
309 'filter_items_list' => $plural_label,
310 'items_list_navigation' => $plural_label,
311 'items_list' => $plural_label,
312 'name_admin_bar' => $plural_label,
313 );
314
315 $args = array(
316 'labels' => $labels,
317 'public' => false,
318 'publicly_queryable' => false,
319 'show_ui' => true,
320 'show_in_menu' => 'edit.php?post_type=project',
321 'rewrite' => array( 'slug' => strtolower( $singular_label ) ),
322 'capability_type' => 'milestone',
323 'has_archive' => true,
324 'hierarchical' => false,
325 'supports' => array( 'title', 'comments' ),
326 'map_meta_cap' => true,
327 );
328
329 register_post_type( $this->get_post_type(), $args );
330
331 $this->post_type_created = true;
332 }
333
334 /**
335 * Add meta boxes to the post type.
336 *
337 * @param string $post_type Post type.
338 *
339 * @since 1.24.0
340 */
341 public function add_meta_box( $post_type ) {
342 if ( $this->get_post_type() !== $post_type ) {
343 return;
344 }
345
346 add_meta_box(
347 'upstream_mimlestone_data',
348 __( 'Data', 'upstream' ),
349 array( $this, 'render_meta_box' ),
350 $this->get_post_type(),
351 'advanced',
352 'high'
353 );
354 }
355
356 /**
357 * Render the metabox for data.
358 *
359 * @param \WP_Post $post Post data.
360 *
361 * @throws \Twig_Error_Loader Exception.
362 * @throws \Twig_Error_Runtime Exception.
363 * @throws \Twig_Error_Syntax Exception.
364 * @since 1.24.0
365 */
366 public function render_meta_box( $post ) {
367 $upstream = \UpStream::instance();
368
369 // Add an nonce field so we can check for it later.
370 wp_nonce_field( 'upstream_milestone_data_meta_box', 'upstream_milestone_data_meta_box_nonce' );
371
372 // Projects.
373 $projects_instances = get_posts(
374 array(
375 'post_type' => 'project',
376 'posts_per_page' => -1,
377 )
378 );
379 $projects = array();
380 if ( ! empty( $projects_instances ) ) {
381 foreach ( $projects_instances as $project ) {
382 $projects[ $project->ID ] = $project->post_title;
383 }
384 }
385
386 $milestone = Factory::get_milestone( $post->ID );
387
388 $context = array(
389 'field_prefix' => '_upstream_milestone_',
390 'members' => (array) $this->project_users_dropdown(),
391 'projects' => $projects,
392 'permissions' => array(
393 'edit_assigned_to' => current_user_can( 'milestone_assigned_to_field' ),
394 'edit_start_date' => current_user_can( 'milestone_start_date_field' ),
395 'edit_end_date' => current_user_can( 'milestone_end_date_field' ),
396 'edit_notes' => current_user_can( 'milestone_notes_field' ),
397 'edit_project' => current_user_can( 'edit_projects' ),
398 ),
399 'labels' => array(
400 'assigned_to' => __( 'Assigned To', 'upstream' ),
401 'none' => __( 'None', 'upstream' ),
402 'start_date' => __( 'Start Date', 'upstream' ),
403 'end_date' => __( 'End Date', 'upstream' ),
404 'notes' => __( 'Notes', 'upstream' ),
405 'project' => __( 'Project', 'upstream' ),
406 'color' => __( 'Color', 'upstream' ),
407 ),
408 'data' => array(
409 'assigned_to' => get_post_meta( $post->ID, 'upst_assigned_to', false ),
410 'start_date' => $milestone->getStartDate( 'upstream' ),
411 'end_date' => $milestone->getEndDate( 'upstream' ),
412 'notes' => $milestone->getNotes(),
413 'project_id' => $milestone->getProjectId(),
414 'color' => $milestone->getColor(),
415 'id' => $milestone->getId(),
416 ),
417 );
418
419 ?>
420
421 <div class="cmb2-wrap">
422 <?php if ( $context['permissions']['edit_project'] ) : ?>
423 <div class="row upstream-milestone-project">
424 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
425 <div class="col-xs-12 col-sm-3 col-md-2 col-lg-2 text-right">
426 <label for="<?php echo esc_attr( $context['field_prefix'] ); ?>project_id"><?php echo esc_html( $context['labels']['project'] ); ?></label>
427 </div>
428 <div class="col-xs-12 col-sm-9 col-md-8 col-lg-8">
429 <select id="<?php echo esc_attr( $context['field_prefix'] ); ?>project_id"
430 name="milestone_data[project_id]" class="form-control"
431 data-placeholder="<?php echo esc_attr( $context['labels']['none'] ); ?>">
432 <?php foreach ( $context['projects'] as $project_id => $project_name ) : ?>
433 <?php if ( $project_id ) : ?>
434 <option value="<?php echo esc_attr( $project_id ); ?>" <?php echo $project_id == $context['data']['project_id'] ? 'selected' : ''; ?> ><?php echo esc_html( $project_name ); ?></option>
435 <?php endif; ?>
436 <?php endforeach; ?>
437 </select>
438 </div>
439 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
440 </div>
441 <?php endif; ?>
442
443 <?php if ( $context['permissions']['edit_assigned_to'] ) : ?>
444 <div class="row upstream-milestone-assigned-to">
445 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
446 <div class="col-xs-12 col-sm-3 col-md-2 col-lg-2 text-right">
447 <label for="<?php echo esc_attr( $context['field_prefix'] ); ?>assigned_to"><?php echo esc_html( $context['labels']['assigned_to'] ); ?></label>
448 </div>
449 <div class="col-xs-12 col-sm-9 col-md-8 col-lg-8">
450 <select id="<?php echo esc_attr( $context['field_prefix'] ); ?>assigned_to"
451 name="milestone_data[assigned_to][]" class="form-control"
452 data-placeholder="<?php echo esc_attr( $context['labels']['none'] ); ?>" multiple>
453 <option></option>
454 <?php foreach ( $context['members'] as $user_id => $user_name ) : ?>
455 <option value="<?php echo esc_attr( $user_id ); ?>" <?php echo in_array( $user_id, $context['data']['assigned_to'] ) ? 'selected' : ''; ?> ><?php echo esc_html( $user_name ); ?></option>
456 <?php endforeach; ?>
457 </select>
458 </div>
459 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
460 </div>
461 <?php endif; ?>
462
463
464 <?php if ( $context['permissions']['edit_start_date'] ) : ?>
465 <div class="row upstream-milestone-start-date">
466 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
467 <div class="col-xs-12 col-sm-3 col-md-2 col-lg-2 text-right">
468 <label for="<?php echo esc_attr( $context['field_prefix'] ); ?>start_date"><?php echo esc_html( $context['labels']['start_date'] ); ?></label>
469 </div>
470 <div class="col-xs-12 col-sm-9 col-md-8 col-lg-8">
471 <input type="text" id="<?php echo esc_attr( $context['field_prefix'] ); ?>start_date"
472 name="milestone_data[start_date]" class="form-control o-datepicker"
473 placeholder="<?php echo esc_attr( $context['labels']['none'] ); ?>" data-elt="end_date"
474 autocomplete="off" value="<?php echo esc_attr( $context['data']['start_date'] ); ?>">
475 <input type="hidden" id="<?php echo esc_attr( $context['field_prefix'] ); ?>start_date_timestamp'; ?>"
476 data-name="start_date">
477 </div>
478 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
479 </div>
480 <?php endif; ?>
481
482 <?php if ( $context['permissions']['edit_end_date'] ) : ?>
483 <div class="row upstream-milestone-end-date">
484 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
485 <div class="col-xs-12 col-sm-3 col-md-2 col-lg-2 text-right">
486 <label for="<?php echo esc_attr( $context['field_prefix'] ); ?>end_date"><?php echo esc_html( $context['labels']['end_date'] ); ?></label>
487 </div>
488 <div class="col-xs-12 col-sm-9 col-md-8 col-lg-8">
489 <input type="text" id="<?php echo esc_attr( $context['field_prefix'] ); ?>end_date"
490 name="milestone_data[end_date]" class="form-control o-datepicker"
491 placeholder="<?php echo esc_attr( $context['labels']['none'] ); ?>" data-egt="start_date"
492 autocomplete="off" value="<?php echo esc_attr( $context['data']['end_date'] ); ?>">
493 <input type="hidden" id="<?php echo esc_attr( $context['field_prefix'] ); ?>end_date_timestamp"
494 data-name="end_date">
495 </div>
496 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
497 </div>
498 <?php endif; ?>
499
500
501 <?php do_action( 'upstream.frontend-edit:render_after.project.items.end_dates', 'milestones' ); ?>
502
503 <div class="row upstream-milestone-color">
504 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
505 <div class="col-xs-12 col-sm-3 col-md-2 col-lg-2 text-right">
506 <label for=""><?php echo esc_html( $context['labels']['color'] ); ?></label>
507 </div>
508 <div class="col-xs-12 col-sm-9 col-md-8 col-lg-8">
509 <input class="color-field" type="text" name="milestone_data[color]" value="<?php echo esc_attr( $context['data']['color'] ); ?>"/>
510 </div>
511 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
512 </div>
513
514 <?php if ( $context['permissions']['edit_notes'] ) : ?>
515 <div class="row upstream-milestone-notes">
516 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
517 <div class="col-xs-12 col-sm-3 col-md-2 col-lg-2 text-right">
518 <label for=""><?php echo esc_html( $context['labels']['notes'] ); ?></label>
519 </div>
520 <div class="col-xs-12 col-sm-9 col-md-8 col-lg-8">
521 <?php
522 wp_editor(
523 $context['data']['notes'],
524 $context['field_prefix'] . 'notes',
525 array(
526 'media_buttons' => true,
527 'textarea_rows' => 5,
528 'textarea_name' => 'milestone_data[notes]',
529 )
530 );
531 ?>
532 </div>
533 <div class="hidden-xs hidden-sm col-md-1 col-lg-1"></div>
534 </div>
535 <?php endif; ?>
536
537 <?php do_action( 'upstream.frontend-edit:render_additional_fields', 'milestone', $context['data'] ); ?>
538 </div>
539
540
541 <?php
542 }
543
544 /**
545 * Returns all users with select roles.
546 * For use in dropdowns.
547 */
548 protected function project_users_dropdown() {
549 $options = array(
550 '' => __( 'None', 'upstream' ),
551 );
552
553 $project_users = upstream_admin_get_all_project_users();
554
555 $options += $project_users;
556
557 return $options;
558 }
559
560 /**
561 * SavePost
562 *
563 * @param int $post_id Post_id.
564 *
565 * @throws \Exception Exception.
566 * @since 1.24.0
567 */
568 public function save_post( $post_id ) {
569 $post_data = wp_unslash( $_POST );
570
571 if ( ! isset( $post_data['milestone_data'] ) ) {
572 return $post_id;
573 }
574
575 $data = $post_data['milestone_data']; // NOTE: Each field is checked in the code below.
576
577 $project_id_field_name = 'project_id';
578 $project_id = absint( $data[ $project_id_field_name ] );
579
580 if ( ! upstream_user_can_access_project( get_current_user_id(), $project_id ) ) {
581 return $post_id;
582 }
583
584 $nonce = isset( $post_data['upstream_milestone_data_meta_box_nonce'] ) ? $post_data['upstream_milestone_data_meta_box_nonce'] : null;
585
586 // Verify that the nonce is valid.
587 if ( ! wp_verify_nonce( $nonce, 'upstream_milestone_data_meta_box' ) ) {
588 return $post_id;
589 }
590
591 // Start date.
592 $start_date_field_name = 'start_date';
593 $start_date = ! empty( $data[ $start_date_field_name ] ) ? sanitize_text_field( $data[ $start_date_field_name ] ) : '';
594
595 // End date.
596 $end_date_field_name = 'end_date';
597 $end_date = ! empty( $data[ $end_date_field_name ] ) ? sanitize_text_field( $data[ $end_date_field_name ] ) : '';
598
599 // Notes.
600 $notes = wp_kses_post( $data['notes'] );
601
602 $color = sanitize_text_field( $data['color'] );
603
604 // Store the values.
605 $milestone = Factory::get_milestone( $post_id );
606 $milestone->setProjectId( $project_id )
607 ->setStartDate( $start_date )
608 ->setEndDate( $end_date )
609 ->setNotes( $notes )
610 ->setColor( $color );
611
612 // If there is no assigned user, there won't be any key assigned_to in the $data array.
613 if ( isset( $data['assigned_to'] ) && is_array( $data['assigned_to'] ) ) {
614 $assigned_to = array_map( 'intval', (array) $data['assigned_to'] );
615
616 if ( $assigned_to > 0 ) {
617 $milestone->setAssignedTo( $assigned_to );
618 }
619 }
620
621 /**
622 * Upstream save milestone
623 *
624 * @param int $project_id
625 */
626 do_action( 'upstream_save_milestone', $post_id, false );
627 }
628
629 /**
630 * Manage_posts_columns
631 *
632 * @param array $columns Post column.
633 *
634 * @return array
635 * @since 1.24.0
636 */
637 public function manage_posts_columns( $columns ) {
638 $new_columns['cb'] = '<input type="checkbox" />';
639 $new_columns['title'] = __( 'Milestone', 'upstream' );
640 $new_columns['taxonomy-upst_milestone_category'] = __( 'Milestone Category', 'upstream' );
641 $new_columns['id'] = __( 'ID', 'upstream' );
642 $new_columns['project'] = __( 'Project', 'upstream' );
643 $new_columns['assigned_to'] = __( 'Assigned To', 'upstream' );
644 $new_columns['start_date'] = __( 'Start Date', 'upstream' );
645 $new_columns['end_date'] = __( 'End Date', 'upstream' );
646
647 return $new_columns;
648 }
649
650 /**
651 * Render_post_columns
652 *
653 * @param array $column Post column.
654 * @param int $post_id Post id.
655 *
656 * @since 1.24.0
657 */
658 public function render_post_columns( $column, $post_id ) {
659 $milestone = Factory::get_milestone( $post_id );
660 $project_id = $milestone->getProjectId();
661
662 if ( 'project' === $column && $project_id ) {
663 $project = get_post( $project_id );
664
665 if ( ! empty( $project ) ) {
666 printf( '<a href="%s">%s</a>', esc_attr( get_edit_post_link( $project->ID ) ), esc_html( $project->post_title ) );
667 }
668 }
669
670 if ( 'id' === $column ) {
671 echo esc_html( $post_id );
672 }
673
674 if ( 'assigned_to' === $column ) {
675 $users_id = $milestone->getAssignedTo();
676
677 if ( empty( $users_id ) ) {
678 echo '<span><i class="text-muted">' . esc_html__( 'none', 'upstream' ) . '</i></span>';
679
680 return;
681 }
682
683 $users = array();
684
685 foreach ( $users_id as $id ) {
686 $u = get_user_by( 'id', $id );
687 // RSD: fix error where $u is null.
688 if ( $u ) {
689 $users[] = $u->display_name;
690 }
691 }
692
693 echo esc_html( implode( ', ', $users ) );
694 }
695
696 if ( 'start_date' === $column ) {
697 echo esc_html( $milestone->getStartDate( 'upstream' ) );
698 }
699
700 if ( 'end_date' === $column ) {
701 echo esc_html( $milestone->getEndDate( 'upstream' ) );
702 }
703 }
704
705 /**
706 * Get_milestone_edit_post_link
707 *
708 * @param string $link Post link.
709 * @param int $id Post id.
710 */
711 public function get_milestone_edit_post_link( $link, $id ) {
712 $post = get_post( $id );
713
714 if ( 'upst_milestone' === $post->post_type ) {
715 $milestone = Factory::get_milestone( $id );
716 $project_id = $milestone->getProjectId();
717
718 if ( $project_id && 'project' === get_post_type( $project_id ) ) {
719 $link = wp_specialchars_decode( get_edit_post_link( $project_id ) ); // remove amp; character.
720 }
721 }
722
723 return $link;
724 }
725
726 /**
727 * Disable_new_milestone_button
728 */
729 public function disable_new_milestone_button() {
730 $cs = get_current_screen();
731
732 if ( isset( $cs->post_type ) && 'upst_milestone' === $cs->post_type ) {
733 echo '<style type="text/css">
734 .page-title-action { display:none; }
735 </style>';
736 }
737 }
738
739 /**
740 * HasAnyMilestone
741 *
742 * @return bool
743 */
744 public function has_any_milestone() {
745 $posts = get_posts(
746 array(
747 'post_type' => Milestone::POST_TYPE,
748 'post_status' => 'publish',
749 'posts_per_page' => -1,
750 )
751 );
752
753 return count( $posts ) > 0;
754 }
755
756 /**
757 * GetMilestonesAsRowset
758 *
759 * @param int $project_id Project_id.
760 *
761 * @return array|mixed|null
762 *
763 * @throws Exception Exception.
764 */
765 public function get_milestones_as_rowset( $project_id ) {
766 $project_milestones = $this->get_milestones_from_project( $project_id );
767 $data = array();
768
769 if ( ! empty( $project_milestones ) ) {
770 foreach ( $project_milestones as $milestone ) {
771 $milestone = \UpStream\Factory::get_milestone( $milestone );
772
773 $row = $milestone->convertToLegacyRowset();
774
775 $data[ $row['id'] ] = $row;
776 }
777
778 $data = apply_filters( 'upstream_project_milestones', $data, $project_id );
779 }
780
781 return $data;
782 }
783
784 /**
785 * Returns all milestones from the project without permissions check
786 *
787 * @param int $project_id project_id.
788 * @param bool $return_as_legacy_dataset return_as_legacy_dataset.
789 *
790 * @return array
791 * @throws Exception Exception.
792 */
793 public function get_all_milestones_from_project( $project_id, $return_as_legacy_dataset = false ) {
794 $posts = get_posts(
795 array(
796 'post_type' => Milestone::POST_TYPE,
797 'post_status' => 'publish',
798 'posts_per_page' => -1,
799 'meta_key' => Milestone::META_PROJECT_ID,
800 'meta_value' => $project_id,
801 'orderby' => 'menu_order',
802 'order' => 'ASC',
803 )
804 );
805
806 $milestones = array();
807
808 if ( ! empty( $posts ) ) {
809 foreach ( $posts as $post ) {
810 if ( true ) {
811
812 if ( $return_as_legacy_dataset ) {
813 $data = Factory::get_milestone( $post )->convertToLegacyRowset();
814 } else {
815 $data = $post;
816 }
817
818 $milestones[ $post->ID ] = $data;
819 }
820 }
821 }
822
823 return $milestones;
824 }
825
826
827 /**
828 * GetMilestonesFromProjectUncached_NoPerms
829 *
830 * @param int $project_id project_id.
831 * @param bool $return_as_legacy_dataset return_as_legacy_dataset.
832 *
833 * @return array
834 * @throws Exception Exception.
835 */
836 public function get_milestones_from_project_uncached_no_perms( $project_id, $return_as_legacy_dataset = false ) {
837 $posts = get_posts(
838 array(
839 'post_type' => Milestone::POST_TYPE,
840 'post_status' => 'publish',
841 'posts_per_page' => -1,
842 'meta_key' => Milestone::META_PROJECT_ID,
843 'meta_value' => $project_id,
844 'orderby' => 'menu_order',
845 'order' => 'ASC',
846 )
847 );
848
849 $milestones = array();
850
851 if ( ! empty( $posts ) ) {
852 foreach ( $posts as $post ) {
853 if ( $return_as_legacy_dataset ) {
854 $data = Factory::get_milestone( $post )->convertToLegacyRowset();
855 } else {
856 $data = $post;
857 }
858
859 $milestones[ $post->ID ] = $data;
860 }
861 }
862
863 return $milestones;
864 }
865
866 /**
867 * GetMilestonesFromProject_NoPerms
868 *
869 * @param int $project_id project_id.
870 * @param bool $return_as_legacy_dataset return_as_legacy_dataset.
871 *
872 * @return array
873 * @throws Exception Exception.
874 */
875 public function get_milestones_from_project_no_perms( $project_id, $return_as_legacy_dataset = false ) {
876 $key = 'get_milestones_from_project_no_perms' . ( (int) $project_id ) . '_' . ( (int) $return_as_legacy_dataset );
877
878 $milestones = \Upstream_Cache::get_instance()->get( $key );
879
880 // should be circumvented because it's only used on saves.
881 if ( false === $milestones ) {
882 $milestones = $this->get_milestones_from_project_uncached_no_perms( (int) $project_id, $return_as_legacy_dataset );
883 \Upstream_Cache::get_instance()->set( $key, $milestones );
884 }
885
886 return $milestones;
887
888 }
889
890 /**
891 * GetMilestonesFromProjectUncached
892 *
893 * @param int $project_id project_id.
894 * @param bool $return_as_legacy_dataset return_as_legacy_dataset.
895 *
896 * @return array
897 * @throws Exception Exception.
898 */
899 public function get_milestones_from_project_uncached( $project_id, $return_as_legacy_dataset = false ) {
900 $posts = get_posts(
901 array(
902 'post_type' => Milestone::POST_TYPE,
903 'post_status' => 'publish',
904 'posts_per_page' => -1,
905 'meta_key' => Milestone::META_PROJECT_ID,
906 'meta_value' => $project_id,
907 'orderby' => 'menu_order',
908 'order' => 'ASC',
909 )
910 );
911
912 $milestones = array();
913
914 if ( ! empty( $posts ) ) {
915 foreach ( $posts as $post ) {
916 if ( upstream_override_access_object( true, UPSTREAM_ITEM_TYPE_MILESTONE, $post->ID, UPSTREAM_ITEM_TYPE_PROJECT, $project_id, UPSTREAM_PERMISSIONS_ACTION_VIEW ) ) {
917
918 if ( $return_as_legacy_dataset ) {
919 $data = Factory::get_milestone( $post )->convertToLegacyRowset();
920 } else {
921 $data = $post;
922 }
923
924 $milestones[ $post->ID ] = $data;
925 }
926 }
927 }
928
929 return $milestones;
930 }
931
932 /**
933 * GetMilestonesFromProject
934 *
935 * @param int $project_id project_id.
936 * @param bool $return_as_legacy_dataset return_as_legacy_dataset.
937 *
938 * @return array
939 * @throws Exception Exception.
940 */
941 public function get_milestones_from_project( $project_id, $return_as_legacy_dataset = false ) {
942 $key = 'get_milestones_from_project' . ( (int) $project_id ) . '_' . ( (int) $return_as_legacy_dataset );
943
944 $milestones = \Upstream_Cache::get_instance()->get( $key );
945 if ( false === $milestones ) {
946 $milestones = $this->get_milestones_from_project_uncached( (int) $project_id, $return_as_legacy_dataset );
947 \Upstream_Cache::get_instance()->set( $key, $milestones );
948 }
949
950 return $milestones;
951
952 }
953
954 /**
955 * GetCategoriesNames
956 *
957 * @param array $categories categories.
958 *
959 * @return string
960 */
961 public function get_categories_names( $categories ) {
962 $names = array();
963
964 if ( is_array( $categories ) && ! empty( $categories ) ) {
965 foreach ( $categories as $category ) {
966 if ( is_numeric( $category ) ) {
967 $category = get_term( $category );
968 }
969
970 if ( is_object( $category ) ) {
971 $names[] = $category->name;
972 }
973 }
974 }
975
976 return implode( ', ', $names );
977 }
978 }
979