Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/inc/apiv2/common/AbstractBaseAPI.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ protected function makeExpandables(Request $request, array $validExpandables): a
protected function getPrimaryKey(): string
{
$features = $this->getFeatures();
# Word-around required since getPrimaryKey is not static in dba/models/*.php
# Work-around required since getPrimaryKey is not static in dba/models/*.php
foreach($features as $key => $value) {
if ($value['pk'] == True) {
return $key;
Expand Down
100 changes: 95 additions & 5 deletions src/inc/apiv2/common/AbstractModelAPI.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ protected static function fetchExpandObjects(array $objects, string $expand): mi
$toOneRelationships = static::getToOneRelationships();
if (array_key_exists($expand, $toOneRelationships)) {
$relationFactory = self::getModelFactory($toOneRelationships[$expand]['relationType']);

if (array_key_exists('junctionTableType', $toOneRelationships[$expand])) {
$junctionTableFactory = self::getModelFactory($toOneRelationships[$expand]['junctionTableType']);
return self::getManyToOneRelationViaIntermediate(
$objects,
$toOneRelationships[$expand]['junctionTableJoinField'],
$junctionTableFactory,
$relationFactory,
$toOneRelationships[$expand]['relationKey'],
$toOneRelationships[$expand]['parentKey']
);
};

return self::getForeignKeyRelation(
$objects,
$toOneRelationships[$expand]['key'],
Expand All @@ -73,7 +86,7 @@ protected static function fetchExpandObjects(array $objects, string $expand): mi
/* Associative entity */
if (array_key_exists('junctionTableType', $toManyRelationships[$expand])) {
$junctionTableFactory = self::getModelFactory($toManyRelationships[$expand]['junctionTableType']);
return self::getManyToOneRelationViaIntermediate(
return self::getManyToManyRelationViaIntermediate(
$objects,
$toManyRelationships[$expand]['key'],
$junctionTableFactory,
Expand Down Expand Up @@ -148,6 +161,66 @@ protected function getPrimaryKeyOther(string $dbaClass): string
}
}

/**
* Retrieve ManyToOne relalation for $objects ('parents') of type $targetFactory via 'intermidate'
* of $intermediateFactory joining on $joinField (between 'intermediate' and 'target'). Filtered by
* $filterField at $intermediateFactory.
*
* @param array $objects Objects Fetch relation for selected Objects
* @param string $objectField Field to use as base for $objects
* @param object $intermediateFactory Factory used as intermediate between parentObject and targetObject
* @param string $filterField Filter field of intermadiateObject to filter against $objects field
* @param object $targetFactory Object properties of objects returned
* @param string $joinField Field to connect 'intermediate' to 'target'

* @return array $many2One which is a map where the key is the id of the parent object and the value is an array of the included
* objects that are included for this parent object
*/
//A bit hacky solution to get a to one through an intermediate table, currently only used by tasks to include a hashlist through the taskwrapper
//another solution can be to overwrite fetchExpandObjects() in tasks.routes
final protected static function getManyToOneRelationViaIntermediate(
array $objects,
string $objectField,
object $intermediateFactory,
object $targetFactory,
string $joinField,
string $parentKey
): array {
assert($intermediateFactory instanceof AbstractModelFactory);
assert($targetFactory instanceof AbstractModelFactory);
$many2One = array();

/* Retrieve Parent -> Intermediate -> Target objects */
$objectIds = [];
foreach($objects as $object) {
$kv = $object->getKeyValueDict();
$objectIds[] = $kv[$objectField];
}
$baseFactory = self::getModelFactory(static::getDBAClass());
$qF = new ContainFilter($objectField, $objectIds, $intermediateFactory);
$jF = new JoinFilter($intermediateFactory, $joinField, $joinField);
$jF2 = new JoinFilter($baseFactory, $objectField, $objectField, $intermediateFactory);
$hO = $targetFactory->filter([Factory::FILTER => $qF, Factory::JOIN => [$jF, $jF2]]);

$intermediateObjectList = $hO[$intermediateFactory->getModelName()];
$targetObjectList = $hO[$targetFactory->getModelName()];
$baseObjectList = $hO[$baseFactory->getModelName()];

$intermediateObject = current($intermediateObjectList);
$targetObject = current($targetObjectList);
$baseObject = current($baseObjectList);

while ($intermediateObject && $targetObject && $baseObject) {
$kv = $baseObject->getKeyValueDict();
$many2One[$kv[$parentKey]] = $targetObject;

$intermediateObject = next($intermediateObjectList);
$targetObject = next($targetObjectList);
$baseObject = next($baseObjectList);
}
return $many2One;
}

/**
* Retrieve ForeignKey Relation
*
Expand Down Expand Up @@ -243,9 +316,10 @@ final protected static function getManyToOneRelation(
* @param object $targetFactory Object properties of objects returned
* @param string $joinField Field to connect 'intermediate' to 'target'

* @return array
* @return array $many2many which is a map where the key is the id of the parent object and the value is an array of the included
* objects that are included for this parent object
*/
final protected static function getManyToOneRelationViaIntermediate(
final protected static function getManyToManyRelationViaIntermediate(
array $objects,
string $objectField,
object $intermediateFactory,
Expand Down Expand Up @@ -372,6 +446,22 @@ final protected function ResourceRecordArrayToUpdateArray($data, $parentId)
return $updates;
}

protected static function addToRelatedResources(array $relatedResources, array $relatedResource) {
$alreadyExists = false;
$searchType = $relatedResource["type"];
$searchId = $relatedResource["id"];
foreach ($relatedResources as $resource) {
if ($resource["id"] == $searchId && $resource["type"] == $searchType) {
$alreadyExists = true;
break;
}
}
if (!$alreadyExists) {
$relatedResources[] = $relatedResource;
}
return $relatedResources;
}

/**
* API entry point for requesting multiple objects
*/
Expand Down Expand Up @@ -484,14 +574,14 @@ public static function getManyResources(object $apiClass, Request $request, Resp
$expandResultObject = $expandResult[$expand][$object->getId()];
if (is_array($expandResultObject)) {
foreach ($expandResultObject as $expandObject) {
$includedResources[] = $apiClass->obj2Resource($expandObject);
$includedResources = self::addToRelatedResources($includedResources, $apiClass->obj2Resource($expandObject));
}
} else {
if ($expandResultObject === null) {
// to-only relation which is nullable
continue;
}
$includedResources[] = $apiClass->obj2Resource($expandResultObject);
$includedResources = self::addToRelatedResources($includedResources, $apiClass->obj2Resource($expandResultObject));
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/inc/apiv2/model/tasks.routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ public static function getToOneRelationships(): array {
'intermediateType' => TaskWrapper::class,
'joinField' => Task::TASK_WRAPPER_ID,
'joinFieldRelation' => TaskWrapper::TASK_WRAPPER_ID,

'junctionTableType' => TaskWrapper::class,
'junctionTableFilterField' => TaskWrapper::HASHLIST_ID,
'junctionTableJoinField' => TaskWrapper::TASK_WRAPPER_ID,

'parentKey' => Task::TASK_ID
],
];
}
Expand Down
2 changes: 1 addition & 1 deletion src/inc/apiv2/model/users.routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected static function fetchExpandObjects(array $objects, string $expand): mi
/* Expand requested section */
switch($expand) {
case 'accessGroups':
return self::getManyToOneRelationViaIntermediate(
return self::getManyToManyRelationViaIntermediate(
$objects,
User::USER_ID,
Factory::getAccessGroupUserFactory(),
Expand Down
Loading