parent_page = apply_filters( 'coauthors_guest_author_parent_page', $this->parent_page ); // Allow users to change the required cap for modifying guest authors $this->list_guest_authors_cap = apply_filters( 'coauthors_guest_author_manage_cap', $this->list_guest_authors_cap ); // Set up default labels, but allow themes to modify $this->labels = apply_filters( 'coauthors_guest_author_labels', array( 'singular' => __( 'Guest Author', 'co-authors-plus' ), 'plural' => __( 'Guest Authors', 'co-authors-plus' ), 'all_items' => __( 'All Guest Authors', 'co-authors-plus' ), 'add_new_item' => __( 'Add New Guest Author', 'co-authors-plus' ), 'edit_item' => __( 'Edit Guest Author', 'co-authors-plus' ), 'new_item' => __( 'New Guest Author', 'co-authors-plus' ), 'view_item' => __( 'View Guest Author', 'co-authors-plus' ), 'search_items' => __( 'Search Guest Authors', 'co-authors-plus' ), 'not_found' => __( 'No guest authors found', 'co-authors-plus' ), 'not_found_in_trash' => __( 'No guest authors found in Trash', 'co-authors-plus' ), 'update_item' => __( 'Update Guest Author', 'co-authors-plus' ), 'metabox_about' => __( 'About the guest author', 'co-authors-plus' ), 'featured_image' => __( 'Avatar', 'co-authors-plus' ), 'set_featured_image' => __( 'Set Avatar', 'co-authors-plus' ), 'use_featured_image' => __( 'Use Avatar', 'co-authors-plus' ), 'remove_featured_image' => __( 'Remove Avatar', 'co-authors-plus' ), ) ); // Register a post type to store our guest authors $args = array( 'label' => $this->labels['singular'], 'labels' => array( 'name' => isset( $this->labels['plural'] ) ? $this->labels['plural'] : '', 'singular_name' => isset( $this->labels['singular'] ) ? $this->labels['singular'] : '', 'add_new' => _x( 'Add New', 'guest author', 'co-authors-plus' ), 'all_items' => isset( $this->labels['all_items'] ) ? $this->labels['all_items'] : '', 'add_new_item' => isset( $this->labels['add_new_item'] ) ? $this->labels['add_new_item'] : '', 'edit_item' => isset( $this->labels['edit_item'] ) ? $this->labels['edit_item'] : '', 'new_item' => isset( $this->labels['new_item'] ) ? $this->labels['new_item'] : '', 'view_item' => isset( $this->labels['view_item'] ) ? $this->labels['view_item'] : '', 'search_items' => isset( $this->labels['search_items'] ) ? $this->labels['search_items'] : '', 'not_found' => isset( $this->labels['not_found'] ) ? $this->labels['not_found'] : '', 'not_found_in_trash' => isset( $this->labels['not_found_in_trash'] ) ? $this->labels['not_found_in_trash'] : '', 'featured_image' => isset( $this->labels['featured_image'] ) ? $this->labels['featured_image'] : '', 'set_featured_image' => isset( $this->labels['set_featured_image'] ) ? $this->labels['set_featured_image'] : '', 'use_featured_image' => isset( $this->labels['use_featured_image'] ) ? $this->labels['use_featured_image'] : '', 'remove_featured_image' => isset( $this->labels['remove_featured_image'] ) ? $this->labels['remove_featured_image'] : '', ), 'public' => true, 'publicly_queryable' => false, 'exclude_from_search' => true, 'show_in_menu' => false, 'supports' => array( 'thumbnail', ), 'taxonomies' => array( $coauthors_plus->coauthor_taxonomy, ), 'rewrite' => false, 'query_var' => false, ); register_post_type( $this->post_type, $args ); // Some of the common sizes used by get_avatar $this->avatar_sizes = array(); // Hacky way to remove the title and the editor remove_post_type_support( $this->post_type, 'title' ); remove_post_type_support( $this->post_type, 'editor' ); } /** * Filter the messages that appear when saving or updating a guest author * * @since 3.0 */ function filter_post_updated_messages( $messages ) { global $post; if ( $this->post_type !== $post->post_type ) { return $messages; } $guest_author = $this->get_guest_author_by( 'ID', $post->ID ); $guest_author_link = $this->filter_author_link( '', $guest_author->ID, $guest_author->user_nicename ); $messages[ $this->post_type ] = array( 0 => '', // Unused. Messages start at index 1. 1 => sprintf( __( 'Guest author updated. View profile', 'co-authors-plus' ), esc_url( $guest_author_link ) ), 2 => __( 'Custom field updated.', 'co-authors-plus' ), 3 => __( 'Custom field deleted.', 'co-authors-plus' ), 4 => __( 'Guest author updated.', 'co-authors-plus' ), /* translators: %s: date and time of the revision */ 5 => isset( $_GET['revision'] ) ? sprintf( __( 'Guest author restored to revision from %s', 'co-authors-plus' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false, 6 => sprintf( __( 'Guest author updated. View profile', 'co-authors-plus' ), esc_url( $guest_author_link ) ), 7 => __( 'Guest author saved.', 'co-authors-plus' ), 8 => sprintf( __( 'Guest author submitted. Preview profile', 'co-authors-plus' ), esc_url( add_query_arg( 'preview', 'true', $guest_author_link ) ) ), 9 => sprintf( __( 'Guest author scheduled for: %1$s. Preview profile', 'co-authors-plus' ), // translators: Publish box date format, see http://php.net/date date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( $guest_author_link ) ), 10 => sprintf( __( 'Guest author updated. Preview profile', 'co-authors-plus' ), esc_url( add_query_arg( 'preview', 'true', $guest_author_link ) ) ), ); return $messages; } /** * Handle the admin action to create a guest author based * on an existing user * * @since 3.0 */ function handle_create_guest_author_action() { if ( ! isset( $_GET['action'], $_GET['nonce'], $_GET['user_id'] ) || 'cap-create-guest-author' !== $_GET['action'] ) { return; } if ( ! wp_verify_nonce( $_GET['nonce'], 'create-guest-author' ) ) { wp_die( esc_html__( "Doin' something fishy, huh?", 'co-authors-plus' ) ); } if ( ! current_user_can( $this->list_guest_authors_cap ) ) { wp_die( esc_html__( "You don't have permission to perform this action.", 'co-authors-plus' ) ); } $user_id = intval( $_GET['user_id'] ); // Create the guest author $post_id = $this->create_guest_author_from_user_id( $user_id ); if ( is_wp_error( $post_id ) ) { wp_die( esc_html( $post_id->get_error_message() ) ); } do_action( 'cap_guest_author_create' ); // Redirect to the edit Guest Author screen $edit_link = get_edit_post_link( $post_id, 'redirect' ); $redirect_to = add_query_arg( 'message', 'guest-author-created', $edit_link ); wp_safe_redirect( esc_url_raw( $redirect_to ) ); exit; } /** * Handle the admin action to delete a guest author and possibly reassign their posts * * @since 3.0 */ function handle_delete_guest_author_action() { global $coauthors_plus; if ( ! isset( $_POST['action'], $_POST['reassign'], $_POST['_wpnonce'], $_POST['id'] ) || 'delete-guest-author' != $_POST['action'] ) { return; } // Verify the user is who they say they are if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'delete-guest-author' ) ) { wp_die( esc_html__( "Doin' something fishy, huh?", 'co-authors-plus' ) ); } // Make sure they can perform the action if ( ! current_user_can( $this->list_guest_authors_cap ) ) { wp_die( esc_html__( "You don't have permission to perform this action.", 'co-authors-plus' ) ); } // Make sure the guest author actually exists $guest_author = $this->get_guest_author_by( 'ID', (int) $_POST['id'] ); if ( ! $guest_author ) { wp_die( esc_html( sprintf( __( "%s can't be deleted because it doesn't exist.", 'co-authors-plus' ), $this->labels['singular'] ) ) ); } // Perform the reassignment if needed $guest_author_term = $coauthors_plus->get_author_term( $guest_author ); switch ( $_POST['reassign'] ) { // Leave assigned to the current linked account case 'leave-assigned': $reassign_to = $guest_author->linked_account; break; // Reassign to a different user case 'reassign-another': $user_nicename = sanitize_title( $_POST['leave-assigned-to'] ); $reassign_to = $coauthors_plus->get_coauthor_by( 'user_nicename', $user_nicename ); if ( ! $reassign_to ) { wp_die( esc_html__( 'Co-author does not exists. Try again?', 'co-authors-plus' ) ); } $reassign_to = $reassign_to->user_login; break; // Remove the byline, but don't delete the post case 'remove-byline': $reassign_to = false; break; default: wp_die( esc_html__( 'Please make sure to pick an option.', 'co-authors-plus' ) ); break; } $retval = $this->delete( $guest_author->ID, $reassign_to ); $args = array( 'page' => 'view-guest-authors', ); if ( is_wp_error( $retval ) ) { $args['message'] = 'delete-error'; } else { $args['message'] = 'guest-author-deleted'; do_action( 'cap_guest_author_del' ); } // Redirect to safety $redirect_to = add_query_arg( array_map( 'rawurlencode', $args ), admin_url( $this->parent_page ) ); wp_safe_redirect( esc_url_raw( $redirect_to ) ); exit; } /** * Given a search query, suggest some co-authors that might match it * * @since 3.0 */ function handle_ajax_search_coauthors_to_assign() { global $coauthors_plus; if ( ! current_user_can( $this->list_guest_authors_cap ) ) { die(); } $search = sanitize_text_field( $_GET['q'] ); if ( ! empty( $_GET['guest_author'] ) ) { $ignore = array( $this->get_guest_author_by( 'ID', (int) $_GET['guest_author'] )->user_login ); } else { $ignore = array(); } $results = wp_list_pluck( $coauthors_plus->search_authors( $search, $ignore ), 'user_login' ); $retval = array(); foreach ( $results as $user_login ) { $coauthor = $coauthors_plus->get_coauthor_by( 'user_login', $user_login ); $retval[] = (object) array( 'display_name' => $coauthor->display_name, 'user_login' => $coauthor->user_login, 'id' => $coauthor->user_nicename, ); } echo wp_json_encode( $retval ); die(); } /** * Some redirection we need to do for linked accounts * * @todo support author ID query vars */ function action_parse_request( $query ) { if ( ! isset( $query->query_vars['author_name'] ) ) { return $query; } // No redirection needed on admin requests if ( is_admin() ) { return $query; } $coauthor = $this->get_guest_author_by( 'linked_account', sanitize_title( $query->query_vars['author_name'] ) ); if ( is_object( $coauthor ) && $query->query_vars['author_name'] != $coauthor->user_login ) { global $wp_rewrite; $link = $wp_rewrite->get_author_permastruct(); if ( empty( $link ) ) { $file = home_url( '/' ); $link = $file . '?author_name=' . $coauthor->user_login; } else { $link = str_replace( '%author%', $coauthor->user_login, $link ); $link = home_url( user_trailingslashit( $link ) ); } wp_safe_redirect( $link ); exit; } return $query; } /** * Add the admin menus for seeing all co-authors * * @since 3.0 */ function action_admin_menu() { add_submenu_page( $this->parent_page, $this->labels['plural'], $this->labels['plural'], $this->list_guest_authors_cap, 'view-guest-authors', array( $this, 'view_guest_authors_list' ) ); } /** * Enqueue any scripts or styles used for Guest Authors * * @since 3.0 */ function action_admin_enqueue_scripts() { global $pagenow; // Enqueue our guest author CSS on the related pages if ( $this->parent_page === $pagenow && isset( $_GET['page'] ) && 'view-guest-authors' === $_GET['page'] ) { wp_enqueue_script( 'jquery-select2', plugins_url( 'lib/select2/select2.min.js', dirname( __FILE__ ) ), array( 'jquery' ), COAUTHORS_PLUS_VERSION ); wp_enqueue_style( 'cap-jquery-select2-css', plugins_url( 'lib/select2/select2.css', dirname( __FILE__ ) ), false, COAUTHORS_PLUS_VERSION ); wp_enqueue_style( 'guest-authors-css', plugins_url( 'css/guest-authors.css', dirname( __FILE__ ) ), false, COAUTHORS_PLUS_VERSION ); wp_enqueue_script( 'guest-authors-js', plugins_url( 'js/guest-authors.js', dirname( __FILE__ ) ), false, COAUTHORS_PLUS_VERSION ); } else if ( in_array( $pagenow, array( 'post.php', 'post-new.php' ) ) && $this->post_type === get_post_type() ) { add_action( 'admin_head', array( $this, 'change_title_icon' ) ); } } /** * Change the icon appearing next to the title * Core doesn't allow us to filter screen_icon(), so changing the ID is the next best thing * * @since 3.0.1 */ function change_title_icon() { ?> parent_page != $pagenow || ! isset( $_REQUEST['message'] ) ) { return; } switch ( $_REQUEST['message'] ) { case 'guest-author-deleted': $message = __( 'Guest author deleted.', 'co-authors-plus' ); break; default: $message = false; break; } if ( $message ) { echo '

