GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

contentSystemAuthors   F
last analyzed

Complexity

Total Complexity 162

Size/Duplication

Total Lines 914
Duplicated Lines 0 %

Importance

Changes 11
Bugs 0 Features 0
Metric Value
eloc 444
c 11
b 0
f 0
dl 0
loc 914
rs 2
wmc 162

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __viewEdit() 0 3 1
A __viewNew() 0 3 1
F __form() 0 355 58
F __actionEdit() 0 254 60
C __actionNew() 0 100 17
D __viewIndex() 0 168 22
A sort() 0 9 3

How to fix   Complexity   

Complex Class

Complex classes like contentSystemAuthors often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use contentSystemAuthors, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @package content
5
 */
6
7
/**
8
 * Controller page for all Symphony Author related activity
9
 * including making new Authors, editing Authors or deleting
10
 * Authors from Symphony
11
 */
12
13
class contentSystemAuthors extends AdministrationPage
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
14
{
15
    public $_Author;
16
    public $_errors = array();
17
18
    public function sort(&$sort, &$order, $params)
19
    {
20
        if (is_null($sort) || $sort == 'name') {
21
            return AuthorManager::fetch("first_name $order,  last_name", $order);
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $order instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
22
        } else {
23
            $sort = General::sanitize($sort);
24
        }
25
26
        return AuthorManager::fetch($sort, $order);
27
    }
28
29
    public function __viewIndex()
30
    {
31
        $this->setPageType('table');
32
        $this->setTitle(__('%1$s &ndash; %2$s', array(__('Authors'), __('Symphony'))));
33
34
        if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
35
            $this->appendSubheading(__('Authors'), Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL().'new/', __('Create a new author'), 'create button', null, array('accesskey' => 'c')));
36
        } else {
37
            $this->appendSubheading(__('Authors'));
38
        }
39
40
        Sortable::initialize($this, $authors, $sort, $order);
41
42
        $columns = array(
43
            array(
44
                'label' => __('Name'),
45
                'sortable' => true,
46
                'handle' => 'name'
47
            ),
48
            array(
49
                'label' => __('Email Address'),
50
                'sortable' => true,
51
                'handle' => 'email'
52
            ),
53
            array(
54
                'label' => __('Last Seen'),
55
                'sortable' => true,
56
                'handle' => 'last_seen'
57
            )
58
        );
59
60
        if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
61
            $columns = array_merge($columns, array(
62
                array(
63
                    'label' => __('User Type'),
64
                    'sortable' => true,
65
                    'handle' => 'user_type'
66
                ),
67
                array(
68
                    'label' => __('Language'),
69
                    'sortable' => true,
70
                    'handle' => 'language'
71
                )
72
            ));
73
        }
74
75
        /**
76
         * Allows the creation of custom table columns for each author. Called
77
         * after all the table headers columns have been added.
78
         *
79
         * @delegate AddCustomAuthorColumn
80
         * @since Symphony 2.7.0
81
         * @param string $context
82
         * '/system/authors/'
83
         * @param array $columns
84
         * An array of the current columns, passed by reference
85
         */
86
        Symphony::ExtensionManager()->notifyMembers('AddCustomAuthorColumn', '/system/authors/', array(
87
            'columns' => &$columns,
88
        ));
89
90
        $aTableHead = Sortable::buildTableHeaders($columns, $sort, $order, (isset($_REQUEST['filter']) ? '&amp;filter=' . $_REQUEST['filter'] : ''));
91
92
        $aTableBody = array();
