Completed
Pull Request — master (#309)
by
unknown
02:27
created

Route::getControllerActions()   A

Complexity

Conditions 2
Paths 4

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
c 0
b 0
f 0
rs 9.4285
cc 2
eloc 11
nc 4
nop 4
1
<?php
2
3
namespace mdm\admin\models;
4
5
use Exception;
6
use mdm\admin\components\Configs;
7
use mdm\admin\components\Helper;
8
use mdm\admin\components\RouteRule;
9
use Yii;
10
use yii\caching\TagDependency;
11
use yii\helpers\VarDumper;
12
13
/**
14
 * Description of Route
15
 *
16
 * @author Misbahul D Munir <[email protected]>
17
 * @since 1.0
18
 */
19
class Route extends \yii\base\Object
20
{
21
    const CACHE_TAG = 'mdm.admin.route';
22
23
    const PREFIX_ADVANCED = '@';
24
    const PREFIX_BASIC = '/';
25
26
    private $_routePrefix;
27
28
    /**
29
     * Assign or remove items
30
     * @param array $routes
31
     * @return array
32
     */
33
    public function addNew($routes)
34
    {
35
        $manager = Configs::authManager();
36
        foreach ($routes as $route) {
37
            try {
38
                $r = explode('&', $route);
39
                $item = $manager->createPermission($this->getPermissionName($route));
40
                if (count($r) > 1) {
41
                    $action = '/' . trim($r[0], '/');
42
                    if (($itemAction = $manager->getPermission($action)) === null) {
43
                        $itemAction = $manager->createPermission($action);
44
                        $manager->add($itemAction);
45
                    }
46
                    unset($r[0]);
47 View Code Duplication
                    foreach ($r as $part) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
48
                        $part = explode('=', $part);
49
                        $item->data['params'][$part[0]] = isset($part[1]) ? $part[1] : '';
50
                    }
51
                    $this->setDefaultRule();
52
                    $item->ruleName = RouteRule::RULE_NAME;
53
                    $manager->add($item);
54
                    $manager->addChild($item, $itemAction);
55
                } else {
56
                    $manager->add($item);
57
                }
58
            } catch (Exception $exc) {
59
                Yii::error($exc->getMessage(), __METHOD__);
60
            }
61
        }
62
        Helper::invalidate();
63
    }
64
65
    /**
66
     * Assign or remove items
67
     * @param array $routes
68
     * @return array
69
     */
70
    public function remove($routes)
71
    {
72
        $manager = Configs::authManager();
73
        foreach ($routes as $route) {
74
            try {
75
                $item = $manager->createPermission($this->getPermissionName($route));
76
                $manager->remove($item);
77
            } catch (Exception $exc) {
78
                Yii::error($exc->getMessage(), __METHOD__);
79
            }
80
        }
81
        Helper::invalidate();
82
    }
83
84
    /**
85
     * Returns route prefix depending on the configuration.
86
     * @return string Route prefix
87
     */
88
    public function getRoutePrefix()
89
    {
90
        if (!$this->_routePrefix) {
91
            $this->_routePrefix = Configs::instance()->advanced ? self::PREFIX_ADVANCED : self::PREFIX_BASIC;
92
        }
93
        return $this->_routePrefix;
94
    }
95
96
    /**
97
     * Returns the correct permission name depending on the configuration.
98
     * @param  string $route Route
99
     * @return string        Permission name
100
     */
101
    public function getPermissionName($route)
102
    {
103
        if (self::PREFIX_BASIC == $this->routePrefix) {
0 ignored issues
show
Bug introduced by
The property routePrefix does not seem to exist. Did you mean _routePrefix?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
104
            return self::PREFIX_BASIC . trim($route, self::PREFIX_BASIC);
105
        } else {
106
            return self::PREFIX_ADVANCED . ltrim(trim($route, self::PREFIX_BASIC), self::PREFIX_ADVANCED);
107
        }
108
    }
109
110
    /**
111
     * Get available and assigned routes
112
     * @return array
113
     */
114
    public function getRoutes()
115
    {
116
        $manager = Configs::authManager();
117
        // Get advanced configuration
118
        $advanced = Configs::instance()->advanced;
119
        if ($advanced) {
120
            // Use advanced route scheme.
121
            // Set advanced route prefix.
122
            $this->_routePrefix = self::PREFIX_ADVANCED;
123
            // Create empty routes array.
124
            $routes = [];
125
            // Save original app.
126
            $yiiApp = Yii::$app;
127
            // Step through each configured application
128
            foreach ($advanced as $id => $configPaths) {
129
                // Force correct id string.
130
                $id = $this->routePrefix . ltrim(trim($id), $this->routePrefix);
0 ignored issues
show
Bug introduced by
The property routePrefix does not seem to exist. Did you mean _routePrefix?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
131
                // Create empty config array.
132
                $config = [];
133
                // Assemble configuration for current app.
134
                foreach ($configPaths as $configPath) {
135
                    // Merge every new configuration with the old config array.
136
                    $config = yii\helpers\ArrayHelper::merge($config, require (Yii::getAlias($configPath)));
137
                }
138
                // Create new app using the config array.
139
                $app = new yii\web\Application($config);
140
                // Get all the routes of the newly created app.
141
                $r = $this->getAppRoutes($app);
142
                // Dump new app
143
                unset($app);
144
                // Prepend the app id to all routes.
145
                foreach ($r as $route) {
146
                    $routes[$id . $route] = $id . $route;
147
                }
148
            }
149
            // Switch back to original app.
150
            Yii::$app = $yiiApp;
151
            unset($yiiApp);
152
        } else {
153
            // Use basic route scheme.
154
            // Set basic route prefix
155
            $this->_routePrefix = self::PREFIX_BASIC;
156
            // Get basic app routes.
157
            $routes = $this->getAppRoutes();
158
        }
159
        $exists = [];
160
        foreach (array_keys($manager->getPermissions()) as $name) {
161
            if ($name[0] !== $this->routePrefix) {
0 ignored issues
show
Bug introduced by
The property routePrefix does not seem to exist. Did you mean _routePrefix?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
162
                continue;
163
            }
164
            $exists[] = $name;
165
            unset($routes[$name]);
166
        }
167
        return [
168
            'available' => array_keys($routes),
169
            'assigned' => $exists,
170
        ];
171
    }
172
173
    /**
174
     * Get list of application routes
175
     * @return array
176
     */
177
    public function getAppRoutes($module = null)
178
    {
179
        if ($module === null) {
180
            $module = Yii::$app;
181
        } elseif (is_string($module)) {
182
            $module = Yii::$app->getModule($module);
183
        }
184
        $key = [__METHOD__, $module->getUniqueId()];
185
        $cache = Configs::instance()->cache;
186
        if ($cache === null || ($result = $cache->get($key)) === false) {
187
            $result = [];
188
            $this->getRouteRecursive($module, $result);
189
            if ($cache !== null) {
190
                $cache->set($key, $result, Configs::instance()->cacheDuration, new TagDependency([
191
                    'tags' => self::CACHE_TAG,
192
                ]));
193
            }
194
        }
195
196
        return $result;
197
    }
198
199
    /**
200
     * Get route(s) recursive
201
     * @param \yii\base\Module $module
202
     * @param array $result
203
     */
204
    protected function getRouteRecursive($module, &$result)
205
    {
206
        $token = "Get Route of '" . get_class($module) . "' with id '" . $module->uniqueId . "'";
207
        Yii::beginProfile($token, __METHOD__);
208
        try {
209
            foreach ($module->getModules() as $id => $child) {
210
                if (($child = $module->getModule($id)) !== null) {
211
                    $this->getRouteRecursive($child, $result);
212
                }
213
            }
214
215
            foreach ($module->controllerMap as $id => $type) {
216
                $this->getControllerActions($type, $id, $module, $result);
0 ignored issues
show
Bug introduced by
It seems like $result defined by parameter $result on line 204 can also be of type array; however, mdm\admin\models\Route::getControllerActions() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and 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...
217
            }
218
219
            $namespace = trim($module->controllerNamespace, '\\') . '\\';
220
            $this->getControllerFiles($module, $namespace, '', $result);
221
            $all = '/' . ltrim($module->uniqueId . '/*', '/');
222
            $result[$all] = $all;
223
        } catch (\Exception $exc) {
224
            Yii::error($exc->getMessage(), __METHOD__);
225
        }
226
        Yii::endProfile($token, __METHOD__);
227
    }
228
229
    /**
230
     * Get list controller under module
231
     * @param \yii\base\Module $module
232
     * @param string $namespace
233
     * @param string $prefix
234
     * @param mixed $result
235
     * @return mixed
236
     */
237
    protected function getControllerFiles($module, $namespace, $prefix, &$result)
238
    {
239
        $path = Yii::getAlias('@' . str_replace('\\', '/', $namespace), false);
240
        $token = "Get controllers from '$path'";
241
        Yii::beginProfile($token, __METHOD__);
242
        try {
243
            if (!is_dir($path)) {
244
                return;
245
            }
246
            foreach (scandir($path) as $file) {
247
                if ($file == '.' || $file == '..') {
248
                    continue;
249
                }
250
                if (is_dir($path . '/' . $file) && preg_match('%^[a-z0-9_/]+$%i', $file . '/')) {
251
                    $this->getControllerFiles($module, $namespace . $file . '\\', $prefix . $file . '/', $result);
252
                } elseif (strcmp(substr($file, -14), 'Controller.php') === 0) {
253
                    $baseName = substr(basename($file), 0, -14);
254
                    $name = strtolower(preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $baseName));
255
                    $id = ltrim(str_replace(' ', '-', $name), '-');
256
                    $className = $namespace . $baseName . 'Controller';
257
                    if (strpos($className, '-') === false && class_exists($className) && is_subclass_of($className, 'yii\base\Controller')) {
258
                        $this->getControllerActions($className, $prefix . $id, $module, $result);
259
                    }
260
                }
261
            }
262
        } catch (\Exception $exc) {
263
            Yii::error($exc->getMessage(), __METHOD__);
264
        }
265
        Yii::endProfile($token, __METHOD__);
266
    }
