From 5da198ec4ed2dbff0e7a8499066a4f0ab9d6cdad Mon Sep 17 00:00:00 2001 From: Stefan Hagspiel Date: Mon, 23 Feb 2026 08:50:37 +0100 Subject: [PATCH 1/6] Refactor SQL queries: enforce parameterized bindings and consistent style --- .../src/Handler/ApplicationLoggerDb.php | 16 ++- .../src/Maintenance/LogArchiveTask.php | 100 ++++++++----- .../Maintenance/LogMailMaintenanceTask.php | 21 +-- .../Command/DeleteUnusedLocaleDataCommand.php | 23 +-- .../src/Command/Document/CleanupCommand.php | 2 +- .../src/Command/MysqlToolsCommand.php | 8 +- .../src/Tool/Adapter/Sql.php | 12 +- .../src/Installer.php | 4 +- .../src/Repository/JobRunRepository.php | 7 +- .../src/Controller/MiscController.php | 29 ++-- .../ResponseExceptionListener.php | 5 +- .../src/DataProvider/GDPR/Assets.php | 11 +- .../src/DataProvider/GDPR/DataObjects.php | 10 +- .../src/Model/Search/Backend/Data/Dao.php | 17 ++- lib/Console/Application.php | 2 +- lib/Db/Helper.php | 8 +- .../CleanupClassificationstoreTablesTask.php | 10 +- .../CleanupBrickTablesTaskHelper.php | 2 +- ...CleanupFieldcollectionTablesTaskHelper.php | 2 +- .../Tasks/DataObject/DataObjectTaskHelper.php | 2 +- .../Tasks/DbCleanupBrokenViewsTask.php | 4 +- lib/Maintenance/Tasks/TmpStoreCleanupTask.php | 2 +- lib/Model/Dao/AbstractDao.php | 2 +- .../ClassDefinitionManager.php | 2 +- .../Listing/Dao/QueryBuilderHelperTrait.php | 2 +- lib/Tool/Requirements.php | 2 +- .../AbstractNotificationService.php | 5 +- models/Asset/Dao.php | 84 ++++++++--- models/Asset/Folder.php | 9 +- models/DataObject/AbstractObject.php | 2 +- models/DataObject/AbstractObject/Dao.php | 112 ++++++++++----- models/DataObject/ClassDefinition.php | 2 +- models/DataObject/ClassDefinition/Dao.php | 86 +++++++----- .../Data/AdvancedManyToManyObjectRelation.php | 44 ++++-- .../Data/AdvancedManyToManyRelation.php | 55 +++++--- .../Data/ReverseObjectRelation.php | 5 +- .../ClassDefinition/Data/UrlSlug.php | 7 +- .../DataObject/ClassDefinition/Helper/Dao.php | 16 +-- models/DataObject/ClassDefinition/Service.php | 2 +- .../CollectionConfig/Dao.php | 17 ++- models/DataObject/Classificationstore/Dao.php | 55 +++++--- .../Classificationstore/GroupConfig/Dao.php | 25 +++- .../Classificationstore/KeyConfig/Dao.php | 21 ++- .../Classificationstore/StoreConfig/Dao.php | 16 ++- models/DataObject/Concrete.php | 9 +- models/DataObject/Concrete/Dao.php | 73 ++++++---- .../Concrete/Dao/InheritanceHelper.php | 56 ++++++-- .../DataObject/Data/AbstractMetadata/Dao.php | 10 +- .../DataObject/Data/ElementMetadata/Dao.php | 10 +- models/DataObject/Data/ObjectMetadata/Dao.php | 4 +- models/DataObject/Fieldcollection/Dao.php | 40 +++--- .../Fieldcollection/Definition/Dao.php | 12 +- models/DataObject/Localizedfield/Dao.php | 93 ++++++------- models/DataObject/Objectbrick/Dao.php | 5 +- models/DataObject/Objectbrick/Data/Dao.php | 75 ++++++---- .../DataObject/Objectbrick/Definition/Dao.php | 28 ++-- models/DataObject/QuantityValue/Unit/Dao.php | 14 +- models/DataObject/Service.php | 7 +- .../DataObject/Traits/CompositeIndexTrait.php | 6 +- models/Dependency/Dao.php | 131 +++++++++++++----- models/Document/Dao.php | 107 ++++++++++---- models/Document/Email/Dao.php | 11 +- models/Document/Hardlink/Dao.php | 11 +- models/Document/Link/Dao.php | 11 +- models/Document/Page/Dao.php | 11 +- models/Document/PageSnippet/Dao.php | 5 +- models/Document/Service/Dao.php | 20 ++- models/Document/Snippet/Dao.php | 11 +- models/Element/Dao.php | 29 ++-- models/Element/PermissionChecker.php | 27 ++-- models/Element/Service.php | 26 +++- models/Element/Tag/Dao.php | 51 ++++--- .../Element/Traits/ScheduledTasksDaoTrait.php | 16 ++- models/Element/Traits/VersionDaoTrait.php | 20 ++- models/Element/WorkflowState/Dao.php | 5 +- models/Site/Dao.php | 5 +- models/Translation/Dao.php | 27 +++- models/Translation/Listing/Dao.php | 20 +-- models/User/AbstractUser/Dao.php | 10 +- models/User/UserRole/Dao.php | 5 +- .../Adapter/DatabaseVersionStorageAdapter.php | 15 +- models/Version/Dao.php | 45 ++++-- 82 files changed, 1267 insertions(+), 662 deletions(-) diff --git a/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php b/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php index c783257a..91d413ad 100644 --- a/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php +++ b/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php @@ -61,7 +61,13 @@ public static function getComponents(): array { $db = Db::get(); - return $db->fetchFirstColumn('SELECT component FROM ' . self::TABLE_NAME . ' WHERE NOT ISNULL(component) GROUP BY component;'); + return $db->createQueryBuilder() + ->select('component') + ->from(self::TABLE_NAME) + ->where('component IS NOT NULL') + ->groupBy('component') + ->executeQuery() + ->fetchFirstColumn(); } /** @@ -83,7 +89,13 @@ public static function getPriorities(): array $db = Db::get(); - $priorityNumbers = $db->fetchFirstColumn('SELECT priority FROM ' . self::TABLE_NAME . ' WHERE NOT ISNULL(priority) GROUP BY priority;'); + $priorityNumbers = $db->createQueryBuilder() + ->select('priority') + ->from(self::TABLE_NAME) + ->where('priority IS NOT NULL') + ->groupBy('priority') + ->executeQuery() + ->fetchFirstColumn(); foreach ($priorityNumbers as $priorityNumber) { $priorities[$priorityNumber] = $priorityNames[$priorityNumber]; } diff --git a/bundles/ApplicationLoggerBundle/src/Maintenance/LogArchiveTask.php b/bundles/ApplicationLoggerBundle/src/Maintenance/LogArchiveTask.php index 872cfb20..c6c4fa17 100644 --- a/bundles/ApplicationLoggerBundle/src/Maintenance/LogArchiveTask.php +++ b/bundles/ApplicationLoggerBundle/src/Maintenance/LogArchiveTask.php @@ -41,42 +41,63 @@ public function __construct( public function execute(): void { - $db = $this->db; $storage = Storage::get('application_log'); $date = new DateTime('now'); - $tablename = ApplicationLoggerDb::TABLE_ARCHIVE_PREFIX.'_'.$date->format('Y').'_'.$date->format('m'); + $archiveTable = sprintf('%s_%s', ApplicationLoggerDb::TABLE_ARCHIVE_PREFIX, $date->format('Y_m')); if (!empty($this->config['applicationlog']['archive_alternative_database'])) { - $tablename = $db->quoteIdentifier($this->config['applicationlog']['archive_alternative_database']).'.'.$tablename; + $archiveTable = sprintf( + '%s.%s', + $this->db->quoteIdentifier($this->config['applicationlog']['archive_alternative_database']), + $archiveTable + ); } - $archive_threshold = (int) ($this->config['applicationlog']['archive_treshold'] ?? 30); + $archiveThreshold = (int) ($this->config['applicationlog']['archive_treshold'] ?? 30); + $sourceTable = ApplicationLoggerDb::TABLE_NAME; + $cutoff = (new DateTimeImmutable())->modify(sprintf('-%d days', $archiveThreshold))->format('Y-m-d H:i:s'); + $whereParams = [$cutoff]; - $timestamp = time(); - $sql = 'SELECT %s FROM '.ApplicationLoggerDb::TABLE_NAME.' WHERE `timestamp` < DATE_SUB(FROM_UNIXTIME('.$timestamp.'), INTERVAL '.$archive_threshold.' DAY)'; - - if ($db->fetchOne(sprintf($sql, 'COUNT(*)')) > 0) { - $db->executeQuery('CREATE TABLE IF NOT EXISTS '.$tablename." ( - id BIGINT(20) NOT NULL, - `pid` INT(11) NULL DEFAULT NULL, - `timestamp` DATETIME NOT NULL, - message VARCHAR(1024), - `priority` ENUM('emergency','alert','critical','error','warning','notice','info','debug') DEFAULT NULL, - fileobject VARCHAR(1024), - info VARCHAR(1024), - component VARCHAR(255), - source VARCHAR(255) NULL DEFAULT NULL, - relatedobject BIGINT(20), - relatedobjecttype ENUM('object', 'document', 'asset'), - maintenanceChecked TINYINT(1) - ) ENGINE = ARCHIVE ROW_FORMAT = DEFAULT;"); - - $db->executeQuery('INSERT INTO '.$tablename.' '.sprintf($sql, '*')); + $count = $this->db->fetchOne( + sprintf('SELECT COUNT(*) FROM %s WHERE `timestamp` < ?', $sourceTable), + $whereParams + ); - $this->logger->debug('Deleting referenced FileObjects of application_logs which are older than '.$archive_threshold.' days'); + if ($count > 0) { + $this->db->executeStatement(sprintf( + "CREATE TABLE IF NOT EXISTS %s ( + id BIGINT(20) NOT NULL, + `pid` INT(11) NULL DEFAULT NULL, + `timestamp` DATETIME NOT NULL, + message VARCHAR(1024), + `priority` ENUM('emergency','alert','critical','error','warning','notice','info','debug') DEFAULT NULL, + fileobject VARCHAR(1024), + info VARCHAR(1024), + component VARCHAR(255), + source VARCHAR(255) NULL DEFAULT NULL, + relatedobject BIGINT(20), + relatedobjecttype ENUM('object', 'document', 'asset'), + maintenanceChecked TINYINT(1) + ) ENGINE = ARCHIVE ROW_FORMAT = DEFAULT", + $archiveTable + )); + + $this->db->executeStatement( + sprintf('INSERT INTO %s SELECT * FROM %s WHERE `timestamp` < ?', $archiveTable, $sourceTable), + $whereParams + ); + + $this->logger->debug(sprintf( + 'Deleting referenced FileObjects of application_logs which are older than %d days', + $archiveThreshold + )); + + $fileObjectPaths = $this->db->fetchAllAssociative( + sprintf('SELECT fileobject FROM %s WHERE `timestamp` < ?', $sourceTable), + $whereParams + ); - $fileObjectPaths = $db->fetchAllAssociative(sprintf($sql, 'fileobject')); foreach ($fileObjectPaths as $objectPath) { $filePath = $objectPath['fileobject']; if ($filePath !== null && $storage->fileExists($filePath)) { @@ -84,27 +105,34 @@ public function execute(): void } } - $db->executeQuery('DELETE FROM '.ApplicationLoggerDb::TABLE_NAME.' WHERE `timestamp` < DATE_SUB(FROM_UNIXTIME('.$timestamp.'), INTERVAL '.$archive_threshold.' DAY);'); + $this->db->executeStatement( + sprintf('DELETE FROM %s WHERE `timestamp` < ?', $sourceTable), + $whereParams + ); } - $archiveTables = $db->fetchFirstColumn( + $archiveTables = $this->db->fetchFirstColumn( 'SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND table_name LIKE ?', [ - $this->config['applicationlog']['archive_alternative_database'] ?: $db->getDatabase(), - ApplicationLoggerDb::TABLE_ARCHIVE_PREFIX.'_%', + $this->config['applicationlog']['archive_alternative_database'] ?: $this->db->getDatabase(), + ApplicationLoggerDb::TABLE_ARCHIVE_PREFIX . '_%', ] ); - foreach ($archiveTables as $archiveTable) { - if (preg_match('/^'.ApplicationLoggerDb::TABLE_ARCHIVE_PREFIX.'_(\d{4})_(\d{2})$/', $archiveTable, $matches)) { - $deleteArchiveLogDate = Carbon::createFromFormat('Y/m', $matches[1].'/'.$matches[2]); - if ($deleteArchiveLogDate->add(new DateInterval('P'.($this->config['applicationlog']['delete_archive_threshold'] ?? 6).'M')) < new DateTimeImmutable()) { - $db->executeStatement('DROP TABLE IF EXISTS `'.($this->config['applicationlog']['archive_alternative_database'] ?: $db->getDatabase()).'`.'.$archiveTable); - $folderName = $deleteArchiveLogDate->format('Y/m'); + foreach ($archiveTables as $archiveTableName) { + if (preg_match('/^' . ApplicationLoggerDb::TABLE_ARCHIVE_PREFIX . '_(\d{4})_(\d{2})$/', $archiveTableName, $matches)) { + $deleteArchiveLogDate = Carbon::createFromFormat('Y/m', $matches[1] . '/' . $matches[2]); + if ($deleteArchiveLogDate->add(new DateInterval('P' . ($this->config['applicationlog']['delete_archive_threshold'] ?? 6) . 'M')) < new DateTimeImmutable()) { + $this->db->executeStatement(sprintf( + 'DROP TABLE IF EXISTS %s.%s', + $this->db->quoteIdentifier($this->config['applicationlog']['archive_alternative_database'] ?: $this->db->getDatabase()), + $this->db->quoteIdentifier($archiveTableName) + )); + $folderName = $deleteArchiveLogDate->format('Y/m'); if ($storage->directoryExists($folderName)) { $storage->deleteDirectory($folderName); } diff --git a/bundles/ApplicationLoggerBundle/src/Maintenance/LogMailMaintenanceTask.php b/bundles/ApplicationLoggerBundle/src/Maintenance/LogMailMaintenanceTask.php index 823e9a16..ec04e6e4 100644 --- a/bundles/ApplicationLoggerBundle/src/Maintenance/LogMailMaintenanceTask.php +++ b/bundles/ApplicationLoggerBundle/src/Maintenance/LogMailMaintenanceTask.php @@ -16,6 +16,7 @@ namespace OpenDxp\Bundle\ApplicationLoggerBundle\Maintenance; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use OpenDxp\Bundle\ApplicationLoggerBundle\Handler\ApplicationLoggerDb; use OpenDxp\Config; @@ -43,7 +44,7 @@ public function execute(): void // getting the enums from priority $priorityColumnDefinition = $this->db->fetchAllAssociative( - 'SHOW COLUMNS FROM ' .ApplicationLoggerDb::TABLE_NAME. " LIKE 'priority'" + sprintf("SHOW COLUMNS FROM %s LIKE 'priority'", ApplicationLoggerDb::TABLE_NAME) ); // type is the actual enum values @@ -59,15 +60,15 @@ public function execute(): void $logLevels[] = $enumValue[$i]; } - $query = 'SELECT * FROM ' - . ApplicationLoggerDb::TABLE_NAME - . ' WHERE maintenanceChecked IS NULL ' - . 'AND priority IN(' - . implode(',', $logLevels) - . ') ' - . 'ORDER BY id DESC'; - - $rows = $this->db->fetchAllAssociative($query); + $rows = $this->db->createQueryBuilder() + ->select('*') + ->from(ApplicationLoggerDb::TABLE_NAME) + ->where('maintenanceChecked IS NULL') + ->andWhere('priority IN (:levels)') + ->setParameter('levels', $logLevels, ArrayParameterType::STRING) + ->orderBy('id', 'DESC') + ->executeQuery() + ->fetchAllAssociative(); $limit = 100; $rowsProcessed = 0; diff --git a/bundles/CoreBundle/src/Command/DeleteUnusedLocaleDataCommand.php b/bundles/CoreBundle/src/Command/DeleteUnusedLocaleDataCommand.php index 9745fdde..e58e7796 100644 --- a/bundles/CoreBundle/src/Command/DeleteUnusedLocaleDataCommand.php +++ b/bundles/CoreBundle/src/Command/DeleteUnusedLocaleDataCommand.php @@ -16,6 +16,7 @@ namespace OpenDxp\Bundle\CoreBundle\Command; +use Doctrine\DBAL\ArrayParameterType; use OpenDxp\Console\AbstractCommand; use OpenDxp\Console\Traits\DryRun; use OpenDxp\Db; @@ -59,11 +60,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $skipLocales = explode(',', $input->getOption('skip-locales')); } - $languageList = []; $validLanguages = Tool::getValidLanguages(); - foreach ($validLanguages as $language) { - $languageList[] = $db->quote($language); - } $tables = $db->fetchAllAssociative("SHOW TABLES LIKE 'object\_localized\_data\_%'"); @@ -72,20 +69,24 @@ protected function execute(InputInterface $input, OutputInterface $output): int $table = current($table); $classId = str_replace('object_localized_data_', '', $table); - $result = $db->fetchAllAssociative('SELECT DISTINCT `language` FROM ' . $table . ' WHERE `language` NOT IN(' . implode(',', $languageList) .')'); + $result = $db->fetchAllAssociative( + sprintf('SELECT DISTINCT `language` FROM %s WHERE `language` NOT IN(?)', $table), + [$validLanguages], + [ArrayParameterType::STRING] + ); $result = ($result ?: []); //delete data from object_localized_data_classID tables foreach ($result as $res) { $language = $res['language']; if (!ArrayHelper::inArrayCaseInsensitive($language, $skipLocales) && !ArrayHelper::inArrayCaseInsensitive($language, $validLanguages)) { - $sqlDeleteData = 'Delete FROM object_localized_data_' . $classId . ' WHERE `language` = ' . $db->quote($language); $printLine = true; + $deleteStmt = sprintf('DELETE FROM object_localized_data_%s WHERE `language` = ?', $classId); if (!$this->isDryRun()) { - $output->writeln($sqlDeleteData); - $db->executeQuery($sqlDeleteData); + $output->writeln(sprintf('DELETE FROM object_localized_data_%s WHERE `language` = %s', $classId, $db->quote($language))); + $db->executeStatement($deleteStmt, [$language]); } else { - $output->writeln($this->dryRunMessage($sqlDeleteData)); + $output->writeln($this->dryRunMessage(sprintf('DELETE FROM object_localized_data_%s WHERE `language` = %s', $classId, $db->quote($language)))); } } } @@ -97,7 +98,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $existingLanguage = str_replace('object_localized_'.$classId.'_', '', $localizedView); if (!ArrayHelper::inArrayCaseInsensitive($existingLanguage, $validLanguages)) { - $sqlDropView = 'DROP VIEW IF EXISTS object_localized_' . $classId . '_' .$existingLanguage; + $sqlDropView = sprintf('DROP VIEW IF EXISTS object_localized_%s_%s', $classId, $existingLanguage); $printLine = true; if (!$this->isDryRun()) { @@ -116,7 +117,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $existingLanguage = str_replace('object_localized_query_'.$classId.'_', '', $localizedTable); if (!ArrayHelper::inArrayCaseInsensitive($existingLanguage, $validLanguages)) { - $sqlDropTable = 'DROP TABLE IF EXISTS object_localized_query_' . $classId . '_' .$existingLanguage; + $sqlDropTable = sprintf('DROP TABLE IF EXISTS object_localized_query_%s_%s', $classId, $existingLanguage); $printLine = true; if (!$this->isDryRun()) { diff --git a/bundles/CoreBundle/src/Command/Document/CleanupCommand.php b/bundles/CoreBundle/src/Command/Document/CleanupCommand.php index e9af73ec..4869c562 100644 --- a/bundles/CoreBundle/src/Command/Document/CleanupCommand.php +++ b/bundles/CoreBundle/src/Command/Document/CleanupCommand.php @@ -89,7 +89,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $tableName = 'documents_' . $filteredDocumentType; try { - $db->executeQuery('DROP TABLE IF EXISTS ' . $tableName); + $db->executeQuery(sprintf('DROP TABLE IF EXISTS %s', $tableName)); } catch (Exception $ex) { $output->writeln(sprintf('Could not drop table %s: %s', $tableName, $ex)); } diff --git a/bundles/CoreBundle/src/Command/MysqlToolsCommand.php b/bundles/CoreBundle/src/Command/MysqlToolsCommand.php index ccc0daff..d3c4c607 100644 --- a/bundles/CoreBundle/src/Command/MysqlToolsCommand.php +++ b/bundles/CoreBundle/src/Command/MysqlToolsCommand.php @@ -55,7 +55,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $db = \OpenDxp\Db::get(); - if ($input->getOption('mode') == 'optimize') { + if ($input->getOption('mode') === 'optimize') { $tables = $db->fetchAllAssociative('SHOW TABLES'); foreach ($tables as $table) { @@ -63,12 +63,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { Logger::debug('Running: OPTIMIZE TABLE ' . $t); - $db->executeQuery('OPTIMIZE TABLE ' . $t); + $db->executeQuery(sprintf('OPTIMIZE TABLE %s', $t)); } catch (Exception $e) { Logger::error((string) $e); } } - } elseif ($input->getOption('mode') == 'warmup') { + } elseif ($input->getOption('mode') === 'warmup') { $tables = $db->fetchAllAssociative('SHOW TABLES'); foreach ($tables as $table) { @@ -76,7 +76,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { Logger::debug("Running: SELECT COUNT(*) FROM $t"); - $res = $db->fetchOne("SELECT COUNT(*) FROM $t"); + $res = $db->fetchOne(sprintf('SELECT COUNT(*) FROM %s', $t)); Logger::debug('Result: ' . $res); } catch (Exception $e) { Logger::error((string) $e); diff --git a/bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php b/bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php index 71219699..7ff4f21d 100644 --- a/bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php +++ b/bundles/CustomReportsBundle/src/Tool/Adapter/Sql.php @@ -140,8 +140,8 @@ protected function getBaseQuery(array $filters, array $fields, bool $ignoreSelec $type = $filter['type']; $operator = $filter['operator']; $maxValue = null; - if ($type == 'date') { - if ($operator == 'eq') { + if ($type === 'date') { + if ($operator === 'eq') { $maxValue = strtotime($value . '+23 hours 59 minutes'); } $value = strtotime($value); @@ -162,7 +162,7 @@ protected function getBaseQuery(array $filters, array $fields, bool $ignoreSelec 'eq' => '=', ]; - if ($type == 'date' && $operator == 'eq') { + if ($type === 'date' && $operator === 'eq') { $condition[] = $db->quoteIdentifier($filter['property']) . ' BETWEEN ' . $db->quote($value) . ' AND ' . $db->quote($maxValue); break; @@ -182,12 +182,12 @@ protected function getBaseQuery(array $filters, array $fields, bool $ignoreSelec if (!preg_match('/(ALTER|CREATE|DROP|RENAME|TRUNCATE|UPDATE|DELETE) /i', $sql, $matches)) { $condition = implode(' AND ', $condition); - $total = 'SELECT COUNT(*) FROM (' . $sql . ') AS somerandxyz WHERE ' . $condition; + $total = sprintf('SELECT COUNT(*) FROM (%s) AS somerandxyz WHERE %s', $sql, $condition); if ($fields && !$extractAllFields) { - $data = 'SELECT `' . implode('`,`', $fields) . '` FROM (' . $sql . ') AS somerandxyz WHERE ' . $condition; + $data = sprintf('SELECT `%s` FROM (%s) AS somerandxyz WHERE %s', implode('`,`', $fields), $sql, $condition); } else { - $data = 'SELECT * FROM (' . $sql . ') AS somerandxyz WHERE ' . $condition; + $data = sprintf('SELECT * FROM (%s) AS somerandxyz WHERE %s', $sql, $condition); } } else { return null; diff --git a/bundles/GenericExecutionEngineBundle/src/Installer.php b/bundles/GenericExecutionEngineBundle/src/Installer.php index 6b6fd8e1..9004da38 100644 --- a/bundles/GenericExecutionEngineBundle/src/Installer.php +++ b/bundles/GenericExecutionEngineBundle/src/Installer.php @@ -191,7 +191,7 @@ private function installLogTable(Schema $schema): void private function removeJobRunTable(Schema $schema): void { if ($schema->hasTable(TableConstants::JOB_RUN_TABLE)) { - $this->db->executeStatement('DROP TABLE ' . TableConstants::JOB_RUN_TABLE); + $this->db->executeStatement(sprintf('DROP TABLE %s', TableConstants::JOB_RUN_TABLE)); } } @@ -201,7 +201,7 @@ private function removeJobRunTable(Schema $schema): void private function removeLogTable(Schema $schema): void { if ($schema->hasTable(TableConstants::ERROR_LOG_TABLE)) { - $this->db->executeStatement('DROP TABLE ' . TableConstants::ERROR_LOG_TABLE); + $this->db->executeStatement(sprintf('DROP TABLE %s', TableConstants::ERROR_LOG_TABLE)); } } diff --git a/bundles/GenericExecutionEngineBundle/src/Repository/JobRunRepository.php b/bundles/GenericExecutionEngineBundle/src/Repository/JobRunRepository.php index 3832ef08..a5ff1ba5 100644 --- a/bundles/GenericExecutionEngineBundle/src/Repository/JobRunRepository.php +++ b/bundles/GenericExecutionEngineBundle/src/Repository/JobRunRepository.php @@ -116,9 +116,10 @@ public function updateLog(JobRun $jobRun, string $message): void { $this->db->executeStatement( - 'UPDATE ' . - TableConstants::JOB_RUN_TABLE . - ' SET log = IF(ISNULL(log),:message,CONCAT(log, "\n", :message)) WHERE id = :id', + sprintf( + 'UPDATE %s SET log = IF(ISNULL(log),:message,CONCAT(log, "\n", :message)) WHERE id = :id', + TableConstants::JOB_RUN_TABLE + ), [ 'id' => $jobRun->getId(), 'message' => (new DateTimeImmutable())->format('c') . ': ' . trim($message), diff --git a/bundles/SeoBundle/src/Controller/MiscController.php b/bundles/SeoBundle/src/Controller/MiscController.php index b8058123..514876cc 100644 --- a/bundles/SeoBundle/src/Controller/MiscController.php +++ b/bundles/SeoBundle/src/Controller/MiscController.php @@ -54,19 +54,30 @@ public function httpErrorLogAction(Request $request): JsonResponse $dir = 'DESC'; } - $condition = ''; + $qb = $db->createQueryBuilder() + ->select('code', 'uri', '`count`', 'date') + ->from('http_error_log') + ->orderBy($sort, $dir) + ->setFirstResult($offset) + ->setMaxResults($limit); + if ($filter) { - $filter = $db->quote('%' . $filter . '%'); + $qb->where('uri LIKE :filter OR code LIKE :filter OR parametersGet LIKE :filter') + ->setParameter('filter', '%' . $filter . '%'); + } - $conditionParts = []; - foreach (['uri', 'code', 'parametersGet'] as $field) { - $conditionParts[] = $field . ' LIKE ' . $filter; - } - $condition = ' WHERE ' . implode(' OR ', $conditionParts); + $logs = $qb->executeQuery()->fetchAllAssociative(); + + $countQb = $db->createQueryBuilder() + ->select('COUNT(*)') + ->from('http_error_log'); + + if ($filter) { + $countQb->where('uri LIKE :filter OR code LIKE :filter OR parametersGet LIKE :filter') + ->setParameter('filter', '%' . $filter . '%'); } - $logs = $db->fetchAllAssociative('SELECT code,uri,`count`,date FROM http_error_log ' . $condition . ' ORDER BY ' . $sort . ' ' . $dir . ' LIMIT ' . $offset . ',' . $limit); - $total = $db->fetchOne('SELECT count(*) FROM http_error_log ' . $condition); + $total = $countQb->executeQuery()->fetchOne(); return $this->jsonResponse([ 'items' => $logs, diff --git a/bundles/SeoBundle/src/EventListener/ResponseExceptionListener.php b/bundles/SeoBundle/src/EventListener/ResponseExceptionListener.php index c9c39ba7..2124bbf2 100644 --- a/bundles/SeoBundle/src/EventListener/ResponseExceptionListener.php +++ b/bundles/SeoBundle/src/EventListener/ResponseExceptionListener.php @@ -85,7 +85,10 @@ protected function logToHttpErrorLog(Request $request, int $statusCode): void $uri = $request->getUri(); $exists = $this->db->fetchOne('SELECT date FROM http_error_log WHERE uri = ?', [$uri]); if ($exists) { - $this->db->executeQuery('UPDATE http_error_log SET `count` = `count` + 1, date = ? WHERE uri = ?', [time(), $uri]); + $this->db->executeStatement( + 'UPDATE http_error_log SET `count` = `count` + 1, date = ? WHERE uri = ?', + [time(), $uri] + ); } else { $this->db->insert('http_error_log', [ 'uri' => $uri, diff --git a/bundles/SimpleBackendSearchBundle/src/DataProvider/GDPR/Assets.php b/bundles/SimpleBackendSearchBundle/src/DataProvider/GDPR/Assets.php index 2ba4152b..18ceb1a0 100644 --- a/bundles/SimpleBackendSearchBundle/src/DataProvider/GDPR/Assets.php +++ b/bundles/SimpleBackendSearchBundle/src/DataProvider/GDPR/Assets.php @@ -56,21 +56,18 @@ public function searchData(int $id, string $firstname, string $lastname, string $conditionParts[] = '( MATCH (`data`,`properties`) AGAINST ("' . $db->quote($queryString) . '" IN BOOLEAN MODE) )'; } - $db = Db::get(); + $conditionParams = []; $typesPart = ''; if ($this->config['types']) { - $typesList = []; - foreach ($this->config['types'] as $type) { - $typesList[] = $db->quote($type); - } - $typesPart = ' AND `type` IN (' . implode(',', $typesList) . ')'; + $typesPart = ' AND `type` IN (?)'; + $conditionParams[] = array_values($this->config['types']); } $conditionParts[] = '( maintype = "asset" ' . $typesPart . ')'; $condition = implode(' AND ', $conditionParts); - $searcherList->setCondition($condition); + $searcherList->setCondition($condition, $conditionParams); $searcherList->setOffset($offset); $searcherList->setLimit($limit); diff --git a/bundles/SimpleBackendSearchBundle/src/DataProvider/GDPR/DataObjects.php b/bundles/SimpleBackendSearchBundle/src/DataProvider/GDPR/DataObjects.php index 831023ac..b78c664b 100644 --- a/bundles/SimpleBackendSearchBundle/src/DataProvider/GDPR/DataObjects.php +++ b/bundles/SimpleBackendSearchBundle/src/DataProvider/GDPR/DataObjects.php @@ -39,6 +39,7 @@ public function searchData(int $id, string $firstname, string $lastname, string $searcherList = new Data\Listing(); $conditionParts = []; + $conditionParams = []; $db = \OpenDxp\Db::get(); //id search @@ -68,15 +69,12 @@ public function searchData(int $id, string $firstname, string $lastname, string } if ($classnames) { - $conditionClassnameParts = []; - foreach ($classnames as $classname) { - $conditionClassnameParts[] = $db->quote($classname); - } - $conditionParts[] = '( subtype IN (' . implode(',', $conditionClassnameParts) . ') )'; + $conditionParts[] = '( subtype IN (?) )'; + $conditionParams[] = $classnames; } $condition = implode(' AND ', $conditionParts); - $searcherList->setCondition($condition); + $searcherList->setCondition($condition, $conditionParams); $searcherList->setOffset($offset); $searcherList->setLimit($limit); diff --git a/bundles/SimpleBackendSearchBundle/src/Model/Search/Backend/Data/Dao.php b/bundles/SimpleBackendSearchBundle/src/Model/Search/Backend/Data/Dao.php index 9ddccc36..8f76a866 100644 --- a/bundles/SimpleBackendSearchBundle/src/Model/Search/Backend/Data/Dao.php +++ b/bundles/SimpleBackendSearchBundle/src/Model/Search/Backend/Data/Dao.php @@ -56,18 +56,23 @@ public function getForElement(Model\Element\ElementInterface $element): void public function save(): void { - $oldFullPath = $this->db->fetchOne('SELECT fullpath FROM search_backend_data WHERE id = :id and maintype = :type FOR UPDATE', [ + $oldFullPath = $this->db->fetchOne('SELECT fullpath FROM search_backend_data WHERE id = :id AND maintype = :type FOR UPDATE', [ 'id' => $this->model->getId()->getId(), 'type' => $this->model->getId()->getType(), ]); if ($oldFullPath && $oldFullPath !== $this->model->getFullPath()) { - $this->db->executeQuery('UPDATE search_backend_data - SET fullpath = replace(fullpath,' . $this->db->quote($oldFullPath . '/') . ',' . $this->db->quote($this->model->getFullPath() . '/') . ') - WHERE fullpath LIKE ' . $this->db->quote(Helper::escapeLike($oldFullPath) . '/%') . ' AND maintype = :type', + $this->db->executeStatement( + 'UPDATE search_backend_data + SET fullpath = REPLACE(fullpath, ?, ?) + WHERE fullpath LIKE ? AND maintype = ?', [ - 'type' => $this->model->getId()->getType(), - ]); + $oldFullPath . '/', + $this->model->getFullPath() . '/', + Helper::escapeLike($oldFullPath) . '/%', + $this->model->getId()->getType(), + ] + ); } $data = [ diff --git a/lib/Console/Application.php b/lib/Console/Application.php index 0c9fea27..956db047 100644 --- a/lib/Console/Application.php +++ b/lib/Console/Application.php @@ -81,7 +81,7 @@ public function __construct(KernelInterface $kernel) $db = OpenDxp\Db::get(); $result = $db->fetchAssociative("SHOW VARIABLES LIKE 'wait_timeout'"); if ($result['Value'] < $timeLimit) { - $db->executeQuery('SET SESSION wait_timeout = ' . $timeLimit); + $db->executeQuery(sprintf('SET SESSION wait_timeout = %d', $timeLimit)); } } diff --git a/lib/Db/Helper.php b/lib/Db/Helper.php index 4c42900e..79afe2f1 100644 --- a/lib/Db/Helper.php +++ b/lib/Db/Helper.php @@ -16,6 +16,7 @@ namespace OpenDxp\Db; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Driver\Result; use Doctrine\DBAL\Types\Type; @@ -75,8 +76,11 @@ public static function selectAndDeleteWhere(Connection $db, string $table, strin if ($idsForDeletion !== []) { $chunks = array_chunk($idsForDeletion, 1000); foreach ($chunks as $chunk) { - $idString = implode(',', array_map($db->quote(...), $chunk)); - $db->executeStatement('DELETE FROM ' . $table . ' WHERE ' . $idColumn . ' IN (' . $idString . ')'); + $db->executeStatement( + 'DELETE FROM ' . $table . ' WHERE ' . $idColumn . ' IN (?)', + [$chunk], + [ArrayParameterType::INTEGER] + ); } } } diff --git a/lib/Maintenance/Tasks/CleanupClassificationstoreTablesTask.php b/lib/Maintenance/Tasks/CleanupClassificationstoreTablesTask.php index f7a81ab3..4a34e3cc 100644 --- a/lib/Maintenance/Tasks/CleanupClassificationstoreTablesTask.php +++ b/lib/Maintenance/Tasks/CleanupClassificationstoreTablesTask.php @@ -36,7 +36,7 @@ public function execute(): void $tableTypes = ['object_classificationstore_data', 'object_classificationstore_groups']; foreach ($tableTypes as $tableType) { $prefix = $tableType . '_'; - $tableNames = $db->fetchAllAssociative("SHOW TABLES LIKE '" . $prefix . "%'"); + $tableNames = $db->fetchAllAssociative(sprintf("SHOW TABLES LIKE '%s%%'", $prefix)); foreach ($tableNames as $tableName) { $tableName = current($tableName); @@ -49,8 +49,12 @@ public function execute(): void continue; } - $fieldsQuery = 'SELECT fieldname FROM ' . $tableName . ' GROUP BY fieldname'; - $fieldNames = $db->fetchFirstColumn($fieldsQuery); + $fieldNames = $db->createQueryBuilder() + ->select('fieldname') + ->from($tableName) + ->groupBy('fieldname') + ->executeQuery() + ->fetchFirstColumn(); foreach ($fieldNames as $fieldName) { $fieldDef = $classDefinition->getFieldDefinition($fieldName); diff --git a/lib/Maintenance/Tasks/DataObject/CleanupBrickTablesTaskHelper.php b/lib/Maintenance/Tasks/DataObject/CleanupBrickTablesTaskHelper.php index 514bba80..1494dd72 100644 --- a/lib/Maintenance/Tasks/DataObject/CleanupBrickTablesTaskHelper.php +++ b/lib/Maintenance/Tasks/DataObject/CleanupBrickTablesTaskHelper.php @@ -46,7 +46,7 @@ public function cleanupCollectionTable(): void $tableTypes = ['store', 'query', 'localized']; foreach ($tableTypes as $tableType) { $prefix = 'object_brick_' . $tableType . '_'; - $tableNames = $this->db->fetchAllAssociative("SHOW TABLES LIKE '" . $prefix . "%'"); + $tableNames = $this->db->fetchAllAssociative(sprintf("SHOW TABLES LIKE '%s%%'", $prefix)); foreach ($tableNames as $tableName) { $tableName = current($tableName); diff --git a/lib/Maintenance/Tasks/DataObject/CleanupFieldcollectionTablesTaskHelper.php b/lib/Maintenance/Tasks/DataObject/CleanupFieldcollectionTablesTaskHelper.php index b5edf399..5ed698c6 100644 --- a/lib/Maintenance/Tasks/DataObject/CleanupFieldcollectionTablesTaskHelper.php +++ b/lib/Maintenance/Tasks/DataObject/CleanupFieldcollectionTablesTaskHelper.php @@ -53,7 +53,7 @@ public function cleanupCollectionTable(): void foreach ($tasks as $task) { $prefix = $task['prefix']; $pattern = $task['pattern']; - $tableNames = $this->db->fetchAllAssociative("SHOW TABLES LIKE '" . $pattern . "'"); + $tableNames = $this->db->fetchAllAssociative(sprintf("SHOW TABLES LIKE '%s'", $pattern)); foreach ($tableNames as $tableName) { $tableName = current($tableName); diff --git a/lib/Maintenance/Tasks/DataObject/DataObjectTaskHelper.php b/lib/Maintenance/Tasks/DataObject/DataObjectTaskHelper.php index c26fa232..5e459e87 100644 --- a/lib/Maintenance/Tasks/DataObject/DataObjectTaskHelper.php +++ b/lib/Maintenance/Tasks/DataObject/DataObjectTaskHelper.php @@ -59,7 +59,7 @@ public function cleanupTable( return; } - $fieldsQuery = 'SELECT fieldname FROM ' . $tableName . ' GROUP BY fieldname'; + $fieldsQuery = sprintf('SELECT fieldname FROM %s GROUP BY fieldname', $tableName); $fieldNames = $this->db->fetchFirstColumn($fieldsQuery); foreach ($fieldNames as $fieldName) { diff --git a/lib/Maintenance/Tasks/DbCleanupBrokenViewsTask.php b/lib/Maintenance/Tasks/DbCleanupBrokenViewsTask.php index ee9f9424..9380c94d 100644 --- a/lib/Maintenance/Tasks/DbCleanupBrokenViewsTask.php +++ b/lib/Maintenance/Tasks/DbCleanupBrokenViewsTask.php @@ -40,13 +40,13 @@ public function execute(): void if ($type === 'VIEW') { try { - $createStatement = $this->db->fetchAssociative('SHOW FIELDS FROM '.$name); + $createStatement = $this->db->fetchAssociative(sprintf('SHOW FIELDS FROM %s', $name)); } catch (Exception $e) { if (str_contains($e->getMessage(), 'references invalid table')) { $this->logger->error('view '.$name.' seems to be a broken one, it will be removed'); $this->logger->error('error message was: '.$e->getMessage()); - $this->db->executeQuery('DROP VIEW '.$name); + $this->db->executeQuery(sprintf('DROP VIEW %s', $name)); } else { $this->logger->error((string) $e); } diff --git a/lib/Maintenance/Tasks/TmpStoreCleanupTask.php b/lib/Maintenance/Tasks/TmpStoreCleanupTask.php index 8227b4be..26178ece 100644 --- a/lib/Maintenance/Tasks/TmpStoreCleanupTask.php +++ b/lib/Maintenance/Tasks/TmpStoreCleanupTask.php @@ -30,6 +30,6 @@ public function __construct(private readonly Connection $db) public function execute(): void { - $this->db->executeQuery('DELETE FROM tmp_store WHERE `expiryDate` < :time', ['time' => time()]); + $this->db->executeStatement('DELETE FROM tmp_store WHERE `expiryDate` < :time', ['time' => time()]); } } diff --git a/lib/Model/Dao/AbstractDao.php b/lib/Model/Dao/AbstractDao.php index 0ce0de52..1f1dbaa2 100644 --- a/lib/Model/Dao/AbstractDao.php +++ b/lib/Model/Dao/AbstractDao.php @@ -72,7 +72,7 @@ public function getValidTableColumns(string $table, bool $cache = true, bool $pr if (!$allColumns || !$cache) { $columns = []; $primaryKeyColumns = []; - $data = $this->db->fetchAllAssociative('SHOW COLUMNS FROM ' . $table); + $data = $this->db->fetchAllAssociative(sprintf('SHOW COLUMNS FROM %s', $table)); foreach ($data as $d) { $fieldName = $d['Field']; $columns[] = $fieldName; diff --git a/lib/Model/DataObject/ClassDefinition/ClassDefinitionManager.php b/lib/Model/DataObject/ClassDefinition/ClassDefinitionManager.php index 8c3c76d2..b36d25e2 100644 --- a/lib/Model/DataObject/ClassDefinition/ClassDefinitionManager.php +++ b/lib/Model/DataObject/ClassDefinition/ClassDefinitionManager.php @@ -120,7 +120,7 @@ public function saveClass(ClassDefinitionInterface $class, bool $saveDefinitionF $definitionModificationDate = null; if ($classId = $class->getId()) { - $definitionModificationDate = $db->fetchOne('SELECT definitionModificationDate FROM classes WHERE id = ?;', [$classId]); + $definitionModificationDate = $db->fetchOne('SELECT definitionModificationDate FROM classes WHERE id = ?', [$classId]); } if (!$definitionModificationDate || $definitionModificationDate !== $class->getModificationDate()) { diff --git a/lib/Model/Listing/Dao/QueryBuilderHelperTrait.php b/lib/Model/Listing/Dao/QueryBuilderHelperTrait.php index ba297bb9..42daa887 100644 --- a/lib/Model/Listing/Dao/QueryBuilderHelperTrait.php +++ b/lib/Model/Listing/Dao/QueryBuilderHelperTrait.php @@ -137,7 +137,7 @@ protected function getTotalCountFromQueryBuilder(QueryBuilder $queryBuilder, str $queryBuilder->distinct(); } - $countSql = 'SELECT COUNT(*) FROM (' . $queryBuilder->getSQL() . ') count_subquery'; + $countSql = sprintf('SELECT COUNT(*) FROM (%s) count_subquery', $queryBuilder->getSQL()); return (int) $this->db->fetchOne( $countSql, diff --git a/lib/Tool/Requirements.php b/lib/Tool/Requirements.php index deab6503..2545a6c7 100644 --- a/lib/Tool/Requirements.php +++ b/lib/Tool/Requirements.php @@ -265,7 +265,7 @@ public static function checkMysql(Connection $db): array $queryCheck = true; try { - $db->executeQuery('DELETE FROM __opendxp_req_check'); + $db->executeStatement('DELETE FROM __opendxp_req_check'); } catch (Exception) { $queryCheck = false; } diff --git a/lib/Workflow/Notification/AbstractNotificationService.php b/lib/Workflow/Notification/AbstractNotificationService.php index ad6f9624..30c05cc4 100644 --- a/lib/Workflow/Notification/AbstractNotificationService.php +++ b/lib/Workflow/Notification/AbstractNotificationService.php @@ -16,7 +16,6 @@ namespace OpenDxp\Workflow\Notification; -use OpenDxp\Db; use OpenDxp\Model\Element\Note; use OpenDxp\Model\User; @@ -53,7 +52,7 @@ protected function getNotificationUsersByName(array $users, array $roles, bool $ if ($roles) { //get roles $roleList = new User\Role\Listing(); - $roleList->setCondition('name IN ('.implode(',', array_map([Db::get(), 'quote'], $roles)).')'); + $roleList->setCondition('name IN (?)', [$roles]); foreach ($roleList->load() as $role) { $userList = new User\Listing(); @@ -72,7 +71,7 @@ protected function getNotificationUsersByName(array $users, array $roles, bool $ if ($users) { //get users $userList = new User\Listing(); - $userList->setCondition('name IN ('.implode(',', array_map([Db::get(), 'quote'], $users)).') and active = 1'); + $userList->setCondition('name IN (?) and active = 1', [$users]); if (!$includeAllUsers) { $userList->addConditionParam('(email IS NOT NULL AND email != "")'); diff --git a/models/Asset/Dao.php b/models/Asset/Dao.php index 3f99ff5f..b156e12f 100644 --- a/models/Asset/Dao.php +++ b/models/Asset/Dao.php @@ -15,6 +15,8 @@ namespace OpenDxp\Model\Asset; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\ParameterType; use Exception; use OpenDxp; use OpenDxp\Db\Helper; @@ -48,9 +50,12 @@ class Dao extends Model\Element\Dao */ public function getById(int $id): void { - $data = $this->db->fetchAssociative("SELECT assets.*, tree_locks.locked FROM assets - LEFT JOIN tree_locks ON assets.id = tree_locks.id AND tree_locks.type = 'asset' - WHERE assets.id = ?", [$id]); + $data = $this->db->fetchAssociative( + 'SELECT assets.*, tree_locks.locked FROM assets + LEFT JOIN tree_locks ON assets.id = tree_locks.id AND tree_locks.type = "asset" + WHERE assets.id = ?', + [$id] + ); if ($data) { $data['hasMetaData'] = (bool)$data['hasMetaData']; @@ -202,22 +207,36 @@ public function updateWorkspaces(): void public function updateChildPaths(string $oldPath): array { //get assets to empty their cache - $assets = $this->db->fetchFirstColumn('SELECT id FROM assets WHERE `path` like ' . $this->db->quote(Helper::escapeLike($oldPath) . '%')); + $assets = $this->db->fetchFirstColumn( + 'SELECT id FROM assets WHERE `path` LIKE ?', + [Helper::escapeLike($oldPath) . '%'] + ); $userId = '0'; if ($user = \OpenDxp\Tool\Admin::getCurrentUser()) { $userId = $user->getId(); } + $newPath = $this->model->getRealFullPath(); + //update assets child paths // we don't update the modification date here, as this can have side-effects when there's an unpublished version for an element - $this->db->executeQuery('update assets set `path` = replace(`path`,' . $this->db->quote($oldPath . '/') . ',' . $this->db->quote($this->model->getRealFullPath() . '/') . "), userModification = '" . $userId . "' where `path` like " . $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';'); + $this->db->executeStatement( + 'UPDATE assets SET `path` = REPLACE(`path`, ?, ?), userModification = ? WHERE `path` LIKE ?', + [$oldPath . '/', $newPath . '/', $userId, Helper::escapeLike($oldPath) . '/%'] + ); //update assets child permission paths - $this->db->executeQuery('update users_workspaces_asset set cpath = replace(cpath,' . $this->db->quote($oldPath . '/') . ',' . $this->db->quote($this->model->getRealFullPath() . '/') . ') where cpath like ' . $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';'); + $this->db->executeStatement( + 'UPDATE users_workspaces_asset SET cpath = REPLACE(cpath, ?, ?) WHERE cpath LIKE ?', + [$oldPath . '/', $newPath . '/', Helper::escapeLike($oldPath) . '/%'] + ); //update assets child properties paths - $this->db->executeQuery('update properties set cpath = replace(cpath,' . $this->db->quote($oldPath . '/') . ',' . $this->db->quote($this->model->getRealFullPath() . '/') . ') where cpath like ' . $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';'); + $this->db->executeStatement( + 'UPDATE properties SET cpath = REPLACE(cpath, ?, ?) WHERE cpath LIKE ?', + [$oldPath . '/', $newPath . '/', Helper::escapeLike($oldPath) . '/%'] + ); return $assets; } @@ -234,11 +253,9 @@ public function getProperties(bool $onlyInherited = false): array // collect properties via parent - ids $parentIds = $this->getParentIds(); $propertiesRaw = $this->db->fetchAllAssociative( - 'SELECT * FROM properties WHERE - ( - (cid IN (' . implode(',', $parentIds) . ") AND inheritable = 1) OR cid = ? ) - AND ctype='asset'", - [$this->model->getId()] + 'SELECT * FROM properties WHERE ((cid IN (?) AND inheritable = 1) OR cid = ?) AND ctype="asset"', + [$parentIds, $this->model->getId()], + [ArrayParameterType::INTEGER, ParameterType::INTEGER] ); // because this should be faster than mysql @@ -312,7 +329,7 @@ public function getVersionCountForUpdate(): int $versionCount = (int) $this->db->fetchOne('SELECT versionCount FROM assets WHERE id = ? FOR UPDATE', [$this->model->getId()]); if (!$this->model instanceof Folder) { - $versionCount2 = (int) $this->db->fetchOne("SELECT MAX(versionCount) FROM versions WHERE cid = ? AND ctype = 'asset'", [$this->model->getId()]); + $versionCount2 = (int) $this->db->fetchOne('SELECT MAX(versionCount) FROM versions WHERE cid = ? AND ctype = "asset"', [$this->model->getId()]); $versionCount = max($versionCount, $versionCount2); } @@ -331,14 +348,14 @@ public function hasChildren(?User $user = null): bool $query = 'SELECT `a`.`id` FROM `assets` a WHERE parentId = ? '; if ($user && !$user->isAdmin()) { - $userIds = $user->getRoles(); + $userIds = array_map('intval', $user->getRoles()); $currentUserId = $user->getId(); $userIds[] = $currentUserId; $inheritedPermission = $this->isInheritingPermission('list', $userIds); $anyAllowedRowOrChildren = 'EXISTS(SELECT list FROM users_workspaces_asset uwa WHERE userId IN (' . implode(',', $userIds) . ') AND list=1 AND LOCATE(CONCAT(`path`,filename),cpath)=1 AND - NOT EXISTS(SELECT list FROM users_workspaces_asset WHERE userId =' . $currentUserId . ' AND list=0 AND cpath = uwa.cpath))'; + NOT EXISTS(SELECT list FROM users_workspaces_asset WHERE userId =' . (int) $currentUserId . ' AND list=0 AND cpath = uwa.cpath))'; $isDisallowedCurrentRow = 'EXISTS(SELECT list FROM users_workspaces_asset WHERE userId IN (' . implode(',', $userIds) . ') AND cid = id AND list=0)'; $query .= ' AND IF(' . $anyAllowedRowOrChildren . ',1,IF(' . $inheritedPermission . ', ' . $isDisallowedCurrentRow . ' = 0, 0)) = 1'; @@ -386,14 +403,14 @@ public function getChildAmount(?User $user = null): int $query = 'SELECT COUNT(*) AS count FROM assets WHERE parentId = ?'; if ($user && !$user->isAdmin()) { - $userIds = $user->getRoles(); + $userIds = array_map('intval', $user->getRoles()); $currentUserId = $user->getId(); $userIds[] = $currentUserId; $inheritedPermission = $this->isInheritingPermission('list', $userIds); $anyAllowedRowOrChildren = 'EXISTS(SELECT list FROM users_workspaces_asset uwa WHERE userId IN (' . implode(',', $userIds) . ') AND list=1 AND LOCATE(CONCAT(`path`,filename),cpath)=1 AND - NOT EXISTS(SELECT list FROM users_workspaces_asset WHERE userId =' . $currentUserId . ' AND list=0 AND cpath = uwa.cpath))'; + NOT EXISTS(SELECT list FROM users_workspaces_asset WHERE userId =' . (int) $currentUserId . ' AND list=0 AND cpath = uwa.cpath))'; $isDisallowedCurrentRow = 'EXISTS(SELECT list FROM users_workspaces_asset WHERE userId IN (' . implode(',', $userIds) . ') AND cid = id AND list=0)'; $query .= ' AND IF(' . $anyAllowedRowOrChildren . ',1,IF(' . $inheritedPermission . ', ' . $isDisallowedCurrentRow . ' = 0, 0)) = 1'; @@ -405,22 +422,35 @@ public function getChildAmount(?User $user = null): int public function isLocked(): bool { // check for an locked element below this element - $belowLocks = $this->db->fetchOne("SELECT tree_locks.id FROM tree_locks INNER JOIN assets ON tree_locks.id = assets.id WHERE assets.path LIKE ? AND tree_locks.type = 'asset' AND tree_locks.locked IS NOT NULL AND tree_locks.locked != '' LIMIT 1", [Helper::escapeLike($this->model->getRealFullPath()) . '/%']); + $belowLocks = $this->db->fetchOne( + 'SELECT tree_locks.id FROM tree_locks INNER JOIN assets ON tree_locks.id = assets.id WHERE assets.path LIKE ? AND tree_locks.type = "asset" AND tree_locks.locked IS NOT NULL AND tree_locks.locked != "" LIMIT 1', + [Helper::escapeLike($this->model->getRealFullPath()) . '/%'] + ); if ($belowLocks > 0) { return true; } $parentIds = $this->getParentIds(); - $inhertitedLocks = $this->db->fetchOne('SELECT id FROM tree_locks WHERE id IN (' . implode(',', $parentIds) . ") AND `type`='asset' AND locked = 'propagate' LIMIT 1"); + $inhertitedLocks = $this->db->fetchOne( + 'SELECT id FROM tree_locks WHERE id IN (?) AND `type`="asset" AND locked = "propagate" LIMIT 1', + [$parentIds], [ArrayParameterType::INTEGER] + ); return $inhertitedLocks > 0; } public function unlockPropagate(): array { - $lockIds = $this->db->fetchFirstColumn('SELECT id from assets WHERE `path` like ' . $this->db->quote(Helper::escapeLike($this->model->getRealFullPath()) . '/%') . ' OR id = ' . $this->model->getId()); - $this->db->executeQuery("DELETE FROM tree_locks WHERE `type` = 'asset' AND id IN (" . implode(',', $lockIds) . ')'); + $lockIds = $this->db->fetchFirstColumn( + 'SELECT id FROM assets WHERE `path` LIKE ? OR id = ?', + [Helper::escapeLike($this->model->getRealFullPath()) . '/%', $this->model->getId()] + ); + $this->db->executeStatement( + 'DELETE FROM tree_locks WHERE `type` = "asset" AND id IN (?)', + [$lockIds], + [ArrayParameterType::INTEGER] + ); return $lockIds; } @@ -453,7 +483,11 @@ public function isAllowed(string $type, User $user): bool $userIds[] = $user->getId(); try { - $permissionsParent = $this->db->fetchOne('SELECT ' . $this->db->quoteIdentifier($type) . ' FROM users_workspaces_asset WHERE cid IN (' . implode(',', $parentIds) . ') AND userId IN (' . implode(',', $userIds) . ') ORDER BY LENGTH(cpath) DESC, FIELD(userId, ' . $user->getId() . ') DESC, ' . $this->db->quoteIdentifier($type) . ' DESC LIMIT 1'); + $permissionsParent = $this->db->fetchOne( + 'SELECT ' . $this->db->quoteIdentifier($type) . ' FROM users_workspaces_asset WHERE cid IN (?) AND userId IN (?) ORDER BY LENGTH(cpath) DESC, FIELD(userId, ?) DESC, ' . $this->db->quoteIdentifier($type) . ' DESC LIMIT 1', + [$parentIds, $userIds, $user->getId()], + [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER] + ); if ($permissionsParent) { return true; @@ -467,7 +501,11 @@ public function isAllowed(string $type, User $user): bool $path = '/'; } - $permissionsChildren = $this->db->fetchOne('SELECT list FROM users_workspaces_asset WHERE cpath LIKE ? AND userId IN (' . implode(',', $userIds) . ') AND list = 1 LIMIT 1', [Helper::escapeLike($path) . '%']); + $permissionsChildren = $this->db->fetchOne( + 'SELECT list FROM users_workspaces_asset WHERE cpath LIKE ? AND userId IN (?) AND list = 1 LIMIT 1', + [Helper::escapeLike($path) . '%', $userIds], + [ParameterType::STRING, ArrayParameterType::INTEGER] + ); if ($permissionsChildren) { return true; } diff --git a/models/Asset/Folder.php b/models/Asset/Folder.php index 5720e867..d039735c 100644 --- a/models/Asset/Folder.php +++ b/models/Asset/Folder.php @@ -107,7 +107,14 @@ public function getPreviewImage(bool $force = false) ]; if ($storage->fileExists($cacheFilePath)) { - $lastUpdate = $db->fetchOne('SELECT MAX(modificationDate) FROM assets WHERE ' . $condition . ' ORDER BY filename ASC LIMIT ' . $limit, $conditionParams); + $lastUpdate = $db->createQueryBuilder() + ->select('MAX(modificationDate)') + ->from('assets') + ->where($condition) + ->setParameters($conditionParams) + ->setMaxResults($limit) + ->executeQuery() + ->fetchOne(); if ($lastUpdate < $storage->lastModified($cacheFilePath)) { return $storage->readStream($cacheFilePath); } diff --git a/models/DataObject/AbstractObject.php b/models/DataObject/AbstractObject.php index e78d17b1..c06a71ad 100644 --- a/models/DataObject/AbstractObject.php +++ b/models/DataObject/AbstractObject.php @@ -1000,7 +1000,7 @@ public function __getRawRelationData(): array { if ($this->__rawRelationData === null) { $db = Db::get(); - $this->__rawRelationData = $db->fetchAllAssociative('SELECT * FROM object_relations_' . $this->getClassId() . ' WHERE src_id = ?', [$this->getId()]); + $this->__rawRelationData = $db->fetchAllAssociative(sprintf('SELECT * FROM object_relations_%s WHERE src_id = ?', $this->getClassId()), [$this->getId()]); } return $this->__rawRelationData; diff --git a/models/DataObject/AbstractObject/Dao.php b/models/DataObject/AbstractObject/Dao.php index 56ac21b5..150ed2e5 100644 --- a/models/DataObject/AbstractObject/Dao.php +++ b/models/DataObject/AbstractObject/Dao.php @@ -15,7 +15,9 @@ namespace OpenDxp\Model\DataObject\AbstractObject; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Exception; +use Doctrine\DBAL\ParameterType; use OpenDxp\Db\Helper; use OpenDxp\Logger; use OpenDxp\Model; @@ -36,9 +38,12 @@ class Dao extends Model\Element\Dao */ public function getById(int $id): void { - $data = $this->db->fetchAssociative("SELECT objects.*, tree_locks.locked as locked FROM objects - LEFT JOIN tree_locks ON objects.id = tree_locks.id AND tree_locks.type = 'object' - WHERE objects.id = ?", [$id]); + $data = $this->db->fetchAssociative( + 'SELECT objects.*, tree_locks.locked as locked FROM objects + LEFT JOIN tree_locks ON objects.id = tree_locks.id AND tree_locks.type = "object" + WHERE objects.id = ?', + [$id] + ); if ($data) { $data['published'] = (bool)$data['published']; @@ -102,7 +107,7 @@ public function update(?bool $isUpdate = null): void // check the type before updating, changing the type or class of an object is not possible $checkColumns = ['type', 'classId', 'className']; - $existingData = $this->db->fetchAssociative('SELECT ' . implode(',', $checkColumns) . ' FROM objects WHERE id = ?', [$this->model->getId()]); + $existingData = $this->db->fetchAssociative('SELECT type, classId, className FROM objects WHERE id = ?', [$this->model->getId()]); foreach ($checkColumns as $column) { if ($column === 'type' && in_array($data[$column], [DataObject::OBJECT_TYPE_VARIANT, DataObject::OBJECT_TYPE_OBJECT]) && (isset($existingData[$column]) && in_array($existingData[$column], [DataObject::OBJECT_TYPE_VARIANT, DataObject::OBJECT_TYPE_OBJECT]))) { // type conversion variant <=> object should be possible @@ -160,15 +165,26 @@ public function updateChildPaths(string $oldPath): ?array $userId = $user->getId(); } + $newPath = $this->model->getRealFullPath(); + //update object child paths // we don't update the modification date here, as this can have side-effects when there's an unpublished version for an element - $this->db->executeQuery('update objects set `path` = replace(`path`,' . $this->db->quote($oldPath . '/') . ',' . $this->db->quote($this->model->getRealFullPath() . '/') . "), userModification = '" . $userId . "' where `path` like " . $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';'); + $this->db->executeStatement( + 'UPDATE objects SET `path` = REPLACE(`path`, ?, ?), userModification = ? WHERE `path` LIKE ?', + [$oldPath . '/', $newPath . '/', $userId, Helper::escapeLike($oldPath) . '/%'] + ); //update object child permission paths - $this->db->executeQuery('update users_workspaces_object set cpath = replace(cpath,' . $this->db->quote($oldPath . '/') . ',' . $this->db->quote($this->model->getRealFullPath() . '/') . ') where cpath like ' . $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';'); + $this->db->executeStatement( + 'UPDATE users_workspaces_object SET cpath = REPLACE(cpath, ?, ?) WHERE cpath LIKE ?', + [$oldPath . '/', $newPath . '/', Helper::escapeLike($oldPath) . '/%'] + ); //update object child properties paths - $this->db->executeQuery('update properties set cpath = replace(cpath,' . $this->db->quote($oldPath . '/') . ',' . $this->db->quote($this->model->getRealFullPath() . '/') . ') where cpath like ' . $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';'); + $this->db->executeStatement( + 'UPDATE properties SET cpath = REPLACE(cpath, ?, ?) WHERE cpath LIKE ?', + [$oldPath . '/', $newPath . '/', Helper::escapeLike($oldPath) . '/%'] + ); return $objects; } @@ -205,7 +221,7 @@ public function getVersionCountForUpdate(): int $versionCount = (int) $this->db->fetchOne('SELECT versionCount FROM objects WHERE id = ? FOR UPDATE', [$this->model->getId()]); if ($this->model instanceof DataObject\Concrete) { - $versionCount2 = (int) $this->db->fetchOne("SELECT MAX(versionCount) FROM versions WHERE cid = ? AND ctype = 'object'", [$this->model->getId()]); + $versionCount2 = (int) $this->db->fetchOne('SELECT MAX(versionCount) FROM versions WHERE cid = ? AND ctype = "object"', [$this->model->getId()]); $versionCount = max($versionCount, $versionCount2); } @@ -224,11 +240,9 @@ public function getProperties(bool $onlyInherited = false): array // collect properties via parent - ids $parentIds = $this->getParentIds(); $propertiesRaw = $this->db->fetchAllAssociative( - 'SELECT name, type, data, cid, inheritable, cpath FROM properties WHERE - ( - (cid IN (' . implode(',', $parentIds) . ") AND inheritable = 1) - OR cid = ? ) AND ctype='object'", - [$this->model->getId()] + 'SELECT name, type, data, cid, inheritable, cpath FROM properties WHERE ((cid IN (?) AND inheritable = 1) OR cid = ?) AND ctype="object"', + [$parentIds, $this->model->getId()], + [ArrayParameterType::INTEGER, ParameterType::INTEGER] ); // because this should be faster than mysql @@ -289,7 +303,7 @@ public function hasChildren( $sql = 'SELECT 1 FROM objects o WHERE parentId = ? '; if ($user && !$user->isAdmin()) { - $roleIds = $user->getRoles(); + $roleIds = array_map('intval', $user->getRoles()); $currentUserId = $user->getId(); $permissionIds = [...$roleIds, $currentUserId]; @@ -299,7 +313,7 @@ public function hasChildren( // $anyAllowedRowOrChildren checks for nested elements that are `list`=1. This is to allow the folders in between from current parent to any nested elements and due the "additive" permission on the element itself, we can simply ignore list=0 children // unless for the same rule found is list=0 on user specific level, in that case it nullifies that entry. $anyAllowedRowOrChildren = 'EXISTS(SELECT list FROM users_workspaces_object uwo WHERE userId IN (' . implode(',', $permissionIds) . ') AND list=1 AND LOCATE(CONCAT(o.path,o.key),cpath)=1 AND - NOT EXISTS(SELECT list FROM users_workspaces_object WHERE userId =' . $currentUserId . ' AND list=0 AND cpath = uwo.cpath))'; + NOT EXISTS(SELECT list FROM users_workspaces_object WHERE userId =' . (int) $currentUserId . ' AND list=0 AND cpath = uwo.cpath))'; // $allowedCurrentRow checks if the current row is blocked, if found a match it "removes/ignores" the entry from object table, doesn't need to check if is list=1 on user level, since it is done in $anyAllowedRowOrChildren (NB: equal or longer cpath) so we are safe to deduce that there are no valid list=1 rules $isDisallowedCurrentRow = 'EXISTS(SELECT list FROM users_workspaces_object uworow WHERE userId IN (' . implode(',', $permissionIds) . ') AND cid = id AND list=0)'; @@ -382,27 +396,33 @@ public function getChildAmount( return 0; } + $params = [$this->model->getId()]; $query = 'SELECT COUNT(*) AS count FROM objects o WHERE parentId = ?'; if ($objectTypes) { - $query .= sprintf(' AND `type` IN (\'%s\')', implode("','", $objectTypes)); + $query .= ' AND `type` IN (?)'; + $params[] = $objectTypes; } if ($user && !$user->isAdmin()) { - $roleIds = $user->getRoles(); + $roleIds = array_map('intval', $user->getRoles()); $currentUserId = $user->getId(); $permissionIds = [...$roleIds, $currentUserId]; $inheritedPermission = $this->isInheritingPermission('list', $permissionIds); $anyAllowedRowOrChildren = 'EXISTS(SELECT list FROM users_workspaces_object uwo WHERE userId IN (' . implode(',', $permissionIds) . ') AND list=1 AND LOCATE(CONCAT(o.path,o.key),cpath)=1 AND - NOT EXISTS(SELECT list FROM users_workspaces_object WHERE userId ='.$currentUserId.' AND list=0 AND cpath = uwo.cpath))'; + NOT EXISTS(SELECT list FROM users_workspaces_object WHERE userId =' . (int) $currentUserId . ' AND list=0 AND cpath = uwo.cpath))'; $isDisallowedCurrentRow = 'EXISTS(SELECT list FROM users_workspaces_object uworow WHERE userId IN (' . implode(',', $permissionIds) . ') AND cid = id AND list=0)'; $query .= ' AND IF(' . $anyAllowedRowOrChildren . ',1,IF(' . $inheritedPermission . ', ' . $isDisallowedCurrentRow . ' = 0, 0)) = 1'; } - return (int) $this->db->fetchOne($query, [$this->model->getId()]); + return (int) $this->db->fetchOne( + $query, + $params, + $objectTypes ? [ParameterType::INTEGER, ArrayParameterType::STRING] : [] + ); } /** @@ -422,22 +442,33 @@ public function getTypeById(int $id): array public function isLocked(): bool { // check for an locked element below this element - $belowLocks = $this->db->fetchOne("SELECT tree_locks.id FROM tree_locks INNER JOIN objects ON tree_locks.id = objects.id WHERE objects.path LIKE ? AND tree_locks.type = 'object' AND tree_locks.locked IS NOT NULL AND tree_locks.locked != '' LIMIT 1", [Helper::escapeLike($this->model->getRealFullPath()) . '/%']); + $belowLocks = $this->db->fetchOne( + 'SELECT tree_locks.id FROM tree_locks INNER JOIN objects ON tree_locks.id = objects.id WHERE objects.path LIKE ? AND tree_locks.type = "object" AND tree_locks.locked IS NOT NULL AND tree_locks.locked != "" LIMIT 1', + [Helper::escapeLike($this->model->getRealFullPath()) . '/%'] + ); if ($belowLocks > 0) { return true; } $parentIds = $this->getParentIds(); - $inhertitedLocks = $this->db->fetchOne('SELECT id FROM tree_locks WHERE id IN (' . implode(',', $parentIds) . ") AND `type`='object' AND locked = 'propagate' LIMIT 1"); + $inhertitedLocks = $this->db->fetchOne('SELECT id FROM tree_locks WHERE id IN (?) AND `type` = "object" AND locked = "propagate" LIMIT 1', [$parentIds], [ArrayParameterType::INTEGER]); return $inhertitedLocks > 0; } public function unlockPropagate(): array { - $lockIds = $this->db->fetchFirstColumn('SELECT id from objects WHERE `path` like ' . $this->db->quote(Helper::escapeLike($this->model->getRealFullPath()) . '/%') . ' OR id = ' . $this->model->getId()); - $this->db->executeStatement("DELETE FROM tree_locks WHERE `type` = 'object' AND id IN (" . implode(',', $lockIds) . ')'); + $lockIds = $this->db->fetchFirstColumn( + 'SELECT id FROM objects WHERE `path` LIKE ? OR id = ?', + [Helper::escapeLike($this->model->getRealFullPath()) . '/%', $this->model->getId()] + ); + + $this->db->executeStatement( + 'DELETE FROM tree_locks WHERE `type` = "object" AND id IN (?)', + [$lockIds], + [ArrayParameterType::INTEGER] + ); return $lockIds; } @@ -453,7 +484,7 @@ public function getClasses(): array } $classIds = $this->db->fetchFirstColumn( - "SELECT DISTINCT classId FROM objects WHERE `path` like ? AND `type` = 'object'", + 'SELECT DISTINCT classId FROM objects WHERE `path` like ? AND `type` = "object"', [Helper::escapeLike($path) . '/%'] ); @@ -496,7 +527,11 @@ public function isAllowed(string $type, User $user): bool $userIds[] = $user->getId(); try { - $permissionsParent = $this->db->fetchOne('SELECT ' . $this->db->quoteIdentifier($type) . ' FROM users_workspaces_object WHERE cid IN (' . implode(',', $parentIds) . ') AND userId IN (' . implode(',', $userIds) . ') ORDER BY LENGTH(cpath) DESC, FIELD(userId, ' . $user->getId() . ') DESC, ' . $this->db->quoteIdentifier($type) . ' DESC LIMIT 1'); + $permissionsParent = $this->db->fetchOne( + 'SELECT ' . $this->db->quoteIdentifier($type) . ' FROM users_workspaces_object WHERE cid IN (?) AND userId IN (?) ORDER BY LENGTH(cpath) DESC, FIELD(userId, ?) DESC, ' . $this->db->quoteIdentifier($type) . ' DESC LIMIT 1', + [$parentIds, $userIds, $user->getId()], + [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER] + ); if ($permissionsParent) { return true; @@ -510,7 +545,11 @@ public function isAllowed(string $type, User $user): bool $path = '/'; } - $permissionsChildren = $this->db->fetchOne('SELECT list FROM users_workspaces_object WHERE cpath LIKE ? AND userId IN (' . implode(',', $userIds) . ') AND list = 1 LIMIT 1', [Helper::escapeLike($path) . '%']); + $permissionsChildren = $this->db->fetchOne( + 'SELECT list FROM users_workspaces_object WHERE cpath LIKE ? AND userId IN (?) AND list = 1 LIMIT 1', + [Helper::escapeLike($path) . '%', $userIds], + [ParameterType::STRING, ArrayParameterType::INTEGER] + ); if ($permissionsChildren) { return true; } @@ -545,7 +584,11 @@ public function getPermissions(?string $type, User $user, bool $quote = true): ? $commaSeparated = in_array($type, ['lView', 'lEdit', 'layouts']); if ($commaSeparated) { - $allPermissions = $this->db->fetchAllAssociative('SELECT ' . $queryType . ',cid,cpath FROM users_workspaces_object WHERE cid IN (' . implode(',', $parentIds) . ') AND userId IN (' . implode(',', $userIds) . ') ORDER BY LENGTH(cpath) DESC, FIELD(userId, ' . $user->getId() . ') DESC, `' . $type . '` DESC'); + $allPermissions = $this->db->fetchAllAssociative( + 'SELECT ' . $queryType . ',cid,cpath FROM users_workspaces_object WHERE cid IN (?) AND userId IN (?) ORDER BY LENGTH(cpath) DESC, FIELD(userId, ?) DESC, `' . $type . '` DESC', + [$parentIds, $userIds, $user->getId()], + [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER] + ); if (!$allPermissions) { return null; } @@ -583,7 +626,11 @@ public function getPermissions(?string $type, User $user, bool $quote = true): ? } $orderByType = $type ? ', `' . $type . '` DESC' : ''; - $permissions = $this->db->fetchAssociative('SELECT ' . $queryType . ' FROM users_workspaces_object WHERE cid IN (' . implode(',', $parentIds) . ') AND userId IN (' . implode(',', $userIds) . ') ORDER BY LENGTH(cpath) DESC, FIELD(userId, ' . $user->getId() . ') DESC' . $orderByType . ' LIMIT 1'); + $permissions = $this->db->fetchAssociative( + 'SELECT ' . $queryType . ' FROM users_workspaces_object WHERE cid IN (?) AND userId IN (?) ORDER BY LENGTH(cpath) DESC, FIELD(userId, ?) DESC' . $orderByType . ' LIMIT 1', + [$parentIds, $userIds, $user->getId()], + [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER] + ); return $permissions ?: null; } catch (\Exception) { @@ -603,8 +650,11 @@ public function getChildPermissions(?string $type, User $user, bool $quote = tru $type = $type && $quote ? '`' . $type . '`' : '*'; $cid = $this->model->getId(); - $sql = 'SELECT ' . $type . ' FROM users_workspaces_object WHERE cid != ' . $cid . ' AND cpath LIKE ' . $this->db->quote(Helper::escapeLike($this->model->getRealFullPath()) . '%') . ' AND userId IN (' . implode(',', $userIds) . ') ORDER BY LENGTH(cpath) DESC'; - $permissions = $this->db->fetchAllAssociative($sql); + $permissions = $this->db->fetchAllAssociative( + 'SELECT ' . $type . ' FROM users_workspaces_object WHERE cid != ? AND cpath LIKE ? AND userId IN (?) ORDER BY LENGTH(cpath) DESC', + [$cid, Helper::escapeLike($this->model->getRealFullPath()) . '%', $userIds], + [ParameterType::INTEGER, ParameterType::STRING, ArrayParameterType::INTEGER] + ); } catch (\Exception) { Logger::warn('Unable to get permission ' . $type . ' for object ' . $this->model->getId()); } @@ -623,7 +673,7 @@ public function saveIndex(int $index): void public function __isBasedOnLatestData(): bool { - $data = $this->db->fetchAssociative('SELECT modificationDate, versionCount from objects WHERE id = ?', [$this->model->getId()]); + $data = $this->db->fetchAssociative('SELECT modificationDate, versionCount FROM objects WHERE id = ?', [$this->model->getId()]); return $data && $data['modificationDate'] == $this->model->__getDataVersionTimestamp() diff --git a/models/DataObject/ClassDefinition.php b/models/DataObject/ClassDefinition.php index 50b519e0..5f4a84b7 100644 --- a/models/DataObject/ClassDefinition.php +++ b/models/DataObject/ClassDefinition.php @@ -343,7 +343,7 @@ public function save(bool $saveDefinitionFile = true): void if (!$this->getId()) { $db = Db::get(); - $maxId = $db->fetchOne('SELECT MAX(CAST(id AS SIGNED)) FROM classes;'); + $maxId = $db->fetchOne('SELECT MAX(CAST(id AS SIGNED)) FROM classes'); $maxId = $maxId ? $maxId + 1 : 1; $this->setId((string) $maxId); } diff --git a/models/DataObject/ClassDefinition/Dao.php b/models/DataObject/ClassDefinition/Dao.php index d4078317..6972493f 100644 --- a/models/DataObject/ClassDefinition/Dao.php +++ b/models/DataObject/ClassDefinition/Dao.php @@ -112,24 +112,39 @@ public function update(): void $protectedColumns = ['oo_id', 'oo_classId', 'oo_className']; $protectedDatastoreColumns = ['oo_id']; - $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $objectTable . "` ( + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `oo_id` int(11) UNSIGNED NOT NULL default '0', - `oo_classId` varchar(50) default '" . $this->model->getId() . "', - `oo_className` varchar(255) default '" . $this->model->getName() . "', + `oo_classId` varchar(50) default '%s', + `oo_className` varchar(255) default '%s', PRIMARY KEY (`oo_id`), - CONSTRAINT `".self::getForeignKeyName($objectTable, 'oo_id').'` FOREIGN KEY (`oo_id`) REFERENCES objects (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;'); + CONSTRAINT `%s` FOREIGN KEY (`oo_id`) REFERENCES objects (`id`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $objectTable, + $this->model->getId(), + $this->model->getName(), + self::getForeignKeyName($objectTable, 'oo_id') + )); // update default value of classname columns - $this->db->executeQuery('ALTER TABLE `' . $objectTable . "` ALTER COLUMN `oo_className` SET DEFAULT '" . $this->model->getName() . "';"); - - $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $objectDatastoreTable . "` ( + $this->db->executeQuery(sprintf( + "ALTER TABLE `%s` ALTER COLUMN `oo_className` SET DEFAULT '%s';", + $objectTable, + $this->model->getName() + )); + + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `oo_id` int(11) UNSIGNED NOT NULL default '0', PRIMARY KEY (`oo_id`), - CONSTRAINT `".self::getForeignKeyName($objectDatastoreTable, 'oo_id').'` FOREIGN KEY (`oo_id`) REFERENCES objects (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;'); - - $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $objectDatastoreTableRelation . "` ( + CONSTRAINT `%s` FOREIGN KEY (`oo_id`) REFERENCES objects (`id`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $objectDatastoreTable, + self::getForeignKeyName($objectDatastoreTable, 'oo_id') + )); + + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `id` BIGINT(20) NOT NULL PRIMARY KEY AUTO_INCREMENT, `src_id` int(11) UNSIGNED NOT NULL DEFAULT '0', `dest_id` int(11) UNSIGNED NOT NULL DEFAULT '0', @@ -142,8 +157,11 @@ public function update(): void INDEX `forward_lookup` (`src_id`, `ownertype`, `ownername`, `position`), INDEX `reverse_lookup` (`dest_id`, `type`), INDEX `fieldname` (`fieldname`), - CONSTRAINT `".self::getForeignKeyName($objectDatastoreTableRelation, 'src_id').'` FOREIGN KEY (`src_id`) REFERENCES objects (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;'); + CONSTRAINT `%s` FOREIGN KEY (`src_id`) REFERENCES objects (`id`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $objectDatastoreTableRelation, + self::getForeignKeyName($objectDatastoreTableRelation, 'src_id') + )); $this->handleEncryption($this->model, [$objectTable, $objectDatastoreTable, $objectDatastoreTableRelation]); @@ -205,7 +223,7 @@ public function update(): void // create view try { //$this->db->executeQuery('CREATE OR REPLACE VIEW `' . $objectView . '` AS SELECT * FROM `objects` left JOIN `' . $objectTable . '` ON `objects`.`id` = `' . $objectTable . '`.`oo_id` WHERE `objects`.`classId` = ' . $this->model->getId() . ';'); - $this->db->executeQuery('CREATE OR REPLACE VIEW `' . $objectView . '` AS SELECT * FROM `' . $objectTable . '` JOIN `objects` ON `objects`.`id` = `' . $objectTable . '`.`oo_id`;'); + $this->db->executeQuery(sprintf('CREATE OR REPLACE VIEW `%s` AS SELECT * FROM `%s` JOIN `objects` ON `objects`.`id` = `%s`.`oo_id`;', $objectView, $objectTable, $objectTable)); } catch (Exception $e) { Logger::debug((string) $e); } @@ -243,47 +261,47 @@ public function delete(): void $objectDatastoreTableRelation = 'object_relations_' . $this->model->getId(); $objectMetadataTable = 'object_metadata_' . $this->model->getId(); - $this->db->executeQuery('DROP TABLE IF EXISTS `' . $objectTable . '`'); - $this->db->executeQuery('DROP TABLE IF EXISTS `' . $objectDatastoreTable . '`'); - $this->db->executeQuery('DROP TABLE IF EXISTS `' . $objectDatastoreTableRelation . '`'); - $this->db->executeQuery('DROP TABLE IF EXISTS `' . $objectMetadataTable . '`'); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS `%s`', $objectTable)); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS `%s`', $objectDatastoreTable)); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS `%s`', $objectDatastoreTableRelation)); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS `%s`', $objectMetadataTable)); - $this->db->executeQuery('DROP VIEW IF EXISTS `object_' . $this->model->getId() . '`'); + $this->db->executeQuery(sprintf('DROP VIEW IF EXISTS `object_%s`', $this->model->getId())); // delete data $this->db->delete('objects', ['classId' => $this->model->getId()]); // remove fieldcollection tables - $allTables = $this->db->fetchAllAssociative("SHOW TABLES LIKE 'object\_collection\_%\_" . $this->model->getId() . "'"); + $allTables = $this->db->fetchAllAssociative(sprintf("SHOW TABLES LIKE 'object\_collection\_%%\_%s'", $this->model->getId())); foreach ($allTables as $table) { $collectionTable = current($table); - $this->db->executeQuery('DROP TABLE IF EXISTS `'.$collectionTable.'`'); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS `%s`', $collectionTable)); } // remove localized fields tables and views - $allViews = $this->db->fetchAllAssociative("SHOW TABLES LIKE 'object\_localized\_" . $this->model->getId() . "\_%'"); + $allViews = $this->db->fetchAllAssociative(sprintf("SHOW TABLES LIKE 'object\_localized\_%s\_%%'", $this->model->getId())); foreach ($allViews as $view) { $localizedView = current($view); - $this->db->executeQuery('DROP VIEW IF EXISTS `'.$localizedView.'`'); + $this->db->executeQuery(sprintf('DROP VIEW IF EXISTS `%s`', $localizedView)); } - $allTables = $this->db->fetchAllAssociative("SHOW TABLES LIKE 'object\_localized\_query\_" . $this->model->getId() . "\_%'"); + $allTables = $this->db->fetchAllAssociative(sprintf("SHOW TABLES LIKE 'object\_localized\_query\_%s\_%%'", $this->model->getId())); foreach ($allTables as $table) { $queryTable = current($table); - $this->db->executeQuery('DROP TABLE IF EXISTS `'.$queryTable.'`'); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS `%s`', $queryTable)); } - $this->db->executeQuery('DROP TABLE IF EXISTS object_localized_data_' . $this->model->getId()); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS object_localized_data_%s', $this->model->getId())); // objectbrick tables - $allTables = $this->db->fetchAllAssociative("SHOW TABLES LIKE 'object\_brick\_%\_" . $this->model->getId() . "'"); + $allTables = $this->db->fetchAllAssociative(sprintf("SHOW TABLES LIKE 'object\_brick\_%%\_%s'", $this->model->getId())); foreach ($allTables as $table) { $brickTable = current($table); - $this->db->executeQuery('DROP TABLE IF EXISTS `'.$brickTable.'`'); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS `%s`', $brickTable)); } - $this->db->executeQuery('DROP TABLE IF EXISTS object_classificationstore_data_'.$this->model->getId()); - $this->db->executeQuery('DROP TABLE IF EXISTS object_classificationstore_groups_'.$this->model->getId()); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS object_classificationstore_data_%s', $this->model->getId())); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS object_classificationstore_groups_%s', $this->model->getId())); // clean slug table DataObject\Data\UrlSlug::handleClassDeleted($this->model->getId()); @@ -296,8 +314,10 @@ public function updateClassNameInObjects(string $newName): void { $this->db->update('objects', ['className' => $newName], ['classId' => $this->model->getId()]); - $this->db->executeStatement('update ' . $this->db->quoteIdentifier('object_query_' . $this->model->getId()) . - ' set oo_classname = :className', ['className' => $newName]); + $this->db->executeStatement( + sprintf('UPDATE %s SET oo_classname = :className', $this->db->quoteIdentifier('object_query_' . $this->model->getId())), + ['className' => $newName] + ); } public function getNameByIdIgnoreCase(string $id): ?string diff --git a/models/DataObject/ClassDefinition/Data/AdvancedManyToManyObjectRelation.php b/models/DataObject/ClassDefinition/Data/AdvancedManyToManyObjectRelation.php index b45efa2f..93ec0f64 100644 --- a/models/DataObject/ClassDefinition/Data/AdvancedManyToManyObjectRelation.php +++ b/models/DataObject/ClassDefinition/Data/AdvancedManyToManyObjectRelation.php @@ -16,6 +16,7 @@ namespace OpenDxp\Model\DataObject\ClassDefinition\Data; +use Doctrine\DBAL\ArrayParameterType; use Exception; use OpenDxp; use OpenDxp\Bundle\AdminBundle\Service\GridData; @@ -122,7 +123,9 @@ protected function loadData(array $data, Localizedfield|AbstractData|\OpenDxp\Mo } $existingTargets = $db->fetchFirstColumn( - 'SELECT id FROM objects WHERE id IN ('.implode(',', $targets).')' + 'SELECT id FROM objects WHERE id IN (?)', + [$targets], + [ArrayParameterType::INTEGER] ); foreach ($data as $key => $relation) { @@ -456,26 +459,41 @@ public function save(Localizedfield|AbstractData|\OpenDxp\Model\DataObject\Objec $ownerName = '/' . $context['containerType'] . '~' . $containerName . '/%'; } - $sql = Db\Helper::quoteInto($db, 'id = ?', $objectId) . " AND ownertype = 'localizedfield' AND " - . Db\Helper::quoteInto($db, 'ownername LIKE ?', $ownerName) - . ' AND ' . Db\Helper::quoteInto($db, 'fieldname = ?', $this->getName()) - . ' AND ' . Db\Helper::quoteInto($db, 'position = ?', $position); + $qb = $db->createQueryBuilder() + ->delete($table) + ->where('id = :id') + ->andWhere('ownertype = "localizedfield"') + ->andWhere('ownername LIKE :ownerName') + ->andWhere('fieldname = :fieldname') + ->andWhere('position = :position') + ->setParameter('id', $objectId) + ->setParameter('ownerName', $ownerName) + ->setParameter('fieldname', $this->getName()) + ->setParameter('position', $position); } else { - $sql = Db\Helper::quoteInto($db, 'id = ?', $objectId) . ' AND ' . Db\Helper::quoteInto($db, 'fieldname = ?', $this->getName()) - . ' AND ' . Db\Helper::quoteInto($db, 'position = ?', $position); + $qb = $db->createQueryBuilder() + ->delete($table) + ->where('id = :id') + ->andWhere('fieldname = :fieldname') + ->andWhere('position = :position') + ->setParameter('id', $objectId) + ->setParameter('fieldname', $this->getName()) + ->setParameter('position', $position); if ($context) { if (!empty($context['fieldname'])) { - $sql .= ' AND '.Db\Helper::quoteInto($db, 'ownername = ?', $context['fieldname']); + $qb->andWhere('ownername = :ownername') + ->setParameter('ownername', $context['fieldname']); } if (!DataObject::isDirtyDetectionDisabled() && $context['containerType']) { - $sql .= ' AND '.Db\Helper::quoteInto($db, 'ownertype = ?', $context['containerType']); + $qb->andWhere('ownertype = :ownertype') + ->setParameter('ownertype', $context['containerType']); } } } - $db->executeStatement('DELETE FROM ' . $table . ' WHERE ' . $sql); + $qb->executeStatement(); if (!empty($objectsMetadata)) { if ($object instanceof DataObject\Localizedfield || $object instanceof DataObject\Objectbrick\Data\AbstractData @@ -537,10 +555,8 @@ public function delete(Localizedfield|AbstractData|\OpenDxp\Model\DataObject\Obj $containerName = $context['fieldname'] ?? null; $index = $context['index']; $db->executeStatement( - 'DELETE FROM object_metadata_' . $object->getClassId() - . ' WHERE ' . Db\Helper::quoteInto($db, 'id = ?', $object->getId()) . " AND ownertype = 'localizedfield' AND " - . Db\Helper::quoteInto($db, 'ownername LIKE ?', '/' . $context['containerType'] . '~' . $containerName . '/' . "$index . /%") - . ' AND ' . Db\Helper::quoteInto($db, 'fieldname = ?', $this->getName()) + sprintf('DELETE FROM object_metadata_%s WHERE id = ? AND ownertype = "localizedfield" AND ownername LIKE ? AND fieldname = ?', $object->getClassId()), + [$object->getId(), '/' . $context['containerType'] . '~' . $containerName . '/' . $index . '/%', $this->getName()] ); } else { $deleteConditions = [ diff --git a/models/DataObject/ClassDefinition/Data/AdvancedManyToManyRelation.php b/models/DataObject/ClassDefinition/Data/AdvancedManyToManyRelation.php index 18bd32b5..c8131ef1 100644 --- a/models/DataObject/ClassDefinition/Data/AdvancedManyToManyRelation.php +++ b/models/DataObject/ClassDefinition/Data/AdvancedManyToManyRelation.php @@ -16,6 +16,7 @@ namespace OpenDxp\Model\DataObject\ClassDefinition\Data; +use Doctrine\DBAL\ArrayParameterType; use Exception; use OpenDxp; use OpenDxp\Db; @@ -119,7 +120,9 @@ protected function loadData(array $data, Localizedfield|AbstractData|\OpenDxp\Mo $result = $db->fetchFirstColumn( 'SELECT ' . $identifier . ' FROM ' . $targetType . 's' - . ' WHERE ' . $identifier . ' IN (' . implode(',', $targetIds) . ')' + . ' WHERE ' . $identifier . ' IN (?)', + [$targetIds], + [ArrayParameterType::INTEGER] ); $existingTargets[$targetType] = $result; } @@ -252,7 +255,9 @@ public function getDataForEditmode(mixed $data, ?DataObject\Concrete $object = n . $identifier . ' id, ' . $typeCol . ' type' . $className . ' ,concat(' . $db->quoteIdentifier($pathCol) . ',' . $db->quoteIdentifier($keyCol) . ') fullpath FROM ' . $targetType . 's' - . ' WHERE ' . $identifier . ' IN (' . implode(',', $targetIds) . ')' + . ' WHERE ' . $identifier . ' IN (?)', + [$targetIds], + [ArrayParameterType::INTEGER] ); $resultMap = []; @@ -544,30 +549,44 @@ public function save( $ownerName = '/' . $context['containerType'] . '~' . $containerName . '/%'; } - $sql = Db\Helper::quoteInto($db, 'id = ?', $objectId) . " AND ownertype = 'localizedfield' AND " - . Db\Helper::quoteInto($db, 'ownername LIKE ?', $ownerName) - . ' AND ' . Db\Helper::quoteInto($db, 'fieldname = ?', $this->getName()) - . ' AND ' . Db\Helper::quoteInto($db, 'position = ?', $position); + $qb = $db->createQueryBuilder() + ->delete($table) + ->where('id = :id') + ->andWhere('ownertype = "localizedfield"') + ->andWhere('ownername LIKE :ownerName') + ->andWhere('fieldname = :fieldname') + ->andWhere('position = :position') + ->setParameter('id', $objectId) + ->setParameter('ownerName', $ownerName) + ->setParameter('fieldname', $this->getName()) + ->setParameter('position', $position); } else { - $sql = Db\Helper::quoteInto($db, 'id = ?', $objectId) . ' AND ' . - Db\Helper::quoteInto($db, 'fieldname = ?', $this->getName()) - . ' AND ' . Db\Helper::quoteInto($db, 'position = ?', $position); + $qb = $db->createQueryBuilder() + ->delete($table) + ->where('id = :id') + ->andWhere('fieldname = :fieldname') + ->andWhere('position = :position') + ->setParameter('id', $objectId) + ->setParameter('fieldname', $this->getName()) + ->setParameter('position', $position); if ($context) { if (!empty($context['fieldname'])) { - $sql .= ' AND ' . Db\Helper::quoteInto($db, 'ownername = ?', $context['fieldname']); + $qb->andWhere('ownername = :ownername') + ->setParameter('ownername', $context['fieldname']); } if (!DataObject::isDirtyDetectionDisabled() && $context['containerType']) { if ($object instanceof Localizedfield) { $context['containerType'] = 'localizedfield'; } - $sql .= ' AND ' . Db\Helper::quoteInto($db, 'ownertype = ?', $context['containerType']); + $qb->andWhere('ownertype = :ownertype') + ->setParameter('ownertype', $context['containerType']); } } } - $db->executeStatement('DELETE FROM ' . $table . ' WHERE ' . $sql); + $qb->executeStatement(); if (!empty($multihrefMetadata)) { if ($object instanceof DataObject\Localizedfield @@ -628,19 +647,15 @@ public function delete(Localizedfield|AbstractData|\OpenDxp\Model\DataObject\Obj if ($context['containerType'] === 'objectbrick') { $db->executeStatement( - 'DELETE FROM object_metadata_' . $object->getClassId() . ' WHERE ' . - Db\Helper::quoteInto($db, 'id = ?', $object->getId()) . " AND ownertype = 'localizedfield' AND " - . Db\Helper::quoteInto($db, 'ownername LIKE ?', '/' . $context['containerType'] . '~' . $containerName . '/%') - . ' AND ' . Db\Helper::quoteInto($db, 'fieldname = ?', $this->getName()) + sprintf('DELETE FROM object_metadata_%s WHERE id = ? AND ownertype = "localizedfield" AND ownername LIKE ? AND fieldname = ?', $object->getClassId()), + [$object->getId(), '/' . $context['containerType'] . '~' . $containerName . '/%', $this->getName()] ); } else { $index = $context['index']; $db->executeStatement( - 'DELETE FROM object_metadata_' . $object->getClassId() . ' WHERE ' . - Db\Helper::quoteInto($db, 'id = ?', $object->getId()) . " AND ownertype = 'localizedfield' AND " - . Db\Helper::quoteInto($db, 'ownername LIKE ?', '/' . $context['containerType'] . '~' . $containerName . '/' . $index . '/%') - . ' AND ' . Db\Helper::quoteInto($db, 'fieldname = ?', $this->getName()) + sprintf('DELETE FROM object_metadata_%s WHERE id = ? AND ownertype = "localizedfield" AND ownername LIKE ? AND fieldname = ?', $object->getClassId()), + [$object->getId(), '/' . $context['containerType'] . '~' . $containerName . '/' . $index . '/%', $this->getName()] ); } } else { diff --git a/models/DataObject/ClassDefinition/Data/ReverseObjectRelation.php b/models/DataObject/ClassDefinition/Data/ReverseObjectRelation.php index d0f3f6f0..0ebcf5b5 100644 --- a/models/DataObject/ClassDefinition/Data/ReverseObjectRelation.php +++ b/models/DataObject/ClassDefinition/Data/ReverseObjectRelation.php @@ -158,7 +158,10 @@ public function load(Localizedfield|AbstractData|\OpenDxp\Model\DataObject\Objec } $db = Db::get(); - $relations = $db->fetchAllAssociative('SELECT * FROM object_relations_'.$this->getOwnerClassId()." WHERE dest_id = ? AND fieldname = ? AND ownertype = 'object'", [$object->getId(), $this->getOwnerFieldName()]); + $relations = $db->fetchAllAssociative( + sprintf('SELECT * FROM object_relations_%s WHERE dest_id = ? AND fieldname = ? AND ownertype = "object"', $this->getOwnerClassId()), + [$object->getId(), $this->getOwnerFieldName()] + ); $relations = array_map(static function ($relation) { $relation['dest_id'] = $relation['src_id']; diff --git a/models/DataObject/ClassDefinition/Data/UrlSlug.php b/models/DataObject/ClassDefinition/Data/UrlSlug.php index 41398705..8a3d8a31 100644 --- a/models/DataObject/ClassDefinition/Data/UrlSlug.php +++ b/models/DataObject/ClassDefinition/Data/UrlSlug.php @@ -186,8 +186,11 @@ public function save(Localizedfield|AbstractData|\OpenDxp\Model\DataObject\Objec 'fieldname' => $this->getName(), ]; $this->enrichDataRow($object, $params, $classId, $deleteDescriptor, 'objectId'); - $conditionParts = Model\DataObject\Service::buildConditionPartsFromDescriptor($deleteDescriptor); - $db->executeQuery('DELETE FROM ' . Model\DataObject\Data\UrlSlug::TABLE_NAME . ' WHERE ' . implode(' AND ', $conditionParts)); + [$conditionParts, $params] = Model\DataObject\Service::buildConditionPartsFromDescriptor($deleteDescriptor); + $db->executeStatement( + sprintf('DELETE FROM %s WHERE %s', Model\DataObject\Data\UrlSlug::TABLE_NAME, implode(' AND ', $conditionParts)), + $params + ); // now save the new data if (is_array($slugs)) { foreach ($slugs as $slug) { diff --git a/models/DataObject/ClassDefinition/Helper/Dao.php b/models/DataObject/ClassDefinition/Helper/Dao.php index 113b6e23..c2c572af 100644 --- a/models/DataObject/ClassDefinition/Helper/Dao.php +++ b/models/DataObject/ClassDefinition/Helper/Dao.php @@ -51,7 +51,7 @@ protected function addIndexToField(DataObject\ClassDefinition\Data $field, strin } } if ($this->indexDoesNotExist($table, $prefix, $indexName)) { - $this->db->executeQuery('ALTER TABLE `' . $table . '` ADD ' . $uniqueStr . 'INDEX `' . $prefix . $indexName . '` (' . $columnName . ');'); + $this->db->executeQuery(sprintf('ALTER TABLE `%s` ADD %sINDEX `%s%s` (%s);', $table, $uniqueStr, $prefix, $indexName, $columnName)); } } } else { @@ -66,7 +66,7 @@ protected function addIndexToField(DataObject\ClassDefinition\Data $field, strin } } if ($this->indexDoesNotExist($table, $prefix, $indexName)) { - $this->db->executeQuery('ALTER TABLE `' . $table . '` ADD ' . $uniqueStr . 'INDEX `' . $prefix . $indexName . '` (' . $columnName . ');'); + $this->db->executeQuery(sprintf('ALTER TABLE `%s` ADD %sINDEX `%s%s` (%s);', $table, $uniqueStr, $prefix, $indexName, $columnName)); } } } elseif (is_array($columnType)) { @@ -74,14 +74,14 @@ protected function addIndexToField(DataObject\ClassDefinition\Data $field, strin foreach (array_keys($columnType) as $fkey) { $indexName = $field->getName().'__'.$fkey; if ($this->indexExists($table, $prefix, $indexName)) { - $this->db->executeQuery('ALTER TABLE `' . $table . '` DROP INDEX `' . $prefix . $indexName . '`;'); + $this->db->executeQuery(sprintf('ALTER TABLE `%s` DROP INDEX `%s%s`;', $table, $prefix, $indexName)); } } } else { // single -column field $indexName = $field->getName(); if ($this->indexExists($table, $prefix, $indexName)) { - $this->db->executeQuery('ALTER TABLE `' . $table . '` DROP INDEX `' . $prefix . $indexName . '`;'); + $this->db->executeQuery(sprintf('ALTER TABLE `%s` DROP INDEX `%s%s`;', $table, $prefix, $indexName)); } } } @@ -99,10 +99,10 @@ protected function addModifyColumn(string $table, string $colName, string $type, $existingColName = current($matchingExisting); } if ($existingColName === null) { - $this->db->executeQuery('ALTER TABLE `' . $table . '` ADD COLUMN `' . $colName . '` ' . $type . $default . ' ' . $null . ';'); + $this->db->executeQuery(sprintf('ALTER TABLE `%s` ADD COLUMN `%s` %s%s %s;', $table, $colName, $type, $default, $null)); $this->resetValidTableColumnsCache($table); } elseif (!DataObject\ClassDefinition\Service::skipColumn($this->tableDefinitions, $table, $colName, $type, $default, $null)) { - $this->db->executeQuery('ALTER TABLE `' . $table . '` CHANGE COLUMN `' . $existingColName . '` `' . $colName . '` ' . $type . $default . ' ' . $null . ';'); + $this->db->executeQuery(sprintf('ALTER TABLE `%s` CHANGE COLUMN `%s` `%s` %s%s %s;', $table, $existingColName, $colName, $type, $default, $null)); } } @@ -121,7 +121,7 @@ protected function removeUnusedColumns(string $table, array $columnsToRemove, ar } } if ($dropColumns) { - $this->db->executeQuery('ALTER TABLE `' . $table . '` ' . implode(', ', $dropColumns) . ';'); + $this->db->executeQuery(sprintf('ALTER TABLE `%s` %s;', $table, implode(', ', $dropColumns))); $this->resetValidTableColumnsCache($table); } } @@ -172,7 +172,7 @@ protected function removeIndices(string $table, array $columnsToRemove, array $p $lowerCaseColumns = array_map(strtolower(...), $protectedColumns); foreach ($columnsToRemove as $value) { if (!in_array(strtolower($value), $lowerCaseColumns) && $this->indexExists($table, 'u_index_', $value)) { - $this->db->executeQuery('ALTER TABLE `'.$table.'` DROP INDEX `u_index_'. $value . '`;'); + $this->db->executeQuery(sprintf('ALTER TABLE `%s` DROP INDEX `u_index_%s`;', $table, $value)); } } $this->resetValidTableColumnsCache($table); diff --git a/models/DataObject/ClassDefinition/Service.php b/models/DataObject/ClassDefinition/Service.php index a7cfd3f0..44cf7448 100644 --- a/models/DataObject/ClassDefinition/Service.php +++ b/models/DataObject/ClassDefinition/Service.php @@ -374,7 +374,7 @@ public static function updateTableDefinitions(array &$tableDefinitions, array $t $db = \OpenDxp\Db::get(); $tmp = []; foreach ($tableNames as $tableName) { - $tmp[$tableName] = $db->fetchAllAssociative('show columns from ' . $tableName); + $tmp[$tableName] = $db->fetchAllAssociative(sprintf('SHOW COLUMNS FROM %s', $tableName)); } foreach ($tmp as $tableName => $columns) { diff --git a/models/DataObject/Classificationstore/CollectionConfig/Dao.php b/models/DataObject/Classificationstore/CollectionConfig/Dao.php index 3946a058..234521a0 100644 --- a/models/DataObject/Classificationstore/CollectionConfig/Dao.php +++ b/models/DataObject/Classificationstore/CollectionConfig/Dao.php @@ -39,7 +39,13 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->fetchAssociative('SELECT * FROM ' . self::TABLE_NAME_COLLECTIONS . ' WHERE id = ?', [$this->model->getId()]); + $data = $this->db->createQueryBuilder() + ->select('*') + ->from(self::TABLE_NAME_COLLECTIONS) + ->where('id = :id') + ->setParameter('id', $this->model->getId()) + ->executeQuery() + ->fetchAssociative(); if ($data) { $this->assignVariablesToModel($data); @@ -60,7 +66,14 @@ public function getByName(?string $name = null): void $name = $this->model->getName(); $storeId = $this->model->getStoreId(); - $data = $this->db->fetchAssociative('SELECT * FROM ' . self::TABLE_NAME_COLLECTIONS . ' WHERE name = ? and storeId = ?', [$name, $storeId]); + $data = $this->db->createQueryBuilder() + ->select('*') + ->from(self::TABLE_NAME_COLLECTIONS) + ->where('name = :name AND storeId = :storeId') + ->setParameter('name', $name) + ->setParameter('storeId', $storeId) + ->executeQuery() + ->fetchAssociative(); if (!empty($data['id'])) { $this->assignVariablesToModel($data); diff --git a/models/DataObject/Classificationstore/Dao.php b/models/DataObject/Classificationstore/Dao.php index 68b1a074..79b9da2c 100644 --- a/models/DataObject/Classificationstore/Dao.php +++ b/models/DataObject/Classificationstore/Dao.php @@ -58,8 +58,10 @@ public function save(): void $dataTable = $this->getDataTableName(); $fieldname = $this->model->getFieldname(); - $dataExists = $this->db->fetchOne('SELECT `id` FROM `'.$dataTable."` WHERE - `id` = '".$objectId."' AND `fieldname` = '".$fieldname."' LIMIT 1"); + $dataExists = $this->db->fetchOne( + sprintf('SELECT `id` FROM `%s` WHERE `id` = ? AND `fieldname` = ? LIMIT 1', $dataTable), + [$objectId, $fieldname] + ); if ($dataExists) { $this->db->delete($dataTable, ['id' => $objectId, 'fieldname' => $fieldname]); } @@ -81,8 +83,10 @@ public function save(): void $groupsTable = $this->getGroupsTableName(); - $dataExists = $this->db->fetchOne('SELECT `id` FROM `'.$groupsTable."` WHERE - `id` = '".$objectId."' AND `fieldname` = '".$fieldname."' LIMIT 1"); + $dataExists = $this->db->fetchOne( + sprintf('SELECT `id` FROM `%s` WHERE `id` = ? AND `fieldname` = ? LIMIT 1', $groupsTable), + [$objectId, $fieldname] + ); if ($dataExists) { $this->db->delete($groupsTable, ['id' => $objectId, 'fieldname' => $fieldname]); } @@ -205,18 +209,20 @@ public function load(): void $fieldname = $this->model->getFieldname(); $groupsTableName = $this->getGroupsTableName(); - $query = 'SELECT * FROM ' . $groupsTableName . ' WHERE id = ' . $objectId . ' AND fieldname = ' . $this->db->quote($fieldname); - - $data = $this->db->fetchAllAssociative($query); + $data = $this->db->fetchAllAssociative( + sprintf('SELECT * FROM %s WHERE id = ? AND fieldname = ?', $groupsTableName), + [$objectId, $fieldname] + ); $list = []; foreach ($data as $item) { $list[$item['groupId']] = true; } - $query = 'SELECT * FROM ' . $dataTableName . ' WHERE id = ' . $objectId . ' AND fieldname = ' . $this->db->quote($fieldname); - - $data = $this->db->fetchAllAssociative($query); + $data = $this->db->fetchAllAssociative( + sprintf('SELECT * FROM %s WHERE id = ? AND fieldname = ?', $dataTableName), + [$objectId, $fieldname] + ); $groupCollectionMapping = []; @@ -276,16 +282,22 @@ public function createUpdateTable(): void $groupsTable = $this->getGroupsTableName(); $dataTable = $this->getDataTableName(); - $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $groupsTable . '` ( + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `id` INT(11) UNSIGNED NOT NULL, `groupId` INT(11) UNSIGNED NOT NULL, `fieldname` VARCHAR(70) NOT NULL, PRIMARY KEY (`id`, `fieldname`, `groupId`), - CONSTRAINT `'.self::getForeignKeyName($groupsTable, 'id').'` FOREIGN KEY (`id`) REFERENCES `objects` (`id`) ON DELETE CASCADE, - CONSTRAINT `'.self::getForeignKeyName($groupsTable, 'groupId').'` FOREIGN KEY (`groupId`) REFERENCES `classificationstore_groups` (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;'); - - $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $dataTable . '` ( + CONSTRAINT `%s` FOREIGN KEY (`id`) REFERENCES `objects` (`id`) ON DELETE CASCADE, + CONSTRAINT `%s` FOREIGN KEY (`groupId`) REFERENCES `classificationstore_groups` (`id`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $groupsTable, + self::getForeignKeyName($groupsTable, 'id'), + self::getForeignKeyName($groupsTable, 'groupId') + )); + + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `id` INT(11) UNSIGNED NOT NULL, `collectionId` BIGINT(20) NULL, `groupId` INT(11) UNSIGNED NOT NULL, @@ -299,9 +311,14 @@ public function createUpdateTable(): void INDEX `keyId` (`keyId`), INDEX `language` (`language`), INDEX `groupKeys` (`id`, `fieldname`, `groupId`), - CONSTRAINT `'.self::getForeignKeyName($dataTable, 'id').'` FOREIGN KEY (`id`) REFERENCES `objects` (`id`) ON DELETE CASCADE, - CONSTRAINT `'.self::getForeignKeyName($dataTable, 'id__fieldname__groupId').'` FOREIGN KEY (`id`, `fieldname`, `groupId`) REFERENCES `' . $groupsTable . '` (`id`, `fieldname`, `groupId`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;'); + CONSTRAINT `%s` FOREIGN KEY (`id`) REFERENCES `objects` (`id`) ON DELETE CASCADE, + CONSTRAINT `%s` FOREIGN KEY (`id`, `fieldname`, `groupId`) REFERENCES `%s` (`id`, `fieldname`, `groupId`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $dataTable, + self::getForeignKeyName($dataTable, 'id'), + self::getForeignKeyName($dataTable, 'id__fieldname__groupId'), + $groupsTable + )); $this->tableDefinitions = []; diff --git a/models/DataObject/Classificationstore/GroupConfig/Dao.php b/models/DataObject/Classificationstore/GroupConfig/Dao.php index 3ad307e5..db9c270f 100644 --- a/models/DataObject/Classificationstore/GroupConfig/Dao.php +++ b/models/DataObject/Classificationstore/GroupConfig/Dao.php @@ -39,7 +39,13 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->fetchAssociative('SELECT * FROM ' . self::TABLE_NAME_GROUPS . ' WHERE id = ?', [$this->model->getId()]); + $data = $this->db->createQueryBuilder() + ->select('*') + ->from(self::TABLE_NAME_GROUPS) + ->where('id = :id') + ->setParameter('id', $this->model->getId()) + ->executeQuery() + ->fetchAssociative(); if ($data) { $this->assignVariablesToModel($data); @@ -60,7 +66,14 @@ public function getByName(?string $name = null): void $name = $this->model->getName(); $storeId = $this->model->getStoreId(); - $data = $this->db->fetchAssociative('SELECT * FROM ' . self::TABLE_NAME_GROUPS . ' WHERE name = ? and storeId = ?', [$name, $storeId]); + $data = $this->db->createQueryBuilder() + ->select('*') + ->from(self::TABLE_NAME_GROUPS) + ->where('name = :name AND storeId = :storeId') + ->setParameter('name', $name) + ->setParameter('storeId', $storeId) + ->executeQuery() + ->fetchAssociative(); if ($data) { $this->assignVariablesToModel($data); @@ -75,7 +88,13 @@ public function hasChildren(): bool return false; } - return (bool) $this->db->fetchOne('SELECT COUNT(*) as amount FROM ' . self::TABLE_NAME_GROUPS . ' WHERE parentId = ?', [$this->model->getId()]); + return (bool) $this->db->createQueryBuilder() + ->select('COUNT(*)') + ->from(self::TABLE_NAME_GROUPS) + ->where('parentId = :parentId') + ->setParameter('parentId', $this->model->getId()) + ->executeQuery() + ->fetchOne(); } /** diff --git a/models/DataObject/Classificationstore/KeyConfig/Dao.php b/models/DataObject/Classificationstore/KeyConfig/Dao.php index 49052955..e8dc811d 100644 --- a/models/DataObject/Classificationstore/KeyConfig/Dao.php +++ b/models/DataObject/Classificationstore/KeyConfig/Dao.php @@ -39,7 +39,13 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->fetchAssociative('SELECT * FROM ' . self::TABLE_NAME_KEYS . ' WHERE id = ?', [$this->model->getId()]); + $data = $this->db->createQueryBuilder() + ->select('*') + ->from(self::TABLE_NAME_KEYS) + ->where('id = :id') + ->setParameter('id', $this->model->getId()) + ->executeQuery() + ->fetchAssociative(); if ($data) { $data['enabled'] = (bool)$data['enabled']; @@ -61,9 +67,14 @@ public function getByName(?string $name = null): void $name = $this->model->getName(); $storeId = $this->model->getStoreId(); - $stmt = 'SELECT * FROM ' . self::TABLE_NAME_KEYS . ' WHERE name = ' . $this->db->quote($name) . ' and storeId = ' . $storeId; - - $data = $this->db->fetchAssociative($stmt); + $data = $this->db->createQueryBuilder() + ->select('*') + ->from(self::TABLE_NAME_KEYS) + ->where('name = :name AND storeId = :storeId') + ->setParameter('name', $name) + ->setParameter('storeId', $storeId) + ->executeQuery() + ->fetchAssociative(); if ($data) { $data['enabled'] = (bool)$data['enabled']; @@ -116,7 +127,7 @@ public function update(): void } } if (is_array($value) || is_object($value)) { - $value = $this->model->getType() == 'select' ? json_encode($value) : \OpenDxp\Tool\Serialize::serialize($value); + $value = $this->model->getType() === 'select' ? json_encode($value) : \OpenDxp\Tool\Serialize::serialize($value); } $data[$key] = $value; diff --git a/models/DataObject/Classificationstore/StoreConfig/Dao.php b/models/DataObject/Classificationstore/StoreConfig/Dao.php index 822923a3..91a41aa7 100644 --- a/models/DataObject/Classificationstore/StoreConfig/Dao.php +++ b/models/DataObject/Classificationstore/StoreConfig/Dao.php @@ -39,7 +39,13 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->fetchAssociative('SELECT * FROM ' . self::TABLE_NAME_STORES . ' WHERE id = ?', [$this->model->getId()]); + $data = $this->db->createQueryBuilder() + ->select('*') + ->from(self::TABLE_NAME_STORES) + ->where('id = :id') + ->setParameter('id', $this->model->getId()) + ->executeQuery() + ->fetchAssociative(); if ($data) { $this->assignVariablesToModel($data); @@ -59,7 +65,13 @@ public function getByName(?string $name = null): void $name = $this->model->getName(); - $data = $this->db->fetchAssociative('SELECT * FROM ' . self::TABLE_NAME_STORES . ' WHERE name = ?', [$name]); + $data = $this->db->createQueryBuilder() + ->select('*') + ->from(self::TABLE_NAME_STORES) + ->where('name = :name') + ->setParameter('name', $name) + ->executeQuery() + ->fetchAssociative(); if ($data) { $this->assignVariablesToModel($data); diff --git a/models/DataObject/Concrete.php b/models/DataObject/Concrete.php index 5ac7c2c7..6a77e338 100644 --- a/models/DataObject/Concrete.php +++ b/models/DataObject/Concrete.php @@ -740,11 +740,12 @@ public function __clone(): void protected function doRetrieveData(array $descriptor, string $table): array { $db = Db::get(); - $conditionParts = Service::buildConditionPartsFromDescriptor($descriptor); + [$conditionParts, $params] = Service::buildConditionPartsFromDescriptor($descriptor); - $query = 'SELECT * FROM ' . $table . ' WHERE ' . implode(' AND ', $conditionParts); - - return $db->fetchAllAssociative($query); + return $db->fetchAllAssociative( + sprintf('SELECT * FROM %s WHERE %s', $table, implode(' AND ', $conditionParts)), + $params + ); } /** diff --git a/models/DataObject/Concrete/Dao.php b/models/DataObject/Concrete/Dao.php index d6e300c8..f9d56f08 100644 --- a/models/DataObject/Concrete/Dao.php +++ b/models/DataObject/Concrete/Dao.php @@ -58,9 +58,12 @@ protected function getInheritanceHelper(): Dao\InheritanceHelper #[Override] public function getById(int $id): void { - $data = $this->db->fetchAssociative("SELECT objects.*, tree_locks.locked as locked FROM objects - LEFT JOIN tree_locks ON objects.id = tree_locks.id AND tree_locks.type = 'object' - WHERE objects.id = ?", [$id]); + $data = $this->db->fetchAssociative( + 'SELECT objects.*, tree_locks.locked as locked FROM objects + LEFT JOIN tree_locks ON objects.id = tree_locks.id AND tree_locks.type = "object" + WHERE objects.id = ?', + [$id] + ); if ($data) { $data['published'] = (bool)$data['published']; @@ -74,7 +77,13 @@ public function getById(int $id): void public function getRelationIds(string $fieldName): array { $relations = []; - $allRelations = $this->db->fetchAllAssociative('SELECT * FROM object_relations_' . $this->model->getClassId() . " WHERE fieldname = ? AND src_id = ? AND ownertype = 'object' ORDER BY `index` ASC", [$fieldName, $this->model->getId()]); + $allRelations = $this->db->fetchAllAssociative( + sprintf( + 'SELECT * FROM object_relations_%s WHERE fieldname = ? AND src_id = ? AND ownertype = "object" ORDER BY `index` ASC', + $this->model->getClassId() + ), + [$fieldName, $this->model->getId()] + ); foreach ($allRelations as $relation) { $relations[] = $relation['dest_id']; } @@ -96,31 +105,38 @@ public function getRelationData(string $field, bool $forOwner, ?string $remoteCl $src = 'dest_id'; } - return $this->db->fetchAllAssociative('SELECT r.' . $dest . ' as dest_id, r.' . $dest . ' as id, r.type, o.className as subtype, o.published as published, concat(o.path ,o.key) as `path` , r.index - FROM objects o, object_relations_' . $classId . " r + // Raw SQL: UNION of 3 queries across objects/assets/documents — not expressible in QB + return $this->db->fetchAllAssociative( + sprintf( + 'SELECT r.%1$s as dest_id, r.%1$s as id, r.type, o.className as subtype, o.published as published, concat(o.path ,o.key) as `path` , r.index + FROM objects o, object_relations_%2$s r WHERE r.fieldname= ? - AND r.ownertype = 'object' - AND r." . $src . ' = ? - AND o.id = r.' . $dest . " - AND r.type='object' + AND r.ownertype = "object" + AND r.%3$s = ? + AND o.id = r.%1$s + AND r.type="object" - UNION SELECT r." . $dest . ' as dest_id, r.' . $dest . ' as id, r.type, a.type as subtype, "null" as published, concat(a.path,a.filename) as `path`, r.index - FROM assets a, object_relations_' . $classId . " r + UNION SELECT r.%1$s as dest_id, r.%1$s as id, r.type, a.type as subtype, "null" as published, concat(a.path,a.filename) as `path`, r.index + FROM assets a, object_relations_%2$s r WHERE r.fieldname= ? - AND r.ownertype = 'object' - AND r." . $src . ' = ? - AND a.id = r.' . $dest . " - AND r.type='asset' + AND r.ownertype = "object" + AND r.%3$s = ? + AND a.id = r.%1$s + AND r.type="asset" - UNION SELECT r." . $dest . ' as dest_id, r.' . $dest . ' as id, r.type, d.type as subtype, d.published as published, concat(d.path,d.key) as `path`, r.index - FROM documents d, object_relations_' . $classId . " r + UNION SELECT r.%1$s as dest_id, r.%1$s as id, r.type, d.type as subtype, d.published as published, concat(d.path,d.key) as `path`, r.index + FROM documents d, object_relations_%2$s r WHERE r.fieldname= ? - AND r.ownertype = 'object' - AND r." . $src . ' = ? - AND d.id = r.' . $dest . " - AND r.type='document' - - ORDER BY `index` ASC", $params); + AND r.ownertype = "object" + AND r.%3$s = ? + AND d.id = r.%1$s + AND r.type="document" + + ORDER BY `index` ASC', + $dest, $classId, $src + ), + $params + ); } /** @@ -128,7 +144,7 @@ public function getRelationData(string $field, bool $forOwner, ?string $remoteCl */ public function getData(): void { - if (!$data = $this->db->fetchAssociative('SELECT * FROM object_store_' . $this->model->getClassId() . ' WHERE oo_id = ? FOR UPDATE', [$this->model->getId()])) { + if (!$data = $this->db->fetchAssociative(sprintf('SELECT * FROM object_store_%s WHERE oo_id = ? FOR UPDATE', $this->model->getClassId()), [$this->model->getId()])) { return; } @@ -247,7 +263,10 @@ public function update(?bool $isUpdate = null): void // get data for query table $data = []; $this->getInheritanceHelper()->resetFieldsToCheck(); - $oldData = $this->db->fetchAssociative('SELECT * FROM object_query_' . $this->model->getClassId() . ' WHERE oo_id = ?', [$this->model->getId()]); + $oldData = $this->db->fetchAssociative( + sprintf('SELECT * FROM object_query_%s WHERE oo_id = ?', $this->model->getClassId()), + [$this->model->getId()] + ); $inheritanceEnabled = $this->model->getClass()->getAllowInherit(); $parentData = null; @@ -259,7 +278,7 @@ public function update(?bool $isUpdate = null): void // we cannot DataObject::setGetInheritedValues(true); and then $this->model->$method(); // so we select the data from the parent object using FOR UPDATE, which causes a lock on this row // so the data of the parent cannot be changed while this transaction is on progress - $parentData = $this->db->fetchAssociative('SELECT * FROM object_query_' . $this->model->getClassId() . ' WHERE oo_id = ? FOR UPDATE', [$parentForInheritance->getId()]); + $parentData = $this->db->fetchAssociative(sprintf('SELECT * FROM object_query_%s WHERE oo_id = ? FOR UPDATE', $this->model->getClassId()), [$parentForInheritance->getId()]); } } diff --git a/models/DataObject/Concrete/Dao/InheritanceHelper.php b/models/DataObject/Concrete/Dao/InheritanceHelper.php index cba26a04..b08f7548 100644 --- a/models/DataObject/Concrete/Dao/InheritanceHelper.php +++ b/models/DataObject/Concrete/Dao/InheritanceHelper.php @@ -16,7 +16,9 @@ namespace OpenDxp\Model\DataObject\Concrete\Dao; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\ParameterType; use Exception; use OpenDxp\Db\Helper; use OpenDxp\Model\DataObject; @@ -137,7 +139,10 @@ public function doUpdate(int $oo_id, bool $createMissingChildrenRows = false, ar $fields = ', `' . $fields . '`'; } - $result = $this->db->fetchAssociative('SELECT ' . $this->idField . ' AS id' . $fields . ' FROM ' . $this->storetable . ' WHERE ' . $this->idField . ' = ?', [$oo_id]); + $result = $this->db->fetchAssociative( + sprintf('SELECT %s AS id%s FROM %s WHERE %s = ?', $this->idField, $fields, $this->storetable, $this->idField), + [$oo_id] + ); $o = [ 'id' => $result['id'], 'values' => $result, @@ -199,7 +204,10 @@ public function doUpdate(int $oo_id, bool $createMissingChildrenRows = false, ar "; $missingIds = $this->db->fetchFirstColumn($query); // create entries for children that don't have an entry yet - $originalEntry = Helper::quoteDataIdentifiers($this->db, $this->db->fetchAssociative('SELECT * FROM ' . $this->querytable . ' WHERE ' . $this->idField . ' = ?', [$oo_id])); + $originalEntry = Helper::quoteDataIdentifiers($this->db, $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE %s = ?', $this->querytable, $this->idField), + [$oo_id] + )); foreach ($missingIds as $id) { $originalEntry[$this->db->quoteIdentifier($this->idField)] = $id; $this->db->insert($this->db->quoteIdentifier($this->querytable), $originalEntry); @@ -262,12 +270,20 @@ public function doDelete(int $objectId, array $params = []): void // remove the query row entirely ... if ($affectedIds) { $objectsWithBrickIds = []; - $objectsWithBricks = $this->db->fetchAllAssociative('SELECT ' . $this->idField . ' FROM ' . $this->storetable . ' WHERE ' . $this->idField . ' IN (' . implode(',', $affectedIds) . ')'); + $objectsWithBricks = $this->db->fetchAllAssociative( + sprintf('SELECT %s FROM %s WHERE %s IN (?)', $this->idField, $this->storetable, $this->idField), + [$affectedIds], + [ArrayParameterType::INTEGER] + ); foreach ($objectsWithBricks as $item) { $objectsWithBrickIds[] = $item[$this->idField]; } - $currentQueryItems = $this->db->fetchAllAssociative('SELECT * FROM ' . $this->querytable . ' WHERE ' . $this->idField . ' IN (' . implode(',', $affectedIds) . ')'); + $currentQueryItems = $this->db->fetchAllAssociative( + sprintf('SELECT * FROM %s WHERE %s IN (?)', $this->querytable, $this->idField), + [$affectedIds], + [ArrayParameterType::INTEGER] + ); foreach ($currentQueryItems as $queryItem) { $toBeRemoved = true; @@ -289,7 +305,7 @@ public function doDelete(int $objectId, array $params = []): void } if ($toBeRemovedItemIds) { - $this->db->executeStatement('DELETE FROM ' . $this->querytable . ' WHERE ' . $this->idField . ' IN (' . implode(',', $toBeRemovedItemIds) . ')'); + $this->db->executeStatement(sprintf('DELETE FROM %s WHERE %s IN (?)', $this->querytable, $this->idField), [$toBeRemovedItemIds], [ArrayParameterType::INTEGER]); } } @@ -439,12 +455,24 @@ protected function getRelationsForNode(array &$node, array $params = []): array $relationCondition = $this->getRelationCondition($params); if (isset($params['language'])) { - $objectRelationsResult = $this->db->fetchAllAssociative('SELECT src_id as id, fieldname, position, count(*) as COUNT FROM ' . $this->relationtable . ' WHERE ' . $relationCondition . " src_id = ? AND fieldname IN('" . implode("','", array_keys($this->relations)) . "') " - . ' GROUP BY position, fieldname' - . ' HAVING `position` = "' . $params['language'] . '" OR ISNULL(`position`)', [$node['id']]); + $objectRelationsResult = $this->db->fetchAllAssociative( + sprintf( + 'SELECT src_id as id, fieldname, position, count(*) as COUNT FROM %s WHERE %s src_id = ? AND fieldname IN(?) GROUP BY position, fieldname HAVING `position` = ? OR ISNULL(`position`)', + $this->relationtable, $relationCondition + ), + [$node['id'], array_keys($this->relations), $params['language']], + [ParameterType::INTEGER, ArrayParameterType::STRING, ParameterType::STRING] + ); $objectRelationsResult = $this->filterResultByLanguage($objectRelationsResult, $params['language'], 'position'); } else { - $objectRelationsResult = $this->db->fetchAllAssociative('SELECT fieldname, count(*) as COUNT FROM ' . $this->relationtable . ' WHERE ' . $relationCondition . " src_id = ? AND fieldname IN('" . implode("','", array_keys($this->relations)) . "') GROUP BY fieldname;", [$node['id']]); + $objectRelationsResult = $this->db->fetchAllAssociative( + sprintf( + 'SELECT fieldname, count(*) as COUNT FROM %s WHERE %s src_id = ? AND fieldname IN(?) GROUP BY fieldname', + $this->relationtable, $relationCondition + ), + [$node['id'], array_keys($this->relations)], + [ParameterType::INTEGER, ArrayParameterType::STRING] + ); } $objectRelations = []; @@ -528,16 +556,18 @@ protected function getIdsToUpdateForRelationfields(array $currentNode, string $f protected function updateQueryTable(int $oo_id, array $ids, string $fieldname): void { if ($ids !== []) { - $value = $this->db->fetchOne("SELECT `$fieldname` FROM " . $this->querytable . ' WHERE ' . $this->idField . ' = ?', [$oo_id]); - $this->db->executeStatement('UPDATE ' . $this->querytable .' SET ' . $this->db->quoteIdentifier($fieldname) . '=? WHERE ' . $this->db->quoteIdentifier($this->idField) . ' IN (' . implode(',', $ids) . ')', [$value]); + $value = $this->db->fetchOne( + sprintf('SELECT %s FROM %s WHERE %s = ?', $this->db->quoteIdentifier($fieldname), $this->querytable, $this->idField), + [$oo_id] + ); + $this->db->executeStatement(sprintf('UPDATE %s SET %s = ? WHERE %s IN (?)', $this->querytable, $this->db->quoteIdentifier($fieldname), $this->db->quoteIdentifier($this->idField)), [$value, $ids], [ParameterType::STRING, ArrayParameterType::INTEGER]); } } protected function updateQueryTableOnDelete(int $oo_id, array $ids, string $fieldname): void { if ($ids !== []) { - $value = null; - $this->db->executeStatement('UPDATE ' . $this->querytable .' SET ' . $this->db->quoteIdentifier($fieldname) . '=? WHERE ' . $this->db->quoteIdentifier($this->idField) . ' IN (' . implode(',', $ids) . ')', [$value]); + $this->db->executeStatement(sprintf('UPDATE %s SET %s = ? WHERE %s IN (?)', $this->querytable, $this->db->quoteIdentifier($fieldname), $this->db->quoteIdentifier($this->idField)), [null, $ids], [ParameterType::NULL, ArrayParameterType::INTEGER]); } } } diff --git a/models/DataObject/Data/AbstractMetadata/Dao.php b/models/DataObject/Data/AbstractMetadata/Dao.php index 5b1999d4..40e12316 100644 --- a/models/DataObject/Data/AbstractMetadata/Dao.php +++ b/models/DataObject/Data/AbstractMetadata/Dao.php @@ -50,7 +50,8 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class): void $classId = $class->getId(); $table = 'object_metadata_' . $classId; - $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $table . "` ( + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `auto_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, `id` int(11) UNSIGNED NOT NULL default '0', `dest_id` int(11) NOT NULL default '0', @@ -73,9 +74,12 @@ public function createOrUpdateTable(DataObject\ClassDefinition $class): void INDEX `ownername` (`ownername`), INDEX `position` (`position`), INDEX `index` (`index`), - CONSTRAINT `".self::getForeignKeyName($table, 'id').'` FOREIGN KEY (`id`) + CONSTRAINT `%s` FOREIGN KEY (`id`) REFERENCES objects (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;'); + ) DEFAULT CHARSET=utf8mb4;", + $table, + self::getForeignKeyName($table, 'id') + )); $this->handleEncryption($class, [$table]); } diff --git a/models/DataObject/Data/ElementMetadata/Dao.php b/models/DataObject/Data/ElementMetadata/Dao.php index f48b73ea..ebfc79db 100644 --- a/models/DataObject/Data/ElementMetadata/Dao.php +++ b/models/DataObject/Data/ElementMetadata/Dao.php @@ -56,8 +56,14 @@ public function load(DataObject\Concrete $source, int $destinationId, string $fi $typeQuery = ' AND `type` = ' . $this->db->quote($destinationType); } - $dataRaw = $this->db->fetchAllAssociative('SELECT * FROM ' . - $this->getTablename($source) . ' WHERE ' . $this->getTablename($source) .'.id = ? AND dest_id = ? AND fieldname = ? AND ownertype = ? AND ownername = ? and position = ? and `index` = ? ' . $typeQuery, [$source->getId(), $destinationId, $fieldname, $ownertype, $ownername, $position, $index]); + $tablename = $this->getTablename($source); + $dataRaw = $this->db->fetchAllAssociative( + sprintf( + 'SELECT * FROM %1$s WHERE %1$s.id = ? AND dest_id = ? AND fieldname = ? AND ownertype = ? AND ownername = ? and position = ? and `index` = ? %2$s', + $tablename, $typeQuery + ), + [$source->getId(), $destinationId, $fieldname, $ownertype, $ownername, $position, $index] + ); if ($dataRaw !== []) { $this->model->setElementTypeAndId($destinationType, $destinationId); $this->model->setFieldname($fieldname); diff --git a/models/DataObject/Data/ObjectMetadata/Dao.php b/models/DataObject/Data/ObjectMetadata/Dao.php index 74436fe8..322b7fbf 100644 --- a/models/DataObject/Data/ObjectMetadata/Dao.php +++ b/models/DataObject/Data/ObjectMetadata/Dao.php @@ -61,9 +61,9 @@ protected function getTablename(DataObject\Concrete $object): string public function load(DataObject\Concrete $source, int $destinationId, string $fieldname, string $ownertype, string $ownername, string $position, int $index, string $destinationType = 'object'): ?DataObject\Data\ObjectMetadata { - $typeQuery = " AND (`type` = 'object' or `type` = '')"; + $typeQuery = ' AND (`type` = "object" OR `type` = "")'; - $query = 'SELECT * FROM ' . $this->getTablename($source) . ' WHERE id = ? AND dest_id = ? AND fieldname = ? AND ownertype = ? AND ownername = ? and position = ? and `index` = ? ' . $typeQuery; + $query = 'SELECT * FROM ' . $this->getTablename($source) . ' WHERE id = ? AND dest_id = ? AND fieldname = ? AND ownertype = ? AND ownername = ? AND position = ? AND `index` = ? ' . $typeQuery; $dataRaw = $this->db->fetchAllAssociative($query, [$source->getId(), $destinationId, $fieldname, $ownertype, $ownername, $position, $index]); if ($dataRaw !== []) { $this->model->setObjectId($destinationId); diff --git a/models/DataObject/Fieldcollection/Dao.php b/models/DataObject/Fieldcollection/Dao.php index 94699440..da38dddb 100644 --- a/models/DataObject/Fieldcollection/Dao.php +++ b/models/DataObject/Fieldcollection/Dao.php @@ -17,7 +17,6 @@ use Exception; use OpenDxp; -use OpenDxp\Db\Helper; use OpenDxp\Logger; use OpenDxp\Model; use OpenDxp\Model\DataObject; @@ -56,7 +55,10 @@ public function load(DataObject\Concrete $object): array $tableName = $definition->getTableName($object->getClass()); try { - $results = $this->db->fetchAllAssociative('SELECT * FROM ' . $tableName . ' WHERE id = ? AND fieldname = ? ORDER BY `index` ASC', [$object->getId(), $this->model->getFieldname()]); + $results = $this->db->fetchAllAssociative( + sprintf('SELECT * FROM %s WHERE id = ? AND fieldname = ? ORDER BY `index` ASC', $tableName), + [$object->getId(), $this->model->getFieldname()] + ); } catch (Exception) { $results = []; } @@ -156,7 +158,10 @@ public function delete(DataObject\Concrete $object, bool $saveMode = false): arr $tableName = $definition->getTableName($object->getClass()); try { - $dataExists = $this->db->fetchOne('SELECT `id` FROM `'.$tableName."` WHERE `id` = '".$object->getId()."' AND `fieldname` = '".$this->model->getFieldname()."' LIMIT 1"); + $dataExists = $this->db->fetchOne( + sprintf('SELECT `id` FROM `%s` WHERE `id` = ? AND `fieldname` = ? LIMIT 1', $tableName), + [$object->getId(), $this->model->getFieldname()] + ); if ($dataExists) { $this->db->delete($tableName, [ 'id' => $object->getId(), @@ -172,8 +177,10 @@ public function delete(DataObject\Concrete $object, bool $saveMode = false): arr $tableName = $definition->getLocalizedTableName($object->getClass()); try { - $dataExists = $this->db->fetchOne('SELECT `ooo_id` FROM `'.$tableName."` WHERE - `ooo_id` = '".$object->getId()."' AND `fieldname` = '".$this->model->getFieldname()."' LIMIT 1 "); + $dataExists = $this->db->fetchOne( + sprintf('SELECT `ooo_id` FROM `%s` WHERE `ooo_id` = ? AND `fieldname` = ? LIMIT 1', $tableName), + [$object->getId(), $this->model->getFieldname()] + ); if ($dataExists) { $this->db->delete($tableName, [ 'ooo_id' => $object->getId(), @@ -230,24 +237,25 @@ public function delete(DataObject\Concrete $object, bool $saveMode = false): arr return []; } - $whereLocalizedFields = "(ownertype = 'localizedfield' AND " - . Helper::quoteInto($this->db, 'ownername LIKE ?', '/fieldcollection~' - . $this->model->getFieldname() . '/%') - . ' AND ' . Helper::quoteInto($this->db, 'src_id = ?', $object->getId()). ')'; - if ($saveMode && (!DataObject::isDirtyDetectionDisabled() && !$this->model->hasDirtyFields() && $hasLocalizedFields)) { // always empty localized fields - $this->db->executeStatement('DELETE FROM object_relations_' . $object->getClassId() . ' WHERE ' . $whereLocalizedFields); + $this->db->executeStatement( + sprintf('DELETE FROM object_relations_%s WHERE (ownertype = "localizedfield" AND ownername LIKE ? AND src_id = ?)', $object->getClassId()), + ['/fieldcollection~' . $this->model->getFieldname() . '/%', $object->getId()] + ); return ['saveLocalizedRelations' => true]; } - $where = "(ownertype = 'fieldcollection' AND " . Helper::quoteInto($this->db, 'ownername = ?', $this->model->getFieldname()) - . ' AND ' . Helper::quoteInto($this->db, 'src_id = ?', $object->getId()) . ')'; - // empty relation table - $this->db->executeStatement('DELETE FROM object_relations_' . $object->getClassId() . ' WHERE ' . $where); - $this->db->executeStatement('DELETE FROM object_relations_' . $object->getClassId() . ' WHERE ' . $whereLocalizedFields); + $this->db->executeStatement( + sprintf('DELETE FROM object_relations_%s WHERE (ownertype = "fieldcollection" AND ownername = ? AND src_id = ?)', $object->getClassId()), + [$this->model->getFieldname(), $object->getId()] + ); + $this->db->executeStatement( + sprintf('DELETE FROM object_relations_%s WHERE (ownertype = "localizedfield" AND ownername LIKE ? AND src_id = ?)', $object->getClassId()), + ['/fieldcollection~' . $this->model->getFieldname() . '/%', $object->getId()] + ); return ['saveFieldcollectionRelations' => true, 'saveLocalizedRelations' => true]; } diff --git a/models/DataObject/Fieldcollection/Definition/Dao.php b/models/DataObject/Fieldcollection/Definition/Dao.php index 5c561481..51288457 100644 --- a/models/DataObject/Fieldcollection/Definition/Dao.php +++ b/models/DataObject/Fieldcollection/Definition/Dao.php @@ -42,22 +42,26 @@ public function getLocalizedTableName(DataObject\ClassDefinition $class): string public function delete(DataObject\ClassDefinition $class): void { $table = $this->getTableName($class); - $this->db->executeQuery('DROP TABLE IF EXISTS `' . $table . '`'); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS `%s`', $table)); } public function createUpdateTable(DataObject\ClassDefinition $class): void { $table = $this->getTableName($class); - $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $table . "` ( + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `id` int(11) UNSIGNED NOT NULL default '0', `index` int(11) default '0', `fieldname` varchar(190) default '', PRIMARY KEY (`id`,`index`,`fieldname`(190)), INDEX `index` (`index`), INDEX `fieldname` (`fieldname`), - CONSTRAINT `".self::getForeignKeyName($table, 'id').'` FOREIGN KEY (`id`) REFERENCES objects (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;'); + CONSTRAINT `%s` FOREIGN KEY (`id`) REFERENCES objects (`id`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $table, + self::getForeignKeyName($table, 'id') + )); $existingColumns = $this->getValidTableColumns($table, false); // no caching of table definition $columnsToRemove = $existingColumns; diff --git a/models/DataObject/Localizedfield/Dao.php b/models/DataObject/Localizedfield/Dao.php index 5b66a1b5..c515836a 100644 --- a/models/DataObject/Localizedfield/Dao.php +++ b/models/DataObject/Localizedfield/Dao.php @@ -15,7 +15,9 @@ namespace OpenDxp\Model\DataObject\Localizedfield; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\ParameterType; use Exception; use OpenDxp\Db; use OpenDxp\Db\Helper; @@ -260,13 +262,12 @@ public function save(array $params = []): void $queryTable ); $this->inheritanceHelper->resetFieldsToCheck(); - $sql = 'SELECT * FROM '.$queryTable.' WHERE ooo_id = '.$object->getId( - )." AND language = '".$language."'"; + $sql = 'SELECT * FROM ' . $queryTable . ' WHERE ooo_id = ? AND language = ?'; $oldData = []; try { - $oldData = $this->db->fetchAssociative($sql); + $oldData = $this->db->fetchAssociative($sql, [$object->getId(), $language]); } catch (TableNotFoundException) { // if the table doesn't exist -> create it! @@ -305,7 +306,7 @@ public function save(array $params = []): void // so we select the data from the parent object using FOR UPDATE, which causes a lock on this row // so the data of the parent cannot be changed while this transaction is on progress $parentData = $this->db->fetchAssociative( - 'SELECT * FROM '.$queryTable.' WHERE ooo_id = ? AND language = ? FOR UPDATE', + sprintf('SELECT * FROM %s WHERE ooo_id = ? AND language = ? FOR UPDATE', $queryTable), [$parentForInheritance->getId(), $language] ); } @@ -557,15 +558,12 @@ public function delete(bool $deleteQuery = true, bool $isUpdate = true): bool throw new Exception('no container type set'); } - $sql = Helper::quoteInto($this->db, 'src_id = ?', $objectId)." AND ownertype = 'localizedfield' AND " - .Helper::quoteInto($this->db, - 'ownername LIKE ?', - '/'.$context['containerType'].'~'.$containerName.'/'.$index.'/%' - ).$dirtyLanguageCondition; + $sqlParams = [$objectId, '/' . $context['containerType'] . '~' . $containerName . '/' . $index . '/%']; + $sql = 'src_id = ? AND ownertype = "localizedfield" AND ownername LIKE ?' . $dirtyLanguageCondition; if ($deleteQuery || $context['containerType'] === 'fieldcollection') { // Fieldcollection don't support delta updates, so we delete the relations and insert them later again - $this->db->executeStatement('DELETE FROM object_relations_'.$object->getClassId().' WHERE '.$sql); + $this->db->executeStatement(sprintf('DELETE FROM object_relations_%s WHERE %s', $object->getClassId(), $sql), $sqlParams); } return true; @@ -576,7 +574,7 @@ public function delete(bool $deleteQuery = true, bool $isUpdate = true): bool $sql = 'ownertype = "localizedfield" AND ownername = "localizedfield" and src_id = '.$this->model->getObject( )->getId().$dirtyLanguageCondition; $this->db->executeStatement( - 'DELETE FROM object_relations_'.$this->model->getObject()->getClassId().' WHERE '.$sql + sprintf('DELETE FROM object_relations_%s WHERE %s', $this->model->getObject()->getClassId(), $sql) ); } @@ -586,9 +584,6 @@ public function delete(bool $deleteQuery = true, bool $isUpdate = true): bool public function load(DataObject\Fieldcollection\Data\AbstractData|DataObject\Objectbrick\Data\AbstractData|DataObject\Concrete $object, array $params = []): void { $validLanguages = Tool::getValidLanguages(); - foreach ($validLanguages as &$language) { - $language = $this->db->quote($language); - } $context = $this->model->getContext(); if (isset($context['containerType']) && $context['containerType'] === 'fieldcollection') { @@ -599,16 +594,9 @@ public function load(DataObject\Fieldcollection\Data\AbstractData|DataObject\Obj $container = DataObject\Fieldcollection\Definition::getByKey($containerKey); $data = $this->db->fetchAllAssociative( - 'SELECT * FROM '.$this->getTableName() - .' WHERE ooo_id = ? AND language IN ('.implode( - ',', - $validLanguages - ).') AND `fieldname` = ? AND `index` = ?', - [ - $this->model->getObject()->getId(), - $fieldname, - $index, - ] + sprintf('SELECT * FROM %s WHERE ooo_id = ? AND language IN (?) AND `fieldname` = ? AND `index` = ?', $this->getTableName()), + [$this->model->getObject()->getId(), $validLanguages, $fieldname, $index], + [ParameterType::INTEGER, ArrayParameterType::STRING, ParameterType::STRING, ParameterType::INTEGER] ); } elseif (isset($context['containerType']) && $context['containerType'] === 'objectbrick') { $containerKey = $context['containerKey']; @@ -616,22 +604,17 @@ public function load(DataObject\Fieldcollection\Data\AbstractData|DataObject\Obj $fieldname = $context['fieldname']; $data = $this->db->fetchAllAssociative( - 'SELECT * FROM '.$this->getTableName() - .' WHERE ooo_id = ? AND language IN ('.implode(',', $validLanguages).') AND `fieldname` = ?', - [ - $this->model->getObject()->getId(), - $fieldname, - ] + sprintf('SELECT * FROM %s WHERE ooo_id = ? AND language IN (?) AND `fieldname` = ?', $this->getTableName()), + [$this->model->getObject()->getId(), $validLanguages, $fieldname], + [ParameterType::INTEGER, ArrayParameterType::STRING, ParameterType::STRING] ); } else { $object->__objectAwareFields['localizedfields'] = true; $container = $this->model->getClass(); $data = $this->db->fetchAllAssociative( - 'SELECT * FROM '.$this->getTableName().' WHERE ooo_id = ? AND language IN ('.implode( - ',', - $validLanguages - ).')', - [$this->model->getObject()->getId()] + sprintf('SELECT * FROM %s WHERE ooo_id = ? AND language IN (?)', $this->getTableName()), + [$this->model->getObject()->getId(), $validLanguages], + [ParameterType::INTEGER, ArrayParameterType::STRING] ); } @@ -733,8 +716,8 @@ public function createLocalizedViews(): void $tablename = $this->getQueryTableName().'_'.$language; // get available columns - $viewColumns = [...$this->db->fetchAllAssociative('SHOW COLUMNS FROM `'.$defaultTable.'`'), ...$this->db->fetchAllAssociative('SHOW COLUMNS FROM `objects`')]; - $localizedColumns = $this->db->fetchAllAssociative('SHOW COLUMNS FROM `'.$tablename.'`'); + $viewColumns = [...$this->db->fetchAllAssociative(sprintf('SHOW COLUMNS FROM `%s`', $defaultTable)), ...$this->db->fetchAllAssociative('SHOW COLUMNS FROM `objects`')]; + $localizedColumns = $this->db->fetchAllAssociative(sprintf('SHOW COLUMNS FROM `%s`', $tablename)); // get view fields $viewFields = []; @@ -797,8 +780,8 @@ public function createUpdateTable(array $params = []): void $context = $this->model->getContext(); if (isset($context['containerType']) && ($context['containerType'] === 'fieldcollection' || $context['containerType'] === 'objectbrick')) { - $this->db->executeQuery( - 'CREATE TABLE IF NOT EXISTS `'.$table."` ( + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `ooo_id` int(11) UNSIGNED NOT NULL default '0', `index` INT(11) NOT NULL DEFAULT '0', `fieldname` VARCHAR(190) NOT NULL DEFAULT '', @@ -807,19 +790,23 @@ public function createUpdateTable(array $params = []): void INDEX `index` (`index`), INDEX `fieldname` (`fieldname`), INDEX `language` (`language`), - CONSTRAINT `".self::getForeignKeyName($table, 'ooo_id').'` FOREIGN KEY (`ooo_id`) REFERENCES objects (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;' - ); + CONSTRAINT `%s` FOREIGN KEY (`ooo_id`) REFERENCES objects (`id`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $table, + self::getForeignKeyName($table, 'ooo_id') + )); } else { - $this->db->executeQuery( - 'CREATE TABLE IF NOT EXISTS `'.$table."` ( + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `ooo_id` int(11) UNSIGNED NOT NULL default '0', `language` varchar(10) NOT NULL DEFAULT '', PRIMARY KEY (`ooo_id`,`language`), INDEX `language` (`language`), - CONSTRAINT `".self::getForeignKeyName($table, 'ooo_id').'` FOREIGN KEY (`ooo_id`) REFERENCES objects (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;' - ); + CONSTRAINT `%s` FOREIGN KEY (`ooo_id`) REFERENCES objects (`id`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $table, + self::getForeignKeyName($table, 'ooo_id') + )); } $this->handleEncryption($this->model->getClass(), [$table]); @@ -876,15 +863,17 @@ public function createUpdateTable(array $params = []): void $queryTable = $this->getQueryTableName(); $queryTable .= '_'.$language; - $this->db->executeQuery( - 'CREATE TABLE IF NOT EXISTS `'.$queryTable."` ( + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `ooo_id` int(11) UNSIGNED NOT NULL default '0', `language` varchar(10) NOT NULL DEFAULT '', PRIMARY KEY (`ooo_id`,`language`), INDEX `language` (`language`), - CONSTRAINT `".self::getForeignKeyName($queryTable, 'ooo_id').'` FOREIGN KEY (`ooo_id`) REFERENCES objects (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;' - ); + CONSTRAINT `%s` FOREIGN KEY (`ooo_id`) REFERENCES objects (`id`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $queryTable, + self::getForeignKeyName($queryTable, 'ooo_id') + )); $this->handleEncryption($this->model->getClass(), [$queryTable]); diff --git a/models/DataObject/Objectbrick/Dao.php b/models/DataObject/Objectbrick/Dao.php index 76ff6b51..65bf81c0 100644 --- a/models/DataObject/Objectbrick/Dao.php +++ b/models/DataObject/Objectbrick/Dao.php @@ -48,7 +48,10 @@ public function load(DataObject\Concrete $object, array $params = []): array $tableName = $definition->getTableName($object->getClass(), false); try { - $results = $this->db->fetchAllAssociative('SELECT * FROM '.$tableName.' WHERE id = ? AND fieldname = ?', [$object->getId(), $this->model->getFieldname()]); + $results = $this->db->fetchAllAssociative( + sprintf('SELECT * FROM %s WHERE id = ? AND fieldname = ?', $tableName), + [$object->getId(), $this->model->getFieldname()] + ); } catch (Exception) { $results = []; } diff --git a/models/DataObject/Objectbrick/Data/Dao.php b/models/DataObject/Objectbrick/Data/Dao.php index 08a1a4c4..ce781135 100644 --- a/models/DataObject/Objectbrick/Data/Dao.php +++ b/models/DataObject/Objectbrick/Data/Dao.php @@ -64,7 +64,7 @@ public function save(DataObject\Concrete $object, array $params = []): void } else { // or brick has been added $existsResult = $this->db->fetchOne( - 'SELECT id FROM ' . $storetable . ' WHERE id = ? LIMIT 1', + sprintf('SELECT id FROM %s WHERE id = ? LIMIT 1', $storetable), [$object->getId()] ); @@ -110,7 +110,7 @@ public function save(DataObject\Concrete $object, array $params = []): void } if ($isBrickUpdate) { - $this->db->update($storetable, Helper::quoteDataIdentifiers($this->db, $data), ['id'=> $object->getId()]); + $this->db->update($storetable, Helper::quoteDataIdentifiers($this->db, $data), ['id' => $object->getId()]); } else { $this->db->insert($storetable, Helper::quoteDataIdentifiers($this->db, $data)); } @@ -124,7 +124,10 @@ public function save(DataObject\Concrete $object, array $params = []): void $data['fieldname'] = $this->model->getFieldname(); $this->inheritanceHelper->resetFieldsToCheck(); - $oldData = $this->db->fetchAssociative('SELECT * FROM ' . $querytable . ' WHERE id = ?', [$object->getId()]); + $oldData = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE id = ?', $querytable), + [$object->getId()] + ); $inheritanceEnabled = $object->getClass()->getAllowInherit(); $parentData = null; @@ -136,7 +139,10 @@ public function save(DataObject\Concrete $object, array $params = []): void // we cannot DataObject::setGetInheritedValues(true); and then $this->model->$method(); // so we select the data from the parent object using FOR UPDATE, which causes a lock on this row // so the data of the parent cannot be changed while this transaction is on progress - $parentData = $this->db->fetchAssociative('SELECT * FROM ' . $querytable . ' WHERE id = ? FOR UPDATE', [$parentForInheritance->getId()]); + $parentData = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE id = ? FOR UPDATE', $querytable), + [$parentForInheritance->getId()] + ); } } @@ -252,7 +258,10 @@ public function delete(DataObject\Concrete $object): void // update data for query table $queryTable = $this->model->getDefinition()->getTableName($object->getClass(), true); - $oldData = $this->db->fetchAssociative('SELECT * FROM ' . $queryTable . ' WHERE id = ?', [$object->getId()]); + $oldData = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE id = ?', $queryTable), + [$object->getId()] + ); $this->db->delete($queryTable, ['id' => $object->getId()]); //update data for relations table @@ -330,32 +339,40 @@ public function getRelationData(string $field, bool $forOwner, ?string $remoteCl $src = 'dest_id'; } - return $this->db->fetchAllAssociative('SELECT r.' . $dest . ' as dest_id, r.' . $dest . ' as id, r.type, o.className as subtype, concat(o.path ,o.key) as `path` , r.index, o.published - FROM objects o, object_relations_' . $classId . " r + // Raw SQL: UNION of 3 queries across objects/assets/documents — not expressible in QB + return $this->db->fetchAllAssociative( + sprintf( + 'SELECT r.%1$s as dest_id, r.%1$s as id, r.type, o.className as subtype, concat(o.path ,o.key) as `path`, r.index, o.published + FROM objects o, object_relations_%2$s r WHERE r.fieldname= ? - AND r.ownertype = 'objectbrick' - AND r." . $src . ' = ? - AND o.id = r.' . $dest . " - AND (position = '" . $this->model->getType() . "' OR position IS NULL OR position = '') - AND r.type='object' - - UNION SELECT r." . $dest . ' as dest_id, r.' . $dest . ' as id, r.type, a.type as subtype, concat(a.path,a.filename) as `path`, r.index, "null" as published - FROM assets a, object_relations_' . $classId . " r + AND r.ownertype = "objectbrick" + AND r.%3$s = ? + AND o.id = r.%1$s + AND (position = "%4$s" OR position IS NULL OR position = "") + AND r.type="object" + + UNION SELECT r.%1$s as dest_id, r.%1$s as id, r.type, a.type as subtype, concat(a.path,a.filename) as `path`, r.index, "null" as published + FROM assets a, object_relations_%2$s r WHERE r.fieldname= ? - AND r.ownertype = 'objectbrick' - AND r." . $src . ' = ? - AND a.id = r.' . $dest . " - AND (position = '" . $this->model->getType() . "' OR position IS NULL OR position = '') - AND r.type='asset' - - UNION SELECT r." . $dest . ' as dest_id, r.' . $dest . ' as id, r.type, d.type as subtype, concat(d.path,d.key) as `path`, r.index, d.published as published - FROM documents d, object_relations_' . $classId . " r + AND r.ownertype = "objectbrick" + AND r.%3$s = ? + AND a.id = r.%1$s + AND (position = "%4$s" OR position IS NULL OR position = "") + AND r.type="asset" + + UNION SELECT r.%1$s as dest_id, r.%1$s as id, r.type, d.type as subtype, concat(d.path,d.key) as `path`, r.index, d.published as published + FROM documents d, object_relations_%2$s r WHERE r.fieldname= ? - AND r.ownertype = 'objectbrick' - AND r." . $src . ' = ? - AND d.id = r.' . $dest . " - AND (position = '" . $this->model->getType() . "' OR position IS NULL OR position = '') - AND r.type='document' - ORDER BY `index` ASC", $params); + AND r.ownertype = "objectbrick" + AND r.%3$s = ? + AND d.id = r.%1$s + AND (position = "%4$s" OR position IS NULL OR position = "") + AND r.type="document" + + ORDER BY `index` ASC', + $dest, $classId, $src, $this->model->getType() + ), + $params + ); } } diff --git a/models/DataObject/Objectbrick/Definition/Dao.php b/models/DataObject/Objectbrick/Definition/Dao.php index 061ae5f0..3ae7e387 100644 --- a/models/DataObject/Objectbrick/Definition/Dao.php +++ b/models/DataObject/Objectbrick/Definition/Dao.php @@ -51,10 +51,10 @@ public function getLocalizedTableName(DataObject\ClassDefinition $class, bool $q public function delete(DataObject\ClassDefinition $class): void { $table = $this->getTableName($class, false); - $this->db->executeQuery('DROP TABLE IF EXISTS `' . $table . '`'); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS `%s`', $table)); $table = $this->getTableName($class, true); - $this->db->executeQuery('DROP TABLE IF EXISTS `' . $table . '`'); + $this->db->executeQuery(sprintf('DROP TABLE IF EXISTS `%s`', $table)); } public function createUpdateTable(DataObject\ClassDefinition $class): void @@ -62,23 +62,31 @@ public function createUpdateTable(DataObject\ClassDefinition $class): void $tableStore = $this->getTableName($class, false); $tableQuery = $this->getTableName($class, true); - $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $tableStore . "` ( + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `id` int(11) UNSIGNED NOT NULL default '0', `fieldname` varchar(190) default '', PRIMARY KEY (`id`,`fieldname`), INDEX `id` (`id`), INDEX `fieldname` (`fieldname`), - CONSTRAINT `".self::getForeignKeyName($tableStore, 'id').'` FOREIGN KEY (`id`) REFERENCES objects (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;'); - - $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $tableQuery . "` ( + CONSTRAINT `%s` FOREIGN KEY (`id`) REFERENCES objects (`id`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $tableStore, + self::getForeignKeyName($tableStore, 'id') + )); + + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `id` int(11) UNSIGNED NOT NULL default '0', `fieldname` varchar(190) default '', PRIMARY KEY (`id`,`fieldname`), INDEX `id` (`id`), INDEX `fieldname` (`fieldname`), - CONSTRAINT `".self::getForeignKeyName($tableQuery, 'id').'` FOREIGN KEY (`id`) REFERENCES objects (`id`) ON DELETE CASCADE - ) DEFAULT CHARSET=utf8mb4;'); + CONSTRAINT `%s` FOREIGN KEY (`id`) REFERENCES objects (`id`) ON DELETE CASCADE + ) DEFAULT CHARSET=utf8mb4;", + $tableQuery, + self::getForeignKeyName($tableQuery, 'id') + )); $existingColumnsStore = $this->getValidTableColumns($tableStore, false); // no caching of table definition $columnsToRemoveStore = $existingColumnsStore; @@ -159,7 +167,7 @@ protected function removeIndices(string $table, array $columnsToRemove, array $p $indexPrefix = str_starts_with($table, 'object_brick_query_') ? 'p_index_' : 'u_index_'; foreach ($columnsToRemove as $value) { if (!in_array(strtolower($value), $protectedColumns)) { - Helper::queryIgnoreError($this->db, 'ALTER TABLE `'.$table.'` DROP INDEX `' . $indexPrefix . $value . '`;'); + Helper::queryIgnoreError($this->db, sprintf('ALTER TABLE `%s` DROP INDEX `%s%s`;', $table, $indexPrefix, $value)); } } diff --git a/models/DataObject/QuantityValue/Unit/Dao.php b/models/DataObject/QuantityValue/Unit/Dao.php index faa779e0..cfd2731d 100644 --- a/models/DataObject/QuantityValue/Unit/Dao.php +++ b/models/DataObject/QuantityValue/Unit/Dao.php @@ -45,7 +45,10 @@ public function init(): void */ public function getByAbbreviation(string $abbreviation): void { - $classRaw = $this->db->fetchAssociative('SELECT * FROM ' . self::TABLE_NAME . ' WHERE abbreviation=' . $this->db->quote($abbreviation)); + $classRaw = $this->db->fetchAssociative( + 'SELECT * FROM ' . self::TABLE_NAME . ' WHERE abbreviation = ?', + [$abbreviation] + ); if (!$classRaw) { throw new Model\Exception\NotFoundException('Unit ' . $abbreviation . ' not found.'); } @@ -57,7 +60,10 @@ public function getByAbbreviation(string $abbreviation): void */ public function getByReference(string $reference): void { - $classRaw = $this->db->fetchAssociative('SELECT * FROM ' . self::TABLE_NAME . ' WHERE reference=' . $this->db->quote($reference)); + $classRaw = $this->db->fetchAssociative( + 'SELECT * FROM ' . self::TABLE_NAME . ' WHERE reference = ?', + [$reference] + ); if (!$classRaw) { throw new Model\Exception\NotFoundException('Unit ' . $reference . ' not found.'); } @@ -84,7 +90,9 @@ public function update(): void { if (!$this->model->getId()) { // mimic autoincrement - $id = $this->db->fetchOne('SELECT CONVERT(SUBSTRING_INDEX(id,\'-\',-1),UNSIGNED INTEGER) AS num FROM quantityvalue_units ORDER BY num DESC LIMIT 1'); + $id = $this->db->fetchOne( + 'SELECT CONVERT(SUBSTRING_INDEX(id,\'-\',-1),UNSIGNED INTEGER) AS num FROM quantityvalue_units ORDER BY num DESC LIMIT 1' + ); $id = $id > 0 ? ($id + 1) : 1; $this->model->setId((string) $id); } diff --git a/models/DataObject/Service.php b/models/DataObject/Service.php index a5568f06..622a8120 100644 --- a/models/DataObject/Service.php +++ b/models/DataObject/Service.php @@ -1390,14 +1390,15 @@ public static function recursiveResetDirtyMap(AbstractObject $object): void */ public static function buildConditionPartsFromDescriptor(array $descriptor): array { - $db = Db::get(); $conditionParts = []; + $params = []; foreach ($descriptor as $key => $value) { $lastChar = is_string($value) ? $value[strlen($value) - 1] : null; - $conditionParts[] = $lastChar === '%' ? $key . ' LIKE ' . $db->quote($value) : $key . ' = ' . $db->quote($value); + $conditionParts[] = $lastChar === '%' ? $key . ' LIKE ?' : $key . ' = ?'; + $params[] = $value; } - return $conditionParts; + return [$conditionParts, $params]; } /** diff --git a/models/DataObject/Traits/CompositeIndexTrait.php b/models/DataObject/Traits/CompositeIndexTrait.php index 7f7fe59c..53e9e5d3 100644 --- a/models/DataObject/Traits/CompositeIndexTrait.php +++ b/models/DataObject/Traits/CompositeIndexTrait.php @@ -74,14 +74,12 @@ public function updateCompositeIndices(string $table, string $type, array $compo } foreach ($drop as $key) { - $this->db->executeQuery('ALTER TABLE `'.$table.'` DROP INDEX `'. $key.'`;'); + $this->db->executeQuery(sprintf('ALTER TABLE `%s` DROP INDEX `%s`;', $table, $key)); } foreach ($add as $key) { $columnName = $newIndicesMap[$key]; - $this->db->executeQuery( - 'ALTER TABLE `'.$table.'` ADD INDEX `' . $key.'` ('.$columnName.');' - ); + $this->db->executeQuery(sprintf('ALTER TABLE `%s` ADD INDEX `%s` (%s);', $table, $key, $columnName)); } } } diff --git a/models/Dependency/Dao.php b/models/Dependency/Dao.php index 65bbb19b..d6c01599 100644 --- a/models/Dependency/Dao.php +++ b/models/Dependency/Dao.php @@ -15,10 +15,11 @@ namespace OpenDxp\Model\Dependency; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Exception\UniqueConstraintViolationException; +use Doctrine\DBAL\ParameterType; use Exception; use OpenDxp; -use OpenDxp\Db\Helper; use OpenDxp\Logger; use OpenDxp\Messenger\SanityCheckMessage; use OpenDxp\Model; @@ -82,31 +83,46 @@ public function getFilterRequiresByPath( } //filterRequiresByPath - $query = " + $params = [ + 'sourceType' => $sourceType, + 'sourceId' => $sourceId, + 'value' => preg_quote((string) $value, '/'), + ]; + + $types = [ + 'sourceType' => ParameterType::STRING, + 'sourceId' => ParameterType::INTEGER, + 'value' => ParameterType::STRING, + ]; + + $query = sprintf( + ' SELECT id, type FROM ( SELECT d.targetid as id, d.targettype as type FROM dependencies d - INNER JOIN objects o ON o.id = d.targetid AND d.targettype= 'object' - WHERE d.sourcetype = '" . $sourceType. "' AND d.sourceid = " . $sourceId . " AND LOWER(CONCAT(o.path, o.key)) RLIKE '".$value."' + INNER JOIN objects o ON o.id = d.targetid AND d.targettype= "object" + WHERE d.sourcetype = :sourceType AND d.sourceid = :sourceId AND LOWER(CONCAT(o.path, o.key)) RLIKE :value UNION SELECT d.targetid as id, d.targettype as type FROM dependencies d - INNER JOIN documents doc ON doc.id = d.targetid AND d.targettype= 'document' - WHERE d.sourcetype = '" . $sourceType. "' AND d.sourceid = " . $sourceId . " AND LOWER(CONCAT(doc.path, doc.key)) RLIKE '".$value."' + INNER JOIN documents doc ON doc.id = d.targetid AND d.targettype= "document" + WHERE d.sourcetype = :sourceType AND d.sourceid = :sourceId AND LOWER(CONCAT(doc.path, doc.key)) RLIKE :value UNION SELECT d.targetid as id, d.targettype as type FROM dependencies d - INNER JOIN assets a ON a.id = d.targetid AND d.targettype= 'asset' - WHERE d.sourcetype = '" . $sourceType. "' AND d.sourceid = " . $sourceId . " AND LOWER(CONCAT(a.path, a.filename)) RLIKE '".$value."' + INNER JOIN assets a ON a.id = d.targetid AND d.targettype= "asset" + WHERE d.sourcetype = :sourceType AND d.sourceid = :sourceId AND LOWER(CONCAT(a.path, a.filename)) RLIKE :value ) dep - ORDER BY " . $orderBy . ' ' . $orderDirection; + ORDER BY %s %s', + $orderBy, $orderDirection + ); if ($offset !== null && $limit !== null) { - $query = sprintf($query . ' LIMIT %d,%d', $offset, $limit); + $query .= sprintf(' LIMIT %d,%d', $offset, $limit); } - $requiresByPath = $this->db->fetchAllAssociative($query); + $requiresByPath = $this->db->fetchAllAssociative($query, $params, $types); if (count($requiresByPath) > 0) { return $requiresByPath; @@ -140,31 +156,46 @@ public function getFilterRequiredByPath( } //filterRequiredByPath - $query = " + $params = [ + 'targetType' => $targetType, + 'targetId' => $targetId, + 'value' => preg_quote((string) $value, '/'), + ]; + + $types = [ + 'targetType' => ParameterType::STRING, + 'targetId' => ParameterType::INTEGER, + 'value' => ParameterType::STRING, + ]; + + $query = sprintf( + ' SELECT id, type FROM ( SELECT d.sourceid as id, d.sourcetype as type FROM dependencies d - INNER JOIN objects o ON o.id = d.sourceid AND d.targettype= 'object' - WHERE d.targettype = '" . $targetType. "' AND d.targetid = " . $targetId . " AND LOWER(CONCAT(o.path, o.key)) RLIKE '".$value."' + INNER JOIN objects o ON o.id = d.sourceid AND d.targettype= "object" + WHERE d.targettype = :targetType AND d.targetid = :targetId AND LOWER(CONCAT(o.path, o.key)) RLIKE :value UNION SELECT d.sourceid as id, d.sourcetype as type FROM dependencies d - INNER JOIN documents doc ON doc.id = d.sourceid AND d.targettype= 'document' - WHERE d.targettype = '" . $targetType. "' AND d.targetid = " . $targetId . " AND LOWER(CONCAT(doc.path, doc.key)) RLIKE '".$value."' + INNER JOIN documents doc ON doc.id = d.sourceid AND d.targettype= "document" + WHERE d.targettype = :targetType AND d.targetid = :targetId AND LOWER(CONCAT(doc.path, doc.key)) RLIKE :value UNION SELECT d.sourceid as id, d.sourcetype as type FROM dependencies d - INNER JOIN assets a ON a.id = d.sourceid AND d.targettype= 'asset' - WHERE d.targettype = '" . $targetType. "' AND d.targetid = " . $targetId . " AND LOWER(CONCAT(a.path, a.filename)) RLIKE '".$value."' + INNER JOIN assets a ON a.id = d.sourceid AND d.targettype= "asset" + WHERE d.targettype = :targetType AND d.targetid = :targetId AND LOWER(CONCAT(a.path, a.filename)) RLIKE :value ) dep - ORDER BY " . $orderBy . ' ' . $orderDirection; + ORDER BY %s %s', + $orderBy, $orderDirection + ); if ($offset !== null && $limit !== null) { - $query = sprintf($query . ' LIMIT %d,%d', $offset, $limit); + $query .= sprintf(' LIMIT %d,%d', $offset, $limit); } - $requiredByPath = $this->db->fetchAllAssociative($query); + $requiredByPath = $this->db->fetchAllAssociative($query, $params, $types); if (count($requiredByPath) > 0) { return $requiredByPath; @@ -183,14 +214,20 @@ public function cleanAllForElement(Element\ElementInterface $element): void $type = Element\Service::getElementType($element); //schedule for sanity check - $data = $this->db->fetchAllAssociative('SELECT `sourceid`, `sourcetype` FROM dependencies WHERE targettype = ? AND targetid = ?', [$type, $id]); + $data = $this->db->fetchAllAssociative( + 'SELECT `sourceid`, `sourcetype` FROM dependencies WHERE targettype = ? AND targetid = ?', + [$type, $id] + ); foreach ($data as $row) { OpenDxp::getContainer()->get('messenger.bus.opendxp-core')->dispatch( new SanityCheckMessage($row['sourcetype'], $row['sourceid']) ); } - Helper::selectAndDeleteWhere($this->db, 'dependencies', 'id', Helper::quoteInto($this->db, 'sourceid = ?', $id) . ' AND ' . Helper::quoteInto($this->db, 'sourcetype = ?', $type)); + $this->db->executeStatement( + 'DELETE FROM dependencies WHERE sourceid = ? AND sourcetype = ?', + [$id, $type] + ); } catch (Exception $e) { Logger::error((string) $e); } @@ -202,7 +239,10 @@ public function cleanAllForElement(Element\ElementInterface $element): void public function clear(): void { try { - Helper::selectAndDeleteWhere($this->db, 'dependencies', 'id', Helper::quoteInto($this->db, 'sourceid = ?', $this->model->getSourceId()) . ' AND ' . Helper::quoteInto($this->db, 'sourcetype = ?', $this->model->getSourceType())); + $this->db->executeStatement( + 'DELETE FROM dependencies WHERE sourceid = ? AND sourcetype = ?', + [$this->model->getSourceId(), $this->model->getSourceType()] + ); } catch (Exception $e) { Logger::error((string) $e); } @@ -214,8 +254,10 @@ public function clear(): void public function save(): void { // get existing dependencies - $existingDependenciesRaw = $this->db->fetchAllAssociative('SELECT id, targetType, targetId FROM dependencies WHERE sourceType= ? AND sourceId = ?', - [$this->model->getSourceType(), $this->model->getSourceId()]); + $existingDependenciesRaw = $this->db->fetchAllAssociative( + 'SELECT id, targetType, targetId FROM dependencies WHERE sourceType = ? AND sourceId = ?', + [$this->model->getSourceType(), $this->model->getSourceId()] + ); $existingDepencies = []; foreach ($existingDependenciesRaw as $dep) { @@ -255,8 +297,7 @@ public function save(): void } if ($idsForDeletion) { - $idString = implode(',', $idsForDeletion); - $this->db->executeStatement('DELETE FROM dependencies WHERE id IN (' . $idString . ')'); + $this->db->executeStatement('DELETE FROM dependencies WHERE id IN (?)', [$idsForDeletion], [ArrayParameterType::INTEGER]); } foreach ($newData as $target) { @@ -287,7 +328,7 @@ public function getRequiredBy(?int $offset = null, ?int $limit = null): array '; if ($offset !== null && $limit !== null) { - $query = sprintf($query . ' LIMIT %d,%d', $offset, $limit); + $query .= sprintf(' LIMIT %d,%d', $offset, $limit); } $data = $this->db->fetchAllAssociative($query, [$this->model->getSourceType(), $this->model->getSourceId()]); @@ -322,31 +363,44 @@ public function getRequiredByWithPath(?int $offset = null, ?int $limit = null, ? $orderDirection = 'ASC'; } - $query = " + $params = [ + 'targetType' => $targetType, + 'targetId' => $targetId, + ]; + + $types = [ + 'targetType' => ParameterType::STRING, + 'targetId' => ParameterType::INTEGER, + ]; + + $query = sprintf( + ' SELECT id, type, path FROM ( SELECT d.sourceid as id, d.sourcetype as `type`, CONCAT(o.path, o.key) as `path` FROM dependencies d JOIN objects o ON o.id = d.sourceid - WHERE d.targettype = '" . $targetType. "' AND d.targetid = " . $targetId . " AND d.sourceType = 'object' + WHERE d.targettype = :targetType AND d.targetid = :targetId AND d.sourceType = "object" UNION SELECT d.sourceid as id, d.sourcetype as `type`, CONCAT(doc.path, doc.key) as `path` FROM dependencies d JOIN documents doc ON doc.id = d.sourceid - WHERE d.targettype = '" . $targetType. "' AND d.targetid = " . $targetId . " AND d.sourceType = 'document' + WHERE d.targettype = :targetType AND d.targetid = :targetId AND d.sourceType = "document" UNION SELECT d.sourceid as id, d.sourcetype as `type`, CONCAT(a.path, a.filename) as `path` FROM dependencies d JOIN assets a ON a.id = d.sourceid - WHERE d.targettype = '" . $targetType. "' AND d.targetid = " . $targetId . " AND d.sourceType = 'asset' + WHERE d.targettype = :targetType AND d.targetid = :targetId AND d.sourceType = "asset" ) dep - ORDER BY " . $orderBy . ' ' . $orderDirection; + ORDER BY %s %s', + $orderBy, $orderDirection + ); if (is_int($offset) && is_int($limit)) { - $query .= ' LIMIT ' . $offset . ', ' . $limit; + $query .= sprintf(' LIMIT %d,%d', $offset, $limit); } - return $this->db->fetchAllAssociative($query); + return $this->db->fetchAllAssociative($query, $params, $types); } /** @@ -354,6 +408,9 @@ public function getRequiredByWithPath(?int $offset = null, ?int $limit = null, ? */ public function getRequiredByTotalCount(): int { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM dependencies WHERE targettype = ? AND targetid = ?', [$this->model->getSourceType(), $this->model->getSourceId()]); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM dependencies WHERE targettype = ? AND targetid = ?', + [$this->model->getSourceType(), $this->model->getSourceId()] + ); } } diff --git a/models/Document/Dao.php b/models/Document/Dao.php index 9f7783eb..14e5c45b 100644 --- a/models/Document/Dao.php +++ b/models/Document/Dao.php @@ -15,6 +15,8 @@ namespace OpenDxp\Model\Document; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\ParameterType; use Exception; use OpenDxp\Db\Helper; use OpenDxp\Logger; @@ -38,9 +40,12 @@ class Dao extends Model\Element\Dao */ public function getById(int $id): void { - $data = $this->db->fetchAssociative("SELECT documents.*, tree_locks.locked FROM documents - LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = 'document' - WHERE documents.id = ?", [$id]); + $data = $this->db->fetchAssociative( + 'SELECT documents.*, tree_locks.locked FROM documents + LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = "document" + WHERE documents.id = ?', + [$id] + ); if ($data) { $data['published'] = (bool)$data['published']; @@ -193,22 +198,36 @@ public function updateWorkspaces(): void public function updateChildPaths(string $oldPath): array { //get documents to empty their cache - $documents = $this->db->fetchAllAssociative('SELECT id, CONCAT(`path`,`key`) as `path` FROM documents WHERE `path` like ?', [Helper::escapeLike($oldPath) . '%']); + $documents = $this->db->fetchAllAssociative( + 'SELECT id, CONCAT(`path`,`key`) as `path` FROM documents WHERE `path` LIKE ?', + [Helper::escapeLike($oldPath) . '%'] + ); $userId = '0'; if ($user = \OpenDxp\Tool\Admin::getCurrentUser()) { $userId = $user->getId(); } + $newPath = $this->model->getRealFullPath(); + //update documents child paths // we don't update the modification date here, as this can have side-effects when there's an unpublished version for an element - $this->db->executeQuery('update documents set `path` = replace(`path`,' . $this->db->quote($oldPath . '/') . ',' . $this->db->quote($this->model->getRealFullPath() . '/') . "), userModification = '" . $userId . "' where `path` like " . $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';'); + $this->db->executeStatement( + 'UPDATE documents SET `path` = REPLACE(`path`, ?, ?), userModification = ? WHERE `path` LIKE ?', + [$oldPath . '/', $newPath . '/', $userId, Helper::escapeLike($oldPath) . '/%'] + ); //update documents child permission paths - $this->db->executeQuery('update users_workspaces_document set cpath = replace(cpath,' . $this->db->quote($oldPath . '/') . ',' . $this->db->quote($this->model->getRealFullPath() . '/') . ') where cpath like ' . $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';'); + $this->db->executeStatement( + 'UPDATE users_workspaces_document SET cpath = REPLACE(cpath, ?, ?) WHERE cpath LIKE ?', + [$oldPath . '/', $newPath . '/', Helper::escapeLike($oldPath) . '/%'] + ); //update documents child properties paths - $this->db->executeQuery('update properties set cpath = replace(cpath,' . $this->db->quote($oldPath . '/') . ',' . $this->db->quote($this->model->getRealFullPath() . '/') . ') where cpath like ' . $this->db->quote(Helper::escapeLike($oldPath) . '/%') . ';'); + $this->db->executeStatement( + 'UPDATE properties SET cpath = REPLACE(cpath, ?, ?) WHERE cpath LIKE ?', + [$oldPath . '/', $newPath . '/', Helper::escapeLike($oldPath) . '/%'] + ); return $documents; } @@ -221,7 +240,10 @@ public function getCurrentFullPath(): ?string $path = null; try { - $path = $this->db->fetchOne('SELECT CONCAT(`path`,`key`) as `path` FROM documents WHERE id = ?', [$this->model->getId()]); + $path = $this->db->fetchOne( + 'SELECT CONCAT(`path`,`key`) as `path` FROM documents WHERE id = ?', + [$this->model->getId()] + ); } catch (Exception) { Logger::error('could not get current document path from DB'); } @@ -235,10 +257,16 @@ public function getVersionCountForUpdate(): int return 0; } - $versionCount = (int) $this->db->fetchOne('SELECT versionCount FROM documents WHERE id = ? FOR UPDATE', [$this->model->getId()]); + $versionCount = (int) $this->db->fetchOne( + 'SELECT versionCount FROM documents WHERE id = ? FOR UPDATE', + [$this->model->getId()] + ); if ($this->model instanceof PageSnippet) { - $versionCount2 = (int) $this->db->fetchOne("SELECT MAX(versionCount) FROM versions WHERE cid = ? AND ctype = 'document'", [$this->model->getId()]); + $versionCount2 = (int) $this->db->fetchOne( + 'SELECT MAX(versionCount) FROM versions WHERE cid = ? AND ctype = "document"', + [$this->model->getId()] + ); $versionCount = max($versionCount, $versionCount2); } @@ -257,18 +285,16 @@ public function getProperties(bool $onlyInherited = false, bool $onlyDirect = fa if ($onlyDirect) { $propertiesRaw = $this->db->fetchAllAssociative( - "SELECT * FROM properties WHERE cid = ? AND ctype='document'", + 'SELECT * FROM properties WHERE cid = ? AND ctype="document"', [$this->model->getId()] ); } else { $parentIds = $this->getParentIds(); $propertiesRaw = $this->db->fetchAllAssociative( - 'SELECT * FROM properties WHERE - ( - (cid IN (' . implode(',', $parentIds) . ") AND inheritable = 1) OR cid = ? - ) AND ctype='document'", - [$this->model->getId()] + 'SELECT * FROM properties WHERE ((cid IN (?) AND inheritable = 1) OR cid = ?) AND ctype="document"', + [$parentIds, $this->model->getId()], + [ArrayParameterType::INTEGER, ParameterType::INTEGER] ); } @@ -330,14 +356,14 @@ public function hasChildren(?bool $includingUnpublished = null, ?User $user = nu $sql = 'SELECT id FROM documents d WHERE parentId = ? '; if ($user && !$user->isAdmin()) { - $userIds = $user->getRoles(); + $userIds = array_map('intval', $user->getRoles()); $currentUserId = $user->getId(); $userIds[] = $currentUserId; $inheritedPermission = $this->isInheritingPermission('list', $userIds); $anyAllowedRowOrChildren = 'EXISTS(SELECT list FROM users_workspaces_document uwd WHERE userId IN (' . implode(',', $userIds) . ') AND list=1 AND LOCATE(CONCAT(d.path,d.`key`),cpath)=1 AND - NOT EXISTS(SELECT list FROM users_workspaces_document WHERE userId =' . $currentUserId . ' AND list=0 AND cpath = uwd.cpath))'; + NOT EXISTS(SELECT list FROM users_workspaces_document WHERE userId =' . (int) $currentUserId . ' AND list=0 AND cpath = uwd.cpath))'; $isDisallowedCurrentRow = 'EXISTS(SELECT list FROM users_workspaces_document WHERE userId IN (' . implode(',', $userIds) . ') AND cid = id AND list=0)'; $sql .= ' AND IF(' . $anyAllowedRowOrChildren . ',1,IF(' . $inheritedPermission . ', ' . $isDisallowedCurrentRow . ' = 0, 0)) = 1'; @@ -365,14 +391,14 @@ public function getChildAmount(?User $user = null): int } $sql = 'SELECT count(*) FROM documents d WHERE parentId = ? '; if ($user && !$user->isAdmin()) { - $userIds = $user->getRoles(); + $userIds = array_map('intval', $user->getRoles()); $currentUserId = $user->getId(); $userIds[] = $currentUserId; $inheritedPermission = $this->isInheritingPermission('list', $userIds); $anyAllowedRowOrChildren = 'EXISTS(SELECT list FROM users_workspaces_document uwd WHERE userId IN (' . implode(',', $userIds) . ') AND list=1 AND LOCATE(CONCAT(d.path,d.`key`),cpath)=1 AND - NOT EXISTS(SELECT list FROM users_workspaces_document WHERE userId =' . $currentUserId . ' AND list=0 AND cpath = uwd.cpath))'; + NOT EXISTS(SELECT list FROM users_workspaces_document WHERE userId =' . (int) $currentUserId . ' AND list=0 AND cpath = uwd.cpath))'; $isDisallowedCurrentRow = 'EXISTS(SELECT list FROM users_workspaces_document WHERE userId IN (' . implode(',', $userIds) . ') AND cid = id AND list=0)'; $sql .= ' AND IF(' . $anyAllowedRowOrChildren . ',1,IF(' . $inheritedPermission . ', ' . $isDisallowedCurrentRow . ' = 0, 0)) = 1'; @@ -418,16 +444,23 @@ public function hasSiblings(?bool $includingUnpublished = null): bool public function isLocked(): bool { // check for an locked element below this element - $belowLocks = $this->db->fetchOne("SELECT tree_locks.id FROM tree_locks - INNER JOIN documents ON tree_locks.id = documents.id - WHERE documents.path LIKE ? AND tree_locks.type = 'document' AND tree_locks.locked IS NOT NULL AND tree_locks.locked != '' LIMIT 1", [Helper::escapeLike($this->model->getRealFullPath()). '/%']); + $belowLocks = $this->db->fetchOne( + 'SELECT tree_locks.id FROM tree_locks + INNER JOIN documents ON tree_locks.id = documents.id + WHERE documents.path LIKE ? AND tree_locks.type = "document" AND tree_locks.locked IS NOT NULL AND tree_locks.locked != "" LIMIT 1', + [Helper::escapeLike($this->model->getRealFullPath()) . '/%'] + ); if ($belowLocks > 0) { return true; } $parentIds = $this->getParentIds(); - $inhertitedLocks = $this->db->fetchOne('SELECT id FROM tree_locks WHERE id IN (' . implode(',', $parentIds) . ") AND `type`='document' AND locked = 'propagate' LIMIT 1"); + $inhertitedLocks = $this->db->fetchOne( + 'SELECT id FROM tree_locks WHERE id IN (?) AND `type` = "document" AND locked = "propagate" LIMIT 1', + [$parentIds], + [ArrayParameterType::INTEGER] + ); return $inhertitedLocks > 0; } @@ -454,8 +487,16 @@ public function updateLocks(): void */ public function unlockPropagate(): array { - $lockIds = $this->db->fetchFirstColumn('SELECT id from documents WHERE `path` like ' . $this->db->quote(Helper::escapeLike($this->model->getRealFullPath()) . '/%') . ' OR id = ' . $this->model->getId()); - $this->db->executeStatement("DELETE FROM tree_locks WHERE `type` = 'document' AND id IN (" . implode(',', $lockIds) . ')'); + $lockIds = $this->db->fetchFirstColumn( + 'SELECT id FROM documents WHERE `path` LIKE ? OR id = ?', + [Helper::escapeLike($this->model->getRealFullPath()) . '/%', $this->model->getId()] + ); + + $this->db->executeStatement( + 'DELETE FROM tree_locks WHERE `type` = "document" AND id IN (?)', + [$lockIds], + [ArrayParameterType::INTEGER] + ); return $lockIds; } @@ -491,7 +532,11 @@ public function isAllowed(string $type, User $user): bool $userIds[] = $user->getId(); try { - $permissionsParent = $this->db->fetchOne('SELECT ' . $this->db->quoteIdentifier($type) . ' FROM users_workspaces_document WHERE cid IN (' . implode(',', $parentIds) . ') AND userId IN (' . implode(',', $userIds) . ') ORDER BY LENGTH(cpath) DESC, FIELD(userId, ' . $user->getId() . ') DESC, ' . $this->db->quoteIdentifier($type) . ' DESC LIMIT 1'); + $permissionsParent = $this->db->fetchOne( + 'SELECT ' . $this->db->quoteIdentifier($type) . ' FROM users_workspaces_document WHERE cid IN (?) AND userId IN (?) ORDER BY LENGTH(cpath) DESC, FIELD(userId, ?) DESC, ' . $this->db->quoteIdentifier($type) . ' DESC LIMIT 1', + [$parentIds, $userIds, $user->getId()], + [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER] + ); if ($permissionsParent) { return true; @@ -505,7 +550,11 @@ public function isAllowed(string $type, User $user): bool $path = '/'; } - $permissionsChildren = $this->db->fetchOne('SELECT list FROM users_workspaces_document WHERE cpath LIKE ? AND userId IN (' . implode(',', $userIds) . ') AND list = 1 LIMIT 1', [Helper::escapeLike($path) . '%']); + $permissionsChildren = $this->db->fetchOne( + 'SELECT list FROM users_workspaces_document WHERE cpath LIKE ? AND userId IN (?) AND list = 1 LIMIT 1', + [Helper::escapeLike($path) . '%', $userIds], + [ParameterType::STRING, ArrayParameterType::INTEGER] + ); if ($permissionsChildren) { return true; } @@ -552,7 +601,7 @@ public function getNextIndex(): int public function __isBasedOnLatestData(): bool { - $data = $this->db->fetchAssociative('SELECT modificationDate,versionCount from documents WHERE id = ?', [$this->model->getId()]); + $data = $this->db->fetchAssociative('SELECT modificationDate,versionCount FROM documents WHERE id = ?', [$this->model->getId()]); return $data['modificationDate'] == $this->model->__getDataVersionTimestamp() && $data['versionCount'] == $this->model->getVersionCount(); } diff --git a/models/Document/Email/Dao.php b/models/Document/Email/Dao.php index f0a13ed1..f4e225df 100644 --- a/models/Document/Email/Dao.php +++ b/models/Document/Email/Dao.php @@ -39,10 +39,13 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->fetchAssociative("SELECT documents.*, documents_email.*, tree_locks.locked FROM documents - LEFT JOIN documents_email ON documents.id = documents_email.id - LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = 'document' - WHERE documents.id = ?", [$this->model->getId()]); + $data = $this->db->fetchAssociative( + 'SELECT documents.*, documents_email.*, tree_locks.locked FROM documents + LEFT JOIN documents_email ON documents.id = documents_email.id + LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = "document" + WHERE documents.id = ?', + [$this->model->getId()] + ); if ($data) { $data['published'] = (bool)$data['published']; diff --git a/models/Document/Hardlink/Dao.php b/models/Document/Hardlink/Dao.php index f38e26e3..b38a4e15 100644 --- a/models/Document/Hardlink/Dao.php +++ b/models/Document/Hardlink/Dao.php @@ -38,10 +38,13 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->fetchAssociative("SELECT documents.*, documents_hardlink.*, tree_locks.locked FROM documents - LEFT JOIN documents_hardlink ON documents.id = documents_hardlink.id - LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = 'document' - WHERE documents.id = ?", [$this->model->getId()]); + $data = $this->db->fetchAssociative( + 'SELECT documents.*, documents_hardlink.*, tree_locks.locked FROM documents + LEFT JOIN documents_hardlink ON documents.id = documents_hardlink.id + LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = "document" + WHERE documents.id = ?', + [$this->model->getId()] + ); if ($data) { $data['published'] = (bool)$data['published']; diff --git a/models/Document/Link/Dao.php b/models/Document/Link/Dao.php index 6081c10e..610cf5c7 100644 --- a/models/Document/Link/Dao.php +++ b/models/Document/Link/Dao.php @@ -38,10 +38,13 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->fetchAssociative("SELECT documents.*, documents_link.*, tree_locks.locked FROM documents - LEFT JOIN documents_link ON documents.id = documents_link.id - LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = 'document' - WHERE documents.id = ?", [$this->model->getId()]); + $data = $this->db->fetchAssociative( + 'SELECT documents.*, documents_link.*, tree_locks.locked FROM documents + LEFT JOIN documents_link ON documents.id = documents_link.id + LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = "document" + WHERE documents.id = ?', + [$this->model->getId()] + ); if ($data) { $data['published'] = (bool)$data['published']; diff --git a/models/Document/Page/Dao.php b/models/Document/Page/Dao.php index 200bf48e..e20f6366 100644 --- a/models/Document/Page/Dao.php +++ b/models/Document/Page/Dao.php @@ -39,10 +39,13 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->fetchAssociative("SELECT documents.*, documents_page.*, tree_locks.locked FROM documents - LEFT JOIN documents_page ON documents.id = documents_page.id - LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = 'document' - WHERE documents.id = ?", [$this->model->getId()]); + $data = $this->db->fetchAssociative( + 'SELECT documents.*, documents_page.*, tree_locks.locked FROM documents + LEFT JOIN documents_page ON documents.id = documents_page.id + LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = "document" + WHERE documents.id = ?', + [$this->model->getId()] + ); if ($data) { $data['published'] = (bool)$data['published']; diff --git a/models/Document/PageSnippet/Dao.php b/models/Document/PageSnippet/Dao.php index 68ea8c1c..24294219 100644 --- a/models/Document/PageSnippet/Dao.php +++ b/models/Document/PageSnippet/Dao.php @@ -43,7 +43,10 @@ public function deleteAllEditables(): void */ public function getEditables(): array { - $editablesRaw = $this->db->fetchAllAssociative('SELECT * FROM documents_editables WHERE documentId = ?', [$this->model->getId()]); + $editablesRaw = $this->db->fetchAllAssociative( + 'SELECT * FROM documents_editables WHERE documentId = ?', + [$this->model->getId()] + ); $editables = []; $loader = OpenDxp::getContainer()->get(Document\Editable\Loader\EditableLoader::class); diff --git a/models/Document/Service/Dao.php b/models/Document/Service/Dao.php index dcfead1e..ed5048aa 100644 --- a/models/Document/Service/Dao.php +++ b/models/Document/Service/Dao.php @@ -39,7 +39,10 @@ public function getDocumentIdByPrettyUrlInSite(Site $site, string $path): int public function getTranslationSourceId(Document $document): mixed { - $sourceId = $this->db->fetchOne('SELECT sourceId FROM documents_translations WHERE id = ?', [$document->getId()]); + $sourceId = $this->db->fetchOne( + 'SELECT sourceId FROM documents_translations WHERE id = ?', + [$document->getId()] + ); if (!$sourceId) { return $document->getId(); } @@ -53,12 +56,18 @@ public function getTranslationSourceId(Document $document): mixed public function getTranslations(Document $document, string $task = 'open'): array { $sourceId = $this->getTranslationSourceId($document); - $data = $this->db->fetchAllAssociative('SELECT id,language FROM documents_translations WHERE sourceId IN(?, ?) UNION SELECT sourceId as id,"source" FROM documents_translations WHERE id = ?', [$sourceId, $document->getId(), $document->getId()]); + $data = $this->db->fetchAllAssociative( + 'SELECT id,language FROM documents_translations WHERE sourceId IN(?, ?) UNION SELECT sourceId as id,"source" FROM documents_translations WHERE id = ?', + [$sourceId, $document->getId(), $document->getId()] + ); if ($task === 'open') { $linkedData = []; foreach ($data as $value) { - $linkedData = $this->db->fetchAllAssociative('SELECT id,language FROM documents_translations WHERE sourceId = ? UNION SELECT sourceId as id,"source" FROM documents_translations WHERE id = ?', [$value['id'], $value['id']]); + $linkedData = $this->db->fetchAllAssociative( + 'SELECT id,language FROM documents_translations WHERE sourceId = ? UNION SELECT sourceId as id,"source" FROM documents_translations WHERE id = ?', + [$value['id'], $value['id']] + ); } if (count($linkedData) > 0) { @@ -118,7 +127,10 @@ public function removeTranslationLink(Document $document, Document $targetDocume $sourceId = $document->getId(); } - $newSourceId = $this->db->fetchOne('SELECT id FROM documents_translations WHERE id = ? AND sourceId = ?', [$targetDocument->getId(), $sourceId]); + $newSourceId = $this->db->fetchOne( + 'SELECT id FROM documents_translations WHERE id = ? AND sourceId = ?', + [$targetDocument->getId(), $sourceId] + ); if (empty($newSourceId)) { $sourceId = $document->getId(); diff --git a/models/Document/Snippet/Dao.php b/models/Document/Snippet/Dao.php index c9d3f7af..e907ca00 100644 --- a/models/Document/Snippet/Dao.php +++ b/models/Document/Snippet/Dao.php @@ -38,10 +38,13 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->fetchAssociative("SELECT documents.*, documents_snippet.*, tree_locks.locked FROM documents - LEFT JOIN documents_snippet ON documents.id = documents_snippet.id - LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = 'document' - WHERE documents.id = ?", [$this->model->getId()]); + $data = $this->db->fetchAssociative( + 'SELECT documents.*, documents_snippet.*, tree_locks.locked FROM documents + LEFT JOIN documents_snippet ON documents.id = documents_snippet.id + LEFT JOIN tree_locks ON documents.id = tree_locks.id AND tree_locks.type = "document" + WHERE documents.id = ?', + [$this->model->getId()] + ); if ($data) { $data['published'] = (bool)$data['published']; diff --git a/models/Element/Dao.php b/models/Element/Dao.php index 635383c0..942fd2f7 100644 --- a/models/Element/Dao.php +++ b/models/Element/Dao.php @@ -15,6 +15,8 @@ namespace OpenDxp\Model\Element; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\ParameterType; use Exception; use OpenDxp\Db\Helper; use OpenDxp\Model; @@ -85,11 +87,11 @@ public function InheritingPermission(string $type, array $userIds, string $table } $fullPath = $current->getPath() . $current->getKey(); - $sql = 'SELECT ' . $this->db->quoteIdentifier($type) . ' FROM users_workspaces_' . $tableSuffix . ' WHERE LOCATE(cpath, ?)=1 AND - userId IN (' . implode(',', $userIds) . ') - ORDER BY LENGTH(cpath) DESC, FIELD(userId, ' . end($userIds) . ') DESC, ' . $this->db->quoteIdentifier($type) . ' DESC LIMIT 1'; + $sql = 'SELECT ' . $this->db->quoteIdentifier($type) . ' FROM users_workspaces_' . $tableSuffix . ' WHERE LOCATE(cpath, ?) = 1 + AND userId IN (?) + ORDER BY LENGTH(cpath) DESC, FIELD(userId, ?) DESC, ' . $this->db->quoteIdentifier($type) . ' DESC LIMIT 1'; - return (int)$this->db->fetchOne($sql, [$fullPath]); + return (int)$this->db->fetchOne($sql, [$fullPath, $userIds, end($userIds)], [ParameterType::STRING, ArrayParameterType::INTEGER, ParameterType::INTEGER]); } /** @@ -117,11 +119,11 @@ protected function permissionByTypes(array $columns, User $user, string $tableSu $highestWorkspaceQuery = ' SELECT userId,cid,`'. implode('`,`', $columns) .'` FROM users_workspaces_'.$tableSuffix.' - WHERE cid IN (' . implode(',', $parentIds) . ') AND userId IN (' . implode(',', $userIds) . ') - ORDER BY LENGTH(cpath) DESC, FIELD(userId, ' . $currentUserId . ') DESC LIMIT 1 + WHERE cid IN (?) AND userId IN (?) + ORDER BY LENGTH(cpath) DESC, FIELD(userId, ?) DESC LIMIT 1 '; - $highestWorkspace = $this->db->fetchAssociative($highestWorkspaceQuery); + $highestWorkspace = $this->db->fetchAssociative($highestWorkspaceQuery, [$parentIds, $userIds, $currentUserId], [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER]); if ($highestWorkspace) { //if it's the current user, this is the permission that rules them all, no need to check others @@ -142,10 +144,10 @@ protected function permissionByTypes(array $columns, User $user, string $tableSu $roleWorkspaceSql = ' SELECT userId,`'. implode('`,`', $columns) .'` FROM users_workspaces_'.$tableSuffix.' - WHERE cid = ' . $highestWorkspace['cid'] . ' AND userId IN (' . implode(',', $userIds) . ') - ORDER BY FIELD(userId, ' . $currentUserId . ') DESC + WHERE cid = ? AND userId IN (?) + ORDER BY FIELD(userId, ?) DESC '; - $objectPermissions = $this->db->fetchAllAssociative($roleWorkspaceSql); + $objectPermissions = $this->db->fetchAllAssociative($roleWorkspaceSql, [$highestWorkspace['cid'], $userIds, $currentUserId], [ParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER]); //this performs the additive rule when conflicting rules with multiple roles, //breaks the loop when permission=1 is found and move on to check next permission type. @@ -178,10 +180,11 @@ private function checkChildrenForPathTraversal(string $tableSuffix, array $userI $permissionsChildren = $this->db->fetchOne(' SELECT list FROM users_workspaces_'.$tableSuffix.' as uw - WHERE cpath LIKE ? AND userId IN (' . implode(',', $userIds) . ') AND list = 1 - AND NOT EXISTS( SELECT list FROM users_workspaces_'.$tableSuffix.' WHERE cid = uw.cid AND list = 0 AND userId ='.end($userIds).') + WHERE cpath LIKE ? AND userId IN (?) AND list = 1 + AND NOT EXISTS( SELECT list FROM users_workspaces_'.$tableSuffix.' WHERE cid = uw.cid AND list = 0 AND userId = ?) LIMIT 1', - [Helper::escapeLike($path) . '%']); + [Helper::escapeLike($path) . '%', $userIds, end($userIds)], + [ParameterType::STRING, ArrayParameterType::INTEGER, ParameterType::INTEGER]); return (int)$permissionsChildren; } diff --git a/models/Element/PermissionChecker.php b/models/Element/PermissionChecker.php index e3ff6f05..19f3b030 100644 --- a/models/Element/PermissionChecker.php +++ b/models/Element/PermissionChecker.php @@ -16,6 +16,8 @@ namespace OpenDxp\Model\Element; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\ParameterType; use Exception; use OpenDxp\Db; use OpenDxp\Db\Helper; @@ -48,7 +50,7 @@ public static function check(ElementInterface $element, array $users): array } $db = Db::get(); $tableName = 'users_workspaces_'.$type; - $tableDesc = $db->fetchAllAssociative('describe '.$tableName); + $tableDesc = $db->fetchAllAssociative(sprintf('DESCRIBE %s', $tableName)); $result = [ 'columns' => [], @@ -92,13 +94,12 @@ public static function check(ElementInterface $element, array $users): array try { $permissionsParent = $db->fetchAssociative( - 'SELECT * FROM users_workspaces_'.$type.' , users u WHERE userId = u.id AND cid IN ('.implode( - ',', - $parentIds - ).') AND userId IN ('.implode( - ',', - $userIds - ).') ORDER BY LENGTH(cpath) DESC, FIELD(userId,'.$user->getId().') DESC, `' . $columnName . '` DESC LIMIT 1' + sprintf( + 'SELECT * FROM users_workspaces_%s, users u WHERE userId = u.id AND cid IN (?) AND userId IN (?) ORDER BY LENGTH(cpath) DESC, FIELD(userId,?) DESC, %s DESC LIMIT 1', + $type, $db->quoteIdentifier($columnName) + ), + [$parentIds, $userIds, $user->getId()], + [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER] ); if ($permissionsParent) { @@ -118,11 +119,9 @@ public static function check(ElementInterface $element, array $users): array } $permissionsChildren = $db->fetchAssociative( - 'SELECT list FROM users_workspaces_'.$type.', users u WHERE userId = u.id AND cpath LIKE ? AND userId IN ('.implode( - ',', - $userIds - ).') AND list = 1 LIMIT 1', - [Helper::escapeLike($path) .'%'] + sprintf('SELECT list FROM users_workspaces_%s, users u WHERE userId = u.id AND cpath LIKE ? AND userId IN (?) AND list = 1 LIMIT 1', $type), + [Helper::escapeLike($path) .'%', $userIds], + [ParameterType::STRING, ArrayParameterType::INTEGER] ); if ($permissionsChildren) { $result[$columnName] = (bool) $permissionsChildren[$columnName]; @@ -187,7 +186,7 @@ protected static function getUserPermissions(User $user, array &$details): void $details[] = self::createDetail($user, 'User Permissions'); $db = Db::get(); - $permissions = $db->fetchFirstColumn('select `key` from users_permission_definitions'); + $permissions = $db->fetchFirstColumn('SELECT `key` FROM users_permission_definitions'); foreach ($permissions as $permissionKey) { $entry = null; diff --git a/models/Element/Service.php b/models/Element/Service.php index 63d9f173..ae7cb6e9 100644 --- a/models/Element/Service.php +++ b/models/Element/Service.php @@ -25,6 +25,7 @@ use DeepCopy\Matcher\PropertyTypeMatcher; use DeepCopy\TypeMatcher\TypeMatcher; use Doctrine\Common\Collections\Collection; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder; use Exception; use League\Csv\EscapeFormula; @@ -324,8 +325,11 @@ public static function filterUnpublishedAdvancedElements(?array $data): array // now do the query; foreach ($mapping as $elementType => $idList) { $idList = array_keys($mapping[$elementType]); - $query = 'SELECT id FROM ' . $elementType . 's WHERE published=1 AND id IN (' . implode(',', $idList) . ');'; - $publishedIds = $db->fetchFirstColumn($query); + $publishedIds = $db->fetchFirstColumn( + sprintf('SELECT id FROM %ss WHERE published = 1 AND id IN (?)', $elementType), + [$idList], + [ArrayParameterType::INTEGER] + ); $publishedMapping[$elementType] = $publishedIds; } @@ -609,20 +613,30 @@ public static function findForbiddenPaths(string $type, Model\User $user): array } $workspaceCids = []; - $userWorkspaces = $db->fetchAllAssociative('SELECT cpath, cid, list FROM users_workspaces_' . $type . ' WHERE userId = ?', [$user->getId()]); + $userWorkspaces = $db->fetchAllAssociative( + sprintf('SELECT cpath, cid, list FROM users_workspaces_%s WHERE userId = ?', $type), + [$user->getId()] + ); // this collects the array that are on user-level, which have top priority foreach ($userWorkspaces as $userWorkspace) { $workspaceCids[] = $userWorkspace['cid']; } if ($userRoleIds = $user->getRoles()) { - $roleWorkspacesSql = 'SELECT cpath, userid, max(list) as list FROM users_workspaces_' . $type . ' WHERE userId IN (' . implode(',', $userRoleIds) . ')'; + $roleWorkspacesSql = sprintf( + 'SELECT cpath, userid, MAX(list) as list FROM users_workspaces_%s WHERE userId IN (?)', + $type + ); + $roleParams = [$userRoleIds]; + $roleTypes = [ArrayParameterType::INTEGER]; if ($workspaceCids) { - $roleWorkspacesSql .= ' AND cid NOT IN (' . implode(',', $workspaceCids) . ')'; + $roleWorkspacesSql .= ' AND cid NOT IN (?)'; + $roleParams[] = $workspaceCids; + $roleTypes[] = ArrayParameterType::INTEGER; } $roleWorkspacesSql .= ' GROUP BY cpath'; - $roleWorkspaces = $db->fetchAllAssociative($roleWorkspacesSql); + $roleWorkspaces = $db->fetchAllAssociative($roleWorkspacesSql, $roleParams, $roleTypes); } $uniquePaths = []; diff --git a/models/Element/Tag/Dao.php b/models/Element/Tag/Dao.php index bb60a1ec..f3056b35 100644 --- a/models/Element/Tag/Dao.php +++ b/models/Element/Tag/Dao.php @@ -15,6 +15,8 @@ namespace OpenDxp\Model\Element\Tag; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\ParameterType; use Exception; use OpenDxp\Db\Helper; use OpenDxp\Model; @@ -79,7 +81,10 @@ public function save(): bool //check for id-path and update it, if path has changed -> update all other tags that have idPath == idPath/id if ($originalIdPath && $originalIdPath != $this->model->getIdPath()) { - $this->db->executeQuery('UPDATE tags SET idPath = REPLACE(idPath, ?, ?) WHERE idPath LIKE ?;', [$originalIdPath, $this->model->getIdPath(), Helper::escapeLike($originalIdPath) . $this->model->getId() . '/%']); + $this->db->executeStatement( + 'UPDATE tags SET idPath = REPLACE(idPath, ?, ?) WHERE idPath LIKE ?', + [$originalIdPath, $this->model->getIdPath(), Helper::escapeLike($originalIdPath) . $this->model->getId() . '/%'] + ); } $this->db->commit(); @@ -102,12 +107,14 @@ public function delete(): array $this->db->beginTransaction(); try { - $toRemoveTagIds = $this->db->fetchFirstColumn('SELECT id FROM tags WHERE ' . Helper::quoteInto($this->db, 'idPath LIKE ?', Helper::escapeLike($this->model->getIdPath()) . $this->model->getId() . '/%')); + $toRemoveTagIds = $this->db->fetchFirstColumn( + 'SELECT id FROM tags WHERE idPath LIKE ?', + [Helper::escapeLike($this->model->getIdPath()) . $this->model->getId() . '/%'] + ); $toRemoveTagIds[] = $this->model->getId(); - $implodedTagIds = implode(',', $toRemoveTagIds); - $this->db->executeStatement('DELETE FROM tags_assignment WHERE tagid IN (' . $implodedTagIds . ')'); - $this->db->executeStatement('DELETE FROM tags WHERE id IN (' . $implodedTagIds . ')'); + $this->db->executeStatement('DELETE FROM tags_assignment WHERE tagid IN (?)', [$toRemoveTagIds], [ArrayParameterType::INTEGER]); + $this->db->executeStatement('DELETE FROM tags WHERE id IN (?)', [$toRemoveTagIds], [ArrayParameterType::INTEGER]); $this->db->commit(); return $toRemoveTagIds; @@ -125,7 +132,10 @@ public function delete(): array public function getTagsForElement(string $cType, int $cId): array { $tags = []; - $tagIds = $this->db->fetchFirstColumn('SELECT tagid FROM tags_assignment WHERE cid = ? AND ctype = ?', [$cId, $cType]); + $tagIds = $this->db->fetchFirstColumn( + 'SELECT tagid FROM tags_assignment WHERE cid = ? AND ctype = ?', + [$cId, $cType] + ); foreach ($tagIds as $tagId) { $tags[] = Model\Element\Tag::getById($tagId); @@ -186,11 +196,7 @@ public function setTagsForElement(string $cType, int $cId, array $tags): void public function batchAssignTagsToElement(string $cType, array $cIds, array $tagIds, bool $replace): void { if ($replace) { - $quotedCIds = []; - foreach ($cIds as $cId) { - $quotedCIds[] = $this->db->quote($cId); - } - $this->db->executeStatement('DELETE FROM tags_assignment WHERE ' . 'ctype = ' . $this->db->quote($cType) . ' AND cid IN (' . implode(',', $quotedCIds) . ')'); + $this->db->executeStatement('DELETE FROM tags_assignment WHERE ctype = ? AND cid IN (?)', [$cType, $cIds], [ParameterType::STRING, ArrayParameterType::INTEGER]); } foreach ($tagIds as $tagId) { @@ -230,12 +236,9 @@ public function getElementsForTag( if ($considerChildTags) { $select->innerJoin('tags_assignment', 'tags', 'tags', 'tags.id = tags_assignment.tagid'); - $select->andWhere( - '(' . - Helper::quoteInto($this->db, 'tags_assignment.tagid = ?', $tag->getId()) . ' OR ' . - Helper::quoteInto($this->db, 'tags.idPath LIKE ?', Helper::escapeLike($tag->getFullIdPath()) . '%') - . ')' - ); + $select->andWhere('tags_assignment.tagid = :considerTagId OR tags.idPath LIKE :considerTagPath') + ->setParameter('considerTagId', $tag->getId(), ParameterType::INTEGER) + ->setParameter('considerTagPath', Helper::escapeLike($tag->getFullIdPath()) . '%', ParameterType::STRING); } else { $select->andWhere('tags_assignment.tagid = :tagId')->setParameter('tagId', $tag->getId()); } @@ -243,20 +246,16 @@ public function getElementsForTag( $select->innerJoin('tags_assignment', $map[$type][0], 'el', 'tags_assignment.cId = el.id'); if ($subtypes !== []) { - foreach ($subtypes as $subType) { - $quotedSubTypes[] = $this->db->quote($subType); - } - $select->andWhere('`type` IN (' . implode(',', $quotedSubTypes) . ')'); + $select->andWhere($select->expr()->in('`type`', ':subtypes')) + ->setParameter('subtypes', $subtypes, ArrayParameterType::STRING); } if ('object' === $type && $classNames !== []) { - foreach ($classNames as $cName) { - $quotedClassNames[] = $this->db->quote($cName); - } - $select->andWhere('className IN ( ' . implode(',', $quotedClassNames) . ' )'); + $select->andWhere($select->expr()->in('className', ':classNames')) + ->setParameter('classNames', $classNames, ArrayParameterType::STRING); } - $res = $this->db->executeQuery((string) $select, $select->getParameters()); + $res = $select->executeQuery(); while ($row = $res->fetchAssociative()) { $el = $map[$type][1]::getById($row['cid']); diff --git a/models/Element/Traits/ScheduledTasksDaoTrait.php b/models/Element/Traits/ScheduledTasksDaoTrait.php index bb07d45e..ab4efe79 100644 --- a/models/Element/Traits/ScheduledTasksDaoTrait.php +++ b/models/Element/Traits/ScheduledTasksDaoTrait.php @@ -16,6 +16,8 @@ namespace OpenDxp\Model\Element\Traits; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\ParameterType; use OpenDxp\Model\Element\Service; /** @@ -32,11 +34,19 @@ public function deleteAllTasks(array $ignoreIds = []): void { $type = Service::getElementType($this->model); if ($this->model->getId()) { - $where = '`cid` = ' . $this->model->getId() . ' AND `ctype` = ' . $this->db->quote($type); if ($ignoreIds) { - $where .= ' AND `id` NOT IN (' . implode(',', $ignoreIds) . ')'; + $this->db->executeStatement( + 'DELETE FROM schedule_tasks WHERE `cid` = ? AND `ctype` = ? AND `id` NOT IN (?)', + [$this->model->getId(), $type, $ignoreIds], + [ParameterType::INTEGER, ParameterType::STRING, ArrayParameterType::INTEGER] + ); + } else { + $this->db->executeStatement( + 'DELETE FROM schedule_tasks WHERE `cid` = ? AND `ctype` = ?', + [$this->model->getId(), $type], + [ParameterType::INTEGER, ParameterType::STRING] + ); } - $this->db->executeStatement('DELETE FROM schedule_tasks WHERE ' . $where); } } } diff --git a/models/Element/Traits/VersionDaoTrait.php b/models/Element/Traits/VersionDaoTrait.php index 921f4c7a..6c438c81 100644 --- a/models/Element/Traits/VersionDaoTrait.php +++ b/models/Element/Traits/VersionDaoTrait.php @@ -30,13 +30,19 @@ trait VersionDaoTrait public function getLatestVersion(?int $userId = null, bool $includingPublished = false): ?Version { $operator = $includingPublished ? '>=' : '>'; - $versionId = $this->db->fetchOne('SELECT id FROM versions WHERE cid = :cid AND ctype = :ctype AND (`date` ' . $operator . ' :mdate OR versionCount ' . $operator . ' :versionCount) AND ((autoSave = 1 AND userId = :userId) OR autoSave = 0) ORDER BY `versionCount` DESC LIMIT 1', [ - 'cid' => $this->model->getId(), - 'ctype' => Element\Service::getElementType($this->model), - 'userId' => $userId, - 'mdate' => $this->model->getModificationDate(), - 'versionCount' => $this->model->getVersionCount(), - ]); + $versionId = $this->db->fetchOne( + sprintf( + 'SELECT id FROM versions WHERE cid = :cid AND ctype = :ctype AND (`date` %1$s :mdate OR versionCount %1$s :versionCount) AND ((autoSave = 1 AND userId = :userId) OR autoSave = 0) ORDER BY `versionCount` DESC LIMIT 1', + $operator + ), + [ + 'cid' => $this->model->getId(), + 'ctype' => Element\Service::getElementType($this->model), + 'userId' => $userId, + 'mdate' => $this->model->getModificationDate(), + 'versionCount' => $this->model->getVersionCount(), + ] + ); if ($versionId) { return Version::getById($versionId); diff --git a/models/Element/WorkflowState/Dao.php b/models/Element/WorkflowState/Dao.php index cc1466a0..5ff20382 100644 --- a/models/Element/WorkflowState/Dao.php +++ b/models/Element/WorkflowState/Dao.php @@ -30,7 +30,10 @@ class Dao extends Model\Dao\AbstractDao */ public function getByPrimary(int $cid, string $ctype, string $workflow): void { - $data = $this->db->fetchAssociative('SELECT * FROM element_workflow_state WHERE cid = ? AND ctype = ? AND workflow = ?', [$cid, $ctype, $workflow]); + $data = $this->db->fetchAssociative( + 'SELECT * FROM element_workflow_state WHERE cid = ? AND ctype = ? AND workflow = ?', + [$cid, $ctype, $workflow] + ); if (!$data) { throw new Model\Exception\NotFoundException('WorkflowStatus item for workflow ' . $workflow . ' with cid ' . $cid . ' and ctype ' . $ctype . ' not found'); diff --git a/models/Site/Dao.php b/models/Site/Dao.php index bd3e14fe..d10300cd 100644 --- a/models/Site/Dao.php +++ b/models/Site/Dao.php @@ -54,7 +54,10 @@ public function getByRootId(int $id): void */ public function getByDomain(string $domain): void { - $data = $this->db->fetchAssociative('SELECT * FROM sites WHERE mainDomain = ? OR domains LIKE ?', [$domain, '%"' . $domain . '"%']); + $data = $this->db->fetchAssociative( + 'SELECT * FROM sites WHERE mainDomain = ? OR domains LIKE ?', + [$domain, '%"' . $domain . '"%'] + ); if (!$data) { // check for wildcards // @TODO: refactor this to be more clear diff --git a/models/Translation/Dao.php b/models/Translation/Dao.php index 873929cc..66491598 100644 --- a/models/Translation/Dao.php +++ b/models/Translation/Dao.php @@ -47,10 +47,15 @@ public function getDatabaseTableName(): string public function getByKey(string $key, ?array $languages = null): void { if (is_array($languages)) { - $sql = 'SELECT * FROM ' . $this->getDatabaseTableName() . ' WHERE `key` = :key - AND `language` IN (:languages) ORDER BY `creationDate` '; + $sql = sprintf( + 'SELECT * FROM %s WHERE `key` = :key AND `language` IN (:languages) ORDER BY `creationDate`', + $this->getDatabaseTableName() + ); } else { - $sql ='SELECT * FROM ' . $this->getDatabaseTableName() . ' WHERE `key` = :key ORDER BY `creationDate` '; + $sql = sprintf( + 'SELECT * FROM %s WHERE `key` = :key ORDER BY `creationDate`', + $this->getDatabaseTableName() + ); } $data = $this->db->fetchAllAssociative($sql, @@ -130,7 +135,12 @@ public function delete(): void */ public function getAvailableLanguages(): array { - $l = $this->db->fetchAllAssociative('SELECT * FROM ' . $this->getDatabaseTableName() . ' GROUP BY `language`;'); + $l = $this->db->createQueryBuilder() + ->select('*') + ->from($this->getDatabaseTableName()) + ->groupBy($this->db->quoteIdentifier('language')) + ->executeQuery() + ->fetchAllAssociative(); $languages = []; foreach ($l as $values) { @@ -169,7 +179,7 @@ public function isAValidDomain(string $domain): bool return false; } - $this->db->fetchOne(sprintf('SELECT * FROM translations_%s LIMIT 1;', $domain)); + $this->db->fetchOne(sprintf('SELECT * FROM translations_%s LIMIT 1', $domain)); return true; } catch (Exception) { @@ -185,7 +195,8 @@ public function createOrUpdateTable(): void throw new Exception('Domain is missing to create new translation domain'); } - $this->db->executeQuery('CREATE TABLE IF NOT EXISTS `' . $table . "` ( + $this->db->executeQuery(sprintf( + "CREATE TABLE IF NOT EXISTS `%s` ( `key` varchar(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '', `type` varchar(10) DEFAULT NULL, `language` varchar(10) NOT NULL DEFAULT '', @@ -196,7 +207,9 @@ public function createOrUpdateTable(): void `userModification` int(11) unsigned DEFAULT NULL, PRIMARY KEY (`key`,`language`), KEY `language` (`language`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", + $table + )); } protected function updateModificationInfos(): void diff --git a/models/Translation/Listing/Dao.php b/models/Translation/Listing/Dao.php index 5804bb58..00cd0765 100644 --- a/models/Translation/Listing/Dao.php +++ b/models/Translation/Listing/Dao.php @@ -15,6 +15,7 @@ namespace OpenDxp\Model\Translation\Listing; +use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder; use OpenDxp\Cache; use OpenDxp\Model; @@ -135,7 +136,7 @@ public function load(): array public function isCacheable(): bool { - $count = $this->db->fetchOne('SELECT COUNT(*) FROM ' . $this->getDatabaseTableName()); + $count = $this->db->fetchOne(sprintf('SELECT COUNT(*) FROM %s', $this->getDatabaseTableName())); $cacheLimit = Model\Translation\Listing::getCacheLimit(); return $count <= $cacheLimit; @@ -143,17 +144,16 @@ public function isCacheable(): bool public function cleanup(): void { - $keysToDelete = $this->db->fetchFirstColumn('SELECT `key` FROM ' . $this->getDatabaseTableName() . ' as tbl1 WHERE - (SELECT count(*) FROM ' . $this->getDatabaseTableName() . " WHERE `key` = tbl1.`key` AND (`text` IS NULL OR `text` = '')) - = (SELECT count(*) FROM " . $this->getDatabaseTableName() . ' WHERE `key` = tbl1.`key`) GROUP BY `key`;'); + $table = $this->getDatabaseTableName(); + $keysToDelete = $this->db->fetchFirstColumn(sprintf( + 'SELECT `key` FROM %s as tbl1 WHERE + (SELECT count(*) FROM %s WHERE `key` = tbl1.`key` AND (`text` IS NULL OR `text` = "")) + = (SELECT count(*) FROM %s WHERE `key` = tbl1.`key`) GROUP BY `key`', + $table, $table, $table + )); if ($keysToDelete) { - $preparedKeys = []; - foreach ($keysToDelete as $value) { - $preparedKeys[] = $this->db->quote($value); - } - - $this->db->executeStatement('DELETE FROM ' . $this->getDatabaseTableName() . ' WHERE ' . '`key` IN (' . implode(',', $preparedKeys) . ')'); + $this->db->executeStatement(sprintf('DELETE FROM %s WHERE `key` IN (?)', $this->getDatabaseTableName()), [$keysToDelete], [ArrayParameterType::STRING]); } } diff --git a/models/User/AbstractUser/Dao.php b/models/User/AbstractUser/Dao.php index f043c85d..68e68e7e 100644 --- a/models/User/AbstractUser/Dao.php +++ b/models/User/AbstractUser/Dao.php @@ -33,7 +33,10 @@ class Dao extends Model\Dao\AbstractDao public function getById(int $id): void { if ($this->model->getType()) { - $data = $this->db->fetchAssociative('SELECT * FROM users WHERE `type` = ? AND id = ?', [$this->model->getType(), $id]); + $data = $this->db->fetchAssociative( + 'SELECT * FROM users WHERE `type` = ? AND id = ?', + [$this->model->getType(), $id] + ); } else { $data = $this->db->fetchAssociative('SELECT * FROM users WHERE `id` = ?', [$id]); } @@ -51,7 +54,10 @@ public function getById(int $id): void */ public function getByName(string $name): void { - $data = $this->db->fetchAssociative('SELECT * FROM users WHERE `type` = ? AND `name` = ?', [$this->model->getType(), $name]); + $data = $this->db->fetchAssociative( + 'SELECT * FROM users WHERE `type` = ? AND `name` = ?', + [$this->model->getType(), $name] + ); if ($data) { $data = $this->castUserDataToBoolean($data); diff --git a/models/User/UserRole/Dao.php b/models/User/UserRole/Dao.php index 5868ff0a..2495f7b1 100644 --- a/models/User/UserRole/Dao.php +++ b/models/User/UserRole/Dao.php @@ -61,7 +61,10 @@ public function loadWorkspaces(): void $workspaces = []; $baseClassName = Element\Service::getBaseClassNameForElement($type); $className = '\\OpenDxp\\Model\\User\\Workspace\\' . $baseClassName; - $result = $this->db->fetchAllAssociative('SELECT * FROM users_workspaces_' . $type . ' WHERE userId = ?', [$this->model->getId()]); + $result = $this->db->fetchAllAssociative( + sprintf('SELECT * FROM users_workspaces_%s WHERE userId = ?', $type), + [$this->model->getId()] + ); foreach ($result as $row) { $workspace = new $className(); $row['list'] = (bool)$row['list']; diff --git a/models/Version/Adapter/DatabaseVersionStorageAdapter.php b/models/Version/Adapter/DatabaseVersionStorageAdapter.php index e00b4216..a84af846 100644 --- a/models/Version/Adapter/DatabaseVersionStorageAdapter.php +++ b/models/Version/Adapter/DatabaseVersionStorageAdapter.php @@ -37,10 +37,13 @@ public function save(Version $version, string $metaData, mixed $binaryDataStream $contents = stream_get_contents($binaryDataStream); } - $query = 'INSERT INTO ' . self::versionsTableName . '(`id`, `cid`, `ctype`, `metaData`, `binaryData`) VALUES (?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE `metaData` = ?, `binaryData` = ?'; + $query = sprintf( + 'INSERT INTO %s (`id`, `cid`, `ctype`, `metaData`, `binaryData`) VALUES (?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE `metaData` = ?, `binaryData` = ?', + self::versionsTableName + ); - $this->databaseConnection->executeQuery( + $this->databaseConnection->executeStatement( $query, [ $version->getId(), @@ -64,12 +67,14 @@ protected function loadData(int $id, { $dataColumn = $binaryData ? 'binaryData' : 'metaData'; - return $this->databaseConnection->fetchOne('SELECT ' . $dataColumn . ' FROM ' . self::versionsTableName . ' WHERE id = :id AND cid = :cid and ctype = :ctype', + return $this->databaseConnection->fetchOne( + sprintf('SELECT %s FROM %s WHERE id = :id AND cid = :cid AND ctype = :ctype', $this->databaseConnection->quoteIdentifier($dataColumn), self::versionsTableName), [ 'id' => $id, 'cid' => $cId, 'ctype' => $cType, - ]); + ] + ); } public function loadMetaData(Version $version): ?string diff --git a/models/Version/Dao.php b/models/Version/Dao.php index 0b190c4f..3afe4923 100644 --- a/models/Version/Dao.php +++ b/models/Version/Dao.php @@ -15,6 +15,8 @@ namespace OpenDxp\Model\Version; +use Doctrine\DBAL\ArrayParameterType; +use Doctrine\DBAL\ParameterType; use OpenDxp; use OpenDxp\Db\Helper; use OpenDxp\Logger; @@ -93,7 +95,11 @@ public function isVersionUsedInScheduler(Model\Version $version): bool public function getBinaryFileIdForHash(string $hash): ?int { - $id = $this->db->fetchOne('SELECT IFNULL(binaryFileId, id) FROM versions WHERE binaryFileHash = ? AND cid = ? AND storageType = ? ORDER BY id ASC LIMIT 1', [$hash, $this->model->getCid(), $this->model->getStorageType()]); + $id = $this->db->fetchOne( + 'SELECT IFNULL(binaryFileId, id) FROM versions WHERE binaryFileHash = ? AND cid = ? AND storageType = ? ORDER BY id ASC LIMIT 1', + [$hash, $this->model->getCid(), $this->model->getStorageType()] + ); + if (!$id) { return null; } @@ -103,7 +109,10 @@ public function getBinaryFileIdForHash(string $hash): ?int public function isBinaryHashInUse(?string $hash): bool { - $count = $this->db->fetchOne('SELECT count(*) FROM versions WHERE binaryFileHash = ? AND cid = ?', [$hash, $this->model->getCid()]); + $count = $this->db->fetchOne( + 'SELECT COUNT(*) FROM versions WHERE binaryFileHash = ? AND cid = ?', + [$hash, $this->model->getCid()] + ); return $count > 1; } @@ -116,13 +125,10 @@ public function isBinaryHashInUse(?string $hash): bool */ public function maintenanceGetOutdatedVersions(array $elementTypes, array $ignoreIds = []): array { - $ignoreIdsList = implode(',', $ignoreIds); - if (!$ignoreIdsList) { - $ignoreIdsList = '0'; // set a default to avoid SQL errors (there's no version with ID 0) - } + $ignoreIds = $ignoreIds ?: [0]; // fallback to [0] to avoid empty IN () — no version has ID 0 $versionIds = []; - Logger::debug("ignore ID's: " . $ignoreIdsList); + Logger::debug(sprintf('ignore ID\'s: %s', implode(',', $ignoreIds))); if ($elementTypes !== []) { $count = 0; @@ -131,18 +137,32 @@ public function maintenanceGetOutdatedVersions(array $elementTypes, array $ignor if (isset($elementType['days'])) { // by days $deadline = time() - ($elementType['days'] * 86400); - $tmpVersionIds = $this->db->fetchFirstColumn('SELECT id FROM versions as a WHERE ctype = ? AND date < ? AND public=0 AND id NOT IN (' . $ignoreIdsList . ')', [$elementType['elementType'], $deadline]); + $tmpVersionIds = $this->db->fetchFirstColumn( + 'SELECT id FROM versions as a WHERE ctype = ? AND date < ? AND public=0 AND id NOT IN (?)', + [$elementType['elementType'], $deadline, $ignoreIds], + [ParameterType::STRING, ParameterType::INTEGER, ArrayParameterType::INTEGER] + ); + $versionIds = [...$versionIds, ...$tmpVersionIds]; } else { // by steps - $versionData = $this->db->executeQuery('SELECT cid FROM versions WHERE ctype = ? AND public=0 AND id NOT IN (' . $ignoreIdsList . ') GROUP BY cid HAVING COUNT(*) > ? LIMIT 1000', [$elementType['elementType'], $elementType['steps'] + 1]); + $versionData = $this->db->executeQuery( + 'SELECT cid FROM versions WHERE ctype = ? AND public=0 AND id NOT IN (?) GROUP BY cid HAVING COUNT(*) > ? LIMIT 1000', + [$elementType['elementType'], $ignoreIds, $elementType['steps'] + 1], + [ParameterType::STRING, ArrayParameterType::INTEGER, ParameterType::INTEGER] + ); + while ($versionInfo = $versionData->fetchAssociative()) { $count++; - $elementVersions = $this->db->fetchFirstColumn('SELECT id FROM versions WHERE cid=? AND ctype = ? AND public=0 AND id NOT IN ('.$ignoreIdsList.') ORDER BY id DESC LIMIT '.($elementType['steps'] + 1).', '.PHP_INT_MAX, [$versionInfo['cid'], $elementType['elementType']]); + $elementVersions = $this->db->fetchFirstColumn( + sprintf('SELECT id FROM versions WHERE cid = ? AND ctype = ? AND public = 0 AND id NOT IN (?) ORDER BY id DESC LIMIT %d, %d', $elementType['steps'] + 1, PHP_INT_MAX), + [$versionInfo['cid'], $elementType['elementType'], $ignoreIds], + [ParameterType::INTEGER, ParameterType::STRING, ArrayParameterType::INTEGER] + ); $versionIds = [...$versionIds, ...$elementVersions]; - Logger::info($versionInfo['cid'].'(object '.$count.') Vcount '.count($versionIds)); + Logger::info(sprintf('%s (object %d) Vcount %d', $versionInfo['cid'], $count, count($versionIds))); // call the garbage collector if memory consumption is > 100MB if (memory_get_usage() > 100000000 && ($count % 100 === 0)) { @@ -162,7 +182,8 @@ public function maintenanceGetOutdatedVersions(array $elementTypes, array $ignor } } } - Logger::info('return ' . count($versionIds) . " ids\n"); + + Logger::info(sprintf('return %d ids', count($versionIds))); return array_map(intval(...), $versionIds); } From cbcc77387efa584af7aa5dfb4e7fae1936e03d86 Mon Sep 17 00:00:00 2001 From: Stefan Hagspiel Date: Mon, 23 Feb 2026 08:59:11 +0100 Subject: [PATCH 2/6] Mark helper methods in `Db/Helper.php` as deprecated --- lib/Db/Helper.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/Db/Helper.php b/lib/Db/Helper.php index 79afe2f1..45b63e25 100644 --- a/lib/Db/Helper.php +++ b/lib/Db/Helper.php @@ -50,6 +50,9 @@ public static function upsert(Connection $connection, string $table, array $data } } + /** + * @deprecated since OpenDXP 1.3 and will be removed in 2.0. + */ public static function fetchPairs(Connection $db, string $sql, array $params = [], array $types = []): array { $stmt = $db->executeQuery($sql, $params, $types); @@ -63,6 +66,9 @@ public static function fetchPairs(Connection $db, string $sql, array $params = [ return $data; } + /** + * @deprecated since OpenDXP 1.3 and will be removed in 2.0. + */ public static function selectAndDeleteWhere(Connection $db, string $table, string $idColumn = 'id', string $where = ''): void { $sql = 'SELECT ' . $db->quoteIdentifier($idColumn) . ' FROM ' . $table; @@ -101,6 +107,9 @@ public static function queryIgnoreError(Connection $db, string $sql, array $excl return null; } + /** + * @deprecated since OpenDXP 1.3 and will be removed in 2.0. Use parameterized queries with ? or :name placeholders instead. + */ public static function quoteInto(Connection $db, string $text, mixed $value, int|string|Type|null $type = null, ?int $count = null): array|string { if ($count === null) { From e17512ffd5cffc2f7499690602369f7ee623c663 Mon Sep 17 00:00:00 2001 From: Stefan Hagspiel Date: Mon, 23 Feb 2026 09:11:39 +0100 Subject: [PATCH 3/6] Standardize variable naming for SQL parameters in `UrlSlug` and `Concrete` classes --- models/DataObject/ClassDefinition/Data/UrlSlug.php | 4 ++-- models/DataObject/Concrete.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/models/DataObject/ClassDefinition/Data/UrlSlug.php b/models/DataObject/ClassDefinition/Data/UrlSlug.php index 8a3d8a31..a381fb76 100644 --- a/models/DataObject/ClassDefinition/Data/UrlSlug.php +++ b/models/DataObject/ClassDefinition/Data/UrlSlug.php @@ -186,10 +186,10 @@ public function save(Localizedfield|AbstractData|\OpenDxp\Model\DataObject\Objec 'fieldname' => $this->getName(), ]; $this->enrichDataRow($object, $params, $classId, $deleteDescriptor, 'objectId'); - [$conditionParts, $params] = Model\DataObject\Service::buildConditionPartsFromDescriptor($deleteDescriptor); + [$conditionParts, $sqlParams] = Model\DataObject\Service::buildConditionPartsFromDescriptor($deleteDescriptor); $db->executeStatement( sprintf('DELETE FROM %s WHERE %s', Model\DataObject\Data\UrlSlug::TABLE_NAME, implode(' AND ', $conditionParts)), - $params + $sqlParams ); // now save the new data if (is_array($slugs)) { diff --git a/models/DataObject/Concrete.php b/models/DataObject/Concrete.php index 6a77e338..90803048 100644 --- a/models/DataObject/Concrete.php +++ b/models/DataObject/Concrete.php @@ -740,11 +740,11 @@ public function __clone(): void protected function doRetrieveData(array $descriptor, string $table): array { $db = Db::get(); - [$conditionParts, $params] = Service::buildConditionPartsFromDescriptor($descriptor); + [$conditionParts, $sqlParams] = Service::buildConditionPartsFromDescriptor($descriptor); return $db->fetchAllAssociative( sprintf('SELECT * FROM %s WHERE %s', $table, implode(' AND ', $conditionParts)), - $params + $sqlParams ); } From c354ea79b649717cb0dce32af1d65e0529b28386 Mon Sep 17 00:00:00 2001 From: Stefan Hagspiel Date: Mon, 23 Feb 2026 09:19:37 +0100 Subject: [PATCH 4/6] Refactor Classificationstore DAO classes to replace QueryBuilder usage with `fetchAssociative` and enforce consistent query style --- .../CollectionConfig/Dao.php | 23 +++++-------- .../Classificationstore/GroupConfig/Dao.php | 34 +++++++------------ .../Classificationstore/KeyConfig/Dao.php | 23 +++++-------- .../Classificationstore/StoreConfig/Dao.php | 22 +++++------- 4 files changed, 36 insertions(+), 66 deletions(-) diff --git a/models/DataObject/Classificationstore/CollectionConfig/Dao.php b/models/DataObject/Classificationstore/CollectionConfig/Dao.php index 234521a0..6f89d71a 100644 --- a/models/DataObject/Classificationstore/CollectionConfig/Dao.php +++ b/models/DataObject/Classificationstore/CollectionConfig/Dao.php @@ -39,13 +39,10 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->createQueryBuilder() - ->select('*') - ->from(self::TABLE_NAME_COLLECTIONS) - ->where('id = :id') - ->setParameter('id', $this->model->getId()) - ->executeQuery() - ->fetchAssociative(); + $data = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE id = ?', self::TABLE_NAME_COLLECTIONS), + [$this->model->getId()] + ); if ($data) { $this->assignVariablesToModel($data); @@ -66,14 +63,10 @@ public function getByName(?string $name = null): void $name = $this->model->getName(); $storeId = $this->model->getStoreId(); - $data = $this->db->createQueryBuilder() - ->select('*') - ->from(self::TABLE_NAME_COLLECTIONS) - ->where('name = :name AND storeId = :storeId') - ->setParameter('name', $name) - ->setParameter('storeId', $storeId) - ->executeQuery() - ->fetchAssociative(); + $data = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE name = ? AND storeId = ?', self::TABLE_NAME_COLLECTIONS), + [$name, $storeId] + ); if (!empty($data['id'])) { $this->assignVariablesToModel($data); diff --git a/models/DataObject/Classificationstore/GroupConfig/Dao.php b/models/DataObject/Classificationstore/GroupConfig/Dao.php index db9c270f..dfab3309 100644 --- a/models/DataObject/Classificationstore/GroupConfig/Dao.php +++ b/models/DataObject/Classificationstore/GroupConfig/Dao.php @@ -39,13 +39,10 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->createQueryBuilder() - ->select('*') - ->from(self::TABLE_NAME_GROUPS) - ->where('id = :id') - ->setParameter('id', $this->model->getId()) - ->executeQuery() - ->fetchAssociative(); + $data = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE id = ?', self::TABLE_NAME_GROUPS), + [$this->model->getId()] + ); if ($data) { $this->assignVariablesToModel($data); @@ -66,14 +63,10 @@ public function getByName(?string $name = null): void $name = $this->model->getName(); $storeId = $this->model->getStoreId(); - $data = $this->db->createQueryBuilder() - ->select('*') - ->from(self::TABLE_NAME_GROUPS) - ->where('name = :name AND storeId = :storeId') - ->setParameter('name', $name) - ->setParameter('storeId', $storeId) - ->executeQuery() - ->fetchAssociative(); + $data = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE name = ? AND storeId = ?', self::TABLE_NAME_GROUPS), + [$name, $storeId] + ); if ($data) { $this->assignVariablesToModel($data); @@ -88,13 +81,10 @@ public function hasChildren(): bool return false; } - return (bool) $this->db->createQueryBuilder() - ->select('COUNT(*)') - ->from(self::TABLE_NAME_GROUPS) - ->where('parentId = :parentId') - ->setParameter('parentId', $this->model->getId()) - ->executeQuery() - ->fetchOne(); + return (bool) $this->db->fetchOne( + sprintf('SELECT COUNT(*) FROM %s WHERE parentId = ?', self::TABLE_NAME_GROUPS), + [$this->model->getId()] + ); } /** diff --git a/models/DataObject/Classificationstore/KeyConfig/Dao.php b/models/DataObject/Classificationstore/KeyConfig/Dao.php index e8dc811d..9da0dd88 100644 --- a/models/DataObject/Classificationstore/KeyConfig/Dao.php +++ b/models/DataObject/Classificationstore/KeyConfig/Dao.php @@ -39,13 +39,10 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->createQueryBuilder() - ->select('*') - ->from(self::TABLE_NAME_KEYS) - ->where('id = :id') - ->setParameter('id', $this->model->getId()) - ->executeQuery() - ->fetchAssociative(); + $data = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE id = ?', self::TABLE_NAME_KEYS), + [$this->model->getId()] + ); if ($data) { $data['enabled'] = (bool)$data['enabled']; @@ -67,14 +64,10 @@ public function getByName(?string $name = null): void $name = $this->model->getName(); $storeId = $this->model->getStoreId(); - $data = $this->db->createQueryBuilder() - ->select('*') - ->from(self::TABLE_NAME_KEYS) - ->where('name = :name AND storeId = :storeId') - ->setParameter('name', $name) - ->setParameter('storeId', $storeId) - ->executeQuery() - ->fetchAssociative(); + $data = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE name = ? AND storeId = ?', self::TABLE_NAME_KEYS), + [$name, $storeId] + ); if ($data) { $data['enabled'] = (bool)$data['enabled']; diff --git a/models/DataObject/Classificationstore/StoreConfig/Dao.php b/models/DataObject/Classificationstore/StoreConfig/Dao.php index 91a41aa7..91cb1a8b 100644 --- a/models/DataObject/Classificationstore/StoreConfig/Dao.php +++ b/models/DataObject/Classificationstore/StoreConfig/Dao.php @@ -39,13 +39,10 @@ public function getById(?int $id = null): void $this->model->setId($id); } - $data = $this->db->createQueryBuilder() - ->select('*') - ->from(self::TABLE_NAME_STORES) - ->where('id = :id') - ->setParameter('id', $this->model->getId()) - ->executeQuery() - ->fetchAssociative(); + $data = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE id = ?', self::TABLE_NAME_STORES), + [$this->model->getId()] + ); if ($data) { $this->assignVariablesToModel($data); @@ -65,13 +62,10 @@ public function getByName(?string $name = null): void $name = $this->model->getName(); - $data = $this->db->createQueryBuilder() - ->select('*') - ->from(self::TABLE_NAME_STORES) - ->where('name = :name') - ->setParameter('name', $name) - ->executeQuery() - ->fetchAssociative(); + $data = $this->db->fetchAssociative( + sprintf('SELECT * FROM %s WHERE name = ?', self::TABLE_NAME_STORES), + [$name] + ); if ($data) { $this->assignVariablesToModel($data); From 588ad475f15391900cbfa64413ad51aebb72837a Mon Sep 17 00:00:00 2001 From: Stefan Hagspiel Date: Fri, 27 Feb 2026 14:55:42 +0100 Subject: [PATCH 5/6] Refactor classes to improve SQL query consistency and replace `fetchOne`/`fetchFirstColumn` with multiline formatting for readability --- .../src/Handler/ApplicationLoggerDb.php | 27 ++-- .../Maintenance/LogMailMaintenanceTask.php | 28 ++-- .../DeleteClassificationStoreCommand.php | 53 +++---- .../src/Model/Glossary/Listing/Dao.php | 18 ++- .../src/Controller/MiscController.php | 3 +- .../src/Model/Redirect/Listing/Dao.php | 12 +- .../Model/Search/Backend/Data/Listing/Dao.php | 18 ++- .../src/Model/Tool/UUID/Listing/Dao.php | 12 +- .../CleanupClassificationstoreTablesTask.php | 9 +- models/Asset/Folder.php | 12 +- models/DataObject/AbstractObject/Dao.php | 110 +++++++++++---- .../ClassDefinition/Listing/Dao.php | 15 +- .../CollectionConfig/Listing/Dao.php | 12 +- .../CollectionGroupRelation/Listing/Dao.php | 12 +- .../GroupConfig/Listing/Dao.php | 12 +- .../KeyConfig/Listing/Dao.php | 12 +- .../KeyGroupRelation/Listing/Dao.php | 12 +- .../StoreConfig/Listing/Dao.php | 12 +- .../Concrete/Dao/InheritanceHelper.php | 133 +++++++++++------- .../QuantityValue/Unit/Listing/Dao.php | 6 +- models/Element/Dao.php | 18 ++- .../Element/Recyclebin/Item/Listing/Dao.php | 12 +- models/Element/Tag/Listing/Dao.php | 18 ++- models/Element/WorkflowState/Listing/Dao.php | 12 +- models/Schedule/Task/Listing/Dao.php | 12 +- models/Site/Listing/Dao.php | 14 +- models/Tool/Email/Blocklist/Listing/Dao.php | 14 +- models/Tool/Email/Log/Listing/Dao.php | 18 ++- models/Translation/Dao.php | 9 +- models/User/Listing/AbstractListing/Dao.php | 6 +- .../Permission/Definition/Listing/Dao.php | 12 +- models/Version/Listing/Dao.php | 12 +- models/WebsiteSetting/Listing/Dao.php | 6 +- 33 files changed, 473 insertions(+), 218 deletions(-) diff --git a/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php b/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php index 91d413ad..7b450478 100644 --- a/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php +++ b/bundles/ApplicationLoggerBundle/src/Handler/ApplicationLoggerDb.php @@ -59,15 +59,11 @@ public function write(LogRecord $record): void */ public static function getComponents(): array { - $db = Db::get(); - - return $db->createQueryBuilder() - ->select('component') - ->from(self::TABLE_NAME) - ->where('component IS NOT NULL') - ->groupBy('component') - ->executeQuery() - ->fetchFirstColumn(); + return Db::get()->fetchFirstColumn(sprintf( + 'SELECT component FROM %s WHERE component IS NOT NULL GROUP BY component', + self::TABLE_NAME + ) + ); } /** @@ -87,15 +83,12 @@ public static function getPriorities(): array 'emergency' => 'EMERG', ]; - $db = Db::get(); + $priorityNumbers = Db::get()->fetchFirstColumn(sprintf( + 'SELECT priority FROM %s WHERE priority IS NOT NULL GROUP BY priority', + self::TABLE_NAME + ) + ); - $priorityNumbers = $db->createQueryBuilder() - ->select('priority') - ->from(self::TABLE_NAME) - ->where('priority IS NOT NULL') - ->groupBy('priority') - ->executeQuery() - ->fetchFirstColumn(); foreach ($priorityNumbers as $priorityNumber) { $priorities[$priorityNumber] = $priorityNames[$priorityNumber]; } diff --git a/bundles/ApplicationLoggerBundle/src/Maintenance/LogMailMaintenanceTask.php b/bundles/ApplicationLoggerBundle/src/Maintenance/LogMailMaintenanceTask.php index ec04e6e4..acab630b 100644 --- a/bundles/ApplicationLoggerBundle/src/Maintenance/LogMailMaintenanceTask.php +++ b/bundles/ApplicationLoggerBundle/src/Maintenance/LogMailMaintenanceTask.php @@ -60,15 +60,14 @@ public function execute(): void $logLevels[] = $enumValue[$i]; } - $rows = $this->db->createQueryBuilder() - ->select('*') - ->from(ApplicationLoggerDb::TABLE_NAME) - ->where('maintenanceChecked IS NULL') - ->andWhere('priority IN (:levels)') - ->setParameter('levels', $logLevels, ArrayParameterType::STRING) - ->orderBy('id', 'DESC') - ->executeQuery() - ->fetchAllAssociative(); + $rows = $this->db->fetchAllAssociative(sprintf( + 'SELECT * FROM %s WHERE maintenanceChecked IS NULL AND priority IN (?) ORDER BY id DESC', + ApplicationLoggerDb::TABLE_NAME + ), + [$logLevels], + [ArrayParameterType::STRING] + ); + $limit = 100; $rowsProcessed = 0; @@ -104,12 +103,9 @@ public function execute(): void // flag them as checked, regardless if email notifications are enabled or not // otherwise, when activating email notifications, you'll receive all log-messages from the past and not // since the point when you enabled the notifications - $this->db->executeQuery( - 'UPDATE ' - . ApplicationLoggerDb::TABLE_NAME - . ' SET maintenanceChecked = 1 ' - . 'WHERE maintenanceChecked != 1 ' - . 'OR maintenanceChecked IS NULL' - ); + $this->db->executeStatement(sprintf( + 'UPDATE %s SET maintenanceChecked = 1 WHERE maintenanceChecked != 1 OR maintenanceChecked IS NULL', + ApplicationLoggerDb::TABLE_NAME + )); } } diff --git a/bundles/CoreBundle/src/Command/DeleteClassificationStoreCommand.php b/bundles/CoreBundle/src/Command/DeleteClassificationStoreCommand.php index 5d1b3a86..aff21a09 100644 --- a/bundles/CoreBundle/src/Command/DeleteClassificationStoreCommand.php +++ b/bundles/CoreBundle/src/Command/DeleteClassificationStoreCommand.php @@ -16,7 +16,7 @@ namespace OpenDxp\Bundle\CoreBundle\Command; -use Exception; +use InvalidArgumentException; use OpenDxp\Cache; use OpenDxp\Console\AbstractCommand; use OpenDxp\Db; @@ -37,9 +37,7 @@ class DeleteClassificationStoreCommand extends AbstractCommand { protected function configure(): void { - $this - ->addArgument('storeId', InputArgument::REQUIRED, 'The store ID to delete') - ; + $this->addArgument('storeId', InputArgument::REQUIRED, 'The store ID to delete'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -47,45 +45,40 @@ protected function execute(InputInterface $input, OutputInterface $output): int $storeId = $input->getArgument('storeId'); if (!is_numeric($storeId)) { - throw new Exception('Invalid store ID'); + throw new InvalidArgumentException('Invalid store ID'); } + $storeId = (int) $storeId; $db = Db::get(); - $tableList = $db->fetchAllAssociative("show tables like 'object_classificationstore_data_%'"); + $tableList = $db->fetchAllAssociative('SHOW TABLES LIKE "object_classificationstore_data_%"'); foreach ($tableList as $table) { $theTable = current($table); - $sql = 'delete from ' . $theTable . ' where keyId In (select id from classificationstore_keys where storeId = ' . $db->quote($storeId) . ')'; - echo $sql . "\n"; - $db->executeQuery($sql); + $output->writeln(sprintf('Deleting classification store data from %s', $theTable)); + $db->executeStatement( + sprintf('DELETE FROM %s WHERE keyId IN (SELECT id FROM classificationstore_keys WHERE storeId = ?)', $theTable), + [$storeId] + ); } - $tableList = $db->fetchAllAssociative("show tables like 'object_classificationstore_groups_%'"); + $tableList = $db->fetchAllAssociative('SHOW TABLES LIKE "object_classificationstore_groups_%"'); foreach ($tableList as $table) { $theTable = current($table); - $sql = 'delete from ' . $theTable . ' where groupId In (select id from classificationstore_groups where storeId = ' . $db->quote($storeId) . ')'; - echo $sql . "\n"; - $db->executeQuery($sql); + $output->writeln(sprintf('Deleting classification store groups from %s', $theTable)); + $db->executeStatement( + sprintf('DELETE FROM %s WHERE groupId IN (SELECT id FROM classificationstore_groups WHERE storeId = ?)', $theTable), + [$storeId] + ); } - $sql = 'delete from classificationstore_keys where storeId = ' . $db->quote($storeId); - echo $sql . "\n"; - $db->executeQuery($sql); - - $sql = 'delete from classificationstore_groups where storeId = ' . $db->quote($storeId); - echo $sql . "\n"; - $db->executeQuery($sql); - - $sql = 'delete from classificationstore_collections where storeId = ' . $db->quote($storeId); - echo $sql . "\n"; - $db->executeQuery($sql); - - $sql = 'delete from classificationstore_stores where id = ' . $db->quote($storeId); - echo $sql . "\n"; - $db->executeQuery($sql); + $output->writeln('Deleting keys, groups, collections and store record'); + $db->executeStatement('DELETE FROM classificationstore_keys WHERE storeId = ?', [$storeId]); + $db->executeStatement('DELETE FROM classificationstore_groups WHERE storeId = ?', [$storeId]); + $db->executeStatement('DELETE FROM classificationstore_collections WHERE storeId = ?', [$storeId]); + $db->executeStatement('DELETE FROM classificationstore_stores WHERE id = ?', [$storeId]); Cache::clearAll(); - return 0; + return self::SUCCESS; } -} +} \ No newline at end of file diff --git a/bundles/GlossaryBundle/src/Model/Glossary/Listing/Dao.php b/bundles/GlossaryBundle/src/Model/Glossary/Listing/Dao.php index d825f3bc..b9647c08 100644 --- a/bundles/GlossaryBundle/src/Model/Glossary/Listing/Dao.php +++ b/bundles/GlossaryBundle/src/Model/Glossary/Listing/Dao.php @@ -34,7 +34,11 @@ class Dao extends Model\Listing\Dao\AbstractDao */ public function load(): array { - $glossarysData = $this->db->fetchFirstColumn('SELECT id FROM glossary' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $glossarysData = $this->db->fetchFirstColumn( + 'SELECT id FROM glossary' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); $glossary = []; foreach ($glossarysData as $glossaryData) { @@ -51,7 +55,11 @@ public function load(): array */ public function getDataArray(): array { - return $this->db->fetchAllAssociative('SELECT * FROM glossary' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return $this->db->fetchAllAssociative( + 'SELECT * FROM glossary' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } /** @@ -60,7 +68,11 @@ public function getDataArray(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM glossary ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM glossary ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/bundles/SeoBundle/src/Controller/MiscController.php b/bundles/SeoBundle/src/Controller/MiscController.php index 514876cc..d36033fb 100644 --- a/bundles/SeoBundle/src/Controller/MiscController.php +++ b/bundles/SeoBundle/src/Controller/MiscController.php @@ -112,8 +112,7 @@ public function httpErrorLogFlushAction(Request $request): JsonResponse { $this->checkPermission('http_errors'); - $db = Db::get(); - $db->executeQuery('TRUNCATE TABLE http_error_log'); + Db::get()->executeQuery('TRUNCATE TABLE http_error_log'); return $this->jsonResponse([ 'success' => true, diff --git a/bundles/SeoBundle/src/Model/Redirect/Listing/Dao.php b/bundles/SeoBundle/src/Model/Redirect/Listing/Dao.php index ae976864..5e4263d2 100644 --- a/bundles/SeoBundle/src/Model/Redirect/Listing/Dao.php +++ b/bundles/SeoBundle/src/Model/Redirect/Listing/Dao.php @@ -32,7 +32,11 @@ class Dao extends Model\Listing\Dao\AbstractDao */ public function load(): array { - $redirectsData = $this->db->fetchFirstColumn('SELECT id FROM redirects' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $redirectsData = $this->db->fetchFirstColumn( + 'SELECT id FROM redirects' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); $redirects = []; foreach ($redirectsData as $redirectData) { @@ -47,7 +51,11 @@ public function load(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM redirects ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM redirects ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/bundles/SimpleBackendSearchBundle/src/Model/Search/Backend/Data/Listing/Dao.php b/bundles/SimpleBackendSearchBundle/src/Model/Search/Backend/Data/Listing/Dao.php index ce715375..a0d3ce85 100644 --- a/bundles/SimpleBackendSearchBundle/src/Model/Search/Backend/Data/Listing/Dao.php +++ b/bundles/SimpleBackendSearchBundle/src/Model/Search/Backend/Data/Listing/Dao.php @@ -36,7 +36,11 @@ class Dao extends AbstractDao public function load(): array { $entries = []; - $data = $this->db->fetchAllAssociative('SELECT * FROM search_backend_data' . $this->getCondition() . $this->getGroupBy() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $data = $this->db->fetchAllAssociative( + 'SELECT * FROM search_backend_data' . $this->getCondition() . $this->getGroupBy() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); foreach ($data as $entryData) { if (!in_array($entryData['maintype'], ['document', 'asset', 'object'], true)) { @@ -68,7 +72,11 @@ public function load(): array public function getTotalCount(): int { - return (int)$this->db->fetchOne('SELECT COUNT(*) FROM search_backend_data' . $this->getCondition() . $this->getGroupBy(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int)$this->db->fetchOne( + 'SELECT COUNT(*) FROM search_backend_data' . $this->getCondition() . $this->getGroupBy(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } public function getCount(): int|string @@ -77,7 +85,11 @@ public function getCount(): int|string return count($this->model->getEntries()); } - return $this->db->fetchOne('SELECT COUNT(*) as amount FROM search_backend_data ' . $this->getCondition() . $this->getGroupBy() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return $this->db->fetchOne( + 'SELECT COUNT(*) as amount FROM search_backend_data ' . $this->getCondition() . $this->getGroupBy() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } #[Override] diff --git a/bundles/UuidBundle/src/Model/Tool/UUID/Listing/Dao.php b/bundles/UuidBundle/src/Model/Tool/UUID/Listing/Dao.php index 43c7d05f..89ecc5b0 100644 --- a/bundles/UuidBundle/src/Model/Tool/UUID/Listing/Dao.php +++ b/bundles/UuidBundle/src/Model/Tool/UUID/Listing/Dao.php @@ -32,7 +32,11 @@ class Dao extends Model\Listing\Dao\AbstractDao */ public function load(): array { - $items = $this->db->fetchFirstColumn('SELECT uuid FROM ' . UUID\Dao::TABLE_NAME .' '. $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $items = $this->db->fetchFirstColumn( + 'SELECT uuid FROM ' . UUID\Dao::TABLE_NAME .' '. $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); $result = []; foreach ($items as $uuid) { $result[] = UUID::getByUuid($uuid); @@ -47,7 +51,11 @@ public function load(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM ' . UUID\Dao::TABLE_NAME .' ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM ' . UUID\Dao::TABLE_NAME .' ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/lib/Maintenance/Tasks/CleanupClassificationstoreTablesTask.php b/lib/Maintenance/Tasks/CleanupClassificationstoreTablesTask.php index 4a34e3cc..5ab66ecf 100644 --- a/lib/Maintenance/Tasks/CleanupClassificationstoreTablesTask.php +++ b/lib/Maintenance/Tasks/CleanupClassificationstoreTablesTask.php @@ -49,12 +49,9 @@ public function execute(): void continue; } - $fieldNames = $db->createQueryBuilder() - ->select('fieldname') - ->from($tableName) - ->groupBy('fieldname') - ->executeQuery() - ->fetchFirstColumn(); + $fieldNames = $db->fetchFirstColumn( + sprintf('SELECT fieldname FROM %s GROUP BY fieldname', $tableName) + ); foreach ($fieldNames as $fieldName) { $fieldDef = $classDefinition->getFieldDefinition($fieldName); diff --git a/models/Asset/Folder.php b/models/Asset/Folder.php index d039735c..a3edf18d 100644 --- a/models/Asset/Folder.php +++ b/models/Asset/Folder.php @@ -107,14 +107,10 @@ public function getPreviewImage(bool $force = false) ]; if ($storage->fileExists($cacheFilePath)) { - $lastUpdate = $db->createQueryBuilder() - ->select('MAX(modificationDate)') - ->from('assets') - ->where($condition) - ->setParameters($conditionParams) - ->setMaxResults($limit) - ->executeQuery() - ->fetchOne(); + $lastUpdate = $db->fetchOne( + sprintf('SELECT MAX(modificationDate) FROM assets WHERE %s', $condition), + $conditionParams + ); if ($lastUpdate < $storage->lastModified($cacheFilePath)) { return $storage->readStream($cacheFilePath); } diff --git a/models/DataObject/AbstractObject/Dao.php b/models/DataObject/AbstractObject/Dao.php index 150ed2e5..b696bf31 100644 --- a/models/DataObject/AbstractObject/Dao.php +++ b/models/DataObject/AbstractObject/Dao.php @@ -301,7 +301,10 @@ public function hasChildren( return false; } - $sql = 'SELECT 1 FROM objects o WHERE parentId = ? '; + $sql = 'SELECT 1 FROM objects o WHERE parentId = ?'; + $params = [$this->model->getId()]; + $types = [ParameterType::INTEGER]; + if ($user && !$user->isAdmin()) { $roleIds = array_map('intval', $user->getRoles()); $currentUserId = $user->getId(); @@ -310,19 +313,41 @@ public function hasChildren( //gets the permission of the ancestors, since it would be the same for each row with same parentId, it is done once outside the query to avoid extra subquery. $inheritedPermission = $this->isInheritingPermission('list', $permissionIds); - // $anyAllowedRowOrChildren checks for nested elements that are `list`=1. This is to allow the folders in between from current parent to any nested elements and due the "additive" permission on the element itself, we can simply ignore list=0 children + // $anyAllowedRowOrChildren checks for nested elements that are `list`=1. + // This is to allow the folders in between from current parent to any nested elements and due the "additive" permission on the element itself, we can simply ignore list=0 children // unless for the same rule found is list=0 on user specific level, in that case it nullifies that entry. - $anyAllowedRowOrChildren = 'EXISTS(SELECT list FROM users_workspaces_object uwo WHERE userId IN (' . implode(',', $permissionIds) . ') AND list=1 AND LOCATE(CONCAT(o.path,o.key),cpath)=1 AND - NOT EXISTS(SELECT list FROM users_workspaces_object WHERE userId =' . (int) $currentUserId . ' AND list=0 AND cpath = uwo.cpath))'; - - // $allowedCurrentRow checks if the current row is blocked, if found a match it "removes/ignores" the entry from object table, doesn't need to check if is list=1 on user level, since it is done in $anyAllowedRowOrChildren (NB: equal or longer cpath) so we are safe to deduce that there are no valid list=1 rules - $isDisallowedCurrentRow = 'EXISTS(SELECT list FROM users_workspaces_object uworow WHERE userId IN (' . implode(',', $permissionIds) . ') AND cid = id AND list=0)'; - - //If no children with list=1 (with no user-level list=0) is found, we consider the inherited permission rule - //if $inheritedPermission=0 then everything is disallowed (or doesn't specify any rule) for that row, we can skip $isDisallowedCurrentRow - //if $inheritedPermission=1, then we are allowed unless the current row is specifically disabled, already knowing from $anyAllowedRowOrChildren that there are no list=1(without user permission list=0),so this "blocker" is the highest cpath available for this row if found - - $sql .= ' AND IF(' . $anyAllowedRowOrChildren . ',1,IF(' . $inheritedPermission . ', ' . $isDisallowedCurrentRow . ' = 0, 0)) = 1'; + $anyAllowedRowOrChildren = 'EXISTS( + SELECT list FROM users_workspaces_object uwo + WHERE userId IN (?) + AND list=1 + AND LOCATE(CONCAT(o.path,o.key),cpath)=1 + AND NOT EXISTS( + SELECT list FROM users_workspaces_object + WHERE userId=? AND list=0 AND cpath = uwo.cpath + ) + )'; + + // $isDisallowedCurrentRow checks if the current row is blocked, if found a match it "removes/ignores" the entry from object table, + // doesn't need to check if is list=1 on user level, since it is done in $anyAllowedRowOrChildren (NB: equal or longer cpath) so we are safe to deduce that there are no valid list=1 rules + $isDisallowedCurrentRow = 'EXISTS( + SELECT list FROM users_workspaces_object uworow + WHERE userId IN (?) + AND cid = id + AND list=0 + )'; + + // If no children with list=1 (with no user-level list=0) is found, we consider the inherited permission rule + // if $inheritedPermission=0 then everything is disallowed (or doesn't specify any rule) for that row, we can skip $isDisallowedCurrentRow + // if $inheritedPermission=1, then we are allowed unless the current row is specifically disabled, + // already knowing from $anyAllowedRowOrChildren that there are no list=1(without user permission list=0),so this "blocker" is the highest cpath available for this row if found + $sql .= sprintf(' AND IF(%s,1,IF(%d,%s = 0,0)) = 1', $anyAllowedRowOrChildren, $inheritedPermission, $isDisallowedCurrentRow); + + $params[] = $permissionIds; // for $anyAllowedRowOrChildren IN (?) + $types[] = ArrayParameterType::INTEGER; + $params[] = $currentUserId; // for $anyAllowedRowOrChildren userId=? + $types[] = ParameterType::INTEGER; + $params[] = $permissionIds; // for $isDisallowedCurrentRow IN (?) + $types[] = ArrayParameterType::INTEGER; } $includingUnpublished ??= !DataObject::doHideUnpublished(); @@ -331,11 +356,14 @@ public function hasChildren( } if ($objectTypes) { - $sql .= " AND `type` IN ('" . implode("','", $objectTypes) . "')"; + $sql .= ' AND `type` IN (?)'; + $params[] = $objectTypes; + $types[] = ArrayParameterType::STRING; } $sql .= ' LIMIT 1'; - $c = $this->db->fetchOne($sql, [$this->model->getId()]); + + $c = $this->db->fetchOne($sql, $params, $types); return (bool)$c; } @@ -359,10 +387,12 @@ public function hasSiblings( $sql = 'SELECT 1 FROM objects WHERE parentId = ?'; $params = [$this->model->getParentId()]; + $types = [ParameterType::INTEGER]; if ($this->model->getId()) { $sql .= ' AND id != ?'; $params[] = $this->model->getId(); + $types[] = ParameterType::INTEGER; } $includingUnpublished ??= !DataObject::doHideUnpublished(); @@ -371,12 +401,14 @@ public function hasSiblings( } if ($objectTypes) { - $sql .= " AND `type` IN ('" . implode("','", $objectTypes) . "')"; + $sql .= ' AND `type` IN (?)'; + $params[] = $objectTypes; + $types[] = ArrayParameterType::STRING; } $sql .= ' LIMIT 1'; - $c = $this->db->fetchOne($sql, $params); + $c = $this->db->fetchOne($sql, $params, $types); return (bool)$c; } @@ -397,11 +429,13 @@ public function getChildAmount( } $params = [$this->model->getId()]; + $types = [ParameterType::INTEGER]; $query = 'SELECT COUNT(*) AS count FROM objects o WHERE parentId = ?'; if ($objectTypes) { $query .= ' AND `type` IN (?)'; $params[] = $objectTypes; + $types[] = ArrayParameterType::STRING; } if ($user && !$user->isAdmin()) { @@ -411,18 +445,34 @@ public function getChildAmount( $inheritedPermission = $this->isInheritingPermission('list', $permissionIds); - $anyAllowedRowOrChildren = 'EXISTS(SELECT list FROM users_workspaces_object uwo WHERE userId IN (' . implode(',', $permissionIds) . ') AND list=1 AND LOCATE(CONCAT(o.path,o.key),cpath)=1 AND - NOT EXISTS(SELECT list FROM users_workspaces_object WHERE userId =' . (int) $currentUserId . ' AND list=0 AND cpath = uwo.cpath))'; - $isDisallowedCurrentRow = 'EXISTS(SELECT list FROM users_workspaces_object uworow WHERE userId IN (' . implode(',', $permissionIds) . ') AND cid = id AND list=0)'; - - $query .= ' AND IF(' . $anyAllowedRowOrChildren . ',1,IF(' . $inheritedPermission . ', ' . $isDisallowedCurrentRow . ' = 0, 0)) = 1'; + $anyAllowedRowOrChildren = 'EXISTS( + SELECT list FROM users_workspaces_object uwo + WHERE userId IN (?) + AND list=1 + AND LOCATE(CONCAT(o.path,o.key),cpath)=1 + AND NOT EXISTS( + SELECT list FROM users_workspaces_object + WHERE userId=? AND list=0 AND cpath = uwo.cpath + ) + )'; + $isDisallowedCurrentRow = 'EXISTS( + SELECT list FROM users_workspaces_object uworow + WHERE userId IN (?) + AND cid = id + AND list=0 + )'; + + $query .= sprintf(' AND IF(%s,1,IF(%d,%s = 0,0)) = 1', $anyAllowedRowOrChildren, $inheritedPermission, $isDisallowedCurrentRow); + + $params[] = $permissionIds; + $types[] = ArrayParameterType::INTEGER; + $params[] = $currentUserId; + $types[] = ParameterType::INTEGER; + $params[] = $permissionIds; + $types[] = ArrayParameterType::INTEGER; } - return (int) $this->db->fetchOne( - $query, - $params, - $objectTypes ? [ParameterType::INTEGER, ArrayParameterType::STRING] : [] - ); + return (int) $this->db->fetchOne($query, $params, $types); } /** @@ -452,7 +502,11 @@ public function isLocked(): bool } $parentIds = $this->getParentIds(); - $inhertitedLocks = $this->db->fetchOne('SELECT id FROM tree_locks WHERE id IN (?) AND `type` = "object" AND locked = "propagate" LIMIT 1', [$parentIds], [ArrayParameterType::INTEGER]); + $inhertitedLocks = $this->db->fetchOne( + 'SELECT id FROM tree_locks WHERE id IN (?) AND `type` = "object" AND locked = "propagate" LIMIT 1', + [$parentIds], + [ArrayParameterType::INTEGER] + ); return $inhertitedLocks > 0; } diff --git a/models/DataObject/ClassDefinition/Listing/Dao.php b/models/DataObject/ClassDefinition/Listing/Dao.php index 3aea5e6c..da1ec340 100644 --- a/models/DataObject/ClassDefinition/Listing/Dao.php +++ b/models/DataObject/ClassDefinition/Listing/Dao.php @@ -27,13 +27,18 @@ class Dao extends Model\Listing\Dao\AbstractDao { /** - * Loads a list of object-classes for the specicifies parameters, returns an array of DataObject\ClassDefinition elements + * Loads a list of object-classes for the specified parameters, returns an array of DataObject\ClassDefinition elements */ public function load(): array { $classes = []; - $classesRaw = $this->db->fetchFirstColumn('SELECT id FROM classes' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $classesRaw = $this->db->fetchFirstColumn( + 'SELECT id FROM classes' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); + foreach ($classesRaw as $classRaw) { if ($class = DataObject\ClassDefinition::getById($classRaw)) { @@ -49,7 +54,11 @@ public function load(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM classes ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM classes ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/DataObject/Classificationstore/CollectionConfig/Listing/Dao.php b/models/DataObject/Classificationstore/CollectionConfig/Listing/Dao.php index 7aedbc69..a27416ae 100644 --- a/models/DataObject/Classificationstore/CollectionConfig/Listing/Dao.php +++ b/models/DataObject/Classificationstore/CollectionConfig/Listing/Dao.php @@ -46,13 +46,21 @@ public function load(): array public function getDataArray(): array { - return $this->db->fetchAllAssociative('SELECT * FROM ' . DataObject\Classificationstore\CollectionConfig\Dao::TABLE_NAME_COLLECTIONS . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return $this->db->fetchAllAssociative( + 'SELECT * FROM ' . DataObject\Classificationstore\CollectionConfig\Dao::TABLE_NAME_COLLECTIONS . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM ' . DataObject\Classificationstore\CollectionConfig\Dao::TABLE_NAME_COLLECTIONS . ' '. $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM ' . DataObject\Classificationstore\CollectionConfig\Dao::TABLE_NAME_COLLECTIONS . ' '. $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/DataObject/Classificationstore/CollectionGroupRelation/Listing/Dao.php b/models/DataObject/Classificationstore/CollectionGroupRelation/Listing/Dao.php index 79eba46a..9689f76c 100644 --- a/models/DataObject/Classificationstore/CollectionGroupRelation/Listing/Dao.php +++ b/models/DataObject/Classificationstore/CollectionGroupRelation/Listing/Dao.php @@ -58,13 +58,21 @@ public function load(): array public function getDataArray(): array { - return $this->db->fetchAllAssociative('SELECT * FROM ' . DataObject\Classificationstore\CollectionGroupRelation\Dao::TABLE_NAME_RELATIONS . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return $this->db->fetchAllAssociative( + 'SELECT * FROM ' . DataObject\Classificationstore\CollectionGroupRelation\Dao::TABLE_NAME_RELATIONS . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM ' . DataObject\Classificationstore\CollectionGroupRelation\Dao::TABLE_NAME_RELATIONS . ' '. $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM ' . DataObject\Classificationstore\CollectionGroupRelation\Dao::TABLE_NAME_RELATIONS . ' '. $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/DataObject/Classificationstore/GroupConfig/Listing/Dao.php b/models/DataObject/Classificationstore/GroupConfig/Listing/Dao.php index 7fd74deb..665162c7 100644 --- a/models/DataObject/Classificationstore/GroupConfig/Listing/Dao.php +++ b/models/DataObject/Classificationstore/GroupConfig/Listing/Dao.php @@ -48,13 +48,21 @@ public function load(): array public function getDataArray(): array { - return $this->db->fetchAllAssociative('SELECT * FROM ' . DataObject\Classificationstore\GroupConfig\Dao::TABLE_NAME_GROUPS . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return $this->db->fetchAllAssociative( + 'SELECT * FROM ' . DataObject\Classificationstore\GroupConfig\Dao::TABLE_NAME_GROUPS . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM ' . DataObject\Classificationstore\GroupConfig\Dao::TABLE_NAME_GROUPS . ' '. $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM ' . DataObject\Classificationstore\GroupConfig\Dao::TABLE_NAME_GROUPS . ' '. $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/DataObject/Classificationstore/KeyConfig/Listing/Dao.php b/models/DataObject/Classificationstore/KeyConfig/Listing/Dao.php index 27c35fe9..73dc8445 100644 --- a/models/DataObject/Classificationstore/KeyConfig/Listing/Dao.php +++ b/models/DataObject/Classificationstore/KeyConfig/Listing/Dao.php @@ -50,13 +50,21 @@ public function load(): array public function getDataArray(): array { - return $this->db->fetchAllAssociative('SELECT * FROM ' . DataObject\Classificationstore\KeyConfig\Dao::TABLE_NAME_KEYS . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return $this->db->fetchAllAssociative( + 'SELECT * FROM ' . DataObject\Classificationstore\KeyConfig\Dao::TABLE_NAME_KEYS . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM ' . DataObject\Classificationstore\KeyConfig\Dao::TABLE_NAME_KEYS . ' '. $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM ' . DataObject\Classificationstore\KeyConfig\Dao::TABLE_NAME_KEYS . ' '. $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/DataObject/Classificationstore/KeyGroupRelation/Listing/Dao.php b/models/DataObject/Classificationstore/KeyGroupRelation/Listing/Dao.php index e71b5aeb..87f3ed54 100644 --- a/models/DataObject/Classificationstore/KeyGroupRelation/Listing/Dao.php +++ b/models/DataObject/Classificationstore/KeyGroupRelation/Listing/Dao.php @@ -65,12 +65,20 @@ public function load(): array public function getDataArray(): array { - return $this->db->fetchAllAssociative('SELECT *' . $this->getFrom() . $this->getWhere() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return $this->db->fetchAllAssociative( + 'SELECT *' . $this->getFrom() . $this->getWhere() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } public function getTotalCount(): int { - return (int) $this->db->fetchOne('SELECT COUNT(*)' . $this->getFrom() . $this->getWhere(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*)' . $this->getFrom() . $this->getWhere(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } private function getWhere(): string diff --git a/models/DataObject/Classificationstore/StoreConfig/Listing/Dao.php b/models/DataObject/Classificationstore/StoreConfig/Listing/Dao.php index 10ca2252..7c4816b6 100644 --- a/models/DataObject/Classificationstore/StoreConfig/Listing/Dao.php +++ b/models/DataObject/Classificationstore/StoreConfig/Listing/Dao.php @@ -46,13 +46,21 @@ public function load(): array public function getDataArray(): array { - return $this->db->fetchAllAssociative('SELECT * FROM ' . DataObject\Classificationstore\StoreConfig\Dao::TABLE_NAME_STORES . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return $this->db->fetchAllAssociative( + 'SELECT * FROM ' . DataObject\Classificationstore\StoreConfig\Dao::TABLE_NAME_STORES . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM ' . DataObject\Classificationstore\StoreConfig\Dao::TABLE_NAME_STORES . ' '. $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM ' . DataObject\Classificationstore\StoreConfig\Dao::TABLE_NAME_STORES . ' '. $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/DataObject/Concrete/Dao/InheritanceHelper.php b/models/DataObject/Concrete/Dao/InheritanceHelper.php index b08f7548..667c4c82 100644 --- a/models/DataObject/Concrete/Dao/InheritanceHelper.php +++ b/models/DataObject/Concrete/Dao/InheritanceHelper.php @@ -143,6 +143,7 @@ public function doUpdate(int $oo_id, bool $createMissingChildrenRows = false, ar sprintf('SELECT %s AS id%s FROM %s WHERE %s = ?', $this->idField, $fields, $this->storetable, $this->idField), [$oo_id] ); + $o = [ 'id' => $result['id'], 'values' => $result, @@ -187,22 +188,26 @@ public function doUpdate(int $oo_id, bool $createMissingChildrenRows = false, ar if ($createMissingChildrenRows && ($this->childFound || ($this->fields === [] && $this->relations === []))) { $object = DataObject\Concrete::getById($oo_id); $classId = $object->getClassId(); - $query = " - WITH RECURSIVE cte(id, classId) as ( - SELECT c.id AS id, c.classId AS classId - FROM objects c - WHERE c.parentid = {$object->getId()} - UNION ALL - SELECT p.id AS id, p.classId AS classId - FROM objects p - INNER JOIN cte on (p.parentid = cte.id) - ) select x.id - FROM cte x - LEFT JOIN {$this->querytable} l on (x.id = l.{$this->idField}) - where x.classId = {$this->db->quote($classId)} - AND l.{$this->queryIdField} is null; - "; - $missingIds = $this->db->fetchFirstColumn($query); + $query = sprintf( + 'WITH RECURSIVE cte(id, classId) AS ( + SELECT c.id AS id, c.classId AS classId + FROM objects c + WHERE c.parentid = ? + UNION ALL + SELECT p.id AS id, p.classId AS classId + FROM objects p + INNER JOIN cte ON (p.parentid = cte.id) + ) + SELECT x.id + FROM cte x + LEFT JOIN %s l ON (x.id = l.%s) + WHERE x.classId = ? + AND l.%s IS NULL', + $this->querytable, + $this->idField, + $this->queryIdField + ); + $missingIds = $this->db->fetchFirstColumn($query, [$object->getId(), $classId]); // create entries for children that don't have an entry yet $originalEntry = Helper::quoteDataIdentifiers($this->db, $this->db->fetchAssociative( sprintf('SELECT * FROM %s WHERE %s = ?', $this->querytable, $this->idField), @@ -329,55 +334,71 @@ protected function buildTree(int $currentParentId, string $fields = '', ?array $ $idfield = $this->idField; if (!$parentIdGroups) { - $object = DataObject::getById($currentParentId); if (isset($params['language'])) { $language = $params['language']; - $query = " - WITH RECURSIVE cte(id, classId, parentId, path) as ( - SELECT c.id AS id, c.classId AS classId, c.parentid AS parentId, c.path as `path` - FROM objects c - WHERE c.parentid = $currentParentId - UNION ALL - SELECT p.id AS id, p.classId AS classId, p.parentid AS parentId, p.path as `path` - FROM objects p - INNER JOIN cte on (p.parentid = cte.id) - ) SELECT l.language AS `language`, - x.id AS id, - x.classId AS classId, - x.parentId AS parentId - $fields + $query = sprintf( + 'WITH RECURSIVE cte(id, classId, parentId, path) AS ( + SELECT c.id AS id, c.classId AS classId, c.parentid AS parentId, c.path AS `path` + FROM objects c + WHERE c.parentid = ? + UNION ALL + SELECT p.id AS id, p.classId AS classId, p.parentid AS parentId, p.path AS `path` + FROM objects p + INNER JOIN cte ON (p.parentid = cte.id) + ) + SELECT l.language AS `language`, + x.id AS id, + x.classId AS classId, + x.parentId AS parentId + %s FROM cte x - LEFT JOIN $storeTable l ON x.id = l.$idfield - WHERE COALESCE(`language`, " . $this->db->quote($language) . ') = ' . $this->db->quote($language) . - ' ORDER BY x.path ASC'; + LEFT JOIN %s l ON x.id = l.%s + WHERE COALESCE(`language`, ?) = ? + ORDER BY x.path ASC', + $fields, + $storeTable, + $idfield + ); + + $queryParams = [$currentParentId, $language, $language]; } else { - $query = " - WITH RECURSIVE cte(id, classId, parentId, path) as ( - SELECT c.id AS id, c.classId AS classId, c.parentid AS parentId, c.path as `path` + $language = null; + + $query = sprintf( + 'WITH RECURSIVE cte(id, classId, parentId, path) AS ( + SELECT c.id AS id, c.classId AS classId, c.parentid AS parentId, c.path AS `path` FROM objects c - WHERE c.parentid = $currentParentId + WHERE c.parentid = ? UNION ALL - SELECT p.id AS id, p.classId AS classId, p.parentid AS parentId, p.path as `path` + SELECT p.id AS id, p.classId AS classId, p.parentid AS parentId, p.path AS `path` FROM objects p - INNER JOIN cte on (p.parentid = cte.id) - ) SELECT x.id AS id, - x.classId AS classId, - x.parentId AS parentId - $fields - FROM cte x - LEFT JOIN $storeTable a ON x.id = a.$idfield - GROUP BY x.id - ORDER BY x.path ASC"; + INNER JOIN cte ON (p.parentid = cte.id) + ) + SELECT x.id AS id, + x.classId AS classId, + x.parentId AS parentId + %s + FROM cte x + LEFT JOIN %s a ON x.id = a.%s + GROUP BY x.id + ORDER BY x.path ASC', + $fields, + $storeTable, + $idfield + ); + + $queryParams = [$currentParentId]; } - $queryCacheKey = 'tree_'.md5($query); + + $queryCacheKey = 'tree_' . md5($query . '|' . $currentParentId . '|' . ($language ?? '')); if (self::$useRuntimeCache) { $parentIdGroups = self::$runtimeCache[$queryCacheKey] ?? null; } if (!$parentIdGroups) { - $result = $this->db->fetchAllAssociative($query); + $result = $this->db->fetchAllAssociative($query, $queryParams); if (isset($params['language'])) { $result = $this->filterResultByLanguage($result, $params['language'], 'language'); @@ -560,14 +581,22 @@ protected function updateQueryTable(int $oo_id, array $ids, string $fieldname): sprintf('SELECT %s FROM %s WHERE %s = ?', $this->db->quoteIdentifier($fieldname), $this->querytable, $this->idField), [$oo_id] ); - $this->db->executeStatement(sprintf('UPDATE %s SET %s = ? WHERE %s IN (?)', $this->querytable, $this->db->quoteIdentifier($fieldname), $this->db->quoteIdentifier($this->idField)), [$value, $ids], [ParameterType::STRING, ArrayParameterType::INTEGER]); + $this->db->executeStatement( + sprintf('UPDATE %s SET %s = ? WHERE %s IN (?)', $this->querytable, $this->db->quoteIdentifier($fieldname), $this->db->quoteIdentifier($this->idField)), + [$value, $ids], + [ParameterType::STRING, ArrayParameterType::INTEGER] + ); } } protected function updateQueryTableOnDelete(int $oo_id, array $ids, string $fieldname): void { if ($ids !== []) { - $this->db->executeStatement(sprintf('UPDATE %s SET %s = ? WHERE %s IN (?)', $this->querytable, $this->db->quoteIdentifier($fieldname), $this->db->quoteIdentifier($this->idField)), [null, $ids], [ParameterType::NULL, ArrayParameterType::INTEGER]); + $this->db->executeStatement( + sprintf('UPDATE %s SET %s = ? WHERE %s IN (?)', $this->querytable, $this->db->quoteIdentifier($fieldname), $this->db->quoteIdentifier($this->idField)), + [null, $ids], + [ParameterType::NULL, ArrayParameterType::INTEGER] + ); } } } diff --git a/models/DataObject/QuantityValue/Unit/Listing/Dao.php b/models/DataObject/QuantityValue/Unit/Listing/Dao.php index 1ad8a9ad..361b035d 100644 --- a/models/DataObject/QuantityValue/Unit/Listing/Dao.php +++ b/models/DataObject/QuantityValue/Unit/Listing/Dao.php @@ -47,7 +47,11 @@ public function load(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM '.DataObject\QuantityValue\Unit\Dao::TABLE_NAME.' ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM '.DataObject\QuantityValue\Unit\Dao::TABLE_NAME.' ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/Element/Dao.php b/models/Element/Dao.php index 942fd2f7..c542a413 100644 --- a/models/Element/Dao.php +++ b/models/Element/Dao.php @@ -91,7 +91,11 @@ public function InheritingPermission(string $type, array $userIds, string $table AND userId IN (?) ORDER BY LENGTH(cpath) DESC, FIELD(userId, ?) DESC, ' . $this->db->quoteIdentifier($type) . ' DESC LIMIT 1'; - return (int)$this->db->fetchOne($sql, [$fullPath, $userIds, end($userIds)], [ParameterType::STRING, ArrayParameterType::INTEGER, ParameterType::INTEGER]); + return (int)$this->db->fetchOne( + $sql, + [$fullPath, $userIds, end($userIds)], + [ParameterType::STRING, ArrayParameterType::INTEGER, ParameterType::INTEGER] + ); } /** @@ -123,7 +127,11 @@ protected function permissionByTypes(array $columns, User $user, string $tableSu ORDER BY LENGTH(cpath) DESC, FIELD(userId, ?) DESC LIMIT 1 '; - $highestWorkspace = $this->db->fetchAssociative($highestWorkspaceQuery, [$parentIds, $userIds, $currentUserId], [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER]); + $highestWorkspace = $this->db->fetchAssociative( + $highestWorkspaceQuery, + [$parentIds, $userIds, $currentUserId], + [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER] + ); if ($highestWorkspace) { //if it's the current user, this is the permission that rules them all, no need to check others @@ -147,7 +155,11 @@ protected function permissionByTypes(array $columns, User $user, string $tableSu WHERE cid = ? AND userId IN (?) ORDER BY FIELD(userId, ?) DESC '; - $objectPermissions = $this->db->fetchAllAssociative($roleWorkspaceSql, [$highestWorkspace['cid'], $userIds, $currentUserId], [ParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER]); + $objectPermissions = $this->db->fetchAllAssociative( + $roleWorkspaceSql, + [$highestWorkspace['cid'], $userIds, $currentUserId], + [ParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER] + ); //this performs the additive rule when conflicting rules with multiple roles, //breaks the loop when permission=1 is found and move on to check next permission type. diff --git a/models/Element/Recyclebin/Item/Listing/Dao.php b/models/Element/Recyclebin/Item/Listing/Dao.php index d530cabf..638bfe0d 100644 --- a/models/Element/Recyclebin/Item/Listing/Dao.php +++ b/models/Element/Recyclebin/Item/Listing/Dao.php @@ -30,7 +30,11 @@ class Dao extends Model\Listing\Dao\AbstractDao */ public function load(): array { - $itemsData = $this->db->fetchFirstColumn('SELECT id FROM recyclebin' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $itemsData = $this->db->fetchFirstColumn( + 'SELECT id FROM recyclebin' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); $items = []; foreach ($itemsData as $itemData) { @@ -48,7 +52,11 @@ public function load(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM recyclebin ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM recyclebin ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/Element/Tag/Listing/Dao.php b/models/Element/Tag/Listing/Dao.php index 641c2937..cd9d3bc1 100644 --- a/models/Element/Tag/Listing/Dao.php +++ b/models/Element/Tag/Listing/Dao.php @@ -30,7 +30,11 @@ class Dao extends Model\Listing\Dao\AbstractDao */ public function load(): array { - $tagsData = $this->db->fetchFirstColumn('SELECT id FROM tags' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $tagsData = $this->db->fetchFirstColumn( + 'SELECT id FROM tags' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); $tags = []; foreach ($tagsData as $tagData) { @@ -49,7 +53,11 @@ public function load(): array */ public function loadIdList(): array { - $tagsIds = $this->db->fetchFirstColumn('SELECT id FROM tags' . $this->getCondition() . $this->getGroupBy() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $tagsIds = $this->db->fetchFirstColumn( + 'SELECT id FROM tags' . $this->getCondition() . $this->getGroupBy() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); return array_map(intval(...), $tagsIds); } @@ -57,7 +65,11 @@ public function loadIdList(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM tags ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM tags ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/Element/WorkflowState/Listing/Dao.php b/models/Element/WorkflowState/Listing/Dao.php index b1442eaa..93e8939b 100644 --- a/models/Element/WorkflowState/Listing/Dao.php +++ b/models/Element/WorkflowState/Listing/Dao.php @@ -30,7 +30,11 @@ class Dao extends Model\Listing\Dao\AbstractDao */ public function load(): array { - $workflowStateData = $this->db->fetchAllAssociative('SELECT cid, ctype, workflow FROM element_workflow_state' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $workflowStateData = $this->db->fetchAllAssociative( + 'SELECT cid, ctype, workflow FROM element_workflow_state' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); $workflowStates = []; foreach ($workflowStateData as $entry) { @@ -47,7 +51,11 @@ public function load(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM element_workflow_state ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM element_workflow_state ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/Schedule/Task/Listing/Dao.php b/models/Schedule/Task/Listing/Dao.php index 5ac533c6..40612841 100644 --- a/models/Schedule/Task/Listing/Dao.php +++ b/models/Schedule/Task/Listing/Dao.php @@ -31,7 +31,11 @@ class Dao extends Model\Listing\Dao\AbstractDao public function load(): array { $tasks = []; - $tasksData = $this->db->fetchFirstColumn('SELECT id FROM schedule_tasks' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $tasksData = $this->db->fetchFirstColumn( + 'SELECT id FROM schedule_tasks' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); foreach ($tasksData as $taskData) { $tasks[] = Model\Schedule\Task::getById($taskData); @@ -45,7 +49,11 @@ public function load(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM schedule_tasks ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM schedule_tasks ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/Site/Listing/Dao.php b/models/Site/Listing/Dao.php index 7d69153c..be4fddc5 100644 --- a/models/Site/Listing/Dao.php +++ b/models/Site/Listing/Dao.php @@ -26,12 +26,16 @@ class Dao extends Model\Listing\Dao\AbstractDao { /** - * Loads a list of thumanils for the specicifies parameters, returns an array of Thumbnail elements + * Loads a list of thumbnails for the specified parameters, returns an array of Thumbnail elements */ public function load(): array { $sites = []; - $sitesData = $this->db->fetchFirstColumn('SELECT id FROM sites' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $sitesData = $this->db->fetchFirstColumn( + 'SELECT id FROM sites' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); foreach ($sitesData as $siteData) { $sites[] = Model\Site::getById($siteData); @@ -45,7 +49,11 @@ public function load(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM sites ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM sites ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/Tool/Email/Blocklist/Listing/Dao.php b/models/Tool/Email/Blocklist/Listing/Dao.php index 926b09a5..da5624f8 100644 --- a/models/Tool/Email/Blocklist/Listing/Dao.php +++ b/models/Tool/Email/Blocklist/Listing/Dao.php @@ -26,11 +26,15 @@ class Dao extends Model\Listing\Dao\AbstractDao { /** - * Loads a list of static routes for the specicifies parameters, returns an array of Tool\Email\Blocklist elements + * Loads a list of static routes for the specified parameters, returns an array of Tool\Email\Blocklist elements */ public function load(): array { - $addressData = $this->db->fetchFirstColumn('SELECT address FROM email_blocklist' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $addressData = $this->db->fetchFirstColumn( + 'SELECT address FROM email_blocklist' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); $addresses = []; foreach ($addressData as $data) { @@ -47,7 +51,11 @@ public function load(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM email_blocklist ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM email_blocklist ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/Tool/Email/Log/Listing/Dao.php b/models/Tool/Email/Log/Listing/Dao.php index 200115d5..8658cc47 100644 --- a/models/Tool/Email/Log/Listing/Dao.php +++ b/models/Tool/Email/Log/Listing/Dao.php @@ -30,7 +30,11 @@ class Dao extends Model\Listing\Dao\AbstractDao */ public function load(): array { - $emailLogs = $this->db->fetchFirstColumn('SELECT id FROM email_log' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $emailLogs = $this->db->fetchFirstColumn( + 'SELECT id FROM email_log' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); $emailLogsArray = []; foreach ($emailLogs as $log) { @@ -46,7 +50,11 @@ public function load(): array */ public function getDataArray(): array { - return $this->db->fetchAllAssociative('SELECT * FROM email_log ' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return $this->db->fetchAllAssociative( + 'SELECT * FROM email_log ' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } /** @@ -55,7 +63,11 @@ public function getDataArray(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM email_log ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM email_log ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/Translation/Dao.php b/models/Translation/Dao.php index 66491598..9cd68fb2 100644 --- a/models/Translation/Dao.php +++ b/models/Translation/Dao.php @@ -135,12 +135,9 @@ public function delete(): void */ public function getAvailableLanguages(): array { - $l = $this->db->createQueryBuilder() - ->select('*') - ->from($this->getDatabaseTableName()) - ->groupBy($this->db->quoteIdentifier('language')) - ->executeQuery() - ->fetchAllAssociative(); + $l = $this->db->fetchAllAssociative( + sprintf('SELECT * FROM %s GROUP BY `language`', $this->getDatabaseTableName()) + ); $languages = []; foreach ($l as $values) { diff --git a/models/User/Listing/AbstractListing/Dao.php b/models/User/Listing/AbstractListing/Dao.php index 8b92c2bd..c1ece2fd 100644 --- a/models/User/Listing/AbstractListing/Dao.php +++ b/models/User/Listing/AbstractListing/Dao.php @@ -32,7 +32,11 @@ class Dao extends Model\Listing\Dao\AbstractDao public function load(): array { $items = []; - $usersData = $this->db->fetchAllAssociative('SELECT id,type FROM users' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $usersData = $this->db->fetchAllAssociative( + 'SELECT id,type FROM users' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); foreach ($usersData as $userData) { $className = Model\User\Service::getClassNameForType($userData['type']); diff --git a/models/User/Permission/Definition/Listing/Dao.php b/models/User/Permission/Definition/Listing/Dao.php index 02f2b02c..65316a76 100644 --- a/models/User/Permission/Definition/Listing/Dao.php +++ b/models/User/Permission/Definition/Listing/Dao.php @@ -31,7 +31,11 @@ class Dao extends Model\Listing\Dao\AbstractDao public function load(): array { $definitions = []; - $definitionsData = $this->db->fetchAllAssociative('SELECT * FROM users_permission_definitions' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $definitionsData = $this->db->fetchAllAssociative( + 'SELECT * FROM users_permission_definitions' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); foreach ($definitionsData as $definitionData) { $definition = new Model\User\Permission\Definition($definitionData); @@ -46,7 +50,11 @@ public function load(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM users_permission_definitions ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM users_permission_definitions ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/Version/Listing/Dao.php b/models/Version/Listing/Dao.php index 4a8404c6..bf9f3dbd 100644 --- a/models/Version/Listing/Dao.php +++ b/models/Version/Listing/Dao.php @@ -65,7 +65,11 @@ public function load(): array */ public function loadIdList(): array { - $versionIds = $this->db->fetchFirstColumn('SELECT id FROM versions' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + $versionIds = $this->db->fetchFirstColumn( + 'SELECT id FROM versions' . $this->getCondition() . $this->getOrder() . $this->getOffsetLimit(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); return array_map(intval(...), $versionIds); } @@ -73,7 +77,11 @@ public function loadIdList(): array public function getTotalCount(): int { try { - return (int) $this->db->fetchOne('SELECT COUNT(*) FROM versions ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) FROM versions ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } catch (Exception) { return 0; } diff --git a/models/WebsiteSetting/Listing/Dao.php b/models/WebsiteSetting/Listing/Dao.php index e5d5b7d7..ab64939d 100644 --- a/models/WebsiteSetting/Listing/Dao.php +++ b/models/WebsiteSetting/Listing/Dao.php @@ -44,6 +44,10 @@ public function load(): array public function getTotalCount(): int { - return (int) $this->db->fetchOne('SELECT COUNT(*) as amount FROM website_settings ' . $this->getCondition(), $this->model->getConditionVariables(), $this->model->getConditionVariableTypes()); + return (int) $this->db->fetchOne( + 'SELECT COUNT(*) as amount FROM website_settings ' . $this->getCondition(), + $this->model->getConditionVariables(), + $this->model->getConditionVariableTypes() + ); } } From 7fbcfde0835ce2a7f2a1901dbbfaf6042fdb5c17 Mon Sep 17 00:00:00 2001 From: Stefan Hagspiel Date: Fri, 27 Feb 2026 15:26:20 +0100 Subject: [PATCH 6/6] Refactor `Asset/Dao.php` to improve SQL query consistency, use parameterized bindings, and standardize permission checks. --- models/Asset/Dao.php | 93 ++++++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 24 deletions(-) diff --git a/models/Asset/Dao.php b/models/Asset/Dao.php index b156e12f..2b0adbe5 100644 --- a/models/Asset/Dao.php +++ b/models/Asset/Dao.php @@ -345,24 +345,47 @@ public function hasChildren(?User $user = null): bool return false; } - $query = 'SELECT `a`.`id` FROM `assets` a WHERE parentId = ? '; + $sql = 'SELECT 1 FROM assets a WHERE parentId = ?'; + $params = [$this->model->getId()]; + $types = [ParameterType::INTEGER]; if ($user && !$user->isAdmin()) { - $userIds = array_map('intval', $user->getRoles()); + $roleIds = array_map('intval', $user->getRoles()); $currentUserId = $user->getId(); - $userIds[] = $currentUserId; - - $inheritedPermission = $this->isInheritingPermission('list', $userIds); - - $anyAllowedRowOrChildren = 'EXISTS(SELECT list FROM users_workspaces_asset uwa WHERE userId IN (' . implode(',', $userIds) . ') AND list=1 AND LOCATE(CONCAT(`path`,filename),cpath)=1 AND - NOT EXISTS(SELECT list FROM users_workspaces_asset WHERE userId =' . (int) $currentUserId . ' AND list=0 AND cpath = uwa.cpath))'; - $isDisallowedCurrentRow = 'EXISTS(SELECT list FROM users_workspaces_asset WHERE userId IN (' . implode(',', $userIds) . ') AND cid = id AND list=0)'; - - $query .= ' AND IF(' . $anyAllowedRowOrChildren . ',1,IF(' . $inheritedPermission . ', ' . $isDisallowedCurrentRow . ' = 0, 0)) = 1'; + $permissionIds = [...$roleIds, $currentUserId]; + + $inheritedPermission = $this->isInheritingPermission('list', $permissionIds); + + $anyAllowedRowOrChildren = 'EXISTS( + SELECT list FROM users_workspaces_asset uwa + WHERE userId IN (?) + AND list=1 + AND LOCATE(CONCAT(`path`,filename),cpath)=1 + AND NOT EXISTS( + SELECT list FROM users_workspaces_asset + WHERE userId=? AND list=0 AND cpath = uwa.cpath + ) + )'; + + $isDisallowedCurrentRow = 'EXISTS( + SELECT list FROM users_workspaces_asset uworow + WHERE userId IN (?) + AND cid = id + AND list=0 + )'; + + $sql .= sprintf(' AND IF(%s,1,IF(%d,%s = 0,0)) = 1', $anyAllowedRowOrChildren, $inheritedPermission, $isDisallowedCurrentRow); + + $params[] = $permissionIds; + $types[] = ArrayParameterType::INTEGER; + $params[] = $currentUserId; + $types[] = ParameterType::INTEGER; + $params[] = $permissionIds; + $types[] = ArrayParameterType::INTEGER; } - $query .= ' LIMIT 1;'; - $c = $this->db->fetchOne($query, [$this->model->getId()]); + $sql .= ' LIMIT 1'; + $c = $this->db->fetchOne($sql, $params, $types); return (bool)$c; } @@ -401,22 +424,44 @@ public function getChildAmount(?User $user = null): int } $query = 'SELECT COUNT(*) AS count FROM assets WHERE parentId = ?'; + $params = [$this->model->getId()]; + $types = [ParameterType::INTEGER]; if ($user && !$user->isAdmin()) { - $userIds = array_map('intval', $user->getRoles()); + $roleIds = array_map('intval', $user->getRoles()); $currentUserId = $user->getId(); - $userIds[] = $currentUserId; - - $inheritedPermission = $this->isInheritingPermission('list', $userIds); - - $anyAllowedRowOrChildren = 'EXISTS(SELECT list FROM users_workspaces_asset uwa WHERE userId IN (' . implode(',', $userIds) . ') AND list=1 AND LOCATE(CONCAT(`path`,filename),cpath)=1 AND - NOT EXISTS(SELECT list FROM users_workspaces_asset WHERE userId =' . (int) $currentUserId . ' AND list=0 AND cpath = uwa.cpath))'; - $isDisallowedCurrentRow = 'EXISTS(SELECT list FROM users_workspaces_asset WHERE userId IN (' . implode(',', $userIds) . ') AND cid = id AND list=0)'; - - $query .= ' AND IF(' . $anyAllowedRowOrChildren . ',1,IF(' . $inheritedPermission . ', ' . $isDisallowedCurrentRow . ' = 0, 0)) = 1'; + $permissionIds = [...$roleIds, $currentUserId]; + + $inheritedPermission = $this->isInheritingPermission('list', $permissionIds); + + $anyAllowedRowOrChildren = 'EXISTS( + SELECT list FROM users_workspaces_asset uwa + WHERE userId IN (?) + AND list=1 + AND LOCATE(CONCAT(`path`,filename),cpath)=1 + AND NOT EXISTS( + SELECT list FROM users_workspaces_asset + WHERE userId=? AND list=0 AND cpath = uwa.cpath + ) + )'; + $isDisallowedCurrentRow = 'EXISTS( + SELECT list FROM users_workspaces_asset uworow + WHERE userId IN (?) + AND cid = id + AND list=0 + )'; + + $query .= sprintf(' AND IF(%s,1,IF(%d,%s = 0,0)) = 1', $anyAllowedRowOrChildren, $inheritedPermission, $isDisallowedCurrentRow); + + $params[] = $permissionIds; + $types[] = ArrayParameterType::INTEGER; + $params[] = $currentUserId; + $types[] = ParameterType::INTEGER; + $params[] = $permissionIds; + $types[] = ArrayParameterType::INTEGER; } - return (int) $this->db->fetchOne($query, [$this->model->getId()]); + return (int) $this->db->fetchOne($query, $params, $types); } public function isLocked(): bool