93
94
        if (!is_array($authors) || empty($authors)) {
95
            $aTableBody = array(
96
                Widget::TableRow(array(Widget::TableData(__('None found.'), 'inactive', null, count($aTableHead))), 'odd')
97
            );
98
        } else {
99
            foreach ($authors as $a) {
100
                // Setup each cell
101
                if (
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces after opening bracket; newline found
Loading history...
102
                    (Symphony::Author()->isDeveloper() || (Symphony::Author()->isManager() && !$a->isDeveloper()))
103
                    || Symphony::Author()->get('id') == $a->get('id')
104
                ) {
105
                    $td1 = Widget::TableData(
106
                        Widget::Anchor($a->getFullName(), Administration::instance()->getCurrentPageURL() . 'edit/' . $a->get('id') . '/', $a->get('username'), 'author')
107
                    );
108
                } else {
109
                    $td1 = Widget::TableData($a->getFullName(), 'inactive');
110
                }
111
112
                // Can this Author be edited by the current Author?
113
                if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
114
                    if ($a->get('id') != Symphony::Author()->get('id')) {
115
                        $td1->appendChild(Widget::Label(__('Select Author %s', array($a->getFullName())), null, 'accessible', null, array(
116
                            'for' => 'author-' . $a->get('id')
117
                        )));
118
                        $td1->appendChild(Widget::Input('items['.$a->get('id').']', 'on', 'checkbox', array(
119
                            'id' => 'author-' . $a->get('id')
120
                        )));
121
                    }
122
                }
123
124
                $td2 = Widget::TableData(Widget::Anchor($a->get('email'), 'mailto:'.$a->get('email'), __('Email this author')));
125
126
                if (!is_null($a->get('last_seen'))) {
127
                    $td3 = Widget::TableData(
128
                        DateTimeObj::format($a->get('last_seen'), __SYM_DATETIME_FORMAT__)
0 ignored issues
show
Bug introduced by
The constant __SYM_DATETIME_FORMAT__ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
It seems like DateTimeObj::format($a->..._SYM_DATETIME_FORMAT__) can also be of type false; however, parameter $value of Widget::TableData() does only seem to accept XMLElement|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

128
                        /** @scrutinizer ignore-type */ DateTimeObj::format($a->get('last_seen'), __SYM_DATETIME_FORMAT__)
Loading history...
129
                    );
130
                } else {
131
                    $td3 = Widget::TableData(__('Unknown'), 'inactive');
132
                }
133
134
                if ($a->isDeveloper()) {
135
                    $type = 'Developer';
136
                } elseif ($a->isManager()) {
137
                    $type = 'Manager';
138
                } else {
139
                    $type = 'Author';
140
                }
141
142
                $td4 = Widget::TableData(__($type));
143
144
                $languages = Lang::getAvailableLanguages();
145
146
                $td5 = Widget::TableData($a->get("language") == null ? __("System Default") : $languages[$a->get("language")]);
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal language does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
Coding Style Comprehensibility introduced by
The string literal System Default does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
147
148
                $tableData = array();
149
                // Add a row to the body array, assigning each cell to the row
150
                if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
151
                    $tableData = array($td1, $td2, $td3, $td4, $td5);
152
                } else {
153
                    $tableData = array($td1, $td2, $td3);
154
                }
155
156
                /**
157
                 * Allows Extensions to inject custom table data for each Author
158
                 * into the Authors Index
159
                 *
160
                 * @delegate AddCustomAuthorColumnData
161
                 * @since Symphony 2.7.0
162
                 * @param string $context
163
                 * '/system/authors/'
164
                 * @param array $tableData
165
                 *  An array of `Widget::TableData`, passed by reference
166
                 * @param array $columns
167
                 * An array of the current columns
168
                 * @param Author $author
169
                 *  The Author object.
170
                 */
171
                Symphony::ExtensionManager()->notifyMembers('AddCustomAuthorColumnData', '/system/authors/', array(
172
                    'tableData' => &$tableData,
173
                    'columns' => $columns,
174
                    'author' => $a,
175
                ));
176
177
                $aTableBody[] = Widget::TableRow($tableData);
178
            }
179
        }
180
181
        $table = Widget::Table(
182
            Widget::TableHead($aTableHead),
183
            null,
184
            Widget::TableBody($aTableBody),
185
            'selectable',
186
            null,
187
            array('role' => 'directory', 'aria-labelledby' => 'symphony-subheading')
188
        );
189
190
        $this->Form->appendChild($table);
191
192
        $version = new XMLElement('p', 'Symphony ' . Symphony::Configuration()->get('version', 'symphony'), array(
193
            'id' => 'version'
194
        ));
195
196
        $this->Form->appendChild($version);
197
    }
198
199
    // Both the Edit and New pages need the same form
200
    public function __viewNew()
201
    {
202
        $this->__form();
203
    }
204
205
    public function __viewEdit()
206
    {
207
        $this->__form();
208
    }
209
210
    public function __form()
