Profile backend

Signed-off-by: Christopher Ng <chrng8@gmail.com>
pull/28751/head
Christopher Ng 3 years ago
parent 7215148a24
commit 309354852f

@ -721,11 +721,11 @@ class UsersController extends AUserData {
*
* @param string $userId
* @param string $key
* @param string|bool $value
* @param string $value
* @return DataResponse
* @throws OCSException
*/
public function editUser(string $userId, string $key, $value): DataResponse {
public function editUser(string $userId, string $key, string $value): DataResponse {
$currentLoggedInUser = $this->userSession->getUser();
$targetUser = $this->userManager->get($userId);
@ -961,11 +961,6 @@ class UsersController extends AUserData {
$this->accountManager->updateAccount($userAccount);
break;
case IAccountManager::PROPERTY_PROFILE_ENABLED:
if (!is_bool($value)) {
throw new OCSException('Invalid value, value must be a boolean', 102);
}
$value = $value === true ? '1' : '0';
$userAccount = $this->accountManager->getAccount($targetUser);
try {
$userProperty = $userAccount->getProperty($key);

@ -33,13 +33,16 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Settings\Settings\Personal;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCP\Accounts\IAccount;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\IAccountProperty;
use OCP\App\IAppManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\Files\FileInfo;
use OCP\IConfig;
use OCP\IGroup;
@ -48,26 +51,36 @@ use OCP\IL10N;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OC\Profile\ProfileManager;
use OCP\Settings\ISettings;
use OCP\Accounts\IAccountProperty;
use OCP\AppFramework\Services\IInitialState;
class PersonalInfo implements ISettings {
use \OC\Profile\TProfileHelper;
/** @var IConfig */
private $config;
/** @var IUserManager */
private $userManager;
/** @var IAccountManager */
private $accountManager;
/** @var ProfileManager */
private $profileManager;
/** @var IGroupManager */
private $groupManager;
/** @var IAppManager */
private $appManager;
/** @var IFactory */
private $l10nFactory;
/** @var IL10N */
private $l;
/** @var IInitialState */
private $initialStateService;
@ -76,6 +89,7 @@ class PersonalInfo implements ISettings {
IUserManager $userManager,
IGroupManager $groupManager,
IAccountManager $accountManager,
ProfileManager $profileManager,
IAppManager $appManager,
IFactory $l10nFactory,
IL10N $l,
@ -84,6 +98,7 @@ class PersonalInfo implements ISettings {
$this->config = $config;
$this->userManager = $userManager;
$this->accountManager = $accountManager;
$this->profileManager = $profileManager;
$this->groupManager = $groupManager;
$this->appManager = $appManager;
$this->l10nFactory = $l10nFactory;
@ -114,7 +129,7 @@ class PersonalInfo implements ISettings {
$totalSpace = \OC_Helper::humanFileSize($storageInfo['total']);
}
$languageParameters = $this->getLanguages($user);
$languageParameters = $this->getLanguageMap($user);
$localeParameters = $this->getLocales($user);
$messageParameters = $this->getMessageParameters($account);
@ -146,9 +161,15 @@ class PersonalInfo implements ISettings {
] + $messageParameters + $languageParameters + $localeParameters;
$personalInfoParameters = [
'displayNames' => $this->getDisplayNames($account),
'emails' => $this->getEmails($account),
'languages' => $this->getLanguages($user),
'userId' => $uid,
'displayNameMap' => $this->getDisplayNameMap($account),
'emailMap' => $this->getEmailMap($account),
'languageMap' => $this->getLanguageMap($user),
'profileEnabled' => $this->isProfileEnabled($account),
'organisationMap' => $this->getOrganisationMap($account),
'roleMap' => $this->getRoleMap($account),
'headlineMap' => $this->getHeadlineMap($account),
'biographyMap' => $this->getBiographyMap($account),
];
$accountParameters = [
@ -156,14 +177,91 @@ class PersonalInfo implements ISettings {
'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
];
$profileParameters = [
'profileConfig' => $this->profileManager->getProfileConfig($user, $user),
];
$this->initialStateService->provideInitialState('personalInfoParameters', $personalInfoParameters);
$this->initialStateService->provideInitialState('accountParameters', $accountParameters);
$this->initialStateService->provideInitialState('profileParameters', $profileParameters);
return new TemplateResponse('settings', 'settings/personal/personal.info', $parameters, '');
}
/**
* @return string the section ID, e.g. 'sharing'
* returns the primary biography in an
* associative array
*/
private function getBiographyMap(IAccount $account): array {
$primaryBiography = [
'value' => $account->getProperty(IAccountManager::PROPERTY_BIOGRAPHY)->getValue(),
'scope' => $account->getProperty(IAccountManager::PROPERTY_BIOGRAPHY)->getScope(),
'verified' => $account->getProperty(IAccountManager::PROPERTY_BIOGRAPHY)->getVerified(),
];
$biographyMap = [
'primaryBiography' => $primaryBiography,
];
return $biographyMap;
}
/**
* returns the primary organisation in an
* associative array
*/
private function getOrganisationMap(IAccount $account): array {
$primaryOrganisation = [
'value' => $account->getProperty(IAccountManager::PROPERTY_ORGANISATION)->getValue(),
'scope' => $account->getProperty(IAccountManager::PROPERTY_ORGANISATION)->getScope(),
'verified' => $account->getProperty(IAccountManager::PROPERTY_ORGANISATION)->getVerified(),
];
$organisationMap = [
'primaryOrganisation' => $primaryOrganisation,
];
return $organisationMap;
}
/**
* returns the primary headline in an
* associative array
*/
private function getHeadlineMap(IAccount $account): array {
$primaryHeadline = [
'value' => $account->getProperty(IAccountManager::PROPERTY_HEADLINE)->getValue(),
'scope' => $account->getProperty(IAccountManager::PROPERTY_HEADLINE)->getScope(),
'verified' => $account->getProperty(IAccountManager::PROPERTY_HEADLINE)->getVerified(),
];
$headlineMap = [
'primaryHeadline' => $primaryHeadline,
];
return $headlineMap;
}
/**
* returns the primary role in an
* associative array
*/
private function getRoleMap(IAccount $account): array {
$primaryRole = [
'value' => $account->getProperty(IAccountManager::PROPERTY_ROLE)->getValue(),
'scope' => $account->getProperty(IAccountManager::PROPERTY_ROLE)->getScope(),
'verified' => $account->getProperty(IAccountManager::PROPERTY_ROLE)->getVerified(),
];
$roleMap = [
'primaryRole' => $primaryRole,
];
return $roleMap;
}
/**
* returns the section ID string, e.g. 'sharing'
* @since 9.1
*/
public function getSection(): string {
@ -184,9 +282,6 @@ class PersonalInfo implements ISettings {
/**
* returns a sorted list of the user's group GIDs
*
* @param IUser $user
* @return array
*/
private function getGroups(IUser $user): array {
$groups = array_map(
@ -205,32 +300,26 @@ class PersonalInfo implements ISettings {
* associative array
*
* NOTE may be extended to provide additional display names (i.e. aliases) in the future
*
* @param IAccount $account
* @return array
*/
private function getDisplayNames(IAccount $account): array {
private function getDisplayNameMap(IAccount $account): array {
$primaryDisplayName = [
'value' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(),
'scope' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getScope(),
'verified' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getVerified(),
];
$displayNames = [
$displayNameMap = [
'primaryDisplayName' => $primaryDisplayName,
];
return $displayNames;
return $displayNameMap;
}
/**
* returns the primary email and additional emails in an
* associative array
*
* @param IAccount $account
* @return array
*/
private function getEmails(IAccount $account): array {
private function getEmailMap(IAccount $account): array {
$systemEmail = [
'value' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(),
'scope' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getScope(),
@ -246,26 +335,23 @@ class PersonalInfo implements ISettings {
'locallyVerified' => $property->getLocallyVerified(),
];
},
$account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties()
$account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties(),
);
$emails = [
$emailMap = [
'primaryEmail' => $systemEmail,
'additionalEmails' => $additionalEmails,
'notificationEmail' => (string)$account->getUser()->getPrimaryEMailAddress(),
];
return $emails;
return $emailMap;
}
/**
* returns the user's active language, common languages, and other languages in an
* associative array
*
* @param IUser $user
* @return array
*/
private function getLanguages(IUser $user): array {
private function getLanguageMap(IUser $user): array {
$forceLanguage = $this->config->getSystemValue('force_language', false);
if ($forceLanguage !== false) {
return [];
@ -340,8 +426,7 @@ class PersonalInfo implements ISettings {
}
/**
* @param IAccount $account
* @return array
* returns the message parameters
*/
private function getMessageParameters(IAccount $account): array {
$needVerifyMessage = [IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER];

@ -24,17 +24,27 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\UserStatus\Listener;
use OCA\UserStatus\AppInfo\Application;
use OCA\UserStatus\Service\JSDataService;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IInitialStateService;
use OCP\IUserSession;
class BeforeTemplateRenderedListener implements IEventListener {
use \OC\Profile\TProfileHelper;
/** @var IAccountManager */
private $accountManager;
/** @var IUserSession */
private $userSession;
/** @var IInitialStateService */
private $initialState;
@ -45,11 +55,19 @@ class BeforeTemplateRenderedListener implements IEventListener {
/**
* BeforeTemplateRenderedListener constructor.
*
* @param IAccountManager $accountManager
* @param IUserSession $userSession
* @param IInitialStateService $initialState
* @param JSDataService $jsDataService
*/
public function __construct(IInitialStateService $initialState,
JSDataService $jsDataService) {
public function __construct(
IAccountManager $accountManager,
IUserSession $userSession,
IInitialStateService $initialState,
JSDataService $jsDataService
) {
$this->accountManager = $accountManager;
$this->userSession = $userSession;
$this->initialState = $initialState;
$this->jsDataService = $jsDataService;
}
@ -58,6 +76,12 @@ class BeforeTemplateRenderedListener implements IEventListener {
* @inheritDoc
*/
public function handle(Event $event): void {
$user = $this->userSession->getUser();
if ($user === null) {
return;
}
$account = $this->accountManager->getAccount($user);
if (!($event instanceof BeforeTemplateRenderedEvent)) {
// Unrelated
return;
@ -71,6 +95,10 @@ class BeforeTemplateRenderedListener implements IEventListener {
return $this->jsDataService;
});
$this->initialState->provideLazyInitialState(Application::APP_ID, 'profileEnabled', function () use ($account) {
return ['profileEnabled' => $this->isProfileEnabled($account)];
});
\OCP\Util::addScript('user_status', 'user-status-menu');
\OCP\Util::addStyle('user_status', 'user-status-menu');
}

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Core\Controller;
use OC\Core\Db\ProfileConfigMapper;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCS\OCSForbiddenException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\IUserSession;
use OC\Profile\ProfileManager;
class ProfileApiController extends OCSController {
/** @var ProfileConfigMapper */
private $configMapper;
/** @var ProfileManager */
private $profileManager;
/** @var IUserManager */
private $userManager;
/** @var IUserSession */
private $userSession;
public function __construct(
IRequest $request,
ProfileConfigMapper $configMapper,
ProfileManager $profileManager,
IUserManager $userManager,
IUserSession $userSession
) {
parent::__construct('core', $request);
$this->configMapper = $configMapper;
$this->profileManager = $profileManager;
$this->userManager = $userManager;
$this->userSession = $userSession;
}
/**
* @NoAdminRequired
* @NoSubAdminRequired
* @PasswordConfirmationRequired
*/
public function setVisibility(string $targetUserId, string $paramId, string $visibility): DataResponse {
$requestingUser = $this->userSession->getUser();
$targetUser = $this->userManager->get($targetUserId);
if (!$this->userManager->userExists($targetUserId)) {
throw new OCSNotFoundException('User does not exist');
}
if ($requestingUser !== $targetUser) {
throw new OCSForbiddenException('Users can only edit their own visibility settings');
}
// Ensure that a profile config is created in the database
$this->profileManager->getProfileConfig($targetUser, $targetUser);
$config = $this->configMapper->get($targetUserId);
if (!in_array($paramId, array_keys($config->getVisibilityMap()), true)) {
throw new OCSBadRequestException('User does not have a profile parameter with ID: ' . $paramId);
}
$config->setVisibility($paramId, $visibility);
$this->configMapper->update($config);
return new DataResponse();
}
}

@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Core\Controller;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IRequest;
use OCP\IUserManager;
use OCP\IUserSession;
use OC\Profile\ProfileManager;
use OCP\UserStatus\IManager as IUserStatusManager;
class ProfilePageController extends Controller {
use \OC\Profile\TProfileHelper;
/** @var IInitialState */
private $initialStateService;
/** @var IAccountManager */
private $accountManager;
/** @var ProfileManager */
private $profileManager;
/** @var IUserManager */
private $userManager;
/** @var IUserSession */
private $userSession;
/** @var IUserStatusManager */
private $userStatusManager;
public function __construct(
$appName,
IRequest $request,
IInitialState $initialStateService,
IAccountManager $accountManager,
ProfileManager $profileManager,
IUserManager $userManager,
IUserSession $userSession,
IUserStatusManager $userStatusManager
) {
parent::__construct($appName, $request);
$this->initialStateService = $initialStateService;
$this->accountManager = $accountManager;
$this->profileManager = $profileManager;
$this->userManager = $userManager;
$this->userSession = $userSession;
$this->userStatusManager = $userStatusManager;
}
/**
* @PublicPage
* @NoCSRFRequired
* @NoAdminRequired
* @NoSubAdminRequired
*/
public function index(string $targetUserId): TemplateResponse {
if (!$this->userManager->userExists($targetUserId)) {
return new TemplateResponse(
'core',
'404-profile',
[],
TemplateResponse::RENDER_AS_GUEST,
);
}
$visitingUser = $this->userSession->getUser();
$targetUser = $this->userManager->get($targetUserId);
$targetAccount = $this->accountManager->getAccount($targetUser);
if (!$this->isProfileEnabled($targetAccount)) {
return new TemplateResponse(
'core',
'404-profile',
[],
TemplateResponse::RENDER_AS_GUEST,
);
}
$userStatuses = $this->userStatusManager->getUserStatuses([$targetUserId]);
$status = array_shift($userStatuses);
if (!empty($status)) {
$this->initialStateService->provideInitialState('status', [
'icon' => $status->getIcon(),
'message' => $status->getMessage(),
]);
}
$this->initialStateService->provideInitialState(
'profileParameters',
$this->profileManager->getProfileParams($targetUser, $visitingUser),
);
\OCP\Util::addScript('core', 'dist/profile');
return new TemplateResponse(
'core',
'profile',
[],
$this->userSession->isLoggedIn() ? TemplateResponse::RENDER_AS_USER : TemplateResponse::RENDER_AS_PUBLIC,
);
}
}

@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Core\Db;
use function Safe\json_decode;
use function Safe\json_encode;
use \JsonSerializable;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\Db\Entity;
use OCP\Profile\ParameterDoesNotExistException;
/**
* @method string getUserId()
* @method void setUserId(string $userId)
* @method string getConfig()
* @method void setConfig(string $config)
*/
class ProfileConfig extends Entity implements JsonSerializable {
/**
* Visible to users, guests, and public access
*
* @since 23.0.0
*/
public const VISIBILITY_SHOW = 'show';
/**
* Visible to users and guests
*
* @since 23.0.0
*/
public const VISIBILITY_SHOW_USERS_ONLY = 'show_users_only';
/**
* Visible to nobody
*
* @since 23.0.0
*/
public const VISIBILITY_HIDE = 'hide';
/**
* Default account property visibility
*
* @since 23.0.0
*/
public const DEFAULT_PROPERTY_VISIBILITY = [
IAccountManager::PROPERTY_ADDRESS => self::VISIBILITY_SHOW_USERS_ONLY,
IAccountManager::PROPERTY_AVATAR => self::VISIBILITY_SHOW,
IAccountManager::PROPERTY_BIOGRAPHY => self::VISIBILITY_SHOW,
IAccountManager::PROPERTY_DISPLAYNAME => self::VISIBILITY_SHOW,
IAccountManager::PROPERTY_HEADLINE => self::VISIBILITY_SHOW,
IAccountManager::PROPERTY_ORGANISATION => self::VISIBILITY_SHOW,
IAccountManager::PROPERTY_ROLE => self::VISIBILITY_SHOW,
IAccountManager::PROPERTY_EMAIL => self::VISIBILITY_SHOW_USERS_ONLY,
IAccountManager::PROPERTY_PHONE => self::VISIBILITY_SHOW_USERS_ONLY,
IAccountManager::PROPERTY_TWITTER => self::VISIBILITY_SHOW,
IAccountManager::PROPERTY_WEBSITE => self::VISIBILITY_SHOW,
];
/**
* Default visibility
*
* @since 23.0.0
*/
public const DEFAULT_VISIBILITY = self::VISIBILITY_SHOW_USERS_ONLY;
/** @var string */
protected $userId;
/** @var string */
protected $config;
public function __construct() {
$this->addType('userId', 'string');
$this->addType('config', 'string');
}
/**
* Returns the config in an associative array
*/
public function getConfigArray(): array {
return json_decode($this->config, true);
}
/**
* Set the config
*/
public function setConfigArray(array $config): void {
$this->setConfig(json_encode($config));
}
/**
* Returns the visibility map in an associative array
*/
public function getVisibilityMap(): array {
$config = $this->getConfigArray();
$visibilityMap = [];
foreach ($config as $paramId => $paramConfig) {
$visibilityMap[$paramId] = $paramConfig['visibility'];
}
return $visibilityMap;
}
/**
* Set the visibility map
*/
public function setVisibilityMap(array $visibilityMap): void {
$config = $this->getConfigArray();
foreach ($visibilityMap as $paramId => $visibility) {
$config[$paramId] = array_merge(
$config[$paramId] ?: [],
['visibility' => $visibility],
);
}
$this->setConfigArray($config);
}
/**
* Returns the visibility of the parameter
*
* @throws ParameterDoesNotExistException
*/
public function getVisibility(string $paramId): string {
$visibilityMap = $this->getVisibilityMap();
if (isset($visibilityMap[$paramId])) {
return $visibilityMap[$paramId];
}
throw new ParameterDoesNotExistException($paramId);
}
/**
* Set the visibility of the parameter
*/
public function setVisibility(string $paramId, string $visibility): void {
$visibilityMap = $this->getVisibilityMap();
$visibilityMap[$paramId] = $visibility;
$this->setVisibilityMap($visibilityMap);
}
public function jsonSerialize(): array {
return [
'userId' => $this->userId,
'config' => $this->getConfigArray(),
];
}
}

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Core\Db;
use OCP\AppFramework\Db\QBMapper;
use OCP\IDBConnection;
class ProfileConfigMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'profile_config', ProfileConfig::class);
}
public function get(string $userId): ProfileConfig {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
return $this->findEntity($qb);
}
public function getArray(string $userId): array {
return $this->get($userId)->getConfigArray();
}
}

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Core\Migrations;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version23000Date20210930122352 extends SimpleMigrationStep {
private const TABLE_NAME = 'profile_config';
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$hasTable = $schema->hasTable(self::TABLE_NAME);
if (!$hasTable) {
$table = $schema->createTable(self::TABLE_NAME);
$table->addColumn('id', Types::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('user_id', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('config', Types::TEXT, [
'notnull' => true,
]);
$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['user_id'], 'user_id');
return $schema;
}
return null;
}
}

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.41333 7.19333C5.37333 9.08 6.92 10.62 8.80667 11.5867L10.2733 10.12C10.4533 9.94 10.72 9.88 10.9533 9.96C11.7 10.2067 12.5067 10.34 13.3333 10.34C13.7 10.34 14 10.64 14 11.0067V13.3333C14 13.7 13.7 14 13.3333 14C7.07333 14 2 8.92667 2 2.66667C2 2.3 2.3 2 2.66667 2H5C5.36667 2 5.66667 2.3 5.66667 2.66667C5.66667 3.5 5.8 4.3 6.04667 5.04667C6.12 5.28 6.06667 5.54 5.88 5.72667L4.41333 7.19333Z" fill="black"/></svg>

After

Width:  |  Height:  |  Size: 523 B

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 1C8.92826 1 9.8185 1.36875 10.4749 2.02513C11.1313 2.6815 11.5 3.57174 11.5 4.5C11.5 5.42826 11.1313 6.3185 10.4749 6.97487C9.8185 7.63125 8.92826 8 8 8C7.07174 8 6.1815 7.63125 5.52513 6.97487C4.86875 6.3185 4.5 5.42826 4.5 4.5C4.5 3.57174 4.86875 2.6815 5.52513 2.02513C6.1815 1.36875 7.07174 1 8 1V1ZM8 9.75C11.8675 9.75 15 11.3162 15 13.25V15H1V13.25C1 11.3162 4.1325 9.75 8 9.75Z" fill="black"/></svg>

After

Width:  |  Height:  |  Size: 514 B

@ -1,3 +1 @@
<svg enable-background="new 0 0 15 15" version="1.1" viewBox="0 0 15 15" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path d="m14.982 7c-0.246-3.744-3.238-6.737-6.982-6.983v-0.017h-1v0.017c-3.744 0.246-6.737 3.239-6.983 6.983h-0.017v1h0.017c0.246 3.744 3.239 6.736 6.983 6.982v0.018h1v-0.018c3.744-0.246 6.736-3.238 6.982-6.982h0.018v-1h-0.018zm-10.287-5.365c-0.483 0.642-0.884 1.447-1.176 2.365h-1.498c0.652-1.017 1.578-1.84 2.674-2.365zm-3.197 3.365h1.758c-0.134 0.632-0.219 1.303-0.246 2h-1.991c0.053-0.704 0.219-1.377 0.479-2zm-0.479 3h1.991c0.027 0.697 0.112 1.368 0.246 2h-1.758c-0.26-0.623-0.426-1.296-0.479-2zm1.002 3h1.497c0.292 0.918 0.693 1.723 1.177 2.365-1.096-0.525-2.022-1.347-2.674-2.365zm4.979 2.936c-1.028-0.275-1.913-1.379-2.45-2.936h2.45v2.936zm0-3.936h-2.731c-0.141-0.623-0.23-1.296-0.259-2h2.99v2zm0-3h-2.99c0.029-0.704 0.118-1.377 0.259-2h2.731v2zm0-3h-2.45c0.537-1.557 1.422-2.661 2.45-2.935v2.935zm5.979 0h-1.496c-0.293-0.918-0.693-1.723-1.178-2.365 1.095 0.525 2.022 1.348 2.674 2.365zm-4.979-2.935c1.027 0.274 1.913 1.378 2.45 2.935h-2.45v-2.935zm0 3.935h2.73c0.142 0.623 0.229 1.296 0.26 2h-2.99v-2zm0 3h2.99c-0.029 0.704-0.118 1.377-0.26 2h-2.73v-2zm0 5.936v-2.936h2.45c-0.537 1.557-1.423 2.661-2.45 2.936zm2.305-0.571c0.483-0.643 0.885-1.447 1.178-2.365h1.496c-0.652 1.018-1.579 1.84-2.674 2.365zm3.197-3.365h-1.758c0.134-0.632 0.219-1.303 0.246-2h1.99c-0.052 0.704-0.218 1.377-0.478 2zm-1.512-3c-0.027-0.697-0.112-1.368-0.246-2h1.758c0.26 0.623 0.426 1.296 0.479 2h-1.991z"/>
</svg>
<svg enable-background="new 0 0 15 15" version="1.1" viewBox="0 0 15 15" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="m14.982 7c-0.246-3.744-3.238-6.737-6.982-6.983v-0.017h-1v0.017c-3.744 0.246-6.737 3.239-6.983 6.983h-0.017v1h0.017c0.246 3.744 3.239 6.736 6.983 6.982v0.018h1v-0.018c3.744-0.246 6.736-3.238 6.982-6.982h0.018v-1h-0.018zm-10.287-5.365c-0.483 0.642-0.884 1.447-1.176 2.365h-1.498c0.652-1.017 1.578-1.84 2.674-2.365zm-3.197 3.365h1.758c-0.134 0.632-0.219 1.303-0.246 2h-1.991c0.053-0.704 0.219-1.377 0.479-2zm-0.479 3h1.991c0.027 0.697 0.112 1.368 0.246 2h-1.758c-0.26-0.623-0.426-1.296-0.479-2zm1.002 3h1.497c0.292 0.918 0.693 1.723 1.177 2.365-1.096-0.525-2.022-1.347-2.674-2.365zm4.979 2.936c-1.028-0.275-1.913-1.379-2.45-2.936h2.45v2.936zm0-3.936h-2.731c-0.141-0.623-0.23-1.296-0.259-2h2.99v2zm0-3h-2.99c0.029-0.704 0.118-1.377 0.259-2h2.731v2zm0-3h-2.45c0.537-1.557 1.422-2.661 2.45-2.935v2.935zm5.979 0h-1.496c-0.293-0.918-0.693-1.723-1.178-2.365 1.095 0.525 2.022 1.348 2.674 2.365zm-4.979-2.935c1.027 0.274 1.913 1.378 2.45 2.935h-2.45v-2.935zm0 3.935h2.73c0.142 0.623 0.229 1.296 0.26 2h-2.99v-2zm0 3h2.99c-0.029 0.704-0.118 1.377-0.26 2h-2.73v-2zm0 5.936v-2.936h2.45c-0.537 1.557-1.423 2.661-2.45 2.936zm2.305-0.571c0.483-0.643 0.885-1.447 1.178-2.365h1.496c-0.652 1.018-1.579 1.84-2.674 2.365zm3.197-3.365h-1.758c0.134-0.632 0.219-1.303 0.246-2h1.99c-0.052 0.704-0.218 1.377-0.478 2zm-1.512-3c-0.027-0.697-0.112-1.368-0.246-2h1.758c0.26 0.623 0.426 1.296 0.479 2h-1.991z"/></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.3194 5.30903C14.329 5.44903 14.329 5.58903 14.329 5.73032C14.329 10.0355 11.0516 15.0006 5.05871 15.0006V14.9981C3.28839 15.0006 1.55484 14.4935 0.0645161 13.5374C0.321935 13.5684 0.580645 13.5839 0.84 13.5845C2.3071 13.5858 3.73226 13.0935 4.88645 12.1871C3.49226 12.1606 2.26968 11.2516 1.84258 9.92452C2.33097 10.0187 2.83419 9.99935 3.31355 9.86839C1.79355 9.56129 0.7 8.22581 0.7 6.67484C0.7 6.66064 0.7 6.6471 0.7 6.63355C1.1529 6.88581 1.66 7.02581 2.17871 7.04129C0.747097 6.08451 0.305806 4.18 1.17032 2.69097C2.82452 4.72645 5.26516 5.96387 7.88516 6.09484C7.62258 4.96322 7.98129 3.77742 8.82774 2.98193C10.14 1.74839 12.2039 1.81161 13.4374 3.12322C14.1671 2.97935 14.8665 2.71161 15.5065 2.33226C15.2632 3.08645 14.7542 3.7271 14.0742 4.13419C14.72 4.05806 15.351 3.88516 15.9452 3.62129C15.5077 4.27677 14.9568 4.84774 14.3194 5.30903Z" fill="black"/></svg>

After

Width:  |  Height:  |  Size: 980 B

@ -33,6 +33,7 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
use OC\Core\Application;
/** @var Application $application */
@ -42,6 +43,7 @@ $application->registerRoutes($this, [
['name' => 'lost#email', 'url' => '/lostpassword/email', 'verb' => 'POST'],
['name' => 'lost#resetform', 'url' => '/lostpassword/reset/form/{token}/{userId}', 'verb' => 'GET'],
['name' => 'lost#setPassword', 'url' => '/lostpassword/set/{token}/{userId}', 'verb' => 'POST'],
['name' => 'ProfilePage#index', 'url' => '/u/{targetUserId}', 'verb' => 'GET'],
['name' => 'user#getDisplayNames', 'url' => '/displaynames', 'verb' => 'POST'],
['name' => 'avatar#getAvatar', 'url' => '/avatar/{userId}/{size}', 'verb' => 'GET'],
['name' => 'avatar#deleteAvatar', 'url' => '/avatar/', 'verb' => 'DELETE'],
@ -117,6 +119,8 @@ $application->registerRoutes($this, [
['root' => '/collaboration', 'name' => 'CollaborationResources#getCollectionsByResource', 'url' => '/resources/{resourceType}/{resourceId}', 'verb' => 'GET'],
['root' => '/collaboration', 'name' => 'CollaborationResources#createCollectionOnResource', 'url' => '/resources/{baseResourceType}/{baseResourceId}', 'verb' => 'POST'],
['root' => '/profile', 'name' => 'ProfileApi#setVisibility', 'url' => '/{targetUserId}', 'verb' => 'PUT'],
// Unified search
['root' => '/search', 'name' => 'UnifiedSearch#getProviders', 'url' => '/providers', 'verb' => 'GET'],
['root' => '/search', 'name' => 'UnifiedSearch#search', 'url' => '/providers/{providerId}/search', 'verb' => 'GET'],

@ -460,6 +460,8 @@ return array(
'OCP\\Preview\\IProvider' => $baseDir . '/lib/public/Preview/IProvider.php',
'OCP\\Preview\\IProviderV2' => $baseDir . '/lib/public/Preview/IProviderV2.php',
'OCP\\Preview\\IVersionedPreviewFile' => $baseDir . '/lib/public/Preview/IVersionedPreviewFile.php',
'OCP\\Profile\\ILinkAction' => $baseDir . '/lib/public/Profile/ILinkAction.php',
'OCP\\Profile\\ParameterDoesNotExistException' => $baseDir . '/lib/public/Profile/ParameterDoesNotExistException.php',
'OCP\\Remote\\Api\\IApiCollection' => $baseDir . '/lib/public/Remote/Api/IApiCollection.php',
'OCP\\Remote\\Api\\IApiFactory' => $baseDir . '/lib/public/Remote/Api/IApiFactory.php',
'OCP\\Remote\\Api\\ICapabilitiesApi' => $baseDir . '/lib/public/Remote/Api/ICapabilitiesApi.php',
@ -804,6 +806,7 @@ return array(
'OC\\Contacts\\ContactsMenu\\Entry' => $baseDir . '/lib/private/Contacts/ContactsMenu/Entry.php',
'OC\\Contacts\\ContactsMenu\\Manager' => $baseDir . '/lib/private/Contacts/ContactsMenu/Manager.php',
'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php',
'OC\\Contacts\\ContactsMenu\\Providers\\ProfileProvider' => $baseDir . '/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php',
'OC\\Core\\Application' => $baseDir . '/core/Application.php',
'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => $baseDir . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php',
'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => $baseDir . '/core/BackgroundJobs/CheckForUserCertificates.php',
@ -924,6 +927,8 @@ return array(
'OC\\Core\\Controller\\OCJSController' => $baseDir . '/core/Controller/OCJSController.php',
'OC\\Core\\Controller\\OCSController' => $baseDir . '/core/Controller/OCSController.php',
'OC\\Core\\Controller\\PreviewController' => $baseDir . '/core/Controller/PreviewController.php',
'OC\\Core\\Controller\\ProfileApiController' => $baseDir . '/core/Controller/ProfileApiController.php',
'OC\\Core\\Controller\\ProfilePageController' => $baseDir . '/core/Controller/ProfilePageController.php',
'OC\\Core\\Controller\\RecommendedAppsController' => $baseDir . '/core/Controller/RecommendedAppsController.php',
'OC\\Core\\Controller\\SearchController' => $baseDir . '/core/Controller/SearchController.php',
'OC\\Core\\Controller\\SetupController' => $baseDir . '/core/Controller/SetupController.php',
@ -940,6 +945,8 @@ return array(
'OC\\Core\\Data\\LoginFlowV2Tokens' => $baseDir . '/core/Data/LoginFlowV2Tokens.php',
'OC\\Core\\Db\\LoginFlowV2' => $baseDir . '/core/Db/LoginFlowV2.php',
'OC\\Core\\Db\\LoginFlowV2Mapper' => $baseDir . '/core/Db/LoginFlowV2Mapper.php',
'OC\\Core\\Db\\ProfileConfig' => $baseDir . '/core/Db/ProfileConfig.php',
'OC\\Core\\Db\\ProfileConfigMapper' => $baseDir . '/core/Db/ProfileConfigMapper.php',
'OC\\Core\\Exception\\LoginFlowV2NotFoundException' => $baseDir . '/core/Exception/LoginFlowV2NotFoundException.php',
'OC\\Core\\Exception\\ResetPasswordException' => $baseDir . '/core/Exception/ResetPasswordException.php',
'OC\\Core\\Middleware\\TwoFactorMiddleware' => $baseDir . '/core/Middleware/TwoFactorMiddleware.php',
@ -980,6 +987,7 @@ return array(
'OC\\Core\\Migrations\\Version22000Date20210216080825' => $baseDir . '/core/Migrations/Version22000Date20210216080825.php',
'OC\\Core\\Migrations\\Version23000Date20210721100600' => $baseDir . '/core/Migrations/Version23000Date20210721100600.php',
'OC\\Core\\Migrations\\Version23000Date20210906132259' => $baseDir . '/core/Migrations/Version23000Date20210906132259.php',
'OC\\Core\\Migrations\\Version23000Date20210930122352' => $baseDir . '/core/Migrations/Version23000Date20210930122352.php',
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php',
@ -1300,6 +1308,12 @@ return array(
'OC\\Preview\\WatcherConnector' => $baseDir . '/lib/private/Preview/WatcherConnector.php',
'OC\\Preview\\WebP' => $baseDir . '/lib/private/Preview/WebP.php',
'OC\\Preview\\XBitmap' => $baseDir . '/lib/private/Preview/XBitmap.php',
'OC\\Profile\\Actions\\EmailAction' => $baseDir . '/lib/private/Profile/Actions/EmailAction.php',
'OC\\Profile\\Actions\\PhoneAction' => $baseDir . '/lib/private/Profile/Actions/PhoneAction.php',
'OC\\Profile\\Actions\\TwitterAction' => $baseDir . '/lib/private/Profile/Actions/TwitterAction.php',
'OC\\Profile\\Actions\\WebsiteAction' => $baseDir . '/lib/private/Profile/Actions/WebsiteAction.php',
'OC\\Profile\\ProfileManager' => $baseDir . '/lib/private/Profile/ProfileManager.php',
'OC\\Profile\\TProfileHelper' => $baseDir . '/lib/private/Profile/TProfileHelper.php',
'OC\\RedisFactory' => $baseDir . '/lib/private/RedisFactory.php',
'OC\\Remote\\Api\\ApiBase' => $baseDir . '/lib/private/Remote/Api/ApiBase.php',
'OC\\Remote\\Api\\ApiCollection' => $baseDir . '/lib/private/Remote/Api/ApiCollection.php',

@ -489,6 +489,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\Preview\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Preview/IProvider.php',
'OCP\\Preview\\IProviderV2' => __DIR__ . '/../../..' . '/lib/public/Preview/IProviderV2.php',
'OCP\\Preview\\IVersionedPreviewFile' => __DIR__ . '/../../..' . '/lib/public/Preview/IVersionedPreviewFile.php',
'OCP\\Profile\\ILinkAction' => __DIR__ . '/../../..' . '/lib/public/Profile/ILinkAction.php',
'OCP\\Profile\\ParameterDoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/Profile/ParameterDoesNotExistException.php',
'OCP\\Remote\\Api\\IApiCollection' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IApiCollection.php',
'OCP\\Remote\\Api\\IApiFactory' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/IApiFactory.php',
'OCP\\Remote\\Api\\ICapabilitiesApi' => __DIR__ . '/../../..' . '/lib/public/Remote/Api/ICapabilitiesApi.php',
@ -833,6 +835,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Contacts\\ContactsMenu\\Entry' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Entry.php',
'OC\\Contacts\\ContactsMenu\\Manager' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Manager.php',
'OC\\Contacts\\ContactsMenu\\Providers\\EMailProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/EMailProvider.php',
'OC\\Contacts\\ContactsMenu\\Providers\\ProfileProvider' => __DIR__ . '/../../..' . '/lib/private/Contacts/ContactsMenu/Providers/ProfileProvider.php',
'OC\\Core\\Application' => __DIR__ . '/../../..' . '/core/Application.php',
'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php',
'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CheckForUserCertificates.php',
@ -953,6 +956,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Controller\\OCJSController' => __DIR__ . '/../../..' . '/core/Controller/OCJSController.php',
'OC\\Core\\Controller\\OCSController' => __DIR__ . '/../../..' . '/core/Controller/OCSController.php',
'OC\\Core\\Controller\\PreviewController' => __DIR__ . '/../../..' . '/core/Controller/PreviewController.php',
'OC\\Core\\Controller\\ProfileApiController' => __DIR__ . '/../../..' . '/core/Controller/ProfileApiController.php',
'OC\\Core\\Controller\\ProfilePageController' => __DIR__ . '/../../..' . '/core/Controller/ProfilePageController.php',
'OC\\Core\\Controller\\RecommendedAppsController' => __DIR__ . '/../../..' . '/core/Controller/RecommendedAppsController.php',
'OC\\Core\\Controller\\SearchController' => __DIR__ . '/../../..' . '/core/Controller/SearchController.php',
'OC\\Core\\Controller\\SetupController' => __DIR__ . '/../../..' . '/core/Controller/SetupController.php',
@ -969,6 +974,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Data\\LoginFlowV2Tokens' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Tokens.php',
'OC\\Core\\Db\\LoginFlowV2' => __DIR__ . '/../../..' . '/core/Db/LoginFlowV2.php',
'OC\\Core\\Db\\LoginFlowV2Mapper' => __DIR__ . '/../../..' . '/core/Db/LoginFlowV2Mapper.php',
'OC\\Core\\Db\\ProfileConfig' => __DIR__ . '/../../..' . '/core/Db/ProfileConfig.php',
'OC\\Core\\Db\\ProfileConfigMapper' => __DIR__ . '/../../..' . '/core/Db/ProfileConfigMapper.php',
'OC\\Core\\Exception\\LoginFlowV2NotFoundException' => __DIR__ . '/../../..' . '/core/Exception/LoginFlowV2NotFoundException.php',
'OC\\Core\\Exception\\ResetPasswordException' => __DIR__ . '/../../..' . '/core/Exception/ResetPasswordException.php',
'OC\\Core\\Middleware\\TwoFactorMiddleware' => __DIR__ . '/../../..' . '/core/Middleware/TwoFactorMiddleware.php',
@ -1009,6 +1016,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Migrations\\Version22000Date20210216080825' => __DIR__ . '/../../..' . '/core/Migrations/Version22000Date20210216080825.php',
'OC\\Core\\Migrations\\Version23000Date20210721100600' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20210721100600.php',
'OC\\Core\\Migrations\\Version23000Date20210906132259' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20210906132259.php',
'OC\\Core\\Migrations\\Version23000Date20210930122352' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20210930122352.php',
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php',
@ -1329,6 +1337,12 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Preview\\WatcherConnector' => __DIR__ . '/../../..' . '/lib/private/Preview/WatcherConnector.php',
'OC\\Preview\\WebP' => __DIR__ . '/../../..' . '/lib/private/Preview/WebP.php',
'OC\\Preview\\XBitmap' => __DIR__ . '/../../..' . '/lib/private/Preview/XBitmap.php',
'OC\\Profile\\Actions\\EmailAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/EmailAction.php',
'OC\\Profile\\Actions\\PhoneAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/PhoneAction.php',
'OC\\Profile\\Actions\\TwitterAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/TwitterAction.php',
'OC\\Profile\\Actions\\WebsiteAction' => __DIR__ . '/../../..' . '/lib/private/Profile/Actions/WebsiteAction.php',
'OC\\Profile\\ProfileManager' => __DIR__ . '/../../..' . '/lib/private/Profile/ProfileManager.php',
'OC\\Profile\\TProfileHelper' => __DIR__ . '/../../..' . '/lib/private/Profile/TProfileHelper.php',
'OC\\RedisFactory' => __DIR__ . '/../../..' . '/lib/private/RedisFactory.php',
'OC\\Remote\\Api\\ApiBase' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiBase.php',
'OC\\Remote\\Api\\ApiCollection' => __DIR__ . '/../../..' . '/lib/private/Remote/Api/ApiCollection.php',

@ -27,10 +27,14 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\AppFramework\Bootstrap;
use OC\Support\CrashReport\Registry;
use function class_exists;
use function class_implements;
use function in_array;
use OC_App;
use OC\Support\CrashReport\Registry;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\QueryException;
@ -39,9 +43,6 @@ use OCP\EventDispatcher\IEventDispatcher;
use OCP\IServerContainer;
use Psr\Log\LoggerInterface;
use Throwable;
use function class_exists;
use function class_implements;
use function in_array;
class Coordinator {
@ -66,11 +67,13 @@ class Coordinator {
/** @var string[] */
private $bootedApps = [];
public function __construct(IServerContainer $container,
Registry $registry,
IManager $dashboardManager,
IEventDispatcher $eventListener,
LoggerInterface $logger) {
public function __construct(
IServerContainer $container,
Registry $registry,
IManager $dashboardManager,
IEventDispatcher $eventListener,
LoggerInterface $logger
) {
$this->serverContainer = $container;
$this->registry = $registry;
$this->dashboardManager = $dashboardManager;

@ -26,9 +26,11 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\AppFramework\Bootstrap;
use Closure;
use function array_shift;
use OC\Support\CrashReport\Registry;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
@ -43,11 +45,11 @@ use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Template\ICustomTemplateProvider;
use OCP\Http\WellKnown\IHandler;
use OCP\Notification\INotifier;
use OCP\Profile\ILinkAction;
use OCP\Search\IProvider;
use OCP\Support\CrashReport\IReporter;
use Psr\Log\LoggerInterface;
use Throwable;
use function array_shift;
class RegistrationContext {
@ -60,6 +62,9 @@ class RegistrationContext {
/** @var ServiceRegistration<IWidget>[] */
private $dashboardPanels = [];
/** @var ServiceRegistration<ILinkAction>[] */
private $profileActions = [];
/** @var ServiceFactoryRegistration[] */
private $services = [];
@ -236,6 +241,13 @@ class RegistrationContext {
$class
);
}
public function registerProfileAction(string $actionClass): void {
$this->context->registerProfileAction(
$this->appId,
$actionClass
);
}
};
}
@ -315,6 +327,13 @@ class RegistrationContext {
$this->calendarProviders[] = new ServiceRegistration($appId, $class);
}
/**
* @psalm-param class-string<ILinkAction> $capability
*/
public function registerProfileAction(string $appId, string $actionClass): void {
$this->profileActions[] = new ServiceRegistration($appId, $actionClass);
}
/**
* @param App[] $apps
*/
@ -552,4 +571,11 @@ class RegistrationContext {
public function getCalendarProviders(): array {
return $this->calendarProviders;
}
/**
* @return ServiceRegistration<ILinkAction>[]
*/
public function getProfileActions(): array {
return $this->profileActions;
}
}

@ -24,11 +24,13 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Contacts\ContactsMenu;
use Exception;
use OC\App\AppManager;
use OC\Contacts\ContactsMenu\Providers\EMailProvider;
use OC\Contacts\ContactsMenu\Providers\ProfileProvider;
use OCP\AppFramework\QueryException;
use OCP\Contacts\ContactsMenu\IProvider;
use OCP\IServerContainer;
@ -67,7 +69,8 @@ class ActionProviderStore {
try {
$providers[] = $this->serverContainer->query($class);
} catch (QueryException $ex) {
$this->logger->error('Could not load contacts menu action provider ' . $class,
$this->logger->error(
'Could not load contacts menu action provider ' . $class,
[
'app' => 'core',
'exception' => $ex,
@ -85,6 +88,7 @@ class ActionProviderStore {
*/
private function getServerProviderClasses(): array {
return [
ProfileProvider::class,
EMailProvider::class,
];
}

@ -1,4 +1,5 @@
<?php
/**
* @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
* @copyright 2017 Lukas Reschke <lukas@statuscode.ch>
@ -27,18 +28,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Contacts\ContactsMenu;
use OC\KnownUser\KnownUserService;
use OCP\Accounts\IAccountManager;
use OCP\Contacts\ContactsMenu\IContactsStore;
use OCP\Contacts\ContactsMenu\IEntry;
use OCP\Contacts\IManager;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory as IL10NFactory;
class ContactsStore implements IContactsStore {
use \OC\Profile\TProfileHelper;
/** @var IAccountManager */
private $accountManager;
/** @var IManager */
private $contactsManager;
@ -49,22 +58,36 @@ class ContactsStore implements IContactsStore {
/** @var IUserManager */
private $userManager;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IGroupManager */
private $groupManager;
/** @var KnownUserService */
private $knownUserService;
public function __construct(IManager $contactsManager,
IConfig $config,
IUserManager $userManager,
IGroupManager $groupManager,
KnownUserService $knownUserService) {
/** @var IL10NFactory */
private $l10nFactory;
public function __construct(
IAccountManager $accountManager,
IManager $contactsManager,
IConfig $config,
IUserManager $userManager,
IURLGenerator $urlGenerator,
IGroupManager $groupManager,
KnownUserService $knownUserService,
IL10NFactory $l10nFactory
) {
$this->accountManager = $accountManager;
$this->contactsManager = $contactsManager;
$this->config = $config;
$this->userManager = $userManager;
$this->urlGenerator = $urlGenerator;
$this->groupManager = $groupManager;
$this->knownUserService = $knownUserService;
$this->l10nFactory = $l10nFactory;
}
/**
@ -116,9 +139,11 @@ class ContactsStore implements IContactsStore {
* @param string $filter
* @return Entry[] the filtered contacts
*/
private function filterContacts(IUser $self,
array $entries,
$filter) {
private function filterContacts(
IUser $self,
array $entries,
$filter
) {
$disallowEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes';
$restrictEnumerationGroup = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
$restrictEnumerationPhone = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
@ -302,6 +327,19 @@ class ContactsStore implements IContactsStore {
}
}
// Provide profile parameters for core/src/OC/contactsmenu/contact.handlebars template
if (isset($contact['UID']) && isset($contact['FN'])) {
$targetUserId = $contact['UID'];
$user = $this->userManager->get($targetUserId);
if (!empty($user)) {
$account = $this->accountManager->getAccount($user);
if ($this->isProfileEnabled($account)) {
$entry->setProfileTitle($this->l10nFactory->get('core')->t('View profile'));
$entry->setProfileUrl($this->urlGenerator->linkToRouteAbsolute('core.ProfilePage.index', ['targetUserId' => $targetUserId]));
}
}
}
// Attach all other properties to the entry too because some
// providers might make use of it.
$entry->setProperties($contact);

@ -24,6 +24,7 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Contacts\ContactsMenu;
use OCP\Contacts\ContactsMenu\IAction;
@ -43,6 +44,12 @@ class Entry implements IEntry {
/** @var string|null */
private $avatar;
/** @var string|null */
private $profileTitle;
/** @var string|null */
private $profileUrl;
/** @var IAction[] */
private $actions = [];
@ -98,6 +105,34 @@ class Entry implements IEntry {
return $this->avatar;
}
/**
* @param string $profileTitle
*/
public function setProfileTitle(string $profileTitle): void {
$this->profileTitle = $profileTitle;
}
/**
* @return string
*/
public function getProfileTitle(): ?string {
return $this->profileTitle;
}
/**
* @param string $profileUrl
*/
public function setProfileUrl(string $profileUrl): void {
$this->profileUrl = $profileUrl;
}
/**
* @return string
*/
public function getProfileUrl(): ?string {
return $this->profileUrl;
}
/**
* @param IAction $action
*/
@ -166,6 +201,8 @@ class Entry implements IEntry {
'actions' => $otherActions,
'lastMessage' => '',
'emailAddresses' => $this->getEMailAddresses(),
'profileTitle' => $this->profileTitle,
'profileUrl' => $this->profileUrl,
];
}
}

@ -0,0 +1,93 @@
<?php
/**
* @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Contacts\ContactsMenu\Providers;
use OCP\Accounts\IAccountManager;
use OCP\Contacts\ContactsMenu\IActionFactory;
use OCP\Contacts\ContactsMenu\IEntry;
use OCP\Contacts\ContactsMenu\IProvider;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\L10N\IFactory as IL10NFactory;
class ProfileProvider implements IProvider {
use \OC\Profile\TProfileHelper;
/** @var IAccountManager */
private $accountManager;
/** @var IActionFactory */
private $actionFactory;
/** @var IL10NFactory */
private $l10nFactory;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IUserManager */
private $userManager;
/**
* @param IAccountManager $accountManager
* @param IActionFactory $actionFactory
* @param IL10NFactory $l10nFactory
* @param IURLGenerator $urlGenerator
* @param IUserManager $userManager
*/
public function __construct(
IAccountManager $accountManager,
IActionFactory $actionFactory,
IL10NFactory $l10nFactory,
IURLGenerator $urlGenerator,
IUserManager $userManager
) {
$this->accountManager = $accountManager;
$this->actionFactory = $actionFactory;
$this->l10nFactory = $l10nFactory;
$this->urlGenerator = $urlGenerator;
$this->userManager = $userManager;
}
/**
* @param IEntry $entry
*/
public function process(IEntry $entry) {
$targetUserId = $entry->getProperty('UID');
$targetUser = $this->userManager->get($targetUserId);
if (!empty($targetUser)) {
$account = $this->accountManager->getAccount($targetUser);
if ($this->isProfileEnabled($account)) {
$iconUrl = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/profile.svg'));
$profileActionText = $this->l10nFactory->get('core')->t('View profile');
$profileUrl = $this->urlGenerator->linkToRouteAbsolute('core.ProfilePage.index', ['targetUserId' => $targetUserId]);
$action = $this->actionFactory->newLinkAction($iconUrl, $profileActionText, $profileUrl);
// Set highest priority (by descending order), other actions have the default priority 10 as defined in lib/private/Contacts/ContactsMenu/Actions/LinkAction.php
$action->setPriority(20);
$entry->addAction($action);
}
}
}
}

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Profile\Actions;
use OCP\Accounts\IAccountManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
class EmailAction implements ILinkAction {
/** @var string */
private $value;
/** @var IAccountManager */
private $accountManager;
/** @var IFactory */
private $l10nFactory;
/** @var IUrlGenerator */
private $urlGenerator;
public function __construct(
IAccountManager $accountManager,
IFactory $l10nFactory,
IURLGenerator $urlGenerator
) {
$this->accountManager = $accountManager;
$this->l10nFactory = $l10nFactory;
$this->urlGenerator = $urlGenerator;
}
public function preload(IUser $targetUser): void {
$account = $this->accountManager->getAccount($targetUser);
$this->value = $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue();
}
public function getAppId(): string {
return 'core';
}
public function getId(): string {
return IAccountManager::PROPERTY_EMAIL;
}
public function getDisplayId(): string {
return $this->l10nFactory->get('core')->t('Email');
}
public function getTitle(): string {
return $this->l10nFactory->get('core')->t('Mail %s', [$this->value]);
}
public function getPriority(): int {
return 20;
}
public function getIcon(): string {
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/mail.svg'));
}
public function getTarget(): ?string {
if (empty($this->value)) {
return null;
}
return 'mailto:' . $this->value;
}
}

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Profile\Actions;
use OCP\Accounts\IAccountManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
class PhoneAction implements ILinkAction {
/** @var string */
private $value;
/** @var IAccountManager */
private $accountManager;
/** @var IFactory */
private $l10nFactory;
/** @var IUrlGenerator */
private $urlGenerator;
public function __construct(
IAccountManager $accountManager,
IFactory $l10nFactory,
IURLGenerator $urlGenerator
) {
$this->accountManager = $accountManager;
$this->l10nFactory = $l10nFactory;
$this->urlGenerator = $urlGenerator;
}
public function preload(IUser $targetUser): void {
$account = $this->accountManager->getAccount($targetUser);
$this->value = $account->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
}
public function getAppId(): string {
return 'core';
}
public function getId(): string {
return IAccountManager::PROPERTY_PHONE;
}
public function getDisplayId(): string {
return $this->l10nFactory->get('core')->t('Phone');
}
public function getTitle(): string {
return $this->l10nFactory->get('core')->t('Call %s', [$this->value]);
}
public function getPriority(): int {
return 30;
}
public function getIcon(): string {
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/phone.svg'));
}
public function getTarget(): ?string {
if (empty($this->value)) {
return null;
}
return 'tel:' . $this->value;
}
}

@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Profile\Actions;
use function Safe\substr;
use OCP\Accounts\IAccountManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
class TwitterAction implements ILinkAction {
/** @var string */
private $value;
/** @var IAccountManager */
private $accountManager;
/** @var IFactory */
private $l10nFactory;
/** @var IUrlGenerator */
private $urlGenerator;
public function __construct(
IAccountManager $accountManager,
IFactory $l10nFactory,
IURLGenerator $urlGenerator
) {
$this->accountManager = $accountManager;
$this->l10nFactory = $l10nFactory;
$this->urlGenerator = $urlGenerator;
}
public function preload(IUser $targetUser): void {
$account = $this->accountManager->getAccount($targetUser);
$this->value = $account->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue();
}
public function getAppId(): string {
return 'core';
}
public function getId(): string {
return IAccountManager::PROPERTY_TWITTER;
}
public function getDisplayId(): string {
return $this->l10nFactory->get('core')->t('Twitter');
}
public function getTitle(): string {
$displayUsername = $this->value[0] === '@' ? $this->value : '@' . $this->value;
return $this->l10nFactory->get('core')->t('View %s on Twitter', [$displayUsername]);
}
public function getPriority(): int {
return 50;
}
public function getIcon(): string {
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/twitter.svg'));
}
public function getTarget(): ?string {
if (empty($this->value)) {
return null;
}
$username = $this->value[0] === '@' ? substr($this->value, 1) : $this->value;
return 'https://twitter.com/' . $username;
}
}

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Profile\Actions;
use OCP\Accounts\IAccountManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
class WebsiteAction implements ILinkAction {
/** @var string */
private $value;
/** @var IAccountManager */
private $accountManager;
/** @var IFactory */
private $l10nFactory;
/** @var IUrlGenerator */
private $urlGenerator;
public function __construct(
IAccountManager $accountManager,
IFactory $l10nFactory,
IURLGenerator $urlGenerator
) {
$this->accountManager = $accountManager;
$this->l10nFactory = $l10nFactory;
$this->urlGenerator = $urlGenerator;
}
public function preload(IUser $targetUser): void {
$account = $this->accountManager->getAccount($targetUser);
$this->value = $account->getProperty(IAccountManager::PROPERTY_WEBSITE)->getValue();
}
public function getAppId(): string {
return 'core';
}
public function getId(): string {
return IAccountManager::PROPERTY_WEBSITE;
}
public function getDisplayId(): string {
return $this->l10nFactory->get('core')->t('Website');
}
public function getTitle(): string {
return $this->l10nFactory->get('core')->t('Visit %s', [$this->value]);
}
public function getPriority(): int {
return 40;
}
public function getIcon(): string {
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/timezone.svg'));
}
public function getTarget(): ?string {
if (empty($this->value)) {
return null;
}
return $this->value;
}
}

@ -0,0 +1,333 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Profile;
use function Safe\usort;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\Core\Db\ProfileConfig;
use OC\Core\Db\ProfileConfigMapper;
use OC\KnownUser\KnownUserService;
use OC\Profile\Actions\EmailAction;
use OC\Profile\Actions\PhoneAction;
use OC\Profile\Actions\TwitterAction;
use OC\Profile\Actions\WebsiteAction;
use OCP\Accounts\IAccountManager;
use OCP\Accounts\PropertyDoesNotExistException;
use OCP\App\IAppManager;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IUser;
use OCP\L10N\IFactory;
use OCP\Profile\ILinkAction;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
/**
* @inheritDoc
*/
class ProfileManager {
/** @var IAccountManager */
private $accountManager;
/** @var IAppManager */
private $appManager;
/** @var ProfileConfigMapper */
private $configMapper;
/** @var ContainerInterface */
private $container;
/** @var KnownUserService */
private $knownUserService;
/** @var IFactory */
private $l10nFactory;
/** @var LoggerInterface */
private $logger;
/** @var Coordinator */
private $coordinator;
/** @var ILinkAction[] */
private $actions = [];
private const ACCOUNT_PROPERTY_ACTIONS = [
EmailAction::class,
PhoneAction::class,
WebsiteAction::class,
TwitterAction::class,
];
/**
* Array of account properties displayed on the profile
*/
private const PROFILE_PROPERTIES = [
IAccountManager::PROPERTY_ADDRESS,
IAccountManager::PROPERTY_BIOGRAPHY,
IAccountManager::PROPERTY_DISPLAYNAME,
IAccountManager::PROPERTY_HEADLINE,
IAccountManager::PROPERTY_ORGANISATION,
IAccountManager::PROPERTY_ROLE,
];
public function __construct(
IAccountManager $accountManager,
IAppManager $appManager,
ProfileConfigMapper $configMapper,
ContainerInterface $container,
KnownUserService $knownUserService,
IFactory $l10nFactory,
LoggerInterface $logger,
Coordinator $coordinator
) {
$this->accountManager = $accountManager;
$this->appManager = $appManager;
$this->configMapper = $configMapper;
$this->container = $container;
$this->knownUserService = $knownUserService;
$this->l10nFactory = $l10nFactory;
$this->logger = $logger;
$this->coordinator = $coordinator;
}
/**
* Register an action for the user
*/
private function registerAction(IUser $targetUser, ?IUser $visitingUser, ILinkAction $action): void {
$action->preload($targetUser);
if ($action->getTarget() === null) {
// Actions without a target are not registered
return;
}
if (isset($this->actions[$action->getId()])) {
$this->logger->error('Cannot register duplicate action: ' . $action->getId());
return;
}
if ($action->getAppId() !== 'core') {
if (!$this->appManager->isEnabledForUser($action->getAppId(), $targetUser)) {
$this->logger->notice('App: ' . $action->getAppId() . ' cannot register actions as it is not enabled for the user: ' . $targetUser->getUID());
return;
}
if ($visitingUser === null) {
$this->logger->notice('App: ' . $action->getAppId() . ' cannot register actions as it is not available to non logged in users');
return;
}
if (!$this->appManager->isEnabledForUser($action->getAppId(), $visitingUser)) {
$this->logger->notice('App: ' . $action->getAppId() . ' cannot register actions as it is not enabled for the visiting user: ' . $visitingUser->getUID());
return;
}
}
// Add action to associative array of actions
$this->actions[$action->getId()] = $action;
}
/**
* Return an array of registered profile actions for the user
*
* @return ILinkAction[]
*/
private function getActions(IUser $targetUser, ?IUser $visitingUser): array {
$context = $this->coordinator->getRegistrationContext();
if ($context === null) {
return [];
}
foreach (self::ACCOUNT_PROPERTY_ACTIONS as $actionClass) {
/** @var ILinkAction $provider */
$provider = $this->container->get($actionClass);
$this->registerAction($targetUser, $visitingUser, $provider);
}
foreach ($context->getProfileActions() as $registration) {
/** @var ILinkAction $provider */
$provider = $this->container->get($registration->getService());
$this->registerAction($targetUser, $visitingUser, $provider);
}
$actionsClone = $this->actions;
// Sort associative array into indexed array in ascending order of priority
usort($actionsClone, function (ILinkAction $a, ILinkAction $b) {
return $a->getPriority() === $b->getPriority() ? 0 : ($a->getPriority() < $b->getPriority() ? -1 : 1);
});
return $actionsClone;
}
/**
* Return whether the profile parameter is visible to the visiting user
*/
private function isParameterVisible(IUser $targetUser, ?IUser $visitingUser, string $paramId): bool {
try {
$account = $this->accountManager->getAccount($targetUser);
$scope = $account->getProperty($paramId)->getScope();
} catch (PropertyDoesNotExistException $e) {
// Allow the exception as not all profile parameters are account properties
}
$visibility = $this->getProfileConfig($targetUser, $visitingUser)[$paramId]['visibility'];
// Handle profile visibility and account property scope
switch ($visibility) {
case ProfileConfig::VISIBILITY_HIDE:
return false;
case ProfileConfig::VISIBILITY_SHOW_USERS_ONLY:
if (!empty($scope)) {
switch ($scope) {
case IAccountManager::SCOPE_PRIVATE:
return $visitingUser !== null && $this->knownUserService->isKnownToUser($targetUser->getUID(), $visitingUser->getUID());
case IAccountManager::SCOPE_LOCAL:
case IAccountManager::SCOPE_FEDERATED:
case IAccountManager::SCOPE_PUBLISHED:
return $visitingUser !== null;
default:
return false;
}
}
return $visitingUser !== null;
case ProfileConfig::VISIBILITY_SHOW:
if (!empty($scope)) {
switch ($scope) {
case IAccountManager::SCOPE_PRIVATE:
return $visitingUser !== null && $this->knownUserService->isKnownToUser($targetUser->getUID(), $visitingUser->getUID());
case IAccountManager::SCOPE_LOCAL:
case IAccountManager::SCOPE_FEDERATED:
case IAccountManager::SCOPE_PUBLISHED:
return true;
default:
return false;
}
}
return true;
default:
return false;
}
}
/**
* @inheritDoc
*/
public function getProfileParams(IUser $targetUser, ?IUser $visitingUser): array {
$account = $this->accountManager->getAccount($targetUser);
// Initialize associative array of profile parameters
$profileParameters = [
'userId' => $account->getUser()->getUID(),
];
// Add account properties
foreach (self::PROFILE_PROPERTIES as $property) {
$profileParameters[$property] =
$this->isParameterVisible($targetUser, $visitingUser, $property)
// Explicitly set to null when value is empty string
? ($account->getProperty($property)->getValue() ?: null)
: null;
}
// Add avatar visibility
$profileParameters['isUserAvatarVisible'] = $this->isParameterVisible($targetUser, $visitingUser, IAccountManager::PROPERTY_AVATAR);
// Add actions
$profileParameters['actions'] = array_map(
function (ILinkAction $action) {
return [
'id' => $action->getId(),
'icon' => $action->getIcon(),
'title' => $action->getTitle(),
'target' => $action->getTarget(),
];
},
// This is needed to reindex the array after filtering
array_values(
array_filter(
$this->getActions($targetUser, $visitingUser),
function (ILinkAction $action) use ($targetUser, $visitingUser) {
return $this->isParameterVisible($targetUser, $visitingUser, $action->getId());
}
),
)
);
return $profileParameters;
}
/**
* @inheritDoc
*/
public function getProfileConfig(IUser $targetUser, ?IUser $visitingUser): array {
try {
$configArray = $this->configMapper->getArray($targetUser->getUID());
} catch (DoesNotExistException $e) {
$config = new ProfileConfig();
$config->setUserId($targetUser->getUID());
// Map of account properties to display IDs
$propertyDisplayMap = [
IAccountManager::PROPERTY_ADDRESS => $this->l10nFactory->get('core')->t('Address'),
IAccountManager::PROPERTY_AVATAR => $this->l10nFactory->get('core')->t('Avatar'),
IAccountManager::PROPERTY_BIOGRAPHY => $this->l10nFactory->get('core')->t('About'),
IAccountManager::PROPERTY_DISPLAYNAME => $this->l10nFactory->get('core')->t('Full name'),
IAccountManager::PROPERTY_HEADLINE => $this->l10nFactory->get('core')->t('Headline'),
IAccountManager::PROPERTY_ORGANISATION => $this->l10nFactory->get('core')->t('Organisation'),
IAccountManager::PROPERTY_ROLE => $this->l10nFactory->get('core')->t('Role'),
IAccountManager::PROPERTY_EMAIL => $this->l10nFactory->get('core')->t('Email'),
IAccountManager::PROPERTY_PHONE => $this->l10nFactory->get('core')->t('Phone'),
IAccountManager::PROPERTY_TWITTER => $this->l10nFactory->get('core')->t('Twitter'),
IAccountManager::PROPERTY_WEBSITE => $this->l10nFactory->get('core')->t('Website'),
];
// Contruct the default config for account properties
$propertiesConfig = [];
foreach ($propertyDisplayMap as $property => $displayId) {
$propertiesConfig[$property] = [
'displayId' => $displayId,
'visibility' => ProfileConfig::DEFAULT_PROPERTY_VISIBILITY[$property] ?: ProfileConfig::DEFAULT_VISIBILITY,
];
}
// Contruct the default config for actions
$actionsConfig = [];
/** @var ILinkAction $action */
foreach ($this->getActions($targetUser, $visitingUser) as $action) {
$actionsConfig[$action->getId()] = [
'displayId' => $action->getDisplayId(),
'visibility' => ProfileConfig::DEFAULT_VISIBILITY,
];
}
// Set the default config
$config->setConfigArray(array_merge($propertiesConfig, $actionsConfig));
$this->configMapper->insert($config);
$configArray = $config->getConfigArray();
}
return $configArray;
}
}

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Profile;
use OCP\Accounts\IAccount;
use OCP\Accounts\IAccountManager;
trait TProfileHelper {
/**
* Returns whether the profile is enabled for the account
*
* @since 23.0.0
*/
protected function isProfileEnabled(IAccount $account): ?bool {
return filter_var(
$account->getProperty(IAccountManager::PROPERTY_PROFILE_ENABLED)->getValue(),
FILTER_VALIDATE_BOOLEAN,
FILTER_NULL_ON_FAILURE,
);
}
}

@ -425,6 +425,9 @@ class Setup {
//and we are done
$config->setSystemValue('installed', true);
$bootstrapCoordinator = \OC::$server->query(\OC\AppFramework\Bootstrap\Coordinator::class);
$bootstrapCoordinator->runInitialRegistration();
// Create a session token for the newly created user
// The token provider requires a working db, so it's not injected on setup
/* @var $userSession User\Session */

@ -32,6 +32,7 @@ namespace OC\Template;
use OC\AppConfig;
use OC\Files\AppData\Factory;
use OC\Memcache\NullCache;
use OCA\Theming\ThemingDefaults;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
@ -62,7 +63,7 @@ class SCSSCacher {
/** @var IConfig */
protected $config;
/** @var \OC_Defaults */
/** @var ThemingDefaults */
private $defaults;
/** @var string */
@ -96,7 +97,7 @@ class SCSSCacher {
* @param Factory $appDataFactory
* @param IURLGenerator $urlGenerator
* @param IConfig $config
* @param \OC_Defaults $defaults
* @param ThemingDefaults $defaults
* @param string $serverRoot
* @param ICacheFactory $cacheFactory
* @param IconsCacher $iconsCacher
@ -106,7 +107,7 @@ class SCSSCacher {
Factory $appDataFactory,
IURLGenerator $urlGenerator,
IConfig $config,
\OC_Defaults $defaults,
ThemingDefaults $defaults,
$serverRoot,
ICacheFactory $cacheFactory,
IconsCacher $iconsCacher,
@ -406,7 +407,7 @@ class SCSSCacher {
}
/**
* @return string SCSS code for variables from OC_Defaults
* @return string SCSS code for variables from ThemingDefaults
*/
private function getInjectedVariables(string $cache = ''): string {
if ($this->injectedVariables !== null) {

@ -26,6 +26,7 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCP\Accounts;
use OCP\IUser;
@ -96,6 +97,31 @@ interface IAccountManager {
public const PROPERTY_ADDRESS = 'address';
public const PROPERTY_TWITTER = 'twitter';
/**
* @since 23.0.0
*/
public const PROPERTY_ORGANISATION = 'organisation';
/**
* @since 23.0.0
*/
public const PROPERTY_ROLE = 'role';
/**
* @since 23.0.0
*/
public const PROPERTY_HEADLINE = 'headline';
/**
* @since 23.0.0
*/
public const PROPERTY_BIOGRAPHY = 'biography';
/**
* @since 23.0.0
*/
public const PROPERTY_PROFILE_ENABLED = 'profile_enabled';
public const COLLECTION_EMAIL = 'additional_mail';
public const NOT_VERIFIED = '0';

@ -26,6 +26,7 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCP\AppFramework\Bootstrap;
use OCP\AppFramework\IAppContainer;
@ -74,6 +75,7 @@ interface IRegistrationContext {
* @since 20.0.0
*/
public function registerDashboardWidget(string $widgetClass): void;
/**
* Register a service
*
@ -237,4 +239,15 @@ interface IRegistrationContext {
* @since 23.0.0
*/
public function registerCalendarProvider(string $class): void;
/**
* Register an implementation of \OCP\Profile\ILinkAction that
* will handle the implementation of a profile action
*
* @param string $actionClass
* @psalm-param class-string<\OCP\Profile\ILinkAction> $actionClass
* @return void
* @since 23.0.0
*/
public function registerProfileAction(string $actionClass): void;
}

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCP\Profile;
use OCP\IUser;
/**
* @since 23.0.0
*/
interface ILinkAction {
/**
* Preload the user specific value required by the action
*
* e.g. the email is loaded for the email action and the userId for the Talk action
*
* @since 23.0.0
*/
public function preload(IUser $targetUser): void;
/**
* Returns the app ID of the action
*
* e.g. 'spreed'
*
* @since 23.0.0
*/
public function getAppId(): string;
/**
* Returns the unique ID of the action
*
* *For account properties this is the constant defined in lib/public/Accounts/IAccountManager.php*
*
* e.g. 'email'
*
* @since 23.0.0
*/
public function getId(): string;
/**
* Returns the translated unique display ID of the action
*
* Should be something short and descriptive of the action
* as this is seen by the end-user when configuring actions
*
* e.g. 'Email'
*
* @since 23.0.0
*/
public function getDisplayId(): string;
/**
* Returns the translated title
*
* e.g. 'Mail user@domain.com'
*
* Use the L10N service to translate it
*
* @since 23.0.0
*/
public function getTitle(): string;
/**
* Returns the priority
*
* *Actions are sorted in ascending order*
*
* e.g. 60
*
* @since 23.0.0
*/
public function getPriority(): int;
/**
* Returns the URL link to the 16*16 SVG icon
*
* @since 23.0.0
*/
public function getIcon(): string;
/**
* Returns the target of the action,
* if null is returned the action won't be registered
*
* e.g. 'mailto:user@domain.com'
*
* @since 23.0.0
*/
public function getTarget(): ?string;
}

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* @copyright 2021 Christopher Ng <chrng8@gmail.com>
*
* @author Christopher Ng <chrng8@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCP\Profile;
/**
* @since 23.0.0
*/
class ParameterDoesNotExistException extends \Exception {
/**
* @since 23.0.0
*/
public function __construct($parameter) {
parent::__construct("Parameter $parameter does not exist.");
}
}

@ -5,7 +5,7 @@ Feature: header
Given I am logged in as the admin
When I open the Settings menu
Then I see that the Settings menu is shown
And I see that the Settings menu has only 6 items
And I see that the Settings menu has only 7 items
And I see that the "Set status" item in the Settings menu is shown
And I see that the "Settings" item in the Settings menu is shown
And I see that the "Apps" item in the Settings menu is shown
@ -17,7 +17,7 @@ Feature: header
Given I am logged in
When I open the Settings menu
Then I see that the Settings menu is shown
And I see that the Settings menu has only 4 items
And I see that the Settings menu has only 5 items
And I see that the "Set status" item in the Settings menu is shown
And I see that the "Settings" item in the Settings menu is shown
And I see that the "Help" item in the Settings menu is shown

@ -27,6 +27,7 @@ namespace Tests\Contacts\ContactsMenu;
use OC\App\AppManager;
use OC\Contacts\ContactsMenu\ActionProviderStore;
use OC\Contacts\ContactsMenu\Providers\EMailProvider;
use OC\Contacts\ContactsMenu\Providers\ProfileProvider;
use OCP\App\IAppManager;
use OCP\AppFramework\QueryException;
use OCP\Contacts\ContactsMenu\IProvider;
@ -61,8 +62,9 @@ class ActionProviderStoreTest extends TestCase {
public function testGetProviders() {
$user = $this->createMock(IUser::class);
$provider1 = $this->createMock(EMailProvider::class);
$provider2 = $this->createMock(IProvider::class);
$provider1 = $this->createMock(ProfileProvider::class);
$provider2 = $this->createMock(EMailProvider::class);
$provider3 = $this->createMock(IProvider::class);
$this->appManager->expects($this->once())
->method('getEnabledAppsForUser')
@ -76,22 +78,25 @@ class ActionProviderStoreTest extends TestCase {
'OCA\Contacts\Provider1',
],
]);
$this->serverContainer->expects($this->exactly(2))
$this->serverContainer->expects($this->exactly(3))
->method('query')
->willReturnMap([
[EMailProvider::class, true, $provider1],
['OCA\Contacts\Provider1', true, $provider2]
[ProfileProvider::class, true, $provider1],
[EMailProvider::class, true, $provider2],
['OCA\Contacts\Provider1', true, $provider3]
]);
$providers = $this->actionProviderStore->getProviders($user);
$this->assertCount(2, $providers);
$this->assertInstanceOf(EMailProvider::class, $providers[0]);
$this->assertCount(3, $providers);
$this->assertInstanceOf(ProfileProvider::class, $providers[0]);
$this->assertInstanceOf(EMailProvider::class, $providers[1]);
}
public function testGetProvidersOfAppWithIncompleInfo() {
$user = $this->createMock(IUser::class);
$provider1 = $this->createMock(EMailProvider::class);
$provider1 = $this->createMock(ProfileProvider::class);
$provider2 = $this->createMock(EMailProvider::class);
$this->appManager->expects($this->once())
->method('getEnabledAppsForUser')
@ -101,16 +106,18 @@ class ActionProviderStoreTest extends TestCase {
->method('getAppInfo')
->with('contacts')
->willReturn([/* Empty info.xml */]);
$this->serverContainer->expects($this->once())
$this->serverContainer->expects($this->exactly(2))
->method('query')
->willReturnMap([
[EMailProvider::class, true, $provider1],
[ProfileProvider::class, true, $provider1],
[EMailProvider::class, true, $provider2],
]);
$providers = $this->actionProviderStore->getProviders($user);
$this->assertCount(1, $providers);
$this->assertInstanceOf(EMailProvider::class, $providers[0]);
$this->assertCount(2, $providers);
$this->assertInstanceOf(ProfileProvider::class, $providers[0]);
$this->assertInstanceOf(EMailProvider::class, $providers[1]);
}

@ -27,42 +27,57 @@ namespace Tests\Contacts\ContactsMenu;
use OC\Contacts\ContactsMenu\ContactsStore;
use OC\KnownUser\KnownUserService;
use OCP\Accounts\IAccountManager;
use OCP\Contacts\IManager;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory as IL10NFactory;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class ContactsStoreTest extends TestCase {
/** @var IAccountManager */
private $accountManager;
/** @var ContactsStore */
private $contactsStore;
/** @var IManager|\PHPUnit\Framework\MockObject\MockObject */
private $contactsManager;
/** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
private $userManager;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */
private $groupManager;
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
private $config;
/** @var KnownUserService|MockObject */
private $knownUserService;
/** @var IL10NFactory */
private $l10nFactory;
protected function setUp(): void {
parent::setUp();
$this->accountManager = $this->createMock(IAccountManager::class);
$this->contactsManager = $this->createMock(IManager::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->config = $this->createMock(IConfig::class);
$this->knownUserService = $this->createMock(KnownUserService::class);
$this->l10nFactory = $this->createMock(IL10NFactory::class);
$this->contactsStore = new ContactsStore(
$this->accountManager,
$this->contactsManager,
$this->config,
$this->userManager,
$this->urlGenerator,
$this->groupManager,
$this->knownUserService
$this->knownUserService,
$this->l10nFactory
);
}

@ -102,7 +102,9 @@ class EntryTest extends TestCase {
'actions' => [],
'lastMessage' => '',
'avatar' => null,
'emailAddresses' => ['user@example.com']
'emailAddresses' => ['user@example.com'],
'profileTitle' => null,
'profileUrl' => null,
];
$this->entry->setId(123);

@ -30,7 +30,7 @@
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
$OC_Version = [23, 0, 0, 2];
$OC_Version = [23, 0, 0, 3];
// The human readable string
$OC_VersionString = '23.0.0 alpha';

Loading…
Cancel
Save