ImportUser::setTmpData()   F
last analyzed

Complexity

Conditions 53
Paths 8193

Size

Total Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 53
nc 8193
nop 0
dl 0
loc 64
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 24 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * @title          Import Users; Process Class
4
 * @desc           Import new Users from CSV data file.
5
 *
6
 * @author         Pierre-Henry Soria <[email protected]>
7
 * @copyright      (c) 2015-2019, Pierre-Henry Soria. All Rights Reserved.
8
 * @license        GNU General Public License; See PH7.LICENSE.txt and PH7.COPYRIGHT.txt in the root directory.
9
 * @package        PH7 / App / System / Module / Admin / Inc / Class
10
 */
11
12
namespace PH7;
13
14
defined('PH7') or exit('Restricted access');
15
16
use PH7\Framework\Ip\Ip;
17
use PH7\Framework\Security\Validate\Validate;
18
use PH7\Framework\Util\Various;
19
20
/** Reset the time limit and increase the memory **/
21
@set_time_limit(0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
22
@ini_set('memory_limit', '528M');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
23
24
class ImportUser extends Core
25
{
26
    const NO_ERROR = 0;
27
    const ERR_BAD_FILE = 1;
28
    const ERR_TOO_LARGE = 2;
29
    const ERR_INVALID = 3;
30
31
    const IMPORT_FILE_EXTENSION = 'csv';
32
33
    /*
34
     * @var array Array containing the DB data types.
35
     */
36
    const DB_TYPES = [
37
        'first_name',
38
        'last_name',
39
        'username',
40
        'email',
41
        'password',
42
        'sex',
43
        'match_sex',
44
        'birth_date',
45
        'description',
46
        'country',
47
        'city',
48
        'state',
49
        'zip_code',
50
        'website',
51
        'social_network_site',
52
        'ip'
53
    ];
54
55
    /** @var bool|resource */
56
    private $rHandler;
57
58
    /** @var array */
59
    private $aFile;
60
61
    /** @var array */
62
    private $aData = [];
63
64
    /** @var array */
65
    private $aTmpData;
66
67
    /** @var array */
68
    private $aFileData;
69
70
    /** @var array */
71
    private $aRes;
72
73
    /**
74
     * @param array $aFile
75
     * @param string $sDelimiter Delimiter Field delimiter (one character).
76
     * @param string $sEnclosure Enclosure Field enclosure (one character).
77
     */
78
    public function __construct(array $aFile, $sDelimiter, $sEnclosure)
79
    {
80
        parent::__construct();
81
82
        // Initialize necessary attributes
83
        $this->aFile = $aFile;
84
        $this->rHandler = @fopen($this->aFile['tmp_name'], 'rb');
85
        $this->aFileData = @fgetcsv($this->rHandler, 0, $sDelimiter, $sEnclosure);
86
        $this->aRes = $this->run();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->run() can be null. However, the property $aRes is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
87
    }
88
89
    /**
90
     * @return array (boolean | string) ['status', 'msg']
91
     */
92
    public function getResponse()
93
    {
94
        return $this->aRes;
95
    }
96
97
    /**
98
     * Check and set the data from the CSV file.
99
     *
100
     * @param int $iRow Number of row of the CSV file
101
     *
102
     * @return void
103
     */
104
    private function setData($iRow)
105
    {
106
        $oUser = new UserCore;
107
108
        foreach (self::DB_TYPES as $sType) {
109
            $sData = !empty($this->aFileData[$this->aTmpData[$sType]]) ? trim($this->aFileData[$this->aTmpData[$sType]]) : $this->aTmpData[$sType];
110
111
            if ($sType === 'username') {
112
                $this->aData[$iRow][$sType] = $oUser->findUsername($sData, $this->aData[$iRow]['first_name'], $this->aData[$iRow]['last_name']);
113
            } elseif ($sType === 'sex') {
114
                $this->aData[$iRow][$sType] = $this->fixGender($sData);
115
            } elseif ($sType === 'match_sex') {
116
                $this->aData[$iRow][$sType] = [$this->fixGender($sData)];
117
            } elseif ($sType === 'birth_date') {
118
                $this->aData[$iRow][$sType] = $this->dateTime->get($sData)->date('Y-m-d');
119
            } else {
120
                $this->aData[$iRow][$sType] = $sData;
121
            }
122
        }
123
124
        unset($oUser);
125
    }
126
127
    /**
128
     * Set default values for the "ImportUser::$aTmpData" array.
129
     *
130
     * @return void
131
     */
132
    private function setDefVals()
133
    {
134
        $sFiveChars = Various::genRnd($this->aFile['name'], 5);
135
136
        $this->aTmpData = [
137
            'email' => 'pierrehenrysoriasanz' . $sFiveChars . '@ph7cms' . $sFiveChars . '.com',
138
            'username' => 'pH7CMS' . $sFiveChars,
139
            'password' => Various::genRnd(),
140
            'first_name' => 'Alex' . $sFiveChars,
141
            'last_name' => 'Rolli' . $sFiveChars,
142
            'sex' => GenderTypeUserCore::GENDERS[array_rand(GenderTypeUserCore::GENDERS)], // Generate gender randomly
143
            'match_sex' => GenderTypeUserCore::GENDERS[array_rand(GenderTypeUserCore::GENDERS)], // Generate one randomly
144
            'birth_date' => $this->getRandomDate(),
145
            'country' => 'US',
146
            'city' => 'Virginia',
147
            'state' => 'Doswell',
148
            'zip_code' => '23047',
149
            'description' => 'Hi all!<br />How are you today?<br /> Bye ;)',
150
            'website' => '',
151
            'social_network_site' => '',
152
            'ip' => Ip::get()
153
        ];
154
    }
155
156
    private function setTmpData()
157
    {
158
        foreach ($this->aFileData as $sKey => $sVal) {
159
            $sVal = $this->cleanValue($sVal);
160
161
            // Test comparisons of strings and adding values in an array "ImportUser::$aTmpData"
162
            if ($sVal === 'username' || $sVal === 'login' || $sVal === 'user' || $sVal === 'nickname') {
163
                $this->aTmpData['username'] = $sKey;
164
            }
165
166
            if ($sVal === 'name' || $sVal === 'firstname' || $sVal === 'givenname' || $sVal === 'forname') {
167
                $this->aTmpData['first_name'] = $sKey;
168
            }
169
170
            if ($sVal === 'lastname' || $sVal === 'surname' || $sVal === 'familyname') {
171
                $this->aTmpData['last_name'] = $sKey;
172
            }
173
174
            if ($sVal === 'matchsex' || $sVal === 'looking' || $sVal === 'lookingfor') {
175
                $this->aTmpData['match_sex'] = $sKey;
176
            }
177
178
            if ($sVal === 'sex' || $sVal === 'gender') {
179
                $this->aTmpData['sex'] = $sKey;
180
            }
181
182
            if ($sVal === 'email' || $sVal === 'mail' || $sVal === 'emailid') {
183
                $this->aTmpData['email'] = $sKey;
184
            }
185
186
            if ($sVal === 'desc' || $sVal === 'description' || $sVal === 'descriptionme' ||
187
                $sVal === 'generaldescription' || $sVal === 'about' || $sVal === 'aboutme' ||
188
                $sVal === 'bio' || $sVal === 'biography' || $sVal === 'comment') {
189
                $this->aTmpData['description'] = $sKey;
190
            }
191
192
            if ($sVal === 'country' || $sVal === 'countryid') {
193
                $this->aTmpData['country'] = $sKey;
194
            }
195
196
            if ($sVal === 'city' || $sVal === 'town') {
197
                $this->aTmpData['city'] = $sKey;
198
            }
199
200
            if ($sVal === 'state' || $sVal === 'district' || $sVal === 'province' || $sVal === 'region') {
201
                $this->aTmpData['state'] = $sKey;
202
            }
203
204
            if (
205
                $sVal === 'zip' || $sVal === 'zipcode' || $sVal === 'postal' || $sVal === 'postcode' ||
206
                $sVal === 'postalcode' || $sVal === 'pin' || $sVal === 'pincode' || $sVal === 'eircode'
207
            ) {
208
                $this->aTmpData['zip_code'] = $sKey;
209
            }
210
211
            if ($sVal === 'website' || $sVal === 'site' || $sVal === 'url') {
212
                $this->aTmpData['website'] = $sKey;
213
            }
214
215
            if ($sVal === 'birthday' || $sVal === 'birthdate' || $sVal === 'dateofbirth' || $sVal === 'dob') {
216
                $this->aTmpData['birth_date'] = $sKey;
217
            }
218
        }
219
    }
220
221
    /**
222
     * Returns the error message for the form.
223
     *
224
     * @param int $iErrType
225
     *
226
     * @return string The error message.
227
     */
228
    private function getErrMsg($iErrType)
229
    {
230
        switch ($iErrType) {
231
            case static::ERR_BAD_FILE:
232
                $sErrMsg = t('Invalid File Format! Please select a valid CSV file containing the member data.');
233
                break;
234
235
            case static::ERR_TOO_LARGE:
236
                $sErrMsg = t('The file is too large. Please select a smaller file or change your server PHP settings. Especially "upload_max_filesize" and "post_max_size" directives in the php.ini file.');
237
                break;
238
239
            case static::ERR_INVALID:
240
                $sErrMsg = t('The file is Invalid/Empty or has incorrect Delimiter/Enclosure set.');
241
                break;
242
        }
243
244
        return $sErrMsg;
0 ignored issues
show
Bug introduced by
The variable $sErrMsg does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
245
    }
246
247
    /**
248
     * Check (and modify if incorrect) the gender type.
249
     *
250
     * @param string $sSex
251
     *
252
     * @return string
253
     */
254
    private function fixGender($sSex)
255
    {
256
        $sSex = strtolower($sSex);
257
258
        if (!GenderTypeUserCore::isGenderValid($sSex)) {
259
            $sSex = GenderTypeUserCore::GENDERS[array_rand(GenderTypeUserCore::GENDERS)];
260
        }
261
262
        return $sSex;
263
    }
264
265
    /**
266
     * Remove the temporary file.
267
     *
268
     * @return void
269
     */
270
    private function removeTmpFile()
271
    {
272
        $this->file->deleteFile($this->aFile['tmp_name']);
273
    }
274
275
    /**
276
     * @return array
277
     */
278
    private function run()
279
    {
280
        $iErrType = $this->hasError();
281
282
        if ($iErrType !== static::NO_ERROR) {
283
            $this->removeTmpFile();
284
            $this->aRes = ['status' => false, 'msg' => $this->getErrMsg($iErrType)];
285
        } else {
286
            $this->setDefVals();
287
            $this->setTmpData();
288
289
            $iRow = 0;
290
            $oUserModel = new UserCoreModel;
291
            $oExistsModel = new ExistsCoreModel;
292
            $oValidate = new Validate;
293
294
            while ($this->aFileData !== false) {
295
                $sEmail = trim($this->aFileData[$this->aTmpData['email']]);
296
                if ($oValidate->email($sEmail) && !$oExistsModel->email($sEmail)) {
297
                    $this->setData($iRow);
298
                    $oUserModel->add(escape($this->aData[$iRow], true));
0 ignored issues
show
Bug introduced by
It seems like escape($this->aData[$iRow], true) targeting escape() can also be of type string; however, PH7\UserCoreModel::add() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
299
                    $iRow++;
300
                }
301
            }
302
303
            $this->removeTmpFile();
304
            fclose($this->rHandler);
305
            unset($this->rHandler, $oUserModel, $oExistsModel, $oValidate, $this->aTmpData, $this->aFileData, $this->aData);
306
307
            return [
308
                'status' => true,
309
                'msg' => nt('%n% user has been successfully added.', '%n% users has been successfully added.', $iRow)
310
            ];
311
        }
312
    }
313
314
    /**
315
     * Generates a random (birth) date.
316
     *
317
     * @return string
318
     */
319
    private function getRandomDate()
320
    {
321
        return date('Y') - mt_rand(20, 50) . '-' . mt_rand(1, 12) . '-' . mt_rand(1, 28);
322
    }
323
324
    /**
325
     * @return int
326
     */
327
    private function hasError()
328
    {
329
        $sExtFile = $this->file->getFileExt($this->aFile['name']);
330
331
        if ($sExtFile !== self::IMPORT_FILE_EXTENSION) {
332
            return static::ERR_BAD_FILE;
333
        }
334
335
        if ($this->aFile['error'] === UPLOAD_ERR_INI_SIZE) {
336
            return static::ERR_TOO_LARGE;
337
        }
338
339
        if (!$this->rHandler || !$this->aFileData || !is_array($this->aFileData)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->aFileData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
340
            return static::ERR_INVALID;
341
        }
342
343
        return static::NO_ERROR;
344
    }
345
346
    /**
347
     * Clean the text to make comparisons easier...
348
     *
349
     * @param $sValue
350
     *
351
     * @return string
352
     */
353
    private function cleanValue($sValue)
354
    {
355
        return strtolower(trim(str_replace(['-', '_', ' '], '', $sValue)));
356
    }
357
}
358