FeatureAbstract::isUserInPercentage()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace QueerCodingGirl\Rollout\Feature;
4
5
6
use QueerCodingGirl\Rollout\Interfaces\RolloutableInterface;
7
use QueerCodingGirl\Rollout\RolloutAbstract;
8
use QueerCodingGirl\Rollout\Interfaces\DeterminableUserInterface;
9
10
/**
11
 * Class FeatureAbstract
12
 * @package QueerCodingGirl\Rollout\Feature
13
 */
14
abstract class FeatureAbstract implements RolloutableInterface
15
{
16
17
    const FEATURE_CONFIGSTRING_SECTION_DELIMITER = '|';
18
19
    const FEATURE_CONFIGSTRING_ENTRY_DELIMITER = ',';
20
21
    /**
22
     * @var string
23
     */
24
    protected $name = '';
25
26
    /**
27
     * @var integer
28
     */
29
    protected $percentage = 0;
30
31
    /**
32
     * @var integer[]
33
     */
34
    protected $users = array();
35
36
    /**
37
     * @var string[]
38
     */
39
    protected $roles = array();
40
41
    /**
42
     * @var string[]
43
     */
44
    protected $groups = array();
45
46
    /**
47
     * Configure the feature with a single string
48
     *
49
     * Format of config string should be: "100|1,2,3,4|ROLE_ADMIN,ROLE_PREMIUM|caretaker,supporter,staff"
50
     * - where "100" is the percentage of user for that the feature should be enabled.
51
     * - where "1,2,3,4" are a comma-separated list of user IDs that should have this feature.
52
     * - where "ROLE_ADMIN,ROLE_PREMIUM" are a comma-separated list of the role names that should have this feature.
53
     * - where "caretaker,supporter,staff" are a comma-separated list of the role names that should have this feature.
54
     *
55
     * Empty section are allowed and silently ignored as long as the format of the string stays the same:
56
     * e.g. "20||ROLE_PREMIUM|" is valid (20 percent and additionally al users with ROLE_PREMIUM will get the feature)
57
     * e.g. "|||" is valid and will completely disable this feature, but it is recommend to use "0|||" instead.
58
     *
59
     * @param string $configString
60
     * @return bool Successfully parsed the config string or not
61
     */
62
    public function configureByConfigString($configString)
63
    {
64
        $successsfullyConfigured = false;
65
        
66
        if (true === is_string($configString)
67
            && '' !== $configString
68
            && 3 === mb_substr_count($configString, self::FEATURE_CONFIGSTRING_SECTION_DELIMITER)
69
        ) {
70
71
            list($percentageString, $usersString, $rolesString, $groupsString) = explode(
72
                self::FEATURE_CONFIGSTRING_SECTION_DELIMITER,
73
                $configString
74
            );
75
            
76
            $this->setPercentage((integer) 0);
77
            if (true === is_numeric($percentageString)) {
78
                $this->setPercentage((integer) $percentageString);
79
            }
80
81
            $this->setUsers(array());
82
            if (true === is_string($usersString) && '' !== $usersString) {
83
                $userIds = explode(self::FEATURE_CONFIGSTRING_ENTRY_DELIMITER, $usersString);
84
                $this->setUsers($userIds);
85
            }
86
87
            $this->setRoles(array());
88
            if (true === is_string($rolesString) && '' !== $rolesString) {
89
                $roleNames = explode(self::FEATURE_CONFIGSTRING_ENTRY_DELIMITER, $rolesString);
90
                $this->setRoles($roleNames);
91
            }
92
93
            $this->setGroups(array());
94
            if (true === is_string($groupsString) && '' !== $groupsString) {
95
                $groupNames = explode(self::FEATURE_CONFIGSTRING_ENTRY_DELIMITER, $groupsString);
96
                $this->setGroups($groupNames);
97
            }
98
99
            $successsfullyConfigured = true;
100
        }
101
        
102
        return $successsfullyConfigured;
103
    }
104
105
    /**
106
     * Resets the feature config
107
     * @return RolloutableInterface $this
108
     */
109
    public function clearConfig()
110
    {
111
        $this->setPercentage((integer) 0);
112
        $this->setUsers(array());
113
        $this->setRoles(array());
114
        $this->setGroups(array());
115
        return $this;
116
    }
117
118
    /**
119
     * Check if this feature is active for the given userId
120
     * @param RolloutAbstract $rollout
121
     * @param DeterminableUserInterface|null $user
122
     * @internal param int $userId
123
     * @return bool
124
     */
125
    public function isActive(RolloutAbstract $rollout, DeterminableUserInterface $user = null)
126
    {
127
        
128
        if (100 === $this->getPercentage()) {
129
            return true;
130
        }
131
        
132
        if (!$user instanceof DeterminableUserInterface) {
133
            return false;
134
        }
135
        
136
        $userId = $user->getId();
137
        
138
        if (true === $this->isUserInPercentage($userId)
139
            || true === $this->isUserInActiveUsers($userId)
140
            || true === $this->isUserInActiveRole($user, $rollout)
141
            || true === $this->isUserInActiveGroup($user, $rollout)
142
        ) {
143
            return true;
144
        }
145
            
146
        
147
        return false;
148
    }
149
150
    /**
151
     * @param integer $userId
152
     * @return bool
153
     */
154
    protected function isUserInPercentage($userId)
155
    {
156
        $return = ((crc32($userId)%100) < $this->getPercentage());
157
        return $return;
158
    }
159
160
    /**
161
     * @param integer $userId
162
     * @return bool
163
     */
164
    protected function isUserInActiveUsers($userId)
165
    {
166
        return in_array($userId, $this->getUsers());
167
    }
168
169
    /**
170
     * @param DeterminableUserInterface $user
171
     * @param RolloutAbstract $rollout
172
     * @return bool
173
     */
174
    protected function isUserInActiveRole(DeterminableUserInterface $user, RolloutAbstract $rollout)
175
    {
176
        foreach ($this->getRoles() as $roleName) {
177
            if (true === $rollout->userHasRole($roleName, $user)) {
178
                return true;
179
            }
180
        }
181
        return false;
182
    }
183
184
    /**
185
     * @param DeterminableUserInterface $user
186
     * @param RolloutAbstract $rollout
187
     * @return bool
188
     */
189
    protected function isUserInActiveGroup(DeterminableUserInterface $user, RolloutAbstract $rollout)
190
    {
191
        foreach ($this->getGroups() as $groupName) {
192
            if (true === $rollout->userHasGroup($groupName, $user)) {
193
                return true;
194
            }
195
        }
196
        return false;
197
    }
198
199
    /**
200
     * Returns the name of the feature as string. A features name has to be unique!
201
     * @return string
202
     */
203
    public function getName()
204
    {
205
        return $this->name;
206
    }
207
208
    /**
209
     * Returns the percentage of users that should be enabled
210
     * @return integer
211
     */
212
    public function getPercentage()
213
    {
214
        return $this->percentage;
215
    }
216
217
    /**
218
     * Returns an array of user IDs that should be explicitly enabled for this feature
219
     * @return integer[]
220
     */
221
    public function getUsers()
222
    {
223
        return $this->users;
224
    }
225
226
    /**
227
     * Returns an array of role names that should be enabled for this feature
228
     * @return string[]
229
     */
230
    public function getRoles()
231
    {
232
        return $this->roles;
233
    }
234
235
    /**
236
     * Returns an array of group names that should be enabled for this feature
237
     * @return string[]
238
     */
239
    public function getGroups()
240
    {
241
        return $this->groups;
242
    }
243
244
    /**
245
     * Set the percentage of users that should be enabled
246
     * @param integer $percentage
247
     * @return RolloutableInterface $this
248
     */
249
    public function setPercentage($percentage)
250
    {
251
        $this->percentage = (integer) $percentage;
252
        return $this;
253
    }
254
255
    /**
256
     * Sets the array of user IDs that should be explicitly enabled for this feature
257
     * @param integer[] $userIds
258
     * @return RolloutableInterface $this
259
     */
260
    public function setUsers(array $userIds)
261
    {
262
        $this->users = $this->checkUserIds($userIds);
263
        return $this;
264
    }
265
266
    /**
267
     * Adds a user to the list of user IDs that should be explicitly enabled for this feature
268
     * @param integer $userId
269
     * @return RolloutableInterface $this
270
     */
271
    public function addUser($userId)
272
    {
273
        if (true === is_numeric($userId) && false === in_array($userId, $this->users)) {
274
            $this->users[] = (int)$userId;
275
        }
276
        return $this;
277
    }
278
279
    /**
280
     * Removes a user from the list of user IDs that should be explicitly enabled for this feature
281
     * @param integer $userId
282
     * @return RolloutableInterface $this
283
     */
284
    public function removeUser($userId)
285
    {
286
        if (true === is_numeric($userId) && true === in_array($userId, $this->users)) {
287
            foreach (array_keys($this->users, (int)$userId, true) as $key) {
288
                unset($this->users[$key]);
289
            }
290
        }
291
        return $this;
292
    }
293
294
    /**
295
     * Sets the array of role names that should be enabled for this feature
296
     * @param string[] $roles
297
     * @return RolloutableInterface $this
298
     */
299
    public function setRoles(array $roles)
300
    {
301
        $this->roles = $this->checkRoles($roles);
302
        return $this;
303
    }
304
305
    /**
306
     * Adds a role to the list of role names that should be enabled for this feature
307
     * @param string $roleName
308
     * @return RolloutableInterface $this
309
     */
310
    public function addRole($roleName)
311
    {
312
        if (true === is_string($roleName) && false === in_array($roleName, $this->roles)) {
313
            $this->roles[] = $roleName;
314
        }
315
        return $this;
316
    }
317
318
    /**
319
     * Removes a role from the list of role names that should be enabled for this feature
320
     * @param string $roleName
321
     * @return RolloutableInterface $this
322
     */
323
    public function removeRole($roleName)
324
    {
325
        if (true === is_string($roleName) && true === in_array($roleName, $this->roles)) {
326
            foreach (array_keys($this->roles, (string)$roleName, true) as $key) {
327
                unset($this->roles[$key]);
328
            }
329
        }
330
        return $this;
331
    }
332
333
    /**
334
     * Sets the array of group names that should be enabled for this feature
335
     * @param string[] $groups
336
     * @return RolloutableInterface $this
337
     */
338
    public function setGroups(array $groups)
339
    {
340
        $this->groups = $this->checkGroups($groups);
341
        return $this;
342
    }
343
344
    /**
345
     * Adds a group to the list of group names that should be enabled for this feature
346
     * @param string $groupName
347
     * @return RolloutableInterface $this
348
     */
349
    public function addGroup($groupName)
350
    {
351
        if (true === is_string($groupName) && false === in_array($groupName, $this->groups)) {
352
            $this->groups[] = $groupName;
353
        }
354
        return $this;
355
    }
356
357
    /**
358
     * Removes a group from the list of group names that should be enabled for this feature
359
     * @param string $groupName
360
     * @return RolloutableInterface $this
361
     */
362
    public function removeGroup($groupName)
363
    {
364
        if (true === is_string($groupName) && true === in_array($groupName, $this->groups)) {
365
            foreach (array_keys($this->groups, (string)$groupName, true) as $key) {
366
                unset($this->groups[$key]);
367
            }
368
        }
369
        return $this;
370
    }
371
372
    /**
373
     * Checks the user IDs and returns only the valid ones
374
     * @param array $userIds
375
     * @return array $checkedUserIds
376
     */
377
    protected function checkUserIds(array $userIds)
378
    {
379
        $checkedUserIds = array();
380
        foreach ($userIds as $userId) {
381
            if (true === is_numeric($userId) && false === in_array($userId, $checkedUserIds)) {
382
                $checkedUserIds[] = (int)$userId;
383
            }
384
        }
385
        return $checkedUserIds;
386
    }
387
388
    /**
389
     * Checks the roles and returns only the valid ones
390
     * @param array $roles
391
     * @return array $checkedRoles
392
     */
393
    protected function checkRoles(array $roles)
394
    {
395
        $checkedRoles = array();
396
        foreach ($roles as $role) {
397
            if (true === is_string($role) && false === in_array($role, $checkedRoles)) {
398
                $checkedRoles[] = (string)$role;
399
            }
400
        }
401
        return $checkedRoles;
402
    }
403
404
    /**
405
     * Checks the groups and returns only the valid ones
406
     * @param array $groups
407
     * @return array $checkedGroups
408
     */
409
    protected function checkGroups(array $groups)
410
    {
411
        $checkedGroups = array();
412
        foreach ($groups as $group) {
413
            if (true === is_string($group) && false === in_array($group, $checkedGroups)) {
414
                $checkedGroups[] = (string)$group;
415
            }
416
        }
417
        return $checkedGroups;
418
    }
419
420
    /**
421
     * @return string
422
     */
423
    public function __toString()
424
    {
425
        return ''
426
            . $this->getPercentage()
427
            . self::FEATURE_CONFIGSTRING_SECTION_DELIMITER
428
            . implode(self::FEATURE_CONFIGSTRING_ENTRY_DELIMITER, $this->getUsers())
429
            . self::FEATURE_CONFIGSTRING_SECTION_DELIMITER
430
            . implode(self::FEATURE_CONFIGSTRING_ENTRY_DELIMITER, $this->getRoles())
431
            . self::FEATURE_CONFIGSTRING_SECTION_DELIMITER
432
            . implode(self::FEATURE_CONFIGSTRING_ENTRY_DELIMITER, $this->getGroups())
433
        ;
434
    }
435
}
436