diff --git a/_inc/client/components/module-settings/modules-per-tab-page.jsx b/_inc/client/components/module-settings/modules-per-tab-page.jsx
index 2da34a10d2c56..126c221d28c91 100644
--- a/_inc/client/components/module-settings/modules-per-tab-page.jsx
+++ b/_inc/client/components/module-settings/modules-per-tab-page.jsx
@@ -128,6 +128,20 @@ const AllModuleSettingsComponent = React.createClass( {
return ( );
case 'wordads':
return ( );
+ case 'google-analytics':
+ if ( 'inactive' === module.configure_url ) {
+ return (
+
diff --git a/_inc/client/pro-status/index.jsx b/_inc/client/pro-status/index.jsx
index 85725426f2a3b..1667455de9675 100644
--- a/_inc/client/pro-status/index.jsx
+++ b/_inc/client/pro-status/index.jsx
@@ -4,6 +4,7 @@
import React from 'react';
import { connect } from 'react-redux';
import { translate as __ } from 'i18n-calypso';
+import { includes } from 'lodash';
import Button from 'components/button';
import SimpleNotice from 'components/notice';
@@ -121,6 +122,22 @@ const ProStatus = React.createClass( {
);
}
+ if ( 'google-analytics' === feature && ! includes( [ 'jetpack_business', 'jetpack_business_monthly' ], sitePlan.product_slug ) ) {
+ if ( this.props.fetchingSiteData ) {
+ return '';
+ }
+
+ return (
+
+ );
+ }
+
if ( sitePlan.product_slug ) {
let btnVals = {};
if ( 'jetpack_free' !== sitePlan.product_slug ) {
diff --git a/_inc/client/search/index.jsx b/_inc/client/search/index.jsx
index ba6d14486b6f0..208be32d1a56b 100644
--- a/_inc/client/search/index.jsx
+++ b/_inc/client/search/index.jsx
@@ -6,6 +6,7 @@ import { connect } from 'react-redux';
import FoldableCard from 'components/foldable-card';
import { ModuleToggle } from 'components/module-toggle';
import forEach from 'lodash/forEach';
+import includes from 'lodash/includes';
import Button from 'components/button';
import Gridicon from 'components/gridicon';
import Collection from 'components/search/search-collection.jsx';
@@ -105,16 +106,14 @@ export const SearchResults = ( {
}
cards = moduleList.map( ( element ) => {
- let isPro = 'scan' === element[0]
- || 'akismet' === element[0]
- || 'backups' === element[0]
- || 'seo-tools' === element[0],
- proProps = {},
+ const isPro = includes( [ 'scan', 'akismet', 'backups', 'seo-tools', 'google-analytics' ], element[0] );
+ let proProps = {},
+ isModuleActive = isModuleActivated( element[0] ),
unavailableDevMode = unavailableInDevMode( element[0] ),
toggle = unavailableDevMode ? __( 'Unavailable in Dev Mode' ) : (
@@ -122,7 +121,7 @@ export const SearchResults = ( {
customClasses = unavailableDevMode ? 'devmode-disabled' : '',
wordAdsSubHeader = element[2];
- if ( 'wordads' === element[0] && ! isModuleActivated( element[0] ) ) {
+ if ( 'wordads' === element[0] && ! isModuleActive ) {
wordAdsSubHeader =
}
@@ -132,13 +131,17 @@ export const SearchResults = ( {
configure_url: ''
};
- if (
+ if ( (
'videopress' !== element[0]
||
'seo-tools' !== element[0]
|| (
'seo-tools' === element[0]
&& ! hasBusiness
+ ) )
+ && (
+ 'google-analytics' !== element[0]
+ || ( 'google-analytics' === element[0] && ! hasBusiness )
)
) {
toggle =
;
@@ -199,7 +202,7 @@ export const SearchResults = ( {
) }
>
{
- isModuleActivated( element[0] ) || isPro ?
+ isModuleActive || isPro ?
:
// Render the long_description if module is deactivated
diff --git a/_inc/lib/admin-pages/class.jetpack-admin-page.php b/_inc/lib/admin-pages/class.jetpack-admin-page.php
index f37c5bd8efba6..870b53ac9c552 100644
--- a/_inc/lib/admin-pages/class.jetpack-admin-page.php
+++ b/_inc/lib/admin-pages/class.jetpack-admin-page.php
@@ -216,15 +216,15 @@ function check_plan_deactivate_modules( $page ) {
$active = Jetpack::get_active_modules();
switch ( $current->plan->product_slug ) {
case 'jetpack_free':
- $to_deactivate = array( 'seo-tools', 'videopress' );
+ $to_deactivate = array( 'seo-tools', 'videopress', 'google-analytics' );
break;
case 'jetpack_personal':
case 'jetpack_personal_monthly':
- $to_deactivate = array( 'seo-tools', 'videopress' );
+ $to_deactivate = array( 'seo-tools', 'videopress', 'google-analytics' );
break;
case 'jetpack_premium':
case 'jetpack_premium_monthly':
- $to_deactivate = array( 'seo-tools' );
+ $to_deactivate = array( 'seo-tools', 'google-analytics' );
break;
}
$to_deactivate = array_intersect( $active, $to_deactivate );
diff --git a/_inc/lib/class.core-rest-api-endpoints.php b/_inc/lib/class.core-rest-api-endpoints.php
index 29e2496fd97f9..84c7bf22e53fd 100644
--- a/_inc/lib/class.core-rest-api-endpoints.php
+++ b/_inc/lib/class.core-rest-api-endpoints.php
@@ -1546,6 +1546,16 @@ public static function get_updateable_data_list( $selector = '' ) {
'validate_callback' => __CLASS__ . '::validate_boolean',
'jp_group' => 'wordads',
),
+
+ // Google Analytics
+ 'google_analytics_tracking_id' => array(
+ 'description' => esc_html__( 'Google Analytics', 'jetpack' ),
+ 'type' => 'string',
+ 'default' => '',
+ 'validate_callback' => __CLASS__ . '::validate_alphanum',
+ 'jp_group' => 'google-analytics',
+ ),
+
// Stats
'admin_bar' => array(
'description' => esc_html__( 'Put a chart showing 48 hours of views in the admin bar.', 'jetpack' ),
@@ -2094,6 +2104,15 @@ public static function prepare_options_for_response( $module = '' ) {
$options = self::split_options( $options, get_option( 'verification_services_codes' ) );
break;
+ case 'google-analytics':
+ $wga = get_option( 'jetpack_wga' );
+ $code = '';
+ if ( is_array( $wga ) && array_key_exists( 'code', $wga ) ) {
+ $code = $wga[ 'code' ];
+ }
+ $options[ 'google_analytics_tracking_id' ][ 'current_value' ] = $code;
+ break;
+
case 'sharedaddy':
// It's local, but it must be broken apart since it's saved as an array.
if ( ! class_exists( 'Sharing_Service' ) && ! @include( JETPACK__PLUGIN_DIR . 'modules/sharedaddy/sharing-service.php' ) ) {
diff --git a/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php b/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php
index a463bcde9e7b3..d5833ccd01d62 100644
--- a/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php
+++ b/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php
@@ -718,6 +718,14 @@ public function update_data( $data ) {
$updated = get_option( $option ) != $value ? update_option( $option, (bool) $value ? 'letitsnow' : '' ) : true;
break;
+ case 'google_analytics_tracking_id':
+ $grouped_options = $grouped_options_current = (array) get_option( 'jetpack_wga' );
+ $grouped_options[ 'code' ] = $value;
+
+ // If option value was the same, consider it done.
+ $updated = $grouped_options_current != $grouped_options ? update_option( 'jetpack_wga', $grouped_options ) : true;
+ break;
+
case 'wp_mobile_featured_images':
case 'wp_mobile_excerpt':
$value = ( 'enabled' === $value ) ? '1' : '0';
diff --git a/class.jetpack.php b/class.jetpack.php
index 3e6656c8f12a2..7f9a871522129 100644
--- a/class.jetpack.php
+++ b/class.jetpack.php
@@ -1217,14 +1217,14 @@ public static function get_active_plan() {
// Set the default options
if ( ! $plan ) {
- $plan = array(
- 'product_slug' => 'jetpack_free',
- 'supports' => array(),
+ $plan = array(
+ 'product_slug' => 'jetpack_free',
+ 'supports' => array(),
);
}
// Define what paid modules are supported by personal plans
- $personal_plans = array(
+ $personal_plans = array(
'jetpack_personal',
'jetpack_personal_monthly',
);
@@ -1261,6 +1261,7 @@ public static function get_active_plan() {
'akismet',
'vaultpress',
'seo-tools',
+ 'google-analytics',
);
}
@@ -2292,7 +2293,7 @@ public static function get_translated_modules( $modules ) {
*/
public static function get_active_modules() {
$active = Jetpack_Options::get_option( 'active_modules' );
-
+
if ( ! is_array( $active ) ) {
$active = array();
}
diff --git a/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php b/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php
index 589f7e8532072..0435d6c2d10ea 100644
--- a/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php
+++ b/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php
@@ -199,7 +199,7 @@ public function get_settings_response() {
'moderation_keys' => get_option( 'moderation_keys' ),
'blacklist_keys' => get_option( 'blacklist_keys' ),
'lang_id' => get_option( 'lang_id' ),
- 'wga' => get_option( 'wga' ),
+ 'wga' => $this->get_google_analytics(),
'disabled_likes' => (bool) get_option( 'disabled_likes' ),
'disabled_reblogs' => (bool) get_option( 'disabled_reblogs' ),
'jetpack_comment_likes_enabled' => (bool) get_option( 'jetpack_comment_likes_enabled', false ),
@@ -266,6 +266,10 @@ protected function get_locale( $key ) {
return false;
}
+ protected function get_google_analytics () {
+ $option_name = defined( 'IS_WPCOM' ) && IS_WPCOM ? 'wga' : 'jetpack_wga';
+ return get_option( $option_name );
+ }
/**
* Updates site settings for authorized users
@@ -352,12 +356,18 @@ public function update_settings() {
}
break;
case 'wga':
+ case 'jetpack_wga':
if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^UA-[\d-]+$/i', $value['code'] ) ) {
return new WP_Error( 'invalid_code', 'Invalid UA ID' );
}
- $wga = get_option( 'wga', array() );
+
+ $is_wpcom = defined( 'IS_WPCOM' ) && IS_WPCOM;
+ $option_name = $is_wpcom ? 'wga' : 'jetpack_wga';
+
+ $wga = get_option( $option_name, array() );
$wga['code'] = $value['code']; // maintain compatibility with wp-google-analytics
- if ( update_option( 'wga', $wga ) ) {
+
+ if ( update_option( $option_name, $wga ) ) {
$updated[ $key ] = $value;
}
@@ -366,10 +376,11 @@ public function update_settings() {
/** This action is documented in modules/widgets/social-media-icons.php */
do_action( 'jetpack_bump_stats_extras', 'google-analytics', $enabled_or_disabled );
- $business_plugins = WPCOM_Business_Plugins::instance();
- $business_plugins->activate_plugin( 'wp-google-analytics' );
+ if ( $is_wpcom ) {
+ $business_plugins = WPCOM_Business_Plugins::instance();
+ $business_plugins->activate_plugin( 'wp-google-analytics' );
+ }
break;
-
case 'jetpack_testimonial':
case 'jetpack_portfolio':
case 'jetpack_comment_likes_enabled':
diff --git a/modules/google-analytics.php b/modules/google-analytics.php
new file mode 100644
index 0000000000000..9705fe8e459d5
--- /dev/null
+++ b/modules/google-analytics.php
@@ -0,0 +1,14 @@
+ $value ) {
+ if ( strpos( strtolower( $value ), strtolower( $site_url ) ) === 0 ) {
+ $track[ $k ] = substr( $track[ $k ], strlen( $site_url ) );
+ }
+ if ( 'data' === $k ) {
+ $track[ $k ] = preg_replace( '/^https?:\/\/|^\/+/i', '', $track[ $k ] );
+ }
+
+ // This way we don't lose search data.
+ if ( 'data' === $k && 'search' === $track['code'] ) {
+ $track[ $k ] = rawurlencode( $track[ $k ] );
+ } else {
+ $track[ $k ] = preg_replace( '/[^a-z0-9\.\/\+\?=-]+/i', '_', $track[ $k ] );
+ }
+
+ $track[ $k ] = trim( $track[ $k ], '_' );
+ }
+ $char = ( strpos( $track['data'], '?' ) === false ) ? '?' : '&';
+ return str_replace( "'", "\'", "/{$track['code']}/{$track['data']}{$char}referer=" . rawurlencode( isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : '' ) ); // Input var okay.
+ }
+
+ /**
+ * Maybe output or return, depending on the context
+ */
+ private function _output_or_return( $val, $maybe ) {
+ if ( $maybe ) {
+ echo $val . "\r\n";
+ } else {
+ return $val;
+ }
+ }
+
+ /**
+ * This injects the Google Analytics code into the footer of the page.
+ *
+ * @param bool[optional] $output - defaults to true, false returns but does NOT echo the code.
+ */
+ public function insert_code( $output = true ) {
+ // If $output is not a boolean false, set it to true (default).
+ $output = ( false !== $output);
+
+ $tracking_id = $this->_get_tracking_code();
+ if ( empty( $tracking_id ) ) {
+ return $this->_output_or_return( '', $output );
+ }
+
+ // If we're in the admin_area, return without inserting code.
+ if ( is_admin() ) {
+ return $this->_output_or_return( '', $output );
+ }
+
+ $custom_vars = array(
+ "_gaq.push(['_setAccount', '{$tracking_id}']);",
+ );
+
+ $track = array();
+ if ( is_404() ) {
+ // This is a 404 and we are supposed to track them.
+ $custom_vars[] = "_gaq.push( [ '_trackEvent', '404', document.location.href, document.referrer ] );";
+ } elseif ( is_search() ) {
+ // Set track for searches, if it's a search, and we are supposed to.
+ $track['data'] = sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ); // Input var okay.
+ $track['code'] = 'search';
+ }
+
+ if ( ! empty( $track ) ) {
+ $track['url'] = $this->_get_url( $track );
+ // adjust the code that we output, account for both types of tracking.
+ $track['url'] = esc_js( str_replace( '&', '&', $track['url'] ) );
+ $custom_vars[] = "_gaq.push(['_trackPageview','{$track['url']}']);";
+ } else {
+ $custom_vars[] = "_gaq.push(['_trackPageview']);";
+ }
+
+ $async_code = "";
+
+ $custom_vars_string = implode( "\r\n", $custom_vars );
+ $async_code = str_replace( '%custom_vars%', $custom_vars_string, $async_code );
+
+ return $this->_output_or_return( $async_code, $output );
+ }
+
+ /**
+ * Used to get the tracking code option
+ *
+ * @return tracking code option value.
+ */
+ private function _get_tracking_code() {
+ $o = get_option( 'jetpack_wga' );
+
+ if ( isset( $o['code'] ) && preg_match( '#UA-[\d-]+#', $o['code'], $matches ) ) {
+ return $o['code'];
+ }
+
+ return '';
+ }
+}
+
+global $jetpack_google_analytics;
+$jetpack_google_analytics = Jetpack_Google_Analytics::get_instance();
diff --git a/modules/module-headings.php b/modules/module-headings.php
index cc2ceea33b2b7..f9bedd07fff8f 100644
--- a/modules/module-headings.php
+++ b/modules/module-headings.php
@@ -49,6 +49,11 @@ function jetpack_get_module_i18n( $key ) {
'description' => _x( 'Increase reach and traffic.', 'Module Description', 'jetpack' ),
),
+ 'google-analytics' => array(
+ 'name' => _x( 'Google Analytics', 'Module Name', 'jetpack' ),
+ 'description' => _x( 'Lets you use Google Analytics to track your WordPress site statistics.', 'Module Description', 'jetpack' ),
+ ),
+
'gravatar-hovercards' => array(
'name' => _x( 'Gravatar Hovercards', 'Module Name', 'jetpack' ),
'description' => _x( 'Enable pop-up business cards over commenters’ Gravatars.', 'Module Description', 'jetpack' ),
diff --git a/modules/module-info.php b/modules/module-info.php
index 6b7d538a9b1ae..80af11299345f 100644
--- a/modules/module-info.php
+++ b/modules/module-info.php
@@ -641,3 +641,18 @@ function jetpack_wordads_more_info() {
}
add_action( 'jetpack_module_more_info_wordads', 'jetpack_wordads_more_info' );
// WordAds: STOP
+
+/**
+ * Google Analytics
+ */
+function jetpack_google_analytics_more_link() {
+ echo 'https://jetpack.com/support/google-analytics';
+}
+add_action( 'jetpack_learn_more_button_google-analytics', 'jetpack_google_analytics_more_link' );
+
+function jetpack_google_analytics_more_info() {
+ esc_html_e(
+ 'Track website statistics with Google Analytics for a deeper understanding of your website visitors and customers.'
+ , 'jetpack' );
+}
+add_action( 'jetpack_module_more_info_google-analytics', 'jetpack_google_analytics_more_info' );
diff --git a/sync/class.jetpack-sync-defaults.php b/sync/class.jetpack-sync-defaults.php
index 748bcc65bb009..e56d0e294ea13 100644
--- a/sync/class.jetpack-sync-defaults.php
+++ b/sync/class.jetpack-sync-defaults.php
@@ -67,7 +67,7 @@ class Jetpack_Sync_Defaults {
'comment_whitelist',
'comment_max_links',
'moderation_keys',
- 'wga',
+ 'jetpack_wga',
'disabled_likes',
'disabled_reblogs',
'jetpack_comment_likes_enabled',
@@ -175,22 +175,22 @@ class Jetpack_Sync_Defaults {
static $default_post_checksum_columns = array(
'ID',
'post_modified',
- );
+ );
static $default_post_meta_checksum_columns = array(
'meta_id',
'meta_value'
- );
+ );
static $default_comment_checksum_columns = array(
'comment_ID',
'comment_content',
- );
+ );
static $default_comment_meta_checksum_columns = array(
'meta_id',
'meta_value'
- );
+ );
static $default_option_checksum_columns = array(
'option_name',
diff --git a/tests/php/sync/test_class.jetpack-sync-options.php b/tests/php/sync/test_class.jetpack-sync-options.php
index f3165fcc83b05..13eda3c514106 100644
--- a/tests/php/sync/test_class.jetpack-sync-options.php
+++ b/tests/php/sync/test_class.jetpack-sync-options.php
@@ -127,7 +127,7 @@ public function test_sync_default_options() {
'comment_whitelist' => 'pineapple',
'comment_max_links' => 99,
'moderation_keys' => 'pineapple',
- 'wga' => 'pineapple',
+ 'jetpack_wga' => 'pineapple',
'disabled_likes' => 'pineapple',
'disabled_reblogs' => 'pineapple',
'jetpack_comment_likes_enabled' => 'pineapple',
@@ -201,7 +201,7 @@ public function test_add_whitelisted_option_on_init_89() {
do_action( 'init' );
$whitelist = $this->options_module->get_options_whitelist();
-
+
$this->assertTrue( in_array( 'foo_option_bar', $whitelist ) );
}
@@ -215,7 +215,7 @@ public function add_jetpack_options_whitelist_filter( $options ) {
}
-
+
function add_option_on_89() {
add_filter( 'jetpack_options_whitelist', array( $this, 'add_jetpack_options_whitelist_filter' ) );
}