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

1,236 lines 41.5 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 // Site ID.
306 $field_id = 'apikey';
307 $field_args = array(
308 'option_key' => $field_id,
309 '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' ),
310 'label_for' => $field_id,
311 'optional_args' => array(
312 'required' => 'required',
313 'placeholder' => 'mydomain.com',
314 ),
315
316 );
317 add_settings_field(
318 $field_id,
319 __( 'Parse.ly Site ID <em>(required)</em>', 'wp-parsely' ),
320 array( $this, 'print_text_tag' ),
321 Parsely::MENU_SLUG,
322 'basic_settings',
323 $field_args
324 );
325
326 // API Secret.
327 $field_id = 'api_secret';
328 $field_args = array(
329 'option_key' => $field_id,
330 'help_text' => __( 'Your API secret is your secret code to <a href="https://www.parse.ly/help/api/analytics/">access our API</a>. It can be found at <code>dash.parsely.com/<var>yoursitedomain</var>/settings/api</code> (replace <var>yoursitedomain</var> with your domain name, e.g. <samp>mydomain.com</samp>).<br />If you haven\'t purchased access to the API and would like to do so, email your account manager or <a href="mailto:support@parsely.com">support@parsely.com</a>.', 'wp-parsely' ),
331 'label_for' => $field_id,
332 );
333 add_settings_field(
334 $field_id,
335 __( 'Parse.ly API Secret', 'wp-parsely' ),
336 array( $this, 'print_text_tag' ),
337 Parsely::MENU_SLUG,
338 'basic_settings',
339 $field_args
340 );
341
342 // Metadata Secret.
343 $field_id = 'metadata_secret';
344 $field_args = array(
345 'option_key' => $field_id,
346 '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' ),
347 'label_for' => $field_id,
348 );
349 add_settings_field(
350 $field_id,
351 __( 'Parse.ly Metadata Secret', 'wp-parsely' ),
352 array( $this, 'print_text_tag' ),
353 Parsely::MENU_SLUG,
354 'basic_settings',
355 $field_args
356 );
357
358 // Metadata Format.
359 $field_id = 'meta_type';
360 $field_args = array(
361 'option_key' => $field_id,
362 'help_text' => __( 'Choose the metadata format for our crawlers to access. Most publishers are fine with <a href="https://www.parse.ly/help/integration/jsonld/">JSON-LD</a>, but if you prefer to use our proprietary metadata format then you can do so here.', 'wp-parsely' ),
363 'radio_options' => array(
364 'json_ld' => 'json_ld',
365 'repeated_metas' => 'repeated_metas',
366 ),
367 'label_for' => Parsely::OPTIONS_KEY . "[$field_id]",
368 'filter' => 'wp_parsely_metadata',
369 );
370 add_settings_field(
371 $field_id,
372 __( 'Metadata Format', 'wp-parsely' ),
373 array( $this, 'print_radio_tags' ),
374 Parsely::MENU_SLUG,
375 'basic_settings',
376 $field_args
377 );
378
379 // Logo.
380 $field_help = __( 'Here you can specify your logo\'s URL by using the "Browse" button or typing the URL manually.', 'wp-parsely' );
381 $field_id = 'logo';
382 add_settings_field(
383 $field_id,
384 __( 'Logo', 'wp-parsely' ),
385 array( $this, 'print_media_single_image' ),
386 Parsely::MENU_SLUG,
387 'basic_settings',
388 array(
389 'title' => __( 'Logo', 'wp-parsely' ), // Passed for legend element.
390 'option_key' => $field_id,
391 'label_for' => $field_id,
392 'help_text' => $field_help,
393 )
394 );
395
396 // Track logged-in users.
397 add_settings_field(
398 'track_authenticated_users',
399 __( 'Track Logged-in Users', 'wp-parsely' ),
400 array( $this, 'print_radio_tags' ),
401 Parsely::MENU_SLUG,
402 'basic_settings',
403 array(
404 'title' => __( 'Track Logged-in Users', 'wp-parsely' ), // Passed for legend element.
405 'option_key' => 'track_authenticated_users',
406 'radio_options' => array(
407 'true' => __( 'Yes, track logged-in users.', 'wp-parsely' ),
408 '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' ),
409 ),
410 'help_text' => (
411 is_multisite() ?
412 __( ' Note: For WordPress multisite, a user must be logged-in to the current site to be considered logged-in.', 'wp-parsely' ) :
413 null
414 ),
415 )
416 );
417
418 // Disable JavaScript.
419 add_settings_field(
420 'disable_javascript',
421 __( 'Disable JavaScript', 'wp-parsely' ),
422 array( $this, 'print_radio_tags' ),
423 Parsely::MENU_SLUG,
424 'basic_settings',
425 array(
426 'title' => __( 'Disable JavaScript', 'wp-parsely' ), // Passed for legend element.
427 'option_key' => 'disable_javascript',
428 'radio_options' => array(
429 'true' => __( 'Yes, disable JavaScript tracking. I want to use a separate system for tracking instead of the Parse.ly plugin.', 'wp-parsely' ),
430 'false' => __( 'No, do not disable JavaScript tracking. I want to the Parse.ly plugin to load the tracker.', 'wp-parsely' ),
431 ),
432 'help_text' => __( '<span style="color:#d63638">WARNING:</span> We highly recommend choosing "No." Disabling the JavaScript tracker will also disable the "Personalize Results" section of the recommendation widget.', 'wp-parsely' ),
433 'filter' => 'wp_parsely_load_js_tracker',
434 )
435 );
436
437 if ( defined( 'AMP__VERSION' ) ) {
438 // Disable AMP tracking.
439 add_settings_field(
440 'disable_amp',
441 __( 'Disable AMP Tracking', 'wp-parsely' ),
442 array( $this, 'print_radio_tags' ),
443 Parsely::MENU_SLUG,
444 'basic_settings',
445 array(
446 'title' => __( 'Disable AMP Tracking', 'wp-parsely' ), // Passed for legend element.
447 'option_key' => 'disable_amp',
448 'radio_options' => array(
449 'true' => __( 'Yes, disable Parse.ly tracking on AMP pages. I use a different system for JavaScript tracking on AMP pages.', 'wp-parsely' ),
450 'false' => __( 'No, do not disable Parse.ly tracking on AMP pages.', 'wp-parsely' ),
451 ),
452 )
453 );
454 }
455 }
456
457 /**
458 * Registers section and settings for Requires Recrawl section.
459 *
460 * @since 3.2.0
461 */
462 private function initialize_requires_recrawl_section(): void {
463 add_settings_section(
464 'requires_recrawl_settings',
465 __( 'Requires Recrawl Settings', 'wp-parsely' ),
466 function (): void {
467 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 />';
468 printf(
469 /* translators: Mailto link */
470 esc_html__( 'Once you have changed a value and and saved, please contact %s to request a recrawl.', 'wp-parsely' ),
471 wp_kses_post( '<a href="mailto:support@parsely.com?subject=' . rawurlencode( 'Please reprocess ' . $this->parsely->get_api_key() ) . '">support@parsely.com</a>' )
472 );
473 },
474 Parsely::MENU_SLUG
475 );
476
477 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
478 $field_id = 'track_post_types_as';
479 $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' );
480 add_settings_field(
481 $field_id,
482 __( 'Track Post Types as', 'wp-parsely' ),
483 array( $this, 'print_track_post_types_table' ),
484 Parsely::MENU_SLUG,
485 'requires_recrawl_settings',
486 array(
487 'title' => __( 'Track Post Types as', 'wp-parsely' ),
488 'option_key' => $field_id,
489 'help_text' => $field_help,
490 'filter' => 'wp_parsely_trackable_statuses',
491 )
492 );
493
494 // Content ID Prefix.
495 $field_id = 'content_id_prefix';
496 $field_args = array(
497 'option_key' => $field_id,
498 'optional_args' => array(
499 'placeholder' => 'WP-',
500 ),
501 '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' ),
502 'label_for' => $field_id,
503 );
504 add_settings_field(
505 $field_id,
506 __( 'Content ID Prefix', 'wp-parsely' ),
507 array( $this, 'print_text_tag' ),
508 Parsely::MENU_SLUG,
509 'requires_recrawl_settings',
510 $field_args
511 );
512
513 // Use top-level categories.
514 add_settings_field(
515 'use_top_level_cats',
516 __( 'Use Top-Level Categories for Section', 'wp-parsely' ),
517 array( $this, 'print_radio_tags' ),
518 Parsely::MENU_SLUG,
519 'requires_recrawl_settings',
520 array(
521 'title' => __( 'Use Top-Level Categories for Section', 'wp-parsely' ), // Passed for legend element.
522 'option_key' => 'use_top_level_cats',
523 'radio_options' => array(
524 'true' => __( 'Yes, use the first category assigned to a post as the section name.', 'wp-parsely' ),
525 'false' => __( 'No, do not use the first category assigned to a post as the section name.', 'wp-parsely' ),
526 ),
527 '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' ),
528 )
529 );
530
531 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
532 $field_id = 'custom_taxonomy_section';
533 $field_args = array(
534 'option_key' => $field_id,
535 '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' ),
536 // filter WordPress taxonomies under the hood that should not appear in dropdown.
537 'select_options' => array_diff(
538 get_taxonomies(),
539 array(
540 'post_tag',
541 'nav_menu',
542 'author',
543 'link_category',
544 'post_format',
545 )
546 ),
547 'label_for' => Parsely::OPTIONS_KEY . "[$field_id]",
548 );
549 add_settings_field(
550 $field_id,
551 __( 'Use Custom Taxonomy for Section', 'wp-parsely' ),
552 array( $this, 'print_select_tag' ),
553 Parsely::MENU_SLUG,
554 'requires_recrawl_settings',
555 $field_args
556 );
557
558 // Use categories and custom taxonomies as tags.
559 add_settings_field(
560 'cats_as_tags',
561 __( 'Add Categories to Tags', 'wp-parsely' ),
562 array( $this, 'print_radio_tags' ),
563 Parsely::MENU_SLUG,
564 'requires_recrawl_settings',
565 array(
566 'title' => __( 'Add Categories to Tags', 'wp-parsely' ), // Passed for legend element.
567 'option_key' => 'cats_as_tags',
568 'radio_options' => array(
569 'true' => __( 'Yes, add all assigned categories and taxonomies to my tags.', 'wp-parsely' ),
570 'false' => __( 'No, do not add all assigned categories and taxonomies to my tags.', 'wp-parsely' ),
571 ),
572 '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' ),
573 )
574 );
575
576 // Lowercase all tags.
577 add_settings_field(
578 'lowercase_tags',
579 __( 'Lowercase All Tags', 'wp-parsely' ),
580 array( $this, 'print_radio_tags' ),
581 Parsely::MENU_SLUG,
582 'requires_recrawl_settings',
583 array(
584 'title' => __( 'Lowercase All Tags', 'wp-parsely' ), // Passed for legend element.
585 'option_key' => 'lowercase_tags',
586 'radio_options' => array(
587 'true' => __( 'Yes, use lowercase versions of my tags to correct for potential misspellings.', 'wp-parsely' ),
588 'false' => __( 'No, do not use lowercase versions of my tags to correct for potential misspellings.', 'wp-parsely' ),
589 ),
590 )
591 );
592
593 add_settings_field(
594 'force_https_canonicals',
595 __( 'Force HTTPS Canonicals', 'wp-parsely' ),
596 array( $this, 'print_radio_tags' ),
597 Parsely::MENU_SLUG,
598 'requires_recrawl_settings',
599 array(
600 'title' => __( 'Force HTTPS Canonicals', 'wp-parsely' ), // Passed for legend element.
601 'option_key' => 'force_https_canonicals',
602 'radio_options' => array(
603 'true' => __( 'Yes, force <code>https</code> canonical URLs by default.', 'wp-parsely' ),
604 'false' => __( 'No, I want to use <code>http</code>.', 'wp-parsely' ),
605 ),
606 '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' ),
607 )
608 );
609 }
610
611 /**
612 * Registers section and settings for Advanced section.
613 *
614 * @since 3.2.0
615 */
616 private function initialize_advanced_section(): void {
617 // These are Advanced Settings.
618 add_settings_section(
619 'advanced_settings',
620 __( 'Advanced Settings', 'wp-parsely' ),
621 '__return_null',
622 Parsely::MENU_SLUG
623 );
624
625 // Disable autotrack.
626 add_settings_field(
627 'disable_autotrack',
628 __( 'Disable Autotracking', 'wp-parsely' ),
629 array( $this, 'print_radio_tags' ),
630 Parsely::MENU_SLUG,
631 'advanced_settings',
632 array(
633 'title' => __( 'Disable Autotracking', 'wp-parsely' ), // Passed for legend element.
634 'option_key' => 'disable_autotrack',
635 'radio_options' => array(
636 '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' ),
637 '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' ),
638 ),
639 )
640 );
641
642 // Clear metadata.
643 add_settings_field(
644 'parsely_wipe_metadata_cache',
645 __( 'Wipe Parse.ly Metadata Info', 'wp-parsely' ),
646 array( $this, 'print_checkbox_tag' ),
647 Parsely::MENU_SLUG,
648 'advanced_settings',
649 array(
650 'option_key' => 'parsely_wipe_metadata_cache',
651 'yes_text' => __( 'Yes, clear all metadata information for Parse.ly posts and re-send all metadata to Parse.ly.', 'wp-parsely' ),
652 'help_text' => __( '<span style="color:#d63638">WARNING:</span> Do not do this unless explicitly instructed by Parse.ly Staff!', 'wp-parsely' ),
653 )
654 );
655 }
656
657 /**
658 * Prints out a warning if the filter for the setting is defined, if any.
659 *
660 * @since 3.4.0
661 *
662 * @param array $args The arguments for the form field. May contain 'filter'.
663 */
664 private function print_filter_text( array $args ): void {
665 if ( isset( $args['filter'] ) && has_filter( $args['filter'] ) ) {
666 echo '<p>';
667 echo '<b><code>' . esc_html( $args['filter'] ) . '</code>' . esc_html__( 'filter hook is in use!', 'wp-parsely' ) . '</b> ';
668 echo esc_html__( 'A callback is attached to the filter hook that might interfere and override this setting.', 'wp-parsely' );
669 echo '</p>';
670 }
671 }
672
673 /**
674 * Prints out the description text, if there is any.
675 *
676 * @since 3.1.0
677 *
678 * @param array $args The arguments for the form field. May contain 'help_text'.
679 */
680 private function print_description_text( array $args ): void {
681 echo isset( $args['help_text'] ) ? '<p class="description" id="' . esc_attr( $args['option_key'] ) . '-description">' . wp_kses_post( $args['help_text'] ) . '</p>' : '';
682 }
683
684 /**
685 * Prints out an input text tag.
686 *
687 * @param array $args The arguments for text tag.
688 */
689 public function print_text_tag( array $args ): void {
690 $options = $this->parsely->get_options();
691 $name = $args['option_key'];
692 $value = $options[ $name ] ?? '';
693 $optional_args = $args['optional_args'] ?? array();
694 $id = esc_attr( $name );
695 $name = Parsely::OPTIONS_KEY . "[$id]";
696 $value = esc_attr( $value );
697 $accepted_args = array( 'placeholder', 'required' );
698
699 echo sprintf( "<input type='text' name='%s' id='%s' value='%s'", esc_attr( $name ), esc_attr( $id ), esc_attr( $value ) );
700 if ( $args['help_text'] ) {
701 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
702 }
703 foreach ( $optional_args as $key => $val ) {
704 if ( \in_array( $key, $accepted_args, true ) ) {
705 echo ' ' . esc_attr( $key ) . '="' . esc_attr( $val ) . '"';
706 }
707 }
708 echo ' />';
709
710 $this->print_description_text( $args );
711 }
712
713 /**
714 * Prints a checkbox tag in the settings page.
715 *
716 * @param array $args Arguments to print to checkbox tag.
717 */
718 public function print_checkbox_tag( array $args ): void {
719 $options = $this->parsely->get_options();
720 $name = $args['option_key'];
721 $value = $options[ $name ];
722 $id = esc_attr( $name );
723 $name = Parsely::OPTIONS_KEY . "[$id]";
724 $yes_text = $args['yes_text'];
725
726 echo sprintf( "<input type='checkbox' name='%s' id='%s_true' value='true' ", esc_attr( $name ), esc_attr( $id ) );
727 if ( $args['help_text'] ) {
728 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
729 }
730 echo checked( true === $value, true, false );
731 echo sprintf( " /> <label for='%s_true'>%s</label>", esc_attr( $id ), esc_html( $yes_text ) );
732
733 $this->print_description_text( $args );
734 }
735
736 /**
737 * Prints out the select tags
738 *
739 * @param array $args The arguments for the select dropdowns.
740 */
741 public function print_select_tag( array $args ): void {
742 $options = $this->parsely->get_options();
743 $name = $args['option_key'];
744 $select_options = $args['select_options'];
745 $selected = $options[ $name ] ?? null;
746 $id = esc_attr( $name );
747 $name = Parsely::OPTIONS_KEY . "[$id]";
748
749 echo sprintf( "<select name='%s' id='%s'", esc_attr( $name ), esc_attr( $name ) );
750 if ( $args['help_text'] ) {
751 echo ' aria-describedby="' . esc_attr( $id ) . '-description"';
752 }
753 echo '>';
754
755 foreach ( $select_options as $key => $val ) {
756 echo '<option value="' . esc_attr( $key ) . '" ';
757 echo selected( $selected, $key, false ) . '>';
758 echo esc_html( $val );
759 echo '</option>';
760 }
761 echo '</select>';
762
763 $this->print_filter_text( $args );
764 $this->print_description_text( $args );
765 }
766
767 /**
768 * Prints the radio buttons.
769 *
770 * @param array $args The arguments for the radio buttons.
771 */
772 public function print_radio_tags( array $args ): void {
773 $name = $args['option_key'];
774 $id = esc_attr( $name );
775 $selected = $this->parsely->get_options()[ $name ];
776
777 if ( is_bool( $selected ) ) {
778 // Converting boolean to string so that we have string type keys for all cases.
779 $selected = $selected ? 'true' : 'false';
780 }
781
782 ?>
783 <fieldset>
784 <legend class="screen-reader-text"><span><?php echo esc_html( $args['title'] ); ?></span></legend>
785 <p>
786 <?php foreach ( $args['radio_options'] as $value => $text ) { ?>
787 <label for="<?php echo esc_attr( "{$id}_{$value}" ); ?>">
788 <input
789 type="radio"
790 name="<?php echo esc_attr( Parsely::OPTIONS_KEY . "[$id]" ); ?>"
791 id="<?php echo esc_attr( "{$id}_{$value}" ); ?>"
792 value="<?php echo esc_attr( $value ); ?>"
793 <?php checked( $selected, $value ); ?>
794 />
795 <?php echo wp_kses_post( $text ); ?>
796 </label>
797 <br />
798 <?php } ?>
799 </p>
800 </fieldset>
801 <?php
802 $this->print_filter_text( $args );
803 $this->print_description_text( $args );
804 }
805
806 /**
807 * Prints out a "single-image browse control" which includes a text input to
808 * store image path and a button to browse for images.
809 *
810 * @param array $args The arguments for the control.
811 */
812 public function print_media_single_image( array $args ): void {
813 $key = $args['option_key'];
814 $input_value = $this->parsely->get_options()[ $key ];
815 $input_name = Parsely::OPTIONS_KEY . "[$key]";
816 $button_text = __( 'Browse', 'wp-parsely' );
817 ?>
818
819 <fieldset class="media-single-image" id="media-single-image-<?php echo esc_attr( $key ); ?>">
820 <legend class="screen-reader-text"><span><?php echo esc_html( $args['title'] ); ?></span></legend>
821 <input class="file-path" type="text" name="<?php echo esc_attr( $input_name ); ?>" id="logo" value="<?php echo esc_attr( $input_value ); ?>" />
822 <button data-option="<?php echo esc_attr( $key ); ?>" class="browse button" type="button"><?php echo esc_html( $button_text ); ?></button>
823 </fieldset>
824
825 <?php
826 $this->print_description_text( $args );
827 }
828
829 /**
830 * Prints out the post tracking options table.
831 *
832 * @since 3.2.0
833 *
834 * @param array<string, string> $args The arguments used in the output HTML elements.
835 */
836 public function print_track_post_types_table( array $args ): void {
837 $option_key = esc_attr( $args['option_key'] );
838 $post_types = get_post_types( array( 'public' => true ) );
839 $values = $this->get_tracking_values_for_display();
840 ?>
841 <fieldset>
842 <legend class="screen-reader-text"><span><?php echo esc_html( $args['title'] ); ?></span></legend>
843 <table class="form-table widefat striped" id="track-post-types">
844 <caption class="screen-reader-text"><?php echo esc_html( $args['title'] ); ?></caption>
845 <thead>
846 <tr>
847 <th scope="col"><?php echo esc_html__( 'Post Type', 'wp-parsely' ); ?></th>
848 <th id="track-post-types--post" scope="col"><?php echo esc_html__( 'Track as Post', 'wp-parsely' ); ?></th>
849 <th id="track-post-types--page" scope="col"><?php echo esc_html__( 'Track as Non-Post', 'wp-parsely' ); ?></th>
850 <th id="track-post-types--none" scope="col"><?php echo esc_html__( 'Do not track', 'wp-parsely' ); ?></th>
851 </tr>
852 </thead>
853 <tbody>
854 <?php
855 foreach ( $post_types as $post_type ) {
856 $group_name = "parsely[{$option_key}][{$post_type}]";
857 $id_post = "{$option_key}_{$post_type}_post";
858 $id_page = "{$option_key}_{$post_type}_page";
859 $id_none = "{$option_key}_{$post_type}_none";
860 $value = $values[ $post_type ] ?? 'none';
861 ?>
862 <tr>
863 <th scope="row"><?php echo esc_html( $post_type ); ?></th>
864 <td>
865 <label aria-labelledby="track-post-types--post" for="<?php echo esc_attr( $id_post ); ?>">
866 <input id="<?php echo esc_attr( $id_post ); ?>" name="<?php echo esc_attr( $group_name ); ?>" type="radio" value="post" <?php checked( $value, 'post' ); ?> />
867 </label>
868 </td>
869 <td>
870 <label aria-labelledby="track-post-types--page" for="<?php echo esc_attr( $id_page ); ?>">
871 <input id="<?php echo esc_attr( $id_page ); ?>" name="<?php echo esc_attr( $group_name ); ?>" type="radio" value="page" <?php checked( $value, 'page' ); ?> />
872 </label>
873 </td>
874 <td>
875 <label aria-labelledby="track-post-types--none" for="<?php echo esc_attr( $id_none ); ?>">
876 <input id="<?php echo esc_attr( $id_none ); ?>" name="<?php echo esc_attr( $group_name ); ?>" type="radio" value="none" <?php checked( $value, 'none' ); ?> />
877 </label>
878 </td>
879 </tr>
880 <?php } ?>
881 </tbody>
882 </table>
883 </fieldset>
884 <?php
885 $this->print_filter_text( $args );
886 $this->print_description_text( $args );
887 }
888
889 /**
890 * Returns the custom post type tracking values in a format that is easily
891 * consumable by the print_track_post_types_table() function.
892 *
893 * @since 3.2.0
894 *
895 * @return array<string, string> Key-value pairs with post type and their 'track as' value.
896 */
897 public function get_tracking_values_for_display(): array {
898 $options = $this->parsely->get_options();
899 $types = array( 'post', 'page' );
900 $result = array();
901
902 foreach ( $types as $type ) {
903 $array_key = "track_{$type}_types";
904 if ( array_key_exists( $array_key, $options ) ) {
905 foreach ( $options[ $array_key ] as $post_type ) {
906 $result[ $post_type ] = $type;
907 }
908 }
909 }
910
911 return $result;
912 }
913
914 /**
915 * Validates the options provided by the user.
916 *
917 * @param array $input Options from the settings page.
918 * @return array List of validated input settings.
919 */
920 public function validate_options( array $input ): array {
921 $options = $this->parsely->get_options();
922
923 if ( empty( $input['apikey'] ) ) {
924 add_settings_error(
925 Parsely::OPTIONS_KEY,
926 'apikey',
927 __( 'Please specify the Site ID', 'wp-parsely' )
928 );
929 } else {
930 $api_key = $this->sanitize_api_key( $input['apikey'] );
931 if ( false === $this->validate_api_key( $api_key ) ) {
932 add_settings_error(
933 Parsely::OPTIONS_KEY,
934 'apikey',
935 __( 'Your Parse.ly Site ID looks incorrect, it should look like "example.com".', 'wp-parsely' )
936 );
937 } else {
938 $input['apikey'] = $api_key;
939 }
940 }
941
942 $input['api_secret'] = sanitize_text_field( $input['api_secret'] );
943
944 if ( ! empty( $input['metadata_secret'] ) ) {
945 if ( strlen( $input['metadata_secret'] ) !== 10 ) {
946 add_settings_error(
947 Parsely::OPTIONS_KEY,
948 'metadata_secret',
949 __( 'Metadata secret is incorrect. Please contact Parse.ly support!', 'wp-parsely' )
950 );
951 } elseif ( isset( $input['parsely_wipe_metadata_cache'] ) && 'true' === $input['parsely_wipe_metadata_cache'] ) {
952 delete_post_meta_by_key( 'parsely_metadata_last_updated' );
953
954 wp_schedule_event( time() + 100, 'everytenminutes', 'parsely_bulk_metas_update' );
955 $input['parsely_wipe_metadata_cache'] = false;
956 }
957 }
958
959 if ( empty( $input['logo'] ) ) {
960 $input['logo'] = self::get_logo_default();
961 }
962
963 // Validate 'Track post type as'.
964 $this->validate_options_post_type_tracking( $input );
965
966 // Track authenticated users.
967 if ( 'true' !== $input['track_authenticated_users'] && 'false' !== $input['track_authenticated_users'] ) {
968 add_settings_error(
969 Parsely::OPTIONS_KEY,
970 'track_authenticated_users',
971 __( 'Value passed for track_authenticated_users must be either "true" or "false".', 'wp-parsely' )
972 );
973 } else {
974 $input['track_authenticated_users'] = 'true' === $input['track_authenticated_users'];
975 }
976
977 if ( 'true' !== $input['disable_javascript'] && 'false' !== $input['disable_javascript'] ) {
978 add_settings_error(
979 Parsely::OPTIONS_KEY,
980 'disable_javascript',
981 __( 'Value passed for disable_javascript must be either "Yes" or "No".', 'wp-parsely' )
982 );
983 } else {
984 $input['disable_javascript'] = 'true' === $input['disable_javascript'];
985 }
986
987 if ( ! isset( $input['disable_autotrack'] ) ) {
988 $input['disable_autotrack'] = $options['disable_autotrack'];
989 } elseif ( 'true' !== $input['disable_autotrack'] && 'false' !== $input['disable_autotrack'] ) {
990 add_settings_error(
991 Parsely::OPTIONS_KEY,
992 'disable_autotrack',
993 __( 'Value passed for disable_autotrack must be either "Yes" or "No".', 'wp-parsely' )
994 );
995 } else {
996 $input['disable_autotrack'] = 'true' === $input['disable_autotrack'];
997 }
998
999 // Allow for Disable AMP setting to be conditionally included on the page.
1000 // If it's not shown, then set the value as what was previously saved.
1001 if ( ! isset( $input['disable_amp'] ) ) {
1002 $input['disable_amp'] = 'true';
1003 if ( false === $options['disable_amp'] ) {
1004 $input['disable_amp'] = 'false';
1005 }
1006 }
1007
1008 if ( 'true' !== $input['disable_amp'] && 'false' !== $input['disable_amp'] ) {
1009 add_settings_error(
1010 Parsely::OPTIONS_KEY,
1011 'disable_amp',
1012 __( 'Value passed for disable_amp must be either "true" or "false".', 'wp-parsely' )
1013 );
1014 } else {
1015 $input['disable_amp'] = 'true' === $input['disable_amp'];
1016 }
1017
1018 $input['api_secret'] = sanitize_text_field( $input['api_secret'] );
1019
1020 // Custom taxonomy as section.
1021 if ( ! isset( $input['meta_type'] ) ) {
1022 $input['meta_type'] = $options['meta_type'];
1023 } else {
1024 $input['meta_type'] = sanitize_text_field( $input['meta_type'] );
1025 }
1026
1027 // Content ID prefix.
1028 if ( ! isset( $input['content_id_prefix'] ) ) {
1029 $input['content_id_prefix'] = $options['content_id_prefix'];
1030 } else {
1031 $input['content_id_prefix'] = sanitize_text_field( $input['content_id_prefix'] );
1032 }
1033
1034 // Allow for Top-level categories setting to be conditionally included on the page.
1035 // If it's not shown, then set the value as what was previously saved.
1036 if ( ! isset( $input['use_top_level_cats'] ) ) {
1037 $input['use_top_level_cats'] = 'true';
1038 if ( false === $options['use_top_level_cats'] ) {
1039 $input['use_top_level_cats'] = 'false';
1040 }
1041 }
1042
1043 // Top-level categories.
1044 if ( 'true' !== $input['use_top_level_cats'] && 'false' !== $input['use_top_level_cats'] ) {
1045 add_settings_error(
1046 Parsely::OPTIONS_KEY,
1047 'use_top_level_cats',
1048 __( 'Value passed for use_top_level_cats must be either "true" or "false".', 'wp-parsely' )
1049 );
1050 } else {
1051 $input['use_top_level_cats'] = 'true' === $input['use_top_level_cats'];
1052 }
1053
1054 // Custom taxonomy as section.
1055 if ( ! isset( $input['custom_taxonomy_section'] ) ) {
1056 $input['custom_taxonomy_section'] = $options['custom_taxonomy_section'];
1057 } else {
1058 $input['custom_taxonomy_section'] = sanitize_text_field( $input['custom_taxonomy_section'] );
1059 }
1060
1061 // Allow for Categories as Tags setting to be conditionally included on the page.
1062 // If it's not shown, then set the value as what was previously saved.
1063 if ( ! isset( $input['cats_as_tags'] ) ) {
1064 $input['cats_as_tags'] = 'true';
1065 if ( false === $options['cats_as_tags'] ) {
1066 $input['cats_as_tags'] = 'false';
1067 }
1068 }
1069
1070 // Child categories as tags.
1071 if ( 'true' !== $input['cats_as_tags'] && 'false' !== $input['cats_as_tags'] ) {
1072 add_settings_error(
1073 Parsely::OPTIONS_KEY,
1074 'cats_as_tags',
1075 __( 'Value passed for cats_as_tags must be either "true" or "false".', 'wp-parsely' )
1076 );
1077 } else {
1078 $input['cats_as_tags'] = 'true' === $input['cats_as_tags'];
1079 }
1080
1081 // Allow for Lowercase Tags setting to be conditionally included on the page.
1082 // If it's not shown, then set the value as what was previously saved.
1083 if ( ! isset( $input['lowercase_tags'] ) ) {
1084 $input['lowercase_tags'] = 'true';
1085 if ( false === $options['lowercase_tags'] ) {
1086 $input['lowercase_tags'] = 'false';
1087 }
1088 }
1089
1090 // Lowercase tags.
1091 if ( 'true' !== $input['lowercase_tags'] && 'false' !== $input['lowercase_tags'] ) {
1092 add_settings_error(
1093 Parsely::OPTIONS_KEY,
1094 'lowercase_tags',
1095 __( 'Value passed for lowercase_tags must be either "true" or "false".', 'wp-parsely' )
1096 );
1097 } else {
1098 $input['lowercase_tags'] = 'true' === $input['lowercase_tags'];
1099 }
1100
1101 // Allow for Force HTTPS Canonical setting to be conditionally included on the page.
1102 // If it's not shown, then set the value as what was previously saved.
1103 if ( ! isset( $input['force_https_canonicals'] ) ) {
1104 $input['force_https_canonicals'] = 'true';
1105 if ( false === $options['force_https_canonicals'] ) {
1106 $input['force_https_canonicals'] = 'false';
1107 }
1108 }
1109
1110 if ( 'true' !== $input['force_https_canonicals'] && 'false' !== $input['force_https_canonicals'] ) {
1111 add_settings_error(
1112 Parsely::OPTIONS_KEY,
1113 'force_https_canonicals',
1114 __( 'Value passed for force_https_canonicals must be either "true" or "false".', 'wp-parsely' )
1115 );
1116 } else {
1117 $input['force_https_canonicals'] = 'true' === $input['force_https_canonicals'];
1118 }
1119
1120 return $input;
1121 }
1122
1123 /**
1124 * Validates the passed API key.
1125 *
1126 * Accepts a www prefix and up to 3 periods.
1127 *
1128 * Valid examples: 'test.com', 'www.test.com', 'subdomain.test.com',
1129 * 'www.subdomain.test.com', 'subdomain.subdomain.test.com'.
1130 *
1131 * Invalid examples: 'test', 'test.com/', 'http://test.com', 'https://test.com',
1132 * 'www.subdomain.subdomain.test.com'.
1133 *
1134 * @since 3.3.0
1135 *
1136 * @param string $api_key The API key to be validated.
1137 * @return bool
1138 */
1139 private function validate_api_key( string $api_key ): bool {
1140 $key_format = '/^((\w+)\.)?(([\w-]+)?)(\.[\w-]+){1,2}$/';
1141
1142 return 1 === preg_match( $key_format, $api_key );
1143 }
1144
1145 /**
1146 * Sanitizes the passed API key.
1147 *
1148 * @since 3.3.0
1149 *
1150 * @param string $api_key The API key to be sanitized.
1151 * @return string
1152 */
1153 private function sanitize_api_key( string $api_key ): string {
1154 return strtolower( sanitize_text_field( $api_key ) );
1155 }
1156
1157 /**
1158 * Receives the $input array from the validate_options() function and
1159 * validate post tracking options.
1160 *
1161 * This function will mutate the $input array.
1162 *
1163 * @since 3.2.0
1164 *
1165 * @param array $input Array passed to validate_options() function.
1166 */
1167 private function validate_options_post_type_tracking( array &$input ): void {
1168 $options = $this->parsely->get_options();
1169 $posts = 'track_post_types';
1170 $pages = 'track_page_types';
1171 $track_as = 'track_post_types_as';
1172 $input[ $posts ] = $options[ $posts ];
1173 $input[ $pages ] = $options[ $pages ];
1174
1175 if ( isset( $input[ $track_as ] ) && is_array( $input[ $track_as ] ) && 0 < count( $input[ $track_as ] ) ) {
1176 $post_types = get_post_types( array( 'public' => true ) );
1177 $temp_posts = array();
1178 $temp_pages = array();
1179
1180 // Create temporary Post and Page arrays, disallowing non-existent post types.
1181 foreach ( $input[ $track_as ] as $key => $value ) {
1182 if ( false === in_array( $key, $post_types, true ) ) {
1183 continue;
1184 }
1185
1186 if ( 'post' === $value ) {
1187 $temp_posts[] = $key;
1188 } elseif ( 'page' === $value ) {
1189 $temp_pages[] = $key;
1190 }
1191 }
1192
1193 // Cleanup and sanitized values assignment.
1194 $input [ $posts ] = self::sanitize_option_array( $temp_posts );
1195 $input [ $pages ] = self::sanitize_option_array( $temp_pages );
1196 }
1197
1198 if ( isset( $input[ $track_as ] ) ) {
1199 unset( $input[ $track_as ] );
1200 }
1201 }
1202
1203 /**
1204 * Returns default logo if one can be found.
1205 *
1206 * @return string
1207 */
1208 private static function get_logo_default(): string {
1209 $custom_logo_id = get_theme_mod( 'custom_logo' );
1210 if ( $custom_logo_id ) {
1211 $logo_attrs = wp_get_attachment_image_src( $custom_logo_id, 'full' );
1212 if ( $logo_attrs ) {
1213 return $logo_attrs[0];
1214 }
1215 }
1216
1217 // get_site_icon_url returns an empty string if one isn't found,
1218 // which is what we want to use as the default anyway.
1219 return get_site_icon_url();
1220 }
1221
1222 /**
1223 * Sanitizes all elements in an option array.
1224 *
1225 * @param array $array Array of options to be sanitized.
1226 * @return array
1227 */
1228 private static function sanitize_option_array( array $array ): array {
1229 $new_array = $array;
1230 foreach ( $array as $key => $val ) {
1231 $new_array[ $key ] = sanitize_text_field( $val );
1232 }
1233 return $new_array;
1234 }
1235 }
1236