diff --git a/.travis.yml b/.travis.yml index da7b0e08..28765d3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,70 @@ language: php php: - - 5.3 - - 5.5 + - 7.1 env: - WP_VERSION=latest - - WP_VERSION=4.1 - - WP_VERSION=4.0 + +matrix: +matrix: + include: + - php: "5.2" + env: WP_VERSION=latest + - php: "5.2" + env: WP_VERSION=4.6 + - php: "5.6" + env: + - WP_VERSION=latest + - SNIFF=1 + - php: "5.6" + env: WP_VERSION=4.6 + - php: "7.0" + env: WP_VERSION=latest + - php: "7.0" + env: WP_VERSION=4.6 + # 7.1 / latest already included above as first build. + - php: "7.1" + env: WP_VERSION=4.6 before_script: - - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION + - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION + # PHPCS + - export PHPCS_DIR=/tmp/phpcs + - export SNIFFS_DIR=/tmp/sniffs + # Install CodeSniffer for WordPress Coding Standards checks. + - if [[ "$SNIFF" == "1" ]]; then git clone -b master --depth 1 https://github.com/squizlabs/PHP_CodeSniffer.git $PHPCS_DIR; fi + # Install WordPress Coding Standards. + - if [[ "$SNIFF" == "1" ]]; then git clone -b master --depth 1 https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git $SNIFFS_DIR; fi + # Install PHP Compatibility sniffs. + - if [[ "$SNIFF" == "1" ]]; then git clone -b master --depth 1 https://github.com/wimg/PHPCompatibility.git $SNIFFS_DIR/PHPCompatibility; fi + # Set install path for PHPCS sniffs. + # @link https://github.com/squizlabs/PHP_CodeSniffer/blob/4237c2fc98cc838730b76ee9cee316f99286a2a7/CodeSniffer.php#L1941 + - if [[ "$SNIFF" == "1" ]]; then $PHPCS_DIR/scripts/phpcs --config-set installed_paths $SNIFFS_DIR; fi + # After CodeSniffer install you should refresh your path. + - if [[ "$SNIFF" == "1" ]]; then phpenv rehash; fi + # Properly handle PHPunit versions + - export PATH="$HOME/.composer/vendor/bin:$PATH" + - | + if [[ ${TRAVIS_PHP_VERSION:0:2} == "7." ]]; then + composer global require "phpunit/phpunit=5.7.*" + elif [[ ${TRAVIS_PHP_VERSION:0:3} != "5.2" ]]; then + composer global require "phpunit/phpunit=4.8.*" + fi + - phpunit --version script: - - make lint - - make phpunit + # Search for PHP syntax errors. + - find -L . -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l + # WordPress Coding Standards. + # @link https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards + # @link http://pear.php.net/package/PHP_CodeSniffer/ + # -p flag: Show progress of the run. + # -s flag: Show sniff codes in all reports. + # -v flag: Print verbose output. + # -n flag: Do not print warnings. (shortcut for --warning-severity=0) + # --standard: Use WordPress as the standard. + # --extensions: Only sniff PHP files. + - if [[ "$SNIFF" == "1" ]]; then $PHPCS_DIR/scripts/phpcs -p -s -v -n . --standard="WordPress-VIP" --extensions=php; fi + # Unit tests + - phpunit diff --git a/co-authors-plus.php b/co-authors-plus.php index 339184a9..bceed1ab 100644 --- a/co-authors-plus.php +++ b/co-authors-plus.php @@ -3,7 +3,7 @@ Plugin Name: Co-Authors Plus Plugin URI: http://wordpress.org/extend/plugins/co-authors-plus/ Description: Allows multiple authors to be assigned to a post. This plugin is an extended version of the Co-Authors plugin developed by Weston Ruter. -Version: 3.1.2 +Version: 3.2.2 Author: Mohammad Jangda, Daniel Bachhuber, Automattic Copyright: 2008-2015 Shared and distributed between Mohammad Jangda, Daniel Bachhuber, Weston Ruter @@ -24,7 +24,7 @@ */ -define( 'COAUTHORS_PLUS_VERSION', '3.1.2' ); +define( 'COAUTHORS_PLUS_VERSION', '3.2.2' ); require_once( dirname( __FILE__ ) . '/template-tags.php' ); require_once( dirname( __FILE__ ) . '/deprecated.php' ); @@ -86,6 +86,9 @@ function __construct() { // Action to set up author auto-suggest add_action( 'wp_ajax_coauthors_ajax_suggest', array( $this, 'ajax_suggest' ) ); + // Action to create user + add_action( 'wp_ajax_coauthors_ajax_create_user', array( $this, 'ajax_create_user' ) ); + // Filter to allow coauthors to edit posts add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 3 ); @@ -116,6 +119,10 @@ function __construct() { // Support infinite scroll for Guest Authors on author pages add_filter( 'infinite_scroll_js_settings', array( $this, 'filter_infinite_scroll_js_settings' ), 10, 2 ); + // Delete CoAuthor Cache on Post Save & Post Delete + add_action( 'save_post', array( $this, 'clear_cache') ); + add_action( 'delete_post', array( $this, 'clear_cache') ); + add_action( 'set_object_terms', array( $this, 'clear_cache_on_terms_set' ), 10, 6 ); } /** @@ -375,14 +382,14 @@ public function coauthors_meta_box( $post ) { ?>
-

