diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc56e0..e91e8f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,11 @@ # Cloudflare Changelog +## 1.1.0 - 2022-04-23 +### Changed +- Automatic element-save cache purges are now sent to the queue to speed up element saves. +- Cleaned up translation files and added new keys to German set. + +### Fixed +- Draft entry and category saves are ignored and will not trigger a cache purge. ## 1.0.3.1 - 2022-01-31 ### Fixed diff --git a/composer.json b/composer.json index d653fe3..0c4aa91 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "workingconcept/craft-cloudflare", "description": "Purge Cloudflare caches from Craft.", "type": "craft-plugin", - "version": "1.0.3.1", + "version": "1.1.0", "keywords": [ "craft", "cms", @@ -27,7 +27,10 @@ ], "scripts": { "post-install-cmd": "\\Pdp\\Installer::updateLocalCache", - "post-update-cmd": "\\Pdp\\Installer::updateLocalCache" + "post-update-cmd": "\\Pdp\\Installer::updateLocalCache", + "check-cs": "ecs check --ansi", + "fix-cs": "ecs check --ansi --fix", + "phpstan": "phpstan --memory-limit=1G" }, "require": { "craftcms/cms": "^3.6.0", @@ -35,27 +38,26 @@ }, "require-dev": { "vlucas/phpdotenv": "^3.4", - "roave/security-advisories": "dev-latest", "codeception/codeception": "^4.0.0", "codeception/module-phpbrowser": "^1.0.0", "codeception/module-asserts": "^1.0.0", "codeception/module-rest": "^1.0.0", "codeception/module-datafactory": "^1.0.0", - "codeception/module-yii2": "^1.0.0" + "codeception/module-yii2": "^1.0.0", + "craftcms/ecs": "dev-main", + "craftcms/phpstan": "dev-main" }, - "repositories": [ - { - "type": "composer", - "url": "https://asset-packagist.org" - } - ], "autoload": { "psr-4": { "workingconcept\\cloudflare\\": "src/" } }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "yiisoft/yii2-composer": true, + "craftcms/plugin-installer": true + } }, "extra": { "name": "Cloudflare", diff --git a/ecs.php b/ecs.php new file mode 100644 index 0000000..0ac9ec8 --- /dev/null +++ b/ecs.php @@ -0,0 +1,17 @@ +parameters(); + $parameters->set(Option::PATHS, [ + __DIR__ . '/src', + __FILE__, + ]); + + $containerConfigurator->import(SetList::CRAFT_CMS_4); +}; diff --git a/package-lock.json b/package-lock.json index 1981429..1f364a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "cloudflare-craft-plugin", - "version": "1.0.1", + "version": "1.0.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.0.1", + "version": "1.0.3.1", "license": "MIT", "devDependencies": { "cross-env": "^7.0.2", @@ -1610,9 +1610,9 @@ } }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "dependencies": { "lodash": "^4.17.14" @@ -4421,12 +4421,23 @@ } }, "node_modules/follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", - "dev": true, + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "engines": { "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, "node_modules/for-in": { @@ -6276,9 +6287,9 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "node_modules/minipass": { @@ -9983,9 +9994,9 @@ } }, "node_modules/url-parse": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", - "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "dependencies": { "querystringify": "^2.1.1", @@ -12700,9 +12711,9 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "requires": { "lodash": "^4.17.14" @@ -15112,9 +15123,9 @@ } }, "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", "dev": true }, "for-in": { @@ -16647,9 +16658,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "dev": true }, "minipass": { @@ -19821,9 +19832,9 @@ } }, "url-parse": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz", - "integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "requires": { "querystringify": "^2.1.1", diff --git a/package.json b/package.json index 406415d..c1e18e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cloudflare-craft-plugin", - "version": "1.0.3.1", + "version": "1.1.0", "description": "![Cloudflare](resources/hero.svg)", "main": "index.js", "scripts": { diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..44554e4 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +includes: + - vendor/craftcms/phpstan/phpstan.neon + +parameters: + level: 5 + paths: + - src diff --git a/src/Cloudflare.php b/src/Cloudflare.php index bee8332..1c1df38 100644 --- a/src/Cloudflare.php +++ b/src/Cloudflare.php @@ -10,29 +10,32 @@ namespace workingconcept\cloudflare; -use workingconcept\cloudflare\helpers\ConfigHelper; -use workingconcept\cloudflare\services\Api; -use workingconcept\cloudflare\services\Rules; -use workingconcept\cloudflare\utilities\PurgeUtility; -use workingconcept\cloudflare\variables\CloudflareVariable; -use workingconcept\cloudflare\models\Settings; -use workingconcept\cloudflare\widgets\QuickPurge as QuickPurgeWidget; use Craft; -use craft\console\Application as ConsoleApplication; +use craft\base\ElementInterface; use craft\base\Plugin; -use craft\web\UrlManager; -use craft\web\twig\variables\CraftVariable; -use craft\services\Dashboard; +use craft\console\Application as ConsoleApplication; +use craft\events\ElementEvent; use craft\events\RegisterComponentTypesEvent; use craft\events\RegisterUrlRulesEvent; -use craft\events\ElementEvent; -use craft\services\Elements; -use craft\base\ElementInterface; +use craft\helpers\ElementHelper; +use craft\helpers\Queue; use craft\helpers\UrlHelper; +use craft\services\Dashboard; +use craft\services\Elements; use craft\services\Utilities; +use craft\web\twig\variables\CraftVariable; +use craft\web\UrlManager; +use GuzzleHttp\Exception\GuzzleException; +use workingconcept\cloudflare\helpers\ConfigHelper; +use workingconcept\cloudflare\models\Settings; +use workingconcept\cloudflare\queue\jobs\PurgeCloudflareCache; +use workingconcept\cloudflare\services\Api; +use workingconcept\cloudflare\services\Rules; +use workingconcept\cloudflare\utilities\PurgeUtility; +use workingconcept\cloudflare\variables\CloudflareVariable; +use workingconcept\cloudflare\widgets\QuickPurge as QuickPurgeWidget; use yii\base\Event; use yii\base\Exception; -use GuzzleHttp\Exception\GuzzleException; /** * Class Cloudflare @@ -41,7 +44,7 @@ * @package Cloudflare * @since 1.0.0 * - * @property Api $api + * @property Api $api * @property Rules $rules */ class Cloudflare extends Plugin @@ -86,15 +89,15 @@ public function init(): void parent::init(); $this->setComponents([ - 'api' => Api::class, - 'rules' => Rules::class + 'api' => Api::class, + 'rules' => Rules::class, ]); // register the variable Event::on( CraftVariable::class, CraftVariable::EVENT_INIT, - static function (Event $event) { + static function(Event $event) { /** @var CraftVariable $variable */ $variable = $event->sender; $variable->set('cloudflare', CloudflareVariable::class); @@ -106,7 +109,7 @@ static function (Event $event) { Event::on( Dashboard::class, Dashboard::EVENT_REGISTER_WIDGET_TYPES, - static function (RegisterComponentTypesEvent $event) { + static function(RegisterComponentTypesEvent $event) { $event->types[] = QuickPurgeWidget::class; } ); @@ -126,7 +129,7 @@ function(RegisterComponentTypesEvent $event) { UrlManager::EVENT_REGISTER_CP_URL_RULES, static function(RegisterUrlRulesEvent $event) { $event->rules['cloudflare/rules'] = [ - 'template' => 'cloudflare/rules' + 'template' => 'cloudflare/rules', ]; } ); @@ -134,7 +137,7 @@ static function(RegisterUrlRulesEvent $event) { if ( ConfigHelper::isConfigured() && - ! empty($this->getSettings()->purgeElements) + !empty($this->getSettings()->purgeElements) ) { Event::on( Elements::class, @@ -184,20 +187,20 @@ public function beforeSaveSettings(): bool /** @var Settings $settings */ $settings = $this->getSettings(); - // save the human-friendly zone name if we have one + // Save the human-friendly zone name if we have one if ($zoneInfo = $this->api->getZoneById( ConfigHelper::getParsedSetting('zone') )) { $settings->zoneName = $zoneInfo->name; } - // don’t save stale key credentials + // Don’t save stale key credentials if ($settings->authType === Settings::AUTH_TYPE_TOKEN) { $settings->apiKey = null; $settings->email = null; } - // don’t save stale token + // Don’t save stale token if ($settings->authType === Settings::AUTH_TYPE_KEY) { $settings->apiToken = null; } @@ -221,10 +224,10 @@ protected function settingsHtml(): string return Craft::$app->view->renderTemplate( 'cloudflare/settings', [ - 'settings' => $this->getSettings(), + 'settings' => $this->getSettings(), 'isConfigured' => ConfigHelper::isConfigured(), 'isCraft31' => ConfigHelper::isCraft31(), - 'elementTypes' => $this->_getElementTypeOptions() + 'elementTypes' => $this->_getElementTypeOptions(), ] ); } @@ -262,7 +265,7 @@ private function _getElementTypeOptions(): array private function _isSupportedElementType(string $elementType): bool { $elementType = ConfigHelper::normalizeClassName($elementType); - + return in_array($elementType, self::$supportedElementTypes, true); } @@ -270,20 +273,22 @@ private function _isSupportedElementType(string $elementType): bool * Returns `true` if the provided element type is both supported and * enabled for purging in the plugin’s settings. * - * @param $elementType + * @param string $elementType * * @return bool */ private function _shouldPurgeElementType($elementType): bool { - if ( ! $this->_isSupportedElementType($elementType)) { + if (!$this->_isSupportedElementType($elementType)) { return false; } $elementType = ConfigHelper::normalizeClassName($elementType); - $purgeElements = $this->getSettings()->purgeElements; + /** @var Settings $settings */ + $settings = $this->getSettings(); + $purgeElements = $settings->purgeElements; - if (empty($purgeElements) || ! is_array($purgeElements)) { + if (empty($purgeElements) || !is_array($purgeElements)) { return false; } @@ -298,14 +303,19 @@ private function _shouldPurgeElementType($elementType): bool */ private function _handleElementChange(bool $isNew, $element): void { - // bail if we don’t have an Element or an Element URL to work with + // Bail if we don’t have an Element or an Element URL to work with if ($element === null || $element->getUrl() === null) { return; } + // Bail if this is not published + if (ElementHelper::isDraftOrRevision($element)) { + return; + } + $className = get_class($element); - if (! $isNew && $this->_shouldPurgeElementType($className)) { + if (!$isNew && $this->_shouldPurgeElementType($className)) { $elementUrl = $element->getUrl(); /** @@ -315,9 +325,7 @@ private function _handleElementChange(bool $isNew, $element): void $elementUrl = UrlHelper::siteUrl($elementUrl); } - $this->api->purgeUrls([ - $elementUrl - ]); + Queue::push(new PurgeCloudflareCache(['urls' => [$elementUrl]])); } /** diff --git a/src/console/controllers/PurgeController.php b/src/console/controllers/PurgeController.php index e8d02a3..db329aa 100644 --- a/src/console/controllers/PurgeController.php +++ b/src/console/controllers/PurgeController.php @@ -18,7 +18,6 @@ class PurgeController extends Controller { /** * Attempt to purge specific URLs. - * https://www.yiiframework.com/doc/guide/2.0/en/tutorial-console#arguments * * @param array $urls * @return int @@ -53,7 +52,7 @@ public function actionPurgeAll(): int /** * Handle Cloudflare’s API response for console output. * - * @param $response + * @param mixed $response * @return int */ private function _handleResult($response): int @@ -72,7 +71,7 @@ private function _handleResult($response): int $this->stdout('✗ purge failed' . PHP_EOL); if (isset($response->errors)) { - foreach($response->errors as $error) { + foreach ($response->errors as $error) { $this->stdout( sprintf('- %s: %s', $error->code, $error->message) . PHP_EOL ); diff --git a/src/controllers/DefaultController.php b/src/controllers/DefaultController.php index 65c730e..4b0701f 100644 --- a/src/controllers/DefaultController.php +++ b/src/controllers/DefaultController.php @@ -10,12 +10,12 @@ namespace workingconcept\cloudflare\controllers; -use workingconcept\cloudflare\Cloudflare; use Craft; use craft\web\Controller; -use yii\web\Response; -use yii\web\BadRequestHttpException; use GuzzleHttp\Exception\GuzzleException; +use workingconcept\cloudflare\Cloudflare; +use yii\web\BadRequestHttpException; +use yii\web\Response; /** * @author Working Concept @@ -36,10 +36,10 @@ public function actionVerifyConnection(): Response $wasSuccessful = Cloudflare::getInstance()->api->verifyConnection(); $return = [ - 'success' => $wasSuccessful + 'success' => $wasSuccessful, ]; - if ( ! $wasSuccessful) { + if (!$wasSuccessful) { $return['errors'] = Cloudflare::getInstance()->api->getConnectionErrors(); } @@ -61,11 +61,9 @@ public function actionFetchZones(): Response * Purges URLs passed in the `urls` parameter, whose value should be a string * with each URL on its own line. * - * @return mixed - * @throws craft\errors\MissingComponentException without a valid session. - * @throws BadRequestHttpException|GuzzleException + * @return Response */ - public function actionPurgeUrls() + public function actionPurgeUrls(): Response { $this->requirePostRequest(); $this->requireAcceptsJson(); @@ -75,8 +73,7 @@ public function actionPurgeUrls() $urls = $request->getBodyParam('urls'); - if (empty($urls)) - { + if (empty($urls)) { $session->setError(Craft::t( 'cloudflare', 'Failed to purge empty or invalid URLs.' @@ -97,10 +94,10 @@ public function actionPurgeUrls() /** * Purges entire Cloudflare zone cache. * - * @return mixed + * @return Response * @throws BadRequestHttpException|GuzzleException */ - public function actionPurgeAll() + public function actionPurgeAll(): Response { $this->requirePostRequest(); $this->requireAcceptsJson(); diff --git a/src/controllers/RulesController.php b/src/controllers/RulesController.php index 7b7d9fd..6d49014 100644 --- a/src/controllers/RulesController.php +++ b/src/controllers/RulesController.php @@ -10,12 +10,12 @@ namespace workingconcept\cloudflare\controllers; -use workingconcept\cloudflare\Cloudflare; use Craft; -use craft\web\Controller; +use craft\errors\MissingComponentException; use craft\errors\SiteNotFoundException; use craft\helpers\UrlHelper; -use craft\errors\MissingComponentException; +use craft\web\Controller; +use workingconcept\cloudflare\Cloudflare; class RulesController extends Controller { @@ -38,4 +38,4 @@ public function actionSave() return $this->redirect(UrlHelper::cpUrl('utilities/cloudflare-purge')); } -} \ No newline at end of file +} diff --git a/src/helpers/ConfigHelper.php b/src/helpers/ConfigHelper.php index b80d7f4..1b7b8a6 100644 --- a/src/helpers/ConfigHelper.php +++ b/src/helpers/ConfigHelper.php @@ -8,9 +8,9 @@ namespace workingconcept\cloudflare\helpers; +use Craft; use craft\console\Application as ConsoleApplication; use workingconcept\cloudflare\Cloudflare; -use Craft; use workingconcept\cloudflare\models\Settings; class ConfigHelper @@ -52,9 +52,9 @@ public static function getParsedSetting($key): ?string * Check post params if we’re in the control panel, where we use AJAX * for initially checking new parameters. */ - $usePost = ! $isConsole && + $usePost = !$isConsole && $request->getIsAjax() && - ! empty($request->getParam($key)) && + !empty($request->getParam($key)) && is_string($request->getParam($key)); $settingValue = $usePost ? $request->getParam($key) : @@ -81,7 +81,7 @@ public static function isCraft31(): bool /** * Strips leading slash from namespaced class and returns it. * - * @param $class + * @param string $class * * @return string */ diff --git a/src/helpers/UrlHelper.php b/src/helpers/UrlHelper.php index 3cae093..4ee1295 100644 --- a/src/helpers/UrlHelper.php +++ b/src/helpers/UrlHelper.php @@ -8,9 +8,10 @@ namespace workingconcept\cloudflare\helpers; -use workingconcept\cloudflare\Cloudflare; use Craft; use Pdp; +use workingconcept\cloudflare\Cloudflare; +use workingconcept\cloudflare\models\Settings; class UrlHelper { @@ -22,7 +23,9 @@ class UrlHelper */ public static function prepUrls($urls = []): array { - $cfDomainName = Cloudflare::getInstance()->getSettings()->zoneName; + /** @var Settings $settings */ + $settings = Cloudflare::getInstance()->getSettings(); + $cfDomainName = $settings->zoneName; $includeZoneCheck = $cfDomainName !== null; // trim leading+trailing whitespace @@ -49,8 +52,6 @@ public static function prepUrls($urls = []): array */ public static function isPurgeableUrl(string $url, bool $includeZoneCheck): bool { - $cfDomainName = Cloudflare::getInstance()->getSettings()->zoneName; - /** * Provided string is a valid URL. */ @@ -68,12 +69,15 @@ public static function isPurgeableUrl(string $url, bool $includeZoneCheck): bool * uses it since it otherwise won't be cleared. */ if ($includeZoneCheck) { - if ( ! $urlDomain = self::getBaseDomainFromUrl($url)) { + if (!$urlDomain = self::getBaseDomainFromUrl($url)) { // bail if we couldn't even get a base domain return false; } - if (strtolower($urlDomain) !== strtolower($cfDomainName)) { + /** @var Settings $settings */ + $settings = Cloudflare::getInstance()->getSettings(); + + if (strtolower($urlDomain) !== strtolower($settings->zoneName)) { Craft::info( sprintf('Ignoring URL outside zone: %s', $url), 'cloudflare' diff --git a/src/migrations/Install.php b/src/migrations/Install.php index fd4627b..cd90d2e 100644 --- a/src/migrations/Install.php +++ b/src/migrations/Install.php @@ -12,8 +12,8 @@ use Craft; use craft\db\Migration; -use workingconcept\cloudflare\db\Table; use craft\db\Table as CraftTable; +use workingconcept\cloudflare\db\Table; /** * @author Working Concept @@ -34,8 +34,7 @@ public function safeUp(): bool { $this->driver = Craft::$app->getConfig()->getDb()->driver; - if ($this->createTables()) - { + if ($this->createTables()) { $this->addForeignKeys(); // Refresh the db schema caches Craft::$app->db->schema->refresh(); @@ -62,10 +61,7 @@ protected function createTables(): bool { $tablesCreated = false; - $tableSchema = Craft::$app->db->schema->getTableSchema(Table::RULES); - - if ($tableSchema === null) - { + if (! Craft::$app->db->tableExists(Table::RULES)) { $tablesCreated = true; $this->createTable( Table::RULES, @@ -77,7 +73,7 @@ protected function createTables(): bool 'siteId' => $this->integer()->notNull(), 'trigger' => $this->string(255)->notNull(), 'urlsToClear' => $this->string(255)->notNull(), - 'refresh' => $this->boolean()->defaultValue(false) + 'refresh' => $this->boolean()->defaultValue(false), ] ); } @@ -91,7 +87,7 @@ protected function createTables(): bool protected function addForeignKeys(): void { $this->addForeignKey( - $this->db->getForeignKeyName(Table::RULES, 'siteId'), + $this->db->getForeignKeyName(), Table::RULES, 'siteId', CraftTable::SITES, diff --git a/src/migrations/m200307_232253_purge_element_settings.php b/src/migrations/m200307_232253_purge_element_settings.php index 0aec13b..7c9ee7c 100644 --- a/src/migrations/m200307_232253_purge_element_settings.php +++ b/src/migrations/m200307_232253_purge_element_settings.php @@ -38,21 +38,18 @@ private function _resaveSettings(): void $settings = $projectConfig->get('plugins.cloudflare.settings'); $schemaVersion = $projectConfig->get('plugins.cloudflare.schemaVersion'); - if (empty($settings) || version_compare($schemaVersion, '1.0.1', '>=')) - { + if (empty($settings) || version_compare($schemaVersion, '1.0.1', '>=')) { echo 'No settings to update.'; return; } $purgeElements = []; - if ($settings['purgeEntryUrls']) - { + if ($settings['purgeEntryUrls']) { $purgeElements[] = 'craft\elements\Entry'; } - if ($settings['purgeAssetUrls']) - { + if ($settings['purgeAssetUrls']) { $purgeElements[] = 'craft\elements\Asset'; } diff --git a/src/models/Settings.php b/src/models/Settings.php index 0c41219..ad9793f 100644 --- a/src/models/Settings.php +++ b/src/models/Settings.php @@ -2,8 +2,8 @@ namespace workingconcept\cloudflare\models; -use craft\base\Model; use Craft; +use craft\base\Model; use workingconcept\cloudflare\Cloudflare; class Settings extends Model @@ -25,17 +25,17 @@ class Settings extends Model public $authType = 'key'; /** - * @var string Account-level API key. + * @var string|null Account-level API key. */ public $apiKey = ''; /** - * @var string Primary account email address. Required with $apiKey. + * @var string|null Primary account email address. Required with $apiKey. */ public $email = ''; /** - * @var string App token. (Alternative to $apiKey + $email.) + * @var string|null App token. (Alternative to $apiKey + $email.) */ public $apiToken = ''; @@ -92,10 +92,10 @@ public function rules(): array [['purgeElements'], 'each', 'rule' => ['in', 'range' => Cloudflare::$supportedElementTypes]], [['apiKey', 'email', 'apiToken', 'zone', 'zoneName', 'userServiceKey'], 'string'], ['zone', 'required'], - [['apiKey', 'email'], 'required', 'when' => static function ($model) { + [['apiKey', 'email'], 'required', 'when' => static function($model) { return $model->authType === self::AUTH_TYPE_KEY; }], - ['apiToken', 'required', 'when' => static function ($model) { + ['apiToken', 'required', 'when' => static function($model) { return $model->authType === self::AUTH_TYPE_TOKEN; }], ]; diff --git a/src/queue/jobs/PurgeCloudflareCache.php b/src/queue/jobs/PurgeCloudflareCache.php new file mode 100644 index 0000000..ecb05ec --- /dev/null +++ b/src/queue/jobs/PurgeCloudflareCache.php @@ -0,0 +1,35 @@ +api->purgeUrls($this->urls); + $this->setProgress($queue, 100); + } + + /** + * @inheritdoc + */ + protected function defaultDescription(): string + { + return Craft::t('cloudflare', 'Purging Cloudflare URLs'); + } +} diff --git a/src/services/Api.php b/src/services/Api.php index 490adfb..ef89344 100644 --- a/src/services/Api.php +++ b/src/services/Api.php @@ -10,16 +10,16 @@ namespace workingconcept\cloudflare\services; -use workingconcept\cloudflare\helpers\ConfigHelper; -use workingconcept\cloudflare\models\Settings; -use workingconcept\cloudflare\helpers\UrlHelper; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\ClientException; -use GuzzleHttp\Exception\RequestException; -use GuzzleHttp\Exception\GuzzleException; use Craft; use craft\base\Component; use craft\helpers\Json; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\GuzzleException; +use GuzzleHttp\Exception\RequestException; +use workingconcept\cloudflare\helpers\ConfigHelper; +use workingconcept\cloudflare\helpers\UrlHelper; +use workingconcept\cloudflare\models\Settings; /** * @author Working Concept @@ -61,7 +61,7 @@ public function getClient(): ?Client 'base_uri' => self::API_BASE_URL, 'headers' => $this->_getClientHeaders(), 'verify' => false, - 'debug' => false + 'debug' => false, ]); } @@ -136,7 +136,7 @@ public function getConnectionErrors(): array */ public function canListZones(): bool { - if ( ! $this->getClient()) { + if (!$this->getClient()) { return false; } @@ -156,7 +156,7 @@ public function canListZones(): bool */ public function getZones(): ?array { - if ( ! $this->getClient()) { + if (!$this->getClient()) { return null; } @@ -196,7 +196,7 @@ public function getZones(): ?array */ public function getZoneById(string $zoneId): ?object { - if (! $this->getClient()) { + if (!$this->getClient()) { return null; } @@ -208,7 +208,7 @@ public function getZoneById(string $zoneId): ?object $responseBody = Json::decode($response->getBody(), false); - if (! $this->_isSuccessfulResponse($response, $responseBody)) { + if (!$this->_isSuccessfulResponse($response, $responseBody)) { Craft::info(sprintf( 'Request failed: %s', $responseBody->errors @@ -216,7 +216,7 @@ public function getZoneById(string $zoneId): ?object return null; } - } catch(RequestException $exception) { + } catch (RequestException $exception) { Craft::info(sprintf( 'Zone request failed: %s', $this->_getExceptionReason($exception) @@ -244,7 +244,7 @@ public function getZoneById(string $zoneId): ?object */ public function purgeZoneCache() { - if ( ! $this->getClient()) { + if (!$this->getClient()) { return null; } @@ -258,7 +258,7 @@ public function purgeZoneCache() $responseBody = Json::decode($response->getBody(), false); - if (! $this->_isSuccessfulResponse($response, $responseBody)) { + if (!$this->_isSuccessfulResponse($response, $responseBody)) { Craft::info(sprintf( 'Zone purge request failed: %s', $responseBody->errors @@ -267,7 +267,7 @@ public function purgeZoneCache() return (object) [ 'success' => false, 'message' => $responseBody, - 'result' => [] + 'result' => [], ]; } @@ -277,7 +277,7 @@ public function purgeZoneCache() ); return $responseBody; - } catch(ClientException | RequestException $exception) { + } catch (ClientException | RequestException $exception) { return $this->_handleApiException($exception, 'zone purge'); } } @@ -293,7 +293,7 @@ public function purgeZoneCache() */ public function purgeUrls(array $urls = []) { - if ( ! $this->getClient()) { + if (!$this->getClient()) { return null; } @@ -316,7 +316,7 @@ public function purgeUrls(array $urls = []) $responseBody = Json::decode($response->getBody(), false); - if (! $this->_isSuccessfulResponse($response, $responseBody)) { + if (!$this->_isSuccessfulResponse($response, $responseBody)) { Craft::info(sprintf( 'Request failed: %s', Json::encode($responseBody->errors) @@ -325,7 +325,7 @@ public function purgeUrls(array $urls = []) return (object) [ 'success' => false, 'message' => $responseBody, - 'result' => [] + 'result' => [], ]; } @@ -338,8 +338,7 @@ public function purgeUrls(array $urls = []) ), 'cloudflare'); return $responseBody; - } - catch(ClientException | RequestException $exception) { + } catch (ClientException | RequestException $exception) { return $this->_handleApiException($exception, 'URL purge', $urls); } } @@ -357,8 +356,8 @@ public function getApiBaseUrl(): string /** * Returns `true` if the provided response status and data indicate success. * - * @param $response - * @param $responseBody + * @param object $response + * @param object $responseBody * @return bool */ private function _isSuccessfulResponse($response, $responseBody): bool @@ -393,7 +392,7 @@ private function _handleApiException($exception, string $action, $urls = []): ob return (object) [ 'success' => false, 'errors' => $responseBody->errors ?? [], - 'result' => [] + 'result' => [], ]; } @@ -415,7 +414,7 @@ private function _handleApiException($exception, string $action, $urls = []): ob */ private function _getPagedZones($page = 1, $perPage = 50) { - if ( ! $this->getClient()) { + if (!$this->getClient()) { return null; } @@ -430,7 +429,7 @@ private function _getPagedZones($page = 1, $perPage = 50) $perPage )); - if ( ! $response->getStatusCode() === 200) { + if (! ($response->getStatusCode() === 200)) { return $this->_failureResponse(sprintf( 'Request failed: %s', $response->getBody() @@ -459,7 +458,7 @@ private function _getClientHeaders(): array $headers = [ 'Content-Type' => 'application/json', - 'Accept' => 'application/json' + 'Accept' => 'application/json', ]; if ($authType === Settings::AUTH_TYPE_KEY) { @@ -478,7 +477,7 @@ private function _getClientHeaders(): array /** * Log message and return standard failure response. * - * @param $message + * @param string $message * * @return object */ @@ -489,7 +488,7 @@ private function _failureResponse($message): object return (object) [ 'success' => false, 'message' => $message, - 'result' => [] + 'result' => [], ]; } @@ -502,8 +501,7 @@ private function _failureResponse($message): object */ private function _getExceptionReason(RequestException $exception): string { - if ($exception->hasResponse()) - { + if ($exception->hasResponse()) { return $exception->getResponse()->getBody()->getContents(); } diff --git a/src/services/Rules.php b/src/services/Rules.php index 42f3802..6368824 100644 --- a/src/services/Rules.php +++ b/src/services/Rules.php @@ -2,16 +2,17 @@ namespace workingconcept\cloudflare\services; -use workingconcept\cloudflare\Cloudflare; -use workingconcept\cloudflare\records\RuleRecord; - use Craft; use craft\base\Component; -use craft\helpers\UrlHelper; -use craft\helpers\Json; use craft\errors\SiteNotFoundException; -use yii\base\NotSupportedException; +use craft\helpers\Json; +use craft\helpers\Queue; +use craft\helpers\UrlHelper; +use workingconcept\cloudflare\Cloudflare; +use workingconcept\cloudflare\queue\jobs\PurgeCloudflareCache; +use workingconcept\cloudflare\records\RuleRecord; use yii\base\Exception; +use yii\base\NotSupportedException; /** * Provides a Cloudflare page rule service @@ -22,17 +23,18 @@ class Rules extends Component { /** * Returns all rules for a table. + * * @return array */ public function getRulesForTable(): array { - $data = []; + $data = []; $rules = $this->getRules(); foreach ($rules as $rule) { $data[(string)$rule->id] = [ 0 => $rule->trigger, - 1 => implode("\n", Json::decode($rule->urlsToClear)) + 1 => implode("\n", Json::decode($rule->urlsToClear)), ]; } @@ -48,7 +50,7 @@ public function getRules(): ?array } /** - * Get supplied rules from the CP view and save them to the database. + * Get supplied rules from the control panel view and save them to the database. * * @return void * @throws SiteNotFoundException @@ -76,12 +78,15 @@ public function saveRules(): void } /** - * @param string $url + * Purge any related URLs we’ve established with custom rules. + * + * @param string $url The URL our custom rules should be checked against + * @param bool $immediately Whether to skip the queue and immediately call Cloudflare’s API * * @return void * @throws Exception */ - public function purgeCachesForUrl(string $url): void + public function purgeCachesForUrl(string $url, bool $immediately = false): void { // max limit for Cloudflare API $cloudflareRuleCountLimit = 30; @@ -108,7 +113,11 @@ public function purgeCachesForUrl(string $url): void ); } - Cloudflare::getInstance()->api->purgeUrls($urlsToPurge); + if ($immediately) { + Cloudflare::getInstance()->api->purgeUrls($urlsToPurge); + } else { + Queue::push(new PurgeCloudflareCache(['urls' => $urlsToPurge])); + } } /** @@ -122,12 +131,9 @@ public function getRulesForUrl(string $url): array { $relatedRules = []; - if ($rules = $this->getRules()) - { - foreach ($rules as $rule) - { - if (preg_match("`" . $rule->trigger . "`", $url)) - { + if ($rules = $this->getRules()) { + foreach ($rules as $rule) { + if (preg_match("`" . $rule->trigger . "`", $url)) { $relatedRules[] = $rule; } } diff --git a/src/templates/settings.twig b/src/templates/settings.twig index 2a44c95..a7d3c55 100644 --- a/src/templates/settings.twig +++ b/src/templates/settings.twig @@ -36,7 +36,7 @@ errors: settings.getErrors('zone'), toggle: true, targetPrefix: 'fields-api-', - instructions: 'Choose whether you\'ll be using an account-level key or scope-limited token.'|t('cloudflare') + instructions: 'Choose whether you’ll be using an account-level key or scope-limited token.'|t('cloudflare') }) }}