PluginProbe
Parse.ly / 2.1.3
Parse.ly v2.1.3
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.3, at wp-parsely.php

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