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.
Completed
Pull Request — integration (#2604)
by Brendan
05:01
created

contentSystemAuthors   F

Complexity

Total Complexity 149

Size/Duplication

Total Lines 816
Duplicated Lines 4.17 %

Coupling/Cohesion

Components 0
Dependencies 16

Importance

Changes 7
Bugs 1 Features 0
Metric Value
c 7
b 1
f 0
dl 34
loc 816
rs 1.263
wmc 149
lcom 0
cbo 16

9 Methods

Rating   Name   Duplication   Size   Complexity  
A parseContext() 13 13 2
A sort() 0 10 3
D __viewIndex() 0 138 22
C __actionIndex() 0 48 8
A __viewNew() 0 4 1
F __form() 0 353 55
A __viewEdit() 0 4 1
C __actionNew() 9 56 11
F __actionEdit() 12 167 46

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
    class contentSystemAuthors extends AdministrationPage
13
    {
14
        public $_Author;
15
        public $_errors = array();
16
17
        /**
18
         * The Authors page has /action/id/flag/ context.
19
         * eg. /edit/1/saved/
20
         *
21
         * @param array $context
22
         * @param array $parts
23
         * @return array
24
         */
25 View Code Duplication
        public function parseContext(array &$context, array $parts)
26
        {
27
            // Order is important!
28
            $params = array_fill_keys(array('action', 'id', 'flag'), null);
29
30
            if (isset($parts[2])) {
31
                $extras = preg_split('/\//', $parts[2], -1, PREG_SPLIT_NO_EMPTY);
32
                list($params['action'], $params['id'], $params['flag']) = $extras;
33
                $params['id'] = (int)$params['id'];
34
            }
35
36
            $context = array_filter($params);
37
        }
38
39
        public function sort(&$sort, &$order, $params)
0 ignored issues
show
Unused Code introduced by
The parameter $params is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
40
        {
41
            if (is_null($sort) || $sort === 'name') {
42
                return AuthorManager::fetch("first_name $order,  last_name", $order);
43
            } else {
44
                $sort = General::sanitize($sort);
45
            }
46
47
            return AuthorManager::fetch($sort, $order);
48
        }
49
50
        public function __viewIndex()
51
        {
52
            $this->setPageType('table');
53
            $this->setTitle(__('%1$s &ndash; %2$s', array(__('Authors'), __('Symphony'))));
54
55
            if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
56
                $this->appendSubheading(__('Authors'),
57
                    Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL() . 'new/',
58
                        __('Create a new author'), 'create button', null, array('accesskey' => 'c')));
59
            } else {
60
                $this->appendSubheading(__('Authors'));
61
            }
62
63
            Sortable::initialize($this, $authors, $sort, $order);
64
65
            $columns = array(
66
                array(
67
                    'label' => __('Name'),
68
                    'sortable' => true,
69
                    'handle' => 'name'
70
                ),
71
                array(
72
                    'label' => __('Email Address'),
73
                    'sortable' => true,
74
                    'handle' => 'email'
75
                ),
76
                array(
77
                    'label' => __('Last Seen'),
78
                    'sortable' => true,
79
                    'handle' => 'last_seen'
80
                )
81
            );
82
83
            if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
84
                $columns = array_merge($columns, array(
85
                    array(
86
                        'label' => __('User Type'),
87
                        'sortable' => true,
88
                        'handle' => 'user_type'
89
                    ),
90
                    array(
91
                        'label' => __('Language'),
92
                        'sortable' => true,
93
                        'handle' => 'language'
94
                    )
95
                ));
96
            }
97
98
            $aTableHead = Sortable::buildTableHeaders($columns, $sort, $order,
99
                (isset($_REQUEST['filter']) ? '&amp;filter=' . $_REQUEST['filter'] : ''));
100
101
            $aTableBody = array();
