Issues (81)

models/Route.php (4 issues)

1
<?php
2
3
namespace toir427\admin\models;
4
5
use Exception;
6
use toir427\admin\components\Configs;
7
use toir427\admin\components\Helper;
8
use toir427\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 \toir427\admin\BaseObject
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
                    foreach ($r as $part) {
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 Best Practice introduced by
The property routePrefix does not exist on toir427\admin\models\Route. Since you implemented __get, consider adding a @property annotation.
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like toir427\admin\components...s::instance()->advanced can also be of type boolean. However, the property $advanced is declared as type array|false. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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 Best Practice introduced by
The property routePrefix does not exist on toir427\admin\models\Route. Since you implemented __get, consider adding a @property annotation.
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
                unset($config['bootstrap']);
140
                $app = new yii\web\Application($config);
141
                // Get all the routes of the newly created app.
142
                $r = $this->getAppRoutes($app);
143
                // Dump new app
144
                unset($app);
145
                // Prepend the app id to all routes.
146
                foreach ($r as $route) {
147
                    $routes[$id . $route] = $id . $route;
148
                }
149
            }
150
            // Switch back to original app.
151
            Yii::$app = $yiiApp;
152
            unset($yiiApp);
153
        } else {
154
            // Use basic route scheme.
155
            // Set basic route prefix
156
            $this->_routePrefix = self::PREFIX_BASIC;
157
            // Get basic app routes.
158
            $routes = $this->getAppRoutes();
159
        }
160
        $exists = [];
161
        foreach (array_keys($manager->getPermissions()) as $name) {
162
            if ($name[0] !== $this->routePrefix) {
163
                continue;
164
            }
165
            $exists[] = $name;
166
            unset($routes[$name]);
167
        }
168
        return [
169
            'available' => array_keys($routes),
170
            'assigned' => $exists,
171
        ];
172
    }
173
174
    /**
175
     * Get list of application routes
176
     * @return array
177
     */
178
    public function getAppRoutes($module = null)
179
    {
180
        if ($module === null) {
181
            $module = Yii::$app;
182
        } elseif (is_string($module)) {
183
            $module = Yii::$app->getModule($module);
184
        }
185
        $key = [__METHOD__, Yii::$app->id, $module->getUniqueId()];
186
        $cache = Configs::instance()->cache;
187
        if ($cache === null || ($result = $cache->get($key)) === false) {
188
            $result = [];
189
            $this->getRouteRecursive($module, $result);
190
            if ($cache !== null) {
191
                $cache->set($key, $result, Configs::instance()->cacheDuration, new TagDependency([
192
                    'tags' => self::CACHE_TAG,
193
                ]));
194
            }
195
        }
196
197
        return $result;
198
    }
199
200
    /**
201
     * Get route(s) recursive
202
     * @param \yii\base\Module $module
203
     * @param array $result
204
     */
205
    protected function getRouteRecursive($module, &$result)
206
    {
207
        $token = "Get Route of '" . get_class($module) . "' with id '" . $module->uniqueId . "'";
208
        Yii::beginProfile($token, __METHOD__);
209
        try {
210
            foreach ($module->getModules() as $id => $child) {
211
                if (($child = $module->getModule($id)) !== null) {
212
                    $this->getRouteRecursive($child, $result);
213
                }
214
            }
215
216
            foreach ($module->controllerMap as $id => $type) {
217
                $this->getControllerActions($type, $id, $module, $result);
218
            }
219
220
            $namespace = trim($module->controllerNamespace, '\\') . '\\';
221
            $this->getControllerFiles($module, $namespace, '', $result);
222
            $all = '/' . ltrim($module->uniqueId . '/*', '/');
223
            $result[$all] = $all;
224
        } catch (\Exception $exc) {
225
            Yii::error($exc->getMessage(), __METHOD__);
226
        }
227
        Yii::endProfile($token, __METHOD__);
228
    }
229
230
    /**
231
     * Get list controller under module
232
     * @param \yii\base\Module $module
233
     * @param string $namespace
234
     * @param string $prefix
235
     * @param mixed $result
236
     * @return mixed
237
     */
238
    protected function getControllerFiles($module, $namespace, $prefix, &$result)
239
    {
240
        $path = Yii::getAlias('@' . str_replace('\\', '/', $namespace), false);
241
        $token = "Get controllers from '$path'";
242
        Yii::beginProfile($token, __METHOD__);
243
        try {
244
            if (!is_dir($path)) {
245
                return;
246
            }
247
            foreach (scandir($path) as $file) {
248
                if ($file == '.' || $file == '..') {
249
                    continue;
250
                }
251
                if (is_dir($path . '/' . $file) && preg_match('%^[a-z0-9_/]+$%i', $file . '/')) {
252
                    $this->getControllerFiles($module, $namespace . $file . '\\', $prefix . $file . '/', $result);
253
                } elseif (strcmp(substr($file, -14), 'Controller.php') === 0) {
254
                    $baseName = substr(basename($file), 0, -14);
255
                    $name = strtolower(preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $baseName));
256
                    $id = ltrim(str_replace(' ', '-', $name), '-');
257
                    $className = $namespace . $baseName . 'Controller';
258
                    if (strpos($className, '-') === false && class_exists($className) && is_subclass_of($className, 'yii\base\Controller')) {
259
                        $this->getControllerActions($className, $prefix . $id, $module, $result);
260
                    }
261
                }
262
            }
263
        } catch (\Exception $exc) {
264
            Yii::error($exc->getMessage(), __METHOD__);
265
        }
266
        Yii::endProfile($token, __METHOD__);
267
    }
268
269
    /**
270
     * Get list action of controller
271
     * @param mixed $type
272
     * @param string $id
273
     * @param \yii\base\Module $module
274
     * @param string $result
275
     */
276
    protected function getControllerActions($type, $id, $module, &$result)
277
    {
278
        $token = "Create controller with cofig=" . VarDumper::dumpAsString($type) . " and id='$id'";
279
        Yii::beginProfile($token, __METHOD__);
280
        try {
281
            /* @var $controller \yii\base\Controller */
282
            $controller = Yii::createObject($type, [$id, $module]);
283
            $this->getActionRoutes($controller, $result);
0 ignored issues
show
$result of type string is incompatible with the type array expected by parameter $result of toir427\admin\models\Route::getActionRoutes(). ( Ignorable by Annotation )

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

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