' . esc_html( $message ) . '

'; } } /** * Register the metaboxes used for Guest Authors * * @since 3.0 */ function action_add_meta_boxes() { global $coauthors_plus; if ( get_post_type() == $this->post_type ) { // Remove the submitpost metabox because we have our own remove_meta_box( 'submitdiv', $this->post_type, 'side' ); remove_meta_box( 'slugdiv', $this->post_type, 'normal' ); add_meta_box( 'coauthors-manage-guest-author-save', __( 'Save', 'co-authors-plus' ), array( $this, 'metabox_manage_guest_author_save' ), $this->post_type, 'side', 'default' ); add_meta_box( 'coauthors-manage-guest-author-slug', __( 'Unique Slug', 'co-authors-plus' ), array( $this, 'metabox_manage_guest_author_slug' ), $this->post_type, 'side', 'default' ); // Our metaboxes with co-author details add_meta_box( 'coauthors-manage-guest-author-name', __( 'Name', 'co-authors-plus' ), array( $this, 'metabox_manage_guest_author_name' ), $this->post_type, 'normal', 'default' ); add_meta_box( 'coauthors-manage-guest-author-contact-info', __( 'Contact Info', 'co-authors-plus' ), array( $this, 'metabox_manage_guest_author_contact_info' ), $this->post_type, 'normal', 'default' ); add_meta_box( 'coauthors-manage-guest-author-bio', $this->labels['metabox_about'], array( $this, 'metabox_manage_guest_author_bio' ), $this->post_type, 'normal', 'default' ); } } /** * View a list table of all guest authors * * @since 3.0 */ function view_guest_authors_list() { // Allow guest authors to be deleted if ( isset( $_GET['action'], $_GET['id'], $_GET['_wpnonce'] ) && 'delete' == $_GET['action'] ) { // Make sure the user is who they say they are if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'guest-author-delete' ) ) { wp_die( esc_html__( "Doin' something fishy, huh?", 'co-authors-plus' ) ); } // Make sure the guest author actually exists $guest_author = $this->get_guest_author_by( 'ID', (int) $_GET['id'] ); if ( ! $guest_author ) { wp_die( esc_html( sprintf( __( "%s can't be deleted because it doesn't exist.", 'co-authors-plus' ), $this->labels['singular'] ) ) ); } // get post count global $coauthors_plus; $count = $coauthors_plus->get_guest_author_post_count( $guest_author ); echo '
'; echo '

