Coder Social home page Coder Social logo

humanmade / authorship Goto Github PK

View Code? Open in Web Editor NEW
61.0 17.0 7.0 1.93 MB

A modern approach to author attribution in WordPress.

License: GNU General Public License v3.0

PHP 90.01% JavaScript 1.40% TypeScript 6.78% SCSS 1.81%
wordpress wordpress-plugin publishing attribution

authorship's Introduction

Authorship

Stable tag: 0.2.16
Requires at least: 5.4
Tested up to: 6.2
Requires PHP: 7.2
License: GPL v3 or later
Contributors: johnbillion, humanmade

A modern approach to author attribution in WordPress.

Description

Authorship is a modern approach to author attribution in WordPress. It supports attributing posts to multiple authors and to guest authors, provides a great UI, and treats API access to author data as a first-class citizen.

Authorship is currently geared toward developers who are implementing custom solutions on WordPress. For example, it doesn't provide an option to automatically display author profiles at the bottom of a post. In the future it will include wider support for existing themes and useful features for implementors and site builders.



Current Status

Alpha. Generally very functional but several features are still in development.

Features

  • Multiple authors per post
  • Guest authors (that can be created in place on the post editing screen)
  • A convenient and user-friendly UI that feels like a part of WordPress
  • Works with the block editor
  • Works with the classic editor
  • Full CRUD support in the REST API and WP-CLI
  • Full support in RSS feeds
  • Full support in Atom feeds
  • Fine-grained user permission controls

Features without a checkmark are still work in progress.

Installation

For normal use

composer require humanmade/authorship

For development use

  • Clone this repo into your plugins directory
  • Install the dependencies:
    composer install && npm install
  • Start the dev server:
    npm run start

Design Decisions

Why another multi-author plugin? What about Co-Authors Plus or Bylines or PublishPress Authors?

Firstly, those plugins are great and have served us well over the years, however they all suffer from similar problems:

  • API: Lack of support for writing and reading author data via the REST API and WP-CLI
  • UI: Limited or custom UI that doesn't feel like a part of WordPress
  • Users: An unnecessary distinction between guest authors and actual WordPress users

Let's look at these points in detail and explain how Authorship addresses them:

API design decisions

There's a lot more to a modern WordPress site than just its theme. Data gets written to and read from its APIs, so these need to be treated as first-class citizens when working with the attributed authors of posts.

Authorship provides:

  • Ability to read and write attributed authors via an authorship field on the wp/v2/posts REST API endpoints
  • Ability to create guest authors via the authorship/v1/users REST API endpoint
  • Read-only access to users who can be attributed to a post via the authorship/v1/users REST API endpoint
  • Ability to specify attributed authors when creating or updating posts via WP-CLI with the --authorship flag

UI design decisions

We'd love it if you activated Authorship and then forgot that its features are provided by a plugin. The UI provides convenient functionality without looking out of place, both in the block editor and the classic editor.

User design decisions

Existing plugins that provide guest author functionality make a distinction between a guest author and a real WordPress user. A guest author exists only as a taxonomy term, which complicates the UX and creates inconsistencies and duplication in the data.

Authorship creates a real WordPress user account for each guest author, which provides several advantages:

  • No custom administration screens for managing guest authors separately from regular users
  • Plugins that customise user profiles work for guest authors too
  • Consistent data structure - you only ever deal with WP_User objects
  • No need to keep data in sync between a user and their "author" profile
  • Promoting a guest author to a functional user is done just by changing their user role

Template Functions

The following template functions are available for use in your theme to fetch the attributed author(s) of a post:

  • \Authorship\get_author_names( $post )
    • Returns a comma-separated list of the names of the attributed author(s)
  • \Authorship\get_author_names_sentence( $post )
    • Returns a sentence stating the names of the attributed author(s), localised to the current language
  • \Authorship\get_author_names_list( $post )
    • Returns an unordered HTML list of the names of the attributed author(s)
  • \Authorship\get_authors( $post )
    • Returns a list of user objects of the attributed authors
  • \Authorship\get_author_ids( $post )
    • Returns a list of user ids of the attributed authors

REST API

The following REST API endpoints and fields are available:

authorship/v1/users endpoint

This endpoint allows:

  • Searching all users who can be attributed to content
  • Creating guest authors

authorship field

This field is added to the endpoint for all suported post types (by default, ones which that have post type support for author), for example wp/v2/posts. This field is readable and writable and accepts and provides an array of IDs of users attributed to the post.

In addition, user objects are embedded in the _embedded['wp:authorship'] field in the response if _embed is set and the authenticated user can list users.

WP-CLI

Authorship implements a custom flag for use with posts, and migration commands. The following WP-CLI flags are available:

  • --authorship

--authorship flag

When creating or updating posts the --authorship flag can be used to specify the IDs of users attributed to the post. The flag accepts a comma-separated list of user IDs. Examples:

  • wp post create --post_title="My New Post" --authorship=4,11
  • wp post update 220 --authorship=13

If this flag is not set:

  • When creating a new post, if the --post_author flag is set then it will be used for attributed authors
  • When updating an existing post, no change will be made to attributed authors

Migration of WordPress authors on existing posts.

