PluginProbe
Parse.ly / 1.12.2
Parse.ly v1.12.2
3.24.1 3.24.0 3.23.7 3.23.6 3.23.5 3.23.4 3.23.3 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.17.0 3.18.0 3.18.1 3.19.0 3.19.1 3.19.2 3.19.3 3.2.0 3.2.1 3.20.0 3.20.1 3.20.2 3.20.3 All 105 releases
← All changes | wp-parsely.php +1327 -163 3.2.01.12.2 View file →
@@ -1,197 +1,1361 @@
1 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.2
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 +/*
31 +TODO List:
32 + * WordPress Network support - going to hold off on any specific support here as content id prefix should work ok for now
33 + * Allow the user to map get_post_types() to Parse.ly post types
34 + * Support: is_search(), is_404()
35 +*/
36 +
2 37 /**
3 - * Parse.ly
38 + * This is the main class for Parsely
4 39 *
5 - * @package Parsely\wp-parsely
6 - * @author Parse.ly
7 - * @copyright 2012 Parse.ly
8 - * @license GPL-2.0-or-later
9 - *
10 - * @wordpress-plugin
11 - * Plugin Name: Parse.ly
12 - * Plugin URI: https://www.parse.ly/help/integration/wordpress
13 - * Description: This plugin makes it a snap to add Parse.ly tracking code and metadata to your WordPress blog.
14 - * Version: 3.2.0
15 - * Author: Parse.ly
16 - * Author URI: https://www.parse.ly
17 - * Text Domain: wp-parsely
18 - * License: GPL-2.0-or-later
19 - * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
20 - * GitHub Plugin URI: https://github.com/Parsely/wp-parsely
21 - * Requires PHP: 7.1
22 - * Requires WP: 5.0.0
40 + * @category Class
41 + * @package Parsely
23 42 */
43 +class Parsely {
44 + /**
45 + * Declare our constants
46 + *
47 + * @codeCoverageIgnoreStart
48 + */
49 + const VERSION = '1.12.2';
50 + const MENU_SLUG = 'parsely'; // Defines the page param passed to options-general.php
51 + const MENU_TITLE = 'Parse.ly'; // Text to be used for the menu as seen in Settings sub-menu
52 + const MENU_PAGE_TITLE = 'Parse.ly > Settings'; // Text shown in <title></title> when the settings screen is viewed
53 + const OPTIONS_KEY = 'parsely'; // Defines the key used to store options in the WP database
54 + const CAPABILITY = 'manage_options'; // The capability required for the user to administer settings
24 55
25 -declare(strict_types=1);
56 + /**
57 + * Declare some class propeties
58 + *
59 + * @var array $option_defaults The defaults we need for the class.
60 + */
61 + private $option_defaults = array(
62 + 'apikey' => '',
63 + 'content_id_prefix' => '',
64 + 'api_secret' => '',
65 + 'use_top_level_cats' => false,
66 + 'custom_taxonomy_section' => 'category',
67 + 'cats_as_tags' => false,
68 + 'track_authenticated_users' => true,
69 + 'lowercase_tags' => true,
70 + 'force_https_canonicals' => false,
71 + 'track_post_types' => array( 'post' ),
72 + 'track_page_types' => array( 'page' ),
73 + 'disable_javascript' => false,
74 + 'meta_type' => 'json_ld',
75 + );
26 76
27 -namespace Parsely;
77 + /**
78 + * Declare some class propeties
79 + *
80 + * @var array $implementation_opts The implementation options for the class.
81 + */
82 + private $implementation_opts = array(
83 + 'standard' => 'Standard',
84 + 'dom_free' => 'DOM-Free',
85 + );
28 86
29 -use Parsely\Endpoints\Related_API_Proxy;
30 -use Parsely\Endpoints\GraphQL_Metadata;
31 -use Parsely\Endpoints\Rest_Metadata;
32 -use Parsely\Integrations\Amp;
33 -use Parsely\Integrations\Facebook_Instant_Articles;
34 -use Parsely\Integrations\Google_Web_Stories;
35 -use Parsely\Integrations\Integrations;
36 -use Parsely\RemoteAPI\Cached_Proxy;
37 -use Parsely\RemoteAPI\Related_Proxy;
38 -use Parsely\RemoteAPI\WordPress_Cache;
39 -use Parsely\UI\Admin_Bar;
40 -use Parsely\UI\Admin_Warning;
41 -use Parsely\UI\Plugins_Actions;
42 -use Parsely\UI\Network_Admin_Sites_List;
43 -use Parsely\UI\Recommended_Widget;
44 -use Parsely\UI\Row_Actions;
45 -use Parsely\UI\Settings_Page;
87 + /**
88 + * The constructor
89 + *
90 + * @category Function
91 + * @package Parsely
92 + */
93 + public function __construct() {
94 + // Run upgrade options if they exist for the version currently defined.
95 + $options = $this->get_options();
96 + if ( empty( $options['plugin_version'] ) || Parsely::VERSION !== $options['plugin_version'] ) {
97 + $method = 'upgrade_plugin_to_version_' . str_replace( '.', '_', Parsely::VERSION );
98 + if ( method_exists( $this, $method ) ) {
99 + call_user_func_array( array( $this, $method ), array( $options ) );
100 + }
101 + // Update our version info.
102 + $options['plugin_version'] = Parsely::VERSION;
103 + update_option( Parsely::OPTIONS_KEY, $options );
104 + }
46 105
47 -if ( class_exists( Parsely::class ) ) {
48 - return;
49 -}
106 + // admin_menu and a settings link.
107 + add_action( 'admin_head', array( $this, 'add_admin_header' ) );
108 + add_action( 'admin_menu', array( $this, 'add_settings_sub_menu' ) );
109 + add_action( 'admin_init', array( $this, 'initialize_settings' ) );
110 + // display warning when plugin hasn't been configured.
111 + add_action( 'admin_footer', array( $this, 'display_admin_warning' ) );
50 112
51 -const PARSELY_VERSION = '3.2.0';
52 -const PARSELY_FILE = __FILE__;
113 + $basename = plugin_basename( __FILE__ );
114 + add_filter(
115 + 'plugin_action_links_' . $basename,
116 + array( $this, 'add_plugin_meta_links' )
117 + );
53 118
54 -require __DIR__ . '/src/class-parsely.php';
55 -require __DIR__ . '/src/class-scripts.php';
56 -require __DIR__ . '/src/class-dashboard-link.php';
57 -require __DIR__ . '/src/UI/class-admin-bar.php';
58 -require __DIR__ . '/src/Endpoints/class-metadata-endpoint.php';
59 -require __DIR__ . '/src/Endpoints/class-graphql-metadata.php';
119 + // inserting parsely code.
120 + add_action( 'wp_head', array( $this, 'insert_parsely_page' ) );
121 + add_action( 'wp_footer', array( $this, 'insert_parsely_javascript' ) );
122 + add_action( 'instant_articles_compat_registry_analytics', array( $this, 'insert_parsely_tracking_fbia' ) );
123 + add_action( 'pre_amp_render_post', array( $this, 'parsely_add_amp_actions' ) );
124 + if ( ! defined( 'WP_PARSELY_TESTING' ) ) {
125 + /**
126 + * Initialize parsely WordPress style
127 + */
128 + function wp_parsely_style_init() {
129 + wp_enqueue_style( 'wp-parsely-style', plugins_url( 'wp-parsely.css', __FILE__ ), array(), filemtime( get_stylesheet_directory() ) );
130 + }
60 131
61 -add_action( 'plugins_loaded', __NAMESPACE__ . '\\parsely_initialize_plugin' );
62 -/**
63 - * Register the basic classes to initialize the plugin.
64 - *
65 - * @return void
66 - */
67 -function parsely_initialize_plugin(): void {
68 - $GLOBALS['parsely'] = new Parsely();
69 - $GLOBALS['parsely']->run();
132 + /**
133 + * Make sure that jquery exists
134 + */
135 + function ensure_jquery_exists() {
136 + wp_enqueue_script( 'jquery' );
137 + }
138 + add_action( 'wp_enqueue_scripts', 'wp_parsely_style_init' );
139 + add_action( 'wp_enqueue_scripts', 'ensure_jquery_exists' );
140 + }
70 141
71 - if ( class_exists( 'WPGraphQL' ) ) {
72 - $graphql = new GraphQL_Metadata( $GLOBALS['parsely'] );
73 - $graphql->run();
74 142 }
75 143
76 - $scripts = new Scripts( $GLOBALS['parsely'] );
77 - $scripts->run();
144 + /**
145 + * Include the parsely admin header
146 + *
147 + * @category Function
148 + * @package Parsely
149 + */
150 + public function add_admin_header() {
151 + include 'parsely-admin-header.php';
152 + }
78 153
79 - $admin_bar = new Admin_Bar( $GLOBALS['parsely'] );
80 - $admin_bar->run();
81 -}
154 + /**
155 + * Parsely settings page in WordPress settings menu.
156 + *
157 + * @category Function
158 + * @package Parsely
159 + */
160 + public function add_settings_sub_menu() {
161 + add_options_page(
162 + Parsely::MENU_PAGE_TITLE,
163 + Parsely::MENU_TITLE,
164 + Parsely::CAPABILITY,
165 + Parsely::MENU_SLUG,
166 + array( $this, 'display_settings' )
167 + );
168 + }
82 169
83 -require __DIR__ . '/src/UI/class-admin-warning.php';
84 -require __DIR__ . '/src/UI/class-plugins-actions.php';
85 -require __DIR__ . '/src/UI/class-row-actions.php';
170 + /**
171 + * Parse.ly settings screen ( options-general.php?page=[MENU_SLUG] )
172 + *
173 + * @category Function
174 + * @package Parsely
175 + */
176 + public function display_settings() {
177 + if ( ! current_user_can( Parsely::CAPABILITY ) ) {
178 + wp_die( esc_attr( 'You do not have sufficient permissions to access this page.' ) );
179 + }
86 180
87 -add_action( 'admin_init', __NAMESPACE__ . '\\parsely_admin_init_register' );
88 -/**
89 - * Register the Parse.ly wp-admin warnings, plugin actions and row actions.
90 - *
91 - * @return void
92 - */
93 -function parsely_admin_init_register(): void {
94 - $admin_warning = new Admin_Warning( $GLOBALS['parsely'] );
95 - $admin_warning->run();
181 + include 'parsely-settings.php';
182 + }
96 183
97 - $plugins_actions = new Plugins_Actions();
98 - $plugins_actions->run();
184 + /**
185 + * Initialize the settings for Parsely
186 + *
187 + * @category Function
188 + * @package Parsely
189 + */
190 + public function initialize_settings() {
191 + // All our options are actually stored in one single array to reduce
192 + // DB queries.
193 + register_setting(
194 + Parsely::OPTIONS_KEY, Parsely::OPTIONS_KEY,
195 + array( $this, 'validate_options' )
196 + );
99 197
100 - $row_actions = new Row_Actions( $GLOBALS['parsely'] );
101 - $row_actions->run();
102 -}
198 + // These are the Required Settings.
199 + add_settings_section(
200 + 'required_settings', 'Required Settings',
201 + array( $this, 'print_required_settings' ),
202 + Parsely::MENU_SLUG
203 + );
103 204
104 -require __DIR__ . '/src/UI/class-settings-page.php';
105 -require __DIR__ . '/src/UI/class-network-admin-sites-list.php';
205 + // Get the API Key.
206 + $h = 'Your Site ID is your own site domain ( e.g. `mydomain.com` )';
106 207
107 -add_action( 'init', __NAMESPACE__ . '\\parsely_wp_admin_early_register' );
108 -/**
109 - * Register the additions the Parse.ly wp-admin settings page and Multisite Network Admin Sites List table.
110 - *
111 - * @return void
112 - */
113 -function parsely_wp_admin_early_register(): void {
114 - $settings_page = new Settings_Page( $GLOBALS['parsely'] );
115 - $settings_page->run();
208 + $field_args = array(
209 + 'option_key' => 'apikey',
210 + 'help_text' => $h,
211 + );
212 + add_settings_field(
213 + 'apikey',
214 + 'Parse.ly Site ID <div class="help-icons"></div>',
215 + array( $this, 'print_text_tag' ),
216 + Parsely::MENU_SLUG, 'required_settings',
217 + $field_args
218 + );
116 219
117 - $network_admin_sites_list = new Network_Admin_Sites_List( $GLOBALS['parsely'] );
118 - $network_admin_sites_list->run();
119 -}
220 + // These are the Optional Settings.
221 + add_settings_section(
222 + 'optional_settings', 'Optional Settings',
223 + array( $this, 'print_optional_settings' ),
224 + Parsely::MENU_SLUG
225 + );
120 226
121 -require __DIR__ . '/src/RemoteAPI/interface-cache.php';
122 -require __DIR__ . '/src/RemoteAPI/interface-proxy.php';
123 -require __DIR__ . '/src/RemoteAPI/class-base-proxy.php';
124 -require __DIR__ . '/src/RemoteAPI/class-cached-proxy.php';
125 -require __DIR__ . '/src/RemoteAPI/class-related-proxy.php';
126 -require __DIR__ . '/src/RemoteAPI/class-wordpress-cache.php';
127 -require __DIR__ . '/src/Endpoints/class-related-api-proxy.php';
128 -require __DIR__ . '/src/Endpoints/class-rest-metadata.php';
227 + $h = 'Your API secret is your secret code to %s%s%saccess our API.%s
228 + It can be found at dash.parsely.com/yoursitedomain/settings/api
229 + ( replace yoursitedown with your domain name, e.g. `mydomain.com` ) If you haven\'t purchased access to the API, and would
230 + like to do so, email your account manager or support@parsely.com!';
231 + $h_link = 'https://www.parse.ly/help/api/analytics/';
129 232
130 -add_action( 'rest_api_init', __NAMESPACE__ . '\\parsely_rest_api_init' );
131 -/**
132 - * Register REST Endpoints that act as a proxy to the Parse.ly API.
133 - * This is needed to get around a CORS issues with Firefox.
134 - *
135 - * @since 3.2.0
136 - *
137 - * @return void
138 - */
139 -function parsely_rest_api_init(): void {
140 - $rest = new Rest_Metadata( $GLOBALS['parsely'] );
141 - $rest->run();
233 + $field_args = array(
234 + 'option_key' => 'api_secret',
235 + 'help_text' => $h,
236 + 'help_link' => $h_link,
237 + );
238 + add_settings_field(
239 + 'api_secret',
240 + 'Parse.ly API Secret <div class="help-icons"></div>',
241 + array( $this, 'print_text_tag' ),
242 + Parsely::MENU_SLUG, 'optional_settings',
243 + $field_args
244 + );
142 245
143 - $proxy = new Related_Proxy( $GLOBALS['parsely'] );
144 - $cached_proxy = new Cached_Proxy( $proxy, new WordPress_Cache( $GLOBALS['wp_object_cache'] ) );
145 - $endpoint = new Related_API_Proxy( $GLOBALS['parsely'], $cached_proxy );
146 - $endpoint->run();
147 -}
246 + $h = 'Choose the metadata format for our crawlers to access. ' .
247 + 'Most publishers are fine with JSON-LD ( %s%s%shttps://www.parse.ly/help/integration/jsonld/%s ), ' .
248 + 'but if you prefer to use our proprietary metadata format then you can do so here.';
249 + $h_link = 'https://www.parse.ly/help/integration/jsonld/';
148 250
149 -require __DIR__ . '/src/blocks/recommendations/class-recommendations-block.php';
251 + add_settings_field(
252 + 'meta_type',
253 + 'Metadata Format <div class="help-icons"></div>',
254 + array( $this, 'print_select_tag' ),
255 + Parsely::MENU_SLUG, 'optional_settings',
256 + array(
257 + 'option_key' => 'meta_type',
258 + 'help_text' => $h,
259 + 'help_link' => $h_link,
260 + // filter WordPress taxonomies under the hood that should not appear in dropdown.
261 + 'select_options' => array(
262 + 'json_ld' => 'json_ld',
263 + 'repeated_metas' => 'repeated_metas',
264 + ),
265 + 'requires_recrawl' => true,
266 + 'multiple' => false,
267 + )
268 + );
150 269
151 -add_action( 'init', __NAMESPACE__ . '\\init_recommendations_block' );
152 -/**
153 - * Register the Recommendations Block.
154 - *
155 - * @return void
156 - */
157 -function init_recommendations_block(): void {
158 - $recommendations_block = new Recommendations_Block();
159 - $recommendations_block->run();
160 -}
270 + // Content ID Prefix.
271 + $h = 'If you use more than one content management system (e.g. ' .
272 + 'WordPress and Drupal), you may end up with duplicate content ' .
273 + 'IDs. Adding a Content ID Prefix will ensure the content IDs ' .
274 + 'from WordPress will not conflict with other content management ' .
275 + 'systems. We recommend using "WP-" for your prefix.';
161 276
162 -require __DIR__ . '/src/UI/class-recommended-widget.php';
277 + $field_args = array(
278 + 'option_key' => 'content_id_prefix',
279 + 'optional_args' => array(
280 + 'placeholder' => 'WP-',
281 + ),
282 + 'help_text' => $h,
283 + 'requires_recrawl' => true,
284 + );
285 + add_settings_field(
286 + 'content_id_prefix',
287 + 'Content ID Prefix <div class="help-icons"></div>',
288 + array( $this, 'print_text_tag' ),
289 + Parsely::MENU_SLUG, 'optional_settings',
290 + $field_args
291 + );
163 292
164 -add_action( 'widgets_init', __NAMESPACE__ . '\\parsely_recommended_widget_register' );
165 -/**
166 - * Register the Parse.ly Recommended widget.
167 - *
168 - * @return void
169 - */
170 -function parsely_recommended_widget_register(): void {
171 - register_widget( Recommended_Widget::class );
293 + // Disable javascript.
294 + $h = 'If you use a separate system for Javascript tracking ( Tealium / Segment / other tag manager solution ) ' .
295 + 'you may want to use that instead of having the plugin load the tracker. WARNING: disabling this option ' .
296 + 'will also disable the "Personalize Results" section of the recommended widget! We highly recommend leaving ' .
297 + 'this option set to "No"!';
298 + add_settings_field(
299 + 'disable_javascript',
300 + 'Disable Javascript <div class="help-icons"></div>',
301 + array( $this, 'print_binary_radio_tag' ),
302 + Parsely::MENU_SLUG, 'optional_settings',
303 + array(
304 + 'option_key' => 'disable_javascript',
305 + 'help_text' => $h,
306 + 'requires_recrawl' => false,
307 + )
308 + );
309 +
310 + // Use top-level categories.
311 + $h = 'wp-parsely will use the first category assigned to a post. ' .
312 + 'With this option selected, if you post a story to News > ' .
313 + 'National > Florida, wp-parsely will use the "News" for the ' .
314 + 'section name in your dashboard instead of "Florida".';
315 + add_settings_field(
316 + 'use_top_level_cats',
317 + 'Use Top-Level Categories for Section <div class="help-icons"></div>',
318 + array( $this, 'print_binary_radio_tag' ),
319 + Parsely::MENU_SLUG, 'optional_settings',
320 + array(
321 + 'option_key' => 'use_top_level_cats',
322 + 'help_text' => $h,
323 + 'requires_recrawl' => true,
324 + )
325 + );
326 +
327 + // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
328 + $h = 'By default, the section value in your Parse.ly dashboard maps to a post\'s category. ' .
329 + 'You can optionally choose a custom taxonomy, if you\'ve created one, to ' .
330 + 'populate the section value instead. ';
331 + add_settings_field(
332 + 'custom_taxonomy_section',
333 + 'Use Custom Taxonomy for Section <div class="help-icons"></div>',
334 + array( $this, 'print_select_tag' ),
335 + Parsely::MENU_SLUG, 'optional_settings',
336 + array(
337 + 'option_key' => 'custom_taxonomy_section',
338 + 'help_text' => $h,
339 + // filter WordPress taxonomies under the hood that should not appear in dropdown.
340 + 'select_options' => array_diff( get_taxonomies(), array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) ),
341 + 'requires_recrawl' => true,
342 + )
343 + );
344 +
345 + // Use categories and custom taxonomies as tags.
346 + $h = 'You can use this option to add all assigned categories and taxonomies to ' .
347 + 'your tags. For example, if you had a post assigned to ' .
348 + 'the categories: "Business/Tech", "Business/Social", your tags would include ' .
349 + '"Business/Tech" and "Business/Social" in addition to your other tags.';
350 + add_settings_field(
351 + 'cats_as_tags',
352 + 'Add Categories to Tags <div class="help-icons"></div>',
353 + array( $this, 'print_binary_radio_tag' ),
354 + Parsely::MENU_SLUG, 'optional_settings',
355 + array(
356 + 'option_key' => 'cats_as_tags',
357 + 'help_text' => $h,
358 + 'requires_recrawl' => true,
359 + )
360 + );
361 +
362 + // Track logged-in users.
363 + $h = 'By default, wp-parsely will track the activity of users that ' .
364 + 'are logged into this site. You can change this setting to only ' .
365 + 'track the activity of anonymous visitors. Note: You will no ' .
366 + 'longer see the Parse.ly tracking code on your site if you ' .
367 + 'browse while logged in.';
368 + add_settings_field(
369 + 'track_authenticated_users',
370 + 'Track Logged-in Users <div class="help-icons"></div>',
371 + array( $this, 'print_binary_radio_tag' ),
372 + Parsely::MENU_SLUG, 'optional_settings',
373 + array(
374 + 'option_key' => 'track_authenticated_users',
375 + 'help_text' => $h,
376 + 'requires_recrawl' => true,
377 + )
378 + );
379 +
380 + // Lowercase all tags.
381 + $h = 'By default, wp-parsely will use lowercase versions of your ' .
382 + 'tags to correct for potential misspellings. You can change this ' .
383 + 'setting to ensure that tag names are used verbatim.';
384 + add_settings_field(
385 + 'lowercase_tags',
386 + 'Lowercase All Tags <div class="help-icons"></div>',
387 + array( $this, 'print_binary_radio_tag' ),
388 + Parsely::MENU_SLUG, 'optional_settings',
389 + array(
390 + 'option_key' => 'lowercase_tags',
391 + 'help_text' => $h,
392 + 'requires_recrawl' => true,
393 + )
394 + );
395 +
396 + $h = 'wp-parsely uses http canonical URLs by default. If this needs to be forced to use https, set this option ' .
397 + ' to true. Note: the default is fine for almost all publishers, it\'s unlikely you\'ll have to change this unless' .
398 + ' directed to do so by a Parsely support rep.';
399 + add_settings_field(
400 + 'force_https_canonicals',
401 + 'Force HTTPS canonicals <div class="help-icons"></div>',
402 + array( $this, 'print_binary_radio_tag' ),
403 + Parsely::MENU_SLUG, 'optional_settings',
404 + array(
405 + 'option_key' => 'force_https_canonicals',
406 + 'help_text' => $h,
407 + 'requires_recrawl' => true,
408 + )
409 + );
410 +
411 + // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
412 + $h = 'By default, Parsely only tracks the default post type as a post page. ' .
413 + 'If you want to track custom post types, select them here!';
414 + add_settings_field(
415 + 'track_post_types',
416 + 'Post Types To Track <div class="help-icons"></div>',
417 + array( $this, 'print_select_tag' ),
418 + Parsely::MENU_SLUG, 'optional_settings',
419 + array(
420 + 'option_key' => 'track_post_types',
421 + 'help_text' => $h,
422 + // filter WordPress taxonomies under the hood that should not appear in dropdown.
423 + 'select_options' => get_post_types(),
424 + 'requires_recrawl' => true,
425 + 'multiple' => true,
426 + )
427 + );
428 +
429 + // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category.
430 + $h = 'By default, Parsely only tracks the default page type as a non-post page. ' .
431 + 'If you want to track custom post types as non-post pages, select them here!';
432 + add_settings_field(
433 + 'track_page_types',
434 + 'Page Types To Track <div class="help-icons"></div>',
435 + array( $this, 'print_select_tag' ),
436 + Parsely::MENU_SLUG, 'optional_settings',
437 + array(
438 + 'option_key' => 'track_page_types',
439 + 'help_text' => $h,
440 + // filter WordPress taxonomies under the hood that should not appear in dropdown.
441 + 'select_options' => get_post_types(),
442 + 'requires_recrawl' => true,
443 + 'multiple' => true,
444 + )
445 + );
446 +
447 + // Dynamic tracking note.
448 + add_settings_field(
449 + 'dynamic_tracking_note', 'Note: ',
450 + array( $this, 'print_dynamic_tracking_note' ),
451 + Parsely::MENU_SLUG, 'optional_settings'
452 + );
453 +
454 + }
455 +
456 + /**
457 + * Validate options from an array
458 + *
459 + * @category Function
460 + * @package Parsely
461 + * @param array $array Array of options to be sanitized.
462 + * @param string $name Unused?.
463 + */
464 + public function validate_option_array( $array, $name ) {
465 + $new_array = $array;
466 + foreach ( $array as $key => $val ) {
467 + $new_array[ $key ] = sanitize_text_field( $val );
468 + }
469 + return $new_array;
470 + }
471 +
472 + /**
473 + * Validate the options provided by the user
474 + *
475 + * @category Function
476 + * @package Parsely
477 + * @param array $input Options from the settings page.
478 + */
479 + public function validate_options( $input ) {
480 + if ( empty( $input['apikey'] ) ) {
481 + add_settings_error(
482 + Parsely::OPTIONS_KEY, 'apikey',
483 + 'Please specify the Site ID'
484 + );
485 + } else {
486 + $input['apikey'] = strtolower( $input['apikey'] );
487 + $input['apikey'] = sanitize_text_field( $input['apikey'] );
488 + if ( strpos( $input['apikey'], '.' ) === false || strpos( $input['apikey'], ' ' ) !== false ) {
489 + add_settings_error(
490 + Parsely::OPTIONS_KEY, 'apikey',
491 + 'Your Parse.ly Site ID looks incorrect, it should look like "example.com".'
492 + );
493 + }
494 + }
495 + // these can't be null, if somebody accidentally deselected them just reset to default.
496 + if ( ! isset( $input['track_post_types'] ) ) {
497 + $input['track_post_types'] = array( 'post' );
498 +
499 + }
500 + if ( ! isset( $input['track_page_types'] ) ) {
501 + $input['track_page_types'] = array( 'page' );
502 + }
503 + $input['track_post_types'] = $this->validate_option_array( $input['track_post_types'], 'track_post_types' );
504 + $input['track_page_types'] = $this->validate_option_array( $input['track_page_types'], 'track_page_types' );
505 +
506 + $input['api_secret'] = sanitize_text_field( $input['api_secret'] );
507 + // Content ID prefix.
508 + $input['content_id_prefix'] = sanitize_text_field( $input['content_id_prefix'] );
509 + $input['custom_taxonomy_section'] = sanitize_text_field( $input['custom_taxonomy_section'] );
510 +
511 + // Custom taxonomy as section.
512 + // Top-level categories.
513 + if ( 'true' !== $input['use_top_level_cats'] && 'false' !== $input['use_top_level_cats'] ) {
514 + add_settings_error(
515 + Parsely::OPTIONS_KEY, 'use_top_level_cats',
516 + 'Value passed for use_top_level_cats must be either "true" or "false".'
517 + );
518 + } else {
519 + $input['use_top_level_cats'] = 'true' === $input['use_top_level_cats'] ? true : false;
520 + }
521 +
522 + // Child categories as tags.
523 + if ( 'true' !== $input['cats_as_tags'] && 'false' !== $input['cats_as_tags'] ) {
524 + add_settings_error(
525 + Parsely::OPTIONS_KEY, 'cats_as_tags',
526 + 'Value passed for cats_as_tags must be either "true" or "false".'
527 + );
528 + } else {
529 + $input['cats_as_tags'] = 'true' === $input['cats_as_tags'] ? true : false;
530 + }
531 +
532 + // Track authenticated users.
533 + if ( 'true' !== $input['track_authenticated_users'] && 'false' !== $input['track_authenticated_users'] ) {
534 + add_settings_error(
535 + Parsely::OPTIONS_KEY, 'track_authenticated_users',
536 + 'Value passed for track_authenticated_users must be either "true" or "false".'
537 + );
538 + } else {
539 + $input['track_authenticated_users'] = 'true' === $input['track_authenticated_users'] ? true : false;
540 + }
541 +
542 + // Lowercase tags.
543 + if ( 'true' !== $input['lowercase_tags'] && 'false' !== $input['lowercase_tags'] ) {
544 + add_settings_error(
545 + Parsely::OPTIONS_KEY, 'lowercase_tags',
546 + 'Value passed for lowercase_tags must be either "true" or "false".'
547 + );
548 + } else {
549 + $input['lowercase_tags'] = 'true' === $input['lowercase_tags'] ? true : false;
550 + }
551 +
552 + if ( 'true' !== $input['force_https_canonicals'] && 'false' !== $input['force_https_canonicals'] ) {
553 + add_settings_error(
554 + Parsely::OPTIONS_KEY, 'force_https_canonicals',
555 + 'Value passed for force_https_canonicals must be either "true" or "false".'
556 + );
557 + } else {
558 + $input['force_https_canonicals'] = 'true' === $input['force_https_canonicals'] ? true : false;
559 + }
560 +
561 + if ( 'true' !== $input['disable_javascript'] && 'false' !== $input['disable_javascript'] ) {
562 + add_settings_error(
563 + Parsely::OPTIONS_KEY, 'disable_javascript',
564 + 'Value passed for disable_javascript must be either "true" or "false".'
565 + );
566 + } else {
567 + $input['disable_javascript'] = 'true' === $input['disable_javascript'] ? true : false;
568 + }
569 +
570 + return $input;
571 + }
572 +
573 + /**
574 + * Not doing anything here
575 + *
576 + * @category Function
577 + * @package Parsely
578 + */
579 + public function print_required_settings() {
580 + // We can optionally print some text here in the future, but we don't
581 + // need to now.
582 + }
583 +
584 + /**
585 + * Not doing anything here
586 + *
587 + * @category Function
588 + * @package Parsely
589 + */
590 + public function print_optional_settings() {
591 + // We can optionally print some text here in the future, but we don't
592 + // need to now.
593 + }
594 +
595 + /**
596 + * Adds a 'Settings' link to the Plugins screen in WP admin
597 + *
598 + * @category Function
599 + * @package Parsely
600 + * @param array $links The links to add.
601 + */
602 + public function add_plugin_meta_links( $links ) {
603 + array_unshift( $links, '<a href="' . $this->get_settings_url() . '">' . __( 'Settings' ) . '</a>' );
604 + return $links;
605 + }
606 +
607 + /**
608 + * Display the admin warning if needed
609 + *
610 + * @category Function
611 + * @package Parsely
612 + */
613 + public function display_admin_warning() {
614 + $options = $this->get_options();
615 + if ( ! isset( $options['apikey'] ) || empty( $options['apikey'] ) ) {
616 + ?>
617 + <div id='message' class='error'>
618 + <p>
619 + <strong>Parse.ly - Dash plugin is not active.</strong>
620 + You need to
621 + <a href='<?php echo esc_html( $this->get_settings_url() ); ?>'>
622 + provide your Parse.ly Dash Site ID
623 + </a>
624 + before things get cooking.
625 + </p>
626 + </div>
627 + <?php
628 + }
629 + }
630 +
631 + /**
632 + * Show our note about dynamic tracking
633 + *
634 + * @category Function
635 + * @package Parsely
636 + */
637 + public function print_dynamic_tracking_note() {
638 + printf(
639 + '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.',
640 + esc_url( 'http://www.parsely.com/help/integration/basic/' ),
641 + esc_url( 'https://www.parsely.com/help/integration/dynamic/' ),
642 + esc_url( 'mailto:support@parsely.com' )
643 + );
644 + }
645 +
646 + /**
647 + * End the code coverage ignore
648 + *
649 + * @codeCoverageIgnoreEnd
650 + */
651 +
652 + /**
653 + * Actually inserts the code for the <meta name='parsely-page'> parameter within the <head></head> tag.
654 + */
655 + public function insert_parsely_page() {
656 + $parsely_options = $this->get_options();
657 +
658 + // 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.
659 + if ( empty( $parsely_options['apikey'] ) || ( ! $parsely_options['track_authenticated_users'] && $this->parsely_is_user_logged_in() ) ) {
660 + return '';
661 + }
662 +
663 + global $wp_query;
664 + global $post;
665 + // Assign default values for LD+JSON.
666 + // TODO: Maping of an install's post types to Parse.ly post types, namely page/post.
667 + $parsely_page = array(
668 + '@context' => 'http://schema.org',
669 + '@type' => 'WebPage',
670 + );
671 +
672 + $current_url = $this->get_current_url();
673 +
674 + if ( in_array( get_post_type(), $parsely_options['track_post_types'], true ) && 'publish' === $post->post_status ) {
675 + $authors = $this->get_author_names( $post );
676 + $category = $this->get_category_name( $post, $parsely_options );
677 + $post_id = $parsely_options['content_id_prefix'] . (string) get_the_ID();
678 +
679 + if ( has_post_thumbnail() ) {
680 + $image_id = get_post_thumbnail_id();
681 + $image_url = wp_get_attachment_image_src( $image_id );
682 + $image_url = $image_url[0];
683 + } else {
684 + $image_url = $this->get_first_image( $post );
685 + }
686 +
687 + $tags = $this->get_tags( $post->ID );
688 + if ( $parsely_options['cats_as_tags'] ) {
689 + $tags = array_merge( $tags, $this->get_categories( $post->ID ) );
690 + // Add custom taxonomy values.
691 + $tags = array_merge( $tags, $this->get_custom_taxonomy_values( $post, $parsely_options ) );
692 + }
693 + // The function 'mb_strtolower' is not enabled by default in php, so this check
694 + // falls back to the native php function 'strtolower' if necessary.
695 + if ( function_exists( 'mb_strtolower' ) ) {
696 + $lowercase_callback = 'mb_strtolower';
697 + } else {
698 + $lowercase_callback = 'strtolower';
699 + }
700 + if ( $parsely_options['lowercase_tags'] ) {
701 + $tags = array_map( $lowercase_callback, $tags );
702 + }
703 + $tags = apply_filters( 'wp_parsely_post_tags', $tags, $post->ID );
704 + $tags = array_map( array( $this, 'get_clean_parsely_page_value' ), $tags );
705 + $tags = array_values( array_unique( $tags ) );
706 +
707 + $parsely_page['@type'] = 'NewsArticle';
708 + $parsely_page['mainEntityOfPage'] = array(
709 + '@type' => 'WebPage',
710 + '@id' => $this->get_current_url( 'post' ),
711 + );
712 + $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_the_title() );
713 + $parsely_page['url'] = $this->get_current_url( 'post' );
714 + if ( ! empty( $image_url ) ) {
715 + $parsely_page['thumbnailUrl'] = $image_url;
716 + $parsely_page['image'] = array(
717 + '@type' => 'ImageObject',
718 + 'url' => $image_url,
719 + );
720 + }
721 + $parsely_page['dateCreated'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true ) );
722 + $parsely_page['datePublished'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true ) );
723 +
724 + if ( get_the_modified_date( 'U', true ) >= get_post_time( 'U', true ) ) {
725 + $parsely_page['dateModified'] = gmdate( 'Y-m-d\TH:i:s\Z', get_the_modified_date( 'U', true ) );
726 + } else {
727 + // Use the post time as the earliest possible modification date.
728 + $parsely_page['dateModified'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true ) );
729 + }
730 +
731 + $author_objects = array();
732 + foreach ( $authors as $author ) {
733 + $author_tag = array(
734 + '@type' => 'Person',
735 + 'name' => $author,
736 + );
737 + array_push( $author_objects, $author_tag );
738 + }
739 +
740 + $parsely_page['articleSection'] = $category;
741 + $parsely_page['author'] = $author_objects;
742 + $parsely_page['creator'] = $authors;
743 + $parsely_page['keywords'] = $tags;
744 +
745 + $parsely_page['publisher'] = array(
746 + '@type' => 'Organization',
747 + 'name' => get_bloginfo( 'name' ),
748 + );
749 +
750 + } elseif ( in_array( get_post_type(), $parsely_options['track_page_types'], true ) && 'publish' === $post->post_status ) {
751 + $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_the_title() );
752 + $parsely_page['url'] = $this->get_current_url( 'post' );
753 + }
754 + if ( is_front_page() ) {
755 + $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_bloginfo( 'name', 'raw' ) );
756 + $parsely_page['url'] = get_home_url();
757 + $parsely_page['@type'] = 'WebPage';
758 + }
759 + if ( is_archive() ) {
760 + $parsely_page['@type'] = 'WebPage';
761 + $parsely_page['url'] = $this->get_current_url();
762 + if ( is_author() ) {
763 + $author = get_user_by( 'slug', get_query_var( 'author_name' ) );
764 + $parsely_page['headline'] = $this->get_clean_parsely_page_value( 'Author - ' . $author->display_name );
765 + } else {
766 + $parsely_page['headline'] = get_the_archive_title();
767 + }
768 + }
769 +
770 + $parsely_page = apply_filters( 'after_set_parsely_page', $parsely_page, $post, $parsely_options );
771 + include 'parsely-parsely-page.php';
772 + return $parsely_page;
773 + }
774 +
775 + /**
776 + * Inserts the JavaScript code required to send off beacon requests
777 + */
778 + public function insert_parsely_javascript() {
779 + $parsely_options = $this->get_options();
780 + // If we don't have an API key, there's no need to proceed.
781 + if ( empty( $parsely_options['apikey'] ) || $parsely_options['disable_javascript'] ) {
782 + return '';
783 + }
784 +
785 + global $post;
786 + $display = true;
787 + if ( in_array( get_post_type(), $parsely_options['track_post_types'], true ) && 'publish' !== $post->post_status ) {
788 + $display = false;
789 + }
790 + if ( ! $parsely_options['track_authenticated_users'] && $this->parsely_is_user_logged_in() ) {
791 + $display = false;
792 + }
793 + if ( ! in_array( get_post_type(), $parsely_options['track_post_types'], true ) && ! in_array( get_post_type(), $parsely_options['track_page_types'], true ) ) {
794 + $display = false;
795 + }
796 + if ( $display ) {
797 + include 'parsely-javascript.php';
798 + }
799 + }
800 +
801 + /**
802 + * Print out the select tags
803 + *
804 + * @param array $args The arguments for the select drop downs.
805 + */
806 + public function print_select_tag( $args ) {
807 + $options = $this->get_options();
808 + $name = $args['option_key'];
809 + $select_options = $args['select_options'];
810 + if ( isset( $args['multiple'] ) ) {
811 + $multiple = $args['multiple'];
812 + } else {
813 + $multiple = false;
814 + }
815 + $selected = isset( $options[ $name ] ) ? $options[ $name ] : null;
816 + $optional_args = isset( $args['optional_args'] ) ? $args['optional_args'] : array();
817 + $id = esc_attr( $name );
818 + $name = Parsely::OPTIONS_KEY . "[$id]";
819 +
820 + if ( isset( $args['help_text'] ) ) {
821 + echo '<div class="parsely-form-controls" data-has-help-text="true">';
822 + }
823 + if ( isset( $args['requires_recrawl'] ) ) {
824 + echo '<div class="parsely-form-controls" data-requires-recrawl="true">';
825 + }
826 +
827 + if ( $multiple ) {
828 + echo sprintf( "<select multiple='multiple' name='%s[]'id='%s'", esc_attr( $name ), esc_attr( $name ) );
829 + } else {
830 + echo sprintf( "<select name='%s' id='%s'", esc_attr( $name ), esc_attr( $name ) );
831 + }
832 +
833 + foreach ( $optional_args as $key => $val ) {
834 + echo ' ' . esc_attr( $key ) . '="' . esc_attr( $val ) . '"';
835 + }
836 + echo '>';
837 +
838 + foreach ( $select_options as $key => $val ) {
839 + echo '<option value="' . esc_attr( $key ) . '" ';
840 +
841 + if ( $multiple ) {
842 + $selected = in_array( $val, $options[ $args['option_key'] ], true );
843 + echo selected( $selected, true, false ) . '>';
844 + } else {
845 + echo selected( $selected, $key, false ) . '>';
846 + }
847 + echo esc_html( $val );
848 + echo '</option>';
849 + }
850 + echo '</select>';
851 +
852 + if ( isset( $args['help_text'] ) ) {
853 + if ( isset( $args['help_link'] ) ) {
854 + echo '<div class="help-text"> <p class="description">' .
855 + sprintf( esc_html( $args['help_text'] ), '<a href="', esc_url( $args['help_link'] ), '">', '</a>' ) .
856 + '</p></div>';
857 + } else {
858 + echo '<div class="help-text"> <p class="description">' . esc_html( $args['help_text'] ) . '</p></div>';
859 + }
860 + }
861 + echo '</div>';
862 + }
863 +
864 + /**
865 + * Print out the radio buttons
866 + *
867 + * @param array $args The arguments for the radio buttons.
868 + */
869 + public function print_binary_radio_tag( $args ) {
870 + $options = $this->get_options();
871 + $name = $args['option_key'];
872 + $value = $options[ $name ];
873 + $id = esc_attr( $name );
874 + $name = Parsely::OPTIONS_KEY . "[$id]";
875 +
876 + if ( isset( $args['help_text'] ) ) {
877 + echo '<div class="parsely-form-controls" data-has-help-text="true">';
878 + }
879 + if ( isset( $args['requires_recrawl'] ) ) {
880 + echo '<div class="parsely-form-controls" data-requires-recrawl="true">';
881 + }
882 +
883 + echo sprintf( "<input type='radio' name='%s' id='%s_true' value='true' ", esc_attr( $name ), esc_attr( $id ) );
884 + echo checked( true === $value, true, false );
885 + 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 ) );
886 + echo checked( true !== $value, true, false );
887 + echo sprintf( " /> <label for='%s_false'>No</label>", esc_attr( $id ) );
888 +
889 + if ( isset( $args['help_text'] ) ) {
890 + echo '<div class="help-text"><p class="description">' . esc_html( $args['help_text'] ) . '</p></div>';
891 + }
892 + echo '</div>';
893 +
894 + }
895 +
896 + /**
897 + * Print out the radio buttons
898 + *
899 + * @param array $args The arguments for text tags.
900 + */
901 + public function print_text_tag( $args ) {
902 + $options = $this->get_options();
903 + $name = $args['option_key'];
904 + $value = isset( $options[ $name ] ) ? $options[ $name ] : '';
905 + $optional_args = isset( $args['optional_args'] ) ? $args['optional_args'] : array();
906 + $id = esc_attr( $name );
907 + $name = Parsely::OPTIONS_KEY . "[$id]";
908 + $value = esc_attr( $value );
909 +
910 + if ( isset( $args['help_text'] ) ) {
911 + echo '<div class="parsely-form-controls" data-has-help-text="true">';
912 + }
913 + if ( isset( $args['requires_recrawl'] ) ) {
914 + echo '<div class="parsely-form-controls" data-requires-recrawl="true">';
915 + }
916 +
917 + echo sprintf( "<input type='text' name='%s' id='%s' value='%s'", esc_attr( $name ), esc_attr( $id ), esc_attr( $value ) );
918 + foreach ( $optional_args as $key => $val ) {
919 + echo ' ' . esc_attr( $key ) . '="' . esc_attr( $val ) . '"';
920 + }
921 + if ( isset( $args['requires_recrawl'] ) ) {
922 + echo ' data-requires-recrawl="true"';
923 + }
924 + echo ' />';
925 +
926 + if ( isset( $args['help_text'] ) ) {
927 + if ( isset( $args['help_link'] ) ) {
928 + echo ' <div class="help-text" id="' .
929 + esc_attr( $args['option_key'] ) .
930 + '_help_text"><p class="description">' .
931 + sprintf( esc_html( $args['help_text'] ), '<a href="', esc_url( $args['help_link'] ), '">', '</a>' ) .
932 + '</p>' .
933 + '</div>';
934 + } else {
935 + echo ' <div class="help-text" id="' .
936 + esc_attr( $args['option_key'] ) .
937 + '_help_text"><p class="description">' .
938 + esc_html( $args['help_text'] ) . '</p>' .
939 + '</div>';
940 + }
941 + }
942 + }
943 +
944 + /**
945 + * Extracts a host ( not TLD ) from a URL
946 + *
947 + * @param string $url The url of the host.
948 + */
949 + private function get_host_from_url( $url ) {
950 + if ( preg_match( '/^https?:\/\/( [^\/]+ )\/.*$/', $url, $matches ) ) {
951 + return $matches[1];
952 + } else {
953 + return $url;
954 + }
955 + }
956 +
957 + /**
958 + * Returns the tags associated with this page or post
959 + *
960 + * @param string $post_id The id of the post you're trying to get tags for.
961 + */
962 + private function get_tags( $post_id ) {
963 + $tags = array();
964 + $wp_tags = wp_get_post_tags( $post_id );
965 + foreach ( $wp_tags as $wp_tag ) {
966 + array_push( $tags, $wp_tag->name );
967 + }
968 +
969 + return $tags;
970 + }
971 +
972 + /**
973 + * Returns an array of all the child categories for the current post
974 + *
975 + * @param string $post_id The id of the post you're trying to get categories for.
976 + * @param string $delimiter What character will delimit the categories.
977 + */
978 + private function get_categories( $post_id, $delimiter = '/' ) {
979 + $tags = array();
980 + $categories = get_the_category( $post_id );
981 + foreach ( $categories as $category ) {
982 + $hierarchy = get_category_parents( $category, false, $delimiter );
983 + $hierarchy = rtrim( $hierarchy, '/' );
984 + array_push( $tags, $hierarchy );
985 + }
986 + // take last element in the hierarchy, a string representing the full parent->child tree,
987 + // and split it into individual category names.
988 + $tags = explode( '/', end( $tags ) );
989 + // remove uncategorized value from tags.
990 + $tags = array_diff( $tags, array( 'Uncategorized' ) );
991 + return $tags;
992 + }
993 +
994 + /**
995 + * Safely returns options for the plugin by assigning defaults contained in optionDefaults. As soon as actual
996 + * options are saved, they override the defaults. This prevents us from having to do a lot of isset() checking
997 + * on variables.
998 + */
999 + private function get_options() {
1000 + $options = get_option( Parsely::OPTIONS_KEY );
1001 + if ( false === $options ) {
1002 + $options = $this->option_defaults;
1003 + } else {
1004 + $options = array_merge( $this->option_defaults, $options );
1005 + }
1006 + return $options;
1007 + }
1008 +
1009 + /**
1010 + * Returns a properly cleaned category/taxonomy value and will optionally use the top-level category/taxonomy value
1011 + * if so instructed via the `use_top_level_cats` option.
1012 + *
1013 + * @param Parsely $post_obj The object for the post.
1014 + * @param array $parsely_options The parsely options.
1015 + */
1016 + private function get_category_name( $post_obj, $parsely_options ) {
1017 + $taxonomy_dropdown_choice = get_the_terms( $post_obj->ID, $parsely_options['custom_taxonomy_section'] );
1018 + // Get top-level taxonomy name for chosen taxonomy and assign to $parent_name; it will be used
1019 + // as the category value if 'use_top_level_cats' option is checked.
1020 + // Assign as "Uncategorized" if no value is checked for the chosen taxonomy.
1021 + if ( ! empty( $taxonomy_dropdown_choice ) ) {
1022 + $first_term = array_shift( $taxonomy_dropdown_choice );
1023 + $parent_name = $this->get_top_level_term( $first_term->term_id, $first_term->taxonomy );
1024 + $child_name = $this->get_bottom_level_term( $post_obj->ID, $parsely_options['custom_taxonomy_section'] );
1025 + $category = $parsely_options['use_top_level_cats'] ? $parent_name : $child_name;
1026 + } else {
1027 + $category = 'Uncategorized';
1028 + }
1029 + $category = apply_filters( 'wp_parsely_post_category', $category, $post_obj, $parsely_options );
1030 + $category = $this->get_clean_parsely_page_value( $category );
1031 + return $category;
1032 + }
1033 +
1034 + /**
1035 + * Return the top-most category/taxonomy value in a hierarcy given a taxonomy value's ID
1036 + * ( WordPress calls taxonomy values 'terms' ).
1037 + *
1038 + * @param string $term_id The id of the top level term.
1039 + * @param string $taxonomy_name The name of the taxonomy.
1040 + */
1041 + private function get_top_level_term( $term_id, $taxonomy_name ) {
1042 + $parent = get_term_by( 'id', $term_id, $taxonomy_name );
1043 + while ( 0 !== $parent->parent ) {
1044 + $parent = get_term_by( 'id', $parent->parent, $taxonomy_name );
1045 + }
1046 + return $parent->name;
1047 + }
1048 +
1049 + /**
1050 + * Return the bottom-most category/taxonomy value in a hierarcy given a post ID
1051 + * ( WordPress calls taxonomy values 'terms' ).
1052 + *
1053 + * @param string $post_id The post id you're interested in.
1054 + * @param string $taxonomy_name The name of the taxonomy.
1055 + */
1056 + private function get_bottom_level_term( $post_id, $taxonomy_name ) {
1057 + $terms = get_the_terms( $post_id, $taxonomy_name );
1058 + $term_ids = wp_list_pluck( $terms, 'term_id' );
1059 + $parents = array_filter( wp_list_pluck( $terms, 'parent' ) );
1060 +
1061 + // Get array of IDs of terms which are not parents.
1062 + $term_ids_not_parents = array_diff( $term_ids, $parents );
1063 + // Get corresponding term objects, which are mapped to array index keys.
1064 + $terms_not_parents = array_intersect_key( $terms, $term_ids_not_parents );
1065 + // remove array index keys.
1066 + $terms_not_parents_cleaned = array();
1067 + foreach ( $terms_not_parents as $index => $value ) {
1068 + array_push( $terms_not_parents_cleaned, $value );
1069 + }
1070 + // if you assign multiple child terms in a custom taxonomy, will only return the first.
1071 + return $terms_not_parents_cleaned[0]->name;
1072 + }
1073 +
1074 + /**
1075 + * Get all term values from custom taxonomies.
1076 + *
1077 + * @param Parsely $post_obj The post object.
1078 + * @param Parsely $parsely_options The pparsely options.
1079 + */
1080 + private function get_custom_taxonomy_values( $post_obj, $parsely_options ) {
1081 + // filter out default WordPress taxonomies.
1082 + $all_taxonomies = array_diff( get_taxonomies(), array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) );
1083 + $all_values = array();
1084 +
1085 + if ( is_array( $all_taxonomies ) ) {
1086 + foreach ( $all_taxonomies as $taxonomy ) {
1087 + $custom_taxonomy_objects = get_the_terms( $post_obj->ID, $taxonomy );
1088 + if ( is_array( $custom_taxonomy_objects ) ) {
1089 + foreach ( $custom_taxonomy_objects as $custom_taxonomy_object ) {
1090 + array_push( $all_values, $custom_taxonomy_object->name );
1091 + }
1092 + }
1093 + }
1094 + }
1095 + return $all_values;
1096 + }
1097 +
1098 + /**
1099 + * Returns a list of coauthors for a post assuming the coauthors plugin is
1100 + * installed. Borrowed from
1101 + * https://github.com/Automattic/Co-Authors-Plus/blob/master/template-tags.php#L3-35
1102 + *
1103 + * @param string $post_id The id of the post.
1104 + */
1105 + private function get_coauthor_names( $post_id ) {
1106 + $coauthors = array();
1107 + if ( class_exists( 'coauthors_plus' ) ) {
1108 + global $post, $post_ID, $coauthors_plus, $wpdb;
1109 +
1110 + $post_id = (int) $post_id;
1111 + if ( ! $post_id && $post_ID ) {
1112 + $post_id = $post_ID;
1113 + }
1114 +
1115 + if ( ! $post_id && $post ) {
1116 + $post_id = $post->ID;
1117 + }
1118 +
1119 + if ( $post_id ) {
1120 + $coauthor_terms = get_the_terms( $post_id, $coauthors_plus->coauthor_taxonomy );
1121 +
1122 + if ( is_array( $coauthor_terms ) && ! empty( $coauthor_terms ) ) {
1123 + foreach ( $coauthor_terms as $coauthor ) {
1124 + $coauthor_slug = preg_replace( '#^cap\-#', '', $coauthor->slug );
1125 + $post_author = $coauthors_plus->get_coauthor_by( 'user_nicename', $coauthor_slug );
1126 + // In case the user has been deleted while plugin was deactivated.
1127 + if ( ! empty( $post_author ) ) {
1128 + $coauthors[] = $post_author;
1129 + }
1130 + }
1131 + } elseif ( ! $coauthors_plus->force_guest_authors ) {
1132 + if ( $post && $post_id === $post->ID ) {
1133 + $post_author = get_userdata( $post->post_author );
1134 + }
1135 + if ( ! empty( $post_author ) ) {
1136 + $coauthors[] = $post_author;
1137 + }
1138 + } // the empty else case is because if we force guest authors, we don't ever care what value wp_posts.post_author has.
1139 + }
1140 + }
1141 + return $coauthors;
1142 + }
1143 +
1144 + /**
1145 + * Determine author name from display name, falling back to firstname
1146 + * lastname, then nickname and finally the nicename.
1147 + *
1148 + * @param string $author The author of the post.
1149 + */
1150 + private function get_author_name( $author ) {
1151 + $author_name = $author->display_name;
1152 + if ( ! empty( $author_name ) ) {
1153 + return $author_name;
1154 + }
1155 +
1156 + $author_name = $author->user_firstname . ' ' . $author->user_lastname;
1157 + if ( ' ' !== $author_name ) {
1158 + return $author_name;
1159 + }
1160 +
1161 + $author_name = $author->nickname;
1162 + if ( ! empty( $author_name ) ) {
1163 + return $author_name;
1164 + }
1165 +
1166 + return $author->user_nicename;
1167 + }
1168 +
1169 + /**
1170 + * Retrieve all the authors for a post as an array. Can include multiple
1171 + * authors if coauthors plugin is in use.
1172 + *
1173 + * @param Parsely $post The post object.
1174 + */
1175 + private function get_author_names( $post ) {
1176 + $authors = $this->get_coauthor_names( $post->ID );
1177 + if ( empty( $authors ) ) {
1178 + $authors = array( get_user_by( 'id', $post->post_author ) );
1179 + }
1180 + $authors = array_map( array( $this, 'get_author_name' ), $authors );
1181 + $authors = apply_filters( 'wp_parsely_post_authors', $authors, $post );
1182 + $authors = array_map( array( $this, 'get_clean_parsely_page_value' ), $authors );
1183 + return $authors;
1184 + }
1185 +
1186 + /**
1187 + * Sanitize content
1188 + *
1189 + * @param string $val The content you'd like sanitized.
1190 + */
1191 + private function get_clean_parsely_page_value( $val ) {
1192 + if ( is_string( $val ) ) {
1193 + $val = str_replace( "\n", '', $val );
1194 + $val = str_replace( "\r", '', $val );
1195 + $val = strip_tags( $val );
1196 + $val = trim( $val );
1197 + return $val;
1198 + } else {
1199 + return $val;
1200 + }
1201 + }
1202 +
1203 +
1204 + /**
1205 + * Get the URL of the plugin settings page
1206 + */
1207 + private function get_settings_url() {
1208 + return admin_url( 'options-general.php?page=' . Parsely::MENU_SLUG );
1209 + }
1210 +
1211 +
1212 + /**
1213 + * Get the URL of the current PHP script.
1214 + * A fall-back implementation to determine permalink
1215 + *
1216 + * @param string $post The post object you're interested in.
1217 + */
1218 + private function get_current_url( $post = 'nonpost' ) {
1219 + $options = $this->get_options();
1220 + $scheme = ( $options['force_https_canonicals'] ? 'https://' : 'http://' );
1221 +
1222 + if ( 'post' === $post ) {
1223 + $permalink = get_permalink();
1224 + $parsed_canonical = wp_parse_url( $permalink );
1225 + $canonical = $scheme . $parsed_canonical['host'] . $parsed_canonical['path'];
1226 + return $canonical;
1227 + }
1228 + $page_url = site_url( null, $scheme );
1229 +
1230 + if ( isset( $_SERVER['SERVER_PORT'] ) ) { // Input var okay.
1231 + $port_number = intval( $_SERVER['SERVER_PORT'] ); // Input var okay.
1232 + }
1233 + if ( 80 !== $port_number && 443 !== $port_number ) {
1234 + $page_url .= ':' . $port_number;
1235 + }
1236 + if ( isset( $_SERVER['REQUEST_URI'] ) ) { // Input var okay.
1237 + $page_url .= sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ); // Input var okay.
1238 + }
1239 + return $page_url;
1240 + }
1241 +
1242 + /**
1243 + * Get the first image from a post
1244 + * https://css-tricks.com/snippets/wordpress/get-the-first-image-from-a-post/
1245 + *
1246 + * @param Parsely $post The post object you're interested in.
1247 + */
1248 + public function get_first_image( $post ) {
1249 + ob_start();
1250 + ob_end_clean();
1251 + if ( preg_match_all( '/<img.+src=[\'"]( [^\'"]+ )[\'"].*>/i', $post->post_content, $matches ) ) {
1252 + $first_img = $matches[1][0];
1253 + return $first_img;
1254 + }
1255 + return '';
1256 + }
1257 +
1258 + /**
1259 + * Add parsely tracking to facebook instant articles
1260 + *
1261 + * @param type $registry The registry info for fbia.
1262 + */
1263 + public function insert_parsely_tracking_fbia( &$registry ) {
1264 + $options = $this->get_options();
1265 + $display_name = 'Parsely Analytics';
1266 + $identifier = 'parsely-analytics-for-wordpress';
1267 +
1268 + $embed_code = '<script>
1269 + PARSELY = {
1270 + autotrack: false,
1271 + onload: function() {
1272 + PARSELY.beacon.trackPageView({
1273 + urlref: \'http://facebook.com/instantarticles\'
1274 + });
1275 + return true;
1276 + }
1277 + }
1278 + </script>
1279 + <div id="parsely-root" style="display: none">
1280 + <span id="parsely-cfg" data-parsely-site="' . esc_attr( $options['apikey'] ) . '"></span>
1281 + </div>
1282 + <script>
1283 + ( function(s, p, d ) {
1284 + var h=d.location.protocol, i=p+"-"+s,
1285 + e=d.getElementById( i), r=d.getElementById(p+"-root" ),
1286 + u=h==="https:"?"d1z2jf7jlzjs58.cloudfront.net"
1287 + :"static."+p+".com";
1288 + if ( e ) return;
1289 + e = d.createElement( s ); e.id = i; e.async = true;
1290 + e.src = h+"//"+u+"/p.js"; r.appendChild( e );
1291 + })( "script", "parsely", document );
1292 + </script>
1293 + <!-- END Parse.ly Include: Standard -->';
1294 +
1295 + $registry[ $identifier ] = array(
1296 + 'name' => $display_name,
1297 + 'payload' => $embed_code,
1298 + );
1299 +
1300 + return $embed_code;
1301 + }
1302 +
1303 + /**
1304 + * Add amp actions.
1305 + */
1306 + public function parsely_add_amp_actions() {
1307 + add_filter( 'amp_post_template_analytics', array( $this, 'parsely_add_amp_analytics' ) );
1308 + }
1309 +
1310 + /**
1311 + * Add amp analytics.
1312 + *
1313 + * @param type $analytics The analytics object you want to add.
1314 + */
1315 + public function parsely_add_amp_analytics( $analytics ) {
1316 + $options = $this->get_options();
1317 +
1318 + if ( empty( $options['apikey'] ) ) {
1319 + return $analytics;
1320 + }
1321 +
1322 + $analytics['parsely'] = array(
1323 + 'type' => 'parsely',
1324 + 'attributes' => array(),
1325 + 'config_data' => array(
1326 + 'vars' => array(
1327 + 'apikey' => $options['apikey'],
1328 + ),
1329 + ),
1330 + );
1331 +
1332 + return $analytics;
1333 + }
1334 +
1335 + /**
1336 + * Check to see if parsely user is logged in
1337 + */
1338 + public function parsely_is_user_logged_in() {
1339 + // can't use $blog_id here because it futzes with the global $blog_id.
1340 + $current_blog_id = get_current_blog_id();
1341 + $current_user_id = get_current_user_id();
1342 + return is_user_member_of_blog( $current_user_id, $current_blog_id );
1343 + }
1344 +
1345 + /**
1346 + * Why is this here?
1347 + */
1348 + public function return_personalized_json() {
1349 +
1350 + }
172 1351 }
173 1352
174 -require __DIR__ . '/src/Integrations/class-integration.php';
175 -require __DIR__ . '/src/Integrations/class-integrations.php';
176 -require __DIR__ . '/src/Integrations/class-amp.php';
177 -require __DIR__ . '/src/Integrations/class-facebook-instant-articles.php';
178 -require __DIR__ . '/src/Integrations/class-google-web-stories.php';
179 1353
180 -add_action( 'init', __NAMESPACE__ . '\\parsely_integrations' );
181 -/**
182 - * Instantiate Integrations collection and register built-in integrations.
183 - *
184 - * @since 2.6.0
185 - *
186 - * @return Integrations
187 - */
188 -function parsely_integrations(): Integrations {
189 - $parsely_integrations = new Integrations();
190 - $parsely_integrations->register( 'amp', Amp::class );
191 - $parsely_integrations->register( 'fbia', Facebook_Instant_Articles::class );
192 - $parsely_integrations->register( 'webstories', Google_Web_Stories::class );
193 - $parsely_integrations = apply_filters( 'wp_parsely_add_integration', $parsely_integrations );
194 - $parsely_integrations->integrate();
195 1354
196 - return $parsely_integrations;
1355 +
1356 +if ( class_exists( 'Parsely' ) ) {
1357 + define( 'PARSELY_VERSION', Parsely::VERSION );
1358 + $parsely = new Parsely();
197 1359 }
1360 +
1361 +require 'class-parsely-recommended-widget.php';