211
    {
212
        // Handle unknown context
213
        if (!in_array($this->_context[0], array('new', 'edit'))) {
214
            Administration::instance()->errorPageNotFound();
215
        }
216
217
        if ($this->_context[0] == 'new' && !Symphony::Author()->isDeveloper() && !Symphony::Author()->isManager()) {
218
            Administration::instance()->throwCustomError(
219
                __('You are not authorised to access this page.'),
220
                __('Access Denied'),
221
                Page::HTTP_STATUS_UNAUTHORIZED
222
            );
223
        }
224
225
        if (isset($this->_context[2])) {
226
            $time = Widget::Time();
227
228
            switch ($this->_context[2]) {
229
                case 'saved':
230
                    $message = __('Author updated at %s.', array($time->generate()));
231
                    break;
232
                case 'created':
233
                    $message = __('Author created at %s.', array($time->generate()));
234
            }
235
236
            $this->pageAlert(
237
                $message
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $message does not seem to be defined for all execution paths leading up to this point.
Loading history...
238
                . ' <a href="' . SYMPHONY_URL . '/system/authors/new/" accesskey="c">'
0 ignored issues
show
Bug introduced by
The constant SYMPHONY_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
239
                . __('Create another?')
240
                . '</a> <a href="' . SYMPHONY_URL . '/system/authors/" accesskey="a">'
241
                . __('View all Authors')
242
                . '</a>',
243
                Alert::SUCCESS
244
            );
245
        }
246
247
        $this->setPageType('form');
248
        $isOwner = false;
249
        $isEditing = ($this->_context[0] == 'edit');
250
        $canonical_link = null;
251
252
        if (isset($_POST['fields'])) {
253
            $author = $this->_Author;
254
        } elseif ($this->_context[0] == 'edit') {
255
            if (!$author_id = (int)$this->_context[1]) {
256
                redirect(SYMPHONY_URL . '/system/authors/');
257
            }
258
259
            if (!$author = AuthorManager::fetchByID($author_id)) {
260
                Administration::instance()->throwCustomError(
261
                    __('The author profile you requested does not exist.'),
262
                    __('Author not found'),
263
                    Page::HTTP_STATUS_NOT_FOUND
264
                );
265
            }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
266
            $canonical_link = '/system/authors/edit/' . $author_id . '/';
267
        } else {
268
            $author = new Author();
269
        }
270
271
        if ($isEditing && $author->get('id') == Symphony::Author()->get('id')) {
272
            $isOwner = true;
273
        }
274
275
        if ($isEditing && !$isOwner && !Symphony::Author()->isDeveloper() && !Symphony::Author()->isManager()) {
276
            Administration::instance()->throwCustomError(
277
                __('You are not authorised to edit other authors.'),
278
                __('Access Denied'),
279
                Page::HTTP_STATUS_FORBIDDEN
280
            );
281
        }
282
283
        $this->setTitle(__(($this->_context[0] == 'new' ? '%2$s &ndash; %3$s' : '%1$s &ndash; %2$s &ndash; %3$s'), array($author->getFullName(), __('Authors'), __('Symphony'))));
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
284
        if ($canonical_link) {
285
            $this->addElementToHead(new XMLElement('link', null, array(
286
                'rel' => 'canonical',
287
                'href' => SYMPHONY_URL . $canonical_link,
288
            )));
289
        }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
290
        $this->appendSubheading(($this->_context[0] == 'new' ? __('Untitled') : $author->getFullName()));
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
291
        $this->insertBreadcrumbs(array(
292
            Widget::Anchor(__('Authors'), SYMPHONY_URL . '/system/authors/'),
293
        ));
294
295
        // Essentials
296
        $group = new XMLElement('fieldset');
297
        $group->setAttribute('class', 'settings');
298
        $group->appendChild(new XMLElement('legend', __('Essentials')));
299
300
        $div = new XMLElement('div');
301
        $div->setAttribute('class', 'two columns');
302
303
        $label = Widget::Label(__('First Name'), null, 'column');
304
        $label->appendChild(Widget::Input('fields[first_name]', $author->get('first_name')));
305
        $div->appendChild((isset($this->_errors['first_name']) ? Widget::Error($label, $this->_errors['first_name']) : $label));
306
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
307
308
        $label = Widget::Label(__('Last Name'), null, 'column');
309
        $label->appendChild(Widget::Input('fields[last_name]', $author->get('last_name')));
310
        $div->appendChild((isset($this->_errors['last_name']) ? Widget::Error($label, $this->_errors['last_name']) : $label));
311
312
        $group->appendChild($div);
313
314
        $label = Widget::Label(__('Email Address'));
315
        $label->appendChild(Widget::Input('fields[email]', $author->get('email'), 'text', array('autocomplete' => 'off')));
316
        $group->appendChild((isset($this->_errors['email']) ? Widget::Error($label, $this->_errors['email']) : $label));
317
318
        $this->Form->appendChild($group);
319
320
        // Login Details
321
        $group = new XMLElement('fieldset');
322
        $group->setAttribute('class', 'settings');
323
        $group->appendChild(new XMLElement('legend', __('Login Details')));
324
325
        $div = new XMLElement('div');
326
327
        $label = Widget::Label(__('Username'));
328
        $label->appendChild(Widget::Input('fields[username]', $author->get('username'), 'text', array('autocomplete' => 'off')));
329
        $div->appendChild((isset($this->_errors['username']) ? Widget::Error($label, $this->_errors['username']) : $label));
330
331
        // Only developers can change the user type. Primary account should NOT be able to change this
332
        if ((Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) && !$author->isPrimaryAccount()) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
333
334
            // Create columns
335
            $div->setAttribute('class', 'two columns');
336
            $label->setAttribute('class', 'column');
337
338
            // User type
339
            $label = Widget::Label(__('User Type'), null, 'column');
340
341
            $options = array(
342
                array('author', false, __('Author')),
343
                array('manager', $author->isManager(), __('Manager'))
344
            );
345
346
            if (Symphony::Author()->isDeveloper()) {
347
                $options[] = array('developer', $author->isDeveloper(), __('Developer'));
348
            }
349
350
            $label->appendChild(Widget::Select('fields[user_type]', $options));
351
            if (isset($this->_errors['user_type'])) {
352
                $div->appendChild(Widget::Error($label, $this->_errors['user_type']));
353
            } else {
354
                $div->appendChild($label);
355
            }
356
        }
357
358
        $group->appendChild($div);
359
360
        // Password
361
        $fieldset = new XMLElement('fieldset', null, array('class' => 'two columns', 'id' => 'password'));
362
        $legend = new XMLElement('legend', __('Password'));
363
        $help = new XMLElement('i', __('Leave password fields blank to keep the current password'));
