Yoast_GA_Dashboards_Collector

Dashboards collector.

Defined (1)

The class is defined in the following location(s).

/admin/dashboards/class-admin-dashboards-collector.php  
  1. class Yoast_GA_Dashboards_Collector { 
  2.  
  3. /** 
  4. * @var boolean $api API storage 
  5. */ 
  6. public $api; 
  7.  
  8. /** 
  9. * @var array $active_metrics Store the active metrics 
  10. */ 
  11. public $active_metrics; 
  12.  
  13. /** 
  14. * Store the dimensions 
  15. * @var array 
  16. */ 
  17. private $dimensions = array(); 
  18.  
  19. /** 
  20. * Store the valid metrics, which should be 
  21. * @var array 
  22. */ 
  23. private $valid_metrics = array(); 
  24.  
  25. /** 
  26. * @var integer $ga_profile_id Store the GA Profile ID 
  27. */ 
  28. public $ga_profile_id; 
  29.  
  30. /** 
  31. * Construct on the dashboards class for GA 
  32. * @param int $ga_profile_id 
  33. * @param array $active_metrics 
  34. * @param array $valid_metrics 
  35. */ 
  36. public function __construct( $ga_profile_id, $active_metrics, $valid_metrics ) { 
  37. $this->ga_profile_id = $ga_profile_id; 
  38.  
  39. $active_metrics = $this->filter_metrics_to_dimensions( $active_metrics ); 
  40. $this->active_metrics = $active_metrics; 
  41.  
  42. add_filter( 'ga_dashboards_dimensions', array( $this, 'filter_dimensions' ), 10, 1 ); 
  43.  
  44. $this->options = Yoast_GA_Dashboards_Api_Options::get_instance(); 
  45.  
  46. $this->init_shutdown_hook(); 
  47.  
  48. /** 
  49. * Fetch the data from Google Analytics and store it 
  50. */ 
  51. public function aggregate_data() { 
  52. if ( is_numeric( $this->ga_profile_id ) ) { 
  53. // ProfileID is set 
  54.  
  55. /** 
  56. * Implement the metric data first 
  57. */ 
  58. if ( is_array( $this->active_metrics ) && count( $this->active_metrics ) >= 1 ) { 
  59. $this->aggregate_metrics( $this->active_metrics ); 
  60.  
  61. /** 
  62. * Now implement the dimensions that are set 
  63. */ 
  64. if ( is_array( $this->dimensions ) && count( $this->dimensions ) >= 1 ) { 
  65. $this->aggregate_dimensions( $this->dimensions ); 
  66. else { 
  67. // Failure on authenticating, please reauthenticate 
  68.  
  69. /** 
  70. * This hook runs on the shutdown to fetch data from GA 
  71. */ 
  72. private function init_shutdown_hook() { 
  73. $this->api = Yoast_Api_Libs::load_api_libraries( array( 'oauth', 'googleanalytics' ) ); 
  74.  
  75. // Hook the WP cron event 
  76. add_action( 'wp', array( $this, 'setup_wp_cron_aggregate' ) ); 
  77.  
  78. // Hook our function to the WP cron event the fetch data daily 
  79. add_action( 'yst_ga_aggregate_data', array( $this, 'aggregate_data' ) ); 
  80.  
  81. // Check if the WP cron did run on time 
  82. if ( isset( $_GET['page'] ) && ( $_GET['page'] === 'yst_ga_dashboard' || $_GET['page'] === 'yst_ga_settings' ) ) { 
  83. add_action( 'shutdown', array( $this, 'check_api_call_hook' ) ); 
  84.  
  85. /** 
  86. * Check if we scheduled the WP cron event, if not, do so. 
  87. */ 
  88. public function setup_wp_cron_aggregate() { 
  89. if ( ! wp_next_scheduled( 'yst_ga_aggregate_data' ) ) { 
  90. // Set the next event of fetching data 
  91. wp_schedule_event( strtotime( date( 'Y-m-d', strtotime( 'tomorrow' ) ) . ' 00:05:00 ' ), 'daily', 'yst_ga_aggregate_data' ); 
  92.  
  93. /** 
  94. * Check if the WP cron did run yesterday. If not, we need to run it form here 
  95. */ 
  96. public function check_api_call_hook() { 
  97. $last_run = $this->get_last_aggregate_run(); 
  98.  
  99.  
  100. /** 
  101. * Transient doesn't exists, so we need to run the 
  102. * hook (This function runs already on Shutdown so 
  103. * we can call it directly from now on) or the last run has ben more than 24 hours 
  104. */ 
  105. if ( $last_run === false || Yoast_GA_Utils::hours_between( strtotime( $last_run ), time() ) >= 24 ) { 
  106. $this->aggregate_data(); 
  107.  
  108.  
  109. /** 
  110. * Get the datetime when the aggregate data function was succesful 
  111. * @return mixed 
  112. */ 
  113. private function get_last_aggregate_run() { 
  114. return get_option( 'yst_ga_last_wp_run' ); 
  115.  
  116. /** 
  117. * Remove metrics and set them as a dimension if needed 
  118. * @param array $metrics 
  119. * @return mixed 
  120. */ 
  121. private function filter_metrics_to_dimensions( $metrics ) { 
  122. $filter_metrics = $this->get_filter_metrics(); 
  123.  
  124. foreach ( $metrics as $key => $metric_name ) { 
  125. if ( isset( $filter_metrics[ $metric_name ] ) ) { 
  126. // Add and set the dimension 
  127. $dimension = array( $filter_metrics[ $metric_name ] ); 
  128. $this->dimensions = array_merge( $this->dimensions, $dimension ); 
  129.  
  130. // Remove it from the metrics after we've added it into dimensions 
  131. unset( $metrics[ $key ] ); 
  132.  
  133. return $metrics; 
  134.  
  135. /** 
  136. * Get array with metrics which we need to filter as a dimension 
  137. * @return array 
  138. */ 
  139. private function get_filter_metrics() { 
  140. return array( 
  141. 'source' => array( 
  142. 'metric' => 'sessions',  
  143. 'dimension' => 'source',  
  144. 'storage_name' => 'source',  
  145. ),  
  146. 'top_pageviews' => array( 
  147. 'metric' => 'pageViews',  
  148. 'dimension' => 'pagePath',  
  149. 'storage_name' => 'top_pageviews',  
  150. ),  
  151. 'top_countries' => array( 
  152. 'metric' => 'sessions',  
  153. 'dimension' => 'country',  
  154. 'storage_name' => 'top_countries',  
  155. ),  
  156. ); 
  157.  
  158. /** 
  159. * Filter function for adding dimensions 
  160. * @filter ga_dashboards_dimensions 
  161. * @param array $dimensions 
  162. * @return array 
  163. */ 
  164. public function filter_dimensions( $dimensions = array() ) { 
  165. if ( is_array( $dimensions ) && count( $dimensions ) >= 1 ) { 
  166. $dimensions = array_merge( $this->dimensions, $dimensions ); 
  167. $this->dimensions = $dimensions; 
  168.  
  169. return $this->dimensions; 
  170.  
  171. /** 
  172. * Get the start and and date for aggregation functionality 
  173. * @return array 
  174. */ 
  175. private function get_date_range() { 
  176. /** 
  177. * Filter: 'yst-ga-filter-api-end-date' - Allow people to change the end date for the dashboard 
  178. * data. Default: yesterday. 
  179. * @api string Date (Y-m-d) 
  180. */ 
  181. return array( 
  182. 'start' => date( 'Y-m-d', strtotime( '-1 month' ) ),  
  183. 'end' => apply_filters( 'yst-ga-filter-api-end-date', date( 'Y-m-d', strtotime( 'yesterday' ) ) ),  
  184. ); 
  185.  
  186. /** 
  187. * Aggregate metrics from GA. This function should be called in the shutdown function. 
  188. * @param array $metrics 
  189. */ 
  190. private function aggregate_metrics( $metrics ) { 
  191. $dates = $this->get_date_range(); 
  192.  
  193. foreach ( $metrics as $metric ) { 
  194. $this->execute_call( $metric, $dates['start'], $dates['end'] ); 
  195.  
  196. /** 
  197. * Aggregate dimensions from GA. This function should be called in the shutdown function. 
  198. * @param array $dimensions 
  199. */ 
  200. private function aggregate_dimensions( $dimensions ) { 
  201. $dates = $this->get_date_range(); 
  202.  
  203. foreach ( $dimensions as $dimension ) { 
  204. if ( isset( $dimension['metric'] ) ) { 
  205. if ( isset( $dimension['id'] ) ) { 
  206. $this->execute_call( $dimension['metric'], $dates['start'], $dates['end'], 'ga:dimension' . $dimension['id'] ); 
  207. elseif ( isset( $dimension['dimension'] ) ) { 
  208. if ( isset( $dimension['storage_name'] ) ) { 
  209. $this->execute_call( $dimension['metric'], $dates['start'], $dates['end'], 'ga:' . $dimension['dimension'], $dimension['storage_name'] ); 
  210. else { 
  211. $this->execute_call( $dimension['metric'], $dates['start'], $dates['end'], 'ga:' . $dimension['dimension'] ); 
  212.  
  213. /** 
  214. * Execute an API call to Google Analytics and store the data in the dashboards data class 
  215. * @param string $metric 
  216. * @param string $start_date 2014-10-16 
  217. * @param string $end_date 2014-11-20 
  218. * @param string $dimensions ga:date 
  219. * @param string $storage_name auto 
  220. * @return bool 
  221. */ 
  222. private function execute_call( $metric, $start_date, $end_date, $dimensions = 'ga:date', $storage_name = 'auto' ) { 
  223. $dimensions = $this->prepare_dimensions( $dimensions, $metric ); 
  224. $params = $this->build_params_for_call( $start_date, $end_date, $dimensions, $metric ); 
  225. $storage_type = $this->get_storage_type( $dimensions ); 
  226.  
  227. $response = Yoast_Google_Analytics::get_instance()->do_request( 'https://www.googleapis.com/analytics/v3/data/ga?' . $params ); 
  228.  
  229. if ( isset( $response['response']['code'] ) && $response['response']['code'] == 200 ) { 
  230.  
  231. // Delete option api_fail because there it's successful now 
  232. delete_option( 'yst_ga_api_call_fail' ); 
  233.  
  234. // Success, set a transient which stores the latest runtime 
  235. update_option( 'yst_ga_last_wp_run', date( 'Y-m-d' ) ); 
  236.  
  237. $response = Yoast_Googleanalytics_Reporting::get_instance()->parse_response( $response, $storage_type, $start_date, $end_date ); 
  238. else { 
  239. // When response is failing, we should count the number of 
  240. $this->save_api_failure(); 
  241.  
  242. return false; 
  243.  
  244. if ( strpos( 'ga:date', $dimensions ) !== false ) { 
  245. return $this->handle_response( $response, $metric, $dimensions, $start_date, $end_date, 'datelist', $storage_name ); 
  246. else { 
  247. return $this->handle_response( $response, $metric, $dimensions, $start_date, $end_date, 'table', $storage_name ); 
  248.  
  249. /** 
  250. * When the API isn't able to get a successful response (code 200), we have to save that the call has failed 
  251. */ 
  252. private function save_api_failure() { 
  253. update_option( 'yst_ga_api_call_fail', true ); 
  254.  
  255. /** 
  256. * Get the storage type from dimensions 
  257. * @param string $dimensions 
  258. * @return string 
  259. */ 
  260. private function get_storage_type( $dimensions ) { 
  261. if ( strpos( 'ga:date', $dimensions ) !== false ) { 
  262. return 'datelist'; 
  263. else { 
  264. return 'table'; 
  265.  
  266. /** 
  267. * Prepare dimensions before adding them as a parameter in a call 
  268. * @param array $dimensions 
  269. * @param array $metric 
  270. * @return array 
  271. */ 
  272. private function prepare_dimensions( $dimensions, $metric ) { 
  273. $filter_metrics = $this->get_filter_metrics(); 
  274.  
  275. // Check if the dimensions param is an array, if so, glue it with implode to a comma separated string. 
  276. if ( is_array( $dimensions ) ) { 
  277. $dimensions = implode( ', ', $dimensions ); 
  278.  
  279. if ( in_array( $metric, $this->valid_metrics ) ) { 
  280. $dimensions = 'ga:date, ' . $dimensions; 
  281. elseif ( isset( $filter_metrics[ str_replace( 'ga:', '', $dimensions ) ] ) ) { 
  282. // Make sure we don't have a ga:date property here 
  283. $dimensions = str_replace( 'ga:date', '', $dimensions ); 
  284.  
  285. return $dimensions; 
  286.  
  287. /** 
  288. * Build the params for a call to Google Analytics, return them prepared for a http query 
  289. * @param string $start_date 
  290. * @param string $end_date 
  291. * @param string $dimensions 
  292. * @param string $metric 
  293. * @return string 
  294. */ 
  295. private function build_params_for_call( $start_date, $end_date, $dimensions, $metric ) { 
  296. /** 
  297. * Filter: 'yst-ga-filter-api-limit' - Allow people to change the max results value in the API 
  298. * calls. Default value is 1000 results per call. 
  299. * @api int 1000 
  300. */ 
  301. $api_call_limit = apply_filters( 'yst-ga-filter-api-limit', 1000 ); 
  302.  
  303. $params = array( 
  304. 'ids' => 'ga:' . $this->ga_profile_id,  
  305. 'start-date' => $start_date,  
  306. 'end-date' => $end_date,  
  307. 'dimensions' => $dimensions,  
  308. 'metrics' => 'ga:' . $metric,  
  309. 'max-results' => $api_call_limit,  
  310. ); 
  311.  
  312. $params = $this->add_sort_direction( $params, $dimensions, $metric ); 
  313. $params = http_build_query( $params ); 
  314.  
  315. return $params; 
  316.  
  317. /** 
  318. * Add a sort direction if we need to (Especially on dimensions which are 
  319. * listed in $this->get_filter_metrics()) 
  320. * @param array $params 
  321. * @param string $dimensions 
  322. * @param string $metric 
  323. * @return array 
  324. */ 
  325. private function add_sort_direction( $params, $dimensions, $metric ) { 
  326. $filter_dimensions = $this->get_filter_metrics(); 
  327.  
  328. foreach ( $filter_dimensions as $dimension ) { 
  329. if ( str_replace( 'ga:', '', $dimensions ) == $dimension['dimension'] && str_replace( 'ga:', '', $metric ) == $dimension['metric'] ) { 
  330. $params['sort'] = '-ga:' . $dimension['metric']; 
  331.  
  332. return $params; 
  333.  
  334. /** 
  335. * Handle the response from the Google Analytics api. 
  336. * @param array|boolean $response 
  337. * @param string $metric 
  338. * @param array $dimensions 
  339. * @param string $start_date 
  340. * @param string $end_date 
  341. * @param string $store_as 
  342. * @param string $storage_name 
  343. * @return bool 
  344. */ 
  345. private function handle_response( $response, $metric, $dimensions, $start_date, $end_date, $store_as = 'table', $storage_name = 'auto' ) { 
  346. if ( is_array( $response ) ) { 
  347. // Success, store this data 
  348. $filter_metrics = $this->get_filter_metrics(); 
  349. $extracted = str_replace( 'ga:', '', str_replace( 'ga:date, ', '', $dimensions ) ); 
  350.  
  351. if ( isset( $filter_metrics[ $extracted ] ) ) { 
  352. $name = $extracted; 
  353. else { 
  354. $name = $metric; 
  355.  
  356. if ( $dimensions !== 'ga:date' && ! isset( $filter_metrics[ $extracted ] ) ) { 
  357. $name = str_replace( 'ga:date, ', '', $dimensions ); 
  358.  
  359. // Overwrite the name if we have a defined one 
  360. if ( $storage_name != 'auto' ) { 
  361. $name = $storage_name; 
  362.  
  363. return Yoast_GA_Dashboards_Data::set( $name, $response, strtotime( $start_date ), strtotime( $end_date ), $store_as ); 
  364. else { 
  365. // Failure on API call try to log it 
  366. $this->log_error( print_r( $response, true ) ); 
  367.  
  368. return false; 
  369.  
  370. /** 
  371. * Log an error while calling the Google Analytics API 
  372. * @param string $error 
  373. */ 
  374. private function log_error( $error ) { 
  375. if ( true == WP_DEBUG ) { 
  376. if ( function_exists( 'error_log' ) ) { 
  377. error_log( 'Google Analytics by Yoast (Dashboard API): ' . $error ); 
  378.