* @copyright (c), Thread */ class UploadBehavior extends Behavior { /** * @event Event that will be call after successful file upload */ const EVENT_AFTER_UPLOAD = 'afterUpload'; /** * Are available 3 indexes: * - `path` Path where the file will be moved. * - `tempPath` Temporary path from where file will be moved. * - `url` Path URL where file will be saved. * * @var array Attributes array */ public $attributes = []; /** * @var boolean If `true` current attribute file will be deleted */ public $unlinkOnSave = true; /** * @var boolean If `true` current attribute file will be deleted after model deletion */ public $unlinkOnDelete = true; /** * @var array Publish path cache array */ protected static $_cachePublishPath = []; /** * @param \yii\base\Component $owner */ public function attach($owner) { parent::attach($owner); if (!is_array($this->attributes) || empty($this->attributes)) { throw new InvalidParamException('Invalid or empty attributes array.'); } else { foreach ($this->attributes as $attribute => $config) { if (isset($config['path']) && !empty($config['path'])) { $this->attributes[$attribute]['path'] = $config['path']; } elseif (isset($config['getBaseUploadPathOwner']) && !empty($config['getBaseUploadPathOwner'])) { $this->attributes[$attribute]['getBaseUploadPathOwner'] = $config['getBaseUploadPathOwner']; } else { throw new InvalidParamException('Path must be set for all attributes.'); } // if (!isset($config['tempPath']) || empty($config['tempPath'])) { $config['tempPath'] = Yii::getAlias('@temp'); } // if (isset($config['getBaseUploadUrlOwner']) && !empty($config['getBaseUploadUrlOwner'])) { $config['url'] = $this->owner->{$config['getBaseUploadUrlOwner']}() . DIRECTORY_SEPARATOR; } elseif (!isset($config['url']) || empty($config['url'])) { $config['url'] = $this->publish($config['path']); } $this->attributes[$attribute]['tempPath'] = FileHelper::normalizePath(Yii::getAlias($config['tempPath'])) . DIRECTORY_SEPARATOR; $this->attributes[$attribute]['url'] = rtrim($config['url'], '/') . '/'; $validator = Validator::createValidator('string', $this->owner, $attribute); $this->owner->validators[] = $validator; unset($validator); } } } /** * @return array */ public function events() { return [ ActiveRecord::EVENT_BEFORE_INSERT => 'beforeInsert', ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeUpdate', ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete' ]; } /** * Function will be called before inserting the new record. */ public function beforeInsert() { foreach ($this->attributes as $attribute => $config) { if ($this->owner->$attribute) { $files = explode(',', $this->owner->$attribute); foreach ($files as $file) { $this->saveFile($attribute, $file); } } } $this->fileExsistInAttributes(); } /** * */ public function fileExsistInAttributes() { foreach ($this->attributes as $attribute => $config) { $base = $this->path($attribute); $files = explode(',', $this->owner->$attribute); $data = []; foreach ($files as $file) { if (is_file($base . '/' . $file)) { $data[] = $file; } } $this->owner->{$attribute} = implode(',', $data); } } /** * */ public function beforeDelete() { if ($this->unlinkOnDelete) { foreach ($this->attributes as $attribute => $config) { if ($this->owner->$attribute) { $files = explode(',', $this->owner->$attribute); foreach ($files as $file) { $this->deleteFile($this->file($attribute, $file)); } } } } } /** * Save model attribute file. * * @param string $attribute Attribute name * @param bool $insert `true` on insert record */ protected function saveFile($attribute, $filename, $insert = true) { $tempFile = $this->tempFile($attribute, $filename); $file = $this->file($attribute, $filename); if (is_file($tempFile) && FileHelper::createDirectory($this->path($attribute))) { if (rename($tempFile, $file)) { $this->triggerEventAfterUpload(); } else { unset($this->owner->$attribute); } } } /** * Delete specified file. * * @param string $file File path * * @return bool `true` if file was successfully deleted */ protected function deleteFile($file) { if (is_file($file)) { return unlink($file); } return false; } /** * @param string $attribute Attribute name * * @return string Old file path */ public function oldFile($attribute, $filename) { return $this->path($attribute) . $filename; } /** * @param string $attribute Attribute name * * @return string Path to file */ public function path($attribute) { if (isset($this->attributes[$attribute]['path']) && !empty($this->attributes[$attribute]['path'])) { return FileHelper::normalizePath($this->attributes[$attribute]['path']) . DIRECTORY_SEPARATOR; } elseif (isset($this->attributes[$attribute]['getBaseUploadPathOwner']) && !empty($this->attributes[$attribute]['getBaseUploadPathOwner'])) { return FileHelper::normalizePath($this->owner->{$this->attributes[$attribute]['getBaseUploadPathOwner']}()) . DIRECTORY_SEPARATOR; } } /** * @param string $attribute Attribute name * * @return string Temporary file path */ public function tempFile($attribute, $filename) { return $this->tempPath($attribute) . $filename; } /** * @param string $attribute Attribute name * * @return string Path to temporary file */ public function tempPath($attribute) { return $this->attributes[$attribute]['tempPath']; } /** * @param string $attribute Attribute name * * @return string File path */ public function file($attribute, $filename) { return $this->path($attribute) . $filename; } /** * Publish given path. * * @param string $path Path * * @return string Published url (/assets/images/image1.png) */ public function publish($path) { if (!isset(static::$_cachePublishPath[$path])) { static::$_cachePublishPath[$path] = Yii::$app->assetManager->publish($path)[1]; } return static::$_cachePublishPath[$path]; } /** * Trigger [[EVENT_AFTER_UPLOAD]] event. */ protected function triggerEventAfterUpload() { $this->owner->trigger(self::EVENT_AFTER_UPLOAD); } /** * Remove attribute and its file. * * @param string $attribute Attribute name * * @return bool Whenever the attribute and its file was removed */ public function removeAttribute($attribute) { if (isset($this->attributes[$attribute])) { if ($this->deleteFile($this->file($attribute))) { return $this->owner->updateAttributes([$attribute => null]); } } return false; } /** * @param string $attribute Attribute name * * @return null|string Full attribute URL */ public function urlAttribute($attribute) { if (isset($this->attributes[$attribute]) && $this->owner->$attribute) { return $this->attributes[$attribute]['url'] . $this->owner->$attribute; } return null; } /** * @param string $attribute Attribute name * * @return string Attribute mime-type */ public function getMimeType($attribute) { return FileHelper::getMimeType($this->file($attribute)); } /** * @param string $attribute Attribute name * * @return boolean Whether file exist or not */ public function fileExists($filename) { return file_exists($this->file($filename)); } /** * Are available 3 indexes: * - `path` Path where the file will be moved. * - `tempPath` Temporary path from where file will be moved. * - `url` Path URL where file will be saved. * - `width` * - `height` * - `crop` * - `thumbnails` - array of thumbnails as prefix => options. Options: * $width - thumbnail width * $height - thumbnail height * @var array Attributes array */ public function beforeUpdate() { foreach ($this->attributes as $attribute => $config) { if ($this->owner->isAttributeChanged($attribute)) { $files = explode(',', $this->owner->$attribute); foreach ($files as $file) { if ($file !== '') { $this->saveFile($attribute, $file); } } $old_files = explode(',', $this->owner->getOldAttribute($attribute)); $old_files = array_diff($old_files, $files); foreach ($old_files as $o_file) { $this->deleteFile($this->oldFile($attribute, $o_file)); } } } $this->fileExsistInAttributes(); } /** * @param $attr * @param $options */ public static function ensureAttribute(&$attr, &$options) { if (!is_array($options)) { $attr = $options; $options = []; } } }