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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,17 @@ Provide a valid `load_callback` within you DataContainer field configuration.
'linkChecker' => array(
'label' => &$GLOBALS['TL_LANG']['tl_sample']['linkChecker'],
'inputType' => 'linkChecker',
'eval' => array(
'linkCheckerTimeout' => 10,
),
'load_callback' => array(
array('MyClass', 'getLinkCheckerHtml'),
),
),
```

You can return html-code with anchor tags, a single link or an array of links within your `load_callback`.
The `linkCheckerTimeout` eval option configures the timeout in seconds for the backend link check. If omitted or invalid, the default timeout is 10 seconds.

```
// MyClass
Expand Down
5 changes: 4 additions & 1 deletion src/EventListener/ExecutePreActionsListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ public function onExecutePreActions(string $action): void
return;
}

$strStatus = $this->linkChecker->test($request->request->get(LinkCheckerWidget::LINKCHECKER_PARAM));
$strStatus = $this->linkChecker->test(
$request->request->get(LinkCheckerWidget::LINKCHECKER_PARAM),
$request->request->get(LinkCheckerWidget::LINKCHECKER_TIMEOUT_PARAM)
);

$objResponse = new ResponseSuccess();
$objResponse->setResult(new ResponseData($strStatus));
Expand Down
42 changes: 34 additions & 8 deletions src/Manager/LinkChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
use Contao\Validator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;