102
103
            if (!is_array($authors) || empty($authors)) {
104
                $aTableBody = array(
105
                    Widget::TableRow(array(Widget::TableData(__('None found.'), 'inactive', null, count($aTableHead))),
106
                        'odd')
107
                );
108
            } else {
109
                foreach ($authors as $a) {
110
                    // Setup each cell
111
                    if (
112
                        (Symphony::Author()->isDeveloper() || (Symphony::Author()->isManager() && !$a->isDeveloper()))
113
                        || Symphony::Author()->get('id') === $a->get('id')
114
                    ) {
115
                        $td1 = Widget::TableData(
116
                            Widget::Anchor($a->getFullName(),
117
                                Administration::instance()->getCurrentPageURL() . 'edit/' . $a->get('id') . '/',
118
                                $a->get('username'), 'author')
119
                        );
120
                    } else {
121
                        $td1 = Widget::TableData($a->getFullName(), 'inactive');
122
                    }
123
124
                    // Can this Author be edited by the current Author?
125
                    if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
126
                        if ($a->get('id') !== Symphony::Author()->get('id')) {
127
                            $td1->appendChild(Widget::Label(__('Select Author %s', array($a->getFullName())), null,
128
                                'accessible', null, array(
129
                                    'for' => 'author-' . $a->get('id')
130
                                )));
131
                            $td1->appendChild(Widget::Input('items[' . $a->get('id') . ']', 'on', 'checkbox', array(
132
                                'id' => 'author-' . $a->get('id')
133
                            )));
134
                        }
135
                    }
136
137
                    $td2 = Widget::TableData(Widget::Anchor($a->get('email'), 'mailto:' . $a->get('email'),
138
                        __('Email this author')));
139
140
                    if (!is_null($a->get('last_seen'))) {
141
                        $td3 = Widget::TableData(
142
                            DateTimeObj::format($a->get('last_seen'), __SYM_DATETIME_FORMAT__)
0 ignored issues
show
Security Bug introduced by
It seems like \DateTimeObj::format($a-..._SYM_DATETIME_FORMAT__) targeting DateTimeObj::format() can also be of type false; however, Widget::TableData() does only seem to accept object<XMLElement>|string, did you maybe forget to handle an error condition?
Loading history...
143
                        );
144
                    } else {
145
                        $td3 = Widget::TableData(__('Unknown'), 'inactive');
146
                    }
147
148
                    if ($a->isDeveloper()) {
149
                        $type = 'Developer';
150
                    } elseif ($a->isManager()) {
151
                        $type = 'Manager';
152
                    } else {
153
                        $type = 'Author';
154
                    }
155
156
                    $td4 = Widget::TableData(__($type));
157
158
                    $languages = Lang::getAvailableLanguages();
159
160
                    $td5 = Widget::TableData($a->get("language") === null ? __("System Default") : $languages[$a->get("language")]);
161
162
                    // Add a row to the body array, assigning each cell to the row
163
                    if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
164
                        $aTableBody[] = Widget::TableRow(array($td1, $td2, $td3, $td4, $td5));
165
                    } else {
166
                        $aTableBody[] = Widget::TableRow(array($td1, $td2, $td3));
167
                    }
168
                }
169
            }
170
171
            $table = Widget::Table(
172
                Widget::TableHead($aTableHead),
173
                null,
174
                Widget::TableBody($aTableBody),
175
                'selectable',
176
                null,
177
                array('role' => 'directory', 'aria-labelledby' => 'symphony-subheading')
178
            );
179
180
            $this->Form->appendChild($table);
181
182
            $version = new XMLElement('p', 'Symphony ' . Symphony::Configuration()->get('version', 'symphony'), array(
183
                'id' => 'version'
184
            ));
185
186
            $this->Form->appendChild($version);
187
        }
188
189
        public function __actionIndex()
0 ignored issues
show
Coding Style introduced by
__actionIndex uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
190
        {
191
            $checked = (is_array($_POST['items'])) ? array_keys($_POST['items']) : null;
192
193
            if (is_array($checked) && !empty($checked)) {
194
                /**
195
                 * Extensions can listen for any custom actions that were added
196
                 * through `AddCustomPreferenceFieldsets` or `AddCustomActions`
197
                 * delegates.
198
                 *
199
                 * @delegate CustomActions
200
                 * @since Symphony 2.3.2
201
                 * @param string $context
202
                 *  '/system/authors/'
203
                 * @param array $checked
204
                 *  An array of the selected rows. The value is usually the ID of the
205
                 *  the associated object.
206
                 */
207
                Symphony::ExtensionManager()->notifyMembers('CustomActions', '/system/authors/', array(
208
                    'checked' => $checked
209
                ));
210
211
                if ($_POST['with-selected'] === 'delete') {
212
                    /**
213
                     * Prior to deleting an author, provided with an array of Author ID's.
214
                     *
215
                     * @delegate AuthorPreDelete
216
                     * @since Symphony 2.2
217
                     * @param string $context
218
                     * '/system/authors/'
219
                     * @param array $author_ids
220
                     *  An array of Author ID that are about to be removed
221
                     */
222
                    Symphony::ExtensionManager()->notifyMembers('AuthorPreDelete', '/system/authors/',
223
                        array('author_ids' => $checked));
224
225
                    foreach ($checked as $author_id) {
226
                        $a = AuthorManager::fetchByID($author_id);
227
228
                        if (is_object($a) && $a->get('id') !== Symphony::Author()->get('id')) {
229
                            AuthorManager::delete($author_id);
230
                        }
231
                    }
232
233
                    redirect(SYMPHONY_URL . '/system/authors/');
234
                }
235
            }
236
        }
