StaticSiteRewriteLinksTask   F
last analyzed

Complexity

Total Complexity 68

Size/Duplication

Total Lines 571
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 232
dl 0
loc 571
rs 2.96
c 1
b 1
f 0
wmc 68

15 Methods

Rating   Name   Duplication   Size   Complexity  
A urlUnrewritable() 0 28 5
B checkInputs() 0 25 7
A pushFailedRewrite() 0 7 1
B countFailureTypes() 0 29 6
A printMessage() 0 12 4
A linkIsThirdParty() 0 4 1
A linkIsBadScheme() 0 6 2
A linkIsNotImported() 0 3 2
D run() 0 157 21
A linkIsAlreadyRewritten() 0 3 2
A writeFailedRewrites() 0 41 5
A setContentSourceID() 0 3 1
A linkIsJunk() 0 3 1
A printTaskInfo() 0 21 5
A badLinkType() 0 15 5

How to fix   Complexity   

Complex Class

Complex classes like StaticSiteRewriteLinksTask often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StaticSiteRewriteLinksTask, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpTek\Exodus\Task;
4
5
use PhpTek\Exodus\Model\FailedURLRewriteObject;
6
use PhpTek\Exodus\Model\FailedURLRewriteSummary;
7
use PhpTek\Exodus\Model\StaticSiteContentSource;
8
use PhpTek\Exodus\Model\StaticSiteImportDataObject;
9
use PhpTek\Exodus\Tool\StaticSiteLinkRewriter;
10
use PhpTek\Exodus\Tool\StaticSiteUtils;
11
use PhpTek\Exodus\Report\FailedURLRewriteReport;
12
use SilverStripe\Assets\File;
13
use SilverStripe\Dev\BuildTask;
14
use SilverStripe\Control\Director;
15
use SilverStripe\Control\Controller;
16
use SilverStripe\ORM\DataObject;
17
use SilverStripe\Versioned\Versioned;
18
use SilverStripe\Forms\FormField;
19
20
/**
21
 * Rewrites content-links found in `img.src` and `a.href` HTML element attributes,
22
 * which were originally imported via {@link StaticSiteImporter}.
23
 *
24
 * The task takes two arguments:
25
 *
26
 * - ImportID:
27
 *   Allows the rewriter to know which content to rewrite, when duplicate imports exist.
28
 *
29
 * - SourceID
30
 *   Allows the rewriter to fetch the correct content relative to the given source of scraped URLs.
31
 *
32
 * - SilentRun
33
 *   Allows the rewriter to run silently, without displaying anything to stdout. This is the
34
 *   default for when the task is run from the front-end.
35
 *
36
 * All rewrite failures are written to an individual {@link FailedURLRewriteObject} DataObject to power the
37
 * CMS {@link FailedURLRewriteReport}. See also {@link FailedURLRewriteSummary}.
38
 *
39
 * @author Sam Minnee <[email protected]>
40
 * @author Russell Michell <[email protected]>
41
 * @author Michael Parkhill <[email protected]>
42
 * @package phptek/silverstripe-exodus
43
 */
