Skip to content

Commit

Permalink
Adds utility and purge buttons to element edit pages. Bump to 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mmikkel committed Apr 25, 2024
1 parent bf08784 commit b6ef5b8
Show file tree
Hide file tree
Showing 12 changed files with 493 additions and 55 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# CloudflareMate Changelog

## Unreleased
## 1.0.0 - 2024-04-25
### Added
- Initial release for Craft 4
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ Are your clouds flaring up again, mate?

## Description

CloudflareMate makes life easier when using Cloudflare to statically cache Craft CMS sites, by
CloudflareMate makes life a little bit easier when using Cloudflare to statically cache Craft CMS sites, by

1. Making sure that requests are served with proper Cache-Control headers
2. Purging Cloudfare caches automatically when elements are created, saved, moved, deleted, etc.
1. ...making sure that requests are served with proper Cache-Control headers
2. ...purging Cloudfare caches automatically when elements are created, saved, moved, deleted, etc.
3. ...exposing some nifty buttons in the CP for purging the edge cache manually

## Requirements

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "vaersaagod/cloudflaremate",
"description": "Are your clouds flaring up again, mate?",
"type": "craft-plugin",
"version": "1.0.0-alpha.1",
"version": "1.0.0",
"require": {
"php": ">=8.1",
"craftcms/cms": "^4.7.0"
Expand Down
57 changes: 54 additions & 3 deletions src/CloudflareMate.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,28 @@
namespace vaersaagod\cloudflaremate;

use Craft;
use craft\elements\Entry;
use craft\events\DefineHtmlEvent;
use craft\helpers\ElementHelper;
use craft\web\twig\variables\CraftVariable;
use Psr\Log\LogLevel;
use craft\base\Element;
use craft\base\Model;
use craft\base\Plugin;
use craft\events\BatchElementActionEvent;
use craft\events\ElementEvent;
use craft\events\RegisterComponentTypesEvent;
use craft\helpers\App;
use craft\log\MonologTarget;
use craft\services\Elements;
use craft\services\Utilities;
use craft\web\Response;

use Psr\Log\LogLevel;

use vaersaagod\cloudflaremate\helpers\ResponseHelper;
use vaersaagod\cloudflaremate\models\Settings;
use vaersaagod\cloudflaremate\services\ElementPurger;
use vaersaagod\cloudflaremate\utilities\CloudflareMateUtility;
use vaersaagod\cloudflaremate\web\twig\variables\CloudflareMateVariable;

use yii\base\Event;
use yii\web\Response as BaseResponse;
Expand Down Expand Up @@ -57,7 +64,7 @@ public function init(): void
]);

