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

1,132 lines 40.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: 1.12.1
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.1';
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(
193 'json_ld' => 'json_ld',
194 'repeated_metas' => 'repeated_metas',
195 ),
196 'requires_recrawl' => true,
197 'multiple' => false,
198 )
199 );
200
201 // Content ID Prefix
202 $h = 'If you use more than one content management system (e.g. ' .
203 'WordPress and Drupal), you may end up with duplicate content ' .
204 'IDs. Adding a Content ID Prefix will ensure the content IDs ' .
205 'from WordPress will not conflict with other content management ' .
206 'systems. We recommend using "WP-" for your prefix.';
207
208 $field_args = array(
209 'option_key' => 'content_id_prefix',
210 'optional_args' => array(
211 'placeholder' => 'WP-',
212 ),
213 'help_text' => $h,
214 'requires_recrawl' => true,
215 );
216 add_settings_field( 'content_id_prefix',
217 'Content ID Prefix <div class="help-icons"></div>',
218 array( $this, 'print_text_tag' ),
219 Parsely::MENU_SLUG, 'optional_settings',
220 $field_args
221 );
222
223 // Disable javascript
224 $h = 'If you use a separate system for Javascript tracking ( Tealium / Segment / other tag manager solution ) ' .
225 'you may want to use that instead of having the plugin load the tracker. WARNING: disabling this option ' .
226 'will also disable the "Personalize Results" section of the recommended widget! We highly recommend leaving ' .
227 'this option set to "No"!';
228 add_settings_field( 'disable_javascript',
229 'Disable Javascript <div class="help-icons"></div>',
230 array( $this, 'print_binary_radio_tag' ),
231 Parsely::MENU_SLUG, 'optional_settings',
232 array(
233 'option_key' => 'disable_javascript',
234 'help_text' => $h,
235 'requires_recrawl' => false,
236 )
237 );
238
239 // Use top-level cats
240 $h = 'wp-parsely will use the first category assigned to a post. ' .
241 'With this option selected, if you post a story to News > ' .
242 'National > Florida, wp-parsely will use the "News" for the ' .
243 'section name in your dashboard instead of "Florida".';
244 add_settings_field( 'use_top_level_cats',
245 'Use Top-Level Categories for Section <div class="help-icons"></div>',
246 array( $this, 'print_binary_radio_tag' ),
247 Parsely::MENU_SLUG, 'optional_settings',
248 array(
249 'option_key' => 'use_top_level_cats',
250 'help_text' => $h,
251 'requires_recrawl' => true,
252 )
253 );
254
255 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category
256 $h = 'By default, the section value in your Parse.ly dashboard maps to a post\'s category. ' .
257 'You can optionally choose a custom taxonomy, if you\'ve created one, to ' .
258 'populate the section value instead. <br>';
259 add_settings_field( 'custom_taxonomy_section',
260 'Use Custom Taxonomy for Section <div class="help-icons"></div>',
261 array( $this, 'print_select_tag' ),
262 Parsely::MENU_SLUG, 'optional_settings',
263 array(
264 'option_key' => 'custom_taxonomy_section',
265 'help_text' => $h,
266 // filter Wordpress taxonomies under the hood that should not appear in dropdown
267 'select_options' => array_diff( get_taxonomies(), array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) ),
268 'requires_recrawl' => true,
269 )
270 );
271
272 // Use categories and custom taxonomies as tags
273 $h = 'You can use this option to add all assigned categories and taxonomies to ' .
274 'your tags. For example, if you had a post assigned to ' .
275 'the categories: "Business/Tech", "Business/Social", your tags would include ' .
276 '"Business/Tech" and "Business/Social" in addition to your other tags.';
277 add_settings_field( 'cats_as_tags',
278 'Add Categories to Tags <div class="help-icons"></div>',
279 array( $this, 'print_binary_radio_tag' ),
280 Parsely::MENU_SLUG, 'optional_settings',
281 array(
282 'option_key' => 'cats_as_tags',
283 'help_text' => $h,
284 'requires_recrawl' => true,
285 )
286 );
287
288 // Track logged-in users
289 $h = 'By default, wp-parsely will track the activity of users that ' .
290 'are logged into this site. You can change this setting to only ' .
291 'track the activity of anonymous visitors. Note: You will no ' .
292 'longer see the Parse.ly tracking code on your site if you ' .
293 'browse while logged in.';
294 add_settings_field( 'track_authenticated_users',
295 'Track Logged-in Users <div class="help-icons"></div>',
296 array( $this, 'print_binary_radio_tag' ),
297 Parsely::MENU_SLUG, 'optional_settings',
298 array(
299 'option_key' => 'track_authenticated_users',
300 'help_text' => $h,
301 'requires_recrawl' => true,
302 )
303 );
304
305 // Lowercase all tags
306 $h = 'By default, wp-parsely will use lowercase versions of your ' .
307 'tags to correct for potential misspellings. You can change this ' .
308 'setting to ensure that tag names are used verbatim.';
309 add_settings_field( 'lowercase_tags',
310 'Lowercase All Tags <div class="help-icons"></div>',
311 array( $this, 'print_binary_radio_tag' ),
312 Parsely::MENU_SLUG, 'optional_settings',
313 array(
314 'option_key' => 'lowercase_tags',
315 'help_text' => $h,
316 'requires_recrawl' => true,
317 )
318 );
319
320 $h = 'wp-parsely uses http canonical URLs by default. If this needs to be forced to use https, set this option ' .
321 ' to true. Note: the default is fine for almost all publishers, it\'s unlikely you\'ll have to change this unless' .
322 ' directed to do so by a Parsely support rep.';
323 add_settings_field( 'force_https_canonicals',
324 'Force HTTPS canonicals <div class="help-icons"></div>',
325 array( $this, 'print_binary_radio_tag' ),
326 Parsely::MENU_SLUG, 'optional_settings',
327 array(
328 'option_key' => 'force_https_canonicals',
329 'help_text' => $h,
330 'requires_recrawl' => true,
331 )
332 );
333
334 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category
335 $h = 'By default, Parsely only tracks the default post type as a post page. ' .
336 'If you want to track custom post types, select them here!<br>';
337 add_settings_field( 'track_post_types',
338 'Post Types To Track <div class="help-icons"></div>',
339 array( $this, 'print_select_tag' ),
340 Parsely::MENU_SLUG, 'optional_settings',
341 array(
342 'option_key' => 'track_post_types',
343 'help_text' => $h,
344 // filter Wordpress taxonomies under the hood that should not appear in dropdown
345 'select_options' => get_post_types(),
346 'requires_recrawl' => true,
347 'multiple' => true,
348 )
349 );
350
351 // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category
352 $h = 'By default, Parsely only tracks the default page type as a non-post page. ' .
353 'If you want to track custom post types as non-post pages, select them here!<br>';
354 add_settings_field( 'track_page_types',
355 'Page Types To Track <div class="help-icons"></div>',
356 array( $this, 'print_select_tag' ),
357 Parsely::MENU_SLUG, 'optional_settings',
358 array(
359 'option_key' => 'track_page_types',
360 'help_text' => $h,
361 // filter Wordpress taxonomies under the hood that should not appear in dropdown
362 'select_options' => get_post_types(),
363 'requires_recrawl' => true,
364 'multiple' => true,
365 )
366 );
367
368 // Dynamic tracking note
369 add_settings_field( 'dynamic_tracking_note', 'Note: ',
370 array( $this, 'print_dynamic_tracking_note' ),
371 Parsely::MENU_SLUG, 'optional_settings' );
372
373 }
374
375 public function validate_option_array( $array, $name ) {
376 $new_array = $array;
377 foreach ( $array as $key => $val ) {
378 $new_array[ $key ] = sanitize_text_field( $val );
379 }
380 return $new_array;
381 }
382
383 public function validate_options( $input ) {
384 if ( empty( $input['apikey'] ) ) {
385 add_settings_error( Parsely::OPTIONS_KEY, 'apikey',
386 'Please specify the Site ID' );
387 } else {
388 $input['apikey'] = strtolower( $input['apikey'] );
389 $input['apikey'] = sanitize_text_field( $input['apikey'] );
390 if ( strpos( $input['apikey'], '.' ) === false || strpos( $input['apikey'], ' ' ) !== false ) {
391 add_settings_error( Parsely::OPTIONS_KEY, 'apikey',
392 'Your Parse.ly Site ID looks incorrect, it should look like "example.com".' );
393 }
394 }
395 // these can't be null, if somebody accidentally deselected them just reset to default
396 if ( ! isset( $input['track_post_types'] ) ) {
397 $input['track_post_types'] = array( 'post' );
398
399 }
400 if ( ! isset( $input['track_page_types'] ) ) {
401 $input['track_page_types'] = array( 'page' );
402 }
403 $input['track_post_types'] = $this->validate_option_array( $input['track_post_types'], 'track_post_types' );
404 $input['track_page_types'] = $this->validate_option_array( $input['track_page_types'], 'track_page_types' );
405
406 $input['api_secret'] = sanitize_text_field( $input['api_secret'] );
407 // Content ID prefix
408 $input['content_id_prefix'] = sanitize_text_field( $input['content_id_prefix'] );
409 $input['custom_taxonomy_section'] = sanitize_text_field( $input['custom_taxonomy_section'] );
410
411 // Custom taxonomy as section
412
413 // Top-level categories
414 if ( 'true' !== $input['use_top_level_cats'] && 'false' !== $input['use_top_level_cats'] ) {
415 add_settings_error( Parsely::OPTIONS_KEY, 'use_top_level_cats',
416 'Value passed for use_top_level_cats must be either "true" or "false".' );
417 } else {
418 $input['use_top_level_cats'] = 'true' === $input['use_top_level_cats'] ? true : false;
419 }
420
421 // Child categories as tags
422 if ( 'true' !== $input['cats_as_tags'] && 'false' !== $input['cats_as_tags'] ) {
423 add_settings_error( Parsely::OPTIONS_KEY, 'cats_as_tags',
424 'Value passed for cats_as_tags must be either "true" or "false".' );
425 } else {
426 $input['cats_as_tags'] = 'true' === $input['cats_as_tags'] ? true : false;
427 }
428
429 // Track authenticated users
430 if ( 'true' !== $input['track_authenticated_users'] && 'false' !== $input['track_authenticated_users'] ) {
431 add_settings_error( Parsely::OPTIONS_KEY, 'track_authenticated_users',
432 'Value passed for track_authenticated_users must be either "true" or "false".' );
433 } else {
434 $input['track_authenticated_users'] = 'true' === $input['track_authenticated_users'] ? true : false;
435 }
436
437 // Lowercase tags
438 if ( 'true' !== $input['lowercase_tags'] && 'false' !== $input['lowercase_tags'] ) {
439 add_settings_error( Parsely::OPTIONS_KEY, 'lowercase_tags',
440 'Value passed for lowercase_tags must be either "true" or "false".' );
441 } else {
442 $input['lowercase_tags'] = 'true' === $input['lowercase_tags'] ? true : false;
443 }
444
445 if ( 'true' !== $input['force_https_canonicals'] && 'false' !== $input['force_https_canonicals'] ) {
446 add_settings_error( Parsely::OPTIONS_KEY, 'force_https_canonicals',
447 'Value passed for force_https_canonicals must be either "true" or "false".' );
448 } else {
449 $input['force_https_canonicals'] = 'true' === $input['force_https_canonicals'] ? true : false;
450 }
451
452 if ( 'true' !== $input['disable_javascript'] && 'false' !== $input['disable_javascript'] ) {
453 add_settings_error( Parsely::OPTIONS_KEY, 'disable_javascript',
454 'Value passed for disable_javascript must be either "true" or "false".' );
455 } else {
456 $input['disable_javascript'] = 'true' === $input['disable_javascript'] ? true : false;
457 }
458
459 return $input;
460 }
461
462 public function print_required_settings() {
463 // We can optionally print some text here in the future, but we don't
464 // need to now
465 }
466
467 public function print_optional_settings() {
468 // We can optionally print some text here in the future, but we don't
469 // need to now
470 return;
471 }
472
473 /**
474 * Adds a 'Settings' link to the Plugins screen in WP admin
475 */
476 public function add_plugin_meta_links( $links ) {
477 array_unshift( $links, '<a href="' . $this->get_settings_url() . '">' . __( 'Settings' ) . '</a>' );
478 return $links;
479 }
480
481 public function display_admin_warning() {
482 $options = $this->get_options();
483 if ( ! isset( $options['apikey'] ) || empty( $options['apikey'] ) ) {
484 ?>
485 <div id='message' class='error'>
486 <p>
487 <strong>Parse.ly - Dash plugin is not active.</strong>
488 You need to
489 <a href='<?php echo esc_html( $this->get_settings_url() ); ?>'>
490 provide your Parse.ly Dash Site ID
491 </a>
492 before things get cooking.
493 </p>
494 </div>
495 <?php
496 }
497 }
498
499 public function print_dynamic_tracking_note() {
500 $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.";
501 echo esc_html( $note );
502 }
503
504 /**
505 * @codeCoverageIgnoreEnd
506 */
507
508 /**
509 * Actually inserts the code for the <meta name='parsely-page'> parameter within the <head></head> tag.
510 */
511 public function insert_parsely_page() {
512 $parsely_options = $this->get_options();
513
514 // 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.
515 if ( empty( $parsely_options['apikey'] ) || ( ! $parsely_options['track_authenticated_users'] && $this->parsely_is_user_logged_in() ) ) {
516 return '';
517 }
518
519 global $wp_query;
520 global $post;
521 // Assign default values for LD+JSON
522 // TODO: Maping of an install's post types to Parse.ly post types ( namely page/post )
523 $parsely_page = array(
524 '@context' => 'http://schema.org',
525 '@type' => 'WebPage',
526 );
527
528 $current_url = $this->get_current_url();
529
530 if ( in_array( get_post_type(), $parsely_options['track_post_types'], true ) && 'publish' === $post->post_status ) {
531 $authors = $this->get_author_names( $post );
532 $category = $this->get_category_name( $post, $parsely_options );
533 $post_id = $parsely_options['content_id_prefix'] . (string) get_the_ID();
534
535 if ( has_post_thumbnail() ) {
536 $image_id = get_post_thumbnail_id();
537 $image_url = wp_get_attachment_image_src( $image_id );
538 $image_url = $image_url[0];
539 } else {
540 $image_url = $this->get_first_image( $post );
541 }
542
543 $tags = $this->get_tags( $post->ID );
544 if ( $parsely_options['cats_as_tags'] ) {
545 $tags = array_merge( $tags, $this->get_categories( $post->ID ) );
546 // add custom taxonomy values
547 $tags = array_merge( $tags, $this->get_custom_taxonomy_values( $post, $parsely_options ) );
548 }
549 // the function 'mb_strtolower' is not enabled by default in php, so this check
550 // falls back to the native php function 'strtolower' if necessary
551 if ( function_exists( 'mb_strtolower' ) ) {
552 $lowercase_callback = 'mb_strtolower';
553 } else {
554 $lowercase_callback = 'strtolower';
555 }
556 if ( $parsely_options['lowercase_tags'] ) {
557 $tags = array_map( $lowercase_callback, $tags );
558 }
559 $tags = apply_filters( 'wp_parsely_post_tags', $tags, $post->ID );
560 $tags = array_map( array( $this, 'get_clean_parsely_page_value' ), $tags );
561 $tags = array_values( array_unique( $tags ) );
562
563 $parsely_page['@type'] = 'NewsArticle';
564 $parsely_page['mainEntityOfPage'] = array(
565 '@type' => 'WebPage',
566 '@id' => $this->get_current_url( 'post' ),
567 );
568 $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_the_title() );
569 $parsely_page['url'] = $this->get_current_url( 'post' );
570 $parsely_page['thumbnailUrl'] = $image_url;
571 $parsely_page['image'] = array(
572 '@type' => 'ImageObject',
573 'url' => $image_url,
574 );
575 $parsely_page['dateCreated'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true ) );
576 $parsely_page['datePublished'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true ) );
577
578 if ( get_the_modified_date( 'U', true ) >= get_post_time( 'U', true ) ) {
579 $parsely_page['dateModified'] = gmdate( 'Y-m-d\TH:i:s\Z', get_the_modified_date( 'U', true ) );
580 } else {
581 // Use the post time as the earliest possible modification date
582 $parsely_page['dateModified'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true ) );
583 }
584
585 $author_objects = array();
586 foreach ( $authors as $author ) {
587 $author_tag = array(
588 '@type' => 'Person',
589 'name' => $author,
590 );
591 array_push( $author_objects, $author_tag );
592 }
593
594 $parsely_page['articleSection'] = $category;
595 $parsely_page['author'] = $author_objects;
596 $parsely_page['creator'] = $authors;
597 $parsely_page['keywords'] = $tags;
598
599 $parsely_page['publisher'] = array(
600 '@type' => 'Organization',
601 'name' => get_bloginfo( 'name' ),
602 );
603
604 } elseif ( in_array( get_post_type(), $parsely_options['track_page_types'], true ) && 'publish' === $post->post_status ) {
605 $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_the_title() );
606 $parsely_page['url'] = $this->get_current_url( 'post' );
607 }
608 if ( is_front_page() ) {
609 $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_bloginfo( 'name', 'raw' ) );
610 $parsely_page['url'] = get_home_url();
611 $parsely_page['@type'] = 'WebPage';
612 }
613 if ( is_archive() ) {
614 $parsely_page['@type'] = 'WebPage';
615 $parsely_page['url'] = $this->get_current_url();
616 if ( is_author() ) {
617 $parsely_page['headline'] = $this->get_clean_parsely_page_value( 'Author - ' . $author->data->display_name );
618 } else {
619 $parsely_page['headline'] = get_the_archive_title();
620 }
621 }
622
623 $parsely_page = apply_filters( 'after_set_parsely_page', $parsely_page, $post, $parsely_options );
624 include( 'parsely-parsely-page.php' );
625 return $parsely_page;
626 }
627
628 /**
629 * Inserts the JavaScript code required to send off beacon requests
630 */
631 public function insert_parsely_javascript() {
632 $parsely_options = $this->get_options();
633 // If we don't have an API key, there's no need to proceed.
634 if ( empty( $parsely_options['apikey'] ) || $parsely_options['disable_javascript'] ) {
635 return '';
636 }
637
638 global $post;
639 $display = true;
640 if ( in_array( get_post_type(), $parsely_options['track_post_types'], true ) && 'publish' !== $post->post_status ) {
641 $display = false;
642 }
643 if ( ! $parsely_options['track_authenticated_users'] && $this->parsely_is_user_logged_in() ) {
644 $display = false;
645 }
646 if ( ! in_array( get_post_type(), $parsely_options['track_post_types'], true ) && ! in_array( get_post_type(), $parsely_options['track_page_types'], true ) ) {
647 $display = false;
648 }
649 if ( $display ) {
650 include( 'parsely-javascript.php' );
651 }
652 }
653
654 public function print_select_tag( $args ) {
655 $options = $this->get_options();
656 $name = $args['option_key'];
657 $select_options = $args['select_options'];
658 if ( isset( $args['multiple'] ) ) {
659 $multiple = $args['multiple'];
660 } else {
661 $multiple = false;
662 }
663 $selected = isset( $options[ $name ] ) ? $options[ $name ] : null;
664 $optional_args = isset( $args['optional_args'] ) ? $args['optional_args'] : array();
665 $id = esc_attr( $name );
666 $name = Parsely::OPTIONS_KEY . "[$id]";
667
668 $tag = '<div class="parsely-form-controls"';
669 if ( isset( $args['help_text'] ) ) {
670 $tag .= ' data-has-help-text="true"';
671 }
672 if ( isset( $args['requires_recrawl'] ) ) {
673 $tag .= ' data-requires-recrawl="true"';
674 }
675 $tag .= '>';
676
677 if ( $multiple ) {
678 $tag .= "<select multiple='multiple' name='$name" . "[]'" . "id='$name'";
679 } else {
680 $tag .= "<select name='$name' id='$name'";
681 }
682
683 foreach ( $optional_args as $key => $val ) {
684 $tag .= ' ' . esc_attr( $key ) . '="' . esc_attr( $val ) . '"';
685 }
686 $tag .= '>';
687
688 foreach ( $select_options as $key => $val ) {
689 $tag .= '<option value="' . esc_attr( $key ) . '" ';
690
691 if ( $multiple ) {
692 $selected = in_array( $val, $options[ $args['option_key'] ], true );
693 $tag .= selected( $selected, true, false ) . '>';
694 } else {
695 $tag .= selected( $selected, $key, false ) . '>';
696 }
697 $tag .= esc_html( $val );
698 $tag .= '</option>';
699 }
700 $tag .= '</select>';
701
702 if ( isset( $args['help_text'] ) ) {
703 $tag .= '<div class="help-text">' .
704 '<p class="description">' . $args['help_text'] . '</p>' .
705 '</div>';
706 }
707 $tag .= '</div>';
708 echo $tag;
709 }
710
711 public function print_binary_radio_tag( $args ) {
712 $options = $this->get_options();
713 $name = $args['option_key'];
714 $value = $options[ $name ];
715 $id = esc_attr( $name );
716 $name = Parsely::OPTIONS_KEY . "[$id]";
717
718 $tag = '<div class="parsely-form-controls"';
719 if ( isset( $args['help_text'] ) ) {
720 $tag .= ' data-has-help-text="true"';
721 }
722 if ( isset( $args['requires_recrawl'] ) ) {
723 $tag .= ' data-requires-recrawl="true"';
724 }
725 $tag .= '>';
726
727 $tag .= "<input type='radio' name='$name' id='$id" . "_true' value='true' " .
728 checked( true === $value, true, false ) . ' />' .
729 "<label for='$id" . "_true'>Yes</label> " .
730 "<input type='radio' name='$name' id='$id" . "_false' value='false' " .
731 checked( true !== $value, true, false ) . ' />' .
732 "<label for='$id" . "_false'>No</label>";
733
734 if ( isset( $args['help_text'] ) ) {
735 $tag .= '<div class="help-text">' .
736 '<p class="description">' . $args['help_text'] . '</p>' .
737 '</div>';
738 }
739 $tag .= '</div>';
740
741 echo $tag;
742 }
743
744 public function print_text_tag( $args ) {
745 $options = $this->get_options();
746 $name = $args['option_key'];
747 $value = isset( $options[ $name ] ) ? $options[ $name ] : '';
748 $optional_args = isset( $args['optional_args'] ) ? $args['optional_args'] : array();
749 $id = esc_attr( $name );
750 $name = Parsely::OPTIONS_KEY . "[$id]";
751 $value = esc_attr( $value );
752
753 $tag = '<div class="parsely-form-controls"';
754 if ( isset( $args['help_text'] ) ) {
755 $tag .= ' data-has-help-text="true"';
756 }
757 if ( isset( $args['requires_recrawl'] ) ) {
758 $tag .= ' data-requires-recrawl="true"';
759 }
760 $tag .= '>';
761
762 $tag .= "<input type='text' name='$name' id='$id' value='$value'";
763 foreach ( $optional_args as $key => $val ) {
764 $tag .= ' ' . esc_attr( $key ) . '="' . esc_attr( $val ) . '"';
765 }
766 if ( isset( $args['requires_recrawl'] ) ) {
767 $tag .= ' data-requires-recrawl="true"';
768 }
769 $tag .= ' />';
770
771 if ( isset( $args['help_text'] ) ) {
772 $tag .= ' <div class="help-text" id="' .
773 esc_attr( $args['option_key'] ) . '_help_text">' .
774 '<p class="description">' . $args['help_text'] . '</p>' .
775 '</div>';
776 }
777 echo $tag;
778 }
779
780 /**
781 * Extracts a host ( not TLD ) from a URL
782 */
783 private function get_host_from_url( $url ) {
784 if ( preg_match( '/^https?:\/\/( [^\/]+ )\/.*$/', $url, $matches ) ) {
785 return $matches[1];
786 } else {
787 return $url;
788 }
789 }
790
791 /**
792 * Returns the tags associated with this page or post
793 */
794 private function get_tags( $post_id ) {
795 $tags = array();
796 $wp_tags = wp_get_post_tags( $post_id );
797 foreach ( $wp_tags as $wp_tag ) {
798 array_push( $tags, $wp_tag->name );
799 }
800
801 return $tags;
802 }
803
804 /**
805 * Returns an array of all the child categories for the current post
806 */
807 private function get_categories( $post_id, $delimiter = '/' ) {
808 $tags = array();
809 $categories = get_the_category( $post_id );
810 foreach ( $categories as $category ) {
811 $hierarchy = get_category_parents( $category, false, $delimiter );
812 $hierarchy = rtrim( $hierarchy, '/' );
813 array_push( $tags, $hierarchy );
814 }
815 // take last element in the hierarchy, a string representing the full parent->child tree,
816 // and split it into individual category names
817 $tags = explode( '/', end( $tags ) );
818 // remove uncategorized value from tags
819 $tags = array_diff( $tags, array( 'Uncategorized' ) );
820 return $tags;
821 }
822
823 /**
824 * Safely returns options for the plugin by assigning defaults contained in optionDefaults. As soon as actual
825 * options are saved, they override the defaults. This prevents us from having to do a lot of isset() checking
826 * on variables.
827 */
828 private function get_options() {
829 $options = get_option( Parsely::OPTIONS_KEY );
830 if ( false === $options ) {
831 $options = $this->option_defaults;
832 } else {
833 $options = array_merge( $this->option_defaults, $options );
834 }
835 return $options;
836 }
837
838 /**
839 * Returns a properly cleaned category/taxonomy value and will optionally use the top-level category/taxonomy value
840 * if so instructed via the `use_top_level_cats` option.
841 */
842 private function get_category_name( $post_obj, $parsely_options ) {
843 $taxonomy_dropdown_choice = get_the_terms( $post_obj->ID, $parsely_options['custom_taxonomy_section'] );
844 // Get top-level taxonomy name for chosen taxonomy and assign to $parent_name; it will be used
845 // as the category value if 'use_top_level_cats' option is checked.
846 // Assign as "Uncategorized" if no value is checked for the chosen taxonomy.
847 if ( ! empty( $taxonomy_dropdown_choice ) ) {
848 $first_term = array_shift( $taxonomy_dropdown_choice );
849 $parent_name = $this->get_top_level_term( $first_term->term_id, $first_term->taxonomy );
850 $child_name = $this->get_bottom_level_term( $post_obj->ID, $parsely_options['custom_taxonomy_section'] );
851 $category = $parsely_options['use_top_level_cats'] ? $parent_name : $child_name;
852 } else {
853 $category = 'Uncategorized';
854 }
855 $category = apply_filters( 'wp_parsely_post_category', $category, $post_obj, $parsely_options );
856 $category = $this->get_clean_parsely_page_value( $category );
857 return $category;
858 }
859
860 /**
861 * Return the top-most category/taxonomy value in a hierarcy given a taxonomy value's ID
862 * ( Wordpress calls taxonomy values 'terms' ).
863 */
864 private function get_top_level_term( $term_id, $taxonomy_name ) {
865 $parent = get_term_by( 'id', $term_id, $taxonomy_name );
866 while ( 0 !== $parent->parent ) {
867 $parent = get_term_by( 'id', $parent->parent, $taxonomy_name );
868 }
869 return $parent->name;
870 }
871
872 private function get_bottom_level_term( $post_id, $taxonomy_name ) {
873 $terms = get_the_terms( $post_id, $taxonomy_name );
874 $term_ids = wp_list_pluck( $terms, 'term_id' );
875 $parents = array_filter( wp_list_pluck( $terms, 'parent' ) );
876
877 //Get array of IDs of terms which are not parents.
878 $term_ids_not_parents = array_diff( $term_ids, $parents );
879 //Get corresponding term objects, which are mapped to array index keys
880 $terms_not_parents = array_intersect_key( $terms, $term_ids_not_parents );
881 //remove array index keys
882 $terms_not_parents_cleaned = array();
883 foreach ( $terms_not_parents as $index => $value ) {
884 array_push( $terms_not_parents_cleaned, $value );
885 }
886 //if you assign multiple child terms in a custom taxonomy, will only return the first
887 return $terms_not_parents_cleaned[0]->name;
888 }
889
890 // Get all term values from custom taxonomies
891 private function get_custom_taxonomy_values( $post_obj, $parsely_options ) {
892 // filter out default WordPress taxonomies
893 $all_taxonomies = array_diff( get_taxonomies(), array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) );
894 $all_values = array();
895
896 if ( is_array( $all_taxonomies ) ) {
897 foreach ( $all_taxonomies as $taxonomy ) {
898 $custom_taxonomy_objects = get_the_terms( $post_obj->ID, $taxonomy );
899 if ( is_array( $custom_taxonomy_objects ) ) {
900 foreach ( $custom_taxonomy_objects as $custom_taxonomy_object ) {
901 array_push( $all_values, $custom_taxonomy_object->name );
902 }
903 }
904 }
905 }
906 return $all_values;
907 }
908
909 /**
910 * Returns a list of coauthors for a post assuming the coauthors plugin is
911 * installed. Borrowed from
912 * https://github.com/Automattic/Co-Authors-Plus/blob/master/template-tags.php#L3-35
913 */
914 private function get_coauthor_names( $post_id ) {
915 $coauthors = array();
916 if ( class_exists( 'coauthors_plus' ) ) {
917 global $post, $post_ID, $coauthors_plus, $wpdb;
918
919 $post_id = (int) $post_id;
920 if ( ! $post_id && $post_ID ) {
921 $post_id = $post_ID;
922 }
923
924 if ( ! $post_id && $post ) {
925 $post_id = $post->ID;
926 }
927
928 if ( $post_id ) {
929 $coauthor_terms = get_the_terms( $post_id, $coauthors_plus->coauthor_taxonomy );
930
931 if ( is_array( $coauthor_terms ) && ! empty( $coauthor_terms ) ) {
932 foreach ( $coauthor_terms as $coauthor ) {
933 $coauthor_slug = preg_replace( '#^cap\-#', '', $coauthor->slug );
934 $post_author = $coauthors_plus->get_coauthor_by( 'user_nicename', $coauthor_slug );
935 // In case the user has been deleted while plugin was deactivated
936 if ( ! empty( $post_author ) ) {
937 $coauthors[] = $post_author;
938 }
939 }
940 } elseif ( ! $coauthors_plus->force_guest_authors ) {
941 if ( $post && $post_id === $post->ID ) {
942 $post_author = get_userdata( $post->post_author );
943 }
944 if ( ! empty( $post_author ) ) {
945 $coauthors[] = $post_author;
946 }
947 } // the empty else case is because if we force guest authors, we don't ever care what value wp_posts.post_author has.
948 }
949 }
950 return $coauthors;
951 }
952
953 /**
954 * Determine author name from display name, falling back to firstname +
955 * lastname, then nickname and finally the nicename.
956 */
957 private function get_author_name( $author ) {
958 $author_name = $author->display_name;
959 if ( ! empty( $author_name ) ) {
960 return $author_name;
961 }
962
963 $author_name = $author->user_firstname . ' ' . $author->user_lastname;
964 if ( ' ' !== $author_name ) {
965 return $author_name;
966 }
967
968 $author_name = $author->nickname;
969 if ( ! empty( $author_name ) ) {
970 return $author_name;
971 }
972
973 return $author->user_nicename;
974 }
975
976 /**
977 * Retrieve all the authors for a post as an array. Can include multiple
978 * authors if coauthors plugin is in use.
979 */
980 private function get_author_names( $post ) {
981 $authors = $this->get_coauthor_names( $post->ID );
982 if ( empty( $authors ) ) {
983 $authors = array( get_user_by( 'id', $post->post_author ) );
984 }
985 $authors = array_map( array( $this, 'get_author_name' ), $authors );
986 $authors = apply_filters( 'wp_parsely_post_authors', $authors, $post );
987 $authors = array_map( array( $this, 'get_clean_parsely_page_value' ), $authors );
988 return $authors;
989 }
990
991 /* sanitize content
992 */
993 private function get_clean_parsely_page_value( $val ) {
994 if ( is_string( $val ) ) {
995 $val = str_replace( "\n", '', $val );
996 $val = str_replace( "\r", '', $val );
997 $val = strip_tags( $val );
998 $val = trim( $val );
999 return $val;
1000 } else {
1001 return $val;
1002 }
1003 }
1004
1005
1006 /* Get the URL of the plugin settings page */
1007 private function get_settings_url() {
1008 return admin_url( 'options-general.php?page=' . Parsely::MENU_SLUG );
1009 }
1010
1011
1012 /**
1013 * Get the URL of the current PHP script.
1014 * A fall-back implementation to determine permalink
1015 */
1016 private function get_current_url( $post = 'nonpost' ) {
1017 $options = $this->get_options();
1018 $scheme = ( $options['force_https_canonicals'] ? 'https://' : 'http://' );
1019
1020 if ( 'post' === $post ) {
1021 $permalink = get_permalink();
1022 $parsed_canonical = parse_url( $permalink );
1023 $canonical = $scheme . $parsed_canonical['host'] . $parsed_canonical['path'];
1024 return $canonical;
1025 }
1026 $page_url = site_url( null, $scheme );
1027
1028 $port_number = intval( $_SERVER['SERVER_PORT'] );
1029 if ( 80 !== $port_number && 443 !== $port_number ) {
1030 $page_url .= ':' . $port_number;
1031 }
1032 $page_url .= esc_html( wp_unslash( $_SERVER['REQUEST_URI'] ) );
1033 return $page_url;
1034 }
1035
1036 /* https://css-tricks.com/snippets/wordpress/get-the-first-image-from-a-post/ */
1037 function get_first_image( $post ) {
1038 ob_start();
1039 ob_end_clean();
1040 if ( preg_match_all( '/<img.+src=[\'"]( [^\'"]+ )[\'"].*>/i', $post->post_content, $matches ) ) {
1041 $first_img = $matches[1][0];
1042 return $first_img;
1043 }
1044 return '';
1045 }
1046
1047 public function insert_parsely_tracking_fbia( &$registry ) {
1048 $options = $this->get_options();
1049 $display_name = 'Parsely Analytics';
1050 $identifier = 'parsely-analytics-for-wordpress';
1051
1052 $embed_code = '<script>
1053 PARSELY = {
1054 autotrack: false,
1055 onload: function() {
1056 PARSELY.beacon.trackPageView({
1057 urlref: \'http://facebook.com/instantarticles\'
1058 });
1059 return true;
1060 }
1061 }
1062 </script>
1063 <div id="parsely-root" style="display: none">
1064 <span id="parsely-cfg" data-parsely-site="' . esc_attr( $options['apikey'] ) . '"></span>
1065 </div>
1066 <script>
1067 ( function(s, p, d ) {
1068 var h=d.location.protocol, i=p+"-"+s,
1069 e=d.getElementById( i), r=d.getElementById(p+"-root" ),
1070 u=h==="https:"?"d1z2jf7jlzjs58.cloudfront.net"
1071 :"static."+p+".com";
1072 if ( e ) return;
1073 e = d.createElement( s ); e.id = i; e.async = true;
1074 e.src = h+"//"+u+"/p.js"; r.appendChild( e );
1075 })( "script", "parsely", document );
1076 </script>
1077 <!-- END Parse.ly Include: Standard -->';
1078
1079 $registry[ $identifier ] = array(
1080 'name' => $display_name,
1081 'payload' => $embed_code,
1082 );
1083
1084 return $embed_code;
1085 }
1086
1087 public function parsely_add_amp_actions() {
1088 add_filter( 'amp_post_template_analytics', array( $this, 'parsely_add_amp_analytics' ) );
1089 }
1090
1091 public function parsely_add_amp_analytics( $analytics ) {
1092 $options = $this->get_options();
1093
1094 if ( empty( $options['apikey'] ) ) {
1095 return $analytics;
1096 }
1097
1098 $analytics['parsely'] = array(
1099 'type' => 'parsely',
1100 'attributes' => array(),
1101 'config_data' => array(
1102 'vars' => array(
1103 'apikey' => $options['apikey'],
1104 ),
1105 ),
1106 );
1107
1108 return $analytics;
1109 }
1110
1111 public function parsely_is_user_logged_in() {
1112 // can't use $blog_id here because it futzes with the global $blog_id
1113 $current_blog_id = get_current_blog_id();
1114 $current_user_id = get_current_user_id();
1115 return is_user_member_of_blog( $current_user_id, $current_blog_id );
1116 }
1117
1118 function return_personalized_json() {
1119
1120 }
1121 }
1122
1123
1124
1125
1126 if ( class_exists( 'Parsely' ) ) {
1127 define( 'PARSELY_VERSION', Parsely::VERSION );
1128 $parsely = new Parsely();
1129 }
1130
1131 include 'recommended_widget.php';
1132