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

1,185 lines 40.2 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
15 use WP_Screen;
16 use const Parsely\PARSELY_FILE;
17
18 /**
19 * Renders the wp-admin Parse.ly plugin settings page.
20 *
21 * @since 3.0.0
22 */
23 final class Settings_Page {
24 /**
25 * Instance of Parsely class.
26 *
27 * @var Parsely
28 */
29 private $parsely;
30
31 /**
32 * Admin page name used for hook suffixes.
33 *
34 * @since 3.2.0
35 *
36 * @var string
37 */
38 private $hook_suffix;
39
40 /**
41 * Screen options name.
42 *
43 * Name must end in `_page` so that set-screen-option hook is triggered for
44 * WP < 5.4.2.
45 *
46 * @since 3.2.0
47 *
48 * @var string
49 */
50 private $screen_options_name = 'wp_parsely_page';
51
52 /**
53 * Constructor.
54 *
55 * @param Parsely $parsely Instance of Parsely class.
56 */
57 public function __construct( Parsely $parsely ) {
58 $this->parsely = $parsely;
59 }
60
61 /**
62 * Registers settings page.
63 *
64 * @since 3.0.0
65 */
66 public function run(): void {
67 add_action( 'admin_menu', array( $this, 'add_settings_sub_menu' ) );
68 add_action( 'admin_init', array( $this, 'initialize_settings' ) );
69 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_settings_assets' ) );
70
71 // Handle saving of screen options.
72 add_filter( 'set-screen-option', array( $this, 'set_screen_option' ), 11, 3 );
73 // Render screen options.
74 add_filter( 'screen_settings', array( $this, 'screen_settings' ), 10, 2 );
75 }
76
77 /**
78 * Enqueues all needed scripts and styles for Parse.ly plugin settings page.
79 *
80 * @param string $hook_suffix The current page being loaded.
81 */
82 public function enqueue_settings_assets( string $hook_suffix ): void {
83 if ( 'settings_page_parsely' === $hook_suffix ) {
84 add_filter( 'media_library_months_with_files', '__return_empty_array' );
85 wp_enqueue_media();
86
87 $admin_settings_asset = require plugin_dir_path( PARSELY_FILE ) . 'build/admin-settings.asset.php';
88 $built_assets_url = plugin_dir_url( PARSELY_FILE ) . '/build/';
89
90 wp_enqueue_script(
91 'parsely-admin-settings',
92 $built_assets_url . 'admin-settings.js',
93 $admin_settings_asset['dependencies'],
94 $admin_settings_asset['version'],
95 true
96 );
97
98 wp_enqueue_style(
99 'parsely-admin-settings',
100 $built_assets_url . 'admin-settings.css',
101 $admin_settings_asset['dependencies'],
102 $admin_settings_asset['version']
103 );
104 }
105 }
106
107 /**
108 * Adds the Parse.ly settings page in WordPress settings menu.
109 */
110 public function add_settings_sub_menu(): void {
111 $suffix = add_options_page(
112 __( 'Parse.ly Settings', 'wp-parsely' ),
113 __( 'Parse.ly', 'wp-parsely' ),
114 Parsely::CAPABILITY,
115 Parsely::MENU_SLUG,
116 array( $this, 'display_settings' )
117 );
118
119 if ( is_string( $suffix ) ) {
120 $this->hook_suffix = $suffix;
121
122 // Adds help text when admin page loads.
123 add_action( 'load-' . $this->hook_suffix, array( $this, 'add_help_text' ) );
124 // Adds screen options when admin page loads.
125 add_action( 'load-' . $this->hook_suffix, array( $this, 'add_screen_options' ) );
126 }
127 }
128
129 /**
130 * Saves the screen option setting.
131 *
132 * Nonce is already checked in set_screen_options() - no need to check here.
133 *
134 * @since 3.2.0
135 *
136 * @param mixed $screen_option The value to save instead of the option value.
137 * Default false (to skip saving the current option).
138 * @param string $option The option name.
139 * @param mixed $value The option value.
140 * @return mixed Updated option value.
141 */
142 public function set_screen_option( $screen_option, string $option, $value ) {
143 if ( $this->screen_options_name === $option ) {
144 // phpcs:ignore WordPress.Security.NonceVerification.Missing
145 if ( isset( $_POST[ $this->screen_options_name ] ) && is_array( $_POST[ $this->screen_options_name ] ) ) {
146 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
147 $unslashed = wp_unslash( $_POST[ $this->screen_options_name ] );
148 $data = array_map( 'sanitize_text_field', $unslashed );
149 }
150 $value = $data ?? array();
151 }
152 return $value;
153 }
154
155 /**
156 * Registers screen options.
157 *
158 * @since 3.2.0
159 */
160 public function add_screen_options(): void {
161 add_screen_option(
162 'wp_parsely_screen_options_requires_recrawl',
163 array(
164 'label' => __( 'Requires Recrawl Settings', 'wp-parsely' ),
165 'option' => 'requires-recrawl',
166 )
167 );
168 add_screen_option(
169 'wp_parsely_screen_options_advanced',
170 array(
171 'label' => __( 'Advanced Settings', 'wp-parsely' ),
172 'option' => 'advanced',
173 )
174 );
175 }
176
177 /**
178 * Renders the screen options block.
179 *
180 * @since 3.2.0
181 *
182 * @param string $screen_settings Screen settings.
183 * @param WP_Screen $screen WP_Screen object.
184 *
185 * @return string The filtered screen settings.
186 */
187 public function screen_settings( string $screen_settings, WP_Screen $screen ): string {
188 if ( $this->hook_suffix !== $screen->base ) {
189 return $screen_settings;
190 }
191
192 $current_screen = get_current_screen();
193 if ( null === $current_screen ) {
194 return $screen_settings;
195 }
196
197 $user_meta = get_user_meta( get_current_user_id(), $this->screen_options_name, true );
198
199 ob_start();
200 ?>
201 <fieldset>
202 <legend><?php esc_html_e( 'Show on screen', 'wp-parsely' ); ?></legend>
203 <input type="hidden" name="wp_screen_options[option]" value="<?php echo esc_attr( $this->screen_options_name ); ?>" />
204 <input type="hidden" name="wp_screen_options[value]" value="yes" />
205 <?php
206 foreach ( $current_screen->get_options() as $option ) {
207 $checked = isset( $user_meta[ $option['option'] ] );
208 $name = $this->screen_options_name . '[' . $option['option'] . ']';
209 ?>
210 <label><input class="hide-section-tog" name="<?php echo esc_attr( $name ); ?>" type="checkbox" id="<?php echo esc_attr( $option['option'] ); ?>" value="true"<?php checked( $checked ); ?>><?php echo esc_html( $option['label'] ); ?></label>
211 <?php
212 }
213 ?>
214 </fieldset>
215 <?php
216 submit_button( __( 'Apply', 'wp-parsely' ), 'primary', 'screen-options-apply' );
217
218 return ob_get_clean();
219 }
220
221 /**
222 * Adds the help tab to the settings page.
223 *
224 * @since 3.1.0
225 */
226 public function add_help_text(): void {
227 $screen = get_current_screen();
228 if ( null === $screen ) {
229 return;
230 }
231
232 $screen->add_help_tab(
233 array(
234 'id' => 'overview',
235 'title' => __( 'Overview', 'wp-parsely' ),
236 'content' => '<p>' . __( 'The only required setting on this page is the Site ID. All of the other settings are optional.', 'wp-parsely' ) . '</p>' .
237 '<p>' . __( 'You must click the Save Changes button at the bottom of the screen for new settings to take effect.', 'wp-parsely' ) . '</p>',
238 )
239 );
240 $screen->add_help_tab(
241 array(
242 'id' => 'requires_recrawl',
243 'title' => __( 'Requires Recrawl', 'wp-parsely' ),
244 'content' => '<p>' . __(
245 'Important: changing any of the values in the Requires Recrawl section on a site currently tracked with Parse.ly will require reprocessing of your Parse.ly data.
246 Once you have changed a value and saved, please contact support@parsely.com to request a recrawl.',
247 'wp-parsely'
248 ) . '</p>' .
249 '<p>' . __(
250 'If you can\'t see these settings, you will need to enable the Requires Recrawl Settings in the Screen Options.',
251 'wp-parsely'
252 ) . '</p>',
253 )
254 );
255 }
256
257 /**
258 * Displays the Parse.ly settings screen (options-general.php?page=[SLUG]).
259 */
260 public function display_settings(): void {
261 if ( ! current_user_can( Parsely::CAPABILITY ) ) {
262 wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wp-parsely' ) );
263 }
264
265 include plugin_dir_path( PARSELY_FILE ) . 'views/parsely-settings.php';
266 }
267
268 /**
269 * Initializes the settings for Parse.ly.
270 */
271 public function initialize_settings(): void {
272 // All our options are actually stored in one single array to reduce DB queries.
273 register_setting(
274 Parsely::OPTIONS_KEY,
275 Parsely::OPTIONS_KEY,
276 array( $this, 'validate_options' )
277 );
278
279 $user_meta = array_keys( (array) get_user_meta( get_current_user_id(), $this->screen_options_name, true ) );
280
281 $this->initialize_basic_section();
282
283 if ( in_array( 'requires-recrawl', $user_meta, true ) ) {
284 $this->initialize_requires_recrawl_section();
285 }
286
287 if ( in_array( 'advanced', $user_meta, true ) ) {
288 $this->initialize_advanced_section();
289 }
290 }
291
292 /**
293 * Registers section and settings for Basic section.
294 *
295 * @since 3.2.0
296 */
297 private function initialize_basic_section(): void {
298 add_settings_section(
299 'basic_settings',
300 __( 'Basic Settings', 'wp-parsely' ),
301 '__return_null',
302 Parsely::MENU_SLUG
303 );
304
305 // Get the API Key.
306 $h = __( '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' );
307 $field_id = 'apikey';
308 $field_args = array(
309 'option_key' => $field_id,
310 'help_text' => $h,
311 'label_for' => $field_id,
312 'optional_args' => array(
313 'required' => 'required',
314 'placeholder' => 'mydomain.com',
315 ),
316
317 );
318 add_settings_field(
319 $field_id,
320 __( 'Parse.ly Site ID <em>(required)</em>', 'wp-parsely' ),
321 array( $this, 'print_text_tag' ),
322 Parsely::MENU_SLUG,
323 'basic_settings',
324 $field_args
325 );
326
327 /* translators: 1: Opening anchor tag markup, 2: Documentation URL, 3: Opening anchor tag markup continued, 4: Closing anchor tag */
328 $h = __( 'Your API secret is your secret code to <a href="https://www.parse.ly/help/api/analytics/">access our API</a>. It can be found at <code>dash.parsely.com/<var>yoursitedomain</var>/settings/api</code> (replace <var>yoursitedomain</var> with your domain name, e.g. <samp>mydomain.com</samp>).<br />If you haven\'t purchased access to the API and would like to do so, email your account manager or <a href="mailto:support@parsely.com">support@parsely.com</a>.', 'wp-parsely' );
329 $field_id = 'api_secret';
330 $field_args = array(
331 'option_key' => $field_id,
332 'help_text' => $h,
333 'label_for' => $field_id,
334 );
335 add_settings_field(
336 $field_id,
337 __( 'Parse.ly API Secret', 'wp-parsely' ),
338 array( $this, 'print_text_tag' ),
339 Parsely::MENU_SLUG,
340 'basic_settings',
341 $field_args
342 );
343
344 $h = __( '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' );
345 $field_id = 'metadata_secret';
346 $field_args = array(
347 'option_key' => $field_id,
348 'help_text' => $h,
349 'label_for' => $field_id,
350 );
351 add_settings_field(
352 $field_id,
353 __( 'Parse.ly Metadata Secret', 'wp-parsely' ),
354 array( $this, 'print_text_tag' ),
355 Parsely::MENU_SLUG,
356 'basic_settings',
357 $field_args
358 );
359
360 // Logo.
361 $field_help = __( 'Here you can specify your logo\'s URL by using the "Browse" button or typing the URL manually.', 'wp-parsely' );
362 $field_id = 'logo';
363 add_settings_field(
364 $field_id,
365 __( 'Logo', 'wp-parsely' ),
366 array( $this, 'print_media_single_image' ),
367 Parsely::MENU_SLUG,
368 'basic_settings',
369 array(
370 'title' => __( 'Logo', 'wp-parsely' ), // Passed for legend element.
371 'option_key' => $field_id,
372 'label_for' => $field_id,
373 'help_text' => $field_help,
374 )
375 );
376
377 // Track logged-in users.
378 $h = __( 'By default, the plugin will track the activity of users that are logged into this site. You can change this setting to only track the activity of anonymous visitors. Note: You will no longer see the Parse.ly tracking code on your site if you browse while logged in.', 'wp-parsely' );
379 add_settings_field(
380 'track_authenticated_users',
381 __( 'Track Logged-in Users', 'wp-parsely' ),
382 array( $this, 'print_binary_radio_tag' ),
383 Parsely::MENU_SLUG,
384 'basic_settings',
385 array(
386 'title' => __( 'Track Logged-in Users', 'wp-parsely' ), // Passed for legend element.
387 'option_key' => 'track_authenticated_users',
388 'help_text' => $h,
389 )
390 );
391
392 // Disable JavaScript.
393 $h = __( 'If you use a separate system for JavaScript tracking (Tealium / Segment / Google Tag Manager / other tag manager solution) you may want to use that instead of having the plugin load the tracker. <span style="color:#d63638">WARNING:</span> disabling this option will also disable the "Personalize Results" section of the recommended widget! We highly recommend leaving this option set to "No".', 'wp-parsely' );
394 add_settings_field(
395 'disable_javascript',
396 __( 'Disable JavaScript', 'wp-parsely' ),
397 array( $this, 'print_binary_radio_tag' ),
398 Parsely::MENU_SLUG,
399 'basic_settings',
400 array(
401 'title' => __( 'Disable JavaScript', 'wp-parsely' ), // Passed for legend element.
402 'option_key' => 'disable_javascript',
403 'help_text' => $h,
404 )
405 );
406
407 if ( defined( 'AMP__VERSION' ) ) {
408 // Disable AMP tracking.
409 $h = __( 'If you use a separate system for JavaScript tracking on AMP pages (Tealium / Segment / Google Tag Manager / other tag manager solution) you may want to use that instead of having the plugin load the tracker.', 'wp-parsely' );
410 add_settings_field(
411 'disable_amp',
412 __( 'Disable AMP Tracking', 'wp-parsely' ),
413 array( $this, 'print_binary_radio_tag' ),
414 Parsely::MENU_SLUG,
415 'basic_settings',
416 array(
417 'title' => __( 'Disable AMP Tracking', 'wp-parsely' ), // Passed for legend element.
418 'option_key' => 'disable_amp',
419 'help_text' => $h,
420 )
421 );
422 }
423 }
424
425 /**
426 * Registers section and settings for Requires Recrawl section.
427 *
428 * @since 3.2.0
429 */
430 private function initialize_requires_recrawl_section(): void {
431 add_settings_section(
432 'requires_recrawl_settings',
433 __( 'Requires Recrawl Settings', 'wp-parsely' ),
434 function (): void {
435 echo '<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 />';
436 printf(
437 /* translators: Mailto link */
438 esc_html__( 'Once you have changed a value and and saved, please contact %s to request a recrawl.', 'wp-parsely' ),
439 wp_kses_post( '<a href="mailto:support@parsely.com?subject=' . rawurlencode( 'Please reprocess ' . $this->parsely->get_api_key() ) . '">support@parsely.com</a>' )
440 );
441 },
442 Parsely::MENU_SLUG
443 );
444
445 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
446 $field_id = 'track_post_types_as';
447 $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' );
448 add_settings_field(
449 $field_id,
450 __( 'Track Post Types as', 'wp-parsely' ),
451 array( $this, 'print_track_post_types_table' ),
452 Parsely::MENU_SLUG,
453 'requires_recrawl_settings',
454 array(
455 'title' => __( 'Track Post Types as', 'wp-parsely' ),
456 'option_key' => $field_id,
457 'help_text' => $field_help,
458 )
459 );
460
461 // Metadata Format.
462 /* translators: 1: Opening anchor tag markup, 2: Documentation URL, 3: Opening anchor tag markup continued, 4: Closing anchor tag */
463 $h = __( 'Choose the metadata format for our crawlers to access. Most publishers are fine with <a href="https://www.parse.ly/help/integration/jsonld/">JSON-LD</a>, but if you prefer to use our proprietary metadata format then you can do so here.', 'wp-parsely' );
464 $field_id = 'meta_type';
465 $field_args = array(
466 'option_key' => $field_id,
467 'help_text' => $h,
468 // filter WordPress taxonomies under the hood that should not appear in dropdown.
469 'select_options' => array(
470 'json_ld' => 'json_ld',
471 'repeated_metas' => 'repeated_metas',
472 ),
473 'label_for' => Parsely::OPTIONS_KEY . "[$field_id]",
474 );
475 add_settings_field(
476 $field_id,
477 __( 'Metadata Format', 'wp-parsely' ),
478 array( $this, 'print_select_tag' ),
479 Parsely::MENU_SLUG,
480 'requires_recrawl_settings',
481 $field_args
482 );
483
484 // Content ID Prefix.
485 $h = __( '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' );
486 $field_id = 'content_id_prefix';
487 $field_args = array(
488 'option_key' => $field_id,
489 'optional_args' => array(
490 'placeholder' => 'WP-',
491 ),
492 'help_text' => $h,
493 'label_for' => $field_id,
494 );
495 add_settings_field(
496 $field_id,
497 __( 'Content ID Prefix', 'wp-parsely' ),
498 array( $this, 'print_text_tag' ),
499 Parsely::MENU_SLUG,
500 'requires_recrawl_settings',
501 $field_args
502 );
503
504 // Use top-level categories.
505 $h = __( 'The plugin will use the first category assigned to a post. With this option selected, if you post a story to News > National > Florida, the plugin will use the "News" for the section name in your dashboard instead of "Florida".', 'wp-parsely' );
506 add_settings_field(
507 'use_top_level_cats',
508 __( 'Use Top-Level Categories for Section', 'wp-parsely' ),
509 array( $this, 'print_binary_radio_tag' ),
510 Parsely::MENU_SLUG,
511 'requires_recrawl_settings',
512 array(
513 'title' => __( 'Use Top-Level Categories for Section', 'wp-parsely' ), // Passed for legend element.
514 'option_key' => 'use_top_level_cats',
515 'help_text' => $h,
516 )
517 );
518
519 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
520 $h = __( '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' );
521 $field_id = 'custom_taxonomy_section';
522 $field_args = array(
523 'option_key' => $field_id,
524 'help_text' => $h,
525 // filter WordPress taxonomies under the hood that should not appear in dropdown.
526 'select_options' => array_diff(
527 get_taxonomies(),
528 array(
529 'post_tag',
530 'nav_menu',
531 'author',
532 'link_category',
533 'post_format',
534 )
535 ),
536 'label_for' => Parsely::OPTIONS_KEY . "[$field_id]",
537 );
538 add_settings_field(
539 $field_id,
540 __( 'Use Custom Taxonomy for Section', 'wp-parsely' ),
541 array( $this, 'print_select_tag' ),
542 Parsely::MENU_SLUG,
543 'requires_recrawl_settings',
544 $field_args
545 );
546
547 // Use categories and custom taxonomies as tags.
548 $h = __( 'You can use this option to add all assigned categories and taxonomies to your tags. For example, if you had a post assigned to the categories: "Business/Tech", "Business/Social", your tags would include "Business/Tech" and "Business/Social" in addition to your other tags.', 'wp-parsely' );
549 add_settings_field(
550 'cats_as_tags',
551 __( 'Add Categories to Tags', 'wp-parsely' ),
552 array( $this, 'print_binary_radio_tag' ),
553 Parsely::MENU_SLUG,
554 'requires_recrawl_settings',
555 array(
556 'title' => __( 'Add Categories to Tags', 'wp-parsely' ), // Passed for legend element.
557 'option_key' => 'cats_as_tags',
558 'help_text' => $h,
559 )
560 );
561
562 // Lowercase all tags.
563 $h = __( 'By default, the plugin will use lowercase versions of your tags to correct for potential misspellings. You can change this setting to ensure that tag names are used verbatim.', 'wp-parsely' );
564 add_settings_field(
565 'lowercase_tags',
566 __( 'Lowercase All Tags', 'wp-parsely' ),
567 array( $this, 'print_binary_radio_tag' ),
568 Parsely::MENU_SLUG,
569 'requires_recrawl_settings',
570 array(
571 'title' => __( 'Lowercase All Tags', 'wp-parsely' ), // Passed for legend element.
572 'option_key' => 'lowercase_tags',
573 'help_text' => $h,
574 )
575 );
576
577 $h = __( 'The plugin uses <code>http</code> canonical URLs by default. If this needs to be forced to use <code>https</code>, set this option to true. Note: the default is fine for almost all publishers, it\'s unlikely you\'ll have to change this unless directed to do so by a Parse.ly support rep.', 'wp-parsely' );
578 add_settings_field(
579 'force_https_canonicals',
580 __( 'Force HTTPS Canonicals', 'wp-parsely' ),
581 array( $this, 'print_binary_radio_tag' ),
582 Parsely::MENU_SLUG,
583 'requires_recrawl_settings',
584 array(
585 'title' => __( 'Force HTTPS Canonicals', 'wp-parsely' ), // Passed for legend element.
586 'option_key' => 'force_https_canonicals',
587 'help_text' => $h,
588 )
589 );
590 }
591
592 /**
593 * Registers section and settings for Advanced section.
594 *
595 * @since 3.2.0
596 */
597 private function initialize_advanced_section(): void {
598 // These are Advanced Settings.
599 add_settings_section(
600 'advanced_settings',
601 __( 'Advanced Settings', 'wp-parsely' ),
602 '__return_null',
603 Parsely::MENU_SLUG
604 );
605
606 // Disable autotrack.
607 $h = __( 'The default behavior of the tracking code is to report an event as soon as the script has finished loading. This setting enables or disables that behavior. Only disable this if you plan to implement Dynamic Tracking yourself.', 'wp-parsely' );
608 add_settings_field(
609 'disable_autotrack',
610 __( 'Disable Autotracking', 'wp-parsely' ),
611 array( $this, 'print_binary_radio_tag' ),
612 Parsely::MENU_SLUG,
613 'advanced_settings',
614 array(
615 'title' => __( 'Disable Autotracking', 'wp-parsely' ), // Passed for legend element.
616 'option_key' => 'disable_autotrack',
617 'help_text' => $h,
618 )
619 );
620
621 // Clear metadata.
622 $h = __( 'Check this radio button and hit "Save Changes" to clear all metadata information for Parse.ly posts and re-send all metadata to Parse.ly.<br /><span style="color:#d63638">WARNING:</span> Do not do this unless explicitly instructed by Parse.ly Staff!', 'wp-parsely' );
623 add_settings_field(
624 'parsely_wipe_metadata_cache',
625 __( 'Wipe Parse.ly Metadata Info', 'wp-parsely' ),
626 array( $this, 'print_checkbox_tag' ),
627 Parsely::MENU_SLUG,
628 'advanced_settings',
629 array(
630 'option_key' => 'parsely_wipe_metadata_cache',
631 'help_text' => $h,
632 )
633 );
634 }
635
636 /**
637 * Prints out the description text, if there is any.
638 *
639 * @since 3.1.0
640 *
641 * @param array $args The arguments for the form field. May contain 'help_text'.
642 */
643 public function print_description_text( $args ): void {
644 echo isset( $args['help_text'] ) ? '<p class="description" id="' . esc_attr( $args['option_key'] ) . '-description">' . wp_kses_post( $args['help_text'] ) . '</p>' : '';
645 }
646
647 /**
648 * Prints out an input text tag.
649 *
650 * @param array $args The arguments for text tag.
651 */
652 public function print_text_tag( array $args ): void {
653 $options = $this->parsely->get_options();
654 $name = $args['option_key'];
655 $value = $options[ $name ] ?? '';
656 $optional_args = $args['optional_args'] ?? array();
657 $id = esc_attr( $name );
658 $name = Parsely::OPTIONS_KEY . "[$id]";
659 $value = esc_attr( $value );
660 $accepted_args = array( 'placeholder', 'required' );
661
662 echo sprintf( "<input type='text' name='%s' id='%s' value='%s'", esc_attr( $name ), esc_attr( $id ), esc_attr( $value ) );
663 if ( $args['help_text'] ) {
664 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
665 }
666 foreach ( $optional_args as $key => $val ) {
667 if ( \in_array( $key, $accepted_args, true ) ) {
668 echo ' ' . esc_attr( $key ) . '="' . esc_attr( $val ) . '"';
669 }
670 }
671 echo ' />';
672
673 $this->print_description_text( $args );
674 }
675
676 /**
677 * Prints a checkbox tag in the settings page.
678 *
679 * @param array $args Arguments to print to checkbox tag.
680 */
681 public function print_checkbox_tag( array $args ): void {
682 $options = $this->parsely->get_options();
683 $name = $args['option_key'];
684 $value = $options[ $name ];
685 $id = esc_attr( $name );
686 $name = Parsely::OPTIONS_KEY . "[$id]";
687
688 echo sprintf( "<input type='checkbox' name='%s' id='%s_true' value='true' ", esc_attr( $name ), esc_attr( $id ) );
689 if ( $args['help_text'] ) {
690 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
691 }
692 echo checked( true === $value, true, false );
693 echo sprintf( " /> <label for='%s_true'>%s</label>", esc_attr( $id ), esc_html__( 'Yes', 'wp-parsely' ) );
694
695 $this->print_description_text( $args );
696 }
697
698 /**
699 * Prints out the select tags
700 *
701 * @param array $args The arguments for the select dropdowns.
702 */
703 public function print_select_tag( array $args ): void {
704 $options = $this->parsely->get_options();
705 $name = $args['option_key'];
706 $select_options = $args['select_options'];
707 $selected = $options[ $name ] ?? null;
708 $id = esc_attr( $name );
709 $name = Parsely::OPTIONS_KEY . "[$id]";
710
711 echo sprintf( "<select name='%s' id='%s'", esc_attr( $name ), esc_attr( $name ) );
712 if ( $args['help_text'] ) {
713 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
714 }
715 echo '>';
716
717 foreach ( $select_options as $key => $val ) {
718 echo '<option value="' . esc_attr( $key ) . '" ';
719 echo selected( $selected, $key, false ) . '>';
720 echo esc_html( $val );
721 echo '</option>';
722 }
723 echo '</select>';
724
725 $this->print_description_text( $args );
726 }
727
728 /**
729 * Prints out the radio buttons.
730 *
731 * @param array $args The arguments for the radio buttons.
732 */
733 public function print_binary_radio_tag( array $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 ?>
740 <fieldset>
741 <legend class="screen-reader-text"><span><?php echo esc_html( $args['title'] ); ?></span></legend>
742 <p>
743 <label for="<?php echo esc_attr( "{$id}_true" ); ?>">
744 <input type="radio" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( "{$id}_true" ); ?>" value="true"<?php checked( $value ); ?> /><?php echo esc_html__( 'Yes', 'wp-parsely' ); ?>
745 </label>
746 <br />
747 <label for="<?php echo esc_attr( "{$id}_false" ); ?>">
748 <input type="radio" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( "{$id}_false" ); ?>" value="false"<?php checked( $value, false ); ?> /><?php echo esc_html__( 'No', 'wp-parsely' ); ?>
749 </label>
750 </p>
751 </fieldset>
752 <?php
753 $this->print_description_text( $args );
754 }
755
756 /**
757 * Prints out a "single-image browse control" which includes a text input to
758 * store image path and a button to browse for images.
759 *
760 * @param array $args The arguments for the control.
761 */
762 public function print_media_single_image( array $args ): void {
763 $key = $args['option_key'];
764 $input_value = $this->parsely->get_options()[ $key ];
765 $input_name = Parsely::OPTIONS_KEY . "[$key]";
766 $button_text = __( 'Browse', 'wp-parsely' );
767 ?>
768
769 <fieldset class="media-single-image" id="media-single-image-<?php echo esc_attr( $key ); ?>">
770 <legend class="screen-reader-text"><span><?php echo esc_html( $args['title'] ); ?></span></legend>
771 <input class="file-path" type="text" name="<?php echo esc_attr( $input_name ); ?>" id="logo" value="<?php echo esc_attr( $input_value ); ?>" />
772 <button data-option="<?php echo esc_attr( $key ); ?>" class="browse button" type="button"><?php echo esc_html( $button_text ); ?></button>
773 </fieldset>
774
775 <?php
776 $this->print_description_text( $args );
777 }
778
779 /**
780 * Prints out the post tracking options table.
781 *
782 * @since 3.2.0
783 *
784 * @param array<string, string> $args The arguments used in the output HTML elements.
785 */
786 public function print_track_post_types_table( array $args ): void {
787 $option_key = esc_attr( $args['option_key'] );
788 $post_types = get_post_types( array( 'public' => true ) );
789 $values = $this->get_tracking_values_for_display();
790 ?>
791 <fieldset>
792 <legend class="screen-reader-text"><span><?php echo esc_html( $args['title'] ); ?></span></legend>
793 <table class="form-table widefat striped" id="track-post-types">
794 <caption class="screen-reader-text"><?php echo esc_html( $args['title'] ); ?></caption>
795 <thead>
796 <tr>
797 <th scope="col"><?php echo esc_html__( 'Post Type', 'wp-parsely' ); ?></th>
798 <th id="track-post-types--post" scope="col"><?php echo esc_html__( 'Track as Post', 'wp-parsely' ); ?></th>
799 <th id="track-post-types--page" scope="col"><?php echo esc_html__( 'Track as Page', 'wp-parsely' ); ?></th>
800 <th id="track-post-types--none" scope="col"><?php echo esc_html__( 'Do not track', 'wp-parsely' ); ?></th>
801 </tr>
802 </thead>
803 <tbody>
804 <?php
805 foreach ( $post_types as $post_type ) {
806 $group_name = "parsely[{$option_key}][{$post_type}]";
807 $id_post = "{$option_key}_{$post_type}_post";
808 $id_page = "{$option_key}_{$post_type}_page";
809 $id_none = "{$option_key}_{$post_type}_none";
810 $value = $values[ $post_type ] ?? 'none';
811 ?>
812 <tr>
813 <th scope="row"><?php echo esc_html( $post_type ); ?></th>
814 <td>
815 <label aria-labelledby="track-post-types--post" for="<?php echo esc_attr( $id_post ); ?>">
816 <input id="<?php echo esc_attr( $id_post ); ?>" name="<?php echo esc_attr( $group_name ); ?>" type="radio" value="post" <?php checked( $value, 'post' ); ?> />
817 </label>
818 </td>
819 <td>
820 <label aria-labelledby="track-post-types--page" for="<?php echo esc_attr( $id_page ); ?>">
821 <input id="<?php echo esc_attr( $id_page ); ?>" name="<?php echo esc_attr( $group_name ); ?>" type="radio" value="page" <?php checked( $value, 'page' ); ?> />
822 </label>
823 </td>
824 <td>
825 <label aria-labelledby="track-post-types--none" for="<?php echo esc_attr( $id_none ); ?>">
826 <input id="<?php echo esc_attr( $id_none ); ?>" name="<?php echo esc_attr( $group_name ); ?>" type="radio" value="none" <?php checked( $value, 'none' ); ?> />
827 </label>
828 </td>
829 </tr>
830 <?php } ?>
831 </tbody>
832 </table>
833 </fieldset>
834 <?php
835 $this->print_description_text( $args );
836 }
837
838 /**
839 * Returns the custom post type tracking values in a format that is easily
840 * consumable by the print_track_post_types_table() function.
841 *
842 * @since 3.2.0
843 *
844 * @return array<string, string> Key-value pairs with post type and their 'track as' value.
845 */
846 public function get_tracking_values_for_display(): array {
847 $options = $this->parsely->get_options();
848 $types = array( 'post', 'page' );
849 $result = array();
850
851 foreach ( $types as $type ) {
852 $array_key = "track_{$type}_types";
853 if ( array_key_exists( $array_key, $options ) ) {
854 foreach ( $options[ $array_key ] as $post_type ) {
855 $result[ $post_type ] = $type;
856 }
857 }
858 }
859
860 return $result;
861 }
862
863 /**
864 * Validates the options provided by the user.
865 *
866 * @param array $input Options from the settings page.
867 * @return array List of validated input settings.
868 */
869 public function validate_options( array $input ): array {
870 $options = $this->parsely->get_options();
871
872 if ( empty( $input['apikey'] ) ) {
873 add_settings_error(
874 Parsely::OPTIONS_KEY,
875 'apikey',
876 __( 'Please specify the Site ID', 'wp-parsely' )
877 );
878 } else {
879 $api_key = $this->sanitize_api_key( $input['apikey'] );
880 if ( false === $this->validate_api_key( $api_key ) ) {
881 add_settings_error(
882 Parsely::OPTIONS_KEY,
883 'apikey',
884 __( 'Your Parse.ly Site ID looks incorrect, it should look like "example.com".', 'wp-parsely' )
885 );
886 } else {
887 $input['apikey'] = $api_key;
888 }
889 }
890
891 $input['api_secret'] = sanitize_text_field( $input['api_secret'] );
892
893 if ( ! empty( $input['metadata_secret'] ) ) {
894 if ( strlen( $input['metadata_secret'] ) !== 10 ) {
895 add_settings_error(
896 Parsely::OPTIONS_KEY,
897 'metadata_secret',
898 __( 'Metadata secret is incorrect. Please contact Parse.ly support!', 'wp-parsely' )
899 );
900 } elseif ( isset( $input['parsely_wipe_metadata_cache'] ) && 'true' === $input['parsely_wipe_metadata_cache'] ) {
901 delete_post_meta_by_key( 'parsely_metadata_last_updated' );
902
903 wp_schedule_event( time() + 100, 'everytenminutes', 'parsely_bulk_metas_update' );
904 $input['parsely_wipe_metadata_cache'] = false;
905 }
906 }
907
908 if ( empty( $input['logo'] ) ) {
909 $input['logo'] = self::get_logo_default();
910 }
911
912 // Validate 'Track post type as'.
913 $this->validate_options_post_type_tracking( $input );
914
915 // Track authenticated users.
916 if ( 'true' !== $input['track_authenticated_users'] && 'false' !== $input['track_authenticated_users'] ) {
917 add_settings_error(
918 Parsely::OPTIONS_KEY,
919 'track_authenticated_users',
920 __( 'Value passed for track_authenticated_users must be either "true" or "false".', 'wp-parsely' )
921 );
922 } else {
923 $input['track_authenticated_users'] = 'true' === $input['track_authenticated_users'];
924 }
925
926 if ( 'true' !== $input['disable_javascript'] && 'false' !== $input['disable_javascript'] ) {
927 add_settings_error(
928 Parsely::OPTIONS_KEY,
929 'disable_javascript',
930 __( 'Value passed for disable_javascript must be either "Yes" or "No".', 'wp-parsely' )
931 );
932 } else {
933 $input['disable_javascript'] = 'true' === $input['disable_javascript'];
934 }
935
936 if ( ! isset( $input['disable_autotrack'] ) ) {
937 $input['disable_autotrack'] = $options['disable_autotrack'];
938 } elseif ( 'true' !== $input['disable_autotrack'] && 'false' !== $input['disable_autotrack'] ) {
939 add_settings_error(
940 Parsely::OPTIONS_KEY,
941 'disable_autotrack',
942 __( 'Value passed for disable_autotrack must be either "Yes" or "No".', 'wp-parsely' )
943 );
944 } else {
945 $input['disable_autotrack'] = 'true' === $input['disable_autotrack'];
946 }
947
948 // Allow for Disable AMP setting to be conditionally included on the page.
949 // If it's not shown, then set the value as what was previously saved.
950 if ( ! isset( $input['disable_amp'] ) ) {
951 $input['disable_amp'] = 'true';
952 if ( false === $options['disable_amp'] ) {
953 $input['disable_amp'] = 'false';
954 }
955 }
956
957 if ( 'true' !== $input['disable_amp'] && 'false' !== $input['disable_amp'] ) {
958 add_settings_error(
959 Parsely::OPTIONS_KEY,
960 'disable_amp',
961 __( 'Value passed for disable_amp must be either "true" or "false".', 'wp-parsely' )
962 );
963 } else {
964 $input['disable_amp'] = 'true' === $input['disable_amp'];
965 }
966
967 $input['api_secret'] = sanitize_text_field( $input['api_secret'] );
968
969 // Custom taxonomy as section.
970 if ( ! isset( $input['meta_type'] ) ) {
971 $input['meta_type'] = $options['meta_type'];
972 } else {
973 $input['meta_type'] = sanitize_text_field( $input['meta_type'] );
974 }
975
976 // Content ID prefix.
977 if ( ! isset( $input['content_id_prefix'] ) ) {
978 $input['content_id_prefix'] = $options['content_id_prefix'];
979 } else {
980 $input['content_id_prefix'] = sanitize_text_field( $input['content_id_prefix'] );
981 }
982
983 // Allow for Top-level categories setting to be conditionally included on the page.
984 // If it's not shown, then set the value as what was previously saved.
985 if ( ! isset( $input['use_top_level_cats'] ) ) {
986 $input['use_top_level_cats'] = 'true';
987 if ( false === $options['use_top_level_cats'] ) {
988 $input['use_top_level_cats'] = 'false';
989 }
990 }
991
992 // Top-level categories.
993 if ( 'true' !== $input['use_top_level_cats'] && 'false' !== $input['use_top_level_cats'] ) {
994 add_settings_error(
995 Parsely::OPTIONS_KEY,
996 'use_top_level_cats',
997 __( 'Value passed for use_top_level_cats must be either "true" or "false".', 'wp-parsely' )
998 );
999 } else {
1000 $input['use_top_level_cats'] = 'true' === $input['use_top_level_cats'];
1001 }
1002
1003 // Custom taxonomy as section.
1004 if ( ! isset( $input['custom_taxonomy_section'] ) ) {
1005 $input['custom_taxonomy_section'] = $options['custom_taxonomy_section'];
1006 } else {
1007 $input['custom_taxonomy_section'] = sanitize_text_field( $input['custom_taxonomy_section'] );
1008 }
1009
1010 // Allow for Categories as Tags setting to be conditionally included on the page.
1011 // If it's not shown, then set the value as what was previously saved.
1012 if ( ! isset( $input['cats_as_tags'] ) ) {
1013 $input['cats_as_tags'] = 'true';
1014 if ( false === $options['cats_as_tags'] ) {
1015 $input['cats_as_tags'] = 'false';
1016 }
1017 }
1018
1019 // Child categories as tags.
1020 if ( 'true' !== $input['cats_as_tags'] && 'false' !== $input['cats_as_tags'] ) {
1021 add_settings_error(
1022 Parsely::OPTIONS_KEY,
1023 'cats_as_tags',
1024 __( 'Value passed for cats_as_tags must be either "true" or "false".', 'wp-parsely' )
1025 );
1026 } else {
1027 $input['cats_as_tags'] = 'true' === $input['cats_as_tags'];
1028 }
1029
1030 // Allow for Lowercase Tags setting to be conditionally included on the page.
1031 // If it's not shown, then set the value as what was previously saved.
1032 if ( ! isset( $input['lowercase_tags'] ) ) {
1033 $input['lowercase_tags'] = 'true';
1034 if ( false === $options['lowercase_tags'] ) {
1035 $input['lowercase_tags'] = 'false';
1036 }
1037 }
1038
1039 // Lowercase tags.
1040 if ( 'true' !== $input['lowercase_tags'] && 'false' !== $input['lowercase_tags'] ) {
1041 add_settings_error(
1042 Parsely::OPTIONS_KEY,
1043 'lowercase_tags',
1044 __( 'Value passed for lowercase_tags must be either "true" or "false".', 'wp-parsely' )
1045 );
1046 } else {
1047 $input['lowercase_tags'] = 'true' === $input['lowercase_tags'];
1048 }
1049
1050 // Allow for Force HTTPS Canonical setting to be conditionally included on the page.
1051 // If it's not shown, then set the value as what was previously saved.
1052 if ( ! isset( $input['force_https_canonicals'] ) ) {
1053 $input['force_https_canonicals'] = 'true';
1054 if ( false === $options['force_https_canonicals'] ) {
1055 $input['force_https_canonicals'] = 'false';
1056 }
1057 }
1058
1059 if ( 'true' !== $input['force_https_canonicals'] && 'false' !== $input['force_https_canonicals'] ) {
1060 add_settings_error(
1061 Parsely::OPTIONS_KEY,
1062 'force_https_canonicals',
1063 __( 'Value passed for force_https_canonicals must be either "true" or "false".', 'wp-parsely' )
1064 );
1065 } else {
1066 $input['force_https_canonicals'] = 'true' === $input['force_https_canonicals'];
1067 }
1068
1069 return $input;
1070 }
1071
1072 /**
1073 * Validates the passed API key.
1074 *
1075 * Accepts a www prefix and up to 3 periods.
1076 *
1077 * Valid examples: 'test.com', 'www.test.com', 'subdomain.test.com',
1078 * 'www.subdomain.test.com', 'subdomain.subdomain.test.com'.
1079 *
1080 * Invalid examples: 'test', 'test.com/', 'http://test.com', 'https://test.com',
1081 * 'www.subdomain.subdomain.test.com'.
1082 *
1083 * @since 3.3.0
1084 *
1085 * @param string $api_key The API key to be validated.
1086 * @return bool
1087 */
1088 private function validate_api_key( string $api_key ): bool {
1089 $key_format = '/^((\w+)\.)?(([\w-]+)?)(\.[\w-]+){1,2}$/';
1090
1091 return 1 === preg_match( $key_format, $api_key );
1092 }
1093
1094 /**
1095 * Sanitizes the passed API key.
1096 *
1097 * @since 3.3.0
1098 *
1099 * @param string $api_key The API key to be sanitized.
1100 * @return string
1101 */
1102 private function sanitize_api_key( string $api_key ): string {
1103 return strtolower( sanitize_text_field( $api_key ) );
1104 }
1105
1106 /**
1107 * Receives the $input array from the validate_options() function and
1108 * validate post tracking options.
1109 *
1110 * This function will mutate the $input array.
1111 *
1112 * @since 3.2.0
1113 *
1114 * @param array $input Array passed to validate_options() function.
1115 */
1116 private function validate_options_post_type_tracking( array &$input ): void {
1117 $options = $this->parsely->get_options();
1118 $posts = 'track_post_types';
1119 $pages = 'track_page_types';
1120 $track_as = 'track_post_types_as';
1121 $input[ $posts ] = $options[ $posts ];
1122 $input[ $pages ] = $options[ $pages ];
1123
1124 if ( isset( $input[ $track_as ] ) && is_array( $input[ $track_as ] ) && 0 < count( $input[ $track_as ] ) ) {
1125 $post_types = get_post_types( array( 'public' => true ) );
1126 $temp_posts = array();
1127 $temp_pages = array();
1128
1129 // Create temporary Post and Page arrays, disallowing non-existent post types.
1130 foreach ( $input[ $track_as ] as $key => $value ) {
1131 if ( false === in_array( $key, $post_types, true ) ) {
1132 continue;
1133 }
1134
1135 if ( 'post' === $value ) {
1136 $temp_posts[] = $key;
1137 } elseif ( 'page' === $value ) {
1138 $temp_pages[] = $key;
1139 }
1140 }
1141
1142 // Cleanup and sanitized values assignment.
1143 $input [ $posts ] = self::sanitize_option_array( $temp_posts );
1144 $input [ $pages ] = self::sanitize_option_array( $temp_pages );
1145 }
1146
1147 if ( isset( $input[ $track_as ] ) ) {
1148 unset( $input[ $track_as ] );
1149 }
1150 }
1151
1152 /**
1153 * Returns default logo if one can be found.
1154 *
1155 * @return string
1156 */
1157 private static function get_logo_default(): string {
1158 $custom_logo_id = get_theme_mod( 'custom_logo' );
1159 if ( $custom_logo_id ) {
1160 $logo_attrs = wp_get_attachment_image_src( $custom_logo_id, 'full' );
1161 if ( $logo_attrs ) {
1162 return $logo_attrs[0];
1163 }
1164 }
1165
1166 // get_site_icon_url returns an empty string if one isn't found,
1167 // which is what we want to use as the default anyway.
1168 return get_site_icon_url();
1169 }
1170
1171 /**
1172 * Sanitizes all elements in an option array.
1173 *
1174 * @param array $array Array of options to be sanitized.
1175 * @return array
1176 */
1177 private static function sanitize_option_array( array $array ): array {
1178 $new_array = $array;
1179 foreach ( $array as $key => $val ) {
1180 $new_array[ $key ] = sanitize_text_field( $val );
1181 }
1182 return $new_array;
1183 }
1184 }
1185