If you activate Authorship on an existing site, all content already created will not have authorship data set for old content. This breaks things such as author archive pages.

This command will set the WordPress author as the authorship user for any posts with no authorship user. (Optionally you can override any existing authorship data, updating it with the WordPress post author).

wp authorship migrate wp-authors --dry-run=true

The command will perform a dry run by default, setting --dry-run=false will make changes to the database.

This command will not overwrite or update Authorship data unless the --overwrite-authors=true flag is set.

PublishPress Authors Migration

Authorship provides a command for creating Authorship data using data from PublishPress Authors. This allows a non-destructive migration path from PublishPress Authors.

With both plugins active, this command will copy PPA data into Authorship:

wp authorship migrate ppa --dry-run=true

The command will perform a dry run by default, setting --dry-run=false will make changes to the database. Guest authors that do not exist as users will be created with blank emails and random passwords.

This command will not overwrite or update Authorship data unless the --overwrite-authors=true flag is set.

Email Notifications

Authorship does not send any email notifications itself, but it does instruct WordPress core to additionally send its emails to attributed authors when appropriate.

  • When a comment on a post is held for moderation, the comment moderation email also gets sent to all attributed authors who have the ability to moderate the comment and have a valid email address
  • When a comment on a post is published, the comment notification email also gets sent to all attributed authors who have a valid email address

This plugin only adjusts the list of email addresses to which these emails get sent. If you want to disable these emails entirely, see the "Email me whenever" section of the Settings -> Discussion screen in WordPress.

Accessibility

Authorship aims to conform to Web Content Accessibility Guidelines (WCAG) 2.1 at level AA but it does not yet fully achieve this. If full support for assistive technology is a requirement of your organisation then Authorship may not be a good fit in its current state.

With regard to the author selection control on the post editing screen:

  • βœ… The visual styles are inherited from WordPress core and are WCAG 2.1 AA compliant
  • βœ… The control is fully accessible using only the keyboard
  • 🚫 The keyboard controls are not very intuitive
  • 🚫 The control is not fully accessible when using a screen reader

The team are actively investigating either replacing the component used to render the control with a fully accessible one, or fixing the accessibility issues of the current one.

Security, Privileges, and Privacy

Great care has been taken to ensure Authorship makes no changes to the user capabilities required to edit content or view sensitive user data on your site. What it does do is:

  • Grant users who are attributed to a post the ability to edit that post if their capabilities allow it
  • Grant users the ability to create and assign guest authors to a post
  • Allow this behaviour to be changed at a granular level with custom capabilities

Assigning Attribution

The capability required to change the attribution of a post matches that which is required by WordPress core to change the post author. This means a user needs the edit_others_post capability for the post type. The result is no change in behaviour from WordPress core with regard to being able to attribute a post to another user.

  • Administrators and Editors can change the attributed authors of a post
  • Authors and Contributors cannot change the attributed authors and see a read-only list when editing a post

Authorship allows the attribution to be changed for any post type that has post type support for author, which by default is Posts and Pages.

Editing Posts

When a user is attributed to a post, that user becomes able to manage that post according to their capabilities as if they were the post author. This means:

  • A post that is attributed to a user with a role of Author can be edited, published, and deleted by that user
  • A post that is attributed to a user with a role of Contributor can be edited by that user while in draft, but cannot be not published, and cannot be edited once published

From a practical point of view this feature only affects users with a role of Author or Contributor. Administrators and Editors can edit other users' posts by default and therefore edit, publish, and delete posts regardless of whether they are attributed to it.

Searching Users

The authorship/v1/users REST API endpoint provides a means of searching users on the site in order to attribute them to a post. Access to this endpoint is granted to all users who have the capability to change the attributed authors of the given post type, which means Editors and Administrators by default. The result is no change in behaviour from WordPress core with regard to being able to search users.

In addition, this endpoint has been designed to expose minimal information about users, for example it does not expose email addresses or capabilities. This allows lower level users such as users with a role of Author to be granted the ability to attribute users to a post without unnecessarily exposing sensitive information about other users.

Creating Guest Authors

The authorship/v1/users REST API endpoint provides a means of creating guest authors that can subsequently be attributed to a post. Access to this endpoint is granted to all users who have the ability to edit others' posts, which means Editors and Administrators by default.

More work is still to be done around the ability to subsequently edit guest authors, but it's worth noting that this is the one area where Authorship diverges from the default capabilities of WordPress core. It allows an Editor role user to create a new user account, which they usually cannot do. However it is tightly controlled:

  • An email address cannot be provided unless the user has the create_users capability, which only Administrators do
  • A user role cannot be provided, it is always set to Guest Author

Capability Customisation

The following custom user capabilities are used by Authorship. These can be granted to or denied from users or roles in order to adjust user access:

  • attribute_post_type
    • Used when attributing users to a given post type
    • Maps to the edit_others_posts capability of the post type by default
  • create_guest_authors
    • Used when creating a guest author
    • Maps to edit_others_posts by default

Contributing

Code contributions, feedback, and feature suggestions are very welcome. See CONTRIBUTING.md for more details.

Team

Authorship is developed and maintained by Human Made and Altis. Its initial development was funded by Siemens.

Human Made Altis DXP Siemens

License

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

Alternatives

