'', 'content_id_prefix' => '', 'api_secret' => '', 'use_top_level_cats' => false, 'custom_taxonomy_section' => 'category', 'cats_as_tags' => false, 'track_authenticated_users' => true, 'lowercase_tags' => true, 'force_https_canonicals' => false, 'track_post_types' => array( 'post' ), 'track_page_types' => array( 'page' ), 'disable_javascript' => false, 'disable_amp' => false, 'meta_type' => 'json_ld', 'logo' => '', 'metadata_secret' => '', 'parsely_wipe_metadata_cache' => false, ); /** * Declare post types that Parse.ly will process as "posts". * * @link https://www.parse.ly/help/integration/jsonld#distinguishing-between-posts-and-pages * * @since 2.5.0 * @var string[] */ private $supported_jsonld_post_types = array( 'NewsArticle', 'Article', 'TechArticle', 'BlogPosting', 'LiveBlogPosting', 'Report', 'Review', 'CreativeWork', ); /** * Declare post types that Parse.ly will process as "non-posts". * * @link https://www.parse.ly/help/integration/jsonld#distinguishing-between-posts-and-pages * * @since 2.5.0 * @var string[] */ private $supported_jsonld_non_post_types = array( 'WebPage', 'Event', 'Hotel', 'Restaurant', 'Movie', ); /** * The constructor * * @category Function * @package Parsely */ public function __construct() { // Run upgrade options if they exist for the version currently defined. $options = $this->get_options(); if ( empty( $options['plugin_version'] ) || self::VERSION !== $options['plugin_version'] ) { $method = 'upgrade_plugin_to_version_' . str_replace( '.', '_', self::VERSION ); if ( method_exists( $this, $method ) ) { call_user_func_array( array( $this, $method ), array( $options ) ); } // Update our version info. $options['plugin_version'] = self::VERSION; update_option( self::OPTIONS_KEY, $options ); } // admin_menu and a settings link. add_action( 'admin_head-settings_page_parsely', array( $this, 'add_admin_header' ) ); add_action( 'admin_menu', array( $this, 'add_settings_sub_menu' ) ); add_action( 'admin_init', array( $this, 'initialize_settings' ) ); // display warning when plugin hasn't been configured. add_action( 'admin_footer', array( $this, 'display_admin_warning' ) ); add_filter( 'plugin_action_links_' . PARSELY_PLUGIN_BASENAME, array( $this, 'add_plugin_meta_links' ) ); add_filter( 'cron_schedules', [ $this, 'wpparsely_add_cron_interval' ] ); add_action( 'parsely_bulk_metas_update', array( $this, 'bulk_update_posts' ) ); // inserting parsely code. add_action( 'wp_head', array( $this, 'insert_parsely_page' ) ); add_action( 'init', array( $this, 'register_js' ) ); // load_js_api should be called prior to load_js_tracker so the relevant scripts are enqueued in order. add_action( 'wp_footer', array( $this, 'load_js_api' ) ); add_action( 'wp_footer', array( $this, 'load_js_tracker' ) ); add_action( 'save_post', array( $this, 'update_metadata_endpoint' ) ); add_action( 'instant_articles_compat_registry_analytics', array( $this, 'insert_parsely_tracking_fbia' ) ); add_action( 'template_redirect', array( $this, 'parsely_add_amp_actions' ) ); add_action( 'wp_enqueue_scripts', [ $this, 'wp_parsely_style_init' ] ); } /** * Adds 10 minute cron interval * * @param array $schedules WP schedules array. */ public function wpparsely_add_cron_interval( $schedules ) { $schedules['everytenminutes'] = array( 'interval' => 600, // time in seconds. 'display' => __( 'Every 10 Minutes', 'wp-parsely' ), ); return $schedules; } /** * Initialize parsely WordPress style */ public function wp_parsely_style_init() { wp_register_style( 'wp-parsely-style', PARSELY_PLUGIN_URL . 'wp-parsely.css', array(), Parsely::VERSION ); } /** * Include the parsely admin header * * @category Function * @package Parsely */ public function add_admin_header() { echo ' '; $admin_script_asset = require PARSELY_PLUGIN_DIR . 'build/admin-page.asset.php'; wp_enqueue_script( 'wp-parsely-admin', PARSELY_PLUGIN_URL . 'build/admin-page.js', $admin_script_asset[ 'dependencies' ], self::get_asset_cache_buster(), true ); } /** * Parsely settings page in WordPress settings menu. * * @category Function * @package Parsely */ public function add_settings_sub_menu() { add_options_page( __( 'Parse.ly Settings', 'wp-parsely' ), __( 'Parse.ly', 'wp-parsely' ), self::CAPABILITY, self::MENU_SLUG, array( $this, 'display_settings' ) ); } /** * Parse.ly settings screen ( options-general.php?page=[MENU_SLUG] ) * * @category Function * @package Parsely */ public function display_settings() { if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'wp-parsely' ) ); } include PARSELY_PLUGIN_DIR . 'src/parsely-settings.php'; } /** * Initialize the settings for Parsely * * @category Function * @package Parsely */ public function initialize_settings() { // All our options are actually stored in one single array to reduce // DB queries. register_setting( self::OPTIONS_KEY, self::OPTIONS_KEY, array( $this, 'validate_options' ) ); // These are the Required Settings. add_settings_section( 'required_settings', __( 'Required Settings', 'wp-parsely' ), array( $this, 'print_required_settings' ), self::MENU_SLUG ); // Get the API Key. $h = __( 'Your Site ID is your own site domain ( e.g. `mydomain.com` )', 'wp-parsely' ); $field_args = array( 'option_key' => 'apikey', 'help_text' => $h, ); add_settings_field( 'apikey', __( 'Parse.ly Site ID', 'wp-parsely' ), array( $this, 'print_text_tag' ), self::MENU_SLUG, 'required_settings', $field_args ); // These are the Optional Settings. add_settings_section( 'optional_settings', __( 'Optional Settings', 'wp-parsely' ), array( $this, 'print_optional_settings' ), self::MENU_SLUG ); /* translators: 1: Opening anchor tag markup, 2: Documentation URL, 3: Opening anchor tag markup continued, 4: Closing anchor tag */ $h = __( 'Your API secret is your secret code to %1$s%2$s%3$saccess our API.%4$s It can be found at dash.parsely.com/yoursitedomain/settings/api ( replace yoursitedomain with your domain name, e.g. `mydomain.com` ) If you haven\'t purchased access to the API, and would like to do so, email your account manager or support@parsely.com!', 'wp-parsely' ); $h_link = 'https://www.parse.ly/help/api/analytics/'; $field_args = array( 'option_key' => 'api_secret', 'help_text' => $h, 'help_link' => $h_link, ); add_settings_field( 'api_secret', __( 'Parse.ly API Secret', 'wp-parsely' ), array( $this, 'print_text_tag' ), self::MENU_SLUG, 'optional_settings', $field_args ); $h = __( 'Your metadata secret is given to you by Parse.ly support. DO NOT enter anything here unless given to you by Parse.ly support!', 'wp-parsely' ); $h_link = 'https://www.parse.ly/help/api/analytics/'; $field_args = array( 'option_key' => 'metadata_secret', 'help_text' => $h, 'help_link' => $h_link, ); add_settings_field( 'metadata_secret', __( 'Parse.ly Metadata Secret', 'wp-parsely' ), array( $this, 'print_text_tag' ), self::MENU_SLUG, 'optional_settings', $field_args ); // Clear metadata. $h = __( 'Check this radio button and hit "Save Changes" to clear all metadata information for Parse.ly posts and re-send all metadata to Parse.ly. WARNING: do not do this unless explicitly instructed by Parse.ly Staff!', 'wp-parsely' ); add_settings_field( 'parsely_wipe_metadata_cache', __( 'Wipe Parse.ly Metadata Info', 'wp-parsely' ), array( $this, 'print_checkbox_tag' ), self::MENU_SLUG, 'optional_settings', array( 'option_key' => 'parsely_wipe_metadata_cache', 'help_text' => $h, 'requires_recrawl' => false, ) ); /* translators: 1: Opening anchor tag markup, 2: Documentation URL, 3: Opening anchor tag markup continued, 4: Closing anchor tag */ $h = __( 'Choose the metadata format for our crawlers to access. Most publishers are fine with JSON-LD ( %1$s%2$s%3$shttps://www.parse.ly/help/integration/jsonld/%4$s ), but if you prefer to use our proprietary metadata format then you can do so here.', 'wp-parsely' ); $h_link = 'https://www.parse.ly/help/integration/jsonld/'; add_settings_field( 'meta_type', __( 'Metadata Format', 'wp-parsely' ), array( $this, 'print_select_tag' ), self::MENU_SLUG, 'optional_settings', array( 'option_key' => 'meta_type', 'help_text' => $h, 'help_link' => $h_link, // filter WordPress taxonomies under the hood that should not appear in dropdown. 'select_options' => array( 'json_ld' => 'json_ld', 'repeated_metas' => 'repeated_metas', ), 'requires_recrawl' => true, 'multiple' => false, ) ); $h = __( 'If you want to specify the url for your logo, you can do so here.', 'wp-parsely' ); $option_defaults['logo'] = $this->get_logo_default(); $field_args = array( 'option_key' => 'logo', 'help_text' => $h, ); add_settings_field( 'logo', __( 'Logo', 'wp-parsely' ), array( $this, 'print_text_tag' ), self::MENU_SLUG, 'optional_settings', $field_args ); // Content ID Prefix. $h = __( 'If you use more than one content management system (e.g. WordPress and Drupal), you may end up with duplicate content IDs. Adding a Content ID Prefix will ensure the content IDs from WordPress will not conflict with other content management systems. We recommend using "WP-" for your prefix.', 'wp-parsely' ); $field_args = array( 'option_key' => 'content_id_prefix', 'optional_args' => array( 'placeholder' => 'WP-', ), 'help_text' => $h, 'requires_recrawl' => true, ); add_settings_field( 'content_id_prefix', __( 'Content ID Prefix', 'wp-parsely' ), array( $this, 'print_text_tag' ), self::MENU_SLUG, 'optional_settings', $field_args ); // Disable JavaScript. $h = __( 'If you use a separate system for JavaScript tracking ( Tealium / Segment / Google Tag Manager / other tag manager solution ) you may want to use that instead of having the plugin load the tracker. WARNING: disabling this option will also disable the "Personalize Results" section of the recommended widget! We highly recommend leaving this option set to "No"!', 'wp-parsely' ); add_settings_field( 'disable_javascript', __( 'Disable JavaScript', 'wp-parsely' ), array( $this, 'print_binary_radio_tag' ), self::MENU_SLUG, 'optional_settings', array( 'title' => __( 'Disable JavaScript', 'wp-parsely' ), // Passed for legend element. 'option_key' => 'disable_javascript', 'help_text' => $h, 'requires_recrawl' => false, ) ); // Disable amp tracking. $h = __( 'If you use a separate system for JavaScript tracking on AMP pages ( Tealium / Segment / Google Tag Manager / other tag manager solution ) you may want to use that instead of having the plugin load the tracker.', 'wp-parsely' ); add_settings_field( 'disable_amp', __( 'Disable AMP Tracking', 'wp-parsely' ), array( $this, 'print_binary_radio_tag' ), self::MENU_SLUG, 'optional_settings', array( 'title' => __( 'Disable AMP Tracking', 'wp-parsely' ), // Passed for legend element. 'option_key' => 'disable_amp', 'help_text' => $h, 'requires_recrawl' => false, ) ); // Use top-level categories. $h = __( 'The plugin will use the first category assigned to a post. With this option selected, if you post a story to News > National > Florida, the plugin will use the "News" for the section name in your dashboard instead of "Florida".', 'wp-parsely' ); add_settings_field( 'use_top_level_cats', __( 'Use Top-Level Categories for Section', 'wp-parsely' ), array( $this, 'print_binary_radio_tag' ), self::MENU_SLUG, 'optional_settings', array( 'title' => __( 'Use Top-Level Categories for Section', 'wp-parsely' ), // Passed for legend element. 'option_key' => 'use_top_level_cats', 'help_text' => $h, 'requires_recrawl' => true, ) ); // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category. $h = __( 'By default, the section value in your Parse.ly dashboard maps to a post\'s category. You can optionally choose a custom taxonomy, if you\'ve created one, to populate the section value instead.', 'wp-parsely' ); add_settings_field( 'custom_taxonomy_section', __( 'Use Custom Taxonomy for Section', 'wp-parsely' ), array( $this, 'print_select_tag' ), self::MENU_SLUG, 'optional_settings', array( 'option_key' => 'custom_taxonomy_section', 'help_text' => $h, // filter WordPress taxonomies under the hood that should not appear in dropdown. 'select_options' => array_diff( get_taxonomies(), array( 'post_tag', 'nav_menu', 'author', 'link_category', 'post_format' ) ), 'requires_recrawl' => true, ) ); // Use categories and custom taxonomies as tags. $h = __( 'You can use this option to add all assigned categories and taxonomies to your tags. For example, if you had a post assigned to the categories: "Business/Tech", "Business/Social", your tags would include "Business/Tech" and "Business/Social" in addition to your other tags.', 'wp-parsely' ); add_settings_field( 'cats_as_tags', __( 'Add Categories to Tags', 'wp-parsely' ), array( $this, 'print_binary_radio_tag' ), self::MENU_SLUG, 'optional_settings', array( 'title' => __( 'Add Categories to Tags', 'wp-parsely' ), // Passed for legend element. 'option_key' => 'cats_as_tags', 'help_text' => $h, 'requires_recrawl' => true, ) ); // Track logged-in users. $h = __( 'By default, the plugin will track the activity of users that are logged into this site. You can change this setting to only track the activity of anonymous visitors. Note: You will no longer see the Parse.ly tracking code on your site if you browse while logged in.', 'wp-parsely' ); add_settings_field( 'track_authenticated_users', __( 'Track Logged-in Users', 'wp-parsely' ), array( $this, 'print_binary_radio_tag' ), self::MENU_SLUG, 'optional_settings', array( 'title' => __( 'Track Logged-in Users', 'wp-parsely' ), // Passed for legend element. 'option_key' => 'track_authenticated_users', 'help_text' => $h, 'requires_recrawl' => false, ) ); // Lowercase all tags. $h = __( 'By default, the plugin will use lowercase versions of your tags to correct for potential misspellings. You can change this setting to ensure that tag names are used verbatim.', 'wp-parsely' ); add_settings_field( 'lowercase_tags', __( 'Lowercase All Tags', 'wp-parsely' ), array( $this, 'print_binary_radio_tag' ), self::MENU_SLUG, 'optional_settings', array( 'title' => __( 'Lowercase All Tags', 'wp-parsely' ), // Passed for legend element. 'option_key' => 'lowercase_tags', 'help_text' => $h, 'requires_recrawl' => true, ) ); $h = __( 'The plugin uses http canonical URLs by default. If this needs to be forced to use https, set this option to true. Note: the default is fine for almost all publishers, it\'s unlikely you\'ll have to change this unless directed to do so by a Parse.ly support rep.', 'wp-parsely' ); add_settings_field( 'force_https_canonicals', __( 'Force HTTPS canonicals', 'wp-parsely' ), array( $this, 'print_binary_radio_tag' ), self::MENU_SLUG, 'optional_settings', array( 'title' => __( 'Force HTTPS canonicals', 'wp-parsely' ), // Passed for legend element. 'option_key' => 'force_https_canonicals', 'help_text' => $h, 'requires_recrawl' => true, ) ); // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category. $h = __( 'By default, Parse.ly only tracks the default post type as a post page. If you want to track custom post types, select them here!', 'wp-parsely' ); add_settings_field( 'track_post_types', __( 'Post Types To Track', 'wp-parsely' ), array( $this, 'print_select_tag' ), self::MENU_SLUG, 'optional_settings', array( 'option_key' => 'track_post_types', 'help_text' => $h, // filter WordPress taxonomies under the hood that should not appear in dropdown. 'select_options' => get_post_types(), 'requires_recrawl' => true, 'multiple' => true, ) ); // Allow use of custom taxonomy to populate articleSection in parselyPage; defaults to category. $h = __( 'By default, Parse.ly only tracks the default page type as a non-post page. If you want to track custom post types as non-post pages, select them here!', 'wp-parsely' ); add_settings_field( 'track_page_types', __( 'Page Types To Track', 'wp-parsely' ), array( $this, 'print_select_tag' ), self::MENU_SLUG, 'optional_settings', array( 'option_key' => 'track_page_types', 'help_text' => $h, // filter WordPress taxonomies under the hood that should not appear in dropdown. 'select_options' => get_post_types(), 'requires_recrawl' => true, 'multiple' => true, ) ); // Dynamic tracking note. add_settings_field( 'dynamic_tracking_note', __( 'Note: ', 'wp-parsely' ), array( $this, 'print_dynamic_tracking_note' ), self::MENU_SLUG, 'optional_settings' ); } /** * Validate options from an array * * @category Function * @package Parsely * @param array $array Array of options to be sanitized. * @param string $name Unused?. */ public function validate_option_array( $array, $name ) { $new_array = $array; foreach ( $array as $key => $val ) { $new_array[ $key ] = sanitize_text_field( $val ); } return $new_array; } /** * Validate the options provided by the user * * @category Function * @package Parsely * @param array $input Options from the settings page. * @return array $input list of validated input settings. */ public function validate_options( $input ) { if ( empty( $input['apikey'] ) ) { add_settings_error( self::OPTIONS_KEY, 'apikey', __( 'Please specify the Site ID', 'wp-parsely' ) ); } else { $input['apikey'] = strtolower( $input['apikey'] ); $input['apikey'] = sanitize_text_field( $input['apikey'] ); if ( strpos( $input['apikey'], '.' ) === false || strpos( $input['apikey'], ' ' ) !== false ) { add_settings_error( self::OPTIONS_KEY, 'apikey', __( 'Your Parse.ly Site ID looks incorrect, it should look like "example.com".', 'wp-parsely' ) ); } } // these can't be null, if somebody accidentally deselected them just reset to default. if ( ! isset( $input['track_post_types'] ) ) { $input['track_post_types'] = array( 'post' ); } if ( ! isset( $input['track_page_types'] ) ) { $input['track_page_types'] = array( 'page' ); } if ( empty( $input['logo'] ) ) { $input['logo'] = $this->get_logo_default(); } $input['track_post_types'] = $this->validate_option_array( $input['track_post_types'], 'track_post_types' ); $input['track_page_types'] = $this->validate_option_array( $input['track_page_types'], 'track_page_types' ); $input['api_secret'] = sanitize_text_field( $input['api_secret'] ); // Content ID prefix. $input['content_id_prefix'] = sanitize_text_field( $input['content_id_prefix'] ); $input['custom_taxonomy_section'] = sanitize_text_field( $input['custom_taxonomy_section'] ); // Custom taxonomy as section. // Top-level categories. if ( 'true' !== $input['use_top_level_cats'] && 'false' !== $input['use_top_level_cats'] ) { add_settings_error( self::OPTIONS_KEY, 'use_top_level_cats', __( 'Value passed for use_top_level_cats must be either "true" or "false".', 'wp-parsely' ) ); } else { $input['use_top_level_cats'] = 'true' === $input['use_top_level_cats']; } // Child categories as tags. if ( 'true' !== $input['cats_as_tags'] && 'false' !== $input['cats_as_tags'] ) { add_settings_error( self::OPTIONS_KEY, 'cats_as_tags', __( 'Value passed for cats_as_tags must be either "true" or "false".', 'wp-parsely' ) ); } else { $input['cats_as_tags'] = 'true' === $input['cats_as_tags']; } // Track authenticated users. if ( 'true' !== $input['track_authenticated_users'] && 'false' !== $input['track_authenticated_users'] ) { add_settings_error( self::OPTIONS_KEY, 'track_authenticated_users', __( 'Value passed for track_authenticated_users must be either "true" or "false".', 'wp-parsely' ) ); } else { $input['track_authenticated_users'] = 'true' === $input['track_authenticated_users']; } // Lowercase tags. if ( 'true' !== $input['lowercase_tags'] && 'false' !== $input['lowercase_tags'] ) { add_settings_error( self::OPTIONS_KEY, 'lowercase_tags', __( 'Value passed for lowercase_tags must be either "true" or "false".', 'wp-parsely' ) ); } else { $input['lowercase_tags'] = 'true' === $input['lowercase_tags']; } if ( 'true' !== $input['force_https_canonicals'] && 'false' !== $input['force_https_canonicals'] ) { add_settings_error( self::OPTIONS_KEY, 'force_https_canonicals', __( 'Value passed for force_https_canonicals must be either "true" or "false".', 'wp-parsely' ) ); } else { $input['force_https_canonicals'] = 'true' === $input['force_https_canonicals']; } if ( 'true' !== $input['disable_javascript'] && 'false' !== $input['disable_javascript'] ) { add_settings_error( self::OPTIONS_KEY, 'disable_javascript', __( 'Value passed for disable_javascript must be either "true" or "false".', 'wp-parsely' ) ); } else { $input['disable_javascript'] = 'true' === $input['disable_javascript']; } if ( 'true' !== $input['disable_amp'] && 'false' !== $input['disable_amp'] ) { add_settings_error( self::OPTIONS_KEY, 'disable_amp', __( 'Value passed for disable_amp must be either "true" or "false".', 'wp-parsely' ) ); } else { $input['disable_amp'] = 'true' === $input['disable_amp']; } if ( ! empty( $input['metadata_secret'] ) ) { if ( strlen( $input['metadata_secret'] ) !== 10 ) { add_settings_error( self::OPTIONS_KEY, 'metadata_secret', __( 'Metadata secret is incorrect. Please contact Parse.ly support!', 'wp-parsely' ) ); } elseif ( 'true' === $input['parsely_wipe_metadata_cache'] ) { delete_post_meta_by_key( 'parsely_metadata_last_updated' ); wp_schedule_event( time() + 100, 'everytenminutes', 'parsely_bulk_metas_update' ); $input['parsely_wipe_metadata_cache'] = false; } } return $input; } /** * Not doing anything here * * @category Function * @package Parsely */ public function print_required_settings() { // We can optionally print some text here in the future, but we don't // need to now. } /** * Not doing anything here * * @category Function * @package Parsely */ public function print_optional_settings() { // We can optionally print some text here in the future, but we don't // need to now. } /** * Adds a 'Settings' link to the Plugins screen in WP admin * * @category Function * @package Parsely * @param array $links The links to add. */ public function add_plugin_meta_links( $links ) { array_unshift( $links, '' . __( 'Settings', 'wp-parsely' ) . '' ); return $links; } /** * Display the admin warning if needed * * @category Function * @package Parsely */ public function display_admin_warning() { $options = $this->get_options(); if ( isset( $options['apikey'] ) && ! empty( $options['apikey'] ) ) { return; } $message = sprintf( /* translators: %s: Plugin settings page URL */ __( 'The Parse.ly plugin is not active. You need to provide your Parse.ly Dash Site ID before things get cooking.', 'wp-parsely' ), esc_url( $this->get_settings_url() ) ); ?>

the standard Parse.ly include that the plugin injects into your page source. Please consult the Parse.ly documentation on dynamic tracking for instructions on implementing dynamic tracking, or contact Parse.ly support (support@parsely.com ) for additional assistance.', 'wp-parsely' ) ), esc_url( 'http://www.parsely.com/help/integration/basic/' ), esc_url( 'https://www.parsely.com/help/integration/dynamic/' ), esc_url( 'mailto:support@parsely.com' ) ); } /** * End the code coverage ignore * * @codeCoverageIgnoreEnd */ /** * Actually inserts the code for the parameter within the tag. */ public function insert_parsely_page() { $parsely_options = $this->get_options(); if ( // No API key. empty( $parsely_options['apikey'] ) || // Chosen not to track logged in users. ( ! $parsely_options['track_authenticated_users'] && $this->parsely_is_user_logged_in() ) || // 404 pages are not tracked. is_404() || // Search pages are not tracked. is_search() ) { return ''; } global $post; // Assign default values for LD+JSON // TODO: Maping of an install's post types to Parse.ly post types (namely page/post). $parsely_page = $this->construct_parsely_metadata( $parsely_options, $post ); // Something went wrong - abort. if ( empty( $parsely_page ) || ! isset( $parsely_page['headline'] ) ) { return; } echo "\n" . '' . "\n"; // Insert JSON-LD or repeated metas. if ( 'json_ld' === $parsely_options['meta_type'] ) { include PARSELY_PLUGIN_DIR . 'views/json-ld.php'; } else { $parsely_post_type = $this->convert_jsonld_to_parsely_type( $parsely_page['@type'] ); if ( is_array( $parsely_page['keywords'] ) ) { $parsely_page['keywords'] = implode( ',', $parsely_page['keywords'] ); } include PARSELY_PLUGIN_DIR . 'views/repeated-metas.php'; } // Add any custom metadata. if ( isset( $parsely_page['custom_metadata'] ) ) { include PARSELY_PLUGIN_DIR . 'views/custom-metadata.php'; } echo '' . "\n\n"; return $parsely_page; } /** * Compare the post_status key against an allowed list (by default, only 'publish'ed content includes tracking data). * * @since 2.5.0 * * @param int|WP_Post $post Which post object or ID to check. * @return bool Should the post status be tracked for the provided post's post_type. By default, only 'publish' is allowed. */ public static function post_has_trackable_status( $post ) { static $cache = array(); $post_id = is_int( $post ) ? $post : $post->ID; if ( isset( $cache[ $post_id ] ) ) { return $cache[ $post_id ]; } /** * Filters the statuses that are permitted to be tracked. * * By default, the only status tracked is 'publish'. Use this filter if you have other published content that has a different (custom) status. * * @since 2.5.0 * * @param string[] $trackable_statuses The list of post statuses that are allowed to be tracked. * @param int|WP_Post $post Which post object or ID is being checked. */ $statuses = apply_filters( 'wp_parsely_trackable_statuses', array( 'publish' ), $post ); $cache[ $post_id ] = in_array( get_post_status( $post ), $statuses, true ); return $cache[ $post_id ]; } /** * Creates parsely metadata object from post metadata. * * @param array $parsely_options parsely_options array. * @param WP_Post $post object. * @return mixed|void */ public function construct_parsely_metadata( array $parsely_options, $post ) { $parsely_page = array( '@context' => 'http://schema.org', '@type' => 'WebPage', ); $current_url = $this->get_current_url(); if ( is_front_page() && ! is_paged() || ( 'page' === get_option( 'show_on_front' ) && ! get_option( 'page_on_front' ) ) ) { $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_bloginfo( 'name', 'raw' ) ); $parsely_page['url'] = home_url(); } elseif ( is_front_page() && is_paged() ) { $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_bloginfo( 'name', 'raw' ) ); $parsely_page['url'] = $current_url; } elseif ( is_home() ) { $parsely_page['headline'] = get_the_title( get_option('page_for_posts', true) ); $parsely_page['url'] = $current_url; } elseif ( is_author() ) { // TODO: why can't we have something like a WP_User object for all the other cases? Much nicer to deal with than functions. $author = ( get_query_var( 'author_name' ) ) ? get_user_by( 'slug', get_query_var( 'author_name' ) ) : get_userdata( get_query_var( 'author' ) ); $parsely_page['headline'] = $this->get_clean_parsely_page_value( 'Author - ' . $author->data->display_name ); $parsely_page['url'] = $current_url; } elseif ( is_category() ) { $category = get_the_category(); $category = $category[0]; $parsely_page['headline'] = $this->get_clean_parsely_page_value( $category->name ); $parsely_page['url'] = $current_url; } elseif ( is_date() ) { if ( is_year() ) { /* translators: %s: Archive year */ $parsely_page['headline'] = sprintf( __( 'Yearly Archive - %s', 'wp-parsely' ), get_the_time( 'Y' ) ); } elseif ( is_month() ) { /* translators: %s: Archive month, formatted as F, Y */ $parsely_page['headline'] = sprintf( __( 'Monthly Archive - %s', 'wp-parsely' ), get_the_time( 'F, Y' ) ); } elseif ( is_day() ) { /* translators: %s: Archive day, formatted as F jS, Y */ $parsely_page['headline'] = sprintf( __( 'Daily Archive - %s', 'wp-parsely' ), get_the_time( 'F jS, Y' ) ); } elseif ( is_time() ) { /* translators: %s: Archive time, formatted as F jS g:i:s A */ $parsely_page['headline'] = sprintf( __( 'Hourly, Minutely, or Secondly Archive - %s', 'wp-parsely' ), get_the_time( 'F jS g:i:s A' ) ); } $parsely_page['url'] = $current_url; } elseif ( is_tag() ) { $tag = single_tag_title( '', false ); if ( empty( $tag ) ) { $tag = single_term_title( '', false ); } /* translators: %s: Tag name */ $parsely_page['headline'] = $this->get_clean_parsely_page_value( sprintf( __( 'Tagged - %s', 'wp-parsely' ), $tag ) ); $parsely_page['url'] = $current_url; } elseif ( in_array( get_post_type( $post ), $parsely_options['track_post_types'], true ) && self::post_has_trackable_status( $post ) ) { $authors = $this->get_author_names( $post ); $category = $this->get_category_name( $post, $parsely_options ); $post_id = $parsely_options['content_id_prefix'] . get_the_ID(); if ( has_post_thumbnail( $post ) ) { $image_id = get_post_thumbnail_id( $post ); $image_url = wp_get_attachment_image_src( $image_id ); $image_url = $image_url[0]; } else { $image_url = $this->get_first_image( $post ); } $tags = $this->get_tags( $post->ID ); if ( $parsely_options['cats_as_tags'] ) { $tags = array_merge( $tags, $this->get_categories( $post->ID ) ); // add custom taxonomy values. $tags = array_merge( $tags, $this->get_custom_taxonomy_values( $post, $parsely_options ) ); } // the function 'mb_strtolower' is not enabled by default in php, so this check // falls back to the native php function 'strtolower' if necessary. if ( function_exists( 'mb_strtolower' ) ) { $lowercase_callback = 'mb_strtolower'; } else { $lowercase_callback = 'strtolower'; } if ( $parsely_options['lowercase_tags'] ) { $tags = array_map( $lowercase_callback, $tags ); } /** * Filters the post tags that are used as metadata keywords. * * @since 1.8.0 * * @param string[] $tags Post tags. * @param int $ID Post ID. */ $tags = apply_filters( 'wp_parsely_post_tags', $tags, $post->ID ); $tags = array_map( array( $this, 'get_clean_parsely_page_value' ), $tags ); $tags = array_values( array_unique( $tags ) ); /** * Filters the JSON-LD @type. * * @since 2.5.0 * * @param array $jsonld_type JSON-LD @type value, default is NewsArticle. * @param integer $id Post ID. * @param string $post_type Post type in WordPress. */ $type = (string) apply_filters( 'wp_parsely_post_type', 'NewsArticle', $post->ID, $post->post_type ); $supported_types = array_merge( $this->supported_jsonld_post_types, $this->supported_jsonld_non_post_types ); // Validate type before passing it further as an invalid type will not be recognized by Parse.ly. if ( ! in_array( $type, $supported_types ) ) { $error = sprintf( __( '@type %1$s is not supported by Parse.ly. Please use a type mentioned in %2$s', 'wp-parsely' ), $type, 'https://www.parse.ly/help/integration/jsonld#distinguishing-between-posts-and-pages' ); trigger_error( $error, E_USER_WARNING ); $type = 'NewsArticle'; } $parsely_page['@type'] = $type; $parsely_page['mainEntityOfPage'] = array( '@type' => 'WebPage', '@id' => $this->get_current_url( 'post' ), ); $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_the_title( $post ) ); $parsely_page['url'] = $this->get_current_url( 'post', $post->ID ); $parsely_page['thumbnailUrl'] = $image_url; $parsely_page['image'] = array( '@type' => 'ImageObject', 'url' => $image_url, ); $parsely_page['dateCreated'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true, $post ) ); $parsely_page['datePublished'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true, $post ) ); if ( get_the_modified_date( 'U', true ) >= get_post_time( 'U', true, $post ) ) { $parsely_page['dateModified'] = gmdate( 'Y-m-d\TH:i:s\Z', get_the_modified_date( 'U', true ) ); } else { // Use the post time as the earliest possible modification date. $parsely_page['dateModified'] = gmdate( 'Y-m-d\TH:i:s\Z', get_post_time( 'U', true, $post ) ); } $parsely_page['articleSection'] = $category; $author_objects = array(); foreach ( $authors as $author ) { $author_tag = array( '@type' => 'Person', 'name' => $author, ); array_push( $author_objects, $author_tag ); } $parsely_page['author'] = $author_objects; $parsely_page['creator'] = $authors; $parsely_page['publisher'] = array( '@type' => 'Organization', 'name' => get_bloginfo( 'name' ), 'logo' => $parsely_options['logo'], ); $parsely_page['keywords'] = $tags; } elseif ( in_array( get_post_type(), $parsely_options['track_page_types'], true ) && self::post_has_trackable_status( $post ) ) { $parsely_page['headline'] = $this->get_clean_parsely_page_value( get_the_title( $post ) ); $parsely_page['url'] = $this->get_current_url( 'post' ); } /** * Filters the structured metadata. * * @deprecated 2.5.0 Use `wp_parsely_metadata` filter instead. * @since 1.10.0 * * @param array $parsely_page Existing structured metadata for a page. * @param WP_Post $post Post object. * @param array $parsely_options The Parsely options. */ $parsely_page = apply_filters_deprecated( 'after_set_parsely_page', array( $parsely_page, $post, $parsely_options ), '2.5.0', 'wp_parsely_metadata' ); /** * Filters the structured metadata. * * @since 2.5.0 * * @param array $parsely_page Existing structured metadata for a page. * @param WP_Post $post Post object. * @param array $parsely_options The Parsely options. */ $parsely_page = apply_filters( 'wp_parsely_metadata', $parsely_page, $post, $parsely_options ); return $parsely_page; } /** * Updates the Parsely metadata endpoint with the new metadata of the post. * * @param int $post_id id of the post to update. * @return string */ public function update_metadata_endpoint( $post_id ) { $parsely_options = $this->get_options(); if ( empty( $parsely_options['apikey'] ) || empty( $parsely_options['metadata_secret'] ) ) { return ''; } $post = get_post( $post_id ); $metadata = $this->construct_parsely_metadata( $parsely_options, $post ); $endpoint_metadata = array( 'canonical_url' => $metadata['url'], 'page_type' => $this->convert_jsonld_to_parsely_type( $metadata['@type'] ), 'title' => $metadata['headline'], 'image_url' => $metadata['thumbnailUrl'], 'pub_date_tmsp' => $metadata['datePublished'], 'section' => $metadata['articleSection'], 'authors' => $metadata['creator'], 'tags' => $metadata['keywords'], ); $parsely_api_endpoint = 'https://api.parsely.com/v2/metadata/posts'; $parsely_metadata_secret = $parsely_options['metadata_secret']; $headers = array( 'Content-Type' => 'application/json', ); $body = wp_json_encode( array( 'secret' => $parsely_metadata_secret, 'apikey' => $parsely_options['apikey'], 'metadata' => $endpoint_metadata, ) ); $response = wp_remote_post( $parsely_api_endpoint, array( 'method' => 'POST', 'headers' => $headers, 'blocking' => false, 'body' => $body, 'data_format' => 'body', ) ); $current_timestamp = time(); $meta_update = update_post_meta( $post_id, 'parsely_metadata_last_updated', $current_timestamp ); } /** * Updates posts with Parsely metadata api in bulk. */ public function bulk_update_posts() { global $wpdb; $parsely_options = $this->get_options(); $allowed_types = array_merge( $parsely_options['track_post_types'], $parsely_options['track_page_types'] ); $allowed_types_string = implode( ', ', array_map( function( $v ) { return "'" . esc_sql( $v ) . "'"; }, $allowed_types ) ); $ids = wp_cache_get( 'parsely_post_ids_need_meta_updating' ); if ( false === $ids ) { $ids = array(); $results = $wpdb->get_results( $wpdb->prepare( "SELECT DISTINCT(id) FROM {$wpdb->posts} WHERE post_type IN (\" . %s . \") AND id NOT IN (SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = 'parsely_metadata_last_updated');", $allowed_types_string ), ARRAY_N ); foreach ( $results as $result ) { array_push( $ids, $result[0] ); } wp_cache_set( 'parsely_post_ids_need_meta_updating', $ids, '', 86400 ); } for ( $i = 0; $i < 100; $i++ ) { $post_id = array_pop( $ids ); if ( null === $post_id ) { wp_clear_scheduled_hook( 'parsely_bulk_metas_update' ); break; } $this->update_metadata_endpoint( $post_id ); } } public static function get_asset_cache_buster() { static $cache_buster; if ( isset( $cache_buster ) ) { return $cache_buster; } $cache_buster = defined( 'WP_DEBUG' ) && WP_DEBUG && empty( 'WP_TESTS_DOMAIN' ) ? wp_rand() : PARSELY_VERSION; /** * Filters the cache buster value for linked scripts and styles. * * @since 2.5.0 * * @param string $cache_buster Plugin version, unless WP_DEBUG is defined and truthy, and tests are not running. */ return apply_filters( 'wp_parsely_cache_buster', $cache_buster ); } public function register_js() { $parsely_options = $this->get_options(); // If we don't have an API key, there's no need to proceed. if ( empty( $parsely_options['apikey'] ) ) { return ''; } wp_register_script( 'wp-parsely-tracker', 'https://cdn.parsely.com/keys/' . $parsely_options['apikey'] . '/p.js', array(), self::get_asset_cache_buster(), true ); $api_script_asset = require PARSELY_PLUGIN_DIR . 'build/init-api.asset.php' ; wp_register_script( 'wp-parsely-api', PARSELY_PLUGIN_URL . 'build/init-api.js', $api_script_asset[ 'dependencies' ], self::get_asset_cache_buster(), true ); wp_localize_script( 'wp-parsely-api', 'wpParsely', // This globally-scoped object holds variables used to interact with the API array( 'apikey' => $parsely_options['apikey'], ) ); } /** * Enqueues the JavaScript code required to send off beacon requests */ public function load_js_tracker() { $parsely_options = $this->get_options(); // If we don't have an API key, there's no need to proceed. if ( empty( $parsely_options['apikey'] ) || $parsely_options['disable_javascript'] ) { return; } global $post; $display = true; if ( in_array( get_post_type(), $parsely_options['track_post_types'], true ) && ! self::post_has_trackable_status( $post ) ) { $display = false; } if ( ! $parsely_options['track_authenticated_users'] && $this->parsely_is_user_logged_in() ) { $display = false; } if ( ! in_array( get_post_type(), $parsely_options['track_post_types'], true ) && ! in_array( get_post_type(), $parsely_options['track_page_types'], true ) ) { $display = false; } /** * Filters whether to include the Parsely JavaScript file. * * If true, the JavaScript files are sourced. * * @since 2.2.0 * @deprecated 2.5.0 Use `wp_parsely_load_js_tracker` filter instead. * * @param bool $display True if the JavaScript file should be included. False if not. */ if ( ! apply_filters_deprecated( 'parsely_filter_insert_javascript', array( $display ), '2.5.0', 'wp_parsely_load_js_tracker' ) ) { return; } /** * Filters whether to enqueue the Parsely JavaScript tracking script from the CDN. * * If true, the script is enqueued. * * @since 2.5.0 * * @param bool $display True if the JavaScript file should be included. False if not. */ if ( ! apply_filters( 'wp_parsely_load_js_tracker', $display ) ) { return; } if ( ! has_filter( 'script_loader_tag', array( $this, 'script_loader_tag' ) ) ) { add_filter( 'script_loader_tag', array( $this, 'script_loader_tag' ), 10, 3 ); } wp_enqueue_script( 'wp-parsely-tracker' ); } public function load_js_api() { $parsely_options = $this->get_options(); // If we don't have an API secret, there's no need to proceed. if ( empty( $parsely_options['api_secret'] ) ) { return; } if ( ! has_filter( 'script_loader_tag', array( $this, 'script_loader_tag' ) ) ) { add_filter( 'script_loader_tag', array( $this, 'script_loader_tag' ), 10, 3 ); } wp_enqueue_script( 'wp-parsely-api' ); } public function script_loader_tag( $tag, $handle, $src ) { $parsely_options = $this->get_options(); if ( in_array( $handle, array( 'wp-parsely', 'wp-parsely-api', 'wp-parsely-tracker', 'wp-parsely-recommended-widget', ) ) ) { // Have ClouldFlare Rocket Loader ignore these scripts: // https://support.cloudflare.com/hc/en-us/articles/200169436-How-can-I-have-Rocket-Loader-ignore-specific-JavaScripts- $tag = preg_replace( '/^ '; $registry[ $identifier ] = array( 'name' => $display_name, 'payload' => $embed_code, ); return $embed_code; } /** * Add amp actions. */ public function parsely_add_amp_actions() { if ( ! function_exists( 'is_amp_endpoint' ) || ! is_amp_endpoint() ) { return ''; } $options = $this->get_options(); if ( $options['disable_amp'] ) { return ''; } add_filter( 'amp_post_template_analytics', array( $this, 'parsely_add_amp_analytics' ) ); add_filter( 'amp_analytics_entries', array( $this, 'parsely_add_amp_native_analytics' ) ); } /** * Add amp analytics. * * @param type $analytics The analytics object you want to add. * @return type */ public function parsely_add_amp_analytics( $analytics ) { $options = $this->get_options(); if ( empty( $options['apikey'] ) ) { return $analytics; } $analytics['parsely'] = array( 'type' => 'parsely', 'attributes' => array(), 'config_data' => array( 'vars' => array( 'apikey' => $options['apikey'], ), ), ); return $analytics; } /** * Add amp native analytics. * * @param type $analytics The analytics object you want to add. * @return string|type */ public function parsely_add_amp_native_analytics( $analytics ) { $options = $this->get_options(); if ( ! empty( $options['disable_amp'] ) && true === $options['disable_amp'] ) { return ''; } if ( empty( $options['apikey'] ) ) { return $analytics; } $analytics['parsely'] = array( 'type' => 'parsely', 'attributes' => array(), 'config' => wp_json_encode( array( 'vars' => array( 'apikey' => $options['apikey'], ), ) ), ); return $analytics; } /** * Check to see if parsely user is logged in */ public function parsely_is_user_logged_in() { // can't use $blog_id here because it futzes with the global $blog_id. $current_blog_id = get_current_blog_id(); $current_user_id = get_current_user_id(); return is_user_member_of_blog( $current_user_id, $current_blog_id ); } /** * Why is this here? */ public function return_personalized_json() { } /** * Convert JSON-LD type to respective Parse.ly page type. * * If the JSON-LD type is one of the types Parse.ly supports as a "post", then "post" will be returned. * Otherwise, for "non-posts" and unknown types, "index" is returned. * * @since 2.5.0 * * @see https://www.parse.ly/help/integration/metatags#field-description * * @param $type string JSON-LD type. * @return string "post" or "index". */ public function convert_jsonld_to_parsely_type( $type ) { return in_array( $type, $this->supported_jsonld_post_types ) ? 'post' : 'index'; } }