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
Push — integration ( 5cdb31...81c93f )
by Brendan
04:03
created

contentSystemAuthors   F

Complexity

Total Complexity 149

Size/Duplication

Total Lines 762
Duplicated Lines 4.46 %

Coupling/Cohesion

Components 0
Dependencies 16

Importance

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

9 Methods

Rating   Name   Duplication   Size   Complexity  
D __viewIndex() 0 130 22
A parseContext() 13 13 2
A sort() 0 10 3
C __actionIndex() 0 47 8
A __viewNew() 0 4 1
A __viewEdit() 0 4 1
F __form() 0 323 55
C __actionNew() 9 52 11
F __actionEdit() 12 156 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
13
class contentSystemAuthors extends AdministrationPage
14
{
15
    public $_Author;
16
    public $_errors = array();
17
18
    /**
19
     * The Authors page has /action/id/flag/ context.
20
     * eg. /edit/1/saved/
21
     *
22
     * @param array $context
23
     * @param array $parts
24
     * @return array
25
     */
26 View Code Duplication
    public function parseContext(array &$context, array $parts)
27
    {
28
        // Order is important!
29
        $params = array_fill_keys(array('action', 'id', 'flag'), null);
30
31
        if (isset($parts[2])) {
32
            $extras = preg_split('/\//', $parts[2], -1, PREG_SPLIT_NO_EMPTY);
33
            list($params['action'], $params['id'], $params['flag']) = $extras;
34
            $params['id'] = (int)$params['id'];
35
        }
36
37
        $context = array_filter($params);
38
    }
39
40
    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...
41
    {
42
        if (is_null($sort) || $sort === 'name') {
43
            return AuthorManager::fetch("first_name $order,  last_name", $order);
44
        } else {
45
            $sort = General::sanitize($sort);
46
        }
47
48
        return AuthorManager::fetch($sort, $order);
49
    }
50
51
    public function __viewIndex()
52
    {
53
        $this->setPageType('table');
54
        $this->setTitle(__('%1$s &ndash; %2$s', array(__('Authors'), __('Symphony'))));
55
56
        if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
57
            $this->appendSubheading(__('Authors'), Widget::Anchor(__('Create New'), Administration::instance()->getCurrentPageURL().'new/', __('Create a new author'), 'create button', null, array('accesskey' => 'c')));
58
        } else {
59
            $this->appendSubheading(__('Authors'));
60
        }
61
62
        Sortable::initialize($this, $authors, $sort, $order);
63
64
        $columns = array(
65
            array(
66
                'label' => __('Name'),
67
                'sortable' => true,
68
                'handle' => 'name'
69
            ),
70
            array(
71
                'label' => __('Email Address'),
72
                'sortable' => true,
73
                'handle' => 'email'
74
            ),
75
            array(
76
                'label' => __('Last Seen'),
77
                'sortable' => true,
78
                'handle' => 'last_seen'
79
            )
80
        );
81
82
        if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
83
            $columns = array_merge($columns, array(
84
                array(
85
                    'label' => __('User Type'),
86
                    'sortable' => true,
87
                    'handle' => 'user_type'
88
                ),
89
                array(
90
                    'label' => __('Language'),
91
                    'sortable' => true,
92
                    'handle' => 'language'
93
                )
94
            ));
95
        }
96
97
        $aTableHead = Sortable::buildTableHeaders($columns, $sort, $order, (isset($_REQUEST['filter']) ? '&amp;filter=' . $_REQUEST['filter'] : ''));
98
99
        $aTableBody = array();
