diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d24543 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..192b1c3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +DON'T BE A DICK PUBLIC LICENSE + +Version 1, December 2009 + +Copyright (C) 2009 Philip Sturgeon email@philsturgeon.co.uk + +Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. + +DON'T BE A DICK PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +Do whatever you like with the original work, just don't be a dick. + +Being a dick includes - but is not limited to - the following instances: + +1a. Outright copyright infringement - Don't just copy this and change the name. +1b. Selling the unmodified original with no work done what-so-ever, that's REALLY being a dick. +1c. Modifying the original work to contain hidden harmful content. That would make you a PROPER dick. + +If you become rich through modifications, related works/services, or supporting the original work, share the love. Only a dick would make loads off this work and not buy the original works creator(s) a pint. + +Code is provided with no warranty. Using somebody else's code and bitching when it goes wrong makes you a DONKEY dick. Fix the problem yourself. A non-dick would submit the fix back. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..409316a --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +Composer Library Template +========================= + +If you are trying to create a new PHP Composer library, whether it will be going to submitted to packagist.org or just in your Github account, this template of files will surely help you make the process a lot easier and faster. + +Features +-------- + +* PSR-4 autoloading compliant structure +* Unit-Testing with PHPUnit +* Comprehensive Guides and tutorial +* Easy to use to any framework or even a plain php file + + +I encourage that you put more information on this readme file instead of leaving it as is. See [http://www.darwinbiler.com/designing-and-making-the-readme-file-for-your-github-repository/](How to make a README file) for more info. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4d21c76 --- /dev/null +++ b/composer.json @@ -0,0 +1,24 @@ +{ + "name": "lvengine/analytics", + "description": "Fetch data from GA", + "license": "proprietary", + "authors": [ + { + "name": "João Serpa | LVEngine", + "email": "joaoserpa@lvengine.com" + } + ], + "type": "project", + "require": { + "php": ">=5.4", + "google/apiclient": "^2.0" + }, + "require-dev": { + "phpunit/phpunit": "5.2.*" + }, + "autoload": { + "psr-4": { + "Analytics\\": "src/Analytics/" + } + } +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..8d5857f --- /dev/null +++ b/index.php @@ -0,0 +1,17 @@ +setViewId("137104990"); +$audience->setDate("2017-08-01", "2017-08-07"); + +echo "
";
+print_r($audience->internalSearches());
+echo "
"; diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..4bfdb04 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,12 @@ + + + + tests + + + \ No newline at end of file diff --git a/src/Analytics/aquisition.php b/src/Analytics/aquisition.php new file mode 100644 index 0000000..9f538cc --- /dev/null +++ b/src/Analytics/aquisition.php @@ -0,0 +1,56 @@ +metricsByDimensions( + array( + 'metrics' => array('sessions'), + 'dimensions' => array('channelGrouping') + ) + ); + } + public function sessionsBySourcesMediums() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('sessions'), + 'dimensions' => array('sourceMedium') + ) + ); + } + public function campaigns() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('users', 'newUsers', 'sessions', 'bounceRate', 'avgSessionDuration'), + 'dimensions' => array('campaign'), + "sorts" => array( + array( + 'fieldName' => 'ga:users', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ) + ), + 'page_size' => 21 + ) + ); + } + // ──────────────────────────────────────────────────────────────────────────────── + +} \ No newline at end of file diff --git a/src/Analytics/audience.php b/src/Analytics/audience.php new file mode 100644 index 0000000..4444ae5 --- /dev/null +++ b/src/Analytics/audience.php @@ -0,0 +1,65 @@ +metricsByDimensions( + array( + 'metrics' => array('sessions'), + 'dimensions' => array('userGender') + ) + ); + } + public function sessionsByAge() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('sessions'), + 'dimensions' => array('userAgeBracket') + ) + ); + } + public function sessionsByCountry() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('sessions'), + 'dimensions' => array('country') + ) + ); + } + public function sessionsAndUsers() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('sessions', 'users'), + 'dimensions' => array('date') + ) + ); + } + public function bounceRate() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('bounceRate'), + ) + ); + } + // ──────────────────────────────────────────────────────────────────────────────── + +} diff --git a/src/Analytics/behavior.php b/src/Analytics/behavior.php new file mode 100644 index 0000000..cfd773e --- /dev/null +++ b/src/Analytics/behavior.php @@ -0,0 +1,72 @@ +metricsByDimensions( + array( + 'metrics' => array('searchUniques'), + 'dimensions' => array('searchKeyword'), + 'sorts' => array( + array( + 'fieldName' => 'ga:searchUniques', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ) + ), + "page_size" => 25 + ) + ); + } + public function internalSearchesWithoutResults() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('totalEvents', 'uniqueEvents'), + 'dimensions' => array('eventLabel'), + 'sorts' => array( + array( + 'fieldName' => 'ga:totalEvents', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ) + ), + 'page_size' => 25, + 'filters' => 'ga:eventAction==Sem Resultados' + ) + ); + } + public function eCommerceEvents() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('totalEvents', 'uniqueEvents'), + 'dimensions' => array('eventAction'), + 'sorts' => array( + array( + 'fieldName' => 'ga:totalEvents', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ) + ), + 'page_size' => 25, + 'filters' => 'ga:eventCategory==eCommerce,ga:eventAction!=Product Impression' + ) + ); + } + // ──────────────────────────────────────────────────────────────────────────────── +} \ No newline at end of file diff --git a/src/Analytics/conversion.php b/src/Analytics/conversion.php new file mode 100644 index 0000000..4cc1291 --- /dev/null +++ b/src/Analytics/conversion.php @@ -0,0 +1,121 @@ +metricsByDimensions( + array( + 'metrics' => array('totalValue'), + 'dimensions' => array('date') + ) + ); + } + public function refundByDay() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('refundAmount'), + 'dimensions' => array('date') + ) + ); + } + public function campaignsRevenue() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('transactionRevenue'), + 'dimensions' => array('campaign'), + 'sorts' => array( + array( + 'fieldName' => 'ga:transactionRevenue', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ) + ) + ) + ); + } + public function topProducts() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('itemRevenue','itemQuantity'), + 'dimensions' => array('productName'), + 'sorts' => array( + array( + 'fieldName' => 'ga:itemRevenue', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ), + array( + 'fieldName' => 'ga:itemQuantity', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ) + ), + "filters" => "ga:itemRevenue!=0" + ) + ); + } + public function topCategories() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('itemRevenue','itemQuantity'), + 'dimensions' => array('productCategoryHierarchy'), + 'sorts' => array( + array( + 'fieldName' => 'ga:itemRevenue', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ), + array( + 'fieldName' => 'ga:itemQuantity', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ) + ), + "filters" => "ga:itemRevenue!=0" + ) + ); + } + public function topBrands() + { + return $this->metricsByDimensions( + array( + 'metrics' => array('itemRevenue','itemQuantity'), + 'dimensions' => array('productBrand'), + 'sorts' => array( + array( + 'fieldName' => 'ga:itemRevenue', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ), + array( + 'fieldName' => 'ga:itemQuantity', + 'orderType' => 'VALUE', + 'sortOrder' => 'DESCENDING' + ) + ), + "filters" => "ga:itemRevenue!=0" + ) + ); + } + // ──────────────────────────────────────────────────────────────────────────────── + +} \ No newline at end of file diff --git a/src/Analytics/credentials.json b/src/Analytics/credentials.json new file mode 100644 index 0000000..cbad99f --- /dev/null +++ b/src/Analytics/credentials.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "e-stat-174216", + "private_key_id": "2143cbb1525bc7d9b788474cfec5c116f21dd95c", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCIilZPtPcvaIwu\nGTcbvbxEPZgt9vJJqXIYR0tihLfKzd7vmwHvBh8RRRRltU4I0si159yy2PCZbXRy\nb6l4zZnyZ35D3X5r5T6YV3K/hQQxGby8VmnSJP2Sz/JkcOpZMp59V+H7QaGuqyGm\nCxEZWtU5oJ8Gx66ExpokRAgHxioIcggOjSgOUCt1P0x9NZo9Xs/TNQseWa+4gqpA\nWRuDjUwTzTcuj+OTetAlWR7s74TRKBA3CX9dmqu3TSSDDCSarjBshWxhelz3UGYj\n52xcvNux8ZbB8fYnZ2FBFOqMV3D0nc3uVWVvLM7YNL0pS9bA6Wd413xQuHFz3UVQ\nQYhLxYMXAgMBAAECggEAAdOpVEytXUwun/PvaabFYjuuvGKy8CsHtJfI7usBUyx4\n9fINeby2ZV1BaMZJt9LWI5+UCNff3uBOMApwFTEvBPAUijo9lZpOFuyzQ35h8CQ8\n2F+RmoxV0NqJDttMj1/0Kzol8wjNmHrVVZWNUi5bGaJyrj0KsoppB+DZzfzSlXqo\nFxPU+9IovoqUqVM3vCdrOXX36fkDFtGcmohRgKOzk4u/ARxVudz7QgbgU9gsd8hI\nAb+AYD9qt7qnyazVs29X94my9SlVtQK/s0eNkvwrfWJUkyeE1eWcCZLlCSdPOBUP\nGv1qt2ArBwHYxldkOKzh0CJoteUbZIvWS2zBaXHlJQKBgQC9qOn6zxWE0EhZEzhq\nZpf2yY9qutj4LzS10GsaojIqC9jnnyGZIO14o/pAGkGNeu4egrqncxNnlIW0Kpmj\nN/8UzGw13rtW3+90ZgEawBVx+pibf19gbwgk3TdQVfwFCxr3HaRBHHwX7rQgOsok\n7EFu7hOiJHERKWI7bQlV23FAnQKBgQC4TNf0dwAYD5S+mMlYs77ORTL4uUNjKSR4\ndU4YvGmFnUt+qACcPFVADH+1sIUZEiz2/+xRyQN02bqWcnHPy5T1Hw1wQxxkV5L0\nKqEeUC/D9vMyZW82RiJuHZ7ZBKv9ril+SdlGWSWsNFRY8oum0GR7oIxs17Zh/3op\nGXXyDUPiQwKBgB5wO40LKzLzkojpMsawzHbJBoFkl2nNebIsTuQpX8+rsxYJTgUb\nacFQ39rl29tu/URcSsSRDW40QfkWVS4C0Kdv33YN1xcsPWv66vZ2GXr/cvqRyKbb\nav0vm68C/b15eMxsL25bufbFUpdRmBuw5xd8kh4VpyfP8noDF9p1q4lpAoGAJLrD\nyyewMBti1H1Um0XvP+KQnvslD+0SJKOUNd/O098ePZaz0G9BuisDhK0ySWXS6kLk\n0QPTmYUO547VWclD3XobzoTBcsn1Mo4QYB8w9cgQfbmzaUie8f0bPDrvH/aGtHF5\nSMjZdjFTogpshIlBjVXYxpRS98LXkLtPQzcbkUsCgYAc/945De1IJeAzvctHVRPM\nX+6XolGpj3ocJrE9lLazLr1bGD7H5PN45lCkha/srZHoeJt1GpOSiTq+3IXpnqTH\n0HRCxK06VhUhpMo+tWdg4qyfdzKgAU1ng0SxmypFDks6vnfgfP1XXGQcI2BmPOmI\nBfCDPcmlV3OamW4AMlvOqA==\n-----END PRIVATE KEY-----\n", + "client_email": "e-stat@e-stat-174216.iam.gserviceaccount.com", + "client_id": "103223646722037248635", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/e-stat%40e-stat-174216.iam.gserviceaccount.com" +} diff --git a/src/Analytics/ga.php b/src/Analytics/ga.php new file mode 100644 index 0000000..cbbebac --- /dev/null +++ b/src/Analytics/ga.php @@ -0,0 +1,489 @@ +config['app_name'] = "LVEngine - Google Analytics"; + + /** + * Config Google_Client + */ + $this->googleClientConfig(); + } + + + // + // ─── CONFIGURATION Methods ────────────────────────────────────────────────────────────── + // + /** + * Method to set default Google Configuration. + * + * @return void + */ + private function googleClientConfig() + { + /** + * Qual o melhor local para o ficheiro? + */ + $KEY_FILE_LOCATION = __DIR__ . '/credentials.json'; + $this->client = new Google_Client(); + $this->client->setApplicationName($this->config['app_name']); + $this->client->setAuthConfig($KEY_FILE_LOCATION); + //$this->client->useApplicationDefaultCredentials(); + $this->client->addScope("https://www.googleapis.com/auth/analytics.readonly"); + $this->analytics = new Google_Service_AnalyticsReporting($this->client); + } + + /** + * Set the View ID. + * + * @param [string] $view_id + * @return void + */ + public function setViewId( $view_id ) + { + $this->config['view_id'] = $view_id; + } + // ──────────────────────────────────────────────────────────────────────────────── + + + // + // ─── METHODS TO HANDLE THE QUERIES ────────────────────────────────────────────── + // + + // + // ─── PRIVATE METHODS ───────────────────────────────────────────── + // + /** + * Validate the Request by checking the required fields. + * + * @return void + */ + private function validateRequest() + { + /** + * View ID. + */ + if ( !isset($this->config['view_id']) || empty($this->config['view_id']) ) + throw new Exception('No VIEW ID set. Use the method ->setViewId([string] $view_id).'); + /** + * Dates. + */ + if (!$this->date_range) + throw new Exception('No date range selected. Use the method ->setDate($begin, $end).'); + else { + if (!$this->date_range->getStartDate()) + throw new Exception('No start date selected. Use the method ->setDate($begin, $end).'); + if (!$this->date_range->getEndDate()) + throw new Exception('No end date selected. Use the method ->setDate($begin, $end).'); + } + } + + /** + * Method to set the sorts. + * + * @param array $sorts + * @return void + */ + private function setSorts( array $sorts ) + { + /** + * Create the sorts array to be inserted in Request. + */ + $this->sorts = array(); + + /** + * Iterate all sorts and add to array. + */ + foreach ($sorts as $sort) { + $new_sort = new Google_Service_AnalyticsReporting_OrderBy(); + $new_sort->setFieldName( $sort['fieldName'] ); + $new_sort->setOrderType( $sort['orderType'] ); + $new_sort->setSortOrder( $sort['sortOrder'] ); + $this->sorts[] = $new_sort; + } + } + + /** + * Method to limit the returned results. + * + * @param [int] $limit + * @return void + */ + private function setPageSize($limit) + { + $this->page_size = $limit; + } + + /** + * Methdo to set filters. + * + * @param [string] $filters + * @return void + */ + private function setFilters($filters) + { + $this->filters = $filters; + } + + /** + * Method to set metrics for Request. + * + * @param array $metrics + * @return void + */ + private function setMetrics( array $metrics ) + { + /** + * Create the metrics array to be inserted in Request. + */ + $this->metrics = array(); + + /** + * Iterate all metrics passed and add to array. + */ + foreach ($metrics as $metric) { + $new_metric = new Google_Service_AnalyticsReporting_Metric(); + $new_metric->setExpression('ga:' . $metric); + $this->metrics[] = $new_metric; + } + } + + /** + * Method to set dimensions for Request. + * + * @param array $dimensions + * @return void + */ + private function setDimensions( array $dimensions ) + { + /** + * Create the dimensions array to be inserted in Request. + */ + $this->dimensions = array(); + + /** + * Iterate all dimensions and add to array. + */ + foreach ($dimensions as $dimension) { + $new_dimension = new Google_Service_AnalyticsReporting_Dimension(); + $new_dimension->setName('ga:' . $dimension); + $this->dimensions[] = $new_dimension; + } + } + + /** + * Method to set and config the Request. + * + * @return void + */ + private function setRequest() + { + // Create the ReportRequest object. + $this->request = new Google_Service_AnalyticsReporting_ReportRequest(); + $this->request->setViewId( $this->config['view_id'] ); + $this->request->setDateRanges( $this->date_range ); + $this->request->setMetrics( $this->metrics ); + $this->request->setDimensions( $this->dimensions ); + $this->request->setOrderBys( $this->sorts ); + $this->request->setPageSize( $this->page_size ); + $this->request->setFiltersExpression( $this->filters ); + } + + /** + * Method to set and config report. + * + * @return void + */ + private function setReport() + { + /** + * Sets and configs the Request. + */ + $this->setRequest(); + + /** + * Create and set Report. + */ + $this->report = new Google_Service_AnalyticsReporting_GetReportsRequest(); + $this->report->setReportRequests( array( $this->request ) ); + + $report = $this->analytics->reports->batchGet( $this->report ); + return $this->getData( $report ); + } + // ───────────────────────────────────────────────────────────────── + + // + // ─── PUBLIC METHODS ────────────────────────────────────────────── + // + /** + * Method to set the date range for report. + * + * @param [date] $begin + * @param [date] $end + * @return void + */ + public function setDate( $begin, $end ) + { + /** + * Create date object. + */ + $this->date_range = new Google_Service_AnalyticsReporting_DateRange(); + /** + * Set the date ranges. + */ + $this->date_range->setStartDate( $begin ); + $this->date_range->setEndDate( $end ); + } + // ───────────────────────────────────────────────────────────────── + + // ──────────────────────────────────────────────────────────────────────────────── + + + + // + // ─── METHODS TO HANDLE RESULTS ────────────────────────────────────────────────── + // + /** + * Method to extract Dimensions from Report. + * + * @param [Report] $report + * @return void + */ + private function getDimensions( $report ) + { + /** + * Prevent dimensions error. + */ + $dimensions = $report->getColumnHeader()->getDimensions(); + if ( !$dimensions ) return null; + + /** + * Only executes if there are dimensions. + */ + $ret_dimensions = array(); + foreach ($dimensions as $dimension) { + $ret_dimensions[] = explode("ga:", $dimension)[1]; + } + return $ret_dimensions; + } + + /** + * Method to extract Metrics Headers from Report. + * + * @param [Report] $reports + * @return void + */ + private function getMetrics( $report ) + { + $metrics = array(); + foreach ($report->getColumnHeader()->getMetricHeader()->getMetricHeaderEntries() as $metric) { + $metrics[] = explode('ga:', $metric->getName())[1]; + } + return $metrics; + } + + private function getData( $reports ) + { + $data = array( + "rows" => array() + ); + foreach ($reports as $report) { + /** + * Get headers. + */ + $dimensionHeaders = $this->getDimensions( $report ); + if ( $dimensionHeaders ) // Dimensions are not required. + $data['dimensions'] = $dimensionHeaders; + + $metricHeaders = $this->getMetrics( $report ); // Metrics are required. + + /** + * Get Rows and iterate. + */ + $rows = $report->getData()->getRows(); + foreach ($rows as $row) { + + /** + * Handle Metrics. + */ + $metrics = $row->getMetrics(); + $ret_metrics = array(); + foreach ($metrics as $metric) { + $i = 0; // Counter. + foreach ($metric->getValues() as $value) { + $ret_metrics[] = array( + "label" => $metricHeaders[$i], + "value" => $value + ); + $i++; // Increase counter. + } + } + + /** + * Only add the dimensions if they exist. + */ + if ( $dimensionHeaders ) { + $data['rows'][] = array( + "dimensions" => $row->getDimensions(), + "metrics" => $ret_metrics + ); + } else { + $data['rows'] = $ret_metrics; + } + } + + } + return $data; + } + // ──────────────────────────────────────────────────────────────────────────────── + + + + // + // ─── API Generic METHODS ──────────────────────────────────────────────────────────────── + // + /** + * Generic Method to build the Requests. + * ---- + * $req = array( + * "metrics" => array(), + * "dimensions" => array(), + * "sorts" => array(), + * "page_size" => int, + * "filters" => string, // comma separated conditions + * ) + * + * @return [array] $data + */ + protected function metricsByDimensions(array $req) + { + try { + /** + * Checks necessary fields. + */ + $this->validateRequest(); + + /** + * Add metrics to request. + */ + $this->setMetrics( $req['metrics'] ); + + /** + * Add dimensions to request. + */ + if ( $req['dimensions'] ) + $this->setDimensions( $req['dimensions'] ); + + /** + * Add Sorts/PageSize/Filters to request. + */ + if ( $req['sorts'] ) + $this->setSorts( $req['sorts'] ); + + if ( $req['page_size'] ) + $this->setPageSize( $req['page_size'] ); + + if ( $req['filters'] ) + $this->setFilters( $req['filters'] ); + + /** + * Make the report. + * --- + * Also formats data. + */ + $data = $this->setReport(); + + /** + * Return $data. + */ + return $data; + + } catch (Exception $e) { + return $e->getMessage(); + } + } + // ───────────────────────────────────────────────────────────────── + + +} + diff --git a/tests/YourClassTest.php b/tests/YourClassTest.php new file mode 100644 index 0000000..cf03578 --- /dev/null +++ b/tests/YourClassTest.php @@ -0,0 +1,39 @@ +assertTrue(is_object($var)); + unset($var); + } + + /** + * Just check if the YourClass has no syntax error + * + * This is just a simple check to make sure your library has no syntax error. This helps you troubleshoot + * any typo before you even use this library in a real project. + * + */ + public function testMethod1(){ + $var = new Buonzz\Template\YourClass; + $this->assertTrue($var->method1("hey") == 'Hello World'); + unset($var); + } + +} \ No newline at end of file