]*)\shref=([\'\"])([a-zA-Z]+):(?:\/){2}?(.*)\2([^>]*)>(.*)<\/a>/isU'; /** * Storage for the currently set options * @var mixed|void */ public $options; /** * @var boolean $do_tracking Should the tracking code be added */ protected $do_tracking = null; /** * Function to output the GA Tracking code in the wp_head() * * @param bool $return_array * * @return mixed */ abstract public function tracking( $return_array = false ); /** * Output tracking link * * @param string $label * @param array $matches * * @return mixed */ abstract protected function output_parse_link( $label, $matches ); /** * Class constructor */ public function __construct() { $options_class = $this->get_options_class(); $this->options = $options_class->options; add_action( 'wp_head', array( $this, 'tracking' ), 8 ); if ( $this->options['track_outbound'] == 1 ) { $this->track_outbound_filters(); } } /** * Get the options class * * @return object|Yoast_GA_Options */ protected function get_options_class() { return Yoast_GA_Options::instance(); } /** * Delegates `get_tracking_code` to the options class * * @return null */ public function get_tracking_code() { return $this->get_options_class()->get_tracking_code(); } /** * Get 1 or 0 if we need to do enhanced link attribution * * @return mixed */ public function get_enhanced_link_attribution() { return $this->options['enhanced_link_attribution']; } /** * Parse article link * * @param array $matches * * @return mixed */ public function parse_article_link( $matches ) { return $this->output_parse_link( 'outbound-article', $matches ); } /** * Parse comment link * * @param array $matches * * @return mixed */ public function parse_comment_link( $matches ) { return $this->output_parse_link( 'outbound-comment', $matches ); } /** * Parse widget link * * @param array $matches * * @return mixed */ public function parse_widget_link( $matches ) { return $this->output_parse_link( 'outbound-widget', $matches ); } /** * Parse menu link * * @param array $matches * * @return mixed */ public function parse_nav_menu( $matches ) { return $this->output_parse_link( 'outbound-menu', $matches ); } /** * Parse the_content or the_excerpt for links * * @param string $text * * @return mixed */ public function the_content( $text ) { if ( false === $this->do_tracking() ) { return $text; } if ( ! is_feed() ) { $text = preg_replace_callback( $this->link_regex, array( $this, 'parse_article_link' ), $text ); } return $text; } /** * Parse the widget content for links * * @param string $text * * @return mixed */ public function widget_content( $text ) { if ( ! $this->do_tracking() ) { return $text; } $text = preg_replace_callback( $this->link_regex, array( $this, 'parse_widget_link' ), $text ); return $text; } /** * Parse the nav menu for links * * @param string $text * * @return mixed */ public function nav_menu( $text ) { if ( ! $this->do_tracking() ) { return $text; } if ( ! is_feed() ) { $text = preg_replace_callback( $this->link_regex, array( $this, 'parse_nav_menu' ), $text ); } return $text; } /** * Parse comment text for links * * @param string $text * * @return mixed */ public function comment_text( $text ) { if ( ! $this->do_tracking() ) { return $text; } if ( ! is_feed() ) { $text = preg_replace_callback( $this->link_regex, array( $this, 'parse_comment_link' ), $text ); } return $text; } /** * Parse the domain * * @param string $uri * * @return array|bool */ public function yoast_ga_get_domain( $uri ) { $hostPattern = '/^(https?:\/\/)?([^\/]+)/i'; $domainPatternUS = '/[^\.\/]+\.[^\.\/]+$/'; $domainPatternUK = '/[^\.\/]+\.[^\.\/]+\.[^\.\/]+$/'; $matching = preg_match( $hostPattern, $uri, $matches ); if ( $matching ) { $host = $matches[2]; if ( preg_match( '/.*\..*\..*\..*$/', $host ) ) { preg_match( $domainPatternUK, $host, $matches ); } else { preg_match( $domainPatternUS, $host, $matches ); } if ( isset( $matches[0] ) ) { return array( 'domain' => $matches[0], 'host' => $host ); } } return false; } /** * Merge the existing onclick with a new one and append it * * @param string $link_attribute * @param string $onclick * * @return string */ public function output_add_onclick( $link_attribute, $onclick ) { if ( preg_match( '/onclick=[\'\"](.*?;)[\'\"]/i', $link_attribute, $matches ) > 0 ) { $js_snippet_single = 'onclick=\'' . $matches[1] . ' ' . $onclick . '\''; $js_snippet_double = 'onclick="' . $matches[1] . ' ' . $onclick . '"'; $link_attribute = str_replace( 'onclick="' . $matches[1] . '"', $js_snippet_double, $link_attribute ); $link_attribute = str_replace( "onclick='" . $matches[1] . "'", $js_snippet_single, $link_attribute ); return $link_attribute; } else { if ( ! is_null( $onclick ) ) { return 'onclick="' . $onclick . '" ' . $link_attribute; } else { return $link_attribute; } } } /** * Generate the full URL * * @param string $link * * @return string */ public function make_full_url( $link ) { switch ( $link['type'] ) { case 'download': case 'internal': case 'internal-as-outbound': case 'outbound': return $link['protocol'] . '://' . $link['original_url']; break; case 'email': return 'mailto:' . $link['original_url']; break; } } /** * Setting the filters for tracking outbound links * */ protected function track_outbound_filters() { add_filter( 'the_content', array( $this, 'the_content' ), 99 ); add_filter( 'widget_text', array( $this, 'widget_content' ), 99 ); add_filter( 'wp_list_bookmarks', array( $this, 'widget_content' ), 99 ); add_filter( 'wp_nav_menu', array( $this, 'widget_content' ), 99 ); add_filter( 'the_excerpt', array( $this, 'the_content' ), 99 ); add_filter( 'comment_text', array( $this, 'comment_text' ), 99 ); } /** * Check if we need to show an actual tracking code * * @return bool */ public function do_tracking() { if ( $this->do_tracking === null ) { global $current_user; get_currentuserinfo(); $this->do_tracking = true; if ( 0 != $current_user->ID && isset( $this->options['ignore_users'] ) ) { if ( ! empty( $current_user->roles ) && in_array( $current_user->roles[0], $this->options['ignore_users'] ) ) { $this->do_tracking = false; } } /** * Filter: 'yst_ga_track_super_admin' - Allows filtering if the Super admin should be tracked in a multi-site setup * * @api array $all_roles */ $track_super_admin = apply_filters( 'yst_ga_track_super_admin', true ); if ( $track_super_admin === false && is_super_admin() ) { $this->do_tracking = false; } } return $this->do_tracking; } /** * Return the target with a lot of parameters * * @param string $category * @param array $matches * * @return array */ protected function get_target( $category, $matches ) { $protocol = $matches[3]; $original_url = $matches[4]; $domain = $this->yoast_ga_get_domain( $matches[4] ); $origin = $this->yoast_ga_get_domain( $_SERVER['HTTP_HOST'] ); $extension = substr( strrchr( $original_url, '.' ), 1 ); $type = $this->get_target_type( $extension, $domain, $origin, $matches ); return array( 'category' => $category, 'type' => $type, 'protocol' => $protocol, 'domain' => $domain['domain'], 'host' => $domain['host'], 'origin_domain' => $origin['domain'], 'origin_host' => $origin['host'], 'extension' => $extension, 'link_attributes' => trim( $matches[1] . ' ' . $matches[5] ), 'link_text' => $matches[6], 'original_url' => $original_url, ); } /** * Getting the type for current target * * @param string $extension * @param array $domain * @param array $origin * @param array $matches * * @return null|string */ protected function get_target_type( $extension, $domain, $origin, $matches ) { $download_extensions = explode( ',', str_replace( '.', '', $this->options['extensions_of_files'] ) ); $download_extensions = array_map( 'trim', $download_extensions ); $protocol = $matches[3]; $original_url = $matches[4]; // Break out immediately if the link is not an http or https link. $type = null; if ( $protocol !== 'http' && $protocol !== 'https' && $protocol !== 'mailto' ) { $type = null; } else { if ( ( $protocol == 'mailto' ) ) { $type = 'email'; } elseif ( in_array( $extension, $download_extensions ) ) { $type = 'download'; } else { $type = $this->parse_outbound_type( $domain, $origin, $original_url ); } } return $type; } /** * Parse the type for outbound links * * @param array $domain * @param array $origin * @param string $original_url * * @return string */ protected function parse_outbound_type( $domain, $origin, $original_url ) { $type = null; if ( $domain['domain'] == $origin['domain'] ) { $out_links = explode( ',', $this->options['track_internal_as_outbound'] ); $out_links = array_unique( array_map( 'trim', $out_links ) ); if ( ! empty( $original_url ) && ! empty( $domain['domain'] ) && count( $out_links ) >= 1 ) { foreach ( $out_links as $out ) { if ( ! empty( $out ) && strpos( $original_url, $domain['domain'] . $out ) !== false ) { $type = 'internal-as-outbound'; } } } if ( ! isset( $type ) ) { $type = 'internal'; } } elseif ( $domain['domain'] != $origin['domain'] ) { $type = 'outbound'; } return $type; } /** * Trims the track_internal_as_label option to prevent commas and whitespaces * * @return string */ protected function sanitize_internal_label() { if ( ! is_null( $this->options['track_internal_as_label'] ) && ! empty( $this->options['track_internal_as_label'] ) ) { $label = $this->options['track_internal_as_label']; $label = trim( $label, ',' ); $label = trim( $label ); } // Be sure label isn't empty, if so, set value with in if ( empty( $label ) ) { $label = 'int'; } return $label; } }