Skip to content

Commit d5f4ea9

Browse files
fix(parse.ly): update meta_type default (#4724)
This PR updates the default meta_type parse.ly config to repeated_metas which is recommended by parse.ly . This also implements a migrator so existing sites with Parse.ly get the updated setting.
1 parent 47d16c6 commit d5f4ea9

4 files changed

Lines changed: 199 additions & 1 deletion

File tree

includes/class-newspack.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ private function includes() {
243243
include_once NEWSPACK_ABSPATH . 'includes/plugins/class-teams-for-memberships.php';
244244
include_once NEWSPACK_ABSPATH . 'includes/plugins/class-newspack-elections.php';
245245
include_once NEWSPACK_ABSPATH . 'includes/plugins/class-yoast.php';
246+
include_once NEWSPACK_ABSPATH . 'includes/plugins/class-parsely.php';
246247
include_once NEWSPACK_ABSPATH . 'includes/class-primary-category.php';
247248

248249
include_once NEWSPACK_ABSPATH . 'includes/class-patches.php';

includes/configuration_managers/class-parsely-configuration-manager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function configure() {
6464
'track_page_types' => [ 'page' ],
6565
'disable_javascript' => false,
6666
'disable_amp' => false,
67-
'meta_type' => 'json_ld',
67+
'meta_type' => 'repeated_metas',
6868
'logo' => '',
6969
'metadata_secret' => '',
7070
'parsely_wipe_metadata_cache' => false,

includes/plugins/class-parsely.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
/**
3+
* Parse.ly integration class.
4+
*
5+
* @package Newspack
6+
*/
7+
8+
namespace Newspack;
9+
10+
defined( 'ABSPATH' ) || exit;
11+
12+
/**
13+
* Parse.ly integration: migrates existing installs off the legacy `json_ld`
14+
* meta_type default.
15+
*/
16+
class Parsely {
17+
/**
18+
* Name of the option that records whether the meta_type migration has run.
19+
*
20+
* @var string
21+
*/
22+
const META_TYPE_MIGRATION_OPTION = 'newspack_parsely_meta_type_migrated';
23+
24+
/**
25+
* Register hooks.
26+
*/
27+
public static function init() {
28+
add_action( 'admin_init', [ __CLASS__, 'migrate_meta_type' ] );
29+
}
30+
31+
/**
32+
* Migrate existing Parse.ly installs from the legacy `json_ld` meta_type
33+
* default to `repeated_metas`, which avoids conflicts with Yoast SEO's own
34+
* JSON-LD output.
35+
*
36+
* The switch is intentional and unconditional for any site still rendering
37+
* `json_ld`: because Parse.ly's own default for an absent `meta_type` is
38+
* also `json_ld`, a missing key is treated as the legacy default and
39+
* rewritten too. This means a deliberate publisher `json_ld` choice is
40+
* indistinguishable from the old default and gets switched as well — which
41+
* is desired, since the Yoast conflict applies regardless of intent.
42+
*
43+
* Runs on every `admin_init` until wp-parsely is active, then migrates the
44+
* site once and records completion. On sites that never activate Parse.ly
45+
* the migration simply stays pending (two cached option reads per request).
46+
*/
47+
public static function migrate_meta_type() {
48+
if ( get_option( self::META_TYPE_MIGRATION_OPTION ) ) {
49+
return;
50+
}
51+
52+
if ( ! is_plugin_active( 'wp-parsely/wp-parsely.php' ) ) {
53+
return;
54+
}
55+
56+
$parsely_settings = get_option( 'parsely', [] );
57+
if ( ! is_array( $parsely_settings ) ) {
58+
$parsely_settings = [];
59+
}
60+
61+
// Treat an absent key as the legacy default: wp-parsely merges its own
62+
// `json_ld` default at read time, so a missing `meta_type` renders the
63+
// same conflicting JSON-LD output we're migrating away from.
64+
$meta_type = $parsely_settings['meta_type'] ?? 'json_ld';
65+
if ( 'json_ld' === $meta_type ) {
66+
$parsely_settings['meta_type'] = 'repeated_metas';
67+
update_option( 'parsely', $parsely_settings );
68+
}
69+
70+
update_option( self::META_TYPE_MIGRATION_OPTION, true );
71+
}
72+
}
73+
Parsely::init();

tests/unit-tests/parsely.php

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
/**
3+
* Tests the Parse.ly meta_type migration.
4+
*
5+
* @package Newspack\Tests
6+
*/
7+
8+
use Newspack\Parsely;
9+
10+
/**
11+
* Tests the Parse.ly meta_type migration.
12+
*/
13+
class Newspack_Test_Parsely extends WP_UnitTestCase {
14+
15+
/**
16+
* Reset migration state and active plugins before each test.
17+
*/
18+
public function set_up() {
19+
parent::set_up();
20+
delete_option( Parsely::META_TYPE_MIGRATION_OPTION );
21+
delete_option( 'parsely' );
22+
update_option( 'active_plugins', [] );
23+
}
24+
25+
/**
26+
* Mark wp-parsely as active.
27+
*/
28+
private function activate_parsely() {
29+
update_option( 'active_plugins', [ 'wp-parsely/wp-parsely.php' ] );
30+
}
31+
32+
/**
33+
* A stored `json_ld` meta_type is switched to `repeated_metas`.
34+
*/
35+
public function test_migrates_explicit_json_ld() {
36+
$this->activate_parsely();
37+
update_option(
38+
'parsely',
39+
[
40+
'apikey' => 'example.com',
41+
'meta_type' => 'json_ld',
42+
]
43+
);
44+
45+
Parsely::migrate_meta_type();
46+
47+
$settings = get_option( 'parsely' );
48+
$this->assertEquals( 'repeated_metas', $settings['meta_type'] );
49+
$this->assertEquals( 'example.com', $settings['apikey'] );
50+
$this->assertNotEmpty( get_option( Parsely::META_TYPE_MIGRATION_OPTION ) );
51+
}
52+
53+
/**
54+
* A missing meta_type key is treated as the legacy `json_ld` default and rewritten.
55+
*/
56+
public function test_migrates_absent_meta_type() {
57+
$this->activate_parsely();
58+
update_option( 'parsely', [ 'apikey' => 'example.com' ] );
59+
60+
Parsely::migrate_meta_type();
61+
62+
$settings = get_option( 'parsely' );
63+
$this->assertEquals( 'repeated_metas', $settings['meta_type'] );
64+
$this->assertEquals( 'example.com', $settings['apikey'] );
65+
}
66+
67+
/**
68+
* An empty stored option array is migrated (absent key === legacy default).
69+
*/
70+
public function test_migrates_empty_option() {
71+
$this->activate_parsely();
72+
update_option( 'parsely', [] );
73+
74+
Parsely::migrate_meta_type();
75+
76+
$settings = get_option( 'parsely' );
77+
$this->assertEquals( 'repeated_metas', $settings['meta_type'] );
78+
}
79+
80+
/**
81+
* A non-`json_ld` meta_type is left untouched.
82+
*/
83+
public function test_leaves_other_meta_type_untouched() {
84+
$this->activate_parsely();
85+
update_option( 'parsely', [ 'meta_type' => 'repeated_metas' ] );
86+
87+
Parsely::migrate_meta_type();
88+
89+
$settings = get_option( 'parsely' );
90+
$this->assertEquals( 'repeated_metas', $settings['meta_type'] );
91+
$this->assertNotEmpty( get_option( Parsely::META_TYPE_MIGRATION_OPTION ) );
92+
}
93+
94+
/**
95+
* Nothing happens — and completion is not recorded — when wp-parsely is inactive,
96+
* so the migration stays pending until the plugin is activated.
97+
*/
98+
public function test_skips_and_stays_pending_when_parsely_inactive() {
99+
update_option( 'parsely', [ 'meta_type' => 'json_ld' ] );
100+
101+
Parsely::migrate_meta_type();
102+
103+
$settings = get_option( 'parsely' );
104+
$this->assertEquals( 'json_ld', $settings['meta_type'] );
105+
$this->assertFalse( get_option( Parsely::META_TYPE_MIGRATION_OPTION ) );
106+
}
107+
108+
/**
109+
* The migration is idempotent: once recorded it does not run again, even if
110+
* the stored meta_type is later reset to `json_ld`.
111+
*/
112+
public function test_is_idempotent() {
113+
$this->activate_parsely();
114+
update_option( 'parsely', [ 'meta_type' => 'json_ld' ] );
115+
116+
Parsely::migrate_meta_type();
117+
$this->assertEquals( 'repeated_metas', get_option( 'parsely' )['meta_type'] );
118+
119+
// Simulate a later manual change back to json_ld; the migration should not re-run.
120+
update_option( 'parsely', [ 'meta_type' => 'json_ld' ] );
121+
Parsely::migrate_meta_type();
122+
$this->assertEquals( 'json_ld', get_option( 'parsely' )['meta_type'] );
123+
}
124+
}

0 commit comments

Comments
 (0)