267
268
    /**
269
     * Get list action of controller
270
     * @param mixed $type
271
     * @param string $id
272
     * @param \yii\base\Module $module
273
     * @param string $result
274
     */
275
    protected function getControllerActions($type, $id, $module, &$result)
276
    {
277
        $token = "Create controller with cofig=" . VarDumper::dumpAsString($type) . " and id='$id'";
278
        Yii::beginProfile($token, __METHOD__);
279
        try {
280
            /* @var $controller \yii\base\Controller */
281
            $controller = Yii::createObject($type, [$id, $module]);
0 ignored issues
show
Documentation introduced by
$type is of type *, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
282
            $this->getActionRoutes($controller, $result);
0 ignored issues
show
Documentation introduced by
$result is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
283
            $all = "/{$controller->uniqueId}/*";
284
            $result[$all] = $all;
285
        } catch (\Exception $exc) {
286
            Yii::error($exc->getMessage(), __METHOD__);
287
        }
288
        Yii::endProfile($token, __METHOD__);
289
    }
290
291
    /**
292
     * Get route of action
293
     * @param \yii\base\Controller $controller
294
     * @param array $result all controller action.
295
     */
296
    protected function getActionRoutes($controller, &$result)
297
    {
298
        $token = "Get actions of controller '" . $controller->uniqueId . "'";
299
        Yii::beginProfile($token, __METHOD__);
300
        try {
301
            $prefix = '/' . $controller->uniqueId . '/';
302
            foreach ($controller->actions() as $id => $value) {
303
                $result[$prefix . $id] = $prefix . $id;
304
            }
305
            $class = new \ReflectionClass($controller);
306
            foreach ($class->getMethods() as $method) {
307
                $name = $method->getName();
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
308
                if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
309
                    $name = strtolower(preg_replace('/(?<![A-Z])[A-Z]/', ' \0', substr($name, 6)));
310
                    $id = $prefix . ltrim(str_replace(' ', '-', $name), '-');
311
                    $result[$id] = $id;
312
                }
313
            }
314
        } catch (\Exception $exc) {
315
            Yii::error($exc->getMessage(), __METHOD__);
316
        }
317
        Yii::endProfile($token, __METHOD__);
318
    }
319
320
    /**
321
     * Ivalidate cache
322
     */
323
    public static function invalidate()
324
    {
325
        if (Configs::cache() !== null) {
326
            TagDependency::invalidate(Configs::cache(), self::CACHE_TAG);
327
        }
328
    }
329
330
    /**
331
     * Set default rule of parameterize route.
332
     */
333
    protected function setDefaultRule()
334
    {
335
        if (Configs::authManager()->getRule(RouteRule::RULE_NAME) === null) {
336
            Configs::authManager()->add(new RouteRule());
337
        }
338
    }
339
}
340