attach_hooks(); } /** * Attach all relevant actions to handle comments. * * @since 1.24.0 */ private function attach_hooks() { if ( upstream_disable_milestones() ) { return; } add_action( 'before_upstream_init', array( $this, 'create_post_type' ) ); add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ), 8 ); add_action( 'save_post', array( $this, 'save_post' ) ); $post_type = $this->get_post_type(); add_filter( 'manage_' . $post_type . '_posts_columns', array( $this, 'manage_posts_columns' ), 10 ); add_action( 'manage_' . $post_type . '_posts_custom_column', array( $this, 'render_post_columns' ), 10, 2 ); add_filter( 'get_edit_post_link', array( $this, 'get_milestone_edit_post_link' ), 10, 2 ); add_action( 'current_screen', array( $this, 'disable_new_milestone_button' ) ); } /** * Return the post type name. * * @return string * @since 1.24.0 */ public function get_post_type() { return Milestone::POST_TYPE; } /** * MigrateLegacyMilestonesForProject * * @param int $project_id Project ID. * * @return bool * @throws \Exception Exception. */ public static function migrate_legacy_milestones_for_project( $project_id ) { // Migrate the milestones. $default_milestones = get_option( 'upstream_milestones', array() ); if ( ! empty( $default_milestones ) ) { $default_milestones = $default_milestones['milestones']; $legacy_milestones = array(); // Organize the milestones by id. foreach ( $default_milestones as $milestone_data ) { $legacy_milestones[ $milestone_data['id'] ] = $milestone_data; } global $wpdb; // Get the project's milestones to convert them into the new post types. $project_milestones = get_post_meta( $project_id, '_upstream_project_milestones', true ); $project_tasks = get_post_meta( $project_id, '_upstream_project_tasks', true ); try { if ( ! empty( $project_milestones ) ) { // Check if the backup register doesn't exist. $legacy_milestones_backup = get_post_meta( $project_id, '_upstream_project_milestones_legacy', true ); if ( empty( $legacy_milestones_backup ) ) { // Move the milestones to a backup register, temporarily. update_post_meta( $project_id, '_upstream_project_milestones_legacy', $project_milestones ); } $wpdb->query( 'START TRANSACTION' ); $updated_tasks = false; foreach ( $project_milestones as $project_milestone ) { $data = $legacy_milestones[ $project_milestone['milestone'] ]; // Check if we already have this milestone in the project. $migrated_milestone = get_posts( array( 'post_type' => Milestone::POST_TYPE, 'post_parent' => $project_id, 'post_status' => 'publish', 'meta_key' => Milestone::META_LEGACY_MILESTONE_CODE, 'meta_value' => $project_milestone['milestone'], ) ); // If the milestone already exists, abort. if ( ! empty( $migrated_milestone ) ) { continue; } // The milestone doesn't exist. Let's create it. $milestone = Factory::create_milestone( $data['title'] ) ->setLegacyId( $project_milestone['id'] ) ->setLegacyMilestoneCode( $project_milestone['milestone'] ) ->setStartDate( $project_milestone['start_date'] ) ->setEndDate( $project_milestone['end_date'] ) ->setAssignedTo( $project_milestone['assigned_to'] ) ->setNotes( $project_milestone['notes'] ) ->setCreatedTimeInUtc( 1 === (int) $project_milestone['notes'] ) ->setProgress( (float) $project_milestone['progress'] ) ->setTaskCount( (int) $project_milestone['task_count'] ) ->setTaskOpen( (int) $project_milestone['task_open'] ) ->setColor( $data['color'] ) // ->setOrder($data['title']) ->setProjectId( $project_id ); // Look for all the tasks to convert the milestone ID. if ( ! empty( $project_tasks ) ) { foreach ( $project_tasks as &$task ) { if ( $task['milestone'] === $milestone->getLegacyId() ) { $task['milestone'] = $milestone->getId(); // Keep the legacy reference for a while. $task['milestone_legacy'] = $milestone->getLegacyId(); $updated_tasks = true; } } } } update_post_meta( $project_id, '_upstream_milestones_migrated', 1 ); // Remove the legacy Milestones. delete_post_meta( $project_id, '_upstream_project_milestones' ); // Update the tasks in the project. if ( $updated_tasks ) { update_post_meta( $project_id, '_upstream_project_tasks', $project_tasks ); } $wpdb->query( 'COMMIT' ); } else { update_post_meta( $project_id, '_upstream_project_milestones_legacy', array() ); } } catch ( \Exception $e ) { $wpdb->query( 'ROLLBACK' ); throw new Exception( 'Error found while migrating a milestone. ' . $e->getMessage() ); } } return true; } /** * FixMilestoneOrdersOnProject * * @param int $project_id Project id. * * @return bool * @throws \Exception Exception. */ public static function fix_milestone_orders_on_project( $project_id ) { try { $project_milestones = self::getInstance()->get_milestones_from_project( $project_id ); if ( ! empty( $project_milestones ) ) { global $wpdb; $wpdb->query( 'START TRANSACTION' ); foreach ( $project_milestones as $project_milestone ) { $milestone = Factory::get_milestone( $project_milestone ); // $milestone->setOrder($milestone->getName()); } $wpdb->query( 'COMMIT' ); } } catch ( \Exception $e ) { $wpdb->query( 'ROLLBACK' ); throw new Exception( 'Error found while fixing the order on a milestone. ' . $e->getMessage() ); } return true; } /** * Create the post type for milestones. * * @since 1.24.0 */ public function create_post_type() { if ( $this->post_type_created ) { return; } $singular_label = upstream_milestone_label(); $plural_label = upstream_milestone_label_plural(); $labels = array( 'name' => $plural_label, 'singular_name' => $singular_label, 'add_new' => sprintf( // translators: %s: singular_label. _x( 'Add new %s', 'upstream' ), $singular_label ), 'edit_item' => sprintf( // translators: %s: singular_label. __( 'Edit %s', 'upstream' ), $singular_label ), 'new_item' => sprintf( // translators: %s: singular_label. __( 'New %s', 'upstream' ), $singular_label ), 'view_item' => sprintf( // translators: %s: singular_label. __( 'View %s', 'upstream' ), $singular_label ), 'view_items' => sprintf( // translators: %s: plural_label. __( 'View %s', 'upstream' ), $plural_label ), 'search_items' => sprintf( // translators: %s: plural_label. __( 'Search %s', 'upstream' ), $plural_label ), 'not_found' => sprintf( // translators: %s: plural_label. __( 'No %s found', 'upstream' ), $plural_label ), 'not_found_in_trash' => sprintf( // translators: %s: singular_label. __( 'No %s found in Trash', 'upstream' ), $singular_label ), 'parent_item_colon' => sprintf( // translators: %s: singular_label. __( 'Parent %s:', 'upstream' ), $singular_label ), 'all_items' => $plural_label, 'archives' => sprintf( // translators: %s: singular_label. __( '%s Archives', 'upstream' ), $singular_label ), 'attributes' => sprintf( // translators: %s: singular_label. __( '%s Attributes', 'upstream' ), $singular_label ), 'insert_into_item' => sprintf( // translators: %s: singular_label. __( 'Insert into %s', 'upstream' ), $singular_label ), 'uploaded_to_this_item' => sprintf( // translators: %s: singular_label. __( 'Uploaded to this %s', 'upstream' ), $singular_label ), 'featured_image' => __( 'Featured Image', 'upstream' ), 'set_featured_image' => __( 'Set featured image', 'upstream' ), 'remove_featured_image' => __( 'Remove featured image', 'upstream' ), 'use_featured_image' => __( 'Use as featured image', 'upstream' ), 'menu_name' => $plural_label, 'filter_items_list' => $plural_label, 'items_list_navigation' => $plural_label, 'items_list' => $plural_label, 'name_admin_bar' => $plural_label, ); $args = array( 'labels' => $labels, 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_in_menu' => 'edit.php?post_type=project', 'rewrite' => array( 'slug' => strtolower( $singular_label ) ), 'capability_type' => 'milestone', 'has_archive' => true, 'hierarchical' => false, 'supports' => array( 'title', 'comments' ), 'map_meta_cap' => true, ); register_post_type( $this->get_post_type(), $args ); $this->post_type_created = true; } /** * Add meta boxes to the post type. * * @param string $post_type Post type. * * @since 1.24.0 */ public function add_meta_box( $post_type ) { if ( $this->get_post_type() !== $post_type ) { return; } add_meta_box( 'upstream_mimlestone_data', __( 'Data', 'upstream' ), array( $this, 'render_meta_box' ), $this->get_post_type(), 'advanced', 'high' ); } /** * Render the metabox for data. * * @param \WP_Post $post Post data. * * @throws \Twig_Error_Loader Exception. * @throws \Twig_Error_Runtime Exception. * @throws \Twig_Error_Syntax Exception. * @since 1.24.0 */ public function render_meta_box( $post ) { $upstream = \UpStream::instance(); // Add an nonce field so we can check for it later. wp_nonce_field( 'upstream_milestone_data_meta_box', 'upstream_milestone_data_meta_box_nonce' ); // Projects. $projects_instances = get_posts( array( 'post_type' => 'project', 'posts_per_page' => -1, ) ); $projects = array(); if ( ! empty( $projects_instances ) ) { foreach ( $projects_instances as $project ) { $projects[ $project->ID ] = $project->post_title; } } $milestone = Factory::get_milestone( $post->ID ); $context = array( 'field_prefix' => '_upstream_milestone_', 'members' => (array) $this->project_users_dropdown(), 'projects' => $projects, 'permissions' => array( 'edit_assigned_to' => current_user_can( 'milestone_assigned_to_field' ), 'edit_start_date' => current_user_can( 'milestone_start_date_field' ), 'edit_end_date' => current_user_can( 'milestone_end_date_field' ), 'edit_notes' => current_user_can( 'milestone_notes_field' ), 'edit_project' => current_user_can( 'edit_projects' ), ), 'labels' => array( 'assigned_to' => __( 'Assigned To', 'upstream' ), 'none' => __( 'None', 'upstream' ), 'start_date' => __( 'Start Date', 'upstream' ), 'end_date' => __( 'End Date', 'upstream' ), 'notes' => __( 'Notes', 'upstream' ), 'project' => __( 'Project', 'upstream' ), 'color' => __( 'Color', 'upstream' ), ), 'data' => array( 'assigned_to' => get_post_meta( $post->ID, 'upst_assigned_to', false ), 'start_date' => $milestone->getStartDate( 'upstream' ), 'end_date' => $milestone->getEndDate( 'upstream' ), 'notes' => $milestone->getNotes(), 'project_id' => $milestone->getProjectId(), 'color' => $milestone->getColor(), 'id' => $milestone->getId(), ), ); ?>