364
        $fieldset->appendChild($legend);
365
        $fieldset->appendChild($help);
366
367
        /*
368
            Password reset rules:
369
            - Primary account can edit all accounts.
370
            - Developers can edit all developers, managers and authors, and their own.
371
            - Managers can edit all Authors, and their own.
372
            - Authors can edit their own.
373
        */
374
        if ($isEditing && !(
375
            // All accounts can edit their own
376
            $isOwner
377
            // Managers can edit all Authors, and their own.
378
            || (Symphony::Author()->isManager() && $author->isAuthor())
379
            // Primary account can edit all accounts.
380
            || Symphony::Author()->isPrimaryAccount()
381
            // Developers can edit all developers, managers and authors, and their own.
382
            || Symphony::Author()->isDeveloper() && $author->isPrimaryAccount() === false
383
        )) {
384
            $fieldset->setAttribute('class', 'three columns');
385
386
            $label = Widget::Label(null, null, 'column');
387
            $label->appendChild(Widget::Input('fields[old-password]', null, 'password', array('placeholder' => __('Old Password'), 'autocomplete' => 'off')));
388
            $fieldset->appendChild((isset($this->_errors['old-password']) ? Widget::Error($label, $this->_errors['old-password']) : $label));
389
        }
390
391
        // New password
392
        $placeholder = ($isEditing ? __('New Password') : __('Password'));
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
393
        $label = Widget::Label(null, null, 'column');
394
        $label->appendChild(Widget::Input('fields[password]', null, 'password', array('placeholder' => $placeholder, 'autocomplete' => 'off')));
395
        $fieldset->appendChild((isset($this->_errors['password']) ? Widget::Error($label, $this->_errors['password']) : $label));
396
397
        // Confirm password
398
        $label = Widget::Label(null, null, 'column');
399
        $label->appendChild(Widget::Input('fields[password-confirmation]', null, 'password', array('placeholder' => __('Confirm Password'), 'autocomplete' => 'off')));
400
        $fieldset->appendChild((isset($this->_errors['password-confirmation']) ? Widget::Error($label, $this->_errors['password']) : $label));
401
402
        $group->appendChild($fieldset);
403
404
        // Auth token
405
        if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
406
            $label = Widget::Label();
407
            $group->appendChild(Widget::Input('fields[auth_token_active]', 'no', 'hidden'));
408
            $input = Widget::Input('fields[auth_token_active]', 'yes', 'checkbox');
409
410
            if ($author->isTokenActive()) {
411
                $input->setAttribute('checked', 'checked');
412
            }
413
414
            $temp = SYMPHONY_URL . '/login/' . $author->createAuthToken() . '/';
415
            $label->setValue(__('%s Allow remote login via', array($input->generate())) . ' <a href="' . $temp . '">' . $temp . '</a>');
416
            $group->appendChild($label);
417
        }
418
419
        $label = Widget::Label(__('Default Area'));
420
421
        $sections = SectionManager::fetch(null, 'ASC', 'sortorder');
422
423
        $options = array();
424
425
        // If the Author is the Developer, allow them to set the Default Area to
426
        // be the Sections Index.
427
        if ($author->isDeveloper()) {
428
            $options[] = array(
429
                '/blueprints/sections/',
430
                $author->get('default_area') == '/blueprints/sections/',
431
                __('Sections Index')
432
            );
433
        }
434
435
        if (is_array($sections) && !empty($sections)) {
436
            foreach ($sections as $s) {
437
                $options[] = array(
438
                    $s->get('id'),
439
                    $author->get('default_area') == $s->get('id'),
440
                    General::sanitize($s->get('name'))
441
                );
442
            }
443
        }
444
445
        /**
446
        * Allows injection or manipulation of the Default Area dropdown for an Author.
447
        * Take care with adding in options that are only valid for Developers, as if a
448
        * normal Author is set to that option, they will be redirected to their own
449
        * Author record.
450
        *
451
        *
452
        * @delegate AddDefaultAuthorAreas
453
        * @since Symphony 2.2
454
        * @param string $context
455
        * '/system/authors/'
456
        * @param array $options
457
        * An associative array of options, suitable for use for the Widget::Select
458
        * function. By default this will be an array of the Sections in the current
459
        * installation. New options should be the path to the page after the `SYMPHONY_URL`
460
        * constant.
461
        * @param string $default_area
462
        * The current `default_area` for this Author.
463
        * @param Author $author
464
        *  The Author object.
465
        *  This parameter is available @since Symphony 2.7.0
466
        */
467
        Symphony::ExtensionManager()->notifyMembers('AddDefaultAuthorAreas', '/system/authors/', array(
468
            'options' => &$options,
469
            'default_area' => $author->get('default_area'),
470
            'author' => $author,
471
        ));
472
473
        $label->appendChild(Widget::Select('fields[default_area]', $options));
474
        $group->appendChild($label);
475
476
        $this->Form->appendChild($group);
477
478
        // Custom Language Selection
479
        $languages = Lang::getAvailableLanguages();
