Completed
Push — issue/v4/1096-manage-applicati... ( e9abb0...a72a4b )
by Stefano
02:42
created

ResourcesShell::edit()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 11
nc 2
nop 1
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2017 ChannelWeb Srl, Chialab Srl
5
 *
6
 * This file is part of BEdita: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published
8
 * by the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
12
 */
13
namespace BEdita\Core\Shell;
14
15
use BEdita\Core\Model\Action\DeleteEntityAction;
16
use BEdita\Core\Model\Action\ListEntitiesAction;
17
use Cake\Console\Shell;
18
use Cake\ORM\Entity;
19
use Cake\ORM\TableRegistry;
20
use Cake\Utility\Inflector;
21
22
/**
23
 * Resource shell commands: list, create, remove, enable and disable common entities
24
 *
25
 * @since 4.0.0
26
 */
27
class ResourcesShell extends Shell
28
{
29
30
    /**
31
     * Accepted resource types
32
     *
33
     * @var array
34
     */
35
    protected $acceptedTypes = ['applications', 'roles', 'endpoints', 'endpoint_permissions'];
36
37
    /**
38
     * Editable resource fields
39
     *
40
     * @var array
41
     */
42
    protected $editableFields = ['api_key', 'description', 'enabled', 'name', 'unchangeable'];
43
44
    /**
45
     * Resource model table
46
     *
47
     * @var \Cake\ORM\Table
48
     */
49
    protected $modelTable = null;
50
51
    /**
52
     * {@inheritDoc}
53
     *
54
     * @codeCoverageIgnore
55
     */
56
    public function getOptionParser()
57
    {
58
        $parser = parent::getOptionParser();
59
60
        $options = [
61
            'type' => [
62
                'help' => 'Entity type (applications, roles, endpoints)',
63
                'short' => 't',
64
                'required' => true,
65
                'default' => null,
66
                'choices' => $this->acceptedTypes,
67
            ],
68
        ];
69
70
        $arguments = [
71
            'name|id' => [
72
                'help' => 'Resource\'s name or id',
73
                'required' => true
74
            ],
75
        ];
76
77
        $parser->addSubcommand('add', [
78
            'help' => 'create a new entity',
79
            'parser' => [
80
                'description' => [
81
                    'Create a new resource.'
82
                ],
83
                'options' => $options,
84
            ]
85
        ]);
86
        $parser->addSubcommand('ls', [
87
            'help' => 'list entities',
88
            'parser' => [
89
                'description' => [
90
                    'List entities.',
91
                    'Option --filter (optional) provides listing filtered by comma separated key=value pairs.'
92
                ],
93
                'options' => $options + [
94
                    'filter' => [
95
                        'help' => 'List entities filtered by comma separated key=value pairs',
96
                        'required' => false
97
                    ],
98
                ],
99
            ]
100
        ]);
101
        $parser->addSubcommand('rm', [
102
            'help' => 'remove an entity',
103
            'parser' => [
104
                'description' => [
105
                    'Remove an entity.',
106
                    'First argument (required) indicates entity\'s id|name.'
107
                ],
108
                'arguments' => $arguments,
109
                'options' => $options,
110
            ]
111
        ]);
112
        $parser->addSubcommand('edit', [
113
            'help' => 'modify an entity field',
114
            'parser' => [
115
                'description' => [
116
                    'Modify a field on a single resource.',
117
                    'Required entity\'s id|name and field'
118
                ],
119
                'arguments' => $arguments,
120
                'options' => $options + [
121
                    'field' => [
122
                        'help' => 'Field name',
123
                        'short' => 'f',
124
                        'required' => true,
125
                        'choices' => $this->editableFields,
126
                    ],
127
                ],
128
            ]
129
        ]);
130
131
        return $parser;
132
    }
133
134
    /**
135
     * Init model table using --type|-t option
136
     *
137
     * @return void
138
     */
139
    protected function initModel()
140
    {
141
        $modelName = Inflector::camelize($this->param('type'));
0 ignored issues
show
Bug introduced by
It seems like $this->param('type') targeting Cake\Console\Shell::param() can also be of type boolean or null; however, Cake\Utility\Inflector::camelize() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
142
        $this->modelTable = TableRegistry::get($modelName);
0 ignored issues
show
Bug introduced by
It seems like $modelName defined by \Cake\Utility\Inflector:...e($this->param('type')) on line 141 can also be of type boolean; however, Cake\ORM\TableRegistry::get() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
143
    }
144
145
    /**
146
     * Create a new resource
147
     *
148
     * @return \Cake\ORM\Entity $entity Entity created
149
     */
150
    public function add()
151
    {
152
        $this->initModel();
153
        $entity = $this->modelTable->newEntity();
154
        if ($this->param('type') === 'endpoint_permissions') {
155
            $this->setupEndpointPermissionEntity($entity);
0 ignored issues
show
Compatibility introduced by
$entity of type object<Cake\Datasource\EntityInterface> is not a sub-type of object<Cake\ORM\Entity>. It seems like you assume a concrete implementation of the interface Cake\Datasource\EntityInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
156
        } else {
157
            $this->setupDefaultEntity($entity);
0 ignored issues
show
Compatibility introduced by
$entity of type object<Cake\Datasource\EntityInterface> is not a sub-type of object<Cake\ORM\Entity>. It seems like you assume a concrete implementation of the interface Cake\Datasource\EntityInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
158
        }
159
160
        $this->modelTable->save($entity);
161
        $this->out('Resource with id ' . $entity->id . ' created');
0 ignored issues
show
Bug introduced by
Accessing id on the interface Cake\Datasource\EntityInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
162
163
        return $entity;
164
    }
165
166
    /**
167
     * Setup entity for endpoint_permissions
168
     *
169
     * @param \Cake\ORM\Entity $entity Entity to add
170
     * @return void
171
     */
172
    protected function setupEndpointPermissionEntity($entity)
173
    {
174
        $fieldsTables = [
175
            'application_id' => 'Applications',
176
            'endpoint_id' => 'Endpoints',
177
            'role_id' => 'Roles',
178
        ];
179
        foreach ($fieldsTables as $field => $table) {
180
            $id = $this->in($table . ' id or name');
181
            if ($id && !is_numeric($id)) {
182
                $id = TableRegistry::get($table)->find()->where(['name' => $id])->firstOrFail()->id;
183
            }
184
            $entity->$field = $id;
185
        }
186
187
        $perms = ['true', 'false', 'block', 'mine'];
188
        foreach (['read', 'write'] as $field) {
189
            $perm = $this->in("'$field' permission", $perms);
190
            $entity->$field = $perm;
191
        }
192
    }
193
194
    /**
195
     * Setup default entity for applications, roles, endpoints
196
     *
197
     * @param \Cake\ORM\Entity $entity Entity to add
198
     * @return void
199
     */
200
    protected function setupDefaultEntity($entity)
201
    {
202
        $name = $this->in('Resource name');
203
        if (empty($name)) {
204
            $this->abort('Resource name cannot be empty');
205
        }
206
        $entity->name = $name;
207
        $description = $this->in('Resource description (optional)');
208
        $entity->description = $description;
209
    }
210
211
    /**
212
     * Modify a resource field
213
     *
214
     * @param mixed $id Resource sid or name
215
     * @return void
216
     */
217
    public function edit($id)
218
    {
219
        $this->initModel();
220
        $entity = $this->getEntity($id);
221
        $field = $this->param('field');
222
        if ($field === 'api_key') {
223
             $entity->api_key = $this->modelTable->generateApiKey();
224
        } else {
225
            $value = $this->in(sprintf('New value for "%s" [current is "%s"]', $field, $entity->get($field)));
0 ignored issues
show
Bug introduced by
It seems like $field defined by $this->param('field') on line 221 can also be of type boolean or null; however, Cake\Datasource\EntityTrait::get() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
226
            $entity->set($field, $value);
0 ignored issues
show
Bug introduced by
It seems like $field defined by $this->param('field') on line 221 can also be of type boolean or null; however, Cake\Datasource\EntityTrait::set() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
227
        }
228
        $this->modelTable->save($entity);
229
        $this->out('Resource with id ' . $entity->id . ' modified');
230
    }
231
232
    /**
233
     * List entities
234
     *
235
     * @return array applications list
236
     */
237
    public function ls()
238
    {
239
        $this->initModel();
240
        $action = new ListEntitiesAction(['table' => $this->modelTable]);
241
        $query = $action(['filter' => $this->param('filter')]);
242
        $results = $query->toArray();
243
        $this->out($results ?: 'empty set');
244
245
        return $results;
246
    }
247
248
    /**
249
     * Remove entity by name or id
250
     *
251
     * @param mixed $id Resource id or name
252
     * @return bool True on success, false on blocked execution
253
     */
254
    public function rm($id)
255
    {
256
        $this->initModel();
257
        $res = $this->in(sprintf('You are REMOVING "%s" with name or id "%s" - are you sure?', $this->param('type'), $id), ['y', 'n'], 'n');
258
        if ($res != 'y') {
259
            $this->info('Remove not executed');
260
261
            return false;
262
        }
263
        $entity = $this->getEntity($id);
264
        $action = new DeleteEntityAction(['table' => $this->modelTable]);
265
        $action(compact('entity'));
266
267
        $this->out('Record ' . $id . ' deleted');
268
269
        return true;
270
    }
271
272
    /**
273
     * Return entity by $id name|id
274
     *
275
     * @param mixed $id entity name|id
276
     * @return \Cake\ORM\Entity entity
277
     */
278
    protected function getEntity($id)
279
    {
280
        if (!is_numeric($id)) {
281
            return $this->modelTable
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->modelTable->find(...> $id))->firstOrFail(); of type Cake\Datasource\EntityInterface|array adds the type array to the return on line 281 which is incompatible with the return type documented by BEdita\Core\Shell\ResourcesShell::getEntity of type Cake\ORM\Entity.
Loading history...
282
                ->find()
283
                ->where(['name' => $id])
284
                ->firstOrFail();
285
        }
286
287
        return $this->modelTable->get($id);
288
    }
289
}
290