Note: To edit post authors, please enable javascript or use a javascript-capable browser', 'co-authors-plus' ), array( 'strong' => array() ) ); ?>

+

Note: To edit post authors, please enable javascript or use a javascript-capable browser', 'co-authors-plus' ), array( 'strong' => array() ) ); ?>

-

Remove to remove them.', 'co-authors-plus' ), array( 'strong' => array() ) ); ?>

+

Remove to remove them.', 'co-authors-plus' ), array( 'strong' => array() ) ); ?>

@@ -506,7 +513,7 @@ function _action_quick_edit_custom_box( $column_name, $post_type ) { @@ -611,12 +618,18 @@ function posts_join_filter( $join, $query ) { } // Check to see that JOIN hasn't already been added. Props michaelingp and nbaxley - $term_relationship_join = " INNER JOIN {$wpdb->term_relationships} ON ({$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id)"; - $term_taxonomy_join = " INNER JOIN {$wpdb->term_taxonomy} ON ( {$wpdb->term_relationships}.term_taxonomy_id = {$wpdb->term_taxonomy}.term_taxonomy_id )"; + $term_relationship_inner_join = " INNER JOIN {$wpdb->term_relationships} ON ({$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id)"; + $term_relationship_left_join = " LEFT JOIN {$wpdb->term_relationships} ON ({$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id)"; + + $term_taxonomy_join = " INNER JOIN {$wpdb->term_relationships} AS tr1 ON ({$wpdb->posts}.ID = tr1.object_id)"; + $term_taxonomy_join .= " INNER JOIN {$wpdb->term_taxonomy} ON ( tr1.term_taxonomy_id = {$wpdb->term_taxonomy}.term_taxonomy_id )"; - if ( false === strpos( $join, trim( $term_relationship_join ) ) ) { - $join .= str_replace( 'INNER JOIN', 'LEFT JOIN', $term_relationship_join ); + // 4.6+ uses a LEFT JOIN for tax queries so we need to check for both + if ( false === strpos( $join, trim( $term_relationship_inner_join ) ) + && false === strpos( $join, trim( $term_relationship_left_join ) ) ) { + $join .= $term_relationship_left_join; } + if ( false === strpos( $join, trim( $term_taxonomy_join ) ) ) { $join .= str_replace( 'INNER JOIN', 'LEFT JOIN', $term_taxonomy_join ); } @@ -1065,6 +1078,14 @@ public function ajax_suggest() { $authors = $this->search_authors( $search, $ignore ); + if ( empty( $authors ) ) { + $author_display_name = $search; + $author_login = str_replace( " ", "-", $search ); + echo __( 'New', 'co-authors-plus' ) . ' | ' . $author_login . ' | ' . $author_display_name . "\n"; + + die(); + } + foreach ( $authors as $author ) { echo esc_html( $author->ID . ' | ' . $author->user_login . ' | ' . $author->display_name . ' | ' . $author->user_email . ' | ' . $author->user_nicename ) . "\n"; } @@ -1073,6 +1094,40 @@ public function ajax_suggest() { } + /** + * Create new guest author if not exists. + */ + public function ajax_create_user() { + global $coauthors_plus; + + if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'coauthors-create' ) ) { + die(); + } + + // Get author by user_login. + $guest_author = $coauthors_plus->guest_authors->get_guest_author_by( 'user_email', $_POST['email'], true ); + + if ( ! $guest_author ){ + // Get author by user_login. + $guest_author = $coauthors_plus->guest_authors->get_guest_author_by( 'user_login', $_POST['login'], true ); + } + + // If user isn't exists. + if ( ! $guest_author ) { + + $guest_author_id = $coauthors_plus->guest_authors->create( array( + 'display_name' => sanitize_text_field( $_POST['name'] ), + 'user_login' => sanitize_text_field( $_POST['login'] ), + 'user_email' => sanitize_email( $_POST['email'] ) , + ) ); + + if ( $guest_author_id ) { + echo wp_json_encode( array( 'success' => true ) ); + } + } + die(); + } + /** * Get matching authors based on a search value */ @@ -1253,6 +1308,21 @@ public function js_vars() { wp_nonce_url( 'admin-ajax.php', 'coauthors-search' ) ) ); ?>; + // AJAX link used for the create new guest user. + var coAuthorsPlus_ajax_create_new_user_link = 'coauthors_ajax_create_user', + 'post_type' => rawurlencode( get_post_type() ), + ), + wp_nonce_url( 'admin-ajax.php', 'coauthors-create' ) + ) + ); ?>; + var coAuthorsPlus_vars = { + 'email_prompt': "", + 'email_invalid': "" + }; coauthor_taxonomy, array( + 'orderby' => 'term_order', + 'order' => 'ASC', + ) ); + + // This usually happens if the taxonomy doesn't exist, which should never happen, but you never know. + if ( is_wp_error( $coauthor_terms ) ) { + return array(); + } + + wp_cache_set( $cache_key, $coauthor_terms, 'co-authors-plus' ); + } + + return $coauthor_terms; + + } + + /** + * Callback to clear the cache on post save and post delete. + * + * @param $post_id The Post ID. + */ + public function clear_cache( $post_id ) { + wp_cache_delete( 'coauthors_post_' . $post_id, 'co-authors-plus' ); + } + + /** + * Callback to clear the cache when an object's terms are changed. + * + * @param $post_id The Post ID. + */ + public function clear_cache_on_terms_set( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) { + + // We only care about the coauthors taxonomy + if ( $this->coauthor_taxonomy !== $taxonomy ) { + return; + } + + wp_cache_delete( 'coauthors_post_' . $object_id, 'co-authors-plus' ); + + } + } global $coauthors_plus; @@ -1595,6 +1727,7 @@ function cap_filter_comment_moderation_email_recipients( $recipients, $comment_i if ( isset( $post_id ) ) { $coauthors = get_coauthors( $post_id ); + $extra_recipients = array(); foreach ( $coauthors as $user ) { if ( ! empty( $user->user_email ) ) { $extra_recipients[] = $user->user_email; @@ -1605,3 +1738,17 @@ function cap_filter_comment_moderation_email_recipients( $recipients, $comment_i } return $recipients; } + +/** + * Retrieve a list of coauthor terms for a single post. + * + * Grabs a correctly ordered list of authors for a single post, appropriately + * cached because it requires `wp_get_object_terms()` to succeed. + * + * @param int $post_id ID of the post for which to retrieve authors. + * @return array Array of coauthor WP_Term objects + */ +function cap_get_coauthor_terms_for_post( $post_id ) { + global $coauthors_plus; + return $coauthors_plus->get_coauthor_terms_for_post( $post_id ); +} diff --git a/js/co-authors-plus.js b/js/co-authors-plus.js index 7691f91b..e3d27af5 100755 --- a/js/co-authors-plus.js +++ b/js/co-authors-plus.js @@ -107,6 +107,28 @@ jQuery( document ).ready(function () { return true; } + /* + * Create new guest author if not exists + * @param string Author Data + * @param object The autosuggest input box + */ + function coauthors_new_author_display( authordata, co ){ + // Reset placeholder. + co.attr( 'value', coAuthorsPlusStrings.search_box_text ) + .focus( function(){ co.val( '' ) } ) + .blur( function(){ co.val( coAuthorsPlusStrings.search_box_text ) } ); + + // Ajax Request to create a new guest author. + jQuery.post(coAuthorsPlus_ajax_create_new_user_link, authordata ,function(res){ + // If guest user created successfully. + if( JSON.parse(res).success == true ){ + coauthors_add_coauthor( authordata, co ); + } + + }); + + return true; + } /* * Add the autosuggest box and text tag to the Co-Authors table @@ -202,7 +224,25 @@ jQuery( document ).ready(function () { author.nicename = jQuery.trim( vals[4] ); if ( author.id=='New' ) { - coauthors_new_author_display( name ); + + // Allow user to enter email address of user. + author.email = prompt( coAuthorsPlus_vars.email_prompt ); + + var email_filter = /^[\w\-\.\+]+\@[a-zA-Z0-9\.\-]+\.[a-zA-z0-9]{2,4}$/; + + if ( ! author.email || ! email_filter.test( author.email ) ) { + + alert( coAuthorsPlus_vars.email_invalid ); + $this.attr( 'value', coAuthorsPlusStrings.search_box_text ) + .focus( function(){ $this.val( '' ) } ) + .blur( function(){ $this.val( coAuthorsPlusStrings.search_box_text ) } ) + ; + return false; + } + + // Create new guest author if not exists. + coauthors_new_author_display( author, $this ); + } else { coauthors_add_coauthor( author, $this ); // Show the delete button if we now have more than one co-author diff --git a/php/class-coauthors-guest-authors.php b/php/class-coauthors-guest-authors.php index 90537899..5c322ed0 100644 --- a/php/class-coauthors-guest-authors.php +++ b/php/class-coauthors-guest-authors.php @@ -675,11 +675,21 @@ function metabox_manage_guest_author_bio() { 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 ''; - echo ''; + printf( ' + + + + + + + + + ', + esc_attr( $pm_key ), + esc_html( $field['label'] ), + esc_attr( $pm_key ), + esc_textarea( $value ) + ); } echo ''; diff --git a/php/class-coauthors-wp-list-table.php b/php/class-coauthors-wp-list-table.php index dacbf203..552f6db0 100644 --- a/php/class-coauthors-wp-list-table.php +++ b/php/class-coauthors-wp-list-table.php @@ -64,6 +64,8 @@ function prepare_items() { 'order' => 'ASC', ); + $args = apply_filters( 'coauthors_guest_author_query_args', $args ); + if ( isset( $_REQUEST['orderby'] ) ) { switch ( $_REQUEST['orderby'] ) { case 'display_name': diff --git a/php/class-wp-cli.php b/php/class-wp-cli.php index 8dcf1ee2..568f988e 100644 --- a/php/class-wp-cli.php +++ b/php/class-wp-cli.php @@ -76,9 +76,9 @@ public function create_terms_for_posts() { $count++; - $terms = wp_get_post_terms( $single_post->ID, $coauthors_plus->coauthor_taxonomy ); - if ( is_wp_error( $terms ) ) { - WP_CLI::error( $terms->get_error_message() ); + $terms = cap_get_coauthor_terms_for_post( $single_post->ID ); + if ( empty( $terms ) ) { + WP_CLI::error( sprintf( 'No co-authors found for post #%d.', $single_post->ID ) ); } if ( ! empty( $terms ) ) { @@ -235,8 +235,13 @@ public function assign_user_to_coauthor( $args, $assoc_args ) { $posts = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_author=%d AND post_type IN ('$post_types')", $user->ID ) ); $affected = 0; foreach ( $posts as $post_id ) { - if ( $coauthors = wp_get_post_terms( $post_id, $coauthors_plus->coauthor_taxonomy ) ) { - WP_CLI::line( sprintf( __( 'Skipping - Post #%d already has co-authors assigned: %s', 'co-authors-plus' ), $post_id, implode( ', ', wp_list_pluck( $coauthors, 'slug' ) ) ) ); + $coauthors = cap_get_coauthor_terms_for_post( $post_id ); + if ( ! empty( $coauthors ) ) { + WP_CLI::line( sprintf( + __( 'Skipping - Post #%d already has co-authors assigned: %s', 'co-authors-plus' ), + $post_id, + implode( ', ', wp_list_pluck( $coauthors, 'slug' ) ) + ) ); continue; } @@ -545,7 +550,7 @@ public function list_posts_without_terms( $args, $assoc_args ) { foreach ( $posts->posts as $single_post ) { - $terms = wp_get_post_terms( $single_post->ID, $coauthors_plus->coauthor_taxonomy ); + $terms = cap_get_coauthor_terms_for_post( $single_post->ID ); if ( empty( $terms ) ) { $saved = array( $single_post->ID, @@ -698,8 +703,8 @@ public function remove_terms_from_revisions() { $affected = 0; foreach ( $ids as $post_id ) { - $terms = wp_get_post_terms( $post_id, 'author' ); - if ( ! $terms ) { + $terms = cap_get_coauthor_terms_for_post( $post_id ); + if ( empty( $terms ) ) { continue; } diff --git a/readme.txt b/readme.txt index 608d7e83..ba030853 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,9 @@ === Co-Authors Plus === Contributors: batmoo, danielbachhuber, automattic Tags: authors, users, multiple authors, coauthors, multi-author, publishing -Tested up to: 4.5 +Tested up to: 4.7.3 Requires at least: 4.1 -Stable tag: 3.1.1 +Stable tag: 3.2.2 Assign multiple bylines to posts, pages, and custom post types via a search-as-you-type input box @@ -57,11 +57,22 @@ Bug fixes and minor enhancements == Changelog == += 3.2.2 = +* Fix broken author ordering in 4.7+ (props mslinnea) +* Fix no moderation e-mail bug (props RobjS) +* Cached functions in CLI commands (props jasonbahl) +* Fix missing echos (props trepmal) +* Add `coauthors_guest_author_query_args` filter (props trepmal) + += 3.2.1 (May 16, 2016) = +* Hotfix for broken Guest Author bio metabox (props JS Morisset) + += 3.2 (May 12, 2016) = +Various minor bug and security fixes + = 3.1.2 (Aug. 31, 2015) = * Minor bug fixes and coding standards changes. * The author's display name is now filtered through the_author in coauthors_posts_links_single() - -= ??? (??? ?? ????) = * New Russian and Ukrainian translations, courtesy of [Jurko Chervony](http://skinik.name/). = 3.1.1 (Mar. 20, 2014) = diff --git a/template-tags.php b/template-tags.php index 725fc47f..ffc398ae 100644 --- a/template-tags.php +++ b/template-tags.php @@ -14,8 +14,7 @@ function get_coauthors( $post_id = 0 ) { } if ( $post_id ) { - $coauthor_terms = get_the_terms( $post_id, $coauthors_plus->coauthor_taxonomy ); - + $coauthor_terms = cap_get_coauthor_terms_for_post( $post_id ); if ( is_array( $coauthor_terms ) && ! empty( $coauthor_terms ) ) { foreach ( $coauthor_terms as $coauthor ) { $coauthor_slug = preg_replace( '#^cap\-#', '', $coauthor->slug ); diff --git a/tests/coauthorsplus-testcase.php b/tests/coauthorsplus-testcase.php index eb56cd81..99116c10 100644 --- a/tests/coauthorsplus-testcase.php +++ b/tests/coauthorsplus-testcase.php @@ -3,65 +3,11 @@ /** * Base unit test class for Co-Authors Plus */ - class CoAuthorsPlus_TestCase extends WP_UnitTestCase { - - protected $suppress = false; - public function setUp() { - global $wpdb; parent::setUp(); - $this->suppress = $wpdb->suppress_errors(); - - $_SERVER['REMOTE_ADDR'] = ''; - - $this->author1 = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'author1' ) ); - $this->editor1 = $this->factory->user->create( array( 'role' => 'editor', 'user_login' => 'editor2' ) ); - - $post = array( - 'post_author' => $this->author1, - 'post_status' => 'publish', - 'post_content' => rand_str(), - 'post_title' => rand_str(), - 'post_type' => 'post', - ); - - $this->author1_post1 = wp_insert_post( $post ); - - $post = array( - 'post_author' => $this->author1, - 'post_status' => 'publish', - 'post_content' => rand_str(), - 'post_title' => rand_str(), - 'post_type' => 'post', - ); - - $this->author1_post2 = wp_insert_post( $post ); - - $page = array( - 'post_author' => $this->author1, - 'post_status' => 'publish', - 'post_content' => rand_str(), - 'post_title' => rand_str(), - 'post_type' => 'page', - ); - - $this->author1_page1 = wp_insert_post( $page ); - - $page = array( - 'post_author' => $this->author1, - 'post_status' => 'publish', - 'post_content' => rand_str(), - 'post_title' => rand_str(), - 'post_type' => 'page', - ); - - $this->author1_page2 = wp_insert_post( $page ); - } - public function tearDown() { - global $wpdb; - parent::tearDown(); - $wpdb->suppress_errors( $this->suppress ); + global $coauthors_plus; + $this->_cap = $coauthors_plus; } } diff --git a/tests/test-author-queried-object.php b/tests/test-author-queried-object.php new file mode 100644 index 00000000..25689cf5 --- /dev/null +++ b/tests/test-author-queried-object.php @@ -0,0 +1,102 @@ +factory->user->create( array( 'user_login' => 'msauthor1' ) ); + $author2 = $this->factory->user->create( array( 'user_login' => 'msauthor2' ) ); + $blog2 = $this->factory->blog->create( array( 'user_id' => $author1 ) ); + + switch_to_blog( $blog2 ); + $wp_rewrite->init(); + + $blog2_post1 = $this->factory->post->create( array( + 'post_status' => 'publish', + 'post_content' => rand_str(), + 'post_title' => rand_str(), + 'post_author' => $author1, + ) ); + + /** + * Author 1 is an author on the blog + */ + $this->go_to( get_author_posts_url( $author1 ) ); + $this->assertQueryTrue( 'is_author', 'is_archive' ); + + /** + * Author 2 is not yet an author on the blog + */ + $this->go_to( get_author_posts_url( $author2 ) ); + $this->assertQueryTrue( 'is_404' ); + + // Add the user to the blog + add_user_to_blog( $blog2, $author2, 'author' ); + + /** + * Author 2 is now on the blog, but not yet published + */ + $this->go_to( get_author_posts_url( $author2 ) ); + $this->assertQueryTrue( 'is_author', 'is_archive' ); + + // Add the user as an author on the original post + $author2_obj = get_user_by( 'id', $author2 ); + $coauthors_plus->add_coauthors( $blog2_post1, array( $author2_obj->user_login ), true ); + + /** + * Author 2 is now on the blog, and published + */ + $this->go_to( get_author_posts_url( $author2 ) ); + $this->assertQueryTrue( 'is_author', 'is_archive' ); + + // Remove the user from the blog + remove_user_from_blog( $author2, $blog2 ); + + /** + * Author 2 was removed from the blog, but still a published author + */ + $this->go_to( get_author_posts_url( $author2 ) ); + $this->assertQueryTrue( 'is_author', 'is_archive' ); + + // Delete the user from the network + wpmu_delete_user( $author2 ); + + /** + * Author 2 is no more + */ + $this->go_to( get_author_posts_url( $author2 ) ); + $this->assertQueryTrue( 'is_404' ); + $this->assertEquals( false, get_user_by( 'id', $author2 ) ); + + restore_current_blog(); + + } +} diff --git a/tests/test-author-queries.php b/tests/test-author-queries.php index 2f6c1524..d6d58c7a 100644 --- a/tests/test-author-queries.php +++ b/tests/test-author-queries.php @@ -1,88 +1,129 @@ factory->user->create( array( 'user_login' => 'msauthor1' ) ); - $author2 = $this->factory->user->create( array( 'user_login' => 'msauthor2' ) ); - $blog2 = $this->factory->blog->create( array( 'user_id' => $author1 ) ); - - switch_to_blog( $blog2 ); - $wp_rewrite->init(); - - $blog2_post1 = $this->factory->post->create( array( + public function test__author_arg__user_is_post_author() { + $author_id = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'batman' ) ); + $author = get_userdata( $author_id ); + $post_id = $this->factory->post->create( array( + 'post_author' => $author_id, 'post_status' => 'publish', - 'post_content' => rand_str(), - 'post_title' => rand_str(), - 'post_author' => $author1, + 'post_type' => 'post', ) ); + $this->_cap->add_coauthors( $post_id, array( $author->user_login ) ); - /** - * Author 1 is an author on the blog - */ - $this->go_to( get_author_posts_url( $author1 ) ); - $this->assertQueryTrue( 'is_author', 'is_archive' ); - - /** - * Author 2 is not yet an author on the blog - */ - $this->go_to( get_author_posts_url( $author2 ) ); - $this->assertQueryTrue( 'is_404' ); - - // Add the user to the blog - add_user_to_blog( $blog2, $author2, 'author' ); - - /** - * Author 2 is now on the blog, but not yet published - */ - $this->go_to( get_author_posts_url( $author2 ) ); - $this->assertQueryTrue( 'is_author', 'is_archive' ); - - // Add the user as an author on the original post - $author2_obj = get_user_by( 'id', $author2 ); - $coauthors_plus->add_coauthors( $blog2_post1, array( $author2_obj->user_login ), true ); - - /** - * Author 2 is now on the blog, and published - */ - $this->go_to( get_author_posts_url( $author2 ) ); - $this->assertQueryTrue( 'is_author', 'is_archive' ); - - // Remove the user from the blog - remove_user_from_blog( $author2, $blog2 ); - - /** - * Author 2 was removed from the blog, but still a published author - */ - $this->go_to( get_author_posts_url( $author2 ) ); - $this->assertQueryTrue( 'is_author', 'is_archive' ); - - // Delete the user from the network - wpmu_delete_user( $author2 ); - - /** - * Author 2 is no more - */ - $this->go_to( get_author_posts_url( $author2 ) ); - $this->assertQueryTrue( 'is_404' ); - $this->assertEquals( false, get_user_by( 'id', $author2 ) ); - - restore_current_blog(); + $query = new WP_Query( array( + 'author' => $author_id, + ) ); + + $this->assertEquals( 1, count( $query->posts ) ); + $this->assertEquals( $post_id, $query->posts[ 0 ]->ID ); + } + + public function test__author_name_arg__user_is_post_author() { + $author_id = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'batman' ) ); + $author = get_userdata( $author_id ); + $post_id = $this->factory->post->create( array( + 'post_author' => $author_id, + 'post_status' => 'publish', + 'post_type' => 'post', + ) ); + $this->_cap->add_coauthors( $post_id, array( $author->user_login ) ); + + $query = new WP_Query( array( + 'author_name' => $author->user_login, + ) ); + + $this->assertEquals( 1, count( $query->posts ) ); + $this->assertEquals( $post_id, $query->posts[ 0 ]->ID ); + } + + public function test__author_name_arg__user_is_coauthor() { + $author1_id = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'batman' ) ); + $author1 = get_userdata( $author1_id ); + $author2_id = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'superman' ) ); + $author2 = get_userdata( $author2_id ); + + $post_id = $this->factory->post->create( array( + 'post_author' => $author1_id, + 'post_status' => 'publish', + 'post_type' => 'post', + ) ); + $this->_cap->add_coauthors( $post_id, array( $author1->user_login, $author2->user_login ) ); + + $query = new WP_Query( array( + 'author_name' => $author2->user_login, + ) ); + + $this->assertEquals( 1, count( $query->posts ) ); + $this->assertEquals( $post_id, $query->posts[ 0 ]->ID ); + } + + public function test__author_arg__user_is_coauthor__author_arg() { + return; // TODO: re-enable; fails currently because WordPress generates query as `post_author IN (id)` which doesn't match our regex in the posts_where filter. + + $author1_id = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'batman' ) ); + $author1 = get_userdata( $author1_id ); + $author2_id = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'superman' ) ); + $author2 = get_userdata( $author2_id ); + + $post_id = $this->factory->post->create( array( + 'post_author' => $author1_id, + 'post_status' => 'publish', + 'post_type' => 'post', + ) ); + $this->_cap->add_coauthors( $post_id, array( $author1->user_login, $author2->user_login ) ); + + $query = new WP_Query( array( + 'author' => $author2_id, + ) ); + + $this->assertEquals( 1, count( $query->posts ) ); + $this->assertEquals( $post_id, $query->posts[ 0 ]->ID ); + } + + public function test__author_name_arg_plus_tax_query__user_is_post_author() { + $author_id = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'batman' ) ); + $author = get_userdata( $author_id ); + $post_id = $this->factory->post->create( array( + 'post_author' => $author_id, + 'post_status' => 'publish', + 'post_type' => 'post', + ) ); + $this->_cap->add_coauthors( $post_id, array( $author->user_login ) ); + wp_set_post_terms( $post_id, 'test', 'post_tag' ); + + $query = new WP_Query( array( + 'author_name' => $author->user_login, + 'tag' => 'test', + ) ); + + $this->assertEquals( 1, count( $query->posts ) ); + $this->assertEquals( $post_id, $query->posts[ 0 ]->ID ); + } + + public function tests__author_name_arg_plus_tax_query__is_coauthor() { + return; // TODO: re-enable; fails currently because our posts_join_filter doesn't add an exclusive JOIN on relationships + taxonomy to match the query mods we make. We'd need aliased JOINs on relationships + taxonomy on top of the JOIN that the tax query already adds. + + $author1_id = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'batman' ) ); + $author1 = get_userdata( $author1_id ); + $author2_id = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'superman' ) ); + $author2 = get_userdata( $author2_id ); + + $post_id = $this->factory->post->create( array( + 'post_author' => $author1_id, + 'post_status' => 'publish', + 'post_type' => 'post', + ) ); + $this->_cap->add_coauthors( $post_id, array( $author1->user_login, $author2->user_login ) ); + wp_set_post_terms( $post_id, 'test', 'post_tag' ); + + $query = new WP_Query( array( + 'author_name' => $author2->user_login, + 'tag' => 'test', + ) ); + $this->assertEquals( 1, count( $query->posts ) ); + $this->assertEquals( $post_id, $query->posts[ 0 ]->ID ); } } diff --git a/tests/test-manage-coauthors.php b/tests/test-manage-coauthors.php index 1f5b1945..fad2f9ca 100644 --- a/tests/test-manage-coauthors.php +++ b/tests/test-manage-coauthors.php @@ -2,6 +2,57 @@ class Test_Manage_CoAuthors extends CoAuthorsPlus_TestCase { + public function setUp() { + parent::setUp(); + + $this->author1 = $this->factory->user->create( array( 'role' => 'author', 'user_login' => 'author1' ) ); + $this->editor1 = $this->factory->user->create( array( 'role' => 'editor', 'user_login' => 'editor2' ) ); + + $post = array( + 'post_author' => $this->author1, + 'post_status' => 'publish', + 'post_content' => rand_str(), + 'post_title' => rand_str(), + 'post_type' => 'post', + ); + + $this->author1_post1 = wp_insert_post( $post ); + + $post = array( + 'post_author' => $this->author1, + 'post_status' => 'publish', + 'post_content' => rand_str(), + 'post_title' => rand_str(), + 'post_type' => 'post', + ); + + $this->author1_post2 = wp_insert_post( $post ); + + $page = array( + 'post_author' => $this->author1, + 'post_status' => 'publish', + 'post_content' => rand_str(), + 'post_title' => rand_str(), + 'post_type' => 'page', + ); + + $this->author1_page1 = wp_insert_post( $page ); + + $page = array( + 'post_author' => $this->author1, + 'post_status' => 'publish', + 'post_content' => rand_str(), + 'post_title' => rand_str(), + 'post_type' => 'page', + ); + + $this->author1_page2 = wp_insert_post( $page ); + } + + public function tearDown() { + parent::tearDown(); + } + /** * Test assigning a Co-Author to a post */ diff --git a/wpcom-helper.php b/wpcom-helper.php new file mode 100644 index 00000000..1c9bb1b1 --- /dev/null +++ b/wpcom-helper.php @@ -0,0 +1,238 @@ +is_enabled() && ! in_array( get_option( 'template' ), wpcom_vip_get_coauthors_plus_auto_apply_themes() ) ) + add_action( 'admin_notices', function() { + + // Allow this to be short-circuted in mu-plugins + if ( ! apply_filters( 'wpcom_coauthors_show_enterprise_notice', true ) ) + return; + + echo '

' . __( "Co-Authors Plus isn't yet integrated with your theme. Please contact support to make it happen." ) . '

'; + } ); +} + +/** + * We want to let Elasticsearch know that it should search the author taxonomy's name as a search field + * See: https://elasticsearchp2.wordpress.com/2015/01/08/in-36757-z-vanguard-says-they/ + * + * @param $es_wp_query_args The ElasticSearch Query Parameters + * @param $query + * + * @return mixed + */ +function co_author_plus_es_support( $es_wp_query_args, $query ){ + if ( empty( $es_wp_query_args['query_fields'] ) ) { + $es_wp_query_args['query_fields'] = array( 'title', 'content', 'author', 'tag', 'category' ); + } + + // Search CAP author names + $es_wp_query_args['query_fields'][] = 'taxonomy.author.name'; + + // Filter based on CAP names + if ( !empty( $query->query['author'] ) ) { + $es_wp_query_args['terms']['author'] = 'cap-' . $query->query['author']; + } + + return $es_wp_query_args; +} +add_filter('wpcom_elasticsearch_wp_query_args', 'co_author_plus_es_support', 10, 2 ); + + +/** + * Change the post authors in the subscription email. + * + * Creates an array of authors, that will be used later. + * + * @param $author WP_User the original author + * @param $post_id + * + * @return array of coauthors + */ +add_filter( 'wpcom_subscriber_email_author', function( $author, $post_id ) { + + $authors = get_coauthors( $post_id ); + return $authors; + +}, 10, 2 ); + +/** + * Change the author avatar url. If there are multiple authors, link the avatar to the post. + * + * @param $author_url + * @param $post_id + * @param $authors + * + * @return string with new author url. + */ +add_filter( 'wpcom_subscriber_email_author_url', function( $author_url, $post_id, $authors ) { + if( is_array( $authors ) ) { + if ( count( $authors ) > 1 ) { + return get_permalink( $post_id ); + } + + return get_author_posts_url( $authors[0]->ID, $authors[0]->user_nicename ); + } + + return get_author_posts_url( $authors->ID, $authors->user_nicename ); +}, 10, 3); + +/** + * Change the avatar to be the avatar of the first author + * + * @param $author_avatar + * @param $post_id + * @param $authors + * + * @return string with the html for the avatar + */ +add_filter( 'wpcom_subscriber_email_author_avatar', function( $author_avatar, $post_id, $authors ) { + if( is_array( $authors ) ) + return coauthors_get_avatar( $authors[0], 50 ); + + return coauthors_get_avatar( $authors, 50 ); +}, 10, 3); + +/** + * Changes the author byline in the subscription email to include all the authors of the post + * + * @param $author_byline + * @param $post_id + * @param $authors + * + * @return string with the byline html + */ +add_filter( 'wpcom_subscriber_email_author_byline_html', function( $author_byline, $post_id, $authors ) { + // Check if $authors is a valid array + if( ! is_array( $authors ) ) { + $authors = array( $authors ); + } + + $byline = 'by '; + foreach( $authors as $author ) { + $byline .= '' . esc_html( $author->display_name ) . ''; + if ( $author != end( $authors ) ) { + $byline .= ', '; + } + } + + return $byline; +}, 10, 3); + +/** + * Change the meta information to include all the authors + * + * @param $meta + * @param $post_id + * @param $authors + * + * @return array with new meta information + */ +add_filter( 'wpcom_subscriber_email_meta', function( $meta, $post_id, $authors ) { + // Check if $authors is a valid array + if( ! is_array( $authors ) ) { + $authors = array( $authors ); + } + + $author_meta = ''; + foreach( $authors as $author ) { + $author_meta .= '' . esc_html( $author->display_name ) . ''; + + if ( $author != end( $authors ) ) { + $author_meta .= ', '; + } + } + + // Only the first entry of meta includes the author listing + $meta[0] = $author_meta; + + return $meta; +}, 10, 3); + +/** + * Change the author information in the text-only subscription email. + * + * @param $author + * @param $post_id + * + * @returns string with the authors + */ +add_filter( 'wpcom_subscriber_text_email_author', function( $author, $post_id ) { + // Check if $authors is a valid array + $authors = get_coauthors( $post_id ); + + $author_text = ''; + foreach( $authors as $author ) { + $author_text .= esc_html( $author->display_name ); + if ( $author != end( $authors ) ) { + $author_text .= ', '; + } + } + + return $author_text; +}, 10, 2); + +/** + * Replace author_url in oembed endpoint response + * Since the oembed specification does not allow multiple urls, we'll go with the first coauthor + * + * The function is meant as a filter for `get_author_posts_url` function, which it is using as well + * Recursion is prevented by a simple check agains original attributes passed to the funciton. That + * also prevents execution in case the only coauthor is real author. + * + * This function is hooked only to oembed endpoint and it should stay that way + */ + +function wpcom_vip_cap_replace_author_link( $link, $author_id, $author_nicename ) { + + //get coauthors and iterate to the first one + //in case there are no coauthors, the Iterator returns current author + $i = new CoAuthorsIterator(); + $i->iterate(); + + //check if the current $author_id and $author_nicename is not the same as the first coauthor + if ( $i->current_author->ID !== $author_id || $i->current_author->user_nicename !== $author_nicename ) { + + //alter the author_url + $link = get_author_posts_url( $i->current_author->ID, $i->current_author->user_nicename ); + + } + + return $link; +} + +add_action( 'init', function() { + //Hook the above callback only on oembed endpoint reply + if ( true === defined( 'WPCOM_VIP_IS_OEMBED' ) && true === constant( 'WPCOM_VIP_IS_OEMBED' ) && true === apply_filters( 'wpcom_vip_coauthors_replace_oembed', false, 'author_url' ) ) { + add_filter( 'author_link', 'wpcom_vip_cap_replace_author_link', 99, 3 ); + } +}, 9 );