480
        if (count($languages) > 1) {
481
            // Get language names
482
            asort($languages);
483
484
            $group = new XMLElement('fieldset');
485
            $group->setAttribute('class', 'settings');
486
            $group->appendChild(new XMLElement('legend', __('Custom Preferences')));
487
488
            $label = Widget::Label(__('Language'));
489
490
            $options = array(
491
                array(null, is_null($author->get('language')), __('System Default'))
492
            );
493
494
            foreach ($languages as $code => $name) {
495
                $options[] = array($code, $code == $author->get('language'), $name);
496
            }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
497
            $select = Widget::Select('fields[language]', $options);
498
            $label->appendChild($select);
499
            $group->appendChild($label);
500
501
            $this->Form->appendChild($group);
502
        }
503
504
        // Administration password double check
505
        if ($isEditing && !$isOwner) {
506
            $group = new XMLElement('fieldset');
507
            $group->setAttribute('class', 'settings');
508
            $group->setAttribute('id', 'confirmation');
509
            $group->appendChild(new XMLElement('legend', __('Confirmation')));
510
            $group->appendChild(new XMLELement('p', __('Please confirm changes to this author with your password.'), array('class' => 'help')));
511
512
            $label = Widget::Label(__('Password'));
513
            $label->appendChild(Widget::Input('fields[confirm-change-password]', null, 'password', array(
514
                'autocomplete' => 'off',
515
                'placeholder' => __('Your Password')
516
            )));
517
            $group->appendChild(
518
                isset($this->_errors['confirm-change-password']) ? Widget::Error($label, $this->_errors['confirm-change-password']) : $label
519
            );
520
521
            $this->Form->appendChild($group);
522
        }
523
524
        // Actions
525
        $div = new XMLElement('div');
526
        $div->setAttribute('class', 'actions');
527
528
        $div->appendChild(Widget::Input('action[save]', ($this->_context[0] == 'edit' ? __('Save Changes') : __('Create Author')), 'submit', array('accesskey' => 's')));
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
529
530
        if ($isEditing && !$isOwner && !$author->isPrimaryAccount()) {
531
            $button = new XMLElement('button', __('Delete'));
532
            $button->setAttributeArray(array('name' => 'action[delete]', 'class' => 'button confirm delete', 'title' => __('Delete this author'), 'type' => 'submit', 'accesskey' => 'd', 'data-message' => __('Are you sure you want to delete this author?')));
533
            $div->appendChild($button);
534
        }
535
536
        $this->Form->appendChild($div);
537
538
        /**
539
        * Allows the injection of custom form fields given the current `$this->Form`
540
        * object. Please note that this custom data should be saved in own extension
541
        * tables and that modifying `tbl_authors` to house your data is highly discouraged.
542
        *
543
        * @delegate AddElementstoAuthorForm
544
        * @since Symphony 2.2
545
        * @param string $context
546
        * '/system/authors/'
547
        * @param XMLElement $form
548
        * The contents of `$this->Form` after all the default form elements have been appended.
549
        * @param Author $author
550
        * The current Author object that is being edited
551
        * @param array $fields
552
        *  The POST fields
553
        *  This parameter is available @since Symphony 2.7.0
554
        * @param array $errors
555
        *  The error array used to validate the Author.
556
        *  Extension should register their own errors elsewhere and used the value
557
        *  to modify the UI accordingly.
558
        *  This parameter is available @since Symphony 2.7.0
559
        */
560
        Symphony::ExtensionManager()->notifyMembers('AddElementstoAuthorForm', '/system/authors/', array(
561
            'form' => &$this->Form,
562
            'author' => $author,
563
            'fields' => isset($_POST['fields']) ? $_POST['fields'] : null,
564
            'errors' => $this->_errors,
565
        ));
566
    }
567
568
    public function __actionNew()
