FileController   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 399
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
wmc 37
eloc 224
c 4
b 1
f 0
dl 0
loc 399
rs 9.44

8 Methods

Rating   Name   Duplication   Size   Complexity  
A importCsvApi8() 0 33 5
B importCsvApi9() 0 31 6
A actionExportStatistics() 0 9 2
B actionImportCsv() 0 73 7
A exportCsvFile() 0 24 3
C actionImportCsvColumns() 0 62 10
A actionDisplayErrors() 0 39 2
A actionExportRedirects() 0 9 2
1
<?php
2
/**
3
 * Retour plugin for Craft CMS
4
 *
5
 * Retour allows you to intelligently redirect legacy URLs, so that you don't
6
 * lose SEO value when rebuilding & restructuring a website
7
 *
8
 * @link      https://nystudio107.com/
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
9
 * @copyright Copyright (c) 2018 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
10
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
11
12
namespace nystudio107\retour\controllers;
13
14
use Craft;
15
use craft\db\Query;
16
use craft\errors\MissingComponentException;
17
use craft\helpers\ArrayHelper;
18
use craft\helpers\UrlHelper;
19
use craft\web\Controller;
20
use League\Csv\AbstractCsv;
21
use League\Csv\Exception;
22
use League\Csv\Reader;
23
use League\Csv\Statement;
24
use League\Csv\Writer;
25
use nystudio107\retour\assetbundles\retour\RetourImportAsset;
26
use nystudio107\retour\helpers\FileLog;
27
use nystudio107\retour\helpers\MultiSite as MultiSiteHelper;
28
use nystudio107\retour\helpers\Permission as PermissionHelper;
29
use nystudio107\retour\helpers\Version as VersionHelper;
30
use nystudio107\retour\Retour;
31
use SplTempFileObject;
32
use yii\base\InvalidConfigException;
33
use yii\web\BadRequestHttpException;
34
use yii\web\ForbiddenHttpException;
35
use yii\web\MethodNotAllowedHttpException;
36
use yii\web\NotFoundHttpException;
37
use yii\web\Response;
38
use yii\web\UploadedFile;
39
40
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
41
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
42
 * @package   Retour
0 ignored issues
show
Coding Style introduced by
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
43
 * @since     3.0.0
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
Coding Style introduced by
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
44
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
45
class FileController extends Controller
46
{
47
    // Constants
48
    // =========================================================================
49
50
    protected const DOCUMENTATION_URL = 'https://github.com/nystudio107/craft-retour/';
51
52
    public const LOG_FILE_NAME = 'retour-csv-import-errors';
53
54
    protected const EXPORT_REDIRECTS_CSV_FIELDS = [
55
        'redirectSrcUrl' => 'Legacy URL Pattern',
56
        'redirectDestUrl' => 'Redirect To',
57
        'redirectMatchType' => 'Match Type',
58
        'redirectHttpCode' => 'HTTP Status',
59
        'siteId' => 'Site ID',
60
        'redirectSrcMatch' => 'Legacy URL Match Type',
61
        'hitCount' => 'Hits',
62
        'associatedElementId' => 'Short Link Element ID',
63
        'hitLastTime' => 'Last Hit',
64
        'priority' => 'Priority',
65
    ];
66
67
    protected const EXPORT_STATISTICS_CSV_FIELDS = [
68
        'redirectSrcUrl' => '404 File Not Found URL',
69
        'referrerUrl' => 'Last Referrer URL',
70
        'remoteIp' => 'Remote IP',
71
        'hitCount' => 'Hits',
72
        'hitLastTime' => 'Last Hit',
73
        'handledByRetour' => 'Handled',
74
        'siteId' => 'Site ID',
75
    ];
76
77
    protected const IMPORT_REDIRECTS_CSV_FIELDS = [
78
        'redirectSrcUrl',
79
        'redirectDestUrl',
80
        'redirectMatchType',
81
        'redirectHttpCode',
82
        'siteId',
83
        'redirectSrcMatch',
84
        'hitCount',
85
        'associatedElementId',
86
        'priority',
87
    ];
88
89
    // Protected Properties
90
    // =========================================================================
91
92
    protected array|bool|int $allowAnonymous = [];
93
94
    // Public Methods
95
    // =========================================================================
96
97
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
98
     * @throws BadRequestHttpException
99
     * @throws Exception
100
     * @throws ForbiddenHttpException
101
     * @throws MissingComponentException
102
     * @throws MethodNotAllowedHttpException
103
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
104
    public function actionImportCsvColumns(): void
105
    {
106
        PermissionHelper::controllerPermissionCheck('retour:redirects');
107
        // If your CSV document was created or is read on a Macintosh computer,
108
        // add the following lines before using the library to help PHP detect line ending in Mac OS X
109
        if (!ini_get('auto_detect_line_endings')) {
110
            ini_set('auto_detect_line_endings', '1');
111
        }
112
        $csv = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $csv is dead and can be removed.
Loading history...
113
        $this->requirePostRequest();
114
        $filename = Craft::$app->getRequest()->getRequiredBodyParam('filename');
115
        $columns = Craft::$app->getRequest()->getRequiredBodyParam('columns');
116
        $headers = null;
117
        // Log the import
118
        Filelog::delete(self::LOG_FILE_NAME);
119
        FileLog::create(self::LOG_FILE_NAME, 'nystudio107\retour\*');
120
        try {
121
            $csv = Reader::createFromPath($filename);
122
            $csv->setDelimiter(Retour::$settings->csvColumnDelimiter ?? ',');
123
            $headers = array_flip($csv->fetchOne(0));
0 ignored issues
show
Deprecated Code introduced by
The function League\Csv\Reader::fetchOne() has been deprecated: since version 9.9.0 ( Ignorable by Annotation )

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

123
            $headers = array_flip(/** @scrutinizer ignore-deprecated */ $csv->fetchOne(0));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
