injitools /
cms-Inji
| 1 | <?php |
||||||||||
| 2 | |||||||||||
| 3 | namespace Inji; |
||||||||||
| 4 | /** |
||||||||||
| 5 | * Module |
||||||||||
| 6 | * |
||||||||||
| 7 | * @author Alexey Krupskiy <[email protected]> |
||||||||||
| 8 | * @link http://inji.ru/ |
||||||||||
| 9 | * @copyright 2015 Alexey Krupskiy |
||||||||||
| 10 | * @license https://github.com/injitools/cms-Inji/blob/master/LICENSE |
||||||||||
| 11 | */ |
||||||||||
| 12 | class Module { |
||||||||||
| 13 | |||||||||||
| 14 | /** |
||||||||||
| 15 | * Storage of cur requested module |
||||||||||
| 16 | * |
||||||||||
| 17 | * @var Module |
||||||||||
| 18 | */ |
||||||||||
| 19 | public static $cur = null; |
||||||||||
| 20 | |||||||||||
| 21 | /** |
||||||||||
| 22 | * Module name |
||||||||||
| 23 | * |
||||||||||
| 24 | * @var string |
||||||||||
| 25 | */ |
||||||||||
| 26 | public $name = ''; |
||||||||||
| 27 | |||||||||||
| 28 | /** |
||||||||||
| 29 | * Module config |
||||||||||
| 30 | * |
||||||||||
| 31 | * @var array |
||||||||||
| 32 | */ |
||||||||||
| 33 | public $config = []; |
||||||||||
| 34 | |||||||||||
| 35 | /** |
||||||||||
| 36 | * Module info |
||||||||||
| 37 | * |
||||||||||
| 38 | * @var array |
||||||||||
| 39 | */ |
||||||||||
| 40 | public $info = []; |
||||||||||
| 41 | |||||||||||
| 42 | /** |
||||||||||
| 43 | * Requested module params |
||||||||||
| 44 | * |
||||||||||
| 45 | * @var array |
||||||||||
| 46 | */ |
||||||||||
| 47 | public $params = []; |
||||||||||
| 48 | |||||||||||
| 49 | /** |
||||||||||
| 50 | * Module directory path |
||||||||||
| 51 | * |
||||||||||
| 52 | * @var string |
||||||||||
| 53 | */ |
||||||||||
| 54 | public $path = ''; |
||||||||||
| 55 | |||||||||||
| 56 | /** |
||||||||||
| 57 | * Module app |
||||||||||
| 58 | * |
||||||||||
| 59 | * @var App |
||||||||||
| 60 | */ |
||||||||||
| 61 | public $app = null; |
||||||||||
| 62 | |||||||||||
| 63 | /** |
||||||||||
| 64 | * Parse cur module |
||||||||||
| 65 | * |
||||||||||
| 66 | * @param App $app |
||||||||||
| 67 | */ |
||||||||||
| 68 | 1 | public function __construct($app) { |
|||||||||
| 69 | 1 | $this->app = $app; |
|||||||||
| 70 | 1 | if (!$this->name) { |
|||||||||
| 71 | $this->name = get_class($this); |
||||||||||
| 72 | } |
||||||||||
| 73 | 1 | $this->path = Router::getLoadedClassPath(get_class($this)); |
|||||||||
| 74 | 1 | $this->info = $this->getInfo(); |
|||||||||
| 75 | 1 | $this->config = Config::module($this->name, !empty($this->info['systemConfig'])); |
|||||||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||||||||
| 76 | 1 | $that = $this; |
|||||||||
| 77 | 1 | \Inji::$inst->listen('Config-change-module-' . $this->app->name . '-' . $this->name, $this->app->name . '-' . $this->name . 'config', function ($event) use ($that) { |
|||||||||
|
0 ignored issues
–
show
function(...) { /* ... */ } of type callable is incompatible with the type string|array|closure expected by parameter $callback of Inji::listen().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||||||
| 78 | $that->config = $event['eventObject']; |
||||||||||
| 79 | return $event['eventObject']; |
||||||||||
| 80 | 1 | }); |
|||||||||
| 81 | 1 | } |
|||||||||
| 82 | |||||||||||
| 83 | /** |
||||||||||
| 84 | * Get all posible directorys for module files |
||||||||||
| 85 | * |
||||||||||
| 86 | * @param string $moduleName |
||||||||||
| 87 | * @return array |
||||||||||
| 88 | */ |
||||||||||
| 89 | 1 | public static function getModulePaths($moduleName) { |
|||||||||
| 90 | 1 | $moduleName = ucfirst($moduleName); |
|||||||||
| 91 | 1 | $paths = []; |
|||||||||
| 92 | 1 | if (App::$cur !== App::$primary) { |
|||||||||
| 93 | $paths['primaryAppPath'] = App::$primary->path . '/modules/' . $moduleName; |
||||||||||
| 94 | } |
||||||||||
| 95 | 1 | $paths['curAppPath'] = App::$cur->path . '/modules/' . $moduleName; |
|||||||||
| 96 | 1 | $paths['systemPath'] = INJI_SYSTEM_DIR . '/modules/' . $moduleName; |
|||||||||
| 97 | 1 | return $paths; |
|||||||||
| 98 | } |
||||||||||
| 99 | |||||||||||
| 100 | /** |
||||||||||
| 101 | * Return directory where places module file |
||||||||||
| 102 | * |
||||||||||
| 103 | * @param string $moduleName |
||||||||||
| 104 | * @return string |
||||||||||
| 105 | */ |
||||||||||
| 106 | public static function getModulePath($moduleName) { |
||||||||||
| 107 | $moduleName = ucfirst($moduleName); |
||||||||||
| 108 | $paths = Module::getModulePaths($moduleName); |
||||||||||
| 109 | foreach ($paths as $path) { |
||||||||||
| 110 | if (file_exists($path . '/' . $moduleName . '.php')) { |
||||||||||
| 111 | return $path; |
||||||||||
| 112 | } |
||||||||||
| 113 | } |
||||||||||
| 114 | } |
||||||||||
| 115 | |||||||||||
| 116 | /** |
||||||||||
| 117 | * Check module for installed |
||||||||||
| 118 | * |
||||||||||
| 119 | * @param string $moduleName |
||||||||||
| 120 | * @param \Inji\App $app |
||||||||||
| 121 | * @return boolean |
||||||||||
| 122 | */ |
||||||||||
| 123 | public static function installed($moduleName, $app) { |
||||||||||
| 124 | if (in_array($moduleName, self::getInstalled($app))) { |
||||||||||
| 125 | return true; |
||||||||||
| 126 | } |
||||||||||
| 127 | return false; |
||||||||||
| 128 | } |
||||||||||
| 129 | |||||||||||
| 130 | /** |
||||||||||
| 131 | * Get installed modules for app |
||||||||||
| 132 | * |
||||||||||
| 133 | * @param \Inji\App $app |
||||||||||
| 134 | * @param App $primary |
||||||||||
| 135 | * @return array |
||||||||||
| 136 | */ |
||||||||||
| 137 | 1 | public static function getInstalled($app, $primary = false) { |
|||||||||
| 138 | 1 | if (!$primary) { |
|||||||||
| 139 | 1 | $primary = \Inji\App::$primary; |
|||||||||
| 140 | } |
||||||||||
| 141 | 1 | $system = !empty(\Inji::$config['modules']) ? \Inji::$config['modules'] : []; |
|||||||||
| 142 | 1 | $primary = !empty($primary->config['modules']) ? $primary->config['modules'] : []; |
|||||||||
| 143 | 1 | $actual = $app !== $primary && !empty($app->config['modules']) ? $app->config['modules'] : []; |
|||||||||
| 144 | 1 | $modules = array_unique(array_merge($system, $primary, $actual)); |
|||||||||
| 145 | 1 | return $modules; |
|||||||||
| 146 | } |
||||||||||
| 147 | |||||||||||
| 148 | /** |
||||||||||
| 149 | * Find module controllers |
||||||||||
| 150 | * |
||||||||||
| 151 | * @param string $moduleName |
||||||||||
| 152 | * @return array |
||||||||||
| 153 | */ |
||||||||||
| 154 | public static function getModuleControllers($moduleName) { |
||||||||||
| 155 | $controllers = []; |
||||||||||
| 156 | $moduleDirs = static::getModulePaths($moduleName); |
||||||||||
| 157 | foreach ($moduleDirs as $moduleDir) { |
||||||||||
| 158 | if (is_dir($moduleDir)) { |
||||||||||
| 159 | foreach (scandir($moduleDir) as $dir) { |
||||||||||
| 160 | if (preg_match('!Controllers$!', $dir) && is_dir($moduleDir . '/' . $dir)) { |
||||||||||
| 161 | $path = $moduleDir . '/' . $dir; |
||||||||||
| 162 | foreach (scandir($path) as $file) { |
||||||||||
| 163 | if (preg_match('!Controller\.php$!', $file) && is_file($path . '/' . $file)) { |
||||||||||
| 164 | $controllerName = preg_replace('!Controller\.php$!', '', $file); |
||||||||||
| 165 | $controllers[preg_replace('!Controllers$!', '', $dir)][$controllerName] = $path . '/' . $file; |
||||||||||
| 166 | } |
||||||||||
| 167 | } |
||||||||||
| 168 | } |
||||||||||
| 169 | } |
||||||||||
| 170 | } |
||||||||||
| 171 | } |
||||||||||
| 172 | return $controllers; |
||||||||||
| 173 | } |
||||||||||
| 174 | |||||||||||
| 175 | /** |
||||||||||
| 176 | * Find module by request |
||||||||||
| 177 | * |
||||||||||
| 178 | * @param \Inji\App $app |
||||||||||
| 179 | * @param array|null $params |
||||||||||
| 180 | * @return \Inji\Module |
||||||||||
| 181 | */ |
||||||||||
| 182 | public static function resolveModule($app, $params = null) { |
||||||||||
| 183 | $search = is_array($params) ? $params : $app->params; |
||||||||||
| 184 | if (!empty($search[0]) && $app->{$search[0]}) { |
||||||||||
| 185 | $module = $app->{$search[0]}; |
||||||||||
| 186 | $module->params = array_slice($search, 1); |
||||||||||
| 187 | return $module; |
||||||||||
| 188 | } |
||||||||||
| 189 | if (!empty($app->config['defaultModule']) && $app->{$app->config['defaultModule']}) { |
||||||||||
| 190 | $module = $app->{$app->config['defaultModule']}; |
||||||||||
| 191 | $module->params = $app->params; |
||||||||||
| 192 | return $module; |
||||||||||
| 193 | } |
||||||||||
| 194 | |||||||||||
| 195 | if ($app->Main) { |
||||||||||
|
0 ignored issues
–
show
The property
Main does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
|
|||||||||||
| 196 | $module = $app->Main; |
||||||||||
| 197 | $module->params = $app->params; |
||||||||||
| 198 | return $module; |
||||||||||
| 199 | } |
||||||||||
| 200 | return null; |
||||||||||
| 201 | } |
||||||||||
| 202 | |||||||||||
| 203 | /** |
||||||||||
| 204 | * Get posible path for controller |
||||||||||
| 205 | * |
||||||||||
| 206 | * @return array |
||||||||||
| 207 | */ |
||||||||||
| 208 | public function getPossibleControllers() { |
||||||||||
| 209 | $possibleClasses = []; |
||||||||||
| 210 | if (!empty($this->params[0]) && ucfirst($this->params[0]) != $this->name) { |
||||||||||
| 211 | $possibleClasses['curApp_slice'] = $this->app->namespace . '\\' . $this->name . '\\' . ucfirst($this->params[0]) . ucfirst($this->app->type) . 'Controller'; |
||||||||||
| 212 | $possibleClasses['system_slice'] = 'Inji\\' . $this->name . '\\' . ucfirst($this->params[0]) . ucfirst($this->app->type) . 'Controller'; |
||||||||||
| 213 | $possibleClasses['universal_curApp_slice'] = $this->app->namespace . '\\' . $this->name . '\\' . ucfirst($this->params[0]) . 'Controller'; |
||||||||||
| 214 | $possibleClasses['universal_system_slice'] = 'Inji\\' . $this->name . '\\' . ucfirst($this->params[0]) . 'Controller'; |
||||||||||
| 215 | } |
||||||||||
| 216 | $possibleClasses['curApp'] = $this->app->namespace . '\\' . $this->name . '\\' . $this->name . ucfirst($this->app->type) . 'Controller'; |
||||||||||
| 217 | $possibleClasses['system'] = 'Inji\\' . $this->name . '\\' . $this->name . ucfirst($this->app->type) . 'Controller'; |
||||||||||
| 218 | |||||||||||
| 219 | $possibleClasses['universal_curApp'] = $this->app->namespace . '\\' . $this->name . '\\' . $this->name . 'Controller'; |
||||||||||
| 220 | $possibleClasses['universal_system'] = 'Inji\\' . $this->name . '\\' . $this->name . 'Controller'; |
||||||||||
| 221 | return $possibleClasses; |
||||||||||
| 222 | } |
||||||||||
| 223 | |||||||||||
| 224 | /** |
||||||||||
| 225 | * Find controller by request |
||||||||||
| 226 | * |
||||||||||
| 227 | * @return \Inji\Controller |
||||||||||
| 228 | */ |
||||||||||
| 229 | public function findController() { |
||||||||||
| 230 | $possibleClasses = $this->getPossibleControllers(); |
||||||||||
| 231 | foreach ($possibleClasses as $possibleClassType => $possibleClass) { |
||||||||||
| 232 | if (class_exists($possibleClass)) { |
||||||||||
| 233 | if (strpos($possibleClassType, 'slice')) { |
||||||||||
| 234 | $controllerName = ucfirst($this->params[0]); |
||||||||||
| 235 | $params = array_slice($this->params, 1); |
||||||||||
| 236 | } else { |
||||||||||
| 237 | $controllerName = $this->name; |
||||||||||
| 238 | $params = $this->params; |
||||||||||
| 239 | } |
||||||||||
| 240 | $controller = new $possibleClass(); |
||||||||||
| 241 | $controller->params = $params; |
||||||||||
| 242 | $controller->module = $this; |
||||||||||
| 243 | $controller->path = Router::getLoadedClassPath($possibleClass); |
||||||||||
| 244 | $controller->name = $controllerName; |
||||||||||
| 245 | return $controller; |
||||||||||
| 246 | } |
||||||||||
| 247 | } |
||||||||||
| 248 | } |
||||||||||
| 249 | |||||||||||
| 250 | /** |
||||||||||
| 251 | * Return module info |
||||||||||
| 252 | * |
||||||||||
| 253 | * @param string $moduleName |
||||||||||
| 254 | * @return array |
||||||||||
| 255 | */ |
||||||||||
| 256 | 1 | public static function getInfo($moduleName = '') { |
|||||||||
| 257 | 1 | if (!$moduleName && get_called_class()) { |
|||||||||
| 258 | 1 | $moduleName = get_called_class(); |
|||||||||
| 259 | } elseif (!$moduleName) { |
||||||||||
| 260 | return []; |
||||||||||
| 261 | } |
||||||||||
| 262 | 1 | $paths = Module::getModulePaths($moduleName); |
|||||||||
| 263 | 1 | foreach ($paths as $path) { |
|||||||||
| 264 | 1 | if (file_exists($path . '/info.php')) { |
|||||||||
| 265 | 1 | return include $path . '/info.php'; |
|||||||||
| 266 | } |
||||||||||
| 267 | } |
||||||||||
| 268 | 1 | return []; |
|||||||||
| 269 | } |
||||||||||
| 270 | |||||||||||
| 271 | /** |
||||||||||
| 272 | * Return snippets by name |
||||||||||
| 273 | * |
||||||||||
| 274 | * @param string $snippetsPath |
||||||||||
| 275 | * @param boolean $extensions |
||||||||||
| 276 | * @param string $dir |
||||||||||
| 277 | * @param string $moduleName |
||||||||||
| 278 | * @return array |
||||||||||
| 279 | */ |
||||||||||
| 280 | public function getSnippets($snippetsPath, $extensions = true, $dir = '/snippets', $moduleName = '') { |
||||||||||
| 281 | $moduleName = $moduleName ? $moduleName : $this->name; |
||||||||||
| 282 | $modulePaths = Module::getModulePaths($moduleName); |
||||||||||
| 283 | $modulePaths = array_reverse($modulePaths); |
||||||||||
| 284 | $modulePaths['templatePath'] = App::$cur->view->template->path . '/modules/' . ucfirst($moduleName); |
||||||||||
|
0 ignored issues
–
show
The property
view does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
|
|||||||||||
| 285 | $snippets = []; |
||||||||||
| 286 | foreach ($modulePaths as $path) { |
||||||||||
| 287 | if (file_exists($path . $dir . '/' . $snippetsPath)) { |
||||||||||
| 288 | $snippetsPaths = array_slice(scandir($path . $dir . '/' . $snippetsPath), 2); |
||||||||||
| 289 | foreach ($snippetsPaths as $snippetPath) { |
||||||||||
| 290 | if (is_dir($path . $dir . '/' . $snippetsPath . '/' . $snippetPath)) { |
||||||||||
| 291 | $snippets[$snippetPath] = include $path . $dir . '/' . $snippetsPath . '/' . $snippetPath . '/info.php'; |
||||||||||
| 292 | } else { |
||||||||||
| 293 | $snippets[pathinfo($snippetPath, PATHINFO_FILENAME)] = include $path . $dir . '/' . $snippetsPath . '/' . $snippetPath; |
||||||||||
| 294 | } |
||||||||||
| 295 | } |
||||||||||
| 296 | } |
||||||||||
| 297 | } |
||||||||||
| 298 | if ($extensions) { |
||||||||||
| 299 | $snippets = array_merge($snippets, $this->getExtensions('snippets', $snippetsPath)); |
||||||||||
| 300 | } |
||||||||||
| 301 | return $snippets; |
||||||||||
| 302 | } |
||||||||||
| 303 | |||||||||||
| 304 | /** |
||||||||||
| 305 | * Return module objects |
||||||||||
| 306 | * |
||||||||||
| 307 | * @return array |
||||||||||
| 308 | */ |
||||||||||
| 309 | public function getObjects($filterNamespace = '') { |
||||||||||
| 310 | $moduleName = $this->name; |
||||||||||
| 311 | $modulePaths = Module::getModulePaths($moduleName); |
||||||||||
| 312 | $modulePaths = array_reverse($modulePaths); |
||||||||||
| 313 | $scanFn = function ($path, $namespace, &$files = []) use (&$scanFn, $filterNamespace) { |
||||||||||
| 314 | if (file_exists($path)) { |
||||||||||
| 315 | foreach (scandir($path) as $item) { |
||||||||||
| 316 | if (in_array($item, ['..', '.'])) { |
||||||||||
| 317 | continue; |
||||||||||
| 318 | } |
||||||||||
| 319 | $filename = pathinfo($item)['filename']; |
||||||||||
| 320 | if (is_dir($path . '/' . $item)) { |
||||||||||
| 321 | $scanFn($path . '/' . $item, $namespace . '\\' . $filename, $files); |
||||||||||
| 322 | } else { |
||||||||||
| 323 | if (!$filterNamespace || strpos($namespace, $filterNamespace) === 0) { |
||||||||||
| 324 | $files[$path . '/' . $item] = $namespace . '\\' . $filename; |
||||||||||
| 325 | } |
||||||||||
| 326 | } |
||||||||||
| 327 | } |
||||||||||
| 328 | } |
||||||||||
| 329 | return $files; |
||||||||||
| 330 | }; |
||||||||||
| 331 | $files = []; |
||||||||||
| 332 | foreach ($modulePaths as $path) { |
||||||||||
| 333 | $scanFn($path . '/objects', $moduleName, $files); |
||||||||||
| 334 | } |
||||||||||
| 335 | return $files; |
||||||||||
| 336 | } |
||||||||||
| 337 | |||||||||||
| 338 | /** |
||||||||||
| 339 | * Return module models |
||||||||||
| 340 | * |
||||||||||
| 341 | * @return array |
||||||||||
| 342 | */ |
||||||||||
| 343 | public static function getModels($moduleName, $filterNamespace = '') { |
||||||||||
| 344 | $modulePaths = Module::getModulePaths($moduleName); |
||||||||||
| 345 | $modulePaths = array_reverse($modulePaths); |
||||||||||
| 346 | $scanFn = function ($path, $namespace, &$files = []) use (&$scanFn, $filterNamespace) { |
||||||||||
| 347 | if (file_exists($path)) { |
||||||||||
| 348 | foreach (scandir($path) as $item) { |
||||||||||
| 349 | if (in_array($item, ['..', '.'])) { |
||||||||||
| 350 | continue; |
||||||||||
| 351 | } |
||||||||||
| 352 | $filename = pathinfo($item)['filename']; |
||||||||||
| 353 | if (is_dir($path . '/' . $item)) { |
||||||||||
| 354 | $scanFn($path . '/' . $item, $namespace . '\\' . $filename, $files); |
||||||||||
| 355 | } else { |
||||||||||
| 356 | if (!$filterNamespace || strpos($namespace, $filterNamespace) === 0) { |
||||||||||
| 357 | $files[$path . '/' . $item] = $namespace . '\\' . $filename; |
||||||||||
| 358 | } |
||||||||||
| 359 | } |
||||||||||
| 360 | } |
||||||||||
| 361 | } |
||||||||||
| 362 | return $files; |
||||||||||
| 363 | }; |
||||||||||
| 364 | $files = []; |
||||||||||
| 365 | foreach ($modulePaths as $path) { |
||||||||||
| 366 | $scanFn($path . '/models', $moduleName, $files); |
||||||||||
| 367 | } |
||||||||||
| 368 | return $files; |
||||||||||
| 369 | } |
||||||||||
| 370 | |||||||||||
| 371 | /** |
||||||||||
| 372 | * Return extensions for type |
||||||||||
| 373 | * |
||||||||||
| 374 | * @param string $extensionType |
||||||||||
| 375 | * @param string $request |
||||||||||
| 376 | * @return array |
||||||||||
| 377 | */ |
||||||||||
| 378 | public function getExtensions($extensionType, $request) { |
||||||||||
| 379 | $extensions = []; |
||||||||||
| 380 | $modules = Module::getInstalled(App::$cur); |
||||||||||
| 381 | $method = 'get' . ucfirst($extensionType); |
||||||||||
| 382 | foreach ($modules as $module) { |
||||||||||
| 383 | $extensions = array_merge($extensions, $this->{$method}($request, false, "/extensions/{$this->name}/" . $extensionType, $module)); |
||||||||||
| 384 | } |
||||||||||
| 385 | return $extensions; |
||||||||||
| 386 | } |
||||||||||
| 387 | |||||||||||
| 388 | 1 | public function checkDbMigration() { |
|||||||||
| 389 | 1 | if (empty($this->info['migrations'])) { |
|||||||||
| 390 | 1 | return true; |
|||||||||
| 391 | } |
||||||||||
| 392 | $code = 'module:' . get_called_class(); |
||||||||||
| 393 | $newMigrations = App::$cur->db->compareMigrations($code, $this->info['migrations']); |
||||||||||
|
0 ignored issues
–
show
The method
compareMigrations() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
The property
db does not exist on Inji\App. Since you implemented __get, consider adding a @property annotation.
Loading history...
The method
compareMigrations() does not exist on null.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. Loading history...
|
|||||||||||
| 394 | foreach ($newMigrations as $version => $migrationOption) { |
||||||||||
| 395 | $migration = include $this->path . '/migrations/' . $migrationOption . '.php'; |
||||||||||
| 396 | App::$cur->db->makeMigration($code, $version, $migration); |
||||||||||
|
0 ignored issues
–
show
The method
makeMigration() does not exist on Inji\Module. It seems like you code against a sub-type of Inji\Module such as Inji\Db.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||||||
| 397 | } |
||||||||||
| 398 | } |
||||||||||
| 399 | |||||||||||
| 400 | public function sitemap() { |
||||||||||
| 401 | return []; |
||||||||||
| 402 | } |
||||||||||
| 403 | } |