569
    {
570
        if (@array_key_exists('save', $_POST['action']) || @array_key_exists('done', $_POST['action'])) {
571
            $fields = $_POST['fields'];
572
573
            $canCreate = Symphony::Author()->isDeveloper() || Symphony::Author()->isManager();
574
575
            if (!$canCreate) {
576
                Administration::instance()->throwCustomError(
577
                    __('You are not authorised to create authors.'),
578
                    __('Access Denied'),
579
                    Page::HTTP_STATUS_UNAUTHORIZED
580
                );
581
            }
582
583
            if (Symphony::Author()->isManager() && $fields['user_type'] !== 'author') {
584
                $this->_errors['user_type'] = __('The user type is invalid. You can only create Authors.');
585
            }
586
587
            $this->_Author = new Author();
588
            $this->_Author->set('user_type', $fields['user_type']);
589
            $this->_Author->set('primary', 'no');
590
            $this->_Author->set('email', $fields['email']);
591
            $this->_Author->set('username', General::sanitize($fields['username']));
592
            $this->_Author->set('first_name', General::sanitize($fields['first_name']));
593
            $this->_Author->set('last_name', General::sanitize($fields['last_name']));
594
            $this->_Author->set('last_seen', null);
595
            $this->_Author->set('password', (trim($fields['password']) == '' ? '' : Cryptography::hash(Symphony::Database()->cleanValue($fields['password']))));
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
596
            $this->_Author->set('default_area', $fields['default_area']);
597
            $this->_Author->set('auth_token_active', ($fields['auth_token_active'] ? $fields['auth_token_active'] : 'no'));
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
598
            $this->_Author->set('language', isset($fields['language']) ? $fields['language'] : null);
599
600
            /**
601
             * Creation of a new Author. The Author object is provided as read
602
             * only through this delegate.
603
             *
604
             * @delegate AuthorPreCreate
605
             * @since Symphony 2.7.0
606
             * @param string $context
607
             * '/system/authors/'
608
             * @param Author $author
609
             *  The Author object that has just been created, but not yet committed, nor validated
610
             * @param array $fields
611
             *  The POST fields
612
             * @param array $errors
613
             *  The error array used to validate the Author, passed by reference.
614
             *  Extension should append to this array if they detect validation problems.
615
             */
616
            Symphony::ExtensionManager()->notifyMembers('AuthorPreCreate', '/system/authors/', array(
617
                'author' => $this->_Author,
618
                'field' => $fields,
619
                'errors' => &$this->_errors,
620
            ));
621
622
            if (empty($this->_errors) && $this->_Author->validate($this->_errors)) {
623
                if ($fields['password'] != $fields['password-confirmation']) {
624
                    $this->_errors['password'] = $this->_errors['password-confirmation'] = __('Passwords did not match');
625
                } elseif ($author_id = $this->_Author->commit()) {
626
                    /**
627
                     * Creation of a new Author. The Author object is provided as read
628
                     * only through this delegate.
629
                     *
630
                     * @delegate AuthorPostCreate
631
                     * @since Symphony 2.2
632
                     * @param string $context
633
                     * '/system/authors/'
634
                     * @param Author $author
635
                     *  The Author object that has just been created
636
                     * @param integer $author_id
637
                     *  The ID of Author ID that was just created
638
                     * @param array $fields
639
                     *  The POST fields
640
                     *  This parameter is available @since Symphony 2.7.0
641
                     * @param array $errors
642
                     *  The error array used to validate the Author, passed by reference.
643
                     *  Extension should append to this array if they detect saving problems.
644
                     *  This parameter is available @since Symphony 2.7.0
645
                     */
646
                    Symphony::ExtensionManager()->notifyMembers('AuthorPostCreate', '/system/authors/', array(
647
                        'author' => $this->_Author,
648
                        'author_id' => $author_id,
649
                        'field' => $fields,
650
                        'errors' => &$this->_errors,
651
                    ));
652
653
                    if (empty($this->_errors)) {
654
                        redirect(SYMPHONY_URL . "/system/authors/edit/$author_id/created/");
0 ignored issues
show
Bug introduced by
The constant SYMPHONY_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $author_id instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
655
                    }
656
                }
657
            }
658
659
            if (is_array($this->_errors) && !empty($this->_errors)) {
660
                $this->pageAlert(__('There were some problems while attempting to save. Please check below for problem fields.'), Alert::ERROR);
661
            } else {
662
                $this->pageAlert(
663
                    __('Unknown errors occurred while attempting to save.')
664
                    . '<a href="' . SYMPHONY_URL . '/system/log/">'
665
                    . __('Check your activity log')
666
                    . '</a>.',
667
                    Alert::ERROR
668
                );
669
            }
670
        }
671
    }
672
673
    public function __actionEdit()