124
        } catch (\Exception $e) {
125
            // If this throws an exception, try to read the CSV file from the data cache
126
            // This can happen on load balancer setups where the Craft temp directory isn't shared
127
            $cache = Craft::$app->getCache();
128
            $cachedFile = $cache->get($filename);
129
            if ($cachedFile !== false) {
130
                $csv = Reader::createFromString($cachedFile);
131
                try {
132
                    $csv->setDelimiter(Retour::$settings->csvColumnDelimiter ?? ',');
133
                } catch (Exception $e) {
134
                    Craft::error($e, __METHOD__);
135
                }
136
                $headers = array_flip($csv->fetchOne(0));
0 ignored issues
show
Deprecated Code introduced by
The function League\Csv\Reader::fetchOne() has been deprecated: since version 9.9.0 ( Ignorable by Annotation )

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

136
                $headers = array_flip(/** @scrutinizer ignore-deprecated */ $csv->fetchOne(0));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
137
                $cache->delete($filename);
138
            } else {
139
                Craft::error("Could not import ${$filename} from the file system, or the cache.", __METHOD__);
140
            }
141
        }
142
        $hasErrors = false;
143
        // If we have headers, then we have a file, so parse it
144
        if ($csv && $headers) {
145
            switch (VersionHelper::getLeagueCsvVersion()) {
146
                case 8:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
147
                    $hasErrors = $this->importCsvApi8($csv, $columns, $headers);
148
                    break;
149
                case 9:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
150
                    $hasErrors = $this->importCsvApi9($csv, $columns, $headers);
151
                    break;
152
                default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
153
                    Craft::$app->getSession()->setNotice(Craft::t('retour', 'Unknown league/csv package API version'));
154
                    break;
155
            }
156
            @unlink($filename);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

156
            /** @scrutinizer ignore-unhandled */ @unlink($filename);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
157
            Retour::$plugin->clearAllCaches();
0 ignored issues
show
Bug introduced by
The method clearAllCaches() does not exist on null. ( Ignorable by Annotation )

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

157
            Retour::$plugin->/** @scrutinizer ignore-call */ 
158
                             clearAllCaches();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
158
            Craft::$app->getSession()->setNotice(Craft::t('retour', 'Redirects imported from CSV file.'));
159
        } else {
160
            Craft::$app->getSession()->setError(Craft::t('retour', 'Redirects could not be imported.'));
161
        }
162
        if ($hasErrors) {
163
            $this->redirect(UrlHelper::actionUrl('retour/file/display-errors'));
164
        } else {
165
            $this->redirect('retour/redirects');
166
        }
167
    }
168
169
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
170
     * @param string|null $siteHandle
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
171
     *
172
     * @return Response
173
     * @throws ForbiddenHttpException
174
     * @throws NotFoundHttpException
