* @copyright 2021 * @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 . * */ namespace OCA\Circles\Command; use daita\MySmallPhpTools\Exceptions\InvalidItemException; use daita\MySmallPhpTools\Exceptions\ItemNotFoundException; use daita\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Deserialize; use daita\MySmallPhpTools\Traits\TArrayTools; use daita\MySmallPhpTools\Traits\TStringTools; use Exception; use OC\Core\Command\Base; use OCA\Circles\AppInfo\Application; use OCA\Circles\Db\CoreQueryBuilder; use OCA\Circles\Exceptions\CircleNotFoundException; use OCA\Circles\Model\Circle; use OCA\Circles\Model\FederatedUser; use OCA\Circles\Model\Member; use OCA\Circles\Service\ConfigService; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Process; /** * Class CirclesTest * * @package OCA\Circles\Command */ class CirclesTest extends Base { use TArrayTools; use TStringTools; use TNC22Deserialize; static $INSTANCES = [ 'global-scale-1', 'global-scale-2', 'global-scale-3', 'passive', 'external', 'trusted' ]; static $TEST_CIRCLES = [ 'test_001' ]; /** @var CoreQueryBuilder */ private $coreQueryBuilder; /** @var ConfigService */ private $configService; /** @var InputInterface */ private $input; /** @var OutputInterface */ private $output; /** @var array */ private $config = []; /** @var string */ private $local = ''; /** @var bool */ private $pOn = false; /** @var array */ private $circles = []; /** @var array */ private $federatedUsers = []; /** * CirclesTest constructor. * * @param CoreQueryBuilder $coreQueryBuilder * @param ConfigService $configService */ public function __construct(CoreQueryBuilder $coreQueryBuilder, ConfigService $configService) { parent::__construct(); $this->coreQueryBuilder = $coreQueryBuilder; $this->configService = $configService; } /** * */ protected function configure() { parent::configure(); $this->setName('circles:test') ->setDescription('testing some features') ->addArgument('deprecated', InputArgument::OPTIONAL, '') ->addOption( 'are-you-aware-this-will-delete-all-my-data', '', InputOption::VALUE_REQUIRED, 'Well, are you ?', '' ) ->addOption('skip-init', '', InputOption::VALUE_NONE, 'Bypass Initialisation') ->addOption('skip-setup', '', InputOption::VALUE_NONE, 'Bypass Circles Setup') ->addOption('only-setup', '', InputOption::VALUE_NONE, 'Stop after Circles Setup, pre-Sync'); } /** * @param InputInterface $input * @param OutputInterface $output * * @return int * @throws Exception */ protected function execute(InputInterface $input, OutputInterface $output): int { $this->input = $input; $this->output = $output; if ($input->getOption('are-you-aware-this-will-delete-all-my-data') === 'yes-i-am') { try { $this->testCirclesApp(); } catch (Exception $e) { if ($this->pOn) { $message = ($e->getMessage() !== '') ? $e->getMessage() : get_class($e); $this->output->writeln('' . $message . ''); } else { throw $e; } } return 0; } $output->writeln(''); $output->writeln( 'Since Nextcloud 22, this command have changed, please read the message below:' ); $output->writeln('This new command is to test the integrity of the Circles App.'); $output->writeln( 'Running this command will REMOVE all your current configuration and all your current Circles.' ); $output->writeln('There is a huge probability that you do not want to do that.'); $output->writeln(''); $output->writeln( 'The old testing command you might looking for have moved to "./occ circles:check"' ); $output->writeln(''); return 0; } /** * @throws CircleNotFoundException * @throws InvalidItemException * @throws ItemNotFoundException */ private function testCirclesApp() { $this->t('Bootup'); $this->loadConfiguration(); if (!$this->input->getOption('skip-setup')) { if (!$this->input->getOption('skip-init')) { $this->t('Initialisation Nextcloud'); $this->initEnvironment(); } $this->t('Initialisation Circles App'); $this->reloadCirclesApp(); $this->configureCirclesApp(); $this->confirmVersion(); $this->confirmEmptyCircles(); if ($this->input->getOption('only-setup')) { return; } $this->syncCircles(); $this->t('Fresh installation status'); $this->statusFreshInstances(); $this->createRemoteLink(); } $this->t('Building Local Database'); $this->buildingLocalDatabase(); $this->t('Testing Basic Circle Creation'); $this->circleCreation001(); $this->t('Adding local users and moderators'); $this->addLocalMemberByUserId(); // $this->addLocalMemberBySingleId(); // $this->addLocalMemberUsingMember(); // $this->levelLocalMemberToModerator(); // $this->addRemoteMemberUsingModerator(); // $this->addRemoteMemberUsingRemoteMember(); // $this->levelRemoteMemberToAdmin(); // $this->addRemoteMemberUsingRemoteAdmin(); // } /** * @throws ItemNotFoundException */ private function loadConfiguration() { $this->p('Loading configuration'); $configuration = file_get_contents(__DIR__ . '/../../testConfiguration.json'); $this->config = json_decode($configuration, true); $this->r(true, 'testConfiguration.json'); $this->p('Checking configuration'); foreach (self::$INSTANCES as $instance) { $cloudId = $this->getConfig($instance, 'config.frontal_cloud_id'); if ($this->configService->isLocalInstance($cloudId)) { $this->local = $instance; } } $this->r(); $this->p('Checking local'); if ($this->local === '') { throw new ItemNotFoundException('local not defined'); } $this->r(true, $this->local); } /** * @throws ItemNotFoundException */ private function initEnvironment() { $this->p('Disabling Password Policy App:'); foreach ($this->getInstances() as $instanceId) { $this->occ($instanceId, 'app:disable password_policy', false, false); $this->pm($instanceId); } $this->r(); foreach ($this->getInstances() as $instance) { $this->p('Creating users on ' . $instance); foreach ($this->getConfigArray($instance, 'users') as $userId) { $this->pm($userId); $this->occ( $instance, 'user:add --password-from-env ' . $userId, false, false, ['OC_PASS' => 'testtest'] ); } $this->r(); foreach ($this->getConfigArray($instance, 'groups') as $groupId => $users) { $this->p('Creating group ' . $groupId . ' on ' . $instance . ''); $this->occ($instance, 'group:add ' . $groupId, false, false); foreach ($users as $userId) { $this->pm($userId); $this->occ($instance, 'group:adduser ' . $groupId . ' ' . $userId, true, false); } $this->r(); } } } /** * @throws ItemNotFoundException */ private function reloadCirclesApp() { $this->p('Reload Circles App'); foreach ($this->getInstances(false) as $instance) { $this->pm($instance); $this->occ($instance, 'circles:clean --uninstall', false, false); $this->occ($instance, 'app:enable circles', true, false); } $this->r(); $this->p('Empty Circles database on local'); $this->coreQueryBuilder->cleanDatabase(); $this->r(); } /** * @throws ItemNotFoundException */ private function configureCirclesApp() { $this->p('Configure Circles App'); foreach ($this->getInstances(true) as $instance) { $this->pm($instance); foreach ($this->getConfigArray($instance, 'config') as $k => $v) { $this->occ($instance, 'config:app:set --value ' . $v . ' circles ' . $k, true, false); } } $this->r(); } /** * @throws ItemNotFoundException * @throws Exception */ private function confirmVersion() { $version = $this->configService->getAppValue('installed_version'); $this->p('Confirming version ' . $version . ''); foreach ($this->getInstances(false) as $instance) { $this->pm($instance); $capabilities = $this->occ($instance, 'circles:check --capabilities'); $v = $this->get('version', $capabilities); if ($v !== $version) { throw new Exception($v); } } $this->r(); } /** * @throws ItemNotFoundException * @throws Exception */ private function confirmEmptyCircles() { $this->p('Confirming empty database'); foreach ($this->getInstances() as $instance) { $this->pm($instance); $result = $this->occ($instance, 'circles:manage:list --all'); if (!is_array($result) || !empty($result)) { throw new Exception('no'); } } $this->r(); } /** * @throws ItemNotFoundException * @throws Exception */ private function syncCircles() { $this->p('Running Circles Sync'); foreach ($this->getInstances() as $instance) { $this->pm($instance); $this->occ($instance, 'circles:sync'); } $this->r(); } /** * @throws CircleNotFoundException * @throws InvalidItemException * @throws ItemNotFoundException */ private function statusFreshInstances() { foreach ($this->getInstances() as $instanceId) { $this->p('Circles on ' . $instanceId); $result = $this->occ($instanceId, 'circles:manage:list --all'); $expectedSize = sizeof($this->getConfigArray($instanceId, 'groups')) + sizeof($this->getConfigArray($instanceId, 'users')) + 1; $this->r((sizeof($result) === $expectedSize), sizeof($result) . ' circles'); $membersList = $groupsList = []; foreach ($result as $item) { /** @var Circle $circle */ $circle = $this->deserialize($item, Circle::class); if ($circle->isConfig(Circle::CFG_SINGLE)) { $membersList[] = $circle; } if ($circle->getSource() === Member::TYPE_GROUP) { $groupsList[] = $circle; } } $instance = $this->getConfig($instanceId, 'config.frontal_cloud_id'); foreach ($this->getConfigArray($instanceId, 'users') as $userId) { $this->p('Checking Single Circle for ' . $userId . '@' . $instance . ''); $circle = $this->getSingleCircleForMember($membersList, $userId, $instance); $compareToOwnerBasedOn = new Circle(); $compareToOwnerBasedOn->setConfig(Circle::CFG_SINGLE) ->setName('user:' . $userId . ':{CIRCLEID}') ->setDisplayName($userId); $compareToOwner = new Member(); $compareToOwner->setUserId($userId) ->setUserType(Member::TYPE_USER) ->setInstance($instance) ->setDisplayName($userId) ->setId('{CIRCLEID}') ->setCircleId('{CIRCLEID}') ->setSingleId('{CIRCLEID}') ->setStatus(Member::STATUS_MEMBER) ->setLevel(Member::LEVEL_OWNER) ->setBasedOn($compareToOwnerBasedOn); $compareTo = new Circle(); $compareTo->setOwner($compareToOwner) ->setConfig(Circle::CFG_SINGLE) ->setName('user:' . $userId . ':{CIRCLEID}') ->setDisplayName($userId); $this->confirmCircleData($circle, $compareTo); $this->r(true, $circle->getSingleId()); } $this->p('Checking Single Circle for Circles App'); $circle = $this->getSingleCircleForMember($membersList, 'circles', $instance); $compareToOwnerBasedOn = new Circle(); $compareToOwnerBasedOn->setConfig(Circle::CFG_SINGLE | Circle::CFG_ROOT) ->setName('app:circles:{CIRCLEID}') ->setDisplayName('circles'); $compareToOwner = new Member(); $compareToOwner->setUserId(Application::APP_ID) ->setUserType(Member::TYPE_APP) ->setInstance($instance) ->setDisplayName(Application::APP_ID) ->setId('{CIRCLEID}') ->setCircleId('{CIRCLEID}') ->setSingleId('{CIRCLEID}') ->setStatus(Member::STATUS_MEMBER) ->setLevel(Member::LEVEL_OWNER) ->setBasedOn($compareToOwnerBasedOn); $compareTo = new Circle(); $compareTo->setOwner($compareToOwner) ->setConfig(Circle::CFG_SINGLE | Circle::CFG_ROOT) ->setName('app:circles:{CIRCLEID}') ->setDisplayName('circles'); $this->confirmCircleData($circle, $compareTo); $this->r(true, $circle->getSingleId()); foreach ($this->getConfigArray($instanceId, 'groups') as $groupId => $members) { $this->p('Checking Circle for ' . $groupId . '@' . $instance . ''); $circle = $this->getCircleFromList($groupsList, 'group:' . $groupId); $appCircle = $this->getSingleCircleForMember($membersList, 'circles', $instance); $appOwner = $appCircle->getOwner(); $compareToOwnerBasedOn = new Circle(); $compareToOwnerBasedOn->setConfig(Circle::CFG_SINGLE | Circle::CFG_ROOT) ->setName($appCircle->getName()) ->setDisplayName($appCircle->getDisplayName()); $compareToOwner = new Member(); $compareToOwner->setUserId($appOwner->getUserId()) ->setUserType($appOwner->getUserType()) ->setInstance($appOwner->getInstance()) ->setDisplayName($appOwner->getDisplayName()) ->setCircleId('{CIRCLEID}') ->setSingleId($appOwner->getSingleId()) ->setStatus($appOwner->getStatus()) ->setLevel($appOwner->getLevel()) ->setBasedOn($compareToOwnerBasedOn); $compareTo = new Circle(); $compareTo->setOwner($compareToOwner) ->setConfig(Circle::CFG_SYSTEM | Circle::CFG_NO_OWNER | Circle::CFG_HIDDEN) ->setName('group:' . $groupId) ->setDisplayName($groupId); $this->confirmCircleData($circle, $compareTo); $this->r(true, $circle->getSingleId()); } $this->output->writeln(''); } } /** * */ private function createRemoteLink() { foreach ($this->getInstances() as $instanceId) { $this->p('Init remote link from ' . $instanceId); $links = $this->getConfigArray($instanceId, 'remote'); foreach ($links as $link => $type) { $remote = $this->getConfig($link, 'config.frontal_cloud_id'); $this->pm($remote . '(' . $type . ')'); $this->occ($instanceId, 'circles:remote ' . $remote . ' --type ' . $type . ' --yes'); } $this->r(); } } /** * @throws InvalidItemException * @throws ItemNotFoundException */ private function buildingLocalDatabase() { $this->circles = $this->federatedUsers = []; foreach ($this->getInstances() as $instanceId) { $this->p('Retrieving Circles from ' . $instanceId); $circles = $this->occ($instanceId, 'circles:manage:list --all'); foreach ($circles as $item) { /** @var Circle $circle */ $circle = $this->deserialize($item, Circle::class); if ($circle->isConfig(Circle::CFG_SINGLE)) { $pos = strrpos($circle->getName(), ':'); if (!$pos) { throw new InvalidItemException('could not parse circle.name'); } $owner = new FederatedUser(); $owner->importFromIFederatedUser($circle->getOwner()); $this->federatedUsers[$instanceId][$owner->getUserId()] = $owner; $this->circles[$instanceId][substr($circle->getName(), 0, $pos)] = $circle; } else { $this->circles[$instanceId][$circle->getName()] = $circle; } } $this->r(); } } /** * @throws InvalidItemException * @throws ItemNotFoundException */ private function circleCreation001() { $this->p('Creating basic circle'); $localInstanceId = 'global-scale-1'; $name = self::$TEST_CIRCLES[0]; $owner = $this->getInstanceUsers($localInstanceId)[1]; $dataCreatedCircle001 = $this->occ($localInstanceId, 'circles:manage:create --type user ' . $owner . ' ' . $name); /** @var Circle $createdCircle */ $createdCircle = $this->deserialize($dataCreatedCircle001, Circle::class); $this->circles[$localInstanceId][$createdCircle->getName()] = $createdCircle; $this->r(true, $createdCircle->getSingleId());; $this->p('Comparing data returned at creation'); if ($createdCircle->getSingleId() === '' || $createdCircle->getOwner()->getId() === '') { throw new InvalidItemException('empty id or owner.member_id'); } $knownOwner = new Member(); $knownOwner->importFromIFederatedUser($this->federatedUsers[$localInstanceId][$owner]); $knownOwner->setCircleId($createdCircle->getSingleId()); $knownOwner->setLevel(Member::LEVEL_OWNER); $knownOwner->setStatus(Member::STATUS_MEMBER); $knownOwner->setId($createdCircle->getOwner()->getId()); $compareTo = new Circle(); $compareTo->setOwner($knownOwner) ->setSingleId($createdCircle->getSingleId()) ->setInitiator($knownOwner) ->setConfig(Circle::CFG_CIRCLE) ->setName($name) ->setDisplayName($name); echo json_encode($createdCircle, JSON_PRETTY_PRINT); $this->confirmCircleData($createdCircle, $compareTo, 'circle', true); $this->r(); $this->p('Comparing local stored data'); $dataCircle = $this->occ($localInstanceId, 'circle:manage:details ' . $createdCircle->getSingleId()); /** @var Circle $tmpCircle */ $tmpCircle = $this->deserialize($dataCircle, Circle::class); $this->confirmCircleData($tmpCircle, $createdCircle); $this->r(true, $tmpCircle->getSingleId()); $links = $this->getConfigArray('global-scale-1', 'remote'); foreach ($this->getInstances(false) as $instanceId) { $this->p('Comparing data stored on ' . $instanceId); $dataCircle = $this->occ($instanceId, 'circle:manage:details ' . $createdCircle->getSingleId(), false); if ($instanceId === $localInstanceId || $links[$instanceId] === 'GlobalScale') { /** @var Circle $tmpCircle */ $tmpCircle = $this->deserialize($dataCircle, Circle::class); // we reset some data that should not be available on remote instance $createdCircle->setInitiator(null) ->getOwner()->setBasedOn(null); $this->confirmCircleData($tmpCircle, $createdCircle); $this->r(true, $tmpCircle->getSingleId()); } else { if (is_null($dataCircle)) { $this->r(true, 'empty'); } else { $this->r(false, 'should be empty'); } } } } /** * @throws InvalidItemException * @throws ItemNotFoundException * @throws CircleNotFoundException */ private function addLocalMemberByUserId() { $this->p('Adding local user as member, based on userId'); $instanceId = 'global-scale-1'; $circleName = self::$TEST_CIRCLES[0]; $member = $this->getInstanceUsers($instanceId)[2]; $addedMember = $this->processMemberAdd($instanceId, $circleName, $member, 'user'); $this->r(true, $addedMember->getId());; // check test2 } /** * @throws CircleNotFoundException * @throws InvalidItemException * @throws ItemNotFoundException */ private function addLocalMemberBySingleId() { $this->p('Adding local user as member, based on singleId'); $localInstanceId = 'global-scale-1'; $name = self::$TEST_CIRCLES[0]; $circle = $this->getCircleByName($localInstanceId, $name); $userId = $this->getInstanceUsers($localInstanceId)[6]; $userCircle = $this->getCircleByName($localInstanceId, 'user:' . $userId); $user = $userCircle->getOwner(); $dataAddedMember = $this->occ( $localInstanceId, 'circles:members:add ' . $circle->getSingleId() . ' ' . $user->getSingleId() ); /** @var Member $addedMember */ $addedMember = $this->deserialize($dataAddedMember, Member::class); $this->r(true, $addedMember->getId());; // check test6 } private function addLocalMemberUsingMember() { $this->p('Adding local member using local Member'); $localInstanceId = 'global-scale-1'; $initiator = $this->getInstanceUsers($localInstanceId)[6]; $member = $this->getInstanceUsers($localInstanceId)[6]; $circleName = self::$TEST_CIRCLES[0]; $circle = $this->getCircleByName($localInstanceId, $circleName); $userId = $this->getInstanceUsers($localInstanceId)[6]; $userCircle = $this->getCircleByName($localInstanceId, 'user:' . $userId); $user = $userCircle->getOwner(); $dataAddedMember = $this->occ( $localInstanceId, 'circles:members:add ' . $circle->getSingleId() . ' ' . $user->getSingleId() ); /** @var Member $addedMember */ $addedMember = $this->deserialize($dataAddedMember, Member::class); $this->r(true, $addedMember->getId());; } private function levelLocalMemberToModerator() { $this->p('Changing local level to Moderator'); } private function addLocalMemberUsingModerator() { $this->p('Adding local member using local Moderator'); } private function levelLocalMemberToModeratorUsingModerator() { // fail $this->p('Changing local level to moderator using local Moderator'); } private function addRemoteMemberUsingModerator() { $this->p('Adding remote user using local Moderator'); } private function addLocalMemberUsingRemoteMember() { // fail $this->p('Adding local user using remote Member'); } private function addRemoteMemberUsingRemoteMember() { // fail $this->p('Adding remote user using remote Member'); } private function levelRemoteMemberToAdmin() { $this->p('Changing remote level to Admin'); } private function addLocalMemberUsingRemoteAdmin() { $this->p('Adding remote member using remote Admin'); } private function addRemoteMemberUsingRemoteAdmin() { $this->p('Adding remote member using remote Admin'); } private function levelRemoteMemberToModeratorUsingRemoteAdmin() { } private function levelRemoteMemberToAdminUsingRemoteAdmin() { // fail } private function verifyMemberList001() { } private function removeLocalMemberUsingRemoteMember() { // fail } private function removeLocalMemberUsingRemoteAdmin() { } private function removeRemoteMemberUsingRemoteMember() { // fail } private function removeRemoteMemberUsingRemoteAdmin() { } /** * @param Circle $circle * @param Circle $compareTo * @param string $prefix * @param bool $versa * @param array $params * * @throws Exception */ private function confirmCircleData( Circle $circle, Circle $compareTo, string $prefix = 'circle', bool $versa = false, array $params = [] ) { if (empty($params)) { $params = [ 'CIRCLEID' => $circle->getSingleId() ]; } $this->compare($compareTo->getSingleId(), $circle->getSingleId(), $prefix . '.id', $params); $this->compare($compareTo->getName(), $circle->getName(), $prefix . '.name', $params); $this->compare( $compareTo->getDisplayName(), $circle->getDisplayName(), $prefix . '.displayName', $params ); $this->compareInt($compareTo->getConfig(), $circle->getConfig(), $prefix . '.config', true); $this->compareInt($compareTo->getSource(), $circle->getSource(), $prefix . '.source'); if ($compareTo->hasOwner()) { $compareToOwner = $compareTo->getOwner(); if ($compareToOwner !== null) { $owner = $circle->getOwner(); if ($owner === null) { throw new Exception('empty owner'); } if ($owner->getCircleId() !== $circle->getSingleId()) { throw new Exception($prefix . '.owner.circleId is different than ' . $prefix . '.id'); } $this->confirmMemberData($owner, $compareToOwner, 'owner', false, $params); } } if ($compareTo->hasInitiator()) { $compareToInitiator = $compareTo->getInitiator(); if ($compareToInitiator !== null) { if (!$circle->hasInitiator()) { throw new Exception('empty initiator'); } $initiator = $circle->getInitiator(); if ($initiator->getCircleId() !== $circle->getSingleId()) { throw new Exception($prefix . '.initiator.circleId is different than ' . $prefix . '.id'); } $this->confirmMemberData($initiator, $compareToInitiator, 'owner', false, $params); } } if ($versa) { $this->confirmCircleData($compareTo, $circle); } } /** * @param Member $member * @param Member $compareTo * @param string $prefix * @param bool $versa * @param array $params * * @throws Exception */ private function confirmMemberData( Member $member, Member $compareTo, string $prefix = 'member', bool $versa = false, array $params = [] ) { $this->compare($compareTo->getId(), $member->getId(), $prefix . '.id', $params); $this->compare($compareTo->getCircleId(), $member->getCircleId(), $prefix . '.circleId', $params); $this->compare($compareTo->getSingleId(), $member->getSingleId(), $prefix . '.singleId', $params); $this->compare($compareTo->getUserId(), $member->getUserId(), $prefix . '.userId', $params); $this->compare( $compareTo->getDisplayName(), $member->getDisplayName(), $prefix . '.displayName', $params ); $this->compareInt($compareTo->getUserType(), $member->getUserType(), $prefix . '.userType'); $this->compare($compareTo->getInstance(), $member->getInstance(), $prefix . '.instance', $params); $this->compareInt($compareTo->getLevel(), $member->getLevel(), $prefix . '.level', true); $this->compare($compareTo->getStatus(), $member->getStatus(), $prefix . '.status', $params); $compareToBasedOn = $compareTo->getBasedOn(); if ($compareToBasedOn !== null) { $basedOn = $member->getBasedOn(); if ($basedOn === null) { throw new Exception('empty ' . $prefix . '.basedOn'); } $this->confirmCircleData($basedOn, $compareToBasedOn, $prefix . '.basedOn', false, $params); } } /** * @param string $expected * @param string $compare * @param string $def * @param array $params * * @throws Exception */ private function compare(string $expected, string $compare, string $def, array $params) { if ($expected !== '' && $this->feedStringWithParams($expected, $params) !== $compare) { throw new Exception($def . ': ' . $compare . ' (' . $expected . ')'); } } /** * @param int $expected * @param int $compare * @param string $def * @param array $params * @param bool $force * * @throws Exception */ private function compareInt(int $expected, int $compare, string $def, bool $force = false) { if (($expected > 0 || ($force && $expected >= 0)) && $expected !== $compare) { throw new Exception('wrong ' . $def . ': ' . $compare . ' (' . $expected . ')'); } } /** * @param bool $localIncluded * * @return array */ private function getInstances(bool $localIncluded = true): array { $instances = self::$INSTANCES; if (!$localIncluded) { $instances = array_diff($instances, [$this->local]); } return $instances; } /** * @param array $circles * @param string $userId * @param string $instance * * @return Circle * @throws CircleNotFoundException */ private function getSingleCircleForMember(array $circles, string $userId, string $instance): Circle { foreach ($circles as $circle) { $owner = $circle->getOwner(); if ($owner->getUserId() === $userId && $owner->getInstance() === $instance) { return $circle; } } throw new CircleNotFoundException('cannot find ' . $userId . ' in the list of Single Circle'); } /** * @param string $instanceId * @param string $name * * @return Circle * @throws CircleNotFoundException */ private function getCircleByName(string $instanceId, string $name): Circle { if (array_key_exists($instanceId, $this->circles) && array_key_exists($name, $this->circles[$instanceId])) { return $this->circles[$instanceId][$name]; } throw new CircleNotFoundException( 'cannot extract \'' . $name . '\' from the list of generated Circles' ); } /** * @param array $circles * @param string $name * * @return Circle * @throws CircleNotFoundException */ private function getCircleFromList(array $circles, string $name): Circle { foreach ($circles as $circle) { if ($circle->getName() === $name) { return $circle; } } throw new CircleNotFoundException( 'cannot extract \'' . $name . '\' from the list of provided Circles' ); } /** * @param string $instance * @param string $key * * @return string * @throws ItemNotFoundException */ private function getConfig(string $instance, string $key): string { $config = $this->getConfigInstance($instance); return $this->get($key, $config); } /** * @param string $instance * @param string $key * * @return array * @throws ItemNotFoundException */ private function getConfigArray(string $instance, string $key): array { $config = $this->getConfigInstance($instance); return $this->getArray($key, $config); } /** * @param string $instance * * @return array * @throws ItemNotFoundException */ private function getConfigInstance(string $instance): array { foreach ($this->getArray('instances', $this->config) as $item) { if (strtolower($this->get('id', $item)) === strtolower($instance)) { return $item; } } throw new ItemNotFoundException($instance . ' not found'); } /** * @param $instanceId * * @return array * @throws ItemNotFoundException */ private function getInstanceUsers($instanceId): array { return $this->getConfigArray($instanceId, 'users'); } /** * @param string $instance * @param string $cmd * @param bool $exceptionOnFail * @param bool $jsonAsOutput * @param array $env * * @return array * @throws ItemNotFoundException * @throws Exception */ private function occ( string $instance, string $cmd, bool $exceptionOnFail = true, bool $jsonAsOutput = true, array $env = [] ): ?array { $configInstance = $this->getConfigInstance($instance); $path = $this->get('path', $configInstance); $occ = rtrim($path, '/') . '/occ'; $command = array_merge([$occ], explode(' ', $cmd)); if ($jsonAsOutput) { $command = array_merge($command, ['--output=json']); } $process = new Process($command); $process->run(null, $env); if ($exceptionOnFail && !$process->isSuccessful()) { throw new Exception(implode(' ', $command) . ' failed'); } $output = json_decode($process->getOutput(), true); if (!is_array($output)) { return null; } return $output; } // // // /** * @param string $title */ private function t(string $title): void { $this->output->writeln(''); $this->output->writeln('### ' . $title . ''); $this->output->writeln(''); } /** * @param string $processing */ private function p(string $processing): void { $this->pOn = true; $this->output->write('- ' . $processing . ': '); } /** * @param string $more */ private function pm(string $more): void { $this->output->write($more . ' '); } /** * @param bool $result * @param string $info */ private function r(bool $result = true, string $info = ''): void { $this->pOn = false; if ($result) { $this->output->writeln('' . (($info !== '') ? $info : 'done') . ''); } else { $this->output->writeln('' . (($info !== '') ? $info : 'done') . ''); } } /** * @param string $instanceId * @param string $circleName * @param string $userId * @param string $type * * @return Member * @throws CircleNotFoundException * @throws InvalidItemException * @throws ItemNotFoundException */ private function processMemberAdd(string $instanceId, string $circleName, string $userId, string $type ): Member { $circle = $this->getCircleByName($instanceId, $circleName); $dataAddedMember = $this->occ( $instanceId, 'circles:members:add ' . $circle->getSingleId() . ' ' . $userId . ' --type ' . $type ); /** @var Member $addedMember */ $addedMember = $this->deserialize($dataAddedMember, Member::class); echo 'ADDEDMEMBER: ' . json_encode($addedMember, JSON_PRETTY_PRINT) . "\n"; $federatedUser = $this->federatedUsers[$instanceId][$userId]; echo 'FEDERATEDUER: ' . json_encode($federatedUser, JSON_PRETTY_PRINT) . "\n"; return $addedMember; } }