'; echo '

' . esc_html( sprintf( __( 'Delete %s', 'co-authors-plus ' ), $this->labels['plural'] ) ) . '

'; echo '

' . esc_html( sprintf( __( 'You have specified this %s for deletion:', 'co-authors-plus' ), strtolower( $this->labels['singular'] ) ) ) . '

'; echo '

#' . esc_html( $guest_author->ID . ': ' . $guest_author->display_name ) . '

'; // display wording differently per post count if ( 0 === $count ) { $post_count_message = '

' . sprintf( __( 'There are no posts associated with this guest author.', 'co-authors-plus' ), strtolower( $this->labels['singular'] ) ) . '

'; } else { $note = '

' . sprintf( __( "Note: If you'd like to delete the %s and all of their posts, you should delete their posts first and then come back to delete the %s.", 'co-authors-plus' ), strtolower( $this->labels['singular'] ), strtolower( $this->labels['singular'] ) ) . '

'; if ( 1 === $count ) { $post_count_message = '

' . sprintf( __( 'There is %d post associated with this guest author. What should be done with the post assigned to this %s?', 'co-authors-plus' ), $count, strtolower( $this->labels['singular'] ) ) . '

'; } else { $post_count_message = '

' . sprintf( __( 'There are %d posts associated with this guest author. What should be done with the posts assigned to this %s?', 'co-authors-plus' ), $count, strtolower( $this->labels['singular'] ) ) . '

'; } $post_count_message .= $note; } $allowed_html = array( 'p' => array( 'class' => array(), ), ); echo wp_kses( $post_count_message, $allowed_html ); echo '
'; // Hidden stuffs echo ''; wp_nonce_field( 'delete-guest-author' ); echo ''; echo '
    '; // only show delete options if post count > 0 if ( $count > 0 ) { // Reassign to another user echo '
  • '; echo ''; echo '
  • '; // Leave mapped to a linked account if ( get_user_by( 'login', $guest_author->linked_account ) ) { echo '
  • '; } // Remove bylines from the posts echo '
  • '; } else { echo ''; } echo '
'; // disable disabled submit button for 0 post count if ( 0 === $count ) { submit_button( __( 'Confirm Deletion', 'co-authors-plus' ), 'secondary', 'submit', true ); } else { submit_button( __( 'Confirm Deletion', 'co-authors-plus' ), 'secondary', 'submit', true, array( 'disabled' => 'disabled' ) ); } echo '
'; echo '
'; } else { echo '
'; echo '

