diff --git a/CHANGELOG.md b/CHANGELOG.md index b1dc597..9740081 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Cloudflare Changelog ## 0.2.9 - 2019-03-23 +### Added +- Added `craft cloudflare/purge/purge-all` and `craft cloudflare/purge/purge-urls` console commands for clearing zone and individual URL caches. ### Changed - Can now auto-purge zone subdomain URLs, useful for CDN-hosted Assets. ### Fixed diff --git a/src/Cloudflare.php b/src/Cloudflare.php index 3df2940..a274fb5 100644 --- a/src/Cloudflare.php +++ b/src/Cloudflare.php @@ -17,6 +17,7 @@ use workingconcept\cloudflare\widgets\QuickPurge as QuickPurgeWidget; use Craft; +use craft\console\Application as ConsoleApplication; use craft\base\Plugin; use craft\web\UrlManager; use craft\web\twig\variables\CraftVariable; @@ -137,6 +138,11 @@ function(ElementEvent $event) { ); } + if (Craft::$app instanceof ConsoleApplication) + { + $this->controllerNamespace = 'workingconcept\cloudflare\console\controllers'; + } + Craft::info( Craft::t( 'cloudflare', diff --git a/src/console/controllers/PurgeController.php b/src/console/controllers/PurgeController.php new file mode 100644 index 0000000..e842bc6 --- /dev/null +++ b/src/console/controllers/PurgeController.php @@ -0,0 +1,96 @@ +stdout("Purging {$urlCount} URL{$plural}..." . PHP_EOL); + + $response = Cloudflare::$plugin->cloudflare->purgeUrls($urls); + + return $this->_handleResult($response); + } + + /** + * Attempt to purge entire zone cache. + * @return int + */ + public function actionPurgeAll(): int + { + $this->stdout('Purging Cloudflare zone...' . PHP_EOL); + + $response = Cloudflare::$plugin->cloudflare->purgeZoneCache(); + + return $this->_handleResult($response); + } + + + // Private Methods + // ========================================================================= + + /** + * Handle Cloudflare's API response for console output. + * + * @param $response + * @return int + */ + private function _handleResult($response): int + { + if ($response === null) + { + $this->stdout('✗ Cloudflare plugin not configured' . PHP_EOL); + return ExitCode::CONFIG; + } + + if (isset($response->success)) + { + if ($response->success) + { + $this->stdout('✓ success' . PHP_EOL); + return ExitCode::OK; + } + + $this->stdout('✗ purge failed' . PHP_EOL); + + if (isset($response->errors)) + { + foreach($response->errors as $error) + { + $this->stdout("- $error->code: $error->message" . PHP_EOL); + } + } + + return ExitCode::UNAVAILABLE; + } + + $this->stdout('✗ purge failed' . PHP_EOL); + return ExitCode::UNAVAILABLE; + } +} \ No newline at end of file diff --git a/src/helpers/UrlHelper.php b/src/helpers/UrlHelper.php new file mode 100644 index 0000000..22a46a7 --- /dev/null +++ b/src/helpers/UrlHelper.php @@ -0,0 +1,119 @@ +getSettings()->zoneName; + $includeZoneCheck = $cfDomainName !== null; + + /** + * First trim leading+trailing whitespace, just in case. + */ + $urls = array_map('trim', $urls); + + /** + * Collect only URLs that have the ability to be cleared. + */ + array_walk($urls, function($url) use ($includeZoneCheck, &$cleanUrls) { + if (self::isPurgeableUrl($url, $includeZoneCheck)) + { + $cleanUrls[] = $url; + } + }); + + return $cleanUrls; + } + + /** + * Make sure the supplied URL is something Cloudflare will be able to purge. + * + * @param string $url URL to be checked. + * @param bool $includeZoneCheck Whether or not to ensure that the URL + * exists on the zone this site is + * configured to use. + * + * @return bool `true` if the URL is worth sending to Cloudflare + */ + public static function isPurgeableUrl($url, $includeZoneCheck): bool + { + $cfDomainName = Cloudflare::$plugin->getSettings()->zoneName; + + /** + * Provided string is a valid URL. + */ + if (filter_var($url, FILTER_VALIDATE_URL) === false) + { + Craft::info( + sprintf('Ignoring invalid URL: %s', $url), + 'cloudflare' + ); + + return false; + } + + /** + * If we've stored the zone name (FQDN) locally, make sure the URL + * uses it since it otherwise won't be cleared. + */ + if ($includeZoneCheck) + { + $urlDomain = self::getBaseDomainFromUrl($url); + + if (strtolower($urlDomain) !== strtolower($cfDomainName)) + { + Craft::info( + sprintf('Ignoring URL outside zone: %s', $url), + 'cloudflare' + ); + + return false; // base domain doesn't match Cloudflare zone + } + } + + return true; + } + + /** + * Gets the domain name and TLD only (no subdomains or query parameters) + * from the given URL. + * + * @param string $url + * @return bool|string `false` if the URL's host can't be parsed + */ + public static function getBaseDomainFromUrl($url) + { + $host = parse_url($url, PHP_URL_HOST); + + $parts = explode('.', $host); + $numParts = count($parts); + + if ($numParts < 2) + { + return false; + } + + // hostname . tld + return "{$parts[$numParts-2]}.{$parts[$numParts-1]}"; + } + +} \ No newline at end of file diff --git a/src/services/CloudflareService.php b/src/services/CloudflareService.php index e6ea3b7..4d640e9 100644 --- a/src/services/CloudflareService.php +++ b/src/services/CloudflareService.php @@ -19,6 +19,7 @@ use craft\console\Application as ConsoleApplication; use GuzzleHttp\Client; use stdClass; +use workingconcept\cloudflare\helpers\UrlHelper; /** * @author Working Concept @@ -246,7 +247,7 @@ public function purgeUrls(array $urls = []) return null; } - $urls = $this->_prepUrls($urls); + $urls = UrlHelper::prepUrls($urls); // don't do anything if URLs are missing if (count($urls) === 0) @@ -318,76 +319,6 @@ public function getApiBaseUrl(): string // Private Methods // ========================================================================= - /** - * Only return URLs that can be sent to Cloudflare. - * - * @param array $urls Array of URL strings to be cleared. - * @return array Validated, trimmed values only. - */ - private function _prepUrls($urls = []): array - { - $cleanUrls = []; // to be populated - $cfDomainName = Cloudflare::$plugin->getSettings()->zoneName; - $includeZoneCheck = $cfDomainName !== null; - - /** - * First trim leading+trailing whitespace, just in case. - */ - $urls = array_map('trim', $urls); - - /** - * Collect only URLs that have the ability to be cleared. - */ - array_walk($urls, function($url) use ($includeZoneCheck) { - if ($this->_isPurgeableUrl($url, $includeZoneCheck)) - { - $cleanUrls[] = $url; - } - }); - - return $cleanUrls; - } - - /** - * Make sure the supplied URL is something Cloudflare will be able to purge. - * - * @param string $url URL to be checked. - * @param bool $includeZoneCheck Whether or not to ensure that the URL - * exists on the zone this site is - * configured to use. - * - * @return bool `true` if the URL is worth sending to Cloudflare - */ - private function _isPurgeableUrl($url, $includeZoneCheck): bool - { - $cfDomainName = Cloudflare::$plugin->getSettings()->zoneName; - - /** - * Provided string is a valid URL. - */ - if (filter_var($url, FILTER_VALIDATE_URL) === false) - { - return false; - } - - /** - * If we've stored the zone name (FQDN) locally, make sure the URL - * uses it since it otherwise won't be cleared. - */ - if ($includeZoneCheck) - { - $urlParts = parse_url($url); - $urlDomain = $urlParts['domain']; // base domain only, without subdomains - - if (stripos($urlDomain, $cfDomainName) === false) - { - return false; // base domain doesn't match Cloudflare zone - } - } - - return true; - } - /** * Fetch zones via API, which returns paginated results. *