100
101
        if (!is_array($authors) || empty($authors)) {
102
            $aTableBody = array(
103
                Widget::TableRow(array(Widget::TableData(__('None found.'), 'inactive', null, count($aTableHead))), 'odd')
104
            );
105
        } else {
106
            foreach ($authors as $a) {
107
                // Setup each cell
108
                if (
109
                    (Symphony::Author()->isDeveloper() || (Symphony::Author()->isManager() && !$a->isDeveloper()))
110
                    || Symphony::Author()->get('id') === $a->get('id')
111
                ) {
112
                    $td1 = Widget::TableData(
113
                        Widget::Anchor($a->getFullName(), Administration::instance()->getCurrentPageURL() . 'edit/' . $a->get('id') . '/', $a->get('username'), 'author')
114
                    );
115
                } else {
116
                    $td1 = Widget::TableData($a->getFullName(), 'inactive');
117
                }
118
119
                // Can this Author be edited by the current Author?
120
                if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
121
                    if ($a->get('id') !== Symphony::Author()->get('id')) {
122
                        $td1->appendChild(Widget::Label(__('Select Author %s', array($a->getFullName())), null, 'accessible', null, array(
123
                            'for' => 'author-' . $a->get('id')
124
                        )));
125
                        $td1->appendChild(Widget::Input('items['.$a->get('id').']', 'on', 'checkbox', array(
126
                            'id' => 'author-' . $a->get('id')
127
                        )));
128
                    }
129
                }
130
131
                $td2 = Widget::TableData(Widget::Anchor($a->get('email'), 'mailto:'.$a->get('email'), __('Email this author')));
132
133
                if (!is_null($a->get('last_seen'))) {
134
                    $td3 = Widget::TableData(
135
                        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...
136
                    );
137
                } else {
138
                    $td3 = Widget::TableData(__('Unknown'), 'inactive');
139
                }
140
141
                if ($a->isDeveloper()) {
142
                    $type = 'Developer';
143
                } elseif ($a->isManager()) {
144
                    $type = 'Manager';
145
                } else {
146
                    $type = 'Author';
147
                }
148
149
                $td4 = Widget::TableData(__($type));
150
151
                $languages = Lang::getAvailableLanguages();
152
153
                $td5 = Widget::TableData($a->get("language") === null ? __("System Default") : $languages[$a->get("language")]);
154
155
                // Add a row to the body array, assigning each cell to the row
156
                if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
157
                    $aTableBody[] = Widget::TableRow(array($td1, $td2, $td3, $td4, $td5));
158
                } else {
159
                    $aTableBody[] = Widget::TableRow(array($td1, $td2, $td3));
160
                }
161
            }
162
        }
163
164
        $table = Widget::Table(
165
            Widget::TableHead($aTableHead),
166
            null,
167
            Widget::TableBody($aTableBody),
168
            'selectable',
169
            null,
170
            array('role' => 'directory', 'aria-labelledby' => 'symphony-subheading')
171
        );
172
173
        $this->Form->appendChild($table);
174
175
        $version = new XMLElement('p', 'Symphony ' . Symphony::Configuration()->get('version', 'symphony'), array(
176
            'id' => 'version'
177
        ));
178
179
        $this->Form->appendChild($version);
180
    }
181
182
    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...
183
    {
184
        $checked = (is_array($_POST['items'])) ? array_keys($_POST['items']) : null;
185
186
        if (is_array($checked) && !empty($checked)) {
187
            /**
188
             * Extensions can listen for any custom actions that were added
189
             * through `AddCustomPreferenceFieldsets` or `AddCustomActions`
190
             * delegates.
191
             *
192
             * @delegate CustomActions
193
             * @since Symphony 2.3.2
194
             * @param string $context
195
             *  '/system/authors/'
196
             * @param array $checked
197
             *  An array of the selected rows. The value is usually the ID of the
198
             *  the associated object.
199
             */
200
            Symphony::ExtensionManager()->notifyMembers('CustomActions', '/system/authors/', array(
201
                'checked' => $checked
202
            ));
203
204
            if ($_POST['with-selected'] === 'delete') {
205
                /**
206
                * Prior to deleting an author, provided with an array of Author ID's.
207
                *
208
                * @delegate AuthorPreDelete
209
                * @since Symphony 2.2
210
                * @param string $context
211
                * '/system/authors/'
212
                * @param array $author_ids
213
                *  An array of Author ID that are about to be removed
214
                */
215
                Symphony::ExtensionManager()->notifyMembers('AuthorPreDelete', '/system/authors/', array('author_ids' => $checked));
216
217
                foreach ($checked as $author_id) {
218
                    $a = AuthorManager::fetchByID($author_id);
219
220
                    if (is_object($a) && $a->get('id') !== Symphony::Author()->get('id')) {
221
                        AuthorManager::delete($author_id);
222
                    }
223
                }
224
225
                redirect(SYMPHONY_URL . '/system/authors/');
226
            }
227
        }
228
    }
