Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions includes/class-newspack.php
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ private function includes() {
include_once NEWSPACK_ABSPATH . 'includes/plugins/class-teams-for-memberships.php';
include_once NEWSPACK_ABSPATH . 'includes/plugins/class-newspack-elections.php';
include_once NEWSPACK_ABSPATH . 'includes/plugins/class-yoast.php';
include_once NEWSPACK_ABSPATH . 'includes/plugins/class-parsely.php';
include_once NEWSPACK_ABSPATH . 'includes/class-primary-category.php';

include_once NEWSPACK_ABSPATH . 'includes/class-patches.php';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public function configure() {
'track_page_types' => [ 'page' ],
'disable_javascript' => false,
'disable_amp' => false,
'meta_type' => 'json_ld',
'meta_type' => 'repeated_metas',
'logo' => '',
'metadata_secret' => '',
'parsely_wipe_metadata_cache' => false,
Expand Down
73 changes: 73 additions & 0 deletions includes/plugins/class-parsely.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php
Comment thread
chickenn00dle marked this conversation as resolved.
/**
* Parse.ly integration class.
*
* @package Newspack
*/

namespace Newspack;

defined( 'ABSPATH' ) || exit;

/**
* Parse.ly integration: migrates existing installs off the legacy `json_ld`
* meta_type default.
*/
class Parsely {
/**
* Name of the option that records whether the meta_type migration has run.
*
* @var string
*/
const META_TYPE_MIGRATION_OPTION = 'newspack_parsely_meta_type_migrated';

/**
* Register hooks.
*/
public static function init() {
add_action( 'admin_init', [ __CLASS__, 'migrate_meta_type' ] );
}

/**
* Migrate existing Parse.ly installs from the legacy `json_ld` meta_type
* default to `repeated_metas`, which avoids conflicts with Yoast SEO's own
* JSON-LD output.
*
* The switch is intentional and unconditional for any site still rendering
* `json_ld`: because Parse.ly's own default for an absent `meta_type` is
* also `json_ld`, a missing key is treated as the legacy default and
* rewritten too. This means a deliberate publisher `json_ld` choice is
* indistinguishable from the old default and gets switched as well — which
* is desired, since the Yoast conflict applies regardless of intent.
*
* Runs on every `admin_init` until wp-parsely is active, then migrates the
* site once and records completion. On sites that never activate Parse.ly
* the migration simply stays pending (two cached option reads per request).
*/
public static function migrate_meta_type() {
if ( get_option( self::META_TYPE_MIGRATION_OPTION ) ) {
Comment thread
chickenn00dle marked this conversation as resolved.
Comment thread
chickenn00dle marked this conversation as resolved.
return;
}

if ( ! is_plugin_active( 'wp-parsely/wp-parsely.php' ) ) {
return;
}

$parsely_settings = get_option( 'parsely', [] );
if ( ! is_array( $parsely_settings ) ) {
$parsely_settings = [];
}

// Treat an absent key as the legacy default: wp-parsely merges its own
// `json_ld` default at read time, so a missing `meta_type` renders the
// same conflicting JSON-LD output we're migrating away from.
$meta_type = $parsely_settings['meta_type'] ?? 'json_ld';
if ( 'json_ld' === $meta_type ) {
$parsely_settings['meta_type'] = 'repeated_metas';
update_option( 'parsely', $parsely_settings );
}

update_option( self::META_TYPE_MIGRATION_OPTION, true );
}
}
Parsely::init();
124 changes: 124 additions & 0 deletions tests/unit-tests/parsely.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php
/**
* Tests the Parse.ly meta_type migration.
*
* @package Newspack\Tests
*/

use Newspack\Parsely;

/**
* Tests the Parse.ly meta_type migration.
*/
class Newspack_Test_Parsely extends WP_UnitTestCase {

/**
* Reset migration state and active plugins before each test.
*/
public function set_up() {
parent::set_up();
delete_option( Parsely::META_TYPE_MIGRATION_OPTION );
delete_option( 'parsely' );
update_option( 'active_plugins', [] );
}

/**
* Mark wp-parsely as active.
*/
private function activate_parsely() {
update_option( 'active_plugins', [ 'wp-parsely/wp-parsely.php' ] );
}

/**
* A stored `json_ld` meta_type is switched to `repeated_metas`.
*/
public function test_migrates_explicit_json_ld() {
$this->activate_parsely();
update_option(
'parsely',
[
'apikey' => 'example.com',
'meta_type' => 'json_ld',
]
);

Parsely::migrate_meta_type();

$settings = get_option( 'parsely' );
$this->assertEquals( 'repeated_metas', $settings['meta_type'] );
$this->assertEquals( 'example.com', $settings['apikey'] );
$this->assertNotEmpty( get_option( Parsely::META_TYPE_MIGRATION_OPTION ) );
}

/**
* A missing meta_type key is treated as the legacy `json_ld` default and rewritten.
*/
public function test_migrates_absent_meta_type() {
$this->activate_parsely();
update_option( 'parsely', [ 'apikey' => 'example.com' ] );

Parsely::migrate_meta_type();

$settings = get_option( 'parsely' );
$this->assertEquals( 'repeated_metas', $settings['meta_type'] );
$this->assertEquals( 'example.com', $settings['apikey'] );
}

/**
* An empty stored option array is migrated (absent key === legacy default).
*/
public function test_migrates_empty_option() {
$this->activate_parsely();
update_option( 'parsely', [] );

Parsely::migrate_meta_type();

$settings = get_option( 'parsely' );
$this->assertEquals( 'repeated_metas', $settings['meta_type'] );
}

/**
* A non-`json_ld` meta_type is left untouched.
*/
public function test_leaves_other_meta_type_untouched() {
$this->activate_parsely();
update_option( 'parsely', [ 'meta_type' => 'repeated_metas' ] );

Parsely::migrate_meta_type();

$settings = get_option( 'parsely' );
$this->assertEquals( 'repeated_metas', $settings['meta_type'] );
$this->assertNotEmpty( get_option( Parsely::META_TYPE_MIGRATION_OPTION ) );
}

/**
* Nothing happens — and completion is not recorded — when wp-parsely is inactive,
* so the migration stays pending until the plugin is activated.
*/
public function test_skips_and_stays_pending_when_parsely_inactive() {
update_option( 'parsely', [ 'meta_type' => 'json_ld' ] );

Parsely::migrate_meta_type();

$settings = get_option( 'parsely' );
$this->assertEquals( 'json_ld', $settings['meta_type'] );
$this->assertFalse( get_option( Parsely::META_TYPE_MIGRATION_OPTION ) );
}

/**
* The migration is idempotent: once recorded it does not run again, even if
* the stored meta_type is later reset to `json_ld`.
*/
public function test_is_idempotent() {
$this->activate_parsely();
update_option( 'parsely', [ 'meta_type' => 'json_ld' ] );

Parsely::migrate_meta_type();
$this->assertEquals( 'repeated_metas', get_option( 'parsely' )['meta_type'] );

// Simulate a later manual change back to json_ld; the migration should not re-run.
update_option( 'parsely', [ 'meta_type' => 'json_ld' ] );
Parsely::migrate_meta_type();
$this->assertEquals( 'json_ld', get_option( 'parsely' )['meta_type'] );
}
}
Loading