_x('Draft', 'Post state/status', 'anno'), 'submitted' => _x('Submitted', 'Post state/status', 'anno'), 'in_review' => _x('In Review', 'Post state/status', 'anno'), 'approved' => _x('Approved', 'Post state/status', 'anno'), 'rejected' => _x('Rejected', 'Post state/status', 'anno'), 'published' => _x('Published', 'Post state/status', 'anno'), ); /** * Remove/Add meta boxes */ function annowf_meta_boxes() { global $post; $post_state = annowf_get_post_state($post->ID); // Remove the WP Publish box remove_meta_box('submitdiv', 'article', 'side'); // Remove author box remove_meta_box('authordiv', 'article', 'normal'); // Remove slug div remove_meta_box('slugdiv', 'article', 'normal'); if (!anno_user_can('manage_public_comments', null, $post->ID)) { remove_meta_box('commentsdiv', 'article', 'normal'); } // Remove Revisions box in favor of audit log box //remove_meta_box('revisionsdiv', 'article', 'normal'); if (anno_user_can('view_audit')) { add_meta_box('audit_log', _x('Audit Log', 'Meta box title', 'anno'), 'annowf_audit_log', 'article', 'normal', 'low'); } // Remove taxonomy/edit boxes when a user is unable to save/edit if (!anno_user_can('edit_post', null, $post->ID)) { remove_meta_box('tagsdiv-article_tag', 'article', 'side'); remove_meta_box('article_category_select', 'article', 'side'); remove_meta_box('postimagediv', 'article', 'side', 'low'); } if ( !(current_user_can('editor') || current_user_can('administrator') ) ) { remove_meta_box('commentstatusdiv', 'article', 'normal'); } // Custom author select box. Only displays co-authors in the dropdown. add_meta_box('authordiv', _x('Author', 'Meta box title', 'anno'), 'annowf_author_meta_box', 'article', 'side', 'low'); // Add the Annotum workflow publish box. global $annowf_states; add_meta_box('submitdiv', _x('Status:', 'Meta box title', 'anno').' '. esc_html($annowf_states[$post_state]), 'annowf_status_meta_box', 'article', 'side', 'high'); // Clone data meta box. Only display if something has been cloned from this post, or it is a clone itself. $posts_cloned = get_post_meta($post->ID, '_anno_posts_cloned', true); $cloned_from = get_post_meta($post->ID, '_anno_cloned_from', true); if (!empty($posts_cloned) || !empty($cloned_from)) { add_meta_box('anno-cloned', _x('Versions', 'Meta box title', 'anno'), 'annowf_cloned_meta_box', 'article', 'side', 'low'); } if (anno_user_can('view_reviewers')) { add_meta_box('anno-reviewers', _x('Reviewers', 'Meta box title', 'anno'), 'annowf_reviewers_meta_box', 'article', 'side', 'low'); } add_meta_box('anno-co-authors', _x('Co-Authors', 'Meta box title', 'anno'), 'annowf_co_authors_meta_box', 'article', 'side', 'low'); } add_action('add_meta_boxes_article', 'annowf_meta_boxes'); /** * Enqueue css for workflow */ function annowf_css() { wp_enqueue_style('anno-workflow', trailingslashit(get_bloginfo('template_directory')).'plugins/workflow/css/workflow.css'); } add_action('admin_print_styles-post-new.php', 'annowf_css'); add_action('admin_print_styles-post.php', 'annowf_css'); /** * Enqueue js for internal comments */ function annowf_js() { wp_enqueue_script('suggest'); wp_enqueue_script('anno-workflow', trailingslashit(get_bloginfo('template_directory')).'plugins/workflow/js/workflow.js', array('jquery', 'suggest')); // Remove Auto-Save feature if a user cannot edit a post. *Note this prevents previewing inputted markup if (!anno_user_can('edit_post')) { wp_dequeue_script('autosave'); } } add_action('admin_print_scripts-post-new.php', 'annowf_js'); add_action('admin_print_scripts-post.php', 'annowf_js'); /** * Article Save action for correcting WP status updating. Fires before insertion into DB */ function annowf_insert_post_data($post, $postarr) { if ($post['post_type'] == 'article' && $post['post_status'] != 'auto-draft') { global $anno_post_save; if (isset($_POST['revert']) && $_POST['revert'] == $anno_post_save['revert']) { $post['post_status'] = 'draft'; } else if (isset($_POST['publish'])) { switch ($_POST['publish']) { case $anno_post_save['publish']: $post['post_status'] = 'publish'; break; case $anno_post_save['reject']: case $anno_post_save['revisions']: $post['post_status'] = 'draft'; break; case $anno_post_save['approve']: case $anno_post_save['review']: $post['post_status'] = 'pending'; break; default: break; } } } return $post; } add_filter('wp_insert_post_data', 'annowf_insert_post_data', 10, 2); /** * Article state transistioning. Fires after post has been inserted into the database */ function annowf_transistion_state($post_id, $post, $post_before) { if ($post->post_type == 'article' && $post->post_status != 'auto-draft') { global $anno_post_save; $current_user = wp_get_current_user(); $old_state = get_post_meta($post->ID, '_post_state', true); $new_state = $old_state; if (empty($old_state)) { $old_state = 'draft'; } if (isset($_POST['revert']) && $_POST['revert'] == $anno_post_save['revert']) { $new_state = 'draft'; } else if (isset($_POST['publish'])) { switch ($_POST['publish']) { // Make sure theres the ability to revert posts if they get a state on accident. case $anno_post_save['revert']: $new_state = 'draft'; break; case $anno_post_save['approve']: // Ensure proper state transitions if (in_array($old_state, array('submitted', 'in_review'))) { $new_state = 'approved'; } break; case $anno_post_save['revisions']: if (in_array($old_state, array('submitted', 'in_review'))) { $new_state = 'draft'; } break; case $anno_post_save['reject']: // Publishing staff can still reject even if editor approves if (in_array($old_state, array('submitted', 'in_review', 'approved'))) { $new_state = 'rejected'; } break; case $anno_post_save['publish']: if ($old_state == 'approved') { $new_state = 'published'; } break; case $anno_post_save['review']: $reviewers = anno_get_reviewers($post->ID); if (is_array($reviewers) && count($reviewers) && in_array($old_state, array('submitted', 'draft'))) { $new_state = 'in_review'; } else if ($old_state == 'draft') { $new_state = 'submitted'; } break; default: break; } } $notification_type = $new_state; if ($old_state == 'draft' && $new_state == 'in_review') { $notification_type = 're_review'; } // Send back for revisions if ($new_state == 'draft' && !empty($old_state) && $old_state != 'draft') { $round = annowf_get_round($post_id); update_post_meta($post_id, '_round', intval($round) + 1); $notification_type = 'revisions'; } if ($new_state != $old_state) { if (empty($new_state)) { $new_state = 'draft'; } update_post_meta($post->ID, '_post_state', $new_state); do_action('anno_state_change', $new_state, $old_state); // Save to audit log annowf_save_audit_item($post->ID, $current_user->ID, 2, array($old_state, $new_state)); // Send notifications, but not for published to draft state if (anno_workflow_enabled('notifications') && !($old_state == 'published' && $new_state == 'draft')) { annowf_send_notification($notification_type, $post); } } // Author has changed, add original author as co-author, remove new author from co-authors if ($post->post_author !== $post_before->post_author) { annowf_add_user_to_post('author', $post_before->post_author, $post->ID); annowf_remove_user_from_post('author', $post->post_author, $post->ID); if (anno_workflow_enabled('notifications')) { annowf_send_notification('primary_author', $post, null, array(anno_user_email($post->post_author))); } } } } add_action('post_updated', 'annowf_transistion_state', 10, 3); /** * Store revisions in the audit log. */ function anno_put_post_revision($rev_id) { $current_user = wp_get_current_user(); $revision = get_post($rev_id); if (!empty($revision)) { $post = get_post($revision->post_parent); } if (!empty($post) && $post->post_type == 'article') { annowf_save_audit_item($post->ID, $current_user->ID, 1, array($revision->ID)); } } add_action('_wp_put_post_revision', 'anno_put_post_revision'); function annowf_create_user_meta_markup($type) { ?>
ID)) { ?>

invite a new user', 'invite user link', 'anno'), add_query_arg( 'user-role', 'author', menu_page_url( Wpcom_Invite_Users::page_slug, false ) ), esc_attr('anno-show-create-' . $type ) ); ?>

ID); if (isset($anno_review_options[$review])) { $extra = $anno_review_options[$review].' '; } } $remove = ' '; // If a user can manage this type of user, show the remove link. if (anno_user_can('manage_'.$type_plural, null, $post_id)) { $remove = '  '._x('remove', 'remove user link for admin screens', 'anno').''; } ?>
  • ID, '36'); ?>
    user_login); ?>
  • user_email), $response['user']); } if ($post_state == 'submitted') { update_post_meta($post_id, '_post_state', 'in_review'); if (anno_workflow_enabled('notifications')) { $post = get_post($post_id); annowf_send_notification('in_review', $post); } } // If the reviewer is being re-added and has already left a review for this round $round = annowf_get_round($post_id); $review = get_user_attribute($response['user']->ID, '_'.$post_id.'_review_'.$round, true); if (!empty($review)) { $reviewed = get_post_meta($post_id, '_round_'.$round.'_reviewed', true); $reviewed[] = $response['user']->ID; update_post_meta($post_id, '_round_'.$round.'_reviewed', array_unique($reviewed)); // Used for incrementation of x of x reviewed $response['increment'] = 1; } else { $response['increment'] = 0; } //Add to the audit log $current_user = wp_get_current_user(); annowf_save_audit_item($post_id, $current_user->ID, 8, array($response['user']->ID)); } unset($response['user']); echo json_encode($response); die(); } add_action('wp_ajax_anno-add-reviewer', 'annowf_add_reviewer'); /** * Handles AJAX request for adding a co-author to a post. */ function annowf_add_co_author() { $response = annowf_add_user('co_author'); if ($response['message'] == 'success') { // Used for quick access when filtering posts on a post-author page $post_id = absint($_POST['post_id']); // Send email if (anno_workflow_enabled('notifications')) { $post = get_post($post_id); annowf_send_notification('co_author_added', $post, '', array($response['user']->user_email), $response['user']); } // Add author to JSON for appending to author dropdown $response['author'] = ''; //Add to the audit log $current_user = wp_get_current_user(); annowf_save_audit_item($post_id, $current_user->ID, 6, array($response['user']->ID)); } unset($response['user']); echo json_encode($response); die(); } add_action('wp_ajax_anno-add-co-author', 'annowf_add_co_author'); /** * AJAX handler for adding a user to a post with a given type * * @param string $type Type of user to add to the post (co_author, reviewer) * @return array Array of data pertaining to user added and JSON data. */ function annowf_add_user($type) { check_ajax_referer('anno_manage_'.$type, '_ajax_nonce-manage-'.$type); $message = 'error'; $html = ''; if (isset($_POST['post_id']) && isset($_POST['user'])) { $user_login = sanitize_text_field( trim( $_POST['user'] ) ); $user = get_user_by( 'login', $user_login ); // Only users of the current site can be be added as authors or reviewers if ( is_object( $user ) && !is_user_member_of_blog( $user->ID ) ) unset( $user ); if (!empty($user)) { $post = get_post($_POST['post_id']); $co_authors = anno_get_authors(absint($_POST['post_id'])); $reviewers = anno_get_reviewers(absint($_POST['post_id'])); if ($type == 'reviewer') { $type_string = _x('reviewer', 'noun describing user', 'anno'); } else { $type_string = _x('co-author', 'noun describing user', 'anno'); } if ($post->post_author == $user->ID) { $html = sprintf(_x('Cannot add author as a %s', 'Adding user error message for article meta box', 'anno'), $type_string); } else if (in_array($user->ID, $co_authors) ) { $html = sprintf(_x('Cannot add %s as %s. User is already a co-author', 'Adding user error message for article meta box', 'anno'), $user->user_login, $type_string); } else if (in_array($user->ID, $reviewers)) { $html = sprintf(_x('Cannot add %s as %s. User is already a reviewer', 'Adding user error message for article meta box', 'anno'), $user->user_login, $type_string); } else if (annowf_add_user_to_post($type, $user->ID, absint($_POST['post_id']))) { $message = 'success'; ob_start(); annowf_user_li_markup($user, $type); $html = ob_get_contents(); ob_end_clean(); } } else { $html = sprintf(_x('User \'%s\' not found or not a member of your blog.', 'Adding user error message for article meta box', 'anno'), $user_login); } } return array('message' => $message, 'html' => $html, 'user' => $user); } /** * Handles AJAX request for remove a reviewer to a post. */ function annowf_remove_reviewer() { $response = annowf_remove_user('reviewer'); $response['decrement'] = 0; if ($response['message'] == 'success') { $post_id = absint($_POST['post_id']); $user_id = absint($_POST['user_id']); // Send back to submitted state if we've removed all the reviewers $reviewers = anno_get_reviewers($post_id); if ( ! is_countable( $reviewers ) || count( $reviewers ) == 0 ) { update_post_meta($post_id, '_post_state', 'submitted'); } // Check if the user had already left a review and send back in response to update dom appropriately $round = annowf_get_round($post_id); $reviews = get_post_meta($post_id, '_round_'.$round.'_reviewed', true); if (!is_array($reviews)) { $reviews = array(); } if (in_array($user_id, $reviews)) { $key = array_search($user_id, $reviews); unset($reviews[$key]); update_post_meta($post_id, '_round_'.$round.'_reviewed', $reviews); $response['decrement'] = 1; } //Add to the audit log $current_user = wp_get_current_user(); annowf_save_audit_item($post_id, $current_user->ID, 9, array($user_id)); } echo json_encode($response); die(); } add_action('wp_ajax_anno-remove-reviewer', 'annowf_remove_reviewer'); /** * Handles AJAX request for remove a co-author to a post. */ function annowf_remove_co_author() { $response = annowf_remove_user('co_author'); if ($response['message'] == 'success') { if (isset($_POST['user_id'])) { //Add to the audit log $current_user = wp_get_current_user(); annowf_save_audit_item(absint($_POST['post_id']), $current_user->ID, 7, array(absint($_POST['user_id']))); } } echo json_encode($response); die(); } add_action('wp_ajax_anno-remove-co_author', 'annowf_remove_co_author'); /** * AJAX handler for removing users from a post for a given type */ function annowf_remove_user($type) { $response = []; check_ajax_referer('anno_manage_'.$type, '_ajax_nonce-manage-'.$type); $response['message'] = 'error'; if (isset($_POST['user_id']) && isset($_POST['post_id'])) { if (annowf_remove_user_from_post($type, absint($_POST['user_id']), absint($_POST['post_id']))) { $response['message'] = 'success'; } } return $response; } /** * Fetch the post state of a given post. If no post state is found * it will be determined by the post status * * @param int $post_id The id to fetch the post stat for * @return string Post state */ function annowf_get_post_state($post_id) { $post_state = get_post_meta($post_id, '_post_state', true); if (!$post_state) { $post = get_post($post_id); if ($post) { switch ($post->post_status) { case 'publish': $post_state = 'published'; break; case 'draft': case 'pending': default: $post_state = 'draft'; break; } update_post_meta($post_id, '_post_state', $post_state); } } return $post_state; } /** * Return the round associated with a post. Rounds are determined by the number * of times they have been in the draft state. (sent for revisions) * * @param int $post_id The id to fetch the post stat for * @return int Round */ function annowf_get_round($post_id) { $round = get_post_meta($post_id, '_round', true); if (!$round) { $round = 0; } return $round; } /** * Typeahead user search AJAX handler */ function annowf_user_search() { global $wpdb; $s = sanitize_text_field( stripslashes( $_GET['q'] ) ); $s = trim( $s ); if ( strlen( $s ) < 2 ) die; // require 2 chars for matching $s = sprintf( '*%s*', $s ); // Enable wildcard searching $search_args = array( 'search' => $s, 'who' => 'authors', 'fields' => array( 'display_name', 'user_login', ), ); $matching_users = get_users( $search_args ); $results = array(); foreach( $matching_users as $matching_user ) { $results[] = $matching_user->user_login; } echo implode($results, "\n"); die; } add_action('wp_ajax_anno-user-search', 'annowf_user_search'); /** * Metabox for posts that have been cloned from this post */ function annowf_cloned_meta_box($post) { //TODO check for trash/deleted $cloned_from = get_post_meta($post->ID, '_anno_cloned_from', true); $cloned_from_post = get_post($cloned_from_post); if (!$cloned_from_post) { return; } ?>
    '.esc_html($cloned_post->post_title).''; ?>
    ID, '_anno_posts_cloned', true); if (!empty($posts_cloned) && is_array($posts_cloned)) { ?>
    '.esc_html($cloned_post->post_title).''; } } } ?>
    $current_user->ID, 'post_status' => 'draft', 'post_title' => sprintf(_x('Cloned: %s', 'Cloned article title prepend', 'anno'), $post->post_title), 'post_content' => $post->post_content, 'post_excerpt' => $post->post_excerpt, 'post_type' => $post->post_type, 'post_parent' => $post->post_parent, ); $new_id = wp_insert_post($new_post); // Add to clone/cloned post meta if ($new_id) { $posts_cloned = get_post_meta($orig_id, '_anno_posts_cloned', true); if (!is_array($posts_cloned)) { $posts_cloned = array($new_id); } else { $posts_cloned[] = $new_id; } update_post_meta($orig_id, '_anno_posts_cloned', $posts_cloned); update_post_meta($new_id, '_anno_cloned_from', $orig_id); } return $new_id; } /** * Custom meta Box For Author select. */ function annowf_author_meta_box($post) { if (!anno_user_can('select_author')) { $author = get_userdata($post->post_author); echo esc_html($author->user_login); } else { $authors = anno_get_authors($post->ID); ?> implode(',', $authors), 'name' => 'post_author_override', 'selected' => empty($post->ID) ? $user_ID : $post->post_author, 'include_selected' => true )); } } /** * Admin request handler. Handles backend permission enforcement, cloning. */ function annowf_admin_request_handler() { global $anno_post_save; // Cloning. This must come before the enforcing of capabilities below. if (isset($_POST['publish']) && $_POST['publish'] == $anno_post_save['clone']) { if (!anno_user_can('clone_post')) { wp_die(_x('You are not allowed to clone this post.', 'Cloned article error message', 'anno')); } $post_id = anno_get_post_id(); $new_id = annowf_clone_post($post_id); if (!empty($new_id)) { $url = add_query_arg('message', 11, get_edit_post_link($new_id, 'url')); } else { $url = add_query_arg('message', 12, get_edit_post_link($post_id, 'url')); } wp_redirect($url); die(); } // Enforce Capabilities on the backend. Determine the action, and its relevant annotum capability if (isset($_POST['action'])) { $wp_action = $_POST['action']; } else if (isset($_GET['action'])) { $wp_action = $_GET['action']; } if (isset( $_POST['deletepost'])) { $wp_action = 'delete'; } if (isset($_POST['post_type'])) { $post_type = $_POST['post_type']; } else if (isset($_GET['post_type'])) { $post_type = $_GET['post_type']; } else if (isset($_GET['revision'])) { // We only get revision when restoring a given revision $rev_id = $_GET['revision']; $rev = get_post($rev_id); if (isset($rev->post_parent)) { $post = get_post($rev->post_parent); if (isset($post->post_type)) { $post_type = $post->post_type; } } } else { $post = get_post(anno_get_post_id()); if (isset($post->post_type)) { $post_type = $post->post_type; } } if (!empty($wp_action) && !empty($post_type) && $post_type == 'article') { switch ($wp_action) { case 'postajaxpost': case 'post': case 'post-quickpress-publish': case 'post-quickpress-save': $anno_cap = 'edit_post'; break; // Creation, editing, restoring from revision case 'editpost': case 'editattachment': case 'autosave': case 'restore': case 'inline-save': $anno_cap = 'edit_post'; break; // For Viewing post-edit screen case 'edit': $anno_cap = 'view_post'; break; case 'trash': case 'untrash': $anno_cap = 'trash_post'; break; case 'delete': $anno_cap = 'admin'; break; default: break; } if (!empty($anno_cap) && !anno_user_can($anno_cap)) { add_filter('user_has_cap', 'annowf_user_has_cap_filter'); } } } add_action('admin_init', 'annowf_admin_request_handler', 0); /** * Filter to remove WP caps from a user for a given action if they do not have the workflow caps */ function annowf_user_has_cap_filter($user_caps) { // Remove all capabilities so the user cannot perform the current action. return false; } /** * Adjust the edit display to override default WP implementation */ function annowf_get_sample_permalink_html($return, $id, $new_title, $new_slug) { $post = get_post($id); if (!empty($post) && $post->post_type == 'article') { if (anno_user_can('edit_slug', null, $id)) { return $return; } else { return ''; } } return $return; } add_filter('get_sample_permalink_html', 'annowf_get_sample_permalink_html', 10, 4); /** * If this article was imported, show a notice to the user, flag is removed on save. * * */ function annowf_imported_admin_notices($empty) { global $pagenow; if ($pagenow == 'post.php') { global $post; $imported = get_post_meta($post->ID, '_anno_imported', true); if ($imported == '1') { // Display notice echo '
    '.__('You are editing an imported article. Updating this article will likely change its XML and HTML output.').'
    '; } } } //add_action('admin_notices', 'annowf_imported_admin_notices') /** * Remove actions TD content if user cannot edit this post in the Workflow context * * @todo Preferably a non-js way to do this. Currently js is the only option */ function annowf_revision_remove_restore_link() { // This is actually the original post, not the revision global $post; if ($post->post_type == 'article' && !anno_user_can('edit_post')) { ?>