public function getPersistedId(ObjectEntity $entity, $temporaryId, $mustExist = TRUE)
 {
     // If it is a persisted id already...
     if (self::isPersistedId($temporaryId)) {
         // ...then return it.
         return $temporaryId;
     }
     // If it's in the entityTemporaryIdMap...
     if (array_key_exists($entity->getName(), $this->entityTemporaryIdMap)) {
         $temporaryIdMap = $this->entityTemporaryIdMap[$entity->getName()];
         if (array_key_exists($temporaryId, $temporaryIdMap)) {
             // ...then return it.
             return $temporaryIdMap[$temporaryId];
         }
     }
     // If a parent is available...
     if ($this->parent != null) {
         // ...then delegate the call.
         return $this->parent->getPersistedId($entity, $temporaryId, $mustExist);
     }
     // OK, so a persisted id is not available. Now what?
     if ($mustExist) {
         throw new Exception("Could not find the persisted equivalent of temporary id '{$temporaryId}' of entity '" . $entity->getName() . "'.", RestResponse::CLIENT_ERROR);
     }
     return NULL;
 }
 public function fetchRelatedObjectRefsOfEntity(MySQLi $mySQLi, ObjectEntity $otherEntity, QueryContext $queryContext, Scope $scope, array &$fetchedObjectRefs)
 {
     // Find the corresponding relationship, ignoring link-relationships.
     $hasTerminatedObjects = FALSE;
     foreach ($this->objectEntity->getRelationships() as $relationship) {
         if ($relationship->getOppositeEntity($this->objectEntity) != $otherEntity) {
             continue;
         }
         // Select the id of the related object.
         $objectIdColumnName = $otherEntity->getObjectIdColumnName();
         $query = new QueryRelatedEntity($this->objectEntity, $this->id, $relationship, $queryContext, $scope);
         $query->setPropertyNames(array($objectIdColumnName));
         $queryString = $query->getQueryString();
         $queryResult = $mySQLi->query($queryString);
         if (!$queryResult) {
             throw new Exception("Error fetching ObjectRefs of entity '" . $otherEntity->getName() . "', associated to '" . $this->objectEntity->getName() . "[{$this->id}]' - " . $mySQLi->error . "\n<!--\n{$queryString}\n-->");
         }
         while ($dbObject = $queryResult->fetch_assoc()) {
             $objectRef = new ObjectRef($otherEntity, $dbObject[$objectIdColumnName]);
             if (!$queryContext->getParameterPublished() and isset($dbObject[QueryEntity::ID_TERMINATED])) {
                 $hasTerminatedObjects = TRUE;
             } else {
                 // Always use the objectRef if we're fetching published data or if it's not terminated.
                 $fetchedObjectRefs[] = $objectRef;
             }
         }
         $queryResult->close();
     }
     return $hasTerminatedObjects;
 }
 private function getOwnedObjectRefs(Schema $schema, ObjectEntity $entity, $id, $isPublished)
 {
     // Find any related objects that are (still) published.
     // First create a set of entities that are 'owned' by the given one...
     $ownedEntities = array();
     foreach ($entity->getRelationships() as $relationship) {
         if ($relationship->getOwnerEntity() == $entity) {
             $oppositeEntity = $relationship->getOppositeEntity($entity);
             if ($oppositeEntity->isObjectEntity()) {
                 $ownedEntities[] = $oppositeEntity;
             }
         }
     }
     // ...and then fetch the published ObjectRefs.
     if (count($ownedEntities) == 0) {
         return NULL;
     }
     $objectRef = new ObjectRef($entity, $id);
     $params = array(RestUrlParams::PUBLISHED => $isPublished ? 'true' : 'false');
     $queryContext = new QueryContext($params, NULL);
     $scope = Scope::parseValue(Scope::VALUE_C_REF . Scope::VALUE_A_REF . Scope::VALUE_O_REF);
     return $objectRef->fetchAllRelatedObjectRefs($schema->getMySQLi(), $ownedEntities, $queryContext, $scope);
 }
 /**
  * Recursively fetches all objects-to-be-deleted and puts them in a map with a list of id's per entity.
  * It also fetches links-to-be-deleted in another map with per link entity a list fkIds per fkColumnName.
  *
  * @param Schema $schema
  * @param ObjectEntity $oneEntity
  * @param <type> $id
  * @param array $objectsToBeDeleted [
  *   entityName => [ id, ... ]
  * ]
  * @param array $linksToBeDeleted [
  *   entityName => [
  *     fkColumnName => [ fkId, ... ]
  *   ]
  * ]
  */
 private function fetchTerminatedObjectsAndLinks(Schema $schema, ObjectEntity $oneEntity, $oneId, array &$objectsVisited, array &$objectsToBeDeleted, array &$linksToBeDeleted)
 {
     $mySQLi = $schema->getMySQLi();
     $oneEntityName = $oneEntity->getName();
     // Find any related object that is 'owned' by the current object.
     foreach ($oneEntity->getRelationships() as $relationship) {
         if ($relationship->getOwnerEntity() != $oneEntity) {
             // Do nothing if $oneEntity is not the owner of the other entity.
             continue;
         }
         // Get the 'other' entity of the relationship.
         $otherEntity = $relationship->getOppositeEntity($oneEntity);
         $otherEntityName = $otherEntity->getName();
         if (!$otherEntity->isObjectEntity()) {
             // Deal with many-to-many relationships.
             $fkColumnNameOne = $relationship->getFkColumnName($oneEntity);
             // Prepare to add any fetched links to the map.
             if (!array_key_exists($otherEntityName, $linksToBeDeleted)) {
                 $linksToBeDeleted[$otherEntityName] = array();
             }
             $fksOfEntityToBeDeleted =& $linksToBeDeleted[$otherEntityName];
             // Prepare to add the fkId.
             if (!array_key_exists($fkColumnNameOne, $fksOfEntityToBeDeleted)) {
                 $fksOfEntityToBeDeleted[$fkColumnNameOne] = array();
             }
             $fkIdsToBeDeleted =& $fksOfEntityToBeDeleted[$fkColumnNameOne];
             $fkIdsToBeDeleted[] = $oneId;
         } else {
             // Deal with one-to-many relationships.
             $fetchedOtherIds = array();
             // Prepare to add any fetched objects to the map.
             if (!array_key_exists($otherEntityName, $objectsToBeDeleted)) {
                 $objectsToBeDeleted[$otherEntityName] = array();
             }
             $otherIdsToBeDeleted =& $objectsToBeDeleted[$otherEntityName];
             if ($relationship->getFkEntity() == $oneEntity) {
                 // If the object at hand holds the foreign key, then fetch those foreign keys.
                 $fkColumnName = $relationship->getFkColumnName($otherEntity);
                 $oneObjectIdColumnName = $oneEntity->getObjectIdColumnName();
                 $queryString = "SELECT DISTINCT d.{$fkColumnName}";
                 if ($oneEntity->getStateIdColumnName() != NULL) {
                     $queryString .= ", s.id_terminated";
                 }
                 $queryString .= " FROM {$oneEntityName} d";
                 if ($oneEntity->getStateIdColumnName() != NULL) {
                     $queryString .= ", " . DbConstants::TABLE_STATE . " s";
                 }
                 $queryString .= " WHERE d.{$oneObjectIdColumnName} = {$oneId}";
                 if ($oneEntity->getStateIdColumnName() != NULL) {
                     $queryString .= " AND s.id = d.{$oneObjectIdColumnName}";
                 }
                 // Execute the query
                 $queryResult = $mySQLi->query($queryString);
                 if (!$queryResult) {
                     throw new Exception("Error fetching foreign key '{$fkColumnName}' of {$oneEntityName}" . "[{$oneId}] - {$mySQLi->error}\n<!--\n{$queryString}\n-->");
                 }
                 while ($queryData = $queryResult->fetch_assoc()) {
                     $otherId = $queryData[$fkColumnName];
                     if ($otherId != NULL) {
                         $fetchedOtherIds[] = $otherId;
                         if ($queryData['id_terminated'] != NULL) {
                             $otherIdsToBeDeleted[] = $otherId;
                         }
                     }
                 }
                 $queryResult->close();
             } else {
                 // The related objects hold the foreign keys, so fetch their ids.
                 $fkColumnName = $relationship->getFkColumnName($oneEntity);
                 $otherObjectIdColumnName = $otherEntity->getObjectIdColumnName();
                 $queryString = "SELECT DISTINCT d.{$otherObjectIdColumnName}";
                 if ($otherEntity->getStateIdColumnName() != NULL) {
                     $queryString .= ", s.id_terminated";
                 }
                 $queryString .= " FROM {$otherEntityName} d";
                 if ($otherEntity->getStateIdColumnName() != NULL) {
                     $queryString .= ", " . DbConstants::TABLE_STATE . " s";
                 }
                 $queryString .= " WHERE d.{$fkColumnName} = {$oneId}";
                 if ($otherEntity->getStateIdColumnName() != NULL) {
                     $queryString .= " AND s.id = d." . $otherEntity->getStateIdColumnName();
                 }
                 // Execute the query
                 $queryResult = $mySQLi->query($queryString);
                 if (!$queryResult) {
                     throw new Exception("Error fetching terminated object ids of entity '{$otherEntityName}'" . " referring to {$oneEntityName}" . "[{$oneId}]" . " - {$mySQLi->error}\n<!--\n{$queryString}\n-->");
                 }
                 while ($queryData = $queryResult->fetch_assoc()) {
                     $otherId = $queryData[$otherObjectIdColumnName];
                     if ($otherId != NULL) {
                         $fetchedOtherIds[] = $otherId;
                         if ($queryData['id_terminated'] != NULL) {
                             $otherIdsToBeDeleted[] = $otherId;
                         }
                     }
                 }
                 $queryResult->close();
             }
             // If the other entity is not a link...
             if (count($fetchedOtherIds) > 0 && $relationship->getFkEntity()->isObjectEntity()) {
                 // ...then continue recursing down the object hierarchy.
                 // Keep track of all visited objects to prevent endless recursion.
                 if (!array_key_exists($otherEntityName, $objectsVisited)) {
                     $objectsVisited[$otherEntityName] = array();
                 }
                 $otherIdsVisited =& $objectsVisited[$otherEntityName];
                 foreach ($fetchedOtherIds as $otherId) {
                     if (!in_array($otherId, $otherIdsVisited)) {
                         $otherIdsVisited[] = $otherId;
                         $this->fetchTerminatedObjectsAndLinks($schema, $otherEntity, $otherId, $objectsVisited, $objectsToBeDeleted, $linksToBeDeleted);
                     }
                 }
             }
         }
     }
 }
 private function getRelatedObjectId(ObjectEntity $foreignEntity)
 {
     $foreignId = NULL;
     foreach ($this->relatedObjects as $key => $relatedObject) {
         if ($foreignEntity == $relatedObject->entity) {
             if ($foreignId != NULL and $relatedObject->id != $foreignId) {
                 throw new Exception("Error in relationship between '" . $this->entity->getName() . "' and '" . $foreignEntity->getName() . "'; expected one id, but got [{$foreignId}] and [{$relatedObject->id}].", RestResponse::CLIENT_ERROR);
             }
             $foreignId = $relatedObject->id;
         }
     }
     return $foreignId;
 }
 private function fetchObjects(ObjectEntity $entity, $queryId, QueryContext $queryContext, Scope $defaultScope, $skipBinaries, DOMDocument $domDoc, DOMNode $xmlParent, array &$xmlElementsWithState, array &$allFetchedObjects = array())
 {
     // Compose and execute the query.
     $mySQLi = $this->schema->getMySQLi();
     $query = new QueryEntity($entity, $queryContext, $defaultScope, $queryId, $skipBinaries);
     $queryString = $query->getQueryString();
     $queryResult = $mySQLi->query($queryString);
     if (!$queryResult) {
         throw new Exception("Error fetching objects of entity '" . $entity->getName() . "' - " . $mySQLi->error . "\n<!--\n{$queryString}\n-->");
     }
     // Convert the query result into XML.
     while ($dbObject = $queryResult->fetch_assoc()) {
         $id = $dbObject[$entity->getObjectIdColumnName()];
         $objectRef = new ObjectRef($entity, $id);
         // Beware of endless recursion!
         $scope = $this->detectEndlessRecursion($objectRef, $query->getScope(), $allFetchedObjects);
         // Create an XML node with the object id as an attribute and insert the node sorted by entity and id.
         $xmlElement = $domDoc->createElementNS($this->namespaceUri, $entity->getName());
         $xmlElement->setAttribute(XmlConstants::ID, $id);
         $this->sortedInsertNode($xmlParent, $xmlElement);
         // Now determine what else to fetch, depending on the given scope.
         $hasTerminatedObjects = $this->fetchRelatedObjects($objectRef, $queryContext, $scope, $skipBinaries, $domDoc, $xmlElement, $xmlElementsWithState, $allFetchedObjects);
         // Add any object properties to the XML.
         $skipBinaryProperties = $skipBinaries || $scope->includes(Scope::TAG_PROPERTIES) == Scope::INCLUDES_REFS_ONLY;
         $publishedState = $this->addPropertiesToXML($dbObject, $entity, $skipBinaryProperties, $domDoc, $xmlElement, $xmlElementsWithState);
         if ($hasTerminatedObjects) {
             $publishedState = FALSE;
         }
         // Add a scope-attribute if it is not a reference, if the value is not empty and if it is not updatable.
         if ($xmlElement->childNodes->length > 0 and strlen($scope->getScopeValue()) > 0 and !$scope->isUpdatable()) {
             $xmlElement->setAttribute(XmlConstants::SCOPE, $scope->getScopeValue());
         }
         // Notify the published-state to the parent node and its ancestors.
         $this->setPublishedStateAndPropagateToAncestors($xmlElement, $publishedState);
     }
     $queryResult->close();
 }