44
class StaticSiteRewriteLinksTask extends BuildTask
45
{
46
    /**
47
     * An inexhaustive list of non http(s) URI schemes which we don't want to try to normalise.
48
     *
49
     * @see http://en.wikipedia.org/wiki/URI_scheme
50
     * @var array
51
     */
52
    private static $non_http_uri_schemes = [
53
        'mailto',
54
        'tel',
55
        'htp',
56
        'ftp',
57
        'ftps',
58
        'res',
59
        'skype',
60
        'ssh',
61
        'telnet',
62
        'gopher',
63
        'rtsp',
64
    ];
65
66
    /**
67
     *
68
     * @var string The prefix to use for the log-file summary.
69
     */
70
    private static $summary_prefix = 'Import No.';
71
72
    /**
73
     * @var string
74
     */
75
    protected $description = 'Rewrites imported links into SilverStripe compatible format.';
76
77
    /**
78
     *
79
     * @var string
80
     */
81
    public $currentPageID = null;
82
83
    /**
84
     * Stores the dodgy URLs for later analysis
85
     *
86
     * @var array
87
     */
88
    public $listFailedRewrites = [];
89
90
    /**
91
     * The ID number of the StaticSiteContentSource which has the links to be rewritten
92
     *
93
     * @var string
94
     */
95
    protected $contentSourceID;
96
97
    /**
98
     * The import identifier
99
     *
100
     * @var string
101
     */
102
    protected $contentImportID;
103
104
    /**
105
     *
106
     * @var
107
     */
108
    protected $silentRun;
109
110
    /**
111
     * The StaticSiteContentSource which has the links to be rewritten
112
     *
113
     * @var StaticSiteContentSource
114
     */
115
    protected $contentSource = null;
116
117
    /**
118
     *
119
     * @var string
120
     */
121
    protected $newLine = '';
122
123
    /**
124
     * Starts the task
125
     *
126
     * @param HTTPRequest $request The request parameter passed from the task initiator, browser or CLI
0 ignored issues
show
Bug introduced by
The type PhpTek\Exodus\Task\HTTPRequest was not found. Did you mean HTTPRequest? If so, make sure to prefix the type with \.
Loading history...
127
     * @return null | void
128
     */
129
    public function run($request)
130
    {
131
        $this->newLine = Director::is_cli() ? PHP_EOL : '<br/>';
132
133
        // Get the StaticSiteContentSource and Import ID from request parameters
134
        $this->contentSourceID = trim($request->getVar('SourceID') ?? '');
135
        $this->contentImportID = trim($request->getVar('ImportID') ?? '');
136
        $this->silentRun = trim($request->getVar('SilentRun') ?? '');
137
138
        if (!$this->checkInputs()) {
139
            return;
140
        }
141
142
        // Load imported objects and filter on ImportID, to know which links should be re-written.
143
        $pages = $this->contentSource->Pages()->filter('StaticSiteImportID', $this->contentImportID);
144
        $files = $this->contentSource->Files()->filter('StaticSiteImportID', $this->contentImportID);
145
146
        $this->printMessage("Processing Import: {$pages->count()} pages, {$files->count()} files", 'NOTICE');
147
148
        if ($pages->count() == 0) {
149
            $this->printMessage("Nothing to rewrite! Did you forget to run an import?", 'NOTICE');
150
        }
151
152
        $pageLookup = $pages;
153
        $fileLookup = $files->map('StaticSiteURL', 'ID');
154
        $task = $this;
155
156
        // Callback for URL rewriter, called from StaticSiteLinkRewriter and passed through $callback($url)
157
        $rewriter = StaticSiteLinkRewriter::create(function ($url) use ($pageLookup, $fileLookup, $task) {
158
            $origUrl = $url;
159
            $anchor = '';
160
161
            if (strpos($url, '#') !== false) {
162
                list($url, $anchor) = explode('#', $url, 2);
163
            }
164
165
            // Just return now if the url is un-rewritable
166
            if ($task->urlUnrewritable($url)) {
167
                // If it's being ignored, log it for a summary used in the CMS report.
168
                $task->pushFailedRewrite($task, $url);
169
                return;
170
            }
171
172
            // Strip the trailing slash, if any
173
            $url = substr($url, -1) == '/' ? rtrim($url, '/') : $url;
174
175
            // The keys to use when URL-matching in $pageLookup and $fileLookup
176
            $pageMapKey = Controller::join_links($task->contentSource->BaseUrl, parse_url($url, PHP_URL_PATH));
0 ignored issues
show
Bug Best Practice introduced by
The property BaseUrl does not exist on PhpTek\Exodus\Model\StaticSiteContentSource. Since you implemented __get, consider adding a @property annotation.
Loading history...
177
            $fileMapKey = Controller::join_links($task->contentSource->BaseUrl, $origUrl);
178
179
            if (strlen($anchor)) {
180
                $task->printMessage("\tFound anchor: '#$anchor' (Removed for matching)");
181
            }
182
183
            $task->printMessage("\tPage-key: '$pageMapKey'");
184
            $task->printMessage("\tFile-key: '$fileMapKey'");
185
186
            $fileLookup = $fileLookup->toArray();
187
188
            /*
189
             * Rewrite SiteTree links by replacing the phpQuery processed Page-URL
190
             * with a CMS shortcode or anchor if one is found in the 'Content' field.
191
             */
192
            if ($siteTreeObject = $pageLookup->find('StaticSiteURL', $pageMapKey)) {
193
                $output = '[sitetree_link,id=' . $siteTreeObject->ID . ']';
194
                $task->printMessage("\tFound: SiteTree ID#" . $siteTreeObject->ID, null, $output);
195
                $anchorPattern = "<[\w]+\s+(name|id)=('|\")?" . $anchor . "('|\")?";
196
197
                if (strlen($anchor) && preg_match("#$anchorPattern#mi", $siteTreeObject->Content)) {
198
                    $output = "#$anchor";
199
                }
200
201
                return $output;
202
                // Rewrite Asset links by replacing phpQuery processed URLs with the appropriate filename.
203
            } elseif (isset($fileLookup[$fileMapKey]) && $fileID = $fileLookup[$fileMapKey]) {
204
                if ($file = DataObject::get_by_id(File::class, $fileID)) {
205
                    $output = $file->RelativeLink();
206
                    $task->printMessage("\tFound: File ID#" . $fileID, null, $output);
207
208
                    return $output;
209
                }
210
            } else {
211
                // Log failures for later analysis
212
                $this->pushFailedRewrite($task, $url);
213
                $task->printMessage("\tRewriter failed. See detail below:");
214
            }
215
216
            return $origUrl;
217
        });
218
219
        // Perform rewriting
220
        $changedFields = 0;
221
222
        foreach ($pages as $i => $page) {
223
            // For the rewriter, so it has some context for urls that couldn't be re-writen.
224
            $this->currentPageID = $page->ID;
225
            $url = $page->StaticSiteURL;
226
227
            // Get the schema that matches the page's legacy url
228
            // TODO should arg 2 for getSchemaForURL() be based on user-input and not baked-in?
229
            if ($schema = $this->contentSource->getSchemaForURL($url, 'text/html')) {
230
                $fields = [];
231
232
                foreach ($schema->ImportRules() as $rule) {
233
                    if (!$rule->PlainText) {
234
                        $fields[] = $rule->FieldName;
235
                    } else {
236
                        $this->printMessage("\tPlain-text enabled in import rule. Skipping.");
237
                    }
238
                }
239
240
                $fields = array_unique($fields);
241
            } else {
242
                $this->printMessage("\tNo schema found for {$page->URLSegment}", 'WARNING');
243
                continue;
244
            }
245
246
            $modified = false;
247
248
            foreach ($fields as $field) {
249
                $task->printMessage("START Rewriter for links in: '$url'");
250
                $newContent = $rewriter->rewriteInContent($page->$field);
251
                // Square-brackets are converted upstream, so change them back.
252
                // Leave this well-alone. Removing it will blank all imported content and titles!
253
                $fieldContent = str_replace(array('%5B', '%5D'), ['[', ']'], $newContent);
254
255
                // If rewrite succeeded, then the content returned should differ from the original
256
                if ($fieldContent != $page->$field) {
257
                    $changedFields++;
258
                    $this->printMessage("\tChanged field: '$field' on page: \"{$page->Title}\" ID: {$page->ID}");
259
                    $page->$field = $fieldContent;
260
                    $modified = true;
261
                } else {
262
                    $task->printMessage("\tNothing to rewrite");
263
                }
264
265
                $task->printMessage("END Rewriter for links in: '$url'");
266
            }
267
268
            if ($modified) {
269
                Versioned::set_reading_mode('Stage.Stage');
270
                $page->write();
271
                $page->publishRecursive();
272
            }
273
        }
274
275
        $newLine = $this->newLine;
276
        $this->printMessage("{$newLine}Complete.");
277
        $this->printMessage("Amended $changedFields content fields for {$pages->count()} pages and {$files->count()} files processed.");
278
279
        $msgNextSteps = " - Not all links will get fixed. It's recommended to also run a 3rd party link-checker over your imported content.";
280
        $msgSeeReport = " - Check the CMS \"" . singleton(FailedURLRewriteReport::class)->title() . "\" report for a summary of failed link-rewrites.";
281
282
        $this->printMessage("Tips:");
283
        $this->printMessage("{$newLine}$msgNextSteps");
284
        $this->printMessage($msgSeeReport);
285
        $this->writeFailedRewrites();
286
    }
287
288
    /**
289
     * Prints notices and warnings and aggregates them into two lists for later analysis,
290
     * depending on $level and whether you're using the CLI or a browser to run the task.
291
     *
292
     * @param string $message The message to log
293
     * @param string $level The log level, e.g. NOTICE or WARNING
294
     * @param string $url The url which was being re-written
295
     * @return mixed null|void
296
     */
297
    public function printMessage($message, $level = null, $url = null)
298
    {
299
        singleton(StaticSiteUtils::class)->log("$level$message$url", '', '');
300
301
        if ($this->silentRun) {
302
            return;
303
        }
304
305
        $url = ($url ? '(' . $url . ') ' : '');
306
        $level = ($level ? '[' . $level . '] ' : '');
307
308
        echo "$level$message$url" . PHP_EOL;
309
    }
310
311
    /**
312
     * Write failed rewrites to the {@link BadImportLog} for later analysis by users
313
     * via the CMS' Report admin.
314
     *
315
     * @return void
316
     * @todo What to do with report summaries when a task for the same import is re-run?
317
     */
318
    public function writeFailedRewrites()
319
    {
320
        $importID = 0;
321
        $postProcessed = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $postProcessed is dead and can be removed.
Loading history...
322
        $uniq = ($this->listFailedRewrites);
323
        foreach ($uniq as $failure) {
324
            $importID = $failure['ImportID']; // Will be the same value each time
325
326
            /*
327
             * Prevent the same bad-link (for the same Import & container page)
328
             * being written.
329
             */
330
            $failureExists = DataObject::get(FailedURLRewriteObject::class)->filter(array(
331
                'ImportID' => $importID,
332
                'OrigUrl' => $failure['OrigUrl'],
333
                'ContainedInID' => $failure['ContainedInID']
334
            ));
335
336
            if ($failureExists->count() > 0) {
337
                continue;
338
            }
339
340
            $failedURLObj = FailedURLRewriteObject::create();
341
            $failedURLObj->BadLinkType = $failure['BadLinkType'];
342
            $failedURLObj->ImportID = $failure['ImportID'];
343
            $failedURLObj->ContainedInID = $failure['ContainedInID'];
344
            $failedURLObj->OrigUrl = $failure['OrigUrl'];
345
            $failedURLObj->write();
346
        }
347
348
        $summaryText = self::$summary_prefix . $this->contentImportID;
349
        foreach ($this->countFailureTypes() as $label => $payload) {
350
            $label = FormField::name_to_label($label) . ': ';
351
            $summaryText .= ' ' . $label . $payload['count'] . ' ' . $payload['desc'] . PHP_EOL;
352
        }
353
354
        // Write the summary, to be shown at the top of the report
355
        $summary = FailedURLRewriteSummary::create();
356
        $summary->Text = $summaryText;
357
        $summary->ImportID = $importID ? $importID : 0;
358
        $summary->write();
359
    }
360
361
    /**
362
     * Returns an array of totals of all the failed URLs, in different categories according to:
363
     * - No. Non $baseURL http(s) URLs
364
     * - No. Non http(s) URI schemes (e.g. mailto, tel etc)
365
     * - No. URLs not imported
366
     * - No. Junk URLs (i.e. those not matching any of the above)
367
     *
368
     * @return array
369
     * @todo too many URLs being collected in $this->listFailedRewrites
370
     */
371
    public function countFailureTypes()
372
    {
373
        $rawData = $this->listFailedRewrites;
374
        $countThirdParty = $countBadScheme = $countNotImported = $countJunk = $countUnknown = 0;
375
        foreach ($rawData as $data) {
376
            $url = $data['OrigUrl'];
377
            if ($this->linkIsJunk($url)) {
378
                ++$countJunk;
379
                continue;
380
            } elseif ($this->linkIsThirdParty($url)) {
381
                ++$countThirdParty;
382
                continue;
383
            } elseif ($this->linkIsBadScheme($url)) {
384
                ++$countBadScheme;
385
                continue;
386
            } elseif ($this->linkIsNotImported($url)) {
387
                ++$countNotImported;
388
                continue;
389
            } else {
390
                ++$countUnknown;
391
            }
392
        }
393
        return [
394
            'Total failed link rewrites'    => ['count' => count($rawData), 'desc' => ''],
395
            'ThirdParty'        => ['count' => $countThirdParty, 'desc' => '(Links to external websites)'],
396
            'BadScheme'         => ['count' => $countBadScheme, 'desc' => '(Links with bad/unimportable scheme)'],
397
            'NotImported'       => ['count' => $countNotImported, 'desc' => '(Links to pages or assets that were not imported)'],
398
            'Junk'                      => ['count' => $countJunk, 'desc' => '(Junk links)'],
399
            'Unknown'           => ['count' => $countUnknown, 'desc' => '(Not categorisable)'],
400
        ];
401
    }
402
403
    /**
404
     * Detects if a link is to a third-party website.
405
     *
406
     * @param string $link
407
     * @return boolean
408
     */
409
    public function linkIsThirdParty($link)
410
    {
411
        $link = trim($link);
412
        return (bool)preg_match("#^http(s)?://#", $link);
413
    }
414
415
    /**
416
     * Detects if a link uses an unsupported protocol (e.g. mailto, tel etc)
417
     *
418
     * @param string $link
419
     * @return boolean
420
     */
421
    public function linkIsBadScheme($link)
422
    {
423
        $nonHTTPSchemes = implode('|', self::$non_http_uri_schemes);
424
        $badScheme = preg_match("#^($nonHTTPSchemes):#", $link);
425
        $alreadyImported = $this->linkIsAlreadyRewritten($link);
426
        return (bool)($badScheme || $alreadyImported);
427
    }
428
429
    /**
430
     * After rewrite task is run, link doesn't match a valid CMS link shortcode.
431
     *
432
     * @param string $link
433
     * @return boolean
434
     */
435
    public function linkIsNotImported($link)
436
    {
437
        return (bool)(stristr($link, 'sitetree') === false && stristr($link, 'assets') === false);
438
    }
439
440
    /**
441
     * Detects if a link has already been re-written.
442
     *
443
     * @param string $link
444
     * @return boolean
445
     */
446
    public function linkIsAlreadyRewritten($link)
447
    {
448
        return (bool)(stristr($link, 'sitetree') !== false || stristr($link, 'assets') !== false);
449
    }
450
451
    /**
452
     * Link begins with non-legitimate character
453
     *
454
     * @param string $link
455
     * @return boolean
456
     */
457
    public function linkIsJunk($link)
458
    {
459
        return (bool)preg_match("#^[^\[\/a-zA-Z\d].+#", $link);
460
    }
461
462
    /**
463
     * What kind of bad link is $link? The returned string should match the ENUM
464
     * values on FailedURLRewriteObject
465
     *
466
     * @param string $link
467
     * @return string
468
     * @todo can we add a check for links with anchors to other pages?
469
     */
470
    public function badLinkType($link)
471
    {
472
        if ($this->linkIsJunk($link)) {
473
            return 'Junk';
474
        }
475
        if ($this->linkIsThirdParty($link)) {
476
            return 'ThirdParty';
477
        }
478
        if ($this->linkIsBadScheme($link)) {
479
            return 'BadScheme';
480
        }
481
        if ($this->linkIsNotImported($link)) {
482
            return 'NotImported';
483
        }
484
        return 'Unknown';
485
    }
486
487
    /**
488
     * Whether or not to ingore a URL. Returns true if a URL is either:
489
     *
490
     *  - An empty string
491
     *  - A non-HTTP scheme like an email link see: self::$non_http_uri_schemes
492
     *  - A CMS sitetree shortcode or file/image asset path, e.g. [sitetree_link,id=1234] or assets/Images/logo.gif
493
     *  - An absolute url, i.e. anything that begins with 'http'
494
     *
495
     * @param string $url A URL
496
     * @return boolean true if the url can be ignored
497
     */
498
    public function urlUnrewritable($url)
499
    {
500
        $url = trim($url);
501
502
        // Link is "Junk"
503
        if ($this->linkIsJunk($url)) {
504
            $this->printMessage("\tIgnoring: $url (Junk URL)");
505
            return true;
506
        }
507
508
        // Not an HTTP protocol
509
        if ($this->linkIsBadScheme($url)) {
510
            $this->printMessage("\tIgnoring: $url (Non-HTTP URL)");
511
            return true;
512
        }
513
514
        // Is external or an absolute url
515
        if ($this->linkIsThirdParty($url)) {
516
            $this->printMessage("\tIgnoring: $url (3rd party URL)");
517
            return true;
518
        }
519
520
        // Has already been processed
521
        if ($this->linkIsAlreadyRewritten($url)) {
522
            $this->printMessage("\tIgnoring: $url (CMS link, already converted)");
523
            return true;
524
        }
525
        return false;
526
    }
527
528
    /**
529
     * Set the ID number of the StaticSiteContentSource
530
     *
531
     * @param int $contentSourceID
532
     * @return void
533
     */
534
    public function setContentSourceID($contentSourceID)
535
    {
536
        $this->contentSourceID = $contentSourceID;
537
    }
538
539
    /**
540
     * Checks the user-passed data is cotia.
541
     *
542
     * @return boolean
543
     */
544
    public function checkInputs(): bool
545
    {
546
        $hasSid = ($this->contentSourceID && is_numeric($this->contentSourceID));
547
        $hasIid = ($this->contentImportID && is_numeric($this->contentImportID));
548
549
        if (!$hasSid || !$hasIid) {
550
            $this->printTaskInfo();
551
            return false;
552
        }
553
554
        // Load the content source using the passed content-source ID and Import ID
555
        $this->contentSource = (StaticSiteContentSource::get()->byID($this->contentSourceID));
0 ignored issues
show
Bug introduced by
$this->contentSourceID of type string is incompatible with the type integer expected by parameter $id of SilverStripe\ORM\DataList::byID(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

555
        $this->contentSource = (StaticSiteContentSource::get()->byID(/** @scrutinizer ignore-type */ $this->contentSourceID));
Loading history...
556
        $contentImport = (StaticSiteImportDataObject::get()->byID($this->contentImportID));
557
558
        if (!$this->contentSource) {
559
            $this->printMessage("No content-source found via SourceID: " . $this->contentSourceID, 'WARNING');
560
            return false;
561
        }
562
563
        if (!$contentImport) {
564
            $this->printMessage("No content-import found via ImportID: " . $this->contentImportID, 'WARNING');
565
            return false;
566
        }
567
568
        return true;
569
    }
570
571
    /**
572
     * Prints information on the options available for running the task and
573
     * debugging and usage examples.
574
     *
575
     * @return void
576
     */
577
    public function printTaskInfo()
578
    {
579
        $msgFragment = (Director::is_cli() ? '' : '?') . 'SourceID=(number) ImportID=(number)';
580
        $this->printMessage("Choose a SourceID and an ImportID e.g. $msgFragment", 'WARNING');
581
        $newLine = $this->newLine;
582
583
        // List the content sources to prompt user for selection
584
        if ($contentSources = StaticSiteContentSource::get()) {
585
            $this->printMessage($newLine . 'Available content-sources:' . $newLine);
586
            foreach ($contentSources as $i => $contentSource) {
587
                $this->printMessage("\tdev/tasks/" . __CLASS__ . ' SourceID=' . $contentSource->ID . ' ImportID=<number>');
588
            }
589
            echo $newLine;
590
            if (Director::is_cli()) {
591
                $this->printMessage('Available command line options: ' . $newLine);
592
                $this->printMessage("\tSourceID=<number> \t\tThe ID of the original crawl.");
593
                $this->printMessage("\tImportID=<number> \t\tThe ID of the import to use.");
594
                $this->printMessage("\tSHOW=pages \tPrint the contents of the pages map.");
595
                $this->printMessage("\tSHOW=files \tPrint the contents of the files map.");
596
            }
597
            echo $newLine;
598
        }
599
    }
600
601
    /**
602
     * Build an array of failed URL rewrites for later reporting.
603
     *
604
     * @param StaticSiteRewriteLinksTask $obj
605
     * @param string $link
606
     * @return void
607
     */
608
    protected function pushFailedRewrite($obj, $link): void
609
    {
610
        array_push($obj->listFailedRewrites, [
611
            'OrigUrl' => $link,
612
            'ImportID' => $obj->contentImportID,
613
            'ContainedInID' => $obj->currentPageID,
614
            'BadLinkType' => $obj->badLinkType($link),
615
        ]);
616
    }
617
}
618