If the Authorship plugin doesn't suit your needs, try these alternatives:

authorship's People

Contributors

goldenapples avatar johnbillion avatar kadamwhite avatar kovshenin avatar mattheu avatar mikelittle avatar nlemoine avatar rmccue avatar roborourke avatar shadyvb avatar tfrommen avatar tomjn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

authorship's Issues

Basic template function

Implement a basic template function that accepts a post object and returns array of WP_User objects related to the post. The taxonomy term that creates the connection between post and users is abstracted from and never seen by the user.

Introduce ability to filter post listing screen by editorial author

As a result of #9 the "Mine" link on the post listing screen will switch to showing posts that you're an assigned author of. We therefore need to either:

  • Convert the "Mine" view into a combo view that shows both posts that you're an assigned author of and the editorial author of (which would mean a combined query for the term and the post_author field), or
  • Introduce a new filter for viewing posts that you're the editorial author of.

Also, really need to nail the terminology. Too many authors.

Provide multiple author names in RSS feed items

When a post is attributed to multiple authors they should all be displayed in the RSS feed item for that post. WordPress uses the Dublin Core namespace for author information in the <dc:creator> element.

The RSS spec is famously vague. The RSS Best Practices Profile says An item MAY contain more than one dc:creator element to credit multiple authors but in practice it seems that RSS clients only display the first. For example SimplePie fully supports multiple <dc:creator> elements but the RSS widget in WordPress core will only display the first.

This means that multiple authors should all be listed in the same <dc:creator> element.

The data for this element comes from the_author() so this might be implemented at that level. Needs investigation as the_author() is used in many places.

Template functions for listing authors

Following on from #6, add two more basic template functions for displaying a list of authors for a post:

  1. A comma separated-list of authors
  2. An unordered list of authors

In both cases the display name for each author should be used, and the link for each author should link to their author archive.

Multiple author selector on the classic editor post editing screen

  • Try to use the same component from the block editor control
  • The Author meta box is hidden by default in the classic editor. Disable it completely.
  • Insert the component in the Publish panel instead of a meta box.
  • Use a server-side REST API request to save the author data?
  • Update documentation

Migration process from PPA

We need a means of migrating an existing site that uses PPA.

  • WP-CLI command for iterating all existing posts and assigning the new Authorship terms
  • Build this in a way that the underlying functionality is reusable so we can introduce a cron event to do this for sites that don't have access to WP-CLI

Marking this as a P3 priority, not because it's not important but because we need the main structure of the plugin in place first.

Create taxonomy

  • Create an authorship taxonomy
  • Associated with all post types that have post type support for author
  • No UI, no public query vars, etc
  • Standard capabilities for now

Prevent guest authors from logging in, disable account related functionality

A user with a role of Guest Author should not be able to log in at all.

  • Password should be set to a long random password when a guest author is created
  • Logging in should be denied at the authentication level
    • Logging in at wp-login.php
    • WordPress core application passwords
    • REST API Basic Auth
    • XML-RPC
  • Ensure the guest author does not get sent an email when their account is created
  • Disable password reset functionality for the user

The Disable Accounts plugin should do most of what we need. If there's anything missing, let's send fixes upstream to that plugin too.

Further WP_Query overrides

Follows on from #9.

Need to update all other author-related query variables, eg. author__in, and convert them to a term query.

Integration with audit log plugins

When a post is updated, changes to the attributed authors need to be recorded in audit trail plugins.

The three big ones are:

When the Authorship plugin is not in use and the author of a post is changed:

  • Simple History correctly logs a change
  • WP Activity Log correctly logs a change
  • Stream does not log the change

Tasks:

  • File a bug with Stream about the above xwp/stream#1222
  • Write an integration for Simple History
  • Write an integration for WP Activity Log
  • Write an integration for Stream

REST API endpoint for listing authors

Similarly to #16 we need a new REST API endpoint for searching and listing all authors from the context of a user who can assign authors to a post.

We need this because we'll allow lower level users to search authors and I don't want to change the permissions of the /users endpoint.

  • Should require authentication as there's no use case for using this in an unauthenticated situation. You can use /users unauthenticated to fetch a list of users with published content.
  • Will be primarily used for searching users from the authors control on post editing screens

Replace post type supports checks with a filterable function

The ability to assign attributed authors to a post is directly connected to whether the post type supports author. These checks should be replaced with calls to a function which encapsulates this check and a filter on its return value, so the feature can be enabled and disabled for all post types without necessarily affecting the post type support.

Maybe even check for a custom post type support of authorship too.

Correct author archive title when queried author isn't author of first post

The get_the_archive_title() function in WordPress core uses the first post in its results as the basis for the title that it constructs for the heading on archives. It should instead use the queried user.

The effect this has is that until we decide how we're handling the $authordata global, this title needs to be manually fixed so it uses the actual queried user on author archives as it currently shows the incorrect user display name if the queried author isn't the same as the post_author of the first post in the results.

Problems identified with existing multi-author and guest author plugins