237
238
        // Both the Edit and New pages need the same form
239
        public function __viewNew()
240
        {
241
            $this->__form();
242
        }
243
244
        public function __form()
0 ignored issues
show
Coding Style introduced by
__form uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
245
        {
246
            // Handle unknown context
247
            if (!in_array($this->_context['action'], array('new', 'edit'))) {
248
                Administration::instance()->errorPageNotFound();
249
            }
250
251
            if ($this->_context['action'] === 'new' && !Symphony::Author()->isDeveloper() && !Symphony::Author()->isManager()) {
252
                Administration::instance()->throwCustomError(
253
                    __('You are not authorised to access this page.'),
254
                    __('Access Denied'),
255
                    Page::HTTP_STATUS_UNAUTHORIZED
256
                );
257
            }
258
259
            if (isset($this->_context['flag'])) {
260
                $time = Widget::Time();
261
262
                switch ($this->_context['flag']) {
263
                    case 'saved':
264
                        $message = __('Author updated at %s.', array($time->generate()));
265
                        break;
266
                    case 'created':
267
                        $message = __('Author created at %s.', array($time->generate()));
268
                }
269
270
                $this->pageAlert(
271
                    $message
0 ignored issues
show
Bug introduced by
The variable $message does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
272
                    . ' <a href="' . SYMPHONY_URL . '/system/authors/new/" accesskey="c">'
273
                    . __('Create another?')
274
                    . '</a> <a href="' . SYMPHONY_URL . '/system/authors/" accesskey="a">'
275
                    . __('View all Authors')
276
                    . '</a>',
277
                    Alert::SUCCESS
278
                );
279
            }
280
281
            $this->setPageType('form');
282
            $isOwner = false;
283
            $isEditing = ($this->_context['action'] === 'edit');
284
285
            if (isset($_POST['fields'])) {
286
                $author = $this->_Author;
287
            } elseif ($isEditing) {
288
                if (!$author_id = $this->_context['id']) {
289
                    redirect(SYMPHONY_URL . '/system/authors/');
290
                }
291
292
                if (!$author = AuthorManager::fetchByID($author_id)) {
293
                    Administration::instance()->throwCustomError(
294
                        __('The author profile you requested does not exist.'),
295
                        __('Author not found'),
296
                        Page::HTTP_STATUS_NOT_FOUND
297
                    );
298
                }
299
            } else {
300
                $author = new Author;
301
            }
302
303
            if ($isEditing && $author->get('id') === Symphony::Author()->get('id')) {
304
                $isOwner = true;
305
            }
306
307
            if ($isEditing && !$isOwner && !Symphony::Author()->isDeveloper() && !Symphony::Author()->isManager()) {
308
                Administration::instance()->throwCustomError(
309
                    __('You are not authorised to edit other authors.'),
310
                    __('Access Denied'),
311
                    Page::HTTP_STATUS_FORBIDDEN
312
                );
313
            }
314
315
            $this->setTitle(__(($this->_context['action'] === 'new' ? '%2$s &ndash; %3$s' : '%1$s &ndash; %2$s &ndash; %3$s'),
316
                array($author->getFullName(), __('Authors'), __('Symphony'))));
317
            $this->appendSubheading(($this->_context['action'] === 'new' ? __('Untitled') : $author->getFullName()));
318
            $this->insertBreadcrumbs(array(
319
                Widget::Anchor(__('Authors'), SYMPHONY_URL . '/system/authors/'),
320
            ));
321
322
            // Essentials
323
            $group = new XMLElement('fieldset');
324
            $group->setAttribute('class', 'settings');
325
            $group->appendChild(new XMLElement('legend', __('Essentials')));
326
327
            $div = new XMLElement('div');
328
            $div->setAttribute('class', 'two columns');
329
330
            $label = Widget::Label(__('First Name'), null, 'column');
331
            $label->appendChild(Widget::Input('fields[first_name]', $author->get('first_name')));
332
            $div->appendChild((isset($this->_errors['first_name']) ? Widget::Error($label,
333
                $this->_errors['first_name']) : $label));
334
335
336
            $label = Widget::Label(__('Last Name'), null, 'column');
337
            $label->appendChild(Widget::Input('fields[last_name]', $author->get('last_name')));
338
            $div->appendChild((isset($this->_errors['last_name']) ? Widget::Error($label,
339
                $this->_errors['last_name']) : $label));
340
341
            $group->appendChild($div);
342
343
            $label = Widget::Label(__('Email Address'));
344
            $label->appendChild(Widget::Input('fields[email]', $author->get('email'), 'text',
345
                array('autocomplete' => 'off')));
346
            $group->appendChild((isset($this->_errors['email']) ? Widget::Error($label,
347
                $this->_errors['email']) : $label));
348
349
            $this->Form->appendChild($group);
350
351
            // Login Details
352
            $group = new XMLElement('fieldset');
353
            $group->setAttribute('class', 'settings');
354
            $group->appendChild(new XMLElement('legend', __('Login Details')));
355
356
            $div = new XMLElement('div');
357
358
            $label = Widget::Label(__('Username'));
359
            $label->appendChild(Widget::Input('fields[username]', $author->get('username'), 'text',
360
                array('autocomplete' => 'off')));
361
            $div->appendChild((isset($this->_errors['username']) ? Widget::Error($label,
362
                $this->_errors['username']) : $label));
363
364
            // Only developers can change the user type. Primary account should NOT be able to change this
365
            if ((Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) && !$author->isPrimaryAccount()) {
366
367
                // Create columns
368
                $div->setAttribute('class', 'two columns');
369
                $label->setAttribute('class', 'column');
370
371
                // User type
372
                $label = Widget::Label(__('User Type'), null, 'column');
373
374
                $options = array(
375
                    array('author', false, __('Author')),
376
                    array('manager', $author->isManager(), __('Manager'))
377
                );
378
379
                if (Symphony::Author()->isDeveloper()) {
380
                    $options[] = array('developer', $author->isDeveloper(), __('Developer'));
381
                }
382
383
                $label->appendChild(Widget::Select('fields[user_type]', $options));
384
                $div->appendChild($label);
385
            }
386
387
            $group->appendChild($div);
388
389
            // Password
390
            $fieldset = new XMLElement('fieldset', null, array('class' => 'two columns', 'id' => 'password'));
391
            $legend = new XMLElement('legend', __('Password'));
392
            $help = new XMLElement('i', __('Leave password fields blank to keep the current password'));
393
            $fieldset->appendChild($legend);
394
            $fieldset->appendChild($help);
395
396
            /*
397
                Password reset rules:
398
                - Primary account can edit all accounts.
399
                - Developers can edit all developers, managers and authors, and their own.
400
                - Managers can edit all Authors, and their own.
401
                - Authors can edit their own.
402
            */
403
            if ($isEditing && !(
404
                    // All accounts can edit their own
405
                    $isOwner
406
                    // Managers can edit all Authors, and their own.
407
                    || (Symphony::Author()->isManager() && $author->isAuthor())
408
                    // Primary account can edit all accounts.
409
                    || Symphony::Author()->isPrimaryAccount()
410
                    // Developers can edit all developers, managers and authors, and their own.
411
                    || Symphony::Author()->isDeveloper() && $author->isPrimaryAccount() === false
412
                )
413
            ) {
414
                $fieldset->setAttribute('class', 'three columns');
415
416
                $label = Widget::Label(null, null, 'column');
417
                $label->appendChild(Widget::Input('fields[old-password]', null, 'password',
418
                    array('placeholder' => __('Old Password'), 'autocomplete' => 'off')));
419
                $fieldset->appendChild((isset($this->_errors['old-password']) ? Widget::Error($label,
420
                    $this->_errors['old-password']) : $label));
421
            }
422
423
            // New password
424
            $placeholder = ($isEditing ? __('New Password') : __('Password'));
425
            $label = Widget::Label(null, null, 'column');
426
            $label->appendChild(Widget::Input('fields[password]', null, 'password',
427
                array('placeholder' => $placeholder, 'autocomplete' => 'off')));
428
            $fieldset->appendChild((isset($this->_errors['password']) ? Widget::Error($label,
429
                $this->_errors['password']) : $label));
430
431
            // Confirm password
432
            $label = Widget::Label(null, null, 'column');
433
            $label->appendChild(Widget::Input('fields[password-confirmation]', null, 'password',
434
                array('placeholder' => __('Confirm Password'), 'autocomplete' => 'off')));
435
            $fieldset->appendChild((isset($this->_errors['password-confirmation']) ? Widget::Error($label,
436
                $this->_errors['password']) : $label));
437
438
            $group->appendChild($fieldset);
439
440
            // Auth token
441
            if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
442
                $label = Widget::Label();
443
                $group->appendChild(Widget::Input('fields[auth_token_active]', 'no', 'hidden'));
444
                $input = Widget::Input('fields[auth_token_active]', 'yes', 'checkbox');
445
446
                if ($author->isTokenActive()) {
447
                    $input->setAttribute('checked', 'checked');
448
                }
449
450
                $temp = SYMPHONY_URL . '/login/' . $author->createAuthToken() . '/';
451
                $label->setValue(__('%s Allow remote login via',
452
                        array($input->generate())) . ' <a href="' . $temp . '">' . $temp . '</a>');
453
                $group->appendChild($label);
454
            }
