/modules/stats.php

  1. <?php 
  2. /** 
  3. * Module Name: Site Stats 
  4. * Module Description: Collect traffic stats and insights. 
  5. * Sort Order: 1 
  6. * Recommendation Order: 2 
  7. * First Introduced: 1.1 
  8. * Requires Connection: Yes 
  9. * Auto Activate: Yes 
  10. * Module Tags: Site Stats, Recommended 
  11. * Feature: Recommended, Traffic 
  12. */ 
  13.  
  14. if ( defined( 'STATS_VERSION' ) ) { 
  15. return; 
  16.  
  17. define( 'STATS_VERSION', '9' ); 
  18. defined( 'STATS_DASHBOARD_SERVER' ) or define( 'STATS_DASHBOARD_SERVER', 'dashboard.wordpress.com' ); 
  19.  
  20. add_action( 'jetpack_modules_loaded', 'stats_load' ); 
  21.  
  22. // Tell HQ about changed settings 
  23. Jetpack_Sync::sync_options( __FILE__,  
  24. 'stats_options',  
  25. 'home',  
  26. 'siteurl',  
  27. 'blogname',  
  28. 'blogdescription',  
  29. 'gmt_offset',  
  30. 'timezone_string',  
  31. 'page_on_front',  
  32. 'permalink_structure',  
  33. 'category_base',  
  34. 'tag_base' 
  35. ); 
  36.  
  37. function stats_load() { 
  38. global $wp_roles; 
  39.  
  40. Jetpack::enable_module_configurable( __FILE__ ); 
  41. Jetpack::module_configuration_load( __FILE__, 'stats_configuration_load' ); 
  42. Jetpack::module_configuration_head( __FILE__, 'stats_configuration_head' ); 
  43. Jetpack::module_configuration_screen( __FILE__, 'stats_configuration_screen' ); 
  44.  
  45. // Tell HQ about changed posts 
  46. $post_stati = get_post_stati( array( 'public' => true ) ); // All public post stati 
  47. $post_stati[] = 'private'; // Content from private stati will be redacted 
  48. Jetpack_Sync::sync_posts( __FILE__, array( 
  49. 'post_types' => get_post_types( array( 'public' => true ) ), // All public post types 
  50. 'post_stati' => $post_stati,  
  51. ) ); 
  52.  
  53. // Generate the tracking code after wp() has queried for posts. 
  54. add_action( 'template_redirect', 'stats_template_redirect', 1 ); 
  55.  
  56. add_action( 'wp_head', 'stats_admin_bar_head', 100 ); 
  57.  
  58. add_action( 'wp_head', 'stats_hide_smile_css' ); 
  59.  
  60. add_action( 'jetpack_admin_menu', 'stats_admin_menu' ); 
  61.  
  62. // Map stats caps 
  63. add_filter( 'map_meta_cap', 'stats_map_meta_caps', 10, 4 ); 
  64.  
  65. if ( isset( $_GET['oldwidget'] ) ) { 
  66. // Old one. 
  67. add_action( 'wp_dashboard_setup', 'stats_register_dashboard_widget' ); 
  68. } else { 
  69. add_action( 'admin_init', 'stats_merged_widget_admin_init' ); 
  70.  
  71. add_filter( 'jetpack_xmlrpc_methods', 'stats_xmlrpc_methods' ); 
  72.  
  73.  
  74. add_filter( 'pre_option_db_version', 'stats_ignore_db_version' ); 
  75.  
  76. /** 
  77. * Delay conditional for current_user_can to after init. 
  78. */ 
  79. function stats_merged_widget_admin_init() { 
  80. if ( current_user_can( 'view_stats' ) ) { 
  81. add_action( 'load-index.php', 'stats_enqueue_dashboard_head' ); 
  82. add_action( 'wp_dashboard_setup', 'stats_register_widget_control_callback' ); // hacky but works 
  83. add_action( 'jetpack_dashboard_widget', 'stats_jetpack_dashboard_widget' ); 
  84.  
  85. function stats_enqueue_dashboard_head() { 
  86. add_action( 'admin_head', 'stats_dashboard_head' ); 
  87.  
  88. /** 
  89. * Prevent sparkline img requests being redirected to upgrade.php. 
  90. * See wp-admin/admin.php where it checks $wp_db_version. 
  91. */ 
  92. function stats_ignore_db_version( $version ) { 
  93. if ( 
  94. is_admin() && 
  95. isset( $_GET['page'] ) && $_GET['page'] == 'stats' && 
  96. isset( $_GET['chart'] ) && strpos($_GET['chart'], 'admin-bar-hours') === 0 
  97. ) { 
  98. global $wp_db_version; 
  99. return $wp_db_version; 
  100. return $version; 
  101.  
  102. /** 
  103. * Maps view_stats cap to read cap as needed 
  104. * 
  105. * @return array Possibly mapped capabilities for meta capability 
  106. */ 
  107. function stats_map_meta_caps( $caps, $cap, $user_id, $args ) { 
  108. // Map view_stats to exists 
  109. if ( 'view_stats' == $cap ) { 
  110. $user = new WP_User( $user_id ); 
  111. $user_role = array_shift( $user->roles ); 
  112. $stats_roles = stats_get_option( 'roles' ); 
  113.  
  114. // Is the users role in the available stats roles? 
  115. if ( is_array( $stats_roles ) && in_array( $user_role, $stats_roles ) ) { 
  116. $caps = array( 'read' ); 
  117.  
  118. return $caps; 
  119.  
  120. function stats_template_redirect() { 
  121. global $wp_the_query, $current_user, $stats_footer; 
  122.  
  123. if ( is_feed() || is_robots() || is_trackback() || is_preview() ) 
  124. return; 
  125.  
  126. $options = stats_get_options(); 
  127.  
  128. // Should we be counting this user's views? 
  129. if ( !empty( $current_user->ID ) ) { 
  130. $count_roles = stats_get_option( 'count_roles' ); 
  131. if ( ! array_intersect( $current_user->roles, $count_roles ) ) 
  132. return; 
  133.  
  134. add_action( 'wp_footer', 'stats_footer', 101 ); 
  135. add_action( 'wp_head', 'stats_add_shutdown_action' ); 
  136.  
  137. $blog = Jetpack_Options::get_option( 'id' ); 
  138. $tz = get_option( 'gmt_offset' ); 
  139. $v = 'ext'; 
  140. $blog_url = parse_url( site_url() ); 
  141. $srv = $blog_url['host']; 
  142. $j = sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ); 
  143. if ( $wp_the_query->is_single || $wp_the_query->is_page || $wp_the_query->is_posts_page ) { 
  144. // Store and reset the queried_object and queried_object_id 
  145. // Otherwise, redirect_canonical() will redirect to home_url( '/' ) for show_on_front = page sites where home_url() is not all lowercase. 
  146. // Repro: 
  147. // 1. Set home_url = http://ExamPle.com/ 
  148. // 2. Set show_on_front = page 
  149. // 3. Set page_on_front = something 
  150. // 4. Visit http://example.com/ 
  151.  
  152. $queried_object = ( isset( $wp_the_query->queried_object ) ) ? $wp_the_query->queried_object : null; 
  153. $queried_object_id = ( isset( $wp_the_query->queried_object_id ) ) ? $wp_the_query->queried_object_id : null; 
  154. $post = $wp_the_query->get_queried_object_id(); 
  155. $wp_the_query->queried_object = $queried_object; 
  156. $wp_the_query->queried_object_id = $queried_object_id; 
  157. } else { 
  158. $post = '0'; 
  159.  
  160. $script = set_url_scheme( '//stats.wp.com/e-' . gmdate( 'YW' ) . '.js' ); 
  161. $data = stats_array( compact( 'v', 'j', 'blog', 'post', 'tz', 'srv' ) ); 
  162.  
  163. $stats_footer = <<<END 
  164. <script type='text/javascript' src='{$script}' async defer></script> 
  165. <script type='text/javascript'> 
  166. _stq = window._stq || []; 
  167. _stq.push([ 'view', {{$data}} ]); 
  168. _stq.push([ 'clickTrackerInit', '{$blog}', '{$post}' ]); 
  169. </script> 
  170.  
  171. END; 
  172.  
  173. function stats_add_shutdown_action() { 
  174. // just in case wp_footer isn't in your theme 
  175. add_action( 'shutdown', 'stats_footer', 101 ); 
  176.  
  177. function stats_footer() { 
  178. global $stats_footer; 
  179. print $stats_footer; 
  180. $stats_footer = ''; 
  181.  
  182. function stats_get_options() { 
  183. $options = get_option( 'stats_options' ); 
  184.  
  185. if ( !isset( $options['version'] ) || $options['version'] < STATS_VERSION ) 
  186. $options = stats_upgrade_options( $options ); 
  187.  
  188. return $options; 
  189.  
  190. function stats_get_option( $option ) { 
  191. $options = stats_get_options(); 
  192.  
  193. if ( $option == 'blog_id' ) 
  194. return Jetpack_Options::get_option( 'id' ); 
  195.  
  196. if ( isset( $options[$option] ) ) 
  197. return $options[$option]; 
  198.  
  199. return null; 
  200.  
  201. function stats_set_option( $option, $value ) { 
  202. $options = stats_get_options(); 
  203.  
  204. $options[$option] = $value; 
  205.  
  206. stats_set_options($options); 
  207.  
  208. function stats_set_options($options) { 
  209. update_option( 'stats_options', $options ); 
  210.  
  211. function stats_upgrade_options( $options ) { 
  212. $defaults = array( 
  213. 'admin_bar' => true,  
  214. 'roles' => array( 'administrator' ),  
  215. 'count_roles' => array(),  
  216. 'blog_id' => Jetpack_Options::get_option( 'id' ),  
  217. 'do_not_track' => true, // @todo 
  218. 'hide_smile' => true,  
  219. ); 
  220.  
  221. if ( isset( $options['reg_users'] ) ) { 
  222. if ( ! function_exists( 'get_editable_roles' ) ) 
  223. require_once( ABSPATH . 'wp-admin/includes/user.php' ); 
  224. if ( $options['reg_users'] ) 
  225. $options['count_roles'] = array_keys( get_editable_roles() ); 
  226. unset( $options['reg_users'] ); 
  227.  
  228. if ( is_array( $options ) && !empty( $options ) ) 
  229. $new_options = array_merge( $defaults, $options ); 
  230. else 
  231. $new_options = $defaults; 
  232.  
  233. $new_options['version'] = STATS_VERSION; 
  234.  
  235. stats_set_options( $new_options ); 
  236.  
  237. stats_update_blog(); 
  238.  
  239. return $new_options; 
  240.  
  241. function stats_array( $kvs ) { 
  242. /** 
  243. * Filter the options added to the JavaScript Stats tracking code. 
  244. * 
  245. * @since 1.1.0 
  246. * 
  247. * @param array $kvs Array of options about the site and page you're on. 
  248. */ 
  249. $kvs = apply_filters( 'stats_array', $kvs ); 
  250. $kvs = array_map( 'addslashes', $kvs ); 
  251. foreach ( $kvs as $k => $v ) 
  252. $jskvs[] = "$k:'$v'"; 
  253. return join( ', ', $jskvs ); 
  254.  
  255. /** 
  256. * Admin Pages 
  257. */ 
  258. function stats_admin_menu() { 
  259. global $pagenow; 
  260.  
  261. // If we're at an old Stats URL, redirect to the new one. 
  262. // Don't even bother with caps, menu_page_url(), etc. Just do it. 
  263. if ( 'index.php' == $pagenow && isset( $_GET['page'] ) && 'stats' == $_GET['page'] ) { 
  264. $redirect_url = str_replace( array( '/wp-admin/index.php?', '/wp-admin/?' ), '/wp-admin/admin.php?', $_SERVER['REQUEST_URI'] ); 
  265. $relative_pos = strpos( $redirect_url, '/wp-admin/' ); 
  266. if ( false !== $relative_pos ) { 
  267. wp_safe_redirect( admin_url( substr( $redirect_url, $relative_pos + 10 ) ) ); 
  268. exit; 
  269.  
  270. $hook = add_submenu_page( 'jetpack', __( 'Site Stats', 'jetpack' ), __( 'Site Stats', 'jetpack' ), 'view_stats', 'stats', 'stats_reports_page' ); 
  271. add_action( "load-$hook", 'stats_reports_load' ); 
  272.  
  273. function stats_admin_path() { 
  274. return Jetpack::module_configuration_url( __FILE__ ); 
  275.  
  276. function stats_reports_load() { 
  277. wp_enqueue_script( 'jquery' ); 
  278. wp_enqueue_script( 'postbox' ); 
  279. wp_enqueue_script( 'underscore' ); 
  280.  
  281. add_action( 'admin_print_styles', 'stats_reports_css' ); 
  282.  
  283. if ( isset( $_GET['nojs'] ) && $_GET['nojs'] ) { 
  284. $parsed = parse_url( admin_url() ); 
  285. // Remember user doesn't want JS 
  286. setcookie( 'stnojs', '1', time() + 172800, $parsed['path'] ); // 2 days 
  287.  
  288. if ( isset( $_COOKIE['stnojs'] ) && $_COOKIE['stnojs'] ) { 
  289. // Detect if JS is on. If so, remove cookie so next page load is via JS 
  290. add_action( 'admin_print_footer_scripts', 'stats_js_remove_stnojs_cookie' ); 
  291. } else if ( !isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) ) { 
  292. // Normal page load. Load page content via JS. 
  293. add_action( 'admin_print_footer_scripts', 'stats_js_load_page_via_ajax' ); 
  294.  
  295. function stats_reports_css() { 
  296. ?> 
  297. <style type="text/css"> 
  298. #stats-loading-wrap p { 
  299. text-align: center; 
  300. font-size: 2em; 
  301. margin: 7.5em 15px 0 0; 
  302. height: 64px; 
  303. line-height: 64px; 
  304. </style> 
  305. <?php 
  306.  
  307. // Detect if JS is on. If so, remove cookie so next page load is via JS
  308. function stats_js_remove_stnojs_cookie() { 
  309. $parsed = parse_url( admin_url() ); 
  310. ?> 
  311. <script type="text/javascript"> 
  312. /* <![CDATA[ */ 
  313. document.cookie = 'stnojs=0; expires=Wed, 9 Mar 2011 16:55:50 UTC; path=<?php echo esc_js( $parsed['path'] ); ?>'; 
  314. /* ]]> */ 
  315. </script> 
  316. <?php 
  317.  
  318. // Normal page load. Load page content via JS. 
  319. function stats_js_load_page_via_ajax() { 
  320. ?> 
  321. <script type="text/javascript"> 
  322. /* <![CDATA[ */ 
  323. if ( -1 == document.location.href.indexOf( 'noheader' ) ) { 
  324. jQuery( function( $ ) { 
  325. $.get( document.location.href + '&noheader', function( responseText ) { 
  326. $( '#stats-loading-wrap' ).replaceWith( responseText ); 
  327. } ); 
  328. } ); 
  329. /* ]]> */ 
  330. </script> 
  331. <?php 
  332.  
  333. function stats_reports_page() { 
  334. if ( isset( $_GET['dashboard'] ) ) 
  335. return stats_dashboard_widget_content(); 
  336.  
  337. $blog_id = stats_get_option( 'blog_id' ); 
  338.  
  339. if ( !isset( $_GET['noheader'] ) && empty( $_GET['nojs'] ) && empty( $_COOKIE['stnojs'] ) ) { 
  340. $nojs_url = add_query_arg( 'nojs', '1' ); 
  341. $http = is_ssl() ? 'https' : 'http'; 
  342. // Loading message 
  343. // No JS fallback message 
  344. ?> 
  345. <div class="wrap"> 
  346. <h2><?php esc_html_e( 'Site Stats', 'jetpack'); ?> <?php if ( current_user_can( 'jetpack_manage_modules' ) ) : ?><a style="font-size:13px;" href="<?php echo esc_url( admin_url('admin.php?page=jetpack&configure=stats') ); ?>"><?php esc_html_e( 'Configure', 'jetpack'); ?></a><?php endif; ?></h2> 
  347. </div> 
  348. <div id="stats-loading-wrap" class="wrap"> 
  349. <p class="hide-if-no-js"><img width="32" height="32" alt="<?php esc_attr_e( 'Loading…', 'jetpack' ); ?>" src="<?php 
  350. /** This filter is documented in modules/shortcodes/audio.php */ 
  351. echo esc_url( apply_filters( 'jetpack_static_url', "{$http}://en.wordpress.com/i/loading/loading-64.gif" ) ); ?>" /></p> 
  352. <p style="font-size: 11pt; margin: 0;"><a href="https://wordpress.com/stats/<?php echo $blog_id; ?>"><?php esc_html_e( 'View stats on WordPress.com right now', 'jetpack' ); ?></a></p> 
  353. <p class="hide-if-js"><?php esc_html_e( 'Your Site Stats work better with Javascript enabled.', 'jetpack' ); ?><br /> 
  354. <a href="<?php echo esc_url( $nojs_url ); ?>"><?php esc_html_e( 'View Site Stats without Javascript', 'jetpack' ); ?></a>.</p> 
  355. </div> 
  356. <?php 
  357. return; 
  358.  
  359. $day = isset( $_GET['day'] ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_GET['day'] ) ? $_GET['day'] : false; 
  360. $q = array( 
  361. 'noheader' => 'true',  
  362. 'proxy' => '',  
  363. 'page' => 'stats',  
  364. 'day' => $day,  
  365. 'blog' => $blog_id,  
  366. 'charset' => get_option( 'blog_charset' ),  
  367. 'color' => get_user_option( 'admin_color' ),  
  368. 'ssl' => is_ssl(),  
  369. 'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),  
  370. ); 
  371. if ( get_locale() !== 'en_US' ) { 
  372. $q['jp_lang'] = get_locale(); 
  373. $args = array( 
  374. 'view' => array( 'referrers', 'postviews', 'searchterms', 'clicks', 'post', 'table' ),  
  375. 'numdays' => 'int',  
  376. 'day' => 'date',  
  377. 'unit' => array( 1, 7, 31, 'human' ),  
  378. 'humanize' => array( 'true' ),  
  379. 'num' => 'int',  
  380. 'summarize' => null,  
  381. 'post' => 'int',  
  382. 'width' => 'int',  
  383. 'height' => 'int',  
  384. 'data' => 'data',  
  385. 'blog_subscribers' => 'int',  
  386. 'comment_subscribers' => null,  
  387. 'type' => array( 'wpcom', 'email', 'pending' ),  
  388. 'pagenum' => 'int',  
  389. ); 
  390. foreach ( $args as $var => $vals ) { 
  391. if ( !isset( $_REQUEST[$var] ) ) 
  392. continue; 
  393. if ( is_array( $vals ) ) { 
  394. if ( in_array( $_REQUEST[$var], $vals ) ) 
  395. $q[$var] = $_REQUEST[$var]; 
  396. } elseif ( $vals == 'int' ) { 
  397. $q[$var] = intval( $_REQUEST[$var] ); 
  398. } elseif ( $vals == 'date' ) { 
  399. if ( preg_match( '/^\d{4}-\d{2}-\d{2}$/', $_REQUEST[$var] ) ) 
  400. $q[$var] = $_REQUEST[$var]; 
  401. } elseif ( $vals == null ) { 
  402. $q[$var] = ''; 
  403. } elseif ( $vals == 'data' ) { 
  404. if ( substr( $_REQUEST[$var], 0, 9 ) == 'index.php' ) 
  405. $q[$var] = $_REQUEST[$var]; 
  406.  
  407. if ( isset( $_GET['chart'] ) ) { 
  408. if ( preg_match( '/^[a-z0-9-]+$/', $_GET['chart'] ) ) { 
  409. $chart = sanitize_title( $_GET['chart'] ); 
  410. $url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-includes/charts/{$chart}.php"; 
  411. } else { 
  412. $url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php"; 
  413.  
  414. $url = add_query_arg( $q, $url ); 
  415. $method = 'GET'; 
  416. $timeout = 90; 
  417. $user_id = JETPACK_MASTER_USER; // means send the wp.com user_id 
  418.  
  419. $get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) ); 
  420. $get_code = wp_remote_retrieve_response_code( $get ); 
  421. if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) { 
  422. stats_print_wp_remote_error( $get, $url ); 
  423. } else { 
  424. if ( !empty( $get['headers']['content-type'] ) ) { 
  425. $type = $get['headers']['content-type']; 
  426. if ( substr( $type, 0, 5 ) == 'image' ) { 
  427. $img = $get['body']; 
  428. header( 'Content-Type: ' . $type ); 
  429. header( 'Content-Length: ' . strlen( $img ) ); 
  430. echo $img; 
  431. die(); 
  432. $body = stats_convert_post_titles( $get['body'] ); 
  433. $body = stats_convert_chart_urls( $body ); 
  434. $body = stats_convert_image_urls( $body ); 
  435. $body = stats_convert_admin_urls( $body ); 
  436. echo $body; 
  437. if ( isset( $_GET['noheader'] ) ) 
  438. die; 
  439.  
  440. function stats_convert_admin_urls( $html ) { 
  441. return str_replace( 'index.php?page=stats', 'admin.php?page=stats', $html ); 
  442.  
  443. function stats_convert_image_urls( $html ) { 
  444. $url = set_url_scheme( 'https://' . STATS_DASHBOARD_SERVER ); 
  445. $html = preg_replace( '|(["\'])(/i/stats.+)\\1|', '$1' . $url . '$2$1', $html ); 
  446. return $html; 
  447.  
  448. function stats_convert_chart_urls( $html ) { 
  449. $html = preg_replace_callback( '|https?://[-.a-z0-9]+/wp-includes/charts/([-.a-z0-9]+).php(\??)|',  
  450. create_function( 
  451. '$matches',  
  452. // If there is a query string, change the beginning '?' to a '&' so it fits into the middle of this query string 
  453. 'return "admin.php?page=stats&noheader&chart=" . $matches[1] . str_replace( "?", "&", $matches[2] );' 
  454. ),  
  455. $html ); 
  456. return $html; 
  457.  
  458. function stats_convert_post_titles( $html ) { 
  459. global $wpdb, $stats_posts; 
  460. $pattern = "<span class='post-(\d+)-link'>.*?</span>"; 
  461. if ( !preg_match_all( "!$pattern!", $html, $matches ) ) 
  462. return $html; 
  463. $posts = get_posts( array( 
  464. 'include' => implode( ', ', $matches[1] ),  
  465. 'post_type' => 'any',  
  466. 'post_status' => 'any',  
  467. 'numberposts' => -1,  
  468. )); 
  469. foreach ( $posts as $post ) 
  470. $stats_posts[$post->ID] = $post; 
  471. $html = preg_replace_callback( "!$pattern!", 'stats_convert_post_title', $html ); 
  472. return $html; 
  473.  
  474. function stats_convert_post_title( $matches ) { 
  475. global $stats_posts; 
  476. $post_id = $matches[1]; 
  477. if ( isset( $stats_posts[$post_id] ) ) 
  478. return '<a href="' . get_permalink( $post_id ) . '" target="_blank">' . get_the_title( $post_id ) . '</a>'; 
  479. return $matches[0]; 
  480.  
  481. function stats_configuration_load() { 
  482. if ( isset( $_POST['action'] ) && $_POST['action'] == 'save_options' && $_POST['_wpnonce'] == wp_create_nonce( 'stats' ) ) { 
  483. $options = stats_get_options(); 
  484. $options['admin_bar'] = isset( $_POST['admin_bar'] ) && $_POST['admin_bar']; 
  485. $options['hide_smile'] = isset( $_POST['hide_smile'] ) && $_POST['hide_smile']; 
  486.  
  487. $options['roles'] = array( 'administrator' ); 
  488. foreach ( get_editable_roles() as $role => $details ) 
  489. if ( isset( $_POST["role_$role"] ) && $_POST["role_$role"] ) 
  490. $options['roles'][] = $role; 
  491.  
  492. $options['count_roles'] = array(); 
  493. foreach ( get_editable_roles() as $role => $details ) 
  494. if ( isset( $_POST["count_role_$role"] ) && $_POST["count_role_$role"] ) 
  495. $options['count_roles'][] = $role; 
  496.  
  497. stats_set_options( $options ); 
  498. stats_update_blog(); 
  499. Jetpack::state( 'message', 'module_configured' ); 
  500. wp_safe_redirect( Jetpack::module_configuration_url( 'stats' ) ); 
  501. exit; 
  502.  
  503. function stats_configuration_head() { 
  504. ?> 
  505. <style type="text/css"> 
  506. #statserror { 
  507. border: 1px solid #766; 
  508. background-color: #d22; 
  509. padding: 1em 3em; 
  510. .stats-smiley { 
  511. vertical-align: 1px; 
  512. </style> 
  513. <?php 
  514.  
  515. function stats_configuration_screen() { 
  516. $options = stats_get_options(); 
  517. ?> 
  518. <div class="narrow"> 
  519. <p><?php printf( __( 'Visit <a href="%s">Site Stats</a> to see your stats.', 'jetpack' ), esc_url( menu_page_url( 'stats', false ) ) ); ?></p> 
  520. <form method="post"> 
  521. <input type='hidden' name='action' value='save_options' /> 
  522. <?php wp_nonce_field( 'stats' ); ?> 
  523. <table id="menu" class="form-table"> 
  524. <tr valign="top"><th scope="row"><label for="admin_bar"><?php _e( 'Admin bar' , 'jetpack' ); ?></label></th> 
  525. <td><label><input type='checkbox'<?php checked( $options['admin_bar'] ); ?> name='admin_bar' id='admin_bar' /> <?php _e( "Put a chart showing 48 hours of views in the admin bar.", 'jetpack' ); ?></label></td></tr> 
  526. <tr valign="top"><th scope="row"><?php _e( 'Registered users', 'jetpack' ); ?></th> 
  527. <td> 
  528. <?php _e( "Count the page views of registered users who are logged in.", 'jetpack' ); ?><br/> 
  529. <?php 
  530. $count_roles = stats_get_option( 'count_roles' ); 
  531. foreach ( get_editable_roles() as $role => $details ) { 
  532. ?> 
  533. <label><input type='checkbox' name='count_role_<?php echo $role; ?>'<?php checked( in_array( $role, $count_roles ) ); ?> /> <?php echo translate_user_role( $details['name'] ); ?></label><br/> 
  534. <?php 
  535. ?> 
  536. </td></tr> 
  537. <tr valign="top"><th scope="row"><?php _e( 'Smiley' , 'jetpack' ); ?></th> 
  538. <td><label><input type='checkbox'<?php checked( isset( $options['hide_smile'] ) && $options['hide_smile'] ); ?> name='hide_smile' id='hide_smile' /> <?php _e( 'Hide the stats smiley face image.', 'jetpack' ); ?></label><br /> <span class="description"><?php _e( 'The image helps collect stats and <strong>makes the world a better place</strong> but should still work when hidden', 'jetpack' ); ?> <img class="stats-smiley" alt="<?php esc_attr_e( 'Smiley face', 'jetpack' ); ?>" src="<?php echo esc_url( plugins_url( 'images/stats-smiley.gif', dirname( __FILE__ ) ) ); ?>" width="6" height="5" /></span></td></tr> 
  539. <tr valign="top"><th scope="row"><?php _e( 'Report visibility' , 'jetpack' ); ?></th> 
  540. <td> 
  541. <?php _e( 'Select the roles that will be able to view stats reports.', 'jetpack' ); ?><br/> 
  542. <?php 
  543. $stats_roles = stats_get_option( 'roles' ); 
  544. foreach ( get_editable_roles() as $role => $details ) { 
  545. ?> 
  546. <label><input type='checkbox' <?php if ( $role == 'administrator' ) echo "disabled='disabled' "; ?>name='role_<?php echo $role; ?>'<?php checked( $role == 'administrator' || in_array( $role, $stats_roles ) ); ?> /> <?php echo translate_user_role( $details['name'] ); ?></label><br/> 
  547. <?php 
  548. ?> 
  549. </td></tr> 
  550. </table> 
  551. <p class="submit"><input type='submit' class='button-primary' value='<?php echo esc_attr( __( 'Save configuration', 'jetpack' ) ); ?>' /></p> 
  552. </form> 
  553. </div> 
  554. <?php 
  555.  
  556. function stats_hide_smile_css() { 
  557. $options = stats_get_options(); 
  558. if ( isset( $options['hide_smile'] ) && $options['hide_smile'] ) { 
  559. ?> 
  560. <style type='text/css'>img#wpstats{display:none}</style><?php 
  561.  
  562. function stats_admin_bar_head() { 
  563. if ( !stats_get_option( 'admin_bar' ) ) 
  564. return; 
  565.  
  566. if ( !current_user_can( 'view_stats' ) ) 
  567. return; 
  568.  
  569. if ( function_exists( 'is_admin_bar_showing' ) && !is_admin_bar_showing() ) { 
  570. return; 
  571.  
  572. add_action( 'admin_bar_menu', 'stats_admin_bar_menu', 100 ); 
  573. ?> 
  574.  
  575. <style type='text/css'> 
  576. #wpadminbar .quicklinks li#wp-admin-bar-stats { 
  577. height: 28px; 
  578. #wpadminbar .quicklinks li#wp-admin-bar-stats a { 
  579. height: 28px; 
  580. padding: 0; 
  581. #wpadminbar .quicklinks li#wp-admin-bar-stats a div { 
  582. height: 28px; 
  583. width: 95px; 
  584. overflow: hidden; 
  585. margin: 0 10px; 
  586. #wpadminbar .quicklinks li#wp-admin-bar-stats a:hover div { 
  587. width: auto; 
  588. margin: 0 8px 0 10px; 
  589. #wpadminbar .quicklinks li#wp-admin-bar-stats a img { 
  590. height: 24px; 
  591. padding: 2px 0; 
  592. max-width: none; 
  593. border: none; 
  594. </style> 
  595. <?php 
  596.  
  597. function stats_admin_bar_menu( &$wp_admin_bar ) { 
  598. $blog_id = stats_get_option( 'blog_id' ); 
  599.  
  600. $url = add_query_arg( 'page', 'stats', admin_url( 'admin.php' ) ); // no menu_page_url() blog-side. 
  601.  
  602. $img_src = esc_attr( add_query_arg( array( 'noheader'=>'', 'proxy'=>'', 'chart'=>'admin-bar-hours-scale' ), $url ) ); 
  603. $img_src_2x = esc_attr( add_query_arg( array( 'noheader'=>'', 'proxy'=>'', 'chart'=>'admin-bar-hours-scale-2x' ), $url ) ); 
  604.  
  605. $alt = esc_attr( __( 'Stats', 'jetpack' ) ); 
  606.  
  607. $title = esc_attr( __( 'Views over 48 hours. Click for more Site Stats.', 'jetpack' ) ); 
  608.  
  609. $menu = array( 'id' => 'stats', 'title' => "<div><script type='text/javascript'>var src;if(typeof(window.devicePixelRatio)=='undefined'||window.devicePixelRatio<2) {src='$img_src';}else{src='$img_src_2x';}document.write('<img src=\''+src+'\' alt=\'$alt\' title=\'$title\' />');</script></div>", 'href' => $url ); 
  610.  
  611. $wp_admin_bar->add_menu( $menu ); 
  612.  
  613. function stats_update_blog() { 
  614. Jetpack::xmlrpc_async_call( 'jetpack.updateBlog', stats_get_blog() ); 
  615.  
  616. function stats_get_blog() { 
  617. $home = parse_url( trailingslashit( get_option( 'home' ) ) ); 
  618. $blog = array( 
  619. 'host' => $home['host'],  
  620. 'path' => $home['path'],  
  621. 'blogname' => get_option( 'blogname' ),  
  622. 'blogdescription' => get_option( 'blogdescription' ),  
  623. 'siteurl' => get_option( 'siteurl' ),  
  624. 'gmt_offset' => get_option( 'gmt_offset' ),  
  625. 'timezone_string' => get_option( 'timezone_string' ),  
  626. 'stats_version' => STATS_VERSION,  
  627. 'stats_api' => 'jetpack',  
  628. 'page_on_front' => get_option( 'page_on_front' ),  
  629. 'permalink_structure' => get_option( 'permalink_structure' ),  
  630. 'category_base' => get_option( 'category_base' ),  
  631. 'tag_base' => get_option( 'tag_base' ),  
  632. ); 
  633. $blog = array_merge( stats_get_options(), $blog ); 
  634. unset( $blog['roles'], $blog['blog_id'] ); 
  635. return stats_esc_html_deep( $blog ); 
  636.  
  637. /** 
  638. * Modified from stripslashes_deep() 
  639. */ 
  640. function stats_esc_html_deep( $value ) { 
  641. if ( is_array( $value ) ) { 
  642. $value = array_map( 'stats_esc_html_deep', $value ); 
  643. } elseif ( is_object( $value ) ) { 
  644. $vars = get_object_vars( $value ); 
  645. foreach ( $vars as $key => $data ) { 
  646. $value->{$key} = stats_esc_html_deep( $data ); 
  647. } elseif ( is_string( $value ) ) { 
  648. $value = esc_html( $value ); 
  649.  
  650. return $value; 
  651.  
  652. function stats_xmlrpc_methods( $methods ) { 
  653. $my_methods = array( 
  654. 'jetpack.getBlog' => 'stats_get_blog',  
  655. ); 
  656.  
  657. return array_merge( $methods, $my_methods ); 
  658.  
  659. function stats_register_dashboard_widget() { 
  660. if ( ! current_user_can( 'view_stats' ) ) 
  661. return; 
  662.  
  663. // wp_dashboard_empty: we load in the content after the page load via JS 
  664. wp_add_dashboard_widget( 'dashboard_stats', __( 'Site Stats', 'jetpack' ), 'wp_dashboard_empty', 'stats_dashboard_widget_control' ); 
  665.  
  666. add_action( 'admin_head', 'stats_dashboard_head' ); 
  667.  
  668. function stats_dashboard_widget_options() { 
  669. $defaults = array( 'chart' => 1, 'top' => 1, 'search' => 7 ); 
  670. if ( ( !$options = get_option( 'stats_dashboard_widget' ) ) || !is_array( $options ) ) 
  671. $options = array(); 
  672.  
  673. // Ignore obsolete option values 
  674. $intervals = array( 1, 7, 31, 90, 365 ); 
  675. foreach ( array( 'top', 'search' ) as $key ) 
  676. if ( isset( $options[$key] ) && !in_array( $options[$key], $intervals ) ) 
  677. unset( $options[$key] ); 
  678.  
  679. return array_merge( $defaults, $options ); 
  680.  
  681. function stats_dashboard_widget_control() { 
  682. $periods = array( 
  683. '1' => __( 'day', 'jetpack' ),  
  684. '7' => __( 'week', 'jetpack' ),  
  685. '31' => __( 'month', 'jetpack' ),  
  686. ); 
  687. $intervals = array( 
  688. '1' => __( 'the past day', 'jetpack' ),  
  689. '7' => __( 'the past week', 'jetpack' ),  
  690. '31' => __( 'the past month', 'jetpack' ),  
  691. '90' => __( 'the past quarter', 'jetpack' ),  
  692. '365' => __( 'the past year', 'jetpack' ),  
  693. ); 
  694. $defaults = array( 
  695. 'top' => 1,  
  696. 'search' => 7,  
  697. ); 
  698.  
  699. $options = stats_dashboard_widget_options(); 
  700.  
  701. if ( 'post' == strtolower( $_SERVER['REQUEST_METHOD'] ) && isset( $_POST['widget_id'] ) && 'dashboard_stats' == $_POST['widget_id'] ) { 
  702. if ( isset( $periods[ $_POST['chart'] ] ) ) 
  703. $options['chart'] = $_POST['chart']; 
  704. foreach ( array( 'top', 'search' ) as $key ) { 
  705. if ( isset( $intervals[ $_POST[$key] ] ) ) 
  706. $options[$key] = $_POST[$key]; 
  707. else 
  708. $options[$key] = $defaults[$key]; 
  709. update_option( 'stats_dashboard_widget', $options ); 
  710. ?> 
  711. <p> 
  712. <label for="chart"><?php _e( 'Chart stats by' , 'jetpack' ); ?></label> 
  713. <select id="chart" name="chart"> 
  714. <?php 
  715. foreach ( $periods as $val => $label ) { 
  716. ?> 
  717. <option value="<?php echo $val; ?>"<?php selected( $val, $options['chart'] ); ?>><?php echo esc_html( $label ); ?></option> 
  718. <?php 
  719. ?> 
  720. </select>. 
  721. </p> 
  722.  
  723. <p> 
  724. <label for="top"><?php _e( 'Show top posts over', 'jetpack' ); ?></label> 
  725. <select id="top" name="top"> 
  726. <?php 
  727. foreach ( $intervals as $val => $label ) { 
  728. ?> 
  729. <option value="<?php echo $val; ?>"<?php selected( $val, $options['top'] ); ?>><?php echo esc_html( $label ); ?></option> 
  730. <?php 
  731. ?> 
  732. </select>. 
  733. </p> 
  734.  
  735. <p> 
  736. <label for="search"><?php _e( 'Show top search terms over', 'jetpack' ); ?></label> 
  737. <select id="search" name="search"> 
  738. <?php 
  739. foreach ( $intervals as $val => $label ) { 
  740. ?> 
  741. <option value="<?php echo $val; ?>"<?php selected( $val, $options['search'] ); ?>><?php echo esc_html( $label ); ?></option> 
  742. <?php 
  743. ?> 
  744. </select>. 
  745. </p> 
  746. <?php 
  747.  
  748. function stats_jetpack_dashboard_widget() { 
  749. ?> 
  750. <h3> 
  751. <span class="js-toggle-stats_dashboard_widget_control"> 
  752. <?php esc_html_e( 'Configure', 'jetpack' ); ?> 
  753. </span> 
  754. <?php esc_html_e( 'Site Stats', 'jetpack' ); ?> 
  755. </h3> 
  756. <form id="stats_dashboard_widget_control" action="<?php esc_url( admin_url() ); ?>" method="post"> 
  757. <?php stats_dashboard_widget_control(); ?> 
  758. <?php wp_nonce_field( 'edit-dashboard-widget_dashboard_stats', 'dashboard-widget-nonce' ); ?> 
  759. <input type="hidden" name="widget_id" value="dashboard_stats" /> 
  760. <?php submit_button( __( 'Submit', 'jetpack' ) ); ?> 
  761. </form> 
  762. <div id="dashboard_stats"> 
  763. <div class="inside"> 
  764. <div style="height: 250px;"></div> 
  765. </div> 
  766. </div> 
  767. <script> 
  768. jQuery(document).ready(function($) { 
  769. $('.js-toggle-stats_dashboard_widget_control').click(function(e) { 
  770. e.preventDefault(); 
  771. $(this).parent().toggleClass('controlVisible'); 
  772. $('#stats_dashboard_widget_control').slideToggle(); 
  773. }); 
  774. }); 
  775. </script> 
  776. <style> 
  777. .js-toggle-stats_dashboard_widget_control { 
  778. float: right; 
  779. font-weight: 400; 
  780. color: #444; 
  781. font-size: .8em; 
  782. text-decoration: underline; 
  783. cursor: pointer; 
  784. #stats_dashboard_widget_control { 
  785. display: none; 
  786. padding: 0 10px; 
  787. overflow: hidden; 
  788. #stats_dashboard_widget_control .button-primary { 
  789. float: right; 
  790. #dashboard_stats { 
  791. box-sizing: border-box; 
  792. width: 100%; 
  793. padding: 0 10px; 
  794. </style> 
  795. <?php 
  796.  
  797. function stats_register_widget_control_callback() { 
  798. $GLOBALS['wp_dashboard_control_callbacks']['dashboard_stats'] = 'stats_dashboard_widget_control'; 
  799. // Javascript and CSS for dashboard widget 
  800. function stats_dashboard_head() { ?> 
  801. <script type="text/javascript"> 
  802. /* <![CDATA[ */ 
  803. jQuery(window).load( function() { 
  804. jQuery( function($) { 
  805. resizeChart(); 
  806. jQuery(window).resize( _.debounce( function() { 
  807. resizeChart(); 
  808. }, 100) ); 
  809. } ); 
  810.  
  811. function resizeChart() { 
  812. var dashStats = jQuery( '#dashboard_stats div.inside' ); 
  813.  
  814. if ( dashStats.find( '.dashboard-widget-control-form' ).length ) { 
  815. return; 
  816.  
  817. if ( ! dashStats.length ) { 
  818. dashStats = jQuery( '#dashboard_stats div.dashboard-widget-content' ); 
  819. var h = parseInt( dashStats.parent().height() ) - parseInt( dashStats.prev().height() ); 
  820. var args = 'width=' + dashStats.width() + '&height=' + h.toString(); 
  821. } else { 
  822. if ( jQuery('#dashboard_stats' ).hasClass('postbox') ) { 
  823. var args = 'width=' + ( dashStats.prev().width() * 2 ).toString(); 
  824. } else { 
  825. var args = 'width=' + ( dashStats.width() * 2 ).toString(); 
  826.  
  827. dashStats.not( '.dashboard-widget-control' ).load( 'admin.php?page=stats&noheader&dashboard&' + args ); 
  828. } ); 
  829. /* ]]> */ 
  830. </script> 
  831. <style type="text/css"> 
  832. /* <![CDATA[ */ 
  833. #stat-chart { 
  834. background: none !important; 
  835. #dashboard_stats .inside { 
  836. margin: 10px 0 0 0 !important; 
  837. #dashboard_stats #stats-graph { 
  838. margin: 0; 
  839. #stats-info { 
  840. border-top: 1px solid #dfdfdf; 
  841. margin: 7px -10px 0 -10px; 
  842. padding: 10px; 
  843. background: #fcfcfc; 
  844. -moz-box-shadow:inset 0 1px 0 #fff; 
  845. -webkit-box-shadow:inset 0 1px 0 #fff; 
  846. box-shadow:inset 0 1px 0 #fff; 
  847. overflow: hidden; 
  848. border-radius: 0 0 2px 2px; 
  849. -webkit-border-radius: 0 0 2px 2px; 
  850. -moz-border-radius: 0 0 2px 2px; 
  851. -khtml-border-radius: 0 0 2px 2px; 
  852. #stats-info #top-posts, #stats-info #top-search { 
  853. float: left; 
  854. width: 50%; 
  855. #top-posts .stats-section-inner p { 
  856. white-space: nowrap; 
  857. overflow: hidden; 
  858. #top-posts .stats-section-inner p a { 
  859. overflow: hidden; 
  860. text-overflow: ellipsis; 
  861. #stats-info div#active { 
  862. border-top: 1px solid #dfdfdf; 
  863. margin: 0 -10px; 
  864. padding: 10px 10px 0 10px; 
  865. -moz-box-shadow:inset 0 1px 0 #fff; 
  866. -webkit-box-shadow:inset 0 1px 0 #fff; 
  867. box-shadow:inset 0 1px 0 #fff; 
  868. overflow: hidden; 
  869. #top-search p { 
  870. color: #999; 
  871. #stats-info h4 { 
  872. font-size: 1em; 
  873. margin: 0 0 .5em 0 !important; 
  874. #stats-info p { 
  875. margin: 0 0 .25em; 
  876. color: #999; 
  877. #stats-info p.widget-loading { 
  878. margin: 1em 0 0; 
  879. color: #333; 
  880. #stats-info p a { 
  881. display: block; 
  882. #stats-info p a.button { 
  883. display: inline; 
  884. /* ]]> */ 
  885. </style> 
  886. <?php 
  887.  
  888. function stats_dashboard_widget_content() { 
  889. if ( !isset( $_GET['width'] ) || ( !$width = (int) ( $_GET['width'] / 2 ) ) || $width < 250 ) 
  890. $width = 370; 
  891. if ( !isset( $_GET['height'] ) || ( !$height = (int) $_GET['height'] - 36 ) || $height < 230 ) 
  892. $height = 180; 
  893.  
  894. $_width = $width - 5; 
  895. $_height = $height - ( $GLOBALS['is_winIE'] ? 16 : 5 ); // hack! 
  896.  
  897. $options = stats_dashboard_widget_options(); 
  898. $blog_id = Jetpack_Options::get_option( 'id' ); 
  899.  
  900. $q = array( 
  901. 'noheader' => 'true',  
  902. 'proxy' => '',  
  903. 'blog' => $blog_id,  
  904. 'page' => 'stats',  
  905. 'chart' => '',  
  906. 'unit' => $options['chart'],  
  907. 'color' => get_user_option( 'admin_color' ),  
  908. 'width' => $_width,  
  909. 'height' => $_height,  
  910. 'ssl' => is_ssl(),  
  911. 'j' => sprintf( '%s:%s', JETPACK__API_VERSION, JETPACK__VERSION ),  
  912. ); 
  913.  
  914. $url = 'https://' . STATS_DASHBOARD_SERVER . "/wp-admin/index.php"; 
  915.  
  916. $url = add_query_arg( $q, $url ); 
  917. $method = 'GET'; 
  918. $timeout = 90; 
  919. $user_id = JETPACK_MASTER_USER; 
  920.  
  921. $get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) ); 
  922. $get_code = wp_remote_retrieve_response_code( $get ); 
  923. if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) { 
  924. stats_print_wp_remote_error( $get, $url ); 
  925. } else { 
  926. $body = stats_convert_post_titles($get['body']); 
  927. $body = stats_convert_chart_urls($body); 
  928. $body = stats_convert_image_urls($body); 
  929. echo $body; 
  930.  
  931. $post_ids = array(); 
  932.  
  933. $csv_end_date = date( 'Y-m-d', current_time( 'timestamp' ) ); 
  934. $csv_args = array( 'top' => "&limit=8&end=$csv_end_date", 'search' => "&limit=5&end=$csv_end_date" ); 
  935. /** translators: Stats dashboard widget postviews list: "$post_title $views Views" */ 
  936. $printf = __( '%1$s %2$s Views' , 'jetpack' ); 
  937.  
  938. foreach ( $top_posts = stats_get_csv( 'postviews', "days=$options[top]$csv_args[top]" ) as $i => $post ) { 
  939. if ( $post['post_id'] == 0 ) { 
  940. unset( $top_posts[$i] ); 
  941. continue; 
  942. $post_ids[] = $post['post_id']; 
  943.  
  944. // cache 
  945. get_posts( array( 'include' => join( ', ', array_unique( $post_ids ) ) ) ); 
  946.  
  947. $searches = array(); 
  948. foreach ( $search_terms = stats_get_csv( 'searchterms', "days=$options[search]$csv_args[search]" ) as $search_term ) { 
  949. if ( $search_term['searchterm'] == 'encrypted_search_terms' ) 
  950. continue; 
  951. $searches[] = esc_html( $search_term['searchterm'] ); 
  952.  
  953. ?> 
  954. <a class="button" href="admin.php?page=stats"><?php _e( 'View All', 'jetpack' ); ?></a> 
  955. <div id="stats-info"> 
  956. <div id="top-posts" class='stats-section'> 
  957. <div class="stats-section-inner"> 
  958. <h4 class="heading"><?php _e( 'Top Posts' , 'jetpack' ); ?></h4> 
  959. <?php 
  960. if ( empty( $top_posts ) ) { 
  961. ?> 
  962. <p class="nothing"><?php _e( 'Sorry, nothing to report.', 'jetpack' ); ?></p> 
  963. <?php 
  964. } else { 
  965. foreach ( $top_posts as $post ) { 
  966. if ( !get_post( $post['post_id'] ) ) 
  967. continue; 
  968. ?> 
  969. <p><?php printf( 
  970. $printf,  
  971. '<a href="' . get_permalink( $post['post_id'] ) . '">' . get_the_title( $post['post_id'] ) . '</a>',  
  972. number_format_i18n( $post['views'] ) 
  973. ); ?></p> 
  974. <?php 
  975. ?> 
  976. </div> 
  977. </div> 
  978. <div id="top-search" class='stats-section'> 
  979. <div class="stats-section-inner"> 
  980. <h4 class="heading"><?php _e( 'Top Searches' , 'jetpack' ); ?></h4> 
  981. <?php 
  982. if ( empty( $searches ) ) { 
  983. ?> 
  984. <p class="nothing"><?php _e( 'Sorry, nothing to report.', 'jetpack' ); ?></p> 
  985. <?php 
  986. } else { 
  987. ?> 
  988. <p><?php echo join( ',   ', $searches );?></p> 
  989. <?php 
  990. ?> 
  991. </div> 
  992. </div> 
  993. </div> 
  994. <div class="clear"></div> 
  995. <?php 
  996. exit; 
  997.  
  998. function stats_print_wp_remote_error( $get, $url ) { 
  999. $state_name = 'stats_remote_error_' . substr( md5( $url ), 0, 8 ); 
  1000. $previous_error = Jetpack::state( $state_name ); 
  1001. $error = md5( serialize( compact( 'get', 'url' ) ) ); 
  1002. Jetpack::state( $state_name, $error ); 
  1003. if ( $error !== $previous_error ) { 
  1004. ?> 
  1005. <div class="wrap"> 
  1006. <p><?php _e( 'We were unable to get your stats just now. Please reload this page to try again.', 'jetpack' ); ?></p> 
  1007. </div> 
  1008. <?php 
  1009. return; 
  1010. ?> 
  1011. <div class="wrap"> 
  1012. <p><?php printf( __( 'We were unable to get your stats just now. Please reload this page to try again. If this error persists, please <a href="%1$s">contact support</a>. In your report please include the information below.', 'jetpack' ), 'http://support.wordpress.com/contact/?jetpack=needs-service' ); ?></p> 
  1013. <pre> 
  1014. User Agent: "<?php echo esc_html( $_SERVER['HTTP_USER_AGENT'] ); ?>" 
  1015. Page URL: "http<?php echo (is_ssl()?'s':'') . '://' . esc_html( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ); ?>" 
  1016. API URL: "<?php echo esc_url( $url ); ?>" 
  1017. <?php 
  1018. if ( is_wp_error( $get ) ) { 
  1019. foreach ( $get->get_error_codes() as $code ) { 
  1020. foreach ( $get->get_error_messages($code) as $message ) { 
  1021. ?> 
  1022. <?php print $code . ': "' . $message . '"' ?> 
  1023.  
  1024. <?php 
  1025. } else { 
  1026. $get_code = wp_remote_retrieve_response_code( $get ); 
  1027. $content_length = strlen( wp_remote_retrieve_body( $get ) ); 
  1028. ?> 
  1029. Response code: "<?php print $get_code ?>" 
  1030. Content length: "<?php print $content_length ?>" 
  1031.  
  1032. <?php 
  1033. ?></pre> 
  1034. </div> 
  1035. <?php 
  1036.  
  1037. /** 
  1038. * Get stats from WordPress.com 
  1039. * 
  1040. * @param string $table The stats which you want to retrieve: postviews, or searchterms 
  1041. * @param array $args { 
  1042. * An associative array of arguments. 
  1043. * 
  1044. * @type bool $end The last day of the desired time frame. Format is 'Y-m-d' (e.g. 2007-05-01) 
  1045. * and default timezone is UTC date. Default value is Now. 
  1046. * @type string $days The length of the desired time frame. Default is 30. Maximum 90 days. 
  1047. * @type int $limit The maximum number of records to return. Default is 10. Maximum 100. 
  1048. * @type int $post_id The ID of the post to retrieve stats data for 
  1049. * @type string $summarize If present, summarizes all matching records. Default Null. 
  1050. * 
  1051. * } 
  1052. * 
  1053. * @return array { 
  1054. * An array of post view data, each post as an array 
  1055. * 
  1056. * array { 
  1057. * The post view data for a single post 
  1058. * 
  1059. * @type string $post_id The ID of the post 
  1060. * @type string $post_title The title of the post 
  1061. * @type string $post_permalink The permalink for the post 
  1062. * @type string $views The number of views for the post within the $num_days specified 
  1063. * } 
  1064. * } 
  1065. */ 
  1066. function stats_get_csv( $table, $args = null ) { 
  1067. $defaults = array( 'end' => false, 'days' => false, 'limit' => 3, 'post_id' => false, 'summarize' => '' ); 
  1068.  
  1069. $args = wp_parse_args( $args, $defaults ); 
  1070. $args['table'] = $table; 
  1071. $args['blog_id'] = Jetpack_Options::get_option( 'id' ); 
  1072.  
  1073. $stats_csv_url = add_query_arg( $args, 'http://stats.wordpress.com/csv.php' ); 
  1074.  
  1075. $key = md5( $stats_csv_url ); 
  1076.  
  1077. // Get cache 
  1078. $stats_cache = get_option( 'stats_cache' ); 
  1079. if ( !$stats_cache || !is_array( $stats_cache ) ) 
  1080. $stats_cache = array(); 
  1081.  
  1082. // Return or expire this key 
  1083. if ( isset( $stats_cache[$key] ) ) { 
  1084. $time = key( $stats_cache[$key] ); 
  1085. if ( time() - $time < 300 ) 
  1086. return $stats_cache[$key][$time]; 
  1087. unset( $stats_cache[$key] ); 
  1088.  
  1089. $stats_rows = array(); 
  1090. do { 
  1091. if ( !$stats = stats_get_remote_csv( $stats_csv_url ) ) 
  1092. break; 
  1093.  
  1094. $labels = array_shift( $stats ); 
  1095.  
  1096. if ( 0 === stripos( $labels[0], 'error' ) ) 
  1097. break; 
  1098.  
  1099. $stats_rows = array(); 
  1100. for ( $s = 0; isset( $stats[$s] ); $s++ ) { 
  1101. $row = array(); 
  1102. foreach ( $labels as $col => $label ) 
  1103. $row[$label] = $stats[$s][$col]; 
  1104. $stats_rows[] = $row; 
  1105. } while( 0 ); 
  1106.  
  1107. // Expire old keys 
  1108. foreach ( $stats_cache as $k => $cache ) 
  1109. if ( !is_array( $cache ) || 300 < time() - key($cache) ) 
  1110. unset( $stats_cache[$k] ); 
  1111.  
  1112. // Set cache 
  1113. $stats_cache[$key] = array( time() => $stats_rows ); 
  1114. update_option( 'stats_cache', $stats_cache ); 
  1115.  
  1116. return $stats_rows; 
  1117.  
  1118. function stats_get_remote_csv( $url ) { 
  1119. $method = 'GET'; 
  1120. $timeout = 90; 
  1121. $user_id = JETPACK_MASTER_USER; 
  1122.  
  1123. $get = Jetpack_Client::remote_request( compact( 'url', 'method', 'timeout', 'user_id' ) ); 
  1124. $get_code = wp_remote_retrieve_response_code( $get ); 
  1125. if ( is_wp_error( $get ) || ( 2 != intval( $get_code / 100 ) && 304 != $get_code ) || empty( $get['body'] ) ) { 
  1126. return array(); // @todo: return an error? 
  1127. } else { 
  1128. return stats_str_getcsv( $get['body'] ); 
  1129.  
  1130. // rather than parsing the csv and its special cases, we create a new file and do fgetcsv on it. 
  1131. function stats_str_getcsv( $csv ) { 
  1132. if ( function_exists( 'str_getcsv' ) ) { 
  1133. $lines = str_getcsv( $csv, "\n" ); 
  1134. return array_map( 'str_getcsv', $lines ); 
  1135. if ( !$temp = tmpfile() ) // tmpfile() automatically unlinks 
  1136. return false; 
  1137.  
  1138. $data = array(); 
  1139.  
  1140. fwrite( $temp, $csv, strlen( $csv ) ); 
  1141. fseek( $temp, 0 ); 
  1142. while ( false !== $row = fgetcsv( $temp, 2000 ) ) 
  1143. $data[] = $row; 
  1144. fclose( $temp ); 
  1145.  
  1146. return $data; 
  1147.  
  1148. /** 
  1149. * Abstract out building the rest api stats path. 
  1150. * 
  1151. * @param string $resource 
  1152. * @return string 
  1153. */ 
  1154. function jetpack_stats_api_path( $resource = '' ) { 
  1155. $resource = ltrim( $resource, '/' ); 
  1156. return sprintf( '/sites/%d/stats/%s', stats_get_option( 'blog_id' ), $resource ); 
  1157.  
  1158. /** 
  1159. * Fetches stats data from the REST API. Caches locally for 5 minutes. 
  1160. * 
  1161. * @link: https://developer.wordpress.com/docs/api/1.1/get/sites/%24site/stats/ 
  1162. * 
  1163. * @param array|string $args The args that are passed to the endpoint 
  1164. * @param string $resource Optional sub-endpoint following /stats/ 
  1165. * @return array|WP_Error 
  1166. */ 
  1167. function stats_get_from_restapi( $args = array(), $resource = '' ) { 
  1168. $endpoint = jetpack_stats_api_path( $resource ); 
  1169. $api_version = '1.1'; 
  1170. $args = wp_parse_args( $args, array() ); 
  1171. $cache_key = md5( implode( '|', array( $endpoint, $api_version, serialize( $args ) ) ) ); 
  1172.  
  1173. // Get cache 
  1174. $stats_cache = Jetpack_Options::get_option( 'restapi_stats_cache', array() ); 
  1175. if ( ! is_array( $stats_cache ) ) { 
  1176. $stats_cache = array(); 
  1177.  
  1178. // Return or expire this key 
  1179. if ( isset( $stats_cache[ $cache_key ] ) ) { 
  1180. $time = key( $stats_cache[ $cache_key ] ); 
  1181. if ( time() - $time < ( 5 * MINUTE_IN_SECONDS ) ) { 
  1182. $cached_stats = $stats_cache[ $cache_key ][ $time ]; 
  1183. $cached_stats = (object) array_merge( array( 'cached_at' => $time ), (array) $cached_stats ); 
  1184. return $cached_stats; 
  1185. unset( $stats_cache[ $cache_key ] ); 
  1186.  
  1187. // Do the dirty work. 
  1188. $response = Jetpack_Client::wpcom_json_api_request_as_blog( $endpoint, $api_version, $args ); 
  1189. if ( 200 !== wp_remote_retrieve_response_code( $response ) ) { 
  1190. // If bad, just return it, don't cache. 
  1191. return $response; 
  1192.  
  1193. $data = json_decode( wp_remote_retrieve_body( $response ) ); 
  1194.  
  1195. // Expire old keys 
  1196. foreach ( $stats_cache as $k => $cache ) { 
  1197. if ( ! is_array( $cache ) || ( 5 * MINUTE_IN_SECONDS ) < time() - key( $cache ) ) { 
  1198. unset( $stats_cache[ $k ] ); 
  1199.  
  1200. // Set cache 
  1201. $stats_cache[ $cache_key ] = array( 
  1202. time() => $data,  
  1203. ); 
  1204. Jetpack_Options::update_option( 'restapi_stats_cache', $stats_cache, false ); 
  1205.  
  1206. return $data; 
.