Automattic / Co-Authors-Plus

Multiple bylines and Guest Authors for WordPress
https://wordpress.org/plugins/co-authors-plus/
GNU General Public License v2.0
293 stars 205 forks source link

Migrating from custom system, and not able to keep the order in authors. Wrong sorting. #1052

Open manuelRod opened 5 months ago

manuelRod commented 5 months ago

Hello,

We are currently migrating from another custom system to co-authors. We are having a lot of trouble with the migration since the ordering of authors seems to be completely random. Sometimes (most of the time) respects the old order, but sometimes it doesn't. Maybe the way we are doing it it's wrong (?)

How are we doing it? Passing an array of authors (with user_nicename) to add_coauthors function.

global $coauthors_plus;
$new_co_authors = [ 'Jhon Doe', 'Test User'];
$coauthors_plus->add_coauthors( $post_id, array_map( 'sanitize_title', $new_co_authors ) );

Sometimes we will get co-authors in the right other, sometimes inverse.

Why the order is sometimes not respected? I tried other combinations, like calling add_coauthors with just one author, n times. Nothing seems to work, and all the time seems random.

Any clues? Are we doing it wrong?

Thanks,

bystrzan commented 5 months ago

Regular posts also doesn't respect the sorting option from post editing screen (gutenberg + hybrid theme)

manuelRod commented 5 months ago

@bystrzan While I was debugging this issue, I noticed that the AJAX call made in the post editor also uses add_coauthors. Meaning, as you mentioned, sometimes the order won't be respected also using the plugin itself.

eddiesshop commented 4 months ago

@manuelRod by any chance, do you still see the same thing happening if you used this PR?

TrivediKavit commented 4 months ago

TL;DR

The issue stems from WordPress Core function wp_set_object_terms and as an extension, wp_set_post_term don not maintain order of the input array terms. This is especially apparent when the input array of terms include terms that were already added previously.

Co-Authors Plus does have its own ordering mechanism but that does not work when adding terms that don't already exist for a particular post programatically / via CLI. The ordering does work when the Post Editor interface is used. The command does fix the ordering on a second run since all the authors required for a post already exist (even if they were in the wrong order after the first run).

Code: https://github.com/Automattic/Co-Authors-Plus/blob/1c29766eeda11276bcf6f20e07171b3efcbc7c03/php/class-coauthors-plus.php#L1011

wp_set_post_terms eventually calls wp_set_object_terms.

function wp_set_post_terms( $post_id = 0, $terms = '', $taxonomy = 'post_tag', $append = false ) {
    $post_id = (int) $post_id;

    // CODE TRUNCATED FOR READABILITY

    return wp_set_object_terms( $post_id, $terms, $taxonomy, $append );
}

References:

  1. Source for wp_set_post_term: https://github.com/WordPress/WordPress/blob/ad3c5a4946cc39fa68350f0e15306da737dd38b0/wp-includes/post.php#L5435
  2. Source for wp_set_object_terms: https://github.com/WordPress/WordPress/blob/aeb9f372381cf7952973a66241aa179db083866f/wp-includes/taxonomy.php#L2798

LOCAL TESTING

So as a proof of concept, I booted up a WordPress instance using Local (https://localwp.com/).

I activated Twenty-Twenty Two theme, and added the following code snippet in that theme's functions.php:

wp_set_post_terms( 19, array('test'), 'post_tag');

// 19 is just the post ID of a post
// array('test') is a term
// `post_tag` is the taxonomy I am adding that term to for Post ID: 19

Once I added that, I dumped the output as follows:

var_dump( wp_get_post_terms( 19, 'post_tag' ) );

// OUTPUT
array(1) {
  [0]=>
  object(WP_Term)#1672 (10) {
    ["term_id"]=>
    int(3182)
    ["name"]=>
    string(4) "test"
    ["slug"]=>
    string(4) "test"
    ["term_group"]=>
    int(0)
    ["term_taxonomy_id"]=>
    int(3182)
    ["taxonomy"]=>
    string(8) "post_tag"
    ["description"]=>
    string(0) ""
    ["parent"]=>
    int(0)
    ["count"]=>
    int(1)
    ["filter"]=>
    string(3) "raw"
  }
}

I then modified the wp_set_post_terms line to:

wp_set_object_terms( 19, array( 'test', 'john-doe'), 'post_tag' );

// I am still passing `test` as first entry

However, now when I dump the terms for this post:

var_dump( wp_get_post_terms( 19, 'post_tag' ) );

// OUTPUT
array(2) {
  [0]=>
  object(WP_Term)#1674 (10) {
    ["term_id"]=>
    int(3183)
    ["name"]=>
    string(8) "john-doe"
    ["slug"]=>
    string(8) "john-doe"
    ["term_group"]=>
    int(0)
    ["term_taxonomy_id"]=>
    int(3183)
    ["taxonomy"]=>
    string(8) "post_tag"
    ["description"]=>
    string(0) ""
    ["parent"]=>
    int(0)
    ["count"]=>
    int(1)
    ["filter"]=>
    string(3) "raw"
  }
  [1]=>
  object(WP_Term)#1675 (10) {
    ["term_id"]=>
    int(3182)
    ["name"]=>
    string(4) "test"
    ["slug"]=>
    string(4) "test"
    ["term_group"]=>
    int(0)
    ["term_taxonomy_id"]=>
    int(3182)
    ["taxonomy"]=>
    string(8) "post_tag"
    ["description"]=>
    string(0) ""
    ["parent"]=>
    int(0)
    ["count"]=>
    int(1)
    ["filter"]=>
    string(3) "raw"
  }
}

The already existing term from the array test is NOT first even though I explicitly mentioned test as the first entry.

So that means wp_set_post_terms DOES NOT preserve the order of the input array passed to it.


WHAT HAPPENS IN CO-AUTHORS PLUS

Co-Authors Plus has an additional field that it uses to maintain order AFTER the terms are added. This column term_order already ships with WordPress Core as part of the wp_term_relationships table. This is the table that maps OBJECT IDs (which are Post IDs for this example) to TERM TAXONOMY IDs (which are Author Term IDs for this example) and has a column named term_order. This column is NOT USED BY DEFAULT in get_the_terms() etc.

WordPress Core Database Description: https://codex.wordpress.org/Database_Description

The issue stems from wp_set_object_terms not allowing you to set an order when assigning terms to a post. The term_order is never touched during assignment of a term to a post.

This is how WordPress Core does it, and it does make logical sense, as one cannot know the custom order of a term in isolation during an insert, and the sort will fallback to being alphabetical or by term_id.

Any custom ordering step, if needed, has to be an additional step AFTER the terms are assigned to a post.

**Co-Authors Plus does not cause order mismatch when using the ordering interface on the Post Editor pages, since order is passed via the custom metabox, which is presumably hooked into other actions preserving the order, presumably using the save_post action.

In the context of your custom command, however, Co-Authors Plus abides by how WordPress Core allows it to set terms, which is without any order, and requires an additional step AFTER the terms are assigned for re-ordering, since there are no metaboxes or save_post action to hook into.**


TEMPORARY SOLUTION

When using the method $coauthors_plus->add_coauthors(), add a verification step that checks whether the order of coauthor terms is the same as the source you are importing it from, and if there is an order mismatch, re-add the same array of authors again.


GOING FORWARD

I am currently exploring the possibility of integrating order verification into the Co-Authors Plus plugin. This process may take some time as I need to familiarize myself with the project's codebase to determine the feasibility and optimal placement of this feature.

Should this integration prove feasible, I will submit a pull request and provide a link to it in this thread.