175
     */
176
    public function actionImportCsv(string $siteHandle = null): Response
177
    {
178
        $variables = [];
179
        PermissionHelper::controllerPermissionCheck('retour:redirects');
180
        // If your CSV document was created or is read on a Macintosh computer,
181
        // add the following lines before using the library to help PHP detect line ending in Mac OS X
182
        if (!ini_get('auto_detect_line_endings')) {
183
            ini_set('auto_detect_line_endings', '1');
184
        }
185
        // Get the site to edit
186
        $siteId = MultiSiteHelper::getSiteIdFromHandle($siteHandle);
187
        $pluginName = Retour::$settings->pluginName;
188
        $templateTitle = Craft::t('retour', 'Import CSV File');
189
        $view = Craft::$app->getView();
190
        // Asset bundle
191
        try {
192
            $view->registerAssetBundle(RetourImportAsset::class);
193
        } catch (InvalidConfigException $e) {
194
            Craft::error($e->getMessage(), __METHOD__);
195
        }
196
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
197
            '@nystudio107/retour/web/assets/dist',
198
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

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

198
        /** @scrutinizer ignore-call */ 
199
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
199
        );
200
        // Enabled sites
201
        MultiSiteHelper::setMultiSiteVariables($siteHandle, $siteId, $variables);
202
        $variables['controllerHandle'] = 'file';
203
204
        // Basic variables
205
        $variables['fullPageForm'] = true;
206
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
207
        $variables['pluginName'] = $pluginName;
208
        $variables['title'] = $templateTitle;
209
        $siteHandleUri = Craft::$app->isMultiSite ? '/' . $siteHandle : '';
210
        $variables['crumbs'] = [
211
            [
212
                'label' => $pluginName,
213
                'url' => UrlHelper::cpUrl('retour'),
214
            ],
215
            [
216
                'label' => 'Redirects',
217
                'url' => UrlHelper::cpUrl('retour/redirects' . $siteHandleUri),
218
            ],
219
        ];
220
        $variables['docTitle'] = "{$pluginName} - Redirects - {$templateTitle}";
221
        $variables['selectedSubnavItem'] = 'redirects';
222
223
        // The CSV file
224
        $file = UploadedFile::getInstanceByName('file');
225
        if ($file !== null) {
226
            $filename = uniqid($file->name, true);
227
            $filePath = Craft::$app->getPath()->getTempPath() . DIRECTORY_SEPARATOR . $filename;
228
            $file->saveAs($filePath, false);
229
            // Also save the file to the cache as a backup way to access it
230
            $fileContents = @file_get_contents($filePath);
231
            if ($fileContents) {
232
                Craft::$app->getCache()->set($filePath, $fileContents);
233
            }
234
            // Read in the headers
235
            $csv = Reader::createFromPath($file->tempName);
236
            try {
237
                $csv->setDelimiter(Retour::$settings->csvColumnDelimiter ?? ',');
238
            } catch (Exception $e) {
239
                Craft::error($e, __METHOD__);
240
            }
241
            $headers = $csv->fetchOne(0);
0 ignored issues
show
Deprecated Code introduced by
The function League\Csv\Reader::fetchOne() has been deprecated: since version 9.9.0 ( Ignorable by Annotation )

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

241
            $headers = /** @scrutinizer ignore-deprecated */ $csv->fetchOne(0);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
242
            Craft::info(print_r($headers, true), __METHOD__);
0 ignored issues
show
Bug introduced by
It seems like print_r($headers, true) can also be of type true; however, parameter $message of yii\BaseYii::info() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

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

242
            Craft::info(/** @scrutinizer ignore-type */ print_r($headers, true), __METHOD__);
Loading history...
243
            $variables['headers'] = $headers;
244
            $variables['filename'] = $filePath;
245
        }
246
247
        // Render the template
248
        return $this->renderTemplate('retour/import/index', $variables);
249
    }
250
251
    /**
252
     * Display the error log if something went wrong
253
     *
254
     * @return Response
255
     * @throws \yii\web\ForbiddenHttpException
256
     */
257
    public function actionDisplayErrors(): Response
