-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FEATURE] Add synchronization status visualization
Resolves #6
- Loading branch information
1 parent
0532e50
commit 4fe925b
Showing
9 changed files
with
384 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
<?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\SynchronizationStatus; | ||
use AawTeam\BackendRoles\Role\SynchronizationStatusFactoryInterface; | ||
use TYPO3\CMS\Core\Utility\MathUtility; | ||
|
||
/** | ||
* IconHandler | ||
*/ | ||
final class IconHandler | ||
{ | ||
public function __construct( | ||
protected SynchronizationStatusFactoryInterface $synchronizationStatusFactory | ||
) {} | ||
|
||
/** | ||
* Implementation of IconFactory hook: | ||
* | ||
* $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Core\Imaging\IconFactory::class]['overrideIconOverlay'] | ||
* | ||
* @param string[] $row | ||
* @param array<string, bool> $status | ||
*/ | ||
public function postOverlayPriorityLookup(string $table, array $row, array $status, string $iconName): string | ||
{ | ||
if ($table !== 'be_groups') { | ||
return $iconName; | ||
} | ||
if (!array_key_exists('uid', $row) || !MathUtility::canBeInterpretedAsInteger($row['uid'])) { | ||
return $iconName; | ||
} | ||
// Do not override an existing overlay (in the case of be_groups records, this would be only the | ||
// 'hidden' overlay [or anything else from other hooks]) | ||
if ($iconName !== '') { | ||
return $iconName; | ||
} | ||
|
||
$syncStatus = $this->synchronizationStatusFactory->createFromBackendGroupUid((int)$row['uid']); | ||
|
||
// Return incoming value ($iconName) when null was returned by mapping | ||
return $this->mapSynchronizationStatusToIconOverlayName($syncStatus) ?? $iconName; | ||
} | ||
|
||
/** | ||
* @todo register our own icons | ||
*/ | ||
protected function mapSynchronizationStatusToIconOverlayName(SynchronizationStatus $syncStatus): ?string | ||
{ | ||
if ($syncStatus->isOutOfSync()) { | ||
return 'overlay-warning'; | ||
} | ||
if ($syncStatus->isSynced()) { | ||
return 'overlay-approved'; | ||
} | ||
|
||
// Default: return null | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
*/ | ||
final class SynchronizationStatus | ||
{ | ||
public const NONE = 0; | ||
public const NOK = 1; | ||
public const OK = 2; | ||
|
||
public function __construct(public readonly int $status) | ||
{ | ||
if (!in_array($status, [self::NONE, self::NOK, self::OK])) { | ||
throw new \InvalidArgumentException('Invalid status: ' . $status); | ||
} | ||
} | ||
|
||
public function isAvailable(): bool | ||
{ | ||
return $this->status !== self::NONE; | ||
} | ||
|
||
public function isOutOfSync(): bool | ||
{ | ||
return $this->status === self::NOK; | ||
} | ||
|
||
public function isSynced(): bool | ||
{ | ||
return $this->status === self::OK; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
<?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 | ||
*/ | ||
final class SynchronizationStatusFactory implements SynchronizationStatusFactoryInterface | ||
{ | ||
public function __construct( | ||
protected Loader $loader, | ||
protected readonly Formatter $formatter, | ||
protected DefinitionFactory $definitionFactory | ||
) {} | ||
|
||
public function create(int $status): SynchronizationStatus | ||
{ | ||
return new SynchronizationStatus($status); | ||
} | ||
|
||
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 | ||
*/ | ||
public function createFromBackendGroupRecord(array $backendGroupRecord): SynchronizationStatus | ||
{ | ||
$identifier = $this->getIdentifierFromBackendGroupRecord($backendGroupRecord); | ||
|
||
// Not synchronized | ||
if ($identifier === '') { | ||
return $this->create(SynchronizationStatus::NONE); | ||
} | ||
$definitions = $this->loader->getRoleDefinitions(); | ||
if (!$definitions->offsetExists($identifier)) { | ||
return $this->create(SynchronizationStatus::NOK); | ||
} | ||
|
||
// 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::NOK; | ||
if ($this->areRoleDefinitionsEqual($definitionFromConfiguration, $definitionFromDatabase)) { | ||
$status = SynchronizationStatus::OK; | ||
} | ||
|
||
return $this->create($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 | ||
*/ | ||
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']; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?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! | ||
*/ | ||
|
||
/** | ||
* SynchronizationStatusFactoryInterface | ||
*/ | ||
interface SynchronizationStatusFactoryInterface | ||
{ | ||
public function create(int $status): SynchronizationStatus; | ||
|
||
public function createFromBackendGroupUid(int $backendGroupuid): SynchronizationStatus; | ||
|
||
/** | ||
* @param string[] $backendGroupRecord | ||
*/ | ||
public function createFromBackendGroupRecord(array $backendGroupRecord): SynchronizationStatus; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
<?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::NONE) | ||
); | ||
self::assertInstanceOf( | ||
SynchronizationStatus::class, | ||
new SynchronizationStatus(SynchronizationStatus::NOK) | ||
); | ||
self::assertInstanceOf( | ||
SynchronizationStatus::class, | ||
new SynchronizationStatus(SynchronizationStatus::OK) | ||
); | ||
} | ||
|
||
#[Test] | ||
#[DataProvider('unknownConstructorStatesDataProvider')] | ||
public function constructorFailsWithUnknownStatusValue(int $status): void | ||
{ | ||
self::expectException(\InvalidArgumentException::class); | ||
new SynchronizationStatus($status); | ||
} | ||
|
||
#[Test] | ||
public function statusConstantIsInterpretedCorrectly(): void | ||
{ | ||
// NOT_SYNCED | ||
self::assertFalse( | ||
(new SynchronizationStatus(SynchronizationStatus::NONE))->isAvailable() | ||
); | ||
self::assertFalse( | ||
(new SynchronizationStatus(SynchronizationStatus::NONE))->isOutOfSync() | ||
); | ||
self::assertFalse( | ||
(new SynchronizationStatus(SynchronizationStatus::NONE))->isSynced() | ||
); | ||
|
||
// OUT_OF_SYNC | ||
self::assertTrue( | ||
(new SynchronizationStatus(SynchronizationStatus::NOK))->isAvailable() | ||
); | ||
self::assertTrue( | ||
(new SynchronizationStatus(SynchronizationStatus::NOK))->isOutOfSync() | ||
); | ||
self::assertFalse( | ||
(new SynchronizationStatus(SynchronizationStatus::NOK))->isSynced() | ||
); | ||
|
||
// SYNC_OK | ||
self::assertTrue( | ||
(new SynchronizationStatus(SynchronizationStatus::OK))->isAvailable() | ||
); | ||
self::assertFalse( | ||
(new SynchronizationStatus(SynchronizationStatus::OK))->isOutOfSync() | ||
); | ||
self::assertTrue( | ||
(new SynchronizationStatus(SynchronizationStatus::OK))->isSynced() | ||
); | ||
} | ||
|
||
/** | ||
* @return mixed[] | ||
*/ | ||
public static function unknownConstructorStatesDataProvider(): array | ||
{ | ||
return [ | ||
'negative-status' => [-2], | ||
'one-below-known' => [-1], | ||
'one-above-known' => [3], | ||
'positive-status' => [4], | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 the synchronization status of be_groups records | ||
showSynchronizationStatus = 1 |
Oops, something went wrong.