Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
use OpenNIT\Locations\LocationScope;
/**
* Deckt den Standortbezug der Portal-Inhalte ab.
*
* Schwerpunkte: Auflösung des Standorts über die E-Mail-Domain (exakter
* Treffer vor Subdomain-Treffer), Vorrang des fest gesetzten Standorts einer
* Person und die Sichtbarkeitsbedingung inklusive Vererbung an Unterstandorte.
*/
return static function (): void {
/**
* Datenbank-Doppel mit Domain-Zuordnungen und Standort-Hierarchie.
*
* @param array<string,int> $domains Domain => Standort-ID
* @param array<int,?int> $parents Standort-ID => übergeordnete ID
*/
$db = function (array $domains, array $parents = []): object {
return new class ($domains, $parents) extends \OpenNIT\Core\Database {
public function __construct(private array $domains, private array $parents)
{
// Verbindung bewusst nicht aufbauen – Abfragen werden bedient.
}
public function fetchAll(string $sql, array $params = []): array
{
if (str_contains($sql, 'FROM location_domains')) {
$rows = [];
foreach ($params as $candidate) {
if (isset($this->domains[$candidate])) {
$rows[] = ['domain' => $candidate, 'location_id' => $this->domains[$candidate]];
}
}
return $rows;
}
if (str_contains($sql, 'FROM ticket_locations')) {
$rows = [];
foreach ($this->parents as $id => $parent) {
$rows[] = ['id' => $id, 'parent_id' => $parent];
}
return $rows;
}
return [];
}
};
};
test('Exakte Domain gewinnt vor der übergeordneten Domain', function () use ($db) {
$scope = new LocationScope($db(['kunde.de' => 5, 'mail.kunde.de' => 9]));
assertSame(9, $scope->forEmail('anna@mail.kunde.de'));
assertSame(5, $scope->forEmail('bert@kunde.de'));
});
test('Subdomain fällt auf die hinterlegte Hauptdomain zurück', function () use ($db) {
$scope = new LocationScope($db(['kunde.de' => 5]));
assertSame(5, $scope->forEmail('anna@standort.kunde.de'));
});
test('Unbekannte Domain und ungültige Adresse liefern keinen Standort', function () use ($db) {
$scope = new LocationScope($db(['kunde.de' => 5]));
assertTrue($scope->forEmail('anna@fremd.de') === null);
assertTrue($scope->forEmail('ohne-adresse') === null);
assertTrue($scope->forEmail(null) === null);
});
test('Fest gesetzter Standort der Person hat Vorrang vor der Domain', function () use ($db) {
$scope = new LocationScope($db(['kunde.de' => 5]));
assertSame(7, $scope->forPerson(['email' => 'anna@kunde.de', 'location_id' => 7]));
assertSame(5, $scope->forPerson(['email' => 'anna@kunde.de', 'location_id' => null]));
});
test('Sichtbarkeit umfasst den eigenen Standort und dessen Oberstandorte', function () use ($db) {
// 12 → 5 → null: Unterstandort eines Kunden
$scope = new LocationScope($db([], [5 => null, 12 => 5, 99 => null]));
[$sql, $params] = $scope->visibilityCondition(12, 'location_id');
assertTrue(str_contains($sql, 'IS NULL'));
assertSame([12, 5], $params);
});
test('Ohne Standort sind nur globale Inhalte sichtbar', function () use ($db) {
$scope = new LocationScope($db([], [5 => null]));
[$sql, $params] = $scope->visibilityCondition(null, 'location_id');
assertSame('location_id IS NULL', $sql);
assertSame([], $params);
});
test('Zyklische Hierarchie bricht die Auflösung sauber ab', function () use ($db) {
$scope = new LocationScope($db([], [1 => 2, 2 => 1]));
$ids = $scope->selfAndAncestors(1);
assertSame([1, 2], $ids);
});
test('Themenbereich erbt den Standort des übergeordneten Bereichs', function () {
$knowledge = new \OpenNIT\Portal\PortalKnowledge(
new class extends \OpenNIT\Core\Database {
public function __construct() {}
public function fetchAll(string $sql, array $params = []): array
{
if (str_contains($sql, 'FROM topic_locations')) {
return [
// Oberbereich an Standort 5; der Unterbereich 2 erbt ihn.
['topic_id' => 1, 'location_id' => 5],
// Eigene Zuordnung schlägt die Vererbung.
['topic_id' => 3, 'location_id' => 9],
];
}
if (str_contains($sql, 'FROM topics')) {
return [
['id' => 1, 'path' => '/1/', 'location_id' => 5],
['id' => 2, 'path' => '/1/2/', 'location_id' => null],
['id' => 3, 'path' => '/1/3/', 'location_id' => 9],
// Komplett standortfrei → für alle sichtbar
['id' => 4, 'path' => '/4/', 'location_id' => null],
];
}
if (str_contains($sql, 'FROM ticket_locations')) {
return [['id' => 5, 'parent_id' => null], ['id' => 9, 'parent_id' => null]];
}
return [];
}
}
);
$ref = new ReflectionMethod($knowledge, 'topicCondition');
$ref->setAccessible(true);
// Person an Standort 5: Oberbereich, geerbter Unterbereich und globaler Bereich
[$sql, $params] = $ref->invoke($knowledge, 5);
assertSame([1, 2, 4], $params);
assertTrue(str_contains($sql, 't.id IN'));
// Person an Standort 9: nur der eigene Bereich und der globale
[, $params9] = $ref->invoke($knowledge, 9);
assertSame([3, 4], $params9);
// Person ohne Standort: ausschließlich globale Bereiche
[, $paramsNone] = $ref->invoke($knowledge, null);
assertSame([4], $paramsNone);
});
test('Ohne standortgebundene Themenbereiche bleibt die Abfrage ungefiltert', function () {
$knowledge = new \OpenNIT\Portal\PortalKnowledge(
new class extends \OpenNIT\Core\Database {
public function __construct() {}
public function fetchAll(string $sql, array $params = []): array
{
if (str_contains($sql, 'FROM topics')) {
return [
['id' => 1, 'path' => '/1/', 'location_id' => null],
['id' => 2, 'path' => '/2/', 'location_id' => null],
];
}
return [];
}
}
);
$ref = new ReflectionMethod($knowledge, 'topicCondition');
$ref->setAccessible(true);
[$sql, $params] = $ref->invoke($knowledge, 5);
assertSame('1=1', $sql);
assertSame([], $params);
});
};