PluginProbe
Parse.ly / 2.4.1
Parse.ly v2.4.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 / wp-parsely.php

wp-parsely.php in Parse.ly 2.4.1, at wp-parsely.php

1,791 lines 58.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Parse.ly
4 *
5 * @package Parsely\wp-parsely
6 * @author Parse.ly
7 * @copyright 2012 Parse.ly
8 * @license GPL-2.0-or-later
9 *
10 * @wordpress-plugin
11 * Plugin Name: Parse.ly
12 * Plugin URI: https://www.parse.ly/help/integration/wordpress
13 * Description: This plugin makes it a snap to add Parse.ly tracking code to your WordPress blog.
14 * Version: 2.4.1
15 * Author: Parse.ly
16 * Author URI: https://www.parse.ly
17 * Text Domain: wp-parsely
18 * License: GPL-2.0-or-later
19 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
20 * GitHub Plugin URI: https://github.com/Parsely/wp-parsely
21 * Requires PHP: 5.6
22 * Requires WP: 4.0.0
23 */
24
25 /**
26 * This is the main class for Parsely
27 *
28 * @category Class
29 * @package Parsely
30 */
31 class Parsely {
32 /**
33 * Declare our constants
34 *
35 * @codeCoverageIgnoreStart
36 */
37 const VERSION = '2.4.1';
38 const MENU_SLUG = 'parsely'; // Defines the page param passed to options-general.php.
39 const MENU_TITLE = 'Parse.ly'; // Text to be used for the menu as seen in Settings sub-menu.
40 const MENU_PAGE_TITLE = 'Parse.ly > Settings'; // Text shown in <title></title> when the settings screen is viewed.
41 const OPTIONS_KEY = 'parsely'; // Defines the key used to store options in the WP database.
42 const CAPABILITY = 'manage_options'; // The capability required for the user to administer settings.
43
44 /**
45 * Declare some class propeties
46 *
47 * @var array $option_defaults The defaults we need for the class.
48 */
49 private $option_defaults = array(
50 'apikey' => '',
51 'content_id_prefix' => '',
52 'api_secret' => '',
53 'use_top_level_cats' => false,
54 'custom_taxonomy_section' => 'category',
55 'cats_as_tags' => false,
56 'track_authenticated_users' => true,
57 'lowercase_tags' => true,
58 'force_https_canonicals' => false,
59 'track_post_types' => array( 'post' ),
60 'track_page_types' => array( 'page' ),
61 'disable_javascript' => false,
62 'disable_amp' => false,
63 'meta_type' => 'json_ld',
64 'logo' => '',
65 'metadata_secret' => '',
66 'parsely_wipe_metadata_cache' => false,
67 );
68
69 /**
70 * The constructor
71 *
72 * @category Function
73 * @package Parsely
74 */
75 public function __construct() {
76 // Run upgrade options if they exist for the version currently defined.
77 $options = $this->get_options();
78 if ( empty( $options['plugin_version'] ) || self::VERSION !== $options['plugin_version'] ) {
79 $method = 'upgrade_plugin_to_version_' . str_replace( '.', '_', self::VERSION );
80 if ( method_exists( $this, $method ) ) {
81 call_user_func_array( array( $this, $method ), array( $options ) );
82 }
83 // Update our version info.
84 $options['plugin_version'] = self::VERSION;
85 update_option( self::OPTIONS_KEY, $options );
86 }
87
88 // admin_menu and a settings link.
89 add_action( 'admin_head', array( $this, 'add_admin_header' ) );
90 add_action( 'admin_menu', array( $this, 'add_settings_sub_menu' ) );
91 add_action( 'admin_init', array( $this, 'initialize_settings' ) );
92 // display warning when plugin hasn't been configured.
93 add_action( 'admin_footer', array( $this, 'display_admin_warning' ) );
94
95 $basename = plugin_basename( __FILE__ );
96 add_filter(
97 'plugin_action_links_' . $basename,
98 array( $this, 'add_plugin_meta_links' )
99 );
100
101 add_filter( 'cron_schedules', [ $this, 'wpparsely_add_cron_interval' ] );
102 add_action( 'parsely_bulk_metas_update', array( $this, 'bulk_update_posts' ) );
103 // inserting parsely code.
104 add_action( 'wp_head', array( $this, 'insert_parsely_page' ) );
105 add_action( 'wp_footer', array( $this, 'insert_parsely_javascript' ) );
106 add_action( 'save_post', array( $this, 'update_metadata_endpoint' ) );
107 add_action( 'instant_articles_compat_registry_analytics', array( $this, 'insert_parsely_tracking_fbia' ) );
108 add_action( 'template_redirect', array( $this, 'parsely_add_amp_actions' ) );
109 if ( ! defined( 'WP_PARSELY_TESTING' ) ) {
110 add_action( 'wp_enqueue_scripts', [ $this, 'wp_parsely_style_init' ] );
111 add_action( 'wp_enqueue_scripts', [ $this, 'ensure_jquery_exists' ] );
112 }
113 }
114
115 /**
116 * Adds 10 minute cron interval
117 *
118 * @param array $schedules WP schedules array.
119 */
120 public function wpparsely_add_cron_interval( $schedules ) {
121 $schedules['everytenminutes'] = array(
122 'interval' => 600, // time in seconds.
123 'display' => 'Every 10 Minutes',
124 );
125 return $schedules;
126 }
127
128 /**
129 * Initialize parsely WordPress style
130 */
131 public function wp_parsely_style_init() {
132 wp_enqueue_style( 'wp-parsely-style', plugins_url( 'wp-parsely.css', __FILE__ ), array(), filemtime( get_stylesheet_directory() ) );
133 }
134
135 /**
136 * Make sure that jquery exists
137 */
138 public function ensure_jquery_exists() {
139 wp_enqueue_script( 'jquery' );
140 }
141
142 /**
143 * Include the parsely admin header
144 *
145 * @category Function
146 * @package Parsely
147 */
148 public function add_admin_header() {
149 include 'parsely-admin-header.php';
150 }
151
152 /**
153 * Parsely settings page in WordPress settings menu.
154 *
155 * @category Function
156 * @package Parsely
157 */
158 public function add_settings_sub_menu() {
159 add_options_page(
160 self::MENU_PAGE_TITLE,
161 self::MENU_TITLE,
162 self::CAPABILITY,
163 self::MENU_SLUG,
164 array( $this, 'display_settings' )
165 );
166 }
167
168 /**
169 * Parse.ly settings screen ( options-general.php?page=[MENU_SLUG] )
170 *
171 * @category Function
172 * @package Parsely
173 */
174 public function display_settings() {
175 if ( ! current_user_can( self::CAPABILITY ) ) {
176 wp_die( esc_attr( 'You do not have sufficient permissions to access this page.' ) );
177 }
178
179 include 'parsely-settings.php';
180 }
181
182 /**
183 * Initialize the settings for Parsely
184 *
185 * @category Function
186 * @package Parsely
187 */
188 public function initialize_settings() {
189 // All our options are actually stored in one single array to reduce
190 // DB queries.
191 register_setting(
192 self::OPTIONS_KEY,
193 self::OPTIONS_KEY,
194 array( $this, 'validate_options' )
195 );
196
197 // These are the Required Settings.
198 add_settings_section(
199 'required_settings',
200 'Required Settings',
201 array( $this, 'print_required_settings' ),
202 self::MENU_SLUG
203 );
204
205 // Get the API Key.
206 $h = 'Your Site ID is your own site domain ( e.g. `mydomain.com` )';
207
208 $field_args = array(
209 'option_key' => 'apikey',
210 'help_text' => $h,
211 );
212 add_settings_field(
213 'apikey',
214 'Parse.ly Site ID <div class="help-icons"></div>',
215 array( $this, 'print_text_tag' ),
216 self::MENU_SLUG,
217 'required_settings',
218 $field_args
219 );
220
221 // These are the Optional Settings.
222 add_settings_section(
223 'optional_settings',
224 'Optional Settings',
225 array( $this, 'print_optional_settings' ),
226 self::MENU_SLUG
227 );
228
229 $h = 'Your API secret is your secret code to %s%s%saccess our API.%s
230 It can be found at dash.parsely.com/yoursitedomain/settings/api
231 ( replace yoursitedown with your domain name, e.g. `mydomain.com` ) If you haven\'t purchased access to the API, and would
232 like to do so, email your account manager or support@parsely.com!';
233 $h_link = 'https://www.parse.ly/help/api/analytics/';
234
235 $field_args = array(
236 'option_key' => 'api_secret',
237 'help_text' => $h,
238 'help_link' => $h_link,
239 );
240 add_settings_field(
241 'api_secret',
242 'Parse.ly API Secret <div class="help-icons"></div>',
243 array( $this, 'print_text_tag' ),
244 self::MENU_SLUG,
245 'optional_settings',
246 $field_args
247 );
248
249 $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!';
250 $h_link = 'https://www.parse.ly/help/api/analytics/';
251
252 $field_args = array(
253 'option_key' => 'metadata_secret',
254 'help_text' => $h,
255 'help_link' => $h_link,
256 );
257 add_settings_field(
258 'metadata_secret',
259 'Parse.ly Metadata Secret <div class="help-icons"></div>',
260 array( $this, 'print_text_tag' ),
261 self::MENU_SLUG,
262 'optional_settings',
263 $field_args
264 );
265
266 // Clear metadata.
267 $h = 'Check this radio button and hit "Save Changes" to clear all metadata information for Parsely posts and re-send all metadata
268 to Parsely. WARNING: do not do this unless explicitly instructed by Parse.ly Staff!';
269 add_settings_field(
270 'parsely_wipe_metadata_cache',
271 'Wipe Parsely Metadata Info <div class="help-icons"></div>',
272 array( $this, 'print_checkbox_tag' ),
273 self::MENU_SLUG,
274 'optional_settings',
275 array(
276 'option_key' => 'parsely_wipe_metadata_cache',
277 'help_text' => $h,
278 'requires_recrawl' => false,
279 )
280 );
281
282 $h = 'Choose the metadata format for our crawlers to access. ' .
283 'Most publishers are fine with JSON-LD ( %s%s%shttps://www.parse.ly/help/integration/jsonld/%s ), ' .
284 'but if you prefer to use our proprietary metadata format then you can do so here.';
285 $h_link = 'https://www.parse.ly/help/integration/jsonld/';
286
287 add_settings_field(
288 'meta_type',
289 'Metadata Format <div class="help-icons"></div>',
290 array( $this, 'print_select_tag' ),
291 self::MENU_SLUG,
292 'optional_settings',
293 array(
294 'option_key' => 'meta_type',
295 'help_text' => $h,
296 'help_link' => $h_link,
297 // filter WordPress taxonomies under the hood that should not appear in dropdown.
298 'select_options' => array(
299 'json_ld' => 'json_ld',
300 'repeated_metas' => 'repeated_metas',
301 ),
302 'requires_recrawl' => true,
303 'multiple' => false,
304 )
305 );
306
307 $h = 'If you want to specify the url for your logo, you can do so here.';
308
309 $option_defaults['logo'] = $this->get_logo_default();
310
311 $field_args = array(
312 'option_key' => 'logo',
313 'help_text' => $h,
314 );
315
316 add_settings_field(
317 'logo',
318 'Logo <div class="help-icons"></div>',
319 array( $this, 'print_text_tag' ),
320 self::MENU_SLUG,
321 'optional_settings',
322 $field_args
323 );
324
325 // Content ID Prefix.
326 $h = 'If you use more than one content management system (e.g. ' .
327 'WordPress and Drupal), you may end up with duplicate content ' .
328 'IDs. Adding a Content ID Prefix will ensure the content IDs ' .
329 'from WordPress will not conflict with other content management ' .
330 'systems. We recommend using "WP-" for your prefix.';
331
332 $field_args = array(
333 'option_key' => 'content_id_prefix',
334 'optional_args' => array(
335 'placeholder' => 'WP-',
336 ),
337 'help_text' => $h,
338 'requires_recrawl' => true,
339 );
340 add_settings_field(
341 'content_id_prefix',
342 'Content ID Prefix <div class="help-icons"></div>',
343 array( $this, 'print_text_tag' ),
344 self::MENU_SLUG,
345 'optional_settings',
346 $field_args
347 );
348
349 // Disable javascript.
350 $h = 'If you use a separate system for Javascript tracking ( Tealium / Segment / Google Tag Manager / other tag manager solution ) ' .
351 'you may want to use that instead of having the plugin load the tracker. WARNING: disabling this option ' .
352 'will also disable the "Personalize Results" section of the recommended widget! We highly recommend leaving ' .
353 'this option set to "No"!';
354 add_settings_field(
355 'disable_javascript',
356 'Disable Javascript <div class="help-icons"></div>',
357 array( $this, 'print_binary_radio_tag' ),
358 self::MENU_SLUG,
359 'optional_settings',
360 array(
361 'option_key' => 'disable_javascript',
362 'help_text' => $h,
363 'requires_recrawl' => false,
364 )
365 );
366
367 // Disable amp tracking.
368 $h = 'If you use a separate system for Javascript tracking on AMP pages ( Tealium / Segment / Google Tag Manager / other tag manager solution ) ' .
369 'you may want to use that instead of having the plugin load the tracker.';
370 add_settings_field(
371 'disable_amp',
372 'Disable Amp Tracking <div class="help-icons"></div>',
373 array( $this, 'print_binary_radio_tag' ),
374 self::MENU_SLUG,
375 'optional_settings',
376 array(
377 'option_key' => 'disable_amp',
378 'help_text' => $h,
379 'requires_recrawl' => false,
380 )
381 );
382
383 // Use top-level categories.
384 $h = 'wp-parsely will use the first category assigned to a post. ' .
385 'With this option selected, if you post a story to News > ' .
386 'National > Florida, wp-parsely will use the "News" for the ' .
387 'section name in your dashboard instead of "Florida".';
388 add_settings_field(
389 'use_top_level_cats',
390 'Use Top-Level Categories for Section <div class="help-icons"></div>',
391 array( $this, 'print_binary_radio_tag' ),
392 self::MENU_SLUG,
393 'optional_settings',
394 array(
395 'option_key' => 'use_top_level_cats',
396 'help_text' => $h,
397 'requires_recrawl' => true,
398 )
399 );
400
401 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
402 $h = 'By default, the section value in your Parse.ly dashboard maps to a post\'s category. ' .
403 'You can optionally choose a custom taxonomy, if you\'ve created one, to ' .
404 'populate the section value instead. ';
405 add_settings_field(
406 'custom_taxonomy_section',
407 'Use Custom Taxonomy for Section <div class="help-icons"></div>',
408 array( $this, 'print_select_tag' ),
409 self::MENU_SLUG,
410 'optional_settings',
411 array(
412 'option_key' => 'custom_taxonomy_section',
413 'help_text' => $h,
414 // filter WordPress taxonomies under the hood that should not appear in dropdown.
415 'select_options' => array_diff( get_taxonomies(), array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) ),
416 'requires_recrawl' => true,
417 )
418 );
419
420 // Use categories and custom taxonomies as tags.
421 $h = 'You can use this option to add all assigned categories and taxonomies to ' .
422 'your tags. For example, if you had a post assigned to ' .
423 'the categories: "Business/Tech", "Business/Social", your tags would include ' .
424 '"Business/Tech" and "Business/Social" in addition to your other tags.';
425 add_settings_field(
426 'cats_as_tags',
427 'Add Categories to Tags <div class="help-icons"></div>',
428 array( $this, 'print_binary_radio_tag' ),
429 self::MENU_SLUG,
430 'optional_settings',
431 array(
432 'option_key' => 'cats_as_tags',
433 'help_text' => $h,
434 'requires_recrawl' => true,
435 )
436 );
437
438 // Track logged-in users.
439 $h = 'By default, wp-parsely will track the activity of users that ' .
440 'are logged into this site. You can change this setting to only ' .
441 'track the activity of anonymous visitors. Note: You will no ' .
442 'longer see the Parse.ly tracking code on your site if you ' .
443 'browse while logged in.';
444 add_settings_field(
445 'track_authenticated_users',
446 'Track Logged-in Users <div class="help-icons"></div>',
447 array( $this, 'print_binary_radio_tag' ),
448 self::MENU_SLUG,
449 'optional_settings',
450 array(
451 'option_key' => 'track_authenticated_users',
452 'help_text' => $h,
453 'requires_recrawl' => true,
454 )
455 );
456
457 // Lowercase all tags.
458 $h = 'By default, wp-parsely will use lowercase versions of your ' .
459 'tags to correct for potential misspellings. You can change this ' .
460 'setting to ensure that tag names are used verbatim.';
461 add_settings_field(
462 'lowercase_tags',
463 'Lowercase All Tags <div class="help-icons"></div>',
464 array( $this, 'print_binary_radio_tag' ),
465 self::MENU_SLUG,
466 'optional_settings',
467 array(
468 'option_key' => 'lowercase_tags',
469 'help_text' => $h,
470 'requires_recrawl' => true,
471 )
472 );
473
474 $h = 'wp-parsely uses http canonical URLs by default. If this needs to be forced to use https, set this option ' .
475 ' to true. Note: the default is fine for almost all publishers, it\'s unlikely you\'ll have to change this unless' .
476 ' directed to do so by a Parsely support rep.';
477 add_settings_field(
478 'force_https_canonicals',
479 'Force HTTPS canonicals <div class="help-icons"></div>',
480 array( $this, 'print_binary_radio_tag' ),
481 self::MENU_SLUG,
482 'optional_settings',
483 array(
484 'option_key' => 'force_https_canonicals',
485 'help_text' => $h,
486 'requires_recrawl' => true,
487 )
488 );
489
490 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
491 $h = 'By default, Parsely only tracks the default post type as a post page. ' .
492 'If you want to track custom post types, select them here!';
493 add_settings_field(
494 'track_post_types',
495 'Post Types To Track <div class="help-icons"></div>',
496 array( $this, 'print_select_tag' ),
497 self::MENU_SLUG,
498 'optional_settings',
499 array(
500 'option_key' => 'track_post_types',
501 'help_text' => $h,
502 // filter WordPress taxonomies under the hood that should not appear in dropdown.
503 'select_options' => get_post_types(),
504 'requires_recrawl' => true,
505 'multiple' => true,
506 )
507 );
508
509 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
510 $h = 'By default, Parsely only tracks the default page type as a non-post page. ' .
511 'If you want to track custom post types as non-post pages, select them here!';
512 add_settings_field(
513 'track_page_types',
514 'Page Types To Track <div class="help-icons"></div>',
515 array( $this, 'print_select_tag' ),
516 self::MENU_SLUG,
517 'optional_settings',
518 array(
519 'option_key' => 'track_page_types',
520 'help_text' => $h,
521 // filter WordPress taxonomies under the hood that should not appear in dropdown.
522 'select_options' => get_post_types(),
523 'requires_recrawl' => true,
524 'multiple' => true,
525 )
526 );
527
528 // Dynamic tracking note.
529 add_settings_field(
530 'dynamic_tracking_note',
531 'Note: ',
532 array( $this, 'print_dynamic_tracking_note' ),
533 self::MENU_SLUG,
534 'optional_settings'
535 );
536 }
537
538 /**
539 * Validate options from an array
540 *
541 * @category Function
542 * @package Parsely
543 * @param array $array Array of options to be sanitized.
544 * @param string $name Unused?.
545 */
546 public function validate_option_array( $array, $name ) {
547 $new_array = $array;
548 foreach ( $array as $key => $val ) {
549 $new_array[ $key ] = sanitize_text_field( $val );
550 }
551 return $new_array;
552 }
553
554 /**
555 * Validate the options provided by the user
556 *
557 * @category Function
558 * @package Parsely
559 * @param array $input Options from the settings page.
560 * @return array $input list of validated input settings.
561 */
562 public function validate_options( $input ) {
563 if ( empty( $input['apikey'] ) ) {
564 add_settings_error(
565 self::OPTIONS_KEY,
566 'apikey',
567 'Please specify the Site ID'
568 );
569 } else {
570 $input['apikey'] = strtolower( $input['apikey'] );
571 $input['apikey'] = sanitize_text_field( $input['apikey'] );
572 if ( strpos( $input['apikey'], '.' ) === false || strpos( $input['apikey'], ' ' ) !== false ) {
573 add_settings_error(
574 self::OPTIONS_KEY,
575 'apikey',
576 'Your Parse.ly Site ID looks incorrect, it should look like "example.com".'
577 );
578 }
579 }
580 // these can't be null, if somebody accidentally deselected them just reset to default.
581 if ( ! isset( $input['track_post_types'] ) ) {
582 $input['track_post_types'] = array( 'post' );
583
584 }
585 if ( ! isset( $input['track_page_types'] ) ) {
586 $input['track_page_types'] = array( 'page' );
587 }
588
589 if ( empty( $input['logo'] ) ) {
590 $input['logo'] = $this->get_logo_default();
591 }
592
593 $input['track_post_types'] = $this->validate_option_array( $input['track_post_types'], 'track_post_types' );
594 $input['track_page_types'] = $this->validate_option_array( $input['track_page_types'], 'track_page_types' );
595
596 $input['api_secret'] = sanitize_text_field( $input['api_secret'] );
597 // Content ID prefix.
598 $input['content_id_prefix'] = sanitize_text_field( $input['content_id_prefix'] );
599 $input['custom_taxonomy_section'] = sanitize_text_field( $input['custom_taxonomy_section'] );
600
601 // Custom taxonomy as section.
602 // Top-level categories.
603 if ( 'true' !== $input['use_top_level_cats'] && 'false' !== $input['use_top_level_cats'] ) {
604 add_settings_error(
605 self::OPTIONS_KEY,
606 'use_top_level_cats',
607 'Value passed for use_top_level_cats must be either "true" or "false".'
608 );
609 } else {
610 $input['use_top_level_cats'] = 'true' === $input['use_top_level_cats'];
611 }
612
613 // Child categories as tags.
614 if ( 'true' !== $input['cats_as_tags'] && 'false' !== $input['cats_as_tags'] ) {
615 add_settings_error(
616 self::OPTIONS_KEY,
617 'cats_as_tags',
618 'Value passed for cats_as_tags must be either "true" or "false".'
619 );
620 } else {
621 $input['cats_as_tags'] = 'true' === $input['cats_as_tags'];
622 }
623
624 // Track authenticated users.
625 if ( 'true' !== $input['track_authenticated_users'] && 'false' !== $input['track_authenticated_users'] ) {
626 add_settings_error(
627 self::OPTIONS_KEY,
628 'track_authenticated_users',
629 'Value passed for track_authenticated_users must be either "true" or "false".'
630 );
631 } else {
632 $input['track_authenticated_users'] = 'true' === $input['track_authenticated_users'];
633 }
634
635 // Lowercase tags.
636 if ( 'true' !== $input['lowercase_tags'] && 'false' !== $input['lowercase_tags'] ) {
637 add_settings_error(
638 self::OPTIONS_KEY,
639 'lowercase_tags',
640 'Value passed for lowercase_tags must be either "true" or "false".'
641 );
642 } else {
643 $input['lowercase_tags'] = 'true' === $input['lowercase_tags'];
644 }
645
646 if ( 'true' !== $input['force_https_canonicals'] && 'false' !== $input['force_https_canonicals'] ) {
647 add_settings_error(
648 self::OPTIONS_KEY,
649 'force_https_canonicals',
650 'Value passed for force_https_canonicals must be either "true" or "false".'
651 );
652 } else {
653 $input['force_https_canonicals'] = 'true' === $input['force_https_canonicals'];
654 }
655
656 if ( 'true' !== $input['disable_javascript'] && 'false' !== $input['disable_javascript'] ) {
657 add_settings_error(
658 self::OPTIONS_KEY,
659 'disable_javascript',
660 'Value passed for disable_javascript must be either "true" or "false".'
661 );
662 } else {
663 $input['disable_javascript'] = 'true' === $input['disable_javascript'];
664 }
665
666 if ( 'true' !== $input['disable_amp'] && 'false' !== $input['disable_amp'] ) {
667 add_settings_error(
668 self::OPTIONS_KEY,
669 'disable_amp',
670 'Value passed for disable_amp must be either "true" or "false".'
671 );
672 } else {
673 $input['disable_amp'] = 'true' === $input['disable_amp'];
674 }
675
676 if ( ! empty( $input['metadata_secret'] ) ) {
677 if ( strlen( $input['metadata_secret'] ) !== 10 ) {
678 add_settings_error(
679 self::OPTIONS_KEY,
680 'metadata_secret',
681 'Metadata secret is incorrect. Please contact Parse.ly support!'
682 );
683 } elseif ( 'true' === $input['parsely_wipe_metadata_cache'] ) {
684 delete_post_meta_by_key( 'parsely_metadata_last_updated' );
685
686 wp_schedule_event( time() + 100, 'everytenminutes', 'parsely_bulk_metas_update' );
687 $input['parsely_wipe_metadata_cache'] = false;
688 }
689 }
690
691 return $input;
692 }
693
694 /**
695 * Not doing anything here
696 *
697 * @category Function
698 * @package Parsely
699 */
700 public function print_required_settings() {
701 // We can optionally print some text here in the future, but we don't
702 // need to now.
703 }
704
705 /**
706 * Not doing anything here
707 *
708 * @category Function
709 * @package Parsely
710 */
711 public function print_optional_settings() {
712 // We can optionally print some text here in the future, but we don't
713 // need to now.
714 }
715
716 /**
717 * Adds a 'Settings' link to the Plugins screen in WP admin
718 *
719 * @category Function
720 * @package Parsely
721 * @param array $links The links to add.
722 */
723 public function add_plugin_meta_links( $links ) {
724 array_unshift( $links, '<a href="' . esc_url( $this->get_settings_url() ) . '">' . __( 'Settings' ) . '</a>' );
725 return $links;
726 }
727
728 /**
729 * Display the admin warning if needed
730 *
731 * @category Function
732 * @package Parsely
733 */
734 public function display_admin_warning() {
735 $options = $this->get_options();
736 if ( ! isset( $options['apikey'] ) || empty( $options['apikey'] ) ) {
737 ?>
738 <div id='message' class='error'>
739 <p>
740 <strong>Parse.ly - Dash plugin is not active.</strong>
741 You need to
742 <a href='<?php echo esc_url( $this->get_settings_url() ); ?>'>
743 provide your Parse.ly Dash Site ID
744 </a>
745 before things get cooking.
746 </p>
747 </div>
748 <?php
749 }
750 }
751
752 /**
753 * Show our note about dynamic tracking
754 *
755 * @category Function
756 * @package Parsely
757 */
758 public function print_dynamic_tracking_note() {
759 printf(
760 '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="%s">the standard Parse.ly include</a> that the plugin injects into your page source. Please consult <a href="%s">the Parse.ly documentation on dynamic tracking</a> for instructions on implementing dynamic tracking, or contact Parse.ly support (<a href="%s">support@parsely.com</a> ) for additional assistance.',
761 esc_url( 'http://www.parsely.com/help/integration/basic/' ),
762 esc_url( 'https://www.parsely.com/help/integration/dynamic/' ),
763 esc_url( 'mailto:support@parsely.com' )
764 );
765 }
766
767 /**
768 * End the code coverage ignore
769 *
770 * @codeCoverageIgnoreEnd
771 */
772
773 /**
774 * Actually inserts the code for the <meta name='parsely-page'> parameter within the <head></head> tag.
775 */
776 public function insert_parsely_page() {
777 $parsely_options = $this->get_options();
778
779 if (
780 // No API key.
781 empty( $parsely_options['apikey'] ) ||
782
783 // Chosen not to track logged in users.
784 ( ! $parsely_options['track_authenticated_users'] && $this->parsely_is_user_logged_in() ) ||
785
786 // 404 pages are not tracked.
787 is_404() ||
788
789 // Search pages are not tracked.
790 is_search()
791 ) {
792 return '';
793 }
794
795 global $post;
796 // Assign default values for LD+JSON
797 // TODO: Maping of an install's post types to Parse.ly post types (namely page/post).
798 $parsely_page = $this->construct_parsely_metadata( $parsely_options, $post );
799 include 'parsely-parsely-page.php';
800 return $parsely_page;
801 }
802
803
804 /**
805 * Creates parsely metadata object from post metadata.
806 *
807 * @param array $parsely_options parsely_options array.
808 * @param WP_Post $post object.
809 * @return mixed|void
810 */
811 public function construct_parsely_metadata( array $parsely_options, $post ) {
812 $parsely_page = array(
813 '@context' => 'http://schema.org',
814 '@type' => 'WebPage',
815 );
816 $current_url = $this->get_current_url();
817
818 if ( is_front_page() && ! is_paged() || ( 'page' === get_option( 'show_on_front' ) && ! get_option( 'page_on_front' ) ) ) {
819 $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_bloginfo( 'name', 'raw' ) );
820 $parsely_page['url'] = home_url();
821 } elseif ( is_front_page() && is_paged() ) {
822 $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_bloginfo( 'name', 'raw' ) );
823 $parsely_page['url'] = $current_url;
824 } elseif ( is_home() ) {
825 $parsely_page['headline'] = get_the_title( get_option('page_for_posts', true) );
826 $parsely_page['url'] = $current_url;
827 } elseif ( is_author() ) {
828 // TODO: why can't we have something like a WP_User object for all the other cases? Much nicer to deal with than functions.
829 $author = ( get_query_var( 'author_name' ) ) ? get_user_by( 'slug', get_query_var( 'author_name' ) ) : get_userdata( get_query_var( 'author' ) );
830 $parsely_page['headline'] = $this->get_clean_parsely_page_value( 'Author - ' . $author->data->display_name );
831 $parsely_page['url'] = $current_url;
832 } elseif ( is_category() ) {
833 $category = get_the_category();
834 $category = $category[0];
835 $parsely_page['headline'] = $this->get_clean_parsely_page_value( $category->name );
836 $parsely_page['url'] = $current_url;
837 } elseif ( is_date() ) {
838 if ( is_year() ) {
839 $parsely_page['headline'] = 'Yearly Archive - ' . get_the_time( 'Y' );
840 } elseif ( is_month() ) {
841 $parsely_page['headline'] = 'Monthly Archive - ' . get_the_time( 'F, Y' );
842 } elseif ( is_day() ) {
843 $parsely_page['headline'] = 'Daily Archive - ' . get_the_time( 'F jS, Y' );
844 } elseif ( is_time() ) {
845 $parsely_page['headline'] = 'Hourly, Minutely, or Secondly Archive - ' . get_the_time( 'F jS g:i:s A' );
846 }
847 $parsely_page['url'] = $current_url;
848 } elseif ( is_tag() ) {
849 $tag = single_tag_title( '', false );
850 if ( empty( $tag ) ) {
851 $tag = single_term_title( '', false );
852 }
853 $parsely_page['headline'] = $this->get_clean_parsely_page_value( 'Tagged - ' . $tag );
854 $parsely_page['url'] = $current_url;
855 } elseif ( in_array( get_post_type( $post ), $parsely_options['track_post_types'], true ) && 'publish' === $post->post_status ) {
856 $authors = $this->get_author_names( $post );
857 $category = $this->get_category_name( $post, $parsely_options );
858 $post_id = $parsely_options['content_id_prefix'] . get_the_ID();
859
860 if ( has_post_thumbnail( $post ) ) {
861 $image_id = get_post_thumbnail_id( $post );
862 $image_url = wp_get_attachment_image_src( $image_id );
863 $image_url = $image_url[0];
864 } else {
865 $image_url = $this->get_first_image( $post );
866 }
867
868 $tags = $this->get_tags( $post->ID );
869 if ( $parsely_options['cats_as_tags'] ) {
870 $tags = array_merge( $tags, $this->get_categories( $post->ID ) );
871 // add custom taxonomy values.
872 $tags = array_merge( $tags, $this->get_custom_taxonomy_values( $post, $parsely_options ) );
873 }
874 // the function 'mb_strtolower' is not enabled by default in php, so this check
875 // falls back to the native php function 'strtolower' if necessary.
876 if ( function_exists( 'mb_strtolower' ) ) {
877 $lowercase_callback = 'mb_strtolower';
878 } else {
879 $lowercase_callback = 'strtolower';
880 }
881 if ( $parsely_options['lowercase_tags'] ) {
882 $tags = array_map( $lowercase_callback, $tags );
883 }
884
885 /**
886 * Filters the post tags that are used as metadata keywords.
887 *
888 * @since 1.8.0
889 *
890 * @param string[] $tags Post tags.
891 * @param int $ID Post ID.
892 */
893 $tags = apply_filters( 'wp_parsely_post_tags', $tags, $post->ID );
894 $tags = array_map( array( $this, 'get_clean_parsely_page_value' ), $tags );
895 $tags = array_values( array_unique( $tags ) );
896
897 $parsely_page['@type'] = 'NewsArticle';
898 $parsely_page['mainEntityOfPage'] = array(
899 '@type' => 'WebPage',
900 '@id' => $this->get_current_url( 'post' ),
901 );
902 $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_the_title( $post ) );
903 $parsely_page['url'] = $this->get_current_url( 'post', $post->ID );
904 $parsely_page['thumbnailUrl'] = $image_url;
905 $parsely_page['image'] = array(
906 '@type' => 'ImageObject',
907 'url' => $image_url,
908 );
909 $parsely_page['dateCreated'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true, $post ) );
910 $parsely_page['datePublished'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true, $post ) );
911 if ( get_the_modified_date( 'U', true ) >= get_post_time( 'U', true, $post ) ) {
912 $parsely_page['dateModified'] = gmdate( 'Y-m-d\TH:i:s\Z', get_the_modified_date( 'U', true ) );
913 } else {
914 // Use the post time as the earliest possible modification date.
915 $parsely_page['dateModified'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true, $post ) );
916 }
917 $parsely_page['articleSection'] = $category;
918 $author_objects = array();
919 foreach ( $authors as $author ) {
920 $author_tag = array(
921 '@type' => 'Person',
922 'name' => $author,
923 );
924 array_push( $author_objects, $author_tag );
925 }
926 $parsely_page['author'] = $author_objects;
927 $parsely_page['creator'] = $authors;
928 $parsely_page['publisher'] = array(
929 '@type' => 'Organization',
930 'name' => get_bloginfo( 'name' ),
931 'logo' => $parsely_options['logo'],
932 );
933 $parsely_page['keywords'] = $tags;
934 } elseif ( in_array( get_post_type(), $parsely_options['track_page_types'], true ) && 'publish' === $post->post_status ) {
935 $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_the_title( $post ) );
936 $parsely_page['url'] = $this->get_current_url( 'post' );
937 }
938
939 /**
940 * Filters the structured metadata.
941 *
942 * @since 1.10.0
943 *
944 * @param array $parsely_page Existing structured metadata for a page.
945 * @param WP_Post $post Post object.
946 * @param array $parsely_options The Parsely options.
947 */
948 $parsely_page = apply_filters( 'after_set_parsely_page', $parsely_page, $post, $parsely_options );
949 return $parsely_page;
950 }
951
952
953 /**
954 * Updates the Parsely metadata endpoint with the new metadata of the post.
955 *
956 * @param int $post_id id of the post to update.
957 * @return string
958 */
959 public function update_metadata_endpoint( $post_id ) {
960 $parsely_options = $this->get_options();
961
962 if ( empty( $parsely_options['apikey'] ) || empty( $parsely_options['metadata_secret'] ) ) {
963 return '';
964 }
965
966 $post = get_post( $post_id );
967 $metadata = $this->construct_parsely_metadata( $parsely_options, $post );
968 $page_type_mapping = array(
969 'NewsArticle' => 'post',
970 'WebPage' => 'index',
971 );
972
973 $endpoint_metadata = array(
974 'canonical_url' => $metadata['url'],
975 'page_type' => $page_type_mapping[ $metadata['@type'] ],
976 'title' => $metadata['headline'],
977 'image_url' => $metadata['thumbnailUrl'],
978 'pub_date_tmsp' => $metadata['datePublished'],
979 'section' => $metadata['articleSection'],
980 'authors' => $metadata['creator'],
981 'tags' => $metadata['keywords'],
982 );
983
984 $parsely_api_endpoint = 'https://api.parsely.com/v2/metadata/posts';
985 $parsely_metadata_secret = $parsely_options['metadata_secret'];
986 $headers = array(
987 'Content-Type' => 'application/json',
988 );
989 $body = wp_json_encode(
990 array(
991 'secret' => $parsely_metadata_secret,
992 'apikey' => $parsely_options['apikey'],
993 'metadata' => $endpoint_metadata,
994 )
995 );
996 $response = wp_remote_post(
997 $parsely_api_endpoint,
998 array(
999 'method' => 'POST',
1000 'headers' => $headers,
1001 'blocking' => false,
1002 'body' => $body,
1003 'data_format' => 'body',
1004 )
1005 );
1006 $current_timestamp = time();
1007 $meta_update = update_post_meta( $post_id, 'parsely_metadata_last_updated', $current_timestamp );
1008
1009 }
1010
1011
1012 /**
1013 * Updates posts with Parsely metadata api in bulk.
1014 */
1015 public function bulk_update_posts() {
1016 global $wpdb;
1017 $parsely_options = $this->get_options();
1018 $allowed_types = array_merge( $parsely_options['track_post_types'], $parsely_options['track_page_types'] );
1019 $allowed_types_string = implode(
1020 ', ',
1021 array_map(
1022 function( $v ) {
1023 return "'" . esc_sql( $v ) . "'";
1024 },
1025 $allowed_types
1026 )
1027 );
1028 $ids = wp_cache_get( 'parsely_post_ids_need_meta_updating' );
1029 if ( false === $ids ) {
1030 $ids = array();
1031 $results = $wpdb->get_results(
1032 $wpdb->prepare( "SELECT DISTINCT(id) FROM {$wpdb->posts} WHERE post_type IN (\" . %s . \") AND id NOT IN (SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = 'parsely_metadata_last_updated');", $allowed_types_string ),
1033 ARRAY_N
1034 );
1035 foreach ( $results as $result ) {
1036 array_push( $ids, $result[0] );
1037 }
1038 wp_cache_set( 'parsely_post_ids_need_meta_updating', $ids, '', 86400 );
1039 }
1040
1041 for ( $i = 0; $i < 100; $i++ ) {
1042 $post_id = array_pop( $ids );
1043 if ( null === $post_id ) {
1044 wp_clear_scheduled_hook( 'parsely_bulk_metas_update' );
1045 break;
1046 }
1047 $this->update_metadata_endpoint( $post_id );
1048 }
1049 }
1050
1051 /**
1052 * Inserts the JavaScript code required to send off beacon requests
1053 */
1054 public function insert_parsely_javascript() {
1055 $parsely_options = $this->get_options();
1056 // If we don't have an API key, there's no need to proceed.
1057 if ( empty( $parsely_options['apikey'] ) || $parsely_options['disable_javascript'] ) {
1058 return '';
1059 }
1060
1061 global $post;
1062 $display = true;
1063 if ( in_array( get_post_type(), $parsely_options['track_post_types'], true ) && 'publish' !== $post->post_status ) {
1064 $display = false;
1065 }
1066 if ( ! $parsely_options['track_authenticated_users'] && $this->parsely_is_user_logged_in() ) {
1067 $display = false;
1068 }
1069 if ( ! in_array( get_post_type(), $parsely_options['track_post_types'], true ) && ! in_array( get_post_type(), $parsely_options['track_page_types'], true ) ) {
1070 $display = false;
1071 }
1072
1073 /**
1074 * Filters whether to include the Parsely JavaScript file.
1075 *
1076 * If true, the file is included.
1077 *
1078 * @since 2.2.0
1079 *
1080 * @param bool $display True if the JavaScript file should be included. False if not.
1081 */
1082 if ( apply_filters( 'parsely_filter_insert_javascript', $display ) ) {
1083 include 'parsely-javascript.php';
1084 }
1085 }
1086
1087 /**
1088 * Print out the select tags
1089 *
1090 * @param array $args The arguments for the select drop downs.
1091 */
1092 public function print_select_tag( $args ) {
1093 $options = $this->get_options();
1094 $name = $args['option_key'];
1095 $select_options = $args['select_options'];
1096 if ( isset( $args['multiple'] ) ) {
1097 $multiple = $args['multiple'];
1098 } else {
1099 $multiple = false;
1100 }
1101 $selected = isset( $options[ $name ] ) ? $options[ $name ] : null;
1102 $id = esc_attr( $name );
1103 $name = self::OPTIONS_KEY . "[$id]";
1104
1105 if ( isset( $args['help_text'] ) ) {
1106 echo '<div class="parsely-form-controls" data-has-help-text="true">';
1107 }
1108 if ( isset( $args['requires_recrawl'] ) ) {
1109 echo '<div class="parsely-form-controls" data-requires-recrawl="true">';
1110 }
1111
1112 if ( $multiple ) {
1113 echo sprintf( "<select multiple='multiple' name='%s[]'id='%s'", esc_attr( $name ), esc_attr( $name ) );
1114 } else {
1115 echo sprintf( "<select name='%s' id='%s'", esc_attr( $name ), esc_attr( $name ) );
1116 }
1117
1118 echo '>';
1119
1120 foreach ( $select_options as $key => $val ) {
1121 echo '<option value="' . esc_attr( $key ) . '" ';
1122
1123 if ( $multiple ) {
1124 $selected = in_array( $val, $options[ $args['option_key'] ], true );
1125 echo selected( $selected, true, false ) . '>';
1126 } else {
1127 echo selected( $selected, $key, false ) . '>';
1128 }
1129 echo esc_html( $val );
1130 echo '</option>';
1131 }
1132 echo '</select>';
1133
1134 if ( isset( $args['help_text'] ) ) {
1135 if ( isset( $args['help_link'] ) ) {
1136 echo '<div class="help-text"> <p class="description">' .
1137 sprintf( esc_html( $args['help_text'] ), '<a href="', esc_url( $args['help_link'] ), '">', '</a>' ) .
1138 '</p></div>';
1139 } else {
1140 echo '<div class="help-text"> <p class="description">' . esc_html( $args['help_text'] ) . '</p></div>';
1141 }
1142 }
1143 echo '</div>';
1144 }
1145
1146 /**
1147 * Print out the radio buttons
1148 *
1149 * @param array $args The arguments for the radio buttons.
1150 */
1151 public function print_binary_radio_tag( $args ) {
1152 $options = $this->get_options();
1153 $name = $args['option_key'];
1154 $value = $options[ $name ];
1155 $id = esc_attr( $name );
1156 $name = self::OPTIONS_KEY . "[$id]";
1157
1158 if ( isset( $args['help_text'] ) ) {
1159 echo '<div class="parsely-form-controls" data-has-help-text="true">';
1160 }
1161 if ( isset( $args['requires_recrawl'] ) ) {
1162 echo '<div class="parsely-form-controls" data-requires-recrawl="true">';
1163 }
1164
1165 echo sprintf( "<input type='radio' name='%s' id='%s_true' value='true' ", esc_attr( $name ), esc_attr( $id ) );
1166 echo checked( true === $value, true, false );
1167 echo sprintf( " /> <label for='%s_true'>Yes</label> <input type='radio' name='%s' id='%s_false' value='false' ", esc_attr( $id ), esc_attr( $name ), esc_attr( $id ) );
1168 echo checked( true !== $value, true, false );
1169 echo sprintf( " /> <label for='%s_false'>No</label>", esc_attr( $id ) );
1170
1171 if ( isset( $args['help_text'] ) ) {
1172 echo '<div class="help-text"><p class="description">' . esc_html( $args['help_text'] ) . '</p></div>';
1173 }
1174 echo '</div>';
1175
1176 }
1177
1178 /**
1179 * Prints a checkbox tag in the settings page.
1180 *
1181 * @param array $args Arguments to print to checkbox tag.
1182 */
1183 public function print_checkbox_tag( $args ) {
1184 $options = $this->get_options();
1185 $name = $args['option_key'];
1186 $value = $options[ $name ];
1187 $id = esc_attr( $name );
1188 $name = self::OPTIONS_KEY . "[$id]";
1189
1190 if ( isset( $args['help_text'] ) ) {
1191 echo '<div class="parsely-form-controls" data-has-help-text="true">';
1192 }
1193 if ( isset( $args['requires_recrawl'] ) ) {
1194 echo '<div class="parsely-form-controls" data-requires-recrawl="true">';
1195 }
1196
1197 echo sprintf( "<input type='checkbox' name='%s' id='%s_true' value='true' ", esc_attr( $name ), esc_attr( $id ) );
1198 echo checked( true === $value, true, false );
1199 echo sprintf( " /> <label for='%s_true'>Yes</label>", esc_attr( $id ) );
1200
1201 if ( isset( $args['help_text'] ) ) {
1202 echo '<div class="help-text"><p class="description">' . esc_html( $args['help_text'] ) . '</p></div>';
1203 }
1204 echo '</div>';
1205
1206 }
1207
1208 /**
1209 * Print out the radio buttons
1210 *
1211 * @param array $args The arguments for text tags.
1212 */
1213 public function print_text_tag( $args ) {
1214 $options = $this->get_options();
1215 $name = $args['option_key'];
1216 $value = isset( $options[ $name ] ) ? $options[ $name ] : '';
1217 $optional_args = isset( $args['optional_args'] ) ? $args['optional_args'] : array();
1218 $id = esc_attr( $name );
1219 $name = self::OPTIONS_KEY . "[$id]";
1220 $value = esc_attr( $value );
1221 $accepted_args = array( 'placeholder' );
1222
1223 if ( isset( $args['help_text'] ) ) {
1224 echo '<div class="parsely-form-controls" data-has-help-text="true">';
1225 }
1226 if ( isset( $args['requires_recrawl'] ) ) {
1227 echo '<div class="parsely-form-controls" data-requires-recrawl="true">';
1228 }
1229
1230 echo sprintf( "<input type='text' name='%s' id='%s' value='%s'", esc_attr( $name ), esc_attr( $id ), esc_attr( $value ) );
1231 foreach ( $optional_args as $key => $val ) {
1232 if ( in_array( $key, $accepted_args, true ) ) {
1233 echo ' ' . esc_attr( $key ) . '="' . esc_attr( $val ) . '"';
1234 }
1235 }
1236 if ( isset( $args['requires_recrawl'] ) ) {
1237 echo ' data-requires-recrawl="true"';
1238 }
1239 echo ' />';
1240
1241 if ( isset( $args['help_text'] ) ) {
1242 if ( isset( $args['help_link'] ) ) {
1243 echo ' <div class="help-text" id="' .
1244 esc_attr( $args['option_key'] ) .
1245 '_help_text"><p class="description">' .
1246 sprintf( esc_html( $args['help_text'] ), '<a href="', esc_url( $args['help_link'] ), '">', '</a>' ) .
1247 '</p>' .
1248 '</div>';
1249 } else {
1250 echo ' <div class="help-text" id="' .
1251 esc_attr( $args['option_key'] ) .
1252 '_help_text"><p class="description">' .
1253 esc_html( $args['help_text'] ) . '</p>' .
1254 '</div>';
1255 }
1256 }
1257 }
1258
1259 /**
1260 * Returns default logo if one can be found
1261 */
1262 private function get_logo_default() {
1263 $custom_logo_id = get_theme_mod( 'custom_logo' );
1264 if ( $custom_logo_id ) {
1265 $logo_attrs = wp_get_attachment_image_src( $custom_logo_id, 'full' );
1266 if ( $logo_attrs ) {
1267 return $logo_attrs[0];
1268 }
1269 }
1270
1271 // get_site_icon_url returns an empty string if one isn't found,
1272 // which is what we want to use as the default anyway.
1273 return get_site_icon_url();
1274 }
1275
1276 /**
1277 * Extracts a host ( not TLD ) from a URL
1278 *
1279 * @param string $url The url of the host.
1280 * @return string $url The host of the url…
1281 */
1282 private function get_host_from_url( $url ) {
1283 if ( preg_match( '/^https?:\/\/( [^\/]+ )\/.*$/', $url, $matches ) ) {
1284 return $matches[1];
1285 }
1286
1287 return $url;
1288 }
1289
1290 /**
1291 * Returns the tags associated with this page or post
1292 *
1293 * @param string $post_id The id of the post you're trying to get tags for.
1294 * @return array $tags The tags of the post represented by the post id.
1295 */
1296 private function get_tags( $post_id ) {
1297 $tags = array();
1298 $wp_tags = wp_get_post_tags( $post_id );
1299 foreach ( $wp_tags as $wp_tag ) {
1300 array_push( $tags, $wp_tag->name );
1301 }
1302
1303 return $tags;
1304 }
1305
1306 /**
1307 * Returns an array of all the child categories for the current post
1308 *
1309 * @param string $post_id The id of the post you're trying to get categories for.
1310 * @param string $delimiter What character will delimit the categories.
1311 * @return array $tags all the child categories of the current post.
1312 */
1313 private function get_categories( $post_id, $delimiter = '/' ) {
1314 $tags = array();
1315 $categories = get_the_category( $post_id );
1316 foreach ( $categories as $category ) {
1317 $hierarchy = get_category_parents( $category, false, $delimiter );
1318 $hierarchy = rtrim( $hierarchy, '/' );
1319 array_push( $tags, $hierarchy );
1320 }
1321 // take last element in the hierarchy, a string representing the full parent->child tree,
1322 // and split it into individual category names.
1323 $tags = explode( '/', end( $tags ) );
1324 // remove uncategorized value from tags.
1325 $tags = array_diff( $tags, array( 'Uncategorized' ) );
1326 return $tags;
1327 }
1328
1329 /**
1330 * Safely returns options for the plugin by assigning defaults contained in optionDefaults. As soon as actual
1331 * options are saved, they override the defaults. This prevents us from having to do a lot of isset() checking
1332 * on variables.
1333 */
1334 private function get_options() {
1335 $options = get_option( self::OPTIONS_KEY );
1336 if ( false === $options ) {
1337 $options = $this->option_defaults;
1338 } else {
1339 $options = array_merge( $this->option_defaults, $options );
1340 }
1341 return $options;
1342 }
1343
1344 /**
1345 * Returns a properly cleaned category/taxonomy value and will optionally use the top-level category/taxonomy value
1346 * if so instructed via the `use_top_level_cats` option.
1347 *
1348 * @param WP_Post $post_obj The object for the post.
1349 * @param array $parsely_options The parsely options.
1350 * @return string $category Cleaned category name for for post in question.
1351 */
1352 private function get_category_name( $post_obj, $parsely_options ) {
1353 $taxonomy_dropdown_choice = get_the_terms( $post_obj->ID, $parsely_options['custom_taxonomy_section'] );
1354 // Get top-level taxonomy name for chosen taxonomy and assign to $parent_name; it will be used
1355 // as the category value if 'use_top_level_cats' option is checked.
1356 // Assign as "Uncategorized" if no value is checked for the chosen taxonomy.
1357 $category = 'Uncategorized';
1358 if ( ! empty( $taxonomy_dropdown_choice ) ) {
1359 if ( $parsely_options['use_top_level_cats'] ) {
1360 $first_term = array_shift( $taxonomy_dropdown_choice );
1361 $term_name = $this->get_top_level_term( $first_term->term_id, $first_term->taxonomy );
1362 } else {
1363 $term_name = $this->get_bottom_level_term( $post_obj->ID, $parsely_options['custom_taxonomy_section'] );
1364 }
1365
1366 if ( $term_name ) {
1367 $category = $term_name;
1368 }
1369 }
1370
1371 /**
1372 * Filters the constructed category name that are used as metadata keywords.
1373 *
1374 * @since 1.8.0
1375 *
1376 * @param string $category Category name.
1377 * @param WP_Post $post_obj Post object.
1378 * @param array $parsely_options The Parsely options.
1379 */
1380 $category = apply_filters( 'wp_parsely_post_category', $category, $post_obj, $parsely_options );
1381 $category = $this->get_clean_parsely_page_value( $category );
1382 return $category;
1383 }
1384
1385 /**
1386 * Return the top-most category/taxonomy value in a hierarcy given a taxonomy value's ID
1387 * ( WordPress calls taxonomy values 'terms' ).
1388 *
1389 * @param string $term_id The id of the top level term.
1390 * @param string $taxonomy_name The name of the taxonomy.
1391 * @return string $parent The top level name of the category / taxonomy.
1392 */
1393 private function get_top_level_term( $term_id, $taxonomy_name ) {
1394 $parent = get_term_by( 'id', $term_id, $taxonomy_name );
1395 while ( false !== $parent && 0 !== $parent->parent ) {
1396 $parent = get_term_by( 'id', $parent->parent, $taxonomy_name );
1397 }
1398 return $parent ? $parent->name : false;
1399 }
1400
1401 /**
1402 * Return the bottom-most category/taxonomy value in a hierarcy given a post ID
1403 * ( WordPress calls taxonomy values 'terms' ).
1404 *
1405 * @param string $post_id The post id you're interested in.
1406 * @param string $taxonomy_name The name of the taxonomy.
1407 * @return string name of the custom taxonomy.
1408 */
1409 private function get_bottom_level_term( $post_id, $taxonomy_name ) {
1410 $terms = get_the_terms( $post_id, $taxonomy_name );
1411 $term_ids = is_array( $terms ) ? wp_list_pluck( $terms, 'term_id' ) : null;
1412 $parents = is_array( $terms ) ? array_filter( wp_list_pluck( $terms, 'parent' ) ) : null;
1413
1414 // Get array of IDs of terms which are not parents.
1415 $term_ids_not_parents = array_diff( $term_ids, $parents );
1416 // Get corresponding term objects, which are mapped to array index keys.
1417 $terms_not_parents = array_intersect_key( $terms, $term_ids_not_parents );
1418 // remove array index keys.
1419 $terms_not_parents_cleaned = array();
1420 foreach ( $terms_not_parents as $index => $value ) {
1421 array_push( $terms_not_parents_cleaned, $value );
1422 }
1423 // if you assign multiple child terms in a custom taxonomy, will only return the first.
1424 return $terms_not_parents_cleaned[0]->name;
1425 }
1426
1427 /**
1428 * Get all term values from custom taxonomies.
1429 *
1430 * @param WP_Post $post_obj The post object.
1431 * @param array $parsely_options The pparsely options.
1432 */
1433 private function get_custom_taxonomy_values( $post_obj, $parsely_options ) {
1434 // filter out default WordPress taxonomies.
1435 $all_taxonomies = array_diff( get_taxonomies(), array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) );
1436 $all_values = array();
1437
1438 if ( is_array( $all_taxonomies ) ) {
1439 foreach ( $all_taxonomies as $taxonomy ) {
1440 $custom_taxonomy_objects = get_the_terms( $post_obj->ID, $taxonomy );
1441 if ( is_array( $custom_taxonomy_objects ) ) {
1442 foreach ( $custom_taxonomy_objects as $custom_taxonomy_object ) {
1443 array_push( $all_values, $custom_taxonomy_object->name );
1444 }
1445 }
1446 }
1447 }
1448 return $all_values;
1449 }
1450
1451 /**
1452 * Returns a list of coauthors for a post assuming the coauthors plugin is
1453 * installed. Borrowed from
1454 * https://github.com/Automattic/Co-Authors-Plus/blob/master/template-tags.php#L3-35
1455 *
1456 * @param string $post_id The id of the post.
1457 */
1458 private function get_coauthor_names( $post_id ) {
1459 $coauthors = array();
1460 if ( class_exists( 'coauthors_plus' ) ) {
1461 global $post, $post_ID, $coauthors_plus, $wpdb;
1462
1463 $post_id = (int) $post_id;
1464 if ( ! $post_id && $post_ID ) {
1465 $post_id = $post_ID;
1466 }
1467
1468 if ( ! $post_id && $post ) {
1469 $post_id = $post->ID;
1470 }
1471
1472 if ( $post_id ) {
1473 $coauthor_terms = get_the_terms( $post_id, $coauthors_plus->coauthor_taxonomy );
1474
1475 if ( is_array( $coauthor_terms ) && ! empty( $coauthor_terms ) ) {
1476 foreach ( $coauthor_terms as $coauthor ) {
1477 $coauthor_slug = preg_replace( '#^cap\-#', '', $coauthor->slug );
1478 $post_author = $coauthors_plus->get_coauthor_by( 'user_nicename', $coauthor_slug );
1479 // In case the user has been deleted while plugin was deactivated.
1480 if ( ! empty( $post_author ) ) {
1481 $coauthors[] = $post_author;
1482 }
1483 }
1484 } elseif ( ! $coauthors_plus->force_guest_authors ) {
1485 if ( $post && $post_id === $post->ID ) {
1486 $post_author = get_userdata( $post->post_author );
1487 }
1488 if ( ! empty( $post_author ) ) {
1489 $coauthors[] = $post_author;
1490 }
1491 } // the empty else case is because if we force guest authors, we don't ever care what value wp_posts.post_author has.
1492 }
1493 }
1494 return $coauthors;
1495 }
1496
1497 /**
1498 * Determine author name from display name, falling back to firstname
1499 * lastname, then nickname and finally the nicename.
1500 *
1501 * @param WP_User $author The author of the post.
1502 */
1503 private function get_author_name( $author ) {
1504 // gracefully handle situation where no author is available.
1505 if ( empty( $author ) || ! is_object( $author ) ) {
1506 return '';
1507 }
1508 $author_name = $author->display_name;
1509 if ( ! empty( $author_name ) ) {
1510 return $author_name;
1511 }
1512
1513 $author_name = $author->user_firstname . ' ' . $author->user_lastname;
1514 if ( ' ' !== $author_name ) {
1515 return $author_name;
1516 }
1517
1518 $author_name = $author->nickname;
1519 if ( ! empty( $author_name ) ) {
1520 return $author_name;
1521 }
1522
1523 return $author->user_nicename;
1524 }
1525
1526 /**
1527 * Retrieve all the authors for a post as an array. Can include multiple
1528 * authors if coauthors plugin is in use.
1529 *
1530 * @param WP_Post $post The post object.
1531 * @return array
1532 */
1533 private function get_author_names( $post ) {
1534 $authors = $this->get_coauthor_names( $post->ID );
1535 if ( empty( $authors ) ) {
1536 $authors = array( get_user_by( 'id', $post->post_author ) );
1537 }
1538
1539 /**
1540 * Filters the list of author WP_User objects for a post.
1541 *
1542 * @since 1.14.0
1543 *
1544 * @param array $authors One or more authors as WP_User objects (may also be `false`).
1545 * @param WP_Post $post Post object.
1546 */
1547 $authors = apply_filters( 'wp_parsely_pre_authors', $authors, $post );
1548
1549 $authors = array_map( array( $this, 'get_author_name' ), $authors );
1550
1551 /**
1552 * Filters the list of author names for a post.
1553 *
1554 * @since 1.14.0
1555 *
1556 * @param string[] $authors One or more author names.
1557 * @param WP_Post $post Post object.
1558 */
1559 $authors = apply_filters( 'wp_parsely_post_authors', $authors, $post );
1560 $authors = array_map( array( $this, 'get_clean_parsely_page_value' ), $authors );
1561 return $authors;
1562 }
1563
1564 /**
1565 * Sanitize content
1566 *
1567 * @param string $val The content you'd like sanitized.
1568 * @return string
1569 */
1570 private function get_clean_parsely_page_value( $val ) {
1571 if ( is_string( $val ) ) {
1572 $val = str_replace( "\n", '', $val );
1573 $val = str_replace( "\r", '', $val );
1574 $val = wp_strip_all_tags( $val );
1575 $val = trim( $val );
1576 return $val;
1577 }
1578
1579 return $val;
1580 }
1581
1582
1583 /**
1584 * Get the URL of the plugin settings page
1585 */
1586 private function get_settings_url() {
1587 return admin_url( 'options-general.php?page=' . self::MENU_SLUG );
1588 }
1589
1590
1591 /**
1592 * Get the URL of the current PHP script.
1593 * A fall-back implementation to determine permalink
1594 *
1595 * @param string $post The post object you're interested in.
1596 * @param int $post_id id of the post you want to get the url for. Optional.
1597 * @return string|void
1598 */
1599 private function get_current_url( $post = 'nonpost', $post_id = 0 ) {
1600 $options = $this->get_options();
1601 $scheme = ( $options['force_https_canonicals'] ? 'https://' : 'http://' );
1602
1603 if ( 'post' === $post ) {
1604 $permalink = get_permalink( $post_id );
1605
1606 /**
1607 * Filters the list of author names for a post.
1608 *
1609 * @since 1.14.0
1610 *
1611 * @param string $permalink The permalink URL or false if post does not exist.
1612 * @param string $post Post object type group ("post" or "nonpost").
1613 */
1614 $permalink = apply_filters( 'wp_parsely_permalink', $permalink, $post );
1615 $parsed_canonical = wp_parse_url( $permalink );
1616 // handle issue if wp_parse_url doesn't return good host & path data, fallback to page url as a last resort.
1617 if ( isset( $parsed_canonical['host'], $parsed_canonical['path'] ) ) {
1618 $canonical = $scheme . $parsed_canonical['host'] . $parsed_canonical['path'];
1619 } elseif ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) { // Input var okay.
1620 $canonical = $scheme . sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) . sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); // Input var okay.
1621 }
1622
1623 return $canonical;
1624 }
1625 $page_url = site_url( null, $scheme );
1626
1627 if ( isset( $_SERVER['SERVER_PORT'] ) ) { // Input var okay.
1628 $port_number = intval( $_SERVER['SERVER_PORT'] ); // Input var okay.
1629 }
1630 if ( 80 !== $port_number && 443 !== $port_number ) {
1631 $page_url .= ':' . $port_number;
1632 }
1633 if ( isset( $_SERVER['REQUEST_URI'] ) ) { // Input var okay.
1634 $page_url .= sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); // Input var okay.
1635 }
1636 return $page_url;
1637 }
1638
1639 /**
1640 * Get the first image from a post
1641 * https://css-tricks.com/snippets/wordpress/get-the-first-image-from-a-post/
1642 *
1643 * @param WP_Post $post The post object you're interested in.
1644 * @return mixed|string
1645 */
1646 public function get_first_image( $post ) {
1647 ob_start();
1648 ob_end_clean();
1649 if ( preg_match_all( '/<img.+src=[\'"]( [^\'"]+ )[\'"].*>/i', $post->post_content, $matches ) ) {
1650 return $matches[1][0];
1651 }
1652 return '';
1653 }
1654
1655 /**
1656 * Add parsely tracking to facebook instant articles
1657 *
1658 * @param type $registry The registry info for fbia.
1659 * @return string
1660 */
1661 public function insert_parsely_tracking_fbia( &$registry ) {
1662 $options = $this->get_options();
1663 $display_name = 'Parsely Analytics';
1664 $identifier = 'parsely-analytics-for-wordpress';
1665
1666 $embed_code = '<script>
1667 PARSELY = {
1668 autotrack: false,
1669 onload: function() {
1670 PARSELY.beacon.trackPageView({
1671 urlref: \'http://facebook.com/instantarticles\'
1672 });
1673 return true;
1674 }
1675 }
1676 </script>
1677 <script data-cfasync="false" id="parsely-cfg" data-parsely-site="' . esc_attr( $options['apikey'] ) . '" src="//cdn.parsely.com/keys/' . esc_attr( $options['apikey'] ) . '/p.js"></script>
1678 <!-- END Parse.ly Include: Standard -->';
1679
1680 $registry[ $identifier ] = array(
1681 'name' => $display_name,
1682 'payload' => $embed_code,
1683 );
1684
1685 return $embed_code;
1686 }
1687
1688 /**
1689 * Add amp actions.
1690 */
1691 public function parsely_add_amp_actions() {
1692 if ( ! function_exists( 'is_amp_endpoint' ) || ! is_amp_endpoint() ) {
1693 return '';
1694 }
1695
1696 $options = $this->get_options();
1697
1698 if ( $options['disable_amp'] ) {
1699 return '';
1700 }
1701
1702 add_filter( 'amp_post_template_analytics', array( $this, 'parsely_add_amp_analytics' ) );
1703 add_filter( 'amp_analytics_entries', array( $this, 'parsely_add_amp_native_analytics' ) );
1704 }
1705
1706 /**
1707 * Add amp analytics.
1708 *
1709 * @param type $analytics The analytics object you want to add.
1710 * @return type
1711 */
1712 public function parsely_add_amp_analytics( $analytics ) {
1713 $options = $this->get_options();
1714
1715 if ( empty( $options['apikey'] ) ) {
1716 return $analytics;
1717 }
1718
1719 $analytics['parsely'] = array(
1720 'type' => 'parsely',
1721 'attributes' => array(),
1722 'config_data' => array(
1723 'vars' => array(
1724 'apikey' => $options['apikey'],
1725 ),
1726 ),
1727 );
1728
1729 return $analytics;
1730 }
1731
1732 /**
1733 * Add amp native analytics.
1734 *
1735 * @param type $analytics The analytics object you want to add.
1736 * @return string|type
1737 */
1738 public function parsely_add_amp_native_analytics( $analytics ) {
1739 $options = $this->get_options();
1740
1741 if ( ! empty( $options['disable_amp'] ) && true === $options['disable_amp'] ) {
1742 return '';
1743 }
1744
1745 if ( empty( $options['apikey'] ) ) {
1746 return $analytics;
1747 }
1748
1749 $analytics['parsely'] = array(
1750 'type' => 'parsely',
1751 'attributes' => array(),
1752 'config' => wp_json_encode(
1753 array(
1754 'vars' => array(
1755 'apikey' => $options['apikey'],
1756 ),
1757 )
1758 ),
1759 );
1760
1761 return $analytics;
1762 }
1763
1764 /**
1765 * Check to see if parsely user is logged in
1766 */
1767 public function parsely_is_user_logged_in() {
1768 // can't use $blog_id here because it futzes with the global $blog_id.
1769 $current_blog_id = get_current_blog_id();
1770 $current_user_id = get_current_user_id();
1771 return is_user_member_of_blog( $current_user_id, $current_blog_id );
1772 }
1773
1774 /**
1775 * Why is this here?
1776 */
1777 public function return_personalized_json() {
1778
1779 }
1780 }
1781
1782
1783
1784
1785 if ( class_exists( 'Parsely' ) ) {
1786 define( 'PARSELY_VERSION', Parsely::VERSION );
1787 $parsely = new Parsely();
1788 }
1789
1790 require 'class-parsely-recommended-widget.php';
1791