258
    {
259
        $variables = [];
260
        PermissionHelper::controllerPermissionCheck('retour:redirects');
261
        $pluginName = Retour::$settings->pluginName;
262
        $templateTitle = Craft::t('retour', 'CSV File Import Errors');
263
        $view = Craft::$app->getView();
264
        // Asset bundle
265
        try {
266
            $view->registerAssetBundle(RetourImportAsset::class);
267
        } catch (InvalidConfigException $e) {
268
            Craft::error($e->getMessage(), __METHOD__);
269
        }
270
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
271
            '@nystudio107/retour/web/assets/dist',
272
            true
0 ignored issues
show
Unused Code introduced by
The call to yii\web\AssetManager::getPublishedUrl() has too many arguments starting with true. ( Ignorable by Annotation )

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

272
        /** @scrutinizer ignore-call */ 
273
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
273
        );
274
        // Basic variables
275
        $variables['fullPageForm'] = true;
276
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
277
        $variables['pluginName'] = $pluginName;
278
        $variables['title'] = $templateTitle;
279
        $variables['crumbs'] = [
280
            [
281
                'label' => $pluginName,
282
                'url' => UrlHelper::cpUrl('retour'),
283
            ],
284
            [
285
                'label' => 'Redirects',
286
                'url' => UrlHelper::cpUrl('retour/redirects'),
287
            ],
288
        ];
289
        $variables['docTitle'] = "{$pluginName} - Redirects - {$templateTitle}";
290
        $variables['selectedSubnavItem'] = 'redirects';
291
        // The error log
292
        $variables['errorLogContents'] = FileLog::getContents(self::LOG_FILE_NAME);
293
294
        // Render the template
295
        return $this->renderTemplate('retour/import/errors', $variables);
296
    }
297
298
    /**
299
     * Export the statistics table as a CSV file
300
     *
301
     * @throws ForbiddenHttpException
302
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
303
    public function actionExportStatistics(): void
304
    {
305
        PermissionHelper::controllerPermissionCheck('retour:redirects');
306
        //Allow the fields to be localized
307
        $fields = self::EXPORT_STATISTICS_CSV_FIELDS;
308
        foreach ($fields as $key => $field) {
309
            $fields[$key] = Craft::t('retour', $field);
310
        }
311
        $this->exportCsvFile('retour-statistics', '{{%retour_stats}}', $fields);
312
    }
313
314
    /**
315
     * Export the redirects table as a CSV file
316
     *
317
     * @throws ForbiddenHttpException
318
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
319
    public function actionExportRedirects(): void
320
    {
321
        PermissionHelper::controllerPermissionCheck('retour:redirects');
322
        //Allow the fields to be localized
323
        $fields = self::EXPORT_REDIRECTS_CSV_FIELDS;
324
        foreach ($fields as $key => $field) {
325
            $fields[$key] = Craft::t('retour', $field);
326
        }
327
        $this->exportCsvFile('retour-redirects', '{{%retour_static_redirects}}', $fields);
328
    }
329
330
331
    // Public Methods
332
    // =========================================================================
333
334
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
335
     * @param AbstractCsv $csv
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
336
     * @param array $columns
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 7 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
337
     * @param array $headers
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 7 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
338
     * @return bool whether the import has any errors
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
339
     */
340
    protected function importCsvApi8(AbstractCsv $csv, array $columns, array $headers): bool
