addAssetsFromRecord($manipulations, $this->owner, AssetManipulationList::STATE_DELETED); // Whitelist assets that exist in other stages $this->addAssetsFromOtherStages($manipulations); // Apply visibility rules based on the final manipulation $this->processManipulation($manipulations); } /** * Ensure that changes to records flush overwritten files, and update the visibility * of other assets. */ public function onBeforeWrite() { // Prepare blank manipulation $manipulations = new AssetManipulationList(); // Mark overwritten object as deleted if($this->owner->isInDB()) { $priorRecord = DataObject::get(get_class($this->owner))->byID($this->owner->ID); if($priorRecord) { $this->addAssetsFromRecord($manipulations, $priorRecord, AssetManipulationList::STATE_DELETED); } } // Add assets from new record with the correct visibility rules $state = $this->getRecordState($this->owner); $this->addAssetsFromRecord($manipulations, $this->owner, $state); // Whitelist assets that exist in other stages $this->addAssetsFromOtherStages($manipulations); // Apply visibility rules based on the final manipulation $this->processManipulation($manipulations); } /** * Check default state of this record * * @param DataObject $record * @return string One of AssetManipulationList::STATE_* constants */ protected function getRecordState($record) { if($this->isVersioned()) { // Check stage this record belongs to $stage = $record->getSourceQueryParam('Versioned.stage') ?: Versioned::get_stage(); // Non-live stages are automatically non-public if($stage !== Versioned::LIVE) { return AssetManipulationList::STATE_PROTECTED; } } // Check if canView permits anonymous viewers return $record->canView(Member::create()) ? AssetManipulationList::STATE_PUBLIC : AssetManipulationList::STATE_PROTECTED; } /** * Given a set of asset manipulations, trigger any necessary publish, protect, or * delete actions on each asset. * * @param AssetManipulationList $manipulations */ protected function processManipulation(AssetManipulationList $manipulations) { // When deleting from stage then check if we should archive assets $archive = $this->owner->config()->keep_archived_assets; // Publish assets $this->publishAll($manipulations->getPublicAssets()); // Protect assets $this->protectAll($manipulations->getProtectedAssets()); // Check deletion policy $deletedAssets = $manipulations->getDeletedAssets(); if ($archive && $this->isVersioned()) { // Archived assets are kept protected $this->protectAll($deletedAssets); } else { // Otherwise remove all assets $this->deleteAll($deletedAssets); } } /** * Checks all stages other than the current stage, and check the visibility * of assets attached to those records. * * @param AssetManipulationList $manipulation Set of manipulations to add assets to */ protected function addAssetsFromOtherStages(AssetManipulationList $manipulation) { // Skip unversioned or unsaved assets if(!$this->isVersioned() || !$this->owner->isInDB()) { return; } // Unauthenticated member to use for checking visibility $baseClass = $this->owner->baseClass(); $filter = array("\"{$baseClass}\".\"ID\"" => $this->owner->ID); $stages = $this->owner->getVersionedStages(); // {@see Versioned::getVersionedStages} foreach ($stages as $stage) { // Skip current stage; These should be handled explicitly if($stage === Versioned::get_stage()) { continue; } // Check if record exists in this stage $record = Versioned::get_one_by_stage($baseClass, $stage, $filter); if (!$record) { continue; } // Check visibility of this record, and record all attached assets $state = $this->getRecordState($record); $this->addAssetsFromRecord($manipulation, $record, $state); } } /** * Given a record, add all assets it contains to the given manipulation. * State can be declared for this record, otherwise the underlying DataObject * will be queried for canView() to see if those assets are public * * @param AssetManipulationList $manipulation Set of manipulations to add assets to * @param DataObject $record Record * @param string $state One of AssetManipulationList::STATE_* constant values. */ protected function addAssetsFromRecord(AssetManipulationList $manipulation, DataObject $record, $state) { // Find all assets attached to this record $assets = $this->findAssets($record); if (empty($assets)) { return; } // Add all assets to this stage foreach ($assets as $asset) { $manipulation->addAsset($asset, $state); } } /** * Return a list of all tuples attached to this dataobject * Note: Variants are excluded * * @param DataObject $record to search * @return array */ protected function findAssets(DataObject $record) { // Search for dbfile instances $files = array(); foreach ($record->db() as $field => $db) { $fieldObj = $record->$field; if(!is_object($fieldObj) || !($record->$field instanceof DBFile)) { continue; } // Omit variant and merge with set $next = $record->dbObject($field)->getValue(); unset($next['Variant']); if ($next) { $files[] = $next; } } // De-dupe return array_map("unserialize", array_unique(array_map("serialize", $files))); } /** * Determine if {@see Versioned) extension rules should be applied to this object * * @return bool */ protected function isVersioned() { return $this->owner->has_extension('Versioned') && class_exists('Versioned'); } /** * Delete all assets in the tuple list * * @param array $assets */ protected function deleteAll($assets) { if (empty($assets)) { return; } $store = $this->getAssetStore(); foreach ($assets as $asset) { $store->delete($asset['Filename'], $asset['Hash']); } } /** * Move all assets in the list to the public store * * @param array $assets */ protected function publishAll($assets) { if (empty($assets)) { return; } $store = $this->getAssetStore(); foreach ($assets as $asset) { $store->publish($asset['Filename'], $asset['Hash']); } } /** * Move all assets in the list to the protected store * * @param array $assets */ protected function protectAll($assets) { if (empty($assets)) { return; } $store = $this->getAssetStore(); foreach ($assets as $asset) { $store->protect($asset['Filename'], $asset['Hash']); } } /** * @return AssetStore */ protected function getAssetStore() { return Injector::inst()->get('AssetStore'); } }