Completed
Push — develop ( 70a960...4f2388 )
by John
03:01
created

Person::getUsername()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Alpha\Model;
4
5
use Alpha\Model\Type\SmallText;
6
use Alpha\Model\Type\Enum;
7
use Alpha\Model\Type\Relation;
8
use Alpha\Util\Helper\Validator;
9
use Alpha\Util\Logging\Logger;
10
use Alpha\Util\Config\ConfigProvider;
11
use Alpha\Util\Email\EmailProviderFactory;
12
use Alpha\Exception\RecordNotFoundException;
13
14
/**
15
 * The main person/user class for the framework.
16
 *
17
 * @since 1.0
18
 *
19
 * @author John Collins <[email protected]>
20
 * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
21
 * @copyright Copyright (c) 2017, John Collins (founder of Alpha Framework).
22
 * All rights reserved.
23
 *
24
 * <pre>
25
 * Redistribution and use in source and binary forms, with or
26
 * without modification, are permitted provided that the
27
 * following conditions are met:
28
 *
29
 * * Redistributions of source code must retain the above
30
 *   copyright notice, this list of conditions and the
31
 *   following disclaimer.
32
 * * Redistributions in binary form must reproduce the above
33
 *   copyright notice, this list of conditions and the
34
 *   following disclaimer in the documentation and/or other
35
 *   materials provided with the distribution.
36
 * * Neither the name of the Alpha Framework nor the names
37
 *   of its contributors may be used to endorse or promote
38
 *   products derived from this software without specific
39
 *   prior written permission.
40
 *
41
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
42
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
43
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
44
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
45
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
46
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
47
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
48
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
49
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
50
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
51
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
52
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
53
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
54
 * </pre>
55
 */
