public static function getAccount(Schema $schema, $accountId)
 {
     if (!array_key_exists($accountId, self::$accounts)) {
         $queryString = "SELECT a.id,a.name FROM " . DbConstants::TABLE_ACCOUNT . " a WHERE a.id = '{$accountId}'";
         $queryResult = $schema->getMySQLi()->query($queryString);
         if (!$queryResult) {
             throw new Exception("Error fetching account for [{$accountId}] - " . $schema->getMySQLi()->error . "\n<-- {$queryString} -->");
         }
         $account = NULL;
         $queryData = $queryResult->fetch_assoc();
         if ($queryData) {
             $account = new PersistentAccount($queryData['id'], $queryData['name']);
         }
         $queryResult->close();
         if ($account == NULL) {
             throw new Exception("Cannot find account for '{$accountId}'.");
         }
         self::$accounts[$accountId] = $account;
     }
     return self::$accounts[$accountId];
 }
 function __construct(Schema $schema, Account $account)
 {
     $this->accountId = $account->getId();
     $mySQLi = $schema->getMySQLi();
     $queryString = "INSERT " . DbConstants::TABLE_AUDIT . " (id_account) VALUES('" . $this->accountId . "')";
     $queryResult = $mySQLi->query($queryString);
     if (!$queryResult) {
         throw new Exception("Error creating audit object for account[" . $this->accountId . "] - " . $mySQLi->error . "\n<!--\n{$queryString}\n-->");
     }
     $this->id = $mySQLi->insert_id;
     // Get the timestamp of the created audit object.
     $queryString = "SELECT a.at FROM " . DbConstants::TABLE_AUDIT . " a WHERE a.id = {$this->id}";
     $queryResult = $mySQLi->query($queryString);
     if (!$queryResult) {
         throw new Exception("Error fetching audit[" . $this->accountId . "] - " . $mySQLi->error . "\n<!--\n{$queryString}\n-->");
     }
     $audit = $queryResult->fetch_assoc();
     $this->at = $audit['at'];
     $queryResult->close();
 }
 private function purgeObsoleteAudits(Schema $schema, Audit $audit)
 {
     $mySQLi = $schema->getMySQLi();
     $queryString = "DELETE a FROM " . DbConstants::TABLE_AUDIT . " a" . " WHERE a.id_account IN(1, " . $audit->getAccountId() . ")" . " AND NOT EXISTS (SELECT 1 FROM " . DbConstants::TABLE_STATE . " WHERE id_created = a.id)" . " AND NOT EXISTS (SELECT 1 FROM " . DbConstants::TABLE_STATE . " WHERE id_published = a.id)" . " AND NOT EXISTS (SELECT 1 FROM " . DbConstants::TABLE_STATE . " WHERE id_terminated = a.id)";
     $queryResult = $mySQLi->query($queryString);
     if (!$queryResult) {
         throw new Exception("Error purging '" . DbConstants::TABLE_AUDIT . "' - {$mySQLi->error}\n<!--\n{$queryString}\n-->");
     }
     return $mySQLi->affected_rows;
 }
 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);
 }
 public function detectObsoleteConnections(Schema $schema, ObjectFetcher $objectFetcher)
 {
     // CHANGED objects can have persisted relationships that don't exist in the parsed dataset.
     // Such persisted relationships must be removed. This implies that either
     //   a. nothing special needs to be done, or
     //   b. persisted connected objects must be 'patched', i.e. their foreign key must be set to NULL, or
     //   c. persisted connected objects must be deleted, or
     //   d. a persisted link (mant-to-many relationship) must be deleted.
     // Option a. is the case when the CHANGED object holds the foreign key and if is NOT an owner of the
     // connected object, or when the connected object is present in the set of ParsedObjects.
     // Option b. applies to connected objects that are not in the set of ParsedObjects. Such connected objects
     // must be added as CHANGED ParsedObjects and filled with all existing property values, except for the
     // foreign key.
     // Option c. applies to situations where the CHANGED object is the owning entity. In this case the existing
     // connected object must be added to the set of ParsedObjects and marked as DELETED.
     // Option d. is similar to option c, but the connected object is a LinkEntity. LinkEntities cannot be referred
     // to by a single id and therefore cannot be dealt with via ParsedObjects. Creating or deleting LinkEntities is
     // done in ParsedObject::establishLinks().
     //
     // Note that ParsedObjects marked as 'touched' are not modified themselves, but they may have modified
     // relationships. These ParsedObjects must be treadet the same as CHANGED objects here.
     //
     // So these are the steps to be taken:
     //   1. loop over the CHANGED and 'touched' ParsedObjects
     //   2. per object, loop over the relationships, ignoring many-to-many (link) relationships
     //   3. check if the entity of the object is NOT the foreign key entity in the relationship
     //      and check if the object is owner of the connected object
     //   4. if either is the case, then see if the parsed data specifies a connection for this relationship
     //   5. if NOT, then add the connected object to the set of ParsedObjects
     //   6. if the connected object is NOT an owner, then fetch its id and mark the newly created ParsedObject
     //      as DELETED
     //   7. if the connected object is the foreign key entity, then fetch and set its id and all its properties
     //      (except for the foreign key) and mark the newly created ParsedObject as CHANGED
     foreach ($this->getChangedAndTouchedObjects() as $changedObject) {
         if ($changedObject->getScope()->includes(Scope::TAG_COMPONENTS) == Scope::INCLUDES_NONE) {
             continue;
         }
         $changedEntity = $changedObject->getEntity();
         foreach ($changedEntity->getRelationships() as $relationship) {
             // Ignore LinkEntities, see ParsedObject::establishLinks().
             if ($relationship->getFkEntity()->isObjectEntity()) {
                 $connectedEntity = $relationship->getOppositeEntity($changedEntity);
                 $isFkEntity = $changedEntity == $relationship->getFkEntity();
                 $isOwnerEntity = $changedEntity == $relationship->getOwnerEntity();
                 if ($isOwnerEntity or !$isFkEntity) {
                     // Check if the parsed data specifies a (new) connection for this relationship.
                     $parsedConnectedObject = NULL;
                     foreach ($changedObject->getRelatedObjects() as $relatedObject) {
                         if ($relatedObject->getEntity() == $connectedEntity) {
                             $parsedConnectedObject = $relatedObject;
                             break;
                         }
                     }
                     // Also check the other direction.
                     foreach ($this->parsedObjects as $parsedObject) {
                         if ($parsedConnectedObject != NULL) {
                             break;
                         }
                         if ($parsedObject->getEntity() == $connectedEntity) {
                             foreach ($parsedObject->getRelatedObjects() as $relatedObject) {
                                 if ($relatedObject->getEntity() == $changedEntity) {
                                     $parsedConnectedObject = $parsedObject;
                                     break;
                                 }
                             }
                         }
                     }
                     // Only do something if there is no (new) connection for this relationship, so any existing
                     // connection must be deleted.
                     if ($parsedConnectedObject == NULL) {
                         $persistedConnectionIds = NULL;
                         if ($isFkEntity) {
                             $persistedConnectionIds = array();
                             $fkColumnName = $relationship->getFkColumnName($connectedEntity);
                             // Fetch the foreign key.
                             $persistedConnectionId = $objectFetcher->getObjectProperty($changedEntity, $changedObject->getId(), $fkColumnName, array());
                             if ($persistedConnectionId != NULL) {
                                 $persistedConnectionIds[] = $persistedConnectionId;
                             }
                         } else {
                             // The existing connected object must be patched or deleted.
                             // Fetch the ids of the connected objects.
                             $changedObjectRef = new ObjectRef($changedEntity, $changedObject->getId());
                             $persistedConnectionIds = $changedObjectRef->fetchRelatedObjectIdsOfEntity($schema->getMySQLi(), $connectedEntity);
                         }
                         foreach ($persistedConnectionIds as $persistedConnectionId) {
                             // Check if the existing connected object is already specified in the current
                             // transaction.
                             $alreadyParsedObject = NULL;
                             foreach ($this->parsedObjects as $parsedObject) {
                                 if ($parsedObject->getEntity() == $connectedEntity and $parsedObject->getId() == $persistedConnectionId) {
                                     $alreadyParsedObject = $parsedObject;
                                     break;
                                 }
                             }
                             // Do nothing if the existing connected object is already present in the set of
                             // ParsedObjects.
                             if ($alreadyParsedObject != NULL) {
                                 continue;
                             }
                             if ($isOwnerEntity) {
                                 $deletedObject = new ParsedObject($connectedEntity, $persistedConnectionId, array(), ParsedObject::DELETED);
                                 $this->deletedObjects[] = $deletedObject;
                             } else {
                                 $propertyValues = $objectFetcher->getPropertyValues($connectedEntity, $persistedConnectionId);
                                 $propertyValues[$relationship->getFkColumnName($changedEntity)] = NULL;
                                 $additionalChangedObject = new ParsedObject($connectedEntity, $persistedConnectionId, $propertyValues, ParsedObject::CHANGED);
                                 $additionalChangedObject->adjustForeignIdProperties();
                                 $this->changedObjects[] = $additionalChangedObject;
                             }
                         }
                     }
                 }
             }
         }
     }
 }