PluginProbe
Parse.ly / 2.1.2
Parse.ly v2.1.2
3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 3.20.3 All 105 releases
wp-parsely / wp-parsely.php

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

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