56
class Person extends ActiveRecord
57
{
58
    /**
59
     * The forum display name of the person.
60
     *
61
     * @var \Alpha\Model\Type\SmallText
62
     *
63
     * @since 1.0
64
     */
65
    protected $username;
66
67
    /**
68
     * The email address for the person.
69
     *
70
     * @var \Alpha\Model\Type\SmallText
71
     *
72
     * @since 1.0
73
     */
74
    protected $email;
75
76
    /**
77
     * The password for the person.
78
     *
79
     * @var \Alpha\Model\Type\SmallText
80
     *
81
     * @since 1.0
82
     */
83
    protected $password;
84
85
    /**
86
     * A Relation containing all of the rights groups that this person belongs to.
87
     *
88
     * @var \Alpha\Model\Type\Relation
89
     *
90
     * @since 1.0
91
     */
92
    protected $rights;
93
94
    /**
95
     * A Relation containing all of the actions carried out by this person.
96
     *
97
     * @var \Alpha\Model\Type\Relation
98
     *
99
     * @since 1.2.2
100
     */
101
    protected $actions;
102
103
    /**
104
     * An array of data display labels for the class properties.
105
     *
106
     * @var array
107
     *
108
     * @since 1.0
109
     */
110
    protected $dataLabels = array('OID' => 'Member ID#',
111
                                    'username' => 'Username',
112
                                    'email' => 'E-mail Address',
113
                                    'password' => 'Password',
114
                                    'state' => 'Account state',
115
                                    'URL' => 'Your site address',
116
                                    'rights' => 'Rights Group Membership',
117
                                    'actions' => 'Actions', );
118
119
    /**
120
     * The name of the database table for the class.
121
     *
122
     * @var string
123
     *
124
     * @since 1.0
125
     */
126
    const TABLE_NAME = 'Person';
127
128
    /**
129
     * The state of the person (account status).
130
     *
131
     * @var Aplha\Model\Type\Enum
132
     *
133
     * @since 1.0
134
     */
135
    protected $state;
136
137
    /**
138
     * The website URL of the person.
139
     *
140
     * @var \Alpha\Model\Type\SmallText
141
     *
142
     * @since 1.0
143
     */
144
    protected $URL;
145
146
    /**
147
     * Trace logger.
148
     *
149
     * @var \Alpha\Util\Logging\Logger
150
     *
151
     * @since 1.0
152
     */
153
    private static $logger = null;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
154
155
    /**
156
     * Constructor for the class that populates all of the complex types with default values.
157
     *
158
     * @since 1.0
159
     */
160
    public function __construct()
161
    {
162
        self::$logger = new Logger('Person');
163
        self::$logger->debug('>>__construct()');
164
165
        // ensure to call the parent constructor
166
        parent::__construct();
167
        $this->username = new SmallText();
168
        $this->username->setRule(Validator::REQUIRED_USERNAME);
169
        $this->username->setSize(70);
170
        $this->username->setHelper('Please provide a name for display on the website (only letters, numbers, and .-_ characters are allowed!).');
171
        $this->email = new SmallText();
172
        $this->email->setRule(Validator::REQUIRED_EMAIL);
173
        $this->email->setSize(70);
174
        $this->email->setHelper('Please provide a valid e-mail address as your username.');
175
        $this->password = new SmallText();
176
        $this->password->setSize(70);
177
        $this->password->setHelper('Please provide a password for logging in.');
178
        $this->password->isPassword(true);
179
        $this->state = new Enum(array(
180
                                    'Active',
181
                                    'Disabled', ));
182
        $this->state->setValue('Active');
183
        $this->URL = new SmallText();
184
        $this->URL->setRule(Validator::OPTIONAL_HTTP_URL);
185
        $this->URL->setHelper('URLs must be in the format http://some_domain/ or left blank!');
186
        // add unique keys to username and email (which is effectively the username in Alpha)
187
        $this->markUnique('username');
188
        $this->markUnique('email');
189
190
        $this->rights = new Relation();
191
        $this->markTransient('rights');
192
193
        $this->actions = new Relation();
194
        $this->markTransient('actions');
195
196
        $this->setupRels();
197
198
        self::$logger->debug('<<__construct');
199
    }
200
201
    /**
202
     * Set up the transient attributes for the rights group after it has loaded.
203
     *
204
     * @since 1.0
205
     */
206
    protected function after_load_callback()
207
    {
208
        $this->setupRels();
209
    }
210
211
    /**
212
     * Set up the transient attributes for the site after it has loaded.
213
     *
214
     * @since 1.0
215
     */
216
    protected function after_loadByAttribute_callback()
217
    {
218
        $this->setupRels();
219
    }
220
221
    /**
222
     * Looks up the OID for the Standard rights group, then relates the new person
223
     * to that group if they are not in it already.  If that group does not exist it
224
     * will be recreated!
225
     *
226
     * @since 1.0
227
     */
228
    protected function after_save_callback()
229
    {
230
        if ($this->getVersionNumber()->getValue() == 1) {
231
            $standardGroup = new Rights();
232
233
            $this->setupRels();
234
235
            if (!$this->inGroup('Standard')) {
236
                try {
237
                    $standardGroup->loadByAttribute('name', 'Standard');
238
                } catch (RecordNotFoundException $e) {
239
                    $standardGroup->set('name', 'Standard');
240
                    $standardGroup->save();
241
                }
242
243
                $lookup = $this->rights->getLookup();
244
                $lookup->setValue(array($this->getID(), $standardGroup->getID()));
245
                $lookup->save();
246
            }
247
        }
248
    }
249
250
    /**
251
     * Encrypts password value if it is plaintext before saving.
252
     *
253
     * @since 2.0
254
     */
255
    protected function before_save_callback()
256
    {
257
        if (password_needs_rehash($this->get('password'), PASSWORD_DEFAULT, ['cost' => 12])) {
258
            $this->set('password', password_hash($this->get('password'), PASSWORD_DEFAULT, ['cost' => 12]));
259
        }
260
    }
261
262
    /**
263
     * Sets up the Relation definitions on this BO.
264
     *
265
     * @since 1.0
266
     */
267
    protected function setupRels()
268
    {
269
        // set up MANY-TO-MANY relation person2rights
270
        if (isset($this->rights)) {
271
            $this->rights->setRelatedClass('Alpha\Model\Person', 'left');
272
            $this->rights->setRelatedClassDisplayField('email', 'left');
273
            $this->rights->setRelatedClass('Alpha\Model\Rights', 'right');
274
            $this->rights->setRelatedClassDisplayField('name', 'right');
275
            $this->rights->setRelationType('MANY-TO-MANY');
276
            $this->rights->setValue($this->getID());
277
        }
278
279
        if (isset($this->actions)) {
280
            $this->actions->setValue($this->OID);
281
            $this->actions->setRelatedClass('Alpha\Model\ActionLog');
282
            $this->actions->setRelatedClassField('created_by');
283
            $this->actions->setRelatedClassDisplayField('message');
284
            $this->actions->setRelationType('ONE-TO-MANY');
285
        }
286
    }
287
288
    /**
289
     * Setter for username.
290
     *
291
     * @param string $username
292
     *
293
     * @since 1.0
294
     */
295
    public function setUsername($username)
296
    {
297
        $this->username->setValue($username);
298
    }
299
300
    /**
301
     * Getter for username.
302
     *
303
     * @return \Alpha\Model\Type\SmallText
304
     *
305
     * @since 1.0
306
     */
307
    public function getUsername()
308
    {
309
        return $this->username;
310
    }
311
312
    /**
313
     * Checks to see if the person is in the rights group specified.
314
     *
315
     * @param string $groupName
316
     *
317
     * @return bool
318
     *
319
     * @since 1.0
320
     */
321
    public function inGroup($groupName)
322
    {
323
        if (self::$logger == null) {
324
            self::$logger = new Logger('Person');
325
        }
326
        self::$logger->debug('>>inGroup(groupName=['.$groupName.'])');
327
328
        $group = new Rights();
329
330
        try {
331
            $group->loadByAttribute('name', $groupName);
332
        } catch (RecordNotFoundException $e) {
333
            self::$logger->error('Unable to load the group named ['.$groupName.']');
334
            self::$logger->debug('<<inGroup [false]');
335
336
            return false;
337
        }
338
339
        $rel = $group->getMembers();
340
341
        try {
342
            // load all person2rights RelationLookup objects for this person
343
            $lookUps = $rel->getLookup()->loadAllByAttribute('leftID', $this->getID());
344
            foreach ($lookUps as $lookUp) {
345
                // the rightID (i.e. Rights OID) will be on the right side of the value array
346
                $ids = $lookUp->getValue();
347
                // if we have found a match, return true right away
348
                if ($ids[1] == $group->getID()) {
349
                    self::$logger->debug('<<inGroup [true]');
350
351
                    return true;
352
                }
353
            }
354
        } catch (RecordNotFoundException $e) {
355
            self::$logger->debug('<<inGroup [false]');
356
357
            return false;
358
        }
359
360
        self::$logger->debug('<<inGroup [false]');
361
362
        return false;
363
    }
364
365
    /**
366
     * Adds this person to the rights group specified.
367
     *
368
     * @param string $groupName
369
     *
370
     * @throws \Alpha\Exception\RecordNotFoundException
371
     *
372
     * @since 2.0
373
     */
374
    public function addToGroup($groupName)
375
    {
376
        if (self::$logger == null) {
377
            self::$logger = new Logger('Person');
378
        }
379
        self::$logger->debug('>>addToGroup(groupName=['.$groupName.'])');
380
381
        $group = new Rights();
382
        $group->loadByAttribute('name', $groupName);
383
384
        $lookup = $this->getPropObject('rights')->getLookup();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Alpha\Model\Type\Type as the method getLookup() does only exist in the following sub-classes of Alpha\Model\Type\Type: Alpha\Model\Type\Relation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
385
        $lookup->setValue(array($this->getOID(), $group->getOID()));
386
        $lookup->save();
387
388
        self::$logger->debug('<<addToGroup');
389
    }
390
391
    /**
392
     * A generic method for mailing a person.
393
     *
394
     * @param string $message
395
     * @param string $subject
396
     *
397
     * @since 1.0
398
     *
399
     * @throws \Alpha\Exception\MailNotSentException
400
     */
401
    public function sendMail($message, $subject)
402
    {
403
        $config = ConfigProvider::getInstance();
404
405
        $body = '<html><head></head><body><p>Dear '.$this->getUsername().',</p>';
406
407
        $body .= $message;
408
409
        $body .= '</body></html>';
410
411
        $mailer = EmailProviderFactory::getInstance('Alpha\Util\Email\EmailProviderPHP');
412
        $mailer->send($this->get('email'), $config->get('email.reply.to'), $subject, $body, true);
413
    }
414
415
    /**
416
     * Generates a random password for the user.
417
     *
418
     * @return string
419
     *
420
     * @since 1.0
421
     */
422
    public function generatePassword()
423
    {
424
        $alphabet = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
425
        // the password will be 7 random characters and 2 numbers
426
        $newPassword = '';
427
        for ($i = 0; $i < 7; ++$i) {
428
            $newPassword .= $alphabet[rand(0, 25)];
429
        }
430
        $newPassword .= rand(0, 100);
431
        $newPassword .= rand(0, 100);
432
433
        return $newPassword;
434
    }
435
436
    /**
437
     * Method for getting a count of the amount of article comments posted by the user.
438
     *
439
     * @return int
440
     *
441
     * @since 1.0
442
     */
443
    public function getCommentCount()
444
    {
445
        $temp = new ArticleComment();
446
447
        $sqlQuery = 'SELECT COUNT(OID) AS post_count FROM '.$temp->getTableName()." WHERE created_by='".$this->OID."';";
448
449
        $result = $this->query($sqlQuery);
450
451
        $row = $result[0];
452
453
        if (isset($row['post_count'])) {
454
            return $row['post_count'];
455
        } else {
456
            return 0;
457
        }
458
    }
459
}
460