Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ This is especially useful for creating short, memorable links for marketing camp
- Hit Count: See how many times an alias is being visited (including the date/time of the last visit)
- Test Interface: You can easily test if an alias works as expected (also with browser languages that differ from your default one)
- Support for Fragment Identifiers: When the alias target is a page, you can specify the exact point where the browser should land (for example, `/target#point-in-page`)
- Sopport for POST Forwarding: When the users send POST requests to the alias, you can configure the package to forward that data via POST

### How It Works

Expand All @@ -67,6 +68,7 @@ There you can manage all the URL aliases.
- you create an alias named `/alias` that points to `/about`
- `/alias` would resolve to `/about`
- but `/alias/me` won't resolve to `/about/me`
- Forwarding files via POST is not supported: the package will only forward *normal* fields received via POST

## Do you really want to say thank you?

Expand Down
2 changes: 1 addition & 1 deletion controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Controller extends Package
{
protected $pkgHandle = 'url_aliases';

protected $pkgVersion = '0.0.4';
protected $pkgVersion = '0.9.0';

/**
* {@inheritdoc}
Expand Down
2 changes: 2 additions & 0 deletions controllers/single_page/dashboard/system/url_aliases.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ public function saveUrlAlias(): JsonResponse
->setEnabled($post->getBoolean('enabled'))
->setAcceptAdditionalQuerystringParams($post->getBoolean('acceptAdditionalQuerystringParams'))
->setForwardQuerystringParams($post->getBoolean('forwardQuerystringParams'))
->setForwardPost($post->getBoolean('forwardPost'))
;
if ($urlAlias->getPath() === '') {
throw new UserMessageException(t('Please specify the path of the alias Url'));
Expand Down Expand Up @@ -273,6 +274,7 @@ private function serializeUrlAlias(UrlAlias $urlAlias, array $services): array
'targetValue' => $urlAlias->getTargetValue(),
'fragmentIdentifier' => $urlAlias->getFragmentIdentifier(),
'forwardQuerystringParams' => $urlAlias->isForwardQuerystringParams(),
'forwardPost' => $urlAlias->isForwardPost(),
'firstHit' => ($d = $urlAlias->getFirstHit()) === null ? null : $d->getTimestamp(),
'lastHit' => ($d = $urlAlias->getLastHit()) === null ? null : $d->getTimestamp(),
'hitCount' => $urlAlias->getHitCount(),
Expand Down
4 changes: 2 additions & 2 deletions single_pages/dashboard/system/url-aliases.php
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ function ready() {
</span>
</a>`,
});

let uaAcceptHeaderBuilderCounter = 0;

Vue.component('ua-accept-header-builder', {
Expand Down Expand Up @@ -658,7 +658,7 @@ function ready() {
this.urlAliases.push(urlAlias);
}
},

testUrlAlias(urlAlias) {
if (!urlAlias?.id || !urlAlias.enabled) {
return;
Expand Down
5 changes: 5 additions & 0 deletions src/Concrete/Entity/Target.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public function getTargetValue(): string;
*/
public function isForwardQuerystringParams(): bool;

/**
* Forward POST requests and received data?
*/
public function isForwardPost(): bool;

/**
* Get the fragment identifier to be appended to page targets.
*/
Expand Down
32 changes: 32 additions & 0 deletions src/Concrete/Entity/UrlAlias.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ class UrlAlias implements Target
*/
protected $forwardQuerystringParams;

/**
* Forward POST requests and received data?
*
* @Doctrine\ORM\Mapping\Column(type="boolean", nullable=false, options={"comment":"Forward POST requests and received data?"})
*
* @var bool
*/
protected $forwardPost;

/**
* The date/time of the first hit.
*
Expand Down Expand Up @@ -176,6 +185,7 @@ public function __construct()
$this->targetValue = '';
$this->fragmentIdentifier = '';
$this->forwardQuerystringParams = false;
$this->forwardPost = false;
$this->firstHit = null;
$this->lastHit = null;
$this->hitCount = 0;
Expand Down Expand Up @@ -377,6 +387,28 @@ public function setForwardQuerystringParams(bool $value): self
return $this;
}

/**
* Forward POST requests and received data?
*
* @see \Concrete\Package\UrlAliases\Entity\Target::isForwardPost()
*/
public function isForwardPost(): bool
{
return $this->forwardPost;
}

/**
* Forward POST requests and received data?
*
* @return $this
*/
public function setForwardPost(bool $value): self
{
$this->forwardPost = $value;

return $this;
}

/**
* Get the date/time of the first hit.
*/
Expand Down
10 changes: 10 additions & 0 deletions src/Concrete/Entity/UrlAlias/LocalizedTarget.php
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,14 @@ public function isForwardQuerystringParams(): bool
{
return $this->getUrlAlias()->isForwardQuerystringParams();
}

/**
* Forward POST requests and received data?
*
* @see \Concrete\Package\UrlAliases\Entity\Target::isForwardPost()
*/
public function isForwardPost(): bool
{
return $this->getUrlAlias()->isForwardPost();
}
}
66 changes: 59 additions & 7 deletions src/Concrete/RequestResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ final class RequestResolver

