PluginProbe
Parse.ly / 2.5.1
Parse.ly v2.5.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 / class-parsely.php

class-parsely.php in Parse.ly 2.5.1, at src/class-parsely.php

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