Skip to content

Instantly share code, notes, and snippets.

@lubieowoce
Last active June 1, 2023 01:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lubieowoce/46d917b10a85d4195185dff347db9206 to your computer and use it in GitHub Desktop.
Save lubieowoce/46d917b10a85d4195185dff347db9206 to your computer and use it in GitHub Desktop.
Integrate Yoast with CoAuthors Plus
<?php
// Written against:
// - Yoast SEO 12.3
// - Co-Authors Plus 3.4
// - Wordpress 4.9.3
// - PHP 7.1
/*
[MIT License]
Copyright 2019 J. Uryga (lolzatu2@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// site-specific, change the Schema type of posts to CreativeWork.
// set to a falsey value if not needed
const REPLACE_POST_SCHEMA_TYPE = 'CreativeWork';
function coauthor_schema_id($coauthor, $context) {
// mirrors `WPSEO_Schema_Utils.get_user_schema_id`
return $context->site_url . \WPSEO_Schema_IDs::PERSON_HASH . wp_hash( $coauthor->user_login . $coauthor->ID );
}
function coauthor_page_schema_id($coauthor, $context) {
// mirrors `WPSEO_Schema_WebPage.generate`
return $context->canonical . WPSEO_Schema_IDs::WEBPAGE_HASH;
}
function get_coauthor_schema_data($coauthor, $context) {
$id = coauthor_schema_id($coauthor, $context);
$data = [
'@id' => $id,
'@type' => ['Person'],
'name' => $coauthor->display_name,
];
if (!empty($coauthor->description)) {
$data['description'] = strip_tags($coauthor->description);
}
if (!empty($coauthor->website)) {
$data['sameAs'] = $coauthor->website;
}
return $data;
}
function get_post_coauthors_schema_data($post_id, $context) {
$coauthors = get_coauthors($post_id);
$pieces = [];
foreach ($coauthors as $coauthor) {
$pieces[] = get_coauthor_schema_data($coauthor, $context);
}
return $pieces;
}
function get_WPSEO_Schema_Context() {
// TODO: figure out a way to get the real context
// instead of creating a new one
// (although they seem functionally equivalent)
return new \WPSEO_Schema_Context();
// if necessary, we can fake the context.
// (mirrors WPSEO_Schema_Context.build_data)
// apparently it's important to not call WPSEO_Frontend::get_instance()
// too early. it's fine to do it in the `wpseo_*` hooks though
// $front = \WPSEO_Frontend::get_instance();
// $context = (object) [
// 'canonical' => $front->canonical(/*echo*/ false, /*un_paged*/ false, /* no_override */ true ),
// 'site_url' => trailingslashit( \WPSEO_Utils::home_url() ),
// ];
// return $context;
}
function is_coauthor_archive() {
return (
is_archive() &&
is_author() &&
is_coauthor(get_queried_object_id())
);
}
function is_coauthor($user_id) {
// TODO: can a user ID and a guest-author-post ID conflict?
// TODO: consider changing to methods that don't fetch
// an entire user object
global $coauthors_plus;
return (
!get_user_by('id', $user_id) &&
((bool) $coauthors_plus->get_coauthor_by('id', $user_id))
);
}
add_action('plugins_loaded', function () {
// the easiest way would be to somehow hook into Wordpress' `get_userdata`
// (since that's mostly what Yoast uses)
// and return the relevant coauthor's data instead.
// unfortunately i haven't found a way to do that,
// so we need to fix all of the following by hand using Yoast's filters.
global $coauthors_plus;
$should_run = (
class_exists('WPSEO_Schema_Author') &&
class_exists('WPSEO_Utils') &&
class_exists('WPSEO_Schema_IDs') &&
class_exists('WPSEO_Utils') &&
class_exists('WPSEO_Frontend') &&
interface_exists('WPSEO_Graph_Piece') &&
isset($coauthors_plus)
);
if (!$should_run) { return; }
// Give Yoast a null user-id for Guest Author profiles.
// Otherwise, Yoast will try to use the [coauthor post] ID as a user ID
// and will get a lot of nulls (or garbage data)
add_filter('wpseo_schema_person_user_id', function ($id) {
if (is_coauthor($id)){
return false;
} else {
return $id;
}
});
// Give Yoast the correct 'Name' and 'User description' snippet variables
// for Posts and Author Archives.
// (Yoast uses the [user who created the coauthor]'s name for the page title)
add_filter('wpseo_replacements', function ($replacements, $replacements_source) {
global $coauthors_plus;
if (is_coauthor_archive() || is_single()) {
$coauthor = false;
if (is_single()) {
$coauthor = get_coauthors()[0] ?: false;
}
else {
$object_id = get_queried_object_id();
$coauthor = $coauthors_plus->get_coauthor_by('id', $object_id);
}
if ($coauthor) {
$replacements['%%name%%'] = $coauthor->display_name;
if (!empty($coauthor->description)) {
$replacements['%%user_description%%'] = strip_tags($coauthor->description);
}
return $replacements;
}
}
return $replacements;
}, /*priority*/ 10, /*n args*/ 2);
// Give Yoast the correct Open Graph `article:author`
// Only supports one author, although Open Graph allows multiple
// (just add multiple `<meta property="article:author" value="...">` tags)
// We could hook into the `wpseo_opengraph` and emit the additional
// authors ourselves, but this is good for now
add_filter('wpseo_opengraph_author_facebook', function($facebook) {
// technically, the `article:author` can be
// any link to a site with `og:type = profile`
// (not necessarily a facebook link)
// but i don't think anyone actually does that.
// e.g. Yoast could do it for author archives, but they don't.
// so we only add the url if it's a facebook profile
if (is_single()) {
$coauthor = get_coauthors()[0] ?: false;
if ($coauthor &&
!empty($coauthor->website) &&
strpos($coauthor->website, 'facebook.com') !== false
) {
return $coauthor->website;
}
}
return $facebook;
});
// All the following filters (`wpseo_schema_{author|article|person}`)
// are called from `WPSEO_Schema.generate`.
// (their names are dynamically constructed based on class names,
// so they're tricky to find in the source)
// Give Yoast the correct author(s) for posts
// NOTE: assumes we only care about guest-authors
// and that only single posts need authors
add_filter('wpseo_schema_author', function ($author) {
if (is_single()) {
$context = get_WPSEO_Schema_Context();
$post = get_post();
$coauthors = get_post_coauthors_schema_data($post->ID, $context);
return $coauthors; // we don't care about the user-author
} else {
// only single posts have authors that need to be shown
return false;
}
});
// Assign the correct authors to Articles
// NOTE: assumes we only care about guest-authors
// and that only single posts need authors
add_filter('wpseo_schema_article', function ($article) {
if (is_single()) {
$context = get_WPSEO_Schema_Context();
$post = get_post();
$coauthors = get_post_coauthors_schema_data($post->ID, $context);
if (REPLACE_POST_SCHEMA_TYPE) {
$article['@type'] = REPLACE_POST_SCHEMA_TYPE;
}
$article['author'] = $coauthors; // we don't care about the user-author
}
else {
// only single posts have authors that need to be shown
unset($article['author']);
}
return $article;
});
// Give Yoast the correct Person for coauthor archives
add_filter('wpseo_schema_person', function ($person) {
if (is_coauthor_archive()) {
global $coauthors_plus;
$context = get_WPSEO_Schema_Context();
$object_id = get_queried_object_id();
$coauthor = $coauthors_plus->get_coauthor_by('id', $object_id);
if ($coauthor) {
$data = get_coauthor_schema_data($coauthor, $context);
$page_id = coauthor_page_schema_id($coauthor, $context);
$data['mainEntityOfPage'] = [
'@type' => 'ProfilePage',
'@id' => $page_id,
];
return $data;
}
}
return $person;
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment