mirror of
https://github.com/nextcloud/server.git
synced 2026-05-22 10:06:37 -04:00
Merge pull request #59932 from nextcloud/carl/copySkeleton
refactor: Move copy skeleton step to a file listener
This commit is contained in:
commit
be2ac0bd86
10 changed files with 147 additions and 74 deletions
|
|
@ -79,6 +79,7 @@ return array(
|
|||
'OCA\\Files\\Listener\\NodeRemovedFromFavoriteListener' => $baseDir . '/../lib/Listener/NodeRemovedFromFavoriteListener.php',
|
||||
'OCA\\Files\\Listener\\RenderReferenceEventListener' => $baseDir . '/../lib/Listener/RenderReferenceEventListener.php',
|
||||
'OCA\\Files\\Listener\\SyncLivePhotosListener' => $baseDir . '/../lib/Listener/SyncLivePhotosListener.php',
|
||||
'OCA\\Files\\Listener\\UserFirstTimeLoggedInListener' => $baseDir . '/../lib/Listener/UserFirstTimeLoggedInListener.php',
|
||||
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php',
|
||||
'OCA\\Files\\Migration\\Version12101Date20221011153334' => $baseDir . '/../lib/Migration/Version12101Date20221011153334.php',
|
||||
'OCA\\Files\\Migration\\Version2003Date20241021095629' => $baseDir . '/../lib/Migration/Version2003Date20241021095629.php',
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ class ComposerStaticInitFiles
|
|||
'OCA\\Files\\Listener\\NodeRemovedFromFavoriteListener' => __DIR__ . '/..' . '/../lib/Listener/NodeRemovedFromFavoriteListener.php',
|
||||
'OCA\\Files\\Listener\\RenderReferenceEventListener' => __DIR__ . '/..' . '/../lib/Listener/RenderReferenceEventListener.php',
|
||||
'OCA\\Files\\Listener\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listener/SyncLivePhotosListener.php',
|
||||
'OCA\\Files\\Listener\\UserFirstTimeLoggedInListener' => __DIR__ . '/..' . '/../lib/Listener/UserFirstTimeLoggedInListener.php',
|
||||
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php',
|
||||
'OCA\\Files\\Migration\\Version12101Date20221011153334' => __DIR__ . '/..' . '/../lib/Migration/Version12101Date20221011153334.php',
|
||||
'OCA\\Files\\Migration\\Version2003Date20241021095629' => __DIR__ . '/..' . '/../lib/Migration/Version2003Date20241021095629.php',
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use OCA\Files\Listener\NodeAddedToFavoriteListener;
|
|||
use OCA\Files\Listener\NodeRemovedFromFavoriteListener;
|
||||
use OCA\Files\Listener\RenderReferenceEventListener;
|
||||
use OCA\Files\Listener\SyncLivePhotosListener;
|
||||
use OCA\Files\Listener\UserFirstTimeLoggedInListener;
|
||||
use OCA\Files\Notification\Notifier;
|
||||
use OCA\Files\Search\FilesSearchProvider;
|
||||
use OCA\Files\Service\TagService;
|
||||
|
|
@ -53,6 +54,7 @@ use OCP\IServerContainer;
|
|||
use OCP\ITagManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Share\IManager as IShareManager;
|
||||
use OCP\User\Events\UserFirstTimeLoggedInEvent;
|
||||
use OCP\Util;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -122,6 +124,8 @@ class Application extends App implements IBootstrap {
|
|||
$context->registerEventListener(LoadSearchPlugins::class, LoadSearchPluginsListener::class);
|
||||
$context->registerEventListener(NodeAddedToFavorite::class, NodeAddedToFavoriteListener::class);
|
||||
$context->registerEventListener(NodeRemovedFromFavorite::class, NodeRemovedFromFavoriteListener::class);
|
||||
$context->registerEventListener(UserFirstTimeLoggedInEvent::class, UserFirstTimeLoggedInListener::class);
|
||||
|
||||
$context->registerSearchProvider(FilesSearchProvider::class);
|
||||
|
||||
$context->registerNotifierService(Notifier::class);
|
||||
|
|
|
|||
44
apps/files/lib/Listener/UserFirstTimeLoggedInListener.php
Normal file
44
apps/files/lib/Listener/UserFirstTimeLoggedInListener.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Files\Listener;
|
||||
|
||||
use OC\Files\Template\TemplateManager;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Files\ISetupManager;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\User\Events\UserFirstTimeLoggedInEvent;
|
||||
|
||||
/**
|
||||
* @template-implements IEventListener<UserFirstTimeLoggedInEvent>
|
||||
*/
|
||||
class UserFirstTimeLoggedInListener implements IEventListener {
|
||||
public function __construct(
|
||||
private readonly TemplateManager $templateManager,
|
||||
private readonly ISetupManager $setupManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
if (!$event instanceof UserFirstTimeLoggedInEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $event->getUser();
|
||||
$this->setupManager->setupForUser($user);
|
||||
|
||||
try {
|
||||
// copy skeleton
|
||||
$this->templateManager->copySkeleton($user->getUID());
|
||||
} catch (NotPermittedException) {
|
||||
// read only uses
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ require_once 'public/Constants.php';
|
|||
class OC {
|
||||
/**
|
||||
* The installation path for Nextcloud on the server (e.g. /srv/http/nextcloud)
|
||||
* @internal Use auto-loaded $serverRoot with DI instead.
|
||||
*/
|
||||
public static string $SERVERROOT = '';
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -64,9 +64,11 @@ class TemplateManager implements ITemplateManager {
|
|||
private readonly IFactory $l10nFactory,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly IFilenameValidator $filenameValidator,
|
||||
private readonly string $serverRoot,
|
||||
) {
|
||||
$this->l10n = $l10nFactory->get('lib');
|
||||
$this->userId = $userSession->getUser()?->getUID();
|
||||
|
||||
}
|
||||
|
||||
#[Override]
|
||||
|
|
@ -321,8 +323,8 @@ class TemplateManager implements ITemplateManager {
|
|||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
$defaultSkeletonDirectory = \OC::$SERVERROOT . '/core/skeleton';
|
||||
$defaultTemplateDirectory = \OC::$SERVERROOT . '/core/skeleton/Templates';
|
||||
$defaultSkeletonDirectory = $this->serverRoot . '/core/skeleton';
|
||||
$defaultTemplateDirectory = $this->serverRoot . '/core/skeleton/Templates';
|
||||
$skeletonPath = $this->config->getSystemValueString('skeletondirectory', $defaultSkeletonDirectory);
|
||||
$skeletonTemplatePath = $this->config->getSystemValueString('templatedirectory', $defaultTemplateDirectory);
|
||||
$isDefaultSkeleton = $skeletonPath === $defaultSkeletonDirectory;
|
||||
|
|
@ -372,7 +374,7 @@ class TemplateManager implements ITemplateManager {
|
|||
if (!$isDefaultTemplates && $folderIsEmpty) {
|
||||
$localizedSkeletonTemplatePath = $this->getLocalizedTemplatePath($skeletonTemplatePath, $userLang);
|
||||
if (!empty($localizedSkeletonTemplatePath) && file_exists($localizedSkeletonTemplatePath)) {
|
||||
\OC_Util::copyr($localizedSkeletonTemplatePath, $folder);
|
||||
$this->copyr($localizedSkeletonTemplatePath, $folder);
|
||||
$userFolder->getStorage()->getScanner()->scan($folder->getInternalPath(), Scanner::SCAN_RECURSIVE);
|
||||
$this->setTemplatePath($userTemplatePath);
|
||||
return $userTemplatePath;
|
||||
|
|
@ -382,7 +384,7 @@ class TemplateManager implements ITemplateManager {
|
|||
if ($path !== null && $isDefaultSkeleton && $isDefaultTemplates && $folderIsEmpty) {
|
||||
$localizedSkeletonPath = $this->getLocalizedTemplatePath($skeletonPath . '/Templates', $userLang);
|
||||
if (!empty($localizedSkeletonPath) && file_exists($localizedSkeletonPath)) {
|
||||
\OC_Util::copyr($localizedSkeletonPath, $folder);
|
||||
$this->copyr($localizedSkeletonPath, $folder);
|
||||
$userFolder->getStorage()->getScanner()->scan($folder->getInternalPath(), Scanner::SCAN_RECURSIVE);
|
||||
$this->setTemplatePath($userTemplatePath);
|
||||
return $userTemplatePath;
|
||||
|
|
@ -413,4 +415,80 @@ class TemplateManager implements ITemplateManager {
|
|||
|
||||
return $localizedSkeletonTemplatePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a local directory recursively by using streams
|
||||
*/
|
||||
private function copyr(string $source, Folder $target): void {
|
||||
// Verify if folder exists
|
||||
$dir = opendir($source);
|
||||
if ($dir === false) {
|
||||
$this->logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy the files
|
||||
while (false !== ($file = readdir($dir))) {
|
||||
if (!Filesystem::isIgnoredDir($file)) {
|
||||
if (is_dir($source . '/' . $file)) {
|
||||
$child = $target->newFolder($file);
|
||||
$this->copyr($source . '/' . $file, $child);
|
||||
} else {
|
||||
$sourceStream = fopen($source . '/' . $file, 'r');
|
||||
if ($sourceStream === false) {
|
||||
$this->logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']);
|
||||
closedir($dir);
|
||||
return;
|
||||
}
|
||||
$target->newFile($file, $sourceStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
}
|
||||
|
||||
public function copySkeleton(string $userId): void {
|
||||
$user = $this->userManager->get($userId);
|
||||
if ($user === null) {
|
||||
throw new \LogicException('Trying to initialize home dir for a non-existent user');
|
||||
}
|
||||
|
||||
$userDirectory = $this->rootFolder->getUserFolder($userId);
|
||||
|
||||
$plainSkeletonDirectory = $this->config->getSystemValueString('skeletondirectory', $this->serverRoot . '/core/skeleton');
|
||||
$userLang = $this->l10nFactory->findLanguage();
|
||||
$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
|
||||
|
||||
if (!file_exists($skeletonDirectory)) {
|
||||
$dialectStart = strpos($userLang, '_');
|
||||
if ($dialectStart !== false) {
|
||||
$skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
|
||||
}
|
||||
if ($dialectStart === false || !file_exists($skeletonDirectory)) {
|
||||
$skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
|
||||
}
|
||||
if (!file_exists($skeletonDirectory)) {
|
||||
$skeletonDirectory = '';
|
||||
}
|
||||
}
|
||||
|
||||
$instanceId = $this->config->getSystemValue('instanceid', '');
|
||||
|
||||
if ($instanceId === null) {
|
||||
throw new \RuntimeException('no instance id!');
|
||||
}
|
||||
$appdata = 'appdata_' . $instanceId;
|
||||
if ($userId === $appdata) {
|
||||
throw new \RuntimeException('username is reserved name: ' . $appdata);
|
||||
}
|
||||
|
||||
if (!empty($skeletonDirectory)) {
|
||||
$this->logger->debug('copying skeleton for ' . $userId . ' from ' . $skeletonDirectory . ' to ' . $userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
|
||||
$this->copyr($skeletonDirectory, $userDirectory);
|
||||
// update the file cache
|
||||
$userDirectory->getStorage()->getScanner()->scan('', Scanner::SCAN_RECURSIVE);
|
||||
|
||||
$this->initializeTemplateDirectory(null, $userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ use OC\Hooks\PublicEmitter;
|
|||
use OC\Http\CookieHelper;
|
||||
use OC\Security\CSRF\CsrfTokenManager;
|
||||
use OC_User;
|
||||
use OC_Util;
|
||||
use OCA\DAV\Connector\Sabre\Auth;
|
||||
use OCP\AppFramework\Db\TTransactional;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
|
|
@ -28,7 +27,6 @@ use OCP\Authentication\Exceptions\ExpiredTokenException;
|
|||
use OCP\Authentication\Exceptions\InvalidTokenException;
|
||||
use OCP\EventDispatcher\GenericEvent;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IRequest;
|
||||
|
|
@ -534,21 +532,6 @@ class Session implements IUserSession, Emitter {
|
|||
}
|
||||
|
||||
if ($firstTimeLogin) {
|
||||
//we need to pass the user name, which may differ from login name
|
||||
$user = $this->getUser()->getUID();
|
||||
OC_Util::setupFS($user);
|
||||
|
||||
// TODO: lock necessary?
|
||||
//trigger creation of user home and /files folder
|
||||
$userFolder = \OC::$server->getUserFolder($user);
|
||||
|
||||
try {
|
||||
// copy skeleton
|
||||
\OC_Util::copySkeleton($user, $userFolder);
|
||||
} catch (NotPermittedException $ex) {
|
||||
// read only uses
|
||||
}
|
||||
|
||||
// trigger any other initialization
|
||||
Server::get(IEventDispatcher::class)->dispatch(IUser::class . '::firstLogin', new GenericEvent($this->getUser()));
|
||||
Server::get(IEventDispatcher::class)->dispatchTyped(new UserFirstTimeLoggedInEvent($this->getUser()));
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
*/
|
||||
use bantu\IniGetWrapper\IniGetWrapper;
|
||||
use OC\Authentication\TwoFactorAuth\Manager as TwoFactorAuthManager;
|
||||
use OC\Files\Cache\Scanner;
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\SetupManager;
|
||||
use OC\Files\Template\TemplateManager;
|
||||
use OC\Setup;
|
||||
use OC\SystemConfig;
|
||||
use OCP\App\IAppManager;
|
||||
|
|
@ -17,7 +17,6 @@ use OCP\Files\FileInfo;
|
|||
use OCP\Files\Folder;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Files\Template\ITemplateManager;
|
||||
use OCP\HintException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IGroupManager;
|
||||
|
|
@ -27,7 +26,6 @@ use OCP\IURLGenerator;
|
|||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCP\Server;
|
||||
use OCP\Share\IManager;
|
||||
|
|
@ -116,49 +114,10 @@ class OC_Util {
|
|||
* @param Folder $userDirectory
|
||||
* @throws NotFoundException
|
||||
* @throws NotPermittedException
|
||||
* @suppress PhanDeprecatedFunction
|
||||
* @deprecated 34.0.0 Not needed anymore, triggered automatically when UserFirstTimeLoggedInEvent is triggered
|
||||
*/
|
||||
public static function copySkeleton($userId, Folder $userDirectory) {
|
||||
/** @var LoggerInterface $logger */
|
||||
$logger = Server::get(LoggerInterface::class);
|
||||
|
||||
$plainSkeletonDirectory = Server::get(IConfig::class)->getSystemValueString('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
|
||||
$userLang = Server::get(IFactory::class)->findLanguage();
|
||||
$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
|
||||
|
||||
if (!file_exists($skeletonDirectory)) {
|
||||
$dialectStart = strpos($userLang, '_');
|
||||
if ($dialectStart !== false) {
|
||||
$skeletonDirectory = str_replace('{lang}', substr($userLang, 0, $dialectStart), $plainSkeletonDirectory);
|
||||
}
|
||||
if ($dialectStart === false || !file_exists($skeletonDirectory)) {
|
||||
$skeletonDirectory = str_replace('{lang}', 'default', $plainSkeletonDirectory);
|
||||
}
|
||||
if (!file_exists($skeletonDirectory)) {
|
||||
$skeletonDirectory = '';
|
||||
}
|
||||
}
|
||||
|
||||
$instanceId = Server::get(IConfig::class)->getSystemValue('instanceid', '');
|
||||
|
||||
if ($instanceId === null) {
|
||||
throw new \RuntimeException('no instance id!');
|
||||
}
|
||||
$appdata = 'appdata_' . $instanceId;
|
||||
if ($userId === $appdata) {
|
||||
throw new \RuntimeException('username is reserved name: ' . $appdata);
|
||||
}
|
||||
|
||||
if (!empty($skeletonDirectory)) {
|
||||
$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('', Scanner::SCAN_RECURSIVE);
|
||||
|
||||
/** @var ITemplateManager $templateManager */
|
||||
$templateManager = Server::get(ITemplateManager::class);
|
||||
$templateManager->initializeTemplateDirectory(null, $userId);
|
||||
}
|
||||
Server::get(TemplateManager::class)->copySkeleton($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -167,6 +126,7 @@ class OC_Util {
|
|||
* @param string $source
|
||||
* @param Folder $target
|
||||
* @return void
|
||||
* @deprecated 34.0.0 Unused, if you really need this functionality, open an issue on GitHub
|
||||
*/
|
||||
public static function copyr($source, Folder $target) {
|
||||
$logger = Server::get(LoggerInterface::class);
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@
|
|||
<file name="status.php"/>
|
||||
<file name="version.php"/>
|
||||
<file name="tests/lib/TestCase.php"/>
|
||||
<file name="tests/lib/Files/Template/*.php"/>
|
||||
<ignoreFiles>
|
||||
<directory name="apps/**/tests"/>
|
||||
<directory name="apps/**/composer"/>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ declare(strict_types=1);
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace lib\Files\Template;
|
||||
namespace Test\Files\Template;
|
||||
|
||||
use OC\AppFramework\Bootstrap\Coordinator;
|
||||
use OC\AppFramework\Bootstrap\RegistrationContext;
|
||||
|
|
@ -22,19 +22,18 @@ use OCP\IConfig;
|
|||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
use OCP\IPreview;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\L10N\IFactory;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Test\TestCase;
|
||||
|
||||
class TemplateManagerTest extends TestCase {
|
||||
|
||||
private IRootFolder $rootFolder;
|
||||
private Coordinator $bootstrapCoordinator;
|
||||
|
||||
private IRootFolder&MockObject $rootFolder;
|
||||
private Coordinator&MockObject $bootstrapCoordinator;
|
||||
private TemplateManager $templateManager;
|
||||
|
||||
#[\Override]
|
||||
|
|
@ -59,7 +58,7 @@ class TemplateManagerTest extends TestCase {
|
|||
$logger,
|
||||
);
|
||||
|
||||
$serverContainer = $this->createMock(IServerContainer::class);
|
||||
$serverContainer = $this->createMock(ContainerInterface::class);
|
||||
$eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
$this->bootstrapCoordinator = $this->createMock(Coordinator::class);
|
||||
$this->bootstrapCoordinator->method('getRegistrationContext')
|
||||
|
|
@ -84,7 +83,8 @@ class TemplateManagerTest extends TestCase {
|
|||
$config,
|
||||
$l10nFactory,
|
||||
$logger,
|
||||
$filenameValidator
|
||||
$filenameValidator,
|
||||
\OC::$SERVERROOT,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ class TemplateManagerTest extends TestCase {
|
|||
return $this->createMock(Folder::class);
|
||||
});
|
||||
$userFolder->method('nodeExists')
|
||||
->willReturnCallback(function ($path) use ($filePath, $fileDirectory) {
|
||||
->willReturnCallback(function ($path) use ($filePath, $fileDirectory): bool {
|
||||
return $path === $fileDirectory;
|
||||
});
|
||||
$this->rootFolder->method('getUserFolder')
|
||||
|
|
|
|||
Loading…
Reference in a new issue