diff options
Diffstat (limited to 'lib/public')
526 files changed, 11271 insertions, 2317 deletions
diff --git a/lib/public/Accounts/IAccount.php b/lib/public/Accounts/IAccount.php index 906fd8baf38..f9218f5fdf6 100644 --- a/lib/public/Accounts/IAccount.php +++ b/lib/public/Accounts/IAccount.php @@ -22,7 +22,7 @@ interface IAccount extends \JsonSerializable { * * @since 15.0.0 * - * @param string $property Must be one of the PROPERTY_ prefixed constants of \OCP\Accounts\IAccountManager + * @param string $property Must be one of the PROPERTY_ prefixed constants of \OCP\Accounts\IAccountManager * @param string $value * @param string $scope Must be one of the VISIBILITY_ prefixed constants of \OCP\Accounts\IAccountManager * @param string $verified \OCP\Accounts\IAccountManager::NOT_VERIFIED | \OCP\Accounts\IAccountManager::VERIFICATION_IN_PROGRESS | \OCP\Accounts\IAccountManager::VERIFIED diff --git a/lib/public/Accounts/IAccountManager.php b/lib/public/Accounts/IAccountManager.php index 9168e17ea10..ae5535ef13b 100644 --- a/lib/public/Accounts/IAccountManager.php +++ b/lib/public/Accounts/IAccountManager.php @@ -48,30 +48,6 @@ interface IAccountManager { public const SCOPE_PUBLISHED = 'v2-published'; /** - * Contact details only visible locally - * - * @since 15.0.0 - * @deprecated 21.0.1 - */ - public const VISIBILITY_PRIVATE = 'private'; - - /** - * Contact details visible on trusted federated servers. - * - * @since 15.0.0 - * @deprecated 21.0.1 - */ - public const VISIBILITY_CONTACTS_ONLY = 'contacts'; - - /** - * Contact details visible on trusted federated servers and in the public lookup server. - * - * @since 15.0.0 - * @deprecated 21.0.1 - */ - public const VISIBILITY_PUBLIC = 'public'; - - /** * The list of allowed scopes * * @since 25.0.0 @@ -81,9 +57,6 @@ interface IAccountManager { self::SCOPE_LOCAL, self::SCOPE_FEDERATED, self::SCOPE_PUBLISHED, - self::VISIBILITY_PRIVATE, - self::VISIBILITY_CONTACTS_ONLY, - self::VISIBILITY_PUBLIC, ]; /** @@ -98,6 +71,7 @@ interface IAccountManager { /** * @since 27.0.0 + * @deprecated 27.0.0 only added for backwards compatibility with provisioning_api UsersController::getCurrentUser */ public const PROPERTY_DISPLAYNAME_LEGACY = 'display-name'; @@ -123,10 +97,16 @@ interface IAccountManager { /** * @since 15.0.0 + * @deprecated 32.0.0 */ public const PROPERTY_TWITTER = 'twitter'; /** + * @since 32.0.0 + */ + public const PROPERTY_BLUESKY = 'bluesky'; + + /** * @since 26.0.0 */ public const PROPERTY_FEDIVERSE = 'fediverse'; @@ -162,25 +142,32 @@ interface IAccountManager { public const PROPERTY_BIRTHDATE = 'birthdate'; /** + * @since 31.0.0 + */ + public const PROPERTY_PRONOUNS = 'pronouns'; + + /** * The list of allowed properties * * @since 25.0.0 */ public const ALLOWED_PROPERTIES = [ + self::PROPERTY_ADDRESS, self::PROPERTY_AVATAR, + self::PROPERTY_BIOGRAPHY, + self::PROPERTY_BIRTHDATE, self::PROPERTY_DISPLAYNAME, - self::PROPERTY_PHONE, self::PROPERTY_EMAIL, - self::PROPERTY_WEBSITE, - self::PROPERTY_ADDRESS, - self::PROPERTY_TWITTER, self::PROPERTY_FEDIVERSE, - self::PROPERTY_ORGANISATION, - self::PROPERTY_ROLE, self::PROPERTY_HEADLINE, - self::PROPERTY_BIOGRAPHY, + self::PROPERTY_ORGANISATION, + self::PROPERTY_PHONE, self::PROPERTY_PROFILE_ENABLED, - self::PROPERTY_BIRTHDATE, + self::PROPERTY_PRONOUNS, + self::PROPERTY_ROLE, + self::PROPERTY_TWITTER, + self::PROPERTY_BLUESKY, + self::PROPERTY_WEBSITE, ]; @@ -228,9 +215,9 @@ interface IAccountManager { * Search for users based on account data * * @param string $property - property or property collection name – since - * NC 22 the implementation MAY add a fitting property collection into the - * search even if a property name was given e.g. email property and email - * collection) + * NC 22 the implementation MAY add a fitting property collection into the + * search even if a property name was given e.g. email property and email + * collection) * @param string[] $values * @return array * diff --git a/lib/public/Accounts/PropertyDoesNotExistException.php b/lib/public/Accounts/PropertyDoesNotExistException.php index d0f10cdf12f..1d27ef1df30 100644 --- a/lib/public/Accounts/PropertyDoesNotExistException.php +++ b/lib/public/Accounts/PropertyDoesNotExistException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Accounts/UserUpdatedEvent.php b/lib/public/Accounts/UserUpdatedEvent.php index 8be8a31b08f..fe9ea62a302 100644 --- a/lib/public/Accounts/UserUpdatedEvent.php +++ b/lib/public/Accounts/UserUpdatedEvent.php @@ -12,6 +12,8 @@ use OCP\EventDispatcher\Event; use OCP\IUser; /** + * This event is triggered when the account data of a user was updated. + * * @since 28.0.0 */ class UserUpdatedEvent extends Event { diff --git a/lib/public/Activity/ActivitySettings.php b/lib/public/Activity/ActivitySettings.php index 24e4681ee54..fa187164e19 100644 --- a/lib/public/Activity/ActivitySettings.php +++ b/lib/public/Activity/ActivitySettings.php @@ -38,8 +38,8 @@ abstract class ActivitySettings implements ISetting { /** * @return int whether the filter should be rather on the top or bottom of - * the admin section. The filters are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. * @since 20.0.0 */ abstract public function getPriority(); diff --git a/lib/public/Activity/IBulkConsumer.php b/lib/public/Activity/IBulkConsumer.php new file mode 100644 index 00000000000..9fdf3516b9a --- /dev/null +++ b/lib/public/Activity/IBulkConsumer.php @@ -0,0 +1,24 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Activity; + +/** + * Interface IBulkConsumer + * + * @since 32.0.0 + */ +interface IBulkConsumer extends IConsumer { + /** + * @param IEvent $event + * @param array $affectedUserIds + * @param ISetting $setting + * @return void + * @since 32.0.0 + */ + public function bulkReceive(IEvent $event, array $affectedUserIds, ISetting $setting): void; +} diff --git a/lib/public/Activity/IEvent.php b/lib/public/Activity/IEvent.php index f204de599b9..6014b75123c 100644 --- a/lib/public/Activity/IEvent.php +++ b/lib/public/Activity/IEvent.php @@ -121,7 +121,7 @@ interface IEvent { * See https://github.com/nextcloud/server/issues/1706 for more information. * * @param string $subject - * @param array $parameters + * @param array<string, array<string, string>> $parameters * @return $this * @throws InvalidValueException if the subject or parameters are invalid * @since 11.0.0 @@ -136,7 +136,7 @@ interface IEvent { public function getRichSubject(): string; /** - * @return array[] + * @return array<string, array<string, string>> * @since 11.0.0 */ public function getRichSubjectParameters(): array; @@ -187,7 +187,7 @@ interface IEvent { * See https://github.com/nextcloud/server/issues/1706 for more information. * * @param string $message - * @param array $parameters + * @param array<string, array<string, string>> $parameters * @return $this * @throws \InvalidArgumentException if the message or parameters are invalid * @since 11.0.0 @@ -202,7 +202,7 @@ interface IEvent { public function getRichMessage(): string; /** - * @return array[] + * @return array<string, array<string, string>> * @since 11.0.0 */ public function getRichMessageParameters(): array; @@ -310,6 +310,10 @@ interface IEvent { public function getLink(): string; /** + * Set the absolute url for the icon (should be colored black or not have a color) + * + * It's automatically color inverted by clients when needed + * * @param string $icon * @return $this * @throws InvalidValueException if the icon is invalid @@ -319,6 +323,10 @@ interface IEvent { public function setIcon(string $icon): self; /** + * Get the absolute url for the icon (should be colored black or not have a color) + * + * It's automatically color inverted by clients when needed + * * @return string * @since 11.0.0 */ diff --git a/lib/public/Activity/IEventMerger.php b/lib/public/Activity/IEventMerger.php index e9355c88004..5d0f691f2d4 100644 --- a/lib/public/Activity/IEventMerger.php +++ b/lib/public/Activity/IEventMerger.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Activity/IFilter.php b/lib/public/Activity/IFilter.php index 2f1f4ccda80..008de6f5bca 100644 --- a/lib/public/Activity/IFilter.php +++ b/lib/public/Activity/IFilter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -25,8 +26,8 @@ interface IFilter { /** * @return int whether the filter should be rather on the top or bottom of - * the admin section. The filters are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority(); diff --git a/lib/public/Activity/IManager.php b/lib/public/Activity/IManager.php index 30020b6eced..d638b8b2c6b 100644 --- a/lib/public/Activity/IManager.php +++ b/lib/public/Activity/IManager.php @@ -52,6 +52,20 @@ interface IManager { public function publish(IEvent $event): void; /** + * Bulk publish an event for multiple users + * taking into account the app specific activity settings + * + * Make sure to call at least the following methods before sending an Event: + * - setApp() + * - setType() + * + * @param IEvent $event + * @throws IncompleteActivityException if required values have not been set + * @since 32.0.0 + */ + public function bulkPublish(IEvent $event, array $affectedUserIds, ISetting $setting): void; + + /** * In order to improve lazy loading a closure can be registered which will be called in case * activity consumers are actually requested * diff --git a/lib/public/Activity/IProvider.php b/lib/public/Activity/IProvider.php index 17fffbb26e2..dec4e7ade64 100644 --- a/lib/public/Activity/IProvider.php +++ b/lib/public/Activity/IProvider.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Activity/ISetting.php b/lib/public/Activity/ISetting.php index 306a0d85632..1304ab8c658 100644 --- a/lib/public/Activity/ISetting.php +++ b/lib/public/Activity/ISetting.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -25,8 +26,8 @@ interface ISetting { /** * @return int whether the filter should be rather on the top or bottom of - * the admin section. The filters are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority(); diff --git a/lib/public/App/AppPathNotFoundException.php b/lib/public/App/AppPathNotFoundException.php index 5e3ea7a9919..56d6571ea68 100644 --- a/lib/public/App/AppPathNotFoundException.php +++ b/lib/public/App/AppPathNotFoundException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/App/Events/AppDisableEvent.php b/lib/public/App/Events/AppDisableEvent.php index 00a74663667..5d16d42785e 100644 --- a/lib/public/App/Events/AppDisableEvent.php +++ b/lib/public/App/Events/AppDisableEvent.php @@ -11,6 +11,8 @@ namespace OCP\App\Events; use OCP\EventDispatcher\Event; /** + * This event is triggered when an app is disabled. + * * @since 27.0.0 */ class AppDisableEvent extends Event { diff --git a/lib/public/App/Events/AppEnableEvent.php b/lib/public/App/Events/AppEnableEvent.php index 70d42263d3e..fe1523d6a83 100644 --- a/lib/public/App/Events/AppEnableEvent.php +++ b/lib/public/App/Events/AppEnableEvent.php @@ -11,6 +11,8 @@ namespace OCP\App\Events; use OCP\EventDispatcher\Event; /** + * This event is triggered when an app is enabled. + * * @since 27.0.0 */ class AppEnableEvent extends Event { diff --git a/lib/public/App/Events/AppUpdateEvent.php b/lib/public/App/Events/AppUpdateEvent.php index de88d65b68b..2cf59ff7949 100644 --- a/lib/public/App/Events/AppUpdateEvent.php +++ b/lib/public/App/Events/AppUpdateEvent.php @@ -11,18 +11,18 @@ namespace OCP\App\Events; use OCP\EventDispatcher\Event; /** + * This event is triggered when an app is updated. + * * @since 27.0.0 */ class AppUpdateEvent extends Event { - private string $appId; - /** * @since 27.0.0 */ - public function __construct(string $appId) { + public function __construct( + private readonly string $appId, + ) { parent::__construct(); - - $this->appId = $appId; } /** diff --git a/lib/public/App/IAppManager.php b/lib/public/App/IAppManager.php index 508143426f5..20019ce1ffd 100644 --- a/lib/public/App/IAppManager.php +++ b/lib/public/App/IAppManager.php @@ -20,15 +20,28 @@ use OCP\IUser; */ interface IAppManager { /** - * Returns the app information from "appinfo/info.xml". + * @since 30.0.0 + */ + public const BACKEND_CALDAV = 'caldav'; + + /** + * Returns the app information from "appinfo/info.xml" for an app * * @param string|null $lang * @return array|null * @since 14.0.0 + * @since 31.0.0 Usage of $path is discontinued and throws an \InvalidArgumentException, use {@see self::getAppInfoByPath} instead. */ public function getAppInfo(string $appId, bool $path = false, $lang = null); /** + * Returns the app information from a given path ending with "/appinfo/info.xml" + * + * @since 31.0.0 + */ + public function getAppInfoByPath(string $path, ?string $lang = null): ?array; + + /** * Returns the app information from "appinfo/info.xml". * * @param string $appId @@ -39,6 +52,14 @@ interface IAppManager { public function getAppVersion(string $appId, bool $useCache = true): string; /** + * Returns the installed version of all apps + * + * @return array<string, string> + * @since 32.0.0 + */ + public function getAppInstalledVersions(bool $onlyEnabled = false): array; + + /** * Returns the app icon or null if none is found * * @param string $appId @@ -46,7 +67,7 @@ interface IAppManager { * @return string|null * @since 29.0.0 */ - public function getAppIcon(string $appId, bool $dark = false): string|null; + public function getAppIcon(string $appId, bool $dark = false): ?string; /** * Check if an app is enabled for user @@ -66,10 +87,18 @@ interface IAppManager { * @param string $appId * @return bool * @since 8.0.0 + * @deprecated 32.0.0 Use either {@see self::isEnabledForUser} or {@see self::isEnabledForAnyone} */ public function isInstalled($appId); /** + * Check if an app is enabled in the instance, either for everyone or for specific groups + * + * @since 32.0.0 + */ + public function isEnabledForAnyone(string $appId): bool; + + /** * Check if an app should be enabled by default * * Notice: This actually checks if the app should be enabled by default @@ -131,17 +160,15 @@ interface IAppManager { * @param bool $automaticDisabled * @since 8.0.0 */ - public function disableApp($appId, $automaticDisabled = false); + public function disableApp($appId, $automaticDisabled = false): void; /** * Get the directory for the given app. * - * @param string $appId - * @return string * @since 11.0.0 * @throws AppPathNotFoundException */ - public function getAppPath($appId); + public function getAppPath(string $appId): string; /** * Get the web path for the given app. @@ -157,7 +184,7 @@ interface IAppManager { * List all apps enabled for a user * * @param \OCP\IUser $user - * @return string[] + * @return list<string> * @since 8.1.0 */ public function getEnabledAppsForUser(IUser $user); @@ -167,14 +194,23 @@ interface IAppManager { * * @return string[] * @since 8.1.0 + * @deprecated 32.0.0 Use either {@see self::getEnabledApps} or {@see self::getEnabledAppsForUser} */ public function getInstalledApps(); /** + * List all apps enabled, either for everyone or for specific groups only + * + * @return list<string> + * @since 32.0.0 + */ + public function getEnabledApps(): array; + + /** * Clear the cached list of apps when enabling/disabling an app * @since 8.1.0 */ - public function clearAppsCache(); + public function clearAppsCache(): void; /** * @param string $appId @@ -190,7 +226,7 @@ interface IAppManager { * @return bool * * This function walks through the Nextcloud directory and loads all apps - * it can find. A directory contains an app if the file /appinfo/info.xml + * it can find. A directory contains an app if the file `/appinfo/info.xml` * exists. * * if $types is set to non-empty array, only apps of those types will be loaded @@ -242,6 +278,8 @@ interface IAppManager { * * @since 25.0.6 * @since 28.0.0 Added optional $withFallbacks parameter + * @deprecated 31.0.0 + * Use @see \OCP\INavigationManager::getDefaultEntryIdForUser() instead */ public function getDefaultAppForUser(?IUser $user = null, bool $withFallbacks = true): string; @@ -250,15 +288,56 @@ interface IAppManager { * * @return string[] The default applications * @since 28.0.0 + * @deprecated 31.0.0 + * Use @see \OCP\INavigationManager::getDefaultEntryIds() instead */ public function getDefaultApps(): array; /** * Set the global default apps with fallbacks * - * @param string[] $appId + * @param string[] $defaultApps * @throws \InvalidArgumentException If any of the apps is not installed * @since 28.0.0 + * @deprecated 31.0.0 + * Use @see \OCP\INavigationManager::setDefaultEntryIds() instead */ public function setDefaultApps(array $defaultApps): void; + + /** + * Check whether the given backend is required by at least one app. + * + * @param self::BACKEND_* $backend Name of the backend, one of `self::BACKEND_*` + * @return bool True if at least one app requires the backend + * + * @since 30.0.0 + */ + public function isBackendRequired(string $backend): bool; + + /** + * Clean the appId from forbidden characters + * + * @psalm-taint-escape callable + * @psalm-taint-escape cookie + * @psalm-taint-escape file + * @psalm-taint-escape has_quotes + * @psalm-taint-escape header + * @psalm-taint-escape html + * @psalm-taint-escape include + * @psalm-taint-escape ldap + * @psalm-taint-escape shell + * @psalm-taint-escape sql + * @psalm-taint-escape unserialize + * + * @since 31.0.0 + */ + public function cleanAppId(string $app): string; + + /** + * Get a list of all apps in the apps folder + * + * @return list<string> an array of app names (string IDs) + * @since 31.0.0 + */ + public function getAllAppsInAppsFolders(): array; } diff --git a/lib/public/AppFramework/ApiController.php b/lib/public/AppFramework/ApiController.php index da1152090c6..729582c8505 100644 --- a/lib/public/AppFramework/ApiController.php +++ b/lib/public/AppFramework/ApiController.php @@ -7,6 +7,7 @@ */ namespace OCP\AppFramework; +use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\Response; @@ -26,13 +27,13 @@ abstract class ApiController extends Controller { * @param string $appName the name of the app * @param IRequest $request an instance of the request * @param string $corsMethods comma separated string of HTTP verbs which - * should be allowed for websites or webapps when calling your API, defaults to - * 'PUT, POST, GET, DELETE, PATCH' + * should be allowed for websites or webapps when calling your API, defaults to + * 'PUT, POST, GET, DELETE, PATCH' * @param string $corsAllowedHeaders comma separated string of HTTP headers - * which should be allowed for websites or webapps when calling your API, - * defaults to 'Authorization, Content-Type, Accept' + * which should be allowed for websites or webapps when calling your API, + * defaults to 'Authorization, Content-Type, Accept' * @param int $corsMaxAge number in seconds how long a preflighted OPTIONS - * request should be cached, defaults to 1728000 seconds + * request should be cached, defaults to 1728000 seconds * @since 7.0.0 */ public function __construct($appName, @@ -51,17 +52,14 @@ abstract class ApiController extends Controller { * This method implements a preflighted cors response for you that you can * link to for the options request * - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage * @since 7.0.0 */ #[NoCSRFRequired] #[PublicPage] + #[NoAdminRequired] public function preflightedCors() { - if (isset($this->request->server['HTTP_ORIGIN'])) { - $origin = $this->request->server['HTTP_ORIGIN']; - } else { + $origin = $this->request->getHeader('origin'); + if ($origin === '') { $origin = '*'; } diff --git a/lib/public/AppFramework/App.php b/lib/public/AppFramework/App.php index a8ba6b701f9..c00fde47418 100644 --- a/lib/public/AppFramework/App.php +++ b/lib/public/AppFramework/App.php @@ -9,10 +9,10 @@ declare(strict_types=1); */ namespace OCP\AppFramework; -use OC\AppFramework\Routing\RouteConfig; -use OC\Route\Router; +use OC\AppFramework\Utility\SimpleContainer; use OC\ServerContainer; -use OCP\Route\IRouter; +use OCP\IConfig; +use OCP\Server; use Psr\Log\LoggerInterface; /** @@ -32,7 +32,7 @@ class App { * some_app_id -> OCA\SomeAppId * @param string $appId the app id * @param string $topNamespace the namespace which should be prepended to - * the transformed app id, defaults to OCA\ + * the transformed app id, defaults to OCA\ * @return string the starting namespace for the app * @since 8.0.0 */ @@ -47,7 +47,7 @@ class App { * @since 6.0.0 */ public function __construct(string $appName, array $urlParams = []) { - $runIsSetupDirectly = \OC::$server->getConfig()->getSystemValueBool('debug') + $runIsSetupDirectly = Server::get(IConfig::class)->getSystemValueBool('debug') && !ini_get('zend.exception_ignore_args'); if ($runIsSetupDirectly) { @@ -58,23 +58,30 @@ class App { $classNameParts = explode('\\', trim($applicationClassName, '\\')); foreach ($e->getTrace() as $step) { - if (isset($step['class'], $step['function'], $step['args'][0]) && - $step['class'] === ServerContainer::class && - $step['function'] === 'query' && - $step['args'][0] === $applicationClassName) { + if (isset($step['class'], $step['function'], $step['args'][0]) + && $step['class'] === ServerContainer::class + && $step['function'] === 'query' + && $step['args'][0] === $applicationClassName) { $setUpViaQuery = true; break; - } elseif (isset($step['class'], $step['function'], $step['args'][0]) && - $step['class'] === ServerContainer::class && - $step['function'] === 'getAppContainer' && - $step['args'][1] === $classNameParts[1]) { + } elseif (isset($step['class'], $step['function'], $step['args'][0]) + && $step['class'] === ServerContainer::class + && $step['function'] === 'getAppContainer' + && $step['args'][1] === $classNameParts[1]) { + $setUpViaQuery = true; + break; + } elseif (isset($step['class'], $step['function'], $step['args'][0]) + && $step['class'] === SimpleContainer::class + && preg_match('/{closure:OC\\\\AppFramework\\\\Utility\\\\SimpleContainer::buildClass\\(\\):\\d+}/', $step['function']) + && $step['args'][0] === $this) { + /* We are setup through a lazy ghost, fine */ $setUpViaQuery = true; break; } } if (!$setUpViaQuery && $applicationClassName !== \OCP\AppFramework\App::class) { - \OCP\Server::get(LoggerInterface::class)->error($e->getMessage(), [ + Server::get(LoggerInterface::class)->error($e->getMessage(), [ 'app' => $appName, 'exception' => $e, ]); @@ -97,35 +104,6 @@ class App { } /** - * This function is to be called to create single routes and restful routes based on the given $routes array. - * - * Example code in routes.php of tasks app (it will register two restful resources): - * $routes = array( - * 'resources' => array( - * 'lists' => array('url' => '/tasklists'), - * 'tasks' => array('url' => '/tasklists/{listId}/tasks') - * ) - * ); - * - * $a = new TasksApp(); - * $a->registerRoutes($this, $routes); - * - * @param \OCP\Route\IRouter $router - * @param array $routes - * @since 6.0.0 - * @suppress PhanAccessMethodInternal - * @deprecated 20.0.0 Just return an array from your routes.php - */ - public function registerRoutes(IRouter $router, array $routes) { - if (!($router instanceof Router)) { - throw new \RuntimeException('Can only setup routes with real router'); - } - - $routeConfig = new RouteConfig($this->container, $router, $routes); - $routeConfig->register(); - } - - /** * This function is called by the routing component to fire up the frameworks dispatch mechanism. * * Example code in routes.php of the task app: diff --git a/lib/public/AppFramework/Attribute/ASince.php b/lib/public/AppFramework/Attribute/ASince.php new file mode 100644 index 00000000000..1e0c45348cf --- /dev/null +++ b/lib/public/AppFramework/Attribute/ASince.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Attribute; + +use Attribute; + +/** + * Abstract base attribute to declare an API's stability. + * + * @since 32.0.0 + */ +#[Consumable(since: '32.0.0')] +abstract class ASince { + /** + * @param string $since For shipped apps and server code such as core/ and lib/, + * this should be the server version. For other apps it + * should be the semantic app version. + */ + public function __construct( + protected string $since, + ) { + } + + public function getSince(): string { + return $this->since; + } +} diff --git a/lib/public/AppFramework/Attribute/Catchable.php b/lib/public/AppFramework/Attribute/Catchable.php new file mode 100644 index 00000000000..d45401550f6 --- /dev/null +++ b/lib/public/AppFramework/Attribute/Catchable.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Attribute; + +use Attribute; + +/** + * Attribute to declare that the exception is "catchable" by apps. + * + * @since 32.0.0 + */ +#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)] +#[Consumable(since: '32.0.0')] +#[Implementable(since: '32.0.0')] +class Catchable extends ASince { +} diff --git a/lib/public/AppFramework/Attribute/Consumable.php b/lib/public/AppFramework/Attribute/Consumable.php new file mode 100644 index 00000000000..2175bb0af88 --- /dev/null +++ b/lib/public/AppFramework/Attribute/Consumable.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Attribute; + +use Attribute; + +/** + * Attribute to declare that the API stability is limited to "consuming" the + * class, interface, enum, etc. Apps are not allowed to implement or replace them. + * + * For events use @see \OCP\AppFramework\Attribute\Listenable + * For exceptions use @see \OCP\AppFramework\Attribute\Catchable + * + * @since 32.0.0 + */ +#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)] +#[Consumable(since: '32.0.0')] +#[Implementable(since: '32.0.0')] +class Consumable extends ASince { +} diff --git a/lib/public/AppFramework/Attribute/Dispatchable.php b/lib/public/AppFramework/Attribute/Dispatchable.php new file mode 100644 index 00000000000..ff703d4749e --- /dev/null +++ b/lib/public/AppFramework/Attribute/Dispatchable.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Attribute; + +use Attribute; + +/** + * Attribute to declare that the event is "dispatchable" by apps. + * + * @since 32.0.0 + */ +#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)] +#[Consumable(since: '32.0.0')] +#[Implementable(since: '32.0.0')] +class Dispatchable extends ASince { +} diff --git a/lib/public/AppFramework/Attribute/ExceptionalImplementable.php b/lib/public/AppFramework/Attribute/ExceptionalImplementable.php new file mode 100644 index 00000000000..23e9f830d9b --- /dev/null +++ b/lib/public/AppFramework/Attribute/ExceptionalImplementable.php @@ -0,0 +1,38 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Attribute; + +use Attribute; + +/** + * Attribute to declare that the API marked as Consumable/Listenable/Catchable + * has an exception and is Implementable/Dispatchable/Throwable by a dedicated + * app. Changes to such an API have to be communicated to the affected app maintainers. + * + * @since 32.0.0 + */ +#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)] +#[Consumable(since: '32.0.0')] +#[Implementable(since: '32.0.0')] +class ExceptionalImplementable { + public function __construct( + protected string $app, + protected ?string $class = null, + ) { + } + + public function getApp(): string { + return $this->app; + } + + public function getClass(): ?string { + return $this->class; + } +} diff --git a/lib/public/AppFramework/Attribute/Implementable.php b/lib/public/AppFramework/Attribute/Implementable.php new file mode 100644 index 00000000000..40ce0e0cf06 --- /dev/null +++ b/lib/public/AppFramework/Attribute/Implementable.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Attribute; + +use Attribute; + +/** + * Attribute to declare that the API stability is limited to "implementing" the + * class, interface, enum, etc. + * + * For events use @see \OCP\AppFramework\Attribute\Dispatchable + * For exceptions use @see \OCP\AppFramework\Attribute\Throwable + * + * @since 32.0.0 + */ +#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)] +#[Consumable(since: '32.0.0')] +#[Implementable(since: '32.0.0')] +class Implementable extends ASince { +} diff --git a/lib/public/AppFramework/Attribute/Listenable.php b/lib/public/AppFramework/Attribute/Listenable.php new file mode 100644 index 00000000000..98c2ca78690 --- /dev/null +++ b/lib/public/AppFramework/Attribute/Listenable.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Attribute; + +use Attribute; + +/** + * Attribute to declare that the event is "listenable" by apps. + * + * @since 32.0.0 + */ +#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)] +#[Consumable(since: '32.0.0')] +#[Implementable(since: '32.0.0')] +class Listenable extends ASince { +} diff --git a/lib/public/AppFramework/Attribute/Throwable.php b/lib/public/AppFramework/Attribute/Throwable.php new file mode 100644 index 00000000000..2c763c76b4c --- /dev/null +++ b/lib/public/AppFramework/Attribute/Throwable.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Attribute; + +use Attribute; + +/** + * Attribute to declare that the exception is "throwable" by apps. + * + * @since 32.0.0 + */ +#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)] +#[Consumable(since: '32.0.0')] +#[Implementable(since: '32.0.0')] +class Throwable extends ASince { +} diff --git a/lib/public/AppFramework/AuthPublicShareController.php b/lib/public/AppFramework/AuthPublicShareController.php index d6c088d4a0c..28a92fedcc9 100644 --- a/lib/public/AppFramework/AuthPublicShareController.php +++ b/lib/public/AppFramework/AuthPublicShareController.php @@ -46,9 +46,6 @@ abstract class AuthPublicShareController extends PublicShareController { } /** - * @PublicPage - * @NoCSRFRequired - * * Show the authentication page * The form has to submit to the authenticate method route * @@ -125,10 +122,6 @@ abstract class AuthPublicShareController extends PublicShareController { } /** - * @UseSession - * @PublicPage - * @BruteForceProtection(action=publicLinkAuth) - * * Authenticate the share * * @since 14.0.0 @@ -196,10 +189,10 @@ abstract class AuthPublicShareController extends PublicShareController { private function getRoute(string $function): string { $app = strtolower($this->appName); $class = (new \ReflectionClass($this))->getShortName(); - if (substr($class, -10) === 'Controller') { + if (str_ends_with($class, 'Controller')) { $class = substr($class, 0, -10); } - return $app .'.'. $class .'.'. $function; + return $app . '.' . $class . '.' . $function; } /** diff --git a/lib/public/AppFramework/Bootstrap/IBootstrap.php b/lib/public/AppFramework/Bootstrap/IBootstrap.php index 81c34524191..7260d2b77a1 100644 --- a/lib/public/AppFramework/Bootstrap/IBootstrap.php +++ b/lib/public/AppFramework/Bootstrap/IBootstrap.php @@ -25,8 +25,6 @@ interface IBootstrap { * At this stage you can assume that all services are registered and the DI * container(s) are ready to be queried. * - * This is also the state where an optional `appinfo/app.php` was loaded. - * * @param IBootContext $context * * @since 20.0.0 diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php index b86f7bcd76d..70b35228c87 100644 --- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php +++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php @@ -17,6 +17,7 @@ use OCP\Collaboration\Reference\IReferenceProvider; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Template\ICustomTemplateProvider; use OCP\IContainer; +use OCP\Mail\Provider\IProvider as IMailProvider; use OCP\Notification\INotifier; use OCP\Preview\IProviderV2; use OCP\SpeechToText\ISpeechToTextProvider; @@ -68,7 +69,7 @@ interface IRegistrationContext { * @param string $name * @param callable $factory * @psalm-param callable(\Psr\Container\ContainerInterface): mixed $factory - * @param bool $shared + * @param bool $shared If set to true the factory result will be cached otherwise every query will call the factory again * * @return void * @see IContainer::registerService() @@ -412,4 +413,38 @@ interface IRegistrationContext { * @since 30.0.0 */ public function registerTaskProcessingTaskType(string $taskProcessingTaskTypeClass): void; + + /** + * Register an implementation of \OCP\Files\Conversion\IConversionProvider + * that will handle the conversion of files from one MIME type to another + * + * @param string $class + * @psalm-param class-string<\OCP\Files\Conversion\IConversionProvider> $class + * + * @return void + * + * @since 31.0.0 + */ + public function registerFileConversionProvider(string $class): void; + + /** + * Register a mail provider + * + * @param string $class + * @psalm-param class-string<IMailProvider> $class + * @since 30.0.0 + */ + public function registerMailProvider(string $class): void; + + + /** + * Register an implementation of \OCP\Config\Lexicon\IConfigLexicon that + * will handle the config lexicon + * + * @param string $configLexiconClass + * + * @psalm-param class-string<\OCP\Config\Lexicon\ILexicon> $configLexiconClass + * @since 31.0.0 + */ + public function registerConfigLexicon(string $configLexiconClass): void; } diff --git a/lib/public/AppFramework/Controller.php b/lib/public/AppFramework/Controller.php index 44e8cecd810..cdeaac99366 100644 --- a/lib/public/AppFramework/Controller.php +++ b/lib/public/AppFramework/Controller.php @@ -123,7 +123,7 @@ abstract class Controller { /** * Serializes and formats a response * @param mixed $response the value that was returned from a controller and - * is not a Response instance + * is not a Response instance * @param string $format the format for which a formatter has been registered * @throws \DomainException if format does not match a registered formatter * @return Response @@ -135,7 +135,7 @@ abstract class Controller { return $responder($response); } - throw new \DomainException('No responder registered for format '. - $format . '!'); + throw new \DomainException('No responder registered for format ' + . $format . '!'); } } diff --git a/lib/public/AppFramework/Db/Entity.php b/lib/public/AppFramework/Db/Entity.php index 46104e2faa4..3094070af5f 100644 --- a/lib/public/AppFramework/Db/Entity.php +++ b/lib/public/AppFramework/Db/Entity.php @@ -7,6 +7,8 @@ */ namespace OCP\AppFramework\Db; +use OCP\DB\Types; + use function lcfirst; use function substr; @@ -23,12 +25,13 @@ abstract class Entity { public $id; private array $_updatedFields = []; + /** @var array<string, \OCP\DB\Types::*> */ private array $_fieldTypes = ['id' => 'integer']; /** * Simple alternative constructor for building entities from a request * @param array $params the array which was obtained via $this->params('key') - * in the controller + * in the controller * @since 7.0.0 */ public static function fromParams(array $params): static { @@ -52,9 +55,8 @@ abstract class Entity { $instance = new static(); foreach ($row as $key => $value) { - $prop = ucfirst($instance->columnToProperty($key)); - $setter = 'set' . $prop; - $instance->$setter($value); + $prop = $instance->columnToProperty($key); + $instance->setter($prop, [$value]); } $instance->resetUpdatedFields(); @@ -64,10 +66,10 @@ abstract class Entity { /** - * @return array with attribute and type + * @return array<string, \OCP\DB\Types::*> with attribute and type * @since 7.0.0 */ - public function getFieldTypes() { + public function getFieldTypes(): array { return $this->_fieldTypes; } @@ -76,50 +78,76 @@ abstract class Entity { * Marks the entity as clean needed for setting the id after the insertion * @since 7.0.0 */ - public function resetUpdatedFields() { + public function resetUpdatedFields(): void { $this->_updatedFields = []; } /** * Generic setter for properties + * + * @throws \InvalidArgumentException * @since 7.0.0 + * */ protected function setter(string $name, array $args): void { // setters should only work for existing attributes - if (property_exists($this, $name)) { - if ($args[0] === $this->$name) { - return; - } - $this->markFieldUpdated($name); - - // if type definition exists, cast to correct type - if ($args[0] !== null && array_key_exists($name, $this->_fieldTypes)) { - $type = $this->_fieldTypes[$name]; - if ($type === 'blob') { - // (B)LOB is treated as string when we read from the DB - if (is_resource($args[0])) { - $args[0] = stream_get_contents($args[0]); - } - $type = 'string'; + if (!property_exists($this, $name)) { + throw new \BadFunctionCallException($name . ' is not a valid attribute'); + } + + if ($args[0] === $this->$name) { + return; + } + $this->markFieldUpdated($name); + + // if type definition exists, cast to correct type + if ($args[0] !== null && array_key_exists($name, $this->_fieldTypes)) { + $type = $this->_fieldTypes[$name]; + if ($type === Types::BLOB) { + // (B)LOB is treated as string when we read from the DB + if (is_resource($args[0])) { + $args[0] = stream_get_contents($args[0]); } + $type = Types::STRING; + } - if ($type === 'datetime') { + switch ($type) { + case Types::BIGINT: + case Types::SMALLINT: + settype($args[0], Types::INTEGER); + break; + case Types::BINARY: + case Types::DECIMAL: + case Types::TEXT: + settype($args[0], Types::STRING); + break; + case Types::TIME: + case Types::DATE: + case Types::DATETIME: + case Types::DATETIME_TZ: if (!$args[0] instanceof \DateTime) { $args[0] = new \DateTime($args[0]); } - } elseif ($type === 'json') { + break; + case Types::TIME_IMMUTABLE: + case Types::DATE_IMMUTABLE: + case Types::DATETIME_IMMUTABLE: + case Types::DATETIME_TZ_IMMUTABLE: + if (!$args[0] instanceof \DateTimeImmutable) { + $args[0] = new \DateTimeImmutable($args[0]); + } + break; + case Types::JSON: if (!is_array($args[0])) { $args[0] = json_decode($args[0], true); } - } else { + break; + default: settype($args[0], $type); - } } - $this->$name = $args[0]; - } else { - throw new \BadFunctionCallException($name . - ' is not a valid attribute'); } + $this->$name = $args[0]; + } /** @@ -131,8 +159,8 @@ abstract class Entity { if (property_exists($this, $name)) { return $this->$name; } else { - throw new \BadFunctionCallException($name . - ' is not a valid attribute'); + throw new \BadFunctionCallException($name + . ' is not a valid attribute'); } } @@ -152,8 +180,8 @@ abstract class Entity { } elseif ($this->isGetterForBoolProperty($methodName)) { return $this->getter(lcfirst(substr($methodName, 2))); } else { - throw new \BadFunctionCallException($methodName . - ' does not exist'); + throw new \BadFunctionCallException($methodName + . ' does not exist'); } } @@ -182,16 +210,17 @@ abstract class Entity { /** * Transform a database columnname to a property + * * @param string $columnName the name of the column * @return string the property name * @since 7.0.0 */ - public function columnToProperty($columnName) { + public function columnToProperty(string $columnName) { $parts = explode('_', $columnName); - $property = null; + $property = ''; foreach ($parts as $part) { - if ($property === null) { + if ($property === '') { $property = $part; } else { $property .= ucfirst($part); @@ -204,16 +233,17 @@ abstract class Entity { /** * Transform a property to a database column name + * * @param string $property the name of the property * @return string the column name * @since 7.0.0 */ - public function propertyToColumn($property) { + public function propertyToColumn(string $property): string { $parts = preg_split('/(?=[A-Z])/', $property); - $column = null; + $column = ''; foreach ($parts as $part) { - if ($column === null) { + if ($column === '') { $column = $part; } else { $column .= '_' . lcfirst($part); @@ -228,19 +258,33 @@ abstract class Entity { * @return array array of updated fields for update query * @since 7.0.0 */ - public function getUpdatedFields() { + public function getUpdatedFields(): array { return $this->_updatedFields; } /** - * Adds type information for a field so that its automatically casted to + * Adds type information for a field so that it's automatically cast to * that value once its being returned from the database + * * @param string $fieldName the name of the attribute - * @param string $type the type which will be used to call settype() + * @param \OCP\DB\Types::* $type the type which will be used to match a cast + * @since 31.0.0 Parameter $type is now restricted to {@see \OCP\DB\Types} constants. The formerly accidentally supported types 'int'|'bool'|'double' are mapped to Types::INTEGER|Types::BOOLEAN|Types::FLOAT accordingly. * @since 7.0.0 */ - protected function addType($fieldName, $type) { + protected function addType(string $fieldName, string $type): void { + /** @psalm-suppress TypeDoesNotContainType */ + if (in_array($type, ['bool', 'double', 'int', 'array', 'object'], true)) { + // Mapping legacy strings to the actual types + $type = match ($type) { + 'int' => Types::INTEGER, + 'bool' => Types::BOOLEAN, + 'double' => Types::FLOAT, + 'array', + 'object' => Types::STRING, + }; + } + $this->_fieldTypes[$fieldName] = $type; } @@ -248,12 +292,13 @@ abstract class Entity { /** * Slugify the value of a given attribute * Warning: This doesn't result in a unique value + * * @param string $attributeName the name of the attribute, which value should be slugified * @return string slugified value * @since 7.0.0 * @deprecated 24.0.0 */ - public function slugify($attributeName) { + public function slugify(string $attributeName): string { // toSlug should only work for existing attributes if (property_exists($this, $attributeName)) { $value = $this->$attributeName; @@ -262,9 +307,8 @@ abstract class Entity { $value = strtolower($value); // trim '-' return trim($value, '-'); - } else { - throw new \BadFunctionCallException($attributeName . - ' is not a valid attribute'); } + + throw new \BadFunctionCallException($attributeName . ' is not a valid attribute'); } } diff --git a/lib/public/AppFramework/Db/QBMapper.php b/lib/public/AppFramework/Db/QBMapper.php index badd2483b58..7fb5b2a9afd 100644 --- a/lib/public/AppFramework/Db/QBMapper.php +++ b/lib/public/AppFramework/Db/QBMapper.php @@ -7,8 +7,10 @@ declare(strict_types=1); */ namespace OCP\AppFramework\Db; +use Generator; use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\DB\Types; use OCP\IDBConnection; /** @@ -33,7 +35,7 @@ abstract class QBMapper { * @param IDBConnection $db Instance of the Db abstraction layer * @param string $tableName the name of the table. set this to allow entity * @param class-string<T>|null $entityClass the name of the entity that the sql should be - * mapped to queries without using sql + * mapped to queries without using sql * @since 14.0.0 */ public function __construct(IDBConnection $db, string $tableName, ?string $entityClass = null) { @@ -202,7 +204,7 @@ abstract class QBMapper { * Returns the type parameter for the QueryBuilder for a specific property * of the $entity * - * @param Entity $entity The entity to get the types from + * @param Entity $entity The entity to get the types from * @psalm-param T $entity * @param string $property The property of $entity to get the type for * @return int|string @@ -217,18 +219,33 @@ abstract class QBMapper { switch ($types[ $property ]) { case 'int': - case 'integer': + case Types::INTEGER: + case Types::SMALLINT: return IQueryBuilder::PARAM_INT; - case 'string': + case Types::STRING: return IQueryBuilder::PARAM_STR; case 'bool': - case 'boolean': + case Types::BOOLEAN: return IQueryBuilder::PARAM_BOOL; - case 'blob': + case Types::BLOB: return IQueryBuilder::PARAM_LOB; - case 'datetime': - return IQueryBuilder::PARAM_DATE; - case 'json': + case Types::DATE: + return IQueryBuilder::PARAM_DATETIME_MUTABLE; + case Types::DATETIME: + return IQueryBuilder::PARAM_DATETIME_MUTABLE; + case Types::DATETIME_TZ: + return IQueryBuilder::PARAM_DATETIME_TZ_MUTABLE; + case Types::DATE_IMMUTABLE: + return IQueryBuilder::PARAM_DATE_IMMUTABLE; + case Types::DATETIME_IMMUTABLE: + return IQueryBuilder::PARAM_DATETIME_IMMUTABLE; + case Types::DATETIME_TZ_IMMUTABLE: + return IQueryBuilder::PARAM_DATETIME_TZ_IMMUTABLE; + case Types::TIME: + return IQueryBuilder::PARAM_TIME_MUTABLE; + case Types::TIME_IMMUTABLE: + return IQueryBuilder::PARAM_TIME_IMMUTABLE; + case Types::JSON: return IQueryBuilder::PARAM_JSON; } @@ -279,8 +296,8 @@ abstract class QBMapper { * @since 14.0.0 */ private function buildDebugMessage(string $msg, IQueryBuilder $sql): string { - return $msg . - ': query "' . $sql->getSQL() . '"; '; + return $msg + . ': query "' . $sql->getSQL() . '"; '; } @@ -295,7 +312,7 @@ abstract class QBMapper { */ protected function mapRowToEntity(array $row): Entity { unset($row['DOCTRINE_ROWNUM']); // remove doctrine/dbal helper column - return \call_user_func($this->entityClass .'::fromRow', $row); + return \call_user_func($this->entityClass . '::fromRow', $row); } @@ -303,8 +320,8 @@ abstract class QBMapper { * Runs a sql query and returns an array of entities * * @param IQueryBuilder $query - * @return Entity[] all fetched entities - * @psalm-return T[] all fetched entities + * @return list<Entity> all fetched entities + * @psalm-return list<T> all fetched entities * @throws Exception * @since 14.0.0 */ @@ -321,6 +338,26 @@ abstract class QBMapper { } } + /** + * Runs a sql query and yields each resulting entity to obtain database entries in a memory-efficient way + * + * @param IQueryBuilder $query + * @return Generator Generator of fetched entities + * @psalm-return Generator<T> Generator of fetched entities + * @throws Exception + * @since 30.0.0 + */ + protected function yieldEntities(IQueryBuilder $query): Generator { + $result = $query->executeQuery(); + try { + while ($row = $result->fetch()) { + yield $this->mapRowToEntity($row); + } + } finally { + $result->closeCursor(); + } + } + /** * Returns an db result and throws exceptions when there are more or less diff --git a/lib/public/AppFramework/Http/Attribute/ARateLimit.php b/lib/public/AppFramework/Http/Attribute/ARateLimit.php index d92fcae1ae1..c06b1180ae3 100644 --- a/lib/public/AppFramework/Http/Attribute/ARateLimit.php +++ b/lib/public/AppFramework/Http/Attribute/ARateLimit.php @@ -17,6 +17,8 @@ namespace OCP\AppFramework\Http\Attribute; */ abstract class ARateLimit { /** + * @param int $limit The maximum number of requests that can be made in the given period in seconds. + * @param int $period The time period in seconds. * @since 27.0.0 */ public function __construct( diff --git a/lib/public/AppFramework/Http/Attribute/AppApiAdminAccessWithoutUser.php b/lib/public/AppFramework/Http/Attribute/AppApiAdminAccessWithoutUser.php new file mode 100644 index 00000000000..6b78fee41af --- /dev/null +++ b/lib/public/AppFramework/Http/Attribute/AppApiAdminAccessWithoutUser.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Http\Attribute; + +use Attribute; + +/** + * Attribute for (sub)administrator controller methods that allow access for ExApps when the User is not set. + * + * @since 30.0.0 + */ +#[Attribute] +class AppApiAdminAccessWithoutUser { +} diff --git a/lib/public/AppFramework/Http/Attribute/AuthorizedAdminSetting.php b/lib/public/AppFramework/Http/Attribute/AuthorizedAdminSetting.php index e81f195b298..83101143fc9 100644 --- a/lib/public/AppFramework/Http/Attribute/AuthorizedAdminSetting.php +++ b/lib/public/AppFramework/Http/Attribute/AuthorizedAdminSetting.php @@ -25,7 +25,7 @@ class AuthorizedAdminSetting { * @since 27.0.0 */ public function __construct( - protected string $settings + protected string $settings, ) { } diff --git a/lib/public/AppFramework/Http/Attribute/BruteForceProtection.php b/lib/public/AppFramework/Http/Attribute/BruteForceProtection.php index 9fd97cdc3ed..0fc1a3b9b6d 100644 --- a/lib/public/AppFramework/Http/Attribute/BruteForceProtection.php +++ b/lib/public/AppFramework/Http/Attribute/BruteForceProtection.php @@ -23,7 +23,7 @@ class BruteForceProtection { * @since 27.0.0 */ public function __construct( - protected string $action + protected string $action, ) { } diff --git a/lib/public/AppFramework/Http/Attribute/CORS.php b/lib/public/AppFramework/Http/Attribute/CORS.php index 2c3eac362cf..ff639635635 100644 --- a/lib/public/AppFramework/Http/Attribute/CORS.php +++ b/lib/public/AppFramework/Http/Attribute/CORS.php @@ -12,7 +12,9 @@ namespace OCP\AppFramework\Http\Attribute; use Attribute; /** - * Attribute for controller methods that can also be accessed by not logged-in user + * Attribute for controller methods that can also be accessed by other websites. + * See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS for an explanation of the functionality and the security implications. + * See https://docs.nextcloud.com/server/latest/developer_manual/digging_deeper/rest_apis.html on how to implement it in your controller. * * @since 27.0.0 */ diff --git a/lib/public/AppFramework/Http/Attribute/ExAppRequired.php b/lib/public/AppFramework/Http/Attribute/ExAppRequired.php new file mode 100644 index 00000000000..eb18da8027c --- /dev/null +++ b/lib/public/AppFramework/Http/Attribute/ExAppRequired.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Http\Attribute; + +use Attribute; + +/** + * Attribute for controller methods that can only be accessed by ExApps + * + * @since 30.0.0 + */ +#[Attribute] +class ExAppRequired { +} diff --git a/lib/public/AppFramework/Http/Attribute/OpenAPI.php b/lib/public/AppFramework/Http/Attribute/OpenAPI.php index e996bffd917..1b44b2a57fe 100644 --- a/lib/public/AppFramework/Http/Attribute/OpenAPI.php +++ b/lib/public/AppFramework/Http/Attribute/OpenAPI.php @@ -51,13 +51,21 @@ class OpenAPI { public const SCOPE_IGNORE = 'ignore'; /** + * APIs used by ExApps. + * Will be set automatically when an ExApp is required to access the route. + * + * @since 30.0.0 + */ + public const SCOPE_EX_APP = 'ex_app'; + + /** * @param self::SCOPE_*|string $scope Scopes are used to define different clients. - * It is recommended to go with the scopes available as self::SCOPE_* constants, - * but in exotic cases other APIs might need documentation as well, - * then a free string can be provided (but it should be `a-z` only). + * It is recommended to go with the scopes available as self::SCOPE_* constants, + * but in exotic cases other APIs might need documentation as well, + * then a free string can be provided (but it should be `a-z` only). * @param ?list<string> $tags Tags can be used to group routes inside a scope - * for easier implementation and reviewing of the API specification. - * It defaults to the controller name in snake_case (should be `a-z` and underscore only). + * for easier implementation and reviewing of the API specification. + * It defaults to the controller name in snake_case (should be `a-z` and underscore only). * @since 28.0.0 */ public function __construct( diff --git a/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php b/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php index 0f0f4b38040..c41e5aa2445 100644 --- a/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php +++ b/lib/public/AppFramework/Http/Attribute/PasswordConfirmationRequired.php @@ -18,4 +18,21 @@ use Attribute; */ #[Attribute] class PasswordConfirmationRequired { + /** + * @param bool $strict - Whether password confirmation needs to happen in the request. + * + * @since 31.0.0 + */ + public function __construct( + protected bool $strict = false, + ) { + } + + /** + * @since 31.0.0 + */ + public function getStrict(): bool { + return $this->strict; + } + } diff --git a/lib/public/AppFramework/Http/Attribute/RequestHeader.php b/lib/public/AppFramework/Http/Attribute/RequestHeader.php new file mode 100644 index 00000000000..1d0fbbfa0c3 --- /dev/null +++ b/lib/public/AppFramework/Http/Attribute/RequestHeader.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\AppFramework\Http\Attribute; + +use Attribute; + +/** + * This attribute allows documenting request headers and is primarily intended for OpenAPI documentation. + * It should be added whenever you use a request header in a controller method, in order to properly describe the header and its functionality. + * There are no checks that ensure the header is set, so you will still need to do this yourself in the controller method. + * + * @since 32.0.0 + */ +#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +class RequestHeader { + /** + * @param lowercase-string $name The name of the request header + * @param non-empty-string $description The description of the request header + * @param bool $indirect Allow indirect usage of the header for example in a middleware. Enabling this turns off the check which ensures that the header must be referenced in the controller method. + */ + public function __construct( + protected string $name, + protected string $description, + protected bool $indirect = false, + ) { + } +} diff --git a/lib/public/AppFramework/Http/ContentSecurityPolicy.php b/lib/public/AppFramework/Http/ContentSecurityPolicy.php index 281aaa06eb8..11ec79bbdb7 100644 --- a/lib/public/AppFramework/Http/ContentSecurityPolicy.php +++ b/lib/public/AppFramework/Http/ContentSecurityPolicy.php @@ -38,7 +38,7 @@ class ContentSecurityPolicy extends EmptyContentSecurityPolicy { ]; /** * @var bool Whether inline CSS is allowed - * TODO: Disallow per default + * TODO: Disallow per default * @link https://github.com/owncloud/core/issues/13458 */ protected $inlineStyleAllowed = true; diff --git a/lib/public/AppFramework/Http/DataDisplayResponse.php b/lib/public/AppFramework/Http/DataDisplayResponse.php index 889c57a7901..e1ded910328 100644 --- a/lib/public/AppFramework/Http/DataDisplayResponse.php +++ b/lib/public/AppFramework/Http/DataDisplayResponse.php @@ -13,9 +13,9 @@ use OCP\AppFramework\Http; * Class DataDisplayResponse * * @since 8.1.0 - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class DataDisplayResponse extends Response { /** diff --git a/lib/public/AppFramework/Http/DataDownloadResponse.php b/lib/public/AppFramework/Http/DataDownloadResponse.php index 80100137c48..ee6bcf0d0c5 100644 --- a/lib/public/AppFramework/Http/DataDownloadResponse.php +++ b/lib/public/AppFramework/Http/DataDownloadResponse.php @@ -13,10 +13,10 @@ use OCP\AppFramework\Http; * Class DataDownloadResponse * * @since 8.0.0 - * @template S of int + * @template S of Http::STATUS_* * @template C of string * @template H of array<string, mixed> - * @template-extends DownloadResponse<int, string, array<string, mixed>> + * @template-extends DownloadResponse<Http::STATUS_*, string, array<string, mixed>> */ class DataDownloadResponse extends DownloadResponse { /** diff --git a/lib/public/AppFramework/Http/DataResponse.php b/lib/public/AppFramework/Http/DataResponse.php index 2ebb66f9e73..2b54ce848ef 100644 --- a/lib/public/AppFramework/Http/DataResponse.php +++ b/lib/public/AppFramework/Http/DataResponse.php @@ -14,10 +14,10 @@ use OCP\AppFramework\Http; * for responders to transform * @since 8.0.0 * @psalm-type DataResponseType = array|int|float|string|bool|object|null|\stdClass|\JsonSerializable - * @template S of int + * @template S of Http::STATUS_* * @template-covariant T of DataResponseType * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class DataResponse extends Response { /** diff --git a/lib/public/AppFramework/Http/DownloadResponse.php b/lib/public/AppFramework/Http/DownloadResponse.php index 058b3070297..190de022d36 100644 --- a/lib/public/AppFramework/Http/DownloadResponse.php +++ b/lib/public/AppFramework/Http/DownloadResponse.php @@ -12,10 +12,10 @@ use OCP\AppFramework\Http; /** * Prompts the user to download the a file * @since 7.0.0 - * @template S of int + * @template S of Http::STATUS_* * @template C of string * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class DownloadResponse extends Response { /** diff --git a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php index 5b4cf7eab8b..b8bbfdb7d67 100644 --- a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php +++ b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php @@ -26,7 +26,7 @@ class EmptyContentSecurityPolicy { protected $strictDynamicAllowedOnScripts = null; /** * @var bool Whether eval in JS scripts is allowed - * TODO: Disallow per default + * TODO: Disallow per default * @link https://github.com/owncloud/core/issues/11925 */ protected $evalScriptAllowed = null; @@ -36,7 +36,7 @@ class EmptyContentSecurityPolicy { protected $allowedScriptDomains = null; /** * @var bool Whether inline CSS is allowed - * TODO: Disallow per default + * TODO: Disallow per default * @link https://github.com/owncloud/core/issues/13458 */ protected $inlineStyleAllowed = null; @@ -89,7 +89,7 @@ class EmptyContentSecurityPolicy { } /** - * Use the according JS nonce + * The base64 encoded nonce to be used for script source. * This method is only for CSPMiddleware, custom values are ignored in mergePolicies of ContentSecurityPolicyManager * * @param string $nonce @@ -106,7 +106,7 @@ class EmptyContentSecurityPolicy { * @param bool $state * @return $this * @since 8.1.0 - * @deprecated Eval should not be used anymore. Please update your scripts. This function will stop functioning in a future version of Nextcloud. + * @deprecated 17.0.0 Eval should not be used anymore. Please update your scripts. This function will stop functioning in a future version of Nextcloud. */ public function allowEvalScript($state = true) { $this->evalScriptAllowed = $state; @@ -448,7 +448,7 @@ class EmptyContentSecurityPolicy { if ($this->strictDynamicAllowed) { $scriptSrc .= '\'strict-dynamic\' '; } - $scriptSrc .= '\'nonce-'.base64_encode($this->jsNonce).'\''; + $scriptSrc .= '\'nonce-' . $this->jsNonce . '\''; $allowedScriptDomains = array_flip($this->allowedScriptDomains); unset($allowedScriptDomains['\'self\'']); $this->allowedScriptDomains = array_flip($allowedScriptDomains); diff --git a/lib/public/AppFramework/Http/Events/BeforeLoginTemplateRenderedEvent.php b/lib/public/AppFramework/Http/Events/BeforeLoginTemplateRenderedEvent.php index d18cad25acc..b724b3a72ad 100644 --- a/lib/public/AppFramework/Http/Events/BeforeLoginTemplateRenderedEvent.php +++ b/lib/public/AppFramework/Http/Events/BeforeLoginTemplateRenderedEvent.php @@ -20,7 +20,9 @@ class BeforeLoginTemplateRenderedEvent extends Event { /** * @since 28.0.0 */ - public function __construct(private TemplateResponse $response) { + public function __construct( + private TemplateResponse $response, + ) { parent::__construct(); } diff --git a/lib/public/AppFramework/Http/FileDisplayResponse.php b/lib/public/AppFramework/Http/FileDisplayResponse.php index 0cc51f7c59f..c18404b7d91 100644 --- a/lib/public/AppFramework/Http/FileDisplayResponse.php +++ b/lib/public/AppFramework/Http/FileDisplayResponse.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -13,9 +14,9 @@ use OCP\Files\SimpleFS\ISimpleFile; * Class FileDisplayResponse * * @since 11.0.0 - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class FileDisplayResponse extends Response implements ICallbackResponse { /** @var File|ISimpleFile */ diff --git a/lib/public/AppFramework/Http/JSONResponse.php b/lib/public/AppFramework/Http/JSONResponse.php index 1614cb8ce01..a226e29a1b5 100644 --- a/lib/public/AppFramework/Http/JSONResponse.php +++ b/lib/public/AppFramework/Http/JSONResponse.php @@ -12,10 +12,10 @@ use OCP\AppFramework\Http; /** * A renderer for JSON calls * @since 6.0.0 - * @template S of int - * @template-covariant T of array|object|\stdClass|\JsonSerializable + * @template S of Http::STATUS_* + * @template-covariant T of null|string|int|float|bool|array|\stdClass|\JsonSerializable * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class JSONResponse extends Response { /** @@ -23,6 +23,11 @@ class JSONResponse extends Response { * @var T */ protected $data; + /** + * Additional `json_encode` flags + * @var int + */ + protected $encodeFlags; /** @@ -30,12 +35,20 @@ class JSONResponse extends Response { * @param T $data the object or array that should be transformed * @param S $statusCode the Http status code, defaults to 200 * @param H $headers + * @param int $encodeFlags Additional `json_encode` flags * @since 6.0.0 + * @since 30.0.0 Added `$encodeFlags` param */ - public function __construct(mixed $data = [], int $statusCode = Http::STATUS_OK, array $headers = []) { + public function __construct( + mixed $data = [], + int $statusCode = Http::STATUS_OK, + array $headers = [], + int $encodeFlags = 0, + ) { parent::__construct($statusCode, $headers); $this->data = $data; + $this->encodeFlags = $encodeFlags; $this->addHeader('Content-Type', 'application/json; charset=utf-8'); } @@ -45,16 +58,19 @@ class JSONResponse extends Response { * @return string the rendered json * @since 6.0.0 * @throws \Exception If data could not get encoded + * + * @psalm-taint-escape has_quotes + * @psalm-taint-escape html */ public function render() { - return json_encode($this->data, JSON_HEX_TAG | JSON_THROW_ON_ERROR); + return json_encode($this->data, JSON_HEX_TAG | JSON_THROW_ON_ERROR | $this->encodeFlags, 2048); } /** * Sets values in the data json array * @psalm-suppress InvalidTemplateParam * @param T $data an array or object which will be transformed - * to JSON + * to JSON * @return JSONResponse Reference to this object * @since 6.0.0 - return value was added in 7.0.0 */ diff --git a/lib/public/AppFramework/Http/NotFoundResponse.php b/lib/public/AppFramework/Http/NotFoundResponse.php index 9ebefe69be1..137d1a26655 100644 --- a/lib/public/AppFramework/Http/NotFoundResponse.php +++ b/lib/public/AppFramework/Http/NotFoundResponse.php @@ -12,9 +12,9 @@ use OCP\AppFramework\Http; /** * A generic 404 response showing an 404 error page as well to the end-user * @since 8.1.0 - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends TemplateResponse<int, array<string, mixed>> + * @template-extends TemplateResponse<Http::STATUS_*, array<string, mixed>> */ class NotFoundResponse extends TemplateResponse { /** diff --git a/lib/public/AppFramework/Http/RedirectResponse.php b/lib/public/AppFramework/Http/RedirectResponse.php index 41fc4d83856..74847205976 100644 --- a/lib/public/AppFramework/Http/RedirectResponse.php +++ b/lib/public/AppFramework/Http/RedirectResponse.php @@ -12,9 +12,9 @@ use OCP\AppFramework\Http; /** * Redirects to a different URL * @since 7.0.0 - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class RedirectResponse extends Response { private $redirectURL; diff --git a/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php b/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php index 3e2fcf6f6c7..0a0c04f671d 100644 --- a/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php +++ b/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php @@ -16,9 +16,9 @@ use OCP\IURLGenerator; * * @since 16.0.0 * @deprecated 23.0.0 Use RedirectResponse() with IURLGenerator::linkToDefaultPageUrl() instead - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends RedirectResponse<int, array<string, mixed>> + * @template-extends RedirectResponse<Http::STATUS_*, array<string, mixed>> */ class RedirectToDefaultAppResponse extends RedirectResponse { /** @@ -30,8 +30,7 @@ class RedirectToDefaultAppResponse extends RedirectResponse { * @deprecated 23.0.0 Use RedirectResponse() with IURLGenerator::linkToDefaultPageUrl() instead */ public function __construct(int $status = Http::STATUS_SEE_OTHER, array $headers = []) { - /** @var IURLGenerator $urlGenerator */ - $urlGenerator = \OC::$server->get(IURLGenerator::class); + $urlGenerator = \OCP\Server::get(IURLGenerator::class); parent::__construct($urlGenerator->linkToDefaultPageUrl(), $status, $headers); } } diff --git a/lib/public/AppFramework/Http/Response.php b/lib/public/AppFramework/Http/Response.php index 0da290ad48b..bdebb12c00d 100644 --- a/lib/public/AppFramework/Http/Response.php +++ b/lib/public/AppFramework/Http/Response.php @@ -18,7 +18,7 @@ use Psr\Log\LoggerInterface; * * It handles headers, HTTP status code, last modified and ETag. * @since 6.0.0 - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> */ class Response { @@ -93,11 +93,10 @@ class Response { // Set expires header $expires = new \DateTime(); - /** @var ITimeFactory $time */ $time = \OCP\Server::get(ITimeFactory::class); $expires->setTimestamp($time->getTime()); - $expires->add(new \DateInterval('PT'.$cacheSeconds.'S')); - $this->addHeader('Expires', $expires->format(\DateTimeInterface::RFC2822)); + $expires->add(new \DateInterval('PT' . $cacheSeconds . 'S')); + $this->addHeader('Expires', $expires->format(\DateTimeInterface::RFC7231)); } else { $this->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); unset($this->headers['Expires']); @@ -111,8 +110,8 @@ class Response { * @param string $name The name of the cookie * @param string $value The value of the cookie * @param \DateTime|null $expireDate Date on that the cookie should expire, if set - * to null cookie will be considered as session - * cookie. + * to null cookie will be considered as session + * cookie. * @param string $sameSite The samesite value of the cookie. Defaults to Lax. Other possibilities are Strict or None * @return $this * @since 8.0.0 @@ -184,10 +183,10 @@ class Response { if ($this->status === Http::STATUS_NOT_MODIFIED && stripos($name, 'x-') === 0) { /** @var IConfig $config */ - $config = \OC::$server->get(IConfig::class); + $config = \OCP\Server::get(IConfig::class); if ($config->getSystemValueBool('debug', false)) { - \OC::$server->get(LoggerInterface::class)->error('Setting custom header on a 304 is not supported (Header: {header})', [ + \OCP\Server::get(LoggerInterface::class)->error('Setting custom header on a 304 is not supported (Header: {header})', [ 'header' => $name, ]); } @@ -229,7 +228,7 @@ class Response { /** * @psalm-suppress UndefinedClass */ - $request = \OC::$server->get(IRequest::class); + $request = \OCP\Server::get(IRequest::class); $mergeWith = [ 'X-Request-Id' => $request->getId(), 'Cache-Control' => 'no-cache, no-store, must-revalidate', @@ -239,7 +238,7 @@ class Response { ]; if ($this->lastModified) { - $mergeWith['Last-Modified'] = $this->lastModified->format(\DateTimeInterface::RFC2822); + $mergeWith['Last-Modified'] = $this->lastModified->format(\DateTimeInterface::RFC7231); } if ($this->ETag) { @@ -289,7 +288,7 @@ class Response { /** * Get the currently used Content-Security-Policy * @return EmptyContentSecurityPolicy|null Used Content-Security-Policy or null if - * none specified. + * none specified. * @since 8.1.0 */ public function getContentSecurityPolicy() { diff --git a/lib/public/AppFramework/Http/StandaloneTemplateResponse.php b/lib/public/AppFramework/Http/StandaloneTemplateResponse.php index f729bd772fb..244a6b80f9f 100644 --- a/lib/public/AppFramework/Http/StandaloneTemplateResponse.php +++ b/lib/public/AppFramework/Http/StandaloneTemplateResponse.php @@ -7,6 +7,8 @@ declare(strict_types=1); */ namespace OCP\AppFramework\Http; +use OCP\AppFramework\Http; + /** * A template response that does not emit the loadAdditionalScripts events. * @@ -14,9 +16,9 @@ namespace OCP\AppFramework\Http; * full nextcloud UI. Like the 2FA page, or the grant page in the login flow. * * @since 16.0.0 - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends TemplateResponse<int, array<string, mixed>> + * @template-extends TemplateResponse<Http::STATUS_*, array<string, mixed>> */ class StandaloneTemplateResponse extends TemplateResponse { } diff --git a/lib/public/AppFramework/Http/StreamResponse.php b/lib/public/AppFramework/Http/StreamResponse.php index 1039e20e5c5..d0e6e3e148a 100644 --- a/lib/public/AppFramework/Http/StreamResponse.php +++ b/lib/public/AppFramework/Http/StreamResponse.php @@ -13,9 +13,9 @@ use OCP\AppFramework\Http; * Class StreamResponse * * @since 8.1.0 - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class StreamResponse extends Response implements ICallbackResponse { /** @var string */ diff --git a/lib/public/AppFramework/Http/Template/ExternalShareMenuAction.php b/lib/public/AppFramework/Http/Template/ExternalShareMenuAction.php index cddf5d19171..281bb559a10 100644 --- a/lib/public/AppFramework/Http/Template/ExternalShareMenuAction.php +++ b/lib/public/AppFramework/Http/Template/ExternalShareMenuAction.php @@ -1,58 +1,29 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\AppFramework\Http\Template; -use OCP\Util; - /** * Class LinkMenuAction * * @since 14.0.0 */ class ExternalShareMenuAction extends SimpleMenuAction { - /** @var string */ - private $owner; - - /** @var string */ - private $displayname; - - /** @var string */ - private $shareName; /** * ExternalShareMenuAction constructor. * - * @param string $label - * @param string $icon - * @param string $owner - * @param string $displayname - * @param string $shareName + * @param string $label Translated label + * @param string $icon Icon CSS class + * @param string $owner Owner user ID (unused) + * @param string $displayname Display name of the owner (unused) + * @param string $shareName Name of the share (unused) * @since 14.0.0 */ public function __construct(string $label, string $icon, string $owner, string $displayname, string $shareName) { parent::__construct('save', $label, $icon); - $this->owner = $owner; - $this->displayname = $displayname; - $this->shareName = $shareName; - } - - /** - * @since 14.0.0 - */ - public function render(): string { - return '<li>' . - ' <button id="save-external-share" class="icon ' . Util::sanitizeHTML($this->getIcon()) . '" data-protected="false" data-owner-display-name="' . Util::sanitizeHTML($this->displayname) . '" data-owner="' . Util::sanitizeHTML($this->owner) . '" data-name="' . Util::sanitizeHTML($this->shareName) . '">' . Util::sanitizeHTML($this->getLabel()) . '</button>' . - '</li>' . - '<li id="external-share-menu-item" class="hidden">' . - ' <span class="menuitem">' . - ' <form class="save-form" action="#">' . - ' <input type="text" id="remote_address" placeholder="user@yourNextcloud.org">' . - ' <input type="submit" value=" " id="save-button-confirm" class="icon-confirm" disabled="disabled"></button>' . - ' </form>' . - ' </span>' . - '</li>'; } } diff --git a/lib/public/AppFramework/Http/Template/IMenuAction.php b/lib/public/AppFramework/Http/Template/IMenuAction.php index d3962300923..124e95fe019 100644 --- a/lib/public/AppFramework/Http/Template/IMenuAction.php +++ b/lib/public/AppFramework/Http/Template/IMenuAction.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -18,12 +19,16 @@ interface IMenuAction { public function getId(): string; /** + * The translated label of the menu item. + * * @since 14.0.0 * @return string */ public function getLabel(): string; /** + * The link this menu item points to. + * * @since 14.0.0 * @return string */ @@ -36,6 +41,9 @@ interface IMenuAction { public function getPriority(): int; /** + * Custom render function. + * The returned HTML will be wrapped within a listitem element (`<li>...</li>`). + * * @since 14.0.0 * @return string */ diff --git a/lib/public/AppFramework/Http/Template/LinkMenuAction.php b/lib/public/AppFramework/Http/Template/LinkMenuAction.php index 69e835f6b82..391802a1dce 100644 --- a/lib/public/AppFramework/Http/Template/LinkMenuAction.php +++ b/lib/public/AppFramework/Http/Template/LinkMenuAction.php @@ -1,12 +1,11 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\AppFramework\Http\Template; -use OCP\Util; - /** * Class LinkMenuAction * @@ -22,24 +21,6 @@ class LinkMenuAction extends SimpleMenuAction { * @since 14.0.0 */ public function __construct(string $label, string $icon, string $link) { - parent::__construct('directLink-container', $label, $icon, $link); - } - - /** - * @return string - * @since 14.0.0 - */ - public function render(): string { - return '<li>' . - '<a id="directLink-container">' . - '<span class="icon ' . Util::sanitizeHTML($this->getIcon()) . '"></span>' . - '<label for="directLink">' . Util::sanitizeHTML($this->getLabel()) . '</label>' . - '</a>' . - '</li>' . - '<li>' . - '<span class="menuitem">' . - '<input id="directLink" type="text" readonly="" value="' . Util::sanitizeHTML($this->getLink()) . '">' . - '</span>' . - '</li>'; + parent::__construct('directLink', $label, $icon, $link); } } diff --git a/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php b/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php index c12cf087755..4c156cdecea 100644 --- a/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php +++ b/lib/public/AppFramework/Http/Template/PublicTemplateResponse.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -8,18 +9,20 @@ namespace OCP\AppFramework\Http\Template; use InvalidArgumentException; use OCP\AppFramework\Http; use OCP\AppFramework\Http\TemplateResponse; +use OCP\IInitialStateService; /** * Class PublicTemplateResponse * * @since 14.0.0 * @template H of array<string, mixed> - * @template S of int - * @template-extends TemplateResponse<int, array<string, mixed>> + * @template S of Http::STATUS_* + * @template-extends TemplateResponse<Http::STATUS_*, array<string, mixed>> */ class PublicTemplateResponse extends TemplateResponse { private $headerTitle = ''; private $headerDetails = ''; + /** @var IMenuAction[] */ private $headerActions = []; private $footerVisible = true; @@ -33,9 +36,39 @@ class PublicTemplateResponse extends TemplateResponse { * @param H $headers * @since 14.0.0 */ - public function __construct(string $appName, string $templateName, array $params = [], $status = Http::STATUS_OK, array $headers = []) { + public function __construct( + string $appName, + string $templateName, + array $params = [], + $status = Http::STATUS_OK, + array $headers = [], + ) { parent::__construct($appName, $templateName, $params, 'public', $status, $headers); - \OC_Util::addScript('core', 'public/publicpage'); + \OCP\Util::addScript('core', 'public-page-menu'); + \OCP\Util::addScript('core', 'public-page-user-menu'); + + $state = \OCP\Server::get(IInitialStateService::class); + $state->provideLazyInitialState('core', 'public-page-menu', function () { + $response = []; + foreach ($this->headerActions as $action) { + // First try in it is a custom action that provides rendered HTML + $rendered = $action->render(); + if ($rendered === '') { + // If simple action, add the response data + if ($action instanceof SimpleMenuAction) { + $response[] = $action->getData(); + } + } else { + // custom action so add the rendered output + $response[] = [ + 'id' => $action->getId(), + 'label' => $action->getLabel(), + 'html' => $rendered, + ]; + } + } + return $response; + }); } /** @@ -138,6 +171,6 @@ class PublicTemplateResponse extends TemplateResponse { 'template' => $this, ]); $this->setParams($params); - return parent::render(); + return parent::render(); } } diff --git a/lib/public/AppFramework/Http/Template/SimpleMenuAction.php b/lib/public/AppFramework/Http/Template/SimpleMenuAction.php index 2bb60975f26..03cb9b4c7ea 100644 --- a/lib/public/AppFramework/Http/Template/SimpleMenuAction.php +++ b/lib/public/AppFramework/Http/Template/SimpleMenuAction.php @@ -1,12 +1,11 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\AppFramework\Http\Template; -use OCP\Util; - /** * Class SimpleMenuAction * @@ -68,6 +67,8 @@ class SimpleMenuAction implements IMenuAction { } /** + * The icon CSS class to use. + * * @return string * @since 14.0.0 */ @@ -92,14 +93,28 @@ class SimpleMenuAction implements IMenuAction { } /** + * Custom render function. + * The returned HTML must be wrapped within a listitem (`<li>...</li>`). + * * If an empty string is returned, the default design is used (based on the label and link specified). * @return string * @since 14.0.0 */ public function render(): string { - $detailContent = ($this->detail !== '') ? ' <span class="download-size">(' . Util::sanitizeHTML($this->detail) . ')</span>' : ''; - return sprintf( - '<li id="%s"><a href="%s"><span class="icon %s"></span>%s %s</a></li>', - Util::sanitizeHTML($this->id), Util::sanitizeHTML($this->link), Util::sanitizeHTML($this->icon), Util::sanitizeHTML($this->label), $detailContent - ); + return ''; + } + + /** + * Return JSON data to let the frontend render the menu entry. + * @return array{id: string, label: string, href: string, icon: string, details: string|null} + * @since 31.0.0 + */ + public function getData(): array { + return [ + 'id' => $this->id, + 'label' => $this->label, + 'href' => $this->link, + 'icon' => $this->icon, + 'details' => $this->detail, + ]; } } diff --git a/lib/public/AppFramework/Http/TemplateResponse.php b/lib/public/AppFramework/Http/TemplateResponse.php index f9ac80cdc80..af37a1a2313 100644 --- a/lib/public/AppFramework/Http/TemplateResponse.php +++ b/lib/public/AppFramework/Http/TemplateResponse.php @@ -1,21 +1,27 @@ <?php +declare(strict_types=1); + + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-only */ + namespace OCP\AppFramework\Http; use OCP\AppFramework\Http; +use OCP\Server; +use OCP\Template\ITemplateManager; /** * Response for a normal template * @since 6.0.0 * - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class TemplateResponse extends Response { /** @@ -72,7 +78,7 @@ class TemplateResponse extends Response { * @param string $appName the name of the app to load the template from * @param string $templateName the name of the template * @param array $params an array of parameters which should be passed to the - * template + * template * @param string $renderAs how the page should be rendered, defaults to user * @param S $status * @param H $headers @@ -180,7 +186,7 @@ class TemplateResponse extends Response { $renderAs = $this->renderAs; } - $template = new \OCP\Template($this->appName, $this->templateName, $renderAs); + $template = Server::get(ITemplateManager::class)->getTemplate($this->appName, $this->templateName, $renderAs); foreach ($this->params as $key => $value) { $template->assign($key, $value); diff --git a/lib/public/AppFramework/Http/TextPlainResponse.php b/lib/public/AppFramework/Http/TextPlainResponse.php index e7c728c37ab..9dfa2c5544d 100644 --- a/lib/public/AppFramework/Http/TextPlainResponse.php +++ b/lib/public/AppFramework/Http/TextPlainResponse.php @@ -12,9 +12,9 @@ use OCP\AppFramework\Http; /** * A renderer for text responses * @since 22.0.0 - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class TextPlainResponse extends Response { /** @var string */ diff --git a/lib/public/AppFramework/Http/TooManyRequestsResponse.php b/lib/public/AppFramework/Http/TooManyRequestsResponse.php index b7b0a98c9e1..f7084ec768d 100644 --- a/lib/public/AppFramework/Http/TooManyRequestsResponse.php +++ b/lib/public/AppFramework/Http/TooManyRequestsResponse.php @@ -8,14 +8,15 @@ declare(strict_types=1); namespace OCP\AppFramework\Http; use OCP\AppFramework\Http; -use OCP\Template; +use OCP\Server; +use OCP\Template\ITemplateManager; /** * A generic 429 response showing an 404 error page as well to the end-user * @since 19.0.0 - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class TooManyRequestsResponse extends Response { /** @@ -34,7 +35,7 @@ class TooManyRequestsResponse extends Response { * @since 19.0.0 */ public function render() { - $template = new Template('core', '429', 'blank'); + $template = Server::get(ITemplateManager::class)->getTemplate('core', '429', TemplateResponse::RENDER_AS_BLANK); return $template->fetchPage(); } } diff --git a/lib/public/AppFramework/Http/ZipResponse.php b/lib/public/AppFramework/Http/ZipResponse.php index 3b9e251d332..a552eb1294f 100644 --- a/lib/public/AppFramework/Http/ZipResponse.php +++ b/lib/public/AppFramework/Http/ZipResponse.php @@ -15,9 +15,9 @@ use OCP\IRequest; * Public library to send several files in one zip archive. * * @since 15.0.0 - * @template S of int + * @template S of Http::STATUS_* * @template H of array<string, mixed> - * @template-extends Response<int, array<string, mixed>> + * @template-extends Response<Http::STATUS_*, array<string, mixed>> */ class ZipResponse extends Response implements ICallbackResponse { /** @var array{internalName: string, resource: resource, size: int, time: int}[] Files to be added to the zip response */ diff --git a/lib/public/AppFramework/OCS/OCSBadRequestException.php b/lib/public/AppFramework/OCS/OCSBadRequestException.php index c229468fb0d..77b8ec6c86d 100644 --- a/lib/public/AppFramework/OCS/OCSBadRequestException.php +++ b/lib/public/AppFramework/OCS/OCSBadRequestException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/AppFramework/OCS/OCSException.php b/lib/public/AppFramework/OCS/OCSException.php index 962bad830e7..02901992f8d 100644 --- a/lib/public/AppFramework/OCS/OCSException.php +++ b/lib/public/AppFramework/OCS/OCSException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/AppFramework/OCS/OCSForbiddenException.php b/lib/public/AppFramework/OCS/OCSForbiddenException.php index 03b1db6104f..0d001377043 100644 --- a/lib/public/AppFramework/OCS/OCSForbiddenException.php +++ b/lib/public/AppFramework/OCS/OCSForbiddenException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/AppFramework/OCS/OCSNotFoundException.php b/lib/public/AppFramework/OCS/OCSNotFoundException.php index 997b0c390f9..67cea9ed759 100644 --- a/lib/public/AppFramework/OCS/OCSNotFoundException.php +++ b/lib/public/AppFramework/OCS/OCSNotFoundException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/AppFramework/OCS/OCSPreconditionFailedException.php b/lib/public/AppFramework/OCS/OCSPreconditionFailedException.php index 2e67263bcb9..4fc2820eaec 100644 --- a/lib/public/AppFramework/OCS/OCSPreconditionFailedException.php +++ b/lib/public/AppFramework/OCS/OCSPreconditionFailedException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/AppFramework/OCSController.php b/lib/public/AppFramework/OCSController.php index b4bb62c41ed..7cde2a7e427 100644 --- a/lib/public/AppFramework/OCSController.php +++ b/lib/public/AppFramework/OCSController.php @@ -44,13 +44,13 @@ abstract class OCSController extends ApiController { * @param string $appName the name of the app * @param IRequest $request an instance of the request * @param string $corsMethods comma separated string of HTTP verbs which - * should be allowed for websites or webapps when calling your API, defaults to - * 'PUT, POST, GET, DELETE, PATCH' + * should be allowed for websites or webapps when calling your API, defaults to + * 'PUT, POST, GET, DELETE, PATCH' * @param string $corsAllowedHeaders comma separated string of HTTP headers - * which should be allowed for websites or webapps when calling your API, - * defaults to 'Authorization, Content-Type, Accept' + * which should be allowed for websites or webapps when calling your API, + * defaults to 'Authorization, Content-Type, Accept' * @param int $corsMaxAge number in seconds how long a preflighted OPTIONS - * request should be cached, defaults to 1728000 seconds + * request should be cached, defaults to 1728000 seconds * @since 8.1.0 */ public function __construct($appName, @@ -81,7 +81,7 @@ abstract class OCSController extends ApiController { * Since the OCS endpoints default to XML we need to find out the format * again * @param mixed $response the value that was returned from a controller and - * is not a Response instance + * is not a Response instance * @param string $format the format for which a formatter has been registered * @throws \DomainException if format does not match a registered formatter * @return Response diff --git a/lib/public/AppFramework/PublicShareController.php b/lib/public/AppFramework/PublicShareController.php index 458606455d1..999b3827565 100644 --- a/lib/public/AppFramework/PublicShareController.php +++ b/lib/public/AppFramework/PublicShareController.php @@ -98,8 +98,8 @@ abstract class PublicShareController extends Controller { } // If we are authenticated properly - if ($this->session->get('public_link_authenticated_token') === $this->getToken() && - $this->session->get('public_link_authenticated_password_hash') === $this->getPasswordHash()) { + if ($this->session->get('public_link_authenticated_token') === $this->getToken() + && $this->session->get('public_link_authenticated_password_hash') === $this->getPasswordHash()) { return true; } diff --git a/lib/public/AppFramework/Services/IInitialState.php b/lib/public/AppFramework/Services/IInitialState.php index 24698108d68..ac58bcad3cc 100644 --- a/lib/public/AppFramework/Services/IInitialState.php +++ b/lib/public/AppFramework/Services/IInitialState.php @@ -37,7 +37,7 @@ interface IInitialState { * * @param string $key * @param Closure $closure returns a primitive or an object that implements JsonSerializable - * @psalm-param Closure():bool|Closure():int|Closure():float|Closure():string|Closure():\JsonSerializable $closure + * @psalm-param Closure():bool|Closure():int|Closure():float|Closure():string|Closure():array|Closure():\JsonSerializable $closure */ public function provideLazyInitialState(string $key, Closure $closure): void; } diff --git a/lib/public/AppFramework/Utility/IControllerMethodReflector.php b/lib/public/AppFramework/Utility/IControllerMethodReflector.php index 577191cac40..95d7fbebb56 100644 --- a/lib/public/AppFramework/Utility/IControllerMethodReflector.php +++ b/lib/public/AppFramework/Utility/IControllerMethodReflector.php @@ -31,9 +31,9 @@ interface IControllerMethodReflector { * Inspects the PHPDoc parameters for types * * @param string $parameter the parameter whose type comments should be - * parsed + * parsed * @return string|null type in the type parameters (@param int $something) - * would return int or null if not existing + * would return int or null if not existing * @since 8.0.0 * @deprecated 22.0.0 this method is only used internally */ diff --git a/lib/public/Authentication/Events/AnyLoginFailedEvent.php b/lib/public/Authentication/Events/AnyLoginFailedEvent.php index fce12e04b8d..e39a39372cb 100644 --- a/lib/public/Authentication/Events/AnyLoginFailedEvent.php +++ b/lib/public/Authentication/Events/AnyLoginFailedEvent.php @@ -31,12 +31,20 @@ class AnyLoginFailedEvent extends Event { /** * @since 26.0.0 + * @deprecated 31.0.0 Use getLoginName() instead */ public function geLoginName(): string { return $this->loginName; } /** + * @since 31.0.0 + */ + public function getLoginName(): string { + return $this->loginName; + } + + /** * @since 26.0.0 */ public function getPassword(): ?string { diff --git a/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php b/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php index 835fd1eac9d..6ca8e68ed95 100644 --- a/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php +++ b/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Authentication/Exceptions/PasswordUnavailableException.php b/lib/public/Authentication/Exceptions/PasswordUnavailableException.php index 6a425f4ddd9..89e254e8ba1 100644 --- a/lib/public/Authentication/Exceptions/PasswordUnavailableException.php +++ b/lib/public/Authentication/Exceptions/PasswordUnavailableException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Authentication/IProvideUserSecretBackend.php b/lib/public/Authentication/IProvideUserSecretBackend.php index c005f6c9d59..dfab35c5f48 100644 --- a/lib/public/Authentication/IProvideUserSecretBackend.php +++ b/lib/public/Authentication/IProvideUserSecretBackend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-only diff --git a/lib/public/Authentication/LoginCredentials/ICredentials.php b/lib/public/Authentication/LoginCredentials/ICredentials.php index a1c802f73b0..9848bbbe821 100644 --- a/lib/public/Authentication/LoginCredentials/ICredentials.php +++ b/lib/public/Authentication/LoginCredentials/ICredentials.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php b/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php index 84b0a9066f2..32ede4f385c 100644 --- a/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php +++ b/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php @@ -8,16 +8,15 @@ declare(strict_types=1); */ namespace OCP\Authentication\TwoFactorAuth; -use OCP\Template; +use OCP\Template\ITemplate; /** * @since 17.0.0 */ interface ILoginSetupProvider { /** - * @return Template - * * @since 17.0.0 + * @since 32.0.0 Broader return type ITemplate instead of \OCP\Template */ - public function getBody(): Template; + public function getBody(): ITemplate; } diff --git a/lib/public/Authentication/TwoFactorAuth/IPersonalProviderSettings.php b/lib/public/Authentication/TwoFactorAuth/IPersonalProviderSettings.php index 610f299c526..3cf7946272e 100644 --- a/lib/public/Authentication/TwoFactorAuth/IPersonalProviderSettings.php +++ b/lib/public/Authentication/TwoFactorAuth/IPersonalProviderSettings.php @@ -6,9 +6,10 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ + namespace OCP\Authentication\TwoFactorAuth; -use OCP\Template; +use OCP\Template\ITemplate; /** * Interface IPersonalProviderSettings @@ -17,9 +18,8 @@ use OCP\Template; */ interface IPersonalProviderSettings { /** - * @return Template - * * @since 15.0.0 + * @since 32.0.0 Broader return type ITemplate instead of \OCP\Template */ - public function getBody(): Template; + public function getBody(): ITemplate; } diff --git a/lib/public/Authentication/TwoFactorAuth/IProvider.php b/lib/public/Authentication/TwoFactorAuth/IProvider.php index f1dd24ff0a2..27c4121f4ac 100644 --- a/lib/public/Authentication/TwoFactorAuth/IProvider.php +++ b/lib/public/Authentication/TwoFactorAuth/IProvider.php @@ -9,7 +9,7 @@ declare(strict_types=1); namespace OCP\Authentication\TwoFactorAuth; use OCP\IUser; -use OCP\Template; +use OCP\Template\ITemplate; /** * @since 9.1.0 @@ -50,11 +50,9 @@ interface IProvider { * Get the template for rending the 2FA provider view * * @since 9.1.0 - * - * @param IUser $user - * @return Template + * @since 32.0.0 Broader return type ITemplate instead of \OCP\Template. */ - public function getTemplate(IUser $user): Template; + public function getTemplate(IUser $user): ITemplate; /** * Verify the given challenge diff --git a/lib/public/Authentication/TwoFactorAuth/IRegistry.php b/lib/public/Authentication/TwoFactorAuth/IRegistry.php index f4cffd7c34c..6817f1763cf 100644 --- a/lib/public/Authentication/TwoFactorAuth/IRegistry.php +++ b/lib/public/Authentication/TwoFactorAuth/IRegistry.php @@ -38,7 +38,7 @@ interface IRegistry { * * @since 14.0.0 * @return array<string, bool> where the array key is the provider ID (string) and the - * value is the enabled state (bool) + * value is the enabled state (bool) */ public function getProviderStates(IUser $user): array; diff --git a/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengeFailed.php b/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengeFailed.php index 15e5ed92509..42ca855df35 100644 --- a/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengeFailed.php +++ b/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengeFailed.php @@ -20,7 +20,8 @@ class TwoFactorProviderChallengeFailed extends Event { */ public function __construct( private IUser $user, - private IProvider $provider) { + private IProvider $provider, + ) { parent::__construct(); } diff --git a/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengePassed.php b/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengePassed.php index 2dc91a98880..396fbf9e9a5 100644 --- a/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengePassed.php +++ b/lib/public/Authentication/TwoFactorAuth/TwoFactorProviderChallengePassed.php @@ -20,7 +20,8 @@ class TwoFactorProviderChallengePassed extends Event { */ public function __construct( private IUser $user, - private IProvider $provider) { + private IProvider $provider, + ) { parent::__construct(); } diff --git a/lib/public/AutoloadNotAllowedException.php b/lib/public/AutoloadNotAllowedException.php index 6e897b49406..a296ea3356a 100644 --- a/lib/public/AutoloadNotAllowedException.php +++ b/lib/public/AutoloadNotAllowedException.php @@ -18,6 +18,6 @@ class AutoloadNotAllowedException extends \DomainException { * @since 8.2.0 */ public function __construct($path) { - parent::__construct('Autoload path not allowed: '.$path); + parent::__construct('Autoload path not allowed: ' . $path); } } diff --git a/lib/public/BackgroundJob/IJob.php b/lib/public/BackgroundJob/IJob.php index 6988e1e682a..28a7df1c377 100644 --- a/lib/public/BackgroundJob/IJob.php +++ b/lib/public/BackgroundJob/IJob.php @@ -10,7 +10,7 @@ namespace OCP\BackgroundJob; use OCP\ILogger; /** - * This interface represend a backgroud job run with cron + * This interface represents a background job run with cron * * To implement a background job, you must extend either \OCP\BackgroundJob\Job, * \OCP\BackgroundJob\TimedJob or \OCP\BackgroundJob\QueuedJob @@ -33,7 +33,7 @@ interface IJob { * @param IJobList $jobList The job list that manages the state of this job * @param ILogger|null $logger * @since 7.0.0 - * @deprecated since 25.0.0 Use start() instead. This method will be removed + * @deprecated 25.0.0 Use start() instead. This method will be removed * with the ILogger interface */ public function execute(IJobList $jobList, ?ILogger $logger = null); diff --git a/lib/public/BackgroundJob/IJobList.php b/lib/public/BackgroundJob/IJobList.php index dbe154ff5f8..c082ef22f2f 100644 --- a/lib/public/BackgroundJob/IJobList.php +++ b/lib/public/BackgroundJob/IJobList.php @@ -61,6 +61,14 @@ interface IJobList { public function remove($job, $argument = null): void; /** + * Remove a job from the list by id + * + * @param int $id + * @since 30.0.0 + */ + public function removeById(int $id): void; + + /** * check if a job is in the list * * @param IJob|class-string<IJob> $job diff --git a/lib/public/BackgroundJob/Job.php b/lib/public/BackgroundJob/Job.php index 3b25db311a4..2483387a9c9 100644 --- a/lib/public/BackgroundJob/Job.php +++ b/lib/public/BackgroundJob/Job.php @@ -39,7 +39,7 @@ abstract class Job implements IJob, IParallelAwareJob { * @return void * * @since 15.0.0 - * @deprecated since 25.0.0 Use start() instead. This method will be removed + * @deprecated 25.0.0 Use start() instead. This method will be removed * with the ILogger interface */ public function execute(IJobList $jobList, ?ILogger $logger = null) { diff --git a/lib/public/BackgroundJob/QueuedJob.php b/lib/public/BackgroundJob/QueuedJob.php index c75948d1a11..75e27d1d60f 100644 --- a/lib/public/BackgroundJob/QueuedJob.php +++ b/lib/public/BackgroundJob/QueuedJob.php @@ -22,7 +22,7 @@ abstract class QueuedJob extends Job { * @param ILogger|null $logger * * @since 15.0.0 - * @deprecated since 25.0.0 Use start() instead. This method will be removed + * @deprecated 25.0.0 Use start() instead. This method will be removed * with the ILogger interface */ final public function execute($jobList, ?ILogger $logger = null) { @@ -35,7 +35,11 @@ abstract class QueuedJob extends Job { * @since 25.0.0 */ final public function start(IJobList $jobList): void { - $jobList->remove($this, $this->argument); + if ($this->id) { + $jobList->removeById($this->id); + } else { + $jobList->remove($this, $this->argument); + } parent::start($jobList); } } diff --git a/lib/public/BackgroundJob/TimedJob.php b/lib/public/BackgroundJob/TimedJob.php index 6665d194789..486c03c5fda 100644 --- a/lib/public/BackgroundJob/TimedJob.php +++ b/lib/public/BackgroundJob/TimedJob.php @@ -8,6 +8,8 @@ declare(strict_types=1); namespace OCP\BackgroundJob; use OCP\ILogger; +use OCP\Server; +use Psr\Log\LoggerInterface; /** * Simple base class to extend to run periodic background jobs. @@ -31,6 +33,15 @@ abstract class TimedJob extends Job { } /** + * Get the interval [seconds] for the job + * + * @since 32.0.0 + */ + public function getInterval(): int { + return $this->interval; + } + + /** * Whether the background job is time sensitive and needs to run soon after * the scheduled interval, of if it is okay to be delayed until a later time. * @@ -52,8 +63,8 @@ abstract class TimedJob extends Job { * @since 24.0.0 */ public function setTimeSensitivity(int $sensitivity): void { - if ($sensitivity !== IJob::TIME_SENSITIVE && - $sensitivity !== IJob::TIME_INSENSITIVE) { + if ($sensitivity !== self::TIME_SENSITIVE + && $sensitivity !== self::TIME_INSENSITIVE) { throw new \InvalidArgumentException('Invalid sensitivity'); } @@ -67,7 +78,7 @@ abstract class TimedJob extends Job { * @param ILogger|null $logger * * @since 15.0.0 - * @deprecated since 25.0.0 Use start() instead + * @deprecated 25.0.0 Use start() instead */ final public function execute(IJobList $jobList, ?ILogger $logger = null) { $this->start($jobList); @@ -80,6 +91,9 @@ abstract class TimedJob extends Job { */ final public function start(IJobList $jobList): void { if (($this->time->getTime() - $this->lastRun) > $this->interval) { + if ($this->interval >= 12 * 60 * 60 && $this->isTimeSensitive()) { + Server::get(LoggerInterface::class)->debug('TimedJob ' . get_class($this) . ' has a configured interval of ' . $this->interval . ' seconds, but is also marked as time sensitive. Please consider marking it as time insensitive to allow more sensitive jobs to run when needed.'); + } parent::start($jobList); } } diff --git a/lib/public/BeforeSabrePubliclyLoadedEvent.php b/lib/public/BeforeSabrePubliclyLoadedEvent.php index c69cf867a43..33f9f8bc378 100644 --- a/lib/public/BeforeSabrePubliclyLoadedEvent.php +++ b/lib/public/BeforeSabrePubliclyLoadedEvent.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Calendar/BackendTemporarilyUnavailableException.php b/lib/public/Calendar/BackendTemporarilyUnavailableException.php index e02ef1a84fd..c2bbb1417ee 100644 --- a/lib/public/Calendar/BackendTemporarilyUnavailableException.php +++ b/lib/public/Calendar/BackendTemporarilyUnavailableException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Calendar/CalendarEventStatus.php b/lib/public/Calendar/CalendarEventStatus.php new file mode 100644 index 00000000000..fd735150578 --- /dev/null +++ b/lib/public/Calendar/CalendarEventStatus.php @@ -0,0 +1,17 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar; + +use OCP\AppFramework\Attribute\Listenable; + +#[Listenable(since: '32.0.0')] +enum CalendarEventStatus: string { + case TENTATIVE = 'TENTATIVE'; + case CONFIRMED = 'CONFIRMED'; + case CANCELLED = 'CANCELLED'; +}; diff --git a/lib/public/Calendar/CalendarExportOptions.php b/lib/public/Calendar/CalendarExportOptions.php new file mode 100644 index 00000000000..bf21dd85ae4 --- /dev/null +++ b/lib/public/Calendar/CalendarExportOptions.php @@ -0,0 +1,68 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar; + +/** + * Calendar Export Options + * + * @since 32.0.0 + */ +final class CalendarExportOptions { + + /** @var 'ical'|'jcal'|'xcal' */ + private string $format = 'ical'; + private ?string $rangeStart = null; + private ?int $rangeCount = null; + + /** + * Gets the export format + * + * @return 'ical'|'jcal'|'xcal' (defaults to ical) + */ + public function getFormat(): string { + return $this->format; + } + + /** + * Sets the export format + * + * @param 'ical'|'jcal'|'xcal' $format + */ + public function setFormat(string $format): void { + $this->format = $format; + } + + /** + * Gets the start of the range to export + */ + public function getRangeStart(): ?string { + return $this->rangeStart; + } + + /** + * Sets the start of the range to export + */ + public function setRangeStart(?string $rangeStart): void { + $this->rangeStart = $rangeStart; + } + + /** + * Gets the number of objects to export + */ + public function getRangeCount(): ?int { + return $this->rangeCount; + } + + /** + * Sets the number of objects to export + */ + public function setRangeCount(?int $rangeCount): void { + $this->rangeCount = $rangeCount; + } +} diff --git a/lib/public/Calendar/Events/AbstractCalendarObjectEvent.php b/lib/public/Calendar/Events/AbstractCalendarObjectEvent.php new file mode 100644 index 00000000000..111ed096f78 --- /dev/null +++ b/lib/public/Calendar/Events/AbstractCalendarObjectEvent.php @@ -0,0 +1,79 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar\Events; + +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IWebhookCompatibleEvent; + +/** + * @since 32.0.0 + */ +abstract class AbstractCalendarObjectEvent extends Event implements IWebhookCompatibleEvent { + + /** + * @param int $calendarId + * @param array $calendarData + * @param array $shares + * @param array $objectData + * @since 32.0.0 + */ + public function __construct( + private int $calendarId, + private array $calendarData, + private array $shares, + private array $objectData, + ) { + parent::__construct(); + } + + /** + * @return int + * @since 32.0.0 + */ + public function getCalendarId(): int { + return $this->calendarId; + } + + /** + * @return array + * @since 32.0.0 + */ + public function getCalendarData(): array { + return $this->calendarData; + } + + /** + * @return array + * @since 32.0.0 + */ + public function getShares(): array { + return $this->shares; + } + + /** + * @return array + * @since 32.0.0 + */ + public function getObjectData(): array { + return $this->objectData; + } + + /** + * @return array + * @since 32.0.0 + */ + public function getWebhookSerializable(): array { + return [ + 'calendarId' => $this->getCalendarId(), + 'calendarData' => $this->getCalendarData(), + 'shares' => $this->getShares(), + 'objectData' => $this->getObjectData(), + ]; + } +} diff --git a/lib/public/Calendar/Events/CalendarObjectCreatedEvent.php b/lib/public/Calendar/Events/CalendarObjectCreatedEvent.php new file mode 100644 index 00000000000..a4d0f40ec55 --- /dev/null +++ b/lib/public/Calendar/Events/CalendarObjectCreatedEvent.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar\Events; + +/** + * @since 32.0.0 + */ +class CalendarObjectCreatedEvent extends AbstractCalendarObjectEvent { +} diff --git a/lib/public/Calendar/Events/CalendarObjectDeletedEvent.php b/lib/public/Calendar/Events/CalendarObjectDeletedEvent.php new file mode 100644 index 00000000000..5466213584e --- /dev/null +++ b/lib/public/Calendar/Events/CalendarObjectDeletedEvent.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar\Events; + +/** + * @since 32.0.0 + */ +class CalendarObjectDeletedEvent extends AbstractCalendarObjectEvent { +} diff --git a/lib/public/Calendar/Events/CalendarObjectMovedEvent.php b/lib/public/Calendar/Events/CalendarObjectMovedEvent.php new file mode 100644 index 00000000000..1c7df0e1ed5 --- /dev/null +++ b/lib/public/Calendar/Events/CalendarObjectMovedEvent.php @@ -0,0 +1,104 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar\Events; + +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IWebhookCompatibleEvent; + +/** + * @since 32.0.0 + */ +class CalendarObjectMovedEvent extends Event implements IWebhookCompatibleEvent { + /** + * @since 32.0.0 + */ + public function __construct( + private int $sourceCalendarId, + private array $sourceCalendarData, + private int $targetCalendarId, + private array $targetCalendarData, + private array $sourceShares, + private array $targetShares, + private array $objectData, + ) { + parent::__construct(); + } + + /** + * @return int + * @since 32.0.0 + */ + public function getSourceCalendarId(): int { + return $this->sourceCalendarId; + } + + /** + * @return array + * @since 32.0.0 + */ + public function getSourceCalendarData(): array { + return $this->sourceCalendarData; + } + + /** + * @return int + * @since 32.0.0 + */ + public function getTargetCalendarId(): int { + return $this->targetCalendarId; + } + + /** + * @return array + * @since 32.0.0 + */ + public function getTargetCalendarData(): array { + return $this->targetCalendarData; + } + + /** + * @return array + * @since 32.0.0 + */ + public function getSourceShares(): array { + return $this->sourceShares; + } + + /** + * @return array + * @since 32.0.0 + */ + public function getTargetShares(): array { + return $this->targetShares; + } + + /** + * @return array + * @since 32.0.0 + */ + public function getObjectData(): array { + return $this->objectData; + } + + /** + * @return array + * @since 32.0.0 + */ + public function getWebhookSerializable(): array { + return [ + 'sourceCalendarId' => $this->getSourceCalendarId(), + 'sourceCalendarData' => $this->getSourceCalendarData(), + 'targetCalendarId' => $this->getTargetCalendarId(), + 'targetCalendarData' => $this->getTargetCalendarData(), + 'sourceShares' => $this->getSourceShares(), + 'targetShares' => $this->getTargetShares(), + 'objectData' => $this->getObjectData(), + ]; + } +} diff --git a/lib/public/Calendar/Events/CalendarObjectMovedToTrashEvent.php b/lib/public/Calendar/Events/CalendarObjectMovedToTrashEvent.php new file mode 100644 index 00000000000..ffbd7b0375a --- /dev/null +++ b/lib/public/Calendar/Events/CalendarObjectMovedToTrashEvent.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar\Events; + +/** + * @since 32.0.0 + */ +class CalendarObjectMovedToTrashEvent extends AbstractCalendarObjectEvent { +} diff --git a/lib/public/Calendar/Events/CalendarObjectRestoredEvent.php b/lib/public/Calendar/Events/CalendarObjectRestoredEvent.php new file mode 100644 index 00000000000..7890e3ca5b3 --- /dev/null +++ b/lib/public/Calendar/Events/CalendarObjectRestoredEvent.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar\Events; + +/** + * @since 32.0.0 + */ +class CalendarObjectRestoredEvent extends AbstractCalendarObjectEvent { +} diff --git a/lib/public/Calendar/Events/CalendarObjectUpdatedEvent.php b/lib/public/Calendar/Events/CalendarObjectUpdatedEvent.php new file mode 100644 index 00000000000..c06b2b8198f --- /dev/null +++ b/lib/public/Calendar/Events/CalendarObjectUpdatedEvent.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar\Events; + +/** + * @since 32.0.0 + */ +class CalendarObjectUpdatedEvent extends AbstractCalendarObjectEvent { +} diff --git a/lib/public/Calendar/IAvailabilityResult.php b/lib/public/Calendar/IAvailabilityResult.php new file mode 100644 index 00000000000..d437a5da047 --- /dev/null +++ b/lib/public/Calendar/IAvailabilityResult.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Calendar; + +/** + * DTO for the availability check results. + * Holds information about whether an attendee is available or not during the request time slot. + * + * @since 31.0.0 + */ +interface IAvailabilityResult { + /** + * Get the attendee's email address. + * + * @since 31.0.0 + */ + public function getAttendeeEmail(): string; + + /** + * Whether the attendee is available during the requested time slot. + * + * @since 31.0.0 + */ + public function isAvailable(): bool; +} diff --git a/lib/public/Calendar/ICalendar.php b/lib/public/Calendar/ICalendar.php index 76257579a8f..50152d1240b 100644 --- a/lib/public/Calendar/ICalendar.php +++ b/lib/public/Calendar/ICalendar.php @@ -8,10 +8,18 @@ declare(strict_types=1); */ namespace OCP\Calendar; +use DateTimeInterface; + /** * Interface ICalendar * * @since 13.0.0 + * + * @psalm-type CalendarSearchOptions = array{ + * timerange?: array{start?: DateTimeInterface, end?: DateTimeInterface}, + * uid?: string, + * types?: string[], + * } */ interface ICalendar { /** @@ -41,25 +49,76 @@ interface ICalendar { public function getDisplayColor(): ?string; /** - * @param string $pattern which should match within the $searchProperties - * @param array $searchProperties defines the properties within the query pattern should match - * @param array $options - optional parameters: - * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] - * @param int|null $limit - limit number of search results - * @param int|null $offset - offset for paging of search results - * @return array an array of events/journals/todos which are arrays of key-value-pairs. the events are sorted by start date (closest first, furthest last) + * Search the current calendar for matching events. + * + * This method searches for events in the calendar that match a given pattern within specified properties. + * The search is case-insensitive. It supports optional parameters such as a time range, limit, and offset. + * The results are sorted by start date, with the closest events appearing first. + * + * @param string $pattern A string to search for within the events. The search is done case-insensitive. + * @param array $searchProperties Defines the properties within which the pattern should match. + * @param array $options Optional parameters for the search: + * - 'timerange' element that can have 'start' (DateTimeInterface), 'end' (DateTimeInterface), or both. + * - 'uid' element to look for events with a given uid. + * - 'types' element to only return events for a given type (e.g. VEVENT or VTODO) + * @psalm-param CalendarSearchOptions $options + * @param int|null $limit Limit the number of search results. + * @param int|null $offset For paging of search results. + * @return array An array of events/journals/todos which are arrays of key-value-pairs. The events are sorted by start date (closest first, furthest last). + * + * Implementation Details: + * + * An event can consist of many sub-events, typically the case for events with recurrence rules. On a database level, + * there's only one event stored (with a matching first occurrence and last occurrence timestamp). Expanding an event + * into sub-events is done on the backend level. Using limit, offset, and timerange comes with some drawbacks. + * When asking the database for events, the result is ordered by the primary key to guarantee a stable order. + * After expanding the events into sub-events, they are sorted by the date (closest to furthest). + * + * Usage Examples: + * + * 1) Find 7 events within the next two weeks: + * + * $dateTime = (new DateTimeImmutable())->setTimestamp($this->timeFactory->getTime()); + * $inTwoWeeks = $dateTime->add(new DateInterval('P14D')); + * + * $calendar->search( + * '', + * [], + * ['timerange' => ['start' => $dateTime, 'end' => $inTwoWeeks]], + * 7 + * ); + * + * Note: When combining timerange and limit, it's possible that the expected outcome is not in the order you would expect. + * + * Example: Create 7 events for tomorrow, starting from 11:00, 30 minutes each. Then create an 8th event for tomorrow at 10:00. + * The above code will list the event at 11:00 first, missing the event at 10:00. The reason is the ordering by the primary key + * and expanding on the backend level. This is a technical limitation. The easiest workaround is to fetch more events + * than you actually need, with the downside of needing more resources. + * + * Related: + * - https://github.com/nextcloud/server/pull/45222 + * - https://github.com/nextcloud/server/issues/53002 + * + * 2) Find all events where the location property contains the string 'Berlin': + * + * $calendar->search( + * 'Berlin', + * ['LOCATION'] + * ); + * * @since 13.0.0 */ public function search(string $pattern, array $searchProperties = [], array $options = [], ?int $limit = null, ?int $offset = null): array; /** - * @return int build up using \OCP\Constants + * @return int build up using {@see \OCP\Constants} * @since 13.0.0 */ public function getPermissions(): int; /** - * Whether the calendar is deleted + * Indicates whether the calendar is in the trash bin + * * @since 26.0.0 */ public function isDeleted(): bool; diff --git a/lib/public/Calendar/ICalendarEventBuilder.php b/lib/public/Calendar/ICalendarEventBuilder.php new file mode 100644 index 00000000000..c99dc60cc8c --- /dev/null +++ b/lib/public/Calendar/ICalendarEventBuilder.php @@ -0,0 +1,117 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Calendar; + +use DateTimeInterface; +use InvalidArgumentException; +use OCP\Calendar\Exceptions\CalendarException; + +/** + * The calendar event builder can be used to conveniently build a calendar event and then serialize + * it to a ICS string. The ICS string can be submitted to calendar instances implementing the + * {@see \OCP\Calendar\ICreateFromString} interface. + * + * Also note this class can not be injected directly with dependency injection. + * Instead, inject {@see \OCP\Calendar\IManager} and use + * {@see \OCP\Calendar\IManager::createEventBuilder()} afterwards. + * + * All setters return self to allow chaining method calls. + * + * @since 31.0.0 + */ +interface ICalendarEventBuilder { + /** + * Set the start date, time and time zone. + * This property is required! + * + * @since 31.0.0 + */ + public function setStartDate(DateTimeInterface $start): self; + + /** + * Set the end date, time and time zone. + * This property is required! + * + * @since 31.0.0 + */ + public function setEndDate(DateTimeInterface $end): self; + + /** + * Set the event summary or title. + * This property is required! + * + * @since 31.0.0 + */ + public function setSummary(string $summary): self; + + /** + * Set the event description. + * + * @since 31.0.0 + */ + public function setDescription(string $description): self; + + /** + * Set the event location. It can either be a physical address or a URL. + * + * @since 31.0.0 + */ + public function setLocation(string $location): self; + + /** + * Set the event status. + * + * @since 32.0.0 + */ + public function setStatus(CalendarEventStatus $status): static; + + /** + * Set the event organizer. + * This property is required if attendees are added! + * + * The "mailto:" prefix is optional and will be added automatically if it is missing. + * + * @since 31.0.0 + */ + public function setOrganizer(string $email, ?string $commonName = null): self; + + /** + * Add a new attendee to the event. + * Adding at least one attendee requires also setting the organizer! + * + * The "mailto:" prefix is optional and will be added automatically if it is missing. + * + * @since 31.0.0 + */ + public function addAttendee(string $email, ?string $commonName = null): self; + + /** + * Serialize the built event to an ICS string if all required properties set. + * + * @since 31.0.0 + * + * @return string The serialized ICS string + * + * @throws InvalidArgumentException If required properties were not set + */ + public function toIcs(): string; + + /** + * Create the event in the given calendar. + * + * @since 31.0.0 + * + * @return string The filename of the created event + * + * @throws InvalidArgumentException If required properties were not set + * @throws CalendarException If writing the event to the calendar fails + */ + public function createInCalendar(ICreateFromString $calendar): string; +} diff --git a/lib/public/Calendar/ICalendarExport.php b/lib/public/Calendar/ICalendarExport.php new file mode 100644 index 00000000000..d884c104a4a --- /dev/null +++ b/lib/public/Calendar/ICalendarExport.php @@ -0,0 +1,31 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar; + +use Generator; + +/** + * ICalendar Interface Extension to export data + * + * @since 32.0.0 + */ +interface ICalendarExport { + + /** + * Export objects + * + * @since 32.0.0 + * + * @param CalendarExportOptions|null $options + * + * @return Generator<\Sabre\VObject\Component\VCalendar> + */ + public function export(?CalendarExportOptions $options): Generator; + +} diff --git a/lib/public/Calendar/ICalendarIsEnabled.php b/lib/public/Calendar/ICalendarIsEnabled.php new file mode 100644 index 00000000000..6bcb487e3dc --- /dev/null +++ b/lib/public/Calendar/ICalendarIsEnabled.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar; + +/** + * ICalendar Interface Extension + * + * @since 32.0.0 + */ +interface ICalendarIsEnabled { + + /** + * Indicates whether the calendar is enabled + * + * @since 32.0.0 + */ + public function isEnabled(): bool; + +} diff --git a/lib/public/Calendar/ICalendarIsShared.php b/lib/public/Calendar/ICalendarIsShared.php new file mode 100644 index 00000000000..6f63c6eefd0 --- /dev/null +++ b/lib/public/Calendar/ICalendarIsShared.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar; + +/** + * ICalendar Interface Extension + * + * @since 31.0.0 + */ +interface ICalendarIsShared { + + /** + * Indicates whether the calendar is shared with the current user + * + * @since 31.0.0 + */ + public function isShared(): bool; + +} diff --git a/lib/public/Calendar/ICalendarIsWritable.php b/lib/public/Calendar/ICalendarIsWritable.php new file mode 100644 index 00000000000..5bf08a25cdc --- /dev/null +++ b/lib/public/Calendar/ICalendarIsWritable.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Calendar; + +/** + * ICalendar Interface Extension + * + * @since 31.0.0 + */ +interface ICalendarIsWritable { + + /** + * Indicates whether the calendar can be modified + * + * @since 31.0.0 + */ + public function isWritable(): bool; + +} diff --git a/lib/public/Calendar/ICreateFromString.php b/lib/public/Calendar/ICreateFromString.php index 5badaa2d4cf..2bb0f2ffa20 100644 --- a/lib/public/Calendar/ICreateFromString.php +++ b/lib/public/Calendar/ICreateFromString.php @@ -17,9 +17,31 @@ use OCP\Calendar\Exceptions\CalendarException; */ interface ICreateFromString extends ICalendar { /** - * @since 23.0.0 + * Create an event in this calendar from an ICS string. + * + * @param string $name the file name - needs to contain the .ics ending + * @param string $calendarData a string containing a valid VEVENT ics * * @throws CalendarException + * + * @since 23.0.0 + * */ public function createFromString(string $name, string $calendarData): void; + + /** + * Create an event in this calendar from an ICS string using a minimal CalDAV server. + * Usually, the createFromString() method should be preferred. + * + * However, in some cases it is useful to not set up a full CalDAV server. + * Missing features include no iMIP plugin, no invitation emails amongst others. + * + * @param string $name the file name - needs to contain the .ics ending + * @param string $calendarData a string containing a valid VEVENT ics + * + * @throws CalendarException + * + * @since 32.0.0 + */ + public function createFromStringMinimal(string $name, string $calendarData): void; } diff --git a/lib/public/Calendar/IHandleImipMessage.php b/lib/public/Calendar/IHandleImipMessage.php index f42ee0fcda7..27190f93f24 100644 --- a/lib/public/Calendar/IHandleImipMessage.php +++ b/lib/public/Calendar/IHandleImipMessage.php @@ -24,7 +24,7 @@ interface IHandleImipMessage extends ICalendar { * * @since 26.0.0 * - * @throws CalendarException on validation failure or calendar write error + * @throws CalendarException on validation failure or calendar write error */ public function handleIMipMessage(string $name, string $calendarData): void; } diff --git a/lib/public/Calendar/IManager.php b/lib/public/Calendar/IManager.php index ff752c0d06e..124dc65f5f6 100644 --- a/lib/public/Calendar/IManager.php +++ b/lib/public/Calendar/IManager.php @@ -8,6 +8,9 @@ declare(strict_types=1); */ namespace OCP\Calendar; +use DateTimeInterface; +use OCP\IUser; + /** * This class provides access to the Nextcloud CalDAV backend. * Use this class exclusively if you want to access calendars. @@ -46,7 +49,7 @@ interface IManager { * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match * @param array $options - optional parameters: - * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] + * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] * @param integer|null $limit - limit number of search results * @param integer|null $offset - offset for paging of search results * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs @@ -138,6 +141,13 @@ interface IManager { public function newQuery(string $principalUri) : ICalendarQuery; /** + * Handle a iMip REQUEST message + * + * @since 31.0.0 + */ + public function handleIMipRequest(string $principalUri, string $sender, string $recipient, string $calendarData): bool; + + /** * Handle a iMip REPLY message * * @since 25.0.0 @@ -150,4 +160,28 @@ interface IManager { * @since 25.0.0 */ public function handleIMipCancel(string $principalUri, string $sender, ?string $replyTo, string $recipient, string $calendarData): bool; + + /** + * Create a new event builder instance. Please have a look at its documentation and the + * \OCP\Calendar\ICreateFromString interface on how to use it. + * + * @since 31.0.0 + */ + public function createEventBuilder(): ICalendarEventBuilder; + + /** + * Check the availability of the given organizer and attendees in the given time range. + * + * @since 31.0.0 + * + * @param IUser $organizer The organizing user from whose perspective to do the availability check. + * @param string[] $attendees Email addresses of attendees to check for (with or without a "mailto:" prefix). Only users on this instance can be checked. The rest will be silently ignored. + * @return IAvailabilityResult[] Availabilities of the organizer and all attendees which are also users on this instance. As such, the array might not contain an entry for each given attendee. + */ + public function checkAvailability( + DateTimeInterface $start, + DateTimeInterface $end, + IUser $organizer, + array $attendees, + ): array; } diff --git a/lib/public/Calendar/IMetadataProvider.php b/lib/public/Calendar/IMetadataProvider.php index bee840c955f..acacf7efdaf 100644 --- a/lib/public/Calendar/IMetadataProvider.php +++ b/lib/public/Calendar/IMetadataProvider.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Calendar/Resource/IBackend.php b/lib/public/Calendar/Resource/IBackend.php index b43d79e3618..23d37c102f2 100644 --- a/lib/public/Calendar/Resource/IBackend.php +++ b/lib/public/Calendar/Resource/IBackend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Calendar/Resource/IManager.php b/lib/public/Calendar/Resource/IManager.php index 7b2fe2e230c..d3a6b2449e3 100644 --- a/lib/public/Calendar/Resource/IManager.php +++ b/lib/public/Calendar/Resource/IManager.php @@ -10,7 +10,6 @@ namespace OCP\Calendar\Resource; /** * @since 14.0.0 - * @deprecated 24.0.0 */ interface IManager { /** @@ -55,4 +54,11 @@ interface IManager { * @deprecated 24.0.0 */ public function clear(); + + /** + * Update all resources from all backends right now. + * + * @since 30.0.0 + */ + public function update(): void; } diff --git a/lib/public/Calendar/Resource/IResource.php b/lib/public/Calendar/Resource/IResource.php index f284eee955f..15abe4e2d0f 100644 --- a/lib/public/Calendar/Resource/IResource.php +++ b/lib/public/Calendar/Resource/IResource.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Calendar/Resource/IResourceMetadata.php b/lib/public/Calendar/Resource/IResourceMetadata.php index acf02bc3609..29f628d6f7f 100644 --- a/lib/public/Calendar/Resource/IResourceMetadata.php +++ b/lib/public/Calendar/Resource/IResourceMetadata.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Calendar/Room/IBackend.php b/lib/public/Calendar/Room/IBackend.php index 52ec2fa5948..c99f5fbdb72 100644 --- a/lib/public/Calendar/Room/IBackend.php +++ b/lib/public/Calendar/Room/IBackend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Calendar/Room/IManager.php b/lib/public/Calendar/Room/IManager.php index 7c750f4e457..b1136ede3fe 100644 --- a/lib/public/Calendar/Room/IManager.php +++ b/lib/public/Calendar/Room/IManager.php @@ -10,7 +10,6 @@ namespace OCP\Calendar\Room; /** * @since 14.0.0 - * @deprecated 24.0.0 */ interface IManager { /** @@ -55,4 +54,11 @@ interface IManager { * @deprecated 24.0.0 */ public function clear(); + + /** + * Update all rooms from all backends right now. + * + * @since 30.0.0 + */ + public function update(): void; } diff --git a/lib/public/Calendar/Room/IRoom.php b/lib/public/Calendar/Room/IRoom.php index 580c676331f..526e65b8f5f 100644 --- a/lib/public/Calendar/Room/IRoom.php +++ b/lib/public/Calendar/Room/IRoom.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Calendar/Room/IRoomMetadata.php b/lib/public/Calendar/Room/IRoomMetadata.php index 3fb4089b6a7..15d4b501e12 100644 --- a/lib/public/Calendar/Room/IRoomMetadata.php +++ b/lib/public/Calendar/Room/IRoomMetadata.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Capabilities/IPublicCapability.php b/lib/public/Capabilities/IPublicCapability.php index 3936d6032af..1a9dd965b16 100644 --- a/lib/public/Capabilities/IPublicCapability.php +++ b/lib/public/Capabilities/IPublicCapability.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php b/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php index 7151f05aef3..1a225178310 100644 --- a/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php +++ b/lib/public/Collaboration/AutoComplete/AutoCompleteEvent.php @@ -12,7 +12,7 @@ use OCP\EventDispatcher\GenericEvent; /** * @since 16.0.0 - * @deprecated Use {@see AutoCompleteFilterEvent} instead + * @deprecated 28.0.0 Use {@see AutoCompleteFilterEvent} instead */ class AutoCompleteEvent extends GenericEvent { /** diff --git a/lib/public/Collaboration/AutoComplete/IManager.php b/lib/public/Collaboration/AutoComplete/IManager.php index cc586b92d7d..2d5443b921d 100644 --- a/lib/public/Collaboration/AutoComplete/IManager.php +++ b/lib/public/Collaboration/AutoComplete/IManager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -18,9 +19,9 @@ interface IManager { public function registerSorter($className); /** - * @param array $sorters list of sorter IDs, separated by "|" - * @param array $sortArray array representation of OCP\Collaboration\Collaborators\ISearchResult - * @param array $context context info of the search, keys: itemType, itemId + * @param array $sorters list of sorter IDs, separated by "|" + * @param array $sortArray array representation of OCP\Collaboration\Collaborators\ISearchResult + * @param array{itemType: string, itemId: string, search?: string} $context context info of the search * @since 13.0.0 */ public function runSorters(array $sorters, array &$sortArray, array $context); diff --git a/lib/public/Collaboration/AutoComplete/ISorter.php b/lib/public/Collaboration/AutoComplete/ISorter.php index 772b1f9c6b9..1009092af6a 100644 --- a/lib/public/Collaboration/AutoComplete/ISorter.php +++ b/lib/public/Collaboration/AutoComplete/ISorter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -23,7 +24,7 @@ interface ISorter { * executes the sort action * * @param array $sortArray the array to be sorted, provided as reference - * @param array $context carries key 'itemType' and 'itemId' of the source object (e.g. a file) + * @param array{itemType: string, itemId: string, search?: string} $context carries key 'itemType' and 'itemId' of the source object (e.g. a file) * @since 13.0.0 */ public function sort(array &$sortArray, array $context); diff --git a/lib/public/Collaboration/Collaborators/ISearch.php b/lib/public/Collaboration/Collaborators/ISearch.php index a991a683acc..d2c5c85c07f 100644 --- a/lib/public/Collaboration/Collaborators/ISearch.php +++ b/lib/public/Collaboration/Collaborators/ISearch.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -24,7 +25,7 @@ interface ISearch { /** * @param array $pluginInfo with keys 'shareType' containing the name of a corresponding constant in \OCP\Share and - * 'class' with the class name of the plugin + * 'class' with the class name of the plugin * @since 13.0.0 */ public function registerPlugin(array $pluginInfo); diff --git a/lib/public/Collaboration/Collaborators/ISearchPlugin.php b/lib/public/Collaboration/Collaborators/ISearchPlugin.php index ec875d7d017..e55a095e2b6 100644 --- a/lib/public/Collaboration/Collaborators/ISearchPlugin.php +++ b/lib/public/Collaboration/Collaborators/ISearchPlugin.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Collaboration/Collaborators/ISearchResult.php b/lib/public/Collaboration/Collaborators/ISearchResult.php index 8e693caa677..bcc5a91ce99 100644 --- a/lib/public/Collaboration/Collaborators/ISearchResult.php +++ b/lib/public/Collaboration/Collaborators/ISearchResult.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Collaboration/Collaborators/SearchResultType.php b/lib/public/Collaboration/Collaborators/SearchResultType.php index f5ea0751c7b..3fdbefdcf1f 100644 --- a/lib/public/Collaboration/Collaborators/SearchResultType.php +++ b/lib/public/Collaboration/Collaborators/SearchResultType.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -11,7 +12,7 @@ namespace OCP\Collaboration\Collaborators; * @since 13.0.0 */ class SearchResultType { - /** @var string */ + /** @var string */ protected $label; /** diff --git a/lib/public/Collaboration/Reference/IPublicReferenceProvider.php b/lib/public/Collaboration/Reference/IPublicReferenceProvider.php new file mode 100644 index 00000000000..db6c3d3828b --- /dev/null +++ b/lib/public/Collaboration/Reference/IPublicReferenceProvider.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Collaboration\Reference; + +/** + * @since 30.0.0 + */ +interface IPublicReferenceProvider extends IReferenceProvider { + /** + * Return a reference with its metadata for a given reference identifier and sharingToken + * + * @since 30.0.0 + */ + public function resolveReferencePublic(string $referenceText, string $sharingToken): ?IReference; + + /** + * Return a custom cache key to be used for caching the metadata + * This could be for example the current sharingToken if the reference + * access permissions are different for each share + * + * Should return null, if the cache is only related to the + * reference id and has no further dependency + * + * @since 30.0.0 + */ + public function getCacheKeyPublic(string $referenceId, string $sharingToken): ?string; +} diff --git a/lib/public/Collaboration/Reference/IReferenceManager.php b/lib/public/Collaboration/Reference/IReferenceManager.php index 4a7de266435..c3cf7ca8e7b 100644 --- a/lib/public/Collaboration/Reference/IReferenceManager.php +++ b/lib/public/Collaboration/Reference/IReferenceManager.php @@ -27,8 +27,9 @@ interface IReferenceManager { * but may still return null in case this is disabled or the fetching fails * * @since 25.0.0 + * @since 30.0.0 optional arguments `$public` and `$sharingToken` */ - public function resolveReference(string $referenceId): ?IReference; + public function resolveReference(string $referenceId, bool $public = false, string $sharingToken = ''): ?IReference; /** * Get a reference by its cache key @@ -42,8 +43,9 @@ interface IReferenceManager { * the cache can then be filled with a separate request from the frontend * * @since 25.0.0 + * @since 30.0.0 optional arguments `$public` and `$sharingToken` */ - public function getReferenceFromCache(string $referenceId): ?IReference; + public function getReferenceFromCache(string $referenceId, bool $public = false, string $sharingToken = ''): ?IReference; /** * Invalidate all cache entries with a prefix or just one if the cache key is provided diff --git a/lib/public/Collaboration/Reference/LinkReferenceProvider.php b/lib/public/Collaboration/Reference/LinkReferenceProvider.php index 79b5164950c..65bdcecb577 100644 --- a/lib/public/Collaboration/Reference/LinkReferenceProvider.php +++ b/lib/public/Collaboration/Reference/LinkReferenceProvider.php @@ -9,7 +9,6 @@ declare(strict_types=1); namespace OCP\Collaboration\Reference; use Fusonic\OpenGraph\Consumer; -use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Psr7\LimitStream; use GuzzleHttp\Psr7\Utils; use OC\Security\RateLimiting\Exception\RateLimitExceededException; @@ -26,7 +25,7 @@ use Psr\Log\LoggerInterface; /** * @since 29.0.0 */ -class LinkReferenceProvider implements IReferenceProvider { +class LinkReferenceProvider implements IReferenceProvider, IPublicReferenceProvider { /** * for image size and webpage header @@ -88,6 +87,14 @@ class LinkReferenceProvider implements IReferenceProvider { } /** + * @inheritDoc + * @since 30.0.0 + */ + public function resolveReferencePublic(string $referenceText, string $sharingToken): ?IReference { + return $this->resolveReference($referenceText); + } + + /** * Populates the reference with OpenGraph data * * @param Reference $reference @@ -107,15 +114,15 @@ class LinkReferenceProvider implements IReferenceProvider { $client = $this->clientService->newClient(); try { - $headResponse = $client->head($reference->getId(), [ 'timeout' => 10 ]); + $headResponse = $client->head($reference->getId(), [ 'timeout' => 3 ]); } catch (\Exception $e) { $this->logger->debug('Failed to perform HEAD request to get target metadata', ['exception' => $e]); return; } $linkContentLength = $headResponse->getHeader('Content-Length'); - if (is_numeric($linkContentLength) && (int) $linkContentLength > self::MAX_CONTENT_LENGTH) { - $this->logger->debug('Skip resolving links pointing to content length > 5 MiB'); + if (is_numeric($linkContentLength) && (int)$linkContentLength > self::MAX_CONTENT_LENGTH) { + $this->logger->debug('[Head] Skip resolving links pointing to content length > 5 MiB'); return; } @@ -129,18 +136,28 @@ class LinkReferenceProvider implements IReferenceProvider { } try { - $response = $client->get($reference->getId(), [ 'timeout' => 10 ]); + $response = $client->get($reference->getId(), [ 'timeout' => 3, 'stream' => true ]); } catch (\Exception $e) { $this->logger->debug('Failed to fetch link for obtaining open graph data', ['exception' => $e]); return; } - $responseBody = (string)$response->getBody(); + $body = $response->getBody(); + if (is_resource($body)) { + $responseContent = fread($body, self::MAX_CONTENT_LENGTH); + if (!feof($body)) { + $this->logger->debug('[Get] Skip resolving links pointing to content length > 5 MiB'); + return; + } + } else { + $this->logger->error('[Get] Impossible to check content length'); + return; + } // OpenGraph handling $consumer = new Consumer(); $consumer->useFallbackMode = true; - $object = $consumer->loadHtml($responseBody); + $object = $consumer->loadHtml($responseContent); $reference->setUrl($reference->getId()); @@ -167,7 +184,7 @@ class LinkReferenceProvider implements IReferenceProvider { $folder = $appData->newFolder('opengraph'); } - $response = $client->get($object->images[0]->url, ['timeout' => 10]); + $response = $client->get($object->images[0]->url, ['timeout' => 3]); $contentType = $response->getHeader('Content-Type'); $contentLength = $response->getHeader('Content-Length'); @@ -178,10 +195,8 @@ class LinkReferenceProvider implements IReferenceProvider { $folder->newFile(md5($reference->getId()), $bodyStream->getContents()); $reference->setImageUrl($this->urlGenerator->linkToRouteAbsolute('core.Reference.preview', ['referenceId' => md5($reference->getId())])); } - } catch (GuzzleException $e) { - $this->logger->info('Failed to fetch and store the open graph image for ' . $reference->getId(), ['exception' => $e]); - } catch (\Throwable $e) { - $this->logger->error('Failed to fetch and store the open graph image for ' . $reference->getId(), ['exception' => $e]); + } catch (\Exception $e) { + $this->logger->debug('Failed to fetch and store the open graph image for ' . $reference->getId(), ['exception' => $e]); } } } @@ -201,4 +216,12 @@ class LinkReferenceProvider implements IReferenceProvider { public function getCacheKey(string $referenceId): ?string { return null; } + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getCacheKeyPublic(string $referenceId, string $sharingToken): ?string { + return null; + } } diff --git a/lib/public/Collaboration/Reference/RenderReferenceEvent.php b/lib/public/Collaboration/Reference/RenderReferenceEvent.php index 09778a20d2a..692098dbf60 100644 --- a/lib/public/Collaboration/Reference/RenderReferenceEvent.php +++ b/lib/public/Collaboration/Reference/RenderReferenceEvent.php @@ -11,8 +11,10 @@ namespace OCP\Collaboration\Reference; use OCP\EventDispatcher\Event; /** - * Event that apps can emit on their page rendering to trigger loading of aditional - * scripts for reference widget rendering + * Event emitted when apps might render references like link previews or smart picker widgets. + * + * This can be used to inject scripts for extending that. + * Further details can be found in the :ref:`Reference providers` deep dive. * * @since 25.0.0 */ diff --git a/lib/public/Color.php b/lib/public/Color.php index f48655ca04d..ed955b8f056 100644 --- a/lib/public/Color.php +++ b/lib/public/Color.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -83,7 +84,7 @@ class Color { * @since 25.0.0 */ public function name(): string { - return sprintf("#%02x%02x%02x", $this->r, $this->g, $this->b); + return sprintf('#%02x%02x%02x', $this->r, $this->g, $this->b); } /** @@ -125,7 +126,7 @@ class Color { * Calculate steps between two Colors * @param int $steps start color * @param Color[] $ends end color - * @return array{0: int, 1: int, 2: int} [r,g,b] steps for each color to go from $steps to $ends + * @return array{0: float, 1: float, 2: float} [r,g,b] steps for each color to go from $steps to $ends * @since 25.0.0 */ private static function stepCalc(int $steps, array $ends): array { diff --git a/lib/public/Comments/CommentsEntityEvent.php b/lib/public/Comments/CommentsEntityEvent.php index 2d0c50ad95b..39568151b61 100644 --- a/lib/public/Comments/CommentsEntityEvent.php +++ b/lib/public/Comments/CommentsEntityEvent.php @@ -38,9 +38,9 @@ class CommentsEntityEvent extends Event { /** * @param string $name * @param \Closure $entityExistsFunction The closure should take one - * argument, which is the id of the entity, that comments - * should be handled for. The return should then be bool, - * depending on whether comments are allowed (true) or not. + * argument, which is the id of the entity, that comments + * should be handled for. The return should then be bool, + * depending on whether comments are allowed (true) or not. * @throws \OutOfBoundsException when the entity name is already taken * @since 9.1.0 */ diff --git a/lib/public/Comments/IComment.php b/lib/public/Comments/IComment.php index e37a3ee9757..cdfcf188761 100644 --- a/lib/public/Comments/IComment.php +++ b/lib/public/Comments/IComment.php @@ -124,21 +124,12 @@ interface IComment { * returns an array containing mentions that are included in the comment * * @return array each mention provides a 'type' and an 'id', see example below + * @psalm-return list<array{type: 'guest'|'email'|'federated_group'|'group'|'federated_team'|'team'|'federated_user'|'user', id: non-empty-lowercase-string}> + * @since 30.0.2 Type 'email' is supported + * @since 29.0.0 Types 'federated_group', 'federated_team', 'team' and 'federated_user' are supported + * @since 23.0.0 Type 'group' is supported + * @since 17.0.0 Type 'guest' is supported * @since 11.0.0 - * - * The return array looks like: - * [ - * [ - * 'type' => 'user', - * 'id' => 'citizen4' - * ], - * [ - * 'type' => 'group', - * 'id' => 'media' - * ], - * … - * ] - * */ public function getMentions(); diff --git a/lib/public/Comments/ICommentsEventHandler.php b/lib/public/Comments/ICommentsEventHandler.php index af36e051b5f..148ead2c367 100644 --- a/lib/public/Comments/ICommentsEventHandler.php +++ b/lib/public/Comments/ICommentsEventHandler.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -9,11 +10,13 @@ namespace OCP\Comments; * Interface ICommentsEventHandler * * @since 11.0.0 + * @deprecated 30.0.0 Register a listener for the CommentsEvent through the IEventDispatcher */ interface ICommentsEventHandler { /** * @param CommentsEvent $event * @since 11.0.0 + * @deprecated 30.0.0 Register a listener for the CommentsEvent through the IEventDispatcher */ public function handle(CommentsEvent $event); } diff --git a/lib/public/Comments/ICommentsManager.php b/lib/public/Comments/ICommentsManager.php index 15a7d8f8cb1..7e59b5c7b06 100644 --- a/lib/public/Comments/ICommentsManager.php +++ b/lib/public/Comments/ICommentsManager.php @@ -85,10 +85,10 @@ interface ICommentsManager { * @param string $objectType the object type, e.g. 'files' * @param string $objectId the id of the object * @param int $limit optional, number of maximum comments to be returned. if - * not specified, all comments are returned. + * not specified, all comments are returned. * @param int $offset optional, starting point * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments - * that may be returned + * that may be returned * @return list<IComment> * @since 9.0.0 */ @@ -97,7 +97,7 @@ interface ICommentsManager { $objectId, $limit = 0, $offset = 0, - ?\DateTime $notOlderThan = null + ?\DateTime $notOlderThan = null, ); /** @@ -106,8 +106,9 @@ interface ICommentsManager { * @param int $lastKnownCommentId the last known comment (will be used as offset) * @param string $sortDirection direction of the comments (`asc` or `desc`) * @param int $limit optional, number of maximum comments to be returned. if - * set to 0, all comments are returned. + * set to 0, all comments are returned. * @param bool $includeLastKnown + * @param string $topmostParentId Limit the comments to a list of replies and its original root comment * @return list<IComment> * @since 14.0.0 * @deprecated 24.0.0 - Use getCommentsWithVerbForObjectSinceComment instead @@ -118,7 +119,8 @@ interface ICommentsManager { int $lastKnownCommentId, string $sortDirection = 'asc', int $limit = 30, - bool $includeLastKnown = false + bool $includeLastKnown = false, + string $topmostParentId = '', ): array; /** @@ -128,8 +130,9 @@ interface ICommentsManager { * @param int $lastKnownCommentId the last known comment (will be used as offset) * @param string $sortDirection direction of the comments (`asc` or `desc`) * @param int $limit optional, number of maximum comments to be returned. if - * set to 0, all comments are returned. + * set to 0, all comments are returned. * @param bool $includeLastKnown + * @param string $topmostParentId Limit the comments to a list of replies and its original root comment * @return list<IComment> * @since 24.0.0 */ @@ -140,7 +143,8 @@ interface ICommentsManager { int $lastKnownCommentId, string $sortDirection = 'asc', int $limit = 30, - bool $includeLastKnown = false + bool $includeLastKnown = false, + string $topmostParentId = '', ): array; /** @@ -175,7 +179,7 @@ interface ICommentsManager { * @param $objectType string the object type, e.g. 'files' * @param $objectId string the id of the object * @param \DateTime|null $notOlderThan optional, timestamp of the oldest comments - * that may be returned + * that may be returned * @param string $verb Limit the verb of the comment - Added in 14.0.0 * @return Int * @since 9.0.0 @@ -240,7 +244,7 @@ interface ICommentsManager { string $objectId, string $verb, string $actorType, - array $actors + array $actors, ): array; /** diff --git a/lib/public/Config/BeforePreferenceDeletedEvent.php b/lib/public/Config/BeforePreferenceDeletedEvent.php index c7026b0f6fc..df89a2900bd 100644 --- a/lib/public/Config/BeforePreferenceDeletedEvent.php +++ b/lib/public/Config/BeforePreferenceDeletedEvent.php @@ -8,11 +8,10 @@ declare(strict_types=1); namespace OCP\Config; +use OCP\AppFramework\Attribute\Listenable; use OCP\EventDispatcher\Event; -/** - * @since 25.0.0 - */ +#[Listenable(since: '25.0.0')] class BeforePreferenceDeletedEvent extends Event { protected string $userId; protected string $appId; diff --git a/lib/public/Config/BeforePreferenceSetEvent.php b/lib/public/Config/BeforePreferenceSetEvent.php index c5abb8429a8..6534e4ae371 100644 --- a/lib/public/Config/BeforePreferenceSetEvent.php +++ b/lib/public/Config/BeforePreferenceSetEvent.php @@ -8,11 +8,10 @@ declare(strict_types=1); namespace OCP\Config; +use OCP\AppFramework\Attribute\Listenable; use OCP\EventDispatcher\Event; -/** - * @since 25.0.0 - */ +#[Listenable(since: '25.0.0')] class BeforePreferenceSetEvent extends Event { protected string $userId; protected string $appId; diff --git a/lib/public/Config/Exceptions/IncorrectTypeException.php b/lib/public/Config/Exceptions/IncorrectTypeException.php new file mode 100644 index 00000000000..dea33233bfe --- /dev/null +++ b/lib/public/Config/Exceptions/IncorrectTypeException.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Config\Exceptions; + +use Exception; +use OCP\AppFramework\Attribute\Throwable; + +#[Throwable(since: '32.0.0')] +class IncorrectTypeException extends Exception { +} diff --git a/lib/public/Config/Exceptions/TypeConflictException.php b/lib/public/Config/Exceptions/TypeConflictException.php new file mode 100644 index 00000000000..c78602180e0 --- /dev/null +++ b/lib/public/Config/Exceptions/TypeConflictException.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Config\Exceptions; + +use Exception; +use OCP\AppFramework\Attribute\Throwable; + +#[Throwable(since: '32.0.0')] +class TypeConflictException extends Exception { +} diff --git a/lib/public/Config/Exceptions/UnknownKeyException.php b/lib/public/Config/Exceptions/UnknownKeyException.php new file mode 100644 index 00000000000..beca4c496a2 --- /dev/null +++ b/lib/public/Config/Exceptions/UnknownKeyException.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Config\Exceptions; + +use Exception; +use OCP\AppFramework\Attribute\Throwable; + +#[Throwable(since: '32.0.0')] +class UnknownKeyException extends Exception { +} diff --git a/lib/public/Config/IUserConfig.php b/lib/public/Config/IUserConfig.php new file mode 100644 index 00000000000..cb42608ea75 --- /dev/null +++ b/lib/public/Config/IUserConfig.php @@ -0,0 +1,742 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Config; + +use Generator; +use OCP\AppFramework\Attribute\Consumable; +use OCP\Config\Exceptions\IncorrectTypeException; +use OCP\Config\Exceptions\UnknownKeyException; + +/** + * This class provides an easy way for apps to store user config in the + * database. + * Supports **lazy loading** + * + * ### What is lazy loading ? + * In order to avoid loading useless user config into memory for each request, + * only non-lazy values are now loaded. + * + * Once a value that is lazy is requested, all lazy values will be loaded. + * + * Similarly, some methods from this class are marked with a warning about ignoring + * lazy loading. Use them wisely and only on parts of the code that are called + * during specific requests or actions to avoid loading the lazy values all the time. + */ +#[Consumable(since: '32.0.0')] +interface IUserConfig { + /** + * @since 32.0.0 + */ + public const FLAG_SENSITIVE = 1; // value is sensitive + /** + * @since 32.0.0 + */ + public const FLAG_INDEXED = 2; // value should be indexed + + /** + * Get list of all userIds with config stored in database. + * If $appId is specified, will only limit the search to this value + * + * **WARNING:** ignore any cache and get data directly from database. + * + * @param string $appId optional id of app + * + * @return list<string> list of userIds + * + * @since 32.0.0 + */ + public function getUserIds(string $appId = ''): array; + + /** + * Get list of all apps that have at least one config + * value related to $userId stored in database + * + * **WARNING:** ignore lazy filtering, all user config are loaded from database + * + * @param string $userId id of the user + * + * @return list<string> list of app ids + * + * @since 32.0.0 + */ + public function getApps(string $userId): array; + + /** + * Returns all keys stored in database, related to user+app. + * Please note that the values are not returned. + * + * **WARNING:** ignore lazy filtering, all user config are loaded from database + * + * @param string $userId id of the user + * @param string $app id of the app + * + * @return list<string> list of stored config keys + * + * @since 32.0.0 + */ + public function getKeys(string $userId, string $app): array; + + /** + * Check if a key exists in the list of stored config values. + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param bool $lazy search within lazy loaded config + * + * @return bool TRUE if key exists + * + * @since 32.0.0 + */ + public function hasKey(string $userId, string $app, string $key, ?bool $lazy = false): bool; + + /** + * best way to see if a value is set as sensitive (not displayed in report) + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param bool|null $lazy search within lazy loaded config + * + * @return bool TRUE if value is sensitive + * @throws UnknownKeyException if config key is not known + * + * @since 32.0.0 + */ + public function isSensitive(string $userId, string $app, string $key, ?bool $lazy = false): bool; + + /** + * best way to see if a value is set as indexed (so it can be search) + * + * @see self::searchUsersByValueString() + * @see self::searchUsersByValueInt() + * @see self::searchUsersByValueBool() + * @see self::searchUsersByValues() + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param bool|null $lazy search within lazy loaded config + * + * @return bool TRUE if value is sensitive + * @throws UnknownKeyException if config key is not known + * + * @since 32.0.0 + */ + public function isIndexed(string $userId, string $app, string $key, ?bool $lazy = false): bool; + + /** + * Returns if the config key stored in database is lazy loaded + * + * **WARNING:** ignore lazy filtering, all config values are loaded from database + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * + * @return bool TRUE if config is lazy loaded + * @throws UnknownKeyException if config key is not known + * @see IUserConfig for details about lazy loading + * + * @since 32.0.0 + */ + public function isLazy(string $userId, string $app, string $key): bool; + + /** + * List all config values from an app with config key starting with $key. + * Returns an array with config key as key, stored value as value. + * + * **WARNING:** ignore lazy filtering, all config values are loaded from database + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $prefix config keys prefix to search, can be empty. + * @param bool $filtered filter sensitive config values + * + * @return array<string, string|int|float|bool|array> [key => value] + * + * @since 32.0.0 + */ + public function getValues(string $userId, string $app, string $prefix = '', bool $filtered = false): array; + + /** + * List all config values of a user. + * Returns an array with config key as key, stored value as value. + * + * **WARNING:** ignore lazy filtering, all config values are loaded from database + * + * @param string $userId id of the user + * @param bool $filtered filter sensitive config values + * + * @return array<string, string|int|float|bool|array> [key => value] + * + * @since 32.0.0 + */ + public function getAllValues(string $userId, bool $filtered = false): array; + + /** + * List all apps storing a specific config key and its stored value. + * Returns an array with appId as key, stored value as value. + * + * @param string $userId id of the user + * @param string $key config key + * @param bool $lazy search within lazy loaded config + * @param ValueType|null $typedAs enforce type for the returned values + * + * @return array<string, string|int|float|bool|array> [appId => value] + * + * @since 32.0.0 + */ + public function getValuesByApps(string $userId, string $key, bool $lazy = false, ?ValueType $typedAs = null): array; + + /** + * List all users storing a specific config key and its stored value. + * Returns an array with userId as key, stored value as value. + * + * **WARNING:** no caching, generate a fresh request + * + * @param string $app id of the app + * @param string $key config key + * @param ValueType|null $typedAs enforce type for the returned values + * @param array|null $userIds limit the search to a list of user ids + * + * @return array<string, string|int|float|bool|array> [userId => value] + * + * @since 32.0.0 + */ + public function getValuesByUsers(string $app, string $key, ?ValueType $typedAs = null, ?array $userIds = null): array; + + /** + * List all users storing a specific config key/value pair. + * Returns a list of user ids. + * + * **WARNING:** no caching, generate a fresh request + * + * @param string $app id of the app + * @param string $key config key + * @param string $value config value + * @param bool $caseInsensitive non-case-sensitive search, only works if $value is a string + * + * @return Generator<string> + * + * @since 32.0.0 + */ + public function searchUsersByValueString(string $app, string $key, string $value, bool $caseInsensitive = false): Generator; + + /** + * List all users storing a specific config key/value pair. + * Returns a list of user ids. + * + * **WARNING:** no caching, generate a fresh request + * + * @param string $app id of the app + * @param string $key config key + * @param int $value config value + * + * @return Generator<string> + * + * @since 32.0.0 + */ + public function searchUsersByValueInt(string $app, string $key, int $value): Generator; + + /** + * List all users storing a specific config key/value pair. + * Returns a list of user ids. + * + * **WARNING:** no caching, generate a fresh request + * + * @param string $app id of the app + * @param string $key config key + * @param array $values list of possible config values + * + * @return Generator<string> + * + * @since 32.0.0 + */ + public function searchUsersByValues(string $app, string $key, array $values): Generator; + + /** + * List all users storing a specific config key/value pair. + * Returns a list of user ids. + * + * **WARNING:** no caching, generate a fresh request + * + * @param string $app id of the app + * @param string $key config key + * @param bool $value config value + * + * @return Generator<string> + * + * @since 32.0.0 + */ + public function searchUsersByValueBool(string $app, string $key, bool $value): Generator; + + /** + * Get user config assigned to a config key. + * If config key is not found in database, default value is returned. + * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE. + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param string $default default value + * @param bool $lazy search within lazy loaded config + * + * @return string stored config value or $default if not set in database + * + * @since 32.0.0 + * + * @see IUserConfig for explanation about lazy loading + * @see getValueInt() + * @see getValueFloat() + * @see getValueBool() + * @see getValueArray() + */ + public function getValueString(string $userId, string $app, string $key, string $default = '', bool $lazy = false): string; + + /** + * Get config value assigned to a config key. + * If config key is not found in database, default value is returned. + * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE. + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param int $default default value + * @param bool $lazy search within lazy loaded config + * + * @return int stored config value or $default if not set in database + * + * @since 32.0.0 + * + * @see IUserConfig for explanation about lazy loading + * @see getValueString() + * @see getValueFloat() + * @see getValueBool() + * @see getValueArray() + */ + public function getValueInt(string $userId, string $app, string $key, int $default = 0, bool $lazy = false): int; + + /** + * Get config value assigned to a config key. + * If config key is not found in database, default value is returned. + * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE. + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param float $default default value + * @param bool $lazy search within lazy loaded config + * + * @return float stored config value or $default if not set in database + * + * @since 32.0.0 + * + * @see IUserConfig for explanation about lazy loading + * @see getValueString() + * @see getValueInt() + * @see getValueBool() + * @see getValueArray() + */ + public function getValueFloat(string $userId, string $app, string $key, float $default = 0, bool $lazy = false): float; + + /** + * Get config value assigned to a config key. + * If config key is not found in database, default value is returned. + * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE. + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param bool $default default value + * @param bool $lazy search within lazy loaded config + * + * @return bool stored config value or $default if not set in database + * + * @since 32.0.0 + * + * @see IUserPrefences for explanation about lazy loading + * @see getValueString() + * @see getValueInt() + * @see getValueFloat() + * @see getValueArray() + */ + public function getValueBool(string $userId, string $app, string $key, bool $default = false, bool $lazy = false): bool; + + /** + * Get config value assigned to a config key. + * If config key is not found in database, default value is returned. + * If config key is set as lazy loaded, the $lazy argument needs to be set to TRUE. + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param array $default default value` + * @param bool $lazy search within lazy loaded config + * + * @return array stored config value or $default if not set in database + * + * @since 32.0.0 + * + * @see IUserConfig for explanation about lazy loading + * @see getValueString() + * @see getValueInt() + * @see getValueFloat() + * @see getValueBool() + */ + public function getValueArray(string $userId, string $app, string $key, array $default = [], bool $lazy = false): array; + + /** + * returns the type of config value + * + * **WARNING:** ignore lazy filtering, all config values are loaded from database + * unless lazy is set to false + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param bool|null $lazy + * + * @return ValueType type of the value + * @throws UnknownKeyException if config key is not known + * @throws IncorrectTypeException if config value type is not known + * + * @since 32.0.0 + */ + public function getValueType(string $userId, string $app, string $key, ?bool $lazy = null): ValueType; + + /** + * returns a bitflag related to config value + * + * **WARNING:** ignore lazy filtering, all config values are loaded from database + * unless lazy is set to false + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param bool $lazy lazy loading + * + * @return int a bitflag in relation to the config value + * @throws UnknownKeyException if config key is not known + * @throws IncorrectTypeException if config value type is not known + * + * @since 32.0.0 + */ + public function getValueFlags(string $userId, string $app, string $key, bool $lazy = false): int; + + /** + * Store a config key and its value in database + * + * If config key is already known with the exact same config value, the database is not updated. + * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded. + * + * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param string $value config value + * @param bool $sensitive if TRUE value will be hidden when listing config values. + * @param bool $lazy set config as lazy loaded + * + * @return bool TRUE if value was different, therefor updated in database + * + * @since 32.0.0 + * + * @see IUserConfig for explanation about lazy loading + * @see setValueInt() + * @see setValueFloat() + * @see setValueBool() + * @see setValueArray() + */ + public function setValueString(string $userId, string $app, string $key, string $value, bool $lazy = false, int $flags = 0): bool; + + /** + * Store a config key and its value in database + * + * When handling huge value around and/or above 2,147,483,647, a debug log will be generated + * on 64bits system, as php int type reach its limit (and throw an exception) on 32bits when using huge numbers. + * + * When using huge numbers, it is advised to use {@see \OCP\Util::numericToNumber()} and {@see setValueString()} + * + * If config key is already known with the exact same config value, the database is not updated. + * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded. + * + * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param int $value config value + * @param bool $sensitive if TRUE value will be hidden when listing config values. + * @param bool $lazy set config as lazy loaded + * + * @return bool TRUE if value was different, therefor updated in database + * + * @since 32.0.0 + * + * @see IUserConfig for explanation about lazy loading + * @see setValueString() + * @see setValueFloat() + * @see setValueBool() + * @see setValueArray() + */ + public function setValueInt(string $userId, string $app, string $key, int $value, bool $lazy = false, int $flags = 0): bool; + + /** + * Store a config key and its value in database. + * + * If config key is already known with the exact same config value, the database is not updated. + * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded. + * + * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param float $value config value + * @param bool $sensitive if TRUE value will be hidden when listing config values. + * @param bool $lazy set config as lazy loaded + * + * @return bool TRUE if value was different, therefor updated in database + * + * @since 32.0.0 + * + * @see IUserConfig for explanation about lazy loading + * @see setValueString() + * @see setValueInt() + * @see setValueBool() + * @see setValueArray() + */ + public function setValueFloat(string $userId, string $app, string $key, float $value, bool $lazy = false, int $flags = 0): bool; + + /** + * Store a config key and its value in database + * + * If config key is already known with the exact same config value, the database is not updated. + * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded. + * + * If config value was previously stored as lazy loaded, status cannot be altered without using {@see deleteKey()} first + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param bool $value config value + * @param bool $lazy set config as lazy loaded + * + * @return bool TRUE if value was different, therefor updated in database + * + * @since 32.0.0 + * + * @see IUserConfig for explanation about lazy loading + * @see setValueString() + * @see setValueInt() + * @see setValueFloat() + * @see setValueArray() + */ + public function setValueBool(string $userId, string $app, string $key, bool $value, bool $lazy = false): bool; + + /** + * Store a config key and its value in database + * + * If config key is already known with the exact same config value, the database is not updated. + * If config key is not supposed to be read during the boot of the cloud, it is advised to set it as lazy loaded. + * + * If config value was previously stored as sensitive or lazy loaded, status cannot be altered without using {@see deleteKey()} first + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param array $value config value + * @param bool $sensitive if TRUE value will be hidden when listing config values. + * @param bool $lazy set config as lazy loaded + * + * @return bool TRUE if value was different, therefor updated in database + * + * @since 32.0.0 + * + * @see IUserConfig for explanation about lazy loading + * @see setValueString() + * @see setValueInt() + * @see setValueFloat() + * @see setValueBool() + */ + public function setValueArray(string $userId, string $app, string $key, array $value, bool $lazy = false, int $flags = 0): bool; + + /** + * switch sensitive status of a config value + * + * **WARNING:** ignore lazy filtering, all config values are loaded from database + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param bool $sensitive TRUE to set as sensitive, FALSE to unset + * + * @return bool TRUE if database update were necessary + * + * @since 32.0.0 + */ + public function updateSensitive(string $userId, string $app, string $key, bool $sensitive): bool; + + /** + * switch sensitive loading status of a config key for all users + * + * **Warning:** heavy on resources, MUST only be used on occ command or migrations + * + * @param string $app id of the app + * @param string $key config key + * @param bool $sensitive TRUE to set as sensitive, FALSE to unset + * + * @since 32.0.0 + */ + public function updateGlobalSensitive(string $app, string $key, bool $sensitive): void; + + + /** + * switch indexed status of a config value + * + * **WARNING:** ignore lazy filtering, all config values are loaded from database + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param bool $indexed TRUE to set as indexed, FALSE to unset + * + * @return bool TRUE if database update were necessary + * + * @since 32.0.0 + */ + public function updateIndexed(string $userId, string $app, string $key, bool $indexed): bool; + + /** + * switch sensitive loading status of a config key for all users + * + * **Warning:** heavy on resources, MUST only be used on occ command or migrations + * + * @param string $app id of the app + * @param string $key config key + * @param bool $indexed TRUE to set as indexed, FALSE to unset + * + * @since 32.0.0 + */ + public function updateGlobalIndexed(string $app, string $key, bool $indexed): void; + + /** + * switch lazy loading status of a config value + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset + * + * @return bool TRUE if database update was necessary + * + * @since 32.0.0 + */ + public function updateLazy(string $userId, string $app, string $key, bool $lazy): bool; + + /** + * switch lazy loading status of a config key for all users + * + * **Warning:** heavy on resources, MUST only be used on occ command or migrations + * + * @param string $app id of the app + * @param string $key config key + * @param bool $lazy TRUE to set as lazy loaded, FALSE to unset + * + * @since 32.0.0 + */ + public function updateGlobalLazy(string $app, string $key, bool $lazy): void; + + /** + * returns an array contains details about a config value + * + * ``` + * [ + * "app" => "myapp", + * "key" => "mykey", + * "value" => "its_value", + * "lazy" => false, + * "type" => 4, + * "typeString" => "string", + * 'sensitive' => true + * ] + * ``` + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * + * @return array + * @throws UnknownKeyException if config key is not known in database + * + * @since 32.0.0 + */ + public function getDetails(string $userId, string $app, string $key): array; + + /** + * Delete single config key from database. + * + * @param string $userId id of the user + * @param string $app id of the app + * @param string $key config key + * + * @since 32.0.0 + */ + public function deleteUserConfig(string $userId, string $app, string $key): void; + + /** + * Delete config values from all users linked to a specific config keys + * + * @param string $app id of the app + * @param string $key config key + * + * @since 32.0.0 + */ + public function deleteKey(string $app, string $key): void; + + /** + * delete all config keys linked to an app + * + * @param string $app id of the app + * + * @since 32.0.0 + */ + public function deleteApp(string $app): void; + + /** + * delete all config keys linked to a user + * + * @param string $userId id of the user + * + * @since 32.0.0 + */ + public function deleteAllUserConfig(string $userId): void; + + /** + * Clear the cache for a single user + * + * The cache will be rebuilt only the next time a user config is requested. + * + * @param string $userId id of the user + * @param bool $reload set to TRUE to refill cache instantly after clearing it + * + * @since 32.0.0 + */ + public function clearCache(string $userId, bool $reload = false): void; + + /** + * Clear the cache for all users. + * The cache will be rebuilt only the next time a user config is requested. + * + * @since 32.0.0 + */ + public function clearCacheAll(): void; +} diff --git a/lib/public/Config/Lexicon/Entry.php b/lib/public/Config/Lexicon/Entry.php new file mode 100644 index 00000000000..c810522dde5 --- /dev/null +++ b/lib/public/Config/Lexicon/Entry.php @@ -0,0 +1,266 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Config\Lexicon; + +use Closure; +use OCP\AppFramework\Attribute\Consumable; +use OCP\Config\ValueType; + +/** + * Model that represent config values within an app config lexicon. + * + * @see ILexicon + */ +#[Consumable(since: '32.0.0')] +class Entry { + /** @since 32.0.0 */ + public const RENAME_INVERT_BOOLEAN = 1; + + private string $definition = ''; + private string $note = ''; + private ?string $default = null; + + /** + * @param string $key config key; can only contain alphanumerical chars and underscore "_" + * @param ValueType $type type of config value + * @param string|int|float|bool|array|Closure|null $defaultRaw default value to be used in case none known + * @param string $definition optional description of config key available when using occ command + * @param bool $lazy set config value as lazy + * @param int $flags set flags + * @param bool $deprecated set config key as deprecated + * @param string|null $rename source in case of a rename of a config key. + * @param int $options additional bitflag options {@see self::RENAME_INVERT_BOOLEAN} + * @param string $note additional note and warning related to the use of the config key. + * + * @since 32.0.0 + * @psalm-suppress PossiblyInvalidCast + * @psalm-suppress RiskyCast + */ + public function __construct( + private readonly string $key, + private readonly ValueType $type, + private null|string|int|float|bool|array|Closure $defaultRaw = null, + string $definition = '', + private readonly bool $lazy = false, + private readonly int $flags = 0, + private readonly bool $deprecated = false, + private readonly ?string $rename = null, + private readonly int $options = 0, + string $note = '', + ) { + // key can only contain alphanumeric chars and underscore "_" + if (preg_match('/[^[:alnum:]_]/', $key)) { + throw new \Exception('invalid config key'); + } + + /** @psalm-suppress UndefinedClass */ + if (\OC::$CLI) { // only store definition if ran from CLI + $this->definition = $definition; + $this->note = $note; + } + } + + /** + * returns the config key + * + * @return string config key + * @since 32.0.0 + */ + public function getKey(): string { + return $this->key; + } + + /** + * get expected type for config value + * + * @return ValueType + * @since 32.0.0 + */ + public function getValueType(): ValueType { + return $this->type; + } + + /** + * @param string $default + * @return string + * @since 32.0.0 + */ + private function convertFromString(string $default): string { + return $default; + } + + /** + * @param int $default + * @return string + * @since 32.0.0 + */ + private function convertFromInt(int $default): string { + return (string)$default; + } + + /** + * @param float $default + * @return string + * @since 32.0.0 + */ + private function convertFromFloat(float $default): string { + return (string)$default; + } + + /** + * @param bool $default + * @return string + * @since 32.0.0 + */ + private function convertFromBool(bool $default): string { + return ($default) ? '1' : '0'; + } + + /** + * @param array $default + * @return string + * @since 32.0.0 + */ + private function convertFromArray(array $default): string { + return json_encode($default); + } + + /** + * returns default value + * + * @return string|null NULL if no default is set + * @since 32.0.0 + */ + public function getDefault(Preset $preset): ?string { + if ($this->default !== null) { + return $this->default; + } + + if ($this->defaultRaw === null) { + return null; + } + + if ($this->defaultRaw instanceof Closure) { + /** @psalm-suppress MixedAssignment we expect closure to returns string|int|float|bool|array */ + $this->defaultRaw = ($this->defaultRaw)($preset); + } + + /** @psalm-suppress MixedArgument closure should be managed previously */ + $this->default = $this->convertToString($this->defaultRaw); + + return $this->default; + } + + /** + * convert $entry into string, based on the expected type for config value + * + * @param string|int|float|bool|array $entry + * + * @return string + * @since 32.0.0 + * @psalm-suppress PossiblyInvalidCast arrays are managed pre-cast + * @psalm-suppress RiskyCast + */ + public function convertToString(string|int|float|bool|array $entry): string { + // in case $default is array but is not expected to be an array... + if ($this->getValueType() !== ValueType::ARRAY && is_array($entry)) { + $entry = json_encode($entry, JSON_THROW_ON_ERROR); + } + + return match ($this->getValueType()) { + ValueType::MIXED => (string)$entry, + ValueType::STRING => $this->convertFromString((string)$entry), + ValueType::INT => $this->convertFromInt((int)$entry), + ValueType::FLOAT => $this->convertFromFloat((float)$entry), + ValueType::BOOL => $this->convertFromBool((bool)$entry), + ValueType::ARRAY => $this->convertFromArray((array)$entry) + }; + } + + /** + * returns definition + * + * @return string + * @since 32.0.0 + */ + public function getDefinition(): string { + return $this->definition; + } + + /** + * returns eventual note + * + * @return string + * @since 32.0.0 + */ + public function getNote(): string { + return $this->note; + } + + /** + * returns if config key is set as lazy + * + * @see IAppConfig for details on lazy config values + * @return bool TRUE if config value is lazy + * @since 32.0.0 + */ + public function isLazy(): bool { + return $this->lazy; + } + + /** + * returns flags + * + * @see IAppConfig for details on sensitive config values + * @return int bitflag about the config value + * @since 32.0.0 + */ + public function getFlags(): int { + return $this->flags; + } + + /** + * @param int $flag + * + * @return bool TRUE is config value bitflag contains $flag + * @since 32.0.0 + */ + public function isFlagged(int $flag): bool { + return (($flag & $this->getFlags()) === $flag); + } + + /** + * should be called/used only during migration/upgrade. + * link to an old config key. + * + * @return string|null not NULL if value can be imported from a previous key + * @since 32.0.0 + */ + public function getRename(): ?string { + return $this->rename; + } + + /** + * @since 32.0.0 + * @return bool TRUE if $option was set during the creation of the entry. + */ + public function hasOption(int $option): bool { + return (($option & $this->options) !== 0); + } + + /** + * returns if config key is set as deprecated + * + * @return bool TRUE if config si deprecated + * @since 32.0.0 + */ + public function isDeprecated(): bool { + return $this->deprecated; + } +} diff --git a/lib/public/Config/Lexicon/ILexicon.php b/lib/public/Config/Lexicon/ILexicon.php new file mode 100644 index 00000000000..05bf5967f24 --- /dev/null +++ b/lib/public/Config/Lexicon/ILexicon.php @@ -0,0 +1,45 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OCP\Config\Lexicon; + +use OCP\AppFramework\Attribute\Implementable; + +/** + * This interface needs to be implemented if you want to define a config lexicon for your application + * The config lexicon is used to avoid conflicts and problems when storing/retrieving config values + */ +#[Implementable(since: '32.0.0')] +interface ILexicon { + + /** + * Define the expected behavior when using config + * keys not set within your application config lexicon. + * + * @return Strictness + * @since 32.0.0 + *@see Strictness + */ + public function getStrictness(): Strictness; + + /** + * define the list of entries of your application config lexicon, related to AppConfig. + * + * @return Entry[] + * @since 32.0.0 + */ + public function getAppConfigs(): array; + + /** + * define the list of entries of your application config lexicon, related to UserPreferences. + * + * @return Entry[] + * @since 32.0.0 + */ + public function getUserConfigs(): array; +} diff --git a/lib/public/Config/Lexicon/Preset.php b/lib/public/Config/Lexicon/Preset.php new file mode 100644 index 00000000000..6dac8736131 --- /dev/null +++ b/lib/public/Config/Lexicon/Preset.php @@ -0,0 +1,50 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OCP\Config\Lexicon; + +use OCP\AppFramework\Attribute\Consumable; + +/** + * list of preset to handle the default behavior of the instance + * + * @see Entry::preset + * + * - **Preset::LARGE** - Large size organisation (> 50k accounts) + * - **Preset::MEDIUM** - Medium size organisation (> 100 accounts) + * - **Preset::SMALL** - Small size organisation (< 100 accounts) + * - **Preset::SHARED** - Shared hosting + * - **Preset::UNIVERSITY** - Education, large size + * - **Preset::SCHOOL** - Eduction, small/medium size + * - **Preset::CLUB** - Club/Association + * - **Preset::FAMILY** - Family + * - **Preset::PRIVATE** - Private + */ +#[Consumable(since: '32.0.0')] +enum Preset: int { + /** @since 32.0.0 */ + case LARGE = 9; + /** @since 32.0.0 */ + case MEDIUM = 8; + /** @since 32.0.0 */ + case SMALL = 7; + /** @since 32.0.0 */ + case SHARED = 6; + /** @since 32.0.0 */ + case UNIVERSITY = 5; + /** @since 32.0.0 */ + case SCHOOL = 4; + /** @since 32.0.0 */ + case CLUB = 3; + /** @since 32.0.0 */ + case FAMILY = 2; + /** @since 32.0.0 */ + case PRIVATE = 1; + /** @since 32.0.0 */ + case NONE = 0; +} diff --git a/lib/public/Config/Lexicon/Strictness.php b/lib/public/Config/Lexicon/Strictness.php new file mode 100644 index 00000000000..8136499cb3e --- /dev/null +++ b/lib/public/Config/Lexicon/Strictness.php @@ -0,0 +1,31 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OCP\Config\Lexicon; + +use OCP\AppFramework\Attribute\Consumable; + +/** + * Strictness regarding using not-listed config keys + * + * - **Strictness::IGNORE** - fully ignore + * - **Strictness::NOTICE** - ignore and report + * - **Strictness::WARNING** - silently block (returns $default) and report + * - **Strictness::EXCEPTION** - block (throws exception) and report + */ +#[Consumable(since: '32.0.0')] +enum Strictness { + /** @since 32.0.0 */ + case IGNORE; // fully ignore + /** @since 32.0.0 */ + case NOTICE; // ignore and report + /** @since 32.0.0 */ + case WARNING; // silently block (returns $default) and report + /** @since 32.0.0 */ + case EXCEPTION; // block (throws exception) and report +} diff --git a/lib/public/Config/ValueType.php b/lib/public/Config/ValueType.php new file mode 100644 index 00000000000..32c734af32b --- /dev/null +++ b/lib/public/Config/ValueType.php @@ -0,0 +1,117 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Config; + +use OCP\AppFramework\Attribute\Consumable; +use OCP\Config\Exceptions\IncorrectTypeException; +use OCP\IAppConfig; +use UnhandledMatchError; + +/** + * Listing of available value type for typed config value + */ +#[Consumable(since: '32.0.0')] +enum ValueType: int { + /** + * @since 32.0.0 + */ + case MIXED = 0; + /** + * @since 32.0.0 + */ + case STRING = 1; + /** + * @since 32.0.0 + */ + case INT = 2; + /** + * @since 32.0.0 + */ + case FLOAT = 3; + /** + * @since 32.0.0 + */ + case BOOL = 4; + /** + * @since 32.0.0 + */ + case ARRAY = 5; + + /** + * get ValueType from string + * + * @param string $definition + * + * @return self + * @throws IncorrectTypeException + * + * @since 32.0.0 + */ + public static function fromStringDefinition(string $definition): self { + try { + return match ($definition) { + 'mixed' => self::MIXED, + 'string' => self::STRING, + 'int' => self::INT, + 'float' => self::FLOAT, + 'bool' => self::BOOL, + 'array' => self::ARRAY + }; + } catch (\UnhandledMatchError) { + throw new IncorrectTypeException('unknown string definition'); + } + } + + /** + * get string definition for current enum value + * + * @return string + * @throws IncorrectTypeException + * + * @since 32.0.0 + */ + public function getDefinition(): string { + try { + return match ($this) { + self::MIXED => 'mixed', + self::STRING => 'string', + self::INT => 'int', + self::FLOAT => 'float', + self::BOOL => 'bool', + self::ARRAY => 'array', + }; + } catch (UnhandledMatchError) { + throw new IncorrectTypeException('unknown type definition ' . $this->value); + } + } + + /** + * get corresponding AppConfig flag value + * + * @return int + * @throws IncorrectTypeException + * + * @since 32.0.0 + */ + public function toAppConfigFlag(): int { + try { + return match ($this) { + self::MIXED => IAppConfig::VALUE_MIXED, + self::STRING => IAppConfig::VALUE_STRING, + self::INT => IAppConfig::VALUE_INT, + self::FLOAT => IAppConfig::VALUE_FLOAT, + self::BOOL => IAppConfig::VALUE_BOOL, + self::ARRAY => IAppConfig::VALUE_ARRAY, + }; + } catch (UnhandledMatchError) { + throw new IncorrectTypeException('unknown type definition ' . $this->value); + } + } + +} diff --git a/lib/public/Console/ReservedOptions.php b/lib/public/Console/ReservedOptions.php new file mode 100644 index 00000000000..aff7a7daa3a --- /dev/null +++ b/lib/public/Console/ReservedOptions.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Console; + +/** + * @since 30.0.0 + */ +final class ReservedOptions { + /** + * @since 30.0.0 + */ + public const DEBUG_LOG = 'debug-log'; + /** + * @since 30.0.0 + */ + public const DEBUG_LOG_LEVEL = 'debug-log-level'; +} diff --git a/lib/public/Constants.php b/lib/public/Constants.php index 8326aa6570e..8d38ade7baf 100644 --- a/lib/public/Constants.php +++ b/lib/public/Constants.php @@ -18,20 +18,20 @@ namespace OCP; */ class Constants { /** - * CRUDS permissions. * @since 8.0.0 */ - public const PERMISSION_CREATE = 4; + public const PERMISSION_READ = 1; /** * @since 8.0.0 */ - public const PERMISSION_READ = 1; + public const PERMISSION_UPDATE = 2; /** + * CRUDS permissions. * @since 8.0.0 */ - public const PERMISSION_UPDATE = 2; + public const PERMISSION_CREATE = 4; /** * @since 8.0.0 @@ -52,7 +52,7 @@ class Constants { * @since 8.0.0 - Updated in 9.0.0 to allow all POSIX chars since we no * longer support windows as server platform. */ - public const FILENAME_INVALID_CHARS = "\\/"; + public const FILENAME_INVALID_CHARS = '\\/'; /** * @since 21.0.0 – default value for autocomplete/search results limit, diff --git a/lib/public/Contacts/ContactsMenu/IAction.php b/lib/public/Contacts/ContactsMenu/IAction.php index 4a7133a999b..00fe8ba35c4 100644 --- a/lib/public/Contacts/ContactsMenu/IAction.php +++ b/lib/public/Contacts/ContactsMenu/IAction.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Contacts/ContactsMenu/IActionFactory.php b/lib/public/Contacts/ContactsMenu/IActionFactory.php index a4785d1c70c..69e6030e95b 100644 --- a/lib/public/Contacts/ContactsMenu/IActionFactory.php +++ b/lib/public/Contacts/ContactsMenu/IActionFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Contacts/ContactsMenu/IContactsStore.php b/lib/public/Contacts/ContactsMenu/IContactsStore.php index ceb68d99849..67913a2d919 100644 --- a/lib/public/Contacts/ContactsMenu/IContactsStore.php +++ b/lib/public/Contacts/ContactsMenu/IContactsStore.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Contacts/ContactsMenu/IEntry.php b/lib/public/Contacts/ContactsMenu/IEntry.php index c361b71bb7c..9ae8a207297 100644 --- a/lib/public/Contacts/ContactsMenu/IEntry.php +++ b/lib/public/Contacts/ContactsMenu/IEntry.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Contacts/ContactsMenu/ILinkAction.php b/lib/public/Contacts/ContactsMenu/ILinkAction.php index 92bccfd8dda..559e04885c5 100644 --- a/lib/public/Contacts/ContactsMenu/ILinkAction.php +++ b/lib/public/Contacts/ContactsMenu/ILinkAction.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Contacts/IManager.php b/lib/public/Contacts/IManager.php index f19e72e0763..60abb18b382 100644 --- a/lib/public/Contacts/IManager.php +++ b/lib/public/Contacts/IManager.php @@ -68,14 +68,14 @@ interface IManager { * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match * @param array $options = array() to define the search behavior - * - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array - * example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']] - * - 'escape_like_param' - If set to false wildcards _ and % are not escaped - * - 'limit' - Set a numeric limit for the search results - * - 'offset' - Set the offset for the limited search results - * - 'enumeration' - (since 23.0.0) Whether user enumeration on system address book is allowed - * - 'fullmatch' - (since 23.0.0) Whether matching on full detail in system address book is allowed - * - 'strict_search' - (since 23.0.0) Whether the search pattern is full string or partial search + * - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array + * example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']] + * - 'escape_like_param' - If set to false wildcards _ and % are not escaped + * - 'limit' - Set a numeric limit for the search results + * - 'offset' - Set the offset for the limited search results + * - 'enumeration' - (since 23.0.0) Whether user enumeration on system address book is allowed + * - 'fullmatch' - (since 23.0.0) Whether matching on full detail in system address book is allowed + * - 'strict_search' - (since 23.0.0) Whether the search pattern is full string or partial search * @psalm-param array{types?: bool, escape_like_param?: bool, limit?: int, offset?: int, enumeration?: bool, fullmatch?: bool, strict_search?: bool} $options * @return array an array of contacts which are arrays of key-value-pairs * @since 6.0.0 diff --git a/lib/public/ContextChat/ContentItem.php b/lib/public/ContextChat/ContentItem.php new file mode 100644 index 00000000000..2b5289ba350 --- /dev/null +++ b/lib/public/ContextChat/ContentItem.php @@ -0,0 +1,57 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024-2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\ContextChat; + +/** + * @since 32.0.0 + */ +class ContentItem { + /** + * @param string $itemId + * @param string $providerId + * @param string $title + * @param string $content + * @param string $documentType + * @param \DateTime $lastModified + * @param string[] $users + * @since 32.0.0 + */ + public function __construct( + /** + * @since 32.0.0 + */ + public string $itemId, + /** + * @since 32.0.0 + */ + public string $providerId, + /** + * @since 32.0.0 + */ + public string $title, + /** + * @since 32.0.0 + */ + public string $content, + /** + * @since 32.0.0 + */ + public string $documentType, + /** + * @since 32.0.0 + */ + public \DateTime $lastModified, + /** + * @since 32.0.0 + */ + public array $users, + ) { + } +} diff --git a/lib/public/ContextChat/Events/ContentProviderRegisterEvent.php b/lib/public/ContextChat/Events/ContentProviderRegisterEvent.php new file mode 100644 index 00000000000..32a542a0dbf --- /dev/null +++ b/lib/public/ContextChat/Events/ContentProviderRegisterEvent.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024-2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\ContextChat\Events; + +use OCP\ContextChat\IContentManager; +use OCP\ContextChat\IContentProvider; +use OCP\EventDispatcher\Event; + +/** + * @since 32.0.0 + */ +class ContentProviderRegisterEvent extends Event { + public function __construct( + private IContentManager $contentManager, + ) { + } + + /** + * @param string $appId + * @param string $providerId + * @param class-string<IContentProvider> $providerClass + * @return void + * @since 32.0.0 + */ + public function registerContentProvider(string $appId, string $providerId, string $providerClass): void { + $this->contentManager->registerContentProvider($appId, $providerId, $providerClass); + } +} diff --git a/lib/public/ContextChat/IContentManager.php b/lib/public/ContextChat/IContentManager.php new file mode 100644 index 00000000000..54e48809291 --- /dev/null +++ b/lib/public/ContextChat/IContentManager.php @@ -0,0 +1,115 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024-2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\ContextChat; + +/** + * @since 32.0.0 + */ +interface IContentManager { + /** + * Checks if the context chat app is enabled or not + * + * @return bool + * @since 32.0.0 + */ + public function isContextChatAvailable(): bool; + + /** + * @param string $appId + * @param string $providerId + * @param class-string<IContentProvider> $providerClass + * @return void + * @since 32.0.0 + */ + public function registerContentProvider(string $appId, string $providerId, string $providerClass): void; + + /** + * Emits an event to collect all content providers + * + * @return void + * @since 32.0.0 + */ + public function collectAllContentProviders(): void; + + /** + * Providers can use this to submit content for indexing in context chat + * + * @param string $appId + * @param ContentItem[] $items + * @return void + * @since 32.0.0 + */ + public function submitContent(string $appId, array $items): void; + + /** + * Update access for a content item for specified users. + * This modifies the access list for the content item, + * allowing or denying access to the specified users. + * If no user has access to the content item, it will be removed from the knowledge base. + * + * @param string $appId + * @param string $providerId + * @param string $itemId + * @param Type\UpdateAccessOp::* $op + * @param array $userIds + * @return void + * @since 32.0.0 + */ + public function updateAccess(string $appId, string $providerId, string $itemId, string $op, array $userIds): void; + + /** + * Update access for content items from the given provider for specified users. + * If no user has access to the content item, it will be removed from the knowledge base. + * + * @param string $appId + * @param string $providerId + * @param Type\UpdateAccessOp::* $op + * @param array $userIds + * @return void + * @since 32.0.0 + */ + public function updateAccessProvider(string $appId, string $providerId, string $op, array $userIds): void; + + /** + * Update access for a content item for specified users declaratively. + * This overwrites the access list for the content item, + * allowing only the specified users access to it. + * + * @param string $appId + * @param string $providerId + * @param string $itemId + * @param array $userIds + * @return void + * @since 32.0.0 + */ + public function updateAccessDeclarative(string $appId, string $providerId, string $itemId, array $userIds): void; + + /** + * Delete all content items and access lists for a provider. + * This does not unregister the provider itself. + * + * @param string $appId + * @param string $providerId + * @return void + * @since 32.0.0 + */ + public function deleteProvider(string $appId, string $providerId): void; + + /** + * Remove a content item from the knowledge base of context chat. + * + * @param string $appId + * @param string $providerId + * @param string[] $itemIds + * @return void + * @since 32.0.0 + */ + public function deleteContent(string $appId, string $providerId, array $itemIds): void; +} diff --git a/lib/public/ContextChat/IContentProvider.php b/lib/public/ContextChat/IContentProvider.php new file mode 100644 index 00000000000..b0c736e3c88 --- /dev/null +++ b/lib/public/ContextChat/IContentProvider.php @@ -0,0 +1,49 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024-2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\ContextChat; + +/** + * This interface defines methods to implement a content provider + * @since 32.0.0 + */ +interface IContentProvider { + /** + * The ID of the provider + * + * @return string + * @since 32.0.0 + */ + public function getId(): string; + + /** + * The ID of the app making the provider avaialble + * + * @return string + * @since 32.0.0 + */ + public function getAppId(): string; + + /** + * The absolute URL to the content item + * + * @param string $id + * @return string + * @since 32.0.0 + */ + public function getItemUrl(string $id): string; + + /** + * Starts the initial import of content items into context chat + * + * @return void + * @since 32.0.0 + */ + public function triggerInitialImport(): void; +} diff --git a/lib/public/ContextChat/Type/UpdateAccessOp.php b/lib/public/ContextChat/Type/UpdateAccessOp.php new file mode 100644 index 00000000000..65100d1ce83 --- /dev/null +++ b/lib/public/ContextChat/Type/UpdateAccessOp.php @@ -0,0 +1,18 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024-2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\ContextChat\Type; + +/** + * @since 32.0.0 + */ +class UpdateAccessOp { + public const ALLOW = 'allow'; + public const DENY = 'deny'; +} diff --git a/lib/public/DB/ISchemaWrapper.php b/lib/public/DB/ISchemaWrapper.php index 16b776c05b9..dcf22b52d3d 100644 --- a/lib/public/DB/ISchemaWrapper.php +++ b/lib/public/DB/ISchemaWrapper.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/DB/QueryBuilder/IExpressionBuilder.php b/lib/public/DB/QueryBuilder/IExpressionBuilder.php index 6df9949cb75..12e30a45071 100644 --- a/lib/public/DB/QueryBuilder/IExpressionBuilder.php +++ b/lib/public/DB/QueryBuilder/IExpressionBuilder.php @@ -51,10 +51,11 @@ interface IExpressionBuilder { * $expr->andX('u.type = ?', 'u.role = ?')); * * @param mixed ...$x Optional clause. Defaults = null, but requires - * at least one defined when converting to string. + * at least one defined when converting to string. * * @return \OCP\DB\QueryBuilder\ICompositeExpression * @since 8.2.0 + * @since 30.0.0 Calling the method without any arguments is deprecated and will throw with the next Doctrine/DBAL update * * @psalm-taint-sink sql $x */ @@ -70,10 +71,11 @@ interface IExpressionBuilder { * $qb->where($qb->expr()->orX('u.type = ?', 'u.role = ?')); * * @param mixed ...$x Optional clause. Defaults = null, but requires - * at least one defined when converting to string. + * at least one defined when converting to string. * * @return \OCP\DB\QueryBuilder\ICompositeExpression * @since 8.2.0 + * @since 30.0.0 Calling the method without any arguments is deprecated and will throw with the next Doctrine/DBAL update * * @psalm-taint-sink sql $x */ @@ -86,7 +88,7 @@ interface IExpressionBuilder { * @param string $operator One of the IExpressionBuilder::* constants. * @param mixed $y The right expression. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 @@ -111,7 +113,7 @@ interface IExpressionBuilder { * @param mixed $x The left expression. * @param mixed $y The right expression. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 @@ -134,7 +136,7 @@ interface IExpressionBuilder { * @param mixed $x The left expression. * @param mixed $y The right expression. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 @@ -157,7 +159,7 @@ interface IExpressionBuilder { * @param mixed $x The left expression. * @param mixed $y The right expression. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 @@ -180,7 +182,7 @@ interface IExpressionBuilder { * @param mixed $x The left expression. * @param mixed $y The right expression. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 @@ -203,7 +205,7 @@ interface IExpressionBuilder { * @param mixed $x The left expression. * @param mixed $y The right expression. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 @@ -226,7 +228,7 @@ interface IExpressionBuilder { * @param mixed $x The left expression. * @param mixed $y The right expression. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 @@ -267,7 +269,7 @@ interface IExpressionBuilder { * @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by LIKE() comparison. * @param mixed $y Argument to be used in LIKE() comparison. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 @@ -284,7 +286,7 @@ interface IExpressionBuilder { * @param ILiteral|IParameter|IQueryFunction|string $x Field in string format to be inspected by NOT LIKE() comparison. * @param mixed $y Argument to be used in NOT LIKE() comparison. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 @@ -301,7 +303,7 @@ interface IExpressionBuilder { * @param string $x Field in string format to be inspected by ILIKE() comparison. * @param mixed $y Argument to be used in ILIKE() comparison. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 9.0.0 @@ -318,7 +320,7 @@ interface IExpressionBuilder { * @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by IN() comparison. * @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by IN() comparison. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 @@ -335,7 +337,7 @@ interface IExpressionBuilder { * @param ILiteral|IParameter|IQueryFunction|string $x The field in string format to be inspected by NOT IN() comparison. * @param ILiteral|IParameter|IQueryFunction|string|array $y The placeholder or the array of values to be used by NOT IN() comparison. * @param mixed|null $type one of the IQueryBuilder::PARAM_* constants - * required when comparing text fields for oci compatibility + * required when comparing text fields for oci compatibility * * @return string * @since 8.2.0 - Parameter $type was added in 9.0.0 diff --git a/lib/public/DB/QueryBuilder/IFunctionBuilder.php b/lib/public/DB/QueryBuilder/IFunctionBuilder.php index 84c3780dbce..480ec1cb1ac 100644 --- a/lib/public/DB/QueryBuilder/IFunctionBuilder.php +++ b/lib/public/DB/QueryBuilder/IFunctionBuilder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/DB/QueryBuilder/IQueryBuilder.php b/lib/public/DB/QueryBuilder/IQueryBuilder.php index 94ab796adf4..4794e7e8877 100644 --- a/lib/public/DB/QueryBuilder/IQueryBuilder.php +++ b/lib/public/DB/QueryBuilder/IQueryBuilder.php @@ -10,8 +10,10 @@ namespace OCP\DB\QueryBuilder; use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Types\Types; use OCP\DB\Exception; use OCP\DB\IResult; +use OCP\IDBConnection; /** * This class provides a wrapper around Doctrine's QueryBuilder @@ -27,7 +29,7 @@ interface IQueryBuilder { /** * @since 9.0.0 */ - public const PARAM_BOOL = ParameterType::BOOLEAN; + public const PARAM_BOOL = Types::BOOLEAN; /** * @since 9.0.0 */ @@ -40,10 +42,60 @@ interface IQueryBuilder { * @since 9.0.0 */ public const PARAM_LOB = ParameterType::LARGE_OBJECT; + + /** + * @since 9.0.0 + * @deprecated 31.0.0 - use PARAM_DATETIME_MUTABLE instead + */ + public const PARAM_DATE = Types::DATETIME_MUTABLE; + + /** + * For passing a \DateTime instance when only interested in the time part (without timezone support) + * @since 31.0.0 + */ + public const PARAM_TIME_MUTABLE = Types::TIME_MUTABLE; + + /** + * For passing a \DateTime instance when only interested in the date part (without timezone support) + * @since 31.0.0 + */ + public const PARAM_DATE_MUTABLE = Types::DATE_MUTABLE; + + /** + * For passing a \DateTime instance (without timezone support) + * @since 31.0.0 + */ + public const PARAM_DATETIME_MUTABLE = Types::DATETIME_MUTABLE; + + /** + * For passing a \DateTime instance with timezone support + * @since 31.0.0 + */ + public const PARAM_DATETIME_TZ_MUTABLE = Types::DATETIMETZ_MUTABLE; + + /** + * For passing a \DateTimeImmutable instance when only interested in the time part (without timezone support) + * @since 31.0.0 + */ + public const PARAM_TIME_IMMUTABLE = Types::TIME_MUTABLE; + /** + * For passing a \DateTime instance when only interested in the date part (without timezone support) * @since 9.0.0 */ - public const PARAM_DATE = 'datetime'; + public const PARAM_DATE_IMMUTABLE = Types::DATE_IMMUTABLE; + + /** + * For passing a \DateTime instance (without timezone support) + * @since 31.0.0 + */ + public const PARAM_DATETIME_IMMUTABLE = Types::DATETIME_IMMUTABLE; + + /** + * For passing a \DateTime instance with timezone support + * @since 31.0.0 + */ + public const PARAM_DATETIME_TZ_IMMUTABLE = Types::DATETIMETZ_IMMUTABLE; /** * @since 24.0.0 @@ -69,7 +121,7 @@ interface IQueryBuilder { * Enable/disable automatic prefixing of table names with the oc_ prefix * * @param bool $enabled If set to true table names will be prefixed with the - * owncloud database prefix automatically. + * owncloud database prefix automatically. * @since 8.2.0 */ public function automaticTablePrefix($enabled); @@ -133,6 +185,8 @@ interface IQueryBuilder { * * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. * @since 8.2.0 + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. */ public function getState(); @@ -146,34 +200,37 @@ interface IQueryBuilder { * that interface changed in a breaking way the adapter \OCP\DB\QueryBuilder\IStatement is returned * to bridge old code to the new API * + * @param ?IDBConnection $connection (optional) the connection to run the query against. since 30.0 * @return IResult|int * @throws Exception since 21.0.0 * @since 8.2.0 * @deprecated 22.0.0 Use executeQuery or executeStatement */ - public function execute(); + public function execute(?IDBConnection $connection = null); /** * Execute for select statements * + * @param ?IDBConnection $connection (optional) the connection to run the query against. since 30.0 * @return IResult * @since 22.0.0 * * @throws Exception * @throws \RuntimeException in case of usage with non select query */ - public function executeQuery(): IResult; + public function executeQuery(?IDBConnection $connection = null): IResult; /** * Execute insert, update and delete statements * + * @param ?IDBConnection $connection (optional) the connection to run the query against. since 30.0 * @return int the number of affected rows * @since 22.0.0 * * @throws Exception * @throws \RuntimeException in case of usage with select query */ - public function executeStatement(): int; + public function executeStatement(?IDBConnection $connection = null): int; /** * Gets the complete SQL string formed by the current specifications of this QueryBuilder. @@ -391,8 +448,8 @@ interface IQueryBuilder { * * <code> * $qb = $conn->getQueryBuilder() - * ->delete('users', 'u') - * ->where('u.id = :user_id'); + * ->delete('users') + * ->where('id = :user_id'); * ->setParameter(':user_id', 1); * </code> * @@ -401,6 +458,7 @@ interface IQueryBuilder { * * @return $this This QueryBuilder instance. * @since 8.2.0 + * @since 30.0.0 Alias is deprecated and will no longer be used with the next Doctrine/DBAL update * * @psalm-taint-sink sql $delete */ @@ -412,9 +470,10 @@ interface IQueryBuilder { * * <code> * $qb = $conn->getQueryBuilder() - * ->update('users', 'u') - * ->set('u.password', md5('password')) - * ->where('u.id = ?'); + * ->update('users') + * ->set('email', ':email') + * ->where('id = :user_id'); + * ->setParameter(':user_id', 1); * </code> * * @param string $update The table whose rows are subject to the update. @@ -422,6 +481,7 @@ interface IQueryBuilder { * * @return $this This QueryBuilder instance. * @since 8.2.0 + * @since 30.0.0 Alias is deprecated and will no longer be used with the next Doctrine/DBAL update * * @psalm-taint-sink sql $update */ @@ -532,12 +592,13 @@ interface IQueryBuilder { * </code> * * @param string $fromAlias The alias that points to a from clause. - * @param string $join The table name to join. + * @param string|IQueryFunction $join The table name to join. * @param string $alias The alias of the join table. * @param string|ICompositeExpression|null $condition The condition for the join. * * @return $this This QueryBuilder instance. * @since 8.2.0 + * @since 30.0.0 Allow passing IQueryFunction as parameter for `$join` to allow join with a sub-query. * * @psalm-taint-sink sql $fromAlias * @psalm-taint-sink sql $join @@ -605,9 +666,10 @@ interface IQueryBuilder { * // You can optionally programmatically build and/or expressions * $qb = $conn->getQueryBuilder(); * - * $or = $qb->expr()->orx(); - * $or->add($qb->expr()->eq('u.id', 1)); - * $or->add($qb->expr()->eq('u.id', 2)); + * $or = $qb->expr()->orx( + * $qb->expr()->eq('u.id', 1), + * $qb->expr()->eq('u.id', 2), + * ); * * $qb->update('users', 'u') * ->set('u.password', md5('password')) @@ -833,6 +895,8 @@ interface IQueryBuilder { * * @return mixed * @since 8.2.0 + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please track the details you need, outside the object. */ public function getQueryPart($queryPartName); @@ -841,6 +905,8 @@ interface IQueryBuilder { * * @return array * @since 8.2.0 + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please track the details you need, outside the object. */ public function getQueryParts(); @@ -851,6 +917,8 @@ interface IQueryBuilder { * * @return $this This QueryBuilder instance. * @since 8.2.0 + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please create a new IQueryBuilder instead. */ public function resetQueryParts($queryPartNames = null); @@ -861,6 +929,8 @@ interface IQueryBuilder { * * @return $this This QueryBuilder instance. * @since 8.2.0 + * @deprecated 30.0.0 This function is going to be removed with the next Doctrine/DBAL update + * and we can not fix this in our wrapper. Please create a new IQueryBuilder instead. */ public function resetQueryPart($queryPartName); @@ -983,15 +1053,27 @@ interface IQueryBuilder { public function getLastInsertId(): int; /** - * Returns the table name quoted and with database prefix as needed by the implementation + * Returns the table name quoted and with database prefix as needed by the implementation. + * If a query function is passed the function is casted to string, + * this allows passing functions as sub-queries for join expression. * * @param string|IQueryFunction $table * @return string * @since 9.0.0 + * @since 24.0.0 accepts IQueryFunction as parameter */ public function getTableName($table); /** + * Returns the table name with database prefix as needed by the implementation + * + * @param string $table + * @return string + * @since 30.0.0 + */ + public function prefixTableName(string $table): string; + + /** * Returns the column name quoted and with table alias prefix as needed by the implementation * * @param string $column @@ -1000,4 +1082,30 @@ interface IQueryBuilder { * @since 9.0.0 */ public function getColumnName($column, $tableAlias = ''); + + /** + * Provide a hint for the shard key for queries where this can't be detected otherwise + * + * @param string $column + * @param mixed $value + * @return $this + * @since 30.0.0 + */ + public function hintShardKey(string $column, mixed $value, bool $overwrite = false): self; + + /** + * Set the query to run across all shards if sharding is enabled. + * + * @return $this + * @since 30.0.0 + */ + public function runAcrossAllShards(): self; + + /** + * Get a list of column names that are expected in the query output + * + * @return array + * @since 30.0.0 + */ + public function getOutputColumns(): array; } diff --git a/lib/public/DB/QueryBuilder/Sharded/IShardMapper.php b/lib/public/DB/QueryBuilder/Sharded/IShardMapper.php new file mode 100644 index 00000000000..fa00fb68719 --- /dev/null +++ b/lib/public/DB/QueryBuilder/Sharded/IShardMapper.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Robin Appelman <robin@icewind.nl> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\DB\QueryBuilder\Sharded; + +/** + * Implementation of logic of mapping shard keys to shards. + * @since 30.0.0 + */ +interface IShardMapper { + /** + * Get the shard number for a given shard key and total shard count + * + * @param int $key + * @param int $count + * @return int + * @since 30.0.0 + */ + public function getShardForKey(int $key, int $count): int; +} diff --git a/lib/public/DB/Types.php b/lib/public/DB/Types.php index 414d81a24c8..969ec5e6611 100644 --- a/lib/public/DB/Types.php +++ b/lib/public/DB/Types.php @@ -41,18 +41,77 @@ final class Types { public const BOOLEAN = 'boolean'; /** + * A datetime instance with only the date set. + * This will be (de)serialized into a \DateTime instance, + * it is recommended to instead use the `DATE_IMMUTABLE` instead. + * + * Warning: When deserialized the timezone will be set to UTC. * @var string * @since 21.0.0 */ public const DATE = 'date'; /** + * An immutable datetime instance with only the date set. + * This will be (de)serialized into a \DateTimeImmutable instance, + * It is recommended to use this over the `DATE` type because + * out `Entity` class works detecting changes through the setter, + * changes on mutable objects can not be detected. + * + * Warning: When deserialized the timezone will be set to UTC. + * @var string + * @since 31.0.0 + */ + public const DATE_IMMUTABLE = 'date_immutable'; + + /** + * A datetime instance with date and time support. + * This will be (de)serialized into a \DateTime instance, + * it is recommended to instead use the `DATETIME_IMMUTABLE` instead. + * + * Warning: When deserialized the timezone will be set to UTC. * @var string * @since 21.0.0 */ public const DATETIME = 'datetime'; /** + * An immutable datetime instance with date and time set. + * This will be (de)serialized into a \DateTimeImmutable instance, + * It is recommended to use this over the `DATETIME` type because + * out `Entity` class works detecting changes through the setter, + * changes on mutable objects can not be detected. + * + * Warning: When deserialized the timezone will be set to UTC. + * @var string + * @since 31.0.0 + */ + public const DATETIME_IMMUTABLE = 'datetime_immutable'; + + + /** + * A datetime instance with timezone support + * This will be (de)serialized into a \DateTime instance, + * it is recommended to instead use the `DATETIME_TZ_IMMUTABLE` instead. + * + * @var string + * @since 31.0.0 + */ + public const DATETIME_TZ = 'datetimetz'; + + /** + * An immutable timezone aware datetime instance with date and time set. + * This will be (de)serialized into a \DateTimeImmutable instance, + * It is recommended to use this over the `DATETIME_TZ` type because + * out `Entity` class works detecting changes through the setter, + * changes on mutable objects can not be detected. + * + * @var string + * @since 31.0.0 + */ + public const DATETIME_TZ_IMMUTABLE = 'datetimetz_immutable'; + + /** * @var string * @since 21.0.0 */ @@ -89,12 +148,30 @@ final class Types { public const TEXT = 'text'; /** + * A datetime instance with only the time set. + * This will be (de)serialized into a \DateTime instance, + * it is recommended to instead use the `TIME_IMMUTABLE` instead. + * + * Warning: When deserialized the timezone will be set to UTC. * @var string * @since 21.0.0 */ public const TIME = 'time'; /** + * A datetime instance with only the time set. + * This will be (de)serialized into a \DateTime instance. + * + * It is recommended to use this over the `DATETIME_TZ` type because + * out `Entity` class works detecting changes through the setter, + * changes on mutable objects can not be detected. + * + * @var string + * @since 31.0.0 + */ + public const TIME_IMMUTABLE = 'time_immutable'; + + /** * @var string * @since 24.0.0 */ diff --git a/lib/public/Dashboard/IAPIWidget.php b/lib/public/Dashboard/IAPIWidget.php index 8fec40cf1b1..c8f98290a0e 100644 --- a/lib/public/Dashboard/IAPIWidget.php +++ b/lib/public/Dashboard/IAPIWidget.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace OCP\Dashboard; +use OCP\Dashboard\Model\WidgetItem; + /** * interface IAPIWidget * @@ -16,7 +18,7 @@ namespace OCP\Dashboard; */ interface IAPIWidget extends IWidget { /** - * @return \OCP\Dashboard\Model\WidgetItem[] The widget items + * @return list<WidgetItem> The widget items * @since 22.0.0 */ public function getItems(string $userId, ?string $since = null, int $limit = 7): array; diff --git a/lib/public/Dashboard/IButtonWidget.php b/lib/public/Dashboard/IButtonWidget.php index fa8d6bc36a6..f4ebd9253c7 100644 --- a/lib/public/Dashboard/IButtonWidget.php +++ b/lib/public/Dashboard/IButtonWidget.php @@ -19,7 +19,7 @@ interface IButtonWidget extends IWidget { * Get the buttons to show on the widget * * @param string $userId - * @return WidgetButton[] + * @return list<WidgetButton> * @since 25.0.0 */ public function getWidgetButtons(string $userId): array; diff --git a/lib/public/Dashboard/IIconWidget.php b/lib/public/Dashboard/IIconWidget.php index c6cfd03e0ff..203a89279b0 100644 --- a/lib/public/Dashboard/IIconWidget.php +++ b/lib/public/Dashboard/IIconWidget.php @@ -14,7 +14,9 @@ namespace OCP\Dashboard; */ interface IIconWidget extends IWidget { /** - * Get the absolute url for the widget icon + * Get the absolute url for the widget icon (should be colored black or not have a color) + * + * The icon will be inverted automatically in mobile clients and when using dark mode * * @return string * @since 25.0.0 diff --git a/lib/public/Dashboard/IWidget.php b/lib/public/Dashboard/IWidget.php index 134758f02c6..c6bac98cae2 100644 --- a/lib/public/Dashboard/IWidget.php +++ b/lib/public/Dashboard/IWidget.php @@ -15,7 +15,12 @@ namespace OCP\Dashboard; */ interface IWidget { /** - * @return string Unique id that identifies the widget, e.g. the app id + * Get a unique identifier for the widget + * + * To ensure uniqueness, it is recommended to user the app id or start with the + * app id followed by a dash. + * + * @return string Unique id that identifies the widget, e.g. the app id. Only use alphanumeric characters, dash and underscore * @since 20.0.0 */ public function getId(): string; @@ -33,6 +38,13 @@ interface IWidget { public function getOrder(): int; /** + * CSS class that shows the widget icon (should be colored black or not have a color) + * + * The icon will be inverted automatically in mobile clients and when using dark mode. + * Therefore, it is NOT recommended to use a css class that sets the background with: + * `var(--icon-…)` as those will adapt to dark/bright mode in the web and still be inverted + * resulting in a dark icon on dark background. + * * @return string css class that displays an icon next to the widget title * @since 20.0.0 */ diff --git a/lib/public/Dashboard/Model/WidgetItem.php b/lib/public/Dashboard/Model/WidgetItem.php index be5fba82323..680daf1b114 100644 --- a/lib/public/Dashboard/Model/WidgetItem.php +++ b/lib/public/Dashboard/Model/WidgetItem.php @@ -134,8 +134,14 @@ final class WidgetItem implements JsonSerializable { /** * @since 22.0.0 - * - * @return array + * @return array{ + * subtitle: string, + * title: string, + * link: string, + * iconUrl: string, + * overlayIconUrl: string, + * sinceId: string, + * } */ public function jsonSerialize(): array { return [ diff --git a/lib/public/Defaults.php b/lib/public/Defaults.php index 9242a230024..6de22caa41e 100644 --- a/lib/public/Defaults.php +++ b/lib/public/Defaults.php @@ -29,7 +29,7 @@ class Defaults { */ public function __construct(?\OC_Defaults $defaults = null) { if ($defaults === null) { - $defaults = \OC::$server->get('ThemingDefaults'); + $defaults = \OCP\Server::get('ThemingDefaults'); } $this->defaults = $defaults; } diff --git a/lib/public/Diagnostics/IQueryLogger.php b/lib/public/Diagnostics/IQueryLogger.php index 1973168803d..07c999023da 100644 --- a/lib/public/Diagnostics/IQueryLogger.php +++ b/lib/public/Diagnostics/IQueryLogger.php @@ -31,7 +31,7 @@ interface IQueryLogger extends SQLLogger { * Mark the end of the current active query. Ending query should store \OCP\Diagnostics\IQuery to * be returned with getQueries() method. * - * @return mixed + * @return void * @since 8.0.0 */ public function stopQuery(); diff --git a/lib/public/DirectEditing/ACreateEmpty.php b/lib/public/DirectEditing/ACreateEmpty.php index f30cc2465f9..6ad499ec760 100644 --- a/lib/public/DirectEditing/ACreateEmpty.php +++ b/lib/public/DirectEditing/ACreateEmpty.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/DirectEditing/ACreateFromTemplate.php b/lib/public/DirectEditing/ACreateFromTemplate.php index 6348b5d3545..e56e9c09cbb 100644 --- a/lib/public/DirectEditing/ACreateFromTemplate.php +++ b/lib/public/DirectEditing/ACreateFromTemplate.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/DirectEditing/ATemplate.php b/lib/public/DirectEditing/ATemplate.php index cc0ed24336b..a70488d8e89 100644 --- a/lib/public/DirectEditing/ATemplate.php +++ b/lib/public/DirectEditing/ATemplate.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/DirectEditing/IToken.php b/lib/public/DirectEditing/IToken.php index 53a67883f54..64abbf939f4 100644 --- a/lib/public/DirectEditing/IToken.php +++ b/lib/public/DirectEditing/IToken.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/DirectEditing/RegisterDirectEditorEvent.php b/lib/public/DirectEditing/RegisterDirectEditorEvent.php index 72d5ea39fe8..cbf9b07185d 100644 --- a/lib/public/DirectEditing/RegisterDirectEditorEvent.php +++ b/lib/public/DirectEditing/RegisterDirectEditorEvent.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Encryption/Exceptions/InvalidHeaderException.php b/lib/public/Encryption/Exceptions/InvalidHeaderException.php new file mode 100644 index 00000000000..f7213577fb6 --- /dev/null +++ b/lib/public/Encryption/Exceptions/InvalidHeaderException.php @@ -0,0 +1,17 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCP\Encryption\Exceptions; + +use OCP\HintException; + +/** + * Class InvalidHeaderException + * + * @since 32.0.0 + */ +class InvalidHeaderException extends HintException { +} diff --git a/lib/public/Encryption/IEncryptionModule.php b/lib/public/Encryption/IEncryptionModule.php index 2c3f33847c9..0bf0ef3476b 100644 --- a/lib/public/Encryption/IEncryptionModule.php +++ b/lib/public/Encryption/IEncryptionModule.php @@ -42,8 +42,8 @@ interface IEncryptionModule { * @param array $accessList who has access to the file contains the key 'users' and 'public' * * @return array $header contain data as key-value pairs which should be - * written to the header, in case of a write operation - * or if no additional data is needed return a empty array + * written to the header, in case of a write operation + * or if no additional data is needed return a empty array * @since 8.1.0 */ public function begin($path, $user, $mode, array $header, array $accessList); @@ -125,7 +125,7 @@ interface IEncryptionModule { * e.g. if all encryption keys exists * * @param string $path - * @param string $uid user for whom we want to check if he can read the file + * @param string $uid user for whom we want to check if they can read the file * @return boolean * @since 8.1.0 */ diff --git a/lib/public/EventDispatcher/GenericEvent.php b/lib/public/EventDispatcher/GenericEvent.php index fb0a7677672..7e646c4d6a7 100644 --- a/lib/public/EventDispatcher/GenericEvent.php +++ b/lib/public/EventDispatcher/GenericEvent.php @@ -18,10 +18,12 @@ use function array_key_exists; /** * Class GenericEvent * - * convenience reimplementation of \Symfony\Component\GenericEvent against + * convenience re-implementation of \Symfony\Component\GenericEvent against * \OCP\EventDispatcher\Event * * @since 18.0.0 + * @template-implements ArrayAccess<array-key, mixed> + * @template-implements IteratorAggregate<array-key, mixed> * @deprecated 22.0.0 use \OCP\EventDispatcher\Event */ class GenericEvent extends Event implements ArrayAccess, IteratorAggregate { diff --git a/lib/public/EventDispatcher/JsonSerializer.php b/lib/public/EventDispatcher/JsonSerializer.php index 1eb75ed7527..d05367b746d 100644 --- a/lib/public/EventDispatcher/JsonSerializer.php +++ b/lib/public/EventDispatcher/JsonSerializer.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace OCP\EventDispatcher; +use OC\Files\Node\NonExistingFile; +use OC\Files\Node\NonExistingFolder; use OCP\Files\FileInfo; use OCP\IUser; @@ -24,10 +26,16 @@ final class JsonSerializer { * @since 30.0.0 */ public static function serializeFileInfo(FileInfo $node): array { - return [ - 'id' => $node->getId(), - 'path' => $node->getPath(), - ]; + if ($node instanceof NonExistingFile || $node instanceof NonExistingFolder) { + return [ + 'path' => $node->getPath(), + ]; + } else { + return [ + 'id' => $node->getId(), + 'path' => $node->getPath(), + ]; + } } /** diff --git a/lib/public/Federation/Exceptions/ActionNotSupportedException.php b/lib/public/Federation/Exceptions/ActionNotSupportedException.php index 690e7a554c7..7f0e0f46907 100644 --- a/lib/public/Federation/Exceptions/ActionNotSupportedException.php +++ b/lib/public/Federation/Exceptions/ActionNotSupportedException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/Exceptions/AuthenticationFailedException.php b/lib/public/Federation/Exceptions/AuthenticationFailedException.php index f9482c3a19c..6ce5314844e 100644 --- a/lib/public/Federation/Exceptions/AuthenticationFailedException.php +++ b/lib/public/Federation/Exceptions/AuthenticationFailedException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/Exceptions/BadRequestException.php b/lib/public/Federation/Exceptions/BadRequestException.php index 52e3b0e0db4..0210437a8d5 100644 --- a/lib/public/Federation/Exceptions/BadRequestException.php +++ b/lib/public/Federation/Exceptions/BadRequestException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/Exceptions/ProviderAlreadyExistsException.php b/lib/public/Federation/Exceptions/ProviderAlreadyExistsException.php index 606fb08d0fc..f753f5f3326 100644 --- a/lib/public/Federation/Exceptions/ProviderAlreadyExistsException.php +++ b/lib/public/Federation/Exceptions/ProviderAlreadyExistsException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/Exceptions/ProviderCouldNotAddShareException.php b/lib/public/Federation/Exceptions/ProviderCouldNotAddShareException.php index 7f1218c349e..168eb2b8aeb 100644 --- a/lib/public/Federation/Exceptions/ProviderCouldNotAddShareException.php +++ b/lib/public/Federation/Exceptions/ProviderCouldNotAddShareException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/Exceptions/ProviderDoesNotExistsException.php b/lib/public/Federation/Exceptions/ProviderDoesNotExistsException.php index 4b3d0341fe9..64dfcf0f856 100644 --- a/lib/public/Federation/Exceptions/ProviderDoesNotExistsException.php +++ b/lib/public/Federation/Exceptions/ProviderDoesNotExistsException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/ICloudFederationFactory.php b/lib/public/Federation/ICloudFederationFactory.php index a25e4ee30f7..5238188b4fa 100644 --- a/lib/public/Federation/ICloudFederationFactory.php +++ b/lib/public/Federation/ICloudFederationFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/ICloudFederationNotification.php b/lib/public/Federation/ICloudFederationNotification.php index 8545c9d51f2..c550a936927 100644 --- a/lib/public/Federation/ICloudFederationNotification.php +++ b/lib/public/Federation/ICloudFederationNotification.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/ICloudFederationProvider.php b/lib/public/Federation/ICloudFederationProvider.php index 067ceba160e..b30041f81d6 100644 --- a/lib/public/Federation/ICloudFederationProvider.php +++ b/lib/public/Federation/ICloudFederationProvider.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/ICloudFederationProviderManager.php b/lib/public/Federation/ICloudFederationProviderManager.php index 9f5258721ab..68adb4b4da7 100644 --- a/lib/public/Federation/ICloudFederationProviderManager.php +++ b/lib/public/Federation/ICloudFederationProviderManager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/ICloudFederationShare.php b/lib/public/Federation/ICloudFederationShare.php index 197ebf1af9a..0b67bbfadee 100644 --- a/lib/public/Federation/ICloudFederationShare.php +++ b/lib/public/Federation/ICloudFederationShare.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Federation/ICloudIdManager.php b/lib/public/Federation/ICloudIdManager.php index 09ef0aae0fa..29e261ab3af 100644 --- a/lib/public/Federation/ICloudIdManager.php +++ b/lib/public/Federation/ICloudIdManager.php @@ -8,11 +8,14 @@ declare(strict_types=1); */ namespace OCP\Federation; +use OCP\AppFramework\Attribute\Consumable; + /** * Interface for resolving federated cloud ids * * @since 12.0.0 */ +#[Consumable(since: '12.0.0')] interface ICloudIdManager { /** * @param string $cloudId @@ -48,9 +51,35 @@ interface ICloudIdManager { * remove scheme/protocol from an url * * @param string $url + * @param bool $httpsOnly * * @return string * @since 28.0.0 + * @since 30.0.0 - Optional parameter $httpsOnly was added + */ + public function removeProtocolFromUrl(string $url, bool $httpsOnly = false): string; + + /** + * @param string $id The remote cloud id + * @param string $user The user id on the remote server + * @param string $remote The base address of the remote server + * @param ?string $displayName The displayname of the remote user + * + * @since 32.0.0 + */ + public function createCloudId(string $id, string $user, string $remote, ?string $displayName = null): ICloudId; + + /** + * @param $resolver The cloud id resolver to register + * + * @since 32.0.0 + */ + public function registerCloudIdResolver(ICloudIdResolver $resolver): void; + + /** + * @param $resolver The cloud id resolver to unregister + * + * @since 32.0.0 */ - public function removeProtocolFromUrl(string $url): string; + public function unregisterCloudIdResolver(ICloudIdResolver $resolver): void; } diff --git a/lib/public/Federation/ICloudIdResolver.php b/lib/public/Federation/ICloudIdResolver.php new file mode 100644 index 00000000000..79f9ed11dd7 --- /dev/null +++ b/lib/public/Federation/ICloudIdResolver.php @@ -0,0 +1,40 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCP\Federation; + +use OCP\AppFramework\Attribute\Consumable; +use OCP\AppFramework\Attribute\Implementable; + +/** + * Interface for resolving federated cloud ids + * + * @since 32.0.0 + */ +#[Consumable(since: '32.0.0')] +#[Implementable(since: '32.0.0')] +interface ICloudIdResolver { + /** + * @param string $cloudId + * @return ICloudId + * @throws \InvalidArgumentException + * + * @since 32.0.0 + */ + public function resolveCloudId(string $cloudId): ICloudId; + + /** + * Check if the input is a correctly formatted cloud id + * + * @param string $cloudId + * @return bool + * + * @since 32.0.0 + */ + public function isValidCloudId(string $cloudId): bool; +} diff --git a/lib/public/Files.php b/lib/public/Files.php index d176c2f2a7b..b169032e16c 100644 --- a/lib/public/Files.php +++ b/lib/public/Files.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -9,6 +10,8 @@ namespace OCP; +use OCP\Files\IMimeTypeDetector; + /** * This class provides access to the internal filesystem abstraction layer. Use * this class exclusively if you want to access files @@ -18,24 +21,56 @@ namespace OCP; class Files { /** * Recursive deletion of folders + * + * @param string $dir path to the folder + * @param bool $deleteSelf if set to false only the content of the folder will be deleted * @return bool * @since 5.0.0 + * @since 32.0.0 added the $deleteSelf parameter * @deprecated 14.0.0 */ - public static function rmdirr($dir) { - return \OC_Helper::rmdirr($dir); + public static function rmdirr($dir, bool $deleteSelf = true) { + if (is_dir($dir)) { + $files = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), + \RecursiveIteratorIterator::CHILD_FIRST + ); + + foreach ($files as $fileInfo) { + /** @var \SplFileInfo $fileInfo */ + if ($fileInfo->isLink()) { + unlink($fileInfo->getPathname()); + } elseif ($fileInfo->isDir()) { + rmdir($fileInfo->getRealPath()); + } else { + unlink($fileInfo->getRealPath()); + } + } + if ($deleteSelf) { + rmdir($dir); + } + } elseif (file_exists($dir)) { + if ($deleteSelf) { + unlink($dir); + } + } + if (!$deleteSelf) { + return true; + } + + return !file_exists($dir); } /** * Get the mimetype form a local file * @param string $path * @return string - * does NOT work for ownClouds filesystem, use OC_FileSystem::getMimeType instead + * does NOT work for ownClouds filesystem, use OC_FileSystem::getMimeType instead * @since 5.0.0 * @deprecated 14.0.0 */ public static function getMimeType($path) { - return \OC::$server->getMimeTypeDetector()->detect($path); + return Server::get(IMimeTypeDetector::class)->detect($path); } /** @@ -51,15 +86,45 @@ class Files { /** * Copy the contents of one stream to another + * + * @template T of null|true * @param resource $source * @param resource $target - * @return int the number of bytes copied + * @param T $includeResult + * @return int|array + * @psalm-return (T is true ? array{0: int, 1: bool} : int) * @since 5.0.0 + * @since 32.0.0 added $includeResult parameter * @deprecated 14.0.0 */ - public static function streamCopy($source, $target) { - [$count, ] = \OC_Helper::streamCopy($source, $target); - return $count; + public static function streamCopy($source, $target, ?bool $includeResult = null) { + if (!$source or !$target) { + return $includeResult ? [0, false] : 0; + } + + $bufSize = 8192; + $count = 0; + $result = true; + while (!feof($source)) { + $buf = fread($source, $bufSize); + if ($buf === false) { + $result = false; + break; + } + + $bytesWritten = fwrite($target, $buf); + if ($bytesWritten !== false) { + $count += $bytesWritten; + } + + if ($bytesWritten === false + || ($bytesWritten < $bufSize && $bytesWritten < strlen($buf)) + ) { + $result = false; + break; + } + } + return $includeResult ? [$count, $result] : $count; } /** @@ -73,16 +138,4 @@ class Files { public static function buildNotExistingFileName($path, $filename) { return \OC_Helper::buildNotExistingFileName($path, $filename); } - - /** - * Gets the Storage for an app - creates the needed folder if they are not - * existent - * @param string $app - * @return \OC\Files\View - * @since 5.0.0 - * @deprecated 14.0.0 use IAppData instead - */ - public static function getStorage($app) { - return \OC_App::getStorage($app); - } } diff --git a/lib/public/Files/Cache/ICache.php b/lib/public/Files/Cache/ICache.php index 6a64fc26386..cd610b15545 100644 --- a/lib/public/Files/Cache/ICache.php +++ b/lib/public/Files/Cache/ICache.php @@ -219,7 +219,7 @@ interface ICache { * search for files by mimetype * * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image') - * where it will search for all mimetypes in the group ('image/*') + * where it will search for all mimetypes in the group ('image/*') * @return ICacheEntry[] an array of cache entries where the mimetype matches the search * @since 9.0.0 * @deprecated 9.0.0 due to lack of pagination, not all backends might implement this @@ -231,7 +231,7 @@ interface ICache { * * @param ISearchQuery $query * @return ICacheEntry[] - * @throw \InvalidArgumentException if the cache is unable to perform the query + * @throws \InvalidArgumentException if the cache is unable to perform the query * @since 12.0.0 */ public function searchQuery(ISearchQuery $query); diff --git a/lib/public/Files/Cache/ICacheEntry.php b/lib/public/Files/Cache/ICacheEntry.php index 11d91e74105..28e673071fd 100644 --- a/lib/public/Files/Cache/ICacheEntry.php +++ b/lib/public/Files/Cache/ICacheEntry.php @@ -161,4 +161,12 @@ interface ICacheEntry extends ArrayAccess { * @since 25.0.0 */ public function getUnencryptedSize(): int; + + /** + * Get the file id of the parent folder + * + * @return int + * @since 32.0.0 + */ + public function getParentId(): int; } diff --git a/lib/public/Files/Cache/IFileAccess.php b/lib/public/Files/Cache/IFileAccess.php index 51945b55a25..7a993d81e7a 100644 --- a/lib/public/Files/Cache/IFileAccess.php +++ b/lib/public/Files/Cache/IFileAccess.php @@ -79,4 +79,36 @@ interface IFileAccess { * @since 29.0.0 */ public function getByFileIdsInStorage(array $fileIds, int $storageId): array; + + /** + * Retrieves files stored in a specific storage that have a specified ancestor in the file hierarchy. + * Allows filtering by mime types, encryption status, and limits the number of results. + * + * @param int $storageId The ID of the storage to search within. + * @param int $folderId The file ID of the ancestor to base the search on. + * @param int $fileIdCursor The last processed file ID. Only files with a higher ID will be included. Defaults to 0. + * @param int $maxResults The maximum number of results to retrieve. If set to 0, all matching files will be retrieved. + * @param list<int> $mimeTypeIds An array of mime types to filter the results. If empty, no mime type filtering will be applied. + * @param bool $endToEndEncrypted Whether to include EndToEndEncrypted files + * @param bool $serverSideEncrypted Whether to include ServerSideEncrypted files + * @return \Generator<ICacheEntry> A generator yielding matching files as cache entries. + * @throws \OCP\DB\Exception + * + * @since 32.0.0 + */ + public function getByAncestorInStorage(int $storageId, int $folderId, int $fileIdCursor = 0, int $maxResults = 100, array $mimeTypeIds = [], bool $endToEndEncrypted = true, bool $serverSideEncrypted = true): \Generator; + + /** + * Retrieves a list of all distinct mounts. + * Allows filtering by specific mount providers. + * Optionally rewrites home directory root paths to avoid cache and trashbin. + * + * @param list<string> $mountProviders An array of mount provider class names to filter. If empty, all providers will be included. + * @param bool $onlyUserFilesMounts Whether to rewrite the root IDs for home directories to only include user files and to only consider mounts with mount points in the user files. + * @return \Generator<array{storage_id: int, root_id: int, overridden_root: int}> A generator yielding mount configurations as an array containing 'storage_id', 'root_id', and 'override_root'. + * @throws \OCP\DB\Exception + * + * @since 32.0.0 + */ + public function getDistinctMounts(array $mountProviders = [], bool $onlyUserFilesMounts = true): \Generator; } diff --git a/lib/public/Files/Cache/IUpdater.php b/lib/public/Files/Cache/IUpdater.php index 3c2bc69715c..2bc702114b4 100644 --- a/lib/public/Files/Cache/IUpdater.php +++ b/lib/public/Files/Cache/IUpdater.php @@ -58,4 +58,11 @@ interface IUpdater { * @since 9.0.0 */ public function renameFromStorage(IStorage $sourceStorage, $source, $target); + + /** + * Copy a file or folder in the cache and update the size, etag and mtime of the parent folders + * + * @since 31.0.0 + */ + public function copyFromStorage(IStorage $sourceStorage, string $source, string $target): void; } diff --git a/lib/public/Files/Cache/IWatcher.php b/lib/public/Files/Cache/IWatcher.php index 75ab14f85c2..62b90f672c6 100644 --- a/lib/public/Files/Cache/IWatcher.php +++ b/lib/public/Files/Cache/IWatcher.php @@ -76,4 +76,10 @@ interface IWatcher { * @since 9.0.0 */ public function cleanFolder($path); + + /** + * register a callback to be called whenever the watcher triggers and update + * @since 31.0.0 + */ + public function onUpdate(callable $callback): void; } diff --git a/lib/public/Files/Config/Event/UserMountAddedEvent.php b/lib/public/Files/Config/Event/UserMountAddedEvent.php new file mode 100644 index 00000000000..8abd7512188 --- /dev/null +++ b/lib/public/Files/Config/Event/UserMountAddedEvent.php @@ -0,0 +1,26 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Config\Event; + +use OCP\EventDispatcher\Event; +use OCP\Files\Config\ICachedMountInfo; + +/** + * Event emitted when a user mount was added. + * + * @since 32.0.0 + */ +class UserMountAddedEvent extends Event { + public function __construct( + public readonly ICachedMountInfo $mountPoint, + ) { + parent::__construct(); + } +} diff --git a/lib/public/Files/Config/Event/UserMountRemovedEvent.php b/lib/public/Files/Config/Event/UserMountRemovedEvent.php new file mode 100644 index 00000000000..0de7cfc4a99 --- /dev/null +++ b/lib/public/Files/Config/Event/UserMountRemovedEvent.php @@ -0,0 +1,26 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Config\Event; + +use OCP\EventDispatcher\Event; +use OCP\Files\Config\ICachedMountInfo; + +/** + * Event emitted when a user mount was removed. + * + * @since 32.0.0 + */ +class UserMountRemovedEvent extends Event { + public function __construct( + public readonly ICachedMountInfo $mountPoint, + ) { + parent::__construct(); + } +} diff --git a/lib/public/Files/Config/Event/UserMountUpdatedEvent.php b/lib/public/Files/Config/Event/UserMountUpdatedEvent.php new file mode 100644 index 00000000000..f797bef134e --- /dev/null +++ b/lib/public/Files/Config/Event/UserMountUpdatedEvent.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Config\Event; + +use OCP\EventDispatcher\Event; +use OCP\Files\Config\ICachedMountInfo; + +/** + * Event emitted when a user mount was moved. + * + * @since 32.0.0 + */ +class UserMountUpdatedEvent extends Event { + public function __construct( + public readonly ICachedMountInfo $oldMountPoint, + public readonly ICachedMountInfo $newMountPoint, + ) { + parent::__construct(); + } +} diff --git a/lib/public/Files/Config/ICachedMountFileInfo.php b/lib/public/Files/Config/ICachedMountFileInfo.php index 7b331059645..a9b30d8ba6d 100644 --- a/lib/public/Files/Config/ICachedMountFileInfo.php +++ b/lib/public/Files/Config/ICachedMountFileInfo.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/Conversion/ConversionMimeProvider.php b/lib/public/Files/Conversion/ConversionMimeProvider.php new file mode 100644 index 00000000000..0daf4a10648 --- /dev/null +++ b/lib/public/Files/Conversion/ConversionMimeProvider.php @@ -0,0 +1,66 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Conversion; + +use JsonSerializable; + +/** + * A tuple-like object representing both an original and target + * MIME type for a file conversion + * + * @since 31.0.0 + */ +class ConversionMimeProvider implements JsonSerializable { + /** + * @param string $from The source MIME type of a file + * @param string $to The target MIME type for the file + * @param string $extension The file extension for the target MIME type (e.g. 'png') + * @param string $displayName The human-readable name of the target MIME type (e.g. 'Image (.png)') + * + * @since 31.0.0 + */ + public function __construct( + private string $from, + private string $to, + private string $extension, + private string $displayName, + ) { + } + + public function getFrom(): string { + return $this->from; + } + + public function getTo(): string { + return $this->to; + } + + public function getExtension(): string { + return $this->extension; + } + + public function getDisplayName(): string { + return $this->displayName; + } + + /** + * @return array{from: string, to: string, extension: string, displayName: string} + * + * @since 31.0.0 + */ + public function jsonSerialize(): array { + return [ + 'from' => $this->from, + 'to' => $this->to, + 'extension' => $this->extension, + 'displayName' => $this->displayName, + ]; + } +} diff --git a/lib/public/Files/Conversion/IConversionManager.php b/lib/public/Files/Conversion/IConversionManager.php new file mode 100644 index 00000000000..ed418129d3b --- /dev/null +++ b/lib/public/Files/Conversion/IConversionManager.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Conversion; + +use OCP\Files\File; + +/** + * @since 31.0.0 + */ +interface IConversionManager { + /** + * Determines whether or not conversion providers are available + * + * @since 31.0.0 + */ + public function hasProviders(): bool; + + /** + * Gets all supported MIME type conversions + * + * @return list<ConversionMimeProvider> + * + * @since 31.0.0 + */ + public function getProviders(): array; + + /** + * Convert a file to a given MIME type + * + * @param File $file The file to be converted + * @param string $targetMimeType The MIME type to convert the file to + * @param ?string $destination The destination to save the converted file + * + * @return string Path to the converted file + * + * @since 31.0.0 + */ + public function convert(File $file, string $targetMimeType, ?string $destination = null): string; +} diff --git a/lib/public/Files/Conversion/IConversionProvider.php b/lib/public/Files/Conversion/IConversionProvider.php new file mode 100644 index 00000000000..3b5c5945c99 --- /dev/null +++ b/lib/public/Files/Conversion/IConversionProvider.php @@ -0,0 +1,41 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Conversion; + +use OCP\Files\File; + +/** + * This interface is implemented by apps that provide + * a file conversion provider + * + * @since 31.0.0 + */ +interface IConversionProvider { + /** + * Get an array of MIME type tuples this conversion provider supports + * + * @return list<ConversionMimeProvider> + * + * @since 31.0.0 + */ + public function getSupportedMimeTypes(): array; + + /** + * Convert a file to a given MIME type + * + * @param File $file The file to be converted + * @param string $targetMimeType The MIME type to convert the file to + * + * @return resource|string Resource or string content of the file + * + * @since 31.0.0 + */ + public function convertFile(File $file, string $targetMimeType): mixed; +} diff --git a/lib/public/Files/DavUtil.php b/lib/public/Files/DavUtil.php index 40d17c77c88..6dde3179bb8 100644 --- a/lib/public/Files/DavUtil.php +++ b/lib/public/Files/DavUtil.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-only diff --git a/lib/public/Files/EmptyFileNameException.php b/lib/public/Files/EmptyFileNameException.php index ec13a9fc2be..1630ce63ea2 100644 --- a/lib/public/Files/EmptyFileNameException.php +++ b/lib/public/Files/EmptyFileNameException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/Events/BeforeFileSystemSetupEvent.php b/lib/public/Files/Events/BeforeFileSystemSetupEvent.php new file mode 100644 index 00000000000..23791aa6ec1 --- /dev/null +++ b/lib/public/Files/Events/BeforeFileSystemSetupEvent.php @@ -0,0 +1,36 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OCP\Files\Events; + +use OCP\EventDispatcher\Event; +use OCP\IUser; + +/** + * Event triggered before the file system is setup + * + * @since 31.0.0 + */ +class BeforeFileSystemSetupEvent extends Event { + /** + * @since 31.0.0 + */ + public function __construct( + private IUser $user, + ) { + parent::__construct(); + } + + /** + * @since 31.0.0 + */ + public function getUser(): IUser { + return $this->user; + } +} diff --git a/lib/public/Files/Events/BeforeZipCreatedEvent.php b/lib/public/Files/Events/BeforeZipCreatedEvent.php index b55b36d3968..0363d385d36 100644 --- a/lib/public/Files/Events/BeforeZipCreatedEvent.php +++ b/lib/public/Files/Events/BeforeZipCreatedEvent.php @@ -10,23 +10,45 @@ declare(strict_types=1); namespace OCP\Files\Events; use OCP\EventDispatcher\Event; +use OCP\Files\Folder; /** + * This event is triggered before a archive is created when a user requested + * downloading a folder or multiple files. + * + * By setting `successful` to false the tar creation can be aborted and the download denied. + * * @since 25.0.0 */ class BeforeZipCreatedEvent extends Event { private string $directory; - private array $files; private bool $successful = true; private ?string $errorMessage = null; + private ?Folder $folder = null; /** + * @param list<string> $files * @since 25.0.0 + * @since 31.0.0 support `OCP\Files\Folder` as `$directory` parameter - passing a string is deprecated now */ - public function __construct(string $directory, array $files) { + public function __construct( + string|Folder $directory, + private array $files, + ) { parent::__construct(); - $this->directory = $directory; - $this->files = $files; + if ($directory instanceof Folder) { + $this->directory = $directory->getPath(); + $this->folder = $directory; + } else { + $this->directory = $directory; + } + } + + /** + * @since 31.0.0 + */ + public function getFolder(): ?Folder { + return $this->folder; } /** diff --git a/lib/public/Files/Events/Node/AbstractNodeEvent.php b/lib/public/Files/Events/Node/AbstractNodeEvent.php index 64b0e3a3aa5..7afb71277f6 100644 --- a/lib/public/Files/Events/Node/AbstractNodeEvent.php +++ b/lib/public/Files/Events/Node/AbstractNodeEvent.php @@ -21,7 +21,7 @@ abstract class AbstractNodeEvent extends Event implements IWebhookCompatibleEven * @since 20.0.0 */ public function __construct( - private Node $node + private Node $node, ) { } diff --git a/lib/public/Files/Events/Node/AbstractNodesEvent.php b/lib/public/Files/Events/Node/AbstractNodesEvent.php index 7941a9e596a..8fa8795d1df 100644 --- a/lib/public/Files/Events/Node/AbstractNodesEvent.php +++ b/lib/public/Files/Events/Node/AbstractNodesEvent.php @@ -22,7 +22,7 @@ abstract class AbstractNodesEvent extends Event implements IWebhookCompatibleEve */ public function __construct( private Node $source, - private Node $target + private Node $target, ) { } diff --git a/lib/public/Files/FileInfo.php b/lib/public/Files/FileInfo.php index 468013ac271..f9957f580e8 100644 --- a/lib/public/Files/FileInfo.php +++ b/lib/public/Files/FileInfo.php @@ -133,7 +133,9 @@ interface FileInfo { public function getId(); /** - * Check whether the file is encrypted + * Check whether the node is encrypted. + * If it is a file, then it is server side encrypted. + * If it is a folder, then it is end-to-end encrypted. * * @return bool * @since 7.0.0 diff --git a/lib/public/Files/Folder.php b/lib/public/Files/Folder.php index 3f05ff49890..a35d2d78bc9 100644 --- a/lib/public/Files/Folder.php +++ b/lib/public/Files/Folder.php @@ -60,6 +60,7 @@ interface Folder extends Node { * @param string $path relative path of the file or folder * @return \OCP\Files\Node * @throws \OCP\Files\NotFoundException + * @throws \OCP\Files\NotPermittedException * @since 6.0.0 */ public function get($path); @@ -198,4 +199,15 @@ interface Folder extends Node { * @since 9.1.0 */ public function getRecent($limit, $offset = 0); + + /** + * Verify if the given path is valid and allowed from this folder. + * + * @param string $path the path from this folder + * @param string $fileName + * @param bool $readonly Check only if the path is allowed for read-only access + * @throws InvalidPathException + * @since 32.0.0 + */ + public function verifyPath($fileName, $readonly = false): void; } diff --git a/lib/public/Files/ForbiddenException.php b/lib/public/Files/ForbiddenException.php index 338757e79eb..514ef8623d3 100644 --- a/lib/public/Files/ForbiddenException.php +++ b/lib/public/Files/ForbiddenException.php @@ -35,6 +35,6 @@ class ForbiddenException extends \Exception { * @since 9.0.0 */ public function getRetry() { - return (bool) $this->retry; + return (bool)$this->retry; } } diff --git a/lib/public/Files/GenericFileException.php b/lib/public/Files/GenericFileException.php index 288d668e3e7..66a3b5e5ac4 100644 --- a/lib/public/Files/GenericFileException.php +++ b/lib/public/Files/GenericFileException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/IAppData.php b/lib/public/Files/IAppData.php index e5a5c2b7143..4d0c4da6a8a 100644 --- a/lib/public/Files/IAppData.php +++ b/lib/public/Files/IAppData.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/IFilenameValidator.php b/lib/public/Files/IFilenameValidator.php new file mode 100644 index 00000000000..9b7fa1e2e2e --- /dev/null +++ b/lib/public/Files/IFilenameValidator.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Files; + +/** + * @since 30.0.0 + */ +interface IFilenameValidator { + + /** + * It is recommended to use `\OCP\Files\Storage\IStorage::isFileValid` instead as this + * only checks if the filename is valid in general but not for a specific storage + * which might have additional naming rules. + * + * @param string $filename The filename to check for validity + * @return bool + * @since 30.0.0 + */ + public function isFilenameValid(string $filename): bool; + + /** + * It is recommended to use `\OCP\Files\Storage\IStorage::isFileValid` instead as this + * only checks if the filename is valid in general but not for a specific storage + * which might have additional naming rules. + * + * This will validate a filename and throw an exception with details on error. + * + * @param string $filename The filename to check for validity + * @throws \OCP\Files\InvalidPathException or one of its child classes in case of an error + * @since 30.0.0 + */ + public function validateFilename(string $filename): void; + + /** + * Sanitize a give filename to comply with admin setup naming constrains. + * + * If no sanitizing is needed the same name is returned. + * + * @param string $name The filename to sanitize + * @param null|string $charReplacement Character to use for replacing forbidden ones - by default underscore, dash or space is used if allowed. + * @throws \InvalidArgumentException if no character replacement was given (and the default could not be applied) or the replacement is not valid. + * @since 32.0.0 + */ + public function sanitizeFilename(string $name, ?string $charReplacement = null): string; + +} diff --git a/lib/public/Files/IMimeTypeDetector.php b/lib/public/Files/IMimeTypeDetector.php index 1c683cdd4b9..1e87cf932ce 100644 --- a/lib/public/Files/IMimeTypeDetector.php +++ b/lib/public/Files/IMimeTypeDetector.php @@ -14,11 +14,11 @@ namespace OCP\Files; * Interface IMimeTypeDetector * @since 8.2.0 * - * Interface to handle mimetypes (detection and icon retrieval) + * Interface to handle MIME type (detection and icon retrieval) **/ interface IMimeTypeDetector { /** - * detect mimetype only based on filename, content of file is not used + * Detect MIME type only based on filename, content of file is not used * @param string $path * @return string * @since 8.2.0 @@ -26,7 +26,7 @@ interface IMimeTypeDetector { public function detectPath($path); /** - * detect mimetype only based on the content of file + * Detect MIME type only based on the content of file * @param string $path * @return string * @since 18.0.0 @@ -34,7 +34,7 @@ interface IMimeTypeDetector { public function detectContent(string $path): string; /** - * detect mimetype based on both filename and content + * Detect MIME type based on both filename and content * * @param string $path * @return string @@ -43,7 +43,7 @@ interface IMimeTypeDetector { public function detect($path); /** - * Get a secure mimetype that won't expose potential XSS. + * Get a secure MIME type that won't expose potential XSS. * * @param string $mimeType * @return string @@ -52,7 +52,7 @@ interface IMimeTypeDetector { public function getSecureMimeType($mimeType); /** - * detect mimetype based on the content of a string + * Detect MIME type based on the content of a string * * @param string $data * @return string @@ -73,4 +73,26 @@ interface IMimeTypeDetector { * @since 28.0.0 */ public function getAllAliases(): array; + + /** + * Get all extension to MIME type mappings. + * + * The return format is an array of the file extension, as the key, + * mapped to a list where the first entry is the MIME type + * and the second entry is the secure MIME type (or null if none). + * Due to PHP idiosyncrasies if a numeric string is set as the extension, + * then also the array key (file extension) is a number instead of a string. + * + * @return array<list{string, string|null}> + * @since 32.0.0 + */ + public function getAllMappings(): array; + + /** + * Get all human readable mime names + * + * @return array<string,string> + * @since 32.0.0 + */ + public function getAllNamings(): array; } diff --git a/lib/public/Files/IMimeTypeLoader.php b/lib/public/Files/IMimeTypeLoader.php index 44261527d53..77c59fb2c0a 100644 --- a/lib/public/Files/IMimeTypeLoader.php +++ b/lib/public/Files/IMimeTypeLoader.php @@ -47,4 +47,14 @@ interface IMimeTypeLoader { * @since 8.2.0 */ public function reset(): void; + + /** + * Update filecache mimetype based on file extension + * + * @param string $ext + * @param int $mimeTypeId + * @return int + * @since 32.0.0 + */ + public function updateFilecache(string $ext, int $mimeTypeId): int; } diff --git a/lib/public/Files/InvalidDirectoryException.php b/lib/public/Files/InvalidDirectoryException.php index 7f87eed1a17..b9640209cbf 100644 --- a/lib/public/Files/InvalidDirectoryException.php +++ b/lib/public/Files/InvalidDirectoryException.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/Lock/LockContext.php b/lib/public/Files/Lock/LockContext.php index 48d9f804fc4..5e61d3e2ec5 100644 --- a/lib/public/Files/Lock/LockContext.php +++ b/lib/public/Files/Lock/LockContext.php @@ -33,7 +33,7 @@ final class LockContext { public function __construct( Node $node, int $type, - string $owner + string $owner, ) { $this->node = $node; $this->type = $type; diff --git a/lib/public/Files/LockNotAcquiredException.php b/lib/public/Files/LockNotAcquiredException.php index 19e0bbc2543..93d861c248f 100644 --- a/lib/public/Files/LockNotAcquiredException.php +++ b/lib/public/Files/LockNotAcquiredException.php @@ -35,6 +35,6 @@ class LockNotAcquiredException extends \Exception { * @since 7.0.0 */ public function __toString(): string { - return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; + return self::class . ": [{$this->code}]: {$this->message}\n"; } } diff --git a/lib/public/Files/Mount/IShareOwnerlessMount.php b/lib/public/Files/Mount/IShareOwnerlessMount.php new file mode 100644 index 00000000000..b73ee620859 --- /dev/null +++ b/lib/public/Files/Mount/IShareOwnerlessMount.php @@ -0,0 +1,18 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Mount; + +/** + * Denotes that shares created under this mountpoint will be manageable by everyone with share permission. + * + * @since 31.0.0 + */ +interface IShareOwnerlessMount { +} diff --git a/lib/public/Files/Node.php b/lib/public/Files/Node.php index e3641a82df3..edef0a6157f 100644 --- a/lib/public/Files/Node.php +++ b/lib/public/Files/Node.php @@ -243,7 +243,7 @@ interface Node extends FileInfo { * Check the type of an existing lock. * * A shared lock can be changed to an exclusive lock is there is exactly one shared lock on the file, - * an exclusive lock can always be changed to a shared lock since there can only be one exclusive lock int he first place. + * an exclusive lock can always be changed to a shared lock since there can only be one exclusive lock in the first place. * * A locked exception will be thrown when these preconditions are not met. * Note that this is also the case if no existing lock exists for the file. diff --git a/lib/public/Files/Notify/IChange.php b/lib/public/Files/Notify/IChange.php index 8f252411a5a..c7c758eec11 100644 --- a/lib/public/Files/Notify/IChange.php +++ b/lib/public/Files/Notify/IChange.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/Notify/INotifyHandler.php b/lib/public/Files/Notify/INotifyHandler.php index 8777779ca4a..09b3dbca919 100644 --- a/lib/public/Files/Notify/INotifyHandler.php +++ b/lib/public/Files/Notify/INotifyHandler.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/Notify/IRenameChange.php b/lib/public/Files/Notify/IRenameChange.php index 3e1ae7ed447..b1bfae5fc00 100644 --- a/lib/public/Files/Notify/IRenameChange.php +++ b/lib/public/Files/Notify/IRenameChange.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/ObjectStore/IObjectStoreMetaData.php b/lib/public/Files/ObjectStore/IObjectStoreMetaData.php new file mode 100644 index 00000000000..9683873be36 --- /dev/null +++ b/lib/public/Files/ObjectStore/IObjectStoreMetaData.php @@ -0,0 +1,47 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCP\Files\ObjectStore; + +/** + * Interface IObjectStoreMetaData + * + * @psalm-type ObjectMetaData = array{mtime?: \DateTime, etag?: string, size?: int, mimetype?: string, filename?: string, original-path?: string, original-storage?: string} + * + * @since 32.0.0 + */ +interface IObjectStoreMetaData { + /** + * Get metadata for an object. + * + * @param string $urn + * @return ObjectMetaData + * + * @since 32.0.0 + */ + public function getObjectMetaData(string $urn): array; + + /** + * List all objects in the object store. + * + * If the object store implementation can do it efficiently, the metadata for each object is also included. + * + * @param string $prefix + * @return \Iterator<array{urn: string, metadata: ?ObjectMetaData}> + * + * @since 32.0.0 + */ + public function listObjects(string $prefix = ''): \Iterator; + + /** + * @param string $urn the unified resource name used to identify the object + * @param resource $stream stream with the data to write + * @param ObjectMetaData $metaData the metadata to set for the object + * @throws \Exception when something goes wrong, message will be logged + * @since 32.0.0 + */ + public function writeObjectWithMetaData(string $urn, $stream, array $metaData): void; +} diff --git a/lib/public/Files/Search/ISearchBinaryOperator.php b/lib/public/Files/Search/ISearchBinaryOperator.php index 661be44596d..fa7ef4d1bb3 100644 --- a/lib/public/Files/Search/ISearchBinaryOperator.php +++ b/lib/public/Files/Search/ISearchBinaryOperator.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/Search/ISearchComparison.php b/lib/public/Files/Search/ISearchComparison.php index 01b69f5d24c..ab298fa0a57 100644 --- a/lib/public/Files/Search/ISearchComparison.php +++ b/lib/public/Files/Search/ISearchComparison.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -51,7 +52,7 @@ interface ISearchComparison extends ISearchOperator { * @since 28.0.0 */ public const COMPARE_DEFINED = 'is-defined'; - + /** * @since 29.0.0 */ diff --git a/lib/public/Files/Search/ISearchOperator.php b/lib/public/Files/Search/ISearchOperator.php index a604bd96b9d..f6ae8edcbb1 100644 --- a/lib/public/Files/Search/ISearchOperator.php +++ b/lib/public/Files/Search/ISearchOperator.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/Search/ISearchOrder.php b/lib/public/Files/Search/ISearchOrder.php index 23f71e2133e..e6e68849443 100644 --- a/lib/public/Files/Search/ISearchOrder.php +++ b/lib/public/Files/Search/ISearchOrder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/Search/ISearchQuery.php b/lib/public/Files/Search/ISearchQuery.php index 109998aee65..1b400c56e5b 100644 --- a/lib/public/Files/Search/ISearchQuery.php +++ b/lib/public/Files/Search/ISearchQuery.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/SimpleFS/ISimpleFile.php b/lib/public/Files/SimpleFS/ISimpleFile.php index 2682c22580d..4e77299ab00 100644 --- a/lib/public/Files/SimpleFS/ISimpleFile.php +++ b/lib/public/Files/SimpleFS/ISimpleFile.php @@ -1,12 +1,15 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCP\Files\SimpleFS; +use OCP\Files\GenericFileException; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\Lock\LockedException; /** * This interface allows to manage simple files. @@ -49,8 +52,10 @@ interface ISimpleFile { /** * Get the content * - * @throws NotPermittedException + * @throws GenericFileException + * @throws LockedException * @throws NotFoundException + * @throws NotPermittedException * @since 11.0.0 */ public function getContent(): string; @@ -59,8 +64,10 @@ interface ISimpleFile { * Overwrite the file * * @param string|resource $data - * @throws NotPermittedException + * @throws GenericFileException + * @throws LockedException * @throws NotFoundException + * @throws NotPermittedException * @since 11.0.0 */ public function putContent($data): void; diff --git a/lib/public/Files/SimpleFS/ISimpleFolder.php b/lib/public/Files/SimpleFS/ISimpleFolder.php index 79b9fca1dac..95efc676688 100644 --- a/lib/public/Files/SimpleFS/ISimpleFolder.php +++ b/lib/public/Files/SimpleFS/ISimpleFolder.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/SimpleFS/ISimpleRoot.php b/lib/public/Files/SimpleFS/ISimpleRoot.php index 5c01c6a2a2e..6be8a1d47c9 100644 --- a/lib/public/Files/SimpleFS/ISimpleRoot.php +++ b/lib/public/Files/SimpleFS/ISimpleRoot.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/Storage.php b/lib/public/Files/Storage.php deleted file mode 100644 index 049841075ca..00000000000 --- a/lib/public/Files/Storage.php +++ /dev/null @@ -1,445 +0,0 @@ -<?php - -/** - * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2016 ownCloud, Inc. - * SPDX-License-Identifier: AGPL-3.0-only - */ -// use OCP namespace for all classes that are considered public. -// This means that they should be used by apps instead of the internal Nextcloud classes - -namespace OCP\Files; - -use OCP\Files\Storage\IStorage; -use OCP\Lock\ILockingProvider; - -/** - * Provide a common interface to all different storage options - * - * All paths passed to the storage are relative to the storage and should NOT have a leading slash. - * - * @since 6.0.0 - * @deprecated 9.0.0 use \OCP\Files\Storage\IStorage instead - */ -interface Storage extends IStorage { - /** - * $parameters is a free form array with the configuration options needed to construct the storage - * - * @param array $parameters - * @since 6.0.0 - */ - public function __construct($parameters); - - /** - * Get the identifier for the storage, - * the returned id should be the same for every storage object that is created with the same parameters - * and two storage objects with the same id should refer to two storages that display the same files. - * - * @return string - * @since 6.0.0 - */ - public function getId(); - - /** - * see https://www.php.net/manual/en/function.mkdir.php - * implementations need to implement a recursive mkdir - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function mkdir($path); - - /** - * see https://www.php.net/manual/en/function.rmdir.php - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function rmdir($path); - - /** - * see https://www.php.net/manual/en/function.opendir.php - * - * @param string $path - * @return resource|false - * @since 6.0.0 - */ - public function opendir($path); - - /** - * see https://www.php.net/manual/en/function.is-dir.php - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function is_dir($path); - - /** - * see https://www.php.net/manual/en/function.is-file.php - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function is_file($path); - - /** - * see https://www.php.net/manual/en/function.stat.php - * only the following keys are required in the result: size and mtime - * - * @param string $path - * @return array|bool - * @since 6.0.0 - */ - public function stat($path); - - /** - * see https://www.php.net/manual/en/function.filetype.php - * - * @param string $path - * @return string|bool - * @since 6.0.0 - */ - public function filetype($path); - - /** - * see https://www.php.net/manual/en/function.filesize.php - * The result for filesize when called on a folder is required to be 0 - * - * @param string $path - * @return false|int|float - * @since 6.0.0 - */ - public function filesize($path); - - /** - * check if a file can be created in $path - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function isCreatable($path); - - /** - * check if a file can be read - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function isReadable($path); - - /** - * check if a file can be written to - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function isUpdatable($path); - - /** - * check if a file can be deleted - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function isDeletable($path); - - /** - * check if a file can be shared - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function isSharable($path); - - /** - * get the full permissions of a path. - * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php - * - * @param string $path - * @return int - * @since 6.0.0 - */ - public function getPermissions($path); - - /** - * see https://www.php.net/manual/en/function.file_exists.php - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function file_exists($path); - - /** - * see https://www.php.net/manual/en/function.filemtime.php - * - * @param string $path - * @return int|bool - * @since 6.0.0 - */ - public function filemtime($path); - - /** - * see https://www.php.net/manual/en/function.file_get_contents.php - * - * @param string $path - * @return string|false - * @since 6.0.0 - */ - public function file_get_contents($path); - - /** - * see https://www.php.net/manual/en/function.file_put_contents.php - * - * @param string $path - * @param mixed $data - * @return int|float|false - * @since 6.0.0 - */ - public function file_put_contents($path, $data); - - /** - * see https://www.php.net/manual/en/function.unlink.php - * - * @param string $path - * @return bool - * @since 6.0.0 - */ - public function unlink($path); - - /** - * see https://www.php.net/manual/en/function.rename.php - * - * @param string $source - * @param string $target - * @return bool - * @since 6.0.0 - */ - public function rename($source, $target); - - /** - * see https://www.php.net/manual/en/function.copy.php - * - * @param string $source - * @param string $target - * @return bool - * @since 6.0.0 - */ - public function copy($source, $target); - - /** - * see https://www.php.net/manual/en/function.fopen.php - * - * @param string $path - * @param string $mode - * @return resource|bool - * @since 6.0.0 - */ - public function fopen($path, $mode); - - /** - * get the mimetype for a file or folder - * The mimetype for a folder is required to be "httpd/unix-directory" - * - * @param string $path - * @return string|bool - * @since 6.0.0 - */ - public function getMimeType($path); - - /** - * see https://www.php.net/manual/en/function.hash-file.php - * - * @param string $type - * @param string $path - * @param bool $raw - * @return string|bool - * @since 6.0.0 - */ - public function hash($type, $path, $raw = false); - - /** - * see https://www.php.net/manual/en/function.disk-free-space.php - * - * @param string $path - * @return int|float|bool - * @since 6.0.0 - */ - public function free_space($path); - - /** - * search for occurrences of $query in file names - * - * @param string $query - * @return array|bool - * @since 6.0.0 - */ - public function search($query); - - /** - * see https://www.php.net/manual/en/function.touch.php - * If the backend does not support the operation, false should be returned - * - * @param string $path - * @param int $mtime - * @return bool - * @since 6.0.0 - */ - public function touch($path, $mtime = null); - - /** - * get the path to a local version of the file. - * The local version of the file can be temporary and doesn't have to be persistent across requests - * - * @param string $path - * @return string|false - * @since 6.0.0 - */ - public function getLocalFile($path); - - /** - * check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @return bool - * @since 6.0.0 - * - * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. - * returning true for other changes in the folder is optional - */ - public function hasUpdated($path, $time); - - /** - * get the ETag for a file or folder - * - * @param string $path - * @return string|false - * @since 6.0.0 - */ - public function getETag($path); - - /** - * Returns whether the storage is local, which means that files - * are stored on the local filesystem instead of remotely. - * Calling getLocalFile() for local storages should always - * return the local files, whereas for non-local storages - * it might return a temporary file. - * - * @return bool true if the files are stored locally, false otherwise - * @since 7.0.0 - */ - public function isLocal(); - - /** - * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class - * - * @template T of IStorage - * @param string $class - * @psalm-param class-string<T> $class - * @return bool - * @since 7.0.0 - * @psalm-assert-if-true T $this - */ - public function instanceOfStorage($class); - - /** - * A custom storage implementation can return an url for direct download of a give file. - * - * For now the returned array can hold the parameter url - in future more attributes might follow. - * - * @param string $path - * @return array|bool - * @since 8.0.0 - */ - public function getDirectDownload($path); - - /** - * @param string $path the path of the target folder - * @param string $fileName the name of the file itself - * @return void - * @throws InvalidPathException - * @since 8.1.0 - */ - public function verifyPath($path, $fileName); - - /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - * @since 8.1.0 - */ - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath); - - /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath - * @return bool - * @since 8.1.0 - */ - public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath); - - /** - * @param string $path The path of the file to acquire the lock for - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - * @since 8.1.0 - */ - public function acquireLock($path, $type, ILockingProvider $provider); - - /** - * @param string $path The path of the file to acquire the lock for - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - * @since 8.1.0 - */ - public function releaseLock($path, $type, ILockingProvider $provider); - - /** - * @param string $path The path of the file to change the lock for - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - * @since 8.1.0 - */ - public function changeLock($path, $type, ILockingProvider $provider); - - /** - * Test a storage for availability - * - * @since 8.2.0 - * @return bool - */ - public function test(); - - /** - * @since 8.2.0 - * @return array [ available, last_checked ] - */ - public function getAvailability(); - - /** - * @since 8.2.0 - * @param bool $isAvailable - */ - public function setAvailability($isAvailable); - - /** - * @since 12.0.0 - * @return mixed - */ - public function needsPartFile(); -} diff --git a/lib/public/Files/Storage/IChunkedFileWrite.php b/lib/public/Files/Storage/IChunkedFileWrite.php index 1095ee7cbfc..0cf27814f0e 100644 --- a/lib/public/Files/Storage/IChunkedFileWrite.php +++ b/lib/public/Files/Storage/IChunkedFileWrite.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -24,28 +25,19 @@ interface IChunkedFileWrite extends IStorage { public function startChunkedWrite(string $targetPath): string; /** - * @param string $targetPath - * @param string $writeToken - * @param string $chunkId * @param resource $data - * @param int|null $size * @throws GenericFileException * @since 26.0.0 */ public function putChunkedWritePart(string $targetPath, string $writeToken, string $chunkId, $data, ?int $size = null): ?array; /** - * @param string $targetPath - * @param string $writeToken - * @return int * @throws GenericFileException * @since 26.0.0 */ public function completeChunkedWrite(string $targetPath, string $writeToken): int; /** - * @param string $targetPath - * @param string $writeToken * @throws GenericFileException * @since 26.0.0 */ diff --git a/lib/public/Files/Storage/IConstructableStorage.php b/lib/public/Files/Storage/IConstructableStorage.php new file mode 100644 index 00000000000..57749fa30fa --- /dev/null +++ b/lib/public/Files/Storage/IConstructableStorage.php @@ -0,0 +1,26 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ +// use OCP namespace for all classes that are considered public. +// This means that they should be used by apps instead of the internal Nextcloud classes + +namespace OCP\Files\Storage; + +/** + * Marks a storage as constructable. Allows to pass the storage as a string to a mounpoint and let it build the instance. + * + * @since 31.0.0 + */ +interface IConstructableStorage { + /** + * @param array $parameters is a free form array with the configuration options needed to construct the storage + * + * @since 31.0.0 + */ + public function __construct(array $parameters); +} diff --git a/lib/public/Files/Storage/IDisableEncryptionStorage.php b/lib/public/Files/Storage/IDisableEncryptionStorage.php index 98a4b4897da..19951da2015 100644 --- a/lib/public/Files/Storage/IDisableEncryptionStorage.php +++ b/lib/public/Files/Storage/IDisableEncryptionStorage.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Files/Storage/ILockingStorage.php b/lib/public/Files/Storage/ILockingStorage.php index abec7d91b83..ceedf33ceab 100644 --- a/lib/public/Files/Storage/ILockingStorage.php +++ b/lib/public/Files/Storage/ILockingStorage.php @@ -21,27 +21,24 @@ interface ILockingStorage { /** * @param string $path The path of the file to acquire the lock for * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider * @throws \OCP\Lock\LockedException * @since 9.0.0 */ - public function acquireLock($path, $type, ILockingProvider $provider); + public function acquireLock(string $path, int $type, ILockingProvider $provider); /** * @param string $path The path of the file to acquire the lock for * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider * @throws \OCP\Lock\LockedException * @since 9.0.0 */ - public function releaseLock($path, $type, ILockingProvider $provider); + public function releaseLock(string $path, int $type, ILockingProvider $provider); /** * @param string $path The path of the file to change the lock for * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider * @throws \OCP\Lock\LockedException * @since 9.0.0 */ - public function changeLock($path, $type, ILockingProvider $provider); + public function changeLock(string $path, int $type, ILockingProvider $provider); } diff --git a/lib/public/Files/Storage/INotifyStorage.php b/lib/public/Files/Storage/INotifyStorage.php index 8656f709116..063ff815581 100644 --- a/lib/public/Files/Storage/INotifyStorage.php +++ b/lib/public/Files/Storage/INotifyStorage.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -36,10 +37,9 @@ interface INotifyStorage { /** * Start the notification handler for this storage * - * @param $path * @return INotifyHandler * * @since 12.0.0 */ - public function notify($path); + public function notify(string $path); } diff --git a/lib/public/Files/Storage/ISharedStorage.php b/lib/public/Files/Storage/ISharedStorage.php new file mode 100644 index 00000000000..69fc60750c5 --- /dev/null +++ b/lib/public/Files/Storage/ISharedStorage.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Files\Storage; + +use OCP\Share\IShare; + +/** + * Interface for a storage that is based on a file share + * + * @since 30.0.0 + */ +interface ISharedStorage extends IStorage { + /** + * The the associated share + * + * @since 30.0.0 + */ + public function getShare(): IShare; +} diff --git a/lib/public/Files/Storage/IStorage.php b/lib/public/Files/Storage/IStorage.php index e18d6768346..5f6c8a0e8a0 100644 --- a/lib/public/Files/Storage/IStorage.php +++ b/lib/public/Files/Storage/IStorage.php @@ -23,17 +23,10 @@ use OCP\Files\InvalidPathException; * All paths passed to the storage are relative to the storage and should NOT have a leading slash. * * @since 9.0.0 + * @since 31.0.0 Moved the constructor to IConstructableStorage so that wrappers can use DI */ interface IStorage { /** - * $parameters is a free form array with the configuration options needed to construct the storage - * - * @param array $parameters - * @since 9.0.0 - */ - public function __construct($parameters); - - /** * Get the identifier for the storage, * the returned id should be the same for every storage object that is created with the same parameters * and two storage objects with the same id should refer to two storages that display the same files. @@ -47,280 +40,243 @@ interface IStorage { * see https://www.php.net/manual/en/function.mkdir.php * implementations need to implement a recursive mkdir * - * @param string $path * @return bool * @since 9.0.0 */ - public function mkdir($path); + public function mkdir(string $path); /** * see https://www.php.net/manual/en/function.rmdir.php * - * @param string $path * @return bool * @since 9.0.0 */ - public function rmdir($path); + public function rmdir(string $path); /** * see https://www.php.net/manual/en/function.opendir.php * - * @param string $path * @return resource|false * @since 9.0.0 */ - public function opendir($path); + public function opendir(string $path); /** * see https://www.php.net/manual/en/function.is-dir.php * - * @param string $path * @return bool * @since 9.0.0 */ - public function is_dir($path); + public function is_dir(string $path); /** * see https://www.php.net/manual/en/function.is-file.php * - * @param string $path * @return bool * @since 9.0.0 */ - public function is_file($path); + public function is_file(string $path); /** * see https://www.php.net/manual/en/function.stat.php * only the following keys are required in the result: size and mtime * - * @param string $path - * @return array|bool + * @return array|false * @since 9.0.0 */ - public function stat($path); + public function stat(string $path); /** * see https://www.php.net/manual/en/function.filetype.php * - * @param string $path - * @return string|bool + * @return string|false * @since 9.0.0 */ - public function filetype($path); + public function filetype(string $path); /** * see https://www.php.net/manual/en/function.filesize.php * The result for filesize when called on a folder is required to be 0 * - * @param string $path - * @return false|int|float + * @return int|float|false * @since 9.0.0 */ - public function filesize($path); + public function filesize(string $path); /** * check if a file can be created in $path * - * @param string $path * @return bool * @since 9.0.0 */ - public function isCreatable($path); + public function isCreatable(string $path); /** * check if a file can be read * - * @param string $path * @return bool * @since 9.0.0 */ - public function isReadable($path); + public function isReadable(string $path); /** * check if a file can be written to * - * @param string $path * @return bool * @since 9.0.0 */ - public function isUpdatable($path); + public function isUpdatable(string $path); /** * check if a file can be deleted * - * @param string $path * @return bool * @since 9.0.0 */ - public function isDeletable($path); + public function isDeletable(string $path); /** * check if a file can be shared * - * @param string $path * @return bool * @since 9.0.0 */ - public function isSharable($path); + public function isSharable(string $path); /** * get the full permissions of a path. * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php * - * @param string $path * @return int * @since 9.0.0 */ - public function getPermissions($path); + public function getPermissions(string $path); /** - * see https://www.php.net/manual/en/function.file_exists.php + * see https://www.php.net/manual/en/function.file-exists.php * - * @param string $path * @return bool * @since 9.0.0 */ - public function file_exists($path); + public function file_exists(string $path); /** * see https://www.php.net/manual/en/function.filemtime.php * - * @param string $path - * @return int|bool + * @return int|false * @since 9.0.0 */ - public function filemtime($path); + public function filemtime(string $path); /** - * see https://www.php.net/manual/en/function.file_get_contents.php + * see https://www.php.net/manual/en/function.file-get-contents.php * - * @param string $path * @return string|false * @since 9.0.0 */ - public function file_get_contents($path); + public function file_get_contents(string $path); /** - * see https://www.php.net/manual/en/function.file_put_contents.php + * see https://www.php.net/manual/en/function.file-put-contents.php * - * @param string $path - * @param mixed $data * @return int|float|false * @since 9.0.0 */ - public function file_put_contents($path, $data); + public function file_put_contents(string $path, mixed $data); /** * see https://www.php.net/manual/en/function.unlink.php * - * @param string $path * @return bool * @since 9.0.0 */ - public function unlink($path); + public function unlink(string $path); /** * see https://www.php.net/manual/en/function.rename.php * - * @param string $source - * @param string $target * @return bool * @since 9.0.0 */ - public function rename($source, $target); + public function rename(string $source, string $target); /** * see https://www.php.net/manual/en/function.copy.php * - * @param string $source - * @param string $target * @return bool * @since 9.0.0 */ - public function copy($source, $target); + public function copy(string $source, string $target); /** * see https://www.php.net/manual/en/function.fopen.php * - * @param string $path - * @param string $mode - * @return resource|bool + * @return resource|false * @since 9.0.0 */ - public function fopen($path, $mode); + public function fopen(string $path, string $mode); /** * get the mimetype for a file or folder * The mimetype for a folder is required to be "httpd/unix-directory" * - * @param string $path - * @return string|bool + * @return string|false * @since 9.0.0 */ - public function getMimeType($path); + public function getMimeType(string $path); /** * see https://www.php.net/manual/en/function.hash-file.php * - * @param string $type - * @param string $path - * @param bool $raw - * @return string|bool + * @return string|false * @since 9.0.0 */ - public function hash($type, $path, $raw = false); + public function hash(string $type, string $path, bool $raw = false); /** - * see https://www.php.net/manual/en/function.free_space.php + * see https://www.php.net/manual/en/function.disk-free-space.php * - * @param string $path - * @return int|float|bool + * @return int|float|false * @since 9.0.0 */ - public function free_space($path); + public function free_space(string $path); /** * see https://www.php.net/manual/en/function.touch.php * If the backend does not support the operation, false should be returned * - * @param string $path - * @param int $mtime * @return bool * @since 9.0.0 */ - public function touch($path, $mtime = null); + public function touch(string $path, ?int $mtime = null); /** * get the path to a local version of the file. * The local version of the file can be temporary and doesn't have to be persistent across requests * - * @param string $path * @return string|false * @since 9.0.0 */ - public function getLocalFile($path); + public function getLocalFile(string $path); /** * check if a file or folder has been updated since $time * - * @param string $path - * @param int $time * @return bool * @since 9.0.0 * * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. * returning true for other changes in the folder is optional */ - public function hasUpdated($path, $time); + public function hasUpdated(string $path, int $time); /** * get the ETag for a file or folder * - * @param string $path * @return string|false * @since 9.0.0 */ - public function getETag($path); + public function getETag(string $path); /** * Returns whether the storage is local, which means that files @@ -338,51 +294,41 @@ interface IStorage { * Check if the storage is an instance of $class or is a wrapper for a storage that is an instance of $class * * @template T of IStorage - * @param string $class * @psalm-param class-string<T> $class * @return bool * @since 9.0.0 * @psalm-assert-if-true T $this */ - public function instanceOfStorage($class); + public function instanceOfStorage(string $class); /** * A custom storage implementation can return an url for direct download of a give file. * * For now the returned array can hold the parameter url - in future more attributes might follow. * - * @param string $path - * @return array|bool + * @return array|false * @since 9.0.0 */ - public function getDirectDownload($path); + public function getDirectDownload(string $path); /** - * @param string $path the path of the target folder - * @param string $fileName the name of the file itself * @return void * @throws InvalidPathException * @since 9.0.0 */ - public function verifyPath($path, $fileName); + public function verifyPath(string $path, string $fileName); /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath * @return bool * @since 9.0.0 */ - public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath); + public function copyFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath); /** - * @param IStorage $sourceStorage - * @param string $sourceInternalPath - * @param string $targetInternalPath * @return bool * @since 9.0.0 */ - public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath); + public function moveFromStorage(IStorage $sourceStorage, string $sourceInternalPath, string $targetInternalPath); /** * Test a storage for availability @@ -400,23 +346,28 @@ interface IStorage { /** * @since 9.0.0 - * @param bool $isAvailable + * @return void + */ + public function setAvailability(bool $isAvailable); + + /** + * @since 12.0.0 + * @since 31.0.0 moved from Storage to IStorage + * @return bool */ - public function setAvailability($isAvailable); + public function needsPartFile(); /** - * @param string $path path for which to retrieve the owner + * @return string|false * @since 9.0.0 */ - public function getOwner($path); + public function getOwner(string $path); /** - * @param string $path - * @param IStorage|null $storage * @return ICache * @since 9.0.0 */ - public function getCache($path = '', $storage = null); + public function getCache(string $path = '', ?IStorage $storage = null); /** * @return IPropagator @@ -448,7 +399,7 @@ interface IStorage { * This can be used for storages that do not have a dedicated owner, where we want to * pass the user that we setup the mountpoint for along to the storage layer * - * @param string|null $user Owner user id + * @param ?string $user Owner user id * @return void * @since 30.0.0 */ diff --git a/lib/public/Files/Storage/IStorageFactory.php b/lib/public/Files/Storage/IStorageFactory.php index 7c207ca9f0a..24f87d2e775 100644 --- a/lib/public/Files/Storage/IStorageFactory.php +++ b/lib/public/Files/Storage/IStorageFactory.php @@ -19,20 +19,15 @@ interface IStorageFactory { * * $callback should be a function of type (string $mountPoint, Storage $storage) => Storage * - * @param string $wrapperName - * @param callable $callback * @return bool true if the wrapper was added, false if there was already a wrapper with this - * name registered + * name registered * @since 8.0.0 */ - public function addStorageWrapper($wrapperName, $callback); + public function addStorageWrapper(string $wrapperName, callable $callback); /** - * @param \OCP\Files\Mount\IMountPoint $mountPoint - * @param string $class - * @param array $arguments - * @return \OCP\Files\Storage + * @return IStorage * @since 8.0.0 */ - public function getInstance(IMountPoint $mountPoint, $class, $arguments); + public function getInstance(IMountPoint $mountPoint, string $class, array $arguments); } diff --git a/lib/public/Files/Storage/IWriteStreamStorage.php b/lib/public/Files/Storage/IWriteStreamStorage.php index 6da34563848..b03f46ef2bc 100644 --- a/lib/public/Files/Storage/IWriteStreamStorage.php +++ b/lib/public/Files/Storage/IWriteStreamStorage.php @@ -19,9 +19,8 @@ interface IWriteStreamStorage extends IStorage { /** * Write the data from a stream to a file * - * @param string $path * @param resource $stream - * @param int|null $size the size of the stream if known in advance + * @param ?int $size the size of the stream if known in advance * @return int the number of bytes written * @throws GenericFileException * @since 15.0.0 diff --git a/lib/public/Files/Template/BeforeGetTemplatesEvent.php b/lib/public/Files/Template/BeforeGetTemplatesEvent.php new file mode 100644 index 00000000000..9fb7453a50c --- /dev/null +++ b/lib/public/Files/Template/BeforeGetTemplatesEvent.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Template; + +use OCP\EventDispatcher\Event; + +/** + * @since 30.0.0 + */ +class BeforeGetTemplatesEvent extends Event { + /** @var array<Template> */ + private array $templates; + /** @var bool */ + private bool $withFields; + + /** + * @param array<Template> $templates + * + * @since 30.0.0 + */ + public function __construct(array $templates, bool $withFields = false) { + parent::__construct(); + + $this->templates = $templates; + $this->withFields = $withFields; + } + + /** + * @return array<Template> + * + * @since 30.0.0 + */ + public function getTemplates(): array { + return $this->templates; + } + + /** + * @return bool + * + * @since 32.0.0 + */ + public function shouldGetFields(): bool { + return $this->withFields; + } +} diff --git a/lib/public/Files/Template/Field.php b/lib/public/Files/Template/Field.php new file mode 100644 index 00000000000..e047e83a29e --- /dev/null +++ b/lib/public/Files/Template/Field.php @@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Template; + +/** + * @since 30.0.0 + */ +abstract class Field implements \JsonSerializable { + public ?string $alias = null; + public ?string $tag = null; + public ?int $id = null; + + /** + * @since 30.0.0 + */ + public function __construct( + private string $index, + private FieldType $type, + ) { + } + + /** + * @since 30.0.0 + */ + abstract public function setValue(mixed $value): void; + + /** + * @return array{ + * index: string, + * type: string, + * alias: ?string, + * tag: ?string, + * id: ?int, + * content?: string, + * checked?: bool, + * } + * @since 30.0.0 + */ + public function jsonSerialize(): array { + return [ + 'index' => $this->index, + 'type' => $this->type->value, + 'alias' => $this->alias, + 'tag' => $this->tag, + 'id' => $this->id, + ]; + } +} diff --git a/lib/public/Files/Template/FieldFactory.php b/lib/public/Files/Template/FieldFactory.php new file mode 100644 index 00000000000..f14d44a8573 --- /dev/null +++ b/lib/public/Files/Template/FieldFactory.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Template; + +use OCP\Files\Template\Fields\CheckBoxField; +use OCP\Files\Template\Fields\RichTextField; + +/** + * @since 30.0.0 + */ +class FieldFactory { + /** + * @since 30.0.0 + */ + public static function createField( + string $index, + FieldType $type, + ): Field { + return match ($type) { + FieldType::RichText => new RichTextField($index, $type), + FieldType::CheckBox => new CheckBoxField($index, $type), + default => throw new InvalidFieldTypeException(), + }; + } +} diff --git a/lib/public/Files/Template/FieldType.php b/lib/public/Files/Template/FieldType.php new file mode 100644 index 00000000000..2d059cadc17 --- /dev/null +++ b/lib/public/Files/Template/FieldType.php @@ -0,0 +1,19 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Template; + +/** + * @since 30.0.0 + */ +enum FieldType: string { + case RichText = 'rich-text'; + case CheckBox = 'checkbox'; + case DropDownList = 'drop-down-list'; + case Picture = 'picture'; + case Date = 'date'; +} diff --git a/lib/public/Files/Template/Fields/CheckBoxField.php b/lib/public/Files/Template/Fields/CheckBoxField.php new file mode 100644 index 00000000000..6fab3ce66a6 --- /dev/null +++ b/lib/public/Files/Template/Fields/CheckBoxField.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Template\Fields; + +use OCP\Files\Template\Field; +use OCP\Files\Template\FieldType; + +/** + * @since 30.0.0 + */ +class CheckBoxField extends Field { + private bool $checked = false; + + /** + * @since 30.0.0 + */ + public function __construct(string $index, FieldType $type) { + parent::__construct($index, $type); + } + + /** + * @since 30.0.0 + */ + public function setValue(mixed $value): void { + if (!is_bool($value)) { + throw new \Exception('Invalid value for checkbox field type'); + } + + $this->checked = $value; + } + + /** + * @return array{ + * index: string, + * type: string, + * alias: ?string, + * tag: ?string, + * id: ?int, + * content?: string, + * checked?: bool, + * } + * @since 30.0.0 + */ + public function jsonSerialize(): array { + $jsonProperties = parent::jsonSerialize(); + + return array_merge($jsonProperties, ['checked' => $this->checked]); + } +} diff --git a/lib/public/Files/Template/Fields/RichTextField.php b/lib/public/Files/Template/Fields/RichTextField.php new file mode 100644 index 00000000000..93ead68747c --- /dev/null +++ b/lib/public/Files/Template/Fields/RichTextField.php @@ -0,0 +1,56 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Template\Fields; + +use OCP\Files\Template\Field; +use OCP\Files\Template\FieldType; + +/** + * @since 30.0.0 + */ +class RichTextField extends Field { + private string $content = ''; + + /** + * @since 30.0.0 + */ + public function __construct(string $index, FieldType $type) { + parent::__construct($index, $type); + } + + /** + * @since 30.0.0 + */ + public function setValue(mixed $value): void { + if (!is_string($value)) { + throw new \Exception('Invalid value for rich-text field type'); + } + + $this->content = $value; + } + + /** + * @return array{ + * index: string, + * type: string, + * alias: ?string, + * tag: ?string, + * id: ?int, + * content?: string, + * checked?: bool, + * } + * @since 30.0.0 + */ + public function jsonSerialize(): array { + $jsonProperties = parent::jsonSerialize(); + + return array_merge($jsonProperties, ['content' => $this->content]); + } +} diff --git a/lib/public/Files/Template/FileCreatedFromTemplateEvent.php b/lib/public/Files/Template/FileCreatedFromTemplateEvent.php index ed585504f70..0636d1dc251 100644 --- a/lib/public/Files/Template/FileCreatedFromTemplateEvent.php +++ b/lib/public/Files/Template/FileCreatedFromTemplateEvent.php @@ -17,15 +17,17 @@ use OCP\Files\File; class FileCreatedFromTemplateEvent extends Event { private $template; private $target; + private $templateFields; /** * @param File|null $template * @param File $target * @since 21.0.0 */ - public function __construct(?File $template, File $target) { + public function __construct(?File $template, File $target, array $templateFields) { $this->template = $template; $this->target = $target; + $this->templateFields = $templateFields; } /** @@ -37,6 +39,14 @@ class FileCreatedFromTemplateEvent extends Event { } /** + * @return array + * @since 30.0.0 + */ + public function getTemplateFields(): array { + return $this->templateFields; + } + + /** * @return File * @since 21.0.0 */ diff --git a/lib/public/Files/Template/ICustomTemplateProvider.php b/lib/public/Files/Template/ICustomTemplateProvider.php index 305f2c9a55e..6136bc4f1c0 100644 --- a/lib/public/Files/Template/ICustomTemplateProvider.php +++ b/lib/public/Files/Template/ICustomTemplateProvider.php @@ -17,7 +17,7 @@ interface ICustomTemplateProvider { /** * Return a list of additional templates that the template provider is offering * - * @return File[] + * @return Template[] * @since 21.0.0 */ public function getCustomTemplates(string $mimetype): array; diff --git a/lib/public/Files/Template/ITemplateManager.php b/lib/public/Files/Template/ITemplateManager.php index 5adcc0ded25..df81bc5604e 100644 --- a/lib/public/Files/Template/ITemplateManager.php +++ b/lib/public/Files/Template/ITemplateManager.php @@ -33,12 +33,21 @@ interface ITemplateManager { /** * Get a list of available file creators and their offered templates * - * @return array + * @return list<array{app: string, label: string, extension: string, iconClass: ?string, iconSvgInline: ?string, mimetypes: list<string>, ratio: ?float, actionLabel: string, templates: list<Template>}> * @since 21.0.0 */ public function listTemplates(): array; /** + * Get the fields for a given template + * + * @param int $fileId + * @return array + * @since 32.0.0 + */ + public function listTemplateFields(int $fileId): array; + + /** * @return bool * @since 21.0.0 */ @@ -67,9 +76,11 @@ interface ITemplateManager { /** * @param string $filePath * @param string $templateId + * @param string $templateType + * @param array $templateFields Since 30.0.0 * @return array * @throws GenericFileException * @since 21.0.0 */ - public function createFromTemplate(string $filePath, string $templateId = '', string $templateType = 'user'): array; + public function createFromTemplate(string $filePath, string $templateId = '', string $templateType = 'user', array $templateFields = []): array; } diff --git a/lib/public/Files/Template/InvalidFieldTypeException.php b/lib/public/Files/Template/InvalidFieldTypeException.php new file mode 100644 index 00000000000..a0c5297526c --- /dev/null +++ b/lib/public/Files/Template/InvalidFieldTypeException.php @@ -0,0 +1,15 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Files\Template; + +/** + * Exception for invalid template field type + * @since 30.0.0 + */ +class InvalidFieldTypeException extends \Exception { +} diff --git a/lib/public/Files/Template/RegisterTemplateCreatorEvent.php b/lib/public/Files/Template/RegisterTemplateCreatorEvent.php index 4f739b1f9d3..a9e7fa01252 100644 --- a/lib/public/Files/Template/RegisterTemplateCreatorEvent.php +++ b/lib/public/Files/Template/RegisterTemplateCreatorEvent.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -17,7 +18,7 @@ class RegisterTemplateCreatorEvent extends Event { * @since 30.0.0 */ public function __construct( - private ITemplateManager $templateManager + private ITemplateManager $templateManager, ) { } diff --git a/lib/public/Files/Template/Template.php b/lib/public/Files/Template/Template.php index 94f5cec268f..7f01c2afa48 100644 --- a/lib/public/Files/Template/Template.php +++ b/lib/public/Files/Template/Template.php @@ -24,6 +24,8 @@ final class Template implements \JsonSerializable { private $hasPreview = false; /** @var string|null */ private $previewUrl = null; + /** @var list<Field> */ + private $fields = []; /** * @since 21.0.0 @@ -49,6 +51,37 @@ final class Template implements \JsonSerializable { } /** + * @param list<Field> $fields + * @since 30.0.0 + */ + public function setFields(array $fields): void { + $this->fields = $fields; + } + + /** + * @return array{ + * templateType: string, + * templateId: string, + * basename: string, + * etag: string, + * fileid: int, + * filename: string, + * lastmod: int, + * mime: string, + * size: int|float, + * type: string, + * hasPreview: bool, + * previewUrl: ?string, + * fields: list<array{ + * index: string, + * type: string, + * alias: ?string, + * tag: ?string, + * id: ?int, + * content?: string, + * checked?: bool, + * }>, + * } * @since 21.0.0 */ public function jsonSerialize(): array { @@ -64,7 +97,8 @@ final class Template implements \JsonSerializable { 'size' => $this->file->getSize(), 'type' => $this->file->getType(), 'hasPreview' => $this->hasPreview, - 'previewUrl' => $this->previewUrl + 'previewUrl' => $this->previewUrl, + 'fields' => array_map(static fn (Field $field) => $field->jsonSerialize(), $this->fields), ]; } } diff --git a/lib/public/Files/Template/TemplateFileCreator.php b/lib/public/Files/Template/TemplateFileCreator.php index b8174ec3ea0..809bd3d0bc2 100644 --- a/lib/public/Files/Template/TemplateFileCreator.php +++ b/lib/public/Files/Template/TemplateFileCreator.php @@ -13,7 +13,7 @@ namespace OCP\Files\Template; */ final class TemplateFileCreator implements \JsonSerializable { protected $appId; - /** @var string[] $mimetypes */ + /** @var list<string> $mimetypes */ protected $mimetypes = []; protected $actionName; protected $fileExtension; @@ -34,7 +34,7 @@ final class TemplateFileCreator implements \JsonSerializable { * @since 21.0.0 */ public function __construct( - string $appId, string $actionName, string $fileExtension + string $appId, string $actionName, string $fileExtension, ) { $this->appId = $appId; $this->actionName = $actionName; @@ -121,7 +121,7 @@ final class TemplateFileCreator implements \JsonSerializable { /** * @since 21.0.0 - * @return array{app: string, label: string, extension: string, iconClass: ?string, iconSvgInline: ?string, mimetypes: string[], ratio: ?float, actionLabel: string} + * @return array{app: string, label: string, extension: string, iconClass: ?string, iconSvgInline: ?string, mimetypes: list<string>, ratio: ?float, actionLabel: string} */ public function jsonSerialize(): array { return [ diff --git a/lib/public/FilesMetadata/AMetadataEvent.php b/lib/public/FilesMetadata/AMetadataEvent.php index 637073a2ffd..8fef0d85db9 100644 --- a/lib/public/FilesMetadata/AMetadataEvent.php +++ b/lib/public/FilesMetadata/AMetadataEvent.php @@ -23,7 +23,7 @@ abstract class AMetadataEvent extends Event { */ public function __construct( protected Node $node, - protected IFilesMetadata $metadata + protected IFilesMetadata $metadata, ) { parent::__construct(); } diff --git a/lib/public/FilesMetadata/Event/MetadataNamedEvent.php b/lib/public/FilesMetadata/Event/MetadataNamedEvent.php index 6b40bc2f379..453eae1d6af 100644 --- a/lib/public/FilesMetadata/Event/MetadataNamedEvent.php +++ b/lib/public/FilesMetadata/Event/MetadataNamedEvent.php @@ -38,7 +38,7 @@ class MetadataNamedEvent extends AMetadataEvent { public function __construct( Node $node, IFilesMetadata $metadata, - private string $name = '' + private string $name = '', ) { parent::__construct($node, $metadata); } diff --git a/lib/public/FilesMetadata/IFilesMetadataManager.php b/lib/public/FilesMetadata/IFilesMetadataManager.php index ba2a5bd2450..4b1a6d32e8e 100644 --- a/lib/public/FilesMetadata/IFilesMetadataManager.php +++ b/lib/public/FilesMetadata/IFilesMetadataManager.php @@ -50,7 +50,7 @@ interface IFilesMetadataManager { public function refreshMetadata( Node $node, int $process = self::PROCESS_LIVE, - string $namedEvent = '' + string $namedEvent = '', ): IFilesMetadata; /** @@ -112,7 +112,7 @@ interface IFilesMetadataManager { public function getMetadataQuery( IQueryBuilder $qb, string $fileTableAlias, - string $fileIdField + string $fileIdField, ): IMetadataQuery; /** diff --git a/lib/public/Files_FullTextSearch/Model/AFilesDocument.php b/lib/public/Files_FullTextSearch/Model/AFilesDocument.php index ba5f504fccf..297d6bc6ffc 100644 --- a/lib/public/Files_FullTextSearch/Model/AFilesDocument.php +++ b/lib/public/Files_FullTextSearch/Model/AFilesDocument.php @@ -16,7 +16,7 @@ use OCP\FullTextSearch\Model\IIndexDocument; * This is mostly used by 3rd party apps that want to complete the IIndexDocument * with more information about a file before its index: * - * \OC::$server->getEventDispatcher()->addListener( + * \OCP\Server::get(IEventDispatcher::class)->addListener( * '\OCA\Files_FullTextSearch::onFileIndexing', * function(GenericEvent $e) { * //@var \OCP\Files\Node $file diff --git a/lib/public/FullTextSearch/Model/ISearchTemplate.php b/lib/public/FullTextSearch/Model/ISearchTemplate.php index 0de907bef19..89f683ca013 100644 --- a/lib/public/FullTextSearch/Model/ISearchTemplate.php +++ b/lib/public/FullTextSearch/Model/ISearchTemplate.php @@ -16,7 +16,7 @@ use OCP\FullTextSearch\IFullTextSearchProvider; * when the getSearchTemplate() method is called. * * The object will contain templates to be displayed, and the list of the different - * options to be available to the user when he start a new search. + * options to be available to the user when they start a new search. * * The display of the Options is generated by the FullTextSearch app and Options * can be displayed in 2 places: diff --git a/lib/public/GlobalScale/IConfig.php b/lib/public/GlobalScale/IConfig.php index 31df152f644..bae5dd7aa66 100644 --- a/lib/public/GlobalScale/IConfig.php +++ b/lib/public/GlobalScale/IConfig.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Group/Backend/ABackend.php b/lib/public/Group/Backend/ABackend.php index 83fdcfaaa33..95af1b85d9b 100644 --- a/lib/public/Group/Backend/ABackend.php +++ b/lib/public/Group/Backend/ABackend.php @@ -64,7 +64,7 @@ abstract class ABackend implements GroupInterface, IBatchMethodsBackend { */ public function getGroupsDetails(array $gids): array { if (!($this instanceof IGroupDetailsBackend || $this->implementsActions(GroupInterface::GROUP_DETAILS))) { - throw new \Exception("Should not have been called"); + throw new \Exception('Should not have been called'); } /** @var IGroupDetailsBackend $this */ $groupData = []; diff --git a/lib/public/Group/Backend/IBatchMethodsBackend.php b/lib/public/Group/Backend/IBatchMethodsBackend.php index 745b76aac50..5853447d5e9 100644 --- a/lib/public/Group/Backend/IBatchMethodsBackend.php +++ b/lib/public/Group/Backend/IBatchMethodsBackend.php @@ -33,7 +33,7 @@ interface IBatchMethodsBackend { * a loop. But a GroupBackend implementation should override this method * to provide a more optimized way to execute this operation. * - * @throw \RuntimeException if called on a backend that doesn't implements IGroupDetailsBackend + * @throws \RuntimeException if called on a backend that doesn't implements IGroupDetailsBackend * * @return array<string, array{displayName?: string}> * @since 28.0.0 diff --git a/lib/public/Group/Backend/INamedBackend.php b/lib/public/Group/Backend/INamedBackend.php index 8b9ef9ffbba..d98cc8cdb83 100644 --- a/lib/public/Group/Backend/INamedBackend.php +++ b/lib/public/Group/Backend/INamedBackend.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Group/Backend/ISearchableGroupBackend.php b/lib/public/Group/Backend/ISearchableGroupBackend.php index 900330da22d..e9909fcdba0 100644 --- a/lib/public/Group/Backend/ISearchableGroupBackend.php +++ b/lib/public/Group/Backend/ISearchableGroupBackend.php @@ -24,11 +24,11 @@ interface ISearchableGroupBackend { * $users = $groupBackend->searchInGroup('admin', 'John', 10, 0); * </code> * - * @param string $gid The group id of the user we want to search + * @param string $gid The group id of the user we want to search * @param string $search The part of the display name or user id of the users we * want to search. This can be empty to get all the users. - * @param int $limit The limit of results - * @param int $offset The offset of the results + * @param int $limit The limit of results + * @param int $offset The offset of the results * @return array<string,IUser> Users indexed by uid * @since 27.0.0 */ diff --git a/lib/public/GroupInterface.php b/lib/public/GroupInterface.php index 9329fe4a454..cbfd74a068a 100644 --- a/lib/public/GroupInterface.php +++ b/lib/public/GroupInterface.php @@ -35,7 +35,7 @@ interface GroupInterface { /** * @since 12.0.0 - * @deprecated 29.0.0 + * @deprecated 29.0.0 */ public const REMOVE_FROM_GOUP = 0x00001000; // oops @@ -86,7 +86,7 @@ interface GroupInterface { /** * Get all groups a user belongs to * @param string $uid Name of the user - * @return array an array of group names + * @return list<string> an array of group names * @since 4.5.0 * * This function fetches all groups a user belongs to. It does not check diff --git a/lib/public/HintException.php b/lib/public/HintException.php index f9bacbd5887..6d9684bddea 100644 --- a/lib/public/HintException.php +++ b/lib/public/HintException.php @@ -21,12 +21,12 @@ class HintException extends \Exception { * HintException constructor. * * @since 23.0.0 - * @param string $message The error message. It will be not revealed to the - * the user (unless the hint is empty) and thus - * should be not translated. - * @param string $hint A useful message that is presented to the end - * user. It should be translated, but must not - * contain sensitive data. + * @param string $message The error message. It will be not revealed to the + * the user (unless the hint is empty) and thus + * should be not translated. + * @param string $hint A useful message that is presented to the end + * user. It should be translated, but must not + * contain sensitive data. * @param int $code * @param \Exception|null $previous */ @@ -43,7 +43,7 @@ class HintException extends \Exception { * @return string */ public function __toString(): string { - return __CLASS__ . ": [{$this->code}]: {$this->message} ({$this->hint})\n"; + return self::class . ": [{$this->code}]: {$this->message} ({$this->hint})\n"; } /** diff --git a/lib/public/Http/Client/IClient.php b/lib/public/Http/Client/IClient.php index 995a68f23bc..e4f46d44e4d 100644 --- a/lib/public/Http/Client/IClient.php +++ b/lib/public/Http/Client/IClient.php @@ -14,30 +14,38 @@ namespace OCP\Http\Client; * @since 8.1.0 */ interface IClient { + + /** + * Default request timeout for requests + * + * @since 31.0.0 + */ + public const DEFAULT_REQUEST_TIMEOUT = 30; + /** * Sends a GET request * @param string $uri * @param array $options Array such as - * 'query' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IResponse * @throws \Exception If the request could not get completed * @since 8.1.0 @@ -48,21 +56,21 @@ interface IClient { * Sends a HEAD request * @param string $uri * @param array $options Array such as - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IResponse * @throws \Exception If the request could not get completed * @since 8.1.0 @@ -73,26 +81,26 @@ interface IClient { * Sends a POST request * @param string $uri * @param array $options Array such as - * 'body' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IResponse * @throws \Exception If the request could not get completed * @since 8.1.0 @@ -103,26 +111,26 @@ interface IClient { * Sends a PUT request * @param string $uri * @param array $options Array such as - * 'body' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IResponse * @throws \Exception If the request could not get completed * @since 8.1.0 @@ -133,26 +141,26 @@ interface IClient { * Sends a PATCH request * @param string $uri * @param array $options Array such as - * 'body' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IResponse * @throws \Exception If the request could not get completed * @since 29.0.0 @@ -163,26 +171,26 @@ interface IClient { * Sends a DELETE request * @param string $uri * @param array $options Array such as - * 'body' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IResponse * @throws \Exception If the request could not get completed * @since 8.1.0 @@ -193,26 +201,26 @@ interface IClient { * Sends an OPTIONS request * @param string $uri * @param array $options Array such as - * 'body' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IResponse * @throws \Exception If the request could not get completed * @since 8.1.0 @@ -234,26 +242,26 @@ interface IClient { * @param string $method The HTTP method to use * @param string $uri * @param array $options Array such as - * 'query' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IResponse * @throws \Exception If the request could not get completed * @since 29.0.0 @@ -264,26 +272,26 @@ interface IClient { * Sends an asynchronous GET request * @param string $uri * @param array $options Array such as - * 'query' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'query' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IPromise * @since 28.0.0 */ @@ -293,21 +301,21 @@ interface IClient { * Sends an asynchronous HEAD request * @param string $uri * @param array $options Array such as - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IPromise * @since 28.0.0 */ @@ -317,26 +325,26 @@ interface IClient { * Sends an asynchronous POST request * @param string $uri * @param array $options Array such as - * 'body' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IPromise * @since 28.0.0 */ @@ -346,26 +354,26 @@ interface IClient { * Sends an asynchronous PUT request * @param string $uri * @param array $options Array such as - * 'body' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IPromise * @since 28.0.0 */ @@ -375,26 +383,26 @@ interface IClient { * Sends an asynchronous DELETE request * @param string $uri * @param array $options Array such as - * 'body' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IPromise * @since 28.0.0 */ @@ -404,26 +412,26 @@ interface IClient { * Sends an asynchronous OPTIONS request * @param string $uri * @param array $options Array such as - * 'body' => [ - * 'field' => 'abc', - * 'other_field' => '123', - * 'file_name' => fopen('/path/to/file', 'r'), - * ], - * 'headers' => [ - * 'foo' => 'bar', - * ], - * 'cookies' => [ - * 'foo' => 'bar', - * ], - * 'allow_redirects' => [ - * 'max' => 10, // allow at most 10 redirects. - * 'strict' => true, // use "strict" RFC compliant redirects. - * 'referer' => true, // add a Referer header - * 'protocols' => ['https'] // only allow https URLs - * ], - * 'sink' => '/path/to/file', // save to a file or a stream - * 'verify' => true, // bool or string to CA file - * 'debug' => true, + * 'body' => [ + * 'field' => 'abc', + * 'other_field' => '123', + * 'file_name' => fopen('/path/to/file', 'r'), + * ], + * 'headers' => [ + * 'foo' => 'bar', + * ], + * 'cookies' => [ + * 'foo' => 'bar', + * ], + * 'allow_redirects' => [ + * 'max' => 10, // allow at most 10 redirects. + * 'strict' => true, // use "strict" RFC compliant redirects. + * 'referer' => true, // add a Referer header + * 'protocols' => ['https'] // only allow https URLs + * ], + * 'sink' => '/path/to/file', // save to a file or a stream + * 'verify' => true, // bool or string to CA file + * 'debug' => true, * @return IPromise * @since 28.0.0 */ diff --git a/lib/public/Http/Client/IPromise.php b/lib/public/Http/Client/IPromise.php index 7abbd7227d7..8778829af0e 100644 --- a/lib/public/Http/Client/IPromise.php +++ b/lib/public/Http/Client/IPromise.php @@ -34,7 +34,7 @@ interface IPromise { * a new promise resolving to the return value of the called handler. * * @param ?callable(IResponse): void $onFulfilled Invoked when the promise fulfills. Gets an \OCP\Http\Client\IResponse passed in as argument - * @param ?callable(Exception): void $onRejected Invoked when the promise is rejected. Gets an \Exception passed in as argument + * @param ?callable(Exception): void $onRejected Invoked when the promise is rejected. Gets an \Exception passed in as argument * * @return IPromise * @since 28.0.0 @@ -76,7 +76,7 @@ interface IPromise { * @return mixed * * @throws LogicException if the promise has no wait function or if the - * promise does not settle after waiting. + * promise does not settle after waiting. * @since 28.0.0 */ public function wait(bool $unwrap = true): mixed; diff --git a/lib/public/Http/Client/IResponse.php b/lib/public/Http/Client/IResponse.php index deec2cf97b1..53032ef2a37 100644 --- a/lib/public/Http/Client/IResponse.php +++ b/lib/public/Http/Client/IResponse.php @@ -15,8 +15,9 @@ namespace OCP\Http\Client; */ interface IResponse { /** - * @return string|resource + * @return null|resource|string * @since 8.1.0 + * @sicne 8.2.0 with stream enabled, the function returns null or a resource */ public function getBody(); diff --git a/lib/public/IAddressBook.php b/lib/public/IAddressBook.php index 479e421d700..5a5cc487cee 100644 --- a/lib/public/IAddressBook.php +++ b/lib/public/IAddressBook.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -37,19 +38,19 @@ namespace OCP { * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match * @param array $options Options to define the output format and search behavior - * - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array - * example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']] - * - 'escape_like_param' - If set to false wildcards _ and % are not escaped - * - 'limit' - Set a numeric limit for the search results - * - 'offset' - Set the offset for the limited search results - * - 'wildcard' - (since 23.0.0) Whether the search should use wildcards + * - 'types' boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array + * example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']] + * - 'escape_like_param' - If set to false wildcards _ and % are not escaped + * - 'limit' - Set a numeric limit for the search results + * - 'offset' - Set the offset for the limited search results + * - 'wildcard' - (since 23.0.0) Whether the search should use wildcards * @psalm-param array{types?: bool, escape_like_param?: bool, limit?: int, offset?: int, wildcard?: bool} $options * @return array an array of contacts which are arrays of key-value-pairs - * example result: - * [ - * ['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => 'a@b.c', 'GEO' => '37.386013;-122.082932'], - * ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['d@e.f', 'g@h.i']] - * ] + * example result: + * [ + * ['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => 'a@b.c', 'GEO' => '37.386013;-122.082932'], + * ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['d@e.f', 'g@h.i']] + * ] * @since 5.0.0 */ public function search($pattern, $searchProperties, $options); diff --git a/lib/public/IAddressBookEnabled.php b/lib/public/IAddressBookEnabled.php new file mode 100644 index 00000000000..ad93aa3f59a --- /dev/null +++ b/lib/public/IAddressBookEnabled.php @@ -0,0 +1,26 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors" + * SPDX-License-Identifier: AGPL-3.0-only + */ +// use OCP namespace for all classes that are considered public. +// This means that they should be used by apps instead of the internal Nextcloud classes + +namespace OCP; + +/** + * IAddressBook Interface extension for checking if the address book is enabled + * + * @since 32.0.0 + */ +interface IAddressBookEnabled extends IAddressBook { + /** + * Check if the address book is enabled + * @return bool + * @since 32.0.0 + */ + public function isEnabled(): bool; +} diff --git a/lib/public/IAppConfig.php b/lib/public/IAppConfig.php index 49f51467308..d9e3e0d95a7 100644 --- a/lib/public/IAppConfig.php +++ b/lib/public/IAppConfig.php @@ -8,6 +8,7 @@ declare(strict_types=1); */ namespace OCP; +use OCP\Config\ValueType; use OCP\Exceptions\AppConfigUnknownKeyException; /** @@ -45,12 +46,15 @@ interface IAppConfig { /** @since 29.0.0 */ public const VALUE_ARRAY = 64; + /** @since 31.0.0 */ + public const FLAG_SENSITIVE = 1; // value is sensitive + /** * Get list of all apps that have at least one config value stored in database * * **WARNING:** ignore lazy filtering, all config values are loaded from database * - * @return string[] list of app ids + * @return list<string> list of app ids * @since 7.0.0 */ public function getApps(): array; @@ -62,13 +66,27 @@ interface IAppConfig { * **WARNING:** ignore lazy filtering, all config values are loaded from database * * @param string $app id of the app + * @return list<string> list of stored config keys + * @see searchKeys to avoid loading lazy config keys * - * @return string[] list of stored config keys * @since 29.0.0 */ public function getKeys(string $app): array; /** + * Returns list of keys stored in database, related to an app. + * Please note that the values are not returned. + * + * @param string $app id of the app + * @param string $prefix returns only keys starting with this value + * @param bool $lazy TRUE to search in lazy config keys + * + * @return list<string> list of stored config keys + * @since 32.0.0 + */ + public function searchKeys(string $app, string $prefix = '', bool $lazy = false): array; + + /** * Check if a key exists in the list of stored config values. * * @param string $app id of the app @@ -433,6 +451,33 @@ interface IAppConfig { public function getDetails(string $app, string $key): array; /** + * returns an array containing details about a config key. + * key/value pair are available only if it exists. + * + * ``` + * [ + * "app" => "myapp", + * "key" => "mykey", + * "value" => "current_value", + * "default" => "default_if_available", + * "definition" => "this is what it does", + * "note" => "enabling this is not compatible with that", + * "lazy" => false, + * "type" => 4, + * "typeString" => "string", + * 'sensitive' => false + * ] + * ``` + * + * @param string $app id of the app + * @param string $key config key + * + * @return array{app: string, key: string, lazy?: bool, valueType?: ValueType, valueTypeName?: string, sensitive?: bool, default?: string, definition?: string, note?: string} + * @since 32.0.0 + */ + public function getKeyDetails(string $app, string $key): array; + + /** * Convert string like 'string', 'integer', 'float', 'bool' or 'array' to * to bitflag {@see VALUE_STRING}, {@see VALUE_INT}, {@see VALUE_FLOAT}, * {@see VALUE_BOOL} and {@see VALUE_ARRAY} @@ -504,4 +549,12 @@ interface IAppConfig { * @deprecated 29.0.0 Use {@see getAllValues()} or {@see searchValues()} */ public function getFilteredValues($app); + + /** + * Returns the installed version of all apps + * + * @return array<string, string> + * @since 32.0.0 + */ + public function getAppInstalledVersions(bool $onlyEnabled = false): array; } diff --git a/lib/public/IBinaryFinder.php b/lib/public/IBinaryFinder.php index 3224ef523ed..28cb62e5c56 100644 --- a/lib/public/IBinaryFinder.php +++ b/lib/public/IBinaryFinder.php @@ -11,7 +11,7 @@ namespace OCP; /** * Service that find the binary path for a program. * - * This interface should be injected via depency injection and must + * This interface should be injected via dependency injection and must * not be implemented in applications. * * @since 25.0.0 diff --git a/lib/public/ICache.php b/lib/public/ICache.php index ba6016c8d28..5e755d81e14 100644 --- a/lib/public/ICache.php +++ b/lib/public/ICache.php @@ -16,6 +16,11 @@ namespace OCP; */ interface ICache { /** + * @since 30.0.0 + */ + public const DEFAULT_TTL = 24 * 60 * 60; + + /** * Get a value from the user cache * @param string $key * @return mixed diff --git a/lib/public/IConfig.php b/lib/public/IConfig.php index b7feabd0ef5..3bc64c5e133 100644 --- a/lib/public/IConfig.php +++ b/lib/public/IConfig.php @@ -206,9 +206,9 @@ interface IConfig { * @param string $userId the userId of the user that we want to get all values from * @psalm-return array<string, array<string, string>> * @return array[] - 2 dimensional array with the following structure: - * [ $appId => - * [ $key => $value ] - * ] + * [ $appId => + * [ $key => $value ] + * ] * @since 24.0.0 */ public function getAllUserValues(string $userId): array; @@ -245,7 +245,8 @@ interface IConfig { * @param string $appName the app to get the user for * @param string $key the key to get the user for * @param string $value the value to get the user for - * @return array of user IDs + * @return list<string> of user IDs + * @since 31.0.0 return type of `list<string>` * @since 8.0.0 */ public function getUsersForUserValue($appName, $key, $value); diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php index a5df85896e2..ea9b71d8958 100644 --- a/lib/public/IDBConnection.php +++ b/lib/public/IDBConnection.php @@ -11,6 +11,8 @@ namespace OCP; use Doctrine\DBAL\Schema\Schema; +use OC\DB\QueryBuilder\Sharded\CrossShardMoveHelper; +use OC\DB\QueryBuilder\Sharded\ShardDefinition; use OCP\DB\Exception; use OCP\DB\IPreparedStatement; use OCP\DB\IResult; @@ -43,6 +45,11 @@ interface IDBConnection { public const PLATFORM_SQLITE = 'sqlite'; /** + * @since 32.0.0 + */ + public const PLATFORM_MARIADB = 'mariadb'; + + /** * Gets the QueryBuilder for the connection. * * @return \OCP\DB\QueryBuilder\IQueryBuilder @@ -134,8 +141,8 @@ interface IDBConnection { * @param string $table The table name (will replace *PREFIX* with the actual prefix) * @param array $input data that should be inserted into the table (column name => value) * @param array|null $compare List of values that should be checked for "if not exists" - * If this is null or an empty array, all keys of $input will be compared - * Please note: text fields (clob) must not be used in the compare array + * If this is null or an empty array, all keys of $input will be compared + * Please note: text fields (clob) must not be used in the compare array * @return int number of inserted rows * @throws Exception used to be the removed dbal exception, since 21.0.0 it's \OCP\DB\Exception * @since 6.0.0 - parameter $compare was added in 8.1.0, return type changed from boolean in 8.1.0 @@ -278,6 +285,7 @@ interface IDBConnection { * * @return \Doctrine\DBAL\Platforms\AbstractPlatform The database platform. * @since 8.0.0 + * @deprecated 30.0.0 Please use {@see self::getDatabaseProvider()} and compare to self::PLATFORM_* constants */ public function getDatabasePlatform(); @@ -293,6 +301,21 @@ interface IDBConnection { public function dropTable(string $table): void; /** + * Truncate a table data if it exists + * + * Cascade is not supported on many platforms but would optionally cascade the truncate by + * following the foreign keys. + * + * @param string $table table name without the prefix + * @param bool $cascade whether to truncate cascading + * @throws Exception + * @since 32.0.0 + * + * @psalm-taint-sink sql $table + */ + public function truncateTable(string $table, bool $cascade): void; + + /** * Check if a table exists * * @param string $table table name without the prefix @@ -339,9 +362,30 @@ interface IDBConnection { /** * Returns the database provider name + * * @link https://github.com/nextcloud/server/issues/30877 + * + * @param bool $strict differentiate between database flavors, e.g. MySQL vs MariaDB + * @return self::PLATFORM_MYSQL|self::PLATFORM_ORACLE|self::PLATFORM_POSTGRES|self::PLATFORM_SQLITE|self::PLATFORM_MARIADB + * @since 32.0.0 Optional parameter $strict was added * @since 28.0.0 - * @return IDBConnection::PLATFORM_* */ - public function getDatabaseProvider(): string; + public function getDatabaseProvider(bool $strict = false): string; + + /** + * Get the shard definition by name, if configured + * + * @param string $name + * @return ShardDefinition|null + * @since 30.0.0 + */ + public function getShardDefinition(string $name): ?ShardDefinition; + + /** + * Get a helper class for implementing cross-shard moves + * + * @return CrossShardMoveHelper + * @since 30.0.0 + */ + public function getCrossShardMoveHelper(): CrossShardMoveHelper; } diff --git a/lib/public/IDateTimeFormatter.php b/lib/public/IDateTimeFormatter.php index 06dc9d41f81..2d47e1182c2 100644 --- a/lib/public/IDateTimeFormatter.php +++ b/lib/public/IDateTimeFormatter.php @@ -16,15 +16,15 @@ interface IDateTimeFormatter { /** * Formats the date of the given timestamp * - * @param int|\DateTime $timestamp - * @param string $format Either 'full', 'long', 'medium' or 'short' - * full: e.g. 'EEEE, MMMM d, y' => 'Wednesday, August 20, 2014' - * long: e.g. 'MMMM d, y' => 'August 20, 2014' - * medium: e.g. 'MMM d, y' => 'Aug 20, 2014' - * short: e.g. 'M/d/yy' => '8/20/14' - * The exact format is dependent on the language - * @param \DateTimeZone|null $timeZone The timezone to use - * @param \OCP\IL10N|null $l The locale to use + * @param int|\DateTime $timestamp + * @param string $format Either 'full', 'long', 'medium' or 'short' + * full: e.g. 'EEEE, MMMM d, y' => 'Wednesday, August 20, 2014' + * long: e.g. 'MMMM d, y' => 'August 20, 2014' + * medium: e.g. 'MMM d, y' => 'Aug 20, 2014' + * short: e.g. 'M/d/yy' => '8/20/14' + * The exact format is dependent on the language + * @param \DateTimeZone|null $timeZone The timezone to use + * @param \OCP\IL10N|null $l The locale to use * @return string Formatted date string * @since 8.0.0 */ @@ -33,16 +33,16 @@ interface IDateTimeFormatter { /** * Formats the date of the given timestamp * - * @param int|\DateTime $timestamp - * @param string $format Either 'full', 'long', 'medium' or 'short' - * full: e.g. 'EEEE, MMMM d, y' => 'Wednesday, August 20, 2014' - * long: e.g. 'MMMM d, y' => 'August 20, 2014' - * medium: e.g. 'MMM d, y' => 'Aug 20, 2014' - * short: e.g. 'M/d/yy' => '8/20/14' - * The exact format is dependent on the language - * Uses 'Today', 'Yesterday' and 'Tomorrow' when applicable - * @param \DateTimeZone|null $timeZone The timezone to use - * @param \OCP\IL10N|null $l The locale to use + * @param int|\DateTime $timestamp + * @param string $format Either 'full', 'long', 'medium' or 'short' + * full: e.g. 'EEEE, MMMM d, y' => 'Wednesday, August 20, 2014' + * long: e.g. 'MMMM d, y' => 'August 20, 2014' + * medium: e.g. 'MMM d, y' => 'Aug 20, 2014' + * short: e.g. 'M/d/yy' => '8/20/14' + * The exact format is dependent on the language + * Uses 'Today', 'Yesterday' and 'Tomorrow' when applicable + * @param \DateTimeZone|null $timeZone The timezone to use + * @param \OCP\IL10N|null $l The locale to use * @return string Formatted relative date string * @since 8.0.0 */ @@ -52,13 +52,13 @@ interface IDateTimeFormatter { * Gives the relative date of the timestamp * Only works for past dates * - * @param int|\DateTime $timestamp - * @param int|\DateTime|null $baseTimestamp Timestamp to compare $timestamp against, defaults to current time - * @param \OCP\IL10N|null $l The locale to use - * @return string Dates returned are: - * < 1 month => Today, Yesterday, n days ago - * < 13 month => last month, n months ago - * >= 13 month => last year, n years ago + * @param int|\DateTime $timestamp + * @param int|\DateTime|null $baseTimestamp Timestamp to compare $timestamp against, defaults to current time + * @param \OCP\IL10N|null $l The locale to use + * @return string Dates returned are: + * < 1 month => Today, Yesterday, n days ago + * < 13 month => last month, n months ago + * >= 13 month => last year, n years ago * @since 8.0.0 */ public function formatDateSpan($timestamp, $baseTimestamp = null, ?\OCP\IL10N $l = null); @@ -67,14 +67,14 @@ interface IDateTimeFormatter { * Formats the time of the given timestamp * * @param int|\DateTime $timestamp - * @param string $format Either 'full', 'long', 'medium' or 'short' - * full: e.g. 'h:mm:ss a zzzz' => '11:42:13 AM GMT+0:00' - * long: e.g. 'h:mm:ss a z' => '11:42:13 AM GMT' - * medium: e.g. 'h:mm:ss a' => '11:42:13 AM' - * short: e.g. 'h:mm a' => '11:42 AM' - * The exact format is dependent on the language - * @param \DateTimeZone|null $timeZone The timezone to use - * @param \OCP\IL10N|null $l The locale to use + * @param string $format Either 'full', 'long', 'medium' or 'short' + * full: e.g. 'h:mm:ss a zzzz' => '11:42:13 AM GMT+0:00' + * long: e.g. 'h:mm:ss a z' => '11:42:13 AM GMT' + * medium: e.g. 'h:mm:ss a' => '11:42:13 AM' + * short: e.g. 'h:mm a' => '11:42 AM' + * The exact format is dependent on the language + * @param \DateTimeZone|null $timeZone The timezone to use + * @param \OCP\IL10N|null $l The locale to use * @return string Formatted time string * @since 8.0.0 */ @@ -83,16 +83,16 @@ interface IDateTimeFormatter { /** * Gives the relative past time of the timestamp * - * @param int|\DateTime $timestamp - * @param int|\DateTime|null $baseTimestamp Timestamp to compare $timestamp against, defaults to current time - * @param \OCP\IL10N|null $l The locale to use - * @return string Dates returned are: - * < 60 sec => seconds ago - * < 1 hour => n minutes ago - * < 1 day => n hours ago - * < 1 month => Yesterday, n days ago - * < 13 month => last month, n months ago - * >= 13 month => last year, n years ago + * @param int|\DateTime $timestamp + * @param int|\DateTime|null $baseTimestamp Timestamp to compare $timestamp against, defaults to current time + * @param \OCP\IL10N|null $l The locale to use + * @return string Dates returned are: + * < 60 sec => seconds ago + * < 1 hour => n minutes ago + * < 1 day => n hours ago + * < 1 month => Yesterday, n days ago + * < 13 month => last month, n months ago + * >= 13 month => last year, n years ago * @since 8.0.0 */ public function formatTimeSpan($timestamp, $baseTimestamp = null, ?\OCP\IL10N $l = null); @@ -101,10 +101,10 @@ interface IDateTimeFormatter { * Formats the date and time of the given timestamp * * @param int|\DateTime $timestamp - * @param string $formatDate See formatDate() for description - * @param string $formatTime See formatTime() for description - * @param \DateTimeZone|null $timeZone The timezone to use - * @param \OCP\IL10N|null $l The locale to use + * @param string $formatDate See formatDate() for description + * @param string $formatTime See formatTime() for description + * @param \DateTimeZone|null $timeZone The timezone to use + * @param \OCP\IL10N|null $l The locale to use * @return string Formatted date and time string * @since 8.0.0 */ @@ -114,11 +114,11 @@ interface IDateTimeFormatter { * Formats the date and time of the given timestamp * * @param int|\DateTime $timestamp - * @param string $formatDate See formatDate() for description - * Uses 'Today', 'Yesterday' and 'Tomorrow' when applicable - * @param string $formatTime See formatTime() for description - * @param \DateTimeZone|null $timeZone The timezone to use - * @param \OCP\IL10N|null $l The locale to use + * @param string $formatDate See formatDate() for description + * Uses 'Today', 'Yesterday' and 'Tomorrow' when applicable + * @param string $formatTime See formatTime() for description + * @param \DateTimeZone|null $timeZone The timezone to use + * @param \OCP\IL10N|null $l The locale to use * @return string Formatted relative date and time string * @since 8.0.0 */ diff --git a/lib/public/IGroupManager.php b/lib/public/IGroupManager.php index d4ea3f70722..ee88990df79 100644 --- a/lib/public/IGroupManager.php +++ b/lib/public/IGroupManager.php @@ -115,6 +115,14 @@ interface IGroupManager { public function isAdmin($userId); /** + * Checks if a userId is eligible to users administration delegation + * @param string $userId + * @return bool if delegated admin + * @since 30.0.0 + */ + public function isDelegatedAdmin(string $userId): bool; + + /** * Checks if a userId is in a group * @param string $userId * @param string $group diff --git a/lib/public/IImage.php b/lib/public/IImage.php index 5cc1187d33e..7ba08c889da 100644 --- a/lib/public/IImage.php +++ b/lib/public/IImage.php @@ -8,6 +8,8 @@ declare(strict_types=1); */ namespace OCP; +use GdImage; + /** * Class for basic image manipulation * @since 8.1.0 @@ -79,7 +81,7 @@ interface IImage { /** * @return string Returns the mimetype of the data. Returns null - * if the data is not valid. + * if the data is not valid. * @since 13.0.0 */ public function dataMimeType(): ?string; @@ -202,4 +204,22 @@ interface IImage { * @since 19.0.0 */ public function resizeCopy(int $maxSize): IImage; + + /** + * Loads an image from a string of data. + * + * @param string $str A string of image data as read from a file. + * + * @since 31.0.0 + */ + public function loadFromData(string $str): GdImage|false; + + /** + * Reads the EXIF data for an image. + * + * @param string $data EXIF data + * + * @since 31.0.0 + */ + public function readExif(string $data): void; } diff --git a/lib/public/IInitialStateService.php b/lib/public/IInitialStateService.php index 672c086b22d..6c482c2cf20 100644 --- a/lib/public/IInitialStateService.php +++ b/lib/public/IInitialStateService.php @@ -11,13 +11,13 @@ use Closure; /** * @since 16.0.0 - * @deprecated 21 Use OCP\AppFramework\Services\IInitialState or OCP\AppFramework\Services\InitialStateProvider + * @deprecated 21 Use {@see \OCP\AppFramework\Services\IInitialState} or {@see \OCP\AppFramework\Services\InitialStateProvider} * @see \OCP\AppFramework\Services\IInitialState */ interface IInitialStateService { /** * Allows an app to provide its initial state to the template system. - * Use this if you know your initial state sill be used for example if + * Use this if you know your initial state still be used for example if * you are in the render function of you controller. * * @since 16.0.0 @@ -26,7 +26,7 @@ interface IInitialStateService { * @param string $key * @param bool|int|float|string|array|\JsonSerializable $data * - * @deprecated 21 Use OCP\AppFramework\Services\IInitialState or OCP\AppFramework\Services\InitialStateProvider + * @deprecated 21 Use {@see \OCP\AppFramework\Services\IInitialState} or {@see \OCP\AppFramework\Services\InitialStateProvider} * @see \OCP\AppFramework\Services\IInitialState::provideInitialState() */ public function provideInitialState(string $appName, string $key, $data): void; @@ -44,7 +44,7 @@ interface IInitialStateService { * @param string $key * @param Closure $closure returns a primitive or an object that implements JsonSerializable * - * @deprecated 21 Use OCP\AppFramework\Services\IInitialState or OCP\AppFramework\Services\InitialStateProvider + * @deprecated 21 Use {@see \OCP\AppFramework\Services\IInitialState} or {@see \OCP\AppFramework\Services\InitialStateProvider} * @see \OCP\AppFramework\Services\IInitialState::provideLazyInitialState() */ public function provideLazyInitialState(string $appName, string $key, Closure $closure): void; diff --git a/lib/public/IL10N.php b/lib/public/IL10N.php index bbc2d535535..9f7dedc5ff2 100644 --- a/lib/public/IL10N.php +++ b/lib/public/IL10N.php @@ -52,7 +52,7 @@ interface IL10N { * @param string $type Type of localization * @param \DateTime|int|string $data parameters for this localization * @param array $options currently supports following options: - * - 'width': handed into \Punic\Calendar::formatDate as second parameter + * - 'width': handed into \Punic\Calendar::formatDate as second parameter * @return string|int|false * * Returns the localized data. diff --git a/lib/public/ILogger.php b/lib/public/ILogger.php index 0716dae07d6..54f9ffeca9a 100644 --- a/lib/public/ILogger.php +++ b/lib/public/ILogger.php @@ -9,157 +9,32 @@ declare(strict_types=1); namespace OCP; /** - * Interface ILogger - * @since 7.0.0 + * Nextcloud logging levels. + * For historical reasons the logging levels are provided as interface constants. * - * This logger interface follows the design guidelines of PSR-3 - * https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#3-psrlogloggerinterface - * @deprecated 20.0.0 use the PSR-3 logger \Psr\Log\LoggerInterface + * @since 7.0.0 + * @since 20.0.0 deprecated logging methods in favor of \Psr\Log\LoggerInterface + * @since 31.0.0 removed deprecated logging methods - the interface is kept for Nextcloud log levels */ interface ILogger { /** * @since 14.0.0 - * @deprecated 20.0.0 */ public const DEBUG = 0; /** * @since 14.0.0 - * @deprecated 20.0.0 */ public const INFO = 1; /** * @since 14.0.0 - * @deprecated 20.0.0 */ public const WARN = 2; /** * @since 14.0.0 - * @deprecated 20.0.0 */ public const ERROR = 3; /** * @since 14.0.0 - * @deprecated 20.0.0 */ public const FATAL = 4; - - /** - * System is unusable. - * - * @param string $message - * @param array $context - * @return null - * @since 7.0.0 - * @deprecated 20.0.0 use \Psr\Log\LoggerInterface::emergency - */ - public function emergency(string $message, array $context = []); - - /** - * Action must be taken immediately. - * - * @param string $message - * @param array $context - * @return null - * @since 7.0.0 - * @deprecated 20.0.0 use \Psr\Log\LoggerInterface::alert - */ - public function alert(string $message, array $context = []); - - /** - * Critical conditions. - * - * @param string $message - * @param array $context - * @return null - * @since 7.0.0 - * @deprecated 20.0.0 use \Psr\Log\LoggerInterface::critical - */ - public function critical(string $message, array $context = []); - - /** - * Runtime errors that do not require immediate action but should typically - * be logged and monitored. - * - * @param string $message - * @param array $context - * @return null - * @since 7.0.0 - * @deprecated 20.0.0 use \Psr\Log\LoggerInterface::error - */ - public function error(string $message, array $context = []); - - /** - * Exceptional occurrences that are not errors. - * - * @param string $message - * @param array $context - * @return null - * @since 7.0.0 - * @deprecated 20.0.0 use \Psr\Log\LoggerInterface::warning - */ - public function warning(string $message, array $context = []); - - /** - * Normal but significant events. - * - * @param string $message - * @param array $context - * @return null - * @since 7.0.0 - * @deprecated 20.0.0 use \Psr\Log\LoggerInterface::notice - */ - public function notice(string $message, array $context = []); - - /** - * Interesting events. - * - * @param string $message - * @param array $context - * @return null - * @since 7.0.0 - * @deprecated 20.0.0 use \Psr\Log\LoggerInterface::info - */ - public function info(string $message, array $context = []); - - /** - * Detailed debug information. - * - * @param string $message - * @param array $context - * @return null - * @since 7.0.0 - * @deprecated 20.0.0 use \Psr\Log\LoggerInterface::debug - */ - public function debug(string $message, array $context = []); - - /** - * Logs with an arbitrary level. - * - * @param int $level - * @param string $message - * @param array $context - * @return mixed - * @since 7.0.0 - * @deprecated 20.0.0 use \Psr\Log\LoggerInterface::log - */ - public function log(int $level, string $message, array $context = []); - - /** - * Logs an exception very detailed - * An additional message can we written to the log by adding it to the - * context. - * - * <code> - * $logger->logException($ex, [ - * 'message' => 'Exception during background job execution' - * ]); - * </code> - * - * @param \Exception|\Throwable $exception - * @param array $context - * @return void - * @since 8.2.0 - * @deprecated 20.0.0 use the `exception` entry in the context of any method in \Psr\Log\LoggerInterface - */ - public function logException(\Throwable $exception, array $context = []); } diff --git a/lib/public/IMemcache.php b/lib/public/IMemcache.php index fbc2719c25d..991af1a8d4f 100644 --- a/lib/public/IMemcache.php +++ b/lib/public/IMemcache.php @@ -30,6 +30,9 @@ interface IMemcache extends ICache { /** * Increase a stored number * + * If no value is stored with the key, it will behave as if a 0 was stored. + * If a non-numeric value is stored, the operation will fail and `false` is returned. + * * @param string $key * @param int $step * @return int | bool @@ -40,6 +43,9 @@ interface IMemcache extends ICache { /** * Decrease a stored number * + * If no value is stored with the key, the operation will fail and `false` is returned. + * If a non-numeric value is stored, the operation will fail and `false` is returned. + * * @param string $key * @param int $step * @return int | bool @@ -50,10 +56,12 @@ interface IMemcache extends ICache { /** * Compare and set * + * Set $key to $new only if it's current value is $new + * * @param string $key * @param mixed $old * @param mixed $new - * @return bool + * @return bool true if the value was successfully set or false if $key wasn't set to $old * @since 8.1.0 */ public function cas($key, $old, $new); @@ -61,10 +69,24 @@ interface IMemcache extends ICache { /** * Compare and delete * + * Delete $key if the stored value is equal to $old + * * @param string $key * @param mixed $old - * @return bool + * @return bool true if the value was successfully deleted or false if $key wasn't set to $old * @since 8.1.0 */ public function cad($key, $old); + + /** + * Negative compare and delete + * + * Delete $key if the stored value is not equal to $old + * + * @param string $key + * @param mixed $old + * @return bool true if the value was successfully deleted or false if $key was set to $old or is not set + * @since 30.0.0 + */ + public function ncad(string $key, mixed $old): bool; } diff --git a/lib/public/INavigationManager.php b/lib/public/INavigationManager.php index eaef1cb35ec..2bd70c04d65 100644 --- a/lib/public/INavigationManager.php +++ b/lib/public/INavigationManager.php @@ -80,4 +80,42 @@ interface INavigationManager { * @since 22.0.0 */ public function setUnreadCounter(string $id, int $unreadCounter): void; + + /** + * Get a navigation entry by id. + * + * @param string $id ID of the navigation entry + * @since 31.0.0 + */ + public function get(string $id): ?array; + + /** + * Returns the id of the user's default entry + * + * If `user` is not passed, the currently logged in user will be used + * + * @param ?IUser $user User to query default entry for + * @param bool $withFallbacks Include fallback values if no default entry was configured manually + * Before falling back to predefined default entries, + * the user defined entry order is considered and the first entry would be used as the fallback. + * @since 31.0.0 + */ + public function getDefaultEntryIdForUser(?IUser $user = null, bool $withFallbacks = true): string; + + /** + * Get the global default entries with fallbacks + * + * @return string[] The default entries + * @since 31.0.0 + */ + public function getDefaultEntryIds(): array; + + /** + * Set the global default entries with fallbacks + * + * @param string[] $ids + * @throws \InvalidArgumentException If any of the entries is not available + * @since 31.0.0 + */ + public function setDefaultEntryIds(array $ids): void; } diff --git a/lib/public/IPreview.php b/lib/public/IPreview.php index 6ab4af1b7ca..3c9eadd4577 100644 --- a/lib/public/IPreview.php +++ b/lib/public/IPreview.php @@ -71,12 +71,14 @@ interface IPreview { * @param bool $crop * @param string $mode * @param string $mimeType To force a given mimetype for the file (files_versions needs this) + * @param bool $cacheResult Whether or not to cache the preview on the filesystem. Default to true. Can be useful to set to false to limit the amount of stored previews. * @return ISimpleFile * @throws NotFoundException * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid) * @since 11.0.0 - \InvalidArgumentException was added in 12.0.0 + * @since 32.0.0 - getPreview($cacheResult) added the $cacheResult argument to the signature */ - public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null); + public function getPreview(File $file, $width = -1, $height = -1, $crop = false, $mode = IPreview::MODE_FILL, $mimeType = null, bool $cacheResult = true); /** * Returns true if the passed mime type is supported @@ -90,10 +92,12 @@ interface IPreview { * Check if a preview can be generated for a file * * @param \OCP\Files\FileInfo $file + * @param string|null $mimeType To force a given mimetype for the file * @return bool * @since 8.0.0 + * @since 32.0.0 - isAvailable($mimeType) added the $mimeType argument to the signature */ - public function isAvailable(\OCP\Files\FileInfo $file); + public function isAvailable(\OCP\Files\FileInfo $file, ?string $mimeType = null); /** * Generates previews of a file diff --git a/lib/public/IRequest.php b/lib/public/IRequest.php index 18efd7a6d16..cbac143d775 100644 --- a/lib/public/IRequest.php +++ b/lib/public/IRequest.php @@ -84,7 +84,7 @@ interface IRequest { * @since 13.0.1 * @since 28.0.0 The regex has a group matching the version number */ - public const USER_AGENT_THUNDERBIRD_ADDON = '/^Mozilla\/5\.0 \([A-Za-z ]+\) Nextcloud\-Thunderbird v([^ ]*).*$/'; + public const USER_AGENT_THUNDERBIRD_ADDON = '/^Filelink for \*cloud\/([1-9]\d*\.\d+\.\d+)$/'; /** * @since 26.0.0 @@ -108,11 +108,11 @@ interface IRequest { * @psalm-taint-source input * * @param string $key the key which you want to access in the URL Parameter - * placeholder, $_POST or $_GET array. - * The priority how they're returned is the following: - * 1. URL parameters - * 2. POST parameters - * 3. GET parameters + * placeholder, $_POST or $_GET array. + * The priority how they're returned is the following: + * 1. URL parameters + * 2. POST parameters + * 3. GET parameters * @param mixed $default If the key is not found, this value will be returned * @return mixed the content of the array * @since 6.0.0 @@ -305,4 +305,14 @@ interface IRequest { * @since 8.1.0 */ public function getServerHost(): string; + + /** + * If decoding the request content failed, throw an exception. + * Currently only \JsonException for json decoding errors, + * but in the future may throw other exceptions for other decoding issues. + * + * @throws \Exception + * @since 32.0.0 + */ + public function throwDecodingExceptionIfAny(): void; } diff --git a/lib/public/ISearch.php b/lib/public/ISearch.php deleted file mode 100644 index 4279ee580c5..00000000000 --- a/lib/public/ISearch.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -/** - * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2016 ownCloud, Inc. - * SPDX-License-Identifier: AGPL-3.0-only - */ -namespace OCP; - -/** - * Small Interface for Search - * @since 7.0.0 - * @deprecated 20.0.0 - */ -interface ISearch { - /** - * Search all providers for $query - * @param string $query - * @param string[] $inApps optionally limit results to the given apps - * @param int $page pages start at page 1 - * @param int $size - * @return array An array of OCP\Search\Result's - * @since 8.0.0 - * @deprecated 20.0.0 - */ - public function searchPaged($query, array $inApps = [], $page = 1, $size = 30); - - /** - * Register a new search provider to search with - * @param string $class class name of a OCP\Search\Provider - * @param array $options optional - * @since 7.0.0 - * @deprecated 20.0.0 - */ - public function registerProvider($class, array $options = []); - - /** - * Remove one existing search provider - * @param string $provider class name of a OCP\Search\Provider - * @since 7.0.0 - * @deprecated 20.0.0 - */ - public function removeProvider($provider); - - /** - * Remove all registered search providers - * @since 7.0.0 - * @deprecated 20.0.0 - */ - public function clearProviders(); -} diff --git a/lib/public/IServerContainer.php b/lib/public/IServerContainer.php index 74e7153f810..6d5095c92da 100644 --- a/lib/public/IServerContainer.php +++ b/lib/public/IServerContainer.php @@ -8,10 +8,6 @@ declare(strict_types=1); */ namespace OCP; -use OCP\Federation\ICloudFederationFactory; -use OCP\Federation\ICloudFederationProviderManager; -use OCP\Log\ILogFactory; -use OCP\Security\IContentSecurityPolicyManager; use Psr\Container\ContainerInterface; /** @@ -26,35 +22,6 @@ use Psr\Container\ContainerInterface; * @since 6.0.0 */ interface IServerContainer extends ContainerInterface, IContainer { - /** - * The calendar manager will act as a broker between consumers for calendar information and - * providers which actual deliver the calendar information. - * - * @return \OCP\Calendar\IManager - * @since 13.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getCalendarManager(); - - /** - * The calendar resource backend manager will act as a broker between consumers - * for calendar resource information an providers which actual deliver the room information. - * - * @return \OCP\Calendar\Resource\IBackend - * @since 14.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getCalendarResourceBackendManager(); - - /** - * The calendar room backend manager will act as a broker between consumers - * for calendar room information an providers which actual deliver the room information. - * - * @return \OCP\Calendar\Room\IBackend - * @since 14.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getCalendarRoomBackendManager(); /** * The contacts manager will act as a broker between consumers for contacts information and @@ -78,25 +45,6 @@ interface IServerContainer extends ContainerInterface, IContainer { public function getRequest(); /** - * Returns the preview manager which can create preview images for a given file - * - * @return \OCP\IPreview - * @since 6.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getPreviewManager(); - - /** - * Returns the tag manager which can get and set tags for different object types - * - * @see \OCP\ITagManager::load() - * @return \OCP\ITagManager - * @since 6.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getTagManager(); - - /** * Returns the root folder of ownCloud's data directory * * @return \OCP\Files\IRootFolder @@ -144,15 +92,6 @@ interface IServerContainer extends ContainerInterface, IContainer { public function getUserSession(); /** - * Returns the navigation manager - * - * @return \OCP\INavigationManager - * @since 6.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getNavigationManager(); - - /** * Returns the config manager * * @return \OCP\IConfig @@ -189,24 +128,6 @@ interface IServerContainer extends ContainerInterface, IContainer { public function getSecureRandom(); /** - * Returns a CredentialsManager instance - * - * @return \OCP\Security\ICredentialsManager - * @since 9.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getCredentialsManager(); - - /** - * Returns the app config manager - * - * @return \OCP\IAppConfig - * @since 7.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getAppConfig(); - - /** * @return \OCP\L10N\IFactory * @since 8.2.0 * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get @@ -238,13 +159,6 @@ interface IServerContainer extends ContainerInterface, IContainer { public function getEncryptionFilesHelper(); /** - * @return \OCP\Encryption\Keys\IStorage - * @since 8.1.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getEncryptionKeyStorage(); - - /** * Returns the URL generator * * @return \OCP\IURLGenerator @@ -299,15 +213,6 @@ interface IServerContainer extends ContainerInterface, IContainer { public function getDatabaseConnection(); /** - * Returns an avatar manager, used for avatar functionality - * - * @return \OCP\IAvatarManager - * @since 6.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getAvatarManager(); - - /** * Returns an job list for controlling background jobs * * @return \OCP\BackgroundJob\IJobList @@ -317,42 +222,6 @@ interface IServerContainer extends ContainerInterface, IContainer { public function getJobList(); /** - * Returns a logger instance - * - * @return \OCP\ILogger - * @since 8.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getLogger(); - - /** - * returns a log factory instance - * - * @return ILogFactory - * @since 14.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getLogFactory(); - - /** - * Returns a router for generating and matching urls - * - * @return \OCP\Route\IRouter - * @since 7.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getRouter(); - - /** - * Returns a search instance - * - * @return \OCP\ISearch - * @since 7.0.0 - * @deprecated 20.0.0 - */ - public function getSearch(); - - /** * Get the certificate manager * * @return \OCP\ICertificateManager @@ -362,35 +231,6 @@ interface IServerContainer extends ContainerInterface, IContainer { public function getCertificateManager(); /** - * Returns an instance of the HTTP client service - * - * @return \OCP\Http\Client\IClientService - * @since 8.1.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getHTTPClientService(); - - /** - * Get the active event logger - * - * @return \OCP\Diagnostics\IEventLogger - * @since 8.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getEventLogger(); - - /** - * Get the active query logger - * - * The returned logger only logs data when debug mode is enabled - * - * @return \OCP\Diagnostics\IQueryLogger - * @since 8.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getQueryLogger(); - - /** * Get the manager for temporary files and folders * * @return \OCP\ITempManager @@ -418,28 +258,6 @@ interface IServerContainer extends ContainerInterface, IContainer { public function getWebRoot(); /** - * @return \OCP\Files\Config\IMountProviderCollection - * @since 8.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getMountProviderCollection(); - - /** - * Get the IniWrapper - * - * @return \bantu\IniGetWrapper\IniGetWrapper - * @since 8.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getIniWrapper(); - /** - * @return \OCP\Command\IBus - * @since 8.1.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getCommandBus(); - - /** * Creates a new mailer * * @return \OCP\Mail\IMailer @@ -458,13 +276,6 @@ interface IServerContainer extends ContainerInterface, IContainer { public function getLockingProvider(); /** - * @return \OCP\Files\Mount\IMountManager - * @since 8.2.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getMountManager(); - - /** * Get the MimeTypeDetector * * @return \OCP\Files\IMimeTypeDetector @@ -492,108 +303,9 @@ interface IServerContainer extends ContainerInterface, IContainer { public function getNotificationManager(); /** - * @return \OCP\Comments\ICommentsManager - * @since 9.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getCommentsManager(); - - /** - * Returns the system-tag manager - * - * @return \OCP\SystemTag\ISystemTagManager - * - * @since 9.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getSystemTagManager(); - - /** - * Returns the system-tag object mapper - * - * @return \OCP\SystemTag\ISystemTagObjectMapper - * - * @since 9.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getSystemTagObjectMapper(); - - /** - * Returns the share manager - * - * @return \OCP\Share\IManager - * @since 9.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getShareManager(); - - /** - * @return IContentSecurityPolicyManager - * @since 9.0.0 - * @deprecated 17.0.0 Use the AddContentSecurityPolicyEvent - */ - public function getContentSecurityPolicyManager(); - - /** - * @return \OCP\IDateTimeZone - * @since 8.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getDateTimeZone(); - - /** - * @return \OCP\IDateTimeFormatter - * @since 8.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getDateTimeFormatter(); - - /** * @return \OCP\Federation\ICloudIdManager * @since 12.0.0 * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get */ public function getCloudIdManager(); - - /** - * @return \OCP\GlobalScale\IConfig - * @since 14.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getGlobalScaleConfig(); - - /** - * @return ICloudFederationFactory - * @since 14.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getCloudFederationFactory(); - - /** - * @return ICloudFederationProviderManager - * @since 14.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getCloudFederationProviderManager(); - - /** - * @return \OCP\Remote\Api\IApiFactory - * @since 13.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getRemoteApiFactory(); - - /** - * @return \OCP\Remote\IInstanceFactory - * @since 13.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getRemoteInstanceFactory(); - - /** - * @return \OCP\Files\Storage\IStorageFactory - * @since 15.0.0 - * @deprecated 20.0.0 have it injected or fetch it through \Psr\Container\ContainerInterface::get - */ - public function getStorageFactory(); } diff --git a/lib/public/ITagManager.php b/lib/public/ITagManager.php index 392cfc3cca6..8c37104828f 100644 --- a/lib/public/ITagManager.php +++ b/lib/public/ITagManager.php @@ -30,7 +30,7 @@ interface ITagManager { * @param array $defaultTags An array of default tags to be used if none are stored. * @param boolean $includeShared Whether to include tags for items shared with this user by others. - always false since 20.0.0 * @param string $userId user for which to retrieve the tags, defaults to the currently - * logged in user + * logged in user * @return \OCP\ITags * @since 6.0.0 - parameter $includeShared and $userId were added in 8.0.0 - $includeShared is always false since 20.0.0 */ diff --git a/lib/public/ITags.php b/lib/public/ITags.php index 004ff00f25e..633cec72085 100644 --- a/lib/public/ITags.php +++ b/lib/public/ITags.php @@ -76,9 +76,9 @@ interface ITags { * ] * ``` * - * @param array $objIds item ids - * @return array|false with object id as key and an array - * of tag names as value or false if an error occurred + * @param list<int> $objIds item ids + * @return array<int, list<string>>|false with object id as key and an array + * of tag names as value or false if an error occurred * @since 8.0.0 */ public function getTagsForObjects(array $objIds); @@ -104,7 +104,7 @@ interface ITags { /** * Checks whether a tag is saved for the given user, - * disregarding the ones shared with him or her. + * disregarding the ones shared with them. * * @param string $name The tag name to check for. * @param string $user The user whose tags are to be checked. @@ -136,7 +136,7 @@ interface ITags { * Add a list of new tags. * * @param string|string[] $names A string with a name or an array of strings containing - * the name(s) of the to add. + * the name(s) of the to add. * @param bool $sync When true, save the tags * @param int|null $id int Optional object id to add to this|these tag(s) * @return bool Returns false on error. diff --git a/lib/public/ITempManager.php b/lib/public/ITempManager.php index 90db91b2ab4..ac8f829f357 100644 --- a/lib/public/ITempManager.php +++ b/lib/public/ITempManager.php @@ -16,20 +16,20 @@ interface ITempManager { /** * Create a temporary file and return the path * - * @param string $postFix - * @return string + * @param string $postFix Postfix appended to the temporary file name + * * @since 8.0.0 */ - public function getTemporaryFile($postFix = ''); + public function getTemporaryFile(string $postFix = ''): string|false; /** * Create a temporary folder and return the path * - * @param string $postFix - * @return string + * @param string $postFix Postfix appended to the temporary folder name + * * @since 8.0.0 */ - public function getTemporaryFolder($postFix = ''); + public function getTemporaryFolder(string $postFix = ''): string|false; /** * Remove the temporary files and folders generated during this request diff --git a/lib/public/IURLGenerator.php b/lib/public/IURLGenerator.php index e950d82cb54..a336d18b00f 100644 --- a/lib/public/IURLGenerator.php +++ b/lib/public/IURLGenerator.php @@ -64,7 +64,7 @@ interface IURLGenerator { * @param string $appName the name of the app * @param string $file the name of the file * @param array $args array with param=>value, will be appended to the returned url - * The value of $args will be urlencoded + * The value of $args will be urlencoded * @return string the url * @since 6.0.0 */ diff --git a/lib/public/IUser.php b/lib/public/IUser.php index 2ed2d0d87b2..945e7e1602a 100644 --- a/lib/public/IUser.php +++ b/lib/public/IUser.php @@ -16,6 +16,11 @@ use InvalidArgumentException; */ interface IUser { /** + * @since 32.0.0 + */ + public const MAX_USERID_LENGTH = 64; + + /** * get the user id * * @return string @@ -50,13 +55,22 @@ interface IUser { * @return int * @since 8.0.0 */ - public function getLastLogin(); + public function getLastLogin(): int; + + /** + * Returns the timestamp of the user's first login, 0 if the user did never login, or -1 if the data is unknown (first login was on an older version) + * + * @since 31.0.0 + */ + public function getFirstLogin(): int; /** - * updates the timestamp of the most recent login of this user + * Updates the timestamp of the most recent login of this user (and first login if needed) + * + * @return bool whether this is the first login * @since 8.0.0 */ - public function updateLastLoginTimestamp(); + public function updateLastLoginTimestamp(): bool; /** * Delete the user @@ -77,6 +91,23 @@ interface IUser { public function setPassword($password, $recoveryPassword = null); /** + * Get the password hash of the user + * + * @return ?string the password hash hashed by `\OCP\Security\IHasher::hash()` + * @since 30.0.0 + */ + public function getPasswordHash(): ?string; + + /** + * Set the password hash of the user + * + * @param string $passwordHash the password hash hashed by `\OCP\Security\IHasher::hash()` + * @throws InvalidArgumentException when `$passwordHash` is not a valid hash + * @since 30.0.0 + */ + public function setPasswordHash(string $passwordHash): bool; + + /** * get the users home folder to mount * * @return string @@ -100,7 +131,7 @@ interface IUser { public function getBackend(); /** - * check if the backend allows the user to change his avatar on Personal page + * check if the backend allows the user to change their avatar on Personal page * * @return bool * @since 8.0.0 @@ -124,6 +155,13 @@ interface IUser { public function canChangeDisplayName(); /** + * Check if the backend supports changing email + * + * @since 32.0.0 + */ + public function canChangeEmail(): bool; + + /** * check if the user is enabled * * @return bool @@ -243,6 +281,15 @@ interface IUser { public function getQuota(); /** + * Get the users' quota in machine readable form. If a specific quota is set + * for the user, then the quota is returned in bytes. Otherwise the default value is returned. + * If a default setting was not set, it is return as `\OCP\Files\FileInfo::SPACE_UNLIMITED`, i.e. quota is not limited. + * + * @since 32.0.0 + */ + public function getQuotaBytes(): int|float; + + /** * set the users' quota * * @param string $quota diff --git a/lib/public/IUserManager.php b/lib/public/IUserManager.php index 851b565f617..dbd1e188bec 100644 --- a/lib/public/IUserManager.php +++ b/lib/public/IUserManager.php @@ -32,14 +32,14 @@ interface IUserManager { /** * register a user backend * - * @param \OCP\UserInterface $backend * @since 8.0.0 + * @return void */ - public function registerBackend($backend); + public function registerBackend(UserInterface $backend); /** * Get the active backends - * @return \OCP\UserInterface[] + * @return UserInterface[] * @since 8.0.0 */ public function getBackends(); @@ -47,16 +47,17 @@ interface IUserManager { /** * remove a user backend * - * @param \OCP\UserInterface $backend * @since 8.0.0 + * @return void */ - public function removeBackend($backend); + public function removeBackend(UserInterface $backend); /** * remove all user backends * @since 8.0.0 + * @return void */ - public function clearBackends() ; + public function clearBackends(); /** * get a user by user id @@ -164,6 +165,16 @@ interface IUserManager { public function countUsers(); /** + * Get how many users exists in total, whithin limit + * + * @param int $limit Limit the count to avoid resource waste. 0 to disable + * @param bool $onlyMappedUsers Count mapped users instead of all users for compatible backends + * + * @since 31.0.0 + */ + public function countUsersTotal(int $limit = 0, bool $onlyMappedUsers = false): int|false; + + /** * @param \Closure $callback * @psalm-param \Closure(\OCP\IUser):void $callback * @param string $search @@ -210,4 +221,26 @@ interface IUserManager { * @since 26.0.0 */ public function validateUserId(string $uid, bool $checkDataDirectory = false): void; + + /** + * Gets the list of users sorted by lastLogin, from most recent to least recent + * + * @param int|null $limit how many records to fetch + * @param int $offset from which offset to fetch + * @param string $search search users based on search params + * @return list<string> list of user IDs + * @since 30.0.0 + */ + public function getLastLoggedInUsers(?int $limit = null, int $offset = 0, string $search = ''): array; + + /** + * Gets the list of users. + * An iterator is returned allowing the caller to stop the iteration at any time. + * The offset argument allows the caller to continue the iteration at a specific offset. + * + * @param int $offset from which offset to fetch + * @return \Iterator<IUser> list of IUser object + * @since 32.0.0 + */ + public function getSeenUsers(int $offset = 0): \Iterator; } diff --git a/lib/public/Image.php b/lib/public/Image.php index e2565cca9a4..84d07d6a4b4 100644 --- a/lib/public/Image.php +++ b/lib/public/Image.php @@ -14,5 +14,11 @@ namespace OCP; * This class provides functions to handle images * @since 6.0.0 */ -class Image extends \OC_Image { +class Image extends \OC\Image implements \OCP\IImage { + /** + * @since 31.0.0 + */ + public function __construct() { + parent::__construct(); + } } diff --git a/lib/public/L10N/IFactory.php b/lib/public/L10N/IFactory.php index 5c2f4dcbfa4..aebd318dfad 100644 --- a/lib/public/L10N/IFactory.php +++ b/lib/public/L10N/IFactory.php @@ -104,6 +104,15 @@ interface IFactory { public function localeExists($locale); /** + * Return the language direction + * + * @param string $language + * @return 'ltr'|'rtl' + * @since 31.0.0 + */ + public function getLanguageDirection(string $language): string; + + /** * iterate through language settings (if provided) in this order: * 1. returns the forced language or: * 2. if applicable, the trunk of 1 (e.g. "fu" instead of "fu_BAR" diff --git a/lib/public/L10N/ILanguageIterator.php b/lib/public/L10N/ILanguageIterator.php index cba0feefdcf..27f850d4235 100644 --- a/lib/public/L10N/ILanguageIterator.php +++ b/lib/public/L10N/ILanguageIterator.php @@ -21,7 +21,7 @@ namespace OCP\L10N; * if settings are not present or truncating is not applicable, the iterator * skips to the next valid item itself * - * + * @template-extends \Iterator<int, string> * @since 14.0.0 */ interface ILanguageIterator extends \Iterator { @@ -36,22 +36,20 @@ interface ILanguageIterator extends \Iterator { * Move forward to next element * * @since 14.0.0 - * @return void */ - #[\ReturnTypeWillChange] - public function next(); + public function next(): void; /** * Return the key of the current element * * @since 14.0.0 */ - public function key():int; + public function key(): int; /** * Checks if current position is valid * * @since 14.0.0 */ - public function valid():bool; + public function valid(): bool; } diff --git a/lib/public/LDAP/IDeletionFlagSupport.php b/lib/public/LDAP/IDeletionFlagSupport.php index 89615c67307..1ae38e1a5d1 100644 --- a/lib/public/LDAP/IDeletionFlagSupport.php +++ b/lib/public/LDAP/IDeletionFlagSupport.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -17,7 +18,7 @@ interface IDeletionFlagSupport { * @since 11.0.0 */ public function flagRecord($uid); - + /** * Unflag record for deletion. * @param string $uid user id diff --git a/lib/public/LDAP/ILDAPProvider.php b/lib/public/LDAP/ILDAPProvider.php index 584b554aa81..7e1f27eb368 100644 --- a/lib/public/LDAP/ILDAPProvider.php +++ b/lib/public/LDAP/ILDAPProvider.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -46,8 +47,8 @@ interface ILDAPProvider { /** * Sanitize a DN received from the LDAP server. - * @param array $dn the DN in question - * @return array the sanitized DN + * @param array|string $dn the DN in question + * @return array|string the sanitized DN * @since 11.0.0 */ public function sanitizeDN($dn); diff --git a/lib/public/LDAP/ILDAPProviderFactory.php b/lib/public/LDAP/ILDAPProviderFactory.php index f175a3abfd0..720027ce012 100644 --- a/lib/public/LDAP/ILDAPProviderFactory.php +++ b/lib/public/LDAP/ILDAPProviderFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Lock/LockedException.php b/lib/public/Lock/LockedException.php index ce41d2d7242..799886a2dbb 100644 --- a/lib/public/Lock/LockedException.php +++ b/lib/public/Lock/LockedException.php @@ -24,6 +24,8 @@ class LockedException extends \Exception { /** @var string|null */ private $existingLock; + private ?string $readablePath; + /** * LockedException constructor. * @@ -34,6 +36,7 @@ class LockedException extends \Exception { * @since 8.1.0 */ public function __construct(string $path, ?\Exception $previous = null, ?string $existingLock = null, ?string $readablePath = null) { + $this->readablePath = $readablePath; if ($readablePath) { $message = "\"$path\"(\"$readablePath\") is locked"; } else { @@ -62,4 +65,13 @@ class LockedException extends \Exception { public function getExistingLock(): ?string { return $this->existingLock; } + + /** + * @return ?string + * @since 32.0.0 + */ + public function getReadablePath(): ?string { + return $this->readablePath; + } + } diff --git a/lib/public/Lockdown/ILockdownManager.php b/lib/public/Lockdown/ILockdownManager.php index 4ae846ae8da..fe8adaecf91 100644 --- a/lib/public/Lockdown/ILockdownManager.php +++ b/lib/public/Lockdown/ILockdownManager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Log/IFileBased.php b/lib/public/Log/IFileBased.php index e3ea25fad09..ace10ee1b68 100644 --- a/lib/public/Log/IFileBased.php +++ b/lib/public/Log/IFileBased.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Log/ILogFactory.php b/lib/public/Log/ILogFactory.php index 49b56ff102c..db7adca2192 100644 --- a/lib/public/Log/ILogFactory.php +++ b/lib/public/Log/ILogFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Log/IWriter.php b/lib/public/Log/IWriter.php index 8d2b438a0fe..2fcbc094881 100644 --- a/lib/public/Log/IWriter.php +++ b/lib/public/Log/IWriter.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Log/RotationTrait.php b/lib/public/Log/RotationTrait.php index 1481ed71833..73b3b16b665 100644 --- a/lib/public/Log/RotationTrait.php +++ b/lib/public/Log/RotationTrait.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -29,7 +30,7 @@ trait RotationTrait { * @since 14.0.0 */ protected function rotate():string { - $rotatedFile = $this->filePath.'.1'; + $rotatedFile = $this->filePath . '.1'; rename($this->filePath, $rotatedFile); return $rotatedFile; } diff --git a/lib/public/Mail/IEMailTemplate.php b/lib/public/Mail/IEMailTemplate.php index 71a02eff19e..66379abd8ae 100644 --- a/lib/public/Mail/IEMailTemplate.php +++ b/lib/public/Mail/IEMailTemplate.php @@ -57,7 +57,7 @@ interface IEMailTemplate { * * @param string $title * @param string|bool $plainTitle Title that is used in the plain text email - * if empty the $title is used, if false none will be used + * if empty the $title is used, if false none will be used * * @since 12.0.0 */ @@ -68,7 +68,7 @@ interface IEMailTemplate { * * @param string $text; Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email * @param string|bool $plainText Text that is used in the plain text email - * if empty the $text is used, if false none will be used + * if empty the $text is used, if false none will be used * * @since 12.0.0 */ @@ -81,9 +81,9 @@ interface IEMailTemplate { * @param string $metaInfo; Note: When $plainMetaInfo falls back to this, HTML is automatically escaped in the HTML email * @param string $icon Absolute path, must be 16*16 pixels * @param string|bool $plainText Text that is used in the plain text email - * if empty the $text is used, if false none will be used + * if empty the $text is used, if false none will be used * @param string|bool $plainMetaInfo Meta info that is used in the plain text email - * if empty the $metaInfo is used, if false none will be used + * if empty the $metaInfo is used, if false none will be used * @param integer plainIndent If > 0, Indent plainText by this amount. * @since 12.0.0 */ @@ -109,7 +109,7 @@ interface IEMailTemplate { * @param string $text Text of button; Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email * @param string $url URL of button * @param string|false $plainText Text of button in plain text version - * if empty the $text is used, if false none will be used + * if empty the $text is used, if false none will be used * * @since 12.0.0 */ diff --git a/lib/public/Mail/IMailer.php b/lib/public/Mail/IMailer.php index 93efdce1a2d..277f7863184 100644 --- a/lib/public/Mail/IMailer.php +++ b/lib/public/Mail/IMailer.php @@ -14,7 +14,7 @@ namespace OCP\Mail; * * Example usage: * - * $mailer = \OC::$server->get(\OCP\Mail\IMailer::class); + * $mailer = \OCP\Server::get(\OCP\Mail\IMailer::class); * $message = $mailer->createMessage(); * $message->setSubject('Your Subject'); * $message->setFrom(['cloud@domain.org' => 'Nextcloud Notifier']); @@ -69,9 +69,9 @@ interface IMailer { * * @param IMessage $message Message to send * @return string[] Array with failed recipients. Be aware that this depends on the used mail backend and - * therefore should be considered + * therefore should be considered * @throws \Exception In case it was not possible to send the message. (for example if an invalid mail address - * has been supplied.) + * has been supplied.) * @since 8.1.0 */ public function send(IMessage $message): array; diff --git a/lib/public/Mail/Provider/Address.php b/lib/public/Mail/Provider/Address.php new file mode 100644 index 00000000000..751fb99d930 --- /dev/null +++ b/lib/public/Mail/Provider/Address.php @@ -0,0 +1,85 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider; + +/** + * Mail Address Object + * + * This object is used to define the address and label of a email address + * + * @since 30.0.0 + * + */ +class Address implements \OCP\Mail\Provider\IAddress { + + /** + * initialize the mail address object + * + * @since 30.0.0 + * + * @param string|null $address mail address (e.g test@example.com) + * @param string|null $label mail address label/name + */ + public function __construct( + protected ?string $address = null, + protected ?string $label = null, + ) { + } + + /** + * sets the mail address + * + * @since 30.0.0 + * + * @param string $value mail address (e.g. test@example.com) + * + * @return self return this object for command chaining + */ + public function setAddress(string $value): self { + $this->address = $value; + return $this; + } + + /** + * gets the mail address + * + * @since 30.0.0 + * + * @return string|null returns the mail address or null if one is not set + */ + public function getAddress(): ?string { + return $this->address; + } + + /** + * sets the mail address label/name + * + * @since 30.0.0 + * + * @param string $value mail address label/name + * + * @return self return this object for command chaining + */ + public function setLabel(string $value): self { + $this->label = $value; + return $this; + } + + /** + * gets the mail address label/name + * + * @since 30.0.0 + * + * @return string|null returns the mail address label/name or null if one is not set + */ + public function getLabel(): ?string { + return $this->label; + } + +} diff --git a/lib/public/Mail/Provider/Attachment.php b/lib/public/Mail/Provider/Attachment.php new file mode 100644 index 00000000000..af7340a3ccf --- /dev/null +++ b/lib/public/Mail/Provider/Attachment.php @@ -0,0 +1,139 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider; + +/** + * Mail Attachment Object + * + * This object is used to define the parameters of a mail attachment + * + * @since 30.0.0 + * + */ +class Attachment implements \OCP\Mail\Provider\IAttachment { + + /** + * initialize the mail attachment object + * + * @since 30.0.0 + * + * @param string|null $contents binary contents of file + * @param string|null $name file name (e.g example.txt) + * @param string|null $type mime type (e.g. text/plain) + * @param bool $embedded embedded status of the attachment, default is false + */ + public function __construct( + protected ?string $contents, + protected ?string $name, + protected ?string $type, + protected bool $embedded = false, + ) { + } + + /** + * sets the attachment file name + * + * @since 30.0.0 + * + * @param string $value file name (e.g example.txt) + * + * @return self return this object for command chaining + */ + public function setName(string $value): self { + $this->name = $value; + return $this; + } + + /** + * gets the attachment file name + * + * @since 30.0.0 + * + * @return string | null returns the attachment file name or null if not set + */ + public function getName(): ?string { + return $this->name; + } + + /** + * sets the attachment mime type + * + * @since 30.0.0 + * + * @param string $value mime type (e.g. text/plain) + * + * @return self return this object for command chaining + */ + public function setType(string $value): self { + $this->type = $value; + return $this; + } + + /** + * gets the attachment mime type + * + * @since 30.0.0 + * + * @return string | null returns the attachment mime type or null if not set + */ + public function getType(): ?string { + return $this->type; + } + + /** + * sets the attachment contents (actual data) + * + * @since 30.0.0 + * + * @param string $value binary contents of file + * + * @return self return this object for command chaining + */ + public function setContents(string $value): self { + $this->contents = $value; + return $this; + } + + /** + * gets the attachment contents (actual data) + * + * @since 30.0.0 + * + * @return string | null returns the attachment contents or null if not set + */ + public function getContents(): ?string { + return $this->contents; + } + + /** + * sets the embedded status of the attachment + * + * @since 30.0.0 + * + * @param bool $value true - embedded / false - not embedded + * + * @return self return this object for command chaining + */ + public function setEmbedded(bool $value): self { + $this->embedded = $value; + return $this; + } + + /** + * gets the embedded status of the attachment + * + * @since 30.0.0 + * + * @return bool embedded status of the attachment + */ + public function getEmbedded(): bool { + return $this->embedded; + } + +} diff --git a/lib/public/Mail/Provider/Exception/Exception.php b/lib/public/Mail/Provider/Exception/Exception.php new file mode 100644 index 00000000000..7514c72a869 --- /dev/null +++ b/lib/public/Mail/Provider/Exception/Exception.php @@ -0,0 +1,18 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider\Exception; + +/** + * Mail Provider Base Exception + * + * @since 30.0.0 + */ +class Exception extends \Exception { + +} diff --git a/lib/public/Mail/Provider/Exception/SendException.php b/lib/public/Mail/Provider/Exception/SendException.php new file mode 100644 index 00000000000..fba0903cbb3 --- /dev/null +++ b/lib/public/Mail/Provider/Exception/SendException.php @@ -0,0 +1,18 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider\Exception; + +/** + * Mail Provider Send Exception + * + * @since 30.0.0 + */ +class SendException extends Exception { + +} diff --git a/lib/public/Mail/Provider/IAddress.php b/lib/public/Mail/Provider/IAddress.php new file mode 100644 index 00000000000..47bb9148968 --- /dev/null +++ b/lib/public/Mail/Provider/IAddress.php @@ -0,0 +1,61 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider; + +/** + * Mail Address Interface + * + * This interface is a base requirement of methods and functionality used to construct a mail address object + * + * @since 30.0.0 + * + */ +interface IAddress { + + /** + * sets the mail address + * + * @since 30.0.0 + * + * @param string $value mail address (test@example.com) + * + * @return self return this object for command chaining + */ + public function setAddress(string $value): self; + + /** + * gets the mail address + * + * @since 30.0.0 + * + * @return string returns the mail address + */ + public function getAddress(): ?string; + + /** + * sets the mail address label/name + * + * @since 30.0.0 + * + * @param string $value mail address label/name + * + * @return self return this object for command chaining + */ + public function setLabel(string $value): self; + + /** + * gets the mail address label/name + * + * @since 30.0.0 + * + * @return string returns the mail address label/name + */ + public function getLabel(): ?string; + +} diff --git a/lib/public/Mail/Provider/IAttachment.php b/lib/public/Mail/Provider/IAttachment.php new file mode 100644 index 00000000000..ff266994919 --- /dev/null +++ b/lib/public/Mail/Provider/IAttachment.php @@ -0,0 +1,101 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider; + +/** + * Mail Attachment Interface + * + * This interface is used for defining individual attachments that are attached to a message + * + * @since 30.0.0 + * + */ +interface IAttachment { + + /** + * sets the attachment file name + * + * @since 30.0.0 + * + * @param string $value file name (e.g example.txt) + * + * @return self return this object for command chaining + */ + public function setName(string $value): self; + + /** + * gets the attachment file name + * + * @since 30.0.0 + * + * @return string | null returns the attachment file name or null if one is not set + */ + public function getName(): ?string; + + /** + * sets the attachment mime type + * + * @since 30.0.0 + * + * @param string $value mime type (e.g. text/plain) + * + * @return self return this object for command chaining + */ + public function setType(string $value): self; + + /** + * gets the attachment mime type + * + * @since 30.0.0 + * + * @return string | null returns the attachment mime type or null if not set + */ + public function getType(): ?string; + + /** + * sets the attachment contents (actual data) + * + * @since 30.0.0 + * + * @param string $value binary contents of file + * + * @return self return this object for command chaining + */ + public function setContents(string $value): self; + + /** + * gets the attachment contents (actual data) + * + * @since 30.0.0 + * + * @return string | null returns the attachment contents or null if not set + */ + public function getContents(): ?string; + + /** + * sets the embedded status of the attachment + * + * @since 30.0.0 + * + * @param bool $value true - embedded / false - not embedded + * + * @return self return this object for command chaining + */ + public function setEmbedded(bool $value): self; + + /** + * gets the embedded status of the attachment + * + * @since 30.0.0 + * + * @return bool embedded status of the attachment + */ + public function getEmbedded(): bool; + +} diff --git a/lib/public/Mail/Provider/IManager.php b/lib/public/Mail/Provider/IManager.php new file mode 100644 index 00000000000..b55fed3f26f --- /dev/null +++ b/lib/public/Mail/Provider/IManager.php @@ -0,0 +1,106 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider; + +/** + * Mail Provider Manager Interface + * + * This interface is a base requirement of methods and functionality used to construct a mail provider manager object + * + * @since 30.0.0 + * + */ +interface IManager { + + /** + * determine if any mail providers are registered + * + * @since 30.0.0 + * + * @return bool + */ + public function has(): bool; + + /** + * retrieve a count of how many mail providers are registered + * + * @since 30.0.0 + * + * @return int + */ + public function count(): int; + + /** + * retrieve which mail providers are registered + * + * @since 30.0.0 + * + * @return array<string,String> collection of provider id and label ['jmap' => 'JMap Connector'] + */ + public function types(): array; + + /** + * retrieve all registered mail providers + * + * @since 30.0.0 + * + * @return array<string,IProvider> collection of provider id and object ['jmap' => IProviderObject] + */ + public function providers(): array; + + /** + * retrieve a provider with a specific id + * + * @since 30.0.0 + * + * @param string $providerId provider id + * + * @return IProvider|null + */ + public function findProviderById(string $providerId): ?IProvider; + + /** + * retrieve all services for all registered mail providers + * + * @since 30.0.0 + * + * @param string $userId user id + * + * @return array<string,array<string,IService>> collection of provider id, service id and object ['jmap' => ['Service1' => IServiceObject]] + */ + public function services(string $userId): array; + + /** + * retrieve a service with a specific id + * + * @since 30.0.0 + * + * @param string $userId user id + * @param string $serviceId service id + * @param string $providerId provider id + * + * @return IService|null returns service object or null if none found + */ + public function findServiceById(string $userId, string $serviceId, ?string $providerId = null): ?IService; + + /** + * retrieve a service for a specific mail address + * returns first service with specific primary address + * + * @since 30.0.0 + * + * @param string $userId user id + * @param string $address mail address (e.g. test@example.com) + * @param string $providerId provider id + * + * @return IService|null returns service object or null if none found + */ + public function findServiceByAddress(string $userId, string $address, ?string $providerId = null): ?IService; + +} diff --git a/lib/public/Mail/Provider/IMessage.php b/lib/public/Mail/Provider/IMessage.php new file mode 100644 index 00000000000..599d9b0566d --- /dev/null +++ b/lib/public/Mail/Provider/IMessage.php @@ -0,0 +1,232 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider; + +/** + * Mail Message Interface + * + * This interface is a base requirement of methods and functionality used to construct a mail message object + * + * @since 30.0.0 + * + */ +interface IMessage { + + /** + * arbitrary unique text string identifying this message + * + * @since 30.0.0 + * + * @return string id of this message + */ + public function id(): string; + + /** + * sets the sender of this message + * + * @since 30.0.0 + * + * @param IAddress $value sender's mail address object + * + * @return self return this object for command chaining + */ + public function setFrom(IAddress $value): self; + + /** + * gets the sender of this message + * + * @since 30.0.0 + * + * @return IAddress|null sender's mail address object + */ + public function getFrom(): ?IAddress; + + /** + * sets the sender's reply to address of this message + * + * @since 30.0.0 + * + * @param IAddress $value senders's reply to mail address object + * + * @return self return this object for command chaining + */ + public function setReplyTo(IAddress $value): self; + + /** + * gets the sender's reply to address of this message + * + * @since 30.0.0 + * + * @return IAddress|null sender's reply to mail address object + */ + public function getReplyTo(): ?IAddress; + + /** + * sets the recipient(s) of this message + * + * @since 30.0.0 + * + * @param IAddress ...$value collection of or one or more mail address objects + * + * @return self return this object for command chaining + */ + public function setTo(IAddress ...$value): self; + + /** + * gets the recipient(s) of this message + * + * @since 30.0.0 + * + * @return array<int,IAddress> collection of all recipient mail address objects + */ + public function getTo(): array; + + /** + * sets the copy to recipient(s) of this message + * + * @since 30.0.0 + * + * @param IAddress ...$value collection of or one or more mail address objects + * + * @return self return this object for command chaining + */ + public function setCc(IAddress ...$value): self; + + /** + * gets the copy to recipient(s) of this message + * + * @since 30.0.0 + * + * @return array<int,IAddress> collection of all copied recipient mail address objects + */ + public function getCc(): array; + + /** + * sets the blind copy to recipient(s) of this message + * + * @since 30.0.0 + * + * @param IAddress ...$value collection of or one or more mail address objects + * + * @return self return this object for command chaining + */ + public function setBcc(IAddress ...$value): self; + + /** + * gets the blind copy to recipient(s) of this message + * + * @since 30.0.0 + * + * @return array<int,IAddress> collection of all blind copied recipient mail address objects + */ + public function getBcc(): array; + + /** + * sets the subject of this message + * + * @since 30.0.0 + * + * @param string $value subject of mail message + * + * @return self return this object for command chaining + */ + public function setSubject(string $value): self; + + /** + * gets the subject of this message + * + * @since 30.0.0 + * + * @return string|null subject of message or null if one is not set + */ + public function getSubject(): ?string; + + /** + * sets the plain text or html body of this message + * + * @since 30.0.0 + * + * @param string $value text or html body of message + * @param bool $html html flag - true for html + * + * @return self return this object for command chaining + */ + public function setBody(string $value, bool $html): self; + + /** + * gets either the html or plain text body of this message + * + * html body will be returned over plain text if html body exists + * + * @since 30.0.0 + * + * @return string|null html/plain body of this message or null if one is not set + */ + public function getBody(): ?string; + + /** + * sets the html body of this message + * + * @since 30.0.0 + * + * @param string $value html body of message + * + * @return self return this object for command chaining + */ + public function setBodyHtml(string $value): self; + + /** + * gets the html body of this message + * + * @since 30.0.0 + * + * @return string|null html body of this message or null if one is not set + */ + public function getBodyHtml(): ?string; + + /** + * sets the plain text body of this message + * + * @since 30.0.0 + * + * @param string $value plain text body of message + * + * @return self return this object for command chaining + */ + public function setBodyPlain(string $value): self; + + /** + * gets the plain text body of this message + * + * @since 30.0.0 + * + * @return string|null plain text body of this message or null if one is not set + */ + public function getBodyPlain(): ?string; + + /** + * sets the attachments of this message + * + * @since 30.0.0 + * + * @param IAttachment ...$value collection of or one or more mail attachment objects + * + * @return self return this object for command chaining + */ + public function setAttachments(IAttachment ...$value): self; + + /** + * gets the attachments of this message + * + * @since 30.0.0 + * + * @return array<int,IAttachment> collection of all mail attachment objects + */ + public function getAttachments(): array; +} diff --git a/lib/public/Mail/Provider/IMessageSend.php b/lib/public/Mail/Provider/IMessageSend.php new file mode 100644 index 00000000000..fe1b2884452 --- /dev/null +++ b/lib/public/Mail/Provider/IMessageSend.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider; + +/** + * Mail Message Send Interface + * + * This interface is the required set of methods and functionality used to extend IService with message sending functionality + * + * @since 30.0.0 + * + */ +interface IMessageSend { + + /** + * send an outbound message + * + * @since 30.0.0 + * + * @param IMessage $message mail message object with all required parameters to send a message + * @param array $options array of options reserved for future use + * + * @throws \OCP\Mail\Provider\Exception\SendException on failure, check message for reason + */ + public function sendMessage(IMessage $message, array $options = []): void; + +} diff --git a/lib/public/Mail/Provider/IProvider.php b/lib/public/Mail/Provider/IProvider.php new file mode 100644 index 00000000000..d89022d7cbf --- /dev/null +++ b/lib/public/Mail/Provider/IProvider.php @@ -0,0 +1,94 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider; + +/** + * Mail Provider Interface + * + * This interface is a base requirement of methods and functionality used to construct a mail provider object + * + * @since 30.0.0 + * + */ +interface IProvider { + + /** + * arbitrary unique text string identifying this provider + * + * @since 30.0.0 + * + * @return string id of this provider (e.g. UUID or 'IMAP/SMTP' or anything else) + */ + public function id(): string; + + /** + * localized human friendly name of this provider + * + * @since 30.0.0 + * + * @return string label/name of this provider (e.g. Plain Old IMAP/SMTP) + */ + public function label(): string; + + /** + * determine if any services are configured for a specific user + * + * @since 30.0.0 + * + * @param string $userId user id + * + * @return bool true if any services are configure for the user + */ + public function hasServices(string $userId): bool; + + /** + * retrieve collection of services for a specific user + * + * @param string $userId user id + * + * @since 30.0.0 + * + * @return array<string,IService> collection of service id and object ['1' => IServiceObject] + */ + public function listServices(string $userId): array; + + /** + * retrieve a service with a specific id + * + * @since 30.0.0 + * + * @param string $userId user id + * @param string $serviceId service id + * + * @return IService|null returns service object or null if none found + */ + public function findServiceById(string $userId, string $serviceId): ?IService; + + /** + * retrieve a service for a specific mail address + * + * @since 30.0.0 + * + * @param string $userId user id + * @param string $address mail address (e.g. test@example.com) + * + * @return IService|null returns service object or null if none found + */ + public function findServiceByAddress(string $userId, string $address): ?IService; + + /** + * construct a new empty service object + * + * @since 30.0.0 + * + * @return IService blank service object + */ + public function initiateService(): IService; + +} diff --git a/lib/public/Mail/Provider/IService.php b/lib/public/Mail/Provider/IService.php new file mode 100644 index 00000000000..e0bb5161aa6 --- /dev/null +++ b/lib/public/Mail/Provider/IService.php @@ -0,0 +1,119 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider; + +/** + * Mail Service Interface + * + * This interface is a base requirement of methods and functionality used to construct a mail service object + * + * @since 30.0.0 + * + */ +interface IService { + + /** + * arbitrary unique text string identifying this service + * + * @since 30.0.0 + * + * @return string id of this service (e.g. 1 or service1 or anything else) + */ + public function id(): string; + + /** + * checks if a service is able of performing an specific action + * + * @since 30.0.0 + * + * @param string $value required ability e.g. 'MessageSend' + * + * @return bool true/false if ability is supplied and found in collection + */ + public function capable(string $value): bool; + + /** + * retrieves a collection of what actions a service can perfrom + * + * @since 30.0.0 + * + * @return array collection of abilities otherwise empty collection + */ + public function capabilities(): array; + + /** + * gets the localized human frendly name of this service + * + * @since 30.0.0 + * + * @return string label/name of service (e.g. ACME Company Mail Service) + */ + public function getLabel(): string; + + /** + * sets the localized human frendly name of this service + * + * @since 30.0.0 + * + * @param string $value label/name of service (e.g. ACME Company Mail Service) + * + * @return self return this object for command chaining + */ + public function setLabel(string $value): self; + + /** + * gets the primary mailing address for this service + * + * @since 30.0.0 + * + * @return IAddress mail address object + */ + public function getPrimaryAddress(): IAddress; + + /** + * sets the primary mailing address for this service + * + * @since 30.0.0 + * + * @param IAddress $value mail address object + * + * @return self return this object for command chaining + */ + public function setPrimaryAddress(IAddress $value): self; + + /** + * gets the secondary mailing addresses (aliases) collection for this service + * + * @since 30.0.0 + * + * @return array<int, IAddress> collection of mail address objects + */ + public function getSecondaryAddresses(): array; + + /** + * sets the secondary mailing addresses (aliases) for this service + * + * @since 30.0.0 + * + * @param IAddress ...$value collection of one or more mail address objects + * + * @return self return this object for command chaining + */ + public function setSecondaryAddresses(IAddress ...$value): self; + + /** + * construct a new empty message object + * + * @since 30.0.0 + * + * @return IMessage blank message object + */ + public function initiateMessage(): IMessage; + +} diff --git a/lib/public/Mail/Provider/Message.php b/lib/public/Mail/Provider/Message.php new file mode 100644 index 00000000000..f7a21c05ed3 --- /dev/null +++ b/lib/public/Mail/Provider/Message.php @@ -0,0 +1,338 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Mail\Provider; + +/** + * Mail Message Object + * + * This object is used to define a mail message that can be used to transfer data to a provider + * + * @since 30.0.0 + * + */ +class Message implements \OCP\Mail\Provider\IMessage { + + /** + * initialize the mail message object + * + * @since 30.0.0 + * + * @param array $data message data array + */ + public function __construct( + protected array $data = [], + ) { + } + + /** + * arbitrary unique text string identifying this message + * + * @since 30.0.0 + * + * @return string id of this message + */ + public function id(): string { + // return id of message + return (isset($this->data['id'])) ? $this->data['id'] : ''; + } + + /** + * sets the sender of this message + * + * @since 30.0.0 + * + * @param IAddress $value sender's mail address object + * + * @return self return this object for command chaining + */ + public function setFrom(IAddress $value): self { + // create or update field in data store with value + $this->data['from'] = $value; + // return this object for command chaining + return $this; + } + + /** + * gets the sender of this message + * + * @since 30.0.0 + * + * @return IAddress|null sender's mail address object + */ + public function getFrom(): ?IAddress { + // evaluate if data store field exists and return value(s) or null otherwise + return (isset($this->data['from'])) ? $this->data['from'] : null; + } + + /** + * sets the sender's reply to address of this message + * + * @since 30.0.0 + * + * @param IAddress $value senders's reply to mail address object + * + * @return self return this object for command chaining + */ + public function setReplyTo(IAddress $value): self { + // create or update field in data store with value + $this->data['replyTo'] = $value; + // return this object for command chaining + return $this; + } + + /** + * gets the sender's reply to address of this message + * + * @since 30.0.0 + * + * @return IAddress|null sender's reply to mail address object + */ + public function getReplyTo(): ?IAddress { + // evaluate if data store field exists and return value(s) or null otherwise + return (isset($this->data['replyTo'])) ? $this->data['replyTo'] : null; + } + + /** + * sets the recipient(s) of this message + * + * @since 30.0.0 + * + * @param IAddress ...$value collection of or one or more mail address objects + * + * @return self return this object for command chaining + */ + public function setTo(IAddress ...$value): self { + // create or update field in data store with value + $this->data['to'] = $value; + // return this object for command chaining + return $this; + } + + /** + * gets the recipient(s) of this message + * + * @since 30.0.0 + * + * @return array<int,IAddress> collection of all recipient mail address objects + */ + public function getTo(): array { + // evaluate if data store field exists and return value(s) or empty collection + return (isset($this->data['to'])) ? $this->data['to'] : []; + } + + /** + * sets the copy to recipient(s) of this message + * + * @since 30.0.0 + * + * @param IAddress ...$value collection of or one or more mail address objects + * + * @return self return this object for command chaining + */ + public function setCc(IAddress ...$value): self { + // create or update field in data store with value + $this->data['cc'] = $value; + // return this object for command chaining + return $this; + } + + /** + * gets the copy to recipient(s) of this message + * + * @since 30.0.0 + * + * @return array<int,IAddress> collection of all copied recipient mail address objects + */ + public function getCc(): array { + // evaluate if data store field exists and return value(s) or empty collection + return (isset($this->data['cc'])) ? $this->data['cc'] : []; + } + + /** + * sets the blind copy to recipient(s) of this message + * + * @since 30.0.0 + * + * @param IAddress ...$value collection of or one or more mail address objects + * + * @return self return this object for command chaining + */ + public function setBcc(IAddress ...$value): self { + // create or update field in data store with value + $this->data['bcc'] = $value; + // return this object for command chaining + return $this; + } + + /** + * gets the blind copy to recipient(s) of this message + * + * @since 30.0.0 + * + * @return array<int,IAddress> collection of all blind copied recipient mail address objects + */ + public function getBcc(): array { + // evaluate if data store field exists and return value(s) or empty collection + return (isset($this->data['bcc'])) ? $this->data['bcc'] : []; + } + + /** + * sets the subject of this message + * + * @since 30.0.0 + * + * @param string $value subject of mail message + * + * @return self return this object for command chaining + */ + public function setSubject(string $value): self { + // create or update field in data store with value + $this->data['subject'] = $value; + // return this object for command chaining + return $this; + } + + /** + * gets the subject of this message + * + * @since 30.0.0 + * + * @return string|null subject of message or null if one is not set + */ + public function getSubject(): ?string { + // evaluate if data store field exists and return value(s) or null otherwise + return (isset($this->data['subject'])) ? $this->data['subject'] : null; + } + + /** + * sets the plain text or html body of this message + * + * @since 30.0.0 + * + * @param string $value text or html body of message + * @param bool $html html flag - true for html + * + * @return self return this object for command chaining + */ + public function setBody(string $value, bool $html = false): self { + // evaluate html flag and create or update appropriate field in data store with value + if ($html) { + $this->data['bodyHtml'] = $value; + } else { + $this->data['bodyPlain'] = $value; + } + // return this object for command chaining + return $this; + } + + /** + * gets either the html or plain text body of this message + * + * html body will be returned over plain text if html body exists + * + * @since 30.0.0 + * + * @return string|null html/plain body of this message or null if one is not set + */ + public function getBody(): ?string { + // evaluate if data store field(s) exists and return value + if (isset($this->data['bodyHtml'])) { + return $this->data['bodyHtml']; + } elseif (isset($this->data['bodyPlain'])) { + return $this->data['bodyPlain']; + } + // return null if data fields did not exist in data store + return null; + } + + /** + * sets the html body of this message + * + * @since 30.0.0 + * + * @param string $value html body of message + * + * @return self return this object for command chaining + */ + public function setBodyHtml(string $value): self { + // create or update field in data store with value + $this->data['bodyHtml'] = $value; + // return this object for command chaining + return $this; + } + + /** + * gets the html body of this message + * + * @since 30.0.0 + * + * @return string|null html body of this message or null if one is not set + */ + public function getBodyHtml(): ?string { + // evaluate if data store field exists and return value(s) or null otherwise + return (isset($this->data['bodyHtml'])) ? $this->data['bodyHtml'] : null; + } + + /** + * sets the plain text body of this message + * + * @since 30.0.0 + * + * @param string $value plain text body of message + * + * @return self return this object for command chaining + */ + public function setBodyPlain(string $value): self { + // create or update field in data store with value + $this->data['bodyPlain'] = $value; + // return this object for command chaining + return $this; + } + + /** + * gets the plain text body of this message + * + * @since 30.0.0 + * + * @return string|null plain text body of this message or null if one is not set + */ + public function getBodyPlain(): ?string { + // evaluate if data store field exists and return value(s) or null otherwise + return (isset($this->data['bodyPlain'])) ? $this->data['bodyPlain'] : null; + } + + /** + * sets the attachments of this message + * + * @since 30.0.0 + * + * @param IAttachment ...$value collection of or one or more mail attachment objects + * + * @return self return this object for command chaining + */ + public function setAttachments(IAttachment ...$value): self { + // create or update field in data store with value + $this->data['attachments'] = $value; + // return this object for command chaining + return $this; + } + + /** + * gets the attachments of this message + * + * @since 30.0.0 + * + * @return array<int,IAttachment> collection of all mail attachment objects + */ + public function getAttachments(): array { + // evaluate if data store field exists and return value(s) or null otherwise + return (isset($this->data['attachments'])) ? $this->data['attachments'] : []; + } + +} diff --git a/lib/public/Migration/Attributes/AddColumn.php b/lib/public/Migration/Attributes/AddColumn.php new file mode 100644 index 00000000000..d84a0c1f60c --- /dev/null +++ b/lib/public/Migration/Attributes/AddColumn.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +/** + * attribute on new column creation + * + * @since 30.0.0 + */ +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class AddColumn extends ColumnMigrationAttribute { + /** + * @return string + * @since 30.0.0 + */ + public function definition(): string { + $type = is_null($this->getType()) ? '' : ' (' . $this->getType()->value . ')'; + return empty($this->getName()) + ? 'Addition of a new column' . $type . ' to table \'' . $this->getTable() . '\'' + : 'Addition of column \'' . $this->getName() . '\'' . $type . ' to table \'' . $this->getTable() . '\''; + } +} diff --git a/lib/public/Migration/Attributes/AddIndex.php b/lib/public/Migration/Attributes/AddIndex.php new file mode 100644 index 00000000000..ee22fe7f128 --- /dev/null +++ b/lib/public/Migration/Attributes/AddIndex.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +/** + * attribute on index creation + * + * @since 30.0.0 + */ +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class AddIndex extends IndexMigrationAttribute { + /** + * @return string + * @since 30.0.0 + */ + public function definition(): string { + $type = is_null($this->getType()) ? '' : ' (' . $this->getType()->value . ')'; + return 'Addition of a new index' . $type . ' to table \'' . $this->getTable() . '\''; + } +} diff --git a/lib/public/Migration/Attributes/ColumnMigrationAttribute.php b/lib/public/Migration/Attributes/ColumnMigrationAttribute.php new file mode 100644 index 00000000000..30b6fe008e6 --- /dev/null +++ b/lib/public/Migration/Attributes/ColumnMigrationAttribute.php @@ -0,0 +1,101 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use JsonSerializable; + +/** + * generic class related to migration attribute about column changes + * + * @since 30.0.0 + */ +class ColumnMigrationAttribute extends MigrationAttribute implements JsonSerializable { + /** + * @param string $table name of the database table + * @param string $name name of the column + * @param ColumnType|null $type type of the column + * @param string $description description of the migration + * @param array $notes notes about the migration/column + * @since 30.0.0 + */ + public function __construct( + string $table, + private string $name = '', + private ?ColumnType $type = null, + string $description = '', + array $notes = [], + ) { + parent::__construct($table, $description, $notes); + } + + /** + * @param string $name + * + * @return $this + * @since 30.0.0 + */ + public function setName(string $name): self { + $this->name = $name; + return $this; + } + + /** + * @return string + * @since 30.0.0 + */ + public function getName(): string { + return $this->name; + } + + /** + * @param ColumnType|null $type + * + * @return $this + * @since 30.0.0 + */ + public function setType(?ColumnType $type): self { + $this->type = $type; + return $this; + } + + /** + * @return ColumnType|null + * @since 30.0.0 + */ + public function getType(): ?ColumnType { + return $this->type; + } + + /** + * @param array $data + * + * @return $this + * @since 30.0.0 + */ + public function import(array $data): self { + parent::import($data); + $this->setName($data['name'] ?? ''); + $this->setType(ColumnType::tryFrom($data['type'] ?? '')); + return $this; + } + + /** + * @return array + * @since 30.0.0 + */ + public function jsonSerialize(): array { + return array_merge( + parent::jsonSerialize(), + [ + 'name' => $this->getName(), + 'type' => $this->getType() ?? '', + ] + ); + } +} diff --git a/lib/public/Migration/Attributes/ColumnType.php b/lib/public/Migration/Attributes/ColumnType.php new file mode 100644 index 00000000000..ab6044e3ae2 --- /dev/null +++ b/lib/public/Migration/Attributes/ColumnType.php @@ -0,0 +1,60 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +/** + * enum ColumnType based on OCP\DB\Types + * + * @see \OCP\DB\Types + * @since 30.0.0 + */ +enum ColumnType : string { + /** @since 30.0.0 */ + case BIGINT = 'bigint'; + /** @since 30.0.0 */ + case BINARY = 'binary'; + /** @since 30.0.0 */ + case BLOB = 'blob'; + /** @since 30.0.0 */ + case BOOLEAN = 'boolean'; + /** + * A column created with `DATE` can be used for both `DATE` and `DATE_IMMUTABLE` + * on the `\OCP\AppFramework\Db\Entity`. + * @since 30.0.0 + */ + case DATE = 'date'; + /** + * A column created with `DATETIME` can be used for both `DATETIME` and `DATETIME_IMMUTABLE` + * on the `\OCP\AppFramework\Db\Entity`. + * @since 30.0.0 + */ + case DATETIME = 'datetime'; + /** + * A column created with `DATETIME_TZ` can be used for both `DATETIME_TZ` and `DATETIME_TZ_IMMUTABLE` + * on the `\OCP\AppFramework\Db\Entity`. + * @since 31.0.0 + */ + case DATETIME_TZ = 'datetimetz'; + /** @since 30.0.0 */ + case DECIMAL = 'decimal'; + /** @since 30.0.0 */ + case FLOAT = 'float'; + /** @since 30.0.0 */ + case INTEGER = 'integer'; + /** @since 30.0.0 */ + case SMALLINT = 'smallint'; + /** @since 30.0.0 */ + case STRING = 'string'; + /** @since 30.0.0 */ + case TEXT = 'text'; + /** @since 30.0.0 */ + case TIME = 'time'; + /** @since 30.0.0 */ + case JSON = 'json'; +} diff --git a/lib/public/Migration/Attributes/CreateTable.php b/lib/public/Migration/Attributes/CreateTable.php new file mode 100644 index 00000000000..df0418fa4bc --- /dev/null +++ b/lib/public/Migration/Attributes/CreateTable.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +/** + * attribute on table creation + * + * @since 30.0.0 + */ +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class CreateTable extends TableMigrationAttribute { + /** + * @return string + * @since 30.0.0 + */ + public function definition(): string { + $definition = 'Creation of new table \'' . $this->getTable() . '\''; + $definition .= empty($this->getColumns()) ? '' : ' with columns ' . implode(', ', $this->getColumns()); + return $definition; + } +} diff --git a/lib/public/Migration/Attributes/DropColumn.php b/lib/public/Migration/Attributes/DropColumn.php new file mode 100644 index 00000000000..a1cd5790cc7 --- /dev/null +++ b/lib/public/Migration/Attributes/DropColumn.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +/** + * attribute on column drop + * + * @since 30.0.0 + */ +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class DropColumn extends ColumnMigrationAttribute { + /** + * @return string + * @since 30.0.0 + */ + public function definition(): string { + return empty($this->getName()) + ? 'Deletion of a column from table \'' . $this->getTable() . '\'' + : 'Deletion of column \'' . $this->getName() . '\' from table \'' . $this->getTable() . '\''; + } +} diff --git a/lib/public/Migration/Attributes/DropIndex.php b/lib/public/Migration/Attributes/DropIndex.php new file mode 100644 index 00000000000..2702cbed9a7 --- /dev/null +++ b/lib/public/Migration/Attributes/DropIndex.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +/** + * attribute on index drop + * + * @since 30.0.0 + */ +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class DropIndex extends IndexMigrationAttribute { + /** + * @return string + * @since 30.0.0 + */ + public function definition(): string { + return 'Deletion of an index from table \'' . $this->getTable() . '\''; + } +} diff --git a/lib/public/Migration/Attributes/DropTable.php b/lib/public/Migration/Attributes/DropTable.php new file mode 100644 index 00000000000..e90e4804a3c --- /dev/null +++ b/lib/public/Migration/Attributes/DropTable.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +/** + * attribute on table drop + * + * @since 30.0.0 + */ +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class DropTable extends TableMigrationAttribute { + /** + * @return string + * @since 30.0.0 + */ + public function definition(): string { + return 'Deletion of table \'' . $this->getTable() . '\''; + } +} diff --git a/lib/public/Migration/Attributes/GenericMigrationAttribute.php b/lib/public/Migration/Attributes/GenericMigrationAttribute.php new file mode 100644 index 00000000000..e63f78088e8 --- /dev/null +++ b/lib/public/Migration/Attributes/GenericMigrationAttribute.php @@ -0,0 +1,49 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use JsonSerializable; + +/** + * generic entry, used to replace migration attribute not yet known in current version + * but used in a future release + * + * @since 30.0.0 + */ +class GenericMigrationAttribute extends MigrationAttribute implements JsonSerializable { + /** + * @param array $details + * @since 30.0.0 + */ + public function __construct( + private readonly array $details = [], + ) { + parent::__construct( + $details['table'] ?? '', + $details['description'] ?? '', + $details['notes'] ?? [] + ); + } + + /** + * @return string + * @since 30.0.0 + */ + public function definition(): string { + return json_encode($this->jsonSerialize(), JSON_UNESCAPED_SLASHES); + } + + /** + * @return array + * @since 30.0.0 + */ + public function jsonSerialize(): array { + return $this->details; + } +} diff --git a/lib/public/Migration/Attributes/IndexMigrationAttribute.php b/lib/public/Migration/Attributes/IndexMigrationAttribute.php new file mode 100644 index 00000000000..88b60a564b3 --- /dev/null +++ b/lib/public/Migration/Attributes/IndexMigrationAttribute.php @@ -0,0 +1,78 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use JsonSerializable; + +/** + * generic class related to migration attribute about index changes + * + * @since 30.0.0 + */ +class IndexMigrationAttribute extends MigrationAttribute implements JsonSerializable { + /** + * @param string $table name of the database table + * @param IndexType|null $type type of the index + * @param string $description description of the migration + * @param array $notes notes abour the migration/index + * @since 30.0.0 + */ + public function __construct( + string $table, + private ?IndexType $type = null, + string $description = '', + array $notes = [], + ) { + parent::__construct($table, $description, $notes); + } + + /** + * @param IndexType|null $type + * + * @return $this + * @since 30.0.0 + */ + public function setType(?IndexType $type): self { + $this->type = $type; + return $this; + } + + /** + * @return IndexType|null + * @since 30.0.0 + */ + public function getType(): ?IndexType { + return $this->type; + } + + /** + * @param array $data + * + * @return $this + * @since 30.0.0 + */ + public function import(array $data): self { + parent::import($data); + $this->setType(IndexType::tryFrom($data['type'] ?? '')); + return $this; + } + + /** + * @return array + * @since 30.0.0 + */ + public function jsonSerialize(): array { + return array_merge( + parent::jsonSerialize(), + [ + 'type' => $this->getType() ?? '', + ] + ); + } +} diff --git a/lib/public/Migration/Attributes/IndexType.php b/lib/public/Migration/Attributes/IndexType.php new file mode 100644 index 00000000000..45c88d81041 --- /dev/null +++ b/lib/public/Migration/Attributes/IndexType.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +/** + * type of index + * + * @since 30.0.0 + */ +enum IndexType : string { + /** @since 30.0.0 */ + case PRIMARY = 'primary'; + /** @since 30.0.0 */ + case INDEX = 'index'; + /** @since 30.0.0 */ + case UNIQUE = 'unique'; +} diff --git a/lib/public/Migration/Attributes/MigrationAttribute.php b/lib/public/Migration/Attributes/MigrationAttribute.php new file mode 100644 index 00000000000..d5b2d52cafb --- /dev/null +++ b/lib/public/Migration/Attributes/MigrationAttribute.php @@ -0,0 +1,118 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use JsonSerializable; + +/** + * @since 30.0.0 + */ +class MigrationAttribute implements JsonSerializable { + /** + * @param string $table name of the database table + * @param string $description description of the migration + * @param array $notes notes about the migration + * @since 30.0.0 + */ + public function __construct( + private string $table, + private string $description = '', + private array $notes = [], + ) { + } + + /** + * @param string $table + * + * @return $this + * @since 30.0.0 + */ + public function setTable(string $table): self { + $this->table = $table; + return $this; + } + + /** + * @return string + * @since 30.0.0 + */ + public function getTable(): string { + return $this->table; + } + + /** + * @param string $description + * + * @return $this + * @since 30.0.0 + */ + public function setDescription(string $description): self { + $this->description = $description; + return $this; + } + + /** + * @return string + * @since 30.0.0 + */ + public function getDescription(): string { + return $this->description; + } + + /** + * @param array $notes + * + * @return $this + * @since 30.0.0 + */ + public function setNotes(array $notes): self { + $this->notes = $notes; + return $this; + } + + /** + * @return array + * @since 30.0.0 + */ + public function getNotes(): array { + return $this->notes; + } + + /** + * @return string + * @since 30.0.0 + */ + public function definition(): string { + return json_encode($this->jsonSerialize(), JSON_UNESCAPED_SLASHES); + } + + /** + * @param array $data + * + * @return self + * @since 30.0.0 + */ + public function import(array $data): self { + return $this->setDescription($data['description'] ?? '') + ->setNotes($data['notes'] ?? []); + } + + /** + * @return array + * @since 30.0.0 + */ + public function jsonSerialize(): array { + return [ + 'class' => get_class($this), + 'table' => $this->getTable(), + 'description' => $this->getDescription(), + 'notes' => $this->getNotes() + ]; + } +} diff --git a/lib/public/Migration/Attributes/ModifyColumn.php b/lib/public/Migration/Attributes/ModifyColumn.php new file mode 100644 index 00000000000..6fc44ebb824 --- /dev/null +++ b/lib/public/Migration/Attributes/ModifyColumn.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use Attribute; + +/** + * attribute on column modification + * + * @since 30.0.0 + */ +#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_CLASS)] +class ModifyColumn extends ColumnMigrationAttribute { + /** + * @return string + * @since 30.0.0 + */ + public function definition(): string { + $type = is_null($this->getType()) ? '' : ' to ' . $this->getType()->value; + return empty($this->getName()) + ? 'Modification of a column from table \'' . $this->getTable() . '\'' . $type + : 'Modification of column \'' . $this->getName() . '\' from table \'' . $this->getTable() . '\'' . $type; + } +} diff --git a/lib/public/Migration/Attributes/TableMigrationAttribute.php b/lib/public/Migration/Attributes/TableMigrationAttribute.php new file mode 100644 index 00000000000..0776e50387e --- /dev/null +++ b/lib/public/Migration/Attributes/TableMigrationAttribute.php @@ -0,0 +1,78 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Migration\Attributes; + +use JsonSerializable; + +/** + * generic class related to migration attribute about table changes + * + * @since 30.0.0 + */ +class TableMigrationAttribute extends MigrationAttribute implements JsonSerializable { + /** + * @param string $table name of the database table + * @param array $columns list of columns + * @param string $description description of the migration + * @param array $notes notes about the migration/table + * @since 30.0.0 + */ + public function __construct( + string $table, + private array $columns = [], + string $description = '', + array $notes = [], + ) { + parent::__construct($table, $description, $notes); + } + + /** + * @param array $columns + * + * @return $this + * @since 30.0.0 + */ + public function setColumns(array $columns): self { + $this->columns = $columns; + return $this; + } + + /** + * @return array + * @since 30.0.0 + */ + public function getColumns(): array { + return $this->columns; + } + + /** + * @param array $data + * + * @return $this + * @since 30.0.0 + */ + public function import(array $data): self { + parent::import($data); + $this->setColumns($data['columns'] ?? []); + return $this; + } + + /** + * @return array + * @since 30.0.0 + */ + public function jsonSerialize(): array { + return array_merge( + parent::jsonSerialize(), + [ + 'columns' => $this->getColumns(), + ] + ); + } +} diff --git a/lib/public/Migration/BigIntMigration.php b/lib/public/Migration/BigIntMigration.php index db50147b28e..0fa7e559f79 100644 --- a/lib/public/Migration/BigIntMigration.php +++ b/lib/public/Migration/BigIntMigration.php @@ -16,7 +16,7 @@ use OCP\DB\ISchemaWrapper; abstract class BigIntMigration extends SimpleMigrationStep { /** * @return array Returns an array with the following structure - * ['table1' => ['column1', 'column2'], ...] + * ['table1' => ['column1', 'column2'], ...] * @since 13.0.0 */ abstract protected function getColumnsByTable(); diff --git a/lib/public/Navigation/Events/LoadAdditionalEntriesEvent.php b/lib/public/Navigation/Events/LoadAdditionalEntriesEvent.php new file mode 100644 index 00000000000..61db417b927 --- /dev/null +++ b/lib/public/Navigation/Events/LoadAdditionalEntriesEvent.php @@ -0,0 +1,17 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Navigation\Events; + +use OCP\EventDispatcher\Event; + +/** + * @since 31.0.0 + */ +class LoadAdditionalEntriesEvent extends Event { +} diff --git a/lib/public/Notification/AlreadyProcessedException.php b/lib/public/Notification/AlreadyProcessedException.php index 0e7458185ff..162abd81864 100644 --- a/lib/public/Notification/AlreadyProcessedException.php +++ b/lib/public/Notification/AlreadyProcessedException.php @@ -8,9 +8,9 @@ declare(strict_types=1); */ namespace OCP\Notification; -/** - * @since 17.0.0 - */ +use OCP\AppFramework\Attribute\Throwable; + +#[Throwable(since: '17.0.0')] class AlreadyProcessedException extends \RuntimeException { /** * @since 17.0.0 diff --git a/lib/public/Notification/IAction.php b/lib/public/Notification/IAction.php index f1cffa49075..722dac72826 100644 --- a/lib/public/Notification/IAction.php +++ b/lib/public/Notification/IAction.php @@ -8,11 +8,9 @@ declare(strict_types=1); */ namespace OCP\Notification; -/** - * Interface IAction - * - * @since 9.0.0 - */ +use OCP\AppFramework\Attribute\Consumable; + +#[Consumable(since: '9.0.0')] interface IAction { /** * @since 17.0.0 diff --git a/lib/public/Notification/IApp.php b/lib/public/Notification/IApp.php index 1574ae8a091..37c352d44cd 100644 --- a/lib/public/Notification/IApp.php +++ b/lib/public/Notification/IApp.php @@ -8,11 +8,9 @@ declare(strict_types=1); */ namespace OCP\Notification; -/** - * Interface IApp - * - * @since 9.0.0 - */ +use OCP\AppFramework\Attribute\Implementable; + +#[Implementable(since: '9.0.0')] interface IApp { /** * @param INotification $notification diff --git a/lib/public/Notification/IDeferrableApp.php b/lib/public/Notification/IDeferrableApp.php index 1820ed7ecd6..00c7d691b10 100644 --- a/lib/public/Notification/IDeferrableApp.php +++ b/lib/public/Notification/IDeferrableApp.php @@ -8,11 +8,9 @@ declare(strict_types=1); */ namespace OCP\Notification; -/** - * Interface IDeferrableApp - * - * @since 20.0.0 - */ +use OCP\AppFramework\Attribute\Implementable; + +#[Implementable(since: '20.0.0')] interface IDeferrableApp extends IApp { /** * Start deferring notifications until `flush()` is called diff --git a/lib/public/Notification/IDismissableNotifier.php b/lib/public/Notification/IDismissableNotifier.php index 39f9658a8c4..d2f649b45a1 100644 --- a/lib/public/Notification/IDismissableNotifier.php +++ b/lib/public/Notification/IDismissableNotifier.php @@ -8,15 +8,16 @@ declare(strict_types=1); */ namespace OCP\Notification; +use OCP\AppFramework\Attribute\Implementable; + /** * Interface INotifier classes should implement if they want to process notifications * that are dismissed by the user. * * This can be useful if dismissing the notification will leave it in an incomplete * state. The handler can choose to for example do some default action. - * - * @since 18.0.0 */ +#[Implementable(since: '18.0.0')] interface IDismissableNotifier extends INotifier { /** * @param INotification $notification diff --git a/lib/public/Notification/IManager.php b/lib/public/Notification/IManager.php index 6a16af2d7a0..207a89344b0 100644 --- a/lib/public/Notification/IManager.php +++ b/lib/public/Notification/IManager.php @@ -8,15 +8,13 @@ declare(strict_types=1); */ namespace OCP\Notification; -/** - * Interface IManager - * - * @since 9.0.0 - */ -interface IManager extends IApp, INotifier { +use OCP\AppFramework\Attribute\Consumable; + +#[Consumable(since: '9.0.0')] +interface IManager extends IApp, IPreloadableNotifier { /** * @param string $appClass The service must implement IApp, otherwise a - * \InvalidArgumentException is thrown later + * \InvalidArgumentException is thrown later * @since 17.0.0 */ public function registerApp(string $appClass): void; @@ -24,8 +22,8 @@ interface IManager extends IApp, INotifier { /** * @param \Closure $service The service must implement INotifier, otherwise a * \InvalidArgumentException is thrown later - * @param \Closure $info An array with the keys 'id' and 'name' containing - * the app id and the app name + * @param \Closure $info An array with the keys 'id' and 'name' containing + * the app id and the app name * @deprecated 17.0.0 use registerNotifierService instead. * @since 8.2.0 - Parameter $info was added in 9.0.0 */ @@ -33,7 +31,7 @@ interface IManager extends IApp, INotifier { /** * @param string $notifierService The service must implement INotifier, otherwise a - * \InvalidArgumentException is thrown later + * \InvalidArgumentException is thrown later * @since 17.0.0 * @deprecated 22.0.0 use the IBootStrap registration context */ diff --git a/lib/public/Notification/INotification.php b/lib/public/Notification/INotification.php index 28d2d6bff5a..a740678376f 100644 --- a/lib/public/Notification/INotification.php +++ b/lib/public/Notification/INotification.php @@ -8,11 +8,9 @@ declare(strict_types=1); */ namespace OCP\Notification; -/** - * Interface INotification - * - * @since 9.0.0 - */ +use OCP\AppFramework\Attribute\Consumable; + +#[Consumable(since: '9.0.0')] interface INotification { /** * @param string $app @@ -137,7 +135,7 @@ interface INotification { * See https://github.com/nextcloud/server/issues/1706 for more information. * * @param string $subject - * @param array $parameters + * @param array<string, array<string, string>> $parameters * @return $this * @throws InvalidValueException if the subject or parameters are invalid * @since 11.0.0 @@ -213,7 +211,7 @@ interface INotification { * See https://github.com/nextcloud/server/issues/1706 for more information. * * @param string $message - * @param array $parameters + * @param array<string, array<string, string>> $parameters * @return $this * @throws InvalidValueException if the message or parameters are invalid * @since 11.0.0 @@ -249,6 +247,10 @@ interface INotification { public function getLink(): string; /** + * Set the absolute url for the icon (should be colored black or not have a color) + * + * It's automatically color inverted by clients when needed + * * @param string $icon * @return $this * @throws InvalidValueException if the icon is invalid @@ -258,12 +260,28 @@ interface INotification { public function setIcon(string $icon): INotification; /** + * Get the absolute url for the icon (should be colored black or not have a color) + * + * It's automatically color inverted by clients when needed + * * @return string * @since 11.0.0 */ public function getIcon(): string; /** + * @return $this + * @throws InvalidValueException if the app is not allowed to send priority notifications + * @since 31.0.0 + */ + public function setPriorityNotification(bool $priorityNotification): INotification; + + /** + * @since 31.0.0 + */ + public function isPriorityNotification(): bool; + + /** * @return IAction * @since 9.0.0 */ diff --git a/lib/public/Notification/INotifier.php b/lib/public/Notification/INotifier.php index 39a962b0392..b6851e3dfb3 100644 --- a/lib/public/Notification/INotifier.php +++ b/lib/public/Notification/INotifier.php @@ -8,11 +8,14 @@ declare(strict_types=1); */ namespace OCP\Notification; +use OCP\AppFramework\Attribute\Implementable; + /** - * Interface INotifier - * - * @since 9.0.0 + * Please consider implementing {@see IPreloadableNotifier} to improve performance. It allows to + * preload and cache data for many notifications at once instead of loading the data for each + * prepared notification separately. */ +#[Implementable(since: '9.0.0')] interface INotifier { /** * Identifier of the notifier, only use [a-z0-9_] diff --git a/lib/public/Notification/IPreloadableNotifier.php b/lib/public/Notification/IPreloadableNotifier.php new file mode 100644 index 00000000000..2bdcd84d254 --- /dev/null +++ b/lib/public/Notification/IPreloadableNotifier.php @@ -0,0 +1,31 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Notification; + +use OCP\AppFramework\Attribute\Implementable; + +/** + * Allow notifier implementations to preload and cache data for many notifications at once to + * improve performance by, for example, bundling SQL queries. + */ +#[Implementable(since: '32.0.0')] +interface IPreloadableNotifier extends INotifier { + /** + * This method provides a way for notifier implementations to preload and cache data for many + * notifications. The data is meant to be consumed later in the {@see INotifier::prepare()} + * method to improve performance. + * + * @since 32.0.0 + * + * @param INotification[] $notifications The notifications which are about to be prepared in the next step. + * @param string $languageCode The code of the language that should be used to prepare the notification. + */ + public function preloadDataForParsing(array $notifications, string $languageCode): void; +} diff --git a/lib/public/Notification/IncompleteNotificationException.php b/lib/public/Notification/IncompleteNotificationException.php index f5ae5254509..49d388ebee6 100644 --- a/lib/public/Notification/IncompleteNotificationException.php +++ b/lib/public/Notification/IncompleteNotificationException.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace OCP\Notification; +use OCP\AppFramework\Attribute\Catchable; + /** * Thrown when {@see \OCP\Notification\IManager::notify()} is called with a notification * that does not have all required fields set: @@ -19,8 +21,7 @@ namespace OCP\Notification; * - objectType * - objectId * - subject - * - * @since 30.0.0 */ +#[Catchable(since: '30.0.0')] class IncompleteNotificationException extends \InvalidArgumentException { } diff --git a/lib/public/Notification/IncompleteParsedNotificationException.php b/lib/public/Notification/IncompleteParsedNotificationException.php index b69967e2781..c31ab129fd4 100644 --- a/lib/public/Notification/IncompleteParsedNotificationException.php +++ b/lib/public/Notification/IncompleteParsedNotificationException.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace OCP\Notification; +use OCP\AppFramework\Attribute\Catchable; + /** * Thrown when {@see \OCP\Notification\IManager::prepare()} is called with a notification * that does not have all required fields set at the end of the manager or after a INotifier @@ -22,8 +24,7 @@ namespace OCP\Notification; * - objectType * - objectId * - parsedSubject - * - * @since 30.0.0 */ +#[Catchable(since: '30.0.0')] class IncompleteParsedNotificationException extends \InvalidArgumentException { } diff --git a/lib/public/Notification/InvalidValueException.php b/lib/public/Notification/InvalidValueException.php index 05cf1a253b2..ec52381ee3c 100644 --- a/lib/public/Notification/InvalidValueException.php +++ b/lib/public/Notification/InvalidValueException.php @@ -9,9 +9,9 @@ declare(strict_types=1); namespace OCP\Notification; -/** - * @since 30.0.0 - */ +use OCP\AppFramework\Attribute\Catchable; + +#[Catchable(since: '30.0.0')] class InvalidValueException extends \InvalidArgumentException { /** * @since 30.0.0 diff --git a/lib/public/Notification/UnknownNotificationException.php b/lib/public/Notification/UnknownNotificationException.php index 7e630c59dd0..976d9179592 100644 --- a/lib/public/Notification/UnknownNotificationException.php +++ b/lib/public/Notification/UnknownNotificationException.php @@ -9,8 +9,8 @@ declare(strict_types=1); namespace OCP\Notification; -/** - * @since 30.0.0 - */ +use OCP\AppFramework\Attribute\Throwable; + +#[Throwable(since: '30.0.0')] class UnknownNotificationException extends \InvalidArgumentException { } diff --git a/lib/public/OCM/Events/ResourceTypeRegisterEvent.php b/lib/public/OCM/Events/ResourceTypeRegisterEvent.php index 11087106cbc..c0129197566 100644 --- a/lib/public/OCM/Events/ResourceTypeRegisterEvent.php +++ b/lib/public/OCM/Events/ResourceTypeRegisterEvent.php @@ -32,7 +32,7 @@ class ResourceTypeRegisterEvent extends Event { * @param string $name * @param list<string> $shareTypes List of supported share recipients, e.g. 'user', 'group', … * @param array<string, string> $protocols List of supported protocols and their location, - * e.g. ['webdav' => '/remote.php/webdav/'] + * e.g. ['webdav' => '/remote.php/webdav/'] * @since 28.0.0 */ public function registerResourceType(string $name, array $shareTypes, array $protocols): void { diff --git a/lib/public/OCM/ICapabilityAwareOCMProvider.php b/lib/public/OCM/ICapabilityAwareOCMProvider.php new file mode 100644 index 00000000000..ae290abd2f8 --- /dev/null +++ b/lib/public/OCM/ICapabilityAwareOCMProvider.php @@ -0,0 +1,60 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\OCM; + +/** + * Version 1.1 and 1.2 extensions to the Open Cloud Mesh Discovery API + * @link https://github.com/cs3org/OCM-API/ + * @since 32.0.0 + */ +interface ICapabilityAwareOCMProvider extends IOCMProvider { + /** + * get the capabilities + * + * @return array + * @since 32.0.0 + */ + public function getCapabilities(): array; + + /** + * get the provider name + * + * @return string + * @since 32.0.0 + */ + public function getProvider(): string; + + /** + * returns the invite accept dialog + * + * @return string + * @since 32.0.0 + */ + public function getInviteAcceptDialog(): string; + + /** + * set the capabilities + * + * @param array $capabilities + * + * @return $this + * @since 32.0.0 + */ + public function setCapabilities(array $capabilities): static; + + /** + * set the invite accept dialog + * + * @param string $inviteAcceptDialog + * + * @return $this + * @since 32.0.0 + */ + public function setInviteAcceptDialog(string $inviteAcceptDialog): static; +} diff --git a/lib/public/OCM/IOCMProvider.php b/lib/public/OCM/IOCMProvider.php index 58b50aca172..7141b0a9ab9 100644 --- a/lib/public/OCM/IOCMProvider.php +++ b/lib/public/OCM/IOCMProvider.php @@ -6,7 +6,6 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ - namespace OCP\OCM; use JsonSerializable; @@ -17,6 +16,7 @@ use OCP\OCM\Exceptions\OCMProviderException; * Model based on the Open Cloud Mesh Discovery API * @link https://github.com/cs3org/OCM-API/ * @since 28.0.0 + * @deprecated 32.0.0 Please use {@see \OCP\OCM\ICapabilityAwareOCMProvider} */ interface IOCMProvider extends JsonSerializable { /** @@ -120,6 +120,22 @@ interface IOCMProvider extends JsonSerializable { */ public function extractProtocolEntry(string $resourceName, string $protocol): string; + // /** + // * store signatory (public/private key pair) to sign outgoing/incoming request + // * + // * @param Signatory $signatory + // * @experimental 31.0.0 + // */ + // public function setSignatory(Signatory $signatory): void; + + // /** + // * signatory (public/private key pair) used to sign outgoing/incoming request + // * + // * @return Signatory|null returns null if no Signatory available + // * @experimental 31.0.0 + // */ + // public function getSignatory(): ?Signatory; + /** * import data from an array * @@ -134,13 +150,18 @@ interface IOCMProvider extends JsonSerializable { /** * @return array{ * enabled: bool, - * apiVersion: string, + * apiVersion: '1.0-proposal1', * endPoint: string, - * resourceTypes: array{ + * publicKey?: array{ + * keyId: string, + * publicKeyPem: string + * }, + * resourceTypes: list<array{ * name: string, - * shareTypes: string[], + * shareTypes: list<string>, * protocols: array<string, string> - * }[] + * }>, + * version: string * } * @since 28.0.0 */ diff --git a/lib/public/OCM/IOCMResource.php b/lib/public/OCM/IOCMResource.php index 788b5563cfc..60bf701e8ea 100644 --- a/lib/public/OCM/IOCMResource.php +++ b/lib/public/OCM/IOCMResource.php @@ -39,7 +39,7 @@ interface IOCMResource extends JsonSerializable { /** * set share types * - * @param string[] $shareTypes + * @param list<string> $shareTypes * * @return $this * @since 28.0.0 @@ -49,7 +49,7 @@ interface IOCMResource extends JsonSerializable { /** * get share types * - * @return string[] + * @return list<string> * @since 28.0.0 */ public function getShareTypes(): array; @@ -85,7 +85,7 @@ interface IOCMResource extends JsonSerializable { /** * @return array{ * name: string, - * shareTypes: string[], + * shareTypes: list<string>, * protocols: array<string, string> * } * @since 28.0.0 diff --git a/lib/public/Preview/BeforePreviewFetchedEvent.php b/lib/public/Preview/BeforePreviewFetchedEvent.php index ac3c8c13b55..8ab875070d9 100644 --- a/lib/public/Preview/BeforePreviewFetchedEvent.php +++ b/lib/public/Preview/BeforePreviewFetchedEvent.php @@ -11,7 +11,13 @@ use OCP\Files\Node; use OCP\IPreview; /** + * Emitted before a file preview is being fetched. + * + * It can be used to block preview rendering by throwing a ``OCP\Files\NotFoundException`` + * * @since 25.0.1 + * @since 28.0.0 the constructor arguments ``$width``, ``$height``, ``$crop`` and ``$mode`` are no longer nullable. + * @since 31.0.0 the constructor arguments ``$mimeType`` was added */ class BeforePreviewFetchedEvent extends \OCP\EventDispatcher\Event { /** @@ -19,14 +25,15 @@ class BeforePreviewFetchedEvent extends \OCP\EventDispatcher\Event { */ public function __construct( private Node $node, - /** @deprecated 28.0.0 null deprecated **/ + /** @deprecated 28.0.0 passing null is deprecated **/ private ?int $width = null, - /** @deprecated 28.0.0 null deprecated **/ + /** @deprecated 28.0.0 passing null is deprecated **/ private ?int $height = null, - /** @deprecated 28.0.0 null deprecated **/ + /** @deprecated 28.0.0 passing null is deprecated **/ private ?bool $crop = null, - /** @deprecated 28.0.0 null deprecated **/ + /** @deprecated 28.0.0 passing null is deprecated **/ private ?string $mode = null, + private ?string $mimeType = null, ) { parent::__construct(); } @@ -66,4 +73,11 @@ class BeforePreviewFetchedEvent extends \OCP\EventDispatcher\Event { public function getMode(): ?string { return $this->mode; } + + /** + * @since 31.0.0 + */ + public function getMimeType(): ?string { + return $this->mimeType; + } } diff --git a/lib/public/Preview/IMimeIconProvider.php b/lib/public/Preview/IMimeIconProvider.php index da4896bec87..4a407f48577 100644 --- a/lib/public/Preview/IMimeIconProvider.php +++ b/lib/public/Preview/IMimeIconProvider.php @@ -18,5 +18,5 @@ interface IMimeIconProvider { * if no preview is available. * @since 28.0.0 */ - public function getMimeIconUrl(string $mime): string|null; + public function getMimeIconUrl(string $mime): ?string; } diff --git a/lib/public/Profile/IProfileManager.php b/lib/public/Profile/IProfileManager.php index 3f5802a5441..aec06fb4c86 100644 --- a/lib/public/Profile/IProfileManager.php +++ b/lib/public/Profile/IProfileManager.php @@ -9,10 +9,12 @@ declare(strict_types=1); namespace OCP\Profile; +use OC\Core\ResponseDefinitions; use OCP\Accounts\IAccountManager; use OCP\IUser; /** + * @psalm-import-type CoreProfileFields from ResponseDefinitions * @since 28.0.0 */ interface IProfileManager { @@ -53,7 +55,9 @@ interface IProfileManager { IAccountManager::PROPERTY_EMAIL => self::VISIBILITY_SHOW_USERS_ONLY, IAccountManager::PROPERTY_PHONE => self::VISIBILITY_SHOW_USERS_ONLY, IAccountManager::PROPERTY_TWITTER => self::VISIBILITY_SHOW, + IAccountManager::PROPERTY_BLUESKY => self::VISIBILITY_SHOW, IAccountManager::PROPERTY_WEBSITE => self::VISIBILITY_SHOW, + IAccountManager::PROPERTY_PRONOUNS => self::VISIBILITY_SHOW, ]; /** @@ -82,7 +86,7 @@ interface IProfileManager { * Return the profile parameters of the target user that are visible to the visiting user * in an associative array * - * @return array{userId: string, address?: ?string, biography?: ?string, displayname?: ?string, headline?: ?string, isUserAvatarVisible?: bool, organisation?: ?string, role?: ?string, actions: list<array{id: string, icon: string, title: string, target: ?string}>} + * @psalm-return CoreProfileFields * @since 28.0.0 */ public function getProfileFields(IUser $targetUser, ?IUser $visitingUser): array; diff --git a/lib/public/Profiler/IProfile.php b/lib/public/Profiler/IProfile.php index ddbad4b4388..89eb709d061 100644 --- a/lib/public/Profiler/IProfile.php +++ b/lib/public/Profiler/IProfile.php @@ -17,7 +17,7 @@ use OCP\DataCollector\IDataCollector; * * ```php * <?php - * $profiler = \OC::$server->get(IProfiler::class); + * $profiler = \OCP\Server::get(IProfiler::class); * $profiles = $profiler->find('/settings/users', 10); * ``` * diff --git a/lib/public/Remote/Api/IApiCollection.php b/lib/public/Remote/Api/IApiCollection.php index da1964997bd..e7181ab20f2 100644 --- a/lib/public/Remote/Api/IApiCollection.php +++ b/lib/public/Remote/Api/IApiCollection.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Remote/Api/IApiFactory.php b/lib/public/Remote/Api/IApiFactory.php index 0e95dc98e70..2089c61be82 100644 --- a/lib/public/Remote/Api/IApiFactory.php +++ b/lib/public/Remote/Api/IApiFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Remote/Api/ICapabilitiesApi.php b/lib/public/Remote/Api/ICapabilitiesApi.php index d9ab037bcc6..d0e3fd7ad80 100644 --- a/lib/public/Remote/Api/ICapabilitiesApi.php +++ b/lib/public/Remote/Api/ICapabilitiesApi.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Remote/Api/IUserApi.php b/lib/public/Remote/Api/IUserApi.php index c8b5e64d003..268594c0340 100644 --- a/lib/public/Remote/Api/IUserApi.php +++ b/lib/public/Remote/Api/IUserApi.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Remote/ICredentials.php b/lib/public/Remote/ICredentials.php index efe87a350c8..c261d57093d 100644 --- a/lib/public/Remote/ICredentials.php +++ b/lib/public/Remote/ICredentials.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Remote/IInstance.php b/lib/public/Remote/IInstance.php index a3c45b396e8..6186c2e1819 100644 --- a/lib/public/Remote/IInstance.php +++ b/lib/public/Remote/IInstance.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -21,7 +22,7 @@ interface IInstance { public function getUrl(); /** - * @return string The of of the remote server with protocol + * @return string The of the remote server with protocol * * @since 13.0.0 * @deprecated 23.0.0 diff --git a/lib/public/Remote/IInstanceFactory.php b/lib/public/Remote/IInstanceFactory.php index 15435901a74..1cd2b3330ce 100644 --- a/lib/public/Remote/IInstanceFactory.php +++ b/lib/public/Remote/IInstanceFactory.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Remote/IUser.php b/lib/public/Remote/IUser.php index 3c22695c20b..329e8876c0d 100644 --- a/lib/public/Remote/IUser.php +++ b/lib/public/Remote/IUser.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/RichObjectStrings/Definitions.php b/lib/public/RichObjectStrings/Definitions.php index 7c1935d0a9c..d6717e54aea 100644 --- a/lib/public/RichObjectStrings/Definitions.php +++ b/lib/public/RichObjectStrings/Definitions.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -368,6 +369,18 @@ class Definitions { 'description' => 'The height in pixels if the file is an image', 'example' => '1080', ], + 'blurhash' => [ + 'since' => '30.0.0', + 'required' => false, + 'description' => 'The blurhash of the image', + 'example' => 'LEHV9uae2yk8pyo0adR*.7kCMdnj', + ], + 'hide-download' => [ + 'since' => '31.0.5', + 'required' => false, + 'description' => 'Whether the download option should be hidden. If not set to `yes` the option can be shown', + 'example' => 'yes', + ], ], ], 'forms-form' => [ diff --git a/lib/public/RichObjectStrings/IRichTextFormatter.php b/lib/public/RichObjectStrings/IRichTextFormatter.php new file mode 100644 index 00000000000..1d72f480b07 --- /dev/null +++ b/lib/public/RichObjectStrings/IRichTextFormatter.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\RichObjectStrings; + +/** + * Parse rich text and format it with the richobjects + * + * @since 31.0.0 + */ +interface IRichTextFormatter { + /** + * @since 31.0.0 + * @param string $message + * @param array<string,array<string,string>> $parameters + * @throws \InvalidArgumentException if a parameter has no name or no type + */ + public function richToParsed(string $message, array $parameters): string; +} diff --git a/lib/public/RichObjectStrings/IValidator.php b/lib/public/RichObjectStrings/IValidator.php index 96b3b6ea743..fc486663c73 100644 --- a/lib/public/RichObjectStrings/IValidator.php +++ b/lib/public/RichObjectStrings/IValidator.php @@ -1,4 +1,7 @@ <?php + +declare(strict_types=1); + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -8,14 +11,52 @@ namespace OCP\RichObjectStrings; /** * Class Validator * + * @psalm-type RichObjectParameter = array{ + * type: string, + * id: string, + * name: string, + * server?: string, + * link?: string, + * 'call-type'?: 'one2one'|'group'|'public', + * 'icon-url'?: string, + * 'message-id'?: string, + * boardname?: string, + * stackname?: string, + * size?: string, + * path?: string, + * mimetype?: string, + * 'preview-available'?: 'yes'|'no', + * 'hide-download'?: 'yes'|'no', + * mtime?: string, + * latitude?: string, + * longitude?: string, + * description?: string, + * thumb?: string, + * website?: string, + * visibility?: '0'|'1', + * assignable?: '0'|'1', + * conversation?: string, + * etag?: string, + * permissions?: string, + * width?: string, + * height?: string, + * blurhash?: string, + * } + * * @since 11.0.0 */ interface IValidator { /** + * Only alphanumeric, dash, underscore and dot are allowed, starting with a character + * @since 31.0.0 + */ + public const PLACEHOLDER_REGEX = '[A-Za-z][A-Za-z0-9\-_.]+'; + + /** * @param string $subject - * @param array[] $parameters + * @param array<non-empty-string, RichObjectParameter> $parameters * @throws InvalidObjectExeption * @since 11.0.0 */ - public function validate($subject, array $parameters); + public function validate(string $subject, array $parameters): void; } diff --git a/lib/public/RichObjectStrings/InvalidObjectExeption.php b/lib/public/RichObjectStrings/InvalidObjectExeption.php index 0316310c5f9..603f4432ba6 100644 --- a/lib/public/RichObjectStrings/InvalidObjectExeption.php +++ b/lib/public/RichObjectStrings/InvalidObjectExeption.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Route/IRoute.php b/lib/public/Route/IRoute.php index fffd4b9c1a1..7dba6225aff 100644 --- a/lib/public/Route/IRoute.php +++ b/lib/public/Route/IRoute.php @@ -34,8 +34,9 @@ interface IRoute { * it is called directly * * @param string $file - * @return void + * @return $this * @since 7.0.0 + * @deprecated 32.0.0 Use a proper controller instead */ public function actionInclude($file); @@ -70,6 +71,7 @@ interface IRoute { * This function is called with $class set to a callable or * to the class with $function * @since 7.0.0 + * @deprecated 32.0.0 Use a proper controller instead */ public function action($class, $function = null); diff --git a/lib/public/SabrePluginEvent.php b/lib/public/SabrePluginEvent.php index 9a0206f6ded..c8c9db58ac9 100644 --- a/lib/public/SabrePluginEvent.php +++ b/lib/public/SabrePluginEvent.php @@ -39,7 +39,7 @@ class SabrePluginEvent extends Event { * @since 8.2.0 */ public function setStatusCode($statusCode) { - $this->statusCode = (int) $statusCode; + $this->statusCode = (int)$statusCode; return $this; } @@ -49,7 +49,7 @@ class SabrePluginEvent extends Event { * @since 8.2.0 */ public function setMessage($message) { - $this->message = (string) $message; + $this->message = (string)$message; return $this; } diff --git a/lib/public/Search/FilterDefinition.php b/lib/public/Search/FilterDefinition.php index 1f43222752d..86b2bb655ff 100644 --- a/lib/public/Search/FilterDefinition.php +++ b/lib/public/Search/FilterDefinition.php @@ -67,7 +67,7 @@ class FilterDefinition { * * @param self::TYPE_* $type * @param bool $exclusive If true, all providers not supporting this filter will be ignored when this filter is provided - * @throw InvalidArgumentException in case of invalid name. Allowed characters are -, 0-9, a-z. + * @throws InvalidArgumentException in case of invalid name. Allowed characters are -, 0-9, a-z. * @since 28.0.0 */ public function __construct( diff --git a/lib/public/Search/IExternalProvider.php b/lib/public/Search/IExternalProvider.php new file mode 100644 index 00000000000..a9a8e3ad6ba --- /dev/null +++ b/lib/public/Search/IExternalProvider.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Search; + +/** + * Interface for search providers that forward user queries to external services. + * + * @since 32.0.0 + */ +interface IExternalProvider extends IProvider { + /** + * Indicates whether this search provider queries external (3rd-party) resources. + * This is used by the Unified Search modal filter (toggle switch). By default, searching through external providers is disabled. + * + * @return bool default false + * + * @since 32.0.0 + */ + public function isExternalProvider(): bool; +} diff --git a/lib/public/Security/Events/GenerateSecurePasswordEvent.php b/lib/public/Security/Events/GenerateSecurePasswordEvent.php index 8adddd529b0..419e7b40ee4 100644 --- a/lib/public/Security/Events/GenerateSecurePasswordEvent.php +++ b/lib/public/Security/Events/GenerateSecurePasswordEvent.php @@ -9,15 +9,34 @@ declare(strict_types=1); namespace OCP\Security\Events; use OCP\EventDispatcher\Event; +use OCP\Security\PasswordContext; /** + * Event to request a secure password to be generated * @since 18.0.0 */ class GenerateSecurePasswordEvent extends Event { - /** @var null|string */ - private $password; + private ?string $password; /** + * Request a secure password to be generated. + * + * By default passwords are generated for the user account context, + * this can be adjusted by passing another `PasswordContext`. + * @since 31.0.0 + */ + public function __construct( + private PasswordContext $context = PasswordContext::ACCOUNT, + ) { + parent::__construct(); + $this->password = null; + } + + /** + * Get the generated password. + * + * If a password generator is registered and successfully generated a password + * that password can get read back. Otherwise `null` is returned. * @since 18.0.0 */ public function getPassword(): ?string { @@ -25,9 +44,20 @@ class GenerateSecurePasswordEvent extends Event { } /** + * Set the generated password. + * + * This is used by password generators to set the generated password. * @since 18.0.0 */ public function setPassword(string $password): void { $this->password = $password; } + + /** + * Get the context this password should generated for. + * @since 31.0.0 + */ + public function getContext(): PasswordContext { + return $this->context; + } } diff --git a/lib/public/Security/Events/ValidatePasswordPolicyEvent.php b/lib/public/Security/Events/ValidatePasswordPolicyEvent.php index 0aa8b516f70..d7ac9442392 100644 --- a/lib/public/Security/Events/ValidatePasswordPolicyEvent.php +++ b/lib/public/Security/Events/ValidatePasswordPolicyEvent.php @@ -9,26 +9,41 @@ declare(strict_types=1); namespace OCP\Security\Events; use OCP\EventDispatcher\Event; +use OCP\Security\PasswordContext; /** + * This event can be emitted to request a validation of a password. + * + * If a password policy app is installed and the password + * is invalid, an `\OCP\HintException` will be thrown. * @since 18.0.0 */ class ValidatePasswordPolicyEvent extends Event { - /** @var string */ - private $password; /** * @since 18.0.0 + * @since 31.0.0 - $context parameter added */ - public function __construct(string $password) { + public function __construct( + private string $password, + private PasswordContext $context = PasswordContext::ACCOUNT, + ) { parent::__construct(); - $this->password = $password; } /** + * Get the password that should be validated. * @since 18.0.0 */ public function getPassword(): string { return $this->password; } + + /** + * Get the context this password should validated for. + * @since 31.0.0 + */ + public function getContext(): PasswordContext { + return $this->context; + } } diff --git a/lib/public/Security/IContentSecurityPolicyManager.php b/lib/public/Security/IContentSecurityPolicyManager.php index 3df0da465b2..00cdcc2c454 100644 --- a/lib/public/Security/IContentSecurityPolicyManager.php +++ b/lib/public/Security/IContentSecurityPolicyManager.php @@ -24,7 +24,7 @@ interface IContentSecurityPolicyManager { * Note that the adjustment is only applied to applications that use AppFramework * controllers. * - * To use this from your `app.php` use `\OC::$server->getContentSecurityPolicyManager()->addDefaultPolicy($policy)`, + * To use this from your `app.php` use `\OCP\Server::get(IContentSecurityPolicyManager::class)->addDefaultPolicy($policy)`, * $policy has to be of type `\OCP\AppFramework\Http\ContentSecurityPolicy`. * * WARNING: Using this API incorrectly may make the instance more insecure. diff --git a/lib/public/Security/ICrypto.php b/lib/public/Security/ICrypto.php index c2ba4cc9c97..78b0fc14d6d 100644 --- a/lib/public/Security/ICrypto.php +++ b/lib/public/Security/ICrypto.php @@ -13,8 +13,8 @@ namespace OCP\Security; * it will use the secret defined in config.php as key. Additionally the message will be HMAC'd. * * Usage: - * $encryptWithDefaultPassword = \OC::$server->getCrypto()->encrypt('EncryptedText'); - * $encryptWithCustomPassword = \OC::$server->getCrypto()->encrypt('EncryptedText', 'password'); + * $encryptWithDefaultPassword = \OCP\Server::get(ICrypto::class)->encrypt('EncryptedText'); + * $encryptWithCustomPassword = \OCP\Server::get(ICrypto::class)->encrypt('EncryptedText', 'password'); * * @since 8.0.0 */ diff --git a/lib/public/Security/IHasher.php b/lib/public/Security/IHasher.php index 378c2cf3f51..d0d6e4e9028 100644 --- a/lib/public/Security/IHasher.php +++ b/lib/public/Security/IHasher.php @@ -19,10 +19,10 @@ namespace OCP\Security; * * Usage: * // Hashing a message - * $hash = \OC::$server->get(\OCP\Security\IHasher::class)->hash('MessageToHash'); + * $hash = \OCP\Server::get(\OCP\Security\IHasher::class)->hash('MessageToHash'); * // Verifying a message - $newHash will contain the newly calculated hash * $newHash = null; - * var_dump(\OC::$server->get(\OCP\Security\IHasher::class)->verify('a', '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', $newHash)); + * var_dump(\OCP\Server::get(\OCP\Security\IHasher::class)->verify('a', '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8', $newHash)); * var_dump($newHash); * * @since 8.0.0 @@ -47,4 +47,11 @@ interface IHasher { * @since 8.0.0 */ public function verify(string $message, string $hash, &$newHash = null): bool ; + + /** + * Check if the prefixed hash is valid + * + * @since 30.0.0 + */ + public function validate(string $prefixedHash): bool; } diff --git a/lib/public/Security/ISecureRandom.php b/lib/public/Security/ISecureRandom.php index 188236dd3f9..0f4a79e08e0 100644 --- a/lib/public/Security/ISecureRandom.php +++ b/lib/public/Security/ISecureRandom.php @@ -14,7 +14,7 @@ namespace OCP\Security; * use a fallback. * * Usage: - * \OC::$server->get(ISecureRandom::class)->generate(10); + * \OCP\Server::get(ISecureRandom::class)->generate(10); * * @since 8.0.0 */ @@ -58,7 +58,7 @@ interface ISecureRandom { * Generate a random string of specified length. * @param int $length The length of the generated string * @param string $characters An optional list of characters to use if no character list is - * specified all valid base64 characters are used. + * specified all valid base64 characters are used. * @return string * @since 8.0.0 */ diff --git a/lib/public/Security/Ip/IAddress.php b/lib/public/Security/Ip/IAddress.php new file mode 100644 index 00000000000..bff7744ddce --- /dev/null +++ b/lib/public/Security/Ip/IAddress.php @@ -0,0 +1,35 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Security\Ip; + +/** + * @since 30.0.0 + */ +interface IAddress { + /** + * Check if a given IP address is valid + * + * @since 30.0.0 + */ + public static function isValid(string $ip): bool; + + /** + * Check if current address is contained by given ranges + * + * @since 30.0.0 + */ + public function matches(IRange ... $ranges): bool; + + /** + * Normalized IP address + * + * @since 30.0.0 + */ + public function __toString(): string; +} diff --git a/lib/public/Security/Ip/IFactory.php b/lib/public/Security/Ip/IFactory.php new file mode 100644 index 00000000000..3b88aa8c756 --- /dev/null +++ b/lib/public/Security/Ip/IFactory.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Security\Ip; + +/** + * @since 30.0.0 + */ +interface IFactory { + /** + * Creates a range from string + * + * @since 30.0.0 + * @throws \InvalidArgumentException on invalid range + */ + public function rangeFromString(string $range): IRange; + + /** + * Creates a address from string + * + * @since 30.0.0 + * @throws \InvalidArgumentException on invalid IP + */ + public function addressFromString(string $ip): IAddress; +} diff --git a/lib/public/Security/Ip/IRange.php b/lib/public/Security/Ip/IRange.php new file mode 100644 index 00000000000..70e1815c75e --- /dev/null +++ b/lib/public/Security/Ip/IRange.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Security\Ip; + +/** + * IP Range (IPv4 or IPv6) + * + * @since 30.0.0 + */ +interface IRange { + /** + * Check if a given range is valid + * + * @since 30.0.0 + */ + public static function isValid(string $range): bool; + + /** + * Check if an address is in the current range + * + * @since 30.0.0 + */ + public function contains(IAddress $address): bool; + + /** + * Normalized IP range + * + * @since 30.0.0 + */ + public function __toString(): string; +} diff --git a/lib/public/Security/Ip/IRemoteAddress.php b/lib/public/Security/Ip/IRemoteAddress.php new file mode 100644 index 00000000000..19a1dab9734 --- /dev/null +++ b/lib/public/Security/Ip/IRemoteAddress.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Security\Ip; + +/** + * IP address of the connected client + * + * @since 30.0.0 + */ +interface IRemoteAddress { + /** + * Check if the current remote address is allowed to perform admin actions + * @since 30.0.0 + */ + public function allowsAdminActions(): bool; +} diff --git a/lib/public/Security/PasswordContext.php b/lib/public/Security/PasswordContext.php new file mode 100644 index 00000000000..909070c09ff --- /dev/null +++ b/lib/public/Security/PasswordContext.php @@ -0,0 +1,29 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Security; + +/** + * Define the context in which a password is used. + * This allows setting a context for password validation and password generation. + * + * @package OCP\Security + * @since 31.0.0 + */ +enum PasswordContext { + /** + * Password used for an user account + * @since 31.0.0 + */ + case ACCOUNT; + + /** + * Password used for (public) shares + * @since 31.0.0 + */ + case SHARING; +} diff --git a/lib/public/Security/RateLimiting/ILimiter.php b/lib/public/Security/RateLimiting/ILimiter.php index 50e2e9008ea..22a07f3d430 100644 --- a/lib/public/Security/RateLimiting/ILimiter.php +++ b/lib/public/Security/RateLimiting/ILimiter.php @@ -34,8 +34,8 @@ interface ILimiter { * */ public function registerAnonRequest(string $identifier, - int $anonLimit, - int $anonPeriod, + int $anonLimit, + int $anonPeriod, string $ip): void; /** @@ -50,7 +50,7 @@ interface ILimiter { * */ public function registerUserRequest(string $identifier, - int $userLimit, - int $userPeriod, - IUser $user): void; + int $userLimit, + int $userPeriod, + IUser $user): void; } diff --git a/lib/public/Server.php b/lib/public/Server.php index 55dbfed578e..e30d67701f6 100644 --- a/lib/public/Server.php +++ b/lib/public/Server.php @@ -15,6 +15,7 @@ use Psr\Container\NotFoundExceptionInterface; * use whenever possible dependency injections instead. * * ```php + * use OCP\ITagManager; * use OCP\Server; * * $tagManager = Server::get(ITagManager::class); @@ -24,12 +25,9 @@ use Psr\Container\NotFoundExceptionInterface; */ final class Server { /** - * @template T - * @param class-string<T>|string $serviceName - * @return T|mixed - * @psalm-template S as class-string<T>|string - * @psalm-param S $serviceName - * @psalm-return (S is class-string<T> ? T : mixed) + * @psalm-template T + * @psalm-param class-string<T>|string $serviceName + * @psalm-return ($serviceName is class-string<T> ? T : mixed) * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface * @since 25.0.0 diff --git a/lib/public/ServerVersion.php b/lib/public/ServerVersion.php new file mode 100644 index 00000000000..d44452d42d0 --- /dev/null +++ b/lib/public/ServerVersion.php @@ -0,0 +1,108 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP; + +/** + * @since 31.0.0 + */ +class ServerVersion { + + private array $version; + private string $versionString; + private string $build; + /** @var 'beta'|'stable'|'enterprise'|'git' */ + private string $channel; + + /** + * @since 31.0.0 + */ + public function __construct() { + $versionFile = __DIR__ . '/../../version.php'; + require $versionFile; + + /** @var int[] $OC_Version */ + $this->version = $OC_Version; + /** @var string $OC_VersionString */ + $this->versionString = $OC_VersionString; + /** @var string $OC_Build */ + $this->build = $OC_Build; + /** @var string $OC_Channel */ + $this->channel = $OC_Channel; + } + + /** + * @since 31.0.0 + */ + public function getMajorVersion(): int { + return $this->version[0]; + } + + /** + * @since 31.0.0 + */ + public function getMinorVersion(): int { + return $this->version[1]; + } + + /** + * @since 31.0.0 + */ + public function getPatchVersion(): int { + return $this->version[2]; + } + + /** + * @since 31.0.0 + */ + public function getVersion(): array { + return $this->version; + } + + /** + * @since 31.0.0 + */ + public function getVersionString(): string { + return $this->versionString; + } + + /** + * @psalm-return 'beta'|'stable'|'enterprise'|'git' + * @since 31.0.0 + */ + public function getChannel(): string { + $updaterChannel = Server::get(IConfig::class)->getSystemValueString('updater.release.channel', $this->channel); + + if (in_array($updaterChannel, ['beta', 'stable', 'enterprise', 'git'], true)) { + return $updaterChannel; + } + + return $this->channel; + } + + /** + * @since 31.0.0 + */ + public function getBuild(): string { + return $this->build; + } + + /** + * @since 31.0.0 + */ + public function getHumanVersion(): string { + $version = $this->getVersionString(); + $build = $this->getBuild(); + if (!empty($build) && $this->getChannel() === 'daily') { + $version .= ' Build:' . $build; + } + return $version; + + } +} diff --git a/lib/public/Settings/DeclarativeSettingsTypes.php b/lib/public/Settings/DeclarativeSettingsTypes.php index 82444af9b82..7edf0cbdd6e 100644 --- a/lib/public/Settings/DeclarativeSettingsTypes.php +++ b/lib/public/Settings/DeclarativeSettingsTypes.php @@ -32,8 +32,9 @@ final class DeclarativeSettingsTypes { /** * IDeclarativeSettingsForm storage_type which is determines where and how the config value is stored * - * - * For `external` storage_type the app implementing \OCP\Settings\SetDeclarativeSettingsValueEvent and \OCP\Settings\GetDeclarativeSettingsValueEvent events is responsible for storing and retrieving the config value. + * For `external` storage_type the app needs to either implement event listeners for \OCP\Settings\SetDeclarativeSettingsValueEvent + * and \OCP\Settings\GetDeclarativeSettingsValueEvent or the IDeclarativeSettingsForm also needs to implement + * IDeclarativeSettingsFormWithHandlers for storing and retrieving the config value. * * @since 29.0.0 */ @@ -43,7 +44,6 @@ final class DeclarativeSettingsTypes { * IDeclarativeSettingsForm storage_type which is determines where and how the config value is stored * * For `internal` storage_type the config value is stored in default `appconfig` and `preferences` tables. - * For `external` storage_type the app implementing \OCP\Settings\SetDeclarativeSettingsValueEvent and \OCP\Settings\GetDeclarativeSettingsValueEvent events is responsible for storing and retrieving the config value. * * @since 29.0.0 */ diff --git a/lib/public/Settings/Events/DeclarativeSettingsRegisterFormEvent.php b/lib/public/Settings/Events/DeclarativeSettingsRegisterFormEvent.php index 951e4ad9298..e861afdd580 100644 --- a/lib/public/Settings/Events/DeclarativeSettingsRegisterFormEvent.php +++ b/lib/public/Settings/Events/DeclarativeSettingsRegisterFormEvent.php @@ -22,7 +22,9 @@ class DeclarativeSettingsRegisterFormEvent extends Event { /** * @since 29.0.0 */ - public function __construct(private IDeclarativeManager $manager) { + public function __construct( + private IDeclarativeManager $manager, + ) { parent::__construct(); } diff --git a/lib/public/Settings/IDeclarativeSettingsForm.php b/lib/public/Settings/IDeclarativeSettingsForm.php index d471cdf4a93..419905b7b23 100644 --- a/lib/public/Settings/IDeclarativeSettingsForm.php +++ b/lib/public/Settings/IDeclarativeSettingsForm.php @@ -27,6 +27,7 @@ namespace OCP\Settings; * label?: string, * default: mixed, * options?: list<string|array{name: string, value: mixed}>, + * sensitive?: boolean, * } * * @psalm-type DeclarativeSettingsFormFieldWithValue = DeclarativeSettingsFormField&array{ diff --git a/lib/public/Settings/IDeclarativeSettingsFormWithHandlers.php b/lib/public/Settings/IDeclarativeSettingsFormWithHandlers.php new file mode 100644 index 00000000000..180df73d995 --- /dev/null +++ b/lib/public/Settings/IDeclarativeSettingsFormWithHandlers.php @@ -0,0 +1,31 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Settings; + +use OCP\IUser; + +/** + * @since 31.0.0 + */ +interface IDeclarativeSettingsFormWithHandlers extends IDeclarativeSettingsForm { + + /** + * This function is called to get the current value of a specific forms field. + * @since 31.0.0 + */ + public function getValue(string $fieldId, IUser $user): mixed; + + /** + * This function is called when a user updated a form field to persist the setting. + * @since 31.0.0 + */ + public function setValue(string $fieldId, mixed $value, IUser $user): void; + +} diff --git a/lib/public/Settings/IIconSection.php b/lib/public/Settings/IIconSection.php index 5a9fbfebf76..4d0fe40aa29 100644 --- a/lib/public/Settings/IIconSection.php +++ b/lib/public/Settings/IIconSection.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -29,8 +30,8 @@ interface IIconSection { /** * @return int whether the form should be rather on the top or bottom of - * the settings navigation. The sections are arranged in ascending order of - * the priority values. It is required to return a value between 0 and 99. + * the settings navigation. The sections are arranged in ascending order of + * the priority values. It is required to return a value between 0 and 99. * * E.g.: 70 * @since 9.1 diff --git a/lib/public/Settings/IManager.php b/lib/public/Settings/IManager.php index d73e4055f31..954fd3fdb56 100644 --- a/lib/public/Settings/IManager.php +++ b/lib/public/Settings/IManager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -89,7 +90,7 @@ interface IManager { /** * Returns a list of admin settings that the given user can use for the give section * - * @return array<int, list<ISettings>> The array of admin settings there admin delegation is allowed. + * @return array<int, list<ISettings>> List of admin-settings the user has access to, with priority as key. * @since 23.0.0 */ public function getAllowedAdminSettings(string $section, IUser $user): array; diff --git a/lib/public/Settings/ISettings.php b/lib/public/Settings/ISettings.php index ed465a07fb3..e33556f0b4a 100644 --- a/lib/public/Settings/ISettings.php +++ b/lib/public/Settings/ISettings.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -25,8 +26,8 @@ interface ISettings { /** * @return int whether the form should be rather on the top or bottom of - * the admin section. The forms are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. * * E.g.: 70 * @since 9.1 diff --git a/lib/public/SetupCheck/CheckServerResponseTrait.php b/lib/public/SetupCheck/CheckServerResponseTrait.php new file mode 100644 index 00000000000..29a2215aa67 --- /dev/null +++ b/lib/public/SetupCheck/CheckServerResponseTrait.php @@ -0,0 +1,162 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\SetupCheck; + +use Generator; +use OCP\Http\Client\IClientService; +use OCP\Http\Client\IResponse; +use OCP\IConfig; +use OCP\IURLGenerator; +use OCP\L10N\IFactory; +use Psr\Log\LoggerInterface; + +/** + * Common trait for setup checks that need to use requests to the same server and check the response + * @since 31.0.0 + */ +trait CheckServerResponseTrait { + protected IConfig $config; + protected IURLGenerator $urlGenerator; + protected IClientService $clientService; + protected LoggerInterface $logger; + + /** + * Common helper string in case a check could not fetch any results + * @since 31.0.0 + */ + protected function serverConfigHelp(): string { + $l10n = \OCP\Server::get(IFactory::class)->get('lib'); + return $l10n->t('To allow this check to run you have to make sure that your Web server can connect to itself. Therefore it must be able to resolve and connect to at least one of its `trusted_domains` or the `overwrite.cli.url`. This failure may be the result of a server-side DNS mismatch or outbound firewall rule.'); + } + + /** + * Get all possible URLs that need to be checked for a local request test. + * This takes all `trusted_domains` and the CLI overwrite URL into account. + * + * @param string $url The absolute path (absolute URL without host but with web-root) to test starting with a / + * @param bool $isRootRequest Set to remove the web-root from URL and host (e.g. when requesting a path in the domain root like '/.well-known') + * @return list<string> List of possible absolute URLs + * @since 31.0.0 + */ + protected function getTestUrls(string $url, bool $isRootRequest = false): array { + $url = '/' . ltrim($url, '/'); + + $webroot = rtrim($this->urlGenerator->getWebroot(), '/'); + if ($isRootRequest === false && $webroot !== '' && str_starts_with($url, $webroot)) { + // The URL contains the web-root but also the base url does so, + // so we need to remove the web-root from the URL. + $url = substr($url, strlen($webroot)); + } + + // Base URLs to test + $baseUrls = []; + + // Try overwrite.cli.url first, it’s supposed to be how the server contacts itself + $cliUrl = $this->config->getSystemValueString('overwrite.cli.url', ''); + if ($cliUrl !== '') { + // The CLI URL already contains the web-root, so we need to normalize it if requested + $baseUrls[] = $this->normalizeUrl( + $cliUrl, + $isRootRequest + ); + } + + // Try URL generator second + // The base URL also contains the webroot so also normalize it + $baseUrls[] = $this->normalizeUrl( + $this->urlGenerator->getBaseUrl(), + $isRootRequest + ); + + /* Last resort: trusted domains */ + $trustedDomains = $this->config->getSystemValue('trusted_domains', []); + foreach ($trustedDomains as $host) { + if (str_contains($host, '*')) { + /* Ignore domains with a wildcard */ + continue; + } + $baseUrls[] = $this->normalizeUrl("https://$host$webroot", $isRootRequest); + $baseUrls[] = $this->normalizeUrl("http://$host$webroot", $isRootRequest); + } + + return array_map(fn (string $host) => $host . $url, array_values(array_unique($baseUrls))); + } + + /** + * Run a HTTP request to check header + * @param string $method The HTTP method to use + * @param string $url The absolute path (URL with webroot but without host) to check, can be the output of `IURLGenerator` + * @param bool $isRootRequest If set the webroot is removed from URLs to make the request target the host's root. Example usage are the /.well-known URLs in the root path. + * @param array{ignoreSSL?: bool, httpErrors?: bool, options?: array} $options HTTP client related options, like + * [ + * // Ignore invalid SSL certificates (e.g. self signed) + * 'ignoreSSL' => true, + * // Ignore requests with HTTP errors (will not yield if request has a 4xx or 5xx response) + * 'httpErrors' => true, + * // Additional options for the HTTP client (see `IClient`) + * 'options' => [], + * ] + * + * @return Generator<int, IResponse> + * @since 31.0.0 + */ + protected function runRequest(string $method, string $url, array $options = [], bool $isRootRequest = false): Generator { + $options = array_merge(['ignoreSSL' => true, 'httpErrors' => true], $options); + + $client = $this->clientService->newClient(); + $requestOptions = $this->getRequestOptions($options['ignoreSSL'], $options['httpErrors']); + $requestOptions = array_merge($requestOptions, $options['options'] ?? []); + + foreach ($this->getTestUrls($url, $isRootRequest) as $testURL) { + try { + yield $client->request($method, $testURL, $requestOptions); + } catch (\Throwable $e) { + $this->logger->debug('Can not connect to local server for running setup checks', ['exception' => $e, 'url' => $testURL]); + } + } + } + + /** + * Get HTTP client options + * @param bool $ignoreSSL If set SSL errors are ignored (e.g. self-signed certificates) + * @since 31.0.0 + */ + private function getRequestOptions(bool $ignoreSSL, bool $httpErrors): array { + $requestOptions = [ + 'connect_timeout' => 10, + 'http_errors' => $httpErrors, + 'nextcloud' => [ + 'allow_local_address' => true, + ], + ]; + if ($ignoreSSL) { + $requestOptions['verify'] = false; + } + return $requestOptions; + } + + /** + * Strip a trailing slash and remove the webroot if requested. + * @param string $url The URL to normalize. Should be an absolute URL containing scheme, host and optionally web-root. + * @param bool $removeWebroot If set the web-root is removed from the URL and an absolute URL with only the scheme and host (optional port) is returned + * @since 31.0.0 + */ + private function normalizeUrl(string $url, bool $removeWebroot): string { + if ($removeWebroot) { + $segments = parse_url($url); + if (!isset($segments['scheme']) || !isset($segments['host'])) { + throw new \InvalidArgumentException('URL is missing scheme or host'); + } + + $port = isset($segments['port']) ? (':' . $segments['port']) : ''; + return $segments['scheme'] . '://' . $segments['host'] . $port; + } + return rtrim($url, '/'); + } +} diff --git a/lib/public/SetupCheck/SetupResult.php b/lib/public/SetupCheck/SetupResult.php index c79c4e762da..7d64b838087 100644 --- a/lib/public/SetupCheck/SetupResult.php +++ b/lib/public/SetupCheck/SetupResult.php @@ -45,6 +45,7 @@ class SetupResult implements \JsonSerializable { /** * @brief Private constructor, use success()/info()/warning()/error() instead * @param self::SUCCESS|self::INFO|self::WARNING|self::ERROR $severity + * @param array<string, array<string, string>> $descriptionParameters * @throws \OCP\RichObjectStrings\InvalidObjectExeption * @since 28.0.0 * @since 28.0.2 Optional parameter ?array $descriptionParameters diff --git a/lib/public/Share/Exceptions/ShareTokenException.php b/lib/public/Share/Exceptions/ShareTokenException.php new file mode 100644 index 00000000000..027d00640e9 --- /dev/null +++ b/lib/public/Share/Exceptions/ShareTokenException.php @@ -0,0 +1,16 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Share\Exceptions; + +use Exception; + +/** + * @since 31.0.0 + */ +class ShareTokenException extends Exception { +} diff --git a/lib/public/Share/IAttributes.php b/lib/public/Share/IAttributes.php index af7277c39c0..9ddd8275dd6 100644 --- a/lib/public/Share/IAttributes.php +++ b/lib/public/Share/IAttributes.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019 ownCloud GmbH * SPDX-License-Identifier: AGPL-3.0-only @@ -13,26 +14,26 @@ namespace OCP\Share; */ interface IAttributes { /** - * Sets an attribute enabled/disabled. If the key did not exist before it will be created. + * Sets an attribute. If the key did not exist before it will be created. * * @param string $scope scope * @param string $key key - * @param bool $enabled enabled + * @param bool|string|array|null $value value * @return IAttributes The modified object * @since 25.0.0 */ - public function setAttribute($scope, $key, $enabled); + public function setAttribute(string $scope, string $key, mixed $value): IAttributes; /** - * Returns if attribute is enabled/disabled for given scope id and key. + * Returns the attribute for given scope id and key. * If attribute does not exist, returns null * * @param string $scope scope * @param string $key key - * @return bool|null + * @return bool|string|array|null * @since 25.0.0 */ - public function getAttribute($scope, $key); + public function getAttribute(string $scope, string $key): mixed; /** * Formats the IAttributes object to array with the following format: @@ -40,13 +41,14 @@ interface IAttributes { * 0 => [ * "scope" => <string>, * "key" => <string>, - * "enabled" => <bool> + * "value" => <bool|string|array|null>, * ], * ... * ] * * @return array formatted IAttributes * @since 25.0.0 + * @since 30.0.0, `enabled` was renamed to `value` */ - public function toArray(); + public function toArray(): array; } diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index 063e776e68a..35915ad9d90 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -13,6 +13,7 @@ use OCP\Files\Node; use OCP\IUser; use OCP\Share\Exceptions\GenericShareException; use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\Exceptions\ShareTokenException; /** * This interface allows to manage sharing files between users and groups. @@ -41,11 +42,12 @@ interface IManager { * The state can't be changed this way: use acceptShare * * @param IShare $share + * @param bool $onlyValid Only updates valid shares, invalid shares will be deleted automatically and are not updated * @return IShare The share object * @throws \InvalidArgumentException * @since 9.0.0 */ - public function updateShare(IShare $share); + public function updateShare(IShare $share, bool $onlyValid = true); /** * Accept a share. @@ -127,10 +129,11 @@ interface IManager { * @param bool $reshares * @param int $limit The maximum number of returned results, -1 for all results * @param int $offset + * @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned * @return IShare[] * @since 9.0.0 */ - public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0); + public function getSharesBy($userId, $shareType, $path = null, $reshares = false, $limit = 50, $offset = 0, bool $onlyValid = true); /** * Get shares shared with $user. @@ -168,11 +171,12 @@ interface IManager { * * @param string $id * @param string|null $recipient userID of the recipient + * @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned * @return IShare * @throws ShareNotFound * @since 9.0.0 */ - public function getShareById($id, $recipient = null); + public function getShareById($id, $recipient = null, bool $onlyValid = true); /** * Get the share by token possible with password @@ -300,7 +304,7 @@ interface IManager { public function shareApiAllowLinks(); /** - * Is password on public link requires + * Is password on public link required * * @param bool $checkGroupMembership Check group membership exclusion * @return bool @@ -460,6 +464,22 @@ interface IManager { */ public function ignoreSecondDisplayName(): bool; + + /** + * Check if custom tokens are allowed + * + * @since 31.0.0 + */ + public function allowCustomTokens(): bool; + + /** + * Check if the current user can view the share + * even if the download is disabled. + * + * @since 32.0.0 + */ + public function allowViewWithoutDownload(): bool; + /** * Check if the current user can enumerate the target user * @@ -473,11 +493,9 @@ interface IManager { /** * Check if sharing is disabled for the given user * - * @param string $userId - * @return bool * @since 9.0.0 */ - public function sharingDisabledForUser($userId); + public function sharingDisabledForUser(?string $userId): bool; /** * Check if outgoing server2server shares are allowed @@ -519,4 +537,12 @@ interface IManager { * @since 18.0.0 */ public function getAllShares(): iterable; + + /** + * Generate a unique share token + * + * @throws ShareTokenException Failed to generate a unique token + * @since 31.0.0 + */ + public function generateToken(): string; } diff --git a/lib/public/Share/IProviderFactory.php b/lib/public/Share/IProviderFactory.php index 34656af2d8c..9107274c8c1 100644 --- a/lib/public/Share/IProviderFactory.php +++ b/lib/public/Share/IProviderFactory.php @@ -39,7 +39,8 @@ interface IProviderFactory { /** * @since 21.0.0 - * @param string $shareProvier + * @since 32.0.0 Fix typo in parameter name + * @param string $shareProviderClass */ - public function registerProvider(string $shareProvier): void; + public function registerProvider(string $shareProviderClass): void; } diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php index 91eebd3afa9..a1bdb01fcd2 100644 --- a/lib/public/Share/IShare.php +++ b/lib/public/Share/IShare.php @@ -368,7 +368,7 @@ interface IShare { * @return \OCP\Share\IShare The modified object * @since 9.0.0 */ - public function setExpirationDate(\DateTime|null $expireDate); + public function setExpirationDate(?\DateTime $expireDate); /** * Get the expiration date @@ -379,7 +379,7 @@ interface IShare { public function getExpirationDate(); /** - * Set overwrite flag for falsy expiry date vavlues + * Set overwrite flag for falsy expiry date values * * @param bool $noExpirationDate * @return \OCP\Share\IShare The modified object @@ -530,6 +530,20 @@ interface IShare { public function getToken(); /** + * Set the parent of this share + * + * @since 9.0.0 + */ + public function setParent(int $parent): self; + + /** + * Get the parent of this share. + * + * @since 9.0.0 + */ + public function getParent(): ?int; + + /** * Set the target path of this share relative to the recipients user folder. * * @param string $target @@ -564,7 +578,7 @@ interface IShare { public function getShareTime(); /** - * Set if the recipient is informed by mail about the share. + * Set if the recipient should be informed by mail about the share. * * @param bool $mailSend * @return \OCP\Share\IShare The modified object @@ -573,7 +587,7 @@ interface IShare { public function setMailSend($mailSend); /** - * Get if the recipient informed by mail about the share. + * Get if the recipient should be informed by mail about the share. * * @return bool * @since 9.0.0 @@ -617,4 +631,27 @@ interface IShare { * @since 15.0.0 */ public function getHideDownload(): bool; + + /** + * Sets a flag that stores whether a reminder via email has been sent + * + * @return self The modified object + * @since 31.0.0 + */ + public function setReminderSent(bool $reminderSent): IShare; + + /** + * Gets a flag that stores whether a reminder via email has been sent + * + * @return bool + * @since 31.0.0 + */ + public function getReminderSent(): bool; + + /** + * Check if the current user can see this share files contents. + * This will check the download permissions as well as the global + * admin setting to allow viewing files without downloading. + */ + public function canSeeContent(): bool; } diff --git a/lib/public/Share/IShareHelper.php b/lib/public/Share/IShareHelper.php index c4a33e717f9..152fc99a446 100644 --- a/lib/public/Share/IShareHelper.php +++ b/lib/public/Share/IShareHelper.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Share/IShareProvider.php b/lib/public/Share/IShareProvider.php index c51bad8f9ef..23187ca833e 100644 --- a/lib/public/Share/IShareProvider.php +++ b/lib/public/Share/IShareProvider.php @@ -45,16 +45,6 @@ interface IShareProvider { public function update(\OCP\Share\IShare $share); /** - * Accept a share. - * - * @param IShare $share - * @param string $recipient - * @return IShare The share object - * @since 17.0.0 - */ - // public function acceptShare(IShare $share, string $recipient): IShare; - - /** * Delete a share * * @param \OCP\Share\IShare $share @@ -218,4 +208,12 @@ interface IShareProvider { * @since 18.0.0 */ public function getAllShares(): iterable; + + /** + * Get all children of this share + * + * @return IShare[] + * @since 9.0.0 + */ + public function getChildren(IShare $parent); } diff --git a/lib/public/Share/IShareProviderSupportsAccept.php b/lib/public/Share/IShareProviderSupportsAccept.php new file mode 100644 index 00000000000..c3a0c2d0218 --- /dev/null +++ b/lib/public/Share/IShareProviderSupportsAccept.php @@ -0,0 +1,27 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Share; + +/** + * Interface IShareProviderSupportsAccept + * + * This interface allows to define IShareProvider that can handle the `acceptShare` method, + * which is available since Nextcloud 17. + * + * @since 30.0.0 + */ +interface IShareProviderSupportsAccept extends IShareProvider { + /** + * Accept a share. + * + * @param IShare $share + * @param string $recipient + * @return IShare The share object + * @since 30.0.0 + */ + public function acceptShare(IShare $share, string $recipient): IShare; +} diff --git a/lib/public/Share/IShareProviderSupportsAllSharesInFolder.php b/lib/public/Share/IShareProviderSupportsAllSharesInFolder.php new file mode 100644 index 00000000000..e27da7682ce --- /dev/null +++ b/lib/public/Share/IShareProviderSupportsAllSharesInFolder.php @@ -0,0 +1,24 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Share; + +use OCP\Files\Folder; + +/** + * Allows defining a IShareProvider with support for the getAllSharesInFolder method. + * + * @since 32.0.0 + */ +interface IShareProviderSupportsAllSharesInFolder extends IShareProvider { + /** + * Get all shares in a folder. + * + * @return array<int, list<IShare>> + * @since 32.0.0 + */ + public function getAllSharesInFolder(Folder $node): array; +} diff --git a/lib/public/Share/IShareProviderWithNotification.php b/lib/public/Share/IShareProviderWithNotification.php new file mode 100644 index 00000000000..c2ba8118175 --- /dev/null +++ b/lib/public/Share/IShareProviderWithNotification.php @@ -0,0 +1,23 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\Share; + +/** + * Interface IShareProvider + * + * @since 30.0.0 + */ +interface IShareProviderWithNotification extends IShareProvider { + /** + * Send a mail notification to the recipient of a share + * @param IShare $share + * @return bool True if the mail was sent successfully + * @throws \Exception If the mail could not be sent + * @since 30.0.0 + */ + public function sendMailNotification(IShare $share): bool; +} diff --git a/lib/public/Share_Backend.php b/lib/public/Share_Backend.php index b77731ccdfd..0fa79854e10 100644 --- a/lib/public/Share_Backend.php +++ b/lib/public/Share_Backend.php @@ -30,14 +30,14 @@ interface Share_Backend { * Get a unique name of the item for the specified user * @param string $itemSource * @param string|false $shareWith User the item is being shared with - * @param array|null $exclude List of similar item names already existing as shared items @deprecated since version OC7 * @return string Target name * * This function needs to verify that the user does not already have an item with this name. * If it does generate a new name e.g. name_# * @since 5.0.0 + * @deprecated 31.0.0 */ - public function generateTarget($itemSource, $shareWith, $exclude = null); + public function generateTarget($itemSource, $shareWith); /** * Converts the shared item sources back into the item in the specified format diff --git a/lib/public/Share_Backend_Collection.php b/lib/public/Share_Backend_Collection.php index 3c3c42ce9f9..8dbe07db851 100644 --- a/lib/public/Share_Backend_Collection.php +++ b/lib/public/Share_Backend_Collection.php @@ -11,7 +11,7 @@ namespace OCP; /** - * Interface for collections of of items implemented by another share backend. + * Interface for collections of items implemented by another share backend. * Extends the Share_Backend interface. * @since 5.0.0 */ diff --git a/lib/public/SpeechToText/Events/AbstractTranscriptionEvent.php b/lib/public/SpeechToText/Events/AbstractTranscriptionEvent.php index eeb7695b387..6554292d413 100644 --- a/lib/public/SpeechToText/Events/AbstractTranscriptionEvent.php +++ b/lib/public/SpeechToText/Events/AbstractTranscriptionEvent.php @@ -13,6 +13,7 @@ use OCP\Files\File; /** * @since 27.0.0 + * @deprecated 30.0.0 */ abstract class AbstractTranscriptionEvent extends Event { /** diff --git a/lib/public/SpeechToText/Events/TranscriptionFailedEvent.php b/lib/public/SpeechToText/Events/TranscriptionFailedEvent.php index 53eb56c2ea9..4c2b0d52017 100644 --- a/lib/public/SpeechToText/Events/TranscriptionFailedEvent.php +++ b/lib/public/SpeechToText/Events/TranscriptionFailedEvent.php @@ -15,6 +15,7 @@ use OCP\Files\File; /** * This Event is emitted if a transcription of a media file using a Speech-To-Text provider failed * @since 27.0.0 + * @deprecated 30.0.0 */ class TranscriptionFailedEvent extends AbstractTranscriptionEvent { /** diff --git a/lib/public/SpeechToText/Events/TranscriptionSuccessfulEvent.php b/lib/public/SpeechToText/Events/TranscriptionSuccessfulEvent.php index 1e333fdc3c7..e8e5359a29e 100644 --- a/lib/public/SpeechToText/Events/TranscriptionSuccessfulEvent.php +++ b/lib/public/SpeechToText/Events/TranscriptionSuccessfulEvent.php @@ -15,6 +15,7 @@ use OCP\Files\File; /** * This Event is emitted when a transcription of a media file happened successfully * @since 27.0.0 + * @deprecated 30.0.0 */ class TranscriptionSuccessfulEvent extends AbstractTranscriptionEvent { /** diff --git a/lib/public/SpeechToText/ISpeechToTextManager.php b/lib/public/SpeechToText/ISpeechToTextManager.php index 27523a69a95..6bd95197695 100644 --- a/lib/public/SpeechToText/ISpeechToTextManager.php +++ b/lib/public/SpeechToText/ISpeechToTextManager.php @@ -17,6 +17,7 @@ use RuntimeException; /** * @since 27.0.0 + * @deprecated 30.0.0 */ interface ISpeechToTextManager { /** @@ -58,11 +59,13 @@ interface ISpeechToTextManager { /** * @param File $file The media file to transcribe + * @param ?string $userId The user that triggered this request + * @param string $appId The app that triggered this request * @returns string The transcription of the passed media file * @throws PreConditionNotMetException If no provider was registered but this method was still called * @throws InvalidArgumentException If the file could not be found or is not of a supported type * @throws RuntimeException If the transcription failed for other reasons * @since 27.0.0 */ - public function transcribeFile(File $file): string; + public function transcribeFile(File $file, ?string $userId, string $appId): string; } diff --git a/lib/public/SpeechToText/ISpeechToTextProvider.php b/lib/public/SpeechToText/ISpeechToTextProvider.php index a1aca06e2e4..dce4a68ea0a 100644 --- a/lib/public/SpeechToText/ISpeechToTextProvider.php +++ b/lib/public/SpeechToText/ISpeechToTextProvider.php @@ -15,6 +15,7 @@ use RuntimeException; /** * @since 27.0.0 + * @deprecated 30.0.0 */ interface ISpeechToTextProvider { /** diff --git a/lib/public/SpeechToText/ISpeechToTextProviderWithId.php b/lib/public/SpeechToText/ISpeechToTextProviderWithId.php index a1d73d10a58..8020c6c9b96 100644 --- a/lib/public/SpeechToText/ISpeechToTextProviderWithId.php +++ b/lib/public/SpeechToText/ISpeechToTextProviderWithId.php @@ -8,6 +8,7 @@ namespace OCP\SpeechToText; /** * @since 28.0.0 + * @deprecated 30.0.0 */ interface ISpeechToTextProviderWithId extends ISpeechToTextProvider { diff --git a/lib/public/SpeechToText/ISpeechToTextProviderWithUserId.php b/lib/public/SpeechToText/ISpeechToTextProviderWithUserId.php index f1b84476b9a..d1bc4216a38 100644 --- a/lib/public/SpeechToText/ISpeechToTextProviderWithUserId.php +++ b/lib/public/SpeechToText/ISpeechToTextProviderWithUserId.php @@ -12,6 +12,7 @@ namespace OCP\SpeechToText; /** * @since 29.0.0 + * @deprecated 30.0.0 */ interface ISpeechToTextProviderWithUserId extends ISpeechToTextProvider { /** diff --git a/lib/public/Support/CrashReport/IRegistry.php b/lib/public/Support/CrashReport/IRegistry.php index 9da4f24aaa3..503a21038ca 100644 --- a/lib/public/Support/CrashReport/IRegistry.php +++ b/lib/public/Support/CrashReport/IRegistry.php @@ -14,7 +14,7 @@ use Throwable; /** * @since 13.0.0 - * @deprecated used internally only + * @deprecated 20.0.0 used internally only */ interface IRegistry { /** @@ -35,7 +35,7 @@ interface IRegistry { * @param string $category * @param array $context * - * @deprecated used internally only + * @deprecated 20.0.0 used internally only * @since 15.0.0 */ public function delegateBreadcrumb(string $message, string $category, array $context = []): void; @@ -46,7 +46,7 @@ interface IRegistry { * @param Exception|Throwable $exception * @param array $context * - * @deprecated used internally only + * @deprecated 20.0.0 used internally only * @since 13.0.0 */ public function delegateReport($exception, array $context = []); @@ -59,7 +59,7 @@ interface IRegistry { * * @return void * - * @deprecated used internally only + * @deprecated 20.0.0 used internally only * @since 17.0.0 */ public function delegateMessage(string $message, array $context = []): void; @@ -68,7 +68,7 @@ interface IRegistry { * Check if any reporter has been registered to delegate to * * @return bool - * @deprecated use internally only + * @deprecated 20.0.0 use internally only * @since 26.0.0 */ public function hasReporters(): bool; diff --git a/lib/public/SystemTag/ISystemTag.php b/lib/public/SystemTag/ISystemTag.php index 8a01779f783..4fd93831955 100644 --- a/lib/public/SystemTag/ISystemTag.php +++ b/lib/public/SystemTag/ISystemTag.php @@ -80,4 +80,20 @@ interface ISystemTag { * @since 22.0.0 */ public function getAccessLevel(): int; + + /** + * Returns the ETag of the tag + * The ETag is a unique identifier for the tag and should change whenever the tag changes + * or whenever elements gets added or removed from the tag. + * + * @since 31.0.0 + */ + public function getETag(): ?string; + + /** + * Returns the color of the tag + * + * @since 31.0.0 + */ + public function getColor(): ?string; } diff --git a/lib/public/SystemTag/ISystemTagManager.php b/lib/public/SystemTag/ISystemTagManager.php index 99d806155dc..96e775d6401 100644 --- a/lib/public/SystemTag/ISystemTagManager.php +++ b/lib/public/SystemTag/ISystemTagManager.php @@ -26,7 +26,7 @@ interface ISystemTagManager { * * @throws \InvalidArgumentException if at least one given tag ids is invalid (string instead of integer, etc.) * @throws TagNotFoundException if at least one given tag ids did no exist - * The message contains a json_encoded array of the ids that could not be found + * The message contains a json_encoded array of the ids that could not be found * * @since 9.0.0, optional parameter $user added in 28.0.0 */ @@ -57,8 +57,10 @@ interface ISystemTagManager { * @return ISystemTag system tag * * @throws TagAlreadyExistsException if tag already exists + * @throws TagCreationForbiddenException if user doesn't have the right to create a new tag * * @since 9.0.0 + * @since 31.0.0 Can throw TagCreationForbiddenExceptionif user doesn't have the right to create a new tag */ public function createTag(string $tagName, bool $userVisible, bool $userAssignable): ISystemTag; @@ -81,14 +83,16 @@ interface ISystemTagManager { * @param string $newName the new tag name * @param bool $userVisible whether the tag is visible by users * @param bool $userAssignable whether the tag is assignable by users + * @param string $color color * * @throws TagNotFoundException if tag with the given id does not exist * @throws TagAlreadyExistsException if there is already another tag - * with the same attributes + * with the same attributes * * @since 9.0.0 + * @since 31.0.0 `$color` parameter added */ - public function updateTag(string $tagId, string $newName, bool $userVisible, bool $userAssignable); + public function updateTag(string $tagId, string $newName, bool $userVisible, bool $userAssignable, ?string $color); /** * Delete the given tags from the database and all their relationships. @@ -106,25 +110,47 @@ interface ISystemTagManager { * given id. * * @param ISystemTag $tag tag to check permission for - * @param IUser $user user to check permission for + * @param IUser|null $user user to check permission for * * @return bool true if the user is allowed to assign/unassign the tag, false otherwise * * @since 9.1.0 + * @since 31.0.0 `$user` can be null to check anonymous permissions */ - public function canUserAssignTag(ISystemTag $tag, IUser $user): bool; + public function canUserAssignTag(ISystemTag $tag, ?IUser $user): bool; + + /** + * Checks whether the given user is allowed to create new tags + * + * @param IUser|null $user user to check permission for + * @return bool true if the user is allowed to create a new tag, false otherwise + * + * @since 31.0.0 + */ + public function canUserCreateTag(?IUser $user): bool; + + /** + * Checks whether the given user is allowed to update tags + * + * @param IUser|null $user user to check permission for + * @return bool true if the user is allowed to update a tag, false otherwise + * + * @since 31.0.0 + */ + public function canUserUpdateTag(?IUser $user): bool; /** * Checks whether the given user is allowed to see the tag with the given id. * * @param ISystemTag $tag tag to check permission for - * @param IUser $user user to check permission for + * @param IUser|null $user user to check permission for * * @return bool true if the user can see the tag, false otherwise * * @since 9.1.0 + * @since 31.0.0 `$user` can be null to check anonymous permissions */ - public function canUserSeeTag(ISystemTag $tag, IUser $user): bool; + public function canUserSeeTag(ISystemTag $tag, ?IUser $user): bool; /** * Set groups that can assign a given tag. diff --git a/lib/public/SystemTag/ISystemTagObjectMapper.php b/lib/public/SystemTag/ISystemTagObjectMapper.php index b3eb0201ce6..c604fa93c58 100644 --- a/lib/public/SystemTag/ISystemTagObjectMapper.php +++ b/lib/public/SystemTag/ISystemTagObjectMapper.php @@ -31,7 +31,7 @@ interface ISystemTagObjectMapper { * @param string $objectType object type * * @return array with object id as key and an array - * of tag ids as value + * of tag ids as value * * @since 9.0.0 */ @@ -48,9 +48,9 @@ interface ISystemTagObjectMapper { * @return string[] array of object ids or empty array if none found * * @throws TagNotFoundException if at least one of the - * given tags does not exist + * given tags does not exist * @throws \InvalidArgumentException When a limit is specified together with - * multiple tag ids + * multiple tag ids * * @since 9.0.0 */ @@ -69,7 +69,7 @@ interface ISystemTagObjectMapper { * @param string|array $tagIds tag id or array of tag ids to assign * * @throws TagNotFoundException if at least one of the - * given tags does not exist + * given tags does not exist * * @since 9.0.0 */ @@ -88,7 +88,7 @@ interface ISystemTagObjectMapper { * @param string|array $tagIds tag id or array of tag ids to unassign * * @throws TagNotFoundException if at least one of the - * given tags does not exist + * given tags does not exist * * @since 9.0.0 */ @@ -101,14 +101,37 @@ interface ISystemTagObjectMapper { * @param string $objectType object type * @param string $tagId tag id to check * @param bool $all true to check that ALL objects have the tag assigned, - * false to check that at least ONE object has the tag. + * false to check that at least ONE object has the tag. * * @return bool true if the condition set by $all is matched, false - * otherwise + * otherwise * * @throws TagNotFoundException if the tag does not exist * * @since 9.0.0 */ public function haveTag($objIds, string $objectType, string $tagId, bool $all = true): bool; + + + /** + * Get the list of object types that have objects assigned to them. + * + * @return string[] list of object types + * + * @since 31.0.0 + */ + public function getAvailableObjectTypes(): array; + + /** + * Set the list of object ids for the given tag. + * This will replace the current list of object ids. + * + * @param string $tagId tag id + * @param string $objectType object type + * @param string[] $objectIds list of object ids + * + * @throws TagNotFoundException if the tag does not exist + * @since 31.0.0 + */ + public function setObjectIdsForTag(string $tagId, string $objectType, array $objectIds): void; } diff --git a/lib/public/SystemTag/MapperEvent.php b/lib/public/SystemTag/MapperEvent.php index f8f130d6473..821d1b5dc5e 100644 --- a/lib/public/SystemTag/MapperEvent.php +++ b/lib/public/SystemTag/MapperEvent.php @@ -9,13 +9,14 @@ declare(strict_types=1); namespace OCP\SystemTag; use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IWebhookCompatibleEvent; /** * Class MapperEvent * * @since 9.0.0 */ -class MapperEvent extends Event { +class MapperEvent extends Event implements IWebhookCompatibleEvent { /** * @since 9.0.0 * @deprecated 22.0.0 @@ -84,4 +85,17 @@ class MapperEvent extends Event { public function getTags(): array { return $this->tags; } + + /** + * @return array + * @since 32.0.0 + */ + public function getWebhookSerializable(): array { + return [ + 'eventType' => $this->getEvent(), + 'objectType' => $this->getObjectType(), + 'objectId' => $this->getObjectId(), + 'tagIds' => $this->getTags(), + ]; + } } diff --git a/lib/public/SystemTag/SystemTagsEntityEvent.php b/lib/public/SystemTag/SystemTagsEntityEvent.php index bccff9eda84..d8f391d0dba 100644 --- a/lib/public/SystemTag/SystemTagsEntityEvent.php +++ b/lib/public/SystemTag/SystemTagsEntityEvent.php @@ -37,9 +37,9 @@ class SystemTagsEntityEvent extends Event { /** * @param string $name * @param \Closure $entityExistsFunction The closure should take one - * argument, which is the id of the entity, that tags - * should be handled for. The return should then be bool, - * depending on whether tags are allowed (true) or not. + * argument, which is the id of the entity, that tags + * should be handled for. The return should then be bool, + * depending on whether tags are allowed (true) or not. * @throws \OutOfBoundsException when the entity name is already taken * @since 9.1.0 */ diff --git a/lib/public/SystemTag/TagCreationForbiddenException.php b/lib/public/SystemTag/TagCreationForbiddenException.php new file mode 100644 index 00000000000..eeae38e31f7 --- /dev/null +++ b/lib/public/SystemTag/TagCreationForbiddenException.php @@ -0,0 +1,18 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OCP\SystemTag; + +/** + * Exception when a user doesn't have the right to create a tag + * + * @since 31.0.0 + */ +class TagCreationForbiddenException extends \RuntimeException { +} diff --git a/lib/public/SystemTag/TagUpdateForbiddenException.php b/lib/public/SystemTag/TagUpdateForbiddenException.php new file mode 100644 index 00000000000..e5c1e76f6fe --- /dev/null +++ b/lib/public/SystemTag/TagUpdateForbiddenException.php @@ -0,0 +1,18 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OCP\SystemTag; + +/** + * Exception when a user doesn't have the right to create a tag + * + * @since 31.0.1 + */ +class TagUpdateForbiddenException extends \RuntimeException { +} diff --git a/lib/public/TaskProcessing/EShapeType.php b/lib/public/TaskProcessing/EShapeType.php index d66de6e01a8..f6cfab6b38f 100644 --- a/lib/public/TaskProcessing/EShapeType.php +++ b/lib/public/TaskProcessing/EShapeType.php @@ -23,6 +23,7 @@ enum EShapeType: int { case Audio = 3; case Video = 4; case File = 5; + case Enum = 6; case ListOfNumbers = 10; case ListOfTexts = 11; case ListOfImages = 12; @@ -32,11 +33,33 @@ enum EShapeType: int { /** * @param mixed $value + * @param ShapeEnumValue[] $enumValues + * @return void + * @throws ValidationException + * @since 30.0.0 + */ + public function validateEnum(mixed $value, array $enumValues): void { + if ($this !== EShapeType::Enum) { + throw new ValidationException('Provider provided enum values for non-enum slot'); + } + foreach ($enumValues as $enumValue) { + if ($value === $enumValue->getValue()) { + return; + } + } + throw new ValidationException('Wrong value given for Enum slot. Got "' . $value . '", but expected one of the provided enum values: "' . implode('", "', array_map(fn ($enumValue) => $enumValue->getValue(), $enumValues)) . '"'); + } + + /** + * @param mixed $value * @return void * @throws ValidationException * @since 30.0.0 */ private function validateNonFileType(mixed $value): void { + if ($this === EShapeType::Enum && !is_string($value)) { + throw new ValidationException('Non-text item provided for Enum slot'); + } if ($this === EShapeType::Text && !is_string($value)) { throw new ValidationException('Non-text item provided for Text slot'); } @@ -89,7 +112,7 @@ enum EShapeType: int { * @throws ValidationException * @since 30.0.0 */ - public function validateOutput(mixed $value) { + public function validateOutputWithFileData(mixed $value): void { $this->validateNonFileType($value); if ($this === EShapeType::Image && !is_string($value)) { throw new ValidationException('Non-image item provided for Image slot'); @@ -118,6 +141,40 @@ enum EShapeType: int { } /** + * @param mixed $value + * @return void + * @throws ValidationException + * @since 30.0.0 + */ + public function validateOutputWithFileIds(mixed $value): void { + $this->validateNonFileType($value); + if ($this === EShapeType::Image && !is_numeric($value)) { + throw new ValidationException('Non-image item provided for Image slot'); + } + if ($this === EShapeType::ListOfImages && (!is_array($value) || count(array_filter($value, fn ($item) => !is_numeric($item))) > 0)) { + throw new ValidationException('Non-image list item provided for ListOfImages slot'); + } + if ($this === EShapeType::Audio && !is_numeric($value)) { + throw new ValidationException('Non-audio item provided for Audio slot'); + } + if ($this === EShapeType::ListOfAudios && (!is_array($value) || count(array_filter($value, fn ($item) => !is_numeric($item))) > 0)) { + throw new ValidationException('Non-audio list item provided for ListOfAudio slot'); + } + if ($this === EShapeType::Video && !is_numeric($value)) { + throw new ValidationException('Non-video item provided for Video slot'); + } + if ($this === EShapeType::ListOfVideos && (!is_array($value) || count(array_filter($value, fn ($item) => !is_numeric($item))) > 0)) { + throw new ValidationException('Non-video list item provided for ListOfTexts slot'); + } + if ($this === EShapeType::File && !is_numeric($value)) { + throw new ValidationException('Non-file item provided for File slot'); + } + if ($this === EShapeType::ListOfFiles && (!is_array($value) || count(array_filter($value, fn ($item) => !is_numeric($item))) > 0)) { + throw new ValidationException('Non-audio list item provided for ListOfFiles slot'); + } + } + + /** * @param EShapeType $type * @return EShapeType * @since 30.0.0 @@ -125,4 +182,13 @@ enum EShapeType: int { public static function getScalarType(EShapeType $type): EShapeType { return EShapeType::from($type->value % 10); } + + /** + * @param EShapeType $type + * @return bool + * @since 30.0.0 + */ + public static function isFileType(EShapeType $type): bool { + return in_array(EShapeType::getScalarType($type), [EShapeType::File, EShapeType::Image, EShapeType::Audio, EShapeType::Video], true); + } } diff --git a/lib/public/TaskProcessing/Events/AbstractTaskProcessingEvent.php b/lib/public/TaskProcessing/Events/AbstractTaskProcessingEvent.php index 84dbb8915f8..55f33327e9f 100644 --- a/lib/public/TaskProcessing/Events/AbstractTaskProcessingEvent.php +++ b/lib/public/TaskProcessing/Events/AbstractTaskProcessingEvent.php @@ -19,7 +19,7 @@ abstract class AbstractTaskProcessingEvent extends Event { * @since 30.0.0 */ public function __construct( - private readonly Task $task + private readonly Task $task, ) { parent::__construct(); } diff --git a/lib/public/TaskProcessing/Events/GetTaskProcessingProvidersEvent.php b/lib/public/TaskProcessing/Events/GetTaskProcessingProvidersEvent.php new file mode 100644 index 00000000000..10c94d20406 --- /dev/null +++ b/lib/public/TaskProcessing/Events/GetTaskProcessingProvidersEvent.php @@ -0,0 +1,68 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\TaskProcessing\Events; + +use OCP\EventDispatcher\Event; +use OCP\TaskProcessing\IProvider; +use OCP\TaskProcessing\ITaskType; + +/** + * Event dispatched by the server to collect Task Processing Providers + * and custom Task Types from listeners (like AppAPI). + * + * Listeners should add their providers and task types using the + * addProvider() and addTaskType() methods. + * + * @since 32.0.0 + */ +class GetTaskProcessingProvidersEvent extends Event { + /** @var IProvider[] */ + private array $providers = []; + + /** @var ITaskType[] */ + private array $taskTypes = []; + + /** + * Add a Task Processing Provider. + * + * @param IProvider $provider The provider instance to add. + * @since 32.0.0 + */ + public function addProvider(IProvider $provider): void { + $this->providers[] = $provider; + } + + /** + * Get all collected Task Processing Providers. + * + * @return IProvider[] + * @since 32.0.0 + */ + public function getProviders(): array { + return $this->providers; + } + + /** + * Add a custom Task Processing Task Type. + * + * @param ITaskType $taskType The task type instance to add. + * @since 32.0.0 + */ + public function addTaskType(ITaskType $taskType): void { + $this->taskTypes[] = $taskType; + } + + /** + * Get all collected custom Task Processing Task Types. + * + * @return ITaskType[] + * @since 32.0.0 + */ + public function getTaskTypes(): array { + return $this->taskTypes; + } +} diff --git a/lib/public/TaskProcessing/IManager.php b/lib/public/TaskProcessing/IManager.php index 87700917c7e..723eca8f615 100644 --- a/lib/public/TaskProcessing/IManager.php +++ b/lib/public/TaskProcessing/IManager.php @@ -26,6 +26,7 @@ use OCP\TaskProcessing\Exception\ValidationException; * @since 30.0.0 */ interface IManager { + /** * @since 30.0.0 */ @@ -38,10 +39,22 @@ interface IManager { public function getProviders(): array; /** - * @return array<string,array{name: string, description: string, inputShape: ShapeDescriptor[], optionalInputShape: ShapeDescriptor[], outputShape: ShapeDescriptor[], optionalOutputShape: ShapeDescriptor[]}> + * @param string $taskTypeId + * @return IProvider + * @throws Exception + * @since 30.0.0 + */ + public function getPreferredProvider(string $taskTypeId); + + /** + * @param bool $showDisabled if false, disabled task types will be filtered + * @param ?string $userId to check if the user is a guest. Will be obtained from session if left to default + * @return array<string, array{name: string, description: string, inputShape: ShapeDescriptor[], inputShapeEnumValues: ShapeEnumValue[][], inputShapeDefaults: array<array-key, numeric|string>, optionalInputShape: ShapeDescriptor[], optionalInputShapeEnumValues: ShapeEnumValue[][], optionalInputShapeDefaults: array<array-key, numeric|string>, outputShape: ShapeDescriptor[], outputShapeEnumValues: ShapeEnumValue[][], optionalOutputShape: ShapeDescriptor[], optionalOutputShapeEnumValues: ShapeEnumValue[][]}> * @since 30.0.0 + * @since 31.0.0 Added the `showDisabled` argument. + * @since 31.0.7 Added the `userId` argument */ - public function getAvailableTaskTypes(): array; + public function getAvailableTaskTypes(bool $showDisabled = false, ?string $userId = null): array; /** * @param Task $task The task to run @@ -54,6 +67,33 @@ interface IManager { public function scheduleTask(Task $task): void; /** + * Run the task and return the finished task + * + * @param Task $task The task to run + * @return Task The result task + * @throws PreConditionNotMetException If no or not the requested provider was registered but this method was still called + * @throws ValidationException the given task input didn't pass validation against the task type's input shape and/or the providers optional input shape specs + * @throws Exception storing the task in the database failed + * @throws UnauthorizedException the user scheduling the task does not have access to the files used in the input + * @since 30.0.0 + */ + public function runTask(Task $task): Task; + + /** + * Process task with a synchronous provider + * + * Prepare task input data and run the process method of the provider + * This should only be used by OC\TaskProcessing\SynchronousBackgroundJob::run() and OCP\TaskProcessing\IManager::runTask() + * + * @param Task $task + * @param ISynchronousProvider $provider + * @return bool True if the task has run successfully + * @throws Exception + * @since 30.0.0 + */ + public function processTask(Task $task, ISynchronousProvider $provider): bool; + + /** * Delete a task that has been scheduled before * * @param Task $task The task to delete @@ -83,11 +123,12 @@ interface IManager { * @param int $id The id of the task * @param string|null $error * @param array|null $result + * @param bool $isUsingFileIds * @throws Exception If the query failed * @throws NotFoundException If the task could not be found * @since 30.0.0 */ - public function setTaskResult(int $id, ?string $error, ?array $result): void; + public function setTaskResult(int $id, ?string $error, ?array $result, bool $isUsingFileIds = false): void; /** * @param int $id @@ -101,13 +142,14 @@ interface IManager { public function setTaskProgress(int $id, float $progress): bool; /** - * @param string|null $taskTypeId + * @param list<string> $taskTypeIds + * @param list<int> $taskIdsToIgnore * @return Task * @throws Exception If the query failed * @throws NotFoundException If no task could not be found * @since 30.0.0 */ - public function getNextScheduledTask(?string $taskTypeId = null): Task; + public function getNextScheduledTask(array $taskTypeIds = [], array $taskIdsToIgnore = []): Task; /** * @param int $id The id of the task @@ -131,6 +173,24 @@ interface IManager { public function getUserTasks(?string $userId, ?string $taskTypeId = null, ?string $customId = null): array; /** + * @param string|null $userId The user id that scheduled the task + * @param string|null $taskTypeId The task type id to filter by + * @param string|null $appId The app ID of the app that submitted the task + * @param string|null $customId The custom task ID + * @param int|null $status The task status + * @param int|null $scheduleAfter Minimum schedule time filter + * @param int|null $endedBefore Maximum ending time filter + * @return list<Task> + * @throws Exception If the query failed + * @throws NotFoundException If the task could not be found + * @since 30.0.0 + */ + public function getTasks( + ?string $userId, ?string $taskTypeId = null, ?string $appId = null, ?string $customId = null, + ?int $status = null, ?int $scheduleAfter = null, ?int $endedBefore = null, + ): array; + + /** * @param string|null $userId * @param string $appId * @param string|null $customId @@ -143,7 +203,7 @@ interface IManager { /** * Prepare the task's input data, so it can be processed by the provider - * ie. this replaces file ids with base64 data + * ie. this replaces file ids with File objects * * @param Task $task * @return array<array-key, list<numeric|string|File>|numeric|string|File> @@ -151,7 +211,27 @@ interface IManager { * @throws GenericFileException * @throws LockedException * @throws ValidationException + * @throws UnauthorizedException * @since 30.0.0 */ public function prepareInputData(Task $task): array; + + /** + * Changes the task status to STATUS_RUNNING and, if successful, returns True. + * + * @param Task $task + * @return bool + * @since 30.0.0 + */ + public function lockTask(Task $task): bool; + + /** + * @param Task $task + * @psalm-param Task::STATUS_* $status + * @param int $status + * @throws \JsonException + * @throws Exception + * @since 30.0.0 + */ + public function setTaskStatus(Task $task, int $status): void; } diff --git a/lib/public/TaskProcessing/IProvider.php b/lib/public/TaskProcessing/IProvider.php index 68a708ca834..a4e752216c7 100644 --- a/lib/public/TaskProcessing/IProvider.php +++ b/lib/public/TaskProcessing/IProvider.php @@ -58,4 +58,52 @@ interface IProvider { * @psalm-return ShapeDescriptor[] */ public function getOptionalOutputShape(): array; + + /** + * Returns the option list for each input shape ENUM slot + * + * @since 30.0.0 + * @psalm-return ShapeEnumValue[][] + */ + public function getInputShapeEnumValues(): array; + + /** + * Returns the default values for input shape slots + * + * @since 30.0.0 + * @psalm-return array<array-key, string|numeric> + */ + public function getInputShapeDefaults(): array; + + /** + * Returns the option list for each optional input shape ENUM slot + * + * @since 30.0.0 + * @psalm-return ShapeEnumValue[][] + */ + public function getOptionalInputShapeEnumValues(): array; + + /** + * Returns the default values for optional input shape slots + * + * @since 30.0.0 + * @psalm-return array<array-key, string|numeric> + */ + public function getOptionalInputShapeDefaults(): array; + + /** + * Returns the option list for each output shape ENUM slot + * + * @since 30.0.0 + * @psalm-return ShapeEnumValue[][] + */ + public function getOutputShapeEnumValues(): array; + + /** + * Returns the option list for each optional output shape ENUM slot + * + * @since 30.0.0 + * @psalm-return ShapeEnumValue[][] + */ + public function getOptionalOutputShapeEnumValues(): array; } diff --git a/lib/public/TaskProcessing/ISynchronousProvider.php b/lib/public/TaskProcessing/ISynchronousProvider.php index 8cd504af189..d7e42684df5 100644 --- a/lib/public/TaskProcessing/ISynchronousProvider.php +++ b/lib/public/TaskProcessing/ISynchronousProvider.php @@ -28,7 +28,7 @@ interface ISynchronousProvider extends IProvider { * @param callable(float):bool $reportProgress Report the task progress. If this returns false, that means the task was cancelled and processing should be stopped. * @psalm-return array<string, list<numeric|string>|numeric|string> * @throws ProcessingException - *@since 30.0.0 + * @since 30.0.0 */ public function process(?string $userId, array $input, callable $reportProgress): array; } diff --git a/lib/public/TaskProcessing/ShapeDescriptor.php b/lib/public/TaskProcessing/ShapeDescriptor.php index 5759b260865..19e57c8a91d 100644 --- a/lib/public/TaskProcessing/ShapeDescriptor.php +++ b/lib/public/TaskProcessing/ShapeDescriptor.php @@ -49,11 +49,11 @@ class ShapeDescriptor implements \JsonSerializable { } /** - * @return array{name: string, description: string, type: "Number"|"Text"|"Audio"|"Image"|"Video"|"File"|"ListOfNumbers"|"ListOfTexts"|"ListOfImages"|"ListOfAudios"|"ListOfVideos"|"ListOfFiles"} + * @return array{name: string, description: string, type: "Number"|"Text"|"Audio"|"Image"|"Video"|"File"|"Enum"|"ListOfNumbers"|"ListOfTexts"|"ListOfImages"|"ListOfAudios"|"ListOfVideos"|"ListOfFiles"} * @since 30.0.0 */ public function jsonSerialize(): array { - /** @var "Number"|"Text"|"Audio"|"Image"|"Video"|"File"|"ListOfNumbers"|"ListOfTexts"|"ListOfImages"|"ListOfAudios"|"ListOfVideos"|"ListOfFiles" $type */ + /** @var "Number"|"Text"|"Audio"|"Image"|"Video"|"File"|"Enum"|"ListOfNumbers"|"ListOfTexts"|"ListOfImages"|"ListOfAudios"|"ListOfVideos"|"ListOfFiles" $type */ $type = $this->getShapeType()->name; return [ 'name' => $this->getName(), diff --git a/lib/public/TaskProcessing/ShapeEnumValue.php b/lib/public/TaskProcessing/ShapeEnumValue.php new file mode 100644 index 00000000000..bc500524304 --- /dev/null +++ b/lib/public/TaskProcessing/ShapeEnumValue.php @@ -0,0 +1,51 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCP\TaskProcessing; + +/** + * Data object for input output shape enum slot value + * @since 30.0.0 + */ +class ShapeEnumValue implements \JsonSerializable { + /** + * @param string $name + * @param string $value + * @since 30.0.0 + */ + public function __construct( + private string $name, + private string $value, + ) { + } + + /** + * @return string + * @since 30.0.0 + */ + public function getName(): string { + return $this->name; + } + + /** + * @return string + * @since 30.0.0 + */ + public function getValue(): string { + return $this->value; + } + + /** + * @return array{name: string, value: string} + * @since 30.0.0 + */ + public function jsonSerialize(): array { + return [ + 'name' => $this->getName(), + 'value' => $this->getValue(), + ]; + } +} diff --git a/lib/public/TaskProcessing/Task.php b/lib/public/TaskProcessing/Task.php index 2ec367e4a0a..71c271cd43d 100644 --- a/lib/public/TaskProcessing/Task.php +++ b/lib/public/TaskProcessing/Task.php @@ -30,6 +30,9 @@ final class Task implements \JsonSerializable { protected int $lastUpdated; + protected ?string $webhookUri = null; + protected ?string $webhookMethod = null; + /** * @since 30.0.0 */ @@ -60,6 +63,10 @@ final class Task implements \JsonSerializable { */ protected int $status = self::STATUS_UNKNOWN; + protected ?int $scheduledAt = null; + protected ?int $startedAt = null; + protected ?int $endedAt = null; + /** * @param string $taskTypeId * @param array<string,list<numeric|string>|numeric|string> $input @@ -198,12 +205,60 @@ final class Task implements \JsonSerializable { } /** - * @psalm-return array{id: ?int, lastUpdated: int, type: string, status: 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN', userId: ?string, appId: string, input: array<array-key, list<numeric|string>|numeric|string>, output: ?array<array-key, list<numeric|string>|numeric|string>, customId: ?string, completionExpectedAt: ?int, progress: ?float} + * @return int|null + * @since 30.0.0 + */ + final public function getScheduledAt(): ?int { + return $this->scheduledAt; + } + + /** + * @param int|null $scheduledAt + * @since 30.0.0 + */ + final public function setScheduledAt(?int $scheduledAt): void { + $this->scheduledAt = $scheduledAt; + } + + /** + * @return int|null + * @since 30.0.0 + */ + final public function getStartedAt(): ?int { + return $this->startedAt; + } + + /** + * @param int|null $startedAt + * @since 30.0.0 + */ + final public function setStartedAt(?int $startedAt): void { + $this->startedAt = $startedAt; + } + + /** + * @return int|null + * @since 30.0.0 + */ + final public function getEndedAt(): ?int { + return $this->endedAt; + } + + /** + * @param int|null $endedAt + * @since 30.0.0 + */ + final public function setEndedAt(?int $endedAt): void { + $this->endedAt = $endedAt; + } + + /** + * @psalm-return array{id: int, lastUpdated: int, type: string, status: 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN', userId: ?string, appId: string, input: array<string, list<numeric|string>|numeric|string>, output: ?array<string, list<numeric|string>|numeric|string>, customId: ?string, completionExpectedAt: ?int, progress: ?float, scheduledAt: ?int, startedAt: ?int, endedAt: ?int} * @since 30.0.0 */ final public function jsonSerialize(): array { return [ - 'id' => $this->getId(), + 'id' => (int)$this->getId(), 'type' => $this->getTaskTypeId(), 'lastUpdated' => $this->getLastUpdated(), 'status' => self::statusToString($this->getStatus()), @@ -214,6 +269,9 @@ final class Task implements \JsonSerializable { 'customId' => $this->getCustomId(), 'completionExpectedAt' => $this->getCompletionExpectedAt()?->getTimestamp(), 'progress' => $this->getProgress(), + 'scheduledAt' => $this->getScheduledAt(), + 'startedAt' => $this->getStartedAt(), + 'endedAt' => $this->getEndedAt(), ]; } @@ -265,6 +323,40 @@ final class Task implements \JsonSerializable { } /** + * @return null|string + * @since 30.0.0 + */ + final public function getWebhookUri(): ?string { + return $this->webhookUri; + } + + /** + * @param string|null $webhookUri + * @return void + * @since 30.0.0 + */ + final public function setWebhookUri(?string $webhookUri): void { + $this->webhookUri = $webhookUri; + } + + /** + * @return null|string + * @since 30.0.0 + */ + final public function getWebhookMethod(): ?string { + return $this->webhookMethod; + } + + /** + * @param string|null $webhookMethod + * @return void + * @since 30.0.0 + */ + final public function setWebhookMethod(?string $webhookMethod): void { + $this->webhookMethod = $webhookMethod; + } + + /** * @param int $status * @return 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN' * @since 30.0.0 diff --git a/lib/public/TaskProcessing/TaskTypes/AnalyzeImages.php b/lib/public/TaskProcessing/TaskTypes/AnalyzeImages.php new file mode 100644 index 00000000000..462016c5c19 --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/AnalyzeImages.php @@ -0,0 +1,96 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type to ask a question about the images + * @since 32.0.0 + */ +class AnalyzeImages implements ITaskType { + /** + * @since 32.0.0 + */ + public const ID = 'core:analyze-images'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 32.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + /** + * @inheritDoc + * @since 32.0.0 + */ + public function getName(): string { + return $this->l->t('Analyze images'); + } + + /** + * @inheritDoc + * @since 32.0.0 + */ + public function getDescription(): string { + return $this->l->t('Ask a question about the given images.'); + } + + /** + * @return string + * @since 32.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 32.0.0 + */ + public function getInputShape(): array { + return [ + 'images' => new ShapeDescriptor( + $this->l->t('Images'), + $this->l->t('Images to ask a question about'), + EShapeType::ListOfImages, + ), + 'input' => new ShapeDescriptor( + $this->l->t('Question'), + $this->l->t('What to ask about the images.'), + EShapeType::Text, + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 32.0.0 + */ + public function getOutputShape(): array { + return [ + 'output' => new ShapeDescriptor( + $this->l->t('Generated response'), + $this->l->t('The answer to the question'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/AudioToAudioChat.php b/lib/public/TaskProcessing/TaskTypes/AudioToAudioChat.php new file mode 100644 index 00000000000..c862437e86b --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/AudioToAudioChat.php @@ -0,0 +1,112 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for audio chat + * @since 32.0.0 + */ +class AudioToAudioChat implements ITaskType { + /** + * @since 32.0.0 + */ + public const ID = 'core:audio2audio:chat'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 32.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + + /** + * @inheritDoc + * @since 32.0.0 + */ + public function getName(): string { + return $this->l->t('Audio chat'); + } + + /** + * @inheritDoc + * @since 32.0.0 + */ + public function getDescription(): string { + return $this->l->t('Voice chat with the assistant'); + } + + /** + * @return string + * @since 32.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 32.0.0 + */ + public function getInputShape(): array { + return [ + 'system_prompt' => new ShapeDescriptor( + $this->l->t('System prompt'), + $this->l->t('Define rules and assumptions that the assistant should follow during the conversation.'), + EShapeType::Text + ), + 'input' => new ShapeDescriptor( + $this->l->t('Chat voice message'), + $this->l->t('Describe a task that you want the assistant to do or ask a question.'), + EShapeType::Audio + ), + 'history' => new ShapeDescriptor( + $this->l->t('Chat history'), + $this->l->t('The history of chat messages before the current message, starting with a message by the user.'), + EShapeType::ListOfTexts + ) + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 32.0.0 + */ + public function getOutputShape(): array { + return [ + 'input_transcript' => new ShapeDescriptor( + $this->l->t('Input transcript'), + $this->l->t('Transcription of the audio input'), + EShapeType::Text, + ), + 'output' => new ShapeDescriptor( + $this->l->t('Response voice message'), + $this->l->t('The generated voice response as part of the conversation'), + EShapeType::Audio + ), + 'output_transcript' => new ShapeDescriptor( + $this->l->t('Output transcript'), + $this->l->t('Transcription of the audio output'), + EShapeType::Text, + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/AudioToText.php b/lib/public/TaskProcessing/TaskTypes/AudioToText.php index 3f23d00d41b..1982d4c9d28 100644 --- a/lib/public/TaskProcessing/TaskTypes/AudioToText.php +++ b/lib/public/TaskProcessing/TaskTypes/AudioToText.php @@ -34,7 +34,7 @@ class AudioToText implements ITaskType { public function __construct( IFactory $l10nFactory, ) { - $this->l = $l10nFactory->get('core'); + $this->l = $l10nFactory->get('lib'); } diff --git a/lib/public/TaskProcessing/TaskTypes/ContextAgentAudioInteraction.php b/lib/public/TaskProcessing/TaskTypes/ContextAgentAudioInteraction.php new file mode 100644 index 00000000000..6cd358040b7 --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/ContextAgentAudioInteraction.php @@ -0,0 +1,118 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for Context Agent interaction + * @since 32.0.0 + */ +class ContextAgentAudioInteraction implements ITaskType { + public const ID = 'core:contextagent:audio-interaction'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 32.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + /** + * @inheritDoc + * @since 32.0.0 + */ + public function getName(): string { + return 'ContextAgent audio'; // We do not translate this + } + + /** + * @inheritDoc + * @since 32.0.0 + */ + public function getDescription(): string { + return $this->l->t('Chat by voice with an agent'); + } + + /** + * @return string + * @since 32.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 32.0.0 + */ + public function getInputShape(): array { + return [ + 'input' => new ShapeDescriptor( + $this->l->t('Chat voice message'), + $this->l->t('Describe a task that you want the agent to do or ask a question.'), + EShapeType::Audio + ), + 'confirmation' => new ShapeDescriptor( + $this->l->t('Confirmation'), + $this->l->t('Whether to confirm previously requested actions: 0 for denial and 1 for confirmation.'), + EShapeType::Number + ), + 'conversation_token' => new ShapeDescriptor( + $this->l->t('Conversation token'), + $this->l->t('A token representing the conversation.'), + EShapeType::Text + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 32.0.0 + */ + public function getOutputShape(): array { + return [ + 'input_transcript' => new ShapeDescriptor( + $this->l->t('Input transcript'), + $this->l->t('Transcription of the audio input'), + EShapeType::Text, + ), + 'output' => new ShapeDescriptor( + $this->l->t('Response voice message'), + $this->l->t('The generated voice response as part of the conversation'), + EShapeType::Audio + ), + 'output_transcript' => new ShapeDescriptor( + $this->l->t('Output transcript'), + $this->l->t('Transcription of the audio output'), + EShapeType::Text, + ), + 'conversation_token' => new ShapeDescriptor( + $this->l->t('The new conversation token'), + $this->l->t('Send this along with the next interaction.'), + EShapeType::Text + ), + 'actions' => new ShapeDescriptor( + $this->l->t('Requested actions by the agent'), + $this->l->t('Actions that the agent would like to carry out in JSON format.'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/ContextAgentInteraction.php b/lib/public/TaskProcessing/TaskTypes/ContextAgentInteraction.php new file mode 100644 index 00000000000..cd08d6f4e3d --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/ContextAgentInteraction.php @@ -0,0 +1,108 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for Context Agent interaction + * @since 31.0.0 + */ +class ContextAgentInteraction implements ITaskType { + public const ID = 'core:contextagent:interaction'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 31.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + /** + * @inheritDoc + * @since 31.0.0 + */ + public function getName(): string { + return 'ContextAgent'; // We do not translate this + } + + /** + * @inheritDoc + * @since 31.0.0 + */ + public function getDescription(): string { + return $this->l->t('Chat with an agent'); + } + + /** + * @return string + * @since 31.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 31.0.0 + */ + public function getInputShape(): array { + return [ + 'input' => new ShapeDescriptor( + $this->l->t('Chat message'), + $this->l->t('A chat message to send to the agent.'), + EShapeType::Text + ), + 'confirmation' => new ShapeDescriptor( + $this->l->t('Confirmation'), + $this->l->t('Whether to confirm previously requested actions: 0 for denial and 1 for confirmation.'), + EShapeType::Number + ), + 'conversation_token' => new ShapeDescriptor( + $this->l->t('Conversation token'), + $this->l->t('A token representing the conversation.'), + EShapeType::Text + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 31.0.0 + */ + public function getOutputShape(): array { + return [ + 'output' => new ShapeDescriptor( + $this->l->t('Generated response'), + $this->l->t('The response from the chat model.'), + EShapeType::Text + ), + 'conversation_token' => new ShapeDescriptor( + $this->l->t('The new conversation token'), + $this->l->t('Send this along with the next interaction.'), + EShapeType::Text + ), + 'actions' => new ShapeDescriptor( + $this->l->t('Requested actions by the agent'), + $this->l->t('Actions that the agent would like to carry out in JSON format.'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/ContextWrite.php b/lib/public/TaskProcessing/TaskTypes/ContextWrite.php index 08a8fc5b301..fd5c6a8f58b 100644 --- a/lib/public/TaskProcessing/TaskTypes/ContextWrite.php +++ b/lib/public/TaskProcessing/TaskTypes/ContextWrite.php @@ -34,7 +34,7 @@ class ContextWrite implements ITaskType { public function __construct( IFactory $l10nFactory, ) { - $this->l = $l10nFactory->get('core'); + $this->l = $l10nFactory->get('lib'); } @@ -43,7 +43,7 @@ class ContextWrite implements ITaskType { * @since 30.0.0 */ public function getName(): string { - return $this->l->t('ContextWrite'); + return $this->l->t('Context write'); } /** diff --git a/lib/public/TaskProcessing/TaskTypes/GenerateEmoji.php b/lib/public/TaskProcessing/TaskTypes/GenerateEmoji.php index 89ddfa58072..2cb22b3b455 100644 --- a/lib/public/TaskProcessing/TaskTypes/GenerateEmoji.php +++ b/lib/public/TaskProcessing/TaskTypes/GenerateEmoji.php @@ -34,7 +34,7 @@ class GenerateEmoji implements ITaskType { public function __construct( IFactory $l10nFactory, ) { - $this->l = $l10nFactory->get('core'); + $this->l = $l10nFactory->get('lib'); } diff --git a/lib/public/TaskProcessing/TaskTypes/TextToImage.php b/lib/public/TaskProcessing/TaskTypes/TextToImage.php index a4b8070e8bb..ed956d244a1 100644 --- a/lib/public/TaskProcessing/TaskTypes/TextToImage.php +++ b/lib/public/TaskProcessing/TaskTypes/TextToImage.php @@ -34,7 +34,7 @@ class TextToImage implements ITaskType { public function __construct( IFactory $l10nFactory, ) { - $this->l = $l10nFactory->get('core'); + $this->l = $l10nFactory->get('lib'); } diff --git a/lib/public/TaskProcessing/TaskTypes/TextToSpeech.php b/lib/public/TaskProcessing/TaskTypes/TextToSpeech.php new file mode 100644 index 00000000000..ce35be32a6f --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/TextToSpeech.php @@ -0,0 +1,92 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for speech generation + * @since 32.0.0 + */ +class TextToSpeech implements ITaskType { + /** + * @since 32.0.0 + */ + public const ID = 'core:text2speech'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 32.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + + /** + * @inheritDoc + * @since 32.0.0 + */ + public function getName(): string { + return $this->l->t('Generate speech'); + } + + /** + * @inheritDoc + * @since 32.0.0 + */ + public function getDescription(): string { + return $this->l->t('Generate speech from a transcript'); + } + + /** + * @return string + * @since 32.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 32.0.0 + */ + public function getInputShape(): array { + return [ + 'input' => new ShapeDescriptor( + $this->l->t('Prompt'), + $this->l->t('Write transcript that you want the assistant to generate speech from'), + EShapeType::Text + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 32.0.0 + */ + public function getOutputShape(): array { + return [ + 'speech' => new ShapeDescriptor( + $this->l->t('Output speech'), + $this->l->t('The generated speech'), + EShapeType::Audio + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/TextToText.php b/lib/public/TaskProcessing/TaskTypes/TextToText.php index 7ac7f2eca05..c39d435688a 100644 --- a/lib/public/TaskProcessing/TaskTypes/TextToText.php +++ b/lib/public/TaskProcessing/TaskTypes/TextToText.php @@ -34,7 +34,7 @@ class TextToText implements ITaskType { public function __construct( IFactory $l10nFactory, ) { - $this->l = $l10nFactory->get('core'); + $this->l = $l10nFactory->get('lib'); } @@ -51,7 +51,7 @@ class TextToText implements ITaskType { * @since 30.0.0 */ public function getDescription(): string { - return $this->l->t('Runs an arbitrary prompt through a language model that retuns a reply'); + return $this->l->t('Runs an arbitrary prompt through a language model that returns a reply'); } /** diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextChangeTone.php b/lib/public/TaskProcessing/TaskTypes/TextToTextChangeTone.php new file mode 100644 index 00000000000..0ea4575a187 --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextChangeTone.php @@ -0,0 +1,93 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for text reformulation + * @since 31.0.0 + */ +class TextToTextChangeTone implements ITaskType { + public const ID = 'core:text2text:changetone'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 31.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + /** + * @inheritDoc + * @since 31.0.0 + */ + public function getName(): string { + return $this->l->t('Change Tone'); + } + + /** + * @inheritDoc + * @since 31.0.0 + */ + public function getDescription(): string { + return $this->l->t('Change the tone of a piece of text.'); + } + + /** + * @return string + * @since 31.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 31.0.0 + */ + public function getInputShape(): array { + return [ + 'input' => new ShapeDescriptor( + $this->l->t('Input text'), + $this->l->t('Write a text that you want the assistant to rewrite in another tone.'), + EShapeType::Text, + ), + 'tone' => new ShapeDescriptor( + $this->l->t('Desired tone'), + $this->l->t('In which tone should your text be rewritten?'), + EShapeType::Enum, + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 31.0.0 + */ + public function getOutputShape(): array { + return [ + 'output' => new ShapeDescriptor( + $this->l->t('Generated response'), + $this->l->t('The rewritten text in the desired tone, written by the assistant:'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextChat.php b/lib/public/TaskProcessing/TaskTypes/TextToTextChat.php new file mode 100644 index 00000000000..9cf1e7ef3ce --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextChat.php @@ -0,0 +1,102 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for text chat + * @since 30.0.0 + */ +class TextToTextChat implements ITaskType { + /** + * @since 30.0.0 + */ + public const ID = 'core:text2text:chat'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 30.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getName(): string { + return $this->l->t('Chat'); + } + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getDescription(): string { + return $this->l->t('Chat with the assistant'); + } + + /** + * @return string + * @since 30.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 30.0.0 + */ + public function getInputShape(): array { + return [ + 'system_prompt' => new ShapeDescriptor( + $this->l->t('System prompt'), + $this->l->t('Define rules and assumptions that the assistant should follow during the conversation.'), + EShapeType::Text + ), + 'input' => new ShapeDescriptor( + $this->l->t('Chat message'), + $this->l->t('Describe a task that you want the assistant to do or ask a question'), + EShapeType::Text + ), + 'history' => new ShapeDescriptor( + $this->l->t('Chat history'), + $this->l->t('The history of chat messages before the current message, starting with a message by the user'), + EShapeType::ListOfTexts + ) + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 30.0.0 + */ + public function getOutputShape(): array { + return [ + 'output' => new ShapeDescriptor( + $this->l->t('Response message'), + $this->l->t('The generated response as part of the conversation'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextChatWithTools.php b/lib/public/TaskProcessing/TaskTypes/TextToTextChatWithTools.php new file mode 100644 index 00000000000..ebc660a3af9 --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextChatWithTools.php @@ -0,0 +1,117 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for invoking Chat-enabled LLMs with tool call support + * @since 31.0.0 + */ +class TextToTextChatWithTools implements ITaskType { + public const ID = 'core:text2text:chatwithtools'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 31.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + /** + * @inheritDoc + * @since 31.0.0 + */ + public function getName(): string { + // TRANSLATORS Tool calling, also known as function calling, is a structured way to give LLMs the ability to make requests back to the application that called it. You define the tools you want to make available to the model, and the model will make tool requests to your app as necessary to fulfill the prompts you give it. + return $this->l->t('Chat with tools'); + } + + /** + * @inheritDoc + * @since 31.0.0 + */ + public function getDescription(): string { + // TRANSLATORS Tool calling, also known as function calling, is a structured way to give LLMs the ability to make requests back to the application that called it. You define the tools you want to make available to the model, and the model will make tool requests to your app as necessary to fulfill the prompts you give it. + return $this->l->t('Chat with the language model with tool calling support.'); + } + + /** + * @return string + * @since 31.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 31.0.0 + */ + public function getInputShape(): array { + return [ + 'system_prompt' => new ShapeDescriptor( + $this->l->t('System prompt'), + $this->l->t('Define rules and assumptions that the assistant should follow during the conversation.'), + EShapeType::Text + ), + 'input' => new ShapeDescriptor( + $this->l->t('Chat message'), + $this->l->t('Describe a task that you want the assistant to do or ask a question'), + EShapeType::Text + ), + 'tool_message' => new ShapeDescriptor( + $this->l->t('Tool message'), + $this->l->t('The result of tool calls in the last interaction'), + EShapeType::Text + ), + 'history' => new ShapeDescriptor( + $this->l->t('Chat history'), + $this->l->t('The history of chat messages before the current message, starting with a message by the user'), + EShapeType::ListOfTexts + ), + // See https://platform.openai.com/docs/api-reference/chat/create#chat-create-tools for the format + 'tools' => new ShapeDescriptor( + // TRANSLATORS Tool calling, also known as function calling, is a structured way to give LLMs the ability to make requests back to the application that called it. You define the tools you want to make available to the model, and the model will make tool requests to your app as necessary to fulfill the prompts you give it. + $this->l->t('Available tools'), + $this->l->t('The available tools in JSON format'), + EShapeType::Text + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 31.0.0 + */ + public function getOutputShape(): array { + return [ + 'output' => new ShapeDescriptor( + $this->l->t('Generated response'), + $this->l->t('The response from the chat model'), + EShapeType::Text + ), + 'tool_calls' => new ShapeDescriptor( + $this->l->t('Tool calls'), + $this->l->t('Tools call instructions from the model in JSON format'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextFormalization.php b/lib/public/TaskProcessing/TaskTypes/TextToTextFormalization.php new file mode 100644 index 00000000000..70e38f78c0b --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextFormalization.php @@ -0,0 +1,92 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for text formalization + * @since 30.0.0 + */ +class TextToTextFormalization implements ITaskType { + /** + * @since 30.0.0 + */ + public const ID = 'core:text2text:formalization'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 30.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getName(): string { + return $this->l->t('Formalize text'); + } + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getDescription(): string { + return $this->l->t('Takes a text and makes it sound more formal'); + } + + /** + * @return string + * @since 30.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 30.0.0 + */ + public function getInputShape(): array { + return [ + 'input' => new ShapeDescriptor( + $this->l->t('Input text'), + $this->l->t('Write a text that you want the assistant to formalize'), + EShapeType::Text + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 30.0.0 + */ + public function getOutputShape(): array { + return [ + 'output' => new ShapeDescriptor( + $this->l->t('Formalized text'), + $this->l->t('The formalized text'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextHeadline.php b/lib/public/TaskProcessing/TaskTypes/TextToTextHeadline.php index 9907a3605b2..dde4ea03042 100644 --- a/lib/public/TaskProcessing/TaskTypes/TextToTextHeadline.php +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextHeadline.php @@ -34,7 +34,7 @@ class TextToTextHeadline implements ITaskType { public function __construct( IFactory $l10nFactory, ) { - $this->l = $l10nFactory->get('core'); + $this->l = $l10nFactory->get('lib'); } diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextProofread.php b/lib/public/TaskProcessing/TaskTypes/TextToTextProofread.php new file mode 100644 index 00000000000..508794490be --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextProofread.php @@ -0,0 +1,91 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for proofreading + * @since 31.0.0 + */ +class TextToTextProofread implements ITaskType { + /** + * @since 31.0.0 + */ + public const ID = 'core:text2text:proofread'; + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 31.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + + /** + * @inheritDoc + * @since 31.0.0 + */ + public function getName(): string { + return $this->l->t('Proofread'); + } + + /** + * @inheritDoc + * @since 31.0.0 + */ + public function getDescription(): string { + return $this->l->t('Proofreads a text and lists corrections'); + } + + /** + * @return string + * @since 31.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 31.0.0 + */ + public function getInputShape(): array { + return [ + 'input' => new ShapeDescriptor( + $this->l->t('Text'), + $this->l->t('The text to proofread'), + EShapeType::Text + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 31.0.0 + */ + public function getOutputShape(): array { + return [ + 'output' => new ShapeDescriptor( + $this->l->t('Corrections'), + $this->l->t('The corrections that should be made in your text'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextReformulation.php b/lib/public/TaskProcessing/TaskTypes/TextToTextReformulation.php new file mode 100644 index 00000000000..120f5316aee --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextReformulation.php @@ -0,0 +1,92 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for text reformulation + * @since 30.0.0 + */ +class TextToTextReformulation implements ITaskType { + /** + * @since 30.0.0 + */ + public const ID = 'core:text2text:reformulation'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 30.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getName(): string { + return $this->l->t('Reformulate text'); + } + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getDescription(): string { + return $this->l->t('Takes a text and reformulates it'); + } + + /** + * @return string + * @since 30.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 30.0.0 + */ + public function getInputShape(): array { + return [ + 'input' => new ShapeDescriptor( + $this->l->t('Input text'), + $this->l->t('Write a text that you want the assistant to reformulate'), + EShapeType::Text + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 30.0.0 + */ + public function getOutputShape(): array { + return [ + 'output' => new ShapeDescriptor( + $this->l->t('Reformulated text'), + $this->l->t('The reformulated text, written by the assistant'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextSimplification.php b/lib/public/TaskProcessing/TaskTypes/TextToTextSimplification.php new file mode 100644 index 00000000000..d107e584e3a --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextSimplification.php @@ -0,0 +1,92 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for text simplification + * @since 30.0.0 + */ +class TextToTextSimplification implements ITaskType { + /** + * @since 30.0.0 + */ + public const ID = 'core:text2text:simplification'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 30.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getName(): string { + return $this->l->t('Simplify text'); + } + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getDescription(): string { + return $this->l->t('Takes a text and simplifies it'); + } + + /** + * @return string + * @since 30.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 30.0.0 + */ + public function getInputShape(): array { + return [ + 'input' => new ShapeDescriptor( + $this->l->t('Input text'), + $this->l->t('Write a text that you want the assistant to simplify'), + EShapeType::Text + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 30.0.0 + */ + public function getOutputShape(): array { + return [ + 'output' => new ShapeDescriptor( + $this->l->t('Simplified text'), + $this->l->t('The simplified text'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php b/lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php index 8b00c64f915..601b478c0bd 100644 --- a/lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php @@ -33,7 +33,7 @@ class TextToTextSummary implements ITaskType { public function __construct( IFactory $l10nFactory, ) { - $this->l = $l10nFactory->get('core'); + $this->l = $l10nFactory->get('lib'); } diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php b/lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php index a08ec6d2c0f..943cc2e2fd4 100644 --- a/lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php @@ -34,7 +34,7 @@ class TextToTextTopics implements ITaskType { public function __construct( IFactory $l10nFactory, ) { - $this->l = $l10nFactory->get('core'); + $this->l = $l10nFactory->get('lib'); } diff --git a/lib/public/TaskProcessing/TaskTypes/TextToTextTranslate.php b/lib/public/TaskProcessing/TaskTypes/TextToTextTranslate.php new file mode 100644 index 00000000000..a02550226ee --- /dev/null +++ b/lib/public/TaskProcessing/TaskTypes/TextToTextTranslate.php @@ -0,0 +1,102 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\TaskProcessing\TaskTypes; + +use OCP\IL10N; +use OCP\L10N\IFactory; +use OCP\TaskProcessing\EShapeType; +use OCP\TaskProcessing\ITaskType; +use OCP\TaskProcessing\ShapeDescriptor; + +/** + * This is the task processing task type for generic text processing + * @since 30.0.0 + */ +class TextToTextTranslate implements ITaskType { + /** + * @since 30.0.0 + */ + public const ID = 'core:text2text:translate'; + + private IL10N $l; + + /** + * @param IFactory $l10nFactory + * @since 30.0.0 + */ + public function __construct( + IFactory $l10nFactory, + ) { + $this->l = $l10nFactory->get('lib'); + } + + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getName(): string { + return $this->l->t('Translate'); + } + + /** + * @inheritDoc + * @since 30.0.0 + */ + public function getDescription(): string { + return $this->l->t('Translate text from one language to another'); + } + + /** + * @return string + * @since 30.0.0 + */ + public function getId(): string { + return self::ID; + } + + /** + * @return ShapeDescriptor[] + * @since 30.0.0 + */ + public function getInputShape(): array { + return [ + 'input' => new ShapeDescriptor( + $this->l->t('Origin text'), + $this->l->t('The text to translate'), + EShapeType::Text + ), + 'origin_language' => new ShapeDescriptor( + $this->l->t('Origin language'), + $this->l->t('The language of the origin text'), + EShapeType::Enum + ), + 'target_language' => new ShapeDescriptor( + $this->l->t('Target language'), + $this->l->t('The desired language to translate the origin text in'), + EShapeType::Enum + ), + ]; + } + + /** + * @return ShapeDescriptor[] + * @since 30.0.0 + */ + public function getOutputShape(): array { + return [ + 'output' => new ShapeDescriptor( + $this->l->t('Result'), + $this->l->t('The translated text'), + EShapeType::Text + ), + ]; + } +} diff --git a/lib/public/Teams/ITeamManager.php b/lib/public/Teams/ITeamManager.php index 5c4c63b4f23..3f737b4229c 100644 --- a/lib/public/Teams/ITeamManager.php +++ b/lib/public/Teams/ITeamManager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -28,7 +29,7 @@ interface ITeamManager { /** * Returns all team resources for a given team and user * - * @return TeamResource[] + * @return list<TeamResource> * @since 29.0.0 */ public function getSharedWith(string $teamId, string $userId): array; diff --git a/lib/public/Teams/ITeamResourceProvider.php b/lib/public/Teams/ITeamResourceProvider.php index 54c4879a47c..ff724ab8ae2 100644 --- a/lib/public/Teams/ITeamResourceProvider.php +++ b/lib/public/Teams/ITeamResourceProvider.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Teams/Team.php b/lib/public/Teams/Team.php index 07fc3f9ccd3..474ebaed84f 100644 --- a/lib/public/Teams/Team.php +++ b/lib/public/Teams/Team.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -18,7 +19,11 @@ class Team implements \JsonSerializable { /** * @since 29.0.0 */ - public function __construct(private string $teamId, private string $displayName, private ?string $link) { + public function __construct( + private string $teamId, + private string $displayName, + private ?string $link, + ) { } /** diff --git a/lib/public/Teams/TeamResource.php b/lib/public/Teams/TeamResource.php index e9c6470b2c9..acb98380562 100644 --- a/lib/public/Teams/TeamResource.php +++ b/lib/public/Teams/TeamResource.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/Template.php b/lib/public/Template.php index 048697ffcc4..c29de52db4f 100644 --- a/lib/public/Template.php +++ b/lib/public/Template.php @@ -7,15 +7,23 @@ */ namespace OCP; +use OCP\Template\ITemplate; + +/* + * We have to require the functions file because this class contains aliases to the functions + */ +require_once __DIR__ . '/../private/Template/functions.php'; + /** * This class provides the template system for owncloud. You can use it to load * specific templates, add data and generate the html code * * @since 8.0.0 + * @deprecated 32.0.0 Use \OCP\Template\ITemplateManager instead */ -class Template extends \OC_Template { +class Template extends \OC_Template implements ITemplate { /** - * Make OC_Helper::imagePath available as a simple function + * Make \OCP\IURLGenerator::imagePath available as a simple function * * @see \OCP\IURLGenerator::imagePath * @@ -23,7 +31,7 @@ class Template extends \OC_Template { * @param string $image * @return string to the image * @since 8.0.0 - * @suppress PhanDeprecatedFunction + * @deprecated 32.0.0 Use the function directly instead */ public static function image_path($app, $image) { return \image_path($app, $image); @@ -31,12 +39,12 @@ class Template extends \OC_Template { /** - * Make OC_Helper::mimetypeIcon available as a simple function + * Make IMimeTypeDetector->mimeTypeIcon available as a simple function * * @param string $mimetype * @return string to the image of this file type. * @since 8.0.0 - * @suppress PhanDeprecatedFunction + * @deprecated 32.0.0 Use the function directly instead */ public static function mimetype_icon($mimetype) { return \mimetype_icon($mimetype); @@ -48,7 +56,7 @@ class Template extends \OC_Template { * @param string $path path to file * @return string to the preview of the image * @since 8.0.0 - * @suppress PhanDeprecatedFunction + * @deprecated 32.0.0 Use the function directly instead */ public static function preview_icon($path) { return \preview_icon($path); @@ -62,23 +70,23 @@ class Template extends \OC_Template { * @param string $token * @return string link to the preview * @since 8.0.0 - * @suppress PhanDeprecatedFunction + * @deprecated 32.0.0 Use the function directly instead */ public static function publicPreview_icon($path, $token) { return \publicPreview_icon($path, $token); } /** - * Make OC_Helper::humanFileSize available as a simple function + * Make \OCP\Util::humanFileSize available as a simple function * Example: 2048 to 2 kB. * * @param int $bytes in bytes * @return string size as string * @since 8.0.0 - * @suppress PhanDeprecatedFunction + * @deprecated 32.0.0 Use \OCP\Util::humanFileSize instead */ public static function human_file_size($bytes) { - return \human_file_size($bytes); + return Util::humanFileSize($bytes); } /** @@ -88,8 +96,8 @@ class Template extends \OC_Template { * @param boolean $dateOnly * @return string human readable interpretation of the timestamp * @since 8.0.0 - * @suppress PhanDeprecatedFunction * @suppress PhanTypeMismatchArgument + * @deprecated 32.0.0 Use the function directly instead */ public static function relative_modified_date($timestamp, $dateOnly = false) { return \relative_modified_date($timestamp, null, $dateOnly); @@ -103,7 +111,7 @@ class Template extends \OC_Template { * @param array $params the parameters * @return string html options * @since 8.0.0 - * @suppress PhanDeprecatedFunction + * @deprecated 32.0.0 Use the function directly instead */ public static function html_select_options($options, $selected, $params = []) { return \html_select_options($options, $selected, $params); diff --git a/lib/public/Template/ITemplate.php b/lib/public/Template/ITemplate.php new file mode 100644 index 00000000000..7131df4d17c --- /dev/null +++ b/lib/public/Template/ITemplate.php @@ -0,0 +1,47 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Template; + +/** + * @since 32.0.0 + */ +interface ITemplate { + /** + * Process the template + * @since 32.0.0 + */ + public function fetchPage(?array $additionalParams = null): string; + + /** + * Proceed the template and print its output. + * @since 32.0.0 + */ + public function printPage(): void; + + /** + * Assign variables + * + * This function assigns a variable. It can be accessed via $_[$key] in + * the template. + * + * If the key existed before, it will be overwritten + * @since 32.0.0 + */ + public function assign(string $key, mixed $value): void; + + /** + * Appends a variable + * + * This function assigns a variable in an array context. If the key already + * exists, the value will be appended. It can be accessed via + * $_[$key][$position] in the template. + */ + public function append(string $key, mixed $value): void; +} diff --git a/lib/public/Template/ITemplateManager.php b/lib/public/Template/ITemplateManager.php new file mode 100644 index 00000000000..05549bbddfd --- /dev/null +++ b/lib/public/Template/ITemplateManager.php @@ -0,0 +1,44 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Template; + +use OCP\AppFramework\Http\TemplateResponse; + +/** + * @since 32.0.0 + */ +interface ITemplateManager { + /** + * @param TemplateResponse::RENDER_AS_* $renderAs + * @throws TemplateNotFoundException if the template cannot be found + * @since 32.0.0 + */ + public function getTemplate(string $app, string $name, string $renderAs = TemplateResponse::RENDER_AS_BLANK, bool $registerCall = true): ITemplate; + + /** + * Shortcut to print a simple page for guests + * @since 32.0.0 + */ + public function printGuestPage(string $application, string $name, array $parameters = []): void; + + /** + * Print a fatal error page and terminates the script + * @since 32.0.0 + * @param string $error_msg The error message to show + * @param string $hint An optional hint message - needs to be properly escape + */ + public function printErrorPage(string $error_msg, string $hint = '', int $statusCode = 500): never; + + /** + * Print error page using Exception details + * @since 32.0.0 + */ + public function printExceptionErrorPage(\Throwable $exception, int $statusCode = 503): never; +} diff --git a/lib/public/Template/TemplateNotFoundException.php b/lib/public/Template/TemplateNotFoundException.php new file mode 100644 index 00000000000..e77fcd8646a --- /dev/null +++ b/lib/public/Template/TemplateNotFoundException.php @@ -0,0 +1,16 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\Template; + +/** + * @since 32.0.0 + */ +class TemplateNotFoundException extends \Exception { +} diff --git a/lib/public/TextProcessing/Events/AbstractTextProcessingEvent.php b/lib/public/TextProcessing/Events/AbstractTextProcessingEvent.php index d61847716bf..8b944254020 100644 --- a/lib/public/TextProcessing/Events/AbstractTextProcessingEvent.php +++ b/lib/public/TextProcessing/Events/AbstractTextProcessingEvent.php @@ -13,13 +13,14 @@ use OCP\TextProcessing\Task; /** * @since 27.1.0 + * @deprecated 30.0.0 */ abstract class AbstractTextProcessingEvent extends Event { /** * @since 27.1.0 */ public function __construct( - private Task $task + private Task $task, ) { parent::__construct(); } diff --git a/lib/public/TextProcessing/Events/TaskFailedEvent.php b/lib/public/TextProcessing/Events/TaskFailedEvent.php index b41c519bab9..dfdb9137f95 100644 --- a/lib/public/TextProcessing/Events/TaskFailedEvent.php +++ b/lib/public/TextProcessing/Events/TaskFailedEvent.php @@ -10,6 +10,7 @@ use OCP\TextProcessing\Task; /** * @since 27.1.0 + * @deprecated 30.0.0 */ class TaskFailedEvent extends AbstractTextProcessingEvent { /** diff --git a/lib/public/TextProcessing/Events/TaskSuccessfulEvent.php b/lib/public/TextProcessing/Events/TaskSuccessfulEvent.php index b96e401364d..0716f4d45c6 100644 --- a/lib/public/TextProcessing/Events/TaskSuccessfulEvent.php +++ b/lib/public/TextProcessing/Events/TaskSuccessfulEvent.php @@ -8,6 +8,7 @@ namespace OCP\TextProcessing\Events; /** * @since 27.1.0 + * @deprecated 30.0.0 */ class TaskSuccessfulEvent extends AbstractTextProcessingEvent { } diff --git a/lib/public/TextProcessing/Exception/TaskFailureException.php b/lib/public/TextProcessing/Exception/TaskFailureException.php index e996811c28e..06fbdf5e765 100644 --- a/lib/public/TextProcessing/Exception/TaskFailureException.php +++ b/lib/public/TextProcessing/Exception/TaskFailureException.php @@ -9,6 +9,7 @@ namespace OCP\TextProcessing\Exception; /** * Exception thrown when a task failed * @since 28.0.0 + * @deprecated 30.0.0 */ class TaskFailureException extends \RuntimeException { } diff --git a/lib/public/TextProcessing/FreePromptTaskType.php b/lib/public/TextProcessing/FreePromptTaskType.php index 2aac04422c1..2433f24dbd7 100644 --- a/lib/public/TextProcessing/FreePromptTaskType.php +++ b/lib/public/TextProcessing/FreePromptTaskType.php @@ -15,6 +15,7 @@ use OCP\L10N\IFactory; /** * This is the text processing task type for free prompting * @since 27.1.0 + * @deprecated 30.0.0 */ class FreePromptTaskType implements ITaskType { private IL10N $l; diff --git a/lib/public/TextProcessing/HeadlineTaskType.php b/lib/public/TextProcessing/HeadlineTaskType.php index 893997ce669..00eb66466ae 100644 --- a/lib/public/TextProcessing/HeadlineTaskType.php +++ b/lib/public/TextProcessing/HeadlineTaskType.php @@ -15,6 +15,7 @@ use OCP\L10N\IFactory; /** * This is the text processing task type for creating headline * @since 27.1.0 + * @deprecated 30.0.0 */ class HeadlineTaskType implements ITaskType { private IL10N $l; diff --git a/lib/public/TextProcessing/IManager.php b/lib/public/TextProcessing/IManager.php index 8869b1d0d60..701ad18940f 100644 --- a/lib/public/TextProcessing/IManager.php +++ b/lib/public/TextProcessing/IManager.php @@ -20,6 +20,7 @@ use RuntimeException; * API surface for apps interacting with and making use of LanguageModel providers * without known which providers are installed * @since 27.1.0 + * @deprecated 30.0.0 */ interface IManager { /** diff --git a/lib/public/TextProcessing/IProvider.php b/lib/public/TextProcessing/IProvider.php index 4f1444e2b0e..24efbc1ec8c 100644 --- a/lib/public/TextProcessing/IProvider.php +++ b/lib/public/TextProcessing/IProvider.php @@ -17,6 +17,7 @@ use RuntimeException; * implement a text processing provider * @psalm-template-covariant T of ITaskType * @since 27.1.0 + * @deprecated 30.0.0 */ interface IProvider { /** diff --git a/lib/public/TextProcessing/IProviderWithExpectedRuntime.php b/lib/public/TextProcessing/IProviderWithExpectedRuntime.php index 46bbcccdc37..b3986e8b3a8 100644 --- a/lib/public/TextProcessing/IProviderWithExpectedRuntime.php +++ b/lib/public/TextProcessing/IProviderWithExpectedRuntime.php @@ -15,6 +15,7 @@ namespace OCP\TextProcessing; * @since 28.0.0 * @template T of ITaskType * @template-extends IProvider<T> + * @deprecated 30.0.0 */ interface IProviderWithExpectedRuntime extends IProvider { /** diff --git a/lib/public/TextProcessing/IProviderWithId.php b/lib/public/TextProcessing/IProviderWithId.php index 7fda3350ae8..359ec9cef71 100644 --- a/lib/public/TextProcessing/IProviderWithId.php +++ b/lib/public/TextProcessing/IProviderWithId.php @@ -13,6 +13,7 @@ namespace OCP\TextProcessing; * @since 28.0.0 * @extends IProvider<T> * @template T of ITaskType + * @deprecated 30.0.0 */ interface IProviderWithId extends IProvider { /** diff --git a/lib/public/TextProcessing/IProviderWithUserId.php b/lib/public/TextProcessing/IProviderWithUserId.php index b97ca2145b7..197dd3d9740 100644 --- a/lib/public/TextProcessing/IProviderWithUserId.php +++ b/lib/public/TextProcessing/IProviderWithUserId.php @@ -15,6 +15,7 @@ namespace OCP\TextProcessing; * @since 28.0.0 * @template T of ITaskType * @template-extends IProvider<T> + * @deprecated 30.0.0 */ interface IProviderWithUserId extends IProvider { /** diff --git a/lib/public/TextProcessing/ITaskType.php b/lib/public/TextProcessing/ITaskType.php index fcff9c8b207..5ec1e8dd049 100644 --- a/lib/public/TextProcessing/ITaskType.php +++ b/lib/public/TextProcessing/ITaskType.php @@ -13,6 +13,7 @@ namespace OCP\TextProcessing; * This is a task type interface that is implemented by text processing * task types * @since 27.1.0 + * @deprecated 30.0.0 */ interface ITaskType { /** diff --git a/lib/public/TextProcessing/SummaryTaskType.php b/lib/public/TextProcessing/SummaryTaskType.php index b77852d3da1..656a50ebc62 100644 --- a/lib/public/TextProcessing/SummaryTaskType.php +++ b/lib/public/TextProcessing/SummaryTaskType.php @@ -15,6 +15,7 @@ use OCP\L10N\IFactory; /** * This is the text processing task type for summaries * @since 27.1.0 + * @deprecated 30.0.0 */ class SummaryTaskType implements ITaskType { private IL10N $l; diff --git a/lib/public/TextProcessing/Task.php b/lib/public/TextProcessing/Task.php index 63fcf2c952a..d968ad9ba3e 100644 --- a/lib/public/TextProcessing/Task.php +++ b/lib/public/TextProcessing/Task.php @@ -13,6 +13,7 @@ namespace OCP\TextProcessing; * This is a text processing task * @since 27.1.0 * @psalm-template-covariant T of ITaskType + * @deprecated 30.0.0 */ final class Task implements \JsonSerializable { protected ?int $id = null; diff --git a/lib/public/TextProcessing/TopicsTaskType.php b/lib/public/TextProcessing/TopicsTaskType.php index 3f1c2c33eda..c693ee3d27f 100644 --- a/lib/public/TextProcessing/TopicsTaskType.php +++ b/lib/public/TextProcessing/TopicsTaskType.php @@ -15,6 +15,7 @@ use OCP\L10N\IFactory; /** * This is the text processing task type for topics extraction * @since 27.1.0 + * @deprecated 30.0.0 */ class TopicsTaskType implements ITaskType { private IL10N $l; diff --git a/lib/public/TextToImage/Events/AbstractTextToImageEvent.php b/lib/public/TextToImage/Events/AbstractTextToImageEvent.php index 4217c75d6b7..df850e1be33 100644 --- a/lib/public/TextToImage/Events/AbstractTextToImageEvent.php +++ b/lib/public/TextToImage/Events/AbstractTextToImageEvent.php @@ -14,13 +14,14 @@ use OCP\TextToImage\Task; /** * @since 28.0.0 + * @deprecated 30.0.0 */ abstract class AbstractTextToImageEvent extends Event { /** * @since 28.0.0 */ public function __construct( - private Task $task + private Task $task, ) { parent::__construct(); } diff --git a/lib/public/TextToImage/Events/TaskFailedEvent.php b/lib/public/TextToImage/Events/TaskFailedEvent.php index eb3049cecc2..8a1b8bbad61 100644 --- a/lib/public/TextToImage/Events/TaskFailedEvent.php +++ b/lib/public/TextToImage/Events/TaskFailedEvent.php @@ -13,6 +13,7 @@ use OCP\TextToImage\Task; /** * @since 28.0.0 + * @deprecated 30.0.0 */ class TaskFailedEvent extends AbstractTextToImageEvent { /** diff --git a/lib/public/TextToImage/Events/TaskSuccessfulEvent.php b/lib/public/TextToImage/Events/TaskSuccessfulEvent.php index c8b125eeb35..ae679437457 100644 --- a/lib/public/TextToImage/Events/TaskSuccessfulEvent.php +++ b/lib/public/TextToImage/Events/TaskSuccessfulEvent.php @@ -11,6 +11,7 @@ namespace OCP\TextToImage\Events; /** * @since 28.0.0 + * @deprecated 30.0.0 */ class TaskSuccessfulEvent extends AbstractTextToImageEvent { } diff --git a/lib/public/TextToImage/Exception/TaskFailureException.php b/lib/public/TextToImage/Exception/TaskFailureException.php index 18d86c6dd1f..89144c3033a 100644 --- a/lib/public/TextToImage/Exception/TaskFailureException.php +++ b/lib/public/TextToImage/Exception/TaskFailureException.php @@ -9,6 +9,7 @@ namespace OCP\TextToImage\Exception; /** * @since 28.0.0 + * @deprecated 30.0.0 */ class TaskFailureException extends TextToImageException { } diff --git a/lib/public/TextToImage/Exception/TaskNotFoundException.php b/lib/public/TextToImage/Exception/TaskNotFoundException.php index a367bc4c849..a488eceb7f4 100644 --- a/lib/public/TextToImage/Exception/TaskNotFoundException.php +++ b/lib/public/TextToImage/Exception/TaskNotFoundException.php @@ -9,6 +9,7 @@ namespace OCP\TextToImage\Exception; /** * @since 28.0.0 + * @deprecated 30.0.0 */ class TaskNotFoundException extends TextToImageException { } diff --git a/lib/public/TextToImage/Exception/TextToImageException.php b/lib/public/TextToImage/Exception/TextToImageException.php index 44b1ccac36a..d8672544072 100644 --- a/lib/public/TextToImage/Exception/TextToImageException.php +++ b/lib/public/TextToImage/Exception/TextToImageException.php @@ -9,6 +9,7 @@ namespace OCP\TextToImage\Exception; /** * @since 28.0.0 + * @deprecated 30.0.0 */ class TextToImageException extends \Exception { } diff --git a/lib/public/TextToImage/IManager.php b/lib/public/TextToImage/IManager.php index a9c3bb8d54b..df8adfc39f4 100644 --- a/lib/public/TextToImage/IManager.php +++ b/lib/public/TextToImage/IManager.php @@ -20,6 +20,7 @@ use RuntimeException; * API surface for apps interacting with and making use of TextToImage providers * without knowing which providers are installed * @since 28.0.0 + * @deprecated 30.0.0 */ interface IManager { /** diff --git a/lib/public/TextToImage/IProvider.php b/lib/public/TextToImage/IProvider.php index ad9deba1730..4a278a0d706 100644 --- a/lib/public/TextToImage/IProvider.php +++ b/lib/public/TextToImage/IProvider.php @@ -15,6 +15,7 @@ use RuntimeException; * This is the interface that is implemented by apps that * implement a text to image provider * @since 28.0.0 + * @deprecated 30.0.0 */ interface IProvider { /** diff --git a/lib/public/TextToImage/IProviderWithUserId.php b/lib/public/TextToImage/IProviderWithUserId.php index c42ec163dbe..a3eedbd9dc0 100644 --- a/lib/public/TextToImage/IProviderWithUserId.php +++ b/lib/public/TextToImage/IProviderWithUserId.php @@ -9,6 +9,7 @@ namespace OCP\TextToImage; /** * @since 29.0.0 + * @deprecated 30.0.0 */ interface IProviderWithUserId extends IProvider { /** diff --git a/lib/public/TextToImage/Task.php b/lib/public/TextToImage/Task.php index a460c082eea..7b94de0fbb3 100644 --- a/lib/public/TextToImage/Task.php +++ b/lib/public/TextToImage/Task.php @@ -20,6 +20,7 @@ use OCP\Image; * This is a text to image task * * @since 28.0.0 + * @deprecated 30.0.0 */ final class Task implements \JsonSerializable { protected ?int $id = null; @@ -80,7 +81,7 @@ final class Task implements \JsonSerializable { $images = []; for ($i = 0; $i < $this->getNumberOfImages(); $i++) { $image = new Image(); - $image->loadFromFileHandle($folder->getFile((string) $i)->read()); + $image->loadFromFileHandle($folder->getFile((string)$i)->read()); $images[] = $image; } return $images; diff --git a/lib/public/Translation/CouldNotTranslateException.php b/lib/public/Translation/CouldNotTranslateException.php index 77948f18da9..fc9f33c879a 100644 --- a/lib/public/Translation/CouldNotTranslateException.php +++ b/lib/public/Translation/CouldNotTranslateException.php @@ -11,6 +11,7 @@ namespace OCP\Translation; /** * @since 27.0.0 + * @deprecated 30.0.0 */ class CouldNotTranslateException extends \RuntimeException { /** diff --git a/lib/public/Translation/IDetectLanguageProvider.php b/lib/public/Translation/IDetectLanguageProvider.php index 28f64ab8ef5..18f40b1aa52 100644 --- a/lib/public/Translation/IDetectLanguageProvider.php +++ b/lib/public/Translation/IDetectLanguageProvider.php @@ -12,6 +12,7 @@ namespace OCP\Translation; /** * @since 26.0.0 + * @deprecated 30.0.0 */ interface IDetectLanguageProvider { /** diff --git a/lib/public/Translation/ITranslationManager.php b/lib/public/Translation/ITranslationManager.php index 8c587f5286f..efeaa2de1e2 100644 --- a/lib/public/Translation/ITranslationManager.php +++ b/lib/public/Translation/ITranslationManager.php @@ -15,6 +15,7 @@ use OCP\PreConditionNotMetException; /** * @since 26.0.0 + * @deprecated 30.0.0 */ interface ITranslationManager { /** diff --git a/lib/public/Translation/ITranslationProvider.php b/lib/public/Translation/ITranslationProvider.php index 45c0467f77e..39267ab9ca5 100644 --- a/lib/public/Translation/ITranslationProvider.php +++ b/lib/public/Translation/ITranslationProvider.php @@ -14,6 +14,7 @@ use RuntimeException; /** * @since 26.0.0 + * @deprecated 30.0.0 */ interface ITranslationProvider { /** diff --git a/lib/public/Translation/ITranslationProviderWithId.php b/lib/public/Translation/ITranslationProviderWithId.php index 1554fd9bb01..93fc641ee02 100644 --- a/lib/public/Translation/ITranslationProviderWithId.php +++ b/lib/public/Translation/ITranslationProviderWithId.php @@ -12,6 +12,7 @@ namespace OCP\Translation; /** * @since 29.0.0 + * @deprecated 30.0.0 */ interface ITranslationProviderWithId extends ITranslationProvider { /** diff --git a/lib/public/Translation/ITranslationProviderWithUserId.php b/lib/public/Translation/ITranslationProviderWithUserId.php index a89058ce488..5bc2255f21f 100644 --- a/lib/public/Translation/ITranslationProviderWithUserId.php +++ b/lib/public/Translation/ITranslationProviderWithUserId.php @@ -12,6 +12,7 @@ namespace OCP\Translation; /** * @since 29.0.0 + * @deprecated 30.0.0 */ interface ITranslationProviderWithUserId extends ITranslationProvider { /** diff --git a/lib/public/Translation/LanguageTuple.php b/lib/public/Translation/LanguageTuple.php index 21edcf24453..07507ece687 100644 --- a/lib/public/Translation/LanguageTuple.php +++ b/lib/public/Translation/LanguageTuple.php @@ -14,6 +14,7 @@ use JsonSerializable; /** * @since 26.0.0 + * @deprecated 30.0.0 */ class LanguageTuple implements JsonSerializable { /** @@ -23,7 +24,7 @@ class LanguageTuple implements JsonSerializable { private string $from, private string $fromLabel, private string $to, - private string $toLabel + private string $toLabel, ) { } diff --git a/lib/public/User/Backend/ICountUsersBackend.php b/lib/public/User/Backend/ICountUsersBackend.php index e337147dd14..194cf465cbe 100644 --- a/lib/public/User/Backend/ICountUsersBackend.php +++ b/lib/public/User/Backend/ICountUsersBackend.php @@ -10,6 +10,7 @@ namespace OCP\User\Backend; /** * @since 14.0.0 + * @deprecated 31.0.0 use and implement ILimitAwareCountUsersBackend instead. */ interface ICountUsersBackend { /** diff --git a/lib/public/User/Backend/IGetRealUIDBackend.php b/lib/public/User/Backend/IGetRealUIDBackend.php index 08ac09f2b5c..8041043f7d5 100644 --- a/lib/public/User/Backend/IGetRealUIDBackend.php +++ b/lib/public/User/Backend/IGetRealUIDBackend.php @@ -17,7 +17,7 @@ interface IGetRealUIDBackend { * For example the database backend accepts different cased UIDs in all the functions * but the internal UID that is to be used should be correctly cased. * - * This little function makes sure that the used UID will be correct hen using the user object + * This little function makes sure that the used UID will be correct when using the user object * * @since 17.0.0 * @param string $uid diff --git a/lib/public/User/Backend/ILimitAwareCountUsersBackend.php b/lib/public/User/Backend/ILimitAwareCountUsersBackend.php new file mode 100644 index 00000000000..a59c0bd8e04 --- /dev/null +++ b/lib/public/User/Backend/ILimitAwareCountUsersBackend.php @@ -0,0 +1,23 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\User\Backend; + +/** + * @since 31.0.0 + */ +interface ILimitAwareCountUsersBackend extends ICountUsersBackend { + /** + * @since 31.0.0 + * + * @param int $limit Limit to stop counting users if there are more than $limit. 0 to disable limiting. + * @return int|false The number of users (may be limited to $limit) on success false on failure + */ + public function countUsers(int $limit = 0): int|false; +} diff --git a/lib/public/User/Backend/IPasswordHashBackend.php b/lib/public/User/Backend/IPasswordHashBackend.php new file mode 100644 index 00000000000..2525b4e45ea --- /dev/null +++ b/lib/public/User/Backend/IPasswordHashBackend.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\User\Backend; + +use InvalidArgumentException; + +/** + * @since 30.0.0 + */ +interface IPasswordHashBackend { + /** + * @return ?string the password hash hashed by `\OCP\Security\IHasher::hash()` + * @since 30.0.0 + */ + public function getPasswordHash(string $userId): ?string; + + /** + * @param string $passwordHash the password hash hashed by `\OCP\Security\IHasher::hash()` + * @throws InvalidArgumentException when `$passwordHash` is not a valid hash + * @since 30.0.0 + */ + public function setPasswordHash(string $userId, string $passwordHash): bool; +} diff --git a/lib/public/User/Events/BeforeUserIdUnassignedEvent.php b/lib/public/User/Events/BeforeUserIdUnassignedEvent.php new file mode 100644 index 00000000000..2dee62521aa --- /dev/null +++ b/lib/public/User/Events/BeforeUserIdUnassignedEvent.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\User\Events; + +use OCP\EventDispatcher\Event; + +/** + * Emitted before removing the mapping between an external user and an internal userid + * @since 31.0.0 + */ +class BeforeUserIdUnassignedEvent extends Event { + /** + * @since 31.0.0 + */ + public function __construct( + private readonly string $userId, + ) { + parent::__construct(); + } + + /** + * @since 31.0.0 + */ + public function getUserId(): string { + return $this->userId; + } +} diff --git a/lib/public/User/Events/OutOfOfficeChangedEvent.php b/lib/public/User/Events/OutOfOfficeChangedEvent.php index b1cdb3abdd7..10865ef7ed5 100644 --- a/lib/public/User/Events/OutOfOfficeChangedEvent.php +++ b/lib/public/User/Events/OutOfOfficeChangedEvent.php @@ -21,7 +21,9 @@ class OutOfOfficeChangedEvent extends Event { /** * @since 28.0.0 */ - public function __construct(private IOutOfOfficeData $data) { + public function __construct( + private IOutOfOfficeData $data, + ) { parent::__construct(); } diff --git a/lib/public/User/Events/OutOfOfficeClearedEvent.php b/lib/public/User/Events/OutOfOfficeClearedEvent.php index 3eb8a9016e7..4153ce98ab5 100644 --- a/lib/public/User/Events/OutOfOfficeClearedEvent.php +++ b/lib/public/User/Events/OutOfOfficeClearedEvent.php @@ -21,7 +21,9 @@ class OutOfOfficeClearedEvent extends Event { /** * @since 28.0.0 */ - public function __construct(private IOutOfOfficeData $data) { + public function __construct( + private IOutOfOfficeData $data, + ) { parent::__construct(); } diff --git a/lib/public/User/Events/OutOfOfficeEndedEvent.php b/lib/public/User/Events/OutOfOfficeEndedEvent.php index e5cbc174d09..0817994f069 100644 --- a/lib/public/User/Events/OutOfOfficeEndedEvent.php +++ b/lib/public/User/Events/OutOfOfficeEndedEvent.php @@ -19,7 +19,9 @@ class OutOfOfficeEndedEvent extends Event { /** * @since 28.0.0 */ - public function __construct(private IOutOfOfficeData $data) { + public function __construct( + private IOutOfOfficeData $data, + ) { parent::__construct(); } diff --git a/lib/public/User/Events/OutOfOfficeScheduledEvent.php b/lib/public/User/Events/OutOfOfficeScheduledEvent.php index 0cf071baf59..f830d2af209 100644 --- a/lib/public/User/Events/OutOfOfficeScheduledEvent.php +++ b/lib/public/User/Events/OutOfOfficeScheduledEvent.php @@ -21,7 +21,9 @@ class OutOfOfficeScheduledEvent extends Event { /** * @since 28.0.0 */ - public function __construct(private IOutOfOfficeData $data) { + public function __construct( + private IOutOfOfficeData $data, + ) { parent::__construct(); } diff --git a/lib/public/User/Events/OutOfOfficeStartedEvent.php b/lib/public/User/Events/OutOfOfficeStartedEvent.php index 9550f5072e0..1b5c69b3cfb 100644 --- a/lib/public/User/Events/OutOfOfficeStartedEvent.php +++ b/lib/public/User/Events/OutOfOfficeStartedEvent.php @@ -19,7 +19,9 @@ class OutOfOfficeStartedEvent extends Event { /** * @since 28.0.0 */ - public function __construct(private IOutOfOfficeData $data) { + public function __construct( + private IOutOfOfficeData $data, + ) { parent::__construct(); } diff --git a/lib/public/User/Events/PasswordUpdatedEvent.php b/lib/public/User/Events/PasswordUpdatedEvent.php index 948b69e54b7..d3fd8f078fe 100644 --- a/lib/public/User/Events/PasswordUpdatedEvent.php +++ b/lib/public/User/Events/PasswordUpdatedEvent.php @@ -50,6 +50,13 @@ class PasswordUpdatedEvent extends Event { } /** + * @since 31.0.0 + */ + public function getUid(): string { + return $this->user->getUID(); + } + + /** * @return string * @since 18.0.0 */ diff --git a/lib/public/User/Events/UserDeletedEvent.php b/lib/public/User/Events/UserDeletedEvent.php index c5b8e11c888..9b74ff96063 100644 --- a/lib/public/User/Events/UserDeletedEvent.php +++ b/lib/public/User/Events/UserDeletedEvent.php @@ -34,4 +34,11 @@ class UserDeletedEvent extends Event { public function getUser(): IUser { return $this->user; } + + /** + * @since 31.0.0 + */ + public function getUid(): string { + return $this->user->getUID(); + } } diff --git a/lib/public/User/Events/UserIdAssignedEvent.php b/lib/public/User/Events/UserIdAssignedEvent.php new file mode 100644 index 00000000000..829bd50c0d0 --- /dev/null +++ b/lib/public/User/Events/UserIdAssignedEvent.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\User\Events; + +use OCP\EventDispatcher\Event; + +/** + * Emitted by backends (like user_ldap) when a user created externally is mapped for the first time and assigned a userid + * @since 31.0.0 + */ +class UserIdAssignedEvent extends Event { + /** + * @since 31.0.0 + */ + public function __construct( + private readonly string $userId, + ) { + parent::__construct(); + } + + /** + * @since 31.0.0 + */ + public function getUserId(): string { + return $this->userId; + } +} diff --git a/lib/public/User/Events/UserIdUnassignedEvent.php b/lib/public/User/Events/UserIdUnassignedEvent.php new file mode 100644 index 00000000000..128648a0753 --- /dev/null +++ b/lib/public/User/Events/UserIdUnassignedEvent.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCP\User\Events; + +use OCP\EventDispatcher\Event; + +/** + * Emitted after removing the mapping between an external user and an internal userid + * @since 31.0.0 + */ +class UserIdUnassignedEvent extends Event { + /** + * @since 31.0.0 + */ + public function __construct( + private readonly string $userId, + ) { + parent::__construct(); + } + + /** + * @since 31.0.0 + */ + public function getUserId(): string { + return $this->userId; + } +} diff --git a/lib/public/User/Events/UserLoggedInEvent.php b/lib/public/User/Events/UserLoggedInEvent.php index 61a7b1f79cd..53f61073a59 100644 --- a/lib/public/User/Events/UserLoggedInEvent.php +++ b/lib/public/User/Events/UserLoggedInEvent.php @@ -46,6 +46,13 @@ class UserLoggedInEvent extends Event { } /** + * @since 31.0.0 + */ + public function getUid(): string { + return $this->user->getUID(); + } + + /** * @since 21.0.0 */ public function getLoginName(): string { diff --git a/lib/public/User/IOutOfOfficeData.php b/lib/public/User/IOutOfOfficeData.php index c3a5bf90184..e3a4e2773bb 100644 --- a/lib/public/User/IOutOfOfficeData.php +++ b/lib/public/User/IOutOfOfficeData.php @@ -22,6 +22,8 @@ use OCP\IUser; * endDate: int, * shortMessage: string, * message: string, + * replacementUserId: ?string, + * replacementUserDisplayName: ?string * } * * @since 28.0.0 @@ -70,6 +72,20 @@ interface IOutOfOfficeData extends JsonSerializable { public function getMessage(): string; /** + * Get the replacement user id for auto responders and similar + * + * @since 30.0.0 + */ + public function getReplacementUserId(): ?string; + + /** + * Get the replacement user displayName for auto responders and similar + * + * @since 30.0.0 + */ + public function getReplacementUserDisplayName(): ?string; + + /** * @return OutOfOfficeData * * @since 28.0.0 diff --git a/lib/public/UserMigration/IMigrator.php b/lib/public/UserMigration/IMigrator.php index 3083233a4c4..8ce2cca8f98 100644 --- a/lib/public/UserMigration/IMigrator.php +++ b/lib/public/UserMigration/IMigrator.php @@ -25,7 +25,7 @@ interface IMigrator { public function export( IUser $user, IExportDestination $exportDestination, - OutputInterface $output + OutputInterface $output, ): void; /** @@ -37,7 +37,7 @@ interface IMigrator { public function import( IUser $user, IImportSource $importSource, - OutputInterface $output + OutputInterface $output, ): void; /** @@ -75,6 +75,6 @@ interface IMigrator { * @since 24.0.0 */ public function canImport( - IImportSource $importSource + IImportSource $importSource, ): bool; } diff --git a/lib/public/UserMigration/TMigratorBasicVersionHandling.php b/lib/public/UserMigration/TMigratorBasicVersionHandling.php index f679c8e87ae..b33425a023d 100644 --- a/lib/public/UserMigration/TMigratorBasicVersionHandling.php +++ b/lib/public/UserMigration/TMigratorBasicVersionHandling.php @@ -31,7 +31,7 @@ trait TMigratorBasicVersionHandling { * @since 24.0.0 */ public function canImport( - IImportSource $importSource + IImportSource $importSource, ): bool { $version = $importSource->getMigratorVersion($this->getId()); if ($version === null) { diff --git a/lib/public/Util.php b/lib/public/Util.php index 4cee9addf10..b3111c54fc7 100644 --- a/lib/public/Util.php +++ b/lib/public/Util.php @@ -18,7 +18,6 @@ use OCP\L10N\IFactory; use OCP\Mail\IMailer; use OCP\Share\IManager; use Psr\Container\ContainerExceptionInterface; -use Psr\Log\LoggerInterface; /** * This class provides different helper functions to make the life of a developer easier @@ -36,9 +35,10 @@ class Util { * get the current installed version of Nextcloud * @return array * @since 4.0.0 + * @deprecated 31.0.0 Use \OCP\ServerVersion::getVersion */ public static function getVersion() { - return \OC_Util::getVersion(); + return Server::get(ServerVersion::class)->getVersion(); } /** @@ -47,11 +47,11 @@ class Util { public static function hasExtendedSupport(): bool { try { /** @var \OCP\Support\Subscription\IRegistry */ - $subscriptionRegistry = \OCP\Server::get(\OCP\Support\Subscription\IRegistry::class); + $subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class); return $subscriptionRegistry->delegateHasExtendedSupport(); } catch (ContainerExceptionInterface $e) { } - return \OC::$server->getConfig()->getSystemValueBool('extendedSupport', false); + return \OCP\Server::get(IConfig::class)->getSystemValueBool('extendedSupport', false); } /** @@ -60,16 +60,17 @@ class Util { * @since 8.1.0 */ public static function setChannel($channel) { - \OC::$server->getConfig()->setSystemValue('updater.release.channel', $channel); + \OCP\Server::get(IConfig::class)->setSystemValue('updater.release.channel', $channel); } /** * Get current update channel * @return string * @since 8.1.0 + * @deprecated 31.0.0 Use \OCP\ServerVersion::getChannel */ public static function getChannel() { - return \OC_Util::getChannel(); + return \OCP\Server::get(ServerVersion::class)->getChannel(); } /** @@ -77,19 +78,16 @@ class Util { * * @return boolean * @since 7.0.0 - * @deprecated 9.1.0 Use \OC::$server->get(\OCP\Share\IManager::class)->sharingDisabledForUser + * @deprecated 9.1.0 Use Server::get(\OCP\Share\IManager::class)->sharingDisabledForUser */ public static function isSharingDisabledForUser() { if (self::$shareManager === null) { - self::$shareManager = \OC::$server->get(IManager::class); + self::$shareManager = Server::get(IManager::class); } - $user = \OC::$server->getUserSession()->getUser(); - if ($user !== null) { - $user = $user->getUID(); - } + $user = Server::get(\OCP\IUserSession::class)->getUser(); - return self::$shareManager->sharingDisabledForUser($user); + return self::$shareManager->sharingDisabledForUser($user?->getUID()); } /** @@ -101,13 +99,15 @@ class Util { } /** - * add a css file - * @param string $application - * @param string $file + * Add a css file + * + * @param string $application application id + * @param ?string $file filename + * @param bool $prepend prepend the style to the beginning of the list * @since 4.0.0 */ - public static function addStyle($application, $file = null) { - \OC_Util::addStyle($application, $file); + public static function addStyle(string $application, ?string $file = null, bool $prepend = false): void { + \OC_Util::addStyle($application, $file, $prepend); } /** @@ -182,7 +182,7 @@ class Util { */ public static function getScripts(): array { // Sort scriptDeps into sortedScriptDeps - $scriptSort = \OC::$server->get(AppScriptSort::class); + $scriptSort = \OCP\Server::get(AppScriptSort::class); $sortedScripts = $scriptSort->sort(self::$scripts, self::$scriptDeps); // Flatten array and remove duplicates @@ -209,7 +209,7 @@ class Util { */ public static function addTranslations($application, $languageCode = null, $init = false) { if (is_null($languageCode)) { - $languageCode = \OC::$server->get(IFactory::class)->findLanguage($application); + $languageCode = \OCP\Server::get(IFactory::class)->findLanguage($application); } if (!empty($application)) { $path = "$application/l10n/$languageCode"; @@ -242,12 +242,12 @@ class Util { * @param string $app app * @param string $file file * @param array $args array with param=>value, will be appended to the returned url - * The value of $args will be urlencoded + * The value of $args will be urlencoded * @return string the url * @since 4.0.0 - parameter $args was added in 4.5.0 */ public static function linkToAbsolute($app, $file, $args = []) { - $urlGenerator = \OC::$server->getURLGenerator(); + $urlGenerator = \OCP\Server::get(IURLGenerator::class); return $urlGenerator->getAbsoluteURL( $urlGenerator->linkTo($app, $file, $args) ); @@ -260,7 +260,7 @@ class Util { * @since 4.0.0 */ public static function linkToRemote($service) { - $urlGenerator = \OC::$server->getURLGenerator(); + $urlGenerator = \OCP\Server::get(IURLGenerator::class); $remoteBase = $urlGenerator->linkTo('', 'remote.php') . '/' . $service; return $urlGenerator->getAbsoluteURL( $remoteBase . (($service[strlen($service) - 1] != '/') ? '/' : '') @@ -273,7 +273,7 @@ class Util { * @since 5.0.0 */ public static function getServerHostName() { - $host_name = \OC::$server->getRequest()->getServerHost(); + $host_name = \OCP\Server::get(IRequest::class)->getServerHost(); // strip away port number (if existing) $colon_pos = strpos($host_name, ':'); if ($colon_pos != false) { @@ -299,19 +299,19 @@ class Util { * @since 5.0.0 */ public static function getDefaultEmailAddress(string $user_part): string { - $config = \OC::$server->getConfig(); + $config = \OCP\Server::get(IConfig::class); $user_part = $config->getSystemValueString('mail_from_address', $user_part); $host_name = self::getServerHostName(); $host_name = $config->getSystemValueString('mail_domain', $host_name); - $defaultEmailAddress = $user_part.'@'.$host_name; + $defaultEmailAddress = $user_part . '@' . $host_name; - $mailer = \OC::$server->get(IMailer::class); + $mailer = \OCP\Server::get(IMailer::class); if ($mailer->validateMailAddress($defaultEmailAddress)) { return $defaultEmailAddress; } // in case we cannot build a valid email address from the hostname let's fallback to 'localhost.localdomain' - return $user_part.'@localhost.localdomain'; + return $user_part . '@localhost.localdomain'; } /** @@ -332,19 +332,70 @@ class Util { * @since 4.0.0 */ public static function humanFileSize(int|float $bytes): string { - return \OC_Helper::humanFileSize($bytes); + if ($bytes < 0) { + return '?'; + } + if ($bytes < 1024) { + return "$bytes B"; + } + $bytes = round($bytes / 1024, 0); + if ($bytes < 1024) { + return "$bytes KB"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return "$bytes MB"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return "$bytes GB"; + } + $bytes = round($bytes / 1024, 1); + if ($bytes < 1024) { + return "$bytes TB"; + } + + $bytes = round($bytes / 1024, 1); + return "$bytes PB"; } /** * Make a computer file size (2 kB to 2048) + * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418 + * * @param string $str file size in a fancy format * @return false|int|float a file size in bytes - * - * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418 * @since 4.0.0 */ public static function computerFileSize(string $str): false|int|float { - return \OC_Helper::computerFileSize($str); + $str = strtolower($str); + if (is_numeric($str)) { + return Util::numericToNumber($str); + } + + $bytes_array = [ + 'b' => 1, + 'k' => 1024, + 'kb' => 1024, + 'mb' => 1024 * 1024, + 'm' => 1024 * 1024, + 'gb' => 1024 * 1024 * 1024, + 'g' => 1024 * 1024 * 1024, + 'tb' => 1024 * 1024 * 1024 * 1024, + 't' => 1024 * 1024 * 1024 * 1024, + 'pb' => 1024 * 1024 * 1024 * 1024 * 1024, + 'p' => 1024 * 1024 * 1024 * 1024 * 1024, + ]; + + $bytes = (float)$str; + + if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && isset($bytes_array[$matches[1]])) { + $bytes *= $bytes_array[$matches[1]]; + } else { + return false; + } + + return Util::numericToNumber(round($bytes)); } /** @@ -383,7 +434,7 @@ class Util { /** * Cached encrypted CSRF token. Some static unit-tests of ownCloud compare - * multiple OC_Template elements which invoke `callRegister`. If the value + * multiple Template elements which invoke `callRegister`. If the value * would not be cached these unit-tests would fail. * @var string */ @@ -392,10 +443,11 @@ class Util { /** * Register an get/post call. This is important to prevent CSRF attacks * @since 4.5.0 + * @deprecated 32.0.0 directly use CsrfTokenManager instead */ public static function callRegister() { if (self::$token === '') { - self::$token = \OC::$server->get(CsrfTokenManager::class)->getToken()->getEncryptedValue(); + self::$token = \OCP\Server::get(CsrfTokenManager::class)->getToken()->getEncryptedValue(); } return self::$token; } @@ -407,7 +459,7 @@ class Util { * string or array of strings before displaying it on a web page. * * @param string|string[] $value - * @return string|string[] an array of sanitized strings or a single sanitized string, depends on the input parameter. + * @return ($value is array ? string[] : string) an array of sanitized strings or a single sanitized string, depends on the input parameter. * @since 4.5.0 */ public static function sanitizeHTML($value) { @@ -439,7 +491,12 @@ class Util { * @since 4.5.0 */ public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') { - return \OC_Helper::mb_array_change_key_case($input, $case, $encoding); + $case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER; + $ret = []; + foreach ($input as $k => $v) { + $ret[mb_convert_case($k, $case, $encoding)] = $v; + } + return $ret; } /** @@ -453,7 +510,18 @@ class Util { * @deprecated 15.0.0 */ public static function recursiveArraySearch($haystack, $needle, $index = null) { - return \OC_Helper::recursiveArraySearch($haystack, $needle, $index); + $aIt = new \RecursiveArrayIterator($haystack); + $it = new \RecursiveIteratorIterator($aIt); + + while ($it->valid()) { + if (((isset($index) and ($it->key() == $index)) or !isset($index)) and ($it->current() == $needle)) { + return $aIt->key(); + } + + $it->next(); + } + + return false; } /** @@ -465,7 +533,10 @@ class Util { * @since 5.0.0 */ public static function maxUploadFilesize(string $dir, int|float|null $free = null): int|float { - return \OC_Helper::maxUploadFilesize($dir, $free); + if (is_null($free) || $free < 0) { + $free = self::freeSpace($dir); + } + return min($free, self::uploadLimit()); } /** @@ -475,7 +546,13 @@ class Util { * @since 7.0.0 */ public static function freeSpace(string $dir): int|float { - return \OC_Helper::freeSpace($dir); + $freeSpace = \OC\Files\Filesystem::free_space($dir); + if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) { + $freeSpace = max($freeSpace, 0); + return $freeSpace; + } else { + return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188 + } } /** @@ -485,40 +562,16 @@ class Util { * @since 7.0.0 */ public static function uploadLimit(): int|float { - return \OC_Helper::uploadLimit(); - } - - /** - * Get a list of characters forbidden in file names - * @return string[] - * @since 29.0.0 - */ - public static function getForbiddenFileNameChars(): array { - // Get always forbidden characters - $invalidChars = str_split(\OCP\Constants::FILENAME_INVALID_CHARS); - if ($invalidChars === false) { - $invalidChars = []; - } - - // Get admin defined invalid characters - $additionalChars = \OCP\Server::get(IConfig::class)->getSystemValue('forbidden_chars', []); - if (!is_array($additionalChars)) { - \OCP\Server::get(LoggerInterface::class)->error('Invalid system config value for "forbidden_chars" is ignored.'); - $additionalChars = []; + $ini = Server::get(IniGetWrapper::class); + $upload_max_filesize = self::computerFileSize($ini->get('upload_max_filesize')) ?: 0; + $post_max_size = self::computerFileSize($ini->get('post_max_size')) ?: 0; + if ($upload_max_filesize === 0 && $post_max_size === 0) { + return INF; + } elseif ($upload_max_filesize === 0 || $post_max_size === 0) { + return max($upload_max_filesize, $post_max_size); //only the non 0 value counts + } else { + return min($upload_max_filesize, $post_max_size); } - return array_merge($invalidChars, $additionalChars); - } - - /** - * Returns whether the given file name is valid - * @param string $file file name to check - * @return bool true if the file name is valid, false otherwise - * @deprecated 8.1.0 use OCP\Files\Storage\IStorage::verifyPath() - * @since 7.0.0 - * @suppress PhanDeprecatedFunction - */ - public static function isValidFileName($file) { - return \OC_Util::isValidFileName($file); } /** @@ -526,7 +579,7 @@ class Util { * @param string $a first string to compare * @param string $b second string to compare * @return int -1 if $b comes before $a, 1 if $a comes before $b - * or 0 if the strings are identical + * or 0 if the strings are identical * @since 7.0.0 */ public static function naturalSortCompare($a, $b) { @@ -563,7 +616,7 @@ class Util { */ public static function needUpgrade() { if (!isset(self::$needUpgradeCache)) { - self::$needUpgradeCache = \OC_Util::needUpgrade(\OC::$server->getSystemConfig()); + self::$needUpgradeCache = \OC_Util::needUpgrade(\OCP\Server::get(\OC\SystemConfig::class)); } return self::$needUpgradeCache; } @@ -601,7 +654,7 @@ class Util { if (!function_exists($functionName)) { return false; } - $ini = \OCP\Server::get(IniGetWrapper::class); + $ini = Server::get(IniGetWrapper::class); $disabled = explode(',', $ini->get('disable_functions') ?: ''); $disabled = array_map('trim', $disabled); if (in_array($functionName, $disabled)) { diff --git a/lib/public/WorkflowEngine/ICheck.php b/lib/public/WorkflowEngine/ICheck.php index 79c1a864ae0..4671eb84c3e 100644 --- a/lib/public/WorkflowEngine/ICheck.php +++ b/lib/public/WorkflowEngine/ICheck.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/WorkflowEngine/IManager.php b/lib/public/WorkflowEngine/IManager.php index b9235abafef..f66a9460f06 100644 --- a/lib/public/WorkflowEngine/IManager.php +++ b/lib/public/WorkflowEngine/IManager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/public/WorkflowEngine/IOperation.php b/lib/public/WorkflowEngine/IOperation.php index 9f867eb811a..cda20acb7e5 100644 --- a/lib/public/WorkflowEngine/IOperation.php +++ b/lib/public/WorkflowEngine/IOperation.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later |