Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add console utility, use UrlHelper.
  • Loading branch information
mattstein committed Mar 23, 2019
1 parent 6e2d33a commit a34b561
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 71 deletions.
2 changes: 2 additions & 0 deletions 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
Expand Down
6 changes: 6 additions & 0 deletions src/Cloudflare.php
Expand Up @@ -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;
Expand Down Expand Up @@ -137,6 +138,11 @@ function(ElementEvent $event) {
);
}

if (Craft::$app instanceof ConsoleApplication)
{
$this->controllerNamespace = 'workingconcept\cloudflare\console\controllers';
}

Craft::info(
Craft::t(
'cloudflare',
Expand Down
96 changes: 96 additions & 0 deletions src/console/controllers/PurgeController.php
@@ -0,0 +1,96 @@
<?php
/**
* Cloudflare plugin for Craft CMS 3.x
*
* Purge Cloudflare caches from Craft.
*
* @link https://workingconcept.com
* @copyright Copyright (c) 2019 Working Concept
*/

namespace workingconcept\cloudflare\console\controllers;

use workingconcept\cloudflare\Cloudflare;
use yii\console\Controller;
use yii\console\ExitCode;

class PurgeController extends Controller
{
// Public Methods
// =========================================================================

/**
* Attempt to purge specific URLs.
* https://www.yiiframework.com/doc/guide/2.0/en/tutorial-console#arguments
*
* @param array $urls
* @return int
*/
public function actionPurgeUrls(array $urls): int
{
$urlCount = count($urls);
$plural = $urlCount == 1 ? '' : 's';

$this->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;
}
}
119 changes: 119 additions & 0 deletions src/helpers/UrlHelper.php
@@ -0,0 +1,119 @@
<?php
/**
* Cloudflare plugin for Craft CMS 3.x
*
* @link https://workingconcept.com
* @copyright Copyright (c) 2019 Working Concept Inc.
*/

namespace workingconcept\cloudflare\helpers;

use workingconcept\cloudflare\Cloudflare;
use Craft;

class UrlHelper
{

/**
* 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.
*/
public static 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, &$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]}";
}

}
73 changes: 2 additions & 71 deletions src/services/CloudflareService.php
Expand Up @@ -19,6 +19,7 @@
use craft\console\Application as ConsoleApplication;
use GuzzleHttp\Client;
use stdClass;
use workingconcept\cloudflare\helpers\UrlHelper;

/**
* @author Working Concept
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
*
Expand Down

0 comments on commit a34b561

Please sign in to comment.