'; echo '

' . esc_html( $this->labels['plural'] ); // @todo caps check for creating a new user $add_new_link = admin_url( "post-new.php?post_type=$this->post_type" ); echo '' . esc_html__( 'Add New', 'co-authors-plus' ) . ''; echo '

'; $cap_list_table = new CoAuthors_WP_List_Table(); $cap_list_table->prepare_items(); echo '
'; echo ''; $cap_list_table->display(); echo '
'; echo '
'; } } /** * Metabox for saving or updating a Guest Author * * @since 3.0 */ function metabox_manage_guest_author_save() { global $post, $coauthors_plus; if ( in_array( $post->post_status, array( 'pending', 'publish', 'draft' ) ) ) { $button_text = $this->labels['update_item']; } else { $button_text = $this->labels['add_new_item']; } submit_button( $button_text, 'primary', 'publish', false ); // Secure all of our requests wp_nonce_field( 'guest-author-nonce', 'guest-author-nonce' ); } /** * Metabox for editing this guest author's slug or changing the linked account * * @since 3.0 */ function metabox_manage_guest_author_slug() { global $post; $pm_key = $this->get_post_meta_key( 'user_login' ); $existing_slug = get_post_meta( $post->ID, $pm_key, true ); echo ''; // Taken from grist_authors. $linked_account_key = $this->get_post_meta_key( 'linked_account' ); $linked_account = get_post_meta( $post->ID, $linked_account_key, true ); if ( $user = get_user_by( 'login', $linked_account ) ) { $linked_account_id = $user->ID; } else { $linked_account_id = -1; } // If user_login is the same as linked account, don't let the association be removed if ( $linked_account == $existing_slug ) { add_filter( 'wp_dropdown_users', array( $this, 'filter_wp_dropdown_users_to_disable' ) ); } $linked_account_user_ids = wp_list_pluck( $this->get_all_linked_accounts(), 'ID' ); if ( false !== ( $key = array_search( $linked_account_id, $linked_account_user_ids ) ) ) { unset( $linked_account_user_ids[ $key ] ); } echo '

'; wp_dropdown_users( apply_filters( 'coauthors_guest_author_linked_account_args', array( 'show_option_none' => __( '-- Not mapped --', 'co-authors-plus' ), 'name' => esc_attr( $this->get_post_meta_key( 'linked_account' ) ), // If we're adding an author or if there is no post author (0), then use -1 (which is show_option_none). // We then take -1 on save and convert it back to 0. (#blamenacin) 'selected' => $linked_account_id, // Don't let user accounts to be linked to more than one guest author 'exclude' => $linked_account_user_ids, ) ) ); echo '

'; remove_filter( 'wp_dropdown_users', array( $this, 'filter_wp_dropdown_users_to_disable' ) ); } /** * Make a wp_dropdown_users disabled * Only applied if the user_login value for the guest author matches its linked account * * @since 3.0 */ public function filter_wp_dropdown_users_to_disable( $output ) { return str_replace( ''; break; default: echo ''; break; } echo ''; } echo ''; } /** * Metabox to display all of the pertient contact details for a Guest Author not linked to * user account * * @since 3.0 */ function metabox_manage_guest_author_contact_info() { global $post; $fields = $this->get_guest_author_fields( 'contact-info' ); echo ''; foreach ( $fields as $field ) { $pm_key = $this->get_post_meta_key( $field['key'] ); $value = get_post_meta( $post->ID, $pm_key, true ); echo ''; } echo '
'; echo ''; echo ''; if ( ! isset( $field['input'] ) ) { $field['input'] = 'text'; } $field['input'] = apply_filters( 'coauthors_name_field_type_'. $pm_key , $field['input'] ); switch ( $field['input'] ) { case 'checkbox': echo ''; break; default: echo ''; break; } echo '
'; } /** * Metabox to edit the bio and other biographical details of the Guest Author * * @since 3.0 */ function metabox_manage_guest_author_bio() { global $post; $fields = $this->get_guest_author_fields( 'about' ); echo ''; foreach ( $fields as $field ) { $pm_key = $this->get_post_meta_key( $field['key'] ); $value = get_post_meta( $post->ID, $pm_key, true ); printf( ' ', esc_attr( $pm_key ), esc_html( $field['label'] ), esc_attr( $pm_key ), esc_textarea( $value ) ); } echo '
'; } /** * When a guest author is created or updated, we need to properly create * the post_name based on some data provided by the user * * @since 3.0 */ function manage_guest_author_filter_post_data( $post_data, $original_args ) { if ( $post_data['post_type'] != $this->post_type ) { return $post_data; } // @todo caps check if ( ! isset( $_POST['guest-author-nonce'] ) || ! wp_verify_nonce( $_POST['guest-author-nonce'], 'guest-author-nonce' ) ) { return $post_data; } // Validate the display name if ( empty( $_POST['cap-display_name'] ) ) { wp_die( esc_html__( 'Guest authors cannot be created without display names.', 'co-authors-plus' ) ); } $post_data['post_title'] = sanitize_text_field( $_POST['cap-display_name'] ); $slug = sanitize_title( get_post_meta( $original_args['ID'], $this->get_post_meta_key( 'user_login' ), true ) ); if ( ! $slug ) { $slug = sanitize_title( $_POST['cap-display_name'] ); } // Uh oh, no guest authors without slugs if ( ! $slug ) { wp_die( esc_html__( 'Guest authors cannot be created without display names.', 'co-authors-plus' ) ); } $post_data['post_name'] = $this->get_post_meta_key( $slug ); // Guest authors can't be created with the same user_login as a user $user_nicename = str_replace( 'cap-', '', $slug ); $user = get_user_by( 'slug', $user_nicename ); if ( $user && is_user_member_of_blog( $user->ID, get_current_blog_id() ) && $user->user_login != get_post_meta( $original_args['ID'], $this->get_post_meta_key( 'linked_account' ), true ) ) { wp_die( esc_html__( 'Guest authors cannot be created with the same user_login value as a user. Try creating a profile from the user on the Manage Users listing instead.', 'co-authors-plus' ) ); } // Guest authors can't have the same post_name value $guest_author = $this->get_guest_author_by( 'post_name', $post_data['post_name'] ); if ( $guest_author && $guest_author->ID != $original_args['ID'] ) { wp_die( esc_html__( 'Display name conflicts with another guest author display name.', 'co-authors-plus' ) ); } return $post_data; } /** * Save the various meta fields associated with our guest author model * * @since 3.0 */ function manage_guest_author_save_meta_fields( $post_id, $post ) { global $coauthors_plus; if ( $post->post_type != $this->post_type ) { return; } // @todo caps check if ( ! isset( $_POST['guest-author-nonce'] ) || ! wp_verify_nonce( $_POST['guest-author-nonce'], 'guest-author-nonce' ) ) { return; } // Save our data to post meta $author_fields = $this->get_guest_author_fields(); foreach ( $author_fields as $author_field ) { $key = $this->get_post_meta_key( $author_field['key'] ); // 'user_login' should only be saved on post update if it doesn't exist if ( 'user_login' == $author_field['key'] && ! get_post_meta( $post_id, $key, true ) ) { $display_name_key = $this->get_post_meta_key( 'display_name' ); $temp_slug = sanitize_title( $_POST[ $display_name_key ] ); update_post_meta( $post_id, $key, $temp_slug ); continue; } if ( 'linked_account' == $author_field['key'] ) { $linked_account_key = $this->get_post_meta_key( 'linked_account' ); if ( ! empty( $_POST[ $linked_account_key ] ) ) { $user_id = intval( $_POST[ $linked_account_key ] ); } else { continue; } $user = get_user_by( 'id', $user_id ); if ( $user_id > 0 && is_object( $user ) ) { $user_login = $user->user_login; } else { $user_login = ''; } update_post_meta( $post_id, $key, $user_login ); continue; } if ( isset( $author_field['input'] ) && 'checkbox' === $author_field['input'] && ! isset( $_POST[ $key ] ) ) { delete_post_meta( $post_id, $key ); } if ( ! isset( $_POST[ $key ] ) ) { continue; } if ( isset( $author_field['sanitize_function'] ) && is_callable( $author_field['sanitize_function'] ) ) { $value = call_user_func( $author_field['sanitize_function'], $_POST[ $key ] ); } else { $value = sanitize_text_field( $_POST[ $key ] ); } update_post_meta( $post_id, $key, $value ); } $author = $this->get_guest_author_by( 'ID', $post_id ); $author_term = $coauthors_plus->update_author_term( $author ); // Add the author as a post term wp_set_post_terms( $post_id, array( $author_term->slug ), $coauthors_plus->coauthor_taxonomy, false ); // Explicitly clear all caches, to remove negative caches that may have existed prior to this // Guest Author's creation / update $this->delete_guest_author_cache( $post_id ); } /** * Return a simulated WP_User object based on the post ID * of a guest author * * @since 3.0 * * @param string $key Key to search by (login,email) * @param string $value Value to search for * @param object|false $coauthor The guest author on success, false on failure */ function get_guest_author_by( $key, $value, $force = false ) { global $wpdb; $cache_key = $this->get_cache_key( $key, $value ); if ( false == $force && false !== ( $retval = wp_cache_get( $cache_key, self::$cache_group ) ) ) { // Properly catch our false condition cache if ( is_object( $retval ) ) { return $retval; } else { return false; } } switch ( $key ) { case 'ID': case 'id': $query = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID=%d AND post_type = %s", $value, $this->post_type ); $post_id = $wpdb->get_var( $query ); if ( empty( $post_id ) ) { $post_id = '0'; } break; case 'user_nicename': case 'post_name': $value = $this->get_post_meta_key( $value ); $query = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_name=%s AND post_type = %s", $value, $this->post_type ); $post_id = $wpdb->get_var( $query ); if ( empty( $post_id ) ) { $post_id = '0'; } break; case 'login': case 'user_login': case 'linked_account': case 'user_email': if ( 'login' == $key ) { $key = 'user_login'; } // Ensure we aren't doing the lookup by the prefixed value if ( 'user_login' == $key ) { $value = preg_replace( '#^cap\-#', '', $value ); } $query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key=%s AND meta_value=%s;", $this->get_post_meta_key( $key ), $value ); $post_id = $wpdb->get_var( $query ); if ( empty( $post_id ) ) { if ( 'user_login' == $key ) { return $this->get_guest_author_by( 'post_name', $value ); // fallback to post_name in case the guest author isn't a linked account } $post_id = '0'; } break; default: $post_id = '0'; break; } if ( ! $post_id ) { // Best hacky way to cache the false condition wp_cache_set( $cache_key, '0', self::$cache_group ); return false; } $guest_author = array( 'ID' => $post_id, ); // Load the guest author fields $fields = $this->get_guest_author_fields(); foreach ( $fields as $field ) { $key = $field['key']; $pm_key = $this->get_post_meta_key( $field['key'] ); $guest_author[ $key ] = get_post_meta( $post_id, $pm_key, true ); } // Support for non-Latin characters. They're stored as urlencoded slugs $guest_author['user_login'] = urldecode( $guest_author['user_login'] ); // Hack to model the WP_User object $guest_author['user_nicename'] = sanitize_title( $guest_author['user_login'] ); $guest_author['type'] = 'guest-author'; wp_cache_set( $cache_key, (object) $guest_author, self::$cache_group ); return (object) $guest_author; } /** * Get an thumbnail for a Guest Author object * * @param object The Guest Author object for which to retrieve the thumbnail. * @param int The desired image size. * @param array|string Optional. An array or string of additional classes. Default null. * @return string The thumbnail image tag, or null if one doesn't exist. */ function get_guest_author_thumbnail( $guest_author, $size, $class = null ) { // See if the guest author has an avatar if ( ! has_post_thumbnail( $guest_author->ID ) ) { return null; } $args = array( 'class' => "avatar avatar-{$size} photo", ); if ( ! empty( $class ) ) { if ( is_array( $class ) ) { $class = implode( ' ', $class ); } $args['class'] .= " $class"; } $size = array( $size, $size ); $thumbnail = get_the_post_thumbnail( $guest_author->ID, $size, $args ); return $thumbnail; } /** * Get all of the meta fields that can be associated with a guest author * * @since 3.0 */ function get_guest_author_fields( $groups = 'all' ) { $groups = (array) $groups; $global_fields = array( // Hidden (included in object, no UI elements) array( 'key' => 'ID', 'label' => __( 'ID', 'co-authors-plus' ), 'group' => 'hidden', 'input' => 'hidden', ), // Name array( 'key' => 'display_name', 'label' => __( 'Display Name', 'co-authors-plus' ), 'group' => 'name', 'required' => true, ), array( 'key' => 'first_name', 'label' => __( 'First Name', 'co-authors-plus' ), 'group' => 'name', ), array( 'key' => 'last_name', 'label' => __( 'Last Name', 'co-authors-plus' ), 'group' => 'name', ), array( 'key' => 'user_login', 'label' => __( 'Slug', 'co-authors-plus' ), 'group' => 'slug', 'required' => true, ), // Contact info array( 'key' => 'user_email', 'label' => __( 'E-mail', 'co-authors-plus' ), 'group' => 'contact-info', 'input' => 'email', ), array( 'key' => 'linked_account', 'label' => __( 'Linked Account', 'co-authors-plus' ), 'group' => 'slug', ), array( 'key' => 'website', 'label' => __( 'Website', 'co-authors-plus' ), 'group' => 'contact-info', 'input' => 'url', ), array( 'key' => 'aim', 'label' => __( 'AIM', 'co-authors-plus' ), 'group' => 'contact-info', ), array( 'key' => 'yahooim', 'label' => __( 'Yahoo IM', 'co-authors-plus' ), 'group' => 'contact-info', ), array( 'key' => 'jabber', 'label' => __( 'Jabber / Google Talk', 'co-authors-plus' ), 'group' => 'contact-info', ), array( 'key' => 'description', 'label' => __( 'Biographical Info', 'co-authors-plus' ), 'group' => 'about', 'sanitize_function' => 'wp_filter_post_kses', ), ); $fields_to_return = array(); foreach ( $global_fields as $single_field ) { if ( in_array( $single_field['group'], $groups ) || 'all' === $groups[0] && 'hidden' !== $single_field['group'] ) { $fields_to_return[] = $single_field; } } return apply_filters( 'coauthors_guest_author_fields', $fields_to_return, $groups ); } /** * Gets a postmeta key by prefixing it with 'cap-' * if not yet prefixed * * @since 3.0 */ function get_post_meta_key( $key ) { if ( 0 !== stripos( $key, 'cap-' ) ) { $key = 'cap-' . $key; } return $key; } /** * Build a cache key for a given key/value * * @param string $key A guest author field * @param string $value The guest author field value * * @return string The generated cache key */ function get_cache_key( $key, $value ) { // Normalize $key and $value switch ( $key ) { case 'post_name': $key = 'user_nicename'; if ( 0 === strpos( $value, 'cap-' ) ) { $value = substr( $value, 4 ); } break; case 'login': $key = 'user_login'; break; } $cache_key = md5( 'guest-author-' . $key . '-' . $value ); return $cache_key; } /** * Get all of the user accounts that have been linked * * @since 3.0 */ function get_all_linked_accounts( $force = false ) { global $wpdb; $cache_key = 'all-linked-accounts'; $retval = wp_cache_get( $cache_key, self::$cache_group ); if ( true === $force || false === $retval ) { $user_logins = $wpdb->get_col( $wpdb->prepare( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key=%s AND meta_value !=''", $this->get_post_meta_key( 'linked_account' ) ) ); $users = array(); foreach ( $user_logins as $user_login ) { $user = get_user_by( 'login', $user_login ); if ( ! $user ) { continue; } $users[] = array( 'ID' => $user->ID, 'user_login' => $user->user_login, ); } $retval = $users; wp_cache_set( $cache_key, $retval, self::$cache_group ); } return ( $retval ) ? $retval : array(); } /** * Filter update post metadata * Clean caches when any of the values have been changed * * @since 3.0 */ function filter_update_post_metadata( $retnull, $object_id, $meta_key, $meta_value, $prev_value ) { if ( $this->post_type != get_post_type( $object_id ) ) { return $retnull; } // If the linked_account is changing, invalidate the cache of all linked accounts // Don't regenerate though, as we haven't saved the new value $linked_account_key = $this->get_post_meta_key( 'linked_account' ); if ( $linked_account_key == $meta_key && get_post_meta( $object_id, $linked_account_key, true ) !== $meta_value ) { $this->delete_guest_author_cache( $object_id ); } // If one of the guest author meta values has changed, we'll need to invalidate all keys if ( false !== strpos( $meta_key, 'cap-' ) && get_post_meta( $object_id, $meta_key, true ) !== $meta_value ) { $this->delete_guest_author_cache( $object_id ); } return null; } /** * Delete all of the cache values associated with a guest author * * @since 3.0 * * @param int|object $guest_author The guest author ID or object */ public function delete_guest_author_cache( $id_or_object ) { if ( is_object( $id_or_object ) ) { $guest_author = $id_or_object; } else { $guest_author = $this->get_guest_author_by( 'ID', $id_or_object, true ); } // Delete the lookup cache associated with each old co-author value $keys = wp_list_pluck( $this->get_guest_author_fields(), 'key' ); $keys = array_merge( $keys, array( 'login', 'post_name', 'user_nicename', 'ID' ) ); foreach ( $keys as $key ) { $value_key = $key; if ( 'post_name' == $key ) { $value_key = 'user_nicename'; } else if ( 'login' == $key ) { $value_key = 'user_login'; } $cache_key = $this->get_cache_key( $key, $guest_author->$value_key ); wp_cache_delete( $cache_key, self::$cache_group ); } // Delete the 'all-linked-accounts' cache wp_cache_delete( 'all-linked-accounts', self::$cache_group ); } /** * Create a guest author * * @since 3.0 */ function create( $args ) { global $coauthors_plus; // Validate the arguments that have been passed $fields = $this->get_guest_author_fields(); foreach ( $fields as $field ) { // Make sure required fields are there if ( isset( $field['required'] ) && $field['required'] && empty( $args[ $field['key'] ] ) ) { return new WP_Error( 'field-required', sprintf( __( '%s is a required field', 'co-authors-plus' ), $field['key'] ) ); } // The user login field shouldn't collide with any existing users if ( 'user_login' == $field['key'] && $existing_coauthor = $coauthors_plus->get_coauthor_by( 'user_login', $args['user_login'], true ) ) { if ( 'guest-author' == $existing_coauthor->type ) { return new WP_Error( 'duplicate-field', __( 'user_login cannot duplicate existing guest author or mapped user', 'co-authors-plus' ) ); } } } // Create the primary post object $new_post = array( 'post_title' => $args['display_name'], 'post_name' => sanitize_title( $this->get_post_meta_key( $args['user_login'] ) ), 'post_type' => $this->post_type, ); $post_id = wp_insert_post( $new_post, true ); if ( is_wp_error( $post_id ) ) { return $post_id; } // Add all of the fields for the new guest author foreach ( $fields as $field ) { $key = $field['key']; if ( empty( $args[ $key ] ) ) { continue; } $pm_key = $this->get_post_meta_key( $key ); update_post_meta( $post_id, $pm_key, $args[ $key ] ); } // Attach the avatar / featured image. if ( ! empty( $args[ 'avatar' ] ) ) { set_post_thumbnail( $post_id, $args[ 'avatar' ] ); } // Make sure the author term exists and that we're assigning it to this post type $author_term = $coauthors_plus->update_author_term( $this->get_guest_author_by( 'ID', $post_id ) ); wp_set_post_terms( $post_id, array( $author_term->slug ), $coauthors_plus->coauthor_taxonomy, false ); // Explicitly clear all caches, to remove negative caches that may have existed prior to this // Guest Author's creation $this->delete_guest_author_cache( $post_id ); return $post_id; } /** * Delete a guest author * * @since 3.0 * * @param int $post_id The ID for the guest author profile * @param string $reassign_to User login value for the co-author to reassign posts to * @return bool|WP_Error $success True on success, WP_Error on a failure */ public function delete( $id, $reassign_to = false ) { global $coauthors_plus; $guest_author = $this->get_guest_author_by( 'ID', $id ); if ( ! $guest_author ) { return new WP_Error( 'guest-author-missing', __( 'Guest author does not exist', 'co-authors-plus' ) ); } $guest_author_term = $coauthors_plus->get_author_term( $guest_author ); if ( $reassign_to ) { // We're reassigning the guest author's posts user to its linked account if ( $guest_author->linked_account == $reassign_to ) { $reassign_to_author = get_user_by( 'login', $reassign_to ); } else { $reassign_to_author = $coauthors_plus->get_coauthor_by( 'user_login', $reassign_to ); } if ( ! $reassign_to_author ) { return new WP_Error( 'reassign-to-missing', __( 'Reassignment co-author does not exist', 'co-authors-plus' ) ); } $reassign_to_term = $coauthors_plus->get_author_term( $reassign_to_author ); // In the case where the guest author and its linked account shared the same term, we don't want to reassign if ( $guest_author_term->term_id != $reassign_to_term->term_id ) { wp_delete_term( $guest_author_term->term_id, $coauthors_plus->coauthor_taxonomy, array( 'default' => $reassign_to_term->term_id, 'force_default' => true ) ); } } else { wp_delete_term( $guest_author_term->term_id, $coauthors_plus->coauthor_taxonomy ); } // Delete the guest author profile wp_delete_post( $guest_author->ID, true ); // Make sure all of the caches are reset $this->delete_guest_author_cache( $guest_author ); return true; } /** * Create a guest author from an existing WordPress user * * @since 3.0 * * @param int $user_id ID for a WordPress user * @return int|WP_Error $retval ID for the new guest author on success, WP_Error on failure */ function create_guest_author_from_user_id( $user_id ) { $user = get_user_by( 'id', $user_id ); if ( ! $user ) { return new WP_Error( 'invalid-user', __( 'No user exists with that ID', 'co-authors-plus' ) ); } $guest_author = array(); foreach ( $this->get_guest_author_fields() as $field ) { $key = $field['key']; if ( ! empty( $user->$key ) ) { $guest_author[ $key ] = $user->$key; } else { $guest_author[ $key ] = ''; } } // Don't need the old user ID unset( $guest_author['ID'] ); // Retain the user mapping and try to produce an unique user_login based on the name. $guest_author['linked_account'] = $guest_author['user_login']; if ( ! empty( $guest_author['display_name'] ) && $guest_author['display_name'] != $guest_author['user_login'] ) { $guest_author['user_login'] = sanitize_title( $guest_author['display_name'] ); } else if ( ! empty( $guest_author['first_name'] ) && ! empty( $guest_author['last_name'] ) ) { $guest_author['user_login'] = sanitize_title( $guest_author['first_name'] . ' ' . $guest_author['last_name'] ); } $retval = $this->create( $guest_author ); return $retval; } /** * Guest authors must have Display Names * * @since 3.0 */ function filter_wp_insert_post_empty_content( $maybe_empty, $postarr ) { if ( $this->post_type != $postarr['post_type'] ) { return $maybe_empty; } if ( empty( $postarr['post_title'] ) ) { return true; } return $maybe_empty; } /** * On the User Management view, add action links to create or edit * guest author profiles * * @since 3.0 * * @param array $actions The existing actions to perform on a user * @param object $user_object A WP_User object * @return array $actions Modified actions */ function filter_user_row_actions( $actions, $user_object ) { if ( ! current_user_can( $this->list_guest_authors_cap ) || is_network_admin() ) { return $actions; } $new_actions = array(); if ( $guest_author = $this->get_guest_author_by( 'linked_account', $user_object->user_login ) ) { $edit_guest_author_link = get_edit_post_link( $guest_author->ID ); $new_actions['edit-guest-author'] = '' . __( 'Edit Profile', 'co-authors-plus' ) . ''; } else { $query_args = array( 'action' => 'cap-create-guest-author', 'user_id' => $user_object->ID, 'nonce' => wp_create_nonce( 'create-guest-author' ), ); $create_guest_author_link = add_query_arg( array_map( 'rawurlencode', $query_args ), admin_url( $this->parent_page ) ); if ( apply_filters( 'coauthors_show_create_profile_user_link', false ) ) { $new_actions['create-guest-author'] = '' . __( 'Create Profile', 'co-authors-plus' ) . ''; } } return $new_actions + $actions; } /** * Filter 'get_avatar' to replace with our own avatar if one exists * * @since 3.0 */ function filter_get_avatar( $avatar, $id_or_email, $size, $default ) { if ( is_object( $id_or_email ) || ! is_email( $id_or_email ) ) { return $avatar; } // See if this matches a guest author $guest_author = $this->get_guest_author_by( 'user_email', $id_or_email ); if ( ! $guest_author ) { return $avatar; } $thumbnail = $this->get_guest_author_thumbnail( $guest_author, $size ); if ( $thumbnail ) { return $thumbnail; } return $avatar; } /** * Filter the URL used in functions like the_author_posts_link() * * @since 3.0 */ function filter_author_link( $link, $author_id, $author_nicename ) { // If we're using this at the top of the loop on author.php, // our queried object should be set correctly if ( ! $author_nicename && is_author() && get_queried_object() ) { $author_nicename = get_queried_object()->user_nicename; } if ( empty( $link ) ) { $link = add_query_arg( 'author_name', rawurlencode( $author_nicename ), home_url() ); } else { global $wp_rewrite; $link = $wp_rewrite->get_author_permastruct(); if ( $link ) { $link = str_replace( '%author%', $author_nicename, $link ); $link = home_url( user_trailingslashit( $link ) ); } else { $link = add_query_arg( 'author_name', rawurlencode( $author_nicename ), home_url() ); } } return $link; } /** * Filter Author Feed Link for non native authors * * @since 3.1 * * @param string $feed_link Required. Original feed link for the author. * @param string $feed Required. Type of feed being generated. * @return string Feed link for the author updated, if needs to be */ public function filter_author_feed_link( $feed_link, $feed ) { if ( ! is_author() ) { return $feed_link; } // Get author, then check if author is guest-author because // that's the only type that will need to be adjusted $author = get_queried_object(); if ( empty( $author ) || 'guest-author' != $author->type ) { return $feed_link; } // The next section is similar to // get_author_feed_link() in wp-includes/link-template.php $permalink_structure = get_option( 'permalink_structure' ); if ( empty( $feed ) ) { $feed = get_default_feed(); } if ( '' == $permalink_structure ) { $link = home_url( "?feed=$feed&author=" . $author->ID ); } else { $link = get_author_posts_url( $author->ID ); $feed_link = ( get_default_feed() === $feed ) ? 'feed' : "feed/$feed"; $link = trailingslashit( $link ) . user_trailingslashit( $feed_link, 'feed' ); } return $link; } /** * Filter Personal Data Exporters to add Guest Author exporter * * @since 3.3.1 */ public function filter_personal_data_exporter( $exporters ) { $exporters['cap-guest-author'] = array( 'exporter_friendly_name' => __( 'Guest Author', 'co-authors-plus' ), 'callback' => array( $this, 'personal_data_exporter' ), ); return $exporters; } /** * Finds and exports personal data associated with an email address for guest authors * * @since 3.3.1 * * @param string $email_address The guest author email address. * @return array An array of personal data. */ public function personal_data_exporter( $email_address ) { $email_address = trim( $email_address ); $data_to_export = array(); $author = $this->get_guest_author_by( 'user_email', $email_address ); if ( ! $author ) { return array( 'data' => array(), 'done' => true, ); } $author_data = array( 'ID' => __( 'ID', 'co-authors-plus' ), 'user_login' => __( 'Login Name', 'co-authors-plus' ), 'display_name' => __( 'Display Name', 'co-authors-plus' ), 'user_email' => __( 'Email', 'co-authors-plus' ), 'first_name' => __( 'First Name', 'co-authors-plus' ), 'last_name' => __( 'Last Name', 'co-authors-plus' ), 'website' => __( 'Website', 'co-authors-plus' ), 'aim' => __( 'AIM', 'co-authors-plus' ), 'yahooim' => __( 'Yahoo IM', 'co-authors-plus' ), 'jabber' => __( 'Jabber / Google Talk', 'co-authors-plus' ), 'description' => __( 'Biographical Info', 'co-authors-plus' ), ); $author_data_to_export = array(); foreach ( $author_data as $key => $name ) { if ( empty( $author->$key ) ) { continue; } $author_data_to_export[] = array( 'name' => $name, 'value' => $author->$key, ); } /** * Filters extra data to allow plugins add data related to guest author * * @since 3.3.1 * * @param array $extra_data A empty array to be populated with extra data * @param int $author->ID The guest author ID * @param string $email_address The guest author email address */ $extra_data = apply_filters( 'coauthors_guest_author_personal_export_extra_data', [], $author->ID, $email_address ); if ( is_array( $extra_data ) && ! empty( $extra_data ) ) { $author_data_to_export = array_merge( $author_data_to_export, $extra_data ); } $data_to_export[] = array( 'group_id' => 'cap-guest-author', 'group_label' => __( 'Guest Author', 'co-authors-plus' ), 'item_id' => "cap-guest-author-{$author->ID}", 'data' => $author_data_to_export, ); return array( 'data' => $data_to_export, 'done' => true, ); } }