PluginProbe
Parse.ly / 3.14.3
Parse.ly v3.14.3
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
wp-parsely / src / UI / class-settings-page.php

class-settings-page.php in Parse.ly 3.14.3, at src/UI/class-settings-page.php

1,415 lines 47.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * UI: Settings page class
4 *
5 * @package Parsely
6 * @since 3.0.0
7 */
8
9 declare(strict_types=1);
10
11 namespace Parsely\UI;
12
13 use Parsely\Parsely;
14 use Parsely\Validator;
15
16 use function Parsely\Utils\get_asset_info;
17
18 use const Parsely\PARSELY_FILE;
19
20 /**
21 * Renders the wp-admin Parse.ly plugin settings page.
22 *
23 * @since 3.0.0
24 *
25 * @phpstan-import-type Parsely_Options from Parsely
26 *
27 * @phpstan-type Setting_Arguments array{
28 * option_key: string,
29 * label_for: string,
30 * title?: string,
31 * help_text?: string,
32 * yes_text?: string,
33 * filter?: string,
34 * optional_args?: Setting_Optional_Args,
35 * select_options?: array<string, string>,
36 * radio_options?: array<string, string>,
37 * }
38 *
39 * @phpstan-type Setting_Optional_Args array{
40 * type?: string,
41 * placeholder?: string,
42 * required?: string,
43 * is_obfuscated_value: bool,
44 * }
45 *
46 * @phpstan-type ParselySettingOptions array{
47 * apikey: string,
48 * api_secret: string,
49 * metadata_secret: string,
50 * meta_type?: string,
51 * logo: string,
52 * track_authenticated_users: bool|string,
53 * disable_javascript: bool|string,
54 * disable_amp?: bool,
55 * track_post_types_as?: array<string, string>,
56 * track_post_types: string[],
57 * track_page_types: string[],
58 * full_metadata_in_non_posts: ?bool,
59 * content_id_prefix?: string,
60 * use_top_level_cats?:bool|string,
61 * custom_taxonomy_section?: string,
62 * cats_as_tags?: bool|string,
63 * lowercase_tags?: bool,
64 * force_https_canonicals?: bool,
65 * disable_autotrack?: bool|string,
66 * }
67 */
68 final class Settings_Page {
69 /**
70 * Instance of Parsely class.
71 *
72 * @var Parsely
73 */
74 private $parsely;
75
76 /**
77 * Admin page name used for hook suffixes.
78 *
79 * @since 3.2.0
80 *
81 * @var string
82 */
83 private $hook_suffix;
84
85 /**
86 * Options for badges that are displayed for managed options.
87 *
88 * @since 3.9.0
89 *
90 * @var array<string, mixed>
91 */
92 private $managed_options_badge = array();
93
94 /**
95 * Constructor.
96 *
97 * @param Parsely $parsely Instance of Parsely class.
98 */
99 public function __construct( Parsely $parsely ) {
100 $this->parsely = $parsely;
101
102 $managed_options_badge = apply_filters(
103 'wp_parsely_managed_options_badge',
104 array(
105 'text' => __( 'Upgrade', 'wp-parsely' ),
106 'url' => 'https://www.parse.ly/getdemo/',
107 )
108 );
109
110 if ( is_array( $managed_options_badge ) ) {
111 $this->managed_options_badge = $managed_options_badge;
112 }
113 }
114
115 /**
116 * Registers settings page.
117 *
118 * @since 3.0.0
119 */
120 public function run(): void {
121 add_action( 'admin_menu', array( $this, 'add_settings_sub_menu' ) );
122 add_action( 'admin_init', array( $this, 'initialize_settings' ) );
123 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_settings_assets' ) );
124 }
125
126 /**
127 * Enqueues all needed scripts and styles for Parse.ly plugin settings page.
128 *
129 * @param string $hook_suffix The current page being loaded.
130 */
131 public function enqueue_settings_assets( string $hook_suffix ): void {
132 if ( 'settings_page_parsely' === $hook_suffix ) {
133 add_filter( 'media_library_months_with_files', '__return_empty_array' );
134 wp_enqueue_media();
135
136 $admin_settings_asset = get_asset_info( 'build/admin-settings.asset.php' );
137 $built_assets_url = plugin_dir_url( PARSELY_FILE ) . '/build/';
138
139 wp_enqueue_script(
140 'parsely-admin-settings',
141 $built_assets_url . 'admin-settings.js',
142 $admin_settings_asset['dependencies'],
143 $admin_settings_asset['version'],
144 true
145 );
146
147 wp_enqueue_style(
148 'parsely-admin-settings',
149 $built_assets_url . 'admin-settings.css',
150 $admin_settings_asset['dependencies'],
151 $admin_settings_asset['version']
152 );
153 }
154 }
155
156 /**
157 * Adds the Parse.ly settings page in WordPress settings menu.
158 */
159 public function add_settings_sub_menu(): void {
160 $suffix = add_options_page(
161 __( 'Parse.ly Settings', 'wp-parsely' ),
162 __( 'Parse.ly', 'wp-parsely' ),
163 Parsely::CAPABILITY, // phpcs:ignore WordPress.WP.Capabilities.Undetermined
164 Parsely::MENU_SLUG,
165 array( $this, 'display_settings' )
166 );
167
168 if ( is_string( $suffix ) ) {
169 $this->hook_suffix = $suffix;
170
171 // Adds help text when admin page loads.
172 add_action( 'load-' . $this->hook_suffix, array( $this, 'add_help_text' ) );
173 }
174 }
175
176 /**
177 * Adds the help tab to the settings page.
178 *
179 * @since 3.1.0
180 */
181 public function add_help_text(): void {
182 $screen = get_current_screen();
183 if ( null === $screen ) {
184 return;
185 }
186
187 $screen->add_help_tab(
188 array(
189 'id' => 'overview',
190 'title' => __( 'Overview', 'wp-parsely' ),
191 'content' => '<p>' . __( 'The only required setting on this page is the Site ID. All of the other settings are optional.', 'wp-parsely' ) . '</p>' .
192 '<p>' . __( 'You must click the Save Changes button at the bottom of the screen for new settings to take effect.', 'wp-parsely' ) . '</p>',
193 )
194 );
195 }
196
197 /**
198 * Displays the Parse.ly settings screen (options-general.php?page=[SLUG]).
199 */
200 public function display_settings(): void {
201 // phpcs:ignore WordPress.WP.Capabilities.Undetermined
202 if ( ! current_user_can( Parsely::CAPABILITY ) ) {
203 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wp-parsely' ) );
204 }
205
206 include_once plugin_dir_path( PARSELY_FILE ) . 'views/parsely-settings.php';
207 }
208
209 /**
210 * Initializes the settings for Parse.ly.
211 */
212 public function initialize_settings(): void {
213 // Add the option first, to prevent double sanitization of the uninitialized option as reported
214 // in https://core.trac.wordpress.org/ticket/21989.
215 // The option will be initialized with the default values.
216 add_option( Parsely::OPTIONS_KEY, $this->parsely->get_options() );
217
218 // All our options are actually stored in one single array to reduce DB queries.
219 register_setting(
220 Parsely::OPTIONS_KEY,
221 Parsely::OPTIONS_KEY,
222 array(
223 'type' => 'array',
224 'sanitize_callback' => array( $this, 'validate_options' ),
225 )
226 );
227
228 $this->initialize_basic_section();
229 $this->initialize_recrawl_section();
230 $this->initialize_advanced_section();
231 }
232
233 /**
234 * Registers section and settings for Basic section.
235 *
236 * @since 3.2.0
237 */
238 private function initialize_basic_section(): void {
239 $are_credentials_managed = $this->parsely->are_credentials_managed;
240 $section_key = 'basic-section';
241
242 add_settings_section(
243 $section_key,
244 __( 'Basic', 'wp-parsely' ),
245 '__return_null',
246 Parsely::MENU_SLUG
247 );
248
249 // Site ID.
250 $field_id = 'apikey';
251 $field_args = array(
252 'option_key' => $field_id,
253 'help_text' => __( 'Your Site ID is typically your own site domain without <code>http(s)://</code> prefixes or trailing <code>/</code> (e.g. <code>mydomain.com</code>).', 'wp-parsely' ),
254 'label_for' => $field_id,
255 'optional_args' => array(
256 'required' => 'required',
257 'placeholder' => 'mydomain.com',
258 'disabled' => $are_credentials_managed,
259 ),
260
261 );
262 add_settings_field(
263 $field_id,
264 __( 'Site ID <em>(required)</em>', 'wp-parsely' ),
265 array( $this, 'print_text_tag' ),
266 Parsely::MENU_SLUG,
267 $section_key,
268 $field_args
269 );
270
271 if ( ! $are_credentials_managed ) {
272 // API Secret.
273 $field_id = 'api_secret';
274 $field_args = array(
275 'option_key' => $field_id,
276 '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' ),
277 'label_for' => $field_id,
278 'optional_args' => array(
279 'type' => 'password',
280 'is_obfuscated_value' => true,
281 ),
282 );
283 add_settings_field(
284 $field_id,
285 __( 'API Secret', 'wp-parsely' ),
286 array( $this, 'print_text_tag' ),
287 Parsely::MENU_SLUG,
288 $section_key,
289 $field_args
290 );
291 }
292
293 // Metadata Format.
294 $field_id = 'meta_type';
295 $field_args = array(
296 'title' => __( 'Metadata Format', 'wp-parsely' ),
297 'option_key' => $field_id,
298 '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' ),
299 'radio_options' => array(
300 'json_ld' => 'json_ld',
301 'repeated_metas' => 'repeated_metas',
302 ),
303 'label_for' => Parsely::OPTIONS_KEY . "[$field_id]",
304 'filter' => 'wp_parsely_metadata',
305 );
306 add_settings_field(
307 $field_id,
308 $this->set_field_label_contents( __( 'Metadata Format', 'wp-parsely' ), $field_id ),
309 array( $this, 'print_radio_tags' ),
310 Parsely::MENU_SLUG,
311 $section_key,
312 $field_args
313 );
314
315 // Logo.
316 $field_help = __( 'Here you can specify your logo\'s URL by using the "Browse" button or typing the URL manually.', 'wp-parsely' );
317 $field_id = 'logo';
318 add_settings_field(
319 $field_id,
320 $this->set_field_label_contents( __( 'Logo', 'wp-parsely' ), $field_id ),
321 array( $this, 'print_media_single_image' ),
322 Parsely::MENU_SLUG,
323 $section_key,
324 array(
325 'title' => __( 'Logo', 'wp-parsely' ), // Passed for legend element.
326 'option_key' => $field_id,
327 'label_for' => $field_id,
328 'help_text' => $field_help,
329 )
330 );
331
332 // Track logged-in users.
333 $field_id = 'track_authenticated_users';
334 add_settings_field(
335 $field_id,
336 $this->set_field_label_contents( __( 'Track Logged-in Users', 'wp-parsely' ), $field_id ),
337 array( $this, 'print_radio_tags' ),
338 Parsely::MENU_SLUG,
339 $section_key,
340 array(
341 'title' => __( 'Track Logged-in Users', 'wp-parsely' ), // Passed for legend element.
342 'option_key' => $field_id,
343 'radio_options' => array(
344 'true' => __( 'Yes, track logged-in users.', 'wp-parsely' ),
345 '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' ),
346 ),
347 'help_text' => (
348 is_multisite() ?
349 __( ' Note: For WordPress multisite, a user must be logged-in to the current site to be considered logged-in.', 'wp-parsely' ) :
350 null
351 ),
352 )
353 );
354
355 // Disable JavaScript.
356 $field_id = 'disable_javascript';
357 add_settings_field(
358 $field_id,
359 $this->set_field_label_contents( __( 'Disable JavaScript', 'wp-parsely' ), $field_id ),
360 array( $this, 'print_radio_tags' ),
361 Parsely::MENU_SLUG,
362 $section_key,
363 array(
364 'title' => __( 'Disable JavaScript', 'wp-parsely' ), // Passed for legend element.
365 'option_key' => $field_id,
366 'radio_options' => array(
367 'true' => __( 'Yes, disable JavaScript tracking. I want to use a separate system for tracking instead of the Parse.ly plugin.', 'wp-parsely' ),
368 'false' => __( 'No, do not disable JavaScript tracking. I want the Parse.ly plugin to load the tracker.', 'wp-parsely' ),
369 ),
370 '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' ),
371 'filter' => 'wp_parsely_load_js_tracker',
372 )
373 );
374
375 if ( defined( 'AMP__VERSION' ) ) {
376 // Disable AMP tracking.
377 $field_id = 'disable_amp';
378 add_settings_field(
379 $field_id,
380 $this->set_field_label_contents( __( 'Disable AMP Tracking', 'wp-parsely' ), $field_id ),
381 array( $this, 'print_radio_tags' ),
382 Parsely::MENU_SLUG,
383 $section_key,
384 array(
385 'title' => __( 'Disable AMP Tracking', 'wp-parsely' ), // Passed for legend element.
386 'option_key' => $field_id,
387 'radio_options' => array(
388 'true' => __( 'Yes, disable Parse.ly tracking on AMP pages. I use a different system for JavaScript tracking on AMP pages.', 'wp-parsely' ),
389 'false' => __( 'No, do not disable Parse.ly tracking on AMP pages.', 'wp-parsely' ),
390 ),
391 )
392 );
393 }
394 }
395
396 /**
397 * Registers section and settings for Recrawl section.
398 *
399 * @since 3.2.0
400 */
401 private function initialize_recrawl_section(): void {
402 $section_key = 'recrawl-section';
403
404 add_settings_section(
405 $section_key,
406 __( 'Recrawl', 'wp-parsely' ),
407 function (): void {
408 echo '<br /><strong>' . wp_kses_post( __( '<span style="color:#d63638">Important:</span> Changing any of these values below on a site currently tracked with Parse.ly will require reprocessing of your Parse.ly data.', 'wp-parsely' ) ) . '</strong><br />';
409 printf(
410 /* translators: Mailto link */
411 esc_html__( 'Once you have changed a value and saved, please contact %s to request a recrawl.', 'wp-parsely' ),
412 wp_kses_post( '<a href="mailto:support@parsely.com?subject=' . rawurlencode( 'Please reprocess ' . $this->parsely->get_site_id() ) . '">support@parsely.com</a>' )
413 );
414 },
415 Parsely::MENU_SLUG
416 );
417
418 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
419 $field_id = 'track_post_types_as';
420 $field_help = __( 'By default, Parse.ly only tracks posts and pages. If you want to track other post types, select how you want to track them here.', 'wp-parsely' );
421 add_settings_field(
422 $field_id,
423 __( 'Track Post Types as', 'wp-parsely' ),
424 array( $this, 'print_track_post_types_table' ),
425 Parsely::MENU_SLUG,
426 $section_key,
427 array(
428 'title' => __( 'Track Post Types as', 'wp-parsely' ),
429 'option_key' => $field_id,
430 'help_text' => $field_help,
431 'filter' => 'wp_parsely_trackable_statuses',
432 )
433 );
434
435 // Use full metadata in non-posts.
436 $field_id = 'full_metadata_in_non_posts';
437 add_settings_field(
438 $field_id,
439 $this->set_field_label_contents( __( 'Use Full Metadata in Non-Posts', 'wp-parsely' ), $field_id ),
440 array( $this, 'print_radio_tags' ),
441 Parsely::MENU_SLUG,
442 $section_key,
443 array(
444 'title' => __( 'Use Full Metadata in Non-Posts', 'wp-parsely' ), // Passed for legend element.
445 'option_key' => $field_id,
446 'radio_options' => array(
447 'true' => __( 'Yes, add full metadata to Post Types being tracked as Non-Posts.', 'wp-parsely' ),
448 'false' => __( 'No, we have code that modifies metadata and that has not been tested for compatibility yet.', 'wp-parsely' ),
449 ),
450 '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' ),
451 )
452 );
453
454 // Content ID Prefix.
455 $field_id = 'content_id_prefix';
456 $field_args = array(
457 'option_key' => $field_id,
458 'optional_args' => array(
459 'placeholder' => 'WP-',
460 ),
461 'help_text' => __( 'If you use more than one content management system (e.g. WordPress and Drupal), you may end up with duplicate content IDs. Adding a Content ID Prefix will ensure the content IDs from WordPress will not conflict with other content management systems. We recommend using "WP-" for your prefix.', 'wp-parsely' ),
462 'label_for' => $field_id,
463 );
464 add_settings_field(
465 $field_id,
466 $this->set_field_label_contents( __( 'Content ID Prefix', 'wp-parsely' ), $field_id ),
467 array( $this, 'print_text_tag' ),
468 Parsely::MENU_SLUG,
469 $section_key,
470 $field_args
471 );
472
473 // Use top-level categories.
474 $field_id = 'use_top_level_cats';
475 add_settings_field(
476 $field_id,
477 $this->set_field_label_contents( __( 'Use Top-Level Categories for Section', 'wp-parsely' ), $field_id ),
478 array( $this, 'print_radio_tags' ),
479 Parsely::MENU_SLUG,
480 $section_key,
481 array(
482 'title' => __( 'Use Top-Level Categories for Section', 'wp-parsely' ), // Passed for legend element.
483 'option_key' => $field_id,
484 'radio_options' => array(
485 'true' => __( 'Yes, use the first category assigned to a post as the section name.', 'wp-parsely' ),
486 'false' => __( 'No, do not use the first category assigned to a post as the section name.', 'wp-parsely' ),
487 ),
488 'help_text' => __( 'If you choose Yes, and post a story to News > National > Florida, the plugin will use "News" for the section name in your dashboard instead of "Florida".', 'wp-parsely' ),
489 )
490 );
491
492 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
493 $field_id = 'custom_taxonomy_section';
494 $field_args = array(
495 'option_key' => $field_id,
496 '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' ),
497 'select_options' => self::get_section_taxonomies(),
498 'label_for' => Parsely::OPTIONS_KEY . "[$field_id]",
499 );
500 add_settings_field(
501 $field_id,
502 $this->set_field_label_contents( __( 'Use Custom Taxonomy for Section', 'wp-parsely' ), $field_id ),
503 array( $this, 'print_select_tag' ),
504 Parsely::MENU_SLUG,
505 $section_key,
506 $field_args
507 );
508
509 // Use categories and custom taxonomies as tags.
510 $field_id = 'cats_as_tags';
511 add_settings_field(
512 $field_id,
513 $this->set_field_label_contents( __( 'Add Categories to Tags', 'wp-parsely' ), $field_id ),
514 array( $this, 'print_radio_tags' ),
515 Parsely::MENU_SLUG,
516 $section_key,
517 array(
518 'title' => __( 'Add Categories to Tags', 'wp-parsely' ), // Passed for legend element.
519 'option_key' => $field_id,
520 'radio_options' => array(
521 'true' => __( 'Yes, add all assigned categories and taxonomies to my tags.', 'wp-parsely' ),
522 'false' => __( 'No, do not add all assigned categories and taxonomies to my tags.', 'wp-parsely' ),
523 ),
524 'help_text' => __( 'If you choose Yes, then a post that has been assigned the categories "Business/Tech" and "Business/Social" will automatically include "Business/Tech" and "Business/Social" as tags, too.', 'wp-parsely' ),
525 )
526 );
527
528 // Lowercase all tags.
529 $field_id = 'lowercase_tags';
530 add_settings_field(
531 $field_id,
532 $this->set_field_label_contents( __( 'Lowercase All Tags', 'wp-parsely' ), $field_id ),
533 array( $this, 'print_radio_tags' ),
534 Parsely::MENU_SLUG,
535 $section_key,
536 array(
537 'title' => __( 'Lowercase All Tags', 'wp-parsely' ), // Passed for legend element.
538 'option_key' => $field_id,
539 'radio_options' => array(
540 'true' => __( 'Yes, use lowercase versions of my tags to correct for potential misspellings.', 'wp-parsely' ),
541 'false' => __( 'No, do not use lowercase versions of my tags to correct for potential misspellings.', 'wp-parsely' ),
542 ),
543 )
544 );
545
546 $field_id = 'force_https_canonicals';
547 add_settings_field(
548 $field_id,
549 $this->set_field_label_contents( __( 'Force HTTPS Canonicals', 'wp-parsely' ), $field_id ),
550 array( $this, 'print_radio_tags' ),
551 Parsely::MENU_SLUG,
552 $section_key,
553 array(
554 'title' => __( 'Force HTTPS Canonicals', 'wp-parsely' ), // Passed for legend element.
555 'option_key' => $field_id,
556 'radio_options' => array(
557 'true' => __( 'Yes, force <code>https</code> canonical URLs by default.', 'wp-parsely' ),
558 'false' => __( 'No, I want to use <code>http</code>.', 'wp-parsely' ),
559 ),
560 'help_text' => __( 'Note: the plugin uses <code>http</code> by default, and this is fine for most publishers. It is unlikely you will have to change this unless directed to do so by a Parse.ly support representative.', 'wp-parsely' ),
561 )
562 );
563 }
564
565 /**
566 * Registers section and settings for Advanced section.
567 *
568 * @since 3.2.0
569 */
570 private function initialize_advanced_section(): void {
571 $are_credentials_managed = $this->parsely->are_credentials_managed;
572 $section_key = 'advanced-section';
573
574 add_settings_section(
575 $section_key,
576 __( 'Advanced', 'wp-parsely' ),
577 '__return_null',
578 Parsely::MENU_SLUG
579 );
580
581 if ( ! $are_credentials_managed ) {
582 // Metadata Secret.
583 $field_id = 'metadata_secret';
584 $field_args = array(
585 'option_key' => $field_id,
586 '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' ),
587 'label_for' => $field_id,
588 'optional_args' => array(
589 'type' => 'password',
590 'is_obfuscated_value' => true,
591 ),
592 );
593 add_settings_field(
594 $field_id,
595 __( 'Metadata Secret', 'wp-parsely' ),
596 array( $this, 'print_text_tag' ),
597 Parsely::MENU_SLUG,
598 $section_key,
599 $field_args
600 );
601 }
602
603 // Disable autotrack.
604 $field_id = 'disable_autotrack';
605 add_settings_field(
606 $field_id,
607 $this->set_field_label_contents( __( 'Disable Autotracking', 'wp-parsely' ), $field_id ),
608 array( $this, 'print_radio_tags' ),
609 Parsely::MENU_SLUG,
610 $section_key,
611 array(
612 'title' => __( 'Disable Autotracking', 'wp-parsely' ), // Passed for legend element.
613 'option_key' => $field_id,
614 'radio_options' => array(
615 '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' ),
616 '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' ),
617 ),
618 )
619 );
620 }
621
622 /**
623 * Shows setting tabs.
624 *
625 * @since 3.8.0
626 */
627 public function show_setting_tabs(): void {
628 global $wp_settings_sections;
629 ?>
630
631 <nav class="nav-tab-wrapper">
632 <?php foreach ( $wp_settings_sections[ Parsely::MENU_SLUG ] as $section ) { ?>
633 <a
634 class="nav-tab <?php echo esc_attr( $section['id'] . '-tab' ); ?>"
635 href=<?php echo esc_url_raw( '?page=' . Parsely::MENU_SLUG . '#' . $section['id'] ); ?>
636 >
637 <?php echo esc_html( $section['title'] ); ?>
638 </a>
639 <?php } ?>
640 </nav>
641
642 <?php
643 }
644
645 /**
646 * Shows content of setting tabs.
647 *
648 * @since 3.8.0
649 */
650 public function show_setting_tabs_content(): void {
651 global $wp_settings_sections;
652
653 foreach ( $wp_settings_sections[ Parsely::MENU_SLUG ] as $section ) {
654 ?>
655
656 <div class="tab-content <?php echo esc_attr( $section['id'] ); ?>">
657 <?php
658 if ( $section['callback'] ) {
659 call_user_func( $section['callback'], $section );
660 }
661 ?>
662
663 <table class="form-table" role="presentation">
664 <?php do_settings_fields( Parsely::MENU_SLUG, $section['id'] ); ?>
665 </table>
666 </div>
667
668 <?php
669 }
670 }
671
672 /**
673 * Prints out a warning if the filter for the setting is defined, if any.
674 *
675 * @since 3.4.0
676 *
677 * @param Setting_Arguments $args The arguments for the form field. May contain 'filter'.
678 */
679 private function print_filter_text( $args ): void {
680 if ( isset( $args['filter'] ) && has_filter( $args['filter'] ) ) {
681 echo '<p>';
682 echo '<b><code>' . esc_html( $args['filter'] ) . '</code>' . esc_html__( 'filter hook is in use!', 'wp-parsely' ) . '</b> ';
683 echo esc_html__( 'A callback is attached to the filter hook that might interfere and override this setting.', 'wp-parsely' );
684 echo '</p>';
685 }
686 }
687
688 /**
689 * Prints out the description text, if there is any.
690 *
691 * @since 3.1.0
692 *
693 * @param Setting_Arguments $args The arguments for the form field. May contain 'help_text'.
694 */
695 private function print_description_text( $args ): void {
696 echo isset( $args['help_text'] ) ? '<p class="description" id="' . esc_attr( $args['option_key'] ) . '-description">' . wp_kses_post( $args['help_text'] ) . '</p>' : '';
697 }
698
699 /**
700 * Prints out an input text tag.
701 *
702 * @param Setting_Arguments $args The arguments for text tag.
703 */
704 public function print_text_tag( $args ): void {
705 $options = $this->parsely->get_options();
706 $name = $args['option_key'];
707 /**
708 * Variable.
709 *
710 * @var string
711 */
712 $value = $options[ $name ] ?? '';
713 $optional_args = $args['optional_args'] ?? array();
714 $id = esc_attr( $name );
715 $name = Parsely::OPTIONS_KEY . "[$id]";
716 $is_obfuscated_value = $optional_args['is_obfuscated_value'] ?? false;
717 $value = $is_obfuscated_value ? $this->get_obfuscated_value( $value ) : esc_attr( $value );
718 $accepted_args = array( 'placeholder', 'required', 'disabled' );
719 $type = $optional_args['type'] ?? 'text';
720
721 $is_managed = key_exists( $id, $this->parsely->managed_options );
722 echo '<fieldset', $is_managed ? ' disabled>' : '>';
723 printf( "<input type='%s' name='%s' id='%s' value='%s'", esc_attr( $type ), esc_attr( $name ), esc_attr( $id ), esc_attr( $value ) );
724
725 if ( isset( $args['help_text'] ) ) {
726 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
727 }
728
729 foreach ( $optional_args as $key => $val ) {
730 if ( \in_array( $key, $accepted_args, true ) && false !== $val ) {
731 // Don't add a placeholder to managed option fields.
732 if ( $is_managed && 'placeholder' === $key ) {
733 continue;
734 }
735
736 // Support attributes without values.
737 echo ' ' . esc_attr( $key );
738 if ( true !== $val ) {
739 echo '="' . esc_attr( $val ) . '"';
740 }
741 }
742 }
743 echo ' /></fieldset>';
744
745 $this->print_description_text( $args );
746 }
747
748 /**
749 * Prints a checkbox tag in the settings page.
750 *
751 * @param Setting_Arguments $args Arguments to print to checkbox tag.
752 */
753 public function print_checkbox_tag( $args ): void {
754 $options = $this->parsely->get_options();
755 $name = $args['option_key'];
756 $value = $options[ $name ];
757 $id = esc_attr( $name );
758 $name = Parsely::OPTIONS_KEY . "[$id]";
759 $yes_text = $args['yes_text'] ?? '';
760
761 $is_managed = key_exists( $id, $this->parsely->managed_options );
762 echo '<fieldset', $is_managed ? ' disabled>' : '>';
763 printf( "<input type='checkbox' name='%s' id='%s_true' value='true' ", esc_attr( $name ), esc_attr( $id ) );
764 if ( isset( $args['help_text'] ) ) {
765 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
766 }
767 echo checked( true === $value, true, false );
768 printf( " /> <label for='%s_true'>%s</label><fieldset>", esc_attr( $id ), esc_html( $yes_text ) );
769
770 $this->print_description_text( $args );
771 }
772
773 /**
774 * Prints out the select tags
775 *
776 * @param Setting_Arguments $args The arguments for the select dropdowns.
777 */
778 public function print_select_tag( $args ): void {
779 $options = $this->parsely->get_options();
780 $name = $args['option_key'];
781 $select_options = $args['select_options'] ?? array();
782 $selected = $options[ $name ] ?? null;
783 $id = esc_attr( $name );
784 $name = Parsely::OPTIONS_KEY . "[$id]";
785
786 $is_managed = key_exists( $id, $this->parsely->managed_options );
787 echo '<fieldset', $is_managed ? ' disabled>' : '>';
788 printf( "<select name='%s' id='%s'", esc_attr( $name ), esc_attr( $name ) );
789 if ( isset( $args['help_text'] ) ) {
790 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
791 }
792 echo '>';
793
794 foreach ( $select_options as $key => $val ) {
795 echo '<option value="' . esc_attr( $key ) . '" ';
796 echo selected( $selected, $key, false ) . '>';
797 echo esc_html( $val );
798 echo '</option>';
799 }
800 echo '</select></fieldset>';
801
802 $this->print_filter_text( $args );
803 $this->print_description_text( $args );
804 }
805
806 /**
807 * Prints the radio buttons.
808 *
809 * @param Setting_Arguments $args The arguments for the radio buttons.
810 */
811 public function print_radio_tags( $args ): void {
812 $name = $args['option_key'];
813 $id = esc_attr( $name );
814 $selected = $this->parsely->get_options()[ $name ];
815 $title = $args['title'] ?? '';
816 $radio_options = $args['radio_options'] ?? array();
817
818 if ( is_bool( $selected ) ) {
819 // Converting boolean to string so that we have string type keys for all cases.
820 $selected = $selected ? 'true' : 'false';
821 }
822
823 $is_managed = key_exists( $name, $this->parsely->managed_options );
824 ?>
825 <fieldset <?php echo $is_managed ? 'disabled' : ''; ?>>
826 <legend class="screen-reader-text"><span><?php echo esc_html( $title ); ?></span></legend>
827 <p>
828 <?php foreach ( $radio_options as $value => $text ) { ?>
829 <label for="<?php echo esc_attr( "{$id}_{$value}" ); ?>">
830 <input
831 type="radio"
832 name="<?php echo esc_attr( Parsely::OPTIONS_KEY . "[$id]" ); ?>"
833 id="<?php echo esc_attr( "{$id}_{$value}" ); ?>"
834 value="<?php echo esc_attr( $value ); ?>"
835 <?php checked( $selected, $value ); ?>
836 />
837 <?php echo wp_kses_post( $text ); ?>
838 </label>
839 <br />
840 <?php } ?>
841 </p>
842 </fieldset>
843 <?php
844 $this->print_filter_text( $args );
845 $this->print_description_text( $args );
846 }
847
848 /**
849 * Prints out a "single-image browse control" which includes a text input to
850 * store image path and a button to browse for images.
851 *
852 * @param Setting_Arguments $args The arguments for the control.
853 */
854 public function print_media_single_image( $args ): void {
855 $key = $args['option_key'];
856 $title = $args['title'] ?? '';
857 /**
858 * Variable.
859 *
860 * @var string
861 */
862 $input_value = $this->parsely->get_options()[ $key ];
863 $input_name = Parsely::OPTIONS_KEY . "[$key]";
864 $button_text = __( 'Browse', 'wp-parsely' );
865
866 $is_managed = key_exists( $key, $this->parsely->managed_options );
867 ?>
868
869 <fieldset class="media-single-image" id="media-single-image-<?php echo esc_attr( $key ); ?>"<?php echo $is_managed ? ' disabled' : ''; ?>>
870 <legend class="screen-reader-text"><span><?php echo esc_html( $title ); ?></span></legend>
871 <input class="file-path" type="text" name="<?php echo esc_attr( $input_name ); ?>" id="logo" value="<?php echo esc_attr( $input_value ); ?>" />
872 <button data-option="<?php echo esc_attr( $key ); ?>" class="browse button" type="button"><?php echo esc_html( $button_text ); ?></button>
873 </fieldset>
874
875 <?php
876 $this->print_description_text( $args );
877 }
878
879 /**
880 * Prints out the post tracking options table.
881 *
882 * @since 3.2.0
883 *
884 * @param Setting_Arguments $args The arguments used in the output HTML elements.
885 */
886 public function print_track_post_types_table( $args ): void {
887 $option_key = esc_attr( $args['option_key'] );
888 $title = $args['title'] ?? '';
889 /**
890 * Variable.
891 *
892 * @var array<string>
893 */
894 $post_types = get_post_types( array( 'public' => true ) );
895 $values = $this->get_tracking_values_for_display();
896 ?>
897 <fieldset>
898 <legend class="screen-reader-text"><span><?php echo esc_html( $title ); ?></span></legend>
899 <table class="form-table widefat striped" id="track-post-types">
900 <caption class="screen-reader-text"><?php echo esc_html( $title ); ?></caption>
901 <thead>
902 <tr>
903 <th scope="col"><?php echo esc_html__( 'Post Type', 'wp-parsely' ); ?></th>
904 <th id="track-post-types--post" scope="col"><?php echo esc_html__( 'Track as Post', 'wp-parsely' ); ?></th>
905 <th id="track-post-types--page" scope="col"><?php echo esc_html__( 'Track as Non-Post', 'wp-parsely' ); ?></th>
906 <th id="track-post-types--none" scope="col"><?php echo esc_html__( 'Do not track', 'wp-parsely' ); ?></th>
907 </tr>
908 </thead>
909 <tbody>
910 <?php
911 foreach ( $post_types as $post_type ) {
912 $group_name = "parsely[{$option_key}][{$post_type}]";
913 $id_post = "{$option_key}_{$post_type}_post";
914 $id_page = "{$option_key}_{$post_type}_page";
915 $id_none = "{$option_key}_{$post_type}_none";
916 $value = $values[ $post_type ] ?? 'none';
917 ?>
918 <tr>
919 <th scope="row"><?php echo esc_html( $post_type ); ?></th>
920 <td>
921 <label aria-labelledby="track-post-types--post" for="<?php echo esc_attr( $id_post ); ?>">
922 <input id="<?php echo esc_attr( $id_post ); ?>" name="<?php echo esc_attr( $group_name ); ?>" type="radio" value="post" <?php checked( $value, 'post' ); ?> />
923 </label>
924 </td>
925 <td>
926 <label aria-labelledby="track-post-types--page" for="<?php echo esc_attr( $id_page ); ?>">
927 <input id="<?php echo esc_attr( $id_page ); ?>" name="<?php echo esc_attr( $group_name ); ?>" type="radio" value="page" <?php checked( $value, 'page' ); ?> />
928 </label>
929 </td>
930 <td>
931 <label aria-labelledby="track-post-types--none" for="<?php echo esc_attr( $id_none ); ?>">
932 <input id="<?php echo esc_attr( $id_none ); ?>" name="<?php echo esc_attr( $group_name ); ?>" type="radio" value="none" <?php checked( $value, 'none' ); ?> />
933 </label>
934 </td>
935 </tr>
936 <?php } ?>
937 </tbody>
938 </table>
939 </fieldset>
940 <?php
941 $this->print_filter_text( $args );
942 $this->print_description_text( $args );
943 }
944
945 /**
946 * Returns the custom post type tracking values in a format that is easily
947 * consumable by the print_track_post_types_table() function.
948 *
949 * @since 3.2.0
950 *
951 * @return array<string> Key-value pairs with post type and their 'track as' value.
952 */
953 public function get_tracking_values_for_display(): array {
954 $options = $this->parsely->get_options();
955 $types = array( 'post', 'page' );
956 $result = array();
957
958 foreach ( $types as $type ) {
959 $array_value = $options[ "track_{$type}_types" ];
960
961 foreach ( $array_value as $post_type ) {
962 $result[ $post_type ] = $type;
963 }
964 }
965
966 return $result;
967 }
968
969 /**
970 * Validates the options provided by the user.
971 *
972 * @param ParselySettingOptions $input Options from the settings page.
973 * @return ParselySettingOptions
974 */
975 public function validate_options( $input ) {
976 $input = $this->validate_basic_section( $input );
977 $input = $this->validate_recrawl_section( $input );
978 $input = $this->validate_advanced_section( $input );
979
980 return $input;
981 }
982
983 /**
984 * Validates fields of Basic Section.
985 *
986 * @param ParselySettingOptions $input Options from the settings page.
987 * @return ParselySettingOptions Validated inputs.
988 */
989 private function validate_basic_section( $input ) {
990 $are_credentials_managed = $this->parsely->are_credentials_managed;
991 $options = $this->parsely->get_options();
992
993 if ( $are_credentials_managed ) {
994 $input['apikey'] = '';
995 $input['api_secret'] = '';
996 } else {
997 $site_id = $this->sanitize_site_id( $input['apikey'] );
998 $api_secret = $this->get_unobfuscated_value( $input['api_secret'], $this->parsely->get_api_secret() );
999
1000 $valid_credentials = Validator::validate_api_credentials( $this->parsely, $site_id, $api_secret );
1001
1002 // When running e2e tests, we need to update the API keys without validating them, as they will be invalid.
1003 // phpcs:ignore WordPress.Security.NonceVerification.Missing
1004 if ( isset( $_POST['e2e_parsely_skip_api_validate'] ) && 'y' === $_POST['e2e_parsely_skip_api_validate'] ) {
1005 $valid_credentials = true;
1006 }
1007
1008 if ( is_wp_error( $valid_credentials ) && Validator::INVALID_API_CREDENTIALS === $valid_credentials->get_error_code() ) {
1009 add_settings_error(
1010 Parsely::OPTIONS_KEY,
1011 'api_secret',
1012 __( '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' )
1013 );
1014 $input['apikey'] = $options['apikey'];
1015 $input['api_secret'] = $options['api_secret'];
1016 }
1017
1018 // Since the API secret is obfuscated, we need to make sure that the value
1019 // is not changed when the credentials are valid.
1020 if ( true === $valid_credentials && $input['api_secret'] !== $api_secret ) {
1021 $input['api_secret'] = $api_secret;
1022 }
1023 }
1024
1025 if ( ! isset( $input['meta_type'] ) ) {
1026 $input['meta_type'] = $options['meta_type'];
1027 } else {
1028 $input['meta_type'] = sanitize_text_field( $input['meta_type'] );
1029 }
1030
1031 if ( '' === $input['logo'] ) {
1032 $input['logo'] = self::get_logo_default();
1033 }
1034
1035 // Track authenticated users.
1036 if ( 'true' !== $input['track_authenticated_users'] && 'false' !== $input['track_authenticated_users'] ) {
1037 add_settings_error(
1038 Parsely::OPTIONS_KEY,
1039 'track_authenticated_users',
1040 __( 'Value passed for track_authenticated_users must be either "true" or "false".', 'wp-parsely' )
1041 );
1042 } else {
1043 $input['track_authenticated_users'] = 'true' === $input['track_authenticated_users'];
1044 }
1045
1046 if ( 'true' !== $input['disable_javascript'] && 'false' !== $input['disable_javascript'] ) {
1047 add_settings_error(
1048 Parsely::OPTIONS_KEY,
1049 'disable_javascript',
1050 __( 'Value passed for disable_javascript must be either "Yes" or "No".', 'wp-parsely' )
1051 );
1052 } else {
1053 $input['disable_javascript'] = 'true' === $input['disable_javascript'];
1054 }
1055
1056 // Allow for Disable AMP setting to be conditionally included on the page.
1057 // If it's not shown, then set the value as what was previously saved.
1058 if ( ! isset( $input['disable_amp'] ) ) {
1059 $input['disable_amp'] = 'true';
1060 if ( false === $options['disable_amp'] ) {
1061 $input['disable_amp'] = 'false';
1062 }
1063 }
1064
1065 if ( 'true' !== $input['disable_amp'] && 'false' !== $input['disable_amp'] ) {
1066 add_settings_error(
1067 Parsely::OPTIONS_KEY,
1068 'disable_amp',
1069 __( 'Value passed for disable_amp must be either "true" or "false".', 'wp-parsely' )
1070 );
1071 } else {
1072 $input['disable_amp'] = 'true' === $input['disable_amp'];
1073 }
1074
1075 return $input;
1076 }
1077
1078 /**
1079 * Validates fields of Recrawl Section.
1080 *
1081 * @param ParselySettingOptions $input Options from the settings page.
1082 * @return ParselySettingOptions Validated inputs.
1083 */
1084 private function validate_recrawl_section( $input ) {
1085 $options = $this->parsely->get_options();
1086
1087 $this->validate_options_post_type_tracking( $input );
1088
1089 // Content ID prefix.
1090 if ( ! isset( $input['content_id_prefix'] ) ) {
1091 $input['content_id_prefix'] = $options['content_id_prefix'];
1092 } else {
1093 $input['content_id_prefix'] = sanitize_text_field( $input['content_id_prefix'] );
1094 }
1095
1096 // Full metadata in non-posts.
1097 if ( ! isset( $input['full_metadata_in_non_posts'] ) ) {
1098 $input['full_metadata_in_non_posts'] = true;
1099 } else {
1100 // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
1101 $input['full_metadata_in_non_posts'] = 'true' == $input['full_metadata_in_non_posts'];
1102 }
1103
1104 // Allow for Top-level categories setting to be conditionally included on the page.
1105 // If it's not shown, then set the value as what was previously saved.
1106 if ( ! isset( $input['use_top_level_cats'] ) ) {
1107 $input['use_top_level_cats'] = 'true';
1108 if ( false === $options['use_top_level_cats'] ) {
1109 $input['use_top_level_cats'] = 'false';
1110 }
1111 }
1112
1113 // Top-level categories.
1114 if ( 'true' !== $input['use_top_level_cats'] && 'false' !== $input['use_top_level_cats'] ) {
1115 add_settings_error(
1116 Parsely::OPTIONS_KEY,
1117 'use_top_level_cats',
1118 __( 'Value passed for use_top_level_cats must be either "true" or "false".', 'wp-parsely' )
1119 );
1120 } else {
1121 $input['use_top_level_cats'] = 'true' === $input['use_top_level_cats'];
1122 }
1123
1124 // Custom taxonomy as section.
1125 if ( ! isset( $input['custom_taxonomy_section'] ) ) {
1126 $input['custom_taxonomy_section'] = $options['custom_taxonomy_section'];
1127 } else {
1128 $input['custom_taxonomy_section'] = sanitize_text_field( $input['custom_taxonomy_section'] );
1129 }
1130
1131 // Allow for Categories as Tags setting to be conditionally included on the page.
1132 // If it's not shown, then set the value as what was previously saved.
1133 if ( ! isset( $input['cats_as_tags'] ) ) {
1134 $input['cats_as_tags'] = 'true';
1135 if ( false === $options['cats_as_tags'] ) {
1136 $input['cats_as_tags'] = 'false';
1137 }
1138 }
1139
1140 // Child categories as tags.
1141 if ( 'true' !== $input['cats_as_tags'] && 'false' !== $input['cats_as_tags'] ) {
1142 add_settings_error(
1143 Parsely::OPTIONS_KEY,
1144 'cats_as_tags',
1145 __( 'Value passed for cats_as_tags must be either "true" or "false".', 'wp-parsely' )
1146 );
1147 } else {
1148 $input['cats_as_tags'] = 'true' === $input['cats_as_tags'];
1149 }
1150
1151 // Allow for Lowercase Tags setting to be conditionally included on the page.
1152 // If it's not shown, then set the value as what was previously saved.
1153 if ( ! isset( $input['lowercase_tags'] ) ) {
1154 $input['lowercase_tags'] = 'true';
1155 if ( false === $options['lowercase_tags'] ) {
1156 $input['lowercase_tags'] = 'false';
1157 }
1158 }
1159
1160 // Lowercase tags.
1161 if ( 'true' !== $input['lowercase_tags'] && 'false' !== $input['lowercase_tags'] ) {
1162 add_settings_error(
1163 Parsely::OPTIONS_KEY,
1164 'lowercase_tags',
1165 __( 'Value passed for lowercase_tags must be either "true" or "false".', 'wp-parsely' )
1166 );
1167 } else {
1168 $input['lowercase_tags'] = 'true' === $input['lowercase_tags'];
1169 }
1170
1171 // Allow for Force HTTPS Canonical setting to be conditionally included on the page.
1172 // If it's not shown, then set the value as what was previously saved.
1173 if ( ! isset( $input['force_https_canonicals'] ) ) {
1174 $input['force_https_canonicals'] = 'true';
1175 if ( false === $options['force_https_canonicals'] ) {
1176 $input['force_https_canonicals'] = 'false';
1177 }
1178 }
1179
1180 if ( 'true' !== $input['force_https_canonicals'] && 'false' !== $input['force_https_canonicals'] ) {
1181 add_settings_error(
1182 Parsely::OPTIONS_KEY,
1183 'force_https_canonicals',
1184 __( 'Value passed for force_https_canonicals must be either "true" or "false".', 'wp-parsely' )
1185 );
1186 } else {
1187 $input['force_https_canonicals'] = 'true' === $input['force_https_canonicals'];
1188 }
1189
1190 return $input;
1191 }
1192
1193 /**
1194 * Validates fields of Advanced Section.
1195 *
1196 * @param ParselySettingOptions $input Options from the settings page.
1197 * @return ParselySettingOptions Validated inputs.
1198 */
1199 private function validate_advanced_section( $input ) {
1200 $are_credentials_managed = $this->parsely->are_credentials_managed;
1201 $options = $this->parsely->get_options();
1202
1203 if ( $are_credentials_managed ) {
1204 $input['metadata_secret'] = '';
1205 } else {
1206 $input['metadata_secret'] = $this->get_unobfuscated_value( $input['metadata_secret'], $this->parsely->get_options()['metadata_secret'] );
1207 $metadata_secret_length = strlen( $input['metadata_secret'] );
1208 if ( $metadata_secret_length > 0 &&
1209 false === Validator::validate_metadata_secret( $input['metadata_secret'] ) ) {
1210 add_settings_error(
1211 Parsely::OPTIONS_KEY,
1212 'metadata_secret',
1213 __( 'The Metadata Secret was not saved because it is incorrect. Please contact Parse.ly support!', 'wp-parsely' )
1214 );
1215 $input['metadata_secret'] = $options['metadata_secret'];
1216 }
1217 }
1218
1219 if ( ! isset( $input['disable_autotrack'] ) ) {
1220 $input['disable_autotrack'] = $options['disable_autotrack'];
1221 } elseif ( 'true' !== $input['disable_autotrack'] && 'false' !== $input['disable_autotrack'] ) {
1222 add_settings_error(
1223 Parsely::OPTIONS_KEY,
1224 'disable_autotrack',
1225 __( 'Value passed for disable_autotrack must be either "Yes" or "No".', 'wp-parsely' )
1226 );
1227 } else {
1228 $input['disable_autotrack'] = 'true' === $input['disable_autotrack'];
1229 }
1230
1231 return $input;
1232 }
1233
1234 /**
1235 * Sanitizes the passed Site ID.
1236 *
1237 * @since 3.3.0
1238 *
1239 * @param string $site_id The Site ID to be sanitized.
1240 * @return string
1241 */
1242 private function sanitize_site_id( string $site_id ): string {
1243 return strtolower( sanitize_text_field( $site_id ) );
1244 }
1245
1246 /**
1247 * Receives the $input array from the validate_options() function and
1248 * validate post tracking options.
1249 *
1250 * This function will mutate the $input array.
1251 *
1252 * @since 3.2.0
1253 *
1254 * @param ParselySettingOptions $input Array passed to validate_options() function.
1255 */
1256 private function validate_options_post_type_tracking( &$input ): void {
1257 $options = $this->parsely->get_options();
1258 $posts = 'track_post_types';
1259 $pages = 'track_page_types';
1260 $track_as = 'track_post_types_as';
1261 $input[ $posts ] = $options[ $posts ];
1262 $input[ $pages ] = $options[ $pages ];
1263
1264 // @phpstan-ignore-next-line
1265 if ( isset( $input[ $track_as ] ) && is_array( $input[ $track_as ] ) && 0 < count( $input[ $track_as ] ) ) {
1266 $post_types = get_post_types( array( 'public' => true ) );
1267 $temp_posts = array();
1268 $temp_pages = array();
1269
1270 // Create temporary Post and Page arrays, disallowing non-existent post types.
1271 foreach ( $input[ $track_as ] as $key => $value ) {
1272 if ( false === in_array( $key, $post_types, true ) ) {
1273 continue;
1274 }
1275
1276 if ( 'post' === $value ) {
1277 $temp_posts[] = $key;
1278 } elseif ( 'page' === $value ) {
1279 $temp_pages[] = $key;
1280 }
1281 }
1282
1283 // Cleanup and sanitized values assignment.
1284 $input [ $posts ] = self::sanitize_option_array( $temp_posts );
1285 $input [ $pages ] = self::sanitize_option_array( $temp_pages );
1286 }
1287
1288 if ( isset( $input[ $track_as ] ) ) {
1289 unset( $input[ $track_as ] );
1290 }
1291 }
1292
1293 /**
1294 * Returns default logo if one can be found.
1295 *
1296 * @return string
1297 */
1298 private static function get_logo_default(): string {
1299 /**
1300 * Variable.
1301 *
1302 * @var int
1303 */
1304 $custom_logo_id = get_theme_mod( 'custom_logo' );
1305 if ( (bool) $custom_logo_id ) {
1306 $logo_attrs = wp_get_attachment_image_src( $custom_logo_id, 'full' );
1307 if ( isset( $logo_attrs[0] ) ) {
1308 return $logo_attrs[0];
1309 }
1310 }
1311
1312 // get_site_icon_url returns an empty string if one isn't found,
1313 // which is what we want to use as the default anyway.
1314 return get_site_icon_url();
1315 }
1316
1317 /**
1318 * Sanitizes all elements in an option array.
1319 *
1320 * @param array<int, string> $options Array of options to be sanitized.
1321 * @return array<int, string>
1322 */
1323 private static function sanitize_option_array( array $options ): array {
1324 $sanitized_options = $options;
1325 foreach ( $options as $key => $val ) {
1326 $sanitized_options[ $key ] = sanitize_text_field( $val );
1327 }
1328
1329 return $sanitized_options;
1330 }
1331
1332 /**
1333 * Gets obfuscated value.
1334 *
1335 * @param string $current_value Current value of the field.
1336 * @return string
1337 */
1338 private function get_obfuscated_value( $current_value ): string {
1339 return str_repeat( '*', strlen( $current_value ) );
1340 }
1341
1342 /**
1343 * Gets unobfuscated value.
1344 *
1345 * @param string $current_value Current value of the field.
1346 * @param string $previous_value Previous value of the field. If current
1347 * value is obfuscated then we will use this.
1348 * @return string
1349 */
1350 private function get_unobfuscated_value( $current_value, $previous_value ): string {
1351 if ( $current_value === $this->get_obfuscated_value( $current_value ) ) {
1352 return '' === $current_value ? $current_value : $previous_value;
1353 }
1354
1355 return $current_value;
1356 }
1357
1358 /**
1359 * Returns the field label's title. If the option is managed, a badge is
1360 * also included.
1361 *
1362 * @since 3.9.0
1363 *
1364 * @param string $title The field's title.
1365 * @param string $option_id The option's ID.
1366 * @return string The resulting content.
1367 */
1368 public function set_field_label_contents( string $title, string $option_id ): string {
1369 $is_managed = key_exists( $option_id, $this->parsely->managed_options );
1370
1371 if ( $is_managed ) {
1372 $text = $this->managed_options_badge['text'] ?? '';
1373 if ( ! is_string( $text ) || '' === $text ) {
1374 return $title;
1375 }
1376
1377 $url = filter_var( $this->managed_options_badge['url'] ?? '', FILTER_VALIDATE_URL );
1378
1379 if ( false === $url ) {
1380 $badge = '<span class="managed-option-badge">' . esc_html( $text ) . '</span>';
1381 } else {
1382 $badge = '<a class="managed-option-badge" href="' . esc_url( $url ) .
1383 '" target="_blank" rel="noopener">' . esc_html( $text ) . '</a>';
1384 }
1385
1386 return "{$title}&nbsp;&nbsp;{$badge}";
1387 }
1388
1389 return $title;
1390 }
1391
1392 /**
1393 * Returns the taxonomies allowed to be used as custom_taxonomy_section
1394 * option values.
1395 *
1396 * @since 3.9.0
1397 *
1398 * @return array<string> Array of taxonomies.
1399 */
1400 public static function get_section_taxonomies(): array {
1401 return array_diff(
1402 get_taxonomies(),
1403 array(
1404 'post_tag',
1405 'nav_menu',
1406 'author',
1407 'link_category',
1408 'post_format',
1409 'wp_theme',
1410 'wp_template_part_area',
1411 )
1412 );
1413 }
1414 }
1415