These issues apply to PublishPress Authors, but mostly affect Co-Authors Plus too.

  • Unnecessary distinction between guest authors and regular users, complicates things like queries and logic, unfamiliar UI and UX, data duplication
  • Inconsistent distinction between editorial author and attributed author depending on whether the post is attributed only to guest author(s) or also to real WP user(s)
  • No REST API or WP-CLI support for CRUD, both for guest authors and the connection for an author with a post
  • No means of syncing guest author data between sites on a Multisite network
  • User capability management isn't very granular and doesn't match core by default
  • Poor UI on post editing screen, uses an old meta box cramped at the bottom of the screen
  • Too much PublishPress branding, why are its admin screens purple?
  • A user needs to create a guest author on another admin screen before it can be used - no way to create one on the fly from the post editing screen
  • Any other user has to be added as an "Author" before they can be attributed on a post, although there is an option to automatically do this for selected roles
  • Duplication of user profile management - if I install a plugin to manage avatars then managing the profile for a guest author is handled differently to that of a regular user
  • When a user is connected to an "Author" all their user fields are duplicated into term meta, but they are not kept up to date when either the user or the term meta is changed
  • If I want to "upgrade" a guest author to an actual user then I have to create the user, then edit the guest author and select their user account, but their data is not synced properly
  • When querying a guest author archive the queried object becomes a term instead of a user, it's inconsistent as an author archive for a real user is a user object
  • Uses \MultipleAuthors\Classes\Objects\Author as a "fake" user object that's compatible with WP_User
  • Multiple "Authors" can be associated with the same user and then author archives break completely

Document the behaviour when deactivating the plugin

Need to document what happens when the plugin is deactivated.

  • Who is attributed to each post?
    • A: The original author of the post
  • Who is able to edit each post?
    • A: The original author of the post, plus Administrators and Editors
  • What happens to Guest Authors?
    • They can now log in
    • They cannot perform any action, they have no capabilities
    • The role will be retained in the database unless it's explicitly removed with remove_role()

Build CSS and JS assets for release

Same problem as johnbillion/query-monitor#551 . We need a mechanism to build the CSS and JS assets for deployment via Composer, and potentially for WordPress.org if we decide to release it there.

Probably go for the same approach, where a GitHub Action builds the plugin into a separate branch and tags it.

Identify functional testing approach for author selection component

