diff --git a/.travis.yml b/.travis.yml index da7b0e08..a0ff2ae8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,85 @@ language: php -php: - - 5.3 - - 5.5 +matrix: + include: + # aliased to a recent 5.6.x version + - php: "5.6" + env: WP_VERSION=4.8 -env: - - WP_VERSION=latest - - WP_VERSION=4.1 - - WP_VERSION=4.0 + - php: '5.6' + env: + - WP_VERSION=latest + #- SNIFF=1 + - WP_MULTISITE=0 + + - php: '5.6' + env: + - WP_VERSION=latest + #- SNIFF=1 + - WP_MULTISITE=1 + + # aliased to a recent 7.x version + - php: '7.0' + env: WP_VERSION=4.8 + + - php: '7.0' + env: WP_VERSION=latest + + - php: '7.1' + env: WP_VERSION=4.8 + + - php: '7.1' + env: WP_VERSION=latest before_script: - - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION + # Set up CodeSniffer + - 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 + # Set up unit tests + - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION + # Properly handle PHPUnit versions + - export PATH="$HOME/.composer/vendor/bin:$PATH" + - | + if [[ ${TRAVIS_PHP_VERSION:0:3} == "5.6" ]]; then + composer global require "phpunit/phpunit=4.8.*" + elif [[ ${TRAVIS_PHP_VERSION:0:2} == "7." && ${WP_VERSION} == "4.8" ]]; then + composer global require "phpunit/phpunit=5.7.*" + fi + - | + if [[ -n $COMPOSER_BIN_DIR && -x $COMPOSER_BIN_DIR/phpunit ]]; then + $COMPOSER_BIN_DIR/phpunit --version + else + phpunit --version + fi 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 + # Run unit tests + - | + if [[ -n $COMPOSER_BIN_DIR && -x $COMPOSER_BIN_DIR/phpunit ]]; then + $COMPOSER_BIN_DIR/phpunit + else + phpunit + fi diff --git a/README.md b/README.md new file mode 100644 index 00000000..2ea555bf --- /dev/null +++ b/README.md @@ -0,0 +1,304 @@ +# Co-Authors Plus + +* Contributors: batmoo, danielbachhuber, automattic +* Tags: authors, users, multiple authors, coauthors, multi-author, publishing +* Tested up to: 5.0 +* Requires at least: 4.1 +* Stable tag: 3.3.1 + +Assign multiple bylines to posts, pages, and custom post types via a search-as-you-type input box + +## Description + +Assign multiple bylines to posts, pages, and custom post types via a search-as-you-type input box. Co-authored posts appear on a co-author's archive page and in their feed. Co-authors may edit the posts they are associated with, and co-authors who are contributors may only edit posts if they have not been published (as is core behavior). + +Add writers as bylines without creating WordPress user accounts. Simply [create a guest author profile](http://vip.wordpress.com/documentation/add-guest-bylines-to-your-content-with-co-authors-plus/) for the writer and assign the byline as you normally would. + +On the frontend, use the [Co-Authors Plus template tags](http://vip.wordpress.com/documentation/incorporate-co-authors-plus-template-tags-into-your-theme/) to list co-authors anywhere you'd normally list the author. + +This plugin is an almost complete rewrite of the [Co-Authors](https://wordpress.org/plugins/co-authors/) plugin originally developed by Weston Ruter (2007). The original plugin was inspired by the '[Multiple Authors](https://txfx.net/2005/08/16/new-plugin-multiple-authors/)' plugin by Mark Jaquith (2005). + +## Frequently Asked Questions + +* How do I add Co-Authors Plus support to my theme? + +If you've just installed Co-Authors Plus, you might notice that the bylines are being added in the backend but aren't appearing on the frontend. You'll need to [add the template tags to your theme](http://vip.wordpress.com/documentation/incorporate-co-authors-plus-template-tags-into-your-theme/) before the bylines will appear. + +* What happens to posts and pages when I delete a user assigned to a post or page as a coauthor? + +When a user is deleted from WordPress, they will be removed from all posts for which they are co-authors. If you chose to reassign their posts to another user, that user will be set as the coauthor instead. + +* Can I use Co-Authors Plus with WordPress multisite? + +Yep! Co-Authors Plus can be activated on a site-by-site basis, or network-activated. If you create guest authors, however, those guest authors will exist on a site-by-site basis. + +* Who needs permission to do what? + +To assign co-authors to posts, a WordPress user will need the `edit_others_posts` capability. This is typically granted to the Editor role, but can be altered with the `coauthors_plus_edit_authors` filter. + +To create new guest author profiles, a WordPress will need the `list_users` capability. This is typically granted to the Administrator role, but can be altered with the `coauthors_guest_author_manage_cap` filter. + +* Can I easily create a list of all co-authors? + +Yep! There's a template tag called `coauthors_wp_list_authors()` that accepts many of the same arguments as `wp_list_authors()`. Look in template-tags.php for more details. + +* I have a large database, will this make it slow? + +If the site has a large database, you may run into issues with heavier than usual queries. You can work around this by disabling compat mode and force it to use simpler, tax-only queries by adding the following to your theme: + +``` +// Use simple tax queries for CAP to improve performance +add_filter( 'coauthors_plus_should_query_post_author', '__return_false' ); +``` + +Note that this requires the site(s) to have proper terms set up for all users. You can do this with the following wp-cli command: + +``` +# This is pretty long-running and can be expensive; be careful! +$ wp --url=example.com co-authors-plus create-terms-for-posts +``` + +## Changelog ## + +**3.3.1 ("Gutentag")** +* 5.0 Compat: Hide core author inputs when using the Block Editor to limit confusion (h/t jonathanstegall). + +**3.3.0 ("Rebecca")** +* Fix private post viewing on front-end #386 +* Reduce amount of sleep #400 +* Author search UX issues #407 +* Remove associated guest user when mapped user id deleted. #414 +* Removed double left join on posts_join_filter #419 +* Fixed WP CLI create-terms-for-posts if no co-authors found #420 +* Pages archive now displays coauthors and quick edit works #422 +* Terminology updated throughout #423 +* Replace hardcoded 'author' with $this->$coauthor_taxonomy #426 +* Move parenthesis to fix esc_html and sprintf #430 +* Added progress to create-guest-authors so users have an idea of how long it will take #431 +* Deleting guest authors is less confusing #432 +* Guest author's featured image is avatar now #433 +* Removed extra image sizing #434 +* Remove duplicated byline #435 +* coauthors_wp_list_authors() has option to list only guest authors now #436 +* remove duplicates from linked accounts on coauthors_wp_list_authors() #437 +* Accurate Guest Author post count on linked accounts #438 +* New README.md #439 +* Filter author archive #441 +* Fix coauthors_links_single() #444 +* Added guest author hooks for create/delete #446 +* Fixes logic for DOING_AUTOSAVE check #450 +* user_login spaces problem when using add_coauthors #453 +* Adding details of filter for slow performance #456 +* Remove redundant test for 404 on Author Archive #457 +* Guest Author Counts are more accurate #461 +* Set $coauthors_loading #468 +* Fix the issue where guest authors with non-ASCII characters can't be used as co-authors #473 +* Fix the issue where incompatibility when `coauthors_auto_apply_template_tags` set to true #474 +* Unit tests/Fix warnings for template tags #475 +* Review and improve test coverage #476 +* Update class-wp-cli.php #480 +* Update .travis.yml file for PHPUnit tests #482 +* Changes to resolve issue #332 about missing coauthor meta #484 + +Props to the many people who helped make this release possible: [catchmyfame](https://github.com/catchmyfame), [danielbachhuber](https://github.com/danielbachhuber), [david-binda](https://github.com/david-binda), [douglas-johnson](https://github.com/douglas-johnson), [castlehouse](https://github.com/castlehouse), [frankar](https://github.com/frankar), [haleeben](https://github.com/haleeben), [jjeaton](https://github.com/jjeaton), [johnbillion](https://github.com/johnbillion), [kevinlisota](https://github.com/kevinlisota), [mattoperry](https://github.com/mattoperry), [mdbitz](https://github.com/mdbitz), [mdchiragpatel](https://github.com/mdchiragpatel), [megfh](https://github.com/megfh), [mjangda](https://github.com/mjangda), [mslinnea](https://github.com/mslinnea), [natebot](https://github.com/natebot), [nickdaugherty](https://github.com/nickdaugherty), [nilzari](https://github.com/nilzari), [philipjohn](https://github.com/philipjohn), [pkevan](https://github.com/pkevan), [rebeccahum](https://github.com/rebeccahum), [ryanmarkel](https://github.com/ryanmarkel), [sanketio](https://github.com/sanketio), [sboisvert](https://github.com/sboisvert), [Spongsta](https://github.com/Spongsta), [srguglielmo](https://github.com/srguglielmo), [timburden](https://github.com/timburden), [trepmal](https://github.com/trepmal), [TylerDurdon](https://github.com/TylerDurdon) + +**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)** +* Bug fix: Co-authors selection UI should appear when creating a new post too. + +**3.1 (Mar. 17, 2014)** +* Manage co-authors from Quick Edit. Props [mpatek](https://github.com/mpatek). +* Updated Spanish translation, courtesy of [sergiomajluf](https://github.com/sergiomajluf). +* Now matches core behavior when displaying author archive on multisite: user of the blog, or previously published author on the blog. +* Breaking change: "Create Profile" link is no longer shown by default on the Manage Users screen. Instead, it can be enabled with the `coauthors_show_create_profile_user_link` filter. +* Guest authors work properly with Jetpack Open Graph tags. Props [hibernation](https://github.com/hibernation). +* Guest author profile editor now supports a few different fields. Props [alpha1](https://github.com/alpha1). +* New `coauthors_count_published_post_types` filter for specifying the post type(s) used when calculating the user's number of published posts. +* Bug fix: Ensure `post_author` is set to one of the co-authors assigned to a post. +* Bug fix: Filter author feed link for guest authors on the author page. Props [hibernation](https://github.com/hibernation). +* Packages a composer.json file for those using Composer. +* Beginnings of unit test coverage for core features. Increased minimum required WordPress version to 3.7 because WordPress.org unit testing framework doesn't work reliabilty below that. + +**3.0.7 (Jan. 27, 2014)** +* Better support for installing Co-Authors Plus as a symlinked directory. [Follow these instructions](http://kaspars.net/blog/wordpress/plugins-via-symlinks) to filter `plugins_url`. +* Links to authors' posts pages to comply to hCard microformat, which Google depends on. +* New `coauthors_emails()` template tag to list email addresses of the co-authors. Props [benlk](https://github.com/benlk). +* Bug fix: Remove extraneous space between last two co-authors output. Props [johnciacia](https://github.com/johnciacia). +* Updated French translation, courtesy of Jojaba (via email). + +**3.0.6 (Dec. 9, 2013)** +* New Swedish translation, courtesy of [alundstroem](https://github.com/alundstroem) +* Updated German translation, courtesy of [krafit](https://github.com/krafit). +* New Dutch translation, courtesy of [kardotim](https://github.com/kardotim) +* New filter for specifying the default author assigned to a post. Props [tannerm](https://github.com/tannerm) +* Bug fix: When filtering a user's published post count, use the value of their guest author profile if one is mapped. +* Added support for checkboxes in Guest Author profiles +* Fix Strict warnings from CPT's that don't define all capabilities +* New swap-coauthors CLI command for replacing one co-author with another + +**3.0.5 (Feb. 18, 2013)** +* New filter `coauthors_search_authors_get_terms_args` allows you to increase the number of matches returned with AJAX co-author selection +* Bug fix: If there isn't an author term yet for a co-author, avoid an erronous join that caused duplicate posts to appear. + +**3.0.4 (Jan. 6, 2013)** = +* Support for automatically adding co-authors to your feeds. Props [cfg](https://github.com/cfg). +* Bug fix: No Co-Authors Plus on attachments. For now. +* Bug fix: Better support for co-authors with non-standard user_nicenames. Props [STRML](https://github.com/STRML). + +**3.0.3 (Dec. 3, 2012)** +* Bug fix: The default order for the 'author' taxonomy should be `term_order`, in order for the author positions to stick. Props [lgedeon](https://github.com/lgedeon) + +**3.0.2 (Nov. 23, 2012)** +* Bug fix: Fall back to non-pretty permalinks when the author permastruct is empty, so that `coauthors_posts_links()` doesn't link to the homepage + +**3.0.1 (Nov. 21, 2012)** +* Add your own custom columns to the guest authors table using filters. Props [cfg](https://github.com/cfg) +* A new wp-cli subcommand for renaming co-authors and another for removing author terms mistakenly assigned to revisions +* Bug fix: Using a featured image for a guest author avatar didn't work. Now it does. +* Bug fix: Don't assign author terms to revisions to avoid unnecessary database bloat +* Bug fix: Make the `coauthors_wp_list_authors()` template tag work again +* Bug fix: Improve capability filtering by properly handling super admin access and situations where `user_id = 0` +* Minor UI enhancements for guest authors + +**3.0 (Nov. 12, 2012)** +* Create guest author profiles for bylines you'd like to assign without creating WordPress user accounts. Guest authors can have all of the same fields as normal users including display name, biography, and avatars. +* Support for non-Latin characters in usernames and guest author names +* wp-cli subcommands for creating, assigning, and reassigning co-authors +* For themes using core template tags like `the_author()` or `the_author_posts_link()`, you enable Co-Authors Plus support with a simple filter +* New author terms are now prefixed with `cap-` to avoid collisions with global scope +* Bug fix: Apply query filters to only `post_types` registered with the taxonomy. Props [Tom Ransom](https://github.com/1bigidea) +* Filter `coauthors_posts_link_single()` with `coauthors_posts_link`. Also adds `rel="author"`. Props [Amit Sannad](https://github.com/asannad) and [Gabriel Koen](https://github.com/mintindeed) +* Filter for the context and priorities of the Co-Authors meta boxes. Props [Tomáš Kapler](https://github.com/tkapler) +* Renamed the post meta box for selecting authors so it applies to many post types. Props [John Blackbourn](https://github.com/johnbillion) + +**2.6.4 (May 7, 2012)** +* Bug fix: Properly filter the user query so users can AJAX search against the display name field again +* If https is used for the admin, also use the secure Gravatar URL. Props [rmcfrazier](https://github.com/rmcfrazier) + +**2.6.3 (Apr. 30, 2012)** +* AJAX user search is back to searching against user login, display name, email address and user ID. The method introduced in v2.6.2 didn't scale well +* French translation courtesy of Sylvain Bérubé +* Spanish translation courtesy of Alejandro Arcos +* Bug fix: Resolved incorrect caps check against user editing an already published post. [See forum thread](http://wordpress.org/support/topic/multiple-authors-cant-edit-pages?replies=17#post-2741243) + +**2.6.2 (Mar. 6, 2012)** +* AJAX user search matches against first name, last name, and nickname fields too, in addition to display name, user login, and email address +* Comment moderation and approved notifications are properly sent to all co-authors with the correct caps +* Filter required capability for user to be returned in an AJAX search with `coauthors_edit_author_cap` +* Filter out administrators and other non-authors from AJAX search with `coauthors_edit_ignored_authors` +* Automatically adds co-authors to Edit Flow's story budget and calendar views +* Bug fix: Don't set post_author value to current user when quick editing a post. This doesn't appear in the UI anywhere, but adds the post to the current user's list of posts +* Bug fix: Properly cc other co-authors on new comment email notifications +* Bug fix: If a user has already been added as an author to a post, don't show them in the AJAX search again +* Bug fix: Allow output constants to be defined in a theme's functions.php file and include filters you can use instead + +**2.6.1 (Dec. 30, 2011)** +* Fix mangled usernames because of sanitize_key http://wordpress.org/support/topic/plugin-co-authors-plus-26-not-working-with-wp-33 + +**2.6 (Dec. 22, 2011)** +* Sortable authors: Drag and drop the order of the authors as you'd like them to appear ([props kingkool68](http://profiles.wordpress.org/users/kingkool68/)) +* Search for authors by display name (instead of nicename which was essentially the same as user_login) +* Option to remove the first author when there are two or more so it's less confusing +* Bumped requirements to WordPress 3.1 +* Bug fix: Update the published post count for each user more reliably + +**2.5.3 (Aug. 14, 2011)** +* Bug fix: Removed extra comma when only two authors were listed. If you used the `COAUTHORS_DEFAULT_BETWEEN_LAST` constant, double-check what you have + +**2.5.2 (Apr. 23, 2011)** +* Bug: Couldn't query terms and authors at the same time (props nbaxley) +* Bug: Authors with empty fields (e.g. first name) were displaying blank in some cases +* Bug: authors with spaces in usernames not getting saved (props MLmsw, Ruben S. and others!) +* Bug: revisions getting wrong user attached (props cliquenoir!) + +**2.5.1 (Mar. 26, 2011)** +* Fix with author post count (throwing errors) + +**2.5 (Mar. 26, 2011)** +* Custom Post Type Support +* Compatibility with WP 3.0 and 3.1 +* Gravatars +* Lots and lots and lots of bug fixes +* Thanks to everyone who submitted bugs, fixes, and suggestions! And for your patience! + +**2.1.1 (Oct. 16, 2009)** +* Fix for coauthors not being added if their username is different from display name +* Fixes to readme.txt (fixes for textual and punctuation errors, language clarification, minor formatting changes) courtesy of [Waldo Jaquith](http://www.vqronline.org) + +**2.1 (Oct. 11, 2009)** +* Fixed issues related to localization. Thanks to Jan Zombik for the fixes. +* Added `set_time_limit` to update function to get around timeout issues when upgrading plugin + +**2.0 (Oct. 11, 2009)** +* Plugin mostly rewritten to make use of taxonomy instead of `post_meta` +* Can now see all authors of a post under the author column from Edit Posts page +* All authors of a post are now notified on a new comment +* Various javascript enhancements +* New option to allow subscribers to be added as authors +* All Authors can edit they posts of which they are coauthors +* FIX: Issues with `wp_coauthors_list` function +* FIX: Issues with coauthored posts not showing up on author archives + +**1.2.0 (Jun. 16, 2012)** +* FIX: Added compatibility for WordPress 2.8 +* FIX: Added new template tags (`get_the_coauthor_meta` & `the_coauthor_meta`) to fix issues related to displaying author info on author archive pages. See [Other Notes](http://wordpress.org/extend/plugins/co-authors-plus/other_notes/) for details. +* FIX: Plugin should now work for plugins not using the `wp_` DB prefix +* FIX: Coauthors should no longer be alphabetically reordered when the post is updated +* FIX: Plugin now used WordPress native AJAX calls to tighten security +* DOCS: Added details about the new template tags + +**1.1.5 (Apr. 26, 2009)** +* FIX: Not searching Updated SQL query for autosuggest to search through first name, last name, and nickname +* FIX: When editing an author, and clicking on a suggested author, the original author was not be removed +* DOCS: Added code comments to javascript; more still to be added +* DOCS: Updated readme information + +**1.1.4 (Apr. 25, 2009)** +* Disabled "New Author" output in suggest box, for now +* Hopefully fixed SVN issue (if you're having trouble with the plugin, please delete the plugin and reinstall) + +**1.1.3 (Apr. 23, 2009)** +* Add blur event to disable input box +* Limit only one edit at a time. +* Checked basic cross-browser compatibility (Firefox 3 OS X, Safari 3 OS X, IE7 Vista). +* Add suggest javascript plugin to Edit Page. + +**1.1.2 (Apr. 19, 2009)** +* Disabled form submit when enter pressed. + +**1.1.1 (Apr. 15, 2009)** +* Changed SQL query to return only contributor-level and above users. + +**1.1.0 (Apr. 14, 2009)** +* Initial beta release. + +## Installation + +1. IMPORTANT: Please disable the original Co-Authors plugin (if you are using it) before installing Co-Authors Plus +2. Extract the coauthors-plus.zip file and upload its contents to the `/wp-content/plugins/` directory. Alternately, you can install directly from the Plugin directory within your WordPress Install. +3. Activate the plugin through the "Plugins" menu in WordPress. +4. Place the appropriate [co-authors template tags](http://vip.wordpress.com/documentation/incorporate-co-authors-plus-template-tags-into-your-theme/) in your template. +5. Add co-authors to your posts and pages. + +## Screenshots + +1. Multiple authors can be added to a Post, Page, or Custom Post Type using an auto-complete interface. +2. The order of your co-authors can be changed by drag and drop. +3. Guest authors allow you to assign bylines without creating WordPress user accounts. You can also override existing WordPress account meta by mapping a guest author to a WordPress user. diff --git a/co-authors-plus.php b/co-authors-plus.php index 339184a9..46315d61 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.3.1 Author: Mohammad Jangda, Daniel Bachhuber, Automattic Copyright: 2008-2015 Shared and distributed between Mohammad Jangda, Daniel Bachhuber, Weston Ruter @@ -22,9 +22,17 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +----------------- + +Glossary: + +User - a WordPress user account +Guest author - a CAP-created co-author +Co-author - in the context of a single post, a guest author or user assigned to the post alongside others +Author - user with the role of author */ -define( 'COAUTHORS_PLUS_VERSION', '3.1.2' ); +define( 'COAUTHORS_PLUS_VERSION', '3.3.1' ); require_once( dirname( __FILE__ ) . '/template-tags.php' ); require_once( dirname( __FILE__ ) . '/deprecated.php' ); @@ -39,7 +47,7 @@ class CoAuthors_Plus { // Name for the taxonomy we're using to store relationships - // and the post type we're using to store co-authors + // and the post type we're using to store guest authors var $coauthor_taxonomy = 'author'; var $coreauthors_meta_box_name = 'authordiv'; @@ -56,6 +64,8 @@ class CoAuthors_Plus { var $having_terms = ''; + var $to_be_filtered_caps = array(); + /** * __construct() */ @@ -68,39 +78,39 @@ function __construct() { // Load admin_init function add_action( 'admin_init', array( $this, 'admin_init' ) ); - // Modify SQL queries to include coauthors + // Modify SQL queries to include guest authors add_filter( 'posts_where', array( $this, 'posts_where_filter' ), 10, 2 ); add_filter( 'posts_join', array( $this, 'posts_join_filter' ), 10, 2 ); add_filter( 'posts_groupby', array( $this, 'posts_groupby_filter' ), 10, 2 ); - // Action to set users when a post is saved + // Action to set co-authors when a post is saved add_action( 'save_post', array( $this, 'coauthors_update_post' ), 10, 2 ); // Filter to set the post_author field when wp_insert_post is called add_filter( 'wp_insert_post_data', array( $this, 'coauthors_set_post_author_field' ), 10, 2 ); - // Action to reassign posts when a user is deleted + // Action to reassign posts when a guest author is deleted add_action( 'delete_user', array( $this, 'delete_user_action' ) ); add_filter( 'get_usernumposts', array( $this, 'filter_count_user_posts' ), 10, 2 ); - // Action to set up author auto-suggest + // Action to set up co-author auto-suggest add_action( 'wp_ajax_coauthors_ajax_suggest', array( $this, 'ajax_suggest' ) ); - // Filter to allow coauthors to edit posts + // Filter to allow co-authors to edit posts add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 3 ); - // Handle the custom author meta box + // Handle the custom co-author meta box add_action( 'add_meta_boxes', array( $this, 'add_coauthors_box' ) ); add_action( 'add_meta_boxes', array( $this, 'remove_authors_box' ) ); - // Removes the author dropdown from the post quick edit + // Removes the co-author dropdown from the post quick edit add_action( 'admin_head', array( $this, 'remove_quick_edit_authors_box' ) ); // Restricts WordPress from blowing away term order on bulk edit add_filter( 'wp_get_object_terms', array( $this, 'filter_wp_get_object_terms' ), 10, 4 ); - // Make sure we've correctly set author data on author pages - add_filter( 'posts_selection', array( $this, 'fix_author_page' ) ); // use posts_selection since it's after WP_Query has built the request and before it's queried any posts + // Make sure we've correctly set data on guest author pages + add_action( 'posts_selection', array( $this, 'fix_author_page' ) ); // use posts_selection since it's after WP_Query has built the request and before it's queried any posts add_action( 'the_post', array( $this, 'fix_author_page' ) ); // Support for Edit Flow's calendar and story budget @@ -110,12 +120,22 @@ function __construct() { // Support Jetpack Open Graph Tags add_filter( 'jetpack_open_graph_tags', array( $this, 'filter_jetpack_open_graph_tags' ), 10, 2 ); - // Filter to send comment moderation notification e-mail to multiple authors + // Filter to send comment moderation notification e-mail to multiple co-authors add_filter( 'comment_moderation_recipients', 'cap_filter_comment_moderation_email_recipients', 10, 2 ); // 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 Co-Author 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 ); + + // Filter to correct author on author archive page + add_filter( 'get_the_archive_title', array( $this, 'filter_author_archive_title'), 10, 2 ); + + // Filter to display author image if exists instead of avatar + add_filter( 'get_avatar_url', array( $this, 'filter_get_avatar_url' ), 10, 2 ); } /** @@ -170,7 +190,7 @@ public function action_init_late() { $post_types_with_authors = array_values( get_post_types() ); foreach ( $post_types_with_authors as $key => $name ) { - if ( ! post_type_supports( $name, 'author' ) || in_array( $name, array( 'revision', 'attachment' ) ) ) { + if ( ! post_type_supports( $name, $this->coauthor_taxonomy ) || in_array( $name, array( 'revision', 'attachment' ) ) ) { unset( $post_types_with_authors[ $key ] ); } } @@ -189,13 +209,13 @@ public function admin_init() { // Add necessary JS variables add_action( 'admin_head', array( $this, 'js_vars' ) ); - // Hooks to add additional coauthors to author column to Edit page + // Hooks to add additional co-authors to 'authors' column to edit page add_filter( 'manage_posts_columns', array( $this, '_filter_manage_posts_columns' ) ); add_filter( 'manage_pages_columns', array( $this, '_filter_manage_posts_columns' ) ); add_action( 'manage_posts_custom_column', array( $this, '_filter_manage_posts_custom_column' ) ); add_action( 'manage_pages_custom_column', array( $this, '_filter_manage_posts_custom_column' ) ); - // Add quick-edit author select field + // Add quick-edit co-author select field add_action( 'quick_edit_custom_box', array( $this, '_action_quick_edit_custom_box' ), 10, 2 ); // Hooks to modify the published post number count on the Users WP List Table @@ -204,7 +224,6 @@ public function admin_init() { // Apply some targeted filters add_action( 'load-edit.php', array( $this, 'load_edit' ) ); - } /** @@ -221,7 +240,7 @@ public function is_guest_authors_enabled() { } /** - * Get a co-author object by a specific type of key + * Get a guest author object by a specific type of key * * @param string $key Key to search by (slug,email) * @param string $value Value to search for @@ -290,14 +309,17 @@ public function is_post_type_enabled( $post_type = null ) { if ( ! $post_type ) { $post_type = get_post_type(); + if ( is_admin() && ! $post_type) { + $post_type = get_current_screen()->post_type; + } } return (bool) in_array( $post_type, $this->supported_post_types ); } /** - * Removes the standard WordPress Author box. - * We don't need it because the Co-Authors one is way cooler. + * Removes the standard WordPress 'Author' box. + * We don't need it because the Co-Authors Plus one is way cooler. */ public function remove_authors_box() { @@ -307,7 +329,7 @@ public function remove_authors_box() { } /** - * Adds a custom Authors box + * Adds a custom 'Authors' box */ public function add_coauthors_box() { @@ -317,7 +339,7 @@ public function add_coauthors_box() { } /** - * Callback for adding the custom author box + * Callback for adding the custom 'Authors' box */ public function coauthors_meta_box( $post ) { global $post, $coauthors_plus, $current_screen; @@ -360,14 +382,16 @@ public function coauthors_meta_box( $post ) { ID, array( 'default' => 'gravatar_default' ) ); ?>
  • - user_email, $this->gravatar_size ); ?> + ID, $this->gravatar_size ); ?> +
  • -

    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() ) ); ?>

    @@ -391,18 +415,18 @@ public function coauthors_meta_box( $post ) { } /** - * Removes the author dropdown from the post quick edit + * Removes the default 'author' dropdown from quick edit */ function remove_quick_edit_authors_box() { global $pagenow; if ( 'edit.php' == $pagenow && $this->is_post_type_enabled() ) { - remove_post_type_support( get_post_type(), 'author' ); + remove_post_type_support( get_post_type(), $this->coauthor_taxonomy ); } } /** - * Add coauthors to author column on edit pages + * Add co-authors to 'authors' column on edit pages * * @param array $post_columns */ @@ -419,7 +443,7 @@ function _filter_manage_posts_columns( $posts_columns ) { $new_columns['coauthors'] = __( 'Authors', 'co-authors-plus' ); } - if ( 'author' === $key ) { + if ( $this->coauthor_taxonomy === $key ) { unset( $new_columns[ $key ] ); } } @@ -427,7 +451,7 @@ function _filter_manage_posts_columns( $posts_columns ) { } /** - * Insert coauthors into post rows on Edit Page + * Insert co-authors into post rows on Edit Page * * @param string $column_name */ @@ -506,7 +530,7 @@ function _action_quick_edit_custom_box( $column_name, $post_type ) { @@ -514,7 +538,7 @@ function _action_quick_edit_custom_box( $column_name, $post_type ) { } /** - * When we update the terms at all, we should update the published post count for each author + * When we update the terms at all, we should update the published post count for each user */ function _update_users_posts_count( $tt_ids, $taxonomy ) { global $wpdb; @@ -601,8 +625,12 @@ function posts_join_filter( $join, $query ) { global $wpdb; if ( $query->is_author() ) { + $post_type = $query->query_vars['post_type']; + if ( 'any' === $post_type ) { + $post_type = get_post_types( array( 'exclude_from_search' => false ) ); + } - if ( ! empty( $query->query_vars['post_type'] ) && ! is_object_in_taxonomy( $query->query_vars['post_type'], $this->coauthor_taxonomy ) ) { + if ( ! empty( $post_type ) && ! is_object_in_taxonomy( $post_type, $this->coauthor_taxonomy ) ) { return $join; } @@ -611,12 +639,17 @@ 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} AS tr1 ON ({$wpdb->posts}.ID = tr1.object_id)"; - if ( false === strpos( $join, trim( $term_relationship_join ) ) ) { - $join .= str_replace( 'INNER JOIN', 'LEFT JOIN', $term_relationship_join ); + $term_taxonomy_join = " INNER JOIN {$wpdb->term_taxonomy} ON ( tr1.term_taxonomy_id = {$wpdb->term_taxonomy}.term_taxonomy_id )"; + + // 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 ); } @@ -625,22 +658,31 @@ function posts_join_filter( $join, $query ) { return $join; } - /** - * Modify the author query posts SQL to include posts co-authored - */ + /** + * Modify the author query posts SQL to include posts co-authored + * + * @param string $where + * @param WP_Query $query + * + * @return string + */ function posts_where_filter( $where, $query ) { global $wpdb; if ( $query->is_author() ) { + $post_type = $query->query_vars['post_type']; + if ( 'any' === $post_type ) { + $post_type = get_post_types( array( 'exclude_from_search' => false ) ); + } - if ( ! empty( $query->query_vars['post_type'] ) && ! is_object_in_taxonomy( $query->query_vars['post_type'], $this->coauthor_taxonomy ) ) { + if ( ! empty( $post_type ) && ! is_object_in_taxonomy( $post_type, $this->coauthor_taxonomy ) ) { return $where; } if ( $query->get( 'author_name' ) ) { $author_name = sanitize_title( $query->get( 'author_name' ) ); } else { - $author_data = get_userdata( $query->get( 'author' ) ); + $author_data = get_userdata( $query->get( $this->coauthor_taxonomy ) ); if ( is_object( $author_data ) ) { $author_name = $author_data->user_nicename; } else { @@ -653,7 +695,7 @@ function posts_where_filter( $where, $query ) { if ( $author_term = $this->get_author_term( $coauthor ) ) { $terms[] = $author_term; } - // If this coauthor has a linked account, we also need to get posts with those terms + // If this co-author has a linked account, we also need to get posts with those terms if ( ! empty( $coauthor->linked_account ) ) { $linked_account = get_user_by( 'login', $coauthor->linked_account ); if ( $guest_author_term = $this->get_author_term( $linked_account ) ) { @@ -679,8 +721,47 @@ function posts_where_filter( $where, $query ) { $this->having_terms .= ' ' . $wpdb->term_taxonomy .'.term_id = \''. $term->term_id .'\' OR '; } $terms_implode = rtrim( $terms_implode, ' OR' ); + + // We need to check the query is the main query as a new query object would result in the wrong ID + $id = is_author() && $query->is_main_query() ? get_queried_object_id() : '\d+'; + + //If we have an ID but it's not a "real" ID that means that this isn't the first time the filter has fired and the object_id has already been replaced by a previous run of this filter. We therefore need to replace the 0 + // This happens when wp_query::get_posts() is run multiple times. + // If previous condition resulted in this being a string there's no point wasting a db query looking for a user. + if ( $id !== '\d+' && false === get_user_by( 'id', $id ) ){ + $id = '\d+'; + } + + // When WordPress generates query as 'post_author IN (id)'. + if ( false !== strpos( $where, "{$wpdb->posts}.post_author IN " ) ) { + + $maybe_both_query = $maybe_both ? '$0 OR' : ''; + + $where = preg_replace( '/\s\b(?:' . $wpdb->posts . '\.)?post_author\s*IN\s*(.*' . $id . '(?:.*private\')?.)/', ' (' . $maybe_both_query . ' ' . $terms_implode . ')', $where, -1 ); #' . $wpdb->postmeta . '.meta_id IS NOT NULL AND + + } else { + $where = preg_replace( '/(\b(?:' . $wpdb->posts . '\.)?post_author\s*=\s*(' . $id . '))/', '(' . $maybe_both_query . ' ' . $terms_implode . ')', $where, -1 ); #' . $wpdb->postmeta . '.meta_id IS NOT NULL AND + } + + // the block targets the private posts clause (if it exists) + if ( + is_user_logged_in() && + is_author() && + get_queried_object_id() != get_current_user_id() + ) { + $current_coauthor = $this->get_coauthor_by( 'user_nicename', wp_get_current_user()->user_nicename ); + $current_coauthor_term = $this->get_author_term( $current_coauthor ); + + if ( is_a( $current_coauthor_term, 'WP_Term' ) ) { + $current_user_query = $wpdb->term_taxonomy . '.taxonomy = \'' . $this->coauthor_taxonomy . '\' AND ' . $wpdb->term_taxonomy . '.term_id = \'' . $current_coauthor_term->term_id . '\''; + $this->having_terms .= ' ' . $wpdb->term_taxonomy . '.term_id = \'' . $current_coauthor_term->term_id . '\' OR '; + + $where = preg_replace( '/(\b(?:' . $wpdb->posts . '\.)?post_author\s*=\s*(' . get_current_user_id() . ') )/', $current_user_query . ' ', $where, -1 ); #' . $wpdb->postmeta . '.meta_id IS NOT NULL AND} + } + } + $this->having_terms = rtrim( $this->having_terms, ' OR' ); - $where = preg_replace( '/(\b(?:' . $wpdb->posts . '\.)?post_author\s*=\s*(\d+))/', '(' . $maybe_both_query . ' ' . $terms_implode . ')', $where, -1 ); #' . $wpdb->postmeta . '.meta_id IS NOT NULL AND + } } return $where; @@ -693,8 +774,11 @@ function posts_groupby_filter( $groupby, $query ) { global $wpdb; if ( $query->is_author() ) { - - if ( ! empty( $query->query_vars['post_type'] ) && ! is_object_in_taxonomy( $query->query_vars['post_type'], $this->coauthor_taxonomy ) ) { + $post_type = $query->query_vars['post_type']; + if ( 'any' === $post_type ) { + $post_type = get_post_types( array( 'exclude_from_search' => false ) ); + } + if ( ! empty( $post_type ) && ! is_object_in_taxonomy( $post_type, $this->coauthor_taxonomy ) ) { return $groupby; } @@ -712,7 +796,7 @@ function posts_groupby_filter( $groupby, $query ) { function coauthors_set_post_author_field( $data, $postarr ) { // Bail on autosave - if ( defined( 'DOING_AUTOSAVE' ) && ! DOING_AUTOSAVE ) { + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return $data; } @@ -723,7 +807,10 @@ function coauthors_set_post_author_field( $data, $postarr ) { // This action happens when a post is saved while editing a post if ( isset( $_REQUEST['coauthors-nonce'] ) && isset( $_POST['coauthors'] ) && is_array( $_POST['coauthors'] ) ) { - $author = sanitize_text_field( $_POST['coauthors'][0] ); + + // rawurlencode() is for encoding coauthor name with special characters to compare names when getting coauthor. + $author = rawurlencode( sanitize_text_field( $_POST['coauthors'][0] ) ); + if ( $author ) { $author_data = $this->get_coauthor_by( 'user_nicename', $author ); // If it's a guest author and has a linked account, store that information in post_author @@ -736,7 +823,7 @@ function coauthors_set_post_author_field( $data, $postarr ) { } } - // If for some reason we don't have the coauthors fields set + // If for some reason we don't have the co-authors fields set if ( ! isset( $data['post_author'] ) ) { $user = wp_get_current_user(); $data['post_author'] = $user->ID; @@ -755,7 +842,7 @@ function coauthors_set_post_author_field( $data, $postarr ) { */ function coauthors_update_post( $post_id, $post ) { - if ( defined( 'DOING_AUTOSAVE' ) && ! DOING_AUTOSAVE ) { + if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } @@ -769,7 +856,7 @@ function coauthors_update_post( $post_id, $post ) { check_admin_referer( 'coauthors-edit', 'coauthors-nonce' ); $coauthors = (array) $_POST['coauthors']; - $coauthors = array_map( 'sanitize_text_field', $coauthors ); + $coauthors = array_map( 'sanitize_title', $coauthors ); $this->add_coauthors( $post_id, $coauthors ); } } else { @@ -777,7 +864,7 @@ function coauthors_update_post( $post_id, $post ) { if ( ! $this->has_author_terms( $post_id ) ) { $user = get_userdata( $post->post_author ); if ( $user ) { - $this->add_coauthors( $post_id, array( $user->user_login ) ); + $this->add_coauthors( $post_id, array( $user->user_nicename ) ); } } } @@ -809,11 +896,16 @@ public function add_coauthors( $post_id, $coauthors, $append = false ) { } // A co-author is always required + // If no coauthor is provided AND no coauthors are currently set, assign to current user - retain old ones otherwise. if ( empty( $coauthors ) ) { - $coauthors = array( $current_user->user_login ); + if( empty( $existing_coauthors ) ) { + $coauthors = array( $current_user->user_login ); + } else { + $coauthors = $existing_coauthors; + } } - // Set the coauthors + // Set the co-authors $coauthors = array_unique( array_merge( $existing_coauthors, $coauthors ) ); $coauthor_objects = array(); foreach ( $coauthors as &$author_name ) { @@ -849,9 +941,9 @@ public function add_coauthors( $post_id, $coauthors, $append = false ) { } /** - * Action taken when user is deleted. - * - User term is removed from all associated posts - * - Option to specify alternate user in place for each post + * Action taken when co-author is deleted. + * - Co-Author term is removed from all associated posts + * - Option to specify alternate co-author in place for each post * @param delete_id */ function delete_user_action( $delete_id ) { @@ -863,13 +955,13 @@ function delete_user_action( $delete_id ) { if ( $reassign_id ) { // Get posts belonging to deleted author $reassign_user = get_user_by( 'id', $reassign_id ); - // Set to new author + // Set to new guest author if ( is_object( $reassign_user ) ) { $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_author = %d", $delete_id ) ); if ( $post_ids ) { foreach ( $post_ids as $post_id ) { - $this->add_coauthors( $post_id, array( $reassign_user->user_login ), true ); + $this->add_coauthors( $post_id, array( $reassign_user->user_nicename ), true ); } } } @@ -880,17 +972,29 @@ function delete_user_action( $delete_id ) { // Delete term wp_delete_term( $delete_user->user_login, $this->coauthor_taxonomy ); } + + if ( $this->is_guest_authors_enabled() ) { + // Get the deleted user data by user id. + $user_data = get_user_by( 'id', $delete_id ); + + // Get the associated user. + $associated_user = $this->guest_authors->get_guest_author_by( 'linked_account', $user_data->data->user_login ); + + if ( isset( $associated_user->ID ) ) { + // Delete associated guest user. + $this->guest_authors->delete( $associated_user->ID ); + } + } } /** - * Restrict WordPress from blowing away author order when bulk editing terms + * Restrict WordPress from blowing away co-author order when bulk editing terms * * @since 2.6 * @props kingkool68, http://wordpress.org/support/topic/plugin-co-authors-plus-making-authors-sortable */ function filter_wp_get_object_terms( $terms, $object_ids, $taxonomies, $args ) { - - if ( ! isset( $_REQUEST['bulk_edit'] ) || "'author'" !== $taxonomies ) { + if ( ! isset( $_REQUEST['bulk_edit'] ) || $this->coauthor_taxonomy !== $taxonomies ) { return $terms; } @@ -925,15 +1029,18 @@ function filter_wp_get_object_terms( $terms, $object_ids, $taxonomies, $args ) { } /** - * Filter the count_users_posts() core function to include our correct count + * Filter the count_users_posts() core function to include our correct count. + * + * @param int $count Post count + * @param int $user_id WP user ID + * @return int Post count */ function filter_count_user_posts( $count, $user_id ) { $user = get_userdata( $user_id ); - $user = $this->get_coauthor_by( 'user_nicename', $user->user_nicename ); $term = $this->get_author_term( $user ); - // Only modify the count if the author already exists as a term + if ( $term && ! is_wp_error( $term ) ) { $count = $term->count; } @@ -942,7 +1049,7 @@ function filter_count_user_posts( $count, $user_id ) { } /** - * Checks to see if the current user can set authors or not + * Checks to see if the current user can set co-authors or not */ function current_user_can_set_authors( $post = null ) { global $typenow; @@ -950,11 +1057,17 @@ function current_user_can_set_authors( $post = null ) { if ( ! $post ) { $post = get_post(); if ( ! $post ) { - return false; + // if user is on pages, you need to grab post type another way + $current_screen = get_current_screen(); + $post_type = ( ! empty( $current_screen->post_type ) ) ? $current_screen->post_type : ''; + } + else { + $post_type = $post->post_type; } } - - $post_type = $post->post_type; + else { + $post_type = $post->post_type; + } // TODO: need to fix this; shouldn't just say no if don't have post_type if ( ! $post_type ) { @@ -979,18 +1092,21 @@ function current_user_can_set_authors( $post = null ) { /** * Fix for author pages 404ing or not properly displaying on author pages * - * If an author has no posts, we only want to force the queried object to be - * the author if they're a member of the blog. + * If a guest author has no posts, we only want to force the queried object to be + * the author if they're a user. * - * If the author does have posts, it doesn't matter that they're not an author. + * If the guest author does have posts, it doesn't matter that they're not an author. * - * Alternatively, on an author archive, if the first story has coauthors and + * Alternatively, on an author archive, if the first story has co-authors and * the first author is NOT the same as the author for the archive, * the query_var is changed. * * Also, we have to do some hacky WP_Query modification for guest authors + * + * @param string $selection The assembled selection query + * @void */ - public function fix_author_page() { + public function fix_author_page( $selection ) { if ( ! is_author() ) { return; @@ -1010,10 +1126,10 @@ public function fix_author_page() { $term = $this->get_author_term( $authordata ); } - if ( ( is_object( $authordata ) ) - || ( ! empty( $term ) && $term->count ) ) { + if ( is_object( $authordata ) || ! empty( $term ) ) { $wp_query->queried_object = $authordata; $wp_query->queried_object_id = $authordata->ID; + add_filter( 'pre_handle_404', '__return_true' ); } else { $wp_query->queried_object = $wp_query->queried_object_id = null; $wp_query->is_author = $wp_query->is_archive = false; @@ -1039,7 +1155,7 @@ public function filter_infinite_scroll_js_settings( $settings ) { $author = get_queried_object(); if ( $author && 'guest-author' == $author->type ) { - unset( $settings['query_args']['author'] ); + unset( $settings['query_args'][$this->coauthor_taxonomy] ); $settings['query_args']['author_name'] = $author->user_nicename; } @@ -1048,7 +1164,7 @@ public function filter_infinite_scroll_js_settings( $settings ) { } /** - * Main function that handles search-as-you-type for adding authors + * Main function that handles search-as-you-type for adding co-authors */ public function ajax_suggest() { @@ -1065,8 +1181,12 @@ public function ajax_suggest() { $authors = $this->search_authors( $search, $ignore ); + // Return message if no authors found + if( empty( $authors ) ) echo apply_filters( 'coauthors_no_matching_authors_message', 'Sorry, no matching authors found.'); + foreach ( $authors as $author ) { - echo esc_html( $author->ID . ' | ' . $author->user_login . ' | ' . $author->display_name . ' | ' . $author->user_email . ' | ' . $author->user_nicename ) . "\n"; + $avatar_url = get_avatar_url( $author->ID, array( 'default' => 'gravatar_default' ) ); + echo esc_html( $author->ID . ' | ' . $author->user_login . ' | ' . $author->display_name . ' | ' . $author->user_email . ' | ' . rawurldecode( $author->user_nicename ) ) . ' | ' . esc_url( $avatar_url ) . "\n"; } die(); @@ -1074,7 +1194,7 @@ public function ajax_suggest() { } /** - * Get matching authors based on a search value + * Get matching co-authors based on a search value */ public function search_authors( $search = '', $ignored_authors = array() ) { @@ -1085,7 +1205,7 @@ public function search_authors( $search = '', $ignored_authors = array() ) { $args = array( 'count_total' => false, 'search' => sprintf( '*%s*', $search ), - 'search_fields' => array( + 'search_columns' => array( 'ID', 'display_name', 'user_email', @@ -1093,9 +1213,7 @@ public function search_authors( $search = '', $ignored_authors = array() ) { ), 'fields' => 'all_with_meta', ); - add_action( 'pre_user_query', array( $this, 'action_pre_user_query' ) ); $found_users = get_users( $args ); - remove_action( 'pre_user_query', array( $this, 'action_pre_user_query' ) ); foreach ( $found_users as $found_user ) { $term = $this->get_author_term( $found_user ); @@ -1118,7 +1236,7 @@ public function search_authors( $search = '', $ignored_authors = array() ) { return array(); } - // Get the co-author objects + // Get the guest author objects $found_users = array(); foreach ( $found_terms as $found_term ) { $found_user = $this->get_coauthor_by( 'user_nicename', $found_term->slug ); @@ -1131,7 +1249,7 @@ public function search_authors( $search = '', $ignored_authors = array() ) { $ignored_authors = apply_filters( 'coauthors_edit_ignored_authors', $ignored_authors ); foreach ( $found_users as $key => $found_user ) { // Make sure the user is contributor and above (or a custom cap) - if ( in_array( $found_user->user_login, $ignored_authors ) ) { + if ( in_array( $found_user->user_nicename, $ignored_authors ) ) { //AJAX sends a list of already present *users_nicenames* unset( $found_users[ $key ] ); } else if ( 'wpuser' === $found_user->type && false === $found_user->has_cap( apply_filters( 'coauthors_edit_author_cap', 'edit_posts' ) ) ) { unset( $found_users[ $key ] ); @@ -1140,17 +1258,6 @@ public function search_authors( $search = '', $ignored_authors = array() ) { return (array) $found_users; } - /** - * Modify get_users() to search display_name instead of user_nicename - */ - function action_pre_user_query( &$user_query ) { - - if ( is_object( $user_query ) ) { - $user_query->query_where = str_replace( 'user_nicename LIKE', 'display_name LIKE', $user_query->query_where ); - } - - } - /** * Modify get_terms() to LIKE against the term description instead of the term name * @@ -1217,7 +1324,7 @@ function filter_views( $views ) { 'author_name' => wp_get_current_user()->user_nicename, ); if ( 'post' != get_post_type() ) { - $mine_args['post_type'] = get_post_type(); + $mine_args['post_type'] = get_current_screen()->post_type; } if ( ! empty( $_REQUEST['author_name'] ) && wp_get_current_user()->user_nicename == $_REQUEST['author_name'] ) { $class = ' class="current"'; @@ -1269,7 +1376,33 @@ public function is_valid_page() { } /** - * Allows coauthors to edit the post they're coauthors of + * Builds list of capabilities that CAP should filter. + * + * Will only work after $this->supported_post_types has been populated. + * Will only run once per request, and then cache the result. + * The result is cached in $this->to_be_filtered_caps since CoAuthors_Plus is only instantiated once and stored as a global. + * + * @return array caps that CAP should filter + */ + public function get_to_be_filtered_caps() { + if( ! empty( $this->supported_post_types ) && empty( $this->to_be_filtered_caps ) ) { + $this->to_be_filtered_caps[] = 'edit_post'; // Need to filter this too, unfortunately: http://core.trac.wordpress.org/ticket/22415 + + foreach( $this->supported_post_types as $single ) { + $obj = get_post_type_object( $single ); + + $this->to_be_filtered_caps[] = $obj->cap->edit_post; + $this->to_be_filtered_caps[] = $obj->cap->edit_others_posts; // This as well: http://core.trac.wordpress.org/ticket/22417 + } + + $this->to_be_filtered_caps = array_unique( $this->to_be_filtered_caps ); + } + + return $this->to_be_filtered_caps; + } + + /** + * Allows guest authors to edit the post they're co-authors of */ function filter_user_has_cap( $allcaps, $caps, $args ) { @@ -1277,11 +1410,16 @@ function filter_user_has_cap( $allcaps, $caps, $args ) { $user_id = isset( $args[1] ) ? $args[1] : 0; $post_id = isset( $args[2] ) ? $args[2] : 0; + if( ! in_array( $cap, $this->get_to_be_filtered_caps(), true ) ) { + return $allcaps; + } + $obj = get_post_type_object( get_post_type( $post_id ) ); if ( ! $obj || 'revision' == $obj->name ) { return $allcaps; } + //Even though we bail if cap is not among the to_be_filtered ones, there is a time in early request processing in which that list is not yet available, so the following block is needed $caps_to_modify = array( $obj->cap->edit_post, 'edit_post', // Need to filter this too, unfortunately: http://core.trac.wordpress.org/ticket/22415 @@ -1388,19 +1526,19 @@ public function update_author_term( $coauthor ) { function filter_ef_calendar_item_information_fields( $information_fields, $post_id ) { // Don't add the author row again if another plugin has removed - if ( ! array_key_exists( 'author', $information_fields ) ) { + if ( ! array_key_exists( $this->coauthor_taxonomy, $information_fields ) ) { return $information_fields; } $co_authors = get_coauthors( $post_id ); if ( count( $co_authors ) > 1 ) { - $information_fields['author']['label'] = __( 'Authors', 'co-authors-plus' ); + $information_fields[$this->coauthor_taxonomy]['label'] = __( 'Authors', 'co-authors-plus' ); } $co_authors_names = ''; foreach ( $co_authors as $co_author ) { $co_authors_names .= $co_author->display_name . ', '; } - $information_fields['author']['value'] = rtrim( $co_authors_names, ', ' ); + $information_fields[$this->coauthor_taxonomy]['value'] = rtrim( $co_authors_names, ', ' ); return $information_fields; } @@ -1417,7 +1555,7 @@ function filter_ef_calendar_item_information_fields( $information_fields, $post_ function filter_ef_story_budget_term_column_value( $column_name, $post, $parent_term ) { // We only want to modify the 'author' column - if ( 'author' != $column_name ) { + if ( $this->coauthor_taxonomy != $column_name ) { return $column_name; } @@ -1463,6 +1601,127 @@ public function filter_jetpack_open_graph_tags( $og_tags, $image_dimensions ) { // Send back the updated Open Graph Tags return apply_filters( 'coauthors_open_graph_tags', $og_tags ); } + + /** + * Retrieve a list of author terms for a single post. + * + * Grabs a correctly ordered list of co-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 co-authors. + * @return array Array of coauthor WP_Term objects + */ + public function get_coauthor_terms_for_post( $post_id ) { + + if ( ! $post_id ) { + return array(); + } + + $cache_key = 'coauthors_post_' . $post_id; + $coauthor_terms = wp_cache_get( $cache_key, 'co-authors-plus' ); + + if ( false === $coauthor_terms ) { + $coauthor_terms = wp_get_object_terms( $post_id, $this->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' ); + + } + + /** + * Filter of the header of author archive pages to correctly display author. + * + * @param $title string Archive Page Title + * + * @return string Archive Page Title + */ + public function filter_author_archive_title( $title ) { + + // Bail if not an author archive template + if ( ! is_author() ) { + return $title; + } + + $author_slug = sanitize_user( get_query_var( 'author_name' ) ); + $author = $this->get_coauthor_by( 'user_nicename', $author_slug ); + + return sprintf( __( 'Author: %s' ), $author->display_name ); + } + + /** + * Get the post count for the guest author + * + * @param object $guest_author guest-author object. + * @return int post count for the guest author + */ + public function get_guest_author_post_count( $guest_author ) { + if ( ! is_object( $guest_author ) ) { + return; + } + + $term = $this->get_author_term( $guest_author ); + $guest_term = get_term_by( 'slug', 'cap-' . $guest_author->user_nicename, $this->coauthor_taxonomy ); + + if ( is_object( $guest_term ) + && ! empty( $guest_author->linked_account ) + && $guest_term->count ) { + return count_user_posts( get_user_by( 'login', $guest_author->linked_account )->ID ); + } elseif ( $term ) { + return $term->count; + } else { + return 0; + } + } + + /** + * Filter to display author image if exists instead of avatar. + * + * @param $url string Avatar URL + * @param $id int Author ID + * + * @return string Avatar URL + */ + public function filter_get_avatar_url( $url, $id ) { + if ( has_post_thumbnail( $id ) ) { + $url = get_the_post_thumbnail_url( $id, $this->gravatar_size ); + } + return $url; + } } global $coauthors_plus; @@ -1595,6 +1854,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 +1865,17 @@ function cap_filter_comment_moderation_email_recipients( $recipients, $comment_i } return $recipients; } + +/** + * Retrieve a list of co-author 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 ); +} \ No newline at end of file diff --git a/css/co-authors-plus.css b/css/co-authors-plus.css index 8f9fdb8f..758bb567 100644 --- a/css/co-authors-plus.css +++ b/css/co-authors-plus.css @@ -42,7 +42,7 @@ width:200px; } #coauthors-list .ui-sortable-helper .coauthor-tag { - cursor: cursor:grabbing; + cursor: grabbing; cursor:-moz-grabbing; cursor:-webkit-grabbing; } @@ -88,6 +88,7 @@ } #coauthors-loading { margin: 10px 0px 5px 10px; + float: left; } #coauthors-readonly { @@ -107,4 +108,10 @@ padding: 5px 3px; margin-left: 30px; font-size: 13px; - } \ No newline at end of file + } + +/** Block Editor Hack for 5.0: Hide the core author input **/ +.block-editor label[for^="post-author-selector-"], +.block-editor select[id^="post-author-selector-"] { + display: none; +} diff --git a/js/co-authors-plus.js b/js/co-authors-plus.js index 7691f91b..1ebffba1 100755 --- a/js/co-authors-plus.js +++ b/js/co-authors-plus.js @@ -11,7 +11,7 @@ jQuery( document ).ready(function () { return false; }; - var $coauthors_loading; + var $coauthors_loading = jQuery(""); function coauthors_delete( elem ) { @@ -39,9 +39,9 @@ jQuery( document ).ready(function () { } /* - * Save coauthor - * @param int Author ID - * @param string Author Name + * Save co-author + * @param int Co-Author ID + * @param string Co-Author Name * @param object The autosuggest input box */ function coauthors_save_coauthor( author, co ) { @@ -59,8 +59,8 @@ jQuery( document ).ready(function () { /* - * Add coauthor - * @param string Author Name + * Add co-author + * @param string Co-Author Name * @param object The autosuggest input box * @param boolean Initial set up or not? */ @@ -70,11 +70,11 @@ jQuery( document ).ready(function () { if ( co && co.siblings( '.coauthor-tag' ).length ) { coauthors_save_coauthor( author, co ); } else { - // Not editing, so we create a new author entry + // Not editing, so we create a new co-author entry if ( count == 0 ) { var coName = ( count == 0 ) ? 'coauthors-main' : ''; - // Add new author to + //coauthors_select_author( co-author ); } var options = { addDelete: true, addEdit: false }; @@ -82,7 +82,7 @@ jQuery( document ).ready(function () { if ( ! co ) var co = coauthors_create_autosuggest( author.name, coName ) var tag = coauthors_create_author_tag( author ); var input = coauthors_create_author_hidden_input( author ); - var $gravatar = coauthors_create_author_gravatar( author, 25 ); + var $gravatar = coauthors_create_author_gravatar( author ); tag.append( $gravatar ); @@ -98,8 +98,9 @@ jQuery( document ).ready(function () { co.bind( 'blur', coauthors_stop_editing ); - // Set the value for the auto-suggest box to the Author's name and hide it - co.val( unescape( author.name ) ) + // Set the value for the auto-suggest box to the co-author's name and hide it + // unescape() is deprecated, so replacing it with decodeURIComponent() here and every places. + co.val( decodeURIComponent( author.name ) ) .hide() .unbind( 'focus' ) ; @@ -132,8 +133,8 @@ jQuery( document ).ready(function () { } /* - * Adds a delete and edit button next to an author - * @param object The row to which the new author should be added + * Adds a delete and edit button next to a co-author + * @param object The row to which the new co-author should be added */ function coauthors_insert_author_edit_cells( $div, options ){ @@ -156,7 +157,7 @@ jQuery( document ).ready(function () { /* * Creates autosuggest input box - * @param string [optional] Name of the author + * @param string [optional] Name of the co-author * @param string [optional] Name to be applied to the input box */ function coauthors_create_autosuggest( authorName, inputName ) { @@ -178,7 +179,7 @@ jQuery( document ).ready(function () { ; if ( authorName ) - $co.attr( 'value', unescape( authorName ) ); + $co.attr( 'value', decodeURIComponent( authorName ) ); else $co.attr( 'value', coAuthorsPlusStrings.search_box_text ) .focus( function(){ $co.val( '' ) } ) @@ -189,7 +190,7 @@ jQuery( document ).ready(function () { } - // Callback for when a user selects an author + // Callback for when a user selects a co-author function coauthors_autosuggest_select() { $this = jQuery( this ); var vals = this.value.split( '|' ); @@ -199,7 +200,12 @@ jQuery( document ).ready(function () { author.login = jQuery.trim( vals[1] ); author.name = jQuery.trim( vals[2] ); author.email = jQuery.trim( vals[3] ); - author.nicename = jQuery.trim( vals[4] ); + if( author.avatar !== '' ){ + author.avatar = jQuery.trim( vals[5] ); + } + + // Decode user-nicename if it has special characters in it. + author.nicename = decodeURIComponent( jQuery.trim( vals[4] ) ); if ( author.id=='New' ) { coauthors_new_author_display( name ); @@ -234,13 +240,13 @@ jQuery( document ).ready(function () { } /* - * Creates the text tag for an author - * @param string Name of the author + * Creates the text tag for a co-author + * @param string Name of the co-author */ function coauthors_create_author_tag( author ) { var $tag = jQuery( '' ) - .html( unescape( author.name ) ) + .text( decodeURIComponent( author.name ) ) .attr( 'title', coAuthorsPlusStrings.input_box_title ) .addClass( 'coauthor-tag' ) // Add Click event to edit @@ -248,38 +254,19 @@ jQuery( document ).ready(function () { return $tag; } - function coauthors_create_author_gravatar( author, size ) { - - var gravatar_link = get_gravatar_link( author.email, size ); + function coauthors_create_author_gravatar( author ) { var $gravatar = jQuery( '' ) .attr( 'alt', author.name ) - .attr( 'src', gravatar_link ) + .attr( 'src', author.avatar ) .addClass( 'coauthor-gravatar' ) ; return $gravatar; } - // MD5 (Message-Digest Algorithm) by WebToolkit -- needed for gravatars - // http://www.webtoolkit.info/javascript-md5.html - function MD5(s){function L(k,d){return(k<>>(32-d))}function K(G,k){var I,d,F,H,x;F=(G&2147483648);H=(k&2147483648);I=(G&1073741824);d=(k&1073741824);x=(G&1073741823)+(k&1073741823);if(I&d){return(x^2147483648^F^H)}if(I|d){if(x&1073741824){return(x^3221225472^F^H)}else{return(x^1073741824^F^H)}}else{return(x^F^H)}}function r(d,F,k){return(d&F)|((~d)&k)}function q(d,F,k){return(d&k)|(F&(~k))}function p(d,F,k){return(d^F^k)}function n(d,F,k){return(F^(d|(~k)))}function u(G,F,aa,Z,k,H,I){G=K(G,K(K(r(F,aa,Z),k),I));return K(L(G,H),F)}function f(G,F,aa,Z,k,H,I){G=K(G,K(K(q(F,aa,Z),k),I));return K(L(G,H),F)}function D(G,F,aa,Z,k,H,I){G=K(G,K(K(p(F,aa,Z),k),I));return K(L(G,H),F)}function t(G,F,aa,Z,k,H,I){G=K(G,K(K(n(F,aa,Z),k),I));return K(L(G,H),F)}function e(G){var Z;var F=G.length;var x=F+8;var k=(x-(x%64))/64;var I=(k+1)*16;var aa=Array(I-1);var d=0;var H=0;while(H>>29;return aa}function B(x){var k="",F="",G,d;for(d=0;d<=3;d++){G=(x>>>(d*8))&255;F="0"+G.toString(16);k=k+F.substr(F.length-2,2)}return k}function J(k){k=k.replace(/\r\n/g,"\n");var d="";for(var F=0;F127)&&(x<2048)){d+=String.fromCharCode((x>>6)|192);d+=String.fromCharCode((x&63)|128)}else{d+=String.fromCharCode((x>>12)|224);d+=String.fromCharCode(((x>>6)&63)|128);d+=String.fromCharCode((x&63)|128)}}}return d}var C=Array();var P,h,E,v,g,Y,X,W,V;var S=7,Q=12,N=17,M=22;var A=5,z=9,y=14,w=20;var o=4,m=11,l=16,j=23;var U=6,T=10,R=15,O=21;s=J(s);C=e(s);Y=1732584193;X=4023233417;W=2562383102;V=271733878;for(P=0;P' ) @@ -287,7 +274,7 @@ jQuery( document ).ready(function () { 'type': 'hidden', 'id': 'coauthors_hidden_input', 'name': 'coauthors[]', - 'value': unescape( author.nicename ) + 'value': decodeURIComponent( author.nicename ) }) ; @@ -319,7 +306,7 @@ jQuery( document ).ready(function () { $coauthors_div.append( table ); } - // Select authors already added to the post + // Select co-authors already added to the post var addedAlready = []; //jQuery('#the-list tr').each(function(){ var count = 0; @@ -337,11 +324,11 @@ jQuery( document ).ready(function () { var newCO = coauthors_create_autosuggest( '', false ); coauthors_add_to_table( newCO ); - $coauthors_loading = jQuery( '#ajax-loading' ).clone().attr( 'id', 'coauthors-loading' ); + $coauthors_loading = jQuery( '#publishing-action .spinner' ).clone().attr( 'id', 'coauthors-loading' ); move_loading( newCO ); - // Make co-authors sortable so an editor can control the order of the authors + // Make co-authors sortable so an editor can control the order of the co-authors jQuery( '#coauthors-edit' ).ready(function( $ ) { $( '#coauthors-list' ).sortable({ axis: 'y', @@ -386,6 +373,7 @@ jQuery( document ).ready(function () { var $post_coauthor_names = jQuery( 'input[name="coauthorsinput[]"]' ); var $post_coauthor_emails = jQuery( 'input[name="coauthorsemails[]"]' ); var $post_coauthor_nicenames = jQuery( 'input[name="coauthorsnicenames[]"]' ); + var $post_coauthoravatars = jQuery( 'input[name="coauthorsavatars[]"]' ); var post_coauthors = []; @@ -394,11 +382,12 @@ jQuery( document ).ready(function () { login: $post_coauthor_logins[i].value, name: $post_coauthor_names[i].value, email: $post_coauthor_emails[i].value, - nicename: $post_coauthor_nicenames[i].value + nicename: $post_coauthor_nicenames[i].value, + avatar: $post_coauthoravatars[i].value, }); } - // Remove the read-only coauthors so we don't get craziness + // Remove the read-only co-authors so we don't get craziness jQuery( '#coauthors-readonly' ).remove(); coauthors_initialize( post_coauthors ); } @@ -425,7 +414,7 @@ jQuery( document ).ready(function () { var el = jQuery( '.inline-edit-group.inline-edit-coauthors', '#edit-' + postId ); el.detach().appendTo( '.quick-edit-row .inline-edit-col-left .inline-edit-col' ).show(); - // initialize coauthors + // initialize co-authors var post_coauthors = jQuery.map( jQuery( '.column-coauthors a', $postRow ), function( el ) { return { login: jQuery( el ).data( 'user_login' ), diff --git a/php/class-coauthors-guest-authors.php b/php/class-coauthors-guest-authors.php index 90537899..caa8bc2e 100644 --- a/php/class-coauthors-guest-authors.php +++ b/php/class-coauthors-guest-authors.php @@ -69,6 +69,9 @@ function __construct() { // Add support for featured thumbnails that we can use for guest author avatars add_filter( 'get_avatar', array( $this, 'filter_get_avatar' ),10 ,5 ); + // Add a Personal Data Exporter to guest authors + add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'filter_personal_data_exporter' ), 1 ); + // Allow users to change where this is placed in the WordPress admin $this->parent_page = apply_filters( 'coauthors_guest_author_parent_page', $this->parent_page ); @@ -89,23 +92,31 @@ function __construct() { '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 authors that aren't WP.com users + // Register a post type to store our guest authors $args = array( 'label' => $this->labels['singular'], 'labels' => array( - 'name' => $this->labels['plural'], - 'singular_name' => $this->labels['singular'], - 'add_new' => _x( 'Add New', 'guest author', 'co-authors-plus' ), - 'all_items' => $this->labels['all_items'], - 'add_new_item' => $this->labels['add_new_item'], - 'edit_item' => $this->labels['edit_item'], - 'new_item' => $this->labels['new_item'], - 'view_item' => $this->labels['view_item'], - 'search_items' => $this->labels['search_items'], - 'not_found' => $this->labels['not_found'], - 'not_found_in_trash' => $this->labels['not_found_in_trash'], + '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, @@ -123,17 +134,7 @@ function __construct() { register_post_type( $this->post_type, $args ); // Some of the common sizes used by get_avatar - $this->avatar_sizes = array( - 32, - 50, - 64, - 96, - 128, - ); - $this->avatar_sizes = apply_filters( 'coauthors_guest_author_avatar_sizes', $this->avatar_sizes ); - foreach ( $this->avatar_sizes as $size ) { - add_image_size( 'guest-author-' . $size, $size, $size, true ); - } + $this->avatar_sizes = array(); // Hacky way to remove the title and the editor remove_post_type_support( $this->post_type, 'title' ); @@ -177,7 +178,7 @@ function filter_post_updated_messages( $messages ) { /** * Handle the admin action to create a guest author based - * on an existing WordPress user + * on an existing user * * @since 3.0 */ @@ -203,6 +204,8 @@ function handle_create_guest_author_action() { 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 ); @@ -273,6 +276,8 @@ function handle_delete_guest_author_action() { $args['message'] = 'delete-error'; } else { $args['message'] = 'guest-author-deleted'; + + do_action( 'cap_guest_author_del' ); } // Redirect to safety @@ -463,36 +468,70 @@ function view_guest_authors_list() { 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 ) . '

    '; - echo '

    ' . esc_html( sprintf( __( 'What should be done with posts assigned to this %s?', 'co-authors-plus' ), strtolower( $this->labels['singular'] ) ) ) . '

    '; - echo '

    ' . esc_html( 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'] ) ) ) . '

    '; + // 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 '
      '; - // Reassign to another user - echo '
    • '; - echo ''; - echo '
    • '; - // Leave mapped to a linked account - if ( get_user_by( 'login', $guest_author->linked_account ) ) { - 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 '
    • '; } - // Remove bylines from the posts - echo '
    • '; + else { + echo ''; + } echo '
    '; - submit_button( __( 'Confirm Deletion', 'co-authors-plus' ), 'secondary', 'submit', true, array( 'disabled' => 'disabled' ) ); + // 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 { @@ -592,7 +631,7 @@ public function filter_wp_dropdown_users_to_disable( $output ) { } /** - * Metabox to display all of the pertient names for a Guest Author without a user account + * Metabox to display all of the pertient names for a Guest Author not linked to user account * * @since 3.0 */ @@ -627,7 +666,8 @@ function metabox_manage_guest_author_name() { } /** - * Metabox to display all of the pertient contact details for a Guest Author without a user account + * Metabox to display all of the pertient contact details for a Guest Author not linked to + * user account * * @since 3.0 */ @@ -675,11 +715,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 ''; @@ -908,11 +958,12 @@ function get_guest_author_by( $key, $value, $force = false ) { /** * 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 - * @return string The thumbnail image tag, or null if one doesn't exist + * @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 ) { + 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; @@ -921,12 +972,15 @@ function get_guest_author_thumbnail( $guest_author, $size ) { $args = array( 'class' => "avatar avatar-{$size} photo", ); - if ( in_array( $size, $this->avatar_sizes ) ) { - $size = 'guest-author-' . $size; - } else { - $size = array( $size, $size ); + 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; @@ -1209,6 +1263,11 @@ function create( $args ) { 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 ); @@ -1454,4 +1513,95 @@ public function filter_author_feed_link( $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, + ); + } } diff --git a/php/class-coauthors-wp-list-table.php b/php/class-coauthors-wp-list-table.php index dacbf203..d8585779 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': @@ -135,10 +137,10 @@ function filter_query_for_search( $where ) { } /** - * Either there are no guest authors, or the search doesn't match any + * Either there are no co-authors, or the search doesn't match any */ function no_items() { - esc_html_e( 'No matching guest authors were found.', 'co-authors-plus' ); + esc_html_e( 'No matching co-authors were found.', 'co-authors-plus' ); } /** @@ -248,12 +250,7 @@ function column_linked_account( $item ) { */ function column_posts( $item ) { global $coauthors_plus; - $term = $coauthors_plus->get_author_term( $item ); - if ( $term ) { - $count = $term->count; - } else { - $count = 0; - } + $count = $coauthors_plus->get_guest_author_post_count( $item ); return '' . $count . ''; } diff --git a/php/class-wp-cli.php b/php/class-wp-cli.php index 8dcf1ee2..b5123c15 100644 --- a/php/class-wp-cli.php +++ b/php/class-wp-cli.php @@ -28,6 +28,7 @@ public function create_guest_authors( $args, $assoc_args ) { $users = get_users(); $created = 0; $skipped = 0; + $progress = \WP_CLI\Utils\make_progress_bar( 'Processing guest authors...', count ( $users ) ); foreach ( $users as $user ) { $result = $coauthors_plus->guest_authors->create_guest_author_from_user_id( $user->ID ); @@ -36,12 +37,12 @@ public function create_guest_authors( $args, $assoc_args ) { } else { $created++; } + $progress->tick(); } - + $progress->finish(); WP_CLI::line( 'All done! Here are your results:' ); WP_CLI::line( "- {$created} guest author profiles were created" ); WP_CLI::line( "- {$skipped} users already had guest author profiles" ); - } /** @@ -76,9 +77,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::line( sprintf( 'No co-authors found for post #%d.', $single_post->ID ) ); } if ( ! empty( $terms ) ) { @@ -115,12 +116,12 @@ public function create_terms_for_posts() { } /** - * Subcommand to assign coauthors to a post based on a given meta key + * Subcommand to assign co-authors to a post based on a given meta key * * @since 3.0 * * @subcommand assign-coauthors - * @synopsis [--meta_key=] [--post_type=] + * @synopsis [--meta_key=] [--post_type=] [--append_coauthors] */ public function assign_coauthors( $args, $assoc_args ) { global $coauthors_plus; @@ -235,16 +236,21 @@ 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; } $coauthors_plus->add_coauthors( $post_id, array( $coauthor->user_login ) ); WP_CLI::line( sprintf( __( "Updating - Adding %s's byline to post #%d", 'co-authors-plus' ), $coauthor->user_login, $post_id ) ); $affected++; - if ( $affected && 0 === $affected % 20 ) { - sleep( 5 ); + if ( $affected && 0 === $affected % 100 ) { + sleep( 2 ); } } WP_CLI::success( sprintf( __( 'All done! %d posts were affected.', 'co-authors-plus' ), $affected ) ); @@ -403,8 +409,8 @@ public function rename_coauthor( $args, $assoc_args ) { } /** - * Swap one Co Author with another on all posts for which they are an author. Unlike rename-coauthor, - * this leaves the original Co Author term intact and works when the 'to' user already has a co-author term. + * Swap one co-author with another on all posts for which they are a co-author. Unlike rename-coauthor, + * this leaves the original co-author term intact and works when the 'to' user already has a co-author term. * * @subcommand swap-coauthors * @synopsis --from= --to= [--post_type=] [--dry=] @@ -545,7 +551,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, @@ -606,7 +612,7 @@ public function migrate_author_terms( $args, $assoc_args ) { } /** - * Update the post count and description for each author + * Update the post count and description for each author and guest author * * @since 3.0 * @@ -698,8 +704,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; } @@ -729,7 +735,7 @@ public function create_guest_authors_from_wxr( $args, $assoc_args ) { } if ( ! class_exists( 'WXR_Parser' ) ) { - require_once( WP_CONTENT_DIR . '/admin-plugins/wordpress-importer/parsers.php' ); + require_once( WP_CONTENT_DIR . '/plugins/wordpress-importer/parsers.php' ); } $parser = new WXR_Parser(); @@ -817,6 +823,9 @@ public function create_guest_authors_from_csv( $args, $assoc_args ) { 'display_name' => sanitize_text_field( $author['display_name'] ), 'user_login' => sanitize_user( $author['user_login'] ), 'user_email' => sanitize_email( $author['user_email'] ), + 'website' => esc_url_raw( $author['website'] ), + 'description' => wp_filter_post_kses( $author['description'] ), + 'avatar' => absint( $author['avatar'] ), ); $display_name_space_pos = strpos( $author['display_name'], ' ' ); @@ -855,6 +864,9 @@ private function create_guest_author( $author ) { 'user_email' => $author['user_email'], 'first_name' => $author['first_name'], 'last_name' => $author['last_name'], + 'website' => $author['website'], + 'description' => $author['description'], + 'avatar' => $author['avatar'], ) ); if ( $guest_author_id ) { diff --git a/readme.txt b/readme.txt index 608d7e83..fd117314 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: 5.0 Requires at least: 4.1 -Stable tag: 3.1.1 +Stable tag: 3.3.1 Assign multiple bylines to posts, pages, and custom post types via a search-as-you-type input box @@ -15,7 +15,7 @@ Add writers as bylines without creating WordPress user accounts. Simply [create On the frontend, use the [Co-Authors Plus template tags](http://vip.wordpress.com/documentation/incorporate-co-authors-plus-template-tags-into-your-theme/) to list co-authors anywhere you'd normally list the author. -This plugin is an almost complete rewrite of the Co-Authors plugin originally developed at [Shepherd Interactive](http://www.shepherd-interactive.com/) (2007). The original plugin was inspired by the 'Multiple Authors' plugin by Mark Jaquith (2005). +This plugin is an almost complete rewrite of the [Co-Authors](https://wordpress.org/plugins/co-authors/) plugin originally developed by Weston Ruter (2007). The original plugin was inspired by the '[Multiple Authors](https://txfx.net/2005/08/16/new-plugin-multiple-authors/)' plugin by Mark Jaquith (2005). == Frequently Asked Questions == @@ -41,6 +41,11 @@ To create new guest author profiles, a WordPress will need the 'list_users' capa Yep! There's a template tag called `coauthors_wp_list_authors()` that accepts many of the same arguments as `wp_list_authors()`. Look in template-tags.php for more details. += Can I disable Guest Authors? + +Yep! Guest authors can be disabled entirely through an apt filter. Having the following line load on `init` will do the trick: +`add_filter( 'coauthors_guest_authors_enabled', '__return_false' )` + == Upgrade Notice == = 3.1 = @@ -57,11 +62,62 @@ Bug fixes and minor enhancements == Changelog == += 3.3.1 ("Gutentag") = +* 5.0 Compat: Hide core author inputs when using the Block Editor to limit confusion (h/t jonathanstegall). + += 3.3.0 ("Rebecca") = +* Fix private post viewing on front-end +* Reduce amount of sleep +* Author search UX issues +* Remove associated guest user when mapped user id deleted. +* Removed double left join on posts_join_filter +* Fixed WP CLI create-terms-for-posts if no co-authors found +* Pages archive now displays coauthors and quick edit works +* Terminology updated throughout +* Replace hardcoded 'author' with $this->$coauthor_taxonomy +* Move parenthesis to fix esc_html and sprintf +* Added progress to create-guest-authors so users have an idea of how long it will take +* Deleting guest authors is less confusing +* Guest author's featured image is avatar now +* Removed extra image sizing +* Remove duplicated byline +* coauthors_wp_list_authors() has option to list only guest authors now +* remove duplicates from linked accounts on coauthors_wp_list_authors() +* Accurate Guest Author post count on linked accounts +* New README.md +* Filter author archive +* Fix coauthors_links_single() +* Added guest author hooks for create/delete +* Fixes logic for DOING_AUTOSAVE check +* user_login spaces problem when using add_coauthors +* Adding details of filter for slow performance +* Remove redundant test for 404 on Author Archive +* Guest Author Counts are more accurate +* Set $coauthors_loading +* Fix the issue where guest authors with non-ASCII characters can't be used as co-authors +* Fix the issue where incompatibility when `coauthors_auto_apply_template_tags` set to true +* Unit tests/Fix warnings for template tags +* Review and improve test coverage +* Update class-wp-cli.php +* Update .travis.yml file for PHPUnit tests +* Changes to resolve issue #332 about missing coauthor meta + += 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..36043623 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 ); @@ -36,6 +35,9 @@ function get_coauthors( $post_id = 0 ) { } } // the empty else case is because if we force guest authors, we don't ever care what value wp_posts.post_author has. } + // remove duplicate $coauthors objects from mapping user accounts to guest authors accounts + $coauthors = array_unique( $coauthors, SORT_REGULAR ); + $coauthors = apply_filters( 'get_coauthors', $coauthors, $post_id ); return $coauthors; } @@ -235,12 +237,33 @@ function coauthors( $between = null, $betweenLast = null, $before = null, $after * @param bool $echo Whether the co-authors should be echoed or returned. Defaults to true. */ function coauthors_posts_links( $between = null, $betweenLast = null, $before = null, $after = null, $echo = true ) { - return coauthors__echo('coauthors_posts_links_single', 'callback', array( - 'between' => $between, + + global $coauthors_plus_template_filters; + + $modify_filter = ! empty( $coauthors_plus_template_filters ) && $coauthors_plus_template_filters instanceof CoAuthors_Template_Filters; + + if ( $modify_filter ) { + + /** + * Removing "the_author" filter so that it won't get called in loop and append names for each author. + * + * Ref : https://github.com/Automattic/Co-Authors-Plus/issues/279 + */ + remove_filter( 'the_author', array( $coauthors_plus_template_filters, 'filter_the_author' ) ); + } + + $coauthors_posts_links = coauthors__echo( 'coauthors_posts_links_single', 'callback', array( + 'between' => $between, 'betweenLast' => $betweenLast, - 'before' => $before, - 'after' => $after, + 'before' => $before, + 'after' => $after, ), null, $echo ); + + if ( $modify_filter ) { + add_filter( 'the_author', array( $coauthors_plus_template_filters, 'filter_the_author' ) ); + } + + return $coauthors_posts_links; } /** @@ -344,12 +367,33 @@ function coauthors_nicknames( $between = null, $betweenLast = null, $before = nu * @param bool $echo Whether the co-authors should be echoed or returned. Defaults to true. */ function coauthors_links( $between = null, $betweenLast = null, $before = null, $after = null, $echo = true ) { - return coauthors__echo( 'coauthors_links_single', 'callback', array( - 'between' => $between, + + global $coauthors_plus_template_filters; + + $modify_filter = ! empty( $coauthors_plus_template_filters ) && $coauthors_plus_template_filters instanceof CoAuthors_Template_Filters; + + if ( $modify_filter ) { + + /** + * Removing "the_author" filter so that it won't get called in loop and append names for each author. + * + * Ref : https://github.com/Automattic/Co-Authors-Plus/issues/279 + */ + remove_filter( 'the_author', array( $coauthors_plus_template_filters, 'filter_the_author' ) ); + } + + $coauthors_links = coauthors__echo( 'coauthors_links_single', 'callback', array( + 'between' => $between, 'betweenLast' => $betweenLast, - 'before' => $before, - 'after' => $after, + 'before' => $before, + 'after' => $after, ), null, $echo ); + + if ( $modify_filter ) { + add_filter( 'the_author', array( $coauthors_plus_template_filters, 'filter_the_author' ) ); + } + + return $coauthors_links; } /** @@ -377,14 +421,22 @@ function coauthors_emails( $between = null, $betweenLast = null, $before = null, * @return string */ function coauthors_links_single( $author ) { - if ( get_the_author_meta( 'url' ) ) { - return sprintf( '%s', - get_the_author_meta( 'url' ), - esc_attr( sprintf( __( 'Visit %s’s website' ), get_the_author() ) ), - get_the_author() + if ( 'guest-author' === $author->type && get_the_author_meta( 'website' ) ) { + return sprintf( '%s', + esc_url( get_the_author_meta( 'website' ) ), + esc_attr( sprintf( __( 'Visit %s’s website' ), esc_html( get_the_author() ) ) ), + esc_html( get_the_author() ) ); - } else { - return get_the_author(); + } + elseif ( get_the_author_meta( 'url' ) ) { + return sprintf( '%s', + esc_url( get_the_author_meta( 'url' ) ), + esc_attr( sprintf( __( 'Visit %s’s website' ), esc_html( get_the_author() ) ) ), + esc_html( get_the_author() ) + ); + } + else { + return esc_html( get_the_author() ); } } @@ -406,22 +458,64 @@ function coauthors_ids( $between = null, $betweenLast = null, $before = null, $a ), null, $echo ); } -function get_the_coauthor_meta( $field ) { - global $wp_query, $post; - - $coauthors = get_coauthors(); - $meta = array(); - - foreach ( $coauthors as $coauthor ) { - $user_id = $coauthor->ID; - $meta[ $user_id ] = get_the_author_meta( $field, $user_id ); - } - return $meta; +/** + * Outputs the co-authors Meta Data + * + * @param string $field Required The user field to retrieve.[login, email, nicename, display_name, url, type] + * @param string $user_id Optional The user ID for meta + * + * @return array $meta Value of the user field + */ +function get_the_coauthor_meta( $field, $user_id = false ) { + global $coauthors_plus; + + if ( ! $user_id ) { + $coauthors = get_coauthors(); + } + else { + $coauthor_data = $coauthors_plus->get_coauthor_by( 'id', $user_id ); + $coauthors = array(); + if ( ! empty( $coauthor_data ) ) { + $coauthors[] = $coauthor_data; + } + } + + $meta = array(); + + if ( in_array( $field, array( 'login', 'pass', 'nicename', 'email', 'url', 'registered', 'activation_key', 'status' ) ) ) { + $field = 'user_' . $field; + } + + foreach ( $coauthors as $coauthor ) { + $user_id = $coauthor->ID; + + if ( isset( $coauthor->type ) && 'user_url' === $field ) { + if ( 'guest-author' === $coauthor->type ) { + $field = 'website'; + } + } + else if ( 'website' === $field ) { + $field = 'user_url'; + } + + if ( isset( $coauthor->$field ) ) { + $meta[ $user_id ] = $coauthor->$field; + } + else { + $meta[ $user_id ] = ''; + } + } + + return $meta; } + function the_coauthor_meta( $field, $user_id = 0 ) { - // TODO: need before after options - echo get_the_coauthor_meta( $field, $user_id ); + // TODO: need before after options + $coauthor_meta = get_the_coauthor_meta( $field, $user_id ); + foreach ( $coauthor_meta as $meta ) { + echo esc_html( $meta ); + } } /** @@ -432,6 +526,7 @@ function the_coauthor_meta( $field, $user_id = 0 ) { * feed (string) (''): If isn't empty, show links to author's feeds. * feed_image (string) (''): If isn't empty, use this image to link to feeds. * echo (boolean) (true): Set to false to return the output, instead of echoing. + * authors_with_posts_only (boolean) (false): If true, don't query for authors with no posts. * @param array $args The argument array. * @return null|string The output, if echo is set to false. */ @@ -439,16 +534,18 @@ function coauthors_wp_list_authors( $args = array() ) { global $coauthors_plus; $defaults = array( - 'optioncount' => false, - 'show_fullname' => false, - 'hide_empty' => true, - 'feed' => '', - 'feed_image' => '', - 'feed_type' => '', - 'echo' => true, - 'style' => 'list', - 'html' => true, - 'number' => 20, // A sane limit to start to avoid breaking all the things + 'optioncount' => false, + 'show_fullname' => false, + 'hide_empty' => true, + 'feed' => '', + 'feed_image' => '', + 'feed_type' => '', + 'echo' => true, + 'style' => 'list', + 'html' => true, + 'number' => 20, // A sane limit to start to avoid breaking all the things + 'guest_authors_only' => false, + 'authors_with_posts_only' => false, ); $args = wp_parse_args( $args, $defaults ); @@ -456,10 +553,15 @@ function coauthors_wp_list_authors( $args = array() ) { $term_args = array( 'orderby' => 'name', - 'hide_empty' => 0, 'number' => (int) $args['number'], + /* + * Historically, this was set to always be `0` ignoring `$args['hide_empty']` value + * To avoid any backwards incompatibility, inventing `authors_with_posts_only` that defaults to false + */ + 'hide_empty' => (boolean) $args['authors_with_posts_only'], ); $author_terms = get_terms( $coauthors_plus->coauthor_taxonomy, $term_args ); + $authors = array(); foreach ( $author_terms as $author_term ) { // Something's wrong in the state of Denmark @@ -469,11 +571,23 @@ function coauthors_wp_list_authors( $args = array() ) { $authors[ $author_term->name ] = $coauthor; - $authors[ $author_term->name ]->post_count = $author_term->count; + // only show guest authors if the $args is set to true + if ( ! $args['guest_authors_only'] || $authors[ $author_term->name ]->type === 'guest-author' ) { + $authors[ $author_term->name ]->post_count = $author_term->count; + } + else { + unset( $authors[ $author_term->name ] ); + } } $authors = apply_filters( 'coauthors_wp_list_authors_array', $authors ); + // remove duplicates from linked accounts + $linked_accounts = array_unique( array_column( $authors, 'linked_account' ) ); + foreach ( $linked_accounts as $linked_account ) { + unset( $authors[$linked_account] ); + } + foreach ( (array) $authors as $author ) { $link = ''; @@ -512,12 +626,16 @@ function coauthors_wp_list_authors( $args = array() ) { if ( empty( $args['feed_image'] ) ) { $link .= '('; } - $link .= 'ID, $args['feed_type'] ) ) . '"'; + + $alt = ''; + $title = ''; if ( ! empty( $args['feed'] ) ) { + $title = ' title="' . esc_attr( $args['feed'] ) . '"'; - $alt = ' alt="' . esc_attr( $args['feed'] ) . '"'; - $name = $feed; + $alt = ' alt="' . esc_attr( $args['feed'] ) . '"'; + $name = $args['feed']; $link .= $title; } @@ -565,11 +683,19 @@ function coauthors_wp_list_authors( $args = array() ) { * This is a replacement for using get_avatar(), which only operates on email addresses and cannot differentiate * between Guest Authors (who may share an email) and regular user accounts * - * @param object $coauthor The Co Author or Guest Author object - * @param int $size The desired size - * @return string The image tag for the avatar, or an empty string if none could be determined + * @param object $coauthor The Co Author or Guest Author object. + * @param int $size The desired size. + * @param string $default Optional. URL for the default image or a default type. Accepts '404' + * (return a 404 instead of a default image), 'retro' (8bit), 'monsterid' + * (monster), 'wavatar' (cartoon face), 'indenticon' (the "quilt"), + * 'mystery', 'mm', or 'mysteryman' (The Oyster Man), 'blank' (transparent GIF), + * or 'gravatar_default' (the Gravatar logo). Default is the value of the + * 'avatar_default' option, with a fallback of 'mystery'. + * @param string $alt Optional. Alternative text to use in <img> tag. Default false. + * @param array|string $class Optional. Array or string of additional classes to add to the <img> element. Default null. + * @return string The image tag for the avatar, or an empty string if none could be determined. */ -function coauthors_get_avatar( $coauthor, $size = 32, $default = '', $alt = false ) { +function coauthors_get_avatar( $coauthor, $size = 32, $default = '', $alt = false, $class = null ) { global $coauthors_plus; if ( ! is_object( $coauthor ) ) { @@ -577,7 +703,7 @@ function coauthors_get_avatar( $coauthor, $size = 32, $default = '', $alt = fals } if ( isset( $coauthor->type ) && 'guest-author' == $coauthor->type ) { - $guest_author_thumbnail = $coauthors_plus->guest_authors->get_guest_author_thumbnail( $coauthor, $size ); + $guest_author_thumbnail = $coauthors_plus->guest_authors->get_guest_author_thumbnail( $coauthor, $size, $class ); if ( $guest_author_thumbnail ) { return $guest_author_thumbnail; @@ -586,7 +712,7 @@ function coauthors_get_avatar( $coauthor, $size = 32, $default = '', $alt = fals // Make sure we're dealing with an object for which we can retrieve an email if ( isset( $coauthor->user_email ) ) { - return get_avatar( $coauthor->user_email, $size, $default, $alt ); + return get_avatar( $coauthor->user_email, $size, $default, $alt, array( 'class' => $class ) ); } // Nothing matched, an invalid object was passed. 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..3ae86c50 --- /dev/null +++ b/tests/test-author-queried-object.php @@ -0,0 +1,81 @@ +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' ); + + // 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->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..04fbe6e8 100644 --- a/tests/test-author-queries.php +++ b/tests/test-author-queries.php @@ -1,88 +1,145 @@ 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_query_as_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(); + wp_set_current_user( $author_id ); + $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_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' => $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() { + $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() { + $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-coauthors-guest-authors.php b/tests/test-coauthors-guest-authors.php new file mode 100644 index 00000000..3cc920e9 --- /dev/null +++ b/tests/test-coauthors-guest-authors.php @@ -0,0 +1,890 @@ +admin1 = $this->factory->user->create_and_get( array( 'role' => 'administrator', 'user_login' => 'admin1' ) ); + $this->author1 = $this->factory->user->create_and_get( array( 'role' => 'author', 'user_login' => 'author1' ) ); + $this->editor1 = $this->factory->user->create_and_get( array( 'role' => 'editor', 'user_login' => 'editor1' ) ); + + $this->post = $this->factory->post->create_and_get( array( + 'post_author' => $this->author1->ID, + 'post_status' => 'publish', + 'post_content' => rand_str(), + 'post_title' => rand_str(), + 'post_type' => 'post', + ) ); + } + + /** + * Checks a simulated WP_User object based on the post ID when key or value is empty. + * + * @covers CoAuthors_Guest_Authors::get_guest_author_by() + */ + public function test_get_guest_author_by_with_empty_key_or_value() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Fetch guest author without forcefully. + $this->assertFalse( $guest_author_obj->get_guest_author_by( '', '' ) ); + $this->assertFalse( $guest_author_obj->get_guest_author_by( 'ID', '' ) ); + $this->assertFalse( $guest_author_obj->get_guest_author_by( '', $this->author1->ID ) ); + + // Fetch guest author forcefully. + $this->assertFalse( $guest_author_obj->get_guest_author_by( '', '', true ) ); + $this->assertFalse( $guest_author_obj->get_guest_author_by( 'ID', '', true ) ); + $this->assertFalse( $guest_author_obj->get_guest_author_by( '', $this->author1->ID, true ) ); + } + + /** + * Checks a simulated WP_User object based on the post ID using cache. + * + * @covers CoAuthors_Guest_Authors::get_guest_author_by() + */ + public function test_get_guest_author_by_using_cache() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + $guest_author_id = $guest_author_obj->create_guest_author_from_user_id( $this->editor1->ID ); + + $cache_key = $guest_author_obj->get_cache_key( 'ID', $guest_author_id ); + + // Checks when guest author does not exist in cache. + $this->assertFalse( wp_cache_get( $cache_key, $guest_author_obj::$cache_group ) ); + + // Checks when guest author exists in cache. + $guest_author = $guest_author_obj->get_guest_author_by( 'ID', $guest_author_id ); + $guest_author_cached = wp_cache_get( $cache_key, $guest_author_obj::$cache_group ); + + $this->assertInstanceOf( stdClass::class, $guest_author ); + $this->assertEquals( $guest_author, $guest_author_cached ); + } + + /** + * Checks a simulated WP_User object based on the post ID using different key/value. + * + * @covers CoAuthors_Guest_Authors::get_guest_author_by() + */ + public function test_get_guest_author_by_with_different_keys() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Checks when user is not a guest author. + $this->assertFalse( $guest_author_obj->get_guest_author_by( 'ID', $this->author1->ID ) ); + $this->assertFalse( $guest_author_obj->get_guest_author_by( 'ID', $this->author1->ID, true ) ); + + $guest_author_id = $guest_author_obj->create_guest_author_from_user_id( $this->editor1->ID ); + + // Checks guest author using ID. + $guest_author = $guest_author_obj->get_guest_author_by( 'ID', $guest_author_id ); + + $this->assertInstanceOf( stdClass::class, $guest_author ); + $this->assertEquals( $guest_author_id, $guest_author->ID ); + $this->assertEquals( $guest_author_obj->post_type, $guest_author->type ); + + // Checks guest author using user_nicename. + $guest_author = $guest_author_obj->get_guest_author_by( 'user_nicename', $this->editor1->user_nicename ); + + $this->assertInstanceOf( stdClass::class, $guest_author ); + $this->assertEquals( $guest_author_obj->post_type, $guest_author->type ); + + // Checks guest author using linked_account. + $guest_author = $guest_author_obj->get_guest_author_by( 'linked_account', $this->editor1->user_login ); + + $this->assertInstanceOf( stdClass::class, $guest_author ); + $this->assertEquals( $guest_author_obj->post_type, $guest_author->type ); + } + + /** + * Checks thumbnail for a guest author object. + * + * @covers CoAuthors_Guest_Authors::get_guest_author_thumbnail() + */ + public function test_get_guest_author_thumbnail() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Checks when guest author does not have any thumbnail. + $guest_author_id = $guest_author_obj->create( array( + 'user_login' => 'author2', + 'display_name' => 'author2', + ) ); + $guest_author = $guest_author_obj->get_guest_author_by( 'ID', $guest_author_id ); + + $this->assertNull( $guest_author_obj->get_guest_author_thumbnail( $guest_author, 0 ) ); + + // Checks when guest author has thumbnail. + $filename = rand_str() . '.jpg'; + $contents = rand_str(); + $upload = wp_upload_bits( $filename, null, $contents ); + + $this->assertTrue( empty( $upload['error'] ) ); + + $attachment_id = $this->_make_attachment( $upload ); + + set_post_thumbnail( $guest_author->ID, $attachment_id ); + + $thumbnail = $guest_author_obj->get_guest_author_thumbnail( $guest_author, 0 ); + + $this->assertContains( 'avatar-0', $thumbnail ); + $this->assertContains( $filename, $thumbnail ); + $this->assertContains( 'src="' . wp_get_attachment_url( $attachment_id ) . '"', $thumbnail ); + } + + /** + * Checks all of the meta fields that can be associated with a guest author. + * + * @covers CoAuthors_Guest_Authors::get_guest_author_fields() + */ + public function test_get_guest_author_fields() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Checks all the meta fields. + $fields = $guest_author_obj->get_guest_author_fields(); + + $this->assertNotEmpty( $fields ); + $this->assertInternalType( 'array', $fields ); + + $keys = wp_list_pluck( $fields, 'key' ); + + $global_fields = array( + 'display_name', + 'first_name', + 'last_name', + 'user_login', + 'user_email', + 'linked_account', + 'website', + 'aim', + 'yahooim', + 'jabber', + 'description', + ); + + $this->assertEquals( $global_fields, $keys ); + + // Checks all the meta fields with group that does not exist. + $fields = $guest_author_obj->get_guest_author_fields( 'test' ); + + $this->assertEmpty( $fields ); + + // Checks all the meta fields with group "name". + $fields = $guest_author_obj->get_guest_author_fields( 'name' ); + $keys = wp_list_pluck( $fields, 'key' ); + + $this->assertEquals( array( 'display_name', 'first_name', 'last_name' ), $keys ); + + // Checks all the meta fields with group "slug". + $fields = $guest_author_obj->get_guest_author_fields( 'slug' ); + $keys = wp_list_pluck( $fields, 'key' ); + + $this->assertEquals( array( 'user_login', 'linked_account' ), $keys ); + + // Checks all the meta fields with group "contact-info". + $fields = $guest_author_obj->get_guest_author_fields( 'contact-info' ); + $keys = wp_list_pluck( $fields, 'key' ); + + $this->assertEquals( array( 'user_email', 'website', 'aim', 'yahooim', 'jabber' ), $keys ); + + // Checks all the meta fields with group "about". + $fields = $guest_author_obj->get_guest_author_fields( 'about' ); + $keys = wp_list_pluck( $fields, 'key' ); + + $this->assertEquals( array( 'description' ), $keys ); + } + + /** + * Checks all of the user accounts that have been linked. + * + * @covers CoAuthors_Guest_Authors::get_all_linked_accounts() + */ + public function test_get_all_linked_accounts() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + $this->assertEmpty( $guest_author_obj->get_all_linked_accounts() ); + + // Checks when guest author ( not linked account ) exists. + $guest_author_obj->create( array( + 'user_login' => 'author2', + 'display_name' => 'author2', + ) ); + + $this->assertEmpty( $guest_author_obj->get_all_linked_accounts() ); + + // Create guest author from existing user and check. + $guest_author_obj->create_guest_author_from_user_id( $this->editor1->ID ); + + $linked_accounts = $guest_author_obj->get_all_linked_accounts(); + $linked_account_ids = wp_list_pluck( $linked_accounts, 'ID' ); + + $this->assertNotEmpty( $linked_accounts ); + $this->assertInternalType( 'array', $linked_accounts ); + $this->assertTrue( in_array( $this->editor1->ID, $linked_account_ids, true ) ); + } + + /** + * Checks all of the user accounts that have been linked using cache. + * + * @covers CoAuthors_Guest_Authors::get_all_linked_accounts() + */ + public function test_get_all_linked_accounts_with_cache() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + $cache_key = 'all-linked-accounts'; + + // Checks when guest author does not exist in cache. + $this->assertFalse( wp_cache_get( $cache_key, $guest_author_obj::$cache_group ) ); + + // Checks when guest author exists in cache. + $guest_author_obj->create_guest_author_from_user_id( $this->editor1->ID ); + + $linked_accounts = $guest_author_obj->get_all_linked_accounts(); + $linked_accounts_cache = wp_cache_get( $cache_key, $guest_author_obj::$cache_group ); + + $this->assertEquals( $linked_accounts, $linked_accounts_cache ); + } + + /** + * Checks guest author from an existing WordPress user. + * + * @covers CoAuthors_Guest_Authors::create_guest_author_from_user_id() + */ + public function test_create_guest_author_from_user_id() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Checks create guest author when user don't exist. + $response = $guest_author_obj->create_guest_author_from_user_id( 0 ); + + $this->assertInstanceOf( 'WP_Error', $response ); + $this->assertEquals( 'invalid-user', $response->get_error_code() ); + + // Checks create guest author when user exist. + $guest_author_id = $guest_author_obj->create_guest_author_from_user_id( $this->editor1->ID ); + $guest_author = $guest_author_obj->get_guest_author_by( 'ID', $guest_author_id ); + + $this->assertInstanceOf( stdClass::class, $guest_author ); + } + + /** + * Checks delete guest author action when $_POST args are not set. + * + * @covers CoAuthors_Guest_Authors::handle_delete_guest_author_action() + */ + public function test_handle_delete_guest_author_action_when_post_args_not_as_expected() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Checks when nothing is set. + $this->assertNull( $guest_author_obj->handle_delete_guest_author_action() ); + + // Back up $_POST. + $_post_backup = $_POST; + + // Checks when action is set but not expected. + $_POST['action'] = 'test'; + $_POST['id'] = $guest_author_obj->create_guest_author_from_user_id( $this->editor1->ID ); + + $this->assertNull( $guest_author_obj->handle_delete_guest_author_action() ); + + // Get guest author and check that is should not be removed. + $guest_author = $guest_author_obj->get_guest_author_by( 'ID', $_POST['id'] ); + + $this->assertNotEmpty( $guest_author ); + + // Checks when _wpnonce and id not set. + $_POST['action'] = 'delete-guest-author'; + $_POST['reassign'] = 'test'; + + $this->assertNull( $guest_author_obj->handle_delete_guest_author_action() ); + + // Get guest author and check that is should not be removed. + $guest_author = $guest_author_obj->get_guest_author_by( 'ID', $_POST['id'] ); + + $this->assertNotEmpty( $guest_author ); + + // Checks when all args set for $_POST but action is not as expected. + $_POST['action'] = 'test'; + $_POST['reassign'] = 'test'; + $_POST['_wpnonce'] = wp_create_nonce( 'delete-guest-author-1' ); + + $this->assertNull( $guest_author_obj->handle_delete_guest_author_action() ); + + // Get guest author and check that is should not be removed. + $guest_author = $guest_author_obj->get_guest_author_by( 'ID', $_POST['id'] ); + + $this->assertNotEmpty( $guest_author ); + + // Restore $_POST from back up. + $_POST = $_post_backup; + } + + /** + * Checks delete guest author action with nonce. + * + * @covers CoAuthors_Guest_Authors::handle_delete_guest_author_action() + */ + public function test_handle_delete_guest_author_action_with_nonce() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Back up $_POST. + $_post_backup = $_POST; + + $expected = __( "Doin' something fishy, huh?", 'co-authors-plus' ); + + $_POST['action'] = 'delete-guest-author'; + $_POST['reassign'] = 'test'; + $_POST['id'] = '0'; + + // Checks when nonce is not as expected. + $_POST['_wpnonce'] = wp_create_nonce( 'delete-guest-author-1' ); + + try { + $guest_author_obj->handle_delete_guest_author_action(); + } catch ( Exception $e ) { + $exception = $e; + } + + $this->assertInstanceOf( 'WPDieException', $exception ); + $this->assertContains( esc_html( $expected ), $exception->getMessage() ); + + // Checks when nonce is as expected. + $_POST['_wpnonce'] = wp_create_nonce( 'delete-guest-author' ); + + try { + $guest_author_obj->handle_delete_guest_author_action(); + } catch ( Exception $e ) { + $exception = $e; + } + + $this->assertNotContains( esc_html( $expected ), $exception->getMessage() ); + + // Restore $_POST from back up. + $_POST = $_post_backup; + } + + /** + * Checks delete guest author action with list_author capability. + * + * @covers CoAuthors_Guest_Authors::handle_delete_guest_author_action() + */ + public function test_handle_delete_guest_author_action_with_list_users_capability() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Back up $_POST. + $_post_backup = $_POST; + + $expected = __( "You don't have permission to perform this action.", 'co-authors-plus' ); + + // Back up current user. + $current_user = get_current_user_id(); + + wp_set_current_user( $this->editor1->ID ); + + $_POST['action'] = 'delete-guest-author'; + $_POST['reassign'] = 'test'; + + // Checks when current user can not have list_users capability. + $_POST['_wpnonce'] = wp_create_nonce( 'delete-guest-author' ); + $_POST['id'] = $guest_author_obj->create_guest_author_from_user_id( $this->editor1->ID ); + + try { + $guest_author_obj->handle_delete_guest_author_action(); + } catch ( Exception $e ) { + $exception = $e; + } + + $this->assertInstanceOf( 'WPDieException', $exception ); + $this->assertContains( esc_html( $expected ), $exception->getMessage() ); + + // Checks when current user has list_users capability. + wp_set_current_user( $this->admin1->ID ); + + $_POST['_wpnonce'] = wp_create_nonce( 'delete-guest-author' ); + $_POST['id'] = $guest_author_obj->create_guest_author_from_user_id( $this->admin1->ID ); + + try { + $guest_author_obj->handle_delete_guest_author_action(); + } catch ( Exception $e ) { + $exception = $e; + } + + $this->assertNotContains( esc_html( $expected ), $exception->getMessage() ); + + // Restore current user from backup. + wp_set_current_user( $current_user ); + + // Restore $_POST from back up. + $_POST = $_post_backup; + } + + /** + * Checks delete guest author action with guest author. + * + * @covers CoAuthors_Guest_Authors::handle_delete_guest_author_action() + */ + public function test_handle_delete_guest_author_action_with_guest_author_existence() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Back up $_POST. + $_post_backup = $_POST; + + $expected = sprintf( __( "%s can't be deleted because it doesn't exist.", 'co-authors-plus' ), $guest_author_obj->labels['singular'] ); + + // Back up current user. + $current_user = get_current_user_id(); + + wp_set_current_user( $this->admin1->ID ); + + $_POST['action'] = 'delete-guest-author'; + $_POST['reassign'] = 'test'; + $_POST['_wpnonce'] = wp_create_nonce( 'delete-guest-author' ); + $_POST['id'] = $this->admin1->ID; + + // Checks when guest author does not exist. + try { + $guest_author_obj->handle_delete_guest_author_action(); + } catch ( Exception $e ) { + $exception = $e; + } + + $this->assertInstanceOf( 'WPDieException', $exception ); + $this->assertContains( esc_html( $expected ), $exception->getMessage() ); + + // Checks when guest author exists. + $_POST['id'] = $guest_author_obj->create_guest_author_from_user_id( $this->admin1->ID ); + + try { + $guest_author_obj->handle_delete_guest_author_action(); + } catch ( Exception $e ) { + $exception = $e; + } + + $this->assertNotContains( esc_html( $expected ), $exception->getMessage() ); + + // Restore current user from backup. + wp_set_current_user( $current_user ); + + // Restore $_POST from back up. + $_POST = $_post_backup; + } + + /** + * Checks delete guest author action with reassign not as expected. + * + * @covers CoAuthors_Guest_Authors::handle_delete_guest_author_action() + */ + public function test_handle_delete_guest_author_action_with_reassign_not_as_expected() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Back up $_POST. + $_post_backup = $_POST; + + $expected = __( 'Please make sure to pick an option.', 'co-authors-plus' ); + + // Back up current user. + $current_user = get_current_user_id(); + + wp_set_current_user( $this->admin1->ID ); + + $_POST['action'] = 'delete-guest-author'; + $_POST['_wpnonce'] = wp_create_nonce( 'delete-guest-author' ); + $_POST['id'] = $guest_author_obj->create_guest_author_from_user_id( $this->admin1->ID ); + + // Checks when reassign is not as expected. + $_POST['reassign'] = 'test'; + + try { + $guest_author_obj->handle_delete_guest_author_action(); + } catch ( Exception $e ) { + $exception = $e; + } + + $this->assertInstanceOf( 'WPDieException', $exception ); + $this->assertContains( esc_html( $expected ), $exception->getMessage() ); + + // Restore current user from backup. + wp_set_current_user( $current_user ); + + // Restore $_POST from back up. + $_POST = $_post_backup; + } + + /** + * Checks delete guest author action when reassign is leave-assigned. + * + * @covers CoAuthors_Guest_Authors::handle_delete_guest_author_action() + */ + public function test_handle_delete_guest_author_action_with_reassign_is_leave_assigned() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Back up $_POST. + $_post_backup = $_POST; + + // Back up current user. + $current_user = get_current_user_id(); + + wp_set_current_user( $this->admin1->ID ); + + $_POST['action'] = 'delete-guest-author'; + $_POST['_wpnonce'] = wp_create_nonce( 'delete-guest-author' ); + $_POST['id'] = $guest_author_obj->create_guest_author_from_user_id( $this->admin1->ID ); + $_POST['reassign'] = 'leave-assigned'; + + add_filter( 'wp_redirect', array( $this, 'catch_redirect_destination' ), 99, 2 ); + + try { + + $guest_author_obj->handle_delete_guest_author_action(); + + } catch( Exception $e ) { + + $this->assertContains( $guest_author_obj->parent_page, $e->getMessage() ); + $this->assertContains( 'page=view-guest-authors', $e->getMessage() ); + $this->assertContains( 'message=guest-author-deleted', $e->getMessage() ); + } + + remove_filter( 'wp_redirect', array( $this, 'catch_redirect_destination' ), 99 ); + + // Restore current user from backup. + wp_set_current_user( $current_user ); + + // Restore $_POST from back up. + $_POST = $_post_backup; + } + + /** + * Checks delete guest author action when reassign is reassign-another. + * + * @covers CoAuthors_Guest_Authors::handle_delete_guest_author_action() + */ + public function test_handle_delete_guest_author_action_with_reassign_is_reassign_another() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Back up $_POST. + $_post_backup = $_POST; + + // Back up current user. + $current_user = get_current_user_id(); + + $expected = __( 'Co-author does not exists. Try again?', 'co-authors-plus' ); + + wp_set_current_user( $this->admin1->ID ); + + $_POST['action'] = 'delete-guest-author'; + $_POST['_wpnonce'] = wp_create_nonce( 'delete-guest-author' ); + $_POST['id'] = $guest_author_obj->create_guest_author_from_user_id( $this->admin1->ID ); + $_POST['reassign'] = 'reassign-another'; + + // When coauthor does not exist. + $_POST['leave-assigned-to'] = 'test'; + + try { + $guest_author_obj->handle_delete_guest_author_action(); + } catch ( Exception $e ) { + $exception = $e; + } + + $this->assertInstanceOf( 'WPDieException', $exception ); + $this->assertContains( esc_html( $expected ), $exception->getMessage() ); + + // When coauthor exists. + $_POST['leave-assigned-to'] = $this->author1->user_nicename; + + add_filter( 'wp_redirect', array( $this, 'catch_redirect_destination' ), 99, 2 ); + + try { + + $guest_author_obj->handle_delete_guest_author_action(); + + } catch ( Exception $e ) { + + //$this->assertContains( $guest_author_obj->parent_page, $e->getMessage() ); + $this->assertContains( 'page=view-guest-authors', $e->getMessage() ); + $this->assertContains( 'message=guest-author-deleted', $e->getMessage() ); + } + + remove_filter( 'wp_redirect', array( $this, 'catch_redirect_destination' ), 99 ); + + // Restore current user from backup. + wp_set_current_user( $current_user ); + + // Restore $_POST from back up. + $_POST = $_post_backup; + } + + /** + * Checks delete guest author action when reassign is remove-byline. + * + * @covers CoAuthors_Guest_Authors::handle_delete_guest_author_action() + */ + public function test_handle_delete_guest_author_action_with_reassign_is_remove_byline() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Back up $_POST. + $_post_backup = $_POST; + + // Back up current user. + $current_user = get_current_user_id(); + + wp_set_current_user( $this->admin1->ID ); + + $_POST['action'] = 'delete-guest-author'; + $_POST['_wpnonce'] = wp_create_nonce( 'delete-guest-author' ); + $_POST['id'] = $guest_author_obj->create_guest_author_from_user_id( $this->admin1->ID ); + $_POST['reassign'] = 'remove-byline'; + + add_filter( 'wp_redirect', array( $this, 'catch_redirect_destination' ), 99, 2 ); + + try { + + $guest_author_obj->handle_delete_guest_author_action(); + + } catch ( Exception $e ) { + + $this->assertContains( $guest_author_obj->parent_page, $e->getMessage() ); + $this->assertContains( 'page=view-guest-authors', $e->getMessage() ); + $this->assertContains( 'message=guest-author-deleted', $e->getMessage() ); + } + + remove_filter( 'wp_redirect', array( $this, 'catch_redirect_destination' ), 99 ); + + // Restore current user from backup. + wp_set_current_user( $current_user ); + + // Restore $_POST from back up. + $_POST = $_post_backup; + } + + /** + * To catch any redirection and throw location and status in Exception. + * + * Note : Destination location can be get from Exception Message and + * status can be get from Exception code. + * + * @param string $location Redirected location. + * @param int $status Status. + * + * @throws \Exception Redirection data. + * + * @return void + **/ + public function catch_redirect_destination( $location, $status ) { + + throw new Exception( $location, $status ); + } + + /** + * Checks delete guest author when he/she does not exist. + * + * @covers CoAuthors_Guest_Authors::delete() + */ + public function test_delete_when_guest_author_not_exist() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + $response = $guest_author_obj->delete( $this->admin1->ID ); + + $this->assertInstanceOf( 'WP_Error', $response ); + $this->assertEquals( 'guest-author-missing', $response->get_error_code() ); + } + + /** + * Checks delete guest author without reassign author. + * + * @covers CoAuthors_Guest_Authors::delete() + */ + public function test_delete_without_reassign() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + $author2 = $this->factory->user->create_and_get(); + $guest_author_id = $guest_author_obj->create_guest_author_from_user_id( $author2->ID ); + $guest_author = $guest_author_obj->get_guest_author_by( 'ID', $guest_author_id ); + $guest_author_term = $coauthors_plus->get_author_term( $guest_author ); + + $response = $guest_author_obj->delete( $guest_author_id ); + + $this->assertTrue( $response ); + $this->assertFalse( get_term_by( 'id', $guest_author_term->term_id, $coauthors_plus->coauthor_taxonomy ) ); + $this->assertNull( get_post( $guest_author_id ) ); + } + + /** + * Checks delete guest author with reassign author but he/she does not exist. + * + * @covers CoAuthors_Guest_Authors::delete() + */ + public function test_delete_with_reassign_author_not_exist() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + // Checks when reassign author is not exist. + $author2 = $this->factory->user->create_and_get(); + $guest_author_id = $guest_author_obj->create_guest_author_from_user_id( $author2->ID ); + + $response = $guest_author_obj->delete( $guest_author_id, 'test' ); + + $this->assertInstanceOf( 'WP_Error', $response ); + $this->assertEquals( 'reassign-to-missing', $response->get_error_code() ); + } + + /** + * Checks delete guest author with reassign author when linked account and author are same user. + * + * @covers CoAuthors_Guest_Authors::delete() + */ + public function test_delete_with_reassign_when_linked_account_and_author_are_same_user() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + $author2 = $this->factory->user->create_and_get(); + $guest_author2_id = $guest_author_obj->create_guest_author_from_user_id( $author2->ID ); + $guest_author2 = $guest_author_obj->get_guest_author_by( 'ID', $guest_author2_id ); + $guest_author2_term = $coauthors_plus->get_author_term( $guest_author2 ); + + $response = $guest_author_obj->delete( $guest_author2_id, $guest_author2->linked_account ); + + $this->assertTrue( $response ); + $this->assertNotEmpty( get_term_by( 'id', $guest_author2_term->term_id, $coauthors_plus->coauthor_taxonomy ) ); + $this->assertNull( get_post( $guest_author2_id ) ); + } + + /** + * Checks delete guest author with reassign author when linked account and author are different user. + * + * @covers CoAuthors_Guest_Authors::delete() + */ + public function test_delete_with_reassign_when_linked_account_and_author_are_different_user() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + $guest_admin_id = $guest_author_obj->create_guest_author_from_user_id( $this->admin1->ID ); + $guest_admin = $guest_author_obj->get_guest_author_by( 'ID', $guest_admin_id ); + + $author2 = $this->factory->user->create_and_get(); + $guest_author_id2 = $guest_author_obj->create_guest_author_from_user_id( $author2->ID ); + $guest_author2 = $guest_author_obj->get_guest_author_by( 'ID', $guest_author_id2 ); + $guest_author_term2 = $coauthors_plus->get_author_term( $guest_author2 ); + + $post = $this->factory->post->create_and_get( array( + 'post_author' => $author2->ID, + ) ); + + $response = $guest_author_obj->delete( $guest_author_id2, $guest_admin->linked_account ); + + // Checks post author, it should be reassigned to new author. + $this->assertEquals( array( $guest_admin->linked_account ), wp_list_pluck( get_coauthors( $post->ID ), 'linked_account' ) ); + $this->assertTrue( $response ); + $this->assertFalse( get_term_by( 'id', $guest_author_term2->term_id, $coauthors_plus->coauthor_taxonomy ) ); + $this->assertNull( get_post( $guest_author_id2 ) ); + } + + /** + * Checks delete guest author with reassign author and without linked account and author is the same user. + * + * @covers CoAuthors_Guest_Authors::delete() + */ + public function test_delete_with_reassign_without_linked_account_and_author_is_same_user() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + $guest_author_id = $guest_author_obj->create( array( + 'user_login' => 'guest_author', + 'display_name' => 'guest_author', + ) ); + $guest_author = $guest_author_obj->get_guest_author_by( 'ID', $guest_author_id ); + $guest_author_term = $coauthors_plus->get_author_term( $guest_author ); + + $response = $guest_author_obj->delete( $guest_author_id, $guest_author->user_login ); + + $this->assertTrue( $response ); + $this->assertNotEmpty( get_term_by( 'id', $guest_author_term->term_id, $coauthors_plus->coauthor_taxonomy ) ); + $this->assertNull( get_post( $guest_author_id ) ); + } + + /** + * Checks delete guest author with reassign author and without linked account and author is other user. + * + * @covers CoAuthors_Guest_Authors::delete() + */ + public function test_delete_with_reassign_without_linked_account_and_author_is_other_user() { + + global $coauthors_plus; + + $guest_author_obj = $coauthors_plus->guest_authors; + + $guest_admin_id = $guest_author_obj->create_guest_author_from_user_id( $this->admin1->ID ); + $guest_admin = $guest_author_obj->get_guest_author_by( 'ID', $guest_admin_id ); + + $guest_author_id = $guest_author_obj->create( array( + 'user_login' => 'guest_author', + 'display_name' => 'guest_author', + ) ); + $guest_author = $guest_author_obj->get_guest_author_by( 'ID', $guest_author_id ); + $guest_author_term = $coauthors_plus->get_author_term( $guest_author ); + + $response = $guest_author_obj->delete( $guest_author_id, $guest_admin->user_login ); + + $this->assertTrue( $response ); + $this->assertFalse( get_term_by( 'id', $guest_author_term->term_id, $coauthors_plus->coauthor_taxonomy ) ); + $this->assertNull( get_post( $guest_author_id ) ); + } +} diff --git a/tests/test-coauthors-plus.php b/tests/test-coauthors-plus.php new file mode 100644 index 00000000..44b6f106 --- /dev/null +++ b/tests/test-coauthors-plus.php @@ -0,0 +1,679 @@ +author1 = $this->factory->user->create_and_get( array( 'role' => 'author', 'user_login' => 'author1' ) ); + $this->editor1 = $this->factory->user->create_and_get( array( 'role' => 'editor', 'user_login' => 'editor1' ) ); + + $this->post = $this->factory->post->create_and_get( array( + 'post_author' => $this->author1->ID, + 'post_status' => 'publish', + 'post_content' => rand_str(), + 'post_title' => rand_str(), + 'post_type' => 'post', + ) ); + } + + /** + * Checks whether the guest authors functionality is enabled or not. + * + * @covers CoAuthors_Plus::is_guest_authors_enabled() + */ + public function test_is_guest_authors_enabled() { + + global $coauthors_plus; + + $this->assertTrue( $coauthors_plus->is_guest_authors_enabled() ); + + add_filter( 'coauthors_guest_authors_enabled', '__return_false' ); + + $this->assertFalse( $coauthors_plus->is_guest_authors_enabled() ); + + remove_filter( 'coauthors_guest_authors_enabled', '__return_false' ); + + $this->assertTrue( $coauthors_plus->is_guest_authors_enabled() ); + } + + /** + * Checks coauthor object when he/she is a guest author. + * + * @covers CoAuthors_Plus::get_coauthor_by() + */ + public function test_get_coauthor_by_when_guest_author() { + + global $coauthors_plus; + + $guest_author_id = $coauthors_plus->guest_authors->create( array( + 'user_login' => 'author2', + 'display_name' => 'author2', + ) ); + + $coauthor = $coauthors_plus->get_coauthor_by( 'id', $guest_author_id ); + + $this->assertInstanceOf( stdClass::class, $coauthor ); + $this->assertObjectHasAttribute( 'ID', $coauthor ); + $this->assertEquals( $guest_author_id, $coauthor->ID ); + $this->assertEquals( 'guest-author', $coauthor->type ); + } + + /** + * Checks coauthor object when he/she is a wp author. + * + * @covers CoAuthors_Plus::get_coauthor_by() + */ + public function test_get_coauthor_by_when_guest_authors_not_enabled() { + + global $coauthors_plus; + + add_filter( 'coauthors_guest_authors_enabled', '__return_false' ); + + $this->assertFalse( $coauthors_plus->get_coauthor_by( '', '' ) ); + + $coauthor = $coauthors_plus->get_coauthor_by( 'id', $this->author1->ID ); + + $this->assertInstanceOf( WP_User::class, $coauthor ); + $this->assertObjectHasAttribute( 'ID', $coauthor ); + $this->assertEquals( $this->author1->ID, $coauthor->ID ); + $this->assertEquals( 'wpuser', $coauthor->type ); + + $coauthor = $coauthors_plus->get_coauthor_by( 'user_login', $this->author1->user_login ); + + $this->assertInstanceOf( WP_User::class, $coauthor ); + $this->assertObjectHasAttribute( 'user_login', $coauthor->data ); + $this->assertEquals( $this->author1->user_login, $coauthor->user_login ); + + $coauthor = $coauthors_plus->get_coauthor_by( 'user_nicename', $this->author1->user_nicename ); + + $this->assertInstanceOf( WP_User::class, $coauthor ); + $this->assertObjectHasAttribute( 'user_nicename', $coauthor->data ); + $this->assertEquals( $this->author1->user_nicename, $coauthor->user_nicename ); + + $coauthor = $coauthors_plus->get_coauthor_by( 'user_email', $this->author1->user_email ); + + $this->assertInstanceOf( WP_User::class, $coauthor ); + $this->assertObjectHasAttribute( 'user_email', $coauthor->data ); + $this->assertEquals( $this->author1->user_email, $coauthor->user_email ); + + remove_filter( 'coauthors_guest_authors_enabled', '__return_false' ); + + $coauthors_plus->guest_authors->create_guest_author_from_user_id( $this->editor1->ID ); + + $coauthor = $coauthors_plus->get_coauthor_by( 'id', $this->editor1->ID ); + + $this->assertInstanceOf( stdClass::class, $coauthor ); + $this->assertObjectHasAttribute( 'linked_account', $coauthor ); + $this->assertEquals( $this->editor1->user_login, $coauthor->linked_account ); + } + + /** + * Checks coauthors plus is enabled for this post type. + * + * @covers CoAuthors_Plus::is_post_type_enabled() + */ + public function test_is_post_type_enabled() { + + global $coauthors_plus, $post; + + // Backing up global post. + $post_backup = $post; + + // Checks when post type is null. + $this->assertFalse( $coauthors_plus->is_post_type_enabled() ); + + // Checks when post type is post. + $this->assertTrue( $coauthors_plus->is_post_type_enabled( 'post' ) ); + + // Checks when post type is page. + $this->assertTrue( $coauthors_plus->is_post_type_enabled( 'page' ) ); + + // Checks when post type is attachment. + $this->assertFalse( $coauthors_plus->is_post_type_enabled( 'attachment' ) ); + + // Checks when post type is revision. + $this->assertFalse( $coauthors_plus->is_post_type_enabled( 'revision' ) ); + + $post = $this->post; + + // Checks when post type set using global post. + $this->assertTrue( $coauthors_plus->is_post_type_enabled() ); + + $post = ''; + $screen = get_current_screen(); + + // Set the edit post current screen. + set_current_screen( 'edit-post' ); + $this->assertTrue( $coauthors_plus->is_post_type_enabled() ); + + $GLOBALS['current_screen'] = $screen; + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks if the current user can set co-authors or not using current screen. + * + * @covers CoAuthors_Plus::current_user_can_set_authors() + */ + public function test_current_user_can_set_authors_using_current_screen() { + + global $coauthors_plus; + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors() ); + + $screen = get_current_screen(); + + // Set the edit post current screen. + set_current_screen( 'edit-post' ); + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors() ); + + $GLOBALS['current_screen'] = $screen; + + // Backing up current user. + $current_user = get_current_user_id(); + + // Checks when current user is author. + wp_set_current_user( $this->author1->ID ); + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors() ); + + set_current_screen( 'edit-post' ); + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors() ); + + $GLOBALS['current_screen'] = $screen; + + // Checks when current user is editor. + wp_set_current_user( $this->editor1->ID ); + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors() ); + + set_current_screen( 'edit-post' ); + + $this->assertTrue( $coauthors_plus->current_user_can_set_authors() ); + + $GLOBALS['current_screen'] = $screen; + + // Checks when current user is admin. + $admin1 = $this->factory->user->create_and_get( array( + 'role' => 'administrator', + ) ); + + wp_set_current_user( $admin1->ID ); + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors() ); + + set_current_screen( 'edit-post' ); + + $this->assertTrue( $coauthors_plus->current_user_can_set_authors() ); + + $GLOBALS['current_screen'] = $screen; + + // Restore current user from backup. + wp_set_current_user( $current_user ); + } + + /** + * Checks if the current user can set co-authors or not using global post. + * + * @covers CoAuthors_Plus::current_user_can_set_authors() + */ + public function test_current_user_can_set_authors_using_global_post() { + + global $coauthors_plus, $post; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors() ); + + // Backing up current user. + $current_user = get_current_user_id(); + + // Checks when current user is author. + wp_set_current_user( $this->author1->ID ); + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors() ); + + // Checks when current user is editor. + wp_set_current_user( $this->editor1->ID ); + + $this->assertTrue( $coauthors_plus->current_user_can_set_authors() ); + + // Checks when current user is super admin. + $admin1 = $this->factory->user->create_and_get( array( + 'role' => 'administrator', + ) ); + + grant_super_admin( $admin1->ID ); + wp_set_current_user( $admin1->ID ); + + $this->assertTrue( $coauthors_plus->current_user_can_set_authors() ); + + // Restore current user from backup. + wp_set_current_user( $current_user ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks if the current user can set co-authors or not using normal post. + * + * @covers CoAuthors_Plus::current_user_can_set_authors() + */ + public function test_current_user_can_set_authors_using_normal_post() { + + global $coauthors_plus; + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors( $this->post ) ); + + // Backing up current user. + $current_user = get_current_user_id(); + + // Checks when current user is author. + wp_set_current_user( $this->author1->ID ); + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors( $this->post ) ); + + // Checks when current user is editor. + wp_set_current_user( $this->editor1->ID ); + + $this->assertTrue( $coauthors_plus->current_user_can_set_authors( $this->post ) ); + + // Checks when current user is super admin. + $admin1 = $this->factory->user->create_and_get( array( + 'role' => 'administrator', + ) ); + + grant_super_admin( $admin1->ID ); + wp_set_current_user( $admin1->ID ); + + $this->assertTrue( $coauthors_plus->current_user_can_set_authors( $this->post ) ); + + // Restore current user from backup. + wp_set_current_user( $current_user ); + } + + /** + * Checks if the current user can set co-authors or not using coauthors_plus_edit_authors filter. + * + * @covers CoAuthors_Plus::current_user_can_set_authors() + */ + public function test_current_user_can_set_authors_using_coauthors_plus_edit_authors_filter() { + + global $coauthors_plus; + + // Backing up current user. + $current_user = get_current_user_id(); + + // Checking when current user is subscriber and filter is true/false. + $subscriber1 = $this->factory->user->create_and_get( array( + 'role' => 'subscriber', + ) ); + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors( $this->post ) ); + + add_filter( 'coauthors_plus_edit_authors', '__return_true' ); + + $this->assertTrue( $coauthors_plus->current_user_can_set_authors( $this->post ) ); + + remove_filter( 'coauthors_plus_edit_authors', '__return_true' ); + + // Checks when current user is editor. + wp_set_current_user( $this->editor1->ID ); + + $this->assertTrue( $coauthors_plus->current_user_can_set_authors( $this->post ) ); + + add_filter( 'coauthors_plus_edit_authors', '__return_false' ); + + $this->assertFalse( $coauthors_plus->current_user_can_set_authors( $this->post ) ); + + remove_filter( 'coauthors_plus_edit_authors', '__return_false' ); + + // Restore current user from backup. + wp_set_current_user( $current_user ); + } + + /** + * Checks matching co-authors based on a search value when no arguments provided. + * + * @covers CoAuthors_Plus::search_authors() + */ + public function test_search_authors_no_args() { + + global $coauthors_plus; + + // Checks when search term is empty. + $authors = $coauthors_plus->search_authors(); + + $this->assertNotEmpty( $authors ); + $this->assertArrayHasKey( 'admin', $authors ); + $this->assertArrayHasKey( $this->author1->user_login, $authors ); + $this->assertArrayHasKey( $this->editor1->user_login, $authors ); + + // Checks when search term is empty and any subscriber exists. + $subscriber1 = $this->factory->user->create_and_get( array( + 'role' => 'subscriber', + ) ); + + $authors = $coauthors_plus->search_authors(); + + $this->assertNotEmpty( $authors ); + $this->assertArrayNotHasKey( $subscriber1->user_login, $authors ); + + // Checks when search term is empty and any contributor exists. + $contributor1 = $this->factory->user->create_and_get( array( + 'role' => 'contributor', + ) ); + + $authors = $coauthors_plus->search_authors(); + + $this->assertNotEmpty( $authors ); + $this->assertArrayHasKey( $contributor1->user_login, $authors ); + } + + /** + * Checks matching co-authors based on a search value when only search keyword is provided. + * + * @covers CoAuthors_Plus::search_authors() + */ + public function test_search_authors_when_search_keyword_provided() { + + global $coauthors_plus; + + // Checks when author does not exist with searched term. + $this->assertEmpty( $coauthors_plus->search_authors( 'test' ) ); + + // Checks when author searched using ID. + $authors = $coauthors_plus->search_authors( $this->author1->ID ); + + $this->assertNotEmpty( $authors ); + $this->assertArrayHasKey( $this->author1->user_login, $authors ); + $this->assertArrayNotHasKey( $this->editor1->user_login, $authors ); + $this->assertArrayNotHasKey( 'admin', $authors ); + + // Checks when author searched using display_name. + $authors = $coauthors_plus->search_authors( $this->author1->display_name ); + + $this->assertNotEmpty( $authors ); + $this->assertArrayHasKey( $this->author1->user_login, $authors ); + $this->assertArrayNotHasKey( $this->editor1->user_login, $authors ); + $this->assertArrayNotHasKey( 'admin', $authors ); + + // Checks when author searched using user_email. + $authors = $coauthors_plus->search_authors( $this->author1->user_email ); + + $this->assertNotEmpty( $authors ); + $this->assertArrayHasKey( $this->author1->user_login, $authors ); + $this->assertArrayNotHasKey( $this->editor1->user_login, $authors ); + $this->assertArrayNotHasKey( 'admin', $authors ); + + // Checks when author searched using user_login. + $authors = $coauthors_plus->search_authors( $this->author1->user_login ); + + $this->assertNotEmpty( $authors ); + $this->assertArrayHasKey( $this->author1->user_login, $authors ); + $this->assertArrayNotHasKey( $this->editor1->user_login, $authors ); + $this->assertArrayNotHasKey( 'admin', $authors ); + + // Checks when any subscriber exists using ID but not author. + $subscriber1 = $this->factory->user->create_and_get( array( + 'role' => 'subscriber', + ) ); + + $this->assertEmpty( $coauthors_plus->search_authors( $subscriber1->ID ) ); + } + + /** + * Checks matching co-authors based on a search value when only ignore authors are provided. + * + * @covers CoAuthors_Plus::search_authors() + */ + public function test_search_authors_when_ignored_authors_provided() { + + global $coauthors_plus; + + // Ignoring single author. + $ignored_authors = array( $this->author1->user_nicename ); + + $authors = $coauthors_plus->search_authors( '', $ignored_authors ); + + $this->assertNotEmpty( $authors ); + $this->assertArrayNotHasKey( $this->author1->user_login, $authors ); + + // Checks when ignoring author1 but also exists one more author with similar kind of data. + $author2 = $this->factory->user->create_and_get( array( + 'role' => 'author', + ) ); + + $authors = $coauthors_plus->search_authors( '', $ignored_authors ); + + $this->assertNotEmpty( $authors ); + $this->assertArrayNotHasKey( $this->author1->user_login, $authors ); + $this->assertArrayHasKey( $author2->user_login, $authors ); + + // Ignoring multiple authors. + $authors = $coauthors_plus->search_authors( '', array( $this->author1->user_nicename, $author2->user_nicename ) ); + + $this->assertNotEmpty( $authors ); + $this->assertArrayNotHasKey( $this->author1->user_login, $authors ); + $this->assertArrayNotHasKey( $author2->user_login, $authors ); + } + + /** + * Checks matching co-authors based on a search value when search keyword as well as ignore authors are provided. + * + * @covers CoAuthors_Plus::search_authors() + */ + public function test_search_authors_when_search_keyword_and_ignored_authors_provided() { + + global $coauthors_plus; + + // Checks when ignoring author1. + $ignored_authors = array( $this->author1->user_nicename ); + + $this->assertEmpty( $coauthors_plus->search_authors( $this->author1->ID, $ignored_authors ) ); + + // Checks when ignoring author1 but also exists one more author with similar kind of data. + $author2 = $this->factory->user->create_and_get( array( + 'role' => 'author', + 'user_login' => 'author2', + ) ); + + $authors = $coauthors_plus->search_authors( 'author', $ignored_authors ); + + $this->assertNotEmpty( $authors ); + $this->assertArrayNotHasKey( $this->author1->user_login, $authors ); + $this->assertArrayHasKey( $author2->user_login, $authors ); + } + + /** + * Checks the author term for a given co-author when passed coauthor is not an object. + * + * @covers CoAuthors_Plus::get_author_term() + */ + public function test_get_author_term_when_coauthor_is_not_object() { + + global $coauthors_plus; + + $this->assertEmpty( $coauthors_plus->get_author_term( '' ) ); + $this->assertEmpty( $coauthors_plus->get_author_term( $this->author1->ID ) ); + $this->assertEmpty( $coauthors_plus->get_author_term( (array) $this->author1 ) ); + } + + /** + * Checks the author term for a given co-author using cache. + * + * @covers CoAuthors_Plus::get_author_term() + */ + public function test_get_author_term_using_caching() { + + global $coauthors_plus; + + $cache_key = 'author-term-' . $this->author1->user_nicename; + + // Checks when term does not exist in cache. + $this->assertFalse( wp_cache_get( $cache_key, 'co-authors-plus' ) ); + + // Checks when term exists in cache. + $author_term = $coauthors_plus->get_author_term( $this->author1 ); + $author_term_cached = wp_cache_get( $cache_key, 'co-authors-plus' ); + + $this->assertInstanceOf( WP_Term::class, $author_term ); + $this->assertEquals( $author_term, $author_term_cached ); + } + + /** + * Checks the author term for a given co-author with having linked account. + * + * @covers CoAuthors_Plus::get_author_term() + */ + public function test_get_author_term_when_author_has_linked_account() { + + global $coauthors_plus; + + // Checks when term exists using linked account. + $coauthor_id = $coauthors_plus->guest_authors->create_guest_author_from_user_id( $this->editor1->ID ); + $coauthor = $coauthors_plus->get_coauthor_by( 'id', $coauthor_id ); + + $author_term = $coauthors_plus->get_author_term( $coauthor ); + + $this->assertInstanceOf( WP_Term::class, $author_term ); + + // Checks when term does not exist or deleted somehow. + wp_delete_term( $author_term->term_id, $author_term->taxonomy ); + + $this->assertFalse( $coauthors_plus->get_author_term( $coauthor ) ); + } + + /** + * Checks the author term for a given co-author without having linked account. + * + * @covers CoAuthors_Plus::get_author_term() + */ + public function test_get_author_term_when_author_has_not_linked_account() { + + global $coauthors_plus; + + // Checks when term exists without linked account. + $coauthor_id = $coauthors_plus->guest_authors->create( array( + 'display_name' => 'guest', + 'user_login' => 'guest', + ) ); + $coauthor = $coauthors_plus->get_coauthor_by( 'id', $coauthor_id ); + + $author_term = $coauthors_plus->get_author_term( $coauthor ); + + $this->assertInstanceOf( WP_Term::class, $author_term ); + + // Checks when term does not exist or deleted somehow. + wp_delete_term( $author_term->term_id, $author_term->taxonomy ); + + $this->assertFalse( $coauthors_plus->get_author_term( $coauthor ) ); + } + + /** + * Checks update author term when passed coauthor is not an object. + * + * @covers CoAuthors_Plus::update_author_term() + */ + public function test_update_author_term_when_coauthor_is_not_object() { + + global $coauthors_plus; + + $this->assertEmpty( $coauthors_plus->update_author_term( '' ) ); + $this->assertEmpty( $coauthors_plus->update_author_term( $this->author1->ID ) ); + $this->assertEmpty( $coauthors_plus->update_author_term( (array) $this->author1 ) ); + } + + /** + * Checks update author term when author term exists for passed coauthor. + * + * @covers CoAuthors_Plus::update_author_term() + */ + public function test_update_author_term_when_author_term_exists() { + + global $coauthors_plus; + + // Checks term description. + $author_term = $coauthors_plus->update_author_term( $this->author1 ); + + // In "update_author_term()", only description is being updated, so asserting that only ( here and everywhere ). + $this->assertEquals( $this->author1->display_name . ' ' . $this->author1->first_name . ' ' . $this->author1->last_name . ' ' . $this->author1->user_login . ' ' . $this->author1->ID . ' ' . $this->author1->user_email, $author_term->description ); + + // Checks term description after updating user. + wp_update_user( array( + 'ID' => $this->author1->ID, + 'first_name' => 'author1', + ) ); + + $author_term = $coauthors_plus->update_author_term( $this->author1 ); + + $this->assertEquals( $this->author1->display_name . ' ' . $this->author1->first_name . ' ' . $this->author1->last_name . ' ' . $this->author1->user_login . ' ' . $this->author1->ID . ' ' . $this->author1->user_email, $author_term->description ); + + // Backup coauthor taxonomy. + $taxonomy_backup = $coauthors_plus->coauthor_taxonomy; + + wp_update_user( array( + 'ID' => $this->author1->ID, + 'last_name' => 'author1', + ) ); + + // Checks with different taxonomy. + $coauthors_plus->coauthor_taxonomy = 'abcd'; + + $this->assertFalse( $coauthors_plus->update_author_term( $this->author1 ) ); + + // Restore coauthor taxonomy from backup. + $coauthors_plus->coauthor_taxonomy = $taxonomy_backup; + } + + /** + * Checks update author term when author term does not exist for passed coauthor. + * + * @covers CoAuthors_Plus::update_author_term() + */ + public function test_update_author_term_when_author_term_not_exist() { + + global $coauthors_plus; + + // Checks term description. + $author_term = $coauthors_plus->update_author_term( $this->editor1 ); + + $this->assertEquals( $this->editor1->display_name . ' ' . $this->editor1->first_name . ' ' . $this->editor1->last_name . ' ' . $this->editor1->user_login . ' ' . $this->editor1->ID . ' ' . $this->editor1->user_email, $author_term->description ); + + // Checks term description after updating user. + wp_update_user( array( + 'ID' => $this->editor1->ID, + 'first_name' => 'editor1', + ) ); + + $author_term = $coauthors_plus->update_author_term( $this->editor1 ); + + $this->assertEquals( $this->editor1->display_name . ' ' . $this->editor1->first_name . ' ' . $this->editor1->last_name . ' ' . $this->editor1->user_login . ' ' . $this->editor1->ID . ' ' . $this->editor1->user_email, $author_term->description ); + + // Backup coauthor taxonomy. + $taxonomy_backup = $coauthors_plus->coauthor_taxonomy; + + wp_update_user( array( + 'ID' => $this->editor1->ID, + 'last_name' => 'editor1', + ) ); + + // Checks with different taxonomy. + $coauthors_plus->coauthor_taxonomy = 'abcd'; + + $this->assertFalse( $coauthors_plus->update_author_term( $this->editor1 ) ); + + // Restore coauthor taxonomy from backup. + $coauthors_plus->coauthor_taxonomy = $taxonomy_backup; + } +} diff --git a/tests/test-manage-coauthors.php b/tests/test-manage-coauthors.php index 1f5b1945..0bc9954c 100644 --- a/tests/test-manage-coauthors.php +++ b/tests/test-manage-coauthors.php @@ -2,6 +2,58 @@ class Test_Manage_CoAuthors extends CoAuthorsPlus_TestCase { + public function setUp() { + parent::setUp(); + + $this->admin1 = $this->factory->user->create( array( 'role' => 'administrator', 'user_login' => 'admin1' ) ); + $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 */ @@ -94,4 +146,283 @@ public function test_post_publish_count_for_coauthor() { $this->assertEquals( 1, count_user_posts( $editor1->ID ) ); } + + /** + * Returns data as it is when post type is not allowed. + * + * @see https://github.com/Automattic/Co-Authors-Plus/issues/198 + * + * @covers ::coauthors_set_post_author_field() + */ + public function test_coauthors_set_post_author_field_when_post_type_is_attachment() { + + global $coauthors_plus; + + $this->assertEquals( 10, has_filter( 'wp_insert_post_data', array( + $coauthors_plus, + 'coauthors_set_post_author_field', + ) ) ); + + $post_id = $this->factory->post->create( array( + 'post_author' => $this->author1, + 'post_type' => 'attachment', + ) ); + + $post = get_post( $post_id ); + + $data = $post_array = array( + 'ID' => $post->ID, + 'post_type' => $post->post_type, + 'post_author' => $post->post_author, + ); + + $new_data = $coauthors_plus->coauthors_set_post_author_field( $data, $post_array ); + + $this->assertEquals( $data, $new_data ); + } + + /** + * Compares data when coauthor is not set in the post array. + * + * @see https://github.com/Automattic/Co-Authors-Plus/issues/198 + * + * @covers ::coauthors_set_post_author_field() + */ + public function test_coauthors_set_post_author_field_when_coauthor_is_not_set() { + + global $coauthors_plus; + + $author1_post1 = get_post( $this->author1_post1 ); + + $data = $post_array = array( + 'ID' => $author1_post1->ID, + 'post_type' => $author1_post1->post_type, + 'post_author' => $author1_post1->post_author, + ); + + $new_data = $coauthors_plus->coauthors_set_post_author_field( $data, $post_array ); + + $this->assertEquals( $data, $new_data ); + } + + /** + * Compares data when coauthor is set in the post array. + * + * @see https://github.com/Automattic/Co-Authors-Plus/issues/198 + * + * @covers ::coauthors_set_post_author_field() + */ + public function test_coauthors_set_post_author_field_when_coauthor_is_set() { + + global $coauthors_plus; + + $user_id = $this->factory->user->create( array( + 'user_login' => 'test_admin', + 'user_nicename' => 'test_admiи', + ) ); + + $user = get_user_by( 'id', $user_id ); + + // Backing up global variables. + $post_backup = $_POST; + $request_backup = $_REQUEST; + + $_REQUEST['coauthors-nonce'] = wp_create_nonce( 'coauthors-edit' );; + $_POST['coauthors'] = array( + $user->user_nicename, + ); + + $post_id = $this->factory->post->create( array( + 'post_author' => $user_id, + ) ); + + $post = get_post( $post_id ); + + $data = $post_array = array( + 'ID' => $post->ID, + 'post_type' => $post->post_type, + 'post_author' => $post->post_author, + ); + + $new_data = $coauthors_plus->coauthors_set_post_author_field( $data, $post_array ); + + $this->assertEquals( $data, $new_data ); + + // Store global variables from backup. + $_POST = $post_backup; + $_REQUEST = $request_backup; + } + + /** + * Compares data when coauthor is set and it is linked with main wp user. + * + * @see https://github.com/Automattic/Co-Authors-Plus/issues/198 + * + * @covers ::coauthors_set_post_author_field() + */ + public function test_coauthors_set_post_author_field_when_guest_author_is_linked_with_wp_user() { + + global $coauthors_plus; + + $author1 = get_user_by( 'id', $this->author1 ); + + $author1_post1 = get_post( $this->author1_post1 ); + + $data = $post_array = array( + 'ID' => $author1_post1->ID, + 'post_type' => $author1_post1->post_type, + 'post_author' => $author1_post1->post_author, + ); + + // Backing up global variables. + $post_backup = $_POST; + $request_backup = $_REQUEST; + + $_REQUEST['coauthors-nonce'] = wp_create_nonce( 'coauthors-edit' );; + $_POST['coauthors'] = array( + $author1->user_nicename, + ); + + // Create guest author with linked account with user. + $coauthors_plus->guest_authors = new CoAuthors_Guest_Authors; + $coauthors_plus->guest_authors->create_guest_author_from_user_id( $this->author1 ); + + $new_data = $coauthors_plus->coauthors_set_post_author_field( $data, $post_array ); + + $this->assertEquals( $data, $new_data ); + + // Store global variables from backup. + $_POST = $post_backup; + $_REQUEST = $request_backup; + } + + /** + * Compares post author when it is not set in the main data array somehow. + * + * @see https://github.com/Automattic/Co-Authors-Plus/issues/198 + * + * @covers ::coauthors_set_post_author_field() + */ + public function test_coauthors_set_post_author_field_when_post_author_is_not_set() { + + global $coauthors_plus; + + wp_set_current_user( $this->author1 ); + + // Backing up global variables. + $post_backup = $_POST; + $request_backup = $_REQUEST; + + $_REQUEST = $_POST = array(); + + $author1_post1 = get_post( $this->author1_post1 ); + + $data = $post_array = array( + 'ID' => $author1_post1->ID, + 'post_type' => $author1_post1->post_type, + 'post_author' => $author1_post1->post_author, + ); + + unset( $data['post_author'] ); + + $new_data = $coauthors_plus->coauthors_set_post_author_field( $data, $post_array ); + + $this->assertEquals( $this->author1, $new_data['post_author'] ); + + // Store global variables from backup. + $_POST = $post_backup; + $_REQUEST = $request_backup; + } + + /** + * Bypass coauthors_update_post() when post type is not allowed. + * + * @see https://github.com/Automattic/Co-Authors-Plus/issues/198 + * + * @covers ::coauthors_update_post() + */ + public function test_coauthors_update_post_when_post_type_is_attachment() { + + global $coauthors_plus; + + $this->assertEquals( 10, has_action( 'save_post', array( + $coauthors_plus, + 'coauthors_update_post', + ) ) ); + + $post_id = $this->factory->post->create( array( + 'post_author' => $this->author1, + 'post_type' => 'attachment', + ) ); + + $post = get_post( $post_id ); + $return = $coauthors_plus->coauthors_update_post( $post_id, $post ); + + $this->assertNull( $return ); + } + + /** + * Checks coauthors when current user can set authors. + * + * @see https://github.com/Automattic/Co-Authors-Plus/issues/198 + * + * @covers ::coauthors_update_post() + */ + public function test_coauthors_update_post_when_current_user_can_set_authors() { + + global $coauthors_plus; + + wp_set_current_user( $this->admin1 ); + + $admin1 = get_user_by( 'id', $this->admin1 ); + $author1 = get_user_by( 'id', $this->author1 ); + + $post_id = $this->factory->post->create( array( + 'post_author' => $this->admin1, + ) ); + + $post = get_post( $post_id ); + + // Backing up global variables. + $post_backup = $_POST; + $request_backup = $_REQUEST; + + $_POST['coauthors-nonce'] = $_REQUEST['coauthors-nonce'] = wp_create_nonce( 'coauthors-edit' ); + $_POST['coauthors'] = array( + $admin1->user_nicename, + $author1->user_nicename, + ); + + $coauthors_plus->coauthors_update_post( $post_id, $post ); + + $coauthors = get_coauthors( $post_id ); + + $this->assertEquals( array( $this->admin1, $this->author1 ), wp_list_pluck( $coauthors, 'ID' ) ); + + // Store global variables from backup. + $_POST = $post_backup; + $_REQUEST = $request_backup; + } + + /** + * Coauthors should be empty if post does not have any author terms + * and current user can not set authors for the post. + * + * @see https://github.com/Automattic/Co-Authors-Plus/issues/198 + * + * @covers ::coauthors_update_post() + */ + public function test_coauthors_update_post_when_post_has_not_author_terms() { + + global $coauthors_plus; + + $post_id = $this->factory->post->create(); + $post = get_post( $post_id ); + + $coauthors_plus->coauthors_update_post( $post_id, $post ); + + $coauthors = get_coauthors( $post_id ); + + $this->assertEmpty( $coauthors ); + } } diff --git a/tests/test-template-tags.php b/tests/test-template-tags.php new file mode 100644 index 00000000..db04488c --- /dev/null +++ b/tests/test-template-tags.php @@ -0,0 +1,1071 @@ +author1 = $this->factory->user->create_and_get( array( 'role' => 'author', 'user_login' => 'author1' ) ); + $this->editor1 = $this->factory->user->create_and_get( array( 'role' => 'editor', 'user_login' => 'editor1' ) ); + + $this->post = $this->factory->post->create_and_get( array( + 'post_author' => $this->author1->ID, + 'post_status' => 'publish', + 'post_content' => rand_str(), + 'post_title' => rand_str(), + 'post_type' => 'post', + ) ); + } + + /** + * Tests for co-authors display names, with links to their posts. + * + * @see https://github.com/Automattic/Co-Authors-Plus/issues/279 + * + * @covers ::coauthors_posts_links() + */ + public function test_coauthors_posts_links() { + + global $coauthors_plus, $coauthors_plus_template_filters; + + // Backing up global post. + $post_backup = $GLOBALS['post']; + + $GLOBALS['post'] = $this->post; + + // Checks for single post author. + $single_cpl = coauthors_posts_links( null, null, null, null, false ); + + $this->assertContains( 'href="' . get_author_posts_url( $this->author1->ID, $this->author1->user_nicename ) . '"', $single_cpl, 'Author link not found.' ); + $this->assertContains( $this->author1->display_name, $single_cpl, 'Author name not found.' ); + + // Checks for multiple post author. + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $multiple_cpl = coauthors_posts_links( null, null, null, null, false ); + + $this->assertContains( 'href="' . get_author_posts_url( $this->author1->ID, $this->author1->user_nicename ) . '"', $multiple_cpl, 'Main author link not found.' ); + $this->assertContains( $this->author1->display_name, $multiple_cpl, 'Main author name not found.' ); + + // Here we are checking author name should not be more then one time. + // Asserting ">{$this->author1->display_name}<" because "$this->author1->display_name" can be multiple times like in href, title, etc. + $this->assertEquals( 1, substr_count( $multiple_cpl, ">{$this->author1->display_name}<" ) ); + $this->assertContains( ' and ', $multiple_cpl, 'Coauthors name separator is not matched.' ); + $this->assertContains( 'href="' . get_author_posts_url( $this->editor1->ID, $this->editor1->user_nicename ) . '"', $multiple_cpl, 'Coauthor link not found.' ); + $this->assertContains( $this->editor1->display_name, $multiple_cpl, 'Coauthor name not found.' ); + + // Here we are checking editor name should not be more then one time. + // Asserting ">{$this->editor1->display_name}<" because "$this->editor1->display_name" can be multiple times like in href, title, etc. + $this->assertEquals( 1, substr_count( $multiple_cpl, ">{$this->editor1->display_name}<" ) ); + + $multiple_cpl = coauthors_links( null, ' or ', null, null, false ); + + $this->assertContains( ' or ', $multiple_cpl, 'Coauthors name separator is not matched.' ); + + $this->assertEquals( 10, has_filter( 'the_author', array( + $coauthors_plus_template_filters, + 'filter_the_author', + ) ) ); + + // Restore backed up post to global. + $GLOBALS['post'] = $post_backup; + } + + /** + * Tests for co-authors display names. + * + * @see https://github.com/Automattic/Co-Authors-Plus/issues/279 + * + * @covers ::coauthors_links() + */ + public function test_coauthors_links() { + + global $coauthors_plus, $coauthors_plus_template_filters; + + // Backing up global post. + $post_backup = $GLOBALS['post']; + + $GLOBALS['post'] = $this->post; + + // Checks for single post author. + $single_cpl = coauthors_links( null, null, null, null, false ); + + $this->assertEquals( $this->author1->display_name, $single_cpl, 'Author name not found.' ); + + // Checks for multiple post author. + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $multiple_cpl = coauthors_links( null, null, null, null, false ); + + $this->assertContains( $this->author1->display_name, $multiple_cpl, 'Main author name not found.' ); + $this->assertEquals( 1, substr_count( $multiple_cpl, $this->author1->display_name ) ); + $this->assertContains( ' and ', $multiple_cpl, 'Coauthors name separator is not matched.' ); + $this->assertContains( $this->editor1->display_name, $multiple_cpl, 'Coauthor name not found.' ); + $this->assertEquals( 1, substr_count( $multiple_cpl, $this->editor1->display_name ) ); + + $multiple_cpl = coauthors_links( null, ' or ', null, null, false ); + + $this->assertContains( ' or ', $multiple_cpl, 'Coauthors name separator is not matched.' ); + + $this->assertEquals( 10, has_filter( 'the_author', array( + $coauthors_plus_template_filters, + 'filter_the_author', + ) ) ); + + // Restore backed up post to global. + $GLOBALS['post'] = $post_backup; + } + + /** + * Checks coauthors when post not exist. + * + * @covers ::get_coauthors() + */ + public function test_get_coauthors_when_post_not_exists() { + + $this->assertEmpty( get_coauthors() ); + } + + /** + * Checks coauthors when post exist (not global). + * + * @covers ::get_coauthors() + */ + public function test_get_coauthors_when_post_exists() { + + global $coauthors_plus; + + // Compare single author. + $this->assertEquals( array( $this->author1->ID ), wp_list_pluck( get_coauthors( $this->post->ID ), 'ID' ) ); + + // Compare multiple authors. + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + $this->assertEquals( array( + $this->author1->ID, + $this->editor1->ID, + ), wp_list_pluck( get_coauthors( $this->post->ID ), 'ID' ) ); + } + + /** + * Checks coauthors when terms for post not exist. + * + * @covers ::get_coauthors() + */ + public function test_get_coauthors_when_terms_for_post_not_exists() { + + $post_id = $this->factory->post->create(); + $this->assertEmpty( get_coauthors( $post_id ) ); + } + + /** + * Checks coauthors when post not exist. + * + * @covers ::get_coauthors() + */ + public function test_get_coauthors_when_global_post_exists() { + + global $post; + + // Backing up global post. + $post_backup = $post; + + $post = $this->factory->post->create_and_get(); + + $this->assertEmpty( get_coauthors() ); + + $user_id = $this->factory->user->create(); + $post = $this->factory->post->create_and_get( array( + 'post_author' => $user_id, + ) ); + + $this->assertEquals( array( $user_id ), wp_list_pluck( get_coauthors(), 'ID' ) ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks coauthors order. + * + * @covers ::get_coauthors() + */ + public function test_coauthors_order() { + + global $coauthors_plus; + + $post_id = $this->factory->post->create(); + + // Checks when no author exist. + $this->assertEmpty( get_coauthors( $post_id ) ); + + // Checks coauthors order. + $coauthors_plus->add_coauthors( $post_id, array( $this->author1->user_login ), true ); + $coauthors_plus->add_coauthors( $post_id, array( $this->editor1->user_login ), true ); + + $expected = array( $this->author1->user_login, $this->editor1->user_login ); + + $this->assertEquals( $expected, wp_list_pluck( get_coauthors( $post_id ), 'user_login' ) ); + + // Checks coauthors order after modifying. + $post_id = $this->factory->post->create(); + + $coauthors_plus->add_coauthors( $post_id, array( $this->editor1->user_login ), true ); + $coauthors_plus->add_coauthors( $post_id, array( $this->author1->user_login ), true ); + + $expected = array( $this->editor1->user_login, $this->author1->user_login ); + + $this->assertEquals( $expected, wp_list_pluck( get_coauthors( $post_id ), 'user_login' ) ); + } + + /** + * Checks whether user is a coauthor of the post when user or post not exists. + * + * @covers ::is_coauthor_for_post() + */ + public function test_is_coauthor_for_post_when_user_or_post_not_exists() { + + global $post; + + // Backing up global post. + $post_backup = $post; + + $this->assertFalse( is_coauthor_for_post( '' ) ); + $this->assertFalse( is_coauthor_for_post( '', $this->post->ID ) ); + $this->assertFalse( is_coauthor_for_post( $this->author1->ID ) ); + + $post = $this->post; + + $this->assertFalse( is_coauthor_for_post( '' ) ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks whether user is a coauthor of the post when user is not expected as ID, + * or user_login is not set in user object. + * + * @covers ::is_coauthor_for_post() + */ + public function test_is_coauthor_for_post_when_user_not_numeric_or_user_login_not_set() { + + $this->assertFalse( is_coauthor_for_post( 'test' ) ); + } + + /** + * Checks whether user is a coauthor of the post when user is set in either way, + * as user_id or user object but he/she is not coauthor of the post. + * + * @covers ::is_coauthor_for_post() + */ + public function test_is_coauthor_for_post_when_user_numeric_or_user_login_set_but_not_coauthor() { + + $this->assertFalse( is_coauthor_for_post( $this->editor1->ID, $this->post->ID ) ); + $this->assertFalse( is_coauthor_for_post( $this->editor1, $this->post->ID ) ); + } + + /** + * Checks whether user is a coauthor of the post. + * + * @covers ::is_coauthor_for_post() + */ + public function test_is_coauthor_for_post_when_user_is_coauthor() { + + global $post, $coauthors_plus; + + // Backing up global post. + $post_backup = $post; + + // Checking with specific post and user_id as well ass user object. + $this->assertTrue( is_coauthor_for_post( $this->author1->ID, $this->post->ID ) ); + $this->assertTrue( is_coauthor_for_post( $this->author1, $this->post->ID ) ); + + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $this->assertTrue( is_coauthor_for_post( $this->editor1->ID, $this->post->ID ) ); + $this->assertTrue( is_coauthor_for_post( $this->editor1, $this->post->ID ) ); + + // Now checking with global post and user_id as well ass user object. + $post = $this->post; + + $this->assertTrue( is_coauthor_for_post( $this->author1->ID ) ); + $this->assertTrue( is_coauthor_for_post( $this->author1 ) ); + + $this->assertTrue( is_coauthor_for_post( $this->editor1->ID ) ); + $this->assertTrue( is_coauthor_for_post( $this->editor1 ) ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Tests for co-authors display names, without links to their posts. + * + * @covers ::coauthors() + * @covers ::coauthors__echo() + **/ + public function test_coauthors() { + + global $post, $coauthors_plus; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + // Checks for single post author. + $coauthors = coauthors( null, null, null, null, false ); + + $this->assertEquals( $this->author1->display_name, $coauthors ); + + $coauthors = coauthors( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->display_name . '', $coauthors ); + + // Checks for multiple post author. + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $coauthors = coauthors( null, null, null, null, false ); + + $this->assertEquals( $this->author1->display_name . ' and ' . $this->editor1->display_name, $coauthors ); + + $coauthors = coauthors( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->display_name . '' . $this->editor1->display_name . '', $coauthors ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks single co-author linked to their post archive. + * + * @covers ::coauthors_posts_links_single() + */ + public function test_coauthors_posts_links_single() { + + global $post; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + $author_link = coauthors_posts_links_single( $this->author1 ); + + $this->assertContains( 'href="' . get_author_posts_url( $this->author1->ID, $this->author1->user_nicename ) . '"', $author_link, 'Author link not found.' ); + $this->assertContains( $this->author1->display_name, $author_link, 'Author name not found.' ); + + // Here we are checking author name should not be more then one time. + // Asserting ">{$this->author1->display_name}<" because "$this->author1->display_name" can be multiple times like in href, title, etc. + $this->assertEquals( 1, substr_count( $author_link, ">{$this->author1->display_name}<" ) ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks co-authors first names, without links to their posts. + * + * @covers ::coauthors_firstnames() + * @covers ::coauthors__echo() + */ + public function test_coauthors_firstnames() { + + global $post, $coauthors_plus; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + // Checking when first name is not set for user, so it should match with user_login. + $first_names = coauthors_firstnames( null, null, null, null, false ); + + $this->assertEquals( $this->author1->user_login, $first_names ); + + $first_names = coauthors_firstnames( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->user_login . '', $first_names ); + + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $first_names = coauthors_firstnames( null, null, null, null, false ); + + $this->assertEquals( $this->author1->user_login . ' and ' . $this->editor1->user_login, $first_names ); + + $first_names = coauthors_firstnames( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->user_login . '' . $this->editor1->user_login . '', $first_names ); + + // Checking when first name is set for user. + $first_name = 'Test'; + $user_id = $this->factory->user->create( array( + 'first_name' => $first_name, + ) ); + $post = $this->factory->post->create_and_get( array( + 'post_author' => $user_id, + ) ); + + $first_names = coauthors_firstnames( null, null, null, null, false ); + + $this->assertEquals( $first_name, $first_names ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks co-authors last names, without links to their posts. + * + * @covers ::coauthors_lastnames() + * @covers ::coauthors__echo() + */ + public function test_coauthors_lastnames() { + + global $post, $coauthors_plus; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + // Checking when last name is not set for user, so it should match with user_login. + $last_names = coauthors_lastnames( null, null, null, null, false ); + + $this->assertEquals( $this->author1->user_login, $last_names ); + + $last_names = coauthors_lastnames( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->user_login . '', $last_names ); + + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $last_names = coauthors_lastnames( null, null, null, null, false ); + + $this->assertEquals( $this->author1->user_login . ' and ' . $this->editor1->user_login, $last_names ); + + $last_names = coauthors_lastnames( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->user_login . '' . $this->editor1->user_login . '', $last_names ); + + // Checking when last name is set for user. + $last_name = 'Test'; + $user_id = $this->factory->user->create( array( + 'last_name' => $last_name, + ) ); + $post = $this->factory->post->create_and_get( array( + 'post_author' => $user_id, + ) ); + + $last_names = coauthors_lastnames( null, null, null, null, false ); + + $this->assertEquals( $last_name, $last_names ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks co-authors nicknames, without links to their posts. + * + * @covers ::coauthors_nicknames() + * @covers ::coauthors__echo() + */ + public function test_coauthors_nicknames() { + + global $post, $coauthors_plus; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + // Checking when nickname is not set for user, so it should match with user_login. + $nick_names = coauthors_nicknames( null, null, null, null, false ); + + $this->assertEquals( $this->author1->user_login, $nick_names ); + + $nick_names = coauthors_nicknames( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->user_login . '', $nick_names ); + + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $nick_names = coauthors_nicknames( null, null, null, null, false ); + + $this->assertEquals( $this->author1->user_login . ' and ' . $this->editor1->user_login, $nick_names ); + + $nick_names = coauthors_nicknames( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->user_login . '' . $this->editor1->user_login . '', $nick_names ); + + // Checking when nickname is set for user. + $nick_name = 'Test'; + $user_id = $this->factory->user->create( array( + 'nickname' => $nick_name, + ) ); + $post = $this->factory->post->create_and_get( array( + 'post_author' => $user_id, + ) ); + + $nick_names = coauthors_nicknames( null, null, null, null, false ); + + $this->assertEquals( $nick_name, $nick_names ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks co-authors email addresses. + * + * @covers ::coauthors_emails() + * @covers ::coauthors__echo() + */ + public function test_coauthors_emails() { + + global $post, $coauthors_plus; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + $emails = coauthors_emails( null, null, null, null, false ); + + $this->assertEquals( $this->author1->user_email, $emails ); + + $emails = coauthors_emails( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->user_email . '', $emails ); + + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $emails = coauthors_emails( null, null, null, null, false ); + + $this->assertEquals( $this->author1->user_email . ' and ' . $this->editor1->user_email, $emails ); + + $emails = coauthors_emails( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->user_email . '' . $this->editor1->user_email . '', $emails ); + + $email = 'test@example.org'; + $user_id = $this->factory->user->create( array( + 'user_email' => $email, + ) ); + $post = $this->factory->post->create_and_get( array( + 'post_author' => $user_id, + ) ); + + $emails = coauthors_emails( null, null, null, null, false ); + + $this->assertEquals( $email, $emails ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks single co-author if he/she is a guest author. + * + * @covers ::coauthors_links_single() + */ + public function test_coauthors_links_single_when_guest_author() { + + global $post, $authordata; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + // Backing up global author data. + $authordata_backup = $authordata; + $authordata = $this->author1; + + // Shows that it's necessary to set $authordata to $this->author1 + $this->assertEquals( $authordata, $this->author1, 'Global $authordata not matching expected $this->author1.' ); + + $this->author1->type = 'guest-author'; + + $this->assertEquals( get_the_author_link(), coauthors_links_single( $this->author1 ), 'Co-Author link generation differs from Core author link one (without user_url)' ); + + wp_update_user( array( 'ID' => $this->author1->ID, 'user_url' => 'example.org' ) ); + $authordata = get_userdata( $this->author1->ID ); // Because wp_update_user flushes cache, but does not update global var + + $this->assertEquals( get_the_author_link(), coauthors_links_single( $this->author1 ), 'Co-Author link generation differs from Core author link one (with user_url)' ); + + $author_link = coauthors_links_single( $this->author1 ); + $this->assertContains( get_the_author_meta( 'url' ), $author_link, 'Author url not found in link.' ); + $this->assertContains( get_the_author(), $author_link, 'Author name not found in link.' ); + + // Here we are checking author name should not be more then one time. + // Asserting ">get_the_author()<" because "get_the_author()" can be multiple times like in href, title, etc. + $this->assertEquals( 1, substr_count( $author_link, '>' . get_the_author() . '<' ) ); + + // Restore global author data from backup. + $authordata = $authordata_backup; + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks single co-author when user's url is set and not a guest author. + * + * @covers ::coauthors_links_single() + */ + public function test_coauthors_links_single_author_url_is_set() { + + global $post, $authordata; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + // Backing up global author data. + $authordata_backup = $authordata; + + $user_id = $this->factory->user->create( array( + 'user_url' => 'example.org', + ) ); + $user = get_user_by( 'id', $user_id ); + + $authordata = $user; + $author_link = coauthors_links_single( $user ); + + $this->assertContains( get_the_author_meta( 'url' ), $author_link, 'Author link not found.' ); + $this->assertContains( get_the_author(), $author_link, 'Author name not found.' ); + + // Here we are checking author name should not be more then one time. + // Asserting ">get_the_author()<" because "get_the_author()" can be multiple times like in href, title, etc. + $this->assertEquals( 1, substr_count( $author_link, '>' . get_the_author() . '<' ) ); + + // Restore global author data from backup. + $authordata = $authordata_backup; + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks single co-author when user's website/url not exist. + * + * @covers ::coauthors_links_single() + */ + public function test_coauthors_links_single_when_url_not_exist() { + + global $post, $authordata; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + // Backing up global author data. + $authordata_backup = $authordata; + + $this->editor1->type = 'guest-author'; + + $author_link = coauthors_links_single( $this->editor1 ); + + $this->assertEquals( get_the_author(), $author_link ); + + $authordata = $this->author1; + $author_link = coauthors_links_single( $this->author1 ); + + $this->assertEquals( get_the_author(), $author_link ); + + // Restore global author data from backup. + $authordata = $authordata_backup; + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks co-authors IDs. + * + * @covers ::coauthors_ids() + * @covers ::coauthors__echo() + */ + public function test_coauthors_ids() { + + global $post, $coauthors_plus; + + // Backing up global post. + $post_backup = $post; + + $post = $this->post; + + $ids = coauthors_ids( null, null, null, null, false ); + + $this->assertEquals( $this->author1->ID, $ids ); + + $ids = coauthors_ids( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->ID . '', $ids ); + + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $ids = coauthors_ids( null, null, null, null, false ); + + $this->assertEquals( $this->author1->ID . ' and ' . $this->editor1->ID, $ids ); + + $ids = coauthors_ids( '', '', '', '', false ); + + $this->assertEquals( '' . $this->author1->ID . '' . $this->editor1->ID . '', $ids ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks co-authors meta. + * + * @covers ::get_the_coauthor_meta() + */ + public function test_get_the_coauthor_meta() { + + global $post; + + // Backing up global post. + $post_backup = $post; + + $this->assertEmpty( get_the_coauthor_meta( '' ) ); + + update_user_meta( $this->author1->ID, 'meta_key', 'meta_value' ); + + $this->assertEmpty( get_the_coauthor_meta( 'meta_key' ) ); + + $post = $this->post; + $meta = get_the_coauthor_meta( 'meta_key' ); + + $this->assertEquals( 'meta_value', $meta[ $this->author1->ID ] ); + + // Restore global post from backup. + $post = $post_backup; + } + + /** + * Checks all the co-authors of the blog with default args. + * + * @covers ::coauthors_wp_list_authors() + */ + public function test_coauthors_wp_list_authors_for_default_args() { + + global $coauthors_plus; + + $args = array( + 'echo' => false, + ); + + $coauthors = coauthors_wp_list_authors( $args ); + + $this->assertContains( 'href="' . get_author_posts_url( $this->author1->ID, $this->author1->user_nicename ) . '"', $coauthors, 'Author link not found.' ); + $this->assertContains( $this->author1->display_name, $coauthors, 'Author name not found.' ); + + $coauthors = coauthors_wp_list_authors( $args ); + + $this->assertNotContains( 'href="' . get_author_posts_url( $this->editor1->ID, $this->editor1->user_nicename ) . '"', $coauthors ); + $this->assertNotContains( $this->editor1->display_name, $coauthors ); + + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $coauthors = coauthors_wp_list_authors( $args ); + + $this->assertContains( 'href="' . get_author_posts_url( $this->author1->ID, $this->author1->user_nicename ) . '"', $coauthors, 'Main author link not found.' ); + $this->assertContains( $this->author1->display_name, $coauthors, 'Main author name not found.' ); + + // Here we are checking author name should not be more then one time. + // Asserting ">{$this->author1->display_name}<" because "$this->author1->display_name" can be multiple times like in href, title, etc. + $this->assertEquals( 1, substr_count( $coauthors, ">{$this->author1->display_name}<" ) ); + + $this->assertContains( '
  • ', $coauthors, 'Coauthors name separator is not matched.' ); + $this->assertContains( 'href="' . get_author_posts_url( $this->editor1->ID, $this->editor1->user_nicename ) . '"', $coauthors, 'Coauthor link not found.' ); + $this->assertContains( $this->editor1->display_name, $coauthors, 'Coauthor name not found.' ); + + // Here we are checking editor name should not be more then one time. + // Asserting ">{$this->editor1->display_name}<" because "$this->editor1->display_name" can be multiple times like in href, title, etc. + $this->assertEquals( 1, substr_count( $coauthors, ">{$this->editor1->display_name}<" ) ); + } + + /** + * Checks all the co-authors of the blog with optioncount option. + * + * @covers ::coauthors_wp_list_authors() + */ + public function test_coauthors_wp_list_authors_for_optioncount() { + + $this->assertContains( '(' . count_user_posts( $this->author1->ID ) . ')', coauthors_wp_list_authors( array( + 'echo' => false, + 'optioncount' => true, + ) ) ); + } + + /** + * Checks all the co-authors of the blog with show_fullname option. + * + * @covers ::coauthors_wp_list_authors() + */ + public function test_coauthors_wp_list_authors_for_show_fullname() { + + $args = array( + 'echo' => false, + 'show_fullname' => true, + ); + + $this->assertContains( $this->author1->display_name, coauthors_wp_list_authors( $args ) ); + + $user = $this->factory->user->create_and_get( array( + 'first_name' => 'First', + 'last_name' => 'Last', + ) ); + + $this->factory->post->create( array( + 'post_author' => $user->ID, + ) ); + + $this->assertContains( "{$user->user_firstname} {$user->user_lastname}", coauthors_wp_list_authors( $args ) ); + } + + /** + * Checks all the co-authors of the blog with hide_empty option. + * + * @covers ::coauthors_wp_list_authors() + */ + public function test_coauthors_wp_list_authors_for_hide_empty() { + + global $coauthors_plus; + + $coauthors_plus->guest_authors->create( array( + 'user_login' => 'author2', + 'display_name' => 'author2', + ) ); + + $this->assertContains( 'author2', coauthors_wp_list_authors( array( + 'echo' => false, + 'hide_empty' => false, + ) ) ); + } + + /** + * Checks all the co-authors of the blog with feed option. + * + * @covers ::coauthors_wp_list_authors() + */ + public function test_coauthors_wp_list_authors_for_feed() { + + $feed_text = 'link to feed'; + $coauthors = coauthors_wp_list_authors( array( + 'echo' => false, + 'feed' => $feed_text, + ) ); + + $this->assertContains( esc_url( get_author_feed_link( $this->author1->ID ) ), $coauthors ); + $this->assertContains( $feed_text, $coauthors ); + } + + /** + * Checks all the co-authors of the blog with feed_image option. + * + * @covers ::coauthors_wp_list_authors() + */ + public function test_coauthors_wp_list_authors_for_feed_image() { + + $feed_image = WP_TESTS_DOMAIN . '/path/to/a/graphic.png'; + $coauthors = coauthors_wp_list_authors( array( + 'echo' => false, + 'feed_image' => $feed_image, + ) ); + + $this->assertContains( esc_url( get_author_feed_link( $this->author1->ID ) ), $coauthors ); + $this->assertContains( $feed_image, $coauthors ); + } + + /** + * Checks all the co-authors of the blog with feed_type option. + * + * @covers ::coauthors_wp_list_authors() + */ + public function test_coauthors_wp_list_authors_for_feed_type() { + + $feed_type = 'atom'; + $feed_text = 'link to feed'; + $coauthors = coauthors_wp_list_authors( array( + 'echo' => false, + 'feed_type' => $feed_type, + 'feed' => $feed_text, + ) ); + + $this->assertContains( esc_url( get_author_feed_link( $this->author1->ID, $feed_type ) ), $coauthors ); + $this->assertContains( $feed_type, $coauthors ); + $this->assertContains( $feed_text, $coauthors ); + } + + /** + * Checks all the co-authors of the blog with style option. + * + * @covers ::coauthors_wp_list_authors() + */ + public function test_coauthors_wp_list_authors_for_style() { + + $coauthors = coauthors_wp_list_authors( array( + 'echo' => false, + 'style' => 'none', + ) ); + + $this->assertNotContains( '
  • ', $coauthors ); + $this->assertNotContains( '
  • ', $coauthors ); + } + + /** + * Checks all the co-authors of the blog with html option. + * + * @covers ::coauthors_wp_list_authors() + */ + public function test_coauthors_wp_list_authors_for_html() { + + global $coauthors_plus; + + $args = array( + 'echo' => false, + 'html' => false, + ); + + $this->assertEquals( $this->author1->display_name, coauthors_wp_list_authors( $args ) ); + + $coauthors_plus->add_coauthors( $this->post->ID, array( $this->editor1->user_login ), true ); + + $this->assertEquals( "{$this->author1->display_name}, {$this->editor1->display_name}", coauthors_wp_list_authors( $args ) ); + } + + /** + * Checks all the co-authors of the blog with guest_authors_only option. + * + * @covers ::coauthors_wp_list_authors() + */ + public function test_coauthors_wp_list_authors_for_guest_authors_only() { + + global $coauthors_plus; + + $args = array( + 'echo' => false, + 'guest_authors_only' => true, + ); + + $this->assertEmpty( coauthors_wp_list_authors( $args ) ); + + $guest_author_id = $coauthors_plus->guest_authors->create( array( + 'user_login' => 'author2', + 'display_name' => 'author2', + ) ); + + $this->assertEmpty( coauthors_wp_list_authors( $args ) ); + + $guest_author = $coauthors_plus->guest_authors->get_guest_author_by( 'id', $guest_author_id ); + + $coauthors_plus->add_coauthors( $this->post->ID, array( $guest_author->user_login ), true ); + + $this->assertContains( $guest_author->display_name, coauthors_wp_list_authors( $args ) ); + } + + /** + * Checks co-author's avatar. + * + * @covers ::coauthors_get_avatar() + */ + public function test_coauthors_get_avatar_default() { + + $this->assertEmpty( coauthors_get_avatar( $this->author1->ID ) ); + $this->assertEquals( preg_match( "|^[^$|", coauthors_get_avatar( $this->author1 ) ), 1 ); + } + + /** + * Checks co-author's avatar when author is a guest author. + * + * @covers ::coauthors_get_avatar() + */ + public function test_coauthors_get_avatar_when_guest_author() { + + global $coauthors_plus; + + $guest_author_id = $coauthors_plus->guest_authors->create( array( + 'user_login' => 'author2', + 'display_name' => 'author2', + ) ); + + $guest_author = $coauthors_plus->guest_authors->get_guest_author_by( 'id', $guest_author_id ); + + $this->assertEquals( preg_match( "|^[^$|", coauthors_get_avatar( $guest_author ) ), 1 ); + + $filename = rand_str() . '.jpg'; + $contents = rand_str(); + $upload = wp_upload_bits( $filename, null, $contents ); + + $this->assertTrue( empty( $upload['error'] ) ); + + $attachment_id = $this->_make_attachment( $upload ); + + set_post_thumbnail( $guest_author->ID, $attachment_id ); + + $avatar = coauthors_get_avatar( $guest_author ); + $attachment_url = wp_get_attachment_url( $attachment_id ); + + $this->assertContains( $filename, $avatar ); + $this->assertContains( 'src="' . $attachment_url . '"', $avatar ); + } + + /** + * Checks co-author's avatar when user's email is not set somehow. + * + * @covers ::coauthors_get_avatar() + */ + public function test_coauthors_get_avatar_when_user_email_not_set() { + + global $coauthors_plus; + + $guest_author_id = $coauthors_plus->guest_authors->create( array( + 'user_login' => 'author2', + 'display_name' => 'author2', + ) ); + + $guest_author = $coauthors_plus->guest_authors->get_guest_author_by( 'id', $guest_author_id ); + + unset( $guest_author->user_email ); + + $this->assertEmpty( coauthors_get_avatar( $guest_author ) ); + } + + /** + * Checks co-author's avatar with size. + * + * @covers ::coauthors_get_avatar() + */ + public function test_coauthors_get_avatar_size() { + + $size = '100'; + $this->assertEquals( preg_match( "|^author1, $size ) ), 1 ); + } + + /** + * Checks co-author's avatar with alt. + * + * @covers ::coauthors_get_avatar() + */ + public function test_coauthors_get_avatar_alt() { + + $alt = 'Test'; + $this->assertEquals( preg_match( "|^$altauthor1, 96, '', $alt ) ), 1 ); + } +} diff --git a/upgrade.php b/upgrade.php index f061cedd..c06f8651 100644 --- a/upgrade.php +++ b/upgrade.php @@ -17,32 +17,29 @@ function coauthors_plus_upgrade_20() { // Get all posts with meta_key _coauthor $all_posts = get_posts( array( 'numberposts' => '-1', 'meta_key' => '_coauthor' ) ); - if ( is_array( $all_posts ) ) { - foreach ( $all_posts as $single_post ) { - - // reset execution time limit - set_time_limit( 60 ); - - // create new array - $coauthors = array(); - // get author id -- try to use get_profile - $coauthor = get_user_by( 'id', (int) $single_post->post_author ); - if ( is_object( $coauthor ) ) { - $coauthors[] = $coauthor->user_login; - } - // get coauthors id - $legacy_coauthors = get_post_meta( $single_post->ID, '_coauthor' ); - - if ( is_array( $legacy_coauthors ) ) { - foreach ( $legacy_coauthors as $legacy_coauthor ) { - $legacy_coauthor_login = get_user_by( 'id', (int) $legacy_coauthor ); - if ( is_object( $legacy_coauthor_login ) && ! in_array( $legacy_coauthor_login->user_login, $coauthors ) ) { - $coauthors[] = $legacy_coauthor_login->user_login; - } + + foreach ( $all_posts as $single_post ) { + // reset execution time limit + set_time_limit( 60 ); + + // create new array + $coauthors = array(); + // get author id -- try to use get_profile + $coauthor = get_user_by( 'id', (int) $single_post->post_author ); + if ( is_object( $coauthor ) ) { + $coauthors[] = $coauthor->user_login; + } + // get coauthors id + $legacy_coauthors = get_post_meta( $single_post->ID, '_coauthor' ); + + if ( is_array( $legacy_coauthors ) ) { + foreach ( $legacy_coauthors as $legacy_coauthor ) { + $legacy_coauthor_login = get_user_by( 'id', (int) $legacy_coauthor ); + if ( is_object( $legacy_coauthor_login ) && ! in_array( $legacy_coauthor_login->user_login, $coauthors ) ) { + $coauthors[] = $legacy_coauthor_login->user_login; } } - $coauthors_plus->add_coauthors( $single_post->ID, $coauthors ); - } + $coauthors_plus->add_coauthors( $single_post->ID, $coauthors ); } } diff --git a/vipgo-helper.php b/vipgo-helper.php new file mode 100644 index 00000000..8debac77 --- /dev/null +++ b/vipgo-helper.php @@ -0,0 +1,9 @@ +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 );