diff --git a/GalleryBehavior.php b/GalleryBehavior.php index 54e52e7..6cfeb08 100644 --- a/GalleryBehavior.php +++ b/GalleryBehavior.php @@ -4,6 +4,7 @@ use Imagine\Image\Box; use Imagine\Image\ImageInterface; +use Yii; use yii\base\Behavior; use yii\base\Exception; use yii\db\ActiveRecord; @@ -17,6 +18,7 @@ * @author Bogdan Savluk * * @property string $galleryId + * @property string $temporaryId */ class GalleryBehavior extends Behavior { @@ -25,6 +27,11 @@ class GalleryBehavior extends Behavior * @var string */ public $pkGlue = '_'; + /** + * The prefix of string for temporary id for new models + * @var string + */ + public $temporaryPrefix = 'temp'; /** * @var string Type name assigned to model in image attachment action * @see GalleryManagerAction::$types @@ -133,6 +140,8 @@ public function events() ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete', ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate', ActiveRecord::EVENT_AFTER_FIND => 'afterFind', + ActiveRecord::EVENT_BEFORE_INSERT => 'afterFind', + ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert', ]; } @@ -142,8 +151,7 @@ public function beforeDelete() foreach ($images as $image) { $this->deleteImage($image->id); } - $dirPath = $this->directory . '/' . $this->getGalleryId(); - @rmdir($dirPath); + $this->removeDirectory($this->getDirectoryPath()); } public function afterFind() @@ -154,10 +162,37 @@ public function afterFind() public function afterUpdate() { $galleryId = $this->getGalleryId(); - if ($this->_galleryId != $galleryId) { + if ($this->_galleryId && ($this->_galleryId != $galleryId)) { $dirPath1 = $this->directory . '/' . $this->_galleryId; $dirPath2 = $this->directory . '/' . $galleryId; - rename($dirPath1, $dirPath2); + if (is_dir($dirPath1)) { + rename($dirPath1, $dirPath2); + } + } + } + + /** + * Move dir and change id to actual + * @throws Exception + * @throws \yii\db\Exception + */ + public function afterInsert() + { + $galleryId = $this->getGalleryId(); + if ($this->_galleryId && ($this->_galleryId != $galleryId)) { + + \Yii::$app->db->createCommand() + ->update( + $this->tableName, + ['ownerId' => $galleryId], + ['ownerId' => $this->_galleryId, 'type' => $this->type] + )->execute(); + + $dirPath1 = $this->directory . '/' . $this->_galleryId; + $dirPath2 = $this->directory . '/' . $galleryId; + if (is_dir($dirPath1)) { + rename($dirPath1, $dirPath2); + } } } @@ -165,6 +200,7 @@ public function afterUpdate() /** * @return GalleryImage[] + * @throws Exception */ public function getImages() { @@ -223,6 +259,39 @@ public function getFilePath($imageId, $version = 'original') return $this->directory . '/' . $this->getFileName($imageId, $version); } + public function getDirectoryPath() + { + return $this->directory . '/' . $this->getGalleryId(); + } + + /** + * Generate a temporary id for new models + * @return string + */ + public function getTemporaryId() + { + return $this->temporaryPrefix . Yii::$app->session->getId(); + } + + /** + * Get Gallery Id + * + * @return mixed as string or integer + * @throws Exception + */ + public function getGalleryId() + { + $pk = $this->owner->getPrimaryKey(); + if ($pk === null) { + $pk = $this->temporaryId; + } + if (is_array($pk)) { + return implode($this->pkGlue, $pk); + } else { + return $pk; + } + } + /** * Replace existing image by specified file * @@ -252,31 +321,48 @@ public function replaceImage($imageId, $path) } } + /** + * Remove single image file + * @param $fileName + * @return bool + */ private function removeFile($fileName) { - return FileHelper::unlink($fileName); + try { + return FileHelper::unlink($fileName); + } catch (\yii\base\ErrorException $exception) { + return false; + } } /** - * Get Gallery Id - * - * @return mixed as string or integer - * @throws Exception + * Remove a folders for gallery files + * @param $filePath string the filename of image + * @return bool */ - public function getGalleryId() + private function removeDirectory($filePath) { - $pk = $this->owner->getPrimaryKey(); - if (is_array($pk)) { - return implode($this->pkGlue, $pk); - } else { - return $pk; + try { + FileHelper::removeDirectory(dirname($filePath)); + } catch (\yii\base\ErrorException $exception) { + return false; } - } + return true; + } + /** + * Create a folders for gallery files + * @param $filePath string the filename of image + * @return bool + */ private function createFolders($filePath) { - return FileHelper::createDirectory(FileHelper::normalizePath(dirname($filePath)), 0777); + try { + return FileHelper::createDirectory(FileHelper::normalizePath(dirname($filePath)), 0777); + } catch (\yii\base\Exception $exception) { + return false; + } } /////////////////////////////// ========== Public Actions ============ /////////////////////////// @@ -287,10 +373,7 @@ public function deleteImage($imageId) $this->removeFile($filePath); } $filePath = $this->getFilePath($imageId, 'original'); - $parts = explode('/', $filePath); - $parts = array_slice($parts, 0, count($parts) - 1); - $dirPath = implode('/', $parts); - @rmdir($dirPath); + $this->removeDirectory($filePath); $db = \Yii::$app->db; $db->createCommand() @@ -314,6 +397,35 @@ function ($image) use (&$removed) { } ); } + if (is_null($this->_images)) { + $this->removeDirectory($this->getDirectoryPath()); + } + } + + /** + * Remove images for expired session + * actions is a {'apiRoute'}?action=deleteOrphan&type={'type'}&behaviorName={'behaviorName'} + * @throws \yii\base\ErrorException + * @throws \yii\db\Exception + */ + public function deleteOrphanImages() + { + $toDelete = \Yii::$app->db->createCommand( + 'SELECT DISTINCT `ownerId` FROM ' . $this->tableName . ' WHERE `ownerId` LIKE :ownerId AND `type` = :type', + [':ownerId' => $this->temporaryPrefix . '%', ':type' => $this->type] + )->queryColumn(); + + foreach ($toDelete as $item) { + \Yii::$app->db->createCommand() + ->delete( + $this->tableName, + [ + 'type' => $this->type, + 'ownerId' => $item, + ] + )->execute(); + FileHelper::removeDirectory($this->directory . '/' . $item); + } } public function addImage($fileName) @@ -383,6 +495,8 @@ public function arrange($order) * @param array $imagesData * * @return GalleryImage[] + * @throws Exception + * @throws \yii\db\Exception */ public function updateImagesData($imagesData) { @@ -432,6 +546,7 @@ public function updateImagesData($imagesData) * Should be called in migration on every model after changes in versions configuration * * @param string|null $oldExtension + * @throws Exception */ public function updateImages($oldExtension = null) { diff --git a/GalleryManagerAction.php b/GalleryManagerAction.php index fc7e3c9..80418ca 100644 --- a/GalleryManagerAction.php +++ b/GalleryManagerAction.php @@ -8,6 +8,7 @@ use yii\db\ActiveRecord; use yii\helpers\Json; use yii\web\HttpException; +use yii\web\NotFoundHttpException; use yii\web\UploadedFile; /** @@ -28,10 +29,17 @@ class GalleryManagerAction extends Action */ public $pkGlue = '_'; + /** + * The prefix of string for temporary id for new models + * @var string + */ + public $temporaryPrefix = 'temp'; + /** * $types to be defined at Controller::actions() * @var array Mapping between types and model class names - * @example 'post'=>'common\models\Post' + * @example 'post' => 'common\models\Post' + * @example 'post' => ['class' => common\models\Post::class] * @see GalleryManagerAction::run */ public $types = []; @@ -52,30 +60,35 @@ public function run($action) $this->type = Yii::$app->request->get('type'); $this->behaviorName = Yii::$app->request->get('behaviorName'); $this->galleryId = Yii::$app->request->get('galleryId'); - $pkNames = call_user_func([$this->types[$this->type], 'primaryKey']); - $pkValues = explode($this->pkGlue, $this->galleryId); - - $pk = array_combine($pkNames, $pkValues); - $this->owner = call_user_func([$this->types[$this->type], 'findOne'], $pk); + if (!array_key_exists($this->type, $this->types)) { + throw new HttpException(400, 'Type does not exists'); + } + $this->owner = Yii::createObject($this->types[$this->type]); + if ($this->galleryId && substr_compare($this->galleryId, $this->temporaryPrefix, 0, strlen($this->temporaryPrefix)) !== 0) { + $pkNames = $this->owner->primaryKey(); + $pkValues = explode($this->pkGlue, $this->galleryId); + $pk = array_combine($pkNames, $pkValues); + $this->owner = $this->owner::findOne($pk); + if (!$this->owner) { + throw new NotFoundHttpException(); + } + } $this->behavior = $this->owner->getBehavior($this->behaviorName); switch ($action) { case 'delete': return $this->actionDelete(Yii::$app->request->post('id')); - break; case 'ajaxUpload': return $this->actionAjaxUpload(); - break; case 'changeData': return $this->actionChangeData(Yii::$app->request->post('photo')); - break; case 'order': return $this->actionOrder(Yii::$app->request->post('order')); - break; + case 'deleteOrphan': + return $this->actionDeleteOrphan(); default: - throw new HttpException(400, 'Action do not exists'); - break; + throw new HttpException(400, 'Action does not exists'); } } @@ -85,7 +98,6 @@ public function run($action) * * @param $ids * - * @throws HttpException * @return string */ protected function actionDelete($ids) @@ -96,12 +108,27 @@ protected function actionDelete($ids) return 'OK'; } + /** + * Removes orphan images for this gallery + * On success returns 'OK' + * + * @return string + * @throws \yii\base\ErrorException + * @throws \yii\db\Exception + */ + protected function actionDeleteOrphan() + { + + $this->behavior->deleteOrphanImages(); + + return 'OK'; + } + /** * Method to handle file upload thought XHR2 * On success returns JSON object with image info. * * @return string - * @throws HttpException */ public function actionAjaxUpload() { @@ -119,8 +146,8 @@ public function actionAjaxUpload() array( 'id' => $image->id, 'rank' => $image->rank, - 'name' => (string)$image->name, - 'description' => (string)$image->description, + 'name' => (string) $image->name, + 'description' => (string) $image->description, 'preview' => $image->getUrl('preview'), ) ); @@ -142,7 +169,6 @@ public function actionOrder($order) $res = $this->behavior->arrange($order); return Json::encode($res); - } /** @@ -151,8 +177,10 @@ public function actionOrder($order) * * @param $imagesData * - * @throws HttpException * @return string + * @throws HttpException + * @throws \yii\base\Exception + * @throws \yii\db\Exception */ public function actionChangeData($imagesData) { @@ -165,8 +193,8 @@ public function actionChangeData($imagesData) $resp[] = array( 'id' => $model->id, 'rank' => $model->rank, - 'name' => (string)$model->name, - 'description' => (string)$model->description, + 'name' => (string) $model->name, + 'description' => (string) $model->description, 'preview' => $model->getUrl('preview'), ); }