From afa42fd7d278db0ba9a642bc26a23bfdb5a76f4d Mon Sep 17 00:00:00 2001 From: Andrew Summers <18727110+summersab@users.noreply.github.com> Date: Fri, 15 Sep 2023 13:00:01 -0500 Subject: [PATCH] Allow options to be provided to Doctrine Signed-off-by: Andrew Summers <18727110+summersab@users.noreply.github.com> Fix issues with return type for `OC_App::findAppInDirectories` --- config/config.sample.php | 28 +++++++++++++++ lib/private/AppFramework/App.php | 51 +++++++++++++++++++++++++--- lib/private/DB/ConnectionFactory.php | 49 +++++++++++++++++++++++++- lib/private/legacy/OC_App.php | 2 +- 4 files changed, 124 insertions(+), 6 deletions(-) diff --git a/config/config.sample.php b/config/config.sample.php index 32354966949da..3bae63936cd30 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1824,6 +1824,34 @@ PDO::MYSQL_ATTR_INIT_COMMAND => 'SET wait_timeout = 28800' ], +/** + * Allows defining custom DB types and options as well as the ability to override + * the default connection parameters as defined by + * OC\DB\ConnectionFactory::defaultConnectionParams. + */ +'dbconnectionparams' => [ + 'dbtype' => [ + 'adapter' => AdapterMySQL::class, + 'charset' => 'UTF8', + 'driver' => 'pdo_dbtype', + 'wrapperClass' => Doctrine\DBAL\Connection::class, + ], +], + +/** + * Allows additional configuration options to be provided for the database + * connection as defined by Doctrine\DBAL\Configuration::class. This is useful + * for specifying a custom logger, middlewares, etc. + */ +'dbconfigurationparams' => [ + "sqllogger" => 'Doctrine\DBAL\Logging\SQLLogger', + "resultcache" => 'Psr\Cache\CacheItemPoolInterface', + "schemaassetsfilter" => 'callable', + "autocommit" => true, + "middlewares" => array( + 'Doctrine\DBAL\Driver\Middleware', + ), +], /** * sqlite3 journal mode can be specified using this configuration parameter - * can be 'WAL' or 'DELETE' see for more details https://www.sqlite.org/wal.html diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php index ffd77da888ed5..99b8bc0ea94e9 100644 --- a/lib/private/AppFramework/App.php +++ b/lib/private/AppFramework/App.php @@ -34,16 +34,15 @@ use OC\AppFramework\DependencyInjection\DIContainer; use OC\AppFramework\Http\Dispatcher; use OC\AppFramework\Http\Request; -use OCP\App\IAppManager; -use OCP\Profiler\IProfiler; use OC\Profiler\RoutingDataCollector; -use OCP\AppFramework\QueryException; use OCP\AppFramework\Http; use OCP\AppFramework\Http\ICallbackResponse; use OCP\AppFramework\Http\IOutput; +use OCP\AppFramework\QueryException; use OCP\Diagnostics\IEventLogger; use OCP\HintException; use OCP\IRequest; +use OCP\Profiler\IProfiler; /** * Entry point for every request in your app. You can consider this as your @@ -69,7 +68,7 @@ public static function buildAppNamespace(string $appId, string $topNamespace = ' return $topNamespace . self::$nameSpaceCache[$appId]; } - $appInfo = \OCP\Server::get(IAppManager::class)->getAppInfo($appId); + $appInfo = self::getAppInfo($appId); if (isset($appInfo['namespace'])) { self::$nameSpaceCache[$appId] = trim($appInfo['namespace']); } else { @@ -91,11 +90,42 @@ public static function buildAppNamespace(string $appId, string $topNamespace = ' return $topNamespace . self::$nameSpaceCache[$appId]; } + public static function getAppInfo(string $appId, bool $path = false, $lang = null) { + if ($path) { + $file = $appId; + } else { + $dir = \OC_App::findAppInDirectories($appId); + + if ($dir === false) { + return null; + } + + $appPath = $dir['path'] . '/' . $appId; + $file = $appPath . '/appinfo/info.xml'; + } + + $parser = new \OC\App\InfoParser(); + $data = $parser->parse($file); + + if (is_array($data)) { + $data = \OC_App::parseAppInfo($data, $lang); + } + + return $data; + } + public static function getAppIdForClass(string $className, string $topNamespace = 'OCA\\'): ?string { if (!str_starts_with($className, $topNamespace)) { return null; } + if (str_starts_with($className, $topNamespace)) { + $classNoTopNamespace = substr($className, strlen($topNamespace)); + $appNameParts = explode('\\', $classNoTopNamespace, 2); + $appName = reset($appNameParts); + return strtolower($appName); + } + foreach (self::$nameSpaceCache as $appId => $namespace) { if (str_starts_with($className, $topNamespace . $namespace . '\\')) { return $appId; @@ -105,6 +135,19 @@ public static function getAppIdForClass(string $className, string $topNamespace return null; } + public static function registerAppClass(string $className): void { + $classParts = explode('\\', $className, 2); + $topNamespace = reset($classParts) . '\\'; + $appId = self::getAppIdForClass($className, $topNamespace); + $dir = \OC_App::findAppInDirectories($appId); + + if ($dir === false) { + throw new \OCP\App\AppPathNotFoundException('App not found in any app directory'); + } + + $appPath = $dir['path'] . '/' . $appId; + \OC_App::registerAutoloading($appId, $appPath); + } /** * Shortcut for calling a controller method and printing the result diff --git a/lib/private/DB/ConnectionFactory.php b/lib/private/DB/ConnectionFactory.php index 1b0ac4363647a..df594c53de93b 100644 --- a/lib/private/DB/ConnectionFactory.php +++ b/lib/private/DB/ConnectionFactory.php @@ -33,6 +33,7 @@ use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Event\Listeners\OracleSessionInit; use Doctrine\DBAL\Event\Listeners\SQLSessionInit; +use OC\AppFramework\App; use OC\SystemConfig; /** @@ -93,6 +94,21 @@ public function __construct(SystemConfig $systemConfig) { if ($collationOverride) { $this->defaultConnectionParams['mysql']['collation'] = $collationOverride; } + + $connectionParams = $this->config->getValue('dbconnectionparams', array()) ?: array(); + + foreach ($connectionParams as $type) { + foreach ($type as $key => $param) { + switch ($key) { + case 'adapter': + case 'wrapperClass': + \OC::$server->get(App::class)::registerAppClass($param); + break; + } + } + } + + $this->defaultConnectionParams = array_replace_recursive($this->defaultConnectionParams, $connectionParams); } /** @@ -158,9 +174,40 @@ public function getConnection($type, $additionalConnectionParams) { break; } /** @var Connection $connection */ + $configuration = new Configuration(); + + foreach ($this->config->getValue('dbconfigurationparams', array()) as $param => $value) { + switch ($param) { + case "sqllogger": + \OC::$server->get(App::class)::registerAppClass($value); + $configuration->setSQLLogger(new $value()); + break; + case "resultcache": + \OC::$server->get(App::class)::registerAppClass($value); + $configuration->setResultCache(new $value()); + break; + case "schemaassetsfilter": + $configuration->setSchemaAssetsFilter($value); + break; + case "autocommit": + $configuration->setAutoCommit($value); + break; + case "middlewares": + $middlewares = array(); + + foreach ($value as $middleware) { + \OC::$server->get(App::class)::registerAppClass($middleware); + array_push($middlewares, new $middleware()); + } + + $configuration->setMiddlewares($value); + break; + } + } + $connection = DriverManager::getConnection( array_merge($this->getDefaultConnectionParams($type), $additionalConnectionParams), - new Configuration(), + $configuration, $eventManager ); return $connection; diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index ac449a62a4ffb..cab769e5b3e8c 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -302,7 +302,7 @@ public static function getInstallPath() { * * @param string $appId * @param bool $ignoreCache ignore cache and rebuild it - * @return false|string + * @return false|array */ public static function findAppInDirectories(string $appId, bool $ignoreCache = false) { $sanitizedAppId = self::cleanAppId($appId);