diff --git a/config/config.sample.php b/config/config.sample.php index 216a32c8eb..9824749a25 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -317,6 +317,21 @@ $CONFIG = [ */ 'skeletondirectory' => '/path/to/nextcloud/core/skeleton', + +/** + * The directory where the template files are located. These files will be + * copied to the template directory of new users. Leave empty to not copy any + * template files. + * ``{lang}`` can be used as a placeholder for the language of the user. + * If the directory does not exist, it falls back to non dialect (from ``de_DE`` + * to ``de``). If that does not exist either, it falls back to ``default`` + * + * If this is not set creating a template directory will only happen if no custom + * ``skeletondirectory`` is defined, otherwise the shipped templates will be used + * to create a template directory for the user. + */ +'templatesdirectory' => '/path/to/nextcloud/templates', + /** * If your user backend does not allow password resets (e.g. when it's a * read-only user backend like LDAP), you can specify a custom link, where the diff --git a/lib/private/Files/Template/TemplateManager.php b/lib/private/Files/Template/TemplateManager.php index 3b033a4404..614440327e 100644 --- a/lib/private/Files/Template/TemplateManager.php +++ b/lib/private/Files/Template/TemplateManager.php @@ -26,6 +26,7 @@ declare(strict_types=1); namespace OC\Files\Template; +use OC\Files\Cache\Scanner; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Folder; use OCP\Files\File; @@ -59,6 +60,7 @@ class TemplateManager implements ITemplateManager { private $l10n; private $logger; private $userId; + private $l10nFactory; public function __construct( IServerContainer $serverContainer, @@ -67,7 +69,7 @@ class TemplateManager implements ITemplateManager { IUserSession $userSession, IPreview $previewManager, IConfig $config, - IFactory $l10n, + IFactory $l10nFactory, LoggerInterface $logger ) { $this->serverContainer = $serverContainer; @@ -75,7 +77,8 @@ class TemplateManager implements ITemplateManager { $this->rootFolder = $rootFolder; $this->previewManager = $previewManager; $this->config = $config; - $this->l10n = $l10n->get('lib'); + $this->l10nFactory = $l10nFactory; + $this->l10n = $l10nFactory->get('lib'); $this->logger = $logger; $user = $userSession->getUser(); $this->userId = $user ? $user->getUID() : null; @@ -224,14 +227,66 @@ class TemplateManager implements ITemplateManager { if ($userId !== null) { $this->userId = $userId; } - $userFolder = $this->rootFolder->getUserFolder($this->userId); - $templateDirectoryPath = $path ?? $this->l10n->t('Templates') . '/'; + + $defaultSkeletonDirectory = \OC::$SERVERROOT . '/core/skeleton'; + $defaultTemplateDirectory = \OC::$SERVERROOT . '/core/skeleton/Templates'; + $skeletonPath = $this->config->getSystemValue('skeletondirectory', $defaultSkeletonDirectory); + $skeletonTemplatePath = $this->config->getSystemValue('templatedirectory', $defaultTemplateDirectory); + $userLang = $this->l10nFactory->getUserLanguage(); + try { - $userFolder->get($templateDirectoryPath); - } catch (NotFoundException $e) { - $folder = $userFolder->newFolder($templateDirectoryPath); - $folder->newFile('Testtemplate.txt'); + $l10n = $this->l10nFactory->get('lib', $userLang); + $userFolder = $this->rootFolder->getUserFolder($this->userId); + $userTemplatePath = $path ?? $l10n->t('Templates') . '/'; + + // All locations are default so we just need to rename the directory to the users language + if ($skeletonPath === $defaultSkeletonDirectory && $skeletonTemplatePath === $defaultTemplateDirectory && $userFolder->nodeExists('Templates')) { + $newPath = $userFolder->getPath() . '/' . $userTemplatePath; + if ($newPath !== $userFolder->get('Templates')->getPath()) { + $userFolder->get('Templates')->move($newPath); + } + $this->setTemplatePath($userTemplatePath); + return; + } + + // A custom template directory is specified + if (!empty($skeletonTemplatePath) && $skeletonTemplatePath !== $defaultTemplateDirectory) { + // In case the shipped template files are in place we remove them + if ($skeletonPath === $defaultSkeletonDirectory && $userFolder->nodeExists('Templates')) { + $shippedSkeletonTemplates = $userFolder->get('Templates'); + $shippedSkeletonTemplates->delete(); + } + try { + $userFolder->get($userTemplatePath); + } catch (NotFoundException $e) { + $folder = $userFolder->newFolder($userTemplatePath); + + $localizedSkeletonTemplatePath = $this->getLocalizedTemplatePath($skeletonTemplatePath, $userLang); + if (!empty($localizedSkeletonTemplatePath) && file_exists($localizedSkeletonTemplatePath)) { + \OC_Util::copyr($localizedSkeletonTemplatePath, $folder); + $userFolder->getStorage()->getScanner()->scan($userTemplatePath, Scanner::SCAN_RECURSIVE); + } + } + $this->setTemplatePath($userTemplatePath); + } + } catch (\Throwable $e) { + $this->logger->error('Failed to rename templates directory to user language ' . $userLang . ' for ' . $userId, ['app' => 'files_templates']); } - $this->setTemplatePath($templateDirectoryPath); + } + + private function getLocalizedTemplatePath(string $skeletonTemplatePath, string $userLang) { + $localizedSkeletonTemplatePath = str_replace('{lang}', $userLang, $skeletonTemplatePath); + + if (!file_exists($localizedSkeletonTemplatePath)) { + $dialectStart = strpos($userLang, '_'); + if ($dialectStart !== false) { + $localizedSkeletonTemplatePath = str_replace('{lang}', substr($userLang, 0, $dialectStart), $skeletonTemplatePath); + } + if ($dialectStart === false || !file_exists($localizedSkeletonTemplatePath)) { + $localizedSkeletonTemplatePath = str_replace('{lang}', 'default', $skeletonTemplatePath); + } + } + + return $localizedSkeletonTemplatePath; } } diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php index 16e68b07cf..05d54cf84e 100644 --- a/lib/private/legacy/OC_Util.php +++ b/lib/private/legacy/OC_Util.php @@ -72,6 +72,7 @@ use OCP\IGroupManager; use OCP\ILogger; use OCP\IUser; use OCP\IUserSession; +use Psr\Log\LoggerInterface; class OC_Util { public static $scripts = []; @@ -412,6 +413,9 @@ class OC_Util { * @suppress PhanDeprecatedFunction */ public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) { + /** @var LoggerInterface $logger */ + $logger = \OC::$server->get(LoggerInterface::class); + $plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton'); $userLang = \OC::$server->getL10NFactory()->findLanguage(); $skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory); @@ -440,14 +444,12 @@ class OC_Util { } if (!empty($skeletonDirectory)) { - \OCP\Util::writeLog( - 'files_skeleton', - 'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'), - ILogger::DEBUG - ); + $logger->debug('copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'), ['app' => 'files_skeleton']); self::copyr($skeletonDirectory, $userDirectory); // update the file cache $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE); + + /** @var ITemplateManager $templateManaer */ $templateManaer = \OC::$server->get(ITemplateManager::class); $templateManaer->initializeTemplateDirectory(null, $userId); } diff --git a/lib/public/Files/Template/ITemplateManager.php b/lib/public/Files/Template/ITemplateManager.php index 94545c17b4..28d57a8b94 100644 --- a/lib/public/Files/Template/ITemplateManager.php +++ b/lib/public/Files/Template/ITemplateManager.php @@ -78,10 +78,11 @@ interface ITemplateManager { public function getTemplatePath(): string; /** - * @param string $path + * @param string|null $path + * @param string|null $userId * @since 21.0.0 */ - public function initializeTemplateDirectory(string $path): void; + public function initializeTemplateDirectory(string $path = null, string $userId = null): void; /** * @param string $filePath diff --git a/lib/public/Files/Template/Template.php b/lib/public/Files/Template/Template.php index b5b90e01f8..28fd00d0f8 100644 --- a/lib/public/Files/Template/Template.php +++ b/lib/public/Files/Template/Template.php @@ -28,6 +28,9 @@ namespace OCP\Files\Template; use OCP\Files\File; +/** + * @since 21.0.0 + */ class Template implements \JsonSerializable { protected $templateType; protected $templateId; @@ -35,20 +38,32 @@ class Template implements \JsonSerializable { protected $hasPreview = false; protected $previewUrl; + /** + * @since 21.0.0 + */ final public function __construct(string $templateType, string $templateId, File $file) { $this->templateType = $templateType; $this->templateId = $templateId; $this->file = $file; } + /** + * @since 21.0.0 + */ final public function setCustomPreviewUrl(string $previewUrl): void { $this->previewUrl = $previewUrl; } + /** + * @since 21.0.0 + */ final public function setHasPreview(bool $hasPreview): void { $this->hasPreview = $hasPreview; } + /** + * @since 21.0.0 + */ final public function jsonSerialize() { return [ 'templateType' => $this->templateType,