229
230
    // Both the Edit and New pages need the same form
231
    public function __viewNew()
232
    {
233
        $this->__form();
234
    }
235
236
    public function __viewEdit()
237
    {
238
        $this->__form();
239
    }
240
241
    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...
242
    {
243
        // Handle unknown context
244
        if (!in_array($this->_context['action'], array('new', 'edit'))) {
245
            Administration::instance()->errorPageNotFound();
246
        }
247
248
        if ($this->_context['action'] === 'new' && !Symphony::Author()->isDeveloper() && !Symphony::Author()->isManager()) {
249
            Administration::instance()->throwCustomError(
250
                __('You are not authorised to access this page.'),
251
                __('Access Denied'),
252
                Page::HTTP_STATUS_UNAUTHORIZED
253
            );
254
        }
255
256
        if (isset($this->_context['flag'])) {
257
            $time = Widget::Time();
258
259
            switch ($this->_context['flag']) {
260
                case 'saved':
261
                    $message = __('Author updated at %s.', array($time->generate()));
262
                    break;
263
                case 'created':
264
                    $message = __('Author created at %s.', array($time->generate()));
265
            }
266
267
            $this->pageAlert(
268
                $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...
269
                . ' <a href="' . SYMPHONY_URL . '/system/authors/new/" accesskey="c">'
270
                . __('Create another?')
271
                . '</a> <a href="' . SYMPHONY_URL . '/system/authors/" accesskey="a">'
272
                . __('View all Authors')
273
                . '</a>',
274
                Alert::SUCCESS
275
            );
276
        }
277
278
        $this->setPageType('form');
279
        $isOwner = false;
280
        $isEditing = ($this->_context['action'] === 'edit');
281
282
        if (isset($_POST['fields'])) {
283
            $author = $this->_Author;
284
        } elseif ($isEditing) {
285
            if (!$author_id = $this->_context['id']) {
286
                redirect(SYMPHONY_URL . '/system/authors/');
287
            }
288
289
            if (!$author = AuthorManager::fetchByID($author_id)) {
290
                Administration::instance()->throwCustomError(
291
                    __('The author profile you requested does not exist.'),
292
                    __('Author not found'),
293
                    Page::HTTP_STATUS_NOT_FOUND
294
                );
295
            }
296
        } else {
297
            $author = new Author;
298
        }
299
300
        if ($isEditing && $author->get('id') === Symphony::Author()->get('id')) {
301
            $isOwner = true;
302
        }
303
304
        if ($isEditing && !$isOwner && !Symphony::Author()->isDeveloper() && !Symphony::Author()->isManager()) {
305
            Administration::instance()->throwCustomError(
306
                __('You are not authorised to edit other authors.'),
307
                __('Access Denied'),
308
                Page::HTTP_STATUS_FORBIDDEN
309
            );
310
        }
311
312
        $this->setTitle(__(($this->_context['action'] === 'new' ? '%2$s &ndash; %3$s' : '%1$s &ndash; %2$s &ndash; %3$s'), array($author->getFullName(), __('Authors'), __('Symphony'))));
313
        $this->appendSubheading(($this->_context['action'] === 'new' ? __('Untitled') : $author->getFullName()));
314
        $this->insertBreadcrumbs(array(
315
            Widget::Anchor(__('Authors'), SYMPHONY_URL . '/system/authors/'),
316
        ));
317
318
        // Essentials
319
        $group = new XMLElement('fieldset');
320
        $group->setAttribute('class', 'settings');
321
        $group->appendChild(new XMLElement('legend', __('Essentials')));
322
323
        $div = new XMLElement('div');
324
        $div->setAttribute('class', 'two columns');
325
326
        $label = Widget::Label(__('First Name'), null, 'column');
327
        $label->appendChild(Widget::Input('fields[first_name]', $author->get('first_name')));
328
        $div->appendChild((isset($this->_errors['first_name']) ? Widget::Error($label, $this->_errors['first_name']) : $label));
329
330
331
        $label = Widget::Label(__('Last Name'), null, 'column');
332
        $label->appendChild(Widget::Input('fields[last_name]', $author->get('last_name')));
333
        $div->appendChild((isset($this->_errors['last_name']) ? Widget::Error($label, $this->_errors['last_name']) : $label));
334
335
        $group->appendChild($div);
336
337
        $label = Widget::Label(__('Email Address'));
338
        $label->appendChild(Widget::Input('fields[email]', $author->get('email'), 'text', array('autocomplete' => 'off')));
339
        $group->appendChild((isset($this->_errors['email']) ? Widget::Error($label, $this->_errors['email']) : $label));
340
341
        $this->Form->appendChild($group);
342
343
        // Login Details
344
        $group = new XMLElement('fieldset');
345
        $group->setAttribute('class', 'settings');
346
        $group->appendChild(new XMLElement('legend', __('Login Details')));
347
348
        $div = new XMLElement('div');
349
350
        $label = Widget::Label(__('Username'));
351
        $label->appendChild(Widget::Input('fields[username]', $author->get('username'), 'text', array('autocomplete' => 'off')));
352
        $div->appendChild((isset($this->_errors['username']) ? Widget::Error($label, $this->_errors['username']) : $label));
353
354
        // Only developers can change the user type. Primary account should NOT be able to change this
355
        if ((Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) && !$author->isPrimaryAccount()) {
356
357
            // Create columns
358
            $div->setAttribute('class', 'two columns');
359
            $label->setAttribute('class', 'column');
360
361
            // User type
362
            $label = Widget::Label(__('User Type'), null, 'column');
363
364
            $options = array(
365
                array('author', false, __('Author')),
366
                array('manager', $author->isManager(), __('Manager'))
367
            );
368
369
            if (Symphony::Author()->isDeveloper()) {
370
                $options[] = array('developer', $author->isDeveloper(), __('Developer'));
371
            }
372
373
            $label->appendChild(Widget::Select('fields[user_type]', $options));
374
            $div->appendChild($label);
375
        }
376
377
        $group->appendChild($div);
378
379
        // Password
380
        $fieldset = new XMLElement('fieldset', null, array('class' => 'two columns', 'id' => 'password'));
381
        $legend = new XMLElement('legend', __('Password'));
382
        $help = new XMLElement('i', __('Leave password fields blank to keep the current password'));
383
        $fieldset->appendChild($legend);
384
        $fieldset->appendChild($help);
385
386
        /*
387
            Password reset rules:
388
            - Primary account can edit all accounts.
389
            - Developers can edit all developers, managers and authors, and their own.
390
            - Managers can edit all Authors, and their own.
391
            - Authors can edit their own.
392
        */
393
        if ($isEditing && !(
394
            // All accounts can edit their own
395
            $isOwner
396
            // Managers can edit all Authors, and their own.
397
            || (Symphony::Author()->isManager() && $author->isAuthor())
398
            // Primary account can edit all accounts.
399
            || Symphony::Author()->isPrimaryAccount()
400
            // Developers can edit all developers, managers and authors, and their own.
401
            || Symphony::Author()->isDeveloper() && $author->isPrimaryAccount() === false
402
        )) {
403
            $fieldset->setAttribute('class', 'three columns');
404
405
            $label = Widget::Label(null, null, 'column');
406
            $label->appendChild(Widget::Input('fields[old-password]', null, 'password', array('placeholder' => __('Old Password'), 'autocomplete' => 'off')));
407
            $fieldset->appendChild((isset($this->_errors['old-password']) ? Widget::Error($label, $this->_errors['old-password']) : $label));
408
        }
409
410
        // New password
411
        $placeholder = ($isEditing ? __('New Password') : __('Password'));
412
        $label = Widget::Label(null, null, 'column');
413
        $label->appendChild(Widget::Input('fields[password]', null, 'password', array('placeholder' => $placeholder, 'autocomplete' => 'off')));
414
        $fieldset->appendChild((isset($this->_errors['password']) ? Widget::Error($label, $this->_errors['password']) : $label));
415
416
        // Confirm password
417
        $label = Widget::Label(null, null, 'column');
418
        $label->appendChild(Widget::Input('fields[password-confirmation]', null, 'password', array('placeholder' => __('Confirm Password'), 'autocomplete' => 'off')));
419
        $fieldset->appendChild((isset($this->_errors['password-confirmation']) ? Widget::Error($label, $this->_errors['password']) : $label));
420
421
        $group->appendChild($fieldset);
422
423
        // Auth token
424
        if (Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) {
425
            $label = Widget::Label();
426
            $group->appendChild(Widget::Input('fields[auth_token_active]', 'no', 'hidden'));
427
            $input = Widget::Input('fields[auth_token_active]', 'yes', 'checkbox');
428
429
            if ($author->isTokenActive()) {
430
                $input->setAttribute('checked', 'checked');
431
            }
432
433
            $temp = SYMPHONY_URL . '/login/' . $author->createAuthToken() . '/';
434
            $label->setValue(__('%s Allow remote login via', array($input->generate())) . ' <a href="' . $temp . '">' . $temp . '</a>');
435
            $group->appendChild($label);
436
        }
437
438
        $label = Widget::Label(__('Default Area'));
439
440
        $sections = SectionManager::fetch(null, 'ASC', 'sortorder');
441
442
        $options = array();
443
444
        // If the Author is the Developer, allow them to set the Default Area to
445
        // be the Sections Index.
446
        if ($author->isDeveloper()) {
447
            $options[] = array('/blueprints/sections/', $author->get('default_area') === '/blueprints/sections/', __('Sections Index'));
448
        }
449
450
        if (is_array($sections) && !empty($sections)) {
451
            foreach ($sections as $s) {
452
                $options[] = array($s->get('id'), $author->get('default_area') === $s->get('id'), $s->get('name'));
453
            }
454
        }
455
456
        /**
457
        * Allows injection or manipulation of the Default Area dropdown for an Author.
458
        * Take care with adding in options that are only valid for Developers, as if a
459
        * normal Author is set to that option, they will be redirected to their own
460
        * Author record.
461
        *
462
        *
463
        * @delegate AddDefaultAuthorAreas
464
        * @since Symphony 2.2
465
        * @param string $context
466
        * '/system/authors/'
467
        * @param array $options
468
        * An associative array of options, suitable for use for the Widget::Select
469
        * function. By default this will be an array of the Sections in the current
470
        * installation. New options should be the path to the page after the `SYMPHONY_URL`
471
        * constant.
472
        * @param string $default_area
473
        * The current `default_area` for this Author.
474
        */
475
        Symphony::ExtensionManager()->notifyMembers('AddDefaultAuthorAreas', '/system/authors/', array(
476
            'options' => &$options,
477
            'default_area' => $author->get('default_area')
478
        ));
479
480
        $label->appendChild(Widget::Select('fields[default_area]', $options));
481
        $group->appendChild($label);
482
483
        $this->Form->appendChild($group);
484
485
        // Custom Language Selection
486
        $languages = Lang::getAvailableLanguages();
487
        if (count($languages) > 1) {
488
            // Get language names
489
            asort($languages);
490
491
            $group = new XMLElement('fieldset');
492
            $group->setAttribute('class', 'settings');
493
            $group->appendChild(new XMLElement('legend', __('Custom Preferences')));
494
495
            $label = Widget::Label(__('Language'));
496
497
            $options = array(
498
                array(null, is_null($author->get('language')), __('System Default'))
499
            );
500
501
            foreach ($languages as $code => $name) {
502
                $options[] = array($code, $code === $author->get('language'), $name);
503
            }
504
            $select = Widget::Select('fields[language]', $options);
505
            $label->appendChild($select);
506
            $group->appendChild($label);
507
508
            $this->Form->appendChild($group);
509
        }
510
511
        // Administration password double check
512
        if ($isEditing && !$isOwner) {
513
            $group = new XMLElement('fieldset');
514
            $group->setAttribute('class', 'settings');
515
            $group->setAttribute('id', 'confirmation');
516
            $group->appendChild(new XMLElement('legend', __('Confirmation')));
517
            $group->appendChild(new XMLELement('p', __('Please confirm changes to this author with your password.'), array('class' => 'help')));
518
519
            $label = Widget::Label(__('Password'));
520
            $label->appendChild(Widget::Input('fields[confirm-change-password]', null, 'password', array(
521
                'autocomplete' => 'off',
522
                'placeholder' => __('Your Password')
523
            )));
524
            $group->appendChild(
525
                isset($this->_errors['confirm-change-password']) ? Widget::Error($label, $this->_errors['confirm-change-password']) : $label
526
            );
527
528
            $this->Form->appendChild($group);
529
        }
530
531
        // Actions
532
        $div = new XMLElement('div');
533
        $div->setAttribute('class', 'actions');
534
535
        $div->appendChild(Widget::Input('action[save]', ($this->_context['action'] === 'edit' ? __('Save Changes') : __('Create Author')), 'submit', array('accesskey' => 's')));
536
537
        if ($isEditing && !$isOwner && !$author->isPrimaryAccount()) {
538
            $button = new XMLElement('button', __('Delete'));
539
            $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?')));
540
            $div->appendChild($button);
541
        }
542
543
        $this->Form->appendChild($div);
544
545
        /**
546
        * Allows the injection of custom form fields given the current `$this->Form`
547
        * object. Please note that this custom data should be saved in own extension
548
        * tables and that modifying `tbl_authors` to house your data is highly discouraged.
549
        *
550
        * @delegate AddElementstoAuthorForm
551
        * @since Symphony 2.2
552
        * @param string $context
553
        * '/system/authors/'
554
        * @param XMLElement $form
555
        * The contents of `$this->Form` after all the default form elements have been appended.
556
        * @param Author $author
557
        * The current Author object that is being edited
558
        */
559
        Symphony::ExtensionManager()->notifyMembers('AddElementstoAuthorForm', '/system/authors/', array(
560
            'form' => &$this->Form,
561
            'author' => $author
562
        ));
563
    }
