Passed
Push — develop ( ee74a6...6a3189 )
by Andrew
05:01
created

FileController::actionImportCsvColumns()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 42
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 28
dl 0
loc 42
rs 8.8497
c 0
b 0
f 0
cc 6
nc 2
nop 0
1
<?php
2
/**
3
 * Retour plugin for Craft CMS 3.x
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/
9
 * @copyright Copyright (c) 2018 nystudio107
10
 */
11
12
namespace nystudio107\retour\controllers;
13
14
use nystudio107\retour\Retour;
15
use nystudio107\retour\assetbundles\retour\RetourImportAsset;
16
use nystudio107\retour\helpers\MultiSite as MultiSiteHelper;
17
use nystudio107\retour\helpers\Permission as PermissionHelper;
18
19
use Craft;
20
use craft\db\Query;
21
use craft\helpers\ArrayHelper;
22
use craft\helpers\UrlHelper;
23
use craft\web\Controller;
24
25
use yii\base\InvalidConfigException;
26
use yii\web\Response;
27
use yii\web\UploadedFile;
28
29
use League\Csv\Reader;
30
use League\Csv\Writer;
31
32
/**
33
 * @author    nystudio107
34
 * @package   Retour
35
 * @since     3.0.0
36
 */
37
class FileController extends Controller
38
{
39
    // Constants
40
    // =========================================================================
41
42
    const DOCUMENTATION_URL = 'https://github.com/nystudio107/craft-retour/';
43
44
    const REDIRECTS_CSV_FIELDS = [
45
        'redirectSrcUrl' => 'Legacy URL Pattern',
46
        'redirectDestUrl' => 'Redirect To',
47
        'redirectMatchType' => 'Match Type',
48
        'redirectHttpCode' => 'HTTP Status',
49
        'hitCount' => 'Hits',
50
        'hitLastTime' => 'Last Hit',
51
    ];
52
53
    const STATISTICS_CSV_FIELDS = [
54
        'redirectSrcUrl' => '404 File Not Found URL',
55
        'referrerUrl' => 'Last Referrer URL',
56
        'hitCount' => 'Hits',
57
        'hitLastTime' => 'Last Hit',
58
        'handledByRetour' => 'Handled',
59
    ];
60
61
    // Protected Properties
62
    // =========================================================================
63
64
    protected $allowAnonymous = [];
65
66
    // Public Methods
67
    // =========================================================================
68
69
    /**
70
     * @throws \yii\web\BadRequestHttpException
71
     * @throws \yii\web\ForbiddenHttpException
72
     * @throws \craft\errors\MissingComponentException
73
     */
74
    public function actionImportCsvColumns()
75
    {
76
        PermissionHelper::controllerPermissionCheck('retour:redirects');
77
        // If your CSV document was created or is read on a Macintosh computer,
78
        // add the following lines before using the library to help PHP detect line ending in Mac OS X
79
        if (!ini_get('auto_detect_line_endings')) {
80
            ini_set('auto_detect_line_endings', '1');
81
        }
82
        $this->requirePostRequest();
83
        $filename = Craft::$app->getRequest()->getRequiredBodyParam('filename');
84
        $columns = Craft::$app->getRequest()->getRequiredBodyParam('columns');
85
        $csv = Reader::createFromPath($filename);
86
        $headers = array_flip($csv->fetchOne(0));
87
        $csv->setOffset(1);
88
        $columns = ArrayHelper::filterEmptyStringsFromArray($columns);
0 ignored issues
show
Bug introduced by
It seems like $columns can also be of type string; however, parameter $arr of craft\helpers\ArrayHelpe...EmptyStringsFromArray() does only seem to accept array, 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

88
        $columns = ArrayHelper::filterEmptyStringsFromArray(/** @scrutinizer ignore-type */ $columns);
Loading history...
89
        $csv->each(function ($row) use ($headers, $columns) {
90
            $redirectConfig = [
91
                'id' => 0,
92
            ];
93
            if (isset($columns[0], $headers[$columns[0]])) {
94
                $redirectConfig['redirectSrcUrl'] = $row[$headers[$columns[0]]] ?? null;
95
            }
96
            if (isset($columns[1], $headers[$columns[1]])) {
97
                $redirectConfig['redirectDestUrl'] = $row[$headers[$columns[1]]] ?? null;
98
            }
99
            if (isset($columns[2], $headers[$columns[2]])) {
100
                $redirectConfig['redirectMatchType'] = $row[$headers[$columns[2]]] ?? null;
101
            }
102
            if (isset($columns[3], $headers[$columns[3]])) {
103
                $redirectConfig['redirectHttpCode'] = $row[$headers[$columns[3]]] ?? null;
104
            }
105
            Craft::debug('Importing row: '.print_r($redirectConfig, true), __METHOD__);
106
            Retour::$plugin->redirects->saveRedirect($redirectConfig);
107
108
            return true;
109
        });
110
111
        @unlink($filename);
0 ignored issues
show
Bug introduced by
It seems like $filename can also be of type array and array; however, parameter $filename of unlink() does only seem to accept 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

111
        @unlink(/** @scrutinizer ignore-type */ $filename);
Loading history...
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

111
        /** @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...
112
        Retour::$plugin->clearAllCaches();
113
        Craft::$app->getSession()->setNotice(Craft::t('retour', 'Redirects imported from CSV file.'));
114
115
        $this->redirect('retour/redirects');
116
    }
117
118
    /**
119
     * @param string|null $siteHandle
120
     *
121
     * @return Response
122
     * @throws \yii\web\ForbiddenHttpException
123
     * @throws \yii\web\NotFoundHttpException
124
     */
125
    public function actionImportCsv(string $siteHandle = null): Response
126
    {
127
        $variables = [];
128
        PermissionHelper::controllerPermissionCheck('retour:redirects');
129
        // If your CSV document was created or is read on a Macintosh computer,
130
        // add the following lines before using the library to help PHP detect line ending in Mac OS X
131
        if (!ini_get('auto_detect_line_endings')) {
132
            ini_set('auto_detect_line_endings', '1');
133
        }
134
        // Get the site to edit
135
        $siteId = MultiSiteHelper::getSiteIdFromHandle($siteHandle);
136
        $pluginName = Retour::$settings->pluginName;
137
        $templateTitle = Craft::t('retour', 'Import CSV File');
138
        $view = Craft::$app->getView();
139
        // Asset bundle
140
        try {
141
            $view->registerAssetBundle(RetourImportAsset::class);
142
        } catch (InvalidConfigException $e) {
143
            Craft::error($e->getMessage(), __METHOD__);
144
        }
145
        $variables['baseAssetsUrl'] = Craft::$app->assetManager->getPublishedUrl(
146
            '@nystudio107/retour/assetbundles/retour/dist',
147
            true
148
        );
149
        // Enabled sites
150
        MultiSiteHelper::setMultiSiteVariables($siteHandle, $siteId, $variables);
151
        $variables['controllerHandle'] = 'file';
152
153
        // Basic variables
154
        $variables['fullPageForm'] = true;
155
        $variables['docsUrl'] = self::DOCUMENTATION_URL;
156
        $variables['pluginName'] = $pluginName;
157
        $variables['title'] = $templateTitle;
158
        $variables['crumbs'] = [
159
            [
160
                'label' => $pluginName,
161
                'url' => UrlHelper::cpUrl('retour'),
162
            ],
163
            [
164
                'label' => 'Redirects',
165
                'url' => UrlHelper::cpUrl('retour/redirects'),
166
            ],
167
        ];
168
        $variables['docTitle'] = "{$pluginName} - Redirects - {$templateTitle}";
169
        $variables['selectedSubnavItem'] = 'redirects';
170
171
        // The CSV file
172
        $file = UploadedFile::getInstanceByName('file');
173
        if ($file !== null) {
174
            $filename = Craft::$app->getPath()->getTempPath().DIRECTORY_SEPARATOR.uniqid($file->name, true);
175
            $file->saveAs($filename, false);
176
            $csv = Reader::createFromPath($file->tempName);
177
            $headers = $csv->fetchOne(0);
178
            Craft::info(print_r($headers, true), __METHOD__);
179
            $variables['headers'] = $headers;
180
            $variables['filename'] = $filename;
181
        }
182
183
        // Render the template
184
        return $this->renderTemplate('retour/import/index', $variables);
185
    }
186
187
    /**
188
     * Export the statistics table as a CSV file
189
     *
190
     * @throws \yii\web\ForbiddenHttpException
191
     */
192
    public function actionExportStatistics()
193
    {
194
        PermissionHelper::controllerPermissionCheck('retour:statistics');
195
        $this->exportCsvFile('retour-statistics', '{{%retour_stats}}', self::STATISTICS_CSV_FIELDS);
196
    }
197
198
    /**
199
     * Export the statistics table as a CSV file
200
     *
201
     * @throws \yii\web\ForbiddenHttpException
202
     */
203
    public function actionExportRedirects()
204
    {
205
        PermissionHelper::controllerPermissionCheck('retour:redirects');
206
        $this->exportCsvFile('retour-redirects', '{{%retour_static_redirects}}', self::REDIRECTS_CSV_FIELDS);
207
    }
208
209
    // Public Methods
210
    // =========================================================================
211
212
    /**
213
     * @param string $filename
214
     * @param string $table
215
     * @param array  $columns
216
     */
217
    protected function exportCsvFile(string $filename, string $table, array $columns)
218
    {
219
        // If your CSV document was created or is read on a Macintosh computer,
220
        // add the following lines before using the library to help PHP detect line ending in Mac OS X
221
        if (!ini_get('auto_detect_line_endings')) {
222
            ini_set('auto_detect_line_endings', '1');
223
        }
224
        // Query the db table
225
        $data = (new Query())
226
            ->from([$table])
227
            ->select(array_keys($columns))
228
            ->orderBy('hitCount DESC')
229
            ->all();
230
        // Create our CSV file writer
231
        $csv = Writer::createFromFileObject(new \SplTempFileObject());
232
        $csv->insertOne(array_values($columns));
233
        $csv->insertAll($data);
234
        $csv->output($filename.'.csv');
235
        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...
236
    }
237
}
238