455
456
            $label = Widget::Label(__('Default Area'));
457
458
            $sections = SectionManager::fetch(null, 'ASC', 'sortorder');
459
460
            $options = array();
461
462
            // If the Author is the Developer, allow them to set the Default Area to
463
            // be the Sections Index.
464
            if ($author->isDeveloper()) {
465
                $options[] = array(
466
                    '/blueprints/sections/',
467
                    $author->get('default_area') === '/blueprints/sections/',
468
                    __('Sections Index')
469
                );
470
            }
471
472
            if (is_array($sections) && !empty($sections)) {
473
                foreach ($sections as $s) {
474
                    $options[] = array($s->get('id'), $author->get('default_area') === $s->get('id'), $s->get('name'));
475
                }
476
            }
477
478
            /**
479
             * Allows injection or manipulation of the Default Area dropdown for an Author.
480
             * Take care with adding in options that are only valid for Developers, as if a
481
             * normal Author is set to that option, they will be redirected to their own
482
             * Author record.
483
             *
484
             *
485
             * @delegate AddDefaultAuthorAreas
486
             * @since Symphony 2.2
487
             * @param string $context
488
             * '/system/authors/'
489
             * @param array $options
490
             * An associative array of options, suitable for use for the Widget::Select
491
             * function. By default this will be an array of the Sections in the current
492
             * installation. New options should be the path to the page after the `SYMPHONY_URL`
493
             * constant.
494
             * @param string $default_area
495
             * The current `default_area` for this Author.
496
             */
