Completed
Push — master ( e25986...ffddd1 )
by Craig
07:06
created

FileIOHelper::createUsers()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 37
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 27
nc 10
nop 1
dl 0
loc 37
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Zikula package.
5
 *
6
 * Copyright Zikula Foundation - http://zikula.org/
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Zikula\ZAuthModule\Helper;
13
14
use Doctrine\ORM\EntityManager;
15
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
16
use Symfony\Component\HttpFoundation\File\UploadedFile;
17
use Symfony\Component\Validator\Constraints\NotNull;
18
use Symfony\Component\Validator\ConstraintViolationListInterface;
19
use Symfony\Component\Validator\Validator\ValidatorInterface;
20
use Zikula\Common\Translator\TranslatorInterface;
21
use Zikula\Common\Translator\TranslatorTrait;
22
use Zikula\Core\Event\GenericEvent;
23
use Zikula\ExtensionsModule\Api\VariableApi;
24
use Zikula\GroupsModule\Entity\GroupEntity;
25
use Zikula\PermissionsModule\Api\PermissionApi;
26
use Zikula\UsersModule\Api\CurrentUserApi;
27
use Zikula\UsersModule\Constant as UsersConstant;
28
use Zikula\UsersModule\Entity\UserEntity;
29
use Zikula\UsersModule\RegistrationEvents;
30
use Zikula\UsersModule\UserEvents;
31
use Zikula\UsersModule\Validator\Constraints\ValidEmail;
32
use Zikula\UsersModule\Validator\Constraints\ValidUname;
33
use Zikula\ZAuthModule\Api\PasswordApi;
34
use Zikula\ZAuthModule\Validator\Constraints\ValidPassword;
35
36
class FileIOHelper
37
{
38
    use TranslatorTrait;
39
40
    /**
41
     * @var VariableApi
42
     */
43
    private $variableApi;
44
45
    /**
46
     * @var PermissionApi
47
     */
48
    private $permissionApi;
49
50
    /**
51
     * @var ValidatorInterface
52
     */
53
    private $validator;
54
55
    /**
56
     * @var EntityManager
57
     */
58
    private $entityManager;
59
60
    /**
61
     * @var MailHelper
62
     */
63
    private $mailHelper;
64
65
    /**
66
     * @var EventDispatcherInterface
67
     */
68
    private $eventDispatcher;
69
70
    /**
71
     * @var CurrentUserApi
72
     */
73
    private $currentUser;
74
75
    /**
76
     * @var PasswordApi
77
     */
78
    private $passwordApi;
79
80
    /**
81
     * RegistrationHelper constructor.
82
     * @param VariableApi $variableApi
83
     * @param PermissionApi $permissionApi
84
     * @param TranslatorInterface $translator
85
     * @param ValidatorInterface $validator
86
     * @param EntityManager $entityManager
87
     * @param MailHelper $mailHelper
88
     * @param EventDispatcherInterface $eventDispatcher
89
     * @param CurrentUserApi $currentUserApi
90
     */
91
    public function __construct(
92
        VariableApi $variableApi,
93
        PermissionApi $permissionApi,
94
        TranslatorInterface $translator,
95
        ValidatorInterface $validator,
96
        EntityManager $entityManager,
97
        MailHelper $mailHelper,
98
        EventDispatcherInterface $eventDispatcher,
99
        CurrentUserApi $currentUserApi,
100
        PasswordApi $passwordApi
101
    ) {
102
        $this->variableApi = $variableApi;
103
        $this->permissionApi = $permissionApi;
104
        $this->setTranslator($translator);
105
        $this->validator = $validator;
106
        $this->entityManager = $entityManager;
107
        $this->mailHelper = $mailHelper;
108
        $this->eventDispatcher = $eventDispatcher;
109
        $this->currentUser = $currentUserApi;
110
        $this->passwordApi = $passwordApi;
111
    }
112
113
    public function setTranslator($translator)
114
    {
115
        $this->translator = $translator;
116
    }
117
118
    /**
119
     * @param UploadedFile $file
120
     * @param string $delimiter
121
     * @return string
122
     */
123
    public function importUsersFromFile(UploadedFile $file, $delimiter = ',')
124
    {
125
        $defaultGroup = $this->variableApi->get('ZikulaGroupsModule', 'defaultgroup');
126
        // get available groups
127
        $allGroups = $this->entityManager->getRepository('ZikulaGroupsModule:GroupEntity')->findAllAndIndexBy('gid');
128
        // create an array with the groups identities where the user can add other users
129
        $allGroupsArray = [];
130
        foreach ($allGroups as $gid => $group) {
131
            if ($this->permissionApi->hasPermission('ZikulaGroupsModule::', $group['gid'] . '::', ACCESS_EDIT)) {
132
                $allGroupsArray[] = $gid;
133
            }
134
        }
135
136
        // read the choosen file
137
        ini_set("auto_detect_line_endings", true); // allows for macintosh line endings ("/r")
138
        if (!$lines = file($file->getPathname())) {
139
            return $this->__("Error! It has not been possible to read the import file.");
140
        }
141
        $expectedFields = ['uname', 'pass', 'email', 'activated', 'sendmail', 'groups'];
142
        $firstLineArray = explode($delimiter, str_replace('"', '', trim($lines[0])));
143
        foreach ($firstLineArray as $field) {
144
            if (!in_array(trim(strtolower($field)), $expectedFields)) {
145
                return $this->__f("Error! The import file does not have the expected field %s in the first row. Please check your import file.", ['%s' => $field]);
146
            }
147
        }
148
        unset($lines[0]);
149
150
        $counter = 1;
151
        $importValues = [];
152
153
        // read the lines and create an array with the values. Check if the values passed are correct and set the default values if it is necessary
154
        foreach ($lines as $line) {
155
            $line = str_replace('"', '', trim($line));
156
            $lineArray = explode($delimiter, $line);
157
158
            // check if the line has all the needed values
159
            if (count($lineArray) != count($firstLineArray)) {
160
                return $this->__f('Error! The number of parameters in line %s is not correct. Please check your import file.', ['%s' => $counter]);
161
            }
162
            $importValues[] = array_combine($firstLineArray, $lineArray);
163
164
            // validate user name
165
            $uname = trim($importValues[$counter - 1]['uname']);
166
            $errors = $this->validator->validate($uname, new ValidUname());
167
            if ($errors->count() > 0) {
168
                return $this->locateErrors($errors, 'username', $counter);
169
            }
170
171
            // validate password
172
            $pass = (string)trim($importValues[$counter - 1]['pass']);
173
            $errors = $this->validator->validate($pass, [new NotNull(), new ValidPassword()]);
174
            if ($errors->count() > 0) {
175
                return $this->locateErrors($errors, 'password', $counter);
176
            }
177
178
            // validate email
179
            $email = trim($importValues[$counter - 1]['email']);
180
            $errors = $this->validator->validate($email, new ValidEmail());
181
            if ($errors->count() > 0) {
182
                return $this->locateErrors($errors, 'email', $counter);
183
            }
184
185
            // validate activation value
186
            $importValues[$counter - 1]['activated'] = isset($importValues[$counter - 1]['activated']) ? (int)$importValues[$counter - 1]['activated'] : UsersConstant::ACTIVATED_ACTIVE;
187
            $activated = $importValues[$counter - 1]['activated'];
188
            if (($activated != UsersConstant::ACTIVATED_INACTIVE) && ($activated != UsersConstant::ACTIVATED_ACTIVE)) {
189
                return $this->locateErrors($this->__('Error! The CSV is not valid: the "activated" column must contain 0 or 1 only.'), 'activated', $counter);
190
            }
191
192
            // validate sendmail
193
            $importValues[$counter - 1]['sendmail'] = isset($importValues[$counter - 1]['sendmail']) ? (int)$importValues[$counter - 1]['sendmail'] : 0;
194
            if ($importValues[$counter - 1]['sendmail'] < 0 || $importValues[$counter - 1]['sendmail'] > 1) {
195
                return $this->locateErrors($this->__('Error! The CSV is not valid: the "sendmail" column must contain 0 or 1 only.'), 'sendmail', $counter);
196
            }
197
198
            // check groups and set defaultGroup as default if there are not groups defined
199
            $importValues[$counter - 1]['groups'] = !empty($importValues[$counter - 1]['groups']) ? $importValues[$counter - 1]['groups'] : $defaultGroup;
200
            $groupsArray = explode('|', $importValues[$counter - 1]['groups']);
201
            foreach ($groupsArray as $group) {
202
                if (!in_array($group, $allGroupsArray)) {
203
                    return $this->locateErrors($this->__f('Sorry! The identity of the group %gid% is not not valid. Perhaps it does not exist. Please check your import file.', ['%gid%' => $group]), 'groups', $counter);
204
                }
205
            }
206
207
            $counter++;
208
        }
209
210
        if (empty($importValues)) {
211
            return $this->__("Error! The import file does not have values.");
212
        }
213
214
        // The values in import file are ready. Proceed creating users
215
        if (!$this->createUsers($importValues)) {
216
            return $this->__("Error! The creation of users has failed.");
217
        }
218
        // send email if indicated
219
        foreach ($importValues as $importValue) {
220
            if ($importValue['activated'] && $importValue['sendmail']) {
221
                $templateArgs = [
222
                    'user' => $importValue
223
                ];
224
                $this->mailHelper->sendNotification($importValue['email'], 'importnotify', $templateArgs);
225
            }
226
        }
227
228
        return '';
229
    }
230
231
    /**
232
     * @param array $importValues
233
     * @return bool
234
     */
235
    private function createUsers(array &$importValues)
236
    {
237
        if (empty($importValues)) {
238
            return false;
239
        }
240
241
        /** @var GroupEntity[] $groups */
242
        $groups = $this->entityManager->getRepository('ZikulaGroupsModule:GroupEntity')->findAllAndIndexBy('gid');
243
        $nowUTC = new \DateTime(null, new \DateTimeZone('UTC'));
244
        // create users
245
        foreach ($importValues as $k => $importValue) {
246
            $unHashedPass = $importValue['pass'];
247
            $importValue['pass'] = $this->passwordApi->getHashedPassword($importValue['pass']);
248
            if (!$importValue['activated']) {
249
                $importValues[$k]['activated'] = UsersConstant::ACTIVATED_PENDING_REG;
250
            } else {
251
                $importValue['approved_date'] = $nowUTC;
252
                $importValue['approved_by'] = $this->currentUser->get('uid');
253
            }
254
            $user = new UserEntity();
255
            $groupsArray = explode('|', $importValue['groups']);
256
            unset($importValue['groups'], $importValue['sendmail']);
257
            $user->merge($importValue);
258
            $user->setUser_Regdate($nowUTC);
259
            foreach ($groupsArray as $group) {
260
                $user->addGroup($groups[$group]);
261
                $groups[$group]->addUser($user);
262
            }
263
            $this->entityManager->persist($user);
264
            $this->entityManager->flush();
265
            $eventName = $importValue['activated'] ? UserEvents::CREATE_ACCOUNT : RegistrationEvents::CREATE_REGISTRATION;
266
            $this->eventDispatcher->dispatch($eventName, new GenericEvent($user));
267
            $importValues[$k]['unHashedPass'] = $unHashedPass;
268
        }
269
270
        return true;
271
    }
272
273
    /**
274
     * Convert errors to string and add current line.
275
     * @param $errors
276
     * @param $type
277
     * @param $line
278
     * @return string
279
     */
280
    private function locateErrors($errors, $type, $line)
281
    {
282
        $errorString = '';
283
        if ($errors instanceof ConstraintViolationListInterface) {
1 ignored issue
show
Bug introduced by
The class Symfony\Component\Valida...tViolationListInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
284
            foreach ($errors as $error) {
285
                $errorString .= $error->getMessage() . '<br>';
286
            }
287
        } elseif (is_array($errors)) {
288
            $errorString .= implode('<br>', $errors);
289
        } else {
290
            $errorString .= $errors;
291
        }
292
        $errorString .= $this->translator->__f('%type error in line %s', ['%type' => ucwords($type), '%s' => $line]);
293
294
        return $errorString;
295
    }
296
}
297