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

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