497
            Symphony::ExtensionManager()->notifyMembers('AddDefaultAuthorAreas', '/system/authors/', array(
498
                'options' => &$options,
499
                'default_area' => $author->get('default_area')
500
            ));
501
502
            $label->appendChild(Widget::Select('fields[default_area]', $options));
503
            $group->appendChild($label);
504
505
            $this->Form->appendChild($group);
506
507
            // Custom Language Selection
508
            $languages = Lang::getAvailableLanguages();
509
            if (count($languages) > 1) {
510
                // Get language names
511
                asort($languages);
512
513
                $group = new XMLElement('fieldset');
514
                $group->setAttribute('class', 'settings');
515
                $group->appendChild(new XMLElement('legend', __('Custom Preferences')));
516
517
                $label = Widget::Label(__('Language'));
518
519
                $options = array(
520
                    array(null, is_null($author->get('language')), __('System Default'))
521
                );
522
523
                foreach ($languages as $code => $name) {
524
                    $options[] = array($code, $code === $author->get('language'), $name);
525
                }
526
                $select = Widget::Select('fields[language]', $options);
527
                $label->appendChild($select);
528
                $group->appendChild($label);
529
530
                $this->Form->appendChild($group);
531
            }
532
533
            // Administration password double check
534
            if ($isEditing && !$isOwner) {
535
                $group = new XMLElement('fieldset');
536
                $group->setAttribute('class', 'settings');
537
                $group->setAttribute('id', 'confirmation');
538
                $group->appendChild(new XMLElement('legend', __('Confirmation')));
539
                $group->appendChild(new XMLELement('p', __('Please confirm changes to this author with your password.'),
540
                    array('class' => 'help')));
541
542
                $label = Widget::Label(__('Password'));
543
                $label->appendChild(Widget::Input('fields[confirm-change-password]', null, 'password', array(
544
                    'autocomplete' => 'off',
545
                    'placeholder' => __('Your Password')
546
                )));
547
                $group->appendChild(
548
                    isset($this->_errors['confirm-change-password']) ? Widget::Error($label,
549
                        $this->_errors['confirm-change-password']) : $label
550
                );
551
552
                $this->Form->appendChild($group);
553
            }
