Skip to content

Commit

Permalink
[WIP][FEATURE] Synchronization status
Browse files Browse the repository at this point in the history
  • Loading branch information
christianfutterlieb committed Feb 17, 2024
1 parent c0c70f2 commit d5ce930
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 0 deletions.
50 changes: 50 additions & 0 deletions Classes/Imaging/IconHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace AawTeam\BackendRoles\Imaging;

/*
* Copyright by Agentur am Wasser | Maeder & Partner AG
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use AawTeam\BackendRoles\Role\SynchronizationStatusFactory;
use TYPO3\CMS\Core\Utility\MathUtility;

/**
* IconHandler
*/
class IconHandler
{
public function __construct(
protected SynchronizationStatusFactory $synchronizationStatusFactory
) {}

/**
* @param mixed[] $parameters
* @return string
*/
public function typeIconUserFunction(array $parameters): string
{
$row = $parameters['row'] ?? null;
if (!is_array($row) || !array_key_exists('uid', $row) || !MathUtility::canBeInterpretedAsInteger($row['uid'])) {
return '';
}

$syncStatus = $this->synchronizationStatusFactory->createFromBackendGroupUid((int)$row['uid']);
if ($syncStatus->isOutOfSync()) {
return 'actions-synchronize';
}
if ($syncStatus->isSyncOk()) {
return 'status-user-admin';
}

// Default return nothing
return '';
}
}
46 changes: 46 additions & 0 deletions Classes/Role/SynchronizationStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace AawTeam\BackendRoles\Role;

/*
* Copyright by Agentur am Wasser | Maeder & Partner AG
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

/**
* SynchronizationStatus
*/
class SynchronizationStatus
{
public const NOT_SYNCED = 0;
public const OUT_OF_SYNC = 1;
public const SYNC_OK = 2;

public function __construct(public readonly int $status)
{
if (!in_array($status, [self::NOT_SYNCED, self::OUT_OF_SYNC, self::SYNC_OK])) {
throw new \InvalidArgumentException('Invalid status: ' . $status);
}
}

public function isSynced(): bool
{
return $this->status !== self::NOT_SYNCED;
}

public function isOutOfSync(): bool
{
return $this->status === self::OUT_OF_SYNC;
}

public function isSyncOk(): bool
{
return $this->status === self::SYNC_OK;
}
}
102 changes: 102 additions & 0 deletions Classes/Role/SynchronizationStatusFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace AawTeam\BackendRoles\Role;

/*
* Copyright by Agentur am Wasser | Maeder & Partner AG
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use AawTeam\BackendRoles\Role\Definition\Formatter;
use AawTeam\BackendRoles\Role\Definition\Loader;
use TYPO3\CMS\Backend\Utility\BackendUtility;

/**
* SynchronizationStatusFactory
*/
class SynchronizationStatusFactory
{
public function __construct(
protected Loader $loader,
protected readonly Formatter $formatter,
protected DefinitionFactory $definitionFactory
) {}

public function createFromBackendGroupUid(int $backendGroupuid): SynchronizationStatus
{
$backendGroupRecord = BackendUtility::getRecord('be_groups', $backendGroupuid);
if ($backendGroupRecord === null) {
throw new \RuntimeException('Cannot load be_groups record with uid ' . $backendGroupuid, 1708168047);
}
return $this->createFromBackendGroupRecord($backendGroupRecord);
}

/**
* @param mixed[] $backendGroupRecord
* @return SynchronizationStatus
*/
public function createFromBackendGroupRecord(array $backendGroupRecord): SynchronizationStatus
{
$identifier = $this->getIdentifierFromBackendGroupRecord($backendGroupRecord);

// Not synchronized
if ($identifier === '') {
return new SynchronizationStatus(SynchronizationStatus::NOT_SYNCED);
}
$definitions = $this->loader->getRoleDefinitions();
if (!$definitions->offsetExists($identifier)) {
return new SynchronizationStatus(SynchronizationStatus::NOT_SYNCED);
}

// be_groups record is synchronized: load definitions
/** @var Definition $definitionFromConfiguration */
$definitionFromConfiguration = $definitions->offsetGet($identifier);
$definitionFromDatabase = $this->definitionFactory->create(
array_merge(
[
'identifier' => $definitionFromConfiguration->getIdentifier(),
'title' => $definitionFromConfiguration->getTitle(),
],
$this->formatter->formatFromDbToArray($backendGroupRecord)
)
);

// Compare the definitions
$status = SynchronizationStatus::OUT_OF_SYNC;
if ($this->areRoleDefinitionsEqual($definitionFromConfiguration, $definitionFromDatabase)) {
$status = SynchronizationStatus::SYNC_OK;
}

return new SynchronizationStatus($status);
}

protected function areRoleDefinitionsEqual(Definition $definition1, Definition $definition2): bool
{
$a = $this->formatter->formatForDatabase($definition1);
$b = $this->formatter->formatForDatabase($definition2);
return array_diff_assoc($a, $b) === [];
}

/**
* @param mixed[] $backendGroupRecord
* @return string
*/
protected function getIdentifierFromBackendGroupRecord(array $backendGroupRecord): string
{
// Input validation
if (!array_key_exists('tx_backendroles_role_identifier', $backendGroupRecord)) {
throw new \InvalidArgumentException('No field "tx_backendroles_role_identifier" found in $backendGroupRecord', 1708168285);
}
if (!is_string($backendGroupRecord['tx_backendroles_role_identifier'])) {
throw new \InvalidArgumentException('Field "tx_backendroles_role_identifier" in $backendGroupRecord must be string', 1708168332);
}

return $backendGroupRecord['tx_backendroles_role_identifier'];
}
}
3 changes: 3 additions & 0 deletions Configuration/Services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ services:
AawTeam\BackendRoles\FormEngine\BackendRoleSelectItemsProcessor:
public: true