The underlying component(s) that we use for the author selection control on the post editing screen will hopefully be tested, but it would be great to have functional tests in place to ensure it actually works in the context of where we use it (the block editor in #7 and the classic editor in #13). Suggestions for testing approach and framework welcome.

Options

Multiple author selector on the block editor post editing screen

Implement multiple author selector on the block editor post editing screen

  • Component that sits within the "Status & Visibility" panel to replace the core one
  • Multi-select input
  • Search and select from all users using the standard /wp/v2/users endpoint for now
  • Default value for a new post should contain the current user
  • Initial list of authors when editing an existing post can come via wp_localize_script() or via the authorship property of the post in the REST API (#5) as this is preloaded with the API middleware on the editing screen
  • Display user avatar and display name
  • Sortable
  • Saves author connections when the post is saved

Potential libraries

  • Reach UI Combobox
    • πŸ’ͺ Strong focus on accessibility
    • 😞 Missing some features like a "create..." option
    • ❓ Not sure if it has dependencies on the rest of Reach UI?
  • React Select
    • πŸ’ͺ Very feature rich
    • βž• More widely used
    • 😞 Has accessibility issues
  • Adobe Spectrum Picker suggested by Ryan
    • πŸ•™ Needs investigating
    • ❓ Seems to only support one selected option?

WP-CLI command for adding guest authors

Similarly to #16 , a convenience command for adding a guest author should be added to WP-CLI which has the same list of required and optional fields as its REST API endpoint counterpart.

WP-CLI support for authors for a post

When updating posts via WP-CLI with wp post update, there should be a flag that can be used to set multiple post authors.

  • Should work the same as other WP-CLI flags that accept a list of IDs, most likely a comma separated list
  • Introduce this as a new flag, eg --authorship=1,2,3
  • The underlying behaviour can probably be enabled by a custom parameter for wp_insert_post(), but needs investigating

Send comment notification to all post authors

If the site is set up to send email notifications for newly published comments (under Settings -> Discussion -> Email me whenever -> Anyone posts a comment), all of the post authors with a valid email address should receive the comment notification email.

Investigate whether assumed approach of taxonomy terms that store a user ID in their name/slug works

The plan is to map a user to a post via a term. This is similar to how CAP and PPA work, except the term will not contain any user-facing data and instead reference a user ID directly via its name and/or slug field.

If we imagine that the WordPress database used foreign keys, then the structure would look like this:

┏━━━━━━━━━━━━━━━━┓     ┏━━━━━━━━━━━━━━━━┓     ┏━━━━━━━━━━━━━━━━┓     ┏━━━━━━━━━━━━━━━━┓     ┏━━━━━━━━━━━━━━━━┓
┃    wp_posts    ┃     ┃    wp_term_    ┃     ┃    wp_term_    ┃     ┃    wp_terms    ┃     ┃    wp_users    ┃
┃                ┃     ┃ relationships  ┃     ┃    taxonomy    ┃     ┃                ┃     ┃                ┃
┗━━━━━━━━━━━━━━━━┛     ┗━━━━━━━━━━━━━━━━┛     ┗━━━━━━━━━━━━━━━━┛     ┗━━━━━━━━━━━━━━━━┛     ┗━━━━━━━━━━━━━━━━┛
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚       ID       β”œβ”€β”€β”€β”€β”€β”€   object_id    β”‚  β”Œβ”€β”€β”€term_taxonomy_idβ”‚  β”Œβ”€β”€β”€    term_id     β”‚  β”Œβ”€β”€β”€       ID       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚term_taxonomy_idβ”œβ”€β”€β”˜  β”‚    term_id     β”œβ”€β”€β”˜  β”‚      slug      β”œβ”€β”€β”˜
                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                                         β–²
                                                                                         ┃
                                                                                         ┃
                                                                                 ┏━━━━━━━┻━━━━━━━┓
                                                                                 ┃ Term ◀─▢ User ┃
                                                                                 ┗━━━━━━━━━━━━━━━┛

We need to do a bit of investigation into whether storing a numeric ID in the name or slug field of a term works as expected. For example, give a term with term_id => 1 and slug => 2:

  • Is it possible to query for term with a slug of 2 or will WordPress internally convert that to a query for the term_id?
  • Does this work in some situations and not others?
  • Would it be safer to add a prefix to the name or slug to avoid confusion around IDs versus names and slugs?
  • Or, maybe WP_Term_Query works as expected now and we're all good.

Provide multiple author names in Atom feed items

Needs investigation. Similarly to RSS (see #32) Atom supports multiple <author> elements but feed clients may only use the first. For example, the RSS widget in WordPress core only displays the first author from an Atom feed if there are multiple.

Send comment moderation notifications to all appropriate authors

If the site is set up to send email notifications for comment moderation (under Settings -> Discussion -> Email me whenever -> A comment is held for moderation), any of the post authors who have the capability to moderate the comment should also receive the comment moderation email.

Investigate accessibility issues of React Select

Unfortunately we weren't able to use Reach UI Combobox as it doesn't support multiple selection.

Some cursory Googling tells us that React Select by default has some accessibility issues. These need to be tested, listed, and then investigated.

WP_Query overrides

  • Adjust WP_Query to override the standard author_name query var and convert it to use a term query so front end author archives work - should only affect post types that support author
  • Adjust WP_Query to override the standard author query var and convert to a term query so author filtering in the admin area works - should only affect post types that support author

REST API read/write for authors of a post

  • New property on any route that accepts or returns a post object:
    • /posts
    • /post/X
    • /pages
    • /pages/X
    • Custom post type routes
  • Reading: Returns an array of user ID integers assigned to the post
  • Writing: Accepts an array of user ID integers to assign as authors of the post
  • Add a new _links element containing a corresponding array of user links
  • Might want to also provide this data as read-only for post types that don't support author for a consistent interface when reading data

Multiple editorial users idea

This is an idea that most likely will not be implemented due to the low value compared to its high complexity, but it serves as a place to see the thought process and discuss the idea in the future.

Idea

Along with supporting multiple attributed authors for a post, it should be possible to support multiple editorial authors for a post. An editorial author is an author who can edit the post but is not necessarily attributed.

Use Case

A post where multiple Author or Contributor level users need to be able to edit the post but not be attributed. Useful for a future collaborative editing workflow.

Notes

  • A post needs to be editorially owned by one or more WordPress users, all of which gain their respective role's capabilities for that post. For example, an editor could add a contributor to the post, and the contributor could edit but not publish it or add further editorial owners. Users can only choose from existing users who have the edit posts capability for that post type.
  • There should be a preference to control which user roles can add additional editorial owners to a post, by default only users who can change the author (I think this is tied to edit others posts). Setting on a per site and per network basis, with filters to hardcode the option and remove its UI.
  • The post listing screen needs to have filters for filtering posts by attributed authors and by editorial owners.
  • A new WP query argument and REST API endpoint should be added for querying by editorial owner. Another term query.
  • The ID of the first user in the list of editorial owners should be set as the post_author field. This allows for some level of compat with plugins which read/write to post_author directly, and also allows for sane behaviour when the plugin is deactivated.
  • A post with no separate attributed authors needs to be attributed to its editorial owner. For query reasons this needs to be explicit. I think the UI for attributed author needs to remain empty until the post is published, so an editor can write drafts and only choose the attributed author before publication without needing to remove themselves from the list.

Granular user capability mapping

Introduce granular user capability mapping for controlling:

  • Who can assign authors to a post
  • Who can create a new guest author
  • Subsequently who can manage guest authors (probably a separate issue)

Document the behaviour of unfiltered_html for lower level users

If an Administrator or Editor places some JavaScript into the content of a post and then adds an Author or Contributor as an attributed author, the JavaScript will be stripped out if the Author or Contributor make an edit to the post. This is due to the unfiltered_html capability.

We mustn't change this functionality, but it ought to be documented so users are clear about what happens in this situation.

Plugin Review

πŸ‘‹πŸ˜ƒ

As already discussed, I have taken some time to review the current state of Authorship, looking both at the codebase, the functionality and user experience, and developer tooling.

Here's what I've got!

Some of the things I already addressed in a PR. Feel free to take as is, or cherry-pick stuff you like, or close entirely. I thought it made sense to implement some of the changes that are quick for me to do, and just a yes/no decision for you. There's more to do where I didn't want to pose anything, so I left these things completely for you.

User Experience

Input Element

The input element is, by default, just as wide as the content. If it's empty, this means that this is just about 2 pixels, making the cursor not appear as Text tool:

image

I suggest to always render the input as wide as its container element:

image

I know that you already have some further UX improvements in mind, including label, border etc., so I'm not going to comment on any of that.

JavaScript Code

Architecture

In my opinion, both the index.tsx and the authors-select.tsx files are doing too much (different things, even).

In the end, the index file will register a plugin that renders the Authorship multi-select input into the PluginPostStatusInfo slot, located in Status & visibility panel. The settings and code that make up that plugin/select would ideally live in one or more dedicated files.

The authors-select.tsx file includes type definitions/interfaces, helper functions, and the actual component, but not the HOC definitions from the index.tsx file. I suggest to split off all type definitions into a new types.ts file, move the helper function into a new utils.ts file or better even individually into utils/arrayMove.ts, and move the complete definition of the Select component, which is currently located in the index file, into the component file. Here, I would extract the anonymous arrow functions passed to both withDispatch and withSelect to allow for better readability and testability (if JS unit tests were ever to get added).

Inside the authors-select.tsx file, both the createOption and the formatOptionLabel functions are independent of anything defined in the AuthorsSelect component/function, hence I suggest to move these functions out of the component. There's no point in redeclaring them on every render.

The SortableMultiValueElement component is independent of anything other than react-select and react-sortable-hoc, meaning only third-party packages. I suggest to move this into a new component file.

And the SortableSelectContainer, as well as the Select component could live in a separate file, too. This would allow for abstracting away some hard-coded props such as class names, formatting, and further configuration, and it would also move the new SortableMultiValueElement component out of the authors-select.tsx file.

Based on previous projects, I suggest the following architecture and file organization, allowing for a better separation of concerns, and thus easier maintainability:

src
+- components
|  +- AuthorsSelect.tsx
|  +- SortableMultiValueElement.tsx
|  +- SortableSelectContainer.tsx
+- utils
|  +- arrayMove.ts
+- index.tsx
+- plugin.tsx
+- style.scss
+- types.ts

πŸ‘‰ I addressed all of the above in #42.

Inline Styles

Why did you decide to use inline styles in the formatOptionLabel function? Could we just add a custom CSS class instead, and define the styles in the style.scss file?

Also, do we really need the additional wrapper around the avatar?

(Not) Using the wp Global

I suggest to not directly reference the wp global, but instead import anything WordPress off the respective @wordpress/... packages/externals.

The two wp.apiFetch references would then be replaced by import apiFetch from '@wordpress/api-fetch';, and wp.plugins.registerPlugin would become import { registerPlugin } from '@wordpress/plugins';. For the latter, we would need to add the @types/wordpress__plugins dependency, as TypeScript doesn't recognize the registerPlugin, for whatever reason. And due to a minor bug in the typings, we now have to pass the icon property in the settings object. It should be marked as optional, but it's not. Adding icon: null is not a big deal, though.
πŸ‘‰ I addressed this in #42.

This also means that we can remove the declare const wp: any; declarations, since wp is not used anymore.

PHP Code

PhpDoc

PhpDoc is "aware" of the current file's scope and imports. So, if you imported (i.e., used) some class, you don't have to provide its FQN. Therefore, you can remove the leading backslash for all WP class such as Β΄\WP` in parameter or return types.
πŸ‘‰ I addressed this in #40.

Array Diff

In the namespace.php file, there are a few places where you "remove" from a given list of capabilities all values that you defined elsewhere. You are doing this by using array_filter and in_array:

$caps = array_filter( $caps, function( string $cap ) use ( $remove ) : bool {
	return ! in_array( $cap, $remove['delete'], true );
} );

Is there any reason you are not using array_diff?

$caps = array_diff( $caps, $remove['delete'] );

This is much less code, no extra function, and should result in the same list of capabilities...?
πŸ‘‰ I addressed this in #40.

Hook Callback Signatures

Looking at the code, it seems you defined the singatures of action and filter callbacks to be all arguments provided by do_action and apply_filters, respectively.

Personally, I feel this is a bit of unuseful information, especially if you have @param documentation, too. I understand this will save yourself some time if you ever were to need one or more of the currently unused arguments, but it might as wellmean more time spent when creating the function initially. But this is personal preference, I guess. πŸ˜‰

Use API Functions

Instead of accessing globals directly, I suggest to make use of API functions, where available.

For example, I would use is_author() instead of $wp_query->is_author() (where you first have to "import" the $wp_query global anyway), and get_query_var() instead of $wp_query->get(). This also makes for easier (proper) unit testing.
πŸ‘‰ I addressed this in #40.

Hook Logic

Instead of checking $unsanitized_postarr['tax_input'][ TAXONOMY ] inside the filter callback added to wp_insert_post, I suggest to check it beforehand, and only register the callback if necessary. Or do you expect it to change (which is technically possible, of course, but not what should happen)...?

In the nested filter callback added to views_edit-{$post_type} in the init_taxonomy function, there is some data that is independent of the individual post type, and yet it is computed over and over again. I suggest to move this out to happen before the array_map (orrather: array_walk, see further down) call. Things I am looking at include the user ID, the term object, and maybe even parts of the "mine" link/message.
πŸ‘‰ I addressed this in #40.

Still in the same nested filter callback, if there is no all view (because a plugin removed it), there will also be no mine view as it depends on all to exist. Instead of the more manual foreach-and-compare approach, I would not unset the "mine" filter always, but overwrite it with the new link. And only if there is no term object for the current user, which means there is no post where they are any kind of author, I would remove the "mine" link.
πŸ‘‰ I addressed this in #40.

Possible Errors

In the filter_rest_post_dispatch function in namespace.php, if the author param is set, but not an array, the foreach will break. I'm not sure what the best way to handle this is, but you could either check for array, or cast to array, or something else entirely.

In the init_taxonomy function in namespace.php, you are using array_map, but you are not doing anything with the data. I assume this should be array_walk, or simply foreach instead?
πŸ‘‰ I addressed this in #40.

Same thing in the init_admin_cols function in the admin.php file. I feel this should be a foreach, not requiring a new anonymous function, but could be array_walk as well. Just not array_map. πŸ˜‰
πŸ‘‰ I addressed this in #40.

Similar to the "mine" view mentioned before, the filter_post_columns function in admin.php, will not add the authorship admin column if the native "author" column has been removed (by some other plugin). If this is what you want, then cool. If we want to render the authorship column always, and just make sure the author one does not render, you would need to adapt the code.

REST Link

I feel the 'https://authorship.hmn.md/{rel}' string, used in filter_rest_response_link_curies could be defined as a constant, just like all the other bits and pieces.
πŸ‘‰ I addressed this in #40.

Taxonomy

The namespace.php file is quite long. How do you feel about splitting of everything directly related to the authorship taxonomy into a new, taxonomy.php file?

Preloading Author Data

Do you think its necessary to preload the author data?

I mean, in theory it's not a bad idea, of course. But there are a few advantages to making an explicit REST API request from within the Block Editor to fetch the author data. The native author select is super slow and I can't think of a reason why users would need to see the UI instantly.

If we'd request the data from the JavaScript app, we wouldn't need to rely on a global variable, which means less manual TypeScript stuff.

But maybe this is what you mean with the TODO not in that function. I wasn't really sure how to understand that comment.

get_author_ids Function

I suggest to introduce a new function, get_author_idsthat would replace thewp_get_post_termsand subsequentarray_map-intvalcalls currently used twice in thetemplate.php` file.

This would make for easier unit testing, and it simplifies both the get_authors and user_is_author function.
πŸ‘‰ I addressed this in #40.

Tests

Just a note that I have not taken a look at the PHP tests, yet.

Tooling

.gitignore

I like the alphabetical sorting. Maybe list folder names before files, though?
πŸ‘‰ I addressed this in #39.

Why is the composer.lock file ignored? The plugin has a Composer production dependency, Asset Loader. Even though the package has an exact version contstraint, I suggest to not ignore the composer.lock file.

Why is the package-lock.json file ignored? The plugin has a few NPM production dependencies, including react-select and react-sortable-hoc. I suggest to not ignore the package-lock.json file.

Composer

Is there anything preventing this from running on PHP 8? If not, I suggest to change the PHP dependency in the composer.json file to "php": ">=7.2".

Just curious, why are you using exact version constraints for some dependencies?
Personally, I would default to non-breaking minors (i.e., using caret format, ^1.2.3) and patch-level releases where I'm unsure about the nature of changes in individual releases (e.g., ~1.2.3).

jq

As far as I can see, the node-jq package is only being used to read a property in the Composer config file, ... for a script inside that very file. This package requires quite a bit of overheadβ€”using WSL 2 on Windows, I had to install more than 130 MB of additional build tools to be able to build jq while running npm install. I suggest to remove the node.jq dependency and script, and hard-code the WordPress path in the composer.json file twice.
πŸ‘‰ I addressed this in #39.

NPM

Are you planning on using HM Linter for Authorship?
If yes, I suggest to use exact version constraints for the HM Coding Standards, set to the same version that HM Linter would use. For the PHP Coding Standards, you already do this. For both JS and (S)CSS, you use ^1.1.1.

There are some dependencies where I suggest to double-check that they are in the correct class, production vs. development-only dependencies:

  • Can the @types/react-select package be a development dependency?
  • Should the classnames package be in dependencies?

The lint script, running two commands concurrently, could do with a tiny bit of more information such as the file format (JS and CS, respectively), and maybe a bit of color.
πŸ‘‰ I addressed this in #39.

PHP_CodeSniffer

Consider adding <arg value="s"/> to the config file, instead of specifying -s on the command line (i.e., the test:phpcs script in the composer.json file). This is extra information that shouldn't negatively affect anything anyway, and, currently, both local usage and GitHub Actions already always print Sniff information.
πŸ‘‰ I addressed this in #39.

Do not lint the build/ folder.
πŸ‘‰ I addressed this in #39.

The PSR2R.Namespaces.UseInAlphabeticalOrder sniff has been removed from the PSR2R ruleset. However, the HM Coding Standards are not quite up to date in terms of PSR2R version. I suggest to exclude the sniff for now.
πŸ‘‰ I addressed this in #39.

The PSR2R.Namespaces.UnusedUseStatement sniff does not respect usage in PHP DocBlock or inline comments. Given that IDEs such as PhpStorm and VS Code require use statements for symbols referenced in comments (in order to be able to navigate to the respective file), I suggest to exclude the sniff (and continue using symbols in non-FQN format).
πŸ‘‰ I addressed this in #39.

TypeScript

Also exclude the lib/ folder.

The code is using native ECMAScript modules, not CommonJS. So I suggest to configure TypeScript accordingly, for example, by using "module": "esnext" and "moduleResolution": "node".

Personally, I like to import actual modules and functions, and not namespaces, so instead of this:

import * as React from 'react';

React.useState( /* ... */ );

I would use that:

import React, { useState } from 'react';

useState( /* ... */ );

By default, TypeScript will complain about the React import, but you can add "allowSyntheticDefaultImports": true to the tsconfig.json file.

GitHub Actions

Why not allow fast failures?

What is (still?) failing when using Composer v2? Can we remove the composer:v1 directive for the "Install PHP2 step?

When installing PHP dependencies, we don't care about progress, and we can't handle any interactions, so let's use the --no-progress --no-interaction flags.
Also, if you don't want tosee any regular output from Composer, you can use the -q flag, which will then replace both --noprogress and --no-suggest, and it will even strip further output. Errors would still get printed to the console, so you would see them on GitHub.
πŸ‘‰ I addressed this in #39.

Both ESLint and stylelint are not executed on CI.
πŸ‘‰ I addressed this in #41.

PHPUnit

For better coverage information, I suggest to tell PHPUnit where the source code lives. This is done by adding filter.whitelist information.
πŸ‘‰ I addressed this in #39.

The PHPUnit config is missing XML schema information, and the current config is not valid. testsuites.testsuite.directory does not support prefix until PHPUnit 8. I suggest to remove the property, and use exclude for undesired non-test files.
πŸ‘‰ I addressed this in #39.

ESLint

ESLint is incorrectly set up. All JavaScript files are actually TypeScript files, having .ts or .tsx as extensions. By default, ESLint does not lint these files, even with the TypeScript parser and/or plugin configured.

Also, ESLint is outdated: currently v5, while v7 is the most recent version branch. In order for everything to work as expected, ESLint and select dependencies have to get udpated, and the config file, as well as the lint:js script in the package.json file needs updating, too.


Great Work! πŸ‘

User capability mapping

User capability mapping so the capabilities of assigned authors acts exactly as if they are the post_author.

Some examples that should be converted into test cases:

  • If an Author and a Contributor are marked as the assigned authors of a post, the Author can publish the article but the Contributor cannot.
  • If an Editor and an Author are marked as the assigned authors of a post, the Editor can change the assigned authors by default but the Author cannot. (Note that in the future an option will be added to control this).

REST API endpoint for guest authors

Introduce a full CRUD endpoint in the REST API for guest authors.

This is needed because we're going to allow lower level users (eg. Authors) to create guest authors, and I don't want to mess about with the permissions of the /users endpoint, and using a new endpoint abstracts this away from user storage.

Mostly expects and exposes the same fields as the /users endpoint, except:

  • Email address should be optional, need to decide on strategy for populating users with a fake email address if one isn't provided, or whether allowing no email address works well
  • User login should be optional and will be generated if one isn't provided
  • role won't be required as the user will always have the guest author role
  • The capabilities required to read and write to this endpoint need to be filterable
  • Need to consider whether this endpoint will behave the same as the /users endpoint for non-authenticated requests, eg. by listing all guest authors that have published content

Guest author role

  • Create new user role of "Guest Author" with no permissions
  • This allows a guest author to be represented by an actual WordPress user with a role of guest author. This allows author archives, profiles, avatars, social links, opengraph, etc to work without any customisation, and allows for user promotion at a later date. See #2 for more info.
  • Fine grained permission for other users to manage guest authors to come later

Remove the default "Author" control on the block editor screen

The default "Author" control should be removed from the block editor screen. Note that in WordPress 5.6 this will change from a standard <select> to a searchable input, so the removal needs to work in 5.6 as well as earlier versions.

I did some cursory research and it seems there's no means of removing this programatically unless support for author is removed from the post type, which isn't desirable.

Other things to try:

  • Hiding the control with CSS
  • Removing the control from the DOM with JS
  • Figuring out a way to prevent the React component from rendering

Decide on XML-RPC compatibility

XML-RPC is unlike the REST API in that its routes are standardised so we can't change them to allow, for example, multiple authors for a post to be specified when a post is created or edited.

Subsequently I don't think it makes sense to try to expose multiple author information via XML-RPC either. What we do need is to decide exactly how XML-RPC should behave, and ensure it does. It might be that author information in XML-RPC should continue to correlate directly with the post_author field.

Avoid initial REST API requests when the component first mounts

When the component first mounts, and every time it remounts without the user list having been changed, we can pull the current list of user obects from the _embedded attribute of the post instead of via the REST API. This will save REST API requests.

Need to add support for embedding the user objects when the _embed query parameter is used.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.