554
555
            // Actions
556
            $div = new XMLElement('div');
557
            $div->setAttribute('class', 'actions');
558
559
            $div->appendChild(Widget::Input('action[save]',
560
                ($this->_context['action'] === 'edit' ? __('Save Changes') : __('Create Author')), 'submit',
561
                array('accesskey' => 's')));
562
563
            if ($isEditing && !$isOwner && !$author->isPrimaryAccount()) {
564
                $button = new XMLElement('button', __('Delete'));
565
                $button->setAttributeArray(array(
566
                    'name' => 'action[delete]',
567
                    'class' => 'button confirm delete',
568
                    'title' => __('Delete this author'),
569
                    'type' => 'submit',
570
                    'accesskey' => 'd',
571
                    'data-message' => __('Are you sure you want to delete this author?')
572
                ));
573
                $div->appendChild($button);
574
            }
575
576
            $this->Form->appendChild($div);
577
578
            /**
579
             * Allows the injection of custom form fields given the current `$this->Form`
580
             * object. Please note that this custom data should be saved in own extension
581
             * tables and that modifying `tbl_authors` to house your data is highly discouraged.
582
             *
583
             * @delegate AddElementstoAuthorForm
584
             * @since Symphony 2.2
585
             * @param string $context
586
             * '/system/authors/'
587
             * @param XMLElement $form
588
             * The contents of `$this->Form` after all the default form elements have been appended.
589
             * @param Author $author
590
             * The current Author object that is being edited
591
             */
592
            Symphony::ExtensionManager()->notifyMembers('AddElementstoAuthorForm', '/system/authors/', array(
593
                'form' => &$this->Form,
594
                'author' => $author
595
            ));
596
        }
597
598
        public function __viewEdit()
599
        {
600
            $this->__form();
601
        }
602
603
        public function __actionNew()
0 ignored issues
show
Coding Style introduced by
__actionNew uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
604
        {
605
            if (@array_key_exists('save', $_POST['action']) || @array_key_exists('done', $_POST['action'])) {
606
                $fields = $_POST['fields'];
607
608
                $this->_Author = new Author;
609
                $this->_Author->set('user_type', $fields['user_type']);
610
                $this->_Author->set('primary', 'no');
611
                $this->_Author->set('email', $fields['email']);
612
                $this->_Author->set('username', General::sanitize($fields['username']));
613
                $this->_Author->set('first_name', General::sanitize($fields['first_name']));
614
                $this->_Author->set('last_name', General::sanitize($fields['last_name']));
615
                $this->_Author->set('last_seen', null);
616
                $this->_Author->set('password',
617
                    (trim($fields['password']) === '' ? '' : Cryptography::hash(Symphony::Database()->cleanValue($fields['password']))));
618
                $this->_Author->set('default_area', $fields['default_area']);
619
                $this->_Author->set('auth_token_active',
620
                    ($fields['auth_token_active'] ? $fields['auth_token_active'] : 'no'));
621
                $this->_Author->set('language', isset($fields['language']) ? $fields['language'] : null);
622
623
                if ($this->_Author->validate($this->_errors)) {
624
                    if ($fields['password'] !== $fields['password-confirmation']) {
625
                        $this->_errors['password'] = $this->_errors['password-confirmation'] = __('Passwords did not match');
626
                    } elseif ($author_id = $this->_Author->commit()) {
627
                        /**
628
                         * Creation of a new Author. The Author object is provided as read
629
                         * only through this delegate.
630
                         *
631
                         * @delegate AuthorPostCreate
632
                         * @since Symphony 2.2
633
                         * @param string $context
634
                         * '/system/authors/'
635
                         * @param Author $author
636
                         *  The Author object that has just been created
637
                         */
638
                        Symphony::ExtensionManager()->notifyMembers('AuthorPostCreate', '/system/authors/',
639
                            array('author' => $this->_Author));
640
641
                        redirect(SYMPHONY_URL . "/system/authors/edit/$author_id/created/");
642
                    }
643
                }
644
645
                if (is_array($this->_errors) && !empty($this->_errors)) {
646
                    $this->pageAlert(__('There were some problems while attempting to save. Please check below for problem fields.'),
647
                        Alert::ERROR);
648 View Code Duplication
                } else {
649
                    $this->pageAlert(
650
                        __('Unknown errors occurred while attempting to save.')
651
                        . '<a href="' . SYMPHONY_URL . '/system/log/">'
652
                        . __('Check your activity log')
653
                        . '</a>.',
654
                        Alert::ERROR
655
                    );
656
                }
657
            }
658
        }