AawTeam\BackendRoles\Imaging\IconHandler:
public: true

locker.backend_roles_synchronization:
class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
factory: ['@TYPO3\CMS\Core\Locking\LockFactory', 'createLocker']
Expand Down
7 changes: 7 additions & 0 deletions Configuration/TCA/Overrides/be_groups.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

use AawTeam\BackendRoles\FormEngine\BackendRoleSelectItemsProcessor;
use AawTeam\BackendRoles\Imaging\IconHandler;

// Add columns
$columns = [
Expand Down Expand Up @@ -55,3 +56,9 @@
}
}
}

// Show/visualize the synchronization status of be_groups records
if ($extConf['showSynchronizationStatus'] ?? true) {
$GLOBALS['TCA']['be_groups']['ctrl']['typeicon_column'] = 'tx_backendroles_role_identifier';
$GLOBALS['TCA']['be_groups']['ctrl']['typeicon_classes']['userFunc'] = IconHandler::class . '->typeIconUserFunction';
}
63 changes: 63 additions & 0 deletions Tests/Unit/Role/SynchronizationStatusTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace AawTeam\BackendRoles\Tests\Unit\Role;

/*
* Copyright by Agentur am Wasser | Maeder & Partner AG
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

use AawTeam\BackendRoles\Role\SynchronizationStatus;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;

/**
* SynchronizationStatusTest
*/
class SynchronizationStatusTest extends UnitTestCase
{
#[Test]
public function constructorAcceptsKnownStatusValue(): void
{
self::assertInstanceOf(
SynchronizationStatus::class,
new SynchronizationStatus(SynchronizationStatus::NOT_SYNCED)
);
self::assertInstanceOf(
SynchronizationStatus::class,
new SynchronizationStatus(SynchronizationStatus::OUT_OF_SYNC)
);
self::assertInstanceOf(
SynchronizationStatus::class,
new SynchronizationStatus(SynchronizationStatus::SYNC_OK)
);
}

#[Test]
#[DataProvider('unknownConstructorStatesDataProvider')]
public function constructorFailsWithUnknownStatusValue(int $status): void
{
self::expectException(\InvalidArgumentException::class);
new SynchronizationStatus($status);
}

/**
* @return mixed[]
*/
public static function unknownConstructorStatesDataProvider(): array
{
return [
'negative-status' => [-2],
'one-below-known' => [-1],
'one-above-known' => [3],
'positive-status' => [4],
];
}
}
3 changes: 3 additions & 0 deletions ext_conf_template.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# cat=basic/enable/001; type=boolean; label=Hide managed columns of be_groups records in TCA
hideManagedBackendUserGroupColumnns = 1

# cat=basic/enable/002; type=boolean; label=Show/visualize the synchronization status of be_groups records
showSynchronizationStatus = 1

0 comments on commit d5ce930

Please sign in to comment.