PluginProbe
Parse.ly / 3.13.1
Parse.ly v3.13.1
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.13.1, at src/UI/class-settings-page.php

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