'', '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() ) ); ?>