PluginProbe
Parse.ly / 3.8.4
Parse.ly v3.8.4
3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 3.20.3 All 105 releases
← All changes | src/UI/class-settings-page.php +230 -902 3.23.53.8.4 View file →
@@ -9,14 +9,12 @@
9 9 declare(strict_types=1);
10 10
11 11 namespace Parsely\UI;
12 12
13 -use Parsely\Content_Helper\Excerpt_Suggestions;
14 13 use Parsely\Parsely;
15 -use Parsely\Permissions;
16 -use Parsely\Utils\Utils;
17 -use Parsely\Validator;
18 14
15 +use function Parsely\Utils\get_asset_info;
16 +
19 17 use const Parsely\PARSELY_FILE;
20 18
21 19 /**
22 20 * Renders the wp-admin Parse.ly plugin settings page.
@@ -22,11 +20,11 @@
22 20 * Renders the wp-admin Parse.ly plugin settings page.
23 21 *
24 22 * @since 3.0.0
25 23 *
24 + * @phpstan-import-type Parsely_Options from Parsely
25 + *
26 26 * @phpstan-type Setting_Arguments array{
27 - * add_fieldset?: bool,
28 - * legend?: string,
29 27 * option_key: string,
30 28 * label_for: string,
31 29 * title?: string,
32 30 * help_text?: string,
@@ -55,35 +53,17 @@
55 53 * disable_amp?: bool,
56 54 * track_post_types_as?: array<string, string>,
57 55 * track_post_types: string[],
58 56 * track_page_types: string[],
59 - * full_metadata_in_non_posts: ?bool,
60 57 * content_id_prefix?: string,
61 58 * use_top_level_cats?:bool|string,
62 59 * custom_taxonomy_section?: string,
63 60 * cats_as_tags?: bool|string,
64 - * content_helper: Parsely_Settings_Options_Content_Helper,
65 - * headline_testing?: Parsely_Options_Headline_Testing,
66 61 * lowercase_tags?: bool,
67 62 * force_https_canonicals?: bool,
68 63 * disable_autotrack?: bool|string,
64 + * parsely_wipe_metadata_cache: bool,
69 65 * }
70 - *
71 - * @phpstan-type Parsely_Settings_Options_Content_Helper array{
72 - * ai_features_enabled?: bool,
73 - * smart_linking?: Parsely_Settings_Options_Content_Helper_Feature,
74 - * title_suggestions?: Parsely_Settings_Options_Content_Helper_Feature,
75 - * excerpt_suggestions?: Parsely_Settings_Options_Content_Helper_Feature,
76 - * traffic_boost?: Parsely_Settings_Options_Content_Helper_Feature,
77 - * }
78 - *
79 - * @phpstan-type Parsely_Settings_Options_Content_Helper_Feature array{
80 - * enabled?: bool,
81 - * allowed_user_roles?: array<string, string>|array<string, bool>
82 - * }
83 - *
84 - * @phpstan-import-type Parsely_Options from Parsely
85 - * @phpstan-import-type Parsely_Options_Headline_Testing from Parsely
86 66 */
87 67 final class Settings_Page {
88 68 /**
89 69 * Instance of Parsely class.
@@ -101,32 +81,8 @@
101 81 */
102 82 private $hook_suffix;
103 83
104 84 /**
105 - * Options for badges that are displayed for managed options.
106 - *
107 - * @since 3.9.0
108 - *
109 - * @var array<string, mixed>
110 - */
111 - private $managed_options_badge = array();
112 -
113 - /**
114 - * The Content Intelligence features that can be configured in the settings
115 - * page.
116 - *
117 - * @since 3.16.0
118 - *
119 - * @var string[]
120 - */
121 - private $configurable_pch_features = array(
122 - 'smart_linking',
123 - 'title_suggestions',
124 - 'excerpt_suggestions',
125 - 'traffic_boost',
126 - );
127 -
128 - /**
129 85 * Constructor.
130 86 *
131 87 * @param Parsely $parsely Instance of Parsely class.
132 88 */
@@ -131,20 +87,8 @@
131 87 * @param Parsely $parsely Instance of Parsely class.
132 88 */
133 89 public function __construct( Parsely $parsely ) {
134 90 $this->parsely = $parsely;
135 -
136 - $managed_options_badge = apply_filters(
137 - 'wp_parsely_managed_options_badge',
138 - array(
139 - 'text' => __( 'Upgrade', 'wp-parsely' ),
140 - 'url' => 'https://www.parse.ly/getdemo/',
141 - )
142 - );
143 -
144 - if ( is_array( $managed_options_badge ) ) {
145 - $this->managed_options_badge = $managed_options_badge;
146 - }
147 91 }
148 92
149 93 /**
150 94 * Registers settings page.
@@ -159,35 +103,33 @@
159 103
160 104 /**
161 105 * Enqueues all needed scripts and styles for Parse.ly plugin settings page.
162 106 *
163 - * @param string|null $hook_suffix The current page being loaded.
107 + * @param string $hook_suffix The current page being loaded.
164 108 */
165 - public function enqueue_settings_assets( ?string $hook_suffix ): void {
166 - if ( ! is_string( $hook_suffix ) || $this->hook_suffix !== $hook_suffix ) {
167 - return;
168 - }
109 + public function enqueue_settings_assets( string $hook_suffix ): void {
110 + if ( 'settings_page_parsely' === $hook_suffix ) {
111 + add_filter( 'media_library_months_with_files', '__return_empty_array' );
112 + wp_enqueue_media();
169 113
170 - add_filter( 'media_library_months_with_files', '__return_empty_array' );
171 - wp_enqueue_media();
114 + $admin_settings_asset = get_asset_info( 'build/admin-settings.asset.php' );
115 + $built_assets_url = plugin_dir_url( PARSELY_FILE ) . '/build/';
172 116
173 - $admin_settings_asset = Utils::get_asset_info( 'build/admin-settings.asset.php' );
174 - $built_assets_url = plugin_dir_url( PARSELY_FILE ) . 'build/';
117 + wp_enqueue_script(
118 + 'parsely-admin-settings',
119 + $built_assets_url . 'admin-settings.js',
120 + $admin_settings_asset['dependencies'],
121 + $admin_settings_asset['version'],
122 + true
123 + );
175 124
176 - wp_enqueue_script(
177 - 'parsely-admin-settings',
178 - $built_assets_url . 'admin-settings.js',
179 - $admin_settings_asset['dependencies'],
180 - $admin_settings_asset['version'],
181 - true
182 - );
183 -
184 - wp_enqueue_style(
185 - 'parsely-admin-settings',
186 - $built_assets_url . 'admin-settings.css',
187 - array(),
188 - $admin_settings_asset['version']
189 - );
125 + wp_enqueue_style(
126 + 'parsely-admin-settings',
127 + $built_assets_url . 'admin-settings.css',
128 + $admin_settings_asset['dependencies'],
129 + $admin_settings_asset['version']
130 + );
131 + }
190 132 }
191 133
192 134 /**
193 135 * Adds the Parse.ly settings page in WordPress settings menu.
@@ -192,13 +134,12 @@
192 134 /**
193 135 * Adds the Parse.ly settings page in WordPress settings menu.
194 136 */
195 137 public function add_settings_sub_menu(): void {
196 - $suffix = add_submenu_page(
197 - 'parsely-dashboard-page',
138 + $suffix = add_options_page(
198 139 __( 'Parse.ly Settings', 'wp-parsely' ),
199 - __( 'Settings', 'wp-parsely' ),
200 - Parsely::CAPABILITY, // phpcs:ignore WordPress.WP.Capabilities.Undetermined
140 + __( 'Parse.ly', 'wp-parsely' ),
141 + Parsely::CAPABILITY,
201 142 Parsely::MENU_SLUG,
202 143 array( $this, 'display_settings' )
203 144 );
204 145
@@ -231,17 +172,16 @@
231 172 );
232 173 }
233 174
234 175 /**
235 - * Displays the Parse.ly settings screen (admin.php?page=[SLUG]).
176 + * Displays the Parse.ly settings screen (options-general.php?page=[SLUG]).
236 177 */
237 178 public function display_settings(): void {
238 - // phpcs:ignore WordPress.WP.Capabilities.Undetermined
239 179 if ( ! current_user_can( Parsely::CAPABILITY ) ) {
240 180 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wp-parsely' ) );
241 181 }
242 182
243 - include_once plugin_dir_path( PARSELY_FILE ) . 'src/UI/settings-page.php';
183 + include_once plugin_dir_path( PARSELY_FILE ) . 'views/parsely-settings.php';
244 184 }
245 185
246 186 /**
247 187 * Initializes the settings for Parse.ly.
@@ -246,26 +186,16 @@
246 186 /**
247 187 * Initializes the settings for Parse.ly.
248 188 */
249 189 public function initialize_settings(): void {
250 - // Add the option first, to prevent double sanitization of the uninitialized option as reported
251 - // in https://core.trac.wordpress.org/ticket/21989.
252 - // The option will be initialized with the default values.
253 - add_option( Parsely::OPTIONS_KEY, $this->parsely->get_options() );
254 -
255 190 // All our options are actually stored in one single array to reduce DB queries.
256 191 register_setting(
257 192 Parsely::OPTIONS_KEY,
258 193 Parsely::OPTIONS_KEY,
259 - array(
260 - 'type' => 'array',
261 - 'sanitize_callback' => array( $this, 'validate_options' ),
262 - )
194 + array( $this, 'validate_options' )
263 195 );
264 196
265 197 $this->initialize_basic_section();
266 - $this->initialize_content_helper_section();
267 - $this->initialize_headline_testing_section();
268 198 $this->initialize_recrawl_section();
269 199 $this->initialize_advanced_section();
270 200 }
271 201
@@ -274,10 +204,9 @@
274 204 *
275 205 * @since 3.2.0
276 206 */
277 207 private function initialize_basic_section(): void {
278 - $are_credentials_managed = $this->parsely->are_credentials_managed;
279 - $section_key = 'basic-section';
208 + $section_key = 'basic-section';
280 209
281 210 add_settings_section(
282 211 $section_key,
283 212 __( 'Basic', 'wp-parsely' ),
@@ -293,15 +222,14 @@
293 222 'label_for' => $field_id,
294 223 'optional_args' => array(
295 224 'required' => 'required',
296 225 'placeholder' => 'mydomain.com',
297 - 'disabled' => $are_credentials_managed,
298 226 ),
299 227
300 228 );
301 229 add_settings_field(
302 230 $field_id,
303 - __( 'Site ID <em>(required)</em>', 'wp-parsely' ),
231 + __( 'Parse.ly Site ID <em>(required)</em>', 'wp-parsely' ),
304 232 array( $this, 'print_text_tag' ),
305 233 Parsely::MENU_SLUG,
306 234 $section_key,
307 235 $field_args
@@ -306,36 +234,54 @@
306 234 $section_key,
307 235 $field_args
308 236 );
309 237
310 - if ( ! $are_credentials_managed ) {
311 - // API Secret.
312 - $field_id = 'api_secret';
313 - $field_args = array(
314 - 'option_key' => $field_id,
315 - 'help_text' => __( 'Your API secret is your secret code to <a href="https://docs.parse.ly/the-parsely-api/">access our API</a>. It can be found at <code>dash.parsely.com/<var>yoursitedomain</var>/settings/api</code> (replace <var>yoursitedomain</var> with your domain name, e.g. <samp>mydomain.com</samp>).<br />If you haven\'t purchased access to the API and would like to do so, email your account manager or <a href="mailto:support@parsely.com">support@parsely.com</a>.', 'wp-parsely' ),
316 - 'label_for' => $field_id,
317 - 'optional_args' => array(
318 - 'type' => 'password',
319 - 'is_obfuscated_value' => true,
320 - ),
321 - );
322 - add_settings_field(
323 - $field_id,
324 - __( 'API Secret', 'wp-parsely' ),
325 - array( $this, 'print_text_tag' ),
326 - Parsely::MENU_SLUG,
327 - $section_key,
328 - $field_args
329 - );
330 - }
238 + // API Secret.
239 + $field_id = 'api_secret';
240 + $field_args = array(
241 + 'option_key' => $field_id,
242 + 'help_text' => __( 'Your API secret is your secret code to <a href="https://www.parse.ly/help/api/analytics/">access our API</a>. It can be found at <code>dash.parsely.com/<var>yoursitedomain</var>/settings/api</code> (replace <var>yoursitedomain</var> with your domain name, e.g. <samp>mydomain.com</samp>).<br />If you haven\'t purchased access to the API and would like to do so, email your account manager or <a href="mailto:support@parsely.com">support@parsely.com</a>.', 'wp-parsely' ),
243 + 'label_for' => $field_id,
244 + 'optional_args' => array(
245 + 'type' => 'password',
246 + 'is_obfuscated_value' => true,
247 + ),
248 + );
249 + add_settings_field(
250 + $field_id,
251 + __( 'Parse.ly API Secret', 'wp-parsely' ),
252 + array( $this, 'print_text_tag' ),
253 + Parsely::MENU_SLUG,
254 + $section_key,
255 + $field_args
256 + );
331 257
258 + // Metadata Secret.
259 + $field_id = 'metadata_secret';
260 + $field_args = array(
261 + 'option_key' => $field_id,
262 + 'help_text' => __( 'Your metadata secret is given to you by Parse.ly support. DO NOT enter anything here unless given to you by Parse.ly support!', 'wp-parsely' ),
263 + 'label_for' => $field_id,
264 + 'optional_args' => array(
265 + 'type' => 'password',
266 + 'is_obfuscated_value' => true,
267 + ),
268 + );
269 + add_settings_field(
270 + $field_id,
271 + __( 'Parse.ly Metadata Secret', 'wp-parsely' ),
272 + array( $this, 'print_text_tag' ),
273 + Parsely::MENU_SLUG,
274 + $section_key,
275 + $field_args
276 + );
277 +
332 278 // Metadata Format.
333 279 $field_id = 'meta_type';
334 280 $field_args = array(
335 281 'title' => __( 'Metadata Format', 'wp-parsely' ),
336 282 'option_key' => $field_id,
337 - 'help_text' => __( 'Choose the metadata format for our crawlers to access. Most publishers are fine with <a href="https://docs.parse.ly/metadata-jsonld/">JSON-LD</a>, but if you prefer to use our proprietary metadata format then you can do so here.', 'wp-parsely' ),
283 + 'help_text' => __( 'Choose the metadata format for our crawlers to access. Most publishers are fine with <a href="https://www.parse.ly/help/integration/jsonld/">JSON-LD</a>, but if you prefer to use our proprietary metadata format then you can do so here.', 'wp-parsely' ),
338 284 'radio_options' => array(
339 285 'json_ld' => 'json_ld',
340 286 'repeated_metas' => 'repeated_metas',
341 287 ),
@@ -343,9 +289,9 @@
343 289 'filter' => 'wp_parsely_metadata',
344 290 );
345 291 add_settings_field(
346 292 $field_id,
347 - $this->set_field_label_contents( __( 'Metadata Format', 'wp-parsely' ), $field_id ),
293 + __( 'Metadata Format', 'wp-parsely' ),
348 294 array( $this, 'print_radio_tags' ),
349 295 Parsely::MENU_SLUG,
350 296 $section_key,
351 297 $field_args
@@ -355,9 +301,9 @@
355 301 $field_help = __( 'Here you can specify your logo\'s URL by using the "Browse" button or typing the URL manually.', 'wp-parsely' );
356 302 $field_id = 'logo';
357 303 add_settings_field(
358 304 $field_id,
359 - $this->set_field_label_contents( __( 'Logo', 'wp-parsely' ), $field_id ),
305 + __( 'Logo', 'wp-parsely' ),
360 306 array( $this, 'print_media_single_image' ),
361 307 Parsely::MENU_SLUG,
362 308 $section_key,
363 309 array(
@@ -368,18 +314,17 @@
368 314 )
369 315 );
370 316
371 317 // Track logged-in users.
372 - $field_id = 'track_authenticated_users';
373 318 add_settings_field(
374 - $field_id,
375 - $this->set_field_label_contents( __( 'Track Logged-in Users', 'wp-parsely' ), $field_id ),
319 + 'track_authenticated_users',
320 + __( 'Track Logged-in Users', 'wp-parsely' ),
376 321 array( $this, 'print_radio_tags' ),
377 322 Parsely::MENU_SLUG,
378 323 $section_key,
379 324 array(
380 325 'title' => __( 'Track Logged-in Users', 'wp-parsely' ), // Passed for legend element.
381 - 'option_key' => $field_id,
326 + 'option_key' => 'track_authenticated_users',
382 327 'radio_options' => array(
383 328 'true' => __( 'Yes, track logged-in users.', 'wp-parsely' ),
384 329 'false' => __( 'No, do not track logged-in users. I do not want to see the Parse.ly tracking code on my site when browsing while logged in.', 'wp-parsely' ),
385 330 ),
@@ -391,23 +336,22 @@
391 336 )
392 337 );
393 338
394 339 // Disable JavaScript.
395 - $field_id = 'disable_javascript';
396 340 add_settings_field(
397 - $field_id,
398 - $this->set_field_label_contents( __( 'Disable JavaScript', 'wp-parsely' ), $field_id ),
341 + 'disable_javascript',
342 + __( 'Disable JavaScript', 'wp-parsely' ),
399 343 array( $this, 'print_radio_tags' ),
400 344 Parsely::MENU_SLUG,
401 345 $section_key,
402 346 array(
403 347 'title' => __( 'Disable JavaScript', 'wp-parsely' ), // Passed for legend element.
404 - 'option_key' => $field_id,
348 + 'option_key' => 'disable_javascript',
405 349 'radio_options' => array(
406 350 'true' => __( 'Yes, disable JavaScript tracking. I want to use a separate system for tracking instead of the Parse.ly plugin.', 'wp-parsely' ),
407 351 'false' => __( 'No, do not disable JavaScript tracking. I want the Parse.ly plugin to load the tracker.', 'wp-parsely' ),
408 352 ),
409 - 'help_text' => __( '<span style="color:#d63638">WARNING:</span> Changing this setting to "Yes" will prevent the plugin from injecting the Parse.ly tracker. Only do so if you are setting the tracker elsewhere (e.g. hardcoded, GTM, another tag manager, etc.).', 'wp-parsely' ),
353 + 'help_text' => __( '<span style="color:#d63638">WARNING:</span> We highly recommend choosing "No." Disabling the JavaScript tracker will also disable the "Personalize Results" section of the recommendation widget.', 'wp-parsely' ),
410 354 'filter' => 'wp_parsely_load_js_tracker',
411 355 )
412 356 );
413 357
@@ -412,18 +356,17 @@
412 356 );
413 357
414 358 if ( defined( 'AMP__VERSION' ) ) {
415 359 // Disable AMP tracking.
416 - $field_id = 'disable_amp';
417 360 add_settings_field(
418 - $field_id,
419 - $this->set_field_label_contents( __( 'Disable AMP Tracking', 'wp-parsely' ), $field_id ),
361 + 'disable_amp',
362 + __( 'Disable AMP Tracking', 'wp-parsely' ),
420 363 array( $this, 'print_radio_tags' ),
421 364 Parsely::MENU_SLUG,
422 365 $section_key,
423 366 array(
424 367 'title' => __( 'Disable AMP Tracking', 'wp-parsely' ), // Passed for legend element.
425 - 'option_key' => $field_id,
368 + 'option_key' => 'disable_amp',
426 369 'radio_options' => array(
427 370 'true' => __( 'Yes, disable Parse.ly tracking on AMP pages. I use a different system for JavaScript tracking on AMP pages.', 'wp-parsely' ),
428 371 'false' => __( 'No, do not disable Parse.ly tracking on AMP pages.', 'wp-parsely' ),
429 372 ),
@@ -432,239 +375,8 @@
432 375 }
433 376 }
434 377
435 378 /**
436 - * Registers section and settings for Content Intelligence section.
437 - *
438 - * @since 3.16.0
439 - */
440 - private function initialize_content_helper_section(): void {
441 - $section_key = 'content-intelligence-section';
442 -
443 - add_settings_section(
444 - $section_key,
445 - __( 'Content Intelligence', 'wp-parsely' ),
446 - '__return_null',
447 - Parsely::MENU_SLUG
448 - );
449 -
450 - // AI Features.
451 - $field_id = 'content_helper[ai_features_enabled]';
452 - $field_args = array(
453 - 'option_key' => $field_id,
454 - 'label_for' => $field_id,
455 - 'yes_text' => __( 'Enabled', 'wp-parsely' ),
456 - 'add_fieldset' => true,
457 - 'legend' => __( 'AI Features', 'wp-parsely' ),
458 - );
459 - add_settings_field(
460 - $field_id,
461 - __( 'AI Features', 'wp-parsely' ),
462 - array( $this, 'print_checkbox_tag' ),
463 - Parsely::MENU_SLUG,
464 - $section_key,
465 - $field_args
466 - );
467 -
468 - // Smart Linking.
469 - $field_id = 'content_helper[smart_linking]';
470 - $field_args = array(
471 - 'option_key' => $field_id,
472 - 'label_for' => $field_id,
473 - 'legend' => __( 'Smart Linking', 'wp-parsely' ),
474 - );
475 - add_settings_field(
476 - $field_id,
477 - __( 'Smart Linking', 'wp-parsely' ),
478 - array( $this, 'print_content_helper_ai_feature_section' ),
479 - Parsely::MENU_SLUG,
480 - $section_key,
481 - $field_args
482 - );
483 -
484 - // Title Suggestions.
485 - $field_id = 'content_helper[title_suggestions]';
486 - $field_args = array(
487 - 'option_key' => $field_id,
488 - 'label_for' => $field_id,
489 - 'legend' => __( 'Title Suggestions', 'wp-parsely' ),
490 - );
491 - add_settings_field(
492 - $field_id,
493 - __( 'Title Suggestions', 'wp-parsely' ),
494 - array( $this, 'print_content_helper_ai_feature_section' ),
495 - Parsely::MENU_SLUG,
496 - $section_key,
497 - $field_args
498 - );
499 -
500 - // Excerpt Suggestions.
501 - $field_id = 'content_helper[excerpt_suggestions]';
502 - $field_args = array(
503 - 'option_key' => $field_id,
504 - 'label_for' => $field_id,
505 - 'legend' => __( 'Excerpt Suggestions', 'wp-parsely' ),
506 - 'filter' => Excerpt_Suggestions::get_feature_filter_name(),
507 - );
508 - add_settings_field(
509 - $field_id,
510 - __( 'Excerpt Suggestions', 'wp-parsely' ),
511 - array( $this, 'print_content_helper_ai_feature_section' ),
512 - Parsely::MENU_SLUG,
513 - $section_key,
514 - $field_args
515 - );
516 -
517 - // Traffic Boost.
518 - $field_id = 'content_helper[traffic_boost]';
519 - $field_args = array(
520 - 'option_key' => $field_id,
521 - 'label_for' => $field_id,
522 - 'legend' => __( 'Engagement Boost (beta)', 'wp-parsely' ),
523 - );
524 - add_settings_field(
525 - $field_id,
526 - __( 'Engagement Boost (beta)', 'wp-parsely' ),
527 - array( $this, 'print_content_helper_ai_feature_section' ),
528 - Parsely::MENU_SLUG,
529 - $section_key,
530 - $field_args
531 - );
532 - }
533 -
534 - /**
535 - * Registers section and settings for Headline Testing section.
536 - *
537 - * @since 3.21.0
538 - */
539 - private function initialize_headline_testing_section(): void {
540 - $section_key = 'headline-testing-section';
541 -
542 - add_settings_section(
543 - $section_key,
544 - __( 'Headline Testing', 'wp-parsely' ),
545 - function (): void {
546 - echo '<p>' . esc_html__( 'Configure Parse.ly Headline Testing to automatically test different headline variations and optimize for engagement.', 'wp-parsely' ) . '</p>';
547 - echo '<p><a href="https://docs.parse.ly/dashboard/optimization-menu/headline-testing-tab/" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Learn more about Headline Testing', 'wp-parsely' ) . '</a></p>';
548 - },
549 - Parsely::MENU_SLUG
550 - );
551 -
552 - // Enable Headline Testing.
553 - $field_id = 'headline_testing[enabled]';
554 - $field_args = array(
555 - 'option_key' => $field_id,
556 - 'label_for' => $field_id,
557 - 'yes_text' => __( 'Enabled', 'wp-parsely' ),
558 - 'add_fieldset' => true,
559 - 'legend' => __( 'Headline Testing', 'wp-parsely' ),
560 - 'help_text' => __( 'Enable Parse.ly Headline Testing to automatically test different headline variations.', 'wp-parsely' ),
561 - );
562 - add_settings_field(
563 - $field_id,
564 - __( 'Headline Testing', 'wp-parsely' ),
565 - array( $this, 'print_checkbox_tag' ),
566 - Parsely::MENU_SLUG,
567 - $section_key,
568 - $field_args
569 - );
570 -
571 - // Installation Method.
572 - $field_id = 'headline_testing[installation_method]';
573 - $field_args = array(
574 - 'option_key' => $field_id,
575 - 'label_for' => $field_id,
576 - 'help_text' => __( 'Choose how you want to install the Headline Testing script. One-line snippet is recommended for most sites.', 'wp-parsely' ),
577 - 'radio_options' => array(
578 - 'one_line' => __( 'One-line Snippet (Recommended)', 'wp-parsely' ),
579 - 'advanced' => __( 'Advanced Installation', 'wp-parsely' ),
580 - ),
581 - );
582 - add_settings_field(
583 - $field_id,
584 - __( 'Installation Method', 'wp-parsely' ),
585 - array( $this, 'print_radio_tags' ),
586 - Parsely::MENU_SLUG,
587 - $section_key,
588 - $field_args
589 - );
590 -
591 - // Enable Flicker Control (Advanced only).
592 - $field_id = 'headline_testing[enable_flicker_control]';
593 - $field_args = array(
594 - 'option_key' => $field_id,
595 - 'label_for' => $field_id,
596 - 'yes_text' => __( 'Enabled', 'wp-parsely' ),
597 - 'help_text' => __( 'Hide page body for up to 500ms to prevent flickering when headlines are replaced. Only available with Advanced installation.', 'wp-parsely' ),
598 - );
599 - add_settings_field(
600 - $field_id,
601 - __( 'Enable Flicker Control', 'wp-parsely' ),
602 - array( $this, 'print_checkbox_tag' ),
603 - Parsely::MENU_SLUG,
604 - $section_key,
605 - $field_args
606 - );
607 -
608 - // Enable Live Updates.
609 - $field_id = 'headline_testing[enable_live_updates]';
610 - $field_args = array(
611 - 'option_key' => $field_id,
612 - 'label_for' => $field_id,
613 - 'yes_text' => __( 'Enabled', 'wp-parsely' ),
614 - 'help_text' => __( 'Watch for new content and automatically update headlines for newly added anchors.', 'wp-parsely' ),
615 - );
616 - add_settings_field(
617 - $field_id,
618 - __( 'Enable Live Updates', 'wp-parsely' ),
619 - array( $this, 'print_checkbox_tag' ),
620 - Parsely::MENU_SLUG,
621 - $section_key,
622 - $field_args
623 - );
624 -
625 - // Live Update Timeout.
626 - $field_id = 'headline_testing[live_update_timeout]';
627 - $field_args = array(
628 - 'option_key' => $field_id,
629 - 'label_for' => $field_id,
630 - 'help_text' => __( 'How long to watch for new content (in milliseconds). Default: 30000 (30 seconds).', 'wp-parsely' ),
631 - 'optional_args' => array(
632 - 'type' => 'number',
633 - 'placeholder' => '30000',
634 - 'min' => '1000',
635 - 'max' => '60000',
636 - 'step' => '1000',
637 - ),
638 - );
639 - add_settings_field(
640 - $field_id,
641 - __( 'Live Update Timeout (ms)', 'wp-parsely' ),
642 - array( $this, 'print_text_tag' ),
643 - Parsely::MENU_SLUG,
644 - $section_key,
645 - $field_args
646 - );
647 -
648 - // Allow After Content Load.
649 - $field_id = 'headline_testing[allow_after_content_load]';
650 - $field_args = array(
651 - 'option_key' => $field_id,
652 - 'label_for' => $field_id,
653 - 'yes_text' => __( 'Enabled', 'wp-parsely' ),
654 - 'help_text' => __( 'Allow headline swapping even after the main content has loaded. May cause flickering. Highly recommended if you are loading your script asynchronously.', 'wp-parsely' ),
655 - );
656 - add_settings_field(
657 - $field_id,
658 - __( 'Allow After Content Load', 'wp-parsely' ),
659 - array( $this, 'print_checkbox_tag' ),
660 - Parsely::MENU_SLUG,
661 - $section_key,
662 - $field_args
663 - );
664 - }
665 -
666 - /**
667 379 * Registers section and settings for Recrawl section.
668 380 *
669 381 * @since 3.2.0
670 382 */
@@ -697,30 +409,12 @@
697 409 array(
698 410 'title' => __( 'Track Post Types as', 'wp-parsely' ),
699 411 'option_key' => $field_id,
700 412 'help_text' => $field_help,
413 + 'filter' => 'wp_parsely_trackable_statuses',
701 414 )
702 415 );
703 416
704 - // Use full metadata in non-posts.
705 - $field_id = 'full_metadata_in_non_posts';
706 - add_settings_field(
707 - $field_id,
708 - $this->set_field_label_contents( __( 'Use Full Metadata in Non-Posts', 'wp-parsely' ), $field_id ),
709 - array( $this, 'print_radio_tags' ),
710 - Parsely::MENU_SLUG,
711 - $section_key,
712 - array(
713 - 'title' => __( 'Use Full Metadata in Non-Posts', 'wp-parsely' ), // Passed for legend element.
714 - 'option_key' => $field_id,
715 - 'radio_options' => array(
716 - 'true' => __( 'Yes, add full metadata to Post Types being tracked as Non-Posts.', 'wp-parsely' ),
717 - 'false' => __( 'No, we have code that modifies metadata and that has not been tested for compatibility yet.', 'wp-parsely' ),
718 - ),
719 - 'help_text' => __( '<strong><span style="color:#d63638">Important: This setting will be removed in the future, force-enabling this behavior.</span></strong> If you\'re using any code that modifies metadata in a way that conflicts with this setting when it is enabled, please apply any needed fixes.', 'wp-parsely' ),
720 - )
721 - );
722 -
723 417 // Content ID Prefix.
724 418 $field_id = 'content_id_prefix';
725 419 $field_args = array(
726 420 'option_key' => $field_id,
@@ -731,9 +425,9 @@
731 425 'label_for' => $field_id,
732 426 );
733 427 add_settings_field(
734 428 $field_id,
735 - $this->set_field_label_contents( __( 'Content ID Prefix', 'wp-parsely' ), $field_id ),
429 + __( 'Content ID Prefix', 'wp-parsely' ),
736 430 array( $this, 'print_text_tag' ),
737 431 Parsely::MENU_SLUG,
738 432 $section_key,
739 433 $field_args
@@ -739,18 +433,17 @@
739 433 $field_args
740 434 );
741 435
742 436 // Use top-level categories.
743 - $field_id = 'use_top_level_cats';
744 437 add_settings_field(
745 - $field_id,
746 - $this->set_field_label_contents( __( 'Use Top-Level Categories for Section', 'wp-parsely' ), $field_id ),
438 + 'use_top_level_cats',
439 + __( 'Use Top-Level Categories for Section', 'wp-parsely' ),
747 440 array( $this, 'print_radio_tags' ),
748 441 Parsely::MENU_SLUG,
749 442 $section_key,
750 443 array(
751 444 'title' => __( 'Use Top-Level Categories for Section', 'wp-parsely' ), // Passed for legend element.
752 - 'option_key' => $field_id,
445 + 'option_key' => 'use_top_level_cats',
753 446 'radio_options' => array(
754 447 'true' => __( 'Yes, use the first category assigned to a post as the section name.', 'wp-parsely' ),
755 448 'false' => __( 'No, do not use the first category assigned to a post as the section name.', 'wp-parsely' ),
756 449 ),
@@ -762,14 +455,24 @@
762 455 $field_id = 'custom_taxonomy_section';
763 456 $field_args = array(
764 457 'option_key' => $field_id,
765 458 'help_text' => __( 'By default, the section value in your Parse.ly dashboard maps to a post\'s category. You can optionally choose a custom taxonomy, if you\'ve created one, to populate the section value instead.', 'wp-parsely' ),
766 - 'select_options' => self::get_section_taxonomies(),
459 + // filter WordPress taxonomies under the hood that should not appear in dropdown.
460 + 'select_options' => array_diff(
461 + get_taxonomies(),
462 + array(
463 + 'post_tag',
464 + 'nav_menu',
465 + 'author',
466 + 'link_category',
467 + 'post_format',
468 + )
469 + ),
767 470 'label_for' => Parsely::OPTIONS_KEY . "[$field_id]",
768 471 );
769 472 add_settings_field(
770 473 $field_id,
771 - $this->set_field_label_contents( __( 'Use Custom Taxonomy for Section', 'wp-parsely' ), $field_id ),
474 + __( 'Use Custom Taxonomy for Section', 'wp-parsely' ),
772 475 array( $this, 'print_select_tag' ),
773 476 Parsely::MENU_SLUG,
774 477 $section_key,
775 478 $field_args
@@ -775,18 +478,17 @@
775 478 $field_args
776 479 );
777 480
778 481 // Use categories and custom taxonomies as tags.
779 - $field_id = 'cats_as_tags';
780 482 add_settings_field(
781 - $field_id,
782 - $this->set_field_label_contents( __( 'Add Categories to Tags', 'wp-parsely' ), $field_id ),
483 + 'cats_as_tags',
484 + __( 'Add Categories to Tags', 'wp-parsely' ),
783 485 array( $this, 'print_radio_tags' ),
784 486 Parsely::MENU_SLUG,
785 487 $section_key,
786 488 array(
787 489 'title' => __( 'Add Categories to Tags', 'wp-parsely' ), // Passed for legend element.
788 - 'option_key' => $field_id,
490 + 'option_key' => 'cats_as_tags',
789 491 'radio_options' => array(
790 492 'true' => __( 'Yes, add all assigned categories and taxonomies to my tags.', 'wp-parsely' ),
791 493 'false' => __( 'No, do not add all assigned categories and taxonomies to my tags.', 'wp-parsely' ),
792 494 ),
@@ -794,18 +496,17 @@
794 496 )
795 497 );
796 498
797 499 // Lowercase all tags.
798 - $field_id = 'lowercase_tags';
799 500 add_settings_field(
800 - $field_id,
801 - $this->set_field_label_contents( __( 'Lowercase All Tags', 'wp-parsely' ), $field_id ),
501 + 'lowercase_tags',
502 + __( 'Lowercase All Tags', 'wp-parsely' ),
802 503 array( $this, 'print_radio_tags' ),
803 504 Parsely::MENU_SLUG,
804 505 $section_key,
805 506 array(
806 507 'title' => __( 'Lowercase All Tags', 'wp-parsely' ), // Passed for legend element.
807 - 'option_key' => $field_id,
508 + 'option_key' => 'lowercase_tags',
808 509 'radio_options' => array(
809 510 'true' => __( 'Yes, use lowercase versions of my tags to correct for potential misspellings.', 'wp-parsely' ),
810 511 'false' => __( 'No, do not use lowercase versions of my tags to correct for potential misspellings.', 'wp-parsely' ),
811 512 ),
@@ -811,18 +512,17 @@
811 512 ),
812 513 )
813 514 );
814 515
815 - $field_id = 'force_https_canonicals';
816 516 add_settings_field(
817 - $field_id,
818 - $this->set_field_label_contents( __( 'Force HTTPS Canonicals', 'wp-parsely' ), $field_id ),
517 + 'force_https_canonicals',
518 + __( 'Force HTTPS Canonicals', 'wp-parsely' ),
819 519 array( $this, 'print_radio_tags' ),
820 520 Parsely::MENU_SLUG,
821 521 $section_key,
822 522 array(
823 523 'title' => __( 'Force HTTPS Canonicals', 'wp-parsely' ), // Passed for legend element.
824 - 'option_key' => $field_id,
524 + 'option_key' => 'force_https_canonicals',
825 525 'radio_options' => array(
826 526 'true' => __( 'Yes, force <code>https</code> canonical URLs by default.', 'wp-parsely' ),
827 527 'false' => __( 'No, I want to use <code>http</code>.', 'wp-parsely' ),
828 528 ),
@@ -836,10 +536,9 @@
836 536 *
837 537 * @since 3.2.0
838 538 */
839 539 private function initialize_advanced_section(): void {
840 - $are_credentials_managed = $this->parsely->are_credentials_managed;
841 - $section_key = 'advanced-section';
540 + $section_key = 'advanced-section';
842 541
843 542 add_settings_section(
844 543 $section_key,
845 544 __( 'Advanced', 'wp-parsely' ),
@@ -846,41 +545,18 @@
846 545 '__return_null',
847 546 Parsely::MENU_SLUG
848 547 );
849 548
850 - if ( ! $are_credentials_managed ) {
851 - // Metadata Secret.
852 - $field_id = 'metadata_secret';
853 - $field_args = array(
854 - 'option_key' => $field_id,
855 - 'help_text' => __( 'Your metadata secret is given to you by Parse.ly support. DO NOT enter anything here unless given to you by Parse.ly support!', 'wp-parsely' ),
856 - 'label_for' => $field_id,
857 - 'optional_args' => array(
858 - 'type' => 'password',
859 - 'is_obfuscated_value' => true,
860 - ),
861 - );
862 - add_settings_field(
863 - $field_id,
864 - __( 'Metadata Secret', 'wp-parsely' ),
865 - array( $this, 'print_text_tag' ),
866 - Parsely::MENU_SLUG,
867 - $section_key,
868 - $field_args
869 - );
870 - }
871 -
872 549 // Disable autotrack.
873 - $field_id = 'disable_autotrack';
874 550 add_settings_field(
875 - $field_id,
876 - $this->set_field_label_contents( __( 'Disable Autotracking', 'wp-parsely' ), $field_id ),
551 + 'disable_autotrack',
552 + __( 'Disable Autotracking', 'wp-parsely' ),
877 553 array( $this, 'print_radio_tags' ),
878 554 Parsely::MENU_SLUG,
879 555 $section_key,
880 556 array(
881 557 'title' => __( 'Disable Autotracking', 'wp-parsely' ), // Passed for legend element.
882 - 'option_key' => $field_id,
558 + 'option_key' => 'disable_autotrack',
883 559 'radio_options' => array(
884 560 'true' => __( 'Yes, disable autotracking. I do not want the tracking code to report an event as soon as the script has finished loading. I plan to implement Dynamic Tracking myself.', 'wp-parsely' ),
885 561 'false' => __( 'No, do not disable autotracking. I want to make sure the default behavior of the tracking code is in place. The tracking code should report an event as soon as the script has finished loading.', 'wp-parsely' ),
886 562 ),
@@ -885,8 +561,22 @@
885 561 'false' => __( 'No, do not disable autotracking. I want to make sure the default behavior of the tracking code is in place. The tracking code should report an event as soon as the script has finished loading.', 'wp-parsely' ),
886 562 ),
887 563 )
888 564 );
565 +
566 + // Clear metadata.
567 + add_settings_field(
568 + 'parsely_wipe_metadata_cache',
569 + __( 'Wipe Parse.ly Metadata Info', 'wp-parsely' ),
570 + array( $this, 'print_checkbox_tag' ),
571 + Parsely::MENU_SLUG,
572 + $section_key,
573 + array(
574 + 'option_key' => 'parsely_wipe_metadata_cache',
575 + 'yes_text' => __( 'Yes, clear all metadata information for Parse.ly posts and re-send all metadata to Parse.ly.', 'wp-parsely' ),
576 + 'help_text' => __( '<span style="color:#d63638">WARNING:</span> Do not do this unless explicitly instructed by Parse.ly Staff!', 'wp-parsely' ),
577 + )
578 + );
889 579 }
890 580
891 581 /**
892 582 * Shows setting tabs.
@@ -970,163 +660,64 @@
970 660 *
971 661 * @param Setting_Arguments $args The arguments for text tag.
972 662 */
973 663 public function print_text_tag( $args ): void {
974 - $options = $this->parsely->get_options();
975 - $name = $args['option_key'];
976 - $raw_value = $this->get_option_value( $name, $options );
977 - $value_as_string = is_scalar( $raw_value ) ? (string) $raw_value : '';
664 + $options = $this->parsely->get_options();
665 + $name = $args['option_key'];
666 + /**
667 + * Variable.
668 + *
669 + * @var string
670 + */
671 + $value = $options[ $name ] ?? '';
978 672 $optional_args = $args['optional_args'] ?? array();
979 673 $id = esc_attr( $name );
980 - $html_name = $this->get_html_name_attribute( $name );
674 + $name = Parsely::OPTIONS_KEY . "[$id]";
981 675 $is_obfuscated_value = $optional_args['is_obfuscated_value'] ?? false;
982 - $value = $is_obfuscated_value ? $this->get_obfuscated_value( $value_as_string ) : esc_attr( $value_as_string );
676 + $value = $is_obfuscated_value ? $this->get_obfuscated_value( $value ) : esc_attr( $value );
677 + $accepted_args = array( 'placeholder', 'required' );
983 678 $type = $optional_args['type'] ?? 'text';
984 - $accepted_args = array( 'placeholder', 'required', 'disabled' );
985 679
986 - if ( 'number' === $type ) {
987 - $accepted_args = array_merge( $accepted_args, array( 'min', 'max', 'step' ) );
988 - }
680 + echo sprintf( "<input type='%s' name='%s' id='%s' value='%s'", esc_attr( $type ), esc_attr( $name ), esc_attr( $id ), esc_attr( $value ) );
989 681
990 - $is_managed = key_exists( $id, $this->parsely->managed_options );
991 - echo '<fieldset', $is_managed ? ' disabled>' : '>';
992 - printf( "<input type='%s' name='%s' id='%s' value='%s'", esc_attr( $type ), esc_attr( $html_name ), esc_attr( $id ), esc_attr( $value ) );
993 -
994 682 if ( isset( $args['help_text'] ) ) {
995 683 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
996 684 }
997 685
998 686 foreach ( $optional_args as $key => $val ) {
999 - if ( \in_array( $key, $accepted_args, true ) && false !== $val ) {
1000 - // Don't add a placeholder to managed option fields.
1001 - if ( $is_managed && 'placeholder' === $key ) {
1002 - continue;
1003 - }
1004 -
1005 - // Support attributes without values.
1006 - echo ' ' . esc_attr( $key );
1007 - if ( true !== $val ) {
1008 - echo '="' . esc_attr( $val ) . '"';
1009 - }
687 + if ( \in_array( $key, $accepted_args, true ) ) {
688 + echo ' ' . esc_attr( $key ) . '="' . esc_attr( (string) $val ) . '"';
1010 689 }
1011 690 }
1012 - echo ' /></fieldset>';
691 + echo ' />';
1013 692
1014 693 $this->print_description_text( $args );
1015 694 }
1016 695
1017 696 /**
1018 - * Prints a checkbox tag.
697 + * Prints a checkbox tag in the settings page.
1019 698 *
1020 - * @since 3.16.0
1021 - *
1022 - * @param Setting_Arguments $args Arguments for the checkbox tag.
1023 - * @param Parsely_Options|null $options The options to use.
699 + * @param Setting_Arguments $args Arguments to print to checkbox tag.
1024 700 */
1025 - public function print_checkbox_tag( $args, $options = null ): void {
1026 - $options = $options ?? $this->parsely->get_options();
1027 - $name = $args['option_key'];
1028 - $has_fieldset = isset( $args['add_fieldset'] ) && true === $args['add_fieldset'];
1029 - $html_id = rtrim( str_replace( array( '[', ']', '__' ), '_', $name ), '_' );
1030 - $html_name = $this->get_html_name_attribute( $name );
1031 - $yes_text = $args['yes_text'] ?? '';
1032 - $value = $this->get_option_value( $name, $options );
701 + public function print_checkbox_tag( $args ): void {
702 + $options = $this->parsely->get_options();
703 + $name = $args['option_key'];
704 + $value = $options[ $name ];
705 + $id = esc_attr( $name );
706 + $name = Parsely::OPTIONS_KEY . "[$id]";
707 + $yes_text = $args['yes_text'] ?? '';
1033 708
1034 - // Fieldset start.
1035 - if ( $has_fieldset ) {
1036 - echo '<fieldset>';
1037 - if ( isset( $args['legend'] ) ) {
1038 - echo '<legend class="screen-reader-text"><span>';
1039 - echo esc_html( $args['legend'] ) . '</span></legend>';
1040 - }
1041 - }
1042 -
1043 - // Label and contained checkbox.
1044 - printf( '<label for="%s">', esc_attr( $html_id ) );
1045 - printf(
1046 - '<input type="checkbox" name="%s" id="%s" value="true" ',
1047 - esc_attr( $html_name ),
1048 - esc_attr( $html_id )
1049 - );
709 + echo sprintf( "<input type='checkbox' name='%s' id='%s_true' value='true' ", esc_attr( $name ), esc_attr( $id ) );
1050 710 if ( isset( $args['help_text'] ) ) {
1051 - echo ' aria-describedby="' . esc_attr( $html_id ) . '-description"';
711 + echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
1052 712 }
1053 - checked( true === $value, true );
1054 - printf( ' /> %s</label>', esc_html( $yes_text ) );
713 + echo checked( true === $value, true, false );
714 + echo sprintf( " /> <label for='%s_true'>%s</label>", esc_attr( $id ), esc_html( $yes_text ) );
1055 715
1056 - // Fieldset end.
1057 - if ( $has_fieldset ) {
1058 - echo '</fieldset>';
1059 - }
1060 -
1061 716 $this->print_description_text( $args );
1062 717 }
1063 718
1064 719 /**
1065 - * Prints a Content Intelligence AI feature section.
1066 - *
1067 - * @since 3.16.0
1068 - *
1069 - * @param Setting_Arguments $args The arguments for the section.
1070 - */
1071 - public function print_content_helper_ai_feature_section( $args ): void {
1072 - $options = $this->parsely->get_options();
1073 - $feature_id = str_replace(
1074 - array( 'content_helper[', ']' ),
1075 - '',
1076 - $args['option_key']
1077 - );
1078 -
1079 - // Convert user role settings to make them work in the settings page.
1080 - $option = &$options['content_helper'][ $feature_id ];
1081 - if ( isset( $option['allowed_user_roles'] ) ) {
1082 - foreach ( $option['allowed_user_roles'] as $role ) {
1083 - $option['allowed_user_roles'][ $role ] = true;
1084 - }
1085 - }
1086 -
1087 - // Feature "Enabled" checkbox.
1088 - $enabled_args = array(
1089 - 'option_key' => $args['option_key'] . '[enabled]',
1090 - 'label_for' => $args['option_key'] . '[enabled]',
1091 - 'yes_text' => __( 'Enabled', 'wp-parsely' ),
1092 - 'add_fieldset' => true,
1093 - );
1094 - if ( isset( $args['legend'] ) ) {
1095 - $enabled_args['legend'] = $args['legend'];
1096 - }
1097 - $this->print_checkbox_tag( $enabled_args, $options );
1098 -
1099 - // User role permissions fieldset.
1100 - echo '<p>' . esc_html__( 'User Permissions', 'wp-parsely' ) . '</p>';
1101 - echo '<fieldset class="user-role-permissions">';
1102 - echo '<legend class="screen-reader-text"><span>';
1103 - printf(
1104 - /* translators: %s: Feature name */
1105 - esc_html__( '%s User Permissions', 'wp-parsely' ),
1106 - esc_html( $args['legend'] ?? __( 'Feature', 'wp-parsely' ) )
1107 - );
1108 - echo '</span></legend>';
1109 -
1110 - // User role checkboxes.
1111 - $user_roles = Permissions::get_user_roles_with_edit_posts_cap();
1112 - foreach ( $user_roles as $key => $name ) {
1113 - $option_key = $args['option_key'] . '[allowed_user_roles][' . $key . ']';
1114 - $role_args = array(
1115 - 'option_key' => $option_key,
1116 - 'label_for' => $option_key,
1117 - 'yes_text' => translate_user_role( $name ),
1118 - );
1119 -
1120 - $this->print_checkbox_tag( $role_args, $options );
1121 - }
1122 -
1123 - echo '</fieldset>';
1124 -
1125 - $this->print_filter_text( $args );
1126 - }
1127 -
1128 - /**
1129 720 * Prints out the select tags
1130 721 *
1131 722 * @param Setting_Arguments $args The arguments for the select dropdowns.
1132 723 */
@@ -1137,11 +728,9 @@
1137 728 $selected = $options[ $name ] ?? null;
1138 729 $id = esc_attr( $name );
1139 730 $name = Parsely::OPTIONS_KEY . "[$id]";
1140 731
1141 - $is_managed = key_exists( $id, $this->parsely->managed_options );
1142 - echo '<fieldset', $is_managed ? ' disabled>' : '>';
1143 - printf( "<select name='%s' id='%s'", esc_attr( $name ), esc_attr( $name ) );
732 + echo sprintf( "<select name='%s' id='%s'", esc_attr( $name ), esc_attr( $name ) );
1144 733 if ( isset( $args['help_text'] ) ) {
1145 734 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
1146 735 }
1147 736 echo '>';
@@ -1151,9 +740,9 @@
1151 740 echo selected( $selected, $key, false ) . '>';
1152 741 echo esc_html( $val );
1153 742 echo '</option>';
1154 743 }
1155 - echo '</select></fieldset>';
744 + echo '</select>';
1156 745
1157 746 $this->print_filter_text( $args );
1158 747 $this->print_description_text( $args );
1159 748 }
@@ -1163,13 +752,11 @@
1163 752 *
1164 753 * @param Setting_Arguments $args The arguments for the radio buttons.
1165 754 */
1166 755 public function print_radio_tags( $args ): void {
1167 - $options = $this->parsely->get_options();
1168 756 $name = $args['option_key'];
1169 757 $id = esc_attr( $name );
1170 - $selected = $this->get_option_value( $name, $options );
1171 - $html_name = $this->get_html_name_attribute( $name );
758 + $selected = $this->parsely->get_options()[ $name ];
1172 759 $title = $args['title'] ?? '';
1173 760 $radio_options = $args['radio_options'] ?? array();
1174 761
1175 762 if ( is_bool( $selected ) ) {
@@ -1176,11 +763,10 @@
1176 763 // Converting boolean to string so that we have string type keys for all cases.
1177 764 $selected = $selected ? 'true' : 'false';
1178 765 }
1179 766
1180 - $is_managed = key_exists( $name, $this->parsely->managed_options );
1181 767 ?>
1182 - <fieldset <?php echo $is_managed ? 'disabled' : ''; ?>>
768 + <fieldset>
1183 769 <legend class="screen-reader-text"><span><?php echo esc_html( $title ); ?></span></legend>
1184 770 <p>
1185 771 <?php foreach ( $radio_options as $value => $text ) { ?>
1186 772 <label for="<?php echo esc_attr( "{$id}_{$value}" ); ?>">
@@ -1185,9 +771,9 @@
1185 771 <?php foreach ( $radio_options as $value => $text ) { ?>
1186 772 <label for="<?php echo esc_attr( "{$id}_{$value}" ); ?>">
1187 773 <input
1188 774 type="radio"
1189 - name="<?php echo esc_attr( $html_name ); ?>"
775 + name="<?php echo esc_attr( Parsely::OPTIONS_KEY . "[$id]" ); ?>"
1190 776 id="<?php echo esc_attr( "{$id}_{$value}" ); ?>"
1191 777 value="<?php echo esc_attr( $value ); ?>"
1192 778 <?php checked( $selected, $value ); ?>
1193 779 />
@@ -1218,13 +804,11 @@
1218 804 */
1219 805 $input_value = $this->parsely->get_options()[ $key ];
1220 806 $input_name = Parsely::OPTIONS_KEY . "[$key]";
1221 807 $button_text = __( 'Browse', 'wp-parsely' );
1222 -
1223 - $is_managed = key_exists( $key, $this->parsely->managed_options );
1224 808 ?>
1225 809
1226 - <fieldset class="media-single-image" id="media-single-image-<?php echo esc_attr( $key ); ?>"<?php echo $is_managed ? ' disabled' : ''; ?>>
810 + <fieldset class="media-single-image" id="media-single-image-<?php echo esc_attr( $key ); ?>">
1227 811 <legend class="screen-reader-text"><span><?php echo esc_html( $title ); ?></span></legend>
1228 812 <input class="file-path" type="text" name="<?php echo esc_attr( $input_name ); ?>" id="logo" value="<?php echo esc_attr( $input_value ); ?>" />
1229 813 <button data-option="<?php echo esc_attr( $key ); ?>" class="browse button" type="button"><?php echo esc_html( $button_text ); ?></button>
1230 814 </fieldset>
@@ -1233,55 +817,8 @@
1233 817 $this->print_description_text( $args );
1234 818 }
1235 819
1236 820 /**
1237 - * Generates the HTML name attribute for a form field.
1238 - *
1239 - * Handles nested options (content_helper, headline_testing) and regular
1240 - * options, properly escaping and formatting the attribute value.
1241 - *
1242 - * @since 3.21.0
1243 - *
1244 - * @param string $name The option key/name.
1245 - * @return string The properly formatted and escaped HTML name attribute value.
1246 - */
1247 - private function get_html_name_attribute( string $name ): string {
1248 - if ( strpos( $name, 'content_helper' ) === 0 ) {
1249 - return Parsely::OPTIONS_KEY . str_replace(
1250 - 'content_helper',
1251 - '[content_helper]',
1252 - esc_attr( $name )
1253 - );
1254 - } elseif ( strpos( $name, 'headline_testing' ) === 0 ) {
1255 - return Parsely::OPTIONS_KEY . str_replace(
1256 - 'headline_testing',
1257 - '[headline_testing]',
1258 - esc_attr( $name )
1259 - );
1260 - } else {
1261 - return Parsely::OPTIONS_KEY . '[' . esc_attr( $name ) . ']';
1262 - }
1263 - }
1264 -
1265 - /**
1266 - * Gets the value of an option, handling both flat and nested option keys.
1267 - *
1268 - * @since 3.21.0
1269 - *
1270 - * @param string $name The option key name (may contain brackets for nested options).
1271 - * @param Parsely_Options $options The options array to retrieve from.
1272 - * @return mixed The option value, or null if not found.
1273 - */
1274 - private function get_option_value( string $name, $options ) {
1275 - // Get raw value based on whether it's a nested option or not.
1276 - if ( false === strpos( $name, '[' ) ) {
1277 - return $options[ $name ] ?? null;
1278 - } else {
1279 - return Parsely::get_nested_option_value( $name, $options ) ?? null;
1280 - }
1281 - }
1282 -
1283 - /**
1284 821 * Prints out the post tracking options table.
1285 822 *
1286 823 * @since 3.2.0
1287 824 *
@@ -1373,14 +910,13 @@
1373 910 /**
1374 911 * Validates the options provided by the user.
1375 912 *
1376 913 * @param ParselySettingOptions $input Options from the settings page.
914 + *
1377 915 * @return ParselySettingOptions
1378 916 */
1379 917 public function validate_options( $input ) {
1380 918 $input = $this->validate_basic_section( $input );
1381 - $input = $this->validate_content_helper_section( $input );
1382 - $input = $this->validate_headline_testing_section( $input );
1383 919 $input = $this->validate_recrawl_section( $input );
1384 920 $input = $this->validate_advanced_section( $input );
1385 921
1386 922 return $input;
@@ -1389,46 +925,50 @@
1389 925 /**
1390 926 * Validates fields of Basic Section.
1391 927 *
1392 928 * @param ParselySettingOptions $input Options from the settings page.
929 + *
1393 930 * @return ParselySettingOptions Validated inputs.
1394 931 */
1395 - private function validate_basic_section( $input ): array {
1396 - $are_credentials_managed = $this->parsely->are_credentials_managed;
1397 - $options = $this->parsely->get_options();
932 + private function validate_basic_section( $input ) {
933 + $options = $this->parsely->get_options();
1398 934
1399 - if ( $are_credentials_managed ) {
1400 - $input['apikey'] = '';
1401 - $input['api_secret'] = '';
935 + if ( '' === $input['apikey'] ) {
936 + add_settings_error(
937 + Parsely::OPTIONS_KEY,
938 + 'apikey',
939 + __( 'Please specify the Site ID', 'wp-parsely' )
940 + );
1402 941 } else {
1403 - $site_id = $this->sanitize_site_id( $input['apikey'] );
1404 - $api_secret = $this->get_unobfuscated_value( $input['api_secret'], $this->parsely->get_api_secret() );
942 + $site_id = $this->sanitize_site_id( $input['apikey'] );
943 + if ( false === $this->validate_site_id( $site_id ) ) {
944 + add_settings_error(
945 + Parsely::OPTIONS_KEY,
946 + 'apikey',
947 + __( 'Your Parse.ly Site ID looks incorrect, it should look like "example.com".', 'wp-parsely' )
948 + );
949 + } else {
950 + $input['apikey'] = $site_id;
951 + }
952 + }
1405 953
1406 - $valid_credentials = Validator::validate_api_credentials( $this->parsely, $site_id, $api_secret );
954 + $input['api_secret'] = $this->get_unobfuscated_value( $input['api_secret'], $this->parsely->get_api_secret() );
1407 955
1408 - // When running e2e tests, we need to update the API keys without validating them, as they will be invalid.
1409 - // phpcs:ignore WordPress.Security.NonceVerification.Missing
1410 - if ( isset( $_POST['e2e_parsely_skip_api_validate'] ) && 'y' === $_POST['e2e_parsely_skip_api_validate'] ) {
1411 - $valid_credentials = true;
1412 - }
1413 -
1414 - if (
1415 - ( is_wp_error( $valid_credentials ) && Validator::INVALID_API_CREDENTIALS === $valid_credentials->get_error_code() ) ||
1416 - ( is_bool( $valid_credentials ) && ! $valid_credentials )
1417 - ) {
956 + $input['metadata_secret'] = $this->get_unobfuscated_value( $input['metadata_secret'], $this->parsely->get_options()['metadata_secret'] );
957 + if ( '' !== $input['metadata_secret'] ) {
958 + if ( strlen( $input['metadata_secret'] ) !== 10 ) {
1418 959 add_settings_error(
1419 960 Parsely::OPTIONS_KEY,
1420 - 'api_secret',
1421 - __( 'The Site ID and API Secret weren\'t saved as they failed to authenticate with the Parse.ly API. Try again with different credentials or contact Parse.ly support', 'wp-parsely' )
961 + 'metadata_secret',
962 + __( 'Metadata secret is incorrect. Please contact Parse.ly support!', 'wp-parsely' )
1422 963 );
1423 - $input['apikey'] = $options['apikey'];
1424 - $input['api_secret'] = $options['api_secret'];
1425 - }
964 + } elseif (
965 + isset( $input['parsely_wipe_metadata_cache'] ) && 'true' === $input['parsely_wipe_metadata_cache'] // @phpstan-ignore-line
966 + ) {
967 + delete_post_meta_by_key( 'parsely_metadata_last_updated' );
1426 968
1427 - // Since the API secret is obfuscated, we need to make sure that the value
1428 - // is not changed when the credentials are valid.
1429 - if ( true === $valid_credentials && $input['api_secret'] !== $api_secret ) {
1430 - $input['api_secret'] = $api_secret;
969 + wp_schedule_event( time() + 100, 'everytenminutes', 'parsely_bulk_metas_update' );
970 + $input['parsely_wipe_metadata_cache'] = false;
1431 971 }
1432 972 }
1433 973
1434 974 if ( ! isset( $input['meta_type'] ) ) {
@@ -1484,166 +1024,12 @@
1484 1024 return $input;
1485 1025 }
1486 1026
1487 1027 /**
1488 - * Validates the fields of the Content Intelligence section.
1028 + * Validates fields of Recrawl Section.
1489 1029 *
1490 - * @since 3.16.0
1491 - *
1492 - * @param ParselySettingOptions $input The settings page options.
1493 - * @return ParselySettingOptions The validated input.
1494 - */
1495 - private function validate_content_helper_section( $input ) {
1496 - /**
1497 - * Sanitizes the Content Intelligence data.
1498 - *
1499 - * @since 3.16.0
1500 - */
1501 - $sanitize = function ( $input ) use ( &$sanitize ) {
1502 - foreach ( $input as $key => $value ) {
1503 - if ( is_array( $value ) ) {
1504 - if ( 'allowed_user_roles' === $key && count( $input[ $key ] ) > 0 ) {
1505 - $passed_roles = array_keys( $input[ $key ] );
1506 - $valid_roles = array_keys(
1507 - Permissions::get_user_roles_with_edit_posts_cap()
1508 - );
1509 - $sanitized_roles = array();
1510 -
1511 - // Sanitize passed user roles and remove invalid ones.
1512 - foreach ( $passed_roles as $user_role ) {
1513 - if ( ! is_string( $user_role ) ) {
1514 - continue;
1515 - }
1516 -
1517 - $user_role = sanitize_text_field( $user_role );
1518 - if ( in_array( $user_role, $valid_roles, true ) ) {
1519 - $sanitized_roles[] = $user_role;
1520 - }
1521 - }
1522 -
1523 - $input[ $key ] = $sanitized_roles;
1524 - } else {
1525 - // Recurse when we have an array that's not user roles.
1526 - $input[ $key ] = $sanitize( $value );
1527 - }
1528 - } else {
1529 - // Enforce a boolean value.
1530 - $input[ $key ] = 'true' === $value || true === $value;
1531 - }
1532 - }
1533 -
1534 - return $input;
1535 - };
1536 -
1537 - // Add any missing data due to unchecked checkboxes.
1538 - if ( ! isset( $input['content_helper']['ai_features_enabled'] ) ) {
1539 - $input['content_helper']['ai_features_enabled'] = false;
1540 - }
1541 - foreach ( $this->configurable_pch_features as $feature_id ) {
1542 - if ( ! isset( $input['content_helper'][ $feature_id ] ) ) {
1543 - $input['content_helper'][ $feature_id ] = array(
1544 - 'enabled' => false,
1545 - 'allowed_user_roles' => array(),
1546 - );
1547 - } else {
1548 - if ( ! isset( $input['content_helper'][ $feature_id ]['enabled'] ) ) {
1549 - // @phpstan-ignore-next-line
1550 - $input['content_helper'][ $feature_id ]['enabled'] = false;
1551 - }
1552 - if ( ! isset( $input['content_helper'][ $feature_id ]['allowed_user_roles'] ) ) {
1553 - // @phpstan-ignore-next-line
1554 - $input['content_helper'][ $feature_id ]['allowed_user_roles'] = array();
1555 - }
1556 - }
1557 - }
1558 -
1559 - // Produce the final array.
1560 - $options = $this->parsely->get_options()['content_helper'];
1561 - $merged = array_merge( $options, $input['content_helper'] );
1562 -
1563 - $input['content_helper'] = $sanitize( $merged );
1564 -
1565 - return $input;
1566 - }
1567 -
1568 - /**
1569 - * Validates fields of Headline Testing Section.
1570 - *
1571 - * @since 3.21.0
1572 - *
1573 1030 * @param ParselySettingOptions $input Options from the settings page.
1574 - * @return ParselySettingOptions Validated inputs.
1575 - */
1576 - private function validate_headline_testing_section( $input ) {
1577 - /**
1578 - * Sanitizes the Headline Testing data.
1579 - *
1580 - * @since 3.21.0
1581 - */
1582 - $sanitize = function ( $input ) use ( &$sanitize ) {
1583 - foreach ( $input as $key => $value ) {
1584 - if ( is_array( $value ) ) {
1585 - // Recurse for nested arrays.
1586 - $input[ $key ] = $sanitize( $value );
1587 - } else {
1588 - $input[ $key ] = $this->sanitize_headline_testing_field( $key, $value );
1589 - }
1590 - }
1591 -
1592 - return $input;
1593 - };
1594 -
1595 - // Initialize headline_testing array if it doesn't exist.
1596 - if ( ! isset( $input['headline_testing'] ) ) {
1597 - $input['headline_testing'] = array();
1598 - }
1599 -
1600 - // Ensure all required keys exist with defaults.
1601 - $input['headline_testing'] = array_merge(
1602 - $this->parsely->get_default_options()['headline_testing'],
1603 - $input['headline_testing']
1604 - );
1605 -
1606 - // Produce the final array.
1607 - $options = $this->parsely->get_options()['headline_testing'];
1608 - $merged = array_merge( $options, $input['headline_testing'] );
1609 -
1610 - $input['headline_testing'] = $sanitize( $merged );
1611 -
1612 - return $input;
1613 - }
1614 -
1615 - /**
1616 - * Sanitizes headline testing field values.
1617 1031 *
1618 - * @since 3.21.0
1619 - *
1620 - * @param string $key The field key.
1621 - * @param mixed $value The field value.
1622 - * @return mixed The sanitized value.
1623 - */
1624 - private function sanitize_headline_testing_field( string $key, $value ) {
1625 - switch ( $key ) {
1626 - case 'enabled':
1627 - case 'enable_flicker_control':
1628 - case 'enable_live_updates':
1629 - case 'allow_after_content_load':
1630 - return 'true' === $value || true === $value;
1631 - case 'installation_method':
1632 - $valid_methods = array( 'one_line', 'advanced' );
1633 - return in_array( $value, $valid_methods, true ) ? $value : 'one_line';
1634 - case 'live_update_timeout':
1635 - $timeout = intval( is_scalar( $value ) ? (string) $value : '0' );
1636 - return ( $timeout >= 1000 && $timeout <= 60000 ) ? $timeout : 30000;
1637 - default:
1638 - return sanitize_text_field( is_scalar( $value ) ? (string) $value : '' );
1639 - }
1640 - }
1641 -
1642 - /**
1643 - * Validates fields of Recrawl Section.
1644 - *
1645 - * @param ParselySettingOptions $input Options from the settings page.
1646 1032 * @return ParselySettingOptions Validated inputs.
1647 1033 */
1648 1034 private function validate_recrawl_section( $input ) {
1649 1035 $options = $this->parsely->get_options();
@@ -1656,16 +1042,8 @@
1656 1042 } else {
1657 1043 $input['content_id_prefix'] = sanitize_text_field( $input['content_id_prefix'] );
1658 1044 }
1659 1045
1660 - // Full metadata in non-posts.
1661 - if ( ! isset( $input['full_metadata_in_non_posts'] ) ) {
1662 - $input['full_metadata_in_non_posts'] = true;
1663 - } else {
1664 - // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1665 - $input['full_metadata_in_non_posts'] = 'true' == $input['full_metadata_in_non_posts'];
1666 - }
1667 -
1668 1046 // Allow for Top-level categories setting to be conditionally included on the page.
1669 1047 // If it's not shown, then set the value as what was previously saved.
1670 1048 if ( ! isset( $input['use_top_level_cats'] ) ) {
1671 1049 $input['use_top_level_cats'] = 'true';
@@ -1757,30 +1135,14 @@
1757 1135 /**
1758 1136 * Validates fields of Advanced Section.
1759 1137 *
1760 1138 * @param ParselySettingOptions $input Options from the settings page.
1139 + *
1761 1140 * @return ParselySettingOptions Validated inputs.
1762 1141 */
1763 1142 private function validate_advanced_section( $input ) {
1764 - $are_credentials_managed = $this->parsely->are_credentials_managed;
1765 - $options = $this->parsely->get_options();
1143 + $options = $this->parsely->get_options();
1766 1144
1767 - if ( $are_credentials_managed ) {
1768 - $input['metadata_secret'] = '';
1769 - } else {
1770 - $input['metadata_secret'] = $this->get_unobfuscated_value( $input['metadata_secret'], $this->parsely->get_options()['metadata_secret'] );
1771 - $metadata_secret_length = strlen( $input['metadata_secret'] );
1772 - if ( $metadata_secret_length > 0 &&
1773 - false === Validator::validate_metadata_secret( $input['metadata_secret'] ) ) {
1774 - add_settings_error(
1775 - Parsely::OPTIONS_KEY,
1776 - 'metadata_secret',
1777 - __( 'The Metadata Secret was not saved because it is incorrect. Please contact Parse.ly support!', 'wp-parsely' )
1778 - );
1779 - $input['metadata_secret'] = $options['metadata_secret'];
1780 - }
1781 - }
1782 -
1783 1145 if ( ! isset( $input['disable_autotrack'] ) ) {
1784 1146 $input['disable_autotrack'] = $options['disable_autotrack'];
1785 1147 } elseif ( 'true' !== $input['disable_autotrack'] && 'false' !== $input['disable_autotrack'] ) {
1786 1148 add_settings_error(
@@ -1795,8 +1157,30 @@
1795 1157 return $input;
1796 1158 }
1797 1159
1798 1160 /**
1161 + * Validates the passed Site ID.
1162 + *
1163 + * Accepts a www prefix and up to 3 periods.
1164 + *
1165 + * Valid examples: 'test.com', 'www.test.com', 'subdomain.test.com',
1166 + * 'www.subdomain.test.com', 'subdomain.subdomain.test.com'.
1167 + *
1168 + * Invalid examples: 'test', 'test.com/', 'http://test.com', 'https://test.com',
1169 + * 'www.subdomain.subdomain.test.com'.
1170 + *
1171 + * @since 3.3.0
1172 + *
1173 + * @param string $site_id The Site ID to be validated.
1174 + * @return bool
1175 + */
1176 + private function validate_site_id( string $site_id ): bool {
1177 + $key_format = '/^((\w+)\.)?(([\w-]+)?)(\.[\w-]+){1,2}$/';
1178 +
1179 + return 1 === preg_match( $key_format, $site_id );
1180 + }
1181 +
1182 + /**
1799 1183 * Sanitizes the passed Site ID.
1800 1184 *
1801 1185 * @since 3.3.0
1802 1186 *
@@ -1880,18 +1264,17 @@
1880 1264
1881 1265 /**
1882 1266 * Sanitizes all elements in an option array.
1883 1267 *
1884 - * @param array<int, string> $options Array of options to be sanitized.
1268 + * @param array<int, string> $array Array of options to be sanitized.
1885 1269 * @return array<int, string>
1886 1270 */
1887 - private static function sanitize_option_array( array $options ): array {
1888 - $sanitized_options = $options;
1889 - foreach ( $options as $key => $val ) {
1890 - $sanitized_options[ $key ] = sanitize_text_field( $val );
1271 + private static function sanitize_option_array( array $array ): array {
1272 + $new_array = $array;
1273 + foreach ( $array as $key => $val ) {
1274 + $new_array[ $key ] = sanitize_text_field( $val );
1891 1275 }
1892 -
1893 - return $sanitized_options;
1276 + return $new_array;
1894 1277 }
1895 1278
1896 1279 /**
1897 1280 * Gets obfuscated value.
@@ -1896,8 +1279,9 @@
1896 1279 /**
1897 1280 * Gets obfuscated value.
1898 1281 *
1899 1282 * @param string $current_value Current value of the field.
1283 + *
1900 1284 * @return string
1901 1285 */
1902 1286 private function get_obfuscated_value( $current_value ): string {
1903 1287 return str_repeat( '*', strlen( $current_value ) );
@@ -1908,8 +1292,9 @@
1908 1292 *
1909 1293 * @param string $current_value Current value of the field.
1910 1294 * @param string $previous_value Previous value of the field. If current
1911 1295 * value is obfuscated then we will use this.
1296 + *
1912 1297 * @return string
1913 1298 */
1914 1299 private function get_unobfuscated_value( $current_value, $previous_value ): string {
1915 1300 if ( $current_value === $this->get_obfuscated_value( $current_value ) ) {
@@ -1916,63 +1301,6 @@
1916 1301 return '' === $current_value ? $current_value : $previous_value;
1917 1302 }
1918 1303
1919 1304 return $current_value;
1920 - }
1921 -
1922 - /**
1923 - * Returns the field label's title. If the option is managed, a badge is
1924 - * also included.
1925 - *
1926 - * @since 3.9.0
1927 - *
1928 - * @param string $title The field's title.
1929 - * @param string $option_id The option's ID.
1930 - * @return string The resulting content.
1931 - */
1932 - public function set_field_label_contents( string $title, string $option_id ): string {
1933 - $is_managed = key_exists( $option_id, $this->parsely->managed_options );
1934 -
1935 - if ( $is_managed ) {
1936 - $text = $this->managed_options_badge['text'] ?? '';
1937 - if ( ! is_string( $text ) || '' === $text ) {
1938 - return $title;
1939 - }
1940 -
1941 - $url = filter_var( $this->managed_options_badge['url'] ?? '', FILTER_VALIDATE_URL );
1942 -
1943 - if ( false === $url ) {
1944 - $badge = '<span class="managed-option-badge">' . esc_html( $text ) . '</span>';
1945 - } else {
1946 - $badge = '<a class="managed-option-badge" href="' . esc_url( $url ) .
1947 - '" target="_blank" rel="noopener">' . esc_html( $text ) . '</a>';
1948 - }
1949 -
1950 - return "{$title}&nbsp;&nbsp;{$badge}";
1951 - }
1952 -
1953 - return $title;
1954 - }
1955 -
1956 - /**
1957 - * Returns the taxonomies allowed to be used as custom_taxonomy_section
1958 - * option values.
1959 - *
1960 - * @since 3.9.0
1961 - *
1962 - * @return array<string> Array of taxonomies.
1963 - */
1964 - public static function get_section_taxonomies(): array {
1965 - return array_diff(
1966 - get_taxonomies(),
1967 - array(
1968 - 'post_tag',
1969 - 'nav_menu',
1970 - 'author',
1971 - 'link_category',
1972 - 'post_format',
1973 - 'wp_theme',
1974 - 'wp_template_part_area',
1975 - )
1976 - );
1977 1305 }
1978 1306 }