owner->class . '_sync', array( 'LocalID' => 'Int', 'ForeignID' => 'Int', 'NeedsUpdate' => 'Boolean' /*'ClassName' => 'Varchar'*/ ), array( 'LocalID' => 'unique (LocalID)', 'ForeignID' => true, 'NeedsUpdate' => true /*'ClassName' => true*/ )); } // Don't need to do anything function augmentSQL(SQLQuery &$query) { } /** * Update the necessary tables when a record is updated */ function augmentWrite(&$manipulation) { // TODO Changed by ischommer, "replace" is not a valid command?! return true; $data['fields']['LocalID'] = $this->owner->ID; $data['fields']['NeedsUpdate'] = 1; $data['where'] = "`LocalID`=" . $this->owner->ID; $data['command'] = 'replace'; $manipulation[$this->owner->class . '_sync'] = $data; } /** * Initiate an update with the receiving URL. This will retrieve all objects that have been updated since their last * synch and send it to the receiving URL. The initiating installation has priority. */ static function update() { $objects = self::getUpdatedObjects(); $xml = << XML; foreach( array_keys( $objects ) as $class ) if( $objects[$class] ) foreach( $objects[$class] as $object ) { $xml .= $object->serialiseAsXML(); $objSent++; } $xml .= ""; preg_match( '/([^\/]+)(.*)[?]?(?:.*)$/', self::$sendURL, $urlParts ); $host = $urlParts[1]; $path = $urlParts[2]; $query = $urlParts[3]; $response = HTTP::sendPostRequest( $host, $path . '/Synchronise/receive', $xml, 'synchronise' ); list( $header, $response ) = explode( "\r\n\r\n", $response, 2 ); Debug::show( $response ); $element = new SimpleXMLElement( $response ); if( $element->mapping ) { foreach( $element->mapping as $mapping ) self::updateMapping( $mapping ); } echo "Sent $objSent objects"; } static function map() { $data = file_get_contents( $_FILES['synchronise']['tmp_name'] ); $element = new SimpleXMLElement( $data ); if( $element->mapping ) { foreach( $element->mapping as $mapping ) self::updateMapping( $mapping ); } } static function updateMapping( $element ) { $tableName = ((string)$element['classname']) . '_sync'; $foreignID = (string)$element['id']; $localID = (string)$element['foreignid']; DB::query("UPDATE `$tableName` SET `ForeignID` = $foreignID, `NeedsUpdate` = 0 WHERE `LocalID` = $localID"); } static $receivedObjects; static $waitingForObject; /** * Receive updated objects and respond with the foreign mappings for these objects */ static function receive() { // get the data from the files array $data = $_REQUEST['synchronise']; $xml = new SimpleXMLElement( trim( $data ) ); echo ''; if( $xml->object ) { $mappings = array(); foreach( $xml->object as $object ) { list( $localID, $foreignID, $className ) = self::receiveObject( $object ); $mappings[$className][$foreignID] = $localID; } self::updateManyManyRelations(); if( count( $mappings ) > 0 ) { foreach( $mappings as $className => $array ) foreach( $array as $foreignID => $localID ) echo ''; } } echo ''; } /** * Initiates a request to receive all updated objects from the server */ static function get() { preg_match( '/([^\/]+)(.*)[?]?(?:.*)$/', self::$sendURL, $urlParts ); $host = $urlParts[1]; $path = $urlParts[2]; $query = $urlParts[3]; $response = HTTP::sendRequest( $host, $path . '/Synchronise/send', $query ); $element = new SimpleXMLElement( $response ); $mappings = array(); $xml = ''; if( $element->object ) { foreach( $element->object as $object ) { list( $localID, $foreignID, $className ) = self::receiveObject( $object ); $xml .= ''; } } $xml .= ''; HTTP::sendPostRequest( $host, $path . '/Synchronise/map', $xml, 'synchronise' ); } static function send() { } /** * Write the owner object to XML so that it can be sent to a client installation */ function serialiseAsXML() { if( !is_a( $this->owner, 'DataObject' ) ) return ''; $xml = ''; if( is_array( $this->owner->db() ) ) { foreach( $this->owner->db() as $field => $type ) if( $fieldObj = $this->owner->obj($field) ) $xml .= '' . $fieldObj->XML() . ''; } if( is_array( $this->owner->has_one() ) ) { foreach( $this->owner->has_one() as $field => $type ) { $idField = $field . 'ID'; $xml .= '' . ( (string)$this->owner->$idField ) . ''; } } // If we need to do anything for a has_many, do it here if( is_array( $this->owner->has_many() ) ) { } if( is_array( $this->owner->many_many() ) ) { foreach( $this->owner->many_many() as $field => $type ) { if( $componentSet = $this->owner->$field() ) { $ids = $componentSet->getIdList(); if( $ids ) foreach( $ids as $id ) $xml .= '' . ( (string)$id ) . ''; } } } $xml .= ''; return $xml; } static $recvManyManyRelations; /** * Receive an XML fragment and update the database */ static function receiveObject( $element ) { if( $element->getName() !== 'object' ) return; $className = (string)$element['classname']; $foreignID = (string)$element['id']; // retrieve the local object if( $foreignID ) { $objects = DataObject::get( $className, "`{$className}_sync`.`ForeignID`=$foreignID", "", "LEFT JOIN `{$className}_sync` ON `LocalID` = `$className`.`ID`", 1 ); if( $objects ) $object = $objects->First(); } if( !$object ) { $object = new $className(); $object->write(); DB::query("UPDATE `{$className}_sync` SET `ForeignID` = $foreignID, `NeedsUpdate` = 0 WHERE `LocalID` = " . $object->ID ); } ob_start(); // Update the fields if( $dbFields = $element->xpath('db') ) foreach( $dbFields as $dbElement ) { $fieldName = ((string)$dbElement['name']); $object->$fieldName = (string)$dbElement; var_export((string)$dbElement); } // Update the fields if( $hasOneElements = $element->xpath('hasOne') ) foreach( $hasOneElements as $dbElement ) { $fieldName = ((string)$dbElement['name']) . 'ID'; $value = (string)$dbElement; // IDs will not necessarily match on each installation. We need to wait until we've received the object that this field // references if( $recvObj = self::$receivedObjects[$value] ) { $object->$fieldName = $recvObj->ID; } else { self::$waitingForObject[$value][] = array( 'object' => $object, 'srcField' => 'ID', 'destField' => $fieldName ); } } // Update the fields if( $manyManyElements = $element->xpath('/manyMany') ) { $manyMany = array(); foreach( $manyManyElements as $dbElement ) { $fieldName = $dbElement['name']; $manyMany[$fieldName][] = (string)$dbElement; } foreach( $manyMany as $relation => $ids ) self::$recvManyManyRelations[] = array( 'object' => $object, 'relation' => $relation, 'ids' => $ids ); // $object->$relation()->setByIDList( $ids ); } $dbg = ob_get_contents(); ob_end_clean(); self::$receivedObjects[$foreignID] = $object; // Now that this object has been received, any objects waiting on it can be updated if( self::$waitingForObject[$foreignID] ) { foreach( self::$waitingForObject[$foreignID] as $waiting ) { $waitingObj = $waiting['object']; $waitField = $waiting['destField']; $srcField = $waiting['srcField']; $waitingObj->$waitField = $object->$srcField; $waitingObj->write(); } unset( self::$waitingForObject[$foreignID] ); } $object->write(); // DB::query("UPDATE `{$className}_sync` SET `LastSync` = NOW() WHERE `LocalID` = " . $object->ID ); return array( $object->ID, $foreignID, $className ); } /** * Local many-many relations will not map to foreign many-many relations. All ids must be updated and this should be done * after all objects have been processed */ static function updateManyManyRelations() { if( !self::$recvManyManyRelations ) return; foreach( self::$recvManyManyRelations as $relation ) { $object = $relation['object']; $relName = $relation['relation']; $localIDs = array(); foreach( $relation['ids'] as $foreignID ) $localIDs[] = self::$receivedObjects[$foreignID]->ID; $object->$relName()->setByIdList( $localIDs ); } } /** * Retrieve all objects that have been updated since the last synchronisation */ static function getUpdatedObjects() { // Find all classes that can be synched $allClasses = ClassInfo::subclassesFor('DataObject'); foreach( $allClasses as $class ) { $instance = singleton($class); if( is_array( $instance->stat('extensions') ) && in_array( 'Synchronised', $instance->stat('extensions') ) ) $synchronised[$class] = true; } // get all updated objects foreach( array_keys( $synchronised ) as $class ) $synchronised[$class] = DataObject::get($class, /*"`{$class}_sync`.`NeedsUpdate` = 1"*/ "", "", "LEFT JOIN `{$class}_sync` ON `LocalID` = `$class`.`ID`"); return $synchronised; } } ?>