// Defer most setup tasks until Craft is fully initialized
Craft::$app->onInit(function() {
Craft::$app->onInit(function () {
$this->attachEventHandlers();
});
}
Expand All @@ -78,6 +85,50 @@ protected function createSettingsModel(): ?Model
private function attachEventHandlers(): void
{

Event::on(
Utilities::class,
Utilities::EVENT_REGISTER_UTILITY_TYPES,
static function (RegisterComponentTypesEvent $event) {
$event->types[] = CloudflareMateUtility::class;
}
);

Event::on(
CraftVariable::class,
CraftVariable::EVENT_INIT,
static function(Event $event) {
/** @var CraftVariable $variable */
$variable = $event->sender;
$variable->set('cloudflareMate', CloudflareMateVariable::class);
}
);

Event::on(
Entry::class,
Element::EVENT_DEFINE_SIDEBAR_HTML,
static function (DefineHtmlEvent $event) {
if ($event->static) {
return;
}
$element = $event->sender;
if (!$element instanceof Element || empty($element->id)) {
return;
}
try {
if (ElementHelper::isDraftOrRevision($element)) {
$canonical = $element->getCanonical();
if ($element->id === $canonical->id) {
return;
}
$element = $canonical;
}
$event->html .= Craft::$app->getView()->renderTemplate('_cloudflaremate/meta-panel.twig', ['element' => $element]);
} catch (\Throwable $e) {
Craft::error($e, __METHOD__);
}
}
);

Event::on(
Response::class,
BaseResponse::EVENT_AFTER_PREPARE,
Expand Down
2 changes: 1 addition & 1 deletion src/console/controllers/PurgeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function actionSite(string|int|null $site = null): int
{
$site = SiteHelper::getSiteFromParam($site);
$this->stdout("Purging site \"$site->handle\"..." . PHP_EOL, BaseConsole::FG_YELLOW);
$result = ApiHelper::purgeSite($site);
$result = PurgeHelper::purgeSite($site);
if ($result) {
$this->stdout("Big success!" . PHP_EOL, BaseConsole::FG_GREEN);
} else {
Expand Down
146 changes: 146 additions & 0 deletions src/controllers/CpController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

namespace vaersaagod\cloudflaremate\controllers;

use Craft;
use craft\helpers\Json;
use craft\web\Controller;

use vaersaagod\cloudflaremate\helpers\CloudflareMateHelper;
use vaersaagod\cloudflaremate\helpers\PurgeHelper;

use vaersaagod\cloudflaremate\helpers\UrisHelper;
use vaersaagod\cloudflaremate\helpers\UrlHelper;
use yii\web\BadRequestHttpException;
use yii\web\Response;

/**
* Cp controller
*/
class CpController extends Controller
{

protected array|int|bool $allowAnonymous = self::ALLOW_ANONYMOUS_NEVER;

/**
* @return Response
* @throws BadRequestHttpException
* @throws \craft\errors\SiteNotFoundException
* @throws \yii\base\InvalidConfigException
* @throws \yii\db\Exception
*/
public function actionPurgeSite(): Response
{
$this->requireAcceptsJson();
$this->requireCpRequest();

$siteHandle = $this->request->getRequiredBodyParam('site');
$site = Craft::$app->getSites()->getSiteByHandle($siteHandle, true);
if (!$site) {
throw new BadRequestHttpException("Invalid site handle: $siteHandle");
}

PurgeHelper::purgeSite($site);

return $this->asSuccess(
message: "Zone purged for site \"$site->name\""
);

}

/**
* @return Response
* @throws BadRequestHttpException
* @throws \craft\errors\SiteNotFoundException
* @throws \yii\base\Exception
* @throws \yii\base\InvalidConfigException
* @throws \yii\db\Exception
*/
public function actionPurgeUri(): Response
{
$this->requireAcceptsJson();
$this->requireCpRequest();

$siteHandle = $this->request->getRequiredBodyParam('site');
$site = Craft::$app->getSites()->getSiteByHandle($siteHandle, true);
if (!$site) {
throw new BadRequestHttpException("Invalid site handle: $siteHandle");
}

$uri = $this->request->getRequiredBodyParam('uri');
$uris = CloudflareMateHelper::getUrisToPurgeFromSourceUrisAndIds([$uri], [], $site->id);

Craft::info("Purge URIs: " . Json::encode($uris), __METHOD__);

if (empty($uris)) {
return $this->asFailure(
message: 'Nothing to purge'
);
}

if (!PurgeHelper::purgeUris($uris)) {
return $this->asFailure(
message: "Failed to purge URI for site \"$site->name\""
);
}

return $this->asSuccess(
message: "URI purged for site \"$site->name\""
);

}

/**
* @return Response
* @throws BadRequestHttpException
* @throws \craft\errors\SiteNotFoundException
* @throws \yii\base\Exception
* @throws \yii\base\InvalidConfigException
* @throws \yii\db\Exception
*/
public function actionPurgeElement(): Response
{
$this->requireAcceptsJson();
$this->requireCpRequest();

$siteId = (int)$this->request->getRequiredBodyParam('siteId');
$site = Craft::$app->getSites()->getSiteById($siteId, true);
if (!$site) {
throw new BadRequestHttpException("Invalid site ID: $siteId");
}

$elementId = (int)$this->request->getRequiredBodyParam('elementId');
$element = Craft::$app->getElements()->getElementById($elementId, null, $site->id);
if (!$element) {
throw new BadRequestHttpException("Invalid element ID: $elementId");
}

$uris = [];

if ($element::hasUris()) {
$uris[] = UrlHelper::getUriFromFullUrl($element->getUrl(), $site);
}

$uris = CloudflareMateHelper::getUrisToPurgeFromSourceUrisAndIds($uris, [$element->id], $site->id);

Craft::info("Purge URIs for element: " . Json::encode($uris), __METHOD__);

if (empty($uris)) {
return $this->asFailure(
message: 'Nothing to purge'
);
}

if (!PurgeHelper::purgeUris($uris)) {
return $this->asFailure(
message: "Failed to purge element for site \"$site->name\""
);
}

return $this->asSuccess(
message: "Element purged for site \"$site->name\""
);

}

}
2 changes: 1 addition & 1 deletion src/helpers/ApiHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static function deleteFiles(string $zoneId, array $files = []): bool

Craft::info('Deleting ' . count($files) . ' files from Cloudflare: ' . json_encode($files), __METHOD__);

// Batch files sa per API limit
// Batch files as per API limit
$requests = [];
$fileBatches = array_chunk($files, ApiHelper::API_URLS_PER_REQUEST_LIMIT);

Expand Down
45 changes: 45 additions & 0 deletions src/helpers/CloudflareMateHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,51 @@ public static function shouldUriBeIgnored(string $uri): bool
return false;
}
}
return false;
}

/**
* @param array $uris
* @param array $elementIds
* @param int $siteId
* @return array
*/
public static function getUrisToPurgeFromSourceUrisAndIds(array $uris, array $elementIds, int $siteId): array
{

if (empty($uris) && empty($elementIds)) {
return [];
}

// Get additional URIs from relations
$relationUris = CloudflareMateHelper::getUrisFromElementRelations($elementIds, $siteId);

$uris = array_unique([
...$uris,
...$relationUris,
]);

// Get additional URIs to purge as per the `additionalUrisToPurge` config setting
$purgePatternUris = CloudflareMateHelper::getAdditionalUrisToPurge($uris);

$uris = array_unique([
...$uris,
...$purgePatternUris,
]);

// Get additional URIs from the uris database table, that begins with any of our uris
$prefixUrls = CloudflareMateHelper::getLoggedUrisByPrefix($uris, $siteId);

$uris = array_unique([
...$uris,
...$prefixUrls,
]);

// Finally, strip out any uris that we want to ignore
$uris = array_filter($uris, static fn(string $uri) => !CloudflareMateHelper::shouldUriBeIgnored($uri));

return array_values($uris);

}

}
46 changes: 1 addition & 45 deletions src/services/ElementPurger.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ private function _purgeInternal(): void
continue;
}
$site = Craft::$app->getSites()->getSiteByHandle($siteHandle, true);
$urisToPurge = $this->_getAllElementUrisToPurge($uris, $elementIds, $site->id);
$urisToPurge = CloudflareMateHelper::getUrisToPurgeFromSourceUrisAndIds($uris, $elementIds, $site->id);
if (empty($urisToPurge)) {
continue;
}
Expand All @@ -231,48 +231,4 @@ private function _purgeInternal(): void

}

/**
* @param array $uris
* @param array $elementIds
* @param int $siteId
* @return array
*/
private function _getAllElementUrisToPurge(array $uris, array $elementIds, int $siteId): array
{

if (empty($uris) && empty($elementIds)) {
return [];
}

// Get additional URIs from relations
$relationUris = CloudflareMateHelper::getUrisFromElementRelations($elementIds, $siteId);

$uris = array_unique([
...$uris,
...$relationUris,
]);

// Get additional URIs to purge as per the `additionalUrisToPurge` config setting
$purgePatternUris = CloudflareMateHelper::getAdditionalUrisToPurge($uris);

$uris = array_unique([
...$uris,
...$purgePatternUris,
]);

// Get additional URIs from the uris database table, that begins with any of our uris
$prefixUrls = CloudflareMateHelper::getLoggedUrisByPrefix($uris, $siteId);

$uris = array_unique([
...$uris,
...$prefixUrls,
]);

// Finally, strip out any uris that we want to ignore
$uris = array_filter($uris, static fn(string $uri) => !CloudflareMateHelper::shouldUriBeIgnored($uri));

return array_values($uris);

}

}
Loading

0 comments on commit b6ef5b8

Please sign in to comment.