diff --git a/Helper/.bashrc b/Config/.bashrc old mode 100755 new mode 100644 similarity index 100% rename from Helper/.bashrc rename to Config/.bashrc diff --git a/Helper/ModuleTestTrait.php b/Helper/ModuleTestTrait.php new file mode 100644 index 0000000..e2b2daa --- /dev/null +++ b/Helper/ModuleTestTrait.php @@ -0,0 +1,769 @@ +markTestSkipped( + 'The TestModule is not tested' + ); + } + + $this->app = new class() extends ApplicationAbstract + { + protected string $appName = 'Api'; + }; + + $this->app->dbPool = $GLOBALS['dbpool']; + $this->app->router = new WebRouter(); + $this->app->dispatcher = new Dispatcher($this->app); + $this->app->appSettings = new CoreSettings(); + $this->app->moduleManager = new ModuleManager($this->app, __DIR__ . '/../../Modules/'); + $this->app->sessionManager = new HttpSession(0); + $this->app->accountManager = new AccountManager($this->app->sessionManager); + $this->app->eventManager = new EventManager($this->app->dispatcher); + $this->app->l11nManager = new L11nManager(); + } + + /** + * @group admin + * @slowThreshold 5000 + * @group module + */ + public function testModuleIntegration() : void + { + $iResponse = new HttpResponse(); + $iRequest = new HttpRequest(new HttpUri('')); + $iRequest->header->account = 1; + $iRequest->setData('status', ModuleStatusUpdateType::INSTALL); + + $iRequest->setData('module', self::NAME); + $this->app->moduleManager->get('Admin')->apiModuleStatusUpdate($iRequest, $iResponse); + + $iRequest->setData('status', ModuleStatusUpdateType::DEACTIVATE, true); + $this->app->moduleManager->get('Admin')->apiModuleStatusUpdate($iRequest, $iResponse); + self::assertFalse($this->app->moduleManager->isActive(self::NAME), 'Module "' . self::NAME . '" is not active.'); + + $iRequest->setData('status', ModuleStatusUpdateType::ACTIVATE, true); + $this->app->moduleManager->get('Admin')->apiModuleStatusUpdate($iRequest, $iResponse); + self::assertTrue($this->app->moduleManager->isActive(self::NAME), 'Module "' . self::NAME . '" is not active.'); + } + + /** + * @group module + * @coversNothing + */ + public function testMembers() : void + { + $module = $this->app->moduleManager->get(self::NAME); + + if ($module::ID === 0) { + return; + } + + self::assertEquals(self::NAME, $module::NAME); + self::assertEquals(\realpath(__DIR__ . '/../../Modules/' . self::NAME), \realpath($module::PATH)); + self::assertGreaterThanOrEqual(0, Version::compare($module::VERSION, '1.0.0')); + } + + /** + * @group module + * @coversNothing + */ + public function testValidMapper() : void + { + $mappers = \glob(__DIR__ . '/../../Modules/' . self::NAME . '/Models/*Mapper.php'); + + foreach ($mappers as $mapper) { + $class = $this->getMapperFromPath($mapper); + $columns = $class::COLUMNS; + + foreach ($columns as $cName => $column) { + if (!\in_array($column['type'], ['int', 'string', 'compress', 'DateTime', 'DateTimeImmutable', 'Json', 'Serializable', 'bool', 'float'])) { + self::assertTrue(false, 'Mapper "' . $class . '" column "' . $cName . '" has invalid type'); + } + + if ($cName !== ($column['name'] ?? false)) { + self::assertTrue(false); + } + } + } + + self::assertTrue(true); + } + + /** + * Get mapper from file path + * + * @param string $mapper Mapper path + * + * @return string + */ + private function getMapperFromPath(string $mapper) : string + { + $name = \substr($mapper, \strlen(__DIR__ . '/../../Modules/' . self::NAME . '/Models/'), -4); + + return '\\Modules\\' . self::NAME . '\\Models\\' . $name; + } + + /** + * @group module + * @coversNothing + */ + public function testMapperAgainstModel() : void + { + $mappers = \glob(__DIR__ . '/../../Modules/' . self::NAME . '/Models/*Mapper.php'); + + foreach ($mappers as $mapper) { + $class = $this->getMapperFromPath($mapper); + if ($class === '\Modules\Admin\Models\ModuleMapper' + || !Autoloader::exists(\substr($class, 0, -6)) + ) { + continue; + } + + $columns = $class::COLUMNS; + $ownsOne = $class::OWNS_ONE; + + $classReflection = new \ReflectionClass(\substr($class, 0, -6)); + $defaultProperties = $classReflection->getDefaultProperties(); + + $invalidAcessors = []; + + foreach ($columns as $cName => $column) { + $isArray = false; + // testing existence of member variable in model + if (\stripos($column['internal'], '/') !== false) { + $column['internal'] = \explode('/', $column['internal'])[0]; + $isArray = true; + } + + if (!$classReflection->hasProperty($column['internal'])) { + self::assertTrue(false, 'Mapper "' . $class . '" column "' . $cName . '" has missing/invalid internal/member'); + } + + $property = $classReflection->getProperty($column['internal']) ?? null; + if ($property === null || (!$property->isPublic() && (!isset($column['private']) || !$column['private']))) { + $invalidAcessors[] = $column['internal']; + } + + // testing correct mapper/model variable type definition + $property = $defaultProperties[$column['internal']] ?? null; + if (!($property === null /* not every value is allowed to be null but this just has to be correctly implemented in the mapper, no additional checks for this case! */ + || (\is_string($property) && ($column['type'] === 'string' || $column['type'] === 'compress')) + || (\is_int($property) && $column['type'] === 'int') + || (\is_array($property) && ($column['type'] === 'Json' || $column['type'] === 'Serializable' || $isArray)) + || (\is_bool($property) && $column['type'] === 'bool') + || (\is_float($property) && $column['type'] === 'float') + || ($property instanceof \DateTime && $column['type'] === 'DateTime') + || ($property instanceof \DateTimeImmutable && $column['type'] === 'DateTimeImmutable') + || (isset($ownsOne[$column['internal']]) && $column['type'] === 'int') // if it is in ownsOne it can be a different type because it is a reference! + )) { + self::assertTrue(false, 'Mapper "' . $class . '" column "' . $cName . '" has invalid type compared to model definition'); + } + } + + if (!empty($invalidAcessors)) { + self::assertTrue(false, 'Mapper "' . $class . '" must define private for "' . \implode(',', $invalidAcessors) . '" or make them public (recommended) in the model'); + } + + // test hasMany variable exists in model + $rel = $class::HAS_MANY; + foreach ($rel as $pName => $def) { + $property = $classReflection->getProperty($pName) ?? null; + if (!\array_key_exists($pName, $defaultProperties) && $property === null) { + self::assertTrue(false, 'Mapper "' . $class . '" property "' . $pName . '" doesn\'t exist in model'); + } + } + } + + self::assertTrue(true); + } + + /** + * @group module + * @coversNothing + */ + public function testValidDbSchema() : void + { + $schemaPath = __DIR__ . '/../../Modules/' . self::NAME . '/Admin/Install/db.json'; + + if (!\is_file($schemaPath)) { + self::assertTrue(true); + + return; + } + + $db = \json_decode(\file_get_contents($schemaPath), true); + + foreach ($db as $name => $table) { + if ($name !== ($table['name'] ?? false)) { + self::assertTrue(false, 'Schema "' . $schemaPath . '" name "' . $name . '" is invalid'); + } + + foreach ($table['fields'] as $cName => $column) { + if ($cName !== ($column['name'] ?? false)) { + self::assertTrue(false, 'Schema "' . $schemaPath . '" name "' . $cName . '" is invalid'); + } + + if (!(\stripos($column['type'] ?? '', 'TINYINT') === 0 + || \stripos($column['type'] ?? '', 'SMALLINT') === 0 + || \stripos($column['type'] ?? '', 'INT') === 0 + || \stripos($column['type'] ?? '', 'BIGINT') === 0 + || \stripos($column['type'] ?? '', 'VARCHAR') === 0 + || \stripos($column['type'] ?? '', 'VARBINARY') === 0 + || \stripos($column['type'] ?? '', 'TEXT') === 0 + || \stripos($column['type'] ?? '', 'LONGTEXT') === 0 + || \stripos($column['type'] ?? '', 'BLOB') === 0 + || \stripos($column['type'] ?? '', 'DATETIME') === 0 + || \stripos($column['type'] ?? '', 'DECIMAL') === 0 + )) { + self::assertTrue(false, 'Schema "' . $schemaPath . '" type "' . ($column['type'] ?? '') . '" is a missing/invalid type'); + } + } + } + + $dbTemplate = \json_decode(\file_get_contents(__DIR__ . '/../../phpOMS/DataStorage/Database/tableDefinition.json'), true); + self::assertTrue(Json::validateTemplate($dbTemplate, $db), 'Invalid db template for ' . self::NAME); + } + + /** + * @group module + * @coversNothing + */ + public function testDbSchemaAgainstDb() : void + { + $builder = new SchemaBuilder($this->app->dbPool->get()); + $tables = $builder->selectTables()->execute()->fetchAll(\PDO::FETCH_COLUMN); + + $schemaPath = __DIR__ . '/../../Modules/' . self::NAME . '/Admin/Install/db.json'; + + if (!\is_file($schemaPath)) { + self::assertTrue(true); + + return; + } + + $db = \json_decode(\file_get_contents($schemaPath), true); + + foreach ($db as $name => $table) { + if (!\in_array($name, $tables)) { + self::assertTrue(false, 'Table ' . $name . ' doesn\'t exist in the database.'); + } + + $field = new SchemaBuilder($this->app->dbPool->get()); + $fields = $field->selectFields($name)->execute()->fetchAll(); + + foreach ($table['fields'] as $cName => $column) { + if (!ArrayUtils::inArrayRecursive($cName, $fields)) { + self::assertTrue(false, 'Couldn\'t find "' . $cName . '" in "' . $name . '"'); + } + } + } + + self::assertTrue(true); + } + + /** + * @group module + * @coversNothing + */ + public function testMapperAgainstDbSchema() : void + { + $schemaPath = __DIR__ . '/../../Modules/' . self::NAME . '/Admin/Install/db.json'; + $mappers = \glob(__DIR__ . '/../../Modules/' . self::NAME . '/Models/*Mapper.php'); + + if (!\is_file($schemaPath)) { + self::assertTrue(true); + + return; + } + + $db = \json_decode(\file_get_contents($schemaPath), true); + + foreach ($mappers as $mapper) { + $class = $this->getMapperFromPath($mapper); + + if (\defined('self::MAPPER_TO_IGNORE') && \in_array(\ltrim($class, '\\'), self::MAPPER_TO_IGNORE) + || empty($class::COLUMNS) + || $class === '\Modules\Admin\Models\ModuleMapper' + ) { + continue; + } + + $table = $class::TABLE; + $columns = $class::COLUMNS; + + foreach ($columns as $cName => $column) { + // testing existence of field name in schema + if (!isset($db[$table]['fields'][$cName])) { + self::assertTrue(false, 'Mapper "' . $class . '" column "' . $cName . '" doesn\'t match schema'); + } + + // testing schema/mapper same column data type + if (!(($column['type'] === 'string' + && (\stripos($db[$table]['fields'][$cName]['type'], 'VARCHAR') === 0 + || \stripos($db[$table]['fields'][$cName]['type'], 'VARBINARY') === 0 + || \stripos($db[$table]['fields'][$cName]['type'], 'BLOB') === 0 + || \stripos($db[$table]['fields'][$cName]['type'], 'TEXT') === 0 + || \stripos($db[$table]['fields'][$cName]['type'], 'LONGTEXT') === 0)) + || ($column['type'] === 'int' + && (\stripos($db[$table]['fields'][$cName]['type'], 'TINYINT') === 0 + || \stripos($db[$table]['fields'][$cName]['type'], 'SMALLINT') === 0 + || \stripos($db[$table]['fields'][$cName]['type'], 'INT') === 0 + || \stripos($db[$table]['fields'][$cName]['type'], 'BIGINT') === 0)) + || ($column['type'] === 'Json' + && (\stripos($db[$table]['fields'][$cName]['type'], 'VARCHAR') === 0 + || \stripos($db[$table]['fields'][$cName]['type'], 'LONGTEXT') === 0 + || \stripos($db[$table]['fields'][$cName]['type'], 'TEXT') === 0)) + || ($column['type'] === 'compress' + && (\stripos($db[$table]['fields'][$cName]['type'], 'BLOB') === 0)) + || ($column['type'] === 'Serializable') + || ($column['type'] === 'bool' && \stripos($db[$table]['fields'][$cName]['type'], 'TINYINT') === 0) + || ($column['type'] === 'float' && \stripos($db[$table]['fields'][$cName]['type'], 'DECIMAL') === 0) + || ($column['type'] === 'DateTime' && \stripos($db[$table]['fields'][$cName]['type'], 'DATETIME') === 0) + || ($column['type'] === 'DateTimeImmutable' && \stripos($db[$table]['fields'][$cName]['type'], 'DATETIME') === 0) + )) { + self::assertTrue(false, 'Schema "' . $schemaPath . '" type "' . ($column['type'] ?? '') . '" is incompatible with mapper "' . $class . '" definition "' . $db[$table]['fields'][$cName]['type'] . '" for field "' . $cName . '"'); + } + } + + // testing schema/mapper same primary key definition + $primary = $class::PRIMARYFIELD; + if (!($db[$table]['fields'][$primary]['primary'] ?? false)) { + self::assertTrue(false, 'Field "' . $primary . '" from mapper "' . $class . '" is not defined as primary key in table "' . $table . '"'); + } + } + + self::assertTrue(true); + } + + /** + * @group module + * @coversNothing + */ + public function testJson() : void + { + $sampleInfo = \json_decode(\file_get_contents(__DIR__ . '/../TestModule/info.json'), true); + $infoTemplate = \json_decode(\file_get_contents(__DIR__ . '/../../phpOMS/Module/infoLayout.json'), true); + + $module = $this->app->moduleManager->get(self::NAME); + + if ($module::ID === 0) { + return; + } + + // validate info.json + $info = \json_decode(\file_get_contents($module::PATH . '/info.json'), true); + self::assertTrue($this->infoJsonTest($info, $sampleInfo), 'Info assert failed for '. self::NAME); + self::assertTrue(Json::validateTemplate($infoTemplate, $info), 'Invalid info template for ' . self::NAME); + } + + /** + * @group module + * @coversNothing + */ + public function testDependency() : void + { + $module = $this->app->moduleManager->get(self::NAME); + + if ($module::ID === 0) { + return; + } + + // validate dependency installation + $info = \json_decode(\file_get_contents($module::PATH . '/info.json'), true); + self::assertTrue($this->dependencyTest($info, $this->app->moduleManager->getInstalledModules(false)), 'Invalid dependency configuration in ' . self::NAME); + } + + /** + * @group module + * @coversNothing + */ + public function testRoutes() : void + { + $this->app->moduleManager = new ModuleManager($this->app, __DIR__ . '/../../Modules/'); + $totalBackendRoutes = \is_file(__DIR__ . '/../../Web/Backend/Routes.php') ? include __DIR__ . '/../../Web/Backend/Routes.php' : []; + $totalApiRoutes = \is_file(__DIR__ . '/../../Web/Api/Routes.php') ? include __DIR__ . '/../../Web/Api/Routes.php' : []; + + $module = $this->app->moduleManager->get(self::NAME); + + if ($module::ID === 0) { + return; + } + + // test routes + if (\is_file($module::PATH . '/Admin/Routes/Web/Backend.php')) { + $moduleRoutes = include $module::PATH . '/Admin/Routes/Web/Backend.php'; + self::assertEquals(1, $this->routesTest($moduleRoutes, $totalBackendRoutes), 'Backend route assert failed for '. self::NAME); + } + + // test routes + if (\is_file($module::PATH . '/Admin/Routes/Web/Api.php')) { + $moduleRoutes = include $module::PATH . '/Admin/Routes/Web/Api.php'; + self::assertEquals(1, $this->routesTest($moduleRoutes, $totalApiRoutes), 'Api route assert failed for '. self::NAME); + } + } + + /** + * @group module + * @coversNothing + */ + public function testHooks() : void + { + $this->app->moduleManager = new ModuleManager($this->app, __DIR__ . '/../../Modules/'); + $totalBackendHooks = \is_file(__DIR__ . '/../../Web/Backend/Hooks.php') ? include __DIR__ . '/../../Web/Backend/Hooks.php' : []; + $totalApiHooks = \is_file(__DIR__ . '/../../Web/Api/Hooks.php') ? include __DIR__ . '/../../Web/Api/Hooks.php' : []; + + $module = $this->app->moduleManager->get(self::NAME); + + if ($module::ID === 0) { + return; + } + + // test hooks + if (\is_file($module::PATH . '/Admin/Hooks/Web/Backend.php')) { + $moduleHooks = include $module::PATH . '/Admin/Hooks/Web/Backend.php'; + self::assertEquals(1, $this->hooksTest($moduleHooks, $totalBackendHooks), 'Backend hook assert failed for '. self::NAME); + } + + // test hooks + if (\is_file($module::PATH . '/Admin/Hooks/Web/Api.php')) { + $moduleHooks = include $module::PATH . '/Admin/Hooks/Web/Api.php'; + self::assertEquals(1, $this->hooksTest($moduleHooks, $totalApiHooks), 'Api hook assert failed for '. self::NAME); + } + } + + /** + * @group final + * @group module + * @coversNothing + */ + public function testNavigation() : void + { + $module = $this->app->moduleManager->get(self::NAME); + + if ($module::ID === 0 + || $this->app->moduleManager->get('Navigation')::ID === 0 + || !\is_file($module::PATH . '/Admin/Install/Navigation.install.json') + ) { + return; + } + + // test if navigation db entries match json files + self::assertTrue( + $this->navLinksTest( + $this->app->dbPool->get(), + \json_decode( + \file_get_contents($module::PATH . '/Admin/Install/Navigation.install.json'), + true + ), + self::NAME + ) + ); + } + + /** + * Test if all navigation links are in the database + * + * @param mixed $db Database connection + * @param array $links Navigation links from install file + * @param string $module Module name + * + * @return bool + */ + private function navLinksTest($db, array $links, string $module) : bool + { + $query = new Builder($db); + $query->select('nav_id')->from('nav')->where('nav_from', '=', $module); + + $result = $query->execute()->fetchAll(\PDO::FETCH_COLUMN); + $it = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($links)); + + foreach ($it as $link) { + if (\is_array($link) + && !\in_array($link['id'], $result) + ) { + return false; + } + } + + return true; + } + + /** + * Test if all dependencies got installed + * + * @param array $info Module info array/file + * @param array $modules Installed modules + * + * @return bool + */ + private function dependencyTest(array $info, array $modules) : bool + { + foreach ($info['dependencies'] as $module => $version) { + if (!isset($modules[$module])) { + return false; + } + } + + return true; + } + + /** + * Test if route destinations exist (in the *Controller and global application route file) + * + * @param array $module Routes of the module from the respective app and module route file + * @param array $total Routing file of the respective application which contains all app routes + * + * @return int + */ + private function routesTest(array $module, array $total) : int + { + foreach ($module as $route => $dests) { + // test route existence after installation + if (!isset($total[$route])) { + return -1; + } + + // test route class + foreach ($dests as $verb) { + $parts = \explode(':', $verb['dest']); + $path = __DIR__ . '/../../' . \ltrim(\strtr($parts[0], '\\', '/'), '/') . '.php'; + if (!\is_file($path)) { + return -2; + } + + // test route method + $content = \file_get_contents($path); + if (\stripos($content, 'function ' . $parts[\count($parts) - 1]) === false + && \strpos($parts[\count($parts) - 1], 'Trait') === false + ) { + return -3; + } + } + } + + return 1; + } + + /** + * Test if hook destinations exist (in the *Controller and global application hook file) + * + * @param array $module Hooks of the module from the respective app and module hook file + * @param array $total Routing file of the respective application which contains all app routes + * + * @return int + */ + private function hooksTest(array $module, array $total) : int + { + foreach ($module as $route => $dests) { + if (!isset($total[$route])) { + return -1; + } + + // test route class + foreach ($dests['callback'] as $callback) { + $parts = \explode(':', $callback); + $path = __DIR__ . '/../../' . \ltrim(\strtr($parts[0], '\\', '/'), '/') . '.php'; + if (!\is_file($path)) { + return -2; + } + + // test route method + $content = \file_get_contents($path); + if (\stripos($content, 'function ' . $parts[\count($parts) - 1]) === false) { + return -3; + } + } + } + + return 1; + } + + /** + * Test if the module info file has the correct types + * + * @param array $module Module info file + * @param array $samle Sample info file (as basis for checking the data types) + * + * @return bool + */ + private function infoJsonTest(array $module, array $sample) : bool + { + try { + if (\gettype($module['name']['id']) === \gettype($sample['name']['id']) + && \gettype($module['name']['internal']) === \gettype($sample['name']['internal']) + && \gettype($module['name']['external']) === \gettype($sample['name']['external']) + && \gettype($module['category']) === \gettype($sample['category']) + && \gettype($module['version']) === \gettype($sample['version']) + && \gettype($module['requirements']) === \gettype($sample['requirements']) + && \gettype($module['creator']) === \gettype($sample['creator']) + && \gettype($module['creator']['name']) === \gettype($sample['creator']['name']) + && \gettype($module['description']) === \gettype($sample['description']) + && \gettype($module['directory']) === \gettype($sample['directory']) + && \gettype($module['dependencies']) === \gettype($sample['dependencies']) + && \gettype($module['providing']) === \gettype($sample['providing']) + && \gettype($module['load']) === \gettype($sample['load']) + ) { + return true; + } + } catch (\Throwable $_) { + return false; + } + + return false; + } + + /** + * @group module + * @coversNothing + */ + public function testRequestLoads() : void + { + if (!\defined('self::URI_LOAD') || empty(self::URI_LOAD)) { + return; + } + + $request = new HttpRequest(new HttpUri(self::URI_LOAD)); + $request->createRequestHashs(2); + + $loaded = $this->app->moduleManager->getUriLoad($request); + + $found = false; + foreach ($loaded[4] as $module) { + if ($module['module_load_file'] === self::NAME) { + $found = true; + break; + } + } + + self::assertTrue($found, 'Module ' . self::NAME . ' is not loaded at ' . self::URI_LOAD . '. The module info.json file ("load" section for type: 4 loads) and the routing files paths should match.'); + self::assertGreaterThan(0, \count($this->app->moduleManager->getLanguageFiles($request))); + self::assertTrue(\in_array(self::NAME, $this->app->moduleManager->getRoutedModules($request))); + + $this->app->moduleManager->initRequestModules($request); + self::assertTrue($this->app->moduleManager->isRunning(self::NAME)); + } + + /** + * @group module + * @coversNothing + */ + public function testLanguage() : void + { + $module = $this->app->moduleManager->get(self::NAME); + if ($module::ID === 0) { + return; + } + + $required = ['en', 'de']; + if (!\is_dir($module::PATH . '/Theme')) { + return; + } + + $themes = \scandir($module::PATH . '/Theme'); + if ($themes === false) { + return; + } + + foreach ($themes as $theme) { + if ($theme === '.' || $theme === '..' || !\is_dir($module::PATH . '/Theme/' . $theme . '/Lang')) { + continue; + } + + $langKeys = []; + + $langFiles = \scandir($module::PATH . '/Theme/' . $theme . '/Lang'); + foreach ($langFiles as $file) { + if ($file === '.' || $file === '..' || \stripos($file, '.lang.') === false) { + continue; + } + + $parts = \explode('.', $file); + $type = ''; + + $type = \strlen($parts[0]) === 2 ? '' : $parts[0]; + + if (!isset($langKeys[$type])) { + $langKeys[$type] = [ + 'required' => null, + 'keys' => [], + ]; + } + + // check if required lanugages files exist IFF any language file of a specific type exists + if ($langKeys[$type]['required'] === null) { + $langKeys[$type]['required'] = true; + + $missingLanguages = []; + foreach ($required as $lang) { + if (!\in_array(($type !== '' ? $type . '.' : '') . $lang . '.lang.php', $langFiles)) { + $langKeys[$type]['required'] = false; + $missingLanguages[] = $lang; + } + } + + if (!empty($missingLanguages)) { + self::assertTrue(false, 'The language files "' . \implode(', ', $missingLanguages) . '" are missing with type "' . $type . '".'); + } + } + + // compare key equality + // every key in the key list must be present in all other language files of the same type and vice versa + $langArray = include $module::PATH . '/Theme/' . $theme . '/Lang/' . $file; + $langArray = \reset($langArray); + + $keys = \array_keys($langArray); + + if (empty($langKeys[$type]['keys'])) { + $langKeys[$type]['keys'] = $keys; + } elseif (!empty($diff1 = \array_diff($langKeys[$type]['keys'], $keys)) + || !empty($diff2 = \array_diff($keys, $langKeys[$type]['keys']))) { + self::assertTrue(false, $file . ': The language keys "' . \implode(', ', \array_merge($diff1, $diff2)) . '" are different.'); + } + } + } + + self::assertTrue(true); + } +} diff --git a/Helper/findMissingAdminTest.php b/Helper/Php/findMissingAdminTest.php old mode 100755 new mode 100644 similarity index 59% rename from Helper/findMissingAdminTest.php rename to Helper/Php/findMissingAdminTest.php index 36ceefc..ee6f90a --- a/Helper/findMissingAdminTest.php +++ b/Helper/Php/findMissingAdminTest.php @@ -14,17 +14,17 @@ declare(strict_types=1); // Find modules where the Module/tests/Admin/AdminTest.php is missing -$modules = \scandir(__DIR__ . '/../../Modules'); +$modules = \scandir(__DIR__ . '/../../../Modules'); foreach ($modules as $module) { if ($module === '..' || $module === '.' - || !\is_dir(__DIR__ . '/../../Modules/' . $module) - || !\is_file(__DIR__ . '/../../Modules/' . $module . '/info.json')) + || !\is_dir(__DIR__ . '/../../../Modules/' . $module) + || !\is_file(__DIR__ . '/../../../Modules/' . $module . '/info.json')) { continue; } - if (!\is_file(__DIR__ . '/../../Modules/' . $module . '/tests/Admin/AdminTest.php')) { + if (!\is_file(__DIR__ . '/../../../Modules/' . $module . '/tests/Admin/AdminTest.php')) { echo $module . "\n"; } } diff --git a/Helper/findMissingApiFunctions.php b/Helper/Php/findMissingApiFunctions.php similarity index 95% rename from Helper/findMissingApiFunctions.php rename to Helper/Php/findMissingApiFunctions.php index 456623d..2785be4 100644 --- a/Helper/findMissingApiFunctions.php +++ b/Helper/Php/findMissingApiFunctions.php @@ -14,7 +14,7 @@ declare(strict_types=1); // Create missing api functions -$modules = \scandir(__DIR__ . '/../../Modules'); +$modules = \scandir(__DIR__ . '/../../../Modules'); $allowed = ['Organization']; @@ -241,22 +241,22 @@ function deleteFunction($module, $modelName) foreach ($modules as $module) { if ($module === '..' || $module === '.' - || !\is_dir(__DIR__ . '/../../Modules/' . $module) - || !\is_dir(__DIR__ . '/../../Modules/' . $module . '/Controller') - || !\is_file(__DIR__ . '/../../Modules/' . $module . '/info.json') + || !\is_dir(__DIR__ . '/../../../Modules/' . $module) + || !\is_dir(__DIR__ . '/../../../Modules/' . $module . '/Controller') + || !\is_file(__DIR__ . '/../../../Modules/' . $module . '/info.json') || (!empty($allowed) && !\in_array($module, $allowed)) ) { continue; } - $controllers = \scandir(__DIR__ . '/../../Modules/' . $module . '/Controller'); + $controllers = \scandir(__DIR__ . '/../../../Modules/' . $module . '/Controller'); foreach ($controllers as $controller) { if (\stripos($controller, 'Api') === false) { continue; } - $content = \file_get_contents(__DIR__ . '/../../Modules/' . $module . '/Controller/' . $controller); + $content = \file_get_contents(__DIR__ . '/../../../Modules/' . $module . '/Controller/' . $controller); $matches = []; \preg_match_all('/(public function )(.*?)(\()/', $content, $matches); @@ -375,7 +375,7 @@ foreach ($modules as $module) { echo "\nMissing functions \"" . $module . "\": \n"; $newContent = \rtrim($content, " }\n") . "\n }\n" . $newContent . "}\n"; - \file_put_contents(__DIR__ . '/../../Modules/' . $module . '/Controller/' . $controller, $newContent); + \file_put_contents(__DIR__ . '/../../../Modules/' . $module . '/Controller/' . $controller, $newContent); } foreach ($missing as $m) { diff --git a/Helper/findMissingNullModelTest.php b/Helper/Php/findMissingNullModelTest.php old mode 100755 new mode 100644 similarity index 74% rename from Helper/findMissingNullModelTest.php rename to Helper/Php/findMissingNullModelTest.php index 434a40a..97ce664 --- a/Helper/findMissingNullModelTest.php +++ b/Helper/Php/findMissingNullModelTest.php @@ -14,18 +14,18 @@ declare(strict_types=1); // Find null models with no null model test and creates the tests -$modules = \scandir(__DIR__ . '/../../Modules'); +$modules = \scandir(__DIR__ . '/../../../Modules'); foreach ($modules as $module) { if ($module === '..' || $module === '.' - || !\is_dir(__DIR__ . '/../../Modules/' . $module) - || !\is_dir(__DIR__ . '/../../Modules/' . $module . '/Models') - || !\is_file(__DIR__ . '/../../Modules/' . $module . '/info.json')) + || !\is_dir(__DIR__ . '/../../../Modules/' . $module) + || !\is_dir(__DIR__ . '/../../../Modules/' . $module . '/Models') + || !\is_file(__DIR__ . '/../../../Modules/' . $module . '/info.json')) { continue; } - $models = \scandir(__DIR__ . '/../../Modules/' . $module . '/Models'); + $models = \scandir(__DIR__ . '/../../../Modules/' . $module . '/Models'); foreach ($models as $model) { if ($model === '..' || $model === '.' @@ -44,15 +44,15 @@ foreach ($modules as $module) { throw new Exception('invalid substr'); } - if (!\is_file(__DIR__ . '/../../Modules/' . $module . '/tests/Models/Null' . $model . 'Test.php')) { + if (!\is_file(__DIR__ . '/../../../Modules/' . $module . '/tests/Models/Null' . $model . 'Test.php')) { echo $module . ': Null' . $model . "\n"; - if (!\is_dir(__DIR__ . '/../../Modules/' . $module . '/tests')) { - \mkdir(__DIR__ . '/../../Modules/' . $module . '/tests'); + if (!\is_dir(__DIR__ . '/../../../Modules/' . $module . '/tests')) { + \mkdir(__DIR__ . '/../../../Modules/' . $module . '/tests'); } - if (!\is_dir(__DIR__ . '/../../Modules/' . $module . '/tests/Models')) { - \mkdir(__DIR__ . '/../../Modules/' . $module . '/tests/Models'); + if (!\is_dir(__DIR__ . '/../../../Modules/' . $module . '/tests/Models')) { + \mkdir(__DIR__ . '/../../../Modules/' . $module . '/tests/Models'); } $test = ' $line) { $file = \reset($parts); $function = \end($parts); - $file = __DIR__ . '/../../' . \strtr($file, '\\', '/') . '.php'; + $file = __DIR__ . '/../../../' . \strtr($file, '\\', '/') . '.php'; if (!\is_file($file)) { $noTestFile[] = $key; @@ -80,8 +80,8 @@ foreach ($invalidDescription as $function) { } $directories = [ - __DIR__ . '/../../phpOMS/tests/**/*Test.php', - __DIR__ . '/../../Modules/**/tests/**/*Test.php' + __DIR__ . '/../../../phpOMS/tests/**/*Test.php', + __DIR__ . '/../../../Modules/**/tests/**/*Test.php' ]; foreach ($directories as $directory) { @@ -103,7 +103,7 @@ foreach ($directories as $directory) { $end = \strpos($line, '('); $function = \substr($line, 20, $end - 20); - $name = \substr(\realpath($file), \strlen(\realpath(__DIR__ . '/../../')) + 1, -4); + $name = \substr(\realpath($file), \strlen(\realpath(__DIR__ . '/../../../')) + 1, -4); $name = \strtr($name, '/', '\\'); $reportKey = $name . ':' . $function; diff --git a/Helper/inspectproject.sh b/Helper/Scripts/inspectproject.sh old mode 100755 new mode 100644 similarity index 100% rename from Helper/inspectproject.sh rename to Helper/Scripts/inspectproject.sh diff --git a/Helper/install.sh b/Helper/Scripts/install.sh old mode 100755 new mode 100644 similarity index 100% rename from Helper/install.sh rename to Helper/Scripts/install.sh diff --git a/Helper/screenshots.txt b/Helper/Scripts/screenshots.txt old mode 100755 new mode 100644 similarity index 100% rename from Helper/screenshots.txt rename to Helper/Scripts/screenshots.txt diff --git a/Helper/serverInstall.sh b/Helper/Scripts/serverInstall.sh similarity index 100% rename from Helper/serverInstall.sh rename to Helper/Scripts/serverInstall.sh diff --git a/Helper/sitespeedAuth.js b/Helper/Scripts/sitespeedAuth.js old mode 100755 new mode 100644 similarity index 100% rename from Helper/sitespeedAuth.js rename to Helper/Scripts/sitespeedAuth.js diff --git a/Helper/sitespeedUrls.txt b/Helper/Scripts/sitespeedUrls.txt old mode 100755 new mode 100644 similarity index 100% rename from Helper/sitespeedUrls.txt rename to Helper/Scripts/sitespeedUrls.txt diff --git a/Helper/testreport.sh b/Helper/testreport.sh deleted file mode 100755 index ede5f37..0000000 --- a/Helper/testreport.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -mkdir -p Build/test - -# php cs + phpstan + eslint file generation -./vendor/bin/phpcs --severity=1 ./ --standard="Build/Config/phpcs.xml" -s --report-junit=Build/test/junit_phpcs.xml -./vendor/bin/phpstan analyse -l 9 -c Build/Config/phpstan.neon --error-format=prettyJson ./ > Build/test/phpstan.json -npx eslint jsOMS/ -c Build/Config/.eslintrc.json -o Build/test/junit_eslint.xml -f junit - -# Remove empty lines and lines with warnings which corrupt the json format -sed -i '/^$/d' Build/test/phpstan.json -sed -i '/^Warning: /d' Build/test/phpstan.json - -# Create report -php ./Tools/TestReportGenerator/src/index.php \ --b /var/www/html/Karaka \ --l /var/www/html/Karaka/Build/Config/reportLang.php \ --c /var/www/html/Karaka/tests/coverage.xml \ --s /var/www/html/Karaka/Build/test/junit_phpcs.xml \ --sj /var/www/html/Karaka/Build/test/junit_eslint.xml \ --a /var/www/html/Karaka/Build/test/phpstan.json \ --u /var/www/html/Karaka/Build/test/junit_php.xml \ --d /var/www/html/Karaka/Build/test/ReportExternal \ ---version 1.0.0 diff --git a/Helper/watcher.sh b/Helper/watcher.sh deleted file mode 100755 index 7291e28..0000000 --- a/Helper/watcher.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -DIRECTORY_TO_OBSERVE="cssOMS jsOMS" -function watcher { - inotifywait -r -e modify,move,create,delete \ - --exclude ".*(\.css|\.php|\.json|\.md|\.sh|\.txt|\.log|\.min\.js)" \ - ${DIRECTORY_TO_OBSERVE} -} - -BUILD_SCRIPT=build_frontend.sh -function build { - bash ${BUILD_SCRIPT} -} - -build -while watcher; do - build -done diff --git a/Inspection/Html/static_text.sh b/Inspection/Html/static_text.sh new file mode 100644 index 0000000..e5a95b8 --- /dev/null +++ b/Inspection/Html/static_text.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +. config.sh + +echo "#################################################" +echo "Start static text inspection" +echo "#################################################" + +grep -rlnP '(title|alt|aria\-label)(=\")((?!\<\?).)*(>)' --include \*.tpl.php Modules >> ${INSPECTION_PATH}/Modules/html/static_text.log +grep -rlnP '(\