659
660
        public function __actionEdit()
0 ignored issues
show
Coding Style introduced by
__actionEdit uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
661
        {
662
            if (!$author_id = $this->_context['id']) {
663
                redirect(SYMPHONY_URL . '/system/authors/');
664
            }
665
666
            $isOwner = ($author_id === Symphony::Author()->get('id'));
667
668
            if (@array_key_exists('save', $_POST['action']) || @array_key_exists('done', $_POST['action'])) {
669
                $fields = $_POST['fields'];
670
                $this->_Author = AuthorManager::fetchByID($author_id);
671
                $authenticated = false;
672
673
                if ($fields['email'] !== $this->_Author->get('email')) {
674
                    $changing_email = true;
675
                }
676
677
                // Check the old password was correct
678
                if (isset($fields['old-password']) && strlen(trim($fields['old-password'])) > 0 && Cryptography::compare(Symphony::Database()->cleanValue(trim($fields['old-password'])),
679
                        $this->_Author->get('password'))
680
                ) {
681
                    $authenticated = true;
682
683
                    // Developers don't need to specify the old password, unless it's their own account
684
                } elseif (
685
686
                    // All accounts can edit their own
687
                    $isOwner
688
                    // Managers can edit all Authors, and their own.
689
                    || (Symphony::Author()->isManager() && $this->_Author->isAuthor())
690
                    // Primary account can edit all accounts.
691
                    || Symphony::Author()->isPrimaryAccount()
692
                    // Developers can edit all developers, managers and authors, and their own.
693
                    || Symphony::Author()->isDeveloper() && $this->_Author->isPrimaryAccount() === false
694
                ) {
695
                    $authenticated = true;
696
                }
697
698
                $this->_Author->set('id', $author_id);
699
700
                if ($this->_Author->isPrimaryAccount() || ($isOwner && Symphony::Author()->isDeveloper())) {
701
                    $this->_Author->set('user_type',
702
                        'developer'); // Primary accounts are always developer, Developers can't lower their level
703
                } elseif ((Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) && isset($fields['user_type'])) {
704
                    $this->_Author->set('user_type', $fields['user_type']); // Only developer can change user type
705
                }
706
707
                $this->_Author->set('email', $fields['email']);
708
                $this->_Author->set('username', General::sanitize($fields['username']));
709
                $this->_Author->set('first_name', General::sanitize($fields['first_name']));
710
                $this->_Author->set('last_name', General::sanitize($fields['last_name']));
711
                $this->_Author->set('language', isset($fields['language']) ? $fields['language'] : null);
712
713
                if (trim($fields['password']) !== '') {
714
                    $this->_Author->set('password',
715
                        Cryptography::hash(Symphony::Database()->cleanValue($fields['password'])));
716
                    $changing_password = true;
717
                }
718
719
                // Don't allow authors to set the Section Index as a default area
720
                // If they had it previously set, just save `null` which will redirect
721
                // the Author (when logging in) to their own Author record
722
                if (
723
                    $this->_Author->get('user_type') === 'author'
724
                    && $fields['default_area'] === '/blueprints/sections/'
725
                ) {
726
                    $this->_Author->set('default_area', null);
727
                } else {
728
                    $this->_Author->set('default_area', $fields['default_area']);
729
                }
730
731
                $this->_Author->set('auth_token_active',
732
                    ($fields['auth_token_active'] ? $fields['auth_token_active'] : 'no'));
733
734
                if ($this->_Author->validate($this->_errors)) {
735
                    // Admin changing another profile
736
                    if (!$isOwner) {
737
                        $entered_password = Symphony::Database()->cleanValue($fields['confirm-change-password']);
738
739
                        if (!isset($fields['confirm-change-password']) || empty($fields['confirm-change-password'])) {
740
                            $this->_errors['confirm-change-password'] = __('Please provide your own password to make changes to this author.');
741
                        } elseif (Cryptography::compare($entered_password,
742
                                Symphony::Author()->get('password')) !== true
743
                        ) {
744
                            $this->_errors['confirm-change-password'] = __('Wrong password, please enter your own password to make changes to this author.');
745
                        }
746
                    }
747
748
                    // Author is changing their password
749
                    if (!$authenticated && ($changing_password || $changing_email)) {
750
                        if ($changing_password) {
0 ignored issues
show
Bug introduced by
The variable $changing_password does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
751
                            $this->_errors['old-password'] = __('Wrong password. Enter old password to change it.');
752
                        } elseif ($changing_email) {
0 ignored issues
show
Bug introduced by
The variable $changing_email does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
753
                            $this->_errors['old-password'] = __('Wrong password. Enter old one to change email address.');
754
                        }
755
756
                        // Passwords provided, but doesn't match.
757
                    } elseif (($fields['password'] !== '' || $fields['password-confirmation'] !== '') && $fields['password'] !== $fields['password-confirmation']) {
758
                        $this->_errors['password'] = $this->_errors['password-confirmation'] = __('Passwords did not match');
759
                    }
760
761
                    // All good, let's save the Author
762
                    if (is_array($this->_errors) && empty($this->_errors) && $this->_Author->commit()) {
763
                        Symphony::Database()->delete('tbl_forgotpass', "`expiry` < ? OR `author_id` = ?", array(
764
                            DateTimeObj::getGMT('c'),
765
                            $author_id
766
                        ));
767
768
                        if ($isOwner) {
769
                            Administration::instance()->login($this->_Author->get('username'),
770
                                $this->_Author->get('password'), true);
771
                        }
772
773
                        /**
774
                         * After editing an author, provided with the Author object
775
                         *
776
                         * @delegate AuthorPostEdit
777
                         * @since Symphony 2.2
778
                         * @param string $context
779
                         * '/system/authors/'
780
                         * @param Author $author
781
                         * An Author object
782
                         */
783
                        Symphony::ExtensionManager()->notifyMembers('AuthorPostEdit', '/system/authors/',
784
                            array('author' => $this->_Author));
785
786
                        redirect(SYMPHONY_URL . '/system/authors/edit/' . $author_id . '/saved/');
787
788
                        // Problems.
789 View Code Duplication
                    } else {
790
                        $this->pageAlert(
791
                            __('Unknown errors occurred while attempting to save.')
792
                            . '<a href="' . SYMPHONY_URL . '/system/log/">'
793
                            . __('Check your activity log')
794
                            . '</a>.',
795
                            Alert::ERROR
796
                        );
797
                    }
798
                }
799
800
                // Author doesn't have valid data, throw back.
801 View Code Duplication
                if (is_array($this->_errors) && !empty($this->_errors)) {
802
                    $this->pageAlert(__('There were some problems while attempting to save. Please check below for problem fields.'),
803
                        Alert::ERROR);
804
                }
805
            } elseif (@array_key_exists('delete', $_POST['action'])) {
806
                /**
807
                 * Prior to deleting an author, provided with the Author ID.
808
                 *
809
                 * @delegate AuthorPreDelete
810
                 * @since Symphony 2.2
811
                 * @param string $context
812
                 * '/system/authors/'
813
                 * @param integer $author_id
814
                 *  The ID of Author ID that is about to be deleted
815
                 */
816
                Symphony::ExtensionManager()->notifyMembers('AuthorPreDelete', '/system/authors/',
817
                    array('author_id' => $author_id));
818
819
                if (!$isOwner) {
820
                    AuthorManager::delete($author_id);
821
                    redirect(SYMPHONY_URL . '/system/authors/');
822
                } else {
823
                    $this->pageAlert(__('You cannot remove yourself as you are the active Author.'), Alert::ERROR);
824
                }
825
            }
826
        }
827
    }
828