class LinkChecker
{
public const STATUS_MAILTO = 'mailto';
public const STATUS_INVALID = 'invalid';
public const STATUS_TIMEOUT = '408';
public const STATUS_TRANSPORT_ERROR = 'transport_error';
public const DEFAULT_TIMEOUT = 10;
public const MAX_TIMEOUT = 60;

public const CLASS_DEFAULT = 'lc-default';
public const CLASS_INFO = 'lc-info';
Expand All @@ -39,13 +43,15 @@ public function __construct(
*
* @return array|bool|mixed
*/
public function test($varLinks)
public function test($varLinks, int|string|null $timeout = null)
{
$timeout = $this->normalizeTimeout($timeout);

if (!\is_array($varLinks)) {
return $this->testOne($varLinks);
return $this->testOne($varLinks, $timeout);
}

return $this->testAll($varLinks);
return $this->testAll($varLinks, $timeout);
}

/**
Expand All @@ -55,7 +61,7 @@ public function test($varLinks)
*
* @return string The translated status code, or false if the link was not tested
*/
protected function testOne(string $url): string
protected function testOne(string $url, int $timeout): string
{
if (str_starts_with($url, 'mailto:')) {
return $this->getResult(static::STATUS_MAILTO);
Expand All @@ -65,9 +71,16 @@ protected function testOne(string $url): string
return $this->getResult(static::STATUS_INVALID);
}

$response = $this->client->request(Request::METHOD_GET, $url);
try {
$response = $this->client->request(Request::METHOD_GET, $url, [
'max_duration' => $timeout,
'timeout' => $timeout,
]);

return $this->getResult($response->getStatusCode());
return $this->getResult($response->getStatusCode());
} catch (TransportExceptionInterface) {
return $this->getResult(static::STATUS_TRANSPORT_ERROR);
}
}

/**
Expand All @@ -77,18 +90,27 @@ protected function testOne(string $url): string
*
* @return array The list of tested links with translated status code, or false if the link was not tested
*/
protected function testAll(array $arrLinks): array
protected function testAll(array $arrLinks, int $timeout): array
{
$arrResults = [];

foreach ($arrLinks as $strKey => $strUrl) {
$arrResults[$strUrl] = $this->testOne($strUrl);
$arrResults[$strUrl] = $this->testOne($strUrl, $timeout);
unset($arrLinks);
}

return $arrResults;
}

protected function normalizeTimeout(int|string|null $timeout): int
{
if (null === $timeout || '' === $timeout || !is_numeric($timeout) || (int) $timeout < 1) {
return static::DEFAULT_TIMEOUT;
}

return min((int) $timeout, static::MAX_TIMEOUT);
}

/**
* Get the styled result.
*/
Expand Down Expand Up @@ -119,6 +141,10 @@ protected function getResult(string $result): string
*/
protected function getStatusClass(string $statusCode): string
{
if (static::STATUS_TRANSPORT_ERROR === $statusCode) {
return static::CLASS_ERROR;
}

$intStart = null;

if (\strlen($statusCode) > 0) {
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/contao/languages/de/default.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@
*/
$arrLang['statusCodes'][LinkChecker::STATUS_MAILTO] = 'E-Mail Adressen werden nicht geprüft.';
$arrLang['statusCodes'][LinkChecker::STATUS_INVALID] = 'Ungültige URL, kann nicht geprüft werden.';
$arrLang['statusCodes'][LinkChecker::STATUS_TIMEOUT] = 'Zeitüberschreitung bei der Anfrage.';
$arrLang['statusCodes'][LinkChecker::STATUS_TRANSPORT_ERROR] = 'Anfrage fehlgeschlagen.';
2 changes: 2 additions & 0 deletions src/Resources/contao/languages/en/default.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@
*/
$arrLang['statusCodes'][LinkChecker::STATUS_MAILTO] = 'Email addresses are not checked.';
$arrLang['statusCodes'][LinkChecker::STATUS_INVALID] = 'Invalid URL, cannot be checked.';
$arrLang['statusCodes'][LinkChecker::STATUS_TIMEOUT] = 'Request timed out.';
$arrLang['statusCodes'][LinkChecker::STATUS_TRANSPORT_ERROR] = 'Request failed.';
4 changes: 2 additions & 2 deletions src/Resources/contao/templates/backend/be_linkchecker.html5
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
<?php foreach ($this->links as $objLink): ?>
<tr>
<td class="link">
<span title="<?= $objLink->title; ?>" class="url" data-linkchecker="true" data-target="<?= $objLink->target; ?>"
<span title="<?= $objLink->title; ?>" class="url" data-linkchecker="true" data-target="<?= $objLink->target; ?>" data-timeout="<?= $objLink->timeout; ?>"
data-url="<?= $objLink->url; ?>"><?= $objLink->text; ?></span>
</td>
<td class="status" id="<?= $objLink->targetID; ?>"><span class="lc-loading-indicator"></span></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</table>
23 changes: 23 additions & 0 deletions src/Resources/public/js/contao-linkchecker-bundle.es6.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
this.element = element;
this.options = options;
this.url = this.element.getAttribute('data-url');
this.timeout = parseInt(this.element.getAttribute('data-timeout'), 10);
this.element.linkchecker = this;

if (isNaN(this.timeout) || this.timeout < 1) {
this.timeout = 10;
}

if (this.element.getAttribute('data-target')) {
this.target = document.querySelector(this.element.getAttribute('data-target'));
}
Expand Down Expand Up @@ -39,6 +44,7 @@

let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.timeout = (_this.timeout + 2) * 1000;
xhr.withCredentials = !!_this.options.withCredentials;
response = null;
updateProgress = function() {
Expand Down Expand Up @@ -72,6 +78,16 @@
}
};
xhr.onerror = function() {
_this._failed();

reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.ontimeout = function() {
_this._failed();

reject({
status: this.status,
statusText: xhr.statusText
Expand Down Expand Up @@ -101,6 +117,7 @@
formData.append(key, value);
}
}
formData.append('lc_timeout', _this.timeout);

_requests.push(xhr);

Expand All @@ -124,6 +141,12 @@
return false;
};

LinkChecker.prototype._failed = function() {
this.target.innerHTML = '-';

return false;
};

LinkCheckerRegistry = {
init: function() {
this.register();
Expand Down
23 changes: 23 additions & 0 deletions src/Resources/public/js/linkchecker.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,13 @@
this.element = element;
this.options = options;
this.url = this.element.getAttribute('data-url');
this.timeout = parseInt(this.element.getAttribute('data-timeout'), 10);
this.element.linkchecker = this;

if (isNaN(this.timeout) || this.timeout < 1) {
this.timeout = 10;
}

if (this.element.getAttribute('data-target')) {
this.target = document.querySelector(this.element.getAttribute('data-target'));
}
Expand Down Expand Up @@ -276,6 +281,7 @@

var xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.timeout = (_this.timeout + 2) * 1000;
xhr.withCredentials = !!_this.options.withCredentials;
response = null;
updateProgress = function() {
Expand Down Expand Up @@ -309,6 +315,16 @@
}
};
xhr.onerror = function () {
_this._failed();

reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.ontimeout = function () {
_this._failed();

reject({
status: this.status,
statusText: xhr.statusText
Expand Down Expand Up @@ -338,6 +354,7 @@
formData.append(key, value);
}
}
formData.append('lc_timeout', _this.timeout);

_requests.push(xhr);

Expand All @@ -361,6 +378,12 @@
return false;
};

LinkChecker.prototype._failed = function () {
this.target.innerHTML = '-';

return false;
};

LinkCheckerRegistry = {
init: function () {
this.register();
Expand Down
Loading
Loading