Skip to content

Commit

Permalink
feat(content-distribution): canonical url (#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
miguelpeixe authored Jan 6, 2025
1 parent 876351a commit 5ca60ce
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 51 deletions.
3 changes: 3 additions & 0 deletions includes/class-content-distribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Newspack_Network\Content_Distribution\Admin;
use Newspack_Network\Content_Distribution\API;
use Newspack_Network\Content_Distribution\Editor;
use Newspack_Network\Content_Distribution\Canonical_Url;
use Newspack_Network\Content_Distribution\Incoming_Post;
use Newspack_Network\Content_Distribution\Outgoing_Post;
use WP_Post;
Expand Down Expand Up @@ -54,6 +55,8 @@ public static function init() {
CLI::init();
API::init();
Editor::init();

Canonical_Url::init();
}

/**
Expand Down
91 changes: 91 additions & 0 deletions includes/content-distribution/class-canonical-url.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php
/**
* Newspack Content Distribution Canonical URL Handler
*
* @package Newspack
*/

namespace Newspack_Network\Content_Distribution;

use Newspack_Network\Content_Distribution;

/**
* Class to filter the canonical URLs for distributed content.
*/
class Canonical_Url {

const OPTION_NAME = 'newspack_network_canonical_url';

/**
* Initialize hooks.
*/
public static function init() {
add_filter( 'get_canonical_url', array( __CLASS__, 'filter_canonical_url' ), 10, 2 );
add_filter( 'wpseo_canonical', array( __CLASS__, 'wpseo_canonical_url' ), 10 );
}

/**
* Filters the canonical URL for distributed content.
*
* @param string $canonical_url Canonical URL.
* @param object $post Post object.
*
* @return string
*/
public static function filter_canonical_url( $canonical_url, $post ) {
if ( ! Content_Distribution::is_post_incoming( $post ) ) {
return $canonical_url;
}

$incoming_post = new Incoming_Post( $post->ID );

if ( ! $incoming_post->is_linked() ) {
return $canonical_url;
}

$canonical_url = $incoming_post->get_original_post_url();

$base_url = get_option( self::OPTION_NAME, '' );
if ( $base_url ) {
$canonical_url = str_replace( $incoming_post->get_original_site_url(), $base_url, $canonical_url );
}

return $canonical_url;
}

/**
* Handles the canonical URL change for distributed content when Yoast SEO is in use.
*
* @param string $canonical_url The Yoast WPSEO deduced canonical URL.
*
* @return string $canonical_url The updated distributor friendly URL.
*/
public static function wpseo_canonical_url( $canonical_url ) {

// Return as is if not on a singular page - taken from rel_canonical().
if ( ! is_singular() ) {
return $canonical_url;
}

$id = get_queried_object_id();

// Return as is if we do not have a object id for context - taken from rel_canonical().
if ( 0 === $id ) {
return $canonical_url;
}

$post = get_post( $id );

// Return as is if we don't have a valid post object - taken from wp_get_canonical_url().
if ( ! $post ) {
return $canonical_url;
}

// Return as is if current post is not published - taken from wp_get_canonical_url().
if ( 'publish' !== $post->post_status ) {
return $canonical_url;
}

return self::filter_canonical_url( $canonical_url, $post );
}
}
26 changes: 25 additions & 1 deletion includes/content-distribution/class-incoming-post.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ class Incoming_Post {
* not configured for distribution.
*/
public function __construct( $payload ) {
$post = null;

if ( is_numeric( $payload ) ) {
$post = get_post( $payload );
$payload = get_post_meta( $payload, self::PAYLOAD_META, true );
}

Expand All @@ -87,7 +90,10 @@ public function __construct( $payload ) {
$this->payload = $payload;
$this->network_post_id = $payload['network_post_id'];

$post = $this->query_post();
if ( ! $post ) {
$post = $this->query_post();
}

if ( $post ) {
$this->ID = $post->ID;
$this->post = $post;
Expand Down Expand Up @@ -134,6 +140,24 @@ protected function get_post_payload() {
return get_post_meta( $this->ID, self::PAYLOAD_META, true );
}

/**
* Get the post original URL.
*
* @return string The post original post URL. Empty string if not found.
*/
public function get_original_post_url() {
return $this->payload['post_url'] ?? '';
}

/**
* Get the post original site URL.
*
* @return string The post original site URL. Empty string if not found.
*/
public function get_original_site_url() {
return $this->payload['site_url'] ?? '';
}

/**
* Find the post from the payload's network post ID.
*
Expand Down
2 changes: 1 addition & 1 deletion includes/incoming-events/class-canonical-url-updated.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace Newspack_Network\Incoming_Events;

use Newspack_Network\Distributor_Customizations\Canonical_Url;
use Newspack_Network\Content_Distribution\Canonical_Url;

/**
* Class to handle the Canonical Url Updated Event
Expand Down
61 changes: 61 additions & 0 deletions tests/unit-tests/content-distribution/test-admin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* Class TestContentDistributionAdmin
*
* @package Newspack_Network
*/

namespace Test\Content_Distribution;

use Newspack_Network\Content_Distribution\Admin;

/**
* Test the Content Distribution Admin class.
*/
class TestAdmin extends \WP_UnitTestCase {
/**
* Test default roles option value.
*/
public function test_default_roles_options() {
$roles = get_option( Admin::CAPABILITY_ROLES_OPTION_NAME );
$this->assertNotEmpty( $roles );
$this->assertContains( 'administrator', $roles );
$this->assertContains( 'editor', $roles );
$this->assertContains( 'author', $roles );
}

/**
* Test default roles capability.
*/
public function test_default_roles_capability() {
$default_roles = get_option( Admin::CAPABILITY_ROLES_OPTION_NAME );
$all_roles = wp_roles();
foreach ( $all_roles->roles as $role_key => $role ) {
$role_obj = get_role( $role_key );
if ( in_array( $role_key, $default_roles, true ) ) {
$this->assertTrue( $role_obj->has_cap( Admin::CAPABILITY ) );
} else {
$this->assertFalse( $role_obj->has_cap( Admin::CAPABILITY ) );
}
}
}

/**
* Test updating roles.
*/
public function test_update_roles() {
$roles = get_option( Admin::CAPABILITY_ROLES_OPTION_NAME );
$roles[] = 'contributor';
update_option( Admin::CAPABILITY_ROLES_OPTION_NAME, $roles );

$role_obj = get_role( 'contributor' );
$this->assertTrue( $role_obj->has_cap( Admin::CAPABILITY ) );

$roles = get_option( Admin::CAPABILITY_ROLES_OPTION_NAME );
$roles = array_diff( $roles, [ 'contributor' ] );
update_option( Admin::CAPABILITY_ROLES_OPTION_NAME, $roles );

$role_obj = get_role( 'contributor' );
$this->assertFalse( $role_obj->has_cap( Admin::CAPABILITY ) );
}
}
43 changes: 43 additions & 0 deletions tests/unit-tests/content-distribution/test-canonical-url.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
/**
* Class TestContentDistributionCanonicalUrl
*
* @package Newspack_Network
*/

namespace Test\Content_Distribution;

use Newspack_Network\Content_Distribution\Incoming_Post;

/**
* Test the Content Distribution Canonical URL class.
*/
class TestCanonicalUrl extends \WP_UnitTestCase {
/**
* Test default canonical URL.
*/
public function test_default_canonical_url() {
$payload = get_sample_payload( '', get_bloginfo( 'url' ) );
$incoming_post = new Incoming_Post( $payload );
$post_id = $incoming_post->insert( $payload );

wp_publish_post( $post_id );

$this->assertEquals( $payload['post_url'], wp_get_canonical_url( get_post( $post_id ) ) );
}

/**
* Test custom canonical URL base.
*/
public function test_custom_canonical_url() {
update_option( 'newspack_network_canonical_url', 'https://custom.test' );

$payload = get_sample_payload( '', get_bloginfo( 'url' ) );
$incoming_post = new Incoming_Post( $payload );
$post_id = $incoming_post->insert( $payload );

wp_publish_post( $post_id );

$this->assertEquals( 'https://custom.test/2021/01/slug', wp_get_canonical_url( get_post( $post_id ) ) );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
* @package Newspack_Network
*/

namespace Test\Content_Distribution;

use Newspack_Network\Content_Distribution;
use Newspack_Network\Content_Distribution\Outgoing_Post;
use Newspack_Network\Hub\Node as Hub_Node;

/**
* Test the Content_Distribution class.
*/
class TestContentDistribution extends WP_UnitTestCase {
class TestContentDistribution extends \WP_UnitTestCase {
/**
* "Mocked" network nodes.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
* @package Newspack_Network
*/

namespace Test\Content_Distribution;

use Newspack_Network\Content_Distribution\Incoming_Post;

/**
* Test the Incoming_Post class.
*/
class TestIncomingPost extends WP_UnitTestCase {
class TestIncomingPost extends \WP_UnitTestCase {
/**
* URL for node that distributes posts.
*
Expand All @@ -36,51 +38,7 @@ class TestIncomingPost extends WP_UnitTestCase {
* Get sample post payload.
*/
private function get_sample_payload() {
return [
'site_url' => $this->node_1,
'post_id' => 1,
'network_post_id' => '1234567890abcdef1234567890abcdef',
'sites' => [ $this->node_2 ],
'post_data' => [
'title' => 'Title',
'post_status' => 'publish',
'date_gmt' => '2021-01-01 00:00:00',
'modified_gmt' => '2021-01-01 00:00:00',
'slug' => 'slug',
'post_type' => 'post',
'raw_content' => 'Content',
'content' => '<p>Content</p>',
'excerpt' => 'Excerpt',
'thumbnail_url' => 'https://picsum.photos/id/1/300/300.jpg',
'taxonomy' => [
'category' => [
[
'name' => 'Category 1',
'slug' => 'category-1',
],
[
'name' => 'Category 2',
'slug' => 'category-2',
],
],
'post_tag' => [
[
'name' => 'Tag 1',
'slug' => 'tag-1',
],
[
'name' => 'Tag 2',
'slug' => 'tag-2',
],
],
],
'post_meta' => [
'single' => [ 'value' ],
'array' => [ [ 'a' => 'b', 'c' => 'd' ] ], // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound
'multiple' => [ 'value 1', 'value 2' ],
],
],
];
return get_sample_payload( $this->node_1, $this->node_2 );
}

/**
Expand Down Expand Up @@ -222,6 +180,26 @@ public function test_insert_post_when_unlinked() {
$this->assertSame( 'Custom Content', $incoming_post->post_content );
}

/**
* Test get original post URL.
*/
public function test_get_original_post_url() {
$post_id = $this->incoming_post->insert();
$original_url = $this->incoming_post->get_original_post_url();
$payload = $this->get_sample_payload();
$this->assertSame( $payload['post_url'], $original_url );
}

/**
* Test get original site URL.
*/
public function test_get_original_site_url() {
$post_id = $this->incoming_post->insert();
$original_url = $this->incoming_post->get_original_site_url();
$payload = $this->get_sample_payload();
$this->assertSame( $payload['site_url'], $original_url );
}

/**
* Test relink post.
*/
Expand Down
Loading

0 comments on commit 5ca60ce

Please sign in to comment.