564
565
    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...
566
    {
567
        if (@array_key_exists('save', $_POST['action']) || @array_key_exists('done', $_POST['action'])) {
568
            $fields = $_POST['fields'];
569
570
            $this->_Author = new Author;
571
            $this->_Author->set('user_type', $fields['user_type']);
572
            $this->_Author->set('primary', 'no');
573
            $this->_Author->set('email', $fields['email']);
574
            $this->_Author->set('username', General::sanitize($fields['username']));
575
            $this->_Author->set('first_name', General::sanitize($fields['first_name']));
576
            $this->_Author->set('last_name', General::sanitize($fields['last_name']));
577
            $this->_Author->set('last_seen', null);
578
            $this->_Author->set('password', (trim($fields['password']) === '' ? '' : Cryptography::hash(Symphony::Database()->cleanValue($fields['password']))));
579
            $this->_Author->set('default_area', $fields['default_area']);
580
            $this->_Author->set('auth_token_active', ($fields['auth_token_active'] ? $fields['auth_token_active'] : 'no'));
581
            $this->_Author->set('language', isset($fields['language']) ? $fields['language'] : null);
582
583
            if ($this->_Author->validate($this->_errors)) {
584
                if ($fields['password'] !== $fields['password-confirmation']) {
585
                    $this->_errors['password'] = $this->_errors['password-confirmation'] = __('Passwords did not match');
586
                } elseif ($author_id = $this->_Author->commit()) {
587
                    /**
588
                     * Creation of a new Author. The Author object is provided as read
589
                     * only through this delegate.
590
                     *
591
                     * @delegate AuthorPostCreate
592
                     * @since Symphony 2.2
593
                     * @param string $context
594
                     * '/system/authors/'
595
                     * @param Author $author
596
                     *  The Author object that has just been created
597
                     */
598
                    Symphony::ExtensionManager()->notifyMembers('AuthorPostCreate', '/system/authors/', array('author' => $this->_Author));
599
600
                    redirect(SYMPHONY_URL . "/system/authors/edit/$author_id/created/");
601
                }
602
            }
603
604
            if (is_array($this->_errors) && !empty($this->_errors)) {
605
                $this->pageAlert(__('There were some problems while attempting to save. Please check below for problem fields.'), Alert::ERROR);
606 View Code Duplication
            } else {
607
                $this->pageAlert(
608
                    __('Unknown errors occurred while attempting to save.')
609
                    . '<a href="' . SYMPHONY_URL . '/system/log/">'
610
                    . __('Check your activity log')
611
                    . '</a>.',
612
                    Alert::ERROR
613
                );
614
            }
615
        }
616
    }
