diff --git a/Model/Attachment.php b/Model/Attachment.php index dd081a0..3b721a5 100644 --- a/Model/Attachment.php +++ b/Model/Attachment.php @@ -52,7 +52,8 @@ class Attachment extends MediaAppModel { ), 'Media.Polymorphic', 'Media.Coupler' => array( - 'baseDirectory' => MEDIA_TRANSFER + 'baseDirectory' => MEDIA_TRANSFER, + 'forceDelete' => false ), 'Media.Meta' => array( 'level' => 2 diff --git a/Model/Behavior/CouplerBehavior.php b/Model/Behavior/CouplerBehavior.php index dc6edb8..d10153b 100644 --- a/Model/Behavior/CouplerBehavior.php +++ b/Model/Behavior/CouplerBehavior.php @@ -19,6 +19,17 @@ App::uses('File', 'Utility'); App::uses('ModelBehavior', 'Model'); + +/** + * Exceptions + * To be moved upon consistent rewrite of error and exception reporting + */ + +class MediaException extends CakeException {} +class MediaDeleteException extends MediaException { + protected $_messageTemplate = 'Some Media file(s) could not be deleted : %s.'; +} + /** * Coupler Behavior Class * @@ -57,13 +68,28 @@ class CouplerBehavior extends ModelBehavior { * * baseDirectory * An absolute path (with trailing slash) to a directory which will be stripped off the file path + * alwaysDeleteRecord + * If set to true, record deletion will proceed even if original media file was not + * found in baseDirectory. Defaults to false. This will not delete generated versions * * @var array */ protected $_defaultSettings = array( - 'baseDirectory' => MEDIA_TRANSFER + 'baseDirectory' => MEDIA_TRANSFER, + 'alwaysDeleteRecord' => false ); +/** + * File Deletion Errors + * + * If coupled files could not be deleted upon record deletion in beforeDelete(), + * the errors are stored here before being thrown as an exception in the afterDelete() + * callback. Errors can be accessed with deletionErrors() method. + * + * @var Array + */ + protected $_deleteErrors = array(); + /** * Setup * @@ -154,9 +180,8 @@ public function beforeSave(Model $Model, $options = array()) { } /** - * Callback, deletes file (if there's one coupled) corresponding to record. If - * the file couldn't be deleted the callback will stop the delete operation and - * not continue to delete the record. + * Callback, deletes file (if there's one coupled) corresponding to record. + * See alwaysDeleteRecord setting above for how failure to delete the file is managed * * @param Model $Model Model using this behavior * @param boolean $cascade If true records that depend on this record will also be deleted @@ -178,11 +203,35 @@ public function beforeDelete(Model $Model, $cascade = true) { $file = $baseDirectory; $file .= $result[$Model->alias]['dirname']; $file .= DS . $result[$Model->alias]['basename']; - - $File = new File($file); - return $File->delete(); + + if (!@unlink($file)) { + if (!array_key_exists($Model->alias, $this->_deleteErrors)) { + $this->_deleteErrors[$Model->alias] = array(); + } + $error = error_get_last(); + $this->_deleteErrors[$Model->alias][$file] = $error['message']; + + if (!$this->settings[$Model->alias]['alwaysDeleteRecord']) { + // The Notice is triggered as a precaution, but in production it is + // advised to use $Model->Behaviors->Coupler->deletionErrors() + // when a record fails to be deleted to see if the error + // originated here and why + trigger_error($error['message'], E_USER_NOTICE); + return false; + } + } + return true; + } + +/** + * Callback, throws Exception if files could not be deleted + * + */ + public function afterDelete(Model $Model) { + if ($errors = $this->deletionErrors($Model)) { + throw new MediaDeleteException(array('files' => join(array_values($errors), ', '))); + } } - /** * Callback, adds the `file` field to each result. * @@ -226,5 +275,20 @@ public function checkRepresent(Model $Model, $check) { $value = current($check); return !empty($value); } - +/** + * Checks if errors were encountered while trying to delete files coupled with records + * + * @param Model $Model Model using this behavior + * @return mixed false if no errors occured, an array containg files that failed to delete + * with associated errors in the format + * array( + * '/path/to/file' => 'Full php error message' + * ) + */ + public function deletionErrors(Model $Model) { + if (array_key_exists($Model->alias, $this->_deleteErrors) && count($errors = $this->_deleteErrors[$Model->alias])) { + return $errors; + } + return false; + } }