674
    {
675
        if (!$author_id = (int)$this->_context[1]) {
676
            redirect(SYMPHONY_URL . '/system/authors/');
0 ignored issues
show
Bug introduced by
The constant SYMPHONY_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
677
        }
678
679
        $isOwner = ($author_id == Symphony::Author()->get('id'));
680
        $fields = $_POST['fields'];
681
        $this->_Author = AuthorManager::fetchByID($author_id);
682
683
        $canEdit = // Managers can edit all Authors, and their own.
684
                (Symphony::Author()->isManager() && $this->_Author->isAuthor())
0 ignored issues
show
Bug introduced by
The method isAuthor() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

684
                (Symphony::Author()->isManager() && $this->_Author->/** @scrutinizer ignore-call */ isAuthor())

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
685
                // Primary account can edit all accounts.
686
                || Symphony::Author()->isPrimaryAccount()
687
                // Developers can edit all developers, managers and authors, and their own,
688
                // but not the primary account
689
                || (Symphony::Author()->isDeveloper() && $this->_Author->isPrimaryAccount() === false);
690
691
        if (@array_key_exists('save', $_POST['action']) || @array_key_exists('done', $_POST['action'])) {
692
            $authenticated = false;
693
694
            if (!$isOwner && !$canEdit) {
695
                Administration::instance()->throwCustomError(
696
                    __('You are not authorised to modify this author.'),
697
                    __('Access Denied'),
698
                    Page::HTTP_STATUS_UNAUTHORIZED
699
                );
700
            }
701
702
            if ($fields['email'] != $this->_Author->get('email')) {
703
                $changing_email = true;
704
            }
705
706
            // Check the old password was correct
707
            if (isset($fields['old-password']) && strlen(trim($fields['old-password'])) > 0 && Cryptography::compare(Symphony::Database()->cleanValue(trim($fields['old-password'])), $this->_Author->get('password'))) {
708
                $authenticated = true;
709
710
                // Developers don't need to specify the old password, unless it's their own account
711
            } elseif (
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces after opening bracket; newline found
Loading history...
712
                // All accounts can edit their own
713
                $isOwner ||
714
                // Is allowed to edit?
715
                $canEdit
716
            ) {
717
                $authenticated = true;
718
            }
719
720
            $this->_Author->set('id', $author_id);
721
722
            if ($this->_Author->isPrimaryAccount() || ($isOwner && Symphony::Author()->isDeveloper())) {
723
                $this->_Author->set('user_type', 'developer'); // Primary accounts are always developer, Developers can't lower their level
724
            } elseif (Symphony::Author()->isManager() && isset($fields['user_type'])) { // Manager can only change user type for author and managers
725
                if ($fields['user_type'] !== 'author' && $fields['user_type'] !== 'manager') {
726
                    $this->_errors['user_type'] = __('The user type is invalid. You can only create Authors.');
727
                } else {
728
                    $this->_Author->set('user_type', $fields['user_type']);
729
                }
730
            } elseif (Symphony::Author()->isDeveloper() && isset($fields['user_type'])) {
731
                $this->_Author->set('user_type', $fields['user_type']); // Only developer can change user type
732
            }
733
734
            $this->_Author->set('email', $fields['email']);
735
            $this->_Author->set('username', General::sanitize($fields['username']));
736
            $this->_Author->set('first_name', General::sanitize($fields['first_name']));
737
            $this->_Author->set('last_name', General::sanitize($fields['last_name']));
738
            $this->_Author->set('language', isset($fields['language']) ? $fields['language'] : null);
739
740
            if (trim($fields['password']) != '') {
741
                $this->_Author->set('password', Cryptography::hash(Symphony::Database()->cleanValue($fields['password'])));
742
                $changing_password = true;
743
            }
744
745
            // Don't allow authors to set the Section Index as a default area
746
            // If they had it previously set, just save `null` which will redirect
747
            // the Author (when logging in) to their own Author record
748
            if (
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces after opening bracket; newline found
Loading history...
749
                $this->_Author->get('user_type') == 'author'
750
                && $fields['default_area'] == '/blueprints/sections/'
751
            ) {
752
                $this->_Author->set('default_area', null);
753
            } else {
754
                $this->_Author->set('default_area', $fields['default_area']);
755
            }
756
757
            $this->_Author->set('auth_token_active', ($fields['auth_token_active'] ? $fields['auth_token_active'] : 'no'));
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
758
759
            /**
760
             * Before editing an author, provided with the Author object
761
             *
762
             * @delegate AuthorPreEdit
763
             * @since Symphony 2.7.0
764
             * @param string $context
765
             * '/system/authors/'
766
             * @param Author $author
767
             * An Author object not yet committed, nor validated
768
             * @param array $fields
769
             *  The POST fields
770
             * @param array $errors
771
             *  The error array used to validate the Author, passed by reference.
772
             *  Extension should append to this array if they detect validation problems.
773
             */
774
            Symphony::ExtensionManager()->notifyMembers('AuthorPreEdit', '/system/authors/', array(
775
                'author' => $this->_Author,
776
                'field' => $fields,
777
                'errors' => &$this->_errors,
778
            ));
779
780
            if (empty($this->_errors) && $this->_Author->validate($this->_errors)) {
781
                // Admin changing another profile
782
                if (!$isOwner) {
783
                    $entered_password = Symphony::Database()->cleanValue($fields['confirm-change-password']);
784
785
                    if (!isset($fields['confirm-change-password']) || empty($fields['confirm-change-password'])) {
786
                        $this->_errors['confirm-change-password'] = __('Please provide your own password to make changes to this author.');
787
                    } elseif (Cryptography::compare($entered_password, Symphony::Author()->get('password')) !== true) {
788
                        $this->_errors['confirm-change-password'] = __('Wrong password, please enter your own password to make changes to this author.');
789
                    }
790
                }
791
792
                // Author is changing their password
793
                if (!$authenticated && ($changing_password || $changing_email)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $changing_email does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $changing_password does not seem to be defined for all execution paths leading up to this point.
Loading history...
794
                    if ($changing_password) {
795
                        $this->_errors['old-password'] = __('Wrong password. Enter old password to change it.');
796
                    } elseif ($changing_email) {
797
                        $this->_errors['old-password'] = __('Wrong password. Enter old one to change email address.');
798
                    }
799
800
                    // Passwords provided, but doesn't match.
801
                } elseif (($fields['password'] != '' || $fields['password-confirmation'] != '') && $fields['password'] != $fields['password-confirmation']) {
802
                    $this->_errors['password'] = $this->_errors['password-confirmation'] = __('Passwords did not match');
803
                }
804
805
                // All good, let's save the Author
806
                if (is_array($this->_errors) && empty($this->_errors) && $this->_Author->commit()) {
807
                    Symphony::Database()->delete('tbl_forgotpass', sprintf("
808
                        `expiry` < '%s' OR `author_id` = %d",
809
                        DateTimeObj::getGMT('c'),
810
                        $author_id
811
                    ));
812
813
                    if ($isOwner) {
814
                        Administration::instance()->login($this->_Author->get('username'), $this->_Author->get('password'), true);
815
                    }
816
817
                    /**
818
                     * After editing an author, provided with the Author object
819
                     *
820
                     * @delegate AuthorPostEdit
821
                     * @since Symphony 2.2
822
                     * @param string $context
823
                     * '/system/authors/'
824
                     * @param Author $author
825
                     * An Author object
826
                     * @param array $fields
827
                     *  The POST fields
828
                     *  This parameter is available @since Symphony 2.7.0
829
                     * @param array $errors
830
                     *  The error array used to validate the Author, passed by reference.
831
                     *  Extension should append to this array if they detect saving problems.
832
                     *  This parameter is available @since Symphony 2.7.0
833
                     */
834
                    Symphony::ExtensionManager()->notifyMembers('AuthorPostEdit', '/system/authors/', array(
835
                        'author' => $this->_Author,
836
                        'field' => $fields,
837
                        'errors' => &$this->_errors,
838
                    ));
839
840
                    if (empty($this->_errors)) {
841
                        redirect(SYMPHONY_URL . '/system/authors/edit/' . $author_id . '/saved/');
842
                    }
843
844
                    // Problems.
845
                } else {
846
                    $this->pageAlert(
847
                        __('Unknown errors occurred while attempting to save.')
848
                        . '<a href="' . SYMPHONY_URL . '/system/log/">'
849
                        . __('Check your activity log')
850
                        . '</a>.',
851
                        Alert::ERROR
852
                    );
853
                }
854
            }
855
856
            // Author doesn't have valid data, throw back.
857
            if (is_array($this->_errors) && !empty($this->_errors)) {
858
                $this->pageAlert(__('There were some problems while attempting to save. Please check below for problem fields.'), Alert::ERROR);
859
            }
860
        } elseif (@array_key_exists('delete', $_POST['action'])) {
861
            // Validate rights
862
            if (!$canEdit) {
863
                $this->pageAlert(__('You are not allowed to delete this author.'), Alert::ERROR);
864
                return;
865
            }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
866
            // Admin changing another profile
867
            if (!$isOwner) {
868
                $entered_password = Symphony::Database()->cleanValue($fields['confirm-change-password']);
869
870
                if (!isset($fields['confirm-change-password']) || empty($fields['confirm-change-password'])) {
871
                    $this->_errors['confirm-change-password'] = __('Please provide your own password to make changes to this author.');
872
                } elseif (Cryptography::compare($entered_password, Symphony::Author()->get('password')) !== true) {
873
                    $this->_errors['confirm-change-password'] = __('Wrong password, please enter your own password to make changes to this author.');
874
                }
875
            }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
876
            if (is_array($this->_errors) && !empty($this->_errors)) {
877
                $this->pageAlert(__('There were some problems while attempting to save. Please check below for problem fields.'), Alert::ERROR);
878
                return;
879
            }
880
881
            $this->_Author = AuthorManager::fetchByID($author_id);
882
883
            /**
884
             * Prior to deleting an author, provided with the Author ID.
885
             *
886
             * @delegate AuthorPreDelete
887
             * @since Symphony 2.2
888
             * @param string $context
889
             * '/system/authors/'
890
             * @param integer $author_id
891
             *  The ID of Author ID that is about to be deleted
892
             * @param Author $author
893
             *  The Author object.
894
             *  This parameter is available @since Symphony 2.7.0
895
             */
896
            Symphony::ExtensionManager()->notifyMembers('AuthorPreDelete', '/system/authors/', array(
897
                'author_id' => $author_id,
898
                'author' => $this->_Author,
899
            ));
900
901
            if (!$isOwner) {
902
                $result = AuthorManager::delete($author_id);
903
904
                /**
905
                 * After deleting an author, provided with the Author ID.
906
                 *
907
                 * @delegate AuthorPostDelete
908
                 * @since Symphony 2.7.0
909
                 * @param string $context
910
                 * '/system/authors/'
911
                 * @param integer $author_id
912
                 *  The ID of Author ID that is about to be deleted
913
                 * @param Author $author
914
                 *  The Author object.
915
                 * @param integer $result
916
                 *  The result of the delete statement
917
                 */
918
                Symphony::ExtensionManager()->notifyMembers('AuthorPostDelete', '/system/authors/', array(
919
                    'author_id' => $author_id,
920
                    'author' => $this->_Author,
921
                    'result' => $result
922
                ));
923
924
                redirect(SYMPHONY_URL . '/system/authors/');
925
            } else {
926
                $this->pageAlert(__('You cannot remove yourself as you are the active Author.'), Alert::ERROR);
927
            }
928
        }
929
    }
930
}
931