public const TESTFIELD_OVERRIDEACCEPTLANGUAGE = 'ua-testing_url_aliases_acceptlanguage';

private const COMMON_RESPONSE_HEADERS = [
'Cache-Control' => 'private, no-store, no-cache, must-revalidate',
];

/**
* @var \Concrete\Package\UrlAliases\Entity\UrlAliasRepository
*/
Expand Down Expand Up @@ -100,13 +104,11 @@ private function buildResponse(Request $request, bool $hitIt, bool $isTesting):
if ($isTesting) {
return $this->buildTestingResponse(t('Users will be redirected to: %s', $resolved->url));
}
$response = $this->responseFactory->redirect(
$resolved->url,
Response::HTTP_TEMPORARY_REDIRECT,
[
'Cache-Control' => 'private, no-store, no-cache, must-revalidate',
]
);
if ($target->isForwardPost() && $request->getMethod() === 'POST') {
$response = $this->buildForwardPostResponse($resolved->url, $request);
} else {
$response = $this->buildRedirectResponse($resolved->url);
}
if ($hitIt) {
$urlAlias->hit();
$this->repo->getEntityManager()->flush();
Expand Down Expand Up @@ -253,4 +255,54 @@ private function inspectLocale(string $locale): array

return [$language, $script, $territory];
}

private function buildRedirectResponse(string $targetUrl): Response
{
return $this->responseFactory->redirect($targetUrl, Response::HTTP_TEMPORARY_REDIRECT, self::COMMON_RESPONSE_HEADERS);
}

private function buildForwardPostResponse(string $targetUrl, Request $request): Response
{
$charset = APP_CHARSET;
$hTargetUrl = htmlspecialchars($targetUrl, ENT_QUOTES, APP_CHARSET);
$html = <<<EOT
<!DOCTYPE html>
<html>
<head>
<meta charset="{$charset}" />
</head>
<body onload="document.forms[0].submit()">
<form method="POST" action="{$hTargetUrl}">

EOT
;
$renderFields = null;
$renderFields = static function (array $data, string $namePrefix = '') use (&$renderFields, &$html): void {
foreach ($data as $key => $value) {
$key = (string) $key;
$name = $namePrefix === '' ? $key : "{$namePrefix}[{$key}]";
if (is_array($value)) {
$renderFields($value, $name);
} else {
$escapedName = htmlspecialchars($name, ENT_QUOTES, APP_CHARSET);
$escapedValue = htmlspecialchars((string) $value, ENT_QUOTES, APP_CHARSET);
$html .= <<<EOT
<input type="hidden" name="{$escapedName}" value="{$escapedValue}" />

EOT
;
}
}
};
$renderFields($request->request->all());
$html .= <<<'EOT'
</form>
</body>
</html>

EOT
;

return new Response($html, Response::HTTP_OK, self::COMMON_RESPONSE_HEADERS);
}
}
8 changes: 8 additions & 0 deletions views/dialogs/edit_url_alias.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@
<?= t('Forward received querystring parameters') ?>
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" v-model="forwardPost" id="ua-urlalias-editing-forwardpost">
<label class="form-check-label" for="ua-urlalias-editing-forwardpost">
<?= t('Forward POST requests and their data (excluding files)') ?>
</label>
</div>
</div>
<div class="dialog-buttons">
<button class="btn btn-secondary pull-left" v-on:click.prevent="cancel()"><?= t('Cancel') ?></button>
Expand Down Expand Up @@ -115,6 +121,7 @@ function ready() {
pathAndQuerystring: <?= json_encode($urlAlias->getPathAndQuerystring()) ?>,
acceptAdditionalQuerystringParams: <?= json_encode($urlAlias->isAcceptAdditionalQuerystringParams()) ?>,
forwardQuerystringParams: <?= json_encode($urlAlias->isForwardQuerystringParams()) ?>,
forwardPost: <?= json_encode($urlAlias->isForwardPost()) ?>,
enabled: <?= json_encode($urlAlias->isEnabled()) ?>,
askFragmentIdentifier: false,
fragmentIdentifier: <?= json_encode($urlAlias->getFragmentIdentifier()) ?>,
Expand Down Expand Up @@ -174,6 +181,7 @@ function ready() {
acceptAdditionalQuerystringParams: this.acceptAdditionalQuerystringParams,
enabled: this.enabled,
forwardQuerystringParams: this.forwardQuerystringParams,
forwardPost: this.forwardPost,
};
data.targetValue = this.$el.querySelector(`:scope [name="target_${data.targetType}"]`).value;
const ev = new CustomEvent('ccm.url_aliases.saveUrlAlias', {
Expand Down