617
618
    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...
619
    {
620
        if (!$author_id = $this->_context['id']) {
621
            redirect(SYMPHONY_URL . '/system/authors/');
622
        }
623
624
        $isOwner = ($author_id === Symphony::Author()->get('id'));
625
626
        if (@array_key_exists('save', $_POST['action']) || @array_key_exists('done', $_POST['action'])) {
627
            $fields = $_POST['fields'];
628
            $this->_Author = AuthorManager::fetchByID($author_id);
629
            $authenticated = false;
630
631
            if ($fields['email'] !== $this->_Author->get('email')) {
632
                $changing_email = true;
633
            }
634
635
            // Check the old password was correct
636
            if (isset($fields['old-password']) && strlen(trim($fields['old-password'])) > 0 && Cryptography::compare(Symphony::Database()->cleanValue(trim($fields['old-password'])), $this->_Author->get('password'))) {
637
                $authenticated = true;
638
639
                // Developers don't need to specify the old password, unless it's their own account
640
            } elseif (
641
642
                // All accounts can edit their own
643
                $isOwner
644
                    // Managers can edit all Authors, and their own.
645
                || (Symphony::Author()->isManager() && $this->_Author->isAuthor())
646
                    // Primary account can edit all accounts.
647
                || Symphony::Author()->isPrimaryAccount()
648
                    // Developers can edit all developers, managers and authors, and their own.
649
                || Symphony::Author()->isDeveloper() && $this->_Author->isPrimaryAccount() === false
650
            ) {
651
                $authenticated = true;
652
            }
653
654
            $this->_Author->set('id', $author_id);
655
656
            if ($this->_Author->isPrimaryAccount() || ($isOwner && Symphony::Author()->isDeveloper())) {
657
                $this->_Author->set('user_type', 'developer'); // Primary accounts are always developer, Developers can't lower their level
658
            } elseif ((Symphony::Author()->isDeveloper() || Symphony::Author()->isManager()) && isset($fields['user_type'])) {
659
                $this->_Author->set('user_type', $fields['user_type']); // Only developer can change user type
660
            }
661
662
            $this->_Author->set('email', $fields['email']);
663
            $this->_Author->set('username', General::sanitize($fields['username']));
664
            $this->_Author->set('first_name', General::sanitize($fields['first_name']));
665
            $this->_Author->set('last_name', General::sanitize($fields['last_name']));
666
            $this->_Author->set('language', isset($fields['language']) ? $fields['language'] : null);
667
668
            if (trim($fields['password']) !== '') {
669
                $this->_Author->set('password', Cryptography::hash(Symphony::Database()->cleanValue($fields['password'])));
670
                $changing_password = true;
671
            }
672
673
            // Don't allow authors to set the Section Index as a default area
674
            // If they had it previously set, just save `null` which will redirect
675
            // the Author (when logging in) to their own Author record
676
            if (
677
                $this->_Author->get('user_type') === 'author'
678
                && $fields['default_area'] === '/blueprints/sections/'
679
            ) {
680
                $this->_Author->set('default_area', null);
681
            } else {
682
                $this->_Author->set('default_area', $fields['default_area']);
683
            }
684
685
            $this->_Author->set('auth_token_active', ($fields['auth_token_active'] ? $fields['auth_token_active'] : 'no'));
686
687
            if ($this->_Author->validate($this->_errors)) {
688
                // Admin changing another profile
689
                if (!$isOwner) {
690
                    $entered_password = Symphony::Database()->cleanValue($fields['confirm-change-password']);
691
692
                    if (!isset($fields['confirm-change-password']) || empty($fields['confirm-change-password'])) {
693
                        $this->_errors['confirm-change-password'] = __('Please provide your own password to make changes to this author.');
694
                    } elseif (Cryptography::compare($entered_password, Symphony::Author()->get('password')) !== true) {
695
                        $this->_errors['confirm-change-password'] = __('Wrong password, please enter your own password to make changes to this author.');
696
                    }
697
                }
698
699
                // Author is changing their password
700
                if (!$authenticated && ($changing_password || $changing_email)) {
701
                    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...
702
                        $this->_errors['old-password'] = __('Wrong password. Enter old password to change it.');
703
                    } 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...
704
                        $this->_errors['old-password'] = __('Wrong password. Enter old one to change email address.');
705
                    }
706
707
                    // Passwords provided, but doesn't match.
708
                } elseif (($fields['password'] !== '' || $fields['password-confirmation'] !== '') && $fields['password'] !== $fields['password-confirmation']) {
709
                    $this->_errors['password'] = $this->_errors['password-confirmation'] = __('Passwords did not match');
710
                }
711
712
                // All good, let's save the Author
713
                if (is_array($this->_errors) && empty($this->_errors) && $this->_Author->commit()) {
714
                    Symphony::Database()->delete('tbl_forgotpass', "`expiry` < ? OR `author_id` = ?", array(
715
                        DateTimeObj::getGMT('c'),
716
                        $author_id
717
                    ));
718
719
                    if ($isOwner) {
720
                        Administration::instance()->login($this->_Author->get('username'), $this->_Author->get('password'), true);
721
                    }
722
723
                    /**
724
                     * After editing an author, provided with the Author object
725
                     *
726
                     * @delegate AuthorPostEdit
727
                     * @since Symphony 2.2
728
                     * @param string $context
729
                     * '/system/authors/'
730
                     * @param Author $author
731
                     * An Author object
732
                     */
733
                    Symphony::ExtensionManager()->notifyMembers('AuthorPostEdit', '/system/authors/', array('author' => $this->_Author));
734
735
                    redirect(SYMPHONY_URL . '/system/authors/edit/' . $author_id . '/saved/');
736
737
                    // Problems.
738 View Code Duplication
                } else {
739
                    $this->pageAlert(
740
                        __('Unknown errors occurred while attempting to save.')
741
                        . '<a href="' . SYMPHONY_URL . '/system/log/">'
742
                        . __('Check your activity log')
743
                        . '</a>.',
744
                        Alert::ERROR
745
                    );
746
                }
747
            }
748
749
            // Author doesn't have valid data, throw back.
750 View Code Duplication
            if (is_array($this->_errors) && !empty($this->_errors)) {
751
                $this->pageAlert(__('There were some problems while attempting to save. Please check below for problem fields.'), Alert::ERROR);
752
            }
753
        } elseif (@array_key_exists('delete', $_POST['action'])) {
754
            /**
755
             * Prior to deleting an author, provided with the Author ID.
756
             *
757
             * @delegate AuthorPreDelete
758
             * @since Symphony 2.2
759
             * @param string $context
760
             * '/system/authors/'
761
             * @param integer $author_id
762
             *  The ID of Author ID that is about to be deleted
763
             */
764
            Symphony::ExtensionManager()->notifyMembers('AuthorPreDelete', '/system/authors/', array('author_id' => $author_id));
765
766
            if (!$isOwner) {
767
                AuthorManager::delete($author_id);
768
                redirect(SYMPHONY_URL . '/system/authors/');
769
            } else {
770
                $this->pageAlert(__('You cannot remove yourself as you are the active Author.'), Alert::ERROR);
771
            }
772
        }
773
    }
774
}
775