341
    {
342
        $hasErrors = false;
343
        /** @phpstan-ignore-next-line */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
344
        $csv->setOffset(1);
0 ignored issues
show
Bug introduced by
The method setOffset() does not exist on League\Csv\AbstractCsv. ( Ignorable by Annotation )

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

344
        $csv->/** @scrutinizer ignore-call */ 
345
              setOffset(1);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
345
        $columns = ArrayHelper::filterEmptyStringsFromArray($columns);
346
        $rowIndex = 1;
347
        /** @phpstan-ignore-next-line */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
348
        $csv->each(function ($row) use ($headers, $columns, &$rowIndex, &$hasErrors) {
0 ignored issues
show
Bug introduced by
The method each() does not exist on League\Csv\AbstractCsv. It seems like you code against a sub-type of League\Csv\AbstractCsv such as League\Csv\Reader. ( Ignorable by Annotation )

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

348
        $csv->/** @scrutinizer ignore-call */ 
349
              each(function ($row) use ($headers, $columns, &$rowIndex, &$hasErrors) {
Loading history...
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
349
            $redirectConfig = [
350
                'id' => 0,
351
            ];
352
            $index = 0;
353
            foreach (self::IMPORT_REDIRECTS_CSV_FIELDS as $importField) {
354
                if (isset($columns[$index], $headers[$columns[$index]])) {
355
                    $redirectConfig[$importField] = empty($row[$headers[$columns[$index]]])
356
                        ? null
357
                        : $row[$headers[$columns[$index]]];
358
                }
359
                $index++;
360
            }
361
            $redirectDump = print_r($redirectConfig, true);
362
            Craft::debug("-> ROW #$rowIndex contents: " . $redirectDump, __METHOD__);
0 ignored issues
show
Bug introduced by
Are you sure $redirectDump of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

362
            Craft::debug("-> ROW #$rowIndex contents: " . /** @scrutinizer ignore-type */ $redirectDump, __METHOD__);
Loading history...
363
            if (!Retour::$plugin->redirects->saveRedirect($redirectConfig)) {
0 ignored issues
show
Bug introduced by
The method saveRedirect() does not exist on null. ( Ignorable by Annotation )

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

363
            if (!Retour::$plugin->redirects->/** @scrutinizer ignore-call */ saveRedirect($redirectConfig)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
364
                Craft::info("-> ROW #$rowIndex contents: " . $redirectDump, __METHOD__);
365
                $hasErrors = true;
366
            }
367
            $rowIndex++;
368
369
            return true;
370
        });
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
371
372
        return $hasErrors;
373
    }
374
375
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
376
     * @param Reader $csv
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
377
     * @param array $columns
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
378
     * @param array $headers
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
379
     * @return bool whether the import has any errors
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
380
     * @throws Exception
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
381
     */
382
    protected function importCsvApi9(Reader $csv, array $columns, array $headers): bool
383
    {
384
        $hasErrors = false;
385
        $stmt = (new Statement())
386
            ->offset(1);
387
        $rows = $stmt->process($csv);
388
        $columns = ArrayHelper::filterEmptyStringsFromArray($columns);
389
        $rowIndex = 1;
390
        foreach ($rows as $row) {
391
            $redirectConfig = [
392
                'id' => 0,
393
            ];
394
            $index = 0;
395
            foreach (self::IMPORT_REDIRECTS_CSV_FIELDS as $importField) {
396
                if (isset($columns[$index], $headers[$columns[$index]])) {
397
                    $redirectConfig[$importField] = empty($row[$headers[$columns[$index]]])
398
                        ? null
399
                        : $row[$headers[$columns[$index]]];
400
                }
401
                $index++;
402
            }
403
            $redirectDump = print_r($redirectConfig, true);
404
            Craft::debug("-> ROW #$rowIndex contents: " . $redirectDump, __METHOD__);
0 ignored issues
show
Bug introduced by
Are you sure $redirectDump of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

404
            Craft::debug("-> ROW #$rowIndex contents: " . /** @scrutinizer ignore-type */ $redirectDump, __METHOD__);
Loading history...
405
            if (!Retour::$plugin->redirects->saveRedirect($redirectConfig)) {
406
                Craft::info("-> ROW #$rowIndex contents: " . $redirectDump, __METHOD__);
407
                $hasErrors = true;
408
            }
409
            $rowIndex++;
410
        }
411
412
        return $hasErrors;
413
    }
414
415
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
416
     * @param string $filename
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
417
     * @param string $table
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
418
     * @param array $columns
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
419
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
420
    protected function exportCsvFile(string $filename, string $table, array $columns): void
421
    {
422
        // If your CSV document was created or is read on a Macintosh computer,
423
        // add the following lines before using the library to help PHP detect line ending in Mac OS X
424
        if (!ini_get('auto_detect_line_endings')) {
425
            ini_set('auto_detect_line_endings', '1');
426
        }
427
        // Query the db table
428
        $data = (new Query())
429
            ->from([$table])
430
            ->select(array_keys($columns))
431
            ->orderBy('hitCount DESC')
432
            ->all();
433
        // Create our CSV file writer
434
        $csv = Writer::createFromFileObject(new SplTempFileObject());
435
        try {
436
            $csv->setDelimiter(Retour::$settings->csvColumnDelimiter ?? ',');
437
        } catch (Exception $e) {
438
            Craft::error($e, __METHOD__);
439
        }
440
        $csv->insertOne(array_values($columns));
441
        $csv->insertAll($data);
442
        $csv->output($filename . '.csv');
443
        exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
444
    }
445
}
446