Skip to content

Commit 63ecfe2

Browse files
committed
add timeout support
1 parent 07bede3 commit 63ecfe2

10 files changed

Lines changed: 102 additions & 12 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ Provide a valid `load_callback` within you DataContainer field configuration.
3535
'linkChecker' => array(
3636
'label' => &$GLOBALS['TL_LANG']['tl_sample']['linkChecker'],
3737
'inputType' => 'linkChecker',
38+
'eval' => array(
39+
'linkCheckerTimeout' => 10,
40+
),
3841
'load_callback' => array(
3942
array('MyClass', 'getLinkCheckerHtml'),
4043
),
4144
),
4245
```
4346

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

4650
```
4751
// MyClass

src/EventListener/ExecutePreActionsListener.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ public function onExecutePreActions(string $action): void
3535
return;
3636
}
3737

38-
$strStatus = $this->linkChecker->test($request->request->get(LinkCheckerWidget::LINKCHECKER_PARAM));
38+
$strStatus = $this->linkChecker->test(
39+
$request->request->get(LinkCheckerWidget::LINKCHECKER_PARAM),
40+
$request->request->get(LinkCheckerWidget::LINKCHECKER_TIMEOUT_PARAM)
41+
);
3942

4043
$objResponse = new ResponseSuccess();
4144
$objResponse->setResult(new ResponseData($strStatus));

src/Manager/LinkChecker.php

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
use Contao\Validator;
1414
use Symfony\Component\HttpFoundation\Request;
1515
use Symfony\Component\HttpFoundation\Response;
16+
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
1617
use Symfony\Contracts\HttpClient\HttpClientInterface;
1718

1819
class LinkChecker
1920
{
2021
public const STATUS_MAILTO = 'mailto';
2122
public const STATUS_INVALID = 'invalid';
2223
public const STATUS_TIMEOUT = '408';
24+
public const DEFAULT_TIMEOUT = 10;
2325

2426
public const CLASS_DEFAULT = 'lc-default';
2527
public const CLASS_INFO = 'lc-info';
@@ -39,13 +41,15 @@ public function __construct(
3941
*
4042
* @return array|bool|mixed
4143
*/
42-
public function test($varLinks)
44+
public function test($varLinks, int|string|null $timeout = null)
4345
{
46+
$timeout = $this->normalizeTimeout($timeout);
47+
4448
if (!\is_array($varLinks)) {
45-
return $this->testOne($varLinks);
49+
return $this->testOne($varLinks, $timeout);
4650
}
4751

48-
return $this->testAll($varLinks);
52+
return $this->testAll($varLinks, $timeout);
4953
}
5054

5155
/**
@@ -55,7 +59,7 @@ public function test($varLinks)
5559
*
5660
* @return string The translated status code, or false if the link was not tested
5761
*/
58-
protected function testOne(string $url): string
62+
protected function testOne(string $url, int $timeout): string
5963
{
6064
if (str_starts_with($url, 'mailto:')) {
6165
return $this->getResult(static::STATUS_MAILTO);
@@ -65,9 +69,16 @@ protected function testOne(string $url): string
6569
return $this->getResult(static::STATUS_INVALID);
6670
}
6771

68-
$response = $this->client->request(Request::METHOD_GET, $url);
72+
try {
73+
$response = $this->client->request(Request::METHOD_GET, $url, [
74+
'max_duration' => $timeout,
75+
'timeout' => $timeout,
76+
]);
6977

70-
return $this->getResult($response->getStatusCode());
78+
return $this->getResult($response->getStatusCode());
79+
} catch (TransportExceptionInterface) {
80+
return $this->getResult(static::STATUS_TIMEOUT);
81+
}
7182
}
7283

7384
/**
@@ -77,18 +88,27 @@ protected function testOne(string $url): string
7788
*
7889
* @return array The list of tested links with translated status code, or false if the link was not tested
7990
*/
80-
protected function testAll(array $arrLinks): array
91+
protected function testAll(array $arrLinks, int $timeout): array
8192
{
8293
$arrResults = [];
8394

8495
foreach ($arrLinks as $strKey => $strUrl) {
85-
$arrResults[$strUrl] = $this->testOne($strUrl);
96+
$arrResults[$strUrl] = $this->testOne($strUrl, $timeout);
8697
unset($arrLinks);
8798
}
8899

89100
return $arrResults;
90101
}
91102

103+
protected function normalizeTimeout(int|string|null $timeout): int
104+
{
105+
if (null === $timeout || '' === $timeout || !is_numeric($timeout) || (int) $timeout < 1) {
106+
return static::DEFAULT_TIMEOUT;
107+
}
108+
109+
return (int) $timeout;
110+
}
111+
92112
/**
93113
* Get the styled result.
94114
*/

src/Resources/contao/languages/de/default.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@
1818
*/
1919
$arrLang['statusCodes'][LinkChecker::STATUS_MAILTO] = 'E-Mail Adressen werden nicht geprüft.';
2020
$arrLang['statusCodes'][LinkChecker::STATUS_INVALID] = 'Ungültige URL, kann nicht geprüft werden.';
21+
$arrLang['statusCodes'][LinkChecker::STATUS_TIMEOUT] = 'Zeitüberschreitung bei der Anfrage.';

src/Resources/contao/languages/en/default.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@
1818
*/
1919
$arrLang['statusCodes'][LinkChecker::STATUS_MAILTO] = 'Email addresses are not checked.';
2020
$arrLang['statusCodes'][LinkChecker::STATUS_INVALID] = 'Invalid URL, cannot be checked.';
21+
$arrLang['statusCodes'][LinkChecker::STATUS_TIMEOUT] = 'Request timed out.';

src/Resources/contao/templates/backend/be_linkchecker.html5

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
<?php foreach ($this->links as $objLink): ?>
1010
<tr>
1111
<td class="link">
12-
<span title="<?= $objLink->title; ?>" class="url" data-linkchecker="true" data-target="<?= $objLink->target; ?>"
12+
<span title="<?= $objLink->title; ?>" class="url" data-linkchecker="true" data-target="<?= $objLink->target; ?>" data-timeout="<?= $objLink->timeout; ?>"
1313
data-url="<?= $objLink->url; ?>"><?= $objLink->text; ?></span>
1414
</td>
1515
<td class="status" id="<?= $objLink->targetID; ?>"><span class="lc-loading-indicator"></span></td>
1616
</tr>
1717
<?php endforeach; ?>
1818
</tbody>
19-
</table>
19+
</table>

src/Resources/public/js/contao-linkchecker-bundle.es6.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@
55
this.element = element;
66
this.options = options;
77
this.url = this.element.getAttribute('data-url');
8+
this.timeout = parseInt(this.element.getAttribute('data-timeout'), 10);
89
this.element.linkchecker = this;
910

11+
if (isNaN(this.timeout) || this.timeout < 1) {
12+
this.timeout = 10;
13+
}
14+
1015
if (this.element.getAttribute('data-target')) {
1116
this.target = document.querySelector(this.element.getAttribute('data-target'));
1217
}
@@ -39,6 +44,7 @@
3944

4045
let xhr = new XMLHttpRequest();
4146
xhr.open(method, url, true);
47+
xhr.timeout = (_this.timeout + 2) * 1000;
4248
xhr.withCredentials = !!_this.options.withCredentials;
4349
response = null;
4450
updateProgress = function() {
@@ -72,6 +78,16 @@
7278
}
7379
};
7480
xhr.onerror = function() {
81+
_this._failed();
82+
83+
reject({
84+
status: this.status,
85+
statusText: xhr.statusText
86+
});
87+
};
88+
xhr.ontimeout = function() {
89+
_this._failed();
90+
7591
reject({
7692
status: this.status,
7793
statusText: xhr.statusText
@@ -101,6 +117,7 @@
101117
formData.append(key, value);
102118
}
103119
}
120+
formData.append('lc_timeout', _this.timeout);
104121

105122
_requests.push(xhr);
106123

@@ -124,6 +141,12 @@
124141
return false;
125142
};
126143

144+
LinkChecker.prototype._failed = function() {
145+
this.target.innerHTML = '-';
146+
147+
return false;
148+
};
149+
127150
LinkCheckerRegistry = {
128151
init: function() {
129152
this.register();

src/Resources/public/js/linkchecker.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,13 @@
240240
this.element = element;
241241
this.options = options;
242242
this.url = this.element.getAttribute('data-url');
243+
this.timeout = parseInt(this.element.getAttribute('data-timeout'), 10);
243244
this.element.linkchecker = this;
244245

246+
if (isNaN(this.timeout) || this.timeout < 1) {
247+
this.timeout = 10;
248+
}
249+
245250
if (this.element.getAttribute('data-target')) {
246251
this.target = document.querySelector(this.element.getAttribute('data-target'));
247252
}
@@ -276,6 +281,7 @@
276281

277282
var xhr = new XMLHttpRequest();
278283
xhr.open(method, url, true);
284+
xhr.timeout = (_this.timeout + 2) * 1000;
279285
xhr.withCredentials = !!_this.options.withCredentials;
280286
response = null;
281287
updateProgress = function() {
@@ -309,6 +315,16 @@
309315
}
310316
};
311317
xhr.onerror = function () {
318+
_this._failed();
319+
320+
reject({
321+
status: this.status,
322+
statusText: xhr.statusText
323+
});
324+
};
325+
xhr.ontimeout = function () {
326+
_this._failed();
327+
312328
reject({
313329
status: this.status,
314330
statusText: xhr.statusText
@@ -338,6 +354,7 @@
338354
formData.append(key, value);
339355
}
340356
}
357+
formData.append('lc_timeout', _this.timeout);
341358

342359
_requests.push(xhr);
343360

@@ -361,6 +378,12 @@
361378
return false;
362379
};
363380

381+
LinkChecker.prototype._failed = function () {
382+
this.target.innerHTML = '-';
383+
384+
return false;
385+
};
386+
364387
LinkCheckerRegistry = {
365388
init: function () {
366389
this.register();

0 commit comments

Comments
 (0)