PluginProbe
Parse.ly / 1.12
Parse.ly v1.12
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 1.12, at wp-parsely.php

1,143 lines 41.4 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: 1.12
8 Requires at least: 4.0.0
9 Author URI: http://www.parsely.com/
10 License: GPL2
11
12 Copyright 2012 Parsely Incorporated
13
14 This program is free software; you can redistribute it and/or modify
15 it under the terms of the GNU General Public License, version 2, as
16 published by the Free Software Foundation.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26
27 Authors: Mike Sukmanowsky ( mike@parsely.com), Xand Lourenco ( xand@parsely.com ), James O'Toole (james.otoole@parsely.com )
28 */
29
30 /* TODO List:
31 * Wordpress Network support - going to hold off on any specific support here as content id prefix should work ok for now
32 * Allow the user to map get_post_types() to Parse.ly post types
33 * Support: is_search(), is_404()
34 */
35
36 class Parsely {
37 /**
38 * @codeCoverageIgnoreStart
39 */
40 const VERSION = '1.12';
41 const MENU_SLUG = 'parsely'; // Defines the page param passed to options-general.php
42 const MENU_TITLE = 'Parse.ly'; // Text to be used for the menu as seen in Settings sub-menu
43 const MENU_PAGE_TITLE = 'Parse.ly > Settings'; // Text shown in <title></title> when the settings screen is viewed
44 const OPTIONS_KEY = 'parsely'; // Defines the key used to store options in the WP database
45 const CAPABILITY = 'manage_options'; // The capability required for the user to administer settings
46
47 private $option_defaults = array(
48 'apikey' => '',
49 'content_id_prefix' => '',
50 'api_secret' => '',
51 'use_top_level_cats' => false,
52 'custom_taxonomy_section' => 'category',
53 'cats_as_tags' => false,
54 'track_authenticated_users' => true,
55 'lowercase_tags' => true,
56 'force_https_canonicals' => false,
57 'track_post_types' => array( 'post' ),
58 'track_page_types' => array( 'page' ),
59 'disable_javascript' => false,
60 'meta_type' => 'json_ld',
61 );
62
63 private $implementation_opts = array(
64 'standard' => 'Standard',
65 'dom_free' => 'DOM-Free',
66 );
67
68 public function __construct() {
69 // Run upgrade options if they exist for the version currently defined
70 $options = $this->get_options();
71 if ( empty( $options['plugin_version'] ) || Parsely::VERSION !== $options['plugin_version'] ) {
72 $method = 'upgrade_plugin_to_version_' . str_replace( '.', '_', Parsely::VERSION );
73 if ( method_exists( $this, $method ) ) {
74 call_user_func_array( array( $this, $method ), array( $options ) );
75 }
76 // Update our version info
77 $options['plugin_version'] = Parsely::VERSION;
78 update_option( Parsely::OPTIONS_KEY, $options );
79 }
80
81 // admin_menu and a settings link
82 add_action( 'admin_head', array( $this, 'add_admin_header' ) );
83 add_action( 'admin_menu', array( $this, 'add_settings_sub_menu' ) );
84 add_action( 'admin_init', array( $this, 'initialize_settings' ) );
85 // display warning when plugin hasn't been configured
86 add_action( 'admin_footer', array( $this, 'display_admin_warning' ) );
87
88 $basename = plugin_basename( __FILE__ );
89 add_filter( 'plugin_action_links_' . $basename,
90 array( $this, 'add_plugin_meta_links' ) );
91
92 // inserting parsely code
93 add_action( 'wp_head', array( $this, 'insert_parsely_page' ) );
94 add_action( 'wp_footer', array( $this, 'insert_parsely_javascript' ) );
95 add_action( 'instant_articles_compat_registry_analytics', array( $this, 'insert_parsely_tracking_fbia' ) );
96 add_action( 'pre_amp_render_post', array( $this, 'parsely_add_amp_actions' ) );
97 if ( ! defined( 'WP_PARSELY_TESTING' ) ) {
98 function wp_parsely_style_init() {
99 wp_enqueue_style( 'wp-parsely-style', plugins_url( 'wp-parsely.css', __FILE__ ), array(), filemtime( get_stylesheet_directory() ) );
100 }
101
102 function ensure_jquery_exists() {
103 wp_enqueue_script( 'jquery' );
104 }
105 add_action( 'wp_enqueue_scripts', 'wp_parsely_style_init' );
106 add_action( 'wp_enqueue_scripts', 'ensure_jquery_exists' );
107 }
108
109 }
110
111 public function add_admin_header() {
112 include( 'parsely-admin-header.php' );
113 }
114
115 /* Parsely settings page in Wordpress settings menu. */
116 public function add_settings_sub_menu() {
117 add_options_page( Parsely::MENU_PAGE_TITLE,
118 Parsely::MENU_TITLE,
119 Parsely::CAPABILITY,
120 Parsely::MENU_SLUG,
121 array( $this, 'display_settings' ) );
122 }
123
124 /* Parse.ly settings screen ( options-general.php?page=[MENU_SLUG] ) */
125 public function display_settings() {
126 if ( ! current_user_can( Parsely::CAPABILITY ) ) {
127 wp_die( esc_attr( 'You do not have sufficient permissions to access this page.' ) );
128 }
129
130 include( 'parsely-settings.php' );
131 }
132
133 public function initialize_settings() {
134 // All our options are actually stored in one single array to reduce
135 // DB queries
136 register_setting( Parsely::OPTIONS_KEY, Parsely::OPTIONS_KEY,
137 array( $this, 'validate_options' ) );
138
139 // Required Settings
140 add_settings_section( 'required_settings', 'Required Settings',
141 array( $this, 'print_required_settings' ),
142 Parsely::MENU_SLUG );
143
144 // API Key
145 $h = 'Your Site ID is your own site domain ( e.g. `mydomain.com` )';
146
147 $field_args = array(
148 'option_key' => 'apikey',
149 'help_text' => $h,
150 );
151 add_settings_field( 'apikey',
152 'Parse.ly Site ID <div class="help-icons"></div>',
153 array( $this, 'print_text_tag' ),
154 Parsely::MENU_SLUG, 'required_settings',
155 $field_args
156 );
157
158 // Optional Settings
159 add_settings_section( 'optional_settings', 'Optional Settings',
160 array( $this, 'print_optional_settings' ),
161 Parsely::MENU_SLUG
162 );
163
164 $h = 'Your API secret is your secret code to <a href="https://www.parse.ly/help/api/analytics/">access our API.</a>
165 It can be found at dash.parsely.com/yoursitedomain/settings/api
166 ( replace yoursitedown with your domain name, e.g. `mydomain.com` ) If you haven\'t purchased access to the API, and would
167 like to do so, email your account manager or support@parsely.com!';
168
169 $field_args = array(
170 'option_key' => 'api_secret',
171 'help_text' => $h,
172 );
173 add_settings_field( 'api_secret',
174 'Parse.ly API Secret <div class="help-icons"></div>',
175 array( $this, 'print_text_tag' ),
176 Parsely::MENU_SLUG, 'optional_settings',
177 $field_args
178 );
179
180 $h = 'Choose the metadata format for our crawlers to access .
181 (<a href="https://www.parse.ly/help/integration/jsonld/">https://www.parse.ly/help/integration/jsonld/</a> ' .
182 'Most publishers are fine with JSON-LD, but if you prefer to use our proprietary metadata format <br>' .
183 'then you can do so here.';
184 add_settings_field( 'meta_type',
185 'Metadata Format <div class="help-icons"></div>',
186 array( $this, 'print_select_tag' ),
187 Parsely::MENU_SLUG, 'optional_settings',
188 array(
189 'option_key' => 'meta_type',
190 'help_text' => $h,
191 // filter Wordpress taxonomies under the hood that should not appear in dropdown
192 'select_options' => array( 'json_ld' => 'json_ld', 'repeated_metas' => 'repeated_metas' ),
193 'requires_recrawl' => true,
194 'multiple' => false,
195 )
196 );
197
198 // Content ID Prefix
199 $h = 'If you use more than one content management system (e.g. ' .
200 'WordPress and Drupal), you may end up with duplicate content ' .
201 'IDs. Adding a Content ID Prefix will ensure the content IDs ' .
202 'from WordPress will not conflict with other content management ' .
203 'systems. We recommend using "WP-" for your prefix.';
204
205 $field_args = array(
206 'option_key' => 'content_id_prefix',
207 'optional_args' => array(
208 'placeholder' => 'WP-',
209 ),
210 'help_text' => $h,
211 'requires_recrawl' => true,
212 );
213 add_settings_field( 'content_id_prefix',
214 'Content ID Prefix <div class="help-icons"></div>',
215 array( $this, 'print_text_tag' ),
216 Parsely::MENU_SLUG, 'optional_settings',
217 $field_args
218 );
219
220 // Disable javascript
221 $h = 'If you use a separate system for Javascript tracking ( Tealium / Segment / other tag manager solution ) ' .
222 'you may want to use that instead of having the plugin load the tracker. WARNING: disabling this option ' .
223 'will also disable the "Personalize Results" section of the recommended widget! We highly recommend leaving ' .
224 'this option set to "No"!';
225 add_settings_field( 'disable_javascript',
226 'Disable Javascript <div class="help-icons"></div>',
227 array( $this, 'print_binary_radio_tag' ),
228 Parsely::MENU_SLUG, 'optional_settings',
229 array(
230 'option_key' => 'disable_javascript',
231 'help_text' => $h,
232 'requires_recrawl' => false,
233 )
234 );
235
236 // Use top-level cats
237 $h = 'wp-parsely will use the first category assigned to a post. ' .
238 'With this option selected, if you post a story to News > ' .
239 'National > Florida, wp-parsely will use the "News" for the ' .
240 'section name in your dashboard instead of "Florida".';
241 add_settings_field( 'use_top_level_cats',
242 'Use Top-Level Categories for Section <div class="help-icons"></div>',
243 array( $this, 'print_binary_radio_tag' ),
244 Parsely::MENU_SLUG, 'optional_settings',
245 array(
246 'option_key' => 'use_top_level_cats',
247 'help_text' => $h,
248 'requires_recrawl' => true,
249 )
250 );
251
252 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category
253 $h = 'By default, the section value in your Parse.ly dashboard maps to a post\'s category. ' .
254 'You can optionally choose a custom taxonomy, if you\'ve created one, to ' .
255 'populate the section value instead. <br>';
256 add_settings_field( 'custom_taxonomy_section',
257 'Use Custom Taxonomy for Section <div class="help-icons"></div>',
258 array( $this, 'print_select_tag' ),
259 Parsely::MENU_SLUG, 'optional_settings',
260 array(
261 'option_key' => 'custom_taxonomy_section',
262 'help_text' => $h,
263 // filter Wordpress taxonomies under the hood that should not appear in dropdown
264 'select_options' => array_diff( get_taxonomies(), array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) ),
265 'requires_recrawl' => true,
266 )
267 );
268
269 // Use categories and custom taxonomies as tags
270 $h = 'You can use this option to add all assigned categories and taxonomies to ' .
271 'your tags. For example, if you had a post assigned to ' .
272 'the categories: "Business/Tech", "Business/Social", your tags would include ' .
273 '"Business/Tech" and "Business/Social" in addition to your other tags.';
274 add_settings_field( 'cats_as_tags',
275 'Add Categories to Tags <div class="help-icons"></div>',
276 array( $this, 'print_binary_radio_tag' ),
277 Parsely::MENU_SLUG, 'optional_settings',
278 array(
279 'option_key' => 'cats_as_tags',
280 'help_text' => $h,
281 'requires_recrawl' => true,
282 )
283 );
284
285 // Track logged-in users
286 $h = 'By default, wp-parsely will track the activity of users that ' .
287 'are logged into this site. You can change this setting to only ' .
288 'track the activity of anonymous visitors. Note: You will no ' .
289 'longer see the Parse.ly tracking code on your site if you ' .
290 'browse while logged in.';
291 add_settings_field( 'track_authenticated_users',
292 'Track Logged-in Users <div class="help-icons"></div>',
293 array( $this, 'print_binary_radio_tag' ),
294 Parsely::MENU_SLUG, 'optional_settings',
295 array(
296 'option_key' => 'track_authenticated_users',
297 'help_text' => $h,
298 'requires_recrawl' => true,
299 )
300 );
301
302 // Lowercase all tags
303 $h = 'By default, wp-parsely will use lowercase versions of your ' .
304 'tags to correct for potential misspellings. You can change this ' .
305 'setting to ensure that tag names are used verbatim.';
306 add_settings_field( 'lowercase_tags',
307 'Lowercase All Tags <div class="help-icons"></div>',
308 array( $this, 'print_binary_radio_tag' ),
309 Parsely::MENU_SLUG, 'optional_settings',
310 array(
311 'option_key' => 'lowercase_tags',
312 'help_text' => $h,
313 'requires_recrawl' => true,
314 )
315 );
316
317 $h = 'wp-parsely uses http canonical URLs by default. If this needs to be forced to use https, set this option ' .
318 ' to true. Note: the default is fine for almost all publishers, it\'s unlikely you\'ll have to change this unless' .
319 ' directed to do so by a Parsely support rep.';
320 add_settings_field( 'force_https_canonicals',
321 'Force HTTPS canonicals <div class="help-icons"></div>',
322 array( $this, 'print_binary_radio_tag' ),
323 Parsely::MENU_SLUG, 'optional_settings',
324 array(
325 'option_key' => 'force_https_canonicals',
326 'help_text' => $h,
327 'requires_recrawl' => true,
328 )
329 );
330
331 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category
332 $h = 'By default, Parsely only tracks the default post type as a post page. ' .
333 'If you want to track custom post types, select them here!<br>';
334 add_settings_field( 'track_post_types',
335 'Post Types To Track <div class="help-icons"></div>',
336 array( $this, 'print_select_tag' ),
337 Parsely::MENU_SLUG, 'optional_settings',
338 array(
339 'option_key' => 'track_post_types',
340 'help_text' => $h,
341 // filter Wordpress taxonomies under the hood that should not appear in dropdown
342 'select_options' => get_post_types(),
343 'requires_recrawl' => true,
344 'multiple' => true,
345 )
346 );
347
348 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category
349 $h = 'By default, Parsely only tracks the default page type as a non-post page. ' .
350 'If you want to track custom post types as non-post pages, select them here!<br>';
351 add_settings_field( 'track_page_types',
352 'Page Types To Track <div class="help-icons"></div>',
353 array( $this, 'print_select_tag' ),
354 Parsely::MENU_SLUG, 'optional_settings',
355 array(
356 'option_key' => 'track_page_types',
357 'help_text' => $h,
358 // filter Wordpress taxonomies under the hood that should not appear in dropdown
359 'select_options' => get_post_types(),
360 'requires_recrawl' => true,
361 'multiple' => true,
362 )
363 );
364
365 // Dynamic tracking note
366 add_settings_field( 'dynamic_tracking_note', 'Note: ',
367 array( $this, 'print_dynamic_tracking_note' ),
368 Parsely::MENU_SLUG, 'optional_settings' );
369
370 }
371
372 public function validate_option_array( $array, $name ) {
373 $new_array = $array;
374 foreach ( $array as $key => $val ) {
375 $new_array[ $key ] = sanitize_text_field( $val );
376 }
377 return $new_array;
378 }
379
380 public function validate_options( $input ) {
381 if ( empty( $input['apikey'] ) ) {
382 add_settings_error( Parsely::OPTIONS_KEY, 'apikey',
383 'Please specify the Site ID' );
384 } else {
385 $input['apikey'] = strtolower( $input['apikey'] );
386 $input['apikey'] = sanitize_text_field( $input['apikey'] );
387 if ( strpos( $input['apikey'], '.' ) === false || strpos( $input['apikey'], ' ' ) !== false ) {
388 add_settings_error( Parsely::OPTIONS_KEY, 'apikey',
389 'Your Parse.ly Site ID looks incorrect, it should look like "example.com".' );
390 }
391 }
392 // these can't be null, if somebody accidentally deselected them just reset to default
393 if ( ! isset( $input['track_post_types'] ) ) {
394 $input['track_post_types'] = array( 'post' );
395
396 }
397 if ( ! isset( $input['track_page_types'] ) ) {
398 $input['track_page_types'] = array( 'page' );
399 }
400 $input['track_post_types'] = $this->validate_option_array( $input['track_post_types'], 'track_post_types' );
401 $input['track_page_types'] = $this->validate_option_array( $input['track_page_types'], 'track_page_types' );
402
403 $input['api_secret'] = sanitize_text_field( $input['api_secret'] );
404 // Content ID prefix
405 $input['content_id_prefix'] = sanitize_text_field( $input['content_id_prefix'] );
406 $input['custom_taxonomy_section'] = sanitize_text_field( $input['custom_taxonomy_section'] );
407
408 // Custom taxonomy as section
409
410 // Top-level categories
411 if ( 'true' !== $input['use_top_level_cats'] && 'false' !== $input['use_top_level_cats'] ) {
412 add_settings_error( Parsely::OPTIONS_KEY, 'use_top_level_cats',
413 'Value passed for use_top_level_cats must be either "true" or "false".' );
414 } else {
415 $input['use_top_level_cats'] = 'true' === $input['use_top_level_cats'] ? true : false;
416 }
417
418 // Child categories as tags
419 if ( 'true' !== $input['cats_as_tags'] && 'false' !== $input['cats_as_tags'] ) {
420 add_settings_error( Parsely::OPTIONS_KEY, 'cats_as_tags',
421 'Value passed for cats_as_tags must be either "true" or "false".' );
422 } else {
423 $input['cats_as_tags'] = 'true' === $input['cats_as_tags'] ? true : false;
424 }
425
426 // Track authenticated users
427 if ( 'true' !== $input['track_authenticated_users'] && 'false' !== $input['track_authenticated_users'] ) {
428 add_settings_error( Parsely::OPTIONS_KEY, 'track_authenticated_users',
429 'Value passed for track_authenticated_users must be either "true" or "false".' );
430 } else {
431 $input['track_authenticated_users'] = 'true' === $input['track_authenticated_users'] ? true : false;
432 }
433
434 // Lowercase tags
435 if ( 'true' !== $input['lowercase_tags'] && 'false' !== $input['lowercase_tags'] ) {
436 add_settings_error( Parsely::OPTIONS_KEY, 'lowercase_tags',
437 'Value passed for lowercase_tags must be either "true" or "false".' );
438 } else {
439 $input['lowercase_tags'] = 'true' === $input['lowercase_tags'] ? true : false;
440 }
441
442 if ( 'true' !== $input['force_https_canonicals'] && 'false' !== $input['force_https_canonicals'] ) {
443 add_settings_error( Parsely::OPTIONS_KEY, 'force_https_canonicals',
444 'Value passed for force_https_canonicals must be either "true" or "false".' );
445 } else {
446 $input['force_https_canonicals'] = 'true' === $input['force_https_canonicals'] ? true : false;
447 }
448
449 if ( 'true' !== $input['disable_javascript'] && 'false' !== $input['disable_javascript'] ) {
450 add_settings_error( Parsely::OPTIONS_KEY, 'disable_javascript',
451 'Value passed for disable_javascript must be either "true" or "false".' );
452 } else {
453 $input['disable_javascript'] = 'true' === $input['disable_javascript'] ? true : false;
454 }
455
456 return $input;
457 }
458
459 public function print_required_settings() {
460 // We can optionally print some text here in the future, but we don't
461 // need to now
462 }
463
464 public function print_optional_settings() {
465 // We can optionally print some text here in the future, but we don't
466 // need to now
467 return;
468 }
469
470 /**
471 * Adds a 'Settings' link to the Plugins screen in WP admin
472 */
473 public function add_plugin_meta_links( $links ) {
474 array_unshift( $links, '<a href="' . $this->get_settings_url() . '">' . __( 'Settings' ) . '</a>' );
475 return $links;
476 }
477
478 public function display_admin_warning() {
479 $options = $this->get_options();
480 if ( ! isset( $options['apikey'] ) || empty( $options['apikey'] ) ) {
481 ?>
482 <div id='message' class='error'>
483 <p>
484 <strong>Parse.ly - Dash plugin is not active.</strong>
485 You need to
486 <a href='<?php echo esc_html( $this->get_settings_url() ); ?>'>
487 provide your Parse.ly Dash Site ID
488 </a>
489 before things get cooking.
490 </p>
491 </div>
492 <?php
493 }
494 }
495
496 public function print_dynamic_tracking_note() {
497 $note = "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='http://www.parsely.com/help/integration/basic/'>the standard Parse.ly include</a> that the plugin injects into your page source. Please consult <a href='https://www.parsely.com/help/integration/dynamic/'>the Parse.ly documentation on dynamic tracking</a> for instructions on implementing dynamic tracking, or contact Parse.ly support (<a href='support@parsely.com'>support@parsely.com</a> ) for additional assistance.";
498 echo esc_html( $note );
499 }
500
501 /**
502 * @codeCoverageIgnoreEnd
503 */
504
505 /**
506 * Actually inserts the code for the <meta name='parsely-page'> parameter within the <head></head> tag.
507 */
508 public function insert_parsely_page() {
509 $parsely_options = $this->get_options();
510
511 // 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.
512 if ( empty( $parsely_options['apikey'] ) || ( ! $parsely_options['track_authenticated_users'] && $this->parsely_is_user_logged_in() ) ) {
513 return '';
514 }
515
516 global $wp_query;
517 global $post;
518 // Assign default values for LD+JSON
519 // TODO: Maping of an install's post types to Parse.ly post types ( namely page/post )
520 $parsely_page = array(
521 '@context' => 'http://schema.org',
522 '@type' => 'WebPage',
523 );
524
525 $current_url = $this->get_current_url();
526
527 if ( in_array( get_post_type(), $parsely_options['track_post_types'], true ) && 'publish' === $post->post_status ) {
528 $authors = $this->get_author_names( $post );
529 $category = $this->get_category_name( $post, $parsely_options );
530 $post_id = $parsely_options['content_id_prefix'] . (string) get_the_ID();
531
532 if ( has_post_thumbnail() ) {
533 $image_id = get_post_thumbnail_id();
534 $image_url = wp_get_attachment_image_src( $image_id );
535 $image_url = $image_url[0];
536 } else {
537 $image_url = $this->get_first_image( $post );
538 }
539
540 $tags = $this->get_tags( $post->ID );
541 if ( $parsely_options['cats_as_tags'] ) {
542 $tags = array_merge( $tags, $this->get_categories( $post->ID ) );
543 // add custom taxonomy values
544 $tags = array_merge( $tags, $this->get_custom_taxonomy_values( $post, $parsely_options ) );
545 }
546 // the function 'mb_strtolower' is not enabled by default in php, so this check
547 // falls back to the native php function 'strtolower' if necessary
548 if ( function_exists( 'mb_strtolower' ) ) {
549 $lowercase_callback = 'mb_strtolower';
550 } else {
551 $lowercase_callback = 'strtolower';
552 }
553 if ( $parsely_options['lowercase_tags'] ) {
554 $tags = array_map( $lowercase_callback, $tags );
555 }
556 $tags = apply_filters( 'wp_parsely_post_tags', $tags, $post->ID );
557 $tags = array_map( array( $this, 'get_clean_parsely_page_value' ), $tags );
558 $tags = array_values( array_unique( $tags ) );
559
560 $parsely_page['@type'] = 'NewsArticle';
561 $parsely_page['mainEntityOfPage'] = array(
562 '@type' => 'WebPage',
563 '@id' => $this->get_current_url( 'post' ),
564 );
565 $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_the_title() );
566 $parsely_page['url'] = $this->get_current_url( 'post' );
567 $parsely_page['thumbnailUrl'] = $image_url;
568 $parsely_page['image'] = array(
569 '@type' => 'ImageObject',
570 'url' => $image_url,
571 );
572 $parsely_page['dateCreated'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true ) );
573 $parsely_page['datePublished'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true ) );
574
575 if ( get_the_modified_date( 'U', true ) >= get_post_time( 'U', true ) ) {
576 $parsely_page['dateModified'] = gmdate( 'Y-m-d\TH:i:s\Z', get_the_modified_date( 'U', true ) );
577 } else {
578 // Use the post time as the earliest possible modification date
579 $parsely_page['dateModified'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true ) );
580 }
581
582 $author_objects = array();
583 foreach ( $authors as $author ) {
584 $author_tag = array(
585 '@type' => 'Person',
586 'name' => $author,
587 );
588 array_push( $author_objects, $author_tag );
589 }
590
591 $parsely_page['articleSection'] = $category;
592 $parsely_page['author'] = $author_objects;
593 $parsely_page['creator'] = $authors;
594 $parsely_page['keywords'] = $tags;
595
596 $parsely_page['publisher'] = array(
597 '@type' => 'Organization',
598 'name' => get_bloginfo( 'name' ),
599 );
600
601 } elseif ( in_array( get_post_type(), $parsely_options['track_page_types'], true ) && 'publish' === $post->post_status ) {
602 $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_the_title() );
603 $parsely_page['url'] = $this->get_current_url( 'post' );
604 } elseif ( is_author() ) {
605 // TODO: why can't we have something like a WP_User object for all the other cases? Much nicer to deal with than functions
606 $author = ( get_query_var( 'author_name' ) ) ? get_user_by( 'slug', get_query_var( 'author_name' ) ) : get_userdata( get_query_var( 'author' ) );
607 $parsely_page['headline'] = $this->get_clean_parsely_page_value( 'Author - ' . $author->data->display_name );
608 $parsely_page['url'] = $current_url;
609 } elseif ( is_category() ) {
610 $category = get_the_category();
611 $category = $category[0];
612 $parsely_page['headline'] = $this->get_clean_parsely_page_value( $category->name );
613 $parsely_page['url'] = $current_url;
614 } elseif ( is_date() ) {
615 if ( is_year() ) {
616 $parsely_page['headline'] = 'Yearly Archive - ' . get_the_time( 'Y' );
617 } elseif ( is_month() ) {
618 $parsely_page['headline'] = 'Monthly Archive - ' . get_the_time( 'F, Y' );
619 } elseif ( is_day() ) {
620 $parsely_page['headline'] = 'Daily Archive - ' . get_the_time( 'F jS, Y' );
621 } elseif ( is_time() ) {
622 $parsely_page['headline'] = 'Hourly, Minutely, or Secondly Archive - ' . get_the_time( 'F jS g:i:s A' );
623 }
624 $parsely_page['url'] = $current_url;
625 } elseif ( is_tag() ) {
626 $tag = single_tag_title( '', false );
627 if ( empty( $tag ) ) {
628 $tag = single_term_title( '', false );
629 }
630 $parsely_page['headline'] = $this->get_clean_parsely_page_value( 'Tagged - ' . $tag );
631 $parsely_page['url'] = $current_url;
632 } elseif ( is_front_page() ) {
633 $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_bloginfo( 'name', 'raw' ) );
634 $parsely_page['url'] = home_url();
635 }
636 $parsely_page = apply_filters( 'after_set_parsely_page', $parsely_page, $post, $parsely_options );
637 include( 'parsely-parsely-page.php' );
638 return $parsely_page;
639 }
640
641 /**
642 * Inserts the JavaScript code required to send off beacon requests
643 */
644 public function insert_parsely_javascript() {
645 $parsely_options = $this->get_options();
646 // If we don't have an API key, there's no need to proceed.
647 if ( empty( $parsely_options['apikey'] ) || $parsely_options['disable_javascript'] ) {
648 return '';
649 }
650
651 global $post;
652 $display = true;
653 if ( in_array( get_post_type(), $parsely_options['track_post_types'], true ) && 'publish' !== $post->post_status ) {
654 $display = false;
655 }
656 if ( ! $parsely_options['track_authenticated_users'] && $this->parsely_is_user_logged_in() ) {
657 $display = false;
658 }
659 if ( ! in_array( get_post_type(), $parsely_options['track_post_types'], true ) && ! in_array( get_post_type(), $parsely_options['track_page_types'], true ) ) {
660 $display = false;
661 }
662 if ( $display ) {
663 include( 'parsely-javascript.php' );
664 }
665 }
666
667 public function print_select_tag( $args ) {
668 $options = $this->get_options();
669 $name = $args['option_key'];
670 $select_options = $args['select_options'];
671 if ( isset( $args['multiple'] ) ) {
672 $multiple = $args['multiple'];
673 } else {
674 $multiple = false;
675 }
676 $selected = isset( $options[ $name ] ) ? $options[ $name ] : null;
677 $optional_args = isset( $args['optional_args'] ) ? $args['optional_args'] : array();
678 $id = esc_attr( $name );
679 $name = Parsely::OPTIONS_KEY . "[$id]";
680
681 $tag = '<div class="parsely-form-controls"';
682 if ( isset( $args['help_text'] ) ) {
683 $tag .= ' data-has-help-text="true"';
684 }
685 if ( isset( $args['requires_recrawl'] ) ) {
686 $tag .= ' data-requires-recrawl="true"';
687 }
688 $tag .= '>';
689
690 if ( $multiple ) {
691 $tag .= "<select multiple='multiple' name='$name" . "[]'" . "id='$name'";
692 } else {
693 $tag .= "<select name='$name' id='$name'";
694 }
695
696 foreach ( $optional_args as $key => $val ) {
697 $tag .= ' ' . esc_attr( $key ) . '="' . esc_attr( $val ) . '"';
698 }
699 $tag .= '>';
700
701 foreach ( $select_options as $key => $val ) {
702 $tag .= '<option value="' . esc_attr( $key ) . '" ';
703
704 if ( $multiple ) {
705 $selected = in_array( $val, $options[ $args['option_key'] ], true );
706 $tag .= selected( $selected, true, false ) . '>';
707 } else {
708 $tag .= selected( $selected, $key, false ) . '>';
709 }
710 $tag .= esc_html( $val );
711 $tag .= '</option>';
712 }
713 $tag .= '</select>';
714
715 if ( isset( $args['help_text'] ) ) {
716 $tag .= '<div class="help-text">' .
717 '<p class="description">' . $args['help_text'] . '</p>' .
718 '</div>';
719 }
720 $tag .= '</div>';
721 echo $tag;
722 }
723
724 public function print_binary_radio_tag( $args ) {
725 $options = $this->get_options();
726 $name = $args['option_key'];
727 $value = $options[ $name ];
728 $id = esc_attr( $name );
729 $name = Parsely::OPTIONS_KEY . "[$id]";
730
731 $tag = '<div class="parsely-form-controls"';
732 if ( isset( $args['help_text'] ) ) {
733 $tag .= ' data-has-help-text="true"';
734 }
735 if ( isset( $args['requires_recrawl'] ) ) {
736 $tag .= ' data-requires-recrawl="true"';
737 }
738 $tag .= '>';
739
740 $tag .= "<input type='radio' name='$name' id='$id" . "_true' value='true' " .
741 checked( true === $value, true, false ) . ' />' .
742 "<label for='$id" . "_true'>Yes</label> " .
743 "<input type='radio' name='$name' id='$id" . "_false' value='false' " .
744 checked( true !== $value, true, false ) . ' />' .
745 "<label for='$id" . "_false'>No</label>";
746
747 if ( isset( $args['help_text'] ) ) {
748 $tag .= '<div class="help-text">' .
749 '<p class="description">' . $args['help_text'] . '</p>' .
750 '</div>';
751 }
752 $tag .= '</div>';
753
754 echo $tag;
755 }
756
757 public function print_text_tag( $args ) {
758 $options = $this->get_options();
759 $name = $args['option_key'];
760 $value = isset( $options[ $name ] ) ? $options[ $name ] : '';
761 $optional_args = isset( $args['optional_args'] ) ? $args['optional_args'] : array();
762 $id = esc_attr( $name );
763 $name = Parsely::OPTIONS_KEY . "[$id]";
764 $value = esc_attr( $value );
765
766 $tag = '<div class="parsely-form-controls"';
767 if ( isset( $args['help_text'] ) ) {
768 $tag .= ' data-has-help-text="true"';
769 }
770 if ( isset( $args['requires_recrawl'] ) ) {
771 $tag .= ' data-requires-recrawl="true"';
772 }
773 $tag .= '>';
774
775 $tag .= "<input type='text' name='$name' id='$id' value='$value'";
776 foreach ( $optional_args as $key => $val ) {
777 $tag .= ' ' . esc_attr( $key ) . '="' . esc_attr( $val ) . '"';
778 }
779 if ( isset( $args['requires_recrawl'] ) ) {
780 $tag .= ' data-requires-recrawl="true"';
781 }
782 $tag .= ' />';
783
784 if ( isset( $args['help_text'] ) ) {
785 $tag .= ' <div class="help-text" id="' .
786 esc_attr( $args['option_key'] ) . '_help_text">' .
787 '<p class="description">' . $args['help_text'] . '</p>' .
788 '</div>';
789 }
790 echo $tag;
791 }
792
793 /**
794 * Extracts a host ( not TLD ) from a URL
795 */
796 private function get_host_from_url( $url ) {
797 if ( preg_match( '/^https?:\/\/( [^\/]+ )\/.*$/', $url, $matches ) ) {
798 return $matches[1];
799 } else {
800 return $url;
801 }
802 }
803
804 /**
805 * Returns the tags associated with this page or post
806 */
807 private function get_tags( $post_id ) {
808 $tags = array();
809 $wp_tags = wp_get_post_tags( $post_id );
810 foreach ( $wp_tags as $wp_tag ) {
811 array_push( $tags, $wp_tag->name );
812 }
813
814 return $tags;
815 }
816
817 /**
818 * Returns an array of all the child categories for the current post
819 */
820 private function get_categories( $post_id, $delimiter = '/' ) {
821 $tags = array();
822 $categories = get_the_category( $post_id );
823 foreach ( $categories as $category ) {
824 $hierarchy = get_category_parents( $category, false, $delimiter );
825 $hierarchy = rtrim( $hierarchy, '/' );
826 array_push( $tags, $hierarchy );
827 }
828 // take last element in the hierarchy, a string representing the full parent->child tree,
829 // and split it into individual category names
830 $tags = explode( '/', end( $tags ) );
831 // remove uncategorized value from tags
832 $tags = array_diff( $tags, array( 'Uncategorized' ) );
833 return $tags;
834 }
835
836 /**
837 * Safely returns options for the plugin by assigning defaults contained in optionDefaults. As soon as actual
838 * options are saved, they override the defaults. This prevents us from having to do a lot of isset() checking
839 * on variables.
840 */
841 private function get_options() {
842 $options = get_option( Parsely::OPTIONS_KEY );
843 if ( false === $options ) {
844 $options = $this->option_defaults;
845 } else {
846 $options = array_merge( $this->option_defaults, $options );
847 }
848 return $options;
849 }
850
851 /**
852 * Returns a properly cleaned category/taxonomy value and will optionally use the top-level category/taxonomy value
853 * if so instructed via the `use_top_level_cats` option.
854 */
855 private function get_category_name( $post_obj, $parsely_options ) {
856 $taxonomy_dropdown_choice = get_the_terms( $post_obj->ID, $parsely_options['custom_taxonomy_section'] );
857 // Get top-level taxonomy name for chosen taxonomy and assign to $parent_name; it will be used
858 // as the category value if 'use_top_level_cats' option is checked.
859 // Assign as "Uncategorized" if no value is checked for the chosen taxonomy.
860 if ( ! empty( $taxonomy_dropdown_choice ) ) {
861 $first_term = array_shift( $taxonomy_dropdown_choice );
862 $parent_name = $this->get_top_level_term( $first_term->term_id, $first_term->taxonomy );
863 $child_name = $this->get_bottom_level_term( $post_obj->ID, $parsely_options['custom_taxonomy_section'] );
864 $category = $parsely_options['use_top_level_cats'] ? $parent_name : $child_name;
865 } else {
866 $category = 'Uncategorized';
867 }
868 $category = apply_filters( 'wp_parsely_post_category', $category, $post_obj, $parsely_options );
869 $category = $this->get_clean_parsely_page_value( $category );
870 return $category;
871 }
872
873 /**
874 * Return the top-most category/taxonomy value in a hierarcy given a taxonomy value's ID
875 * ( Wordpress calls taxonomy values 'terms' ).
876 */
877 private function get_top_level_term( $term_id, $taxonomy_name ) {
878 $parent = get_term_by( 'id', $term_id, $taxonomy_name );
879 while ( 0 !== $parent->parent ) {
880 $parent = get_term_by( 'id', $parent->parent, $taxonomy_name );
881 }
882 return $parent->name;
883 }
884
885 private function get_bottom_level_term( $post_id, $taxonomy_name ) {
886 $terms = get_the_terms( $post_id, $taxonomy_name );
887 $term_ids = wp_list_pluck( $terms, 'term_id' );
888 $parents = array_filter( wp_list_pluck( $terms, 'parent' ) );
889
890 //Get array of IDs of terms which are not parents.
891 $term_ids_not_parents = array_diff( $term_ids, $parents );
892 //Get corresponding term objects, which are mapped to array index keys
893 $terms_not_parents = array_intersect_key( $terms, $term_ids_not_parents );
894 //remove array index keys
895 $terms_not_parents_cleaned = array();
896 foreach ( $terms_not_parents as $index => $value ) {
897 array_push( $terms_not_parents_cleaned, $value );
898 }
899 //if you assign multiple child terms in a custom taxonomy, will only return the first
900 return $terms_not_parents_cleaned[0]->name;
901 }
902
903 // Get all term values from custom taxonomies
904 private function get_custom_taxonomy_values( $post_obj, $parsely_options ) {
905 // filter out default WordPress taxonomies
906 $all_taxonomies = array_diff( get_taxonomies(), array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) );
907 $all_values = array();
908
909 if ( is_array( $all_taxonomies ) ) {
910 foreach ( $all_taxonomies as $taxonomy ) {
911 $custom_taxonomy_objects = get_the_terms( $post_obj->ID, $taxonomy );
912 if ( is_array( $custom_taxonomy_objects ) ) {
913 foreach ( $custom_taxonomy_objects as $custom_taxonomy_object ) {
914 array_push( $all_values, $custom_taxonomy_object->name );
915 }
916 }
917 }
918 }
919 return $all_values;
920 }
921
922 /**
923 * Returns a list of coauthors for a post assuming the coauthors plugin is
924 * installed. Borrowed from
925 * https://github.com/Automattic/Co-Authors-Plus/blob/master/template-tags.php#L3-35
926 */
927 private function get_coauthor_names( $post_id ) {
928 $coauthors = array();
929 if ( class_exists( 'coauthors_plus' ) ) {
930 global $post, $post_ID, $coauthors_plus, $wpdb;
931
932 $post_id = (int) $post_id;
933 if ( ! $post_id && $post_ID ) {
934 $post_id = $post_ID;
935 }
936
937 if ( ! $post_id && $post ) {
938 $post_id = $post->ID;
939 }
940
941 if ( $post_id ) {
942 $coauthor_terms = get_the_terms( $post_id, $coauthors_plus->coauthor_taxonomy );
943
944 if ( is_array( $coauthor_terms ) && ! empty( $coauthor_terms ) ) {
945 foreach ( $coauthor_terms as $coauthor ) {
946 $coauthor_slug = preg_replace( '#^cap\-#', '', $coauthor->slug );
947 $post_author = $coauthors_plus->get_coauthor_by( 'user_nicename', $coauthor_slug );
948 // In case the user has been deleted while plugin was deactivated
949 if ( ! empty( $post_author ) ) {
950 $coauthors[] = $post_author;
951 }
952 }
953 } elseif ( ! $coauthors_plus->force_guest_authors ) {
954 if ( $post && $post_id === $post->ID ) {
955 $post_author = get_userdata( $post->post_author );
956 }
957 if ( ! empty( $post_author ) ) {
958 $coauthors[] = $post_author;
959 }
960 } // the empty else case is because if we force guest authors, we don't ever care what value wp_posts.post_author has.
961 }
962 }
963 return $coauthors;
964 }
965
966 /**
967 * Determine author name from display name, falling back to firstname +
968 * lastname, then nickname and finally the nicename.
969 */
970 private function get_author_name( $author ) {
971 $author_name = $author->display_name;
972 if ( ! empty( $author_name ) ) {
973 return $author_name;
974 }
975
976 $author_name = $author->user_firstname . ' ' . $author->user_lastname;
977 if ( ' ' !== $author_name ) {
978 return $author_name;
979 }
980
981 $author_name = $author->nickname;
982 if ( ! empty( $author_name ) ) {
983 return $author_name;
984 }
985
986 return $author->user_nicename;
987 }
988
989 /**
990 * Retrieve all the authors for a post as an array. Can include multiple
991 * authors if coauthors plugin is in use.
992 */
993 private function get_author_names( $post ) {
994 $authors = $this->get_coauthor_names( $post->ID );
995 if ( empty( $authors ) ) {
996 $authors = array( get_user_by( 'id', $post->post_author ) );
997 }
998 $authors = array_map( array( $this, 'get_author_name' ), $authors );
999 $authors = apply_filters( 'wp_parsely_post_authors', $authors, $post );
1000 $authors = array_map( array( $this, 'get_clean_parsely_page_value' ), $authors );
1001 return $authors;
1002 }
1003
1004 /* sanitize content
1005 */
1006 private function get_clean_parsely_page_value( $val ) {
1007 if ( is_string( $val ) ) {
1008 $val = str_replace( "\n", '', $val );
1009 $val = str_replace( "\r", '', $val );
1010 $val = strip_tags( $val );
1011 $val = trim( $val );
1012 return $val;
1013 } else {
1014 return $val;
1015 }
1016 }
1017
1018
1019 /* Get the URL of the plugin settings page */
1020 private function get_settings_url() {
1021 return admin_url( 'options-general.php?page=' . Parsely::MENU_SLUG );
1022 }
1023
1024
1025 /**
1026 * Get the URL of the current PHP script.
1027 * A fall-back implementation to determine permalink
1028 */
1029 private function get_current_url( $post = 'nonpost' ) {
1030 $options = $this->get_options();
1031 $scheme = ( $options['force_https_canonicals'] ? 'https://' : 'http://' );
1032
1033 if ( 'post' === $post ) {
1034 $permalink = get_permalink();
1035 $parsed_canonical = parse_url( $permalink );
1036 $canonical = $scheme . $parsed_canonical['host'] . $parsed_canonical['path'];
1037 return $canonical;
1038 }
1039 $page_url = site_url( null, $scheme );
1040 if ( 80 !== intval( $_SERVER['SERVER_PORT'] ) || 443 !== intval( $_SERVER['SERVER_PORT'] ) ) {
1041 $page_url .= ':' . esc_url( wp_unslash( $_SERVER['SERVER_PORT'] ) );
1042 }
1043 $page_url .= esc_url( wp_unslash( $_SERVER['REQUEST_URI'] ) );
1044 return $page_url;
1045 }
1046
1047 /* https://css-tricks.com/snippets/wordpress/get-the-first-image-from-a-post/ */
1048 function get_first_image( $post ) {
1049 ob_start();
1050 ob_end_clean();
1051 if ( preg_match_all( '/<img.+src=[\'"]( [^\'"]+ )[\'"].*>/i', $post->post_content, $matches ) ) {
1052 $first_img = $matches[1][0];
1053 return $first_img;
1054 }
1055 return '';
1056 }
1057
1058 public function insert_parsely_tracking_fbia( &$registry ) {
1059 $options = $this->get_options();
1060 $display_name = 'Parsely Analytics';
1061 $identifier = 'parsely-analytics-for-wordpress';
1062
1063 $embed_code = '<script>
1064 PARSELY = {
1065 autotrack: false,
1066 onload: function() {
1067 PARSELY.beacon.trackPageView({
1068 urlref: \'http://facebook.com/instantarticles\'
1069 });
1070 return true;
1071 }
1072 }
1073 </script>
1074 <div id="parsely-root" style="display: none">
1075 <span id="parsely-cfg" data-parsely-site="' . esc_attr( $options['apikey'] ) . '"></span>
1076 </div>
1077 <script>
1078 ( function(s, p, d ) {
1079 var h=d.location.protocol, i=p+"-"+s,
1080 e=d.getElementById( i), r=d.getElementById(p+"-root" ),
1081 u=h==="https:"?"d1z2jf7jlzjs58.cloudfront.net"
1082 :"static."+p+".com";
1083 if ( e ) return;
1084 e = d.createElement( s ); e.id = i; e.async = true;
1085 e.src = h+"//"+u+"/p.js"; r.appendChild( e );
1086 })( "script", "parsely", document );
1087 </script>
1088 <!-- END Parse.ly Include: Standard -->';
1089
1090 $registry[ $identifier ] = array(
1091 'name' => $display_name,
1092 'payload' => $embed_code,
1093 );
1094
1095 return $embed_code;
1096 }
1097
1098 public function parsely_add_amp_actions() {
1099 add_filter( 'amp_post_template_analytics', array( $this, 'parsely_add_amp_analytics' ) );
1100 }
1101
1102 public function parsely_add_amp_analytics( $analytics ) {
1103 $options = $this->get_options();
1104
1105 if ( empty( $options['apikey'] ) ) {
1106 return $analytics;
1107 }
1108
1109 $analytics['parsely'] = array(
1110 'type' => 'parsely',
1111 'attributes' => array(),
1112 'config_data' => array(
1113 'vars' => array(
1114 'apikey' => $options['apikey'],
1115 ),
1116 ),
1117 );
1118
1119 return $analytics;
1120 }
1121
1122 public function parsely_is_user_logged_in() {
1123 // can't use $blog_id here because it futzes with the global $blog_id
1124 $current_blog_id = get_current_blog_id();
1125 $current_user_id = get_current_user_id();
1126 return is_user_member_of_blog( $current_user_id, $current_blog_id );
1127 }
1128
1129 function return_personalized_json() {
1130
1131 }
1132 }
1133
1134
1135
1136
1137 if ( class_exists( 'Parsely' ) ) {
1138 define( 'PARSELY_VERSION', Parsely::VERSION );
1139 $parsely = new Parsely();
1140 }
1141
1142 include 'recommended_widget.php';
1143