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

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

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