diff --git a/.gitignore b/.gitignore index ff8bfd7..7fd88fd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ phpunit.xml psalm.xml vendor .php-cs-fixer.cache - +.env \ No newline at end of file diff --git a/README.md b/README.md index c3117dc..a58bce6 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,115 @@ +# Cloudflare Analytics GraphQL API PHP Client + +This package is a simple PHP client for the Cloudflare Analytics GraphQL API. + +> ⚠️ **Note:** This package is not official and is not affiliated with Cloudflare. It is a community-driven package. + +> 🚨 **Note 2:** This package is under development and is not ready for production. + +## Installation + +You can install the package via composer: + +```bash +composer require the3labsteam/php-cloudflare-analytics +``` + +## Configuration Add in your .env file the following variables: ```dotenv CLOUDFLARE_API_TOKEN='your_cloudflare_api_token' +CLOUDFLARE_ZONE_TAG_ID='zoneTag' +``` + +or you can pass the token and zoneTag as parameters in the constructor. + +```php +use The3LabsTeam\PhpCloudflareAnalytics\CloudflareAnalytics; + +$cf = new CloudflareAnalytics( + token: 'your_cloudflare_api_token', zoneTag: 'zoneTag' +); ``` ## Usage -Default use: +You can use the following methods to build your query: ```php -(new \The3LabsTeam\PhpCloudflareAnalytics\CloudflareAnalytics('zoneTag')) +use The3LabsTeam\PhpCloudflareAnalytics\CloudflareAnalytics; + +$cf = new CloudflareAnalytics; + +$results = $cf->select('firewallEventsAdaptive AS firewall') + ->get('firewall.datetime', 'firewall.action'); ``` -next you can use the following methods: -```php -->getLast6Hours($param, $paramType) +The `get` method will return an array with the results. -->getLast24Hours($param, $paramType) +## Available fields -->getLast7Days($param, $paramType) +- `firewallEventsAdaptive` +- `httpRequests1mGroups` +- `httpRequestsAdaptiveGroups` +- `threatsAdaptiveGroups` +- `threatsByCountryAdaptiveGroups` -->getLastMonth($param, $paramType) -``` +## Default fields -and you can pass the following parameters to get the data: +- `datetime`: 1 day +- `take`: 10 +- `orderBy`: `datetime` -- PARAM => `sum` or `uniq` -- PARAM TYPE - - SUM: `request`, `pageViews`, `cachedBytes`, `cachedRequests`, `threats` - - UNIQ: `uniques` +## Demo -### Example +Get last http visits: -Get the total number of requests in the last 6 hours: +```php +$results = $cf->select('httpRequestsAdaptiveGroups AS http') + ->get('http.datetime', 'http.requests'); +``` + +Get last http visits between two dates: ```php -$cloudflare = new \The3LabsTeam\PhpCloudflareAnalytics\CloudflareAnalytics('29djm3nr...'); -$cloudflare->getLast6Hours('sum', 'request'); +$startDate = (new DateTime)->sub(new DateInterval('P1D'))->format('c'); +$endDate = (new DateTime)->format('c'); + +$results = $cf->select('httpRequestsAdaptiveGroups AS http') + ->whereBetween('http', $startDate, $endDate) + ->get('http.datetime', 'http.requests'); ``` -Get the total number of unique visitors in the last 24 hours: +Get last http visits, limit the results to 2: ```php -$cloudflare = new \The3LabsTeam\PhpCloudflareAnalytics\CloudflareAnalytics('29djm3nr...'); -$cloudflare->getLast24Hours('uniq', 'uniques'); +$results = $cf->select('httpRequestsAdaptiveGroups AS http') + ->take('http', 2) + ->get('http.datetime', 'http.requests'); ``` -Get the total number of page views in the last 7 days: +Get last http visits, order by datetime: ```php -$cloudflare = new \The3LabsTeam\PhpCloudflareAnalytics\CloudflareAnalytics('29djm3nr...'); -$cloudflare->geLast7Days('sum', 'pageViews'); +$results = $cf->select('httpRequestsAdaptiveGroups AS http') + ->orderBy('http', 'datetime') + ->get('http.datetime', 'http.requests'); ``` +## Testing + +```bash +composer test +``` + +## License + +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. + +## Credits + +- [The3LabsTeam](https://3labs.it) +// TODO: Add the rest of the file \ No newline at end of file diff --git a/composer.json b/composer.json index 54ff0d8..ef4293f 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ } ], "require": { - "php": "^8.1" + "php": "^8.1", + "vlucas/phpdotenv": "^5.6" }, "require-dev": { "pestphp/pest": "^2.20", diff --git a/src/CloudflareAnalytics.php b/src/CloudflareAnalytics.php index d4e1d27..3a58b41 100755 --- a/src/CloudflareAnalytics.php +++ b/src/CloudflareAnalytics.php @@ -2,24 +2,53 @@ namespace The3LabsTeam\PhpCloudflareAnalytics; +use DateInterval; +use DateTime; +use Dotenv\Dotenv; + class CloudflareAnalytics { - public string $api_token; + private string $apiToken; + + private string $zoneTag; + + protected string $endpoint; + + protected array $selectors = []; - public string $zoneTag; + protected array $filters = []; - public string $endpoint; + protected array $orderBys = []; - public function __construct(string $zoneTag) + protected $takes = []; + + /** + * CloudflareAnalytics constructor. + */ + public function __construct(?string $apiToken = null, ?string $zoneTag = null) { - $this->api_token = env('CLOUDFLARE_API_TOKEN'); - $this->zoneTag = $zoneTag; + $dotenv = Dotenv::createImmutable(__DIR__.'/../'); + $dotenv->load(); + + $this->apiToken = $apiToken ?? $_ENV['CLOUDFLARE_API_TOKEN']; + $this->zoneTag = $zoneTag ?? $_ENV['CLOUDFLARE_ZONE_TAG_ID']; $this->endpoint = 'https://api.cloudflare.com/client/v4/graphql'; } - // ================== UTILITY ================== // + public function select(...$selectors) + { + foreach ($selectors as $selector) { + [$key, $alias] = explode(' AS ', $selector); + $this->selectors[$alias] = $key; + } - protected function graphQLQuery($query) + return $this; + } + + /** + * Query the Cloudflare API + */ + protected function query($query) { $ch = curl_init($this->endpoint); curl_setopt_array($ch, [ @@ -28,7 +57,7 @@ protected function graphQLQuery($query) CURLOPT_POSTFIELDS => json_encode(['query' => $query]), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', - 'Authorization: Bearer '.$this->api_token, + 'Authorization: Bearer '.$this->apiToken, ], ]); @@ -44,155 +73,105 @@ protected function graphQLQuery($query) return $response; } - protected function sumTotal($response, $zonesType, $param, $paramType) + /** + * Get the total views between two dates - Returns the total views + */ + public function whereBetween(string $context, string $startDate, string $endDate) { - $response = $response['data']['viewer']['zones'][0][$zonesType]; + $this->filters[$context] = [ + 'startDate' => $startDate, + 'endDate' => $endDate, + ]; - $total = 0; - foreach ($response as $key => $value) { - $total += $value[$param][$paramType]; - } - - return $total; + return $this; } - // ================== DEFAULT FUNCTIONS ================== // - - //TODO: Merge all the functions in one function with parameters /** - * Get the total views between two dates - Returns the total views - * - * @param $param - 'sum' | 'uniq' - * @param $paramType - sum : 'request', 'pageViews', 'cachedBytes', 'cachedRequests', 'threats' | uniq: 'uniques' - * @return int|mixed + * Set the order by field and direction */ - public function getBetweenDates($startDate, $endDate, $param = 'sum', $paramType = 'pageViews') + public function orderBy(string $context, string $field, string $direction = 'ASC') { - $query = <<zoneTag"}) { - httpRequests1dGroups( - limit: 1000 - filter: { - date_geq: "$startDate" - date_lt: "$endDate" - } - ) { - sum { - requests - pageViews - cachedBytes - cachedRequests - threats - } - uniq { - uniques - } - } - } - } - } - GRAPHQL; + $this->orderBys[$context][] = $field.'_'.$direction; - $response = $this->graphQLQuery($query); + return $this; + } - return $this->sumTotal($response, 'httpRequests1dGroups', $param, $paramType); + /** + * Get a specific number of results + */ + public function take($alias, $limit) + { + $this->takes[$alias] = $limit; + + return $this; } /** - * Get the total views between two dates - Return the total views - * - * @return array + * Get data */ - public function getBetweenHours($sub, $param, $paramType) + public function get(...$fields) { - // Current date/time in ISO 8601 format - $endDate = date('c'); - $startDate = date('c', strtotime($sub)); + $queries = []; + foreach ($this->selectors as $alias => $selector) { + $filter = $this->filters[$alias] ?? []; + $orderBy = $this->orderBys[$alias] ?? ['datetime_DESC']; + $limit = isset($this->takes[$alias]) ? $this->takes[$alias] : 10; - $query = <<zoneTag"}) { - httpRequests1hGroups( - limit: 1000 - filter: { - datetime_geq: "$startDate" + $startDate = $filter['startDate'] ?? (new DateTime)->sub(new DateInterval('P1D'))->format('c'); + $endDate = $filter['endDate'] ?? (new DateTime)->format('c'); + + $fieldsList = implode("\n", array_map(fn ($f) => str_replace("$alias.", '', $f), $fields)); + + $queries[] = <<formatOrderBy($orderBy)} + ] + ) { + $fieldsList } - } - GRAPHQL; - - $response = $this->graphQLQuery($query); + GRAPHQL; + } - return $this->sumTotal($response, 'httpRequests1hGroups', $param, $paramType); - } + $query = <<zoneTag"}) { + {$this->formatQueries($queries)} + } + } + } + GRAPHQL; - // ================== DEFAULT PRESET ================== // + $response = $this->query($query); - /** - * Get the total views last 6 hours - Returns the total views - * - * @return int|mixed - */ - public function getLast6Hours($param, $paramType) - { - return $this->getBetweenHours(sub: '-6 hours', param: $param, paramType: $paramType); + return $response; } - /** - * Get the total views last 24 hours - Returns the total views - * - * @return int|mixed - */ - public function getLast24Hours($param, $paramType) + private function formatOrderBy(array $orderBy) { - return $this->getBetweenHours(sub: '-24 hours', param: $param, paramType: $paramType); + return implode("\n", array_map(fn ($o) => $o, $orderBy)); } - /** - * Get the total views last 7 days - Returns the total views - * - * @return int|mixed - */ - public function getLast7Days($param, $paramType) + private function formatQueries(array $queries) { - // Current date/time in Y-m-d - $startDate = date('Y-m-d', strtotime('-7 days')); - $endDate = date('Y-m-d'); - - return $this->getBetweenDates(startDate: $startDate, endDate: $endDate, param: $param, paramType: $paramType); + return implode("\n", array_map(fn ($q) => $q, $queries)); } - /** - * Get the total views last month - Returns the total views - * - * @return int|mixed - */ - public function getLastMonth($param, $paramType) + private function formatTakes() { - // Current date/time in Y-m-d - $startDate = date('Y-m-d', strtotime('-1 month')); - $endDate = date('Y-m-d'); + $takes = []; + foreach ($this->takes as $alias => $limit) { + if (is_int($limit)) { + $takes[] = "{$alias}: {$limit}"; + } + } - return $this->getBetweenDates(startDate: $startDate, endDate: $endDate, param: $param, paramType: $paramType); + return implode(', ', $takes); } } diff --git a/tests/CloudflareAnalyticsTest.php b/tests/CloudflareAnalyticsTest.php new file mode 100644 index 0000000..4c69692 --- /dev/null +++ b/tests/CloudflareAnalyticsTest.php @@ -0,0 +1,88 @@ +cf = new CloudflareAnalytics; +}); + +it('can get firewall data', function () { + $cf = new CloudflareAnalytics; + + $results = $cf->select('firewallEventsAdaptive AS firewall') + ->get('firewall.datetime', 'firewall.action'); + + $this->assertIsArray($results); + $this->assertGreaterThan(0, $results); +}); + +it('can get firewall between two dates', function () { + $startDate = (new DateTime)->sub(new DateInterval('P1D'))->format('c'); + $endDate = (new DateTime)->format('c'); + + $cf = new CloudflareAnalytics; + + $results = $cf->select('firewallEventsAdaptive AS firewall') + ->whereBetween('firewall', $startDate, $endDate) + ->orderBy('firewall.datetime', 'DESC') + ->take('firewall', 2) + ->get('firewall.datetime', 'firewall.action'); + + $this->assertIsArray($results); + $this->assertGreaterThan(0, $results); +}); + +it('can get firewall data with a specific limit', function () { + $limit = 10; + + $cf = new CloudflareAnalytics; + + $results = $cf->select('firewallEventsAdaptive AS firewall') + ->take('firewall', $limit) + ->get('firewall.datetime', 'firewall.action'); + + $this->assertIsArray($results); + $this->assertGreaterThan(0, $results); +}); + +it('can get firewall data with a specific order', function () { + $cf = new CloudflareAnalytics; + + $results = $cf->select('firewallEventsAdaptive AS firewall') + ->orderBy('firewall.datetime', 'DESC') + ->get('firewall.datetime', 'firewall.action'); + + $this->assertIsArray($results); + $this->assertGreaterThan(0, $results); +}); + +it('can get firewall data with a specific order and limit', function () { + $limit = 10; + + $cf = new CloudflareAnalytics; + + $results = $cf->select('firewallEventsAdaptive AS firewall') + ->orderBy('firewall.datetime', 'DESC') + ->take('firewall', $limit) + ->get('firewall.datetime', 'firewall.action'); + + $this->assertIsArray($results); + $this->assertGreaterThan(0, $results); +}); + +it('can get firewall data with a specific order and limit between two dates', function () { + $startDate = (new DateTime)->sub(new DateInterval('P1D'))->format('c'); + $endDate = (new DateTime)->format('c'); + $limit = 10; + + $cf = new CloudflareAnalytics; + + $results = $cf->select('firewallEventsAdaptive AS firewall') + ->whereBetween('firewall', $startDate, $endDate) + ->orderBy('firewall.datetime', 'DESC') + ->take('firewall', $limit) + ->get('firewall.datetime', 'firewall.action'); + + $this->assertIsArray($results); + $this->assertGreaterThan(0, $results); +}); diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 5d36321..0000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,5 +0,0 @@ -toBeTrue(); -});