diff --git a/composer.json b/composer.json
index c0da283ceb..69335e3a65 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,7 @@
"guzzlehttp/guzzle": "^5.3",
"guzzlehttp/ringphp": "^1.1",
"platformsh/console-form": ">=0.0.37 <2.0",
- "platformsh/client": ">=0.78.1 <2.0",
+ "platformsh/client": ">=0.79.0 <2.0",
"symfony/console": "^3.0 >=3.2",
"symfony/yaml": "^3.0 || ^2.6",
"symfony/finder": "^3.0",
diff --git a/composer.lock b/composer.lock
index 2f67edbbe6..3d7498e569 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "a7baeaf0a600d06a2099b5c73997a253",
+ "content-hash": "363a27ed00bf2e8a635fedb1e7fedc62",
"packages": [
{
"name": "cocur/slugify",
@@ -918,16 +918,16 @@
},
{
"name": "platformsh/client",
- "version": "0.78.1",
+ "version": "0.79.0",
"source": {
"type": "git",
"url": "https://github.com/platformsh/platformsh-client-php.git",
- "reference": "4aac948237b97fabc199c8fe040599cddd96af49"
+ "reference": "13d9cf4cc76067ea8cb9e08660ad7bcf5004806e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/4aac948237b97fabc199c8fe040599cddd96af49",
- "reference": "4aac948237b97fabc199c8fe040599cddd96af49",
+ "url": "https://api.github.com/repos/platformsh/platformsh-client-php/zipball/13d9cf4cc76067ea8cb9e08660ad7bcf5004806e",
+ "reference": "13d9cf4cc76067ea8cb9e08660ad7bcf5004806e",
"shasum": ""
},
"require": {
@@ -959,9 +959,9 @@
"description": "Platform.sh API client",
"support": {
"issues": "https://github.com/platformsh/platformsh-client-php/issues",
- "source": "https://github.com/platformsh/platformsh-client-php/tree/0.78.1"
+ "source": "https://github.com/platformsh/platformsh-client-php/tree/0.79.0"
},
- "time": "2023-12-12T16:08:35+00:00"
+ "time": "2023-12-18T12:27:13+00:00"
},
{
"name": "platformsh/console-form",
diff --git a/src/Command/User/UserAddCommand.php b/src/Command/User/UserAddCommand.php
index c35f2ad8c4..85aae2ea6c 100644
--- a/src/Command/User/UserAddCommand.php
+++ b/src/Command/User/UserAddCommand.php
@@ -1,14 +1,15 @@
getArgument('email');
if (!$email) {
$update = stripos($input->getFirstArgument(), ':u');
- if ($update && $input->isInteractive()) {
- $choices = [];
- foreach ($this->api()->getProjectAccesses($project) as $access) {
- $account = $this->api()->getAccount($access);
- $choices[$account['email']] = $this->getUserLabel($access);
- }
- $email = $questionHelper->choose($choices, 'Enter a number to choose a user to update:');
+ if (!$input->isInteractive()) {
+ throw new InvalidArgumentException('An email address is required (in non-interactive mode).');
+ } elseif ($update) {
+ $email = $questionHelper->choose($this->listUsers($project), 'Enter a number to choose a user to update:');
} else {
$question = new Question("Enter the user's email address: ");
$question->setValidator(function ($answer) {
@@ -140,41 +138,59 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
$this->validateEmail($email);
- // Check the user's existing role on the project.
- $existingProjectAccess = $this->api()->loadProjectAccessByEmail($project, $email);
$existingTypeRoles = [];
- if ($existingProjectAccess) {
- // Exit if the user is the owner already.
- if ($existingProjectAccess->id === $project->owner) {
- if ($hasOutput) {
- $this->stdErr->writeln('');
- }
-
- $this->stdErr->writeln(sprintf('The user %s is the owner of %s.', $this->getUserLabel($existingProjectAccess), $this->api()->getProjectLabel($project)));
- if ($specifiedProjectRole || $specifiedTypeRoles) {
- $this->stdErr->writeln('');
- $this->stdErr->writeln("The project owner's role(s) cannot be changed.");
+ $existingProjectRole = null;
+ $existingUserLabel = null;
+ $existingUserId = null;
+
+ /**
+ * @var ProjectUserAccess|ProjectAccess|null $selection
+ */
+ $selection = $this->loadProjectUserByEmail($project, $email);
+ if ($selection instanceof ProjectUserAccess) {
+ $existingUserId = $selection->user_id;
+ $existingProjectRole = $selection->getProjectRole();
+ $existingTypeRoles = $selection->getEnvironmentTypeRoles();
+ $userInfo = $selection->getUserInfo();
+ $existingUserLabel = sprintf('%s (%s)', trim($userInfo->first_name . ' ' . $userInfo->last_name), $userInfo->email);
+ } elseif ($selection instanceof ProjectAccess) {
+ $existingUserId = $selection->id;
+ $existingProjectRole = $selection->role;
+ $existingUserLabel = $this->getUserLabel($selection);
+ $existingTypeRoles = $this->getTypeRoles($selection, $environmentTypes);
+ }
+
+ if ($existingUserId !== null) {
+ $this->debug(sprintf('The user %s already exists on the project (user ID: %s)', $email, $existingUserId));
+ }
+
+ // Exit if the user is the owner already.
+ if ($existingUserId !== null && $existingUserId === $project->owner) {
+ if ($hasOutput) {
+ $this->stdErr->writeln('');
+ }
- return 1;
- }
+ $this->stdErr->writeln(sprintf('The user %s is the owner of %s.', $existingUserLabel, $this->api()->getProjectLabel($project)));
+ if ($specifiedProjectRole || $specifiedTypeRoles) {
+ $this->stdErr->writeln('');
+ $this->stdErr->writeln("The project owner's role(s) cannot be changed.");
- return 0;
+ return 1;
}
- // Check the user's existing role(s) on the project's environments and types.
- $existingTypeRoles = $this->getTypeRoles($existingProjectAccess, $environmentTypes);
+ return 0;
}
// If the user already exists, print a summary of their roles on the
// project and environments.
- if ($existingProjectAccess) {
+ if ($existingUserId !== null) {
if ($hasOutput) {
$this->stdErr->writeln('');
}
- $this->stdErr->writeln(sprintf('Current role(s) of %s on %s:', $this->getUserLabel($existingProjectAccess), $this->api()->getProjectLabel($project)));
- $this->stdErr->writeln(sprintf(' Project role: %s', $existingProjectAccess->role));
- if ($existingProjectAccess->role !== ProjectAccess::ROLE_ADMIN) {
+ $this->stdErr->writeln(sprintf('Current role(s) of %s on %s:', $existingUserLabel, $this->api()->getProjectLabel($project)));
+ $this->stdErr->writeln(sprintf(' Project role: %s', $existingProjectRole));
+ if ($existingProjectRole !== ProjectAccess::ROLE_ADMIN) {
foreach ($environmentTypes as $type) {
$role = isset($existingTypeRoles[$type->id]) ? $existingTypeRoles[$type->id] : '[none]';
$this->stdErr->writeln(sprintf(' Role on environment type %s: %s', $type->id, $role));
@@ -184,7 +200,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
// Resolve or merge the project role.
- $desiredProjectRole = $specifiedProjectRole ?: ($existingProjectAccess ? $existingProjectAccess->role : ProjectAccess::ROLE_VIEWER);
+ $desiredProjectRole = $specifiedProjectRole ?: ($existingProjectRole ?: ProjectAccess::ROLE_VIEWER);
$provideProjectForm = !$input->getOption('role') && $input->isInteractive();
if ($provideProjectForm) {
if ($hasOutput) {
@@ -219,9 +235,9 @@ protected function execute(InputInterface $input, OutputInterface $output)
// Build a list of the changes that are going to be made.
$changesText = [];
- if ($existingProjectAccess) {
- if ($existingProjectAccess->role !== $desiredProjectRole) {
- $changesText[] = sprintf('Project role: %s -> %s', $existingProjectAccess->role, $desiredProjectRole);
+ if ($existingUserId !== null) {
+ if ($existingProjectRole !== $desiredProjectRole) {
+ $changesText[] = sprintf('Project role: %s -> %s', $existingProjectRole, $desiredProjectRole);
}
} else {
$changesText[] = sprintf('Project role: %s', $desiredProjectRole);
@@ -271,12 +287,12 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->stdErr->writeln('No environment types selected.');
$this->stdErr->writeln('A non-admin user must be added to at least one environment type.');
- if ($existingProjectAccess) {
+ if ($existingUserId !== null) {
$this->stdErr->writeln('');
$this->stdErr->writeln(sprintf(
'To delete the user, run: %s user:delete %s',
$this->config()->get('application.executable'),
- $this->api()->getAccount($existingProjectAccess)['email']
+ OsUtil::escapeShellArg($email)
));
}
@@ -290,7 +306,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
// Print a summary of the changes that are about to be made.
- if ($existingProjectAccess) {
+ if ($existingUserId !== null) {
$this->stdErr->writeln('Summary of changes:');
} else {
$this->stdErr->writeln(sprintf('Adding the user %s to %s:', $email, $this->api()->getProjectLabel($project)));
@@ -301,7 +317,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->stdErr->writeln('');
// Ask for confirmation.
- if ($existingProjectAccess) {
+ if ($existingUserId !== null) {
if (!$questionHelper->confirm('Are you sure you want to make these change(s)?')) {
return 1;
}
@@ -317,7 +333,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$this->stdErr->writeln('');
// If the user does not already exist on the project, then use the Invitations API.
- if (!$existingProjectAccess) {
+ if ($existingUserId === null) {
$this->stdErr->writeln('Inviting the user to the project...');
$permissions = [];
foreach ($desiredTypeRoles as $type => $role) {
@@ -340,56 +356,68 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 0;
}
- // Make the desired changes at the project level.
- if ($existingProjectAccess->role !== $desiredProjectRole) {
- $this->stdErr->writeln("Setting the user's project role to: $desiredProjectRole");
- $result = $existingProjectAccess->update(['role' => $desiredProjectRole]);
- $activities = $result->getActivities();
- $userId = $existingProjectAccess->id;
- } else {
- $userId = $existingProjectAccess->id;
- $activities = [];
- }
-
- // Make the desired changes at the environment type level.
- if ($desiredProjectRole !== ProjectAccess::ROLE_ADMIN) {
- foreach ($typeChanges as $typeId => $role) {
- $type = $project->getEnvironmentType($typeId);
- if (!$type) {
- $this->stdErr->writeln('Environment type not found: ' . $typeId . '');
- continue;
- }
- $access = $type->getUser($userId);
- if ($role === 'none') {
- if ($access) {
- $this->stdErr->writeln("Removing the user from the environment type $typeId");
- $result = $access->delete();
- } else {
+ $activities = [];
+ if ($selection instanceof ProjectUserAccess) {
+ $permissions = [$desiredProjectRole];
+ foreach ($desiredTypeRoles as $typeId => $role) {
+ $permissions[] = sprintf('%s:%s', $typeId, $role);
+ }
+ if ($permissions != $selection->permissions) {
+ $this->stdErr->writeln("Updating the user's project access");
+ $this->debug('New permissions: ' . implode(', ', $permissions));
+ $selection->update(['permissions' => $permissions]);
+ } else {
+ $this->stdErr->writeln('No changes to make');
+ $this->debug('Permissions match: ' . implode(', ', $permissions));
+ }
+ } elseif ($selection instanceof ProjectAccess) {
+ // Make the desired changes at the project level.
+ if ($existingProjectRole !== $desiredProjectRole) {
+ $this->stdErr->writeln("Setting the user's project role to: $desiredProjectRole");
+ $result = $selection->update(['role' => $desiredProjectRole]);
+ $activities = $result->getActivities();
+ }
+
+ // Make the desired changes at the environment type level.
+ if ($desiredProjectRole !== ProjectAccess::ROLE_ADMIN) {
+ foreach ($typeChanges as $typeId => $role) {
+ $type = $project->getEnvironmentType($typeId);
+ if (!$type) {
+ $this->stdErr->writeln('Environment type not found: ' . $typeId . '');
continue;
}
- } elseif ($access) {
- if ($access->role === $role) {
- continue;
+ $access = $type->getUser($existingUserId);
+ if ($role === 'none') {
+ if ($access) {
+ $this->stdErr->writeln("Removing the user from the environment type $typeId");
+ $result = $access->delete();
+ } else {
+ continue;
+ }
+ } elseif ($access) {
+ if ($access->role === $role) {
+ continue;
+ }
+ $this->stdErr->writeln("Setting the user's role on the environment type $typeId to: $role");
+ $result = $access->update(['role' => $role]);
+ } else {
+ $this->stdErr->writeln("Adding the user to the environment type: $typeId");
+ $result = $type->addUser($existingUserId, $role);
}
- $this->stdErr->writeln("Setting the user's role on the environment type $typeId to: $role");
- $result = $access->update(['role' => $role]);
- } else {
- $this->stdErr->writeln("Adding the user to the environment type: $typeId");
- $result = $type->addUser($userId, $role);
+ $activities = array_merge($activities, $result->getActivities());
}
- $activities = array_merge($activities, $result->getActivities());
}
}
// Wait for activities to complete.
- if (!$activities) {
- $this->redeployWarning();
- } elseif ($this->shouldWait($input)) {
+ if ($activities && $this->shouldWait($input)) {
/** @var \Platformsh\Cli\Service\ActivityMonitor $activityMonitor */
$activityMonitor = $this->getService('activity_monitor');
if (!$activityMonitor->waitMultiple($activities, $project)) {
return 1;
}
+ } elseif (!$this->centralizedPermissionsEnabled()) {
+ $this->redeployWarning();
}
return 0;
diff --git a/src/Command/User/UserCommandBase.php b/src/Command/User/UserCommandBase.php
new file mode 100644
index 0000000000..66a87e4461
--- /dev/null
+++ b/src/Command/User/UserCommandBase.php
@@ -0,0 +1,145 @@
+config()->get('api.centralized_permissions');
+ }
+
+ /**
+ * Loads a project user by email address.
+ *
+ * Uses 2 different APIs to support backwards compatibility.
+ *
+ * @return ProjectUserAccess|ProjectAccess|null
+ * Null if the user does not exist, a ProjectUserAccess object if
+ * "Centralized Permissions" are enabled, or a ProjectAccess object
+ * otherwise.
+ */
+ protected function loadProjectUserByEmail(Project $project, $email, $reset = false)
+ {
+ $cacheKey = $project->id . ':' . $email;
+ if ($reset || !isset(self::$userCache[$cacheKey])) {
+ if ($this->centralizedPermissionsEnabled()) {
+ self::$userCache[$cacheKey] = $this->doLoadProjectUserByEmail($project, $email);
+ } else {
+ self::$userCache[$cacheKey] = $this->doLoadLegacyProjectAccessByEmail($project, $email);
+ }
+ }
+ return self::$userCache[$cacheKey];
+ }
+
+ /**
+ * Loads a legacy project user ("project access" record) by email address.
+ *
+ * @param Project $project
+ * @param string $email
+ *
+ * @return ProjectAccess|null
+ */
+ private function doLoadLegacyProjectAccessByEmail(Project $project, $email)
+ {
+ foreach ($this->api()->getProjectAccesses($project) as $user) {
+ $account = $this->api()->getAccount($user);
+ if ($account['email'] === $email || strtolower($account['email']) === strtolower($email)) {
+ return $user;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Loads a project user by email, by paging through all the users on the project.
+ *
+ * @TODO replace this with a more efficient API when available
+ *
+ * @param string $email
+ * @param Project $project
+ * @return ProjectUserAccess|null
+ */
+ private function doLoadProjectUserByEmail(Project $project, $email)
+ {
+ $client = $this->api()->getHttpClient();
+
+ $progress = new ProgressMessage($this->stdErr);
+ $progress->showIfOutputDecorated('Loading user information...');
+ $endpointUrl = $project->getUri() . '/user-access';
+ $collection = ProjectUserAccess::getCollectionWithParent($endpointUrl, $client, [
+ 'query' => ['page[size]' => 50],
+ ])['collection'];
+ $userRef = null;
+ while (true) {
+ $data = $collection->getData();
+ if (!empty($data['ref:users'])) {
+ foreach ($data['ref:users'] as $candidate) {
+ /** @var UserRef $candidate */
+ if ($candidate->email === $email) {
+ $userRef = $candidate;
+ break;
+ }
+ }
+ }
+ if (isset($userRef)) {
+ foreach ($data['items'] as $itemData) {
+ if (isset($itemData['user_id']) && $itemData['user_id'] === $userRef->id) {
+ $itemData['ref:users'][$userRef->id] = $userRef;
+ $progress->done();
+ return new ProjectUserAccess($itemData, $endpointUrl, $client);
+ }
+ }
+ }
+ if (!$collection->hasNextPage()) {
+ break;
+ }
+ $collection = $collection->fetchNextPage();
+ }
+ $progress->done();
+ return null;
+ }
+
+ /**
+ * Lists project users.
+ *
+ * Uses 2 different APIs to support backwards compatibility.
+ *
+ * @param Project $project
+ *
+ * @return array An array of user labels keyed by email address.
+ */
+ protected function listUsers(Project $project)
+ {
+ $choices = [];
+ if ($this->centralizedPermissionsEnabled()) {
+ $result = ProjectUserAccess::getCollectionWithParent($project->getUri() . '/user-access', $this->api()->getHttpClient(), ['query' => ['page[size]' => 200]]);
+ foreach ($result['items'] as $item) {
+ /** @var ProjectUserAccess $item */
+ $userInfo = $item->getUserInfo();
+ $choices[$userInfo->email] = sprintf('%s (%s)', trim($userInfo->first_name . ' ' . $userInfo->last_name), $userInfo->email);
+ }
+ } else {
+ foreach ($this->api()->getProjectAccesses($project) as $access) {
+ $account = $this->api()->getAccount($access);
+ $choices[$account['email']] = sprintf('%s (%s)', $account['display_name'], $account['email']);
+ }
+ }
+ ksort($choices, SORT_NATURAL);
+ return $choices;
+ }
+}
diff --git a/src/Command/User/UserDeleteCommand.php b/src/Command/User/UserDeleteCommand.php
index 7faf04a604..8650cc8df6 100644
--- a/src/Command/User/UserDeleteCommand.php
+++ b/src/Command/User/UserDeleteCommand.php
@@ -1,12 +1,12 @@
validateInput($input);
-
$project = $this->getSelectedProject();
-
$email = $input->getArgument('email');
- $selectedUser = $this->api()->loadProjectAccessByEmail($project, $email);
- if (empty($selectedUser)) {
+
+ $selection = $this->loadProjectUserByEmail($project, $email);
+ if (!$selection) {
$this->stdErr->writeln("User not found: $email");
return 1;
}
+ $userId = $selection instanceof ProjectUserAccess ? $selection->user_id : $selection->id;
- if ($project->owner === $selectedUser->id) {
+ if ($project->owner === $userId) {
$this->stdErr->writeln(sprintf(
'The user %s is the owner of the project %s.',
$email,
@@ -49,7 +49,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 1;
}
- $result = $selectedUser->delete();
+ $result = $selection->delete();
$this->stdErr->writeln("User $email deleted");
@@ -63,7 +63,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
// If the user was deleting themselves from the project, then invalidate
// the projects cache.
- if ($this->api()->getMyUserId() === $selectedUser->id) {
+ if ($this->api()->getMyUserId() === $userId) {
$this->api()->clearProjectsCache();
}
diff --git a/src/Command/User/UserGetCommand.php b/src/Command/User/UserGetCommand.php
index 5d08909c5a..ea9dc64e3f 100644
--- a/src/Command/User/UserGetCommand.php
+++ b/src/Command/User/UserGetCommand.php
@@ -1,14 +1,14 @@
getArgument('email');
if ($email === null && $input->isInteractive()) {
- $choices = [];
- foreach ($this->api()->getProjectAccesses($project) as $access) {
- $account = $this->api()->getAccount($access);
- $choices[$account['email']] = sprintf('%s (%s)', $account['display_name'], $account['email']);
- }
- $email = $questionHelper->choose($choices, 'Enter a number to choose a user:');
+ $email = $questionHelper->choose($this->listUsers($project), 'Enter a number to choose a user:');
}
- $projectAccess = $this->api()->loadProjectAccessByEmail($project, $email);
- if (!$projectAccess) {
+
+ $selection = $this->loadProjectUserByEmail($project, $email);
+ if (!$selection) {
$this->stdErr->writeln("User not found: $email");
return 1;
}
if ($input->getOption('pipe')) {
- $this->displayRole($projectAccess, $level, $output);
+ $this->displayRole($selection, $level, $output);
return 0;
}
@@ -89,17 +85,23 @@ protected function execute(InputInterface $input, OutputInterface $output)
}
/**
- * @param \Platformsh\Client\Model\ProjectAccess $projectAccess
- * @param string $level
- * @param \Symfony\Component\Console\Output\OutputInterface $output
+ * @param ProjectAccess|ProjectUserAccess $user
+ * @param string $level
+ * @param OutputInterface $output
*/
- private function displayRole(ProjectAccess $projectAccess, $level, OutputInterface $output)
+ private function displayRole($user, $level, OutputInterface $output)
{
- if ($level !== 'environment') {
- $currentRole = $projectAccess->role;
+ if ($level === 'environment') {
+ if ($user instanceof ProjectAccess) {
+ $access = $this->getSelectedEnvironment()->getUser($user->id);
+ $currentRole = $access ? $access->role : 'none';
+ } else {
+ $typeRoles = $user->getEnvironmentTypeRoles();
+ $envType = $this->getSelectedEnvironment()->type;
+ $currentRole = isset($typeRoles[$envType]) ? $typeRoles[$envType] : 'none';
+ }
} else {
- $access = $this->getSelectedEnvironment()->getUser($projectAccess->id);
- $currentRole = $access ? $access->role : 'none';
+ $currentRole = $user instanceof ProjectAccess ? $user->role : $user->getProjectRole();
}
$output->writeln($currentRole);
}
diff --git a/src/Command/User/UserListCommand.php b/src/Command/User/UserListCommand.php
index 72b732328d..a9b09b6034 100644
--- a/src/Command/User/UserListCommand.php
+++ b/src/Command/User/UserListCommand.php
@@ -1,14 +1,14 @@
'Email address', 'Name', 'role' => 'Project role', 'ID'];
+ private $tableHeader = ['email' => 'Email address', 'name' => 'Name', 'role' => 'Project role', 'id' => 'ID'];
protected function configure()
{
@@ -26,25 +26,45 @@ protected function execute(InputInterface $input, OutputInterface $output)
$project = $this->getSelectedProject();
- $rows = [];
- $i = 0;
/** @var \Platformsh\Cli\Service\Table $table */
$table = $this->getService('table');
- foreach ($this->api()->getProjectAccesses($project) as $projectAccess) {
- $account = $this->api()->getAccount($projectAccess);
- $role = $projectAccess->role;
- $weight = $i++;
- if ($project->owner === $projectAccess->id) {
- $weight = -1;
- if (!$table->formatIsMachineReadable()) {
- $role .= ' (owner)';
+
+ $rows = [];
+
+ if ($this->centralizedPermissionsEnabled()) {
+ $result = ProjectUserAccess::getCollectionWithParent($project->getUri() . '/user-access', $this->api()->getHttpClient(), ['query' => ['page[size]' => 200]]);
+ foreach ($result['items'] as $item) {
+ /** @var ProjectUserAccess $item */
+ $userInfo = $item->getUserInfo();
+ $rows[] = [
+ 'email' => $userInfo->email,
+ 'name' => trim(sprintf('%s %s', $userInfo->first_name, $userInfo->last_name)),
+ 'role' => $item->getProjectRole(),
+ 'id' => $item->user_id,
+ ];
+ }
+ } else {
+ $i = 0;
+ foreach ($this->api()->getProjectAccesses($project) as $projectAccess) {
+ $account = $this->api()->getAccount($projectAccess);
+ $role = $projectAccess->role;
+ $weight = $i++;
+ if ($project->owner === $projectAccess->id) {
+ $weight = -1;
+ if (!$table->formatIsMachineReadable()) {
+ $role .= ' (owner)';
+ }
}
+ $rows[$weight] = [
+ 'email' => $account['email'],
+ 'name' => $account['display_name'],
+ 'role' => $role,
+ 'id' => $projectAccess->id,
+ ];
}
- $rows[$weight] = ['email' => $account['email'], $account['display_name'], 'role' => $role, $projectAccess->id];
+ ksort($rows);
}
- ksort($rows);
-
if (!$table->formatIsMachineReadable()) {
$this->stdErr->writeln(sprintf(
'Users on the project %s:',
diff --git a/src/Service/Api.php b/src/Service/Api.php
index 125e2471ee..8ee70a514d 100644
--- a/src/Service/Api.php
+++ b/src/Service/Api.php
@@ -1060,27 +1060,6 @@ public function getProjectAccesses(Project $project, $reset = false)
return self::$projectAccessesCache[$project->id];
}
- /**
- * Load a project user ("project access" record) by email address.
- *
- * @param Project $project
- * @param string $email
- * @param bool $reset
- *
- * @return ProjectAccess|false
- */
- public function loadProjectAccessByEmail(Project $project, $email, $reset = false)
- {
- foreach ($this->getProjectAccesses($project, $reset) as $user) {
- $account = $this->getAccount($user);
- if ($account['email'] === $email || strtolower($account['email']) === strtolower($email)) {
- return $user;
- }
- }
-
- return false;
- }
-
/**
* Returns a project label.
*