From bb5b6a4191e849483692f93011e147b4b8ae304d Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 4 Apr 2013 16:53:50 -0400 Subject: [PATCH] Finished FileValidator and UploadedFile. Added Model::formName(). --- framework/base/Model.php | 22 ++- framework/validators/FileValidator.php | 283 ++++++++++++++++++--------------- framework/web/UploadedFile.php | 246 ++++++++++++++++++++++++++++ framework/widgets/ActiveForm.php | 63 ++++---- 4 files changed, 450 insertions(+), 164 deletions(-) create mode 100644 framework/web/UploadedFile.php diff --git a/framework/base/Model.php b/framework/base/Model.php index 611b12e..7f55239 100644 --- a/framework/base/Model.php +++ b/framework/base/Model.php @@ -8,8 +8,8 @@ namespace yii\base; use yii\helpers\StringHelper; -use yii\validators\Validator; use yii\validators\RequiredValidator; +use yii\validators\Validator; /** * Model is the base class for data models. @@ -169,6 +169,26 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess } /** + * Returns the form name that this model class should use. + * + * The form name is mainly used by [[\yii\web\ActiveForm]] to determine how to name + * the input fields for the attributes in a model. If the form name is "A" and an attribute + * name is "b", then the corresponding input name would be "A[b]". If the form name is + * an empty string, then the input name would be "b". + * + * By default, this method returns the model class name (without the namespace part) + * as the form name. You may override it when the model is used in different forms. + * + * @return string the form name of this model class. + */ + public function formName() + { + $class = get_class($this); + $pos = strrpos($class, '\\'); + return $pos === false ? $class : substr($class, $pos + 1); + } + + /** * Returns the list of attribute names. * By default, this method returns all public non-static properties of the class. * You may override this method to change the default behavior. diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php index b05ac2a..c104c05 100644 --- a/framework/validators/FileValidator.php +++ b/framework/validators/FileValidator.php @@ -7,47 +7,19 @@ namespace yii\validators; +use Yii; +use yii\helpers\FileHelper; +use yii\web\UploadedFile; + /** - * CFileValidator verifies if an attribute is receiving a valid uploaded file. - * - * It uses the model class and attribute name to retrieve the information - * about the uploaded file. It then checks if a file is uploaded successfully, - * if the file size is within the limit and if the file type is allowed. - * - * This validator will attempt to fetch uploaded data if attribute is not - * previously set. Please note that this cannot be done if input is tabular: - *
- *  foreach($models as $i=>$model)
- *     $model->attribute = CUploadedFile::getInstance($model, "[$i]attribute");
- * 
- * Please note that you must use {@link CUploadedFile::getInstances} for multiple - * file uploads. - * - * When using CFileValidator with an active record, the following code is often used: - *
- *  if($model->save())
- *  {
- *     // single upload
- *     $model->attribute->saveAs($path);
- *     // multiple upload
- *     foreach($model->attribute as $file)
- *        $file->saveAs($path);
- *  }
- * 
- * - * You can use {@link CFileValidator} to validate the file attribute. + * FileValidator verifies if an attribute is receiving a valid uploaded file. * * @author Qiang Xue * @since 2.0 */ -class CFileValidator extends Validator +class FileValidator extends Validator { /** - * @var boolean whether the attribute requires a file to be uploaded or not. - * Defaults to false, meaning a file is required to be uploaded. - */ - public $allowEmpty = false; - /** * @var mixed a list of file name extensions that are allowed to be uploaded. * This can be either an array or a string consisting of file extension names * separated by space or comma (e.g. "gif, jpg"). @@ -66,136 +38,179 @@ class CFileValidator extends Validator * Defaults to null, meaning no limit. * Note, the size limit is also affected by 'upload_max_filesize' INI setting * and the 'MAX_FILE_SIZE' hidden field value. - * @see tooLarge + * @see tooBig */ public $maxSize; /** + * @var integer the maximum file count the given attribute can hold. + * It defaults to 1, meaning single file upload. By defining a higher number, + * multiple uploads become possible. + */ + public $maxFiles = 1; + /** + * @var string the error message used when a file is not uploaded correctly. + */ + public $message; + /** + * @var string the error message used when no file is uploaded. + */ + public $uploadRequired; + /** * @var string the error message used when the uploaded file is too large. - * @see maxSize + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the maximum size allowed (see [[getSizeLimit()]]) */ - public $tooLarge; + public $tooBig; /** * @var string the error message used when the uploaded file is too small. - * @see minSize + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[minSize]] */ public $tooSmall; /** * @var string the error message used when the uploaded file has an extension name - * that is not listed among {@link extensions}. + * that is not listed in [[extensions]]. You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {extensions}: the list of the allowed extensions. */ public $wrongType; /** - * @var integer the maximum file count the given attribute can hold. - * It defaults to 1, meaning single file upload. By defining a higher number, - * multiple uploads become possible. - */ - public $maxFiles = 1; - /** - * @var string the error message used if the count of multiple uploads exceeds - * limit. + * @var string the error message used if the count of multiple uploads exceeds limit. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[maxFiles]] */ public $tooMany; /** - * Set the attribute and then validates using {@link validateFile}. - * If there is any error, the error message is added to the object. - * @param \yii\base\Model $object the object being validated - * @param string $attribute the attribute being validated + * Initializes the validator. */ - public function validateAttribute($object, $attribute) + public function init() { - if ($this->maxFiles > 1) - { - $files = $object->$attribute; - if (!is_array($files) || !isset($files[0]) || !$files[0] instanceof CUploadedFile) - $files = CUploadedFile::getInstances($object, $attribute); - if (array() === $files) - return $this->emptyAttribute($object, $attribute); - if (count($files) > $this->maxFiles) - { - $message = $this->tooMany !== null ? $this->tooMany : \Yii::t('yii|{attribute} cannot accept more than {limit} files.'); - $this->addError($object, $attribute, $message, array('{attribute}' => $attribute, '{limit}' => $this->maxFiles)); - } else - foreach ($files as $file) - $this->validateFile($object, $attribute, $file); - } else - { - $file = $object->$attribute; - if (!$file instanceof CUploadedFile) - { - $file = CUploadedFile::getInstance($object, $attribute); - if (null === $file) - return $this->emptyAttribute($object, $attribute); - } - $this->validateFile($object, $attribute, $file); + parent::init(); + if ($this->message === null) { + $this->message = Yii::t('yii|File upload failed.'); + } + if ($this->uploadRequired === null) { + $this->uploadRequired = Yii::t('yii|Please upload a file.'); } + if ($this->tooMany === null) { + $this->tooMany = Yii::t('yii|You can upload at most {limit} files.'); + } + if ($this->wrongType === null) { + $this->wrongType = Yii::t('yii|Only files with these extensions are allowed: {extensions}.'); + } + if ($this->tooBig === null) { + $this->tooBig = Yii::t('yii|The file "{file}" is too big. Its size cannot exceed {limit} bytes.'); + } + if ($this->tooSmall === null) { + $this->tooSmall = Yii::t('yii|The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.'); + } + if (!is_array($this->types)) { + $this->types = preg_split('/[\s,]+/', strtolower($this->types), -1, PREG_SPLIT_NO_EMPTY); + } } /** - * Internally validates a file object. + * Validates the attribute. * @param \yii\base\Model $object the object being validated * @param string $attribute the attribute being validated - * @param CUploadedFile $file uploaded file passed to check against a set of rules */ - public function validateFile($object, $attribute, $file) + public function validateAttribute($object, $attribute) { - if (null === $file || ($error = $file->getError()) == UPLOAD_ERR_NO_FILE) - return $this->emptyAttribute($object, $attribute); - elseif ($error == UPLOAD_ERR_INI_SIZE || $error == UPLOAD_ERR_FORM_SIZE || $this->maxSize !== null && $file->getSize() > $this->maxSize) - { - $message = $this->tooLarge !== null ? $this->tooLarge : \Yii::t('yii|The file "{file}" is too large. Its size cannot exceed {limit} bytes.'); - $this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit())); - } elseif ($error == UPLOAD_ERR_PARTIAL) - throw new CException(\Yii::t('yii|The file "{file}" was only partially uploaded.', array('{file}' => $file->getName()))); - elseif ($error == UPLOAD_ERR_NO_TMP_DIR) - throw new CException(\Yii::t('yii|Missing the temporary folder to store the uploaded file "{file}".', array('{file}' => $file->getName()))); - elseif ($error == UPLOAD_ERR_CANT_WRITE) - throw new CException(\Yii::t('yii|Failed to write the uploaded file "{file}" to disk.', array('{file}' => $file->getName()))); - elseif (defined('UPLOAD_ERR_EXTENSION') && $error == UPLOAD_ERR_EXTENSION) // available for PHP 5.2.0 or above - throw new CException(\Yii::t('yii|File upload was stopped by extension.')); - - if ($this->minSize !== null && $file->getSize() < $this->minSize) - { - $message = $this->tooSmall !== null ? $this->tooSmall : \Yii::t('yii|The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.'); - $this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{limit}' => $this->minSize)); - } - - if ($this->types !== null) - { - if (is_string($this->types)) - $types = preg_split('/[\s,]+/', strtolower($this->types), -1, PREG_SPLIT_NO_EMPTY); - else - $types = $this->types; - if (!in_array(strtolower($file->getExtensionName()), $types)) - { - $message = $this->wrongType !== null ? $this->wrongType : \Yii::t('yii|The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.'); - $this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{extensions}' => implode(', ', $types))); + if ($this->maxFiles > 1) { + $files = $object->$attribute; + if (!is_array($files)) { + $this->addError($object, $attribute, $this->uploadRequired); + return; + } + foreach ($files as $i => $file) { + if (!$file instanceof UploadedFile || $file->getError() == UPLOAD_ERR_NO_FILE) { + unset($files[$i]); + } + } + $object->$attribute = array_values($files); + if ($files === array()) { + $this->addError($object, $attribute, $this->uploadRequired); + } + if (count($files) > $this->maxFiles) { + $this->addError($object, $attribute, $this->tooMany, array('{attribute}' => $attribute, '{limit}' => $this->maxFiles)); + } else { + foreach ($files as $file) { + $this->validateFile($object, $attribute, $file); + } + } + } else { + $file = $object->$attribute; + if ($file instanceof UploadedFile && $file->getError() != UPLOAD_ERR_NO_FILE) { + $this->validateFile($object, $attribute, $file); + } else { + $this->addError($object, $attribute, $this->uploadRequired); } } } /** - * Raises an error to inform end user about blank attribute. + * Internally validates a file object. * @param \yii\base\Model $object the object being validated * @param string $attribute the attribute being validated + * @param UploadedFile $file uploaded file passed to check against a set of rules */ - public function emptyAttribute($object, $attribute) + protected function validateFile($object, $attribute, $file) { - if (!$this->allowEmpty) - { - $message = $this->message !== null ? $this->message : \Yii::t('yii|{attribute} cannot be blank.'); - $this->addError($object, $attribute, $message); + switch ($file->getError()) { + case UPLOAD_ERR_OK: + if ($this->maxSize !== null && $file->getSize() > $this->maxSize) { + $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit())); + } + if ($this->minSize !== null && $file->getSize() < $this->minSize) { + $this->addError($object, $attribute, $this->tooSmall, array('{file}' => $file->getName(), '{limit}' => $this->minSize)); + } + if (!empty($this->types) && !in_array(strtolower(FileHelper::getExtension($file->getName())), $this->types, true)) { + $this->addError($object, $attribute, $this->wrongType, array('{file}' => $file->getName(), '{extensions}' => implode(', ', $this->types))); + } + break; + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit())); + break; + case UPLOAD_ERR_PARTIAL: + $this->addError($object, $attribute, $this->message); + Yii::warning('File was only partially uploaded: ' . $file->getName(), __METHOD__); + break; + case UPLOAD_ERR_NO_TMP_DIR: + $this->addError($object, $attribute, $this->message); + Yii::warning('Missing the temporary folder to store the uploaded file: ' . $file->getName(), __METHOD__); + break; + case UPLOAD_ERR_CANT_WRITE: + $this->addError($object, $attribute, $this->message); + Yii::warning('Failed to write the uploaded file to disk: ', $file->getName(), __METHOD__); + break; + case UPLOAD_ERR_EXTENSION: + $this->addError($object, $attribute, $this->message); + Yii::warning('File upload was stopped by some PHP extension: ', $file->getName(), __METHOD__); + break; + default: + break; } } /** * Returns the maximum size allowed for uploaded files. * This is determined based on three factors: - * + * + * - 'upload_max_filesize' in php.ini + * - 'MAX_FILE_SIZE' hidden field + * - [[maxSize]] * * @return integer the size limit for uploaded files. */ @@ -203,10 +218,12 @@ class CFileValidator extends Validator { $limit = ini_get('upload_max_filesize'); $limit = $this->sizeToBytes($limit); - if ($this->maxSize !== null && $limit > 0 && $this->maxSize < $limit) + if ($this->maxSize !== null && $limit > 0 && $this->maxSize < $limit) { $limit = $this->maxSize; - if (isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE'] > 0 && $_POST['MAX_FILE_SIZE'] < $limit) - $limit = $_POST['MAX_FILE_SIZE']; + } + if (isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE'] > 0 && $_POST['MAX_FILE_SIZE'] < $limit) { + $limit = (int)$_POST['MAX_FILE_SIZE']; + } return $limit; } @@ -218,12 +235,18 @@ class CFileValidator extends Validator */ private function sizeToBytes($sizeStr) { - switch (substr($sizeStr, -1)) - { - case 'M': case 'm': return (int)$sizeStr * 1048576; - case 'K': case 'k': return (int)$sizeStr * 1024; - case 'G': case 'g': return (int)$sizeStr * 1073741824; - default: return (int)$sizeStr; + switch (substr($sizeStr, -1)) { + case 'M': + case 'm': + return (int)$sizeStr * 1048576; + case 'K': + case 'k': + return (int)$sizeStr * 1024; + case 'G': + case 'g': + return (int)$sizeStr * 1073741824; + default: + return (int)$sizeStr; } } } \ No newline at end of file diff --git a/framework/web/UploadedFile.php b/framework/web/UploadedFile.php new file mode 100644 index 0000000..c67281c --- /dev/null +++ b/framework/web/UploadedFile.php @@ -0,0 +1,246 @@ + + * @since 2.0 + */ +class UploadedFile extends \yii\base\Object +{ + private static $_files; + private $_name; + private $_tempName; + private $_type; + private $_size; + private $_error; + + + /** + * Constructor. + * Instead of using the constructor to create a new instance, + * you should normally call [[getInstance()]] or [[getInstances()]] + * to obtain new instances. + * @param string $name the original name of the file being uploaded + * @param string $tempName the path of the uploaded file on the server. + * @param string $type the MIME-type of the uploaded file (such as "image/gif"). + * @param integer $size the actual size of the uploaded file in bytes + * @param integer $error the error code + */ + public function __construct($name, $tempName, $type, $size, $error) + { + $this->_name = $name; + $this->_tempName = $tempName; + $this->_type = $type; + $this->_size = $size; + $this->_error = $error; + } + + /** + * String output. + * This is PHP magic method that returns string representation of an object. + * The implementation here returns the uploaded file's name. + * @return string the string representation of the object + */ + public function __toString() + { + return $this->_name; + } + + /** + * Returns an uploaded file for the given model attribute. + * The file should be uploaded using [[ActiveForm::fileInput()]]. + * @param \yii\base\Model $model the data model + * @param string $attribute the attribute name. The attribute name may contain array indexes. + * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array. + * @return UploadedFile the instance of the uploaded file. + * Null is returned if no file is uploaded for the specified model attribute. + * @see getInstanceByName + */ + public static function getInstance($model, $attribute) + { + $name = ActiveForm::getInputName($model, $attribute); + return static::getInstanceByName($name); + } + + /** + * Returns all uploaded files for the given model attribute. + * @param \yii\base\Model $model the data model + * @param string $attribute the attribute name. The attribute name may contain array indexes + * for tabular file uploading, e.g. '[1]file'. + * @return UploadedFile[] array of UploadedFile objects. + * Empty array is returned if no available file was found for the given attribute. + */ + public static function getInstances($model, $attribute) + { + $name = ActiveForm::getInputName($model, $attribute); + return static::getInstancesByName($name); + } + + /** + * Returns an uploaded file according to the given file input name. + * The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]'). + * @param string $name the name of the file input field. + * @return UploadedFile the instance of the uploaded file. + * Null is returned if no file is uploaded for the specified name. + */ + public static function getInstanceByName($name) + { + $files = static::loadFiles(); + return isset($files[$name]) ? $files[$name] : null; + } + + /** + * Returns an array of uploaded files corresponding to the specified file input name. + * This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]', + * 'files[n]'..., and you can retrieve them all by passing 'files' as the name. + * @param string $name the name of the array of files + * @return UploadedFile[] the array of CUploadedFile objects. Empty array is returned + * if no adequate upload was found. Please note that this array will contain + * all files from all sub-arrays regardless how deeply nested they are. + */ + public static function getInstancesByName($name) + { + $files = static::loadFiles(); + if (isset($files[$name])) { + return array($files[$name]); + } + $results = array(); + foreach ($files as $key => $file) { + if (strpos($key, "{$name}[") === 0) { + $results[] = self::$_files[$key]; + } + } + return $results; + } + + /** + * Cleans up the loaded UploadedFile instances. + * This method is mainly used by test scripts to set up a fixture. + */ + public static function reset() + { + self::$_files = null; + } + + /** + * Saves the uploaded file. + * Note that this method uses php's move_uploaded_file() method. If the target file `$file` + * already exists, it will be overwritten. + * @param string $file the file path used to save the uploaded file + * @param boolean $deleteTempFile whether to delete the temporary file after saving. + * If true, you will not be able to save the uploaded file again in the current request. + * @return boolean true whether the file is saved successfully + * @see error + */ + public function saveAs($file, $deleteTempFile = true) + { + if ($this->_error == UPLOAD_ERR_OK) { + if ($deleteTempFile) { + return move_uploaded_file($this->_tempName, $file); + } elseif (is_uploaded_file($this->_tempName)) { + return copy($this->_tempName, $file); + } + } + return false; + } + + /** + * @return string the original name of the file being uploaded + */ + public function getName() + { + return $this->_name; + } + + /** + * @return string the path of the uploaded file on the server. + * Note, this is a temporary file which will be automatically deleted by PHP + * after the current request is processed. + */ + public function getTempName() + { + return $this->_tempName; + } + + /** + * @return string the MIME-type of the uploaded file (such as "image/gif"). + * Since this MIME type is not checked on the server side, do not take this value for granted. + * Instead, use [[FileHelper::getMimeType()]] to determine the exact MIME type. + */ + public function getType() + { + return $this->_type; + } + + /** + * @return integer the actual size of the uploaded file in bytes + */ + public function getSize() + { + return $this->_size; + } + + /** + * Returns an error code describing the status of this file uploading. + * @return integer the error code + * @see http://www.php.net/manual/en/features.file-upload.errors.php + */ + public function getError() + { + return $this->_error; + } + + /** + * @return boolean whether there is an error with the uploaded file. + * Check [[error]] for detailed error code information. + */ + public function getHasError() + { + return $this->_error != UPLOAD_ERR_OK; + } + + /** + * Creates UploadedFile instances from $_FILE. + * @return array the UploadedFile instances + */ + private static function loadFiles() + { + if (self::$_files === null) { + self::$_files = array(); + if (isset($_FILES) && is_array($_FILES)) { + foreach ($_FILES as $class => $info) { + self::loadFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']); + } + } + } + return self::$_files; + } + + /** + * Creates UploadedFile instances from $_FILE recursively. + * @param string $key key for identifying uploaded file: class name and sub-array indexes + * @param mixed $names file names provided by PHP + * @param mixed $tempNames temporary file names provided by PHP + * @param mixed $types file types provided by PHP + * @param mixed $sizes file sizes provided by PHP + * @param mixed $errors uploading issues provided by PHP + */ + private static function loadFilesRecursive($key, $names, $tempNames, $types, $sizes, $errors) + { + if (is_array($names)) { + foreach ($names as $i => $name) { + self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]); + } + } else { + self::$_files[$key] = new self($names, $tempNames, $types, $sizes, $errors); + } + } +} diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index 8ac5365..48bc181 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -52,10 +52,6 @@ class ActiveForm extends Widget public $enableClientValidation = false; public $options = array(); - /** - * @var array model-class mapped to name prefix - */ - public $modelMap; /** * @param Model|Model[] $models @@ -240,35 +236,6 @@ class ActiveForm extends Widget return Html::radioList($name, $checked, $items, $options); } - public function getInputName($model, $attribute) - { - $class = get_class($model); - if (isset($this->modelMap[$class])) { - $class = $this->modelMap[$class]; - } elseif (($pos = strrpos($class, '\\')) !== false) { - $class = substr($class, $pos + 1); - } - if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - $prefix = $matches[1]; - $attribute = $matches[2]; - $suffix = $matches[3]; - if ($class === '' && $prefix === '') { - return $attribute . $suffix; - } elseif ($class !== '') { - return $class . $prefix . "[$attribute]" . $suffix; - } else { - throw new InvalidParamException('Model name cannot be mapped to empty for tabular inputs.'); - } - } - - public function getInputId($model, $attribute) - { - $name = $this->getInputName($model, $attribute); - return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); - } - public function getAttributeValue($model, $attribute) { if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { @@ -299,4 +266,34 @@ class ActiveForm extends Widget throw new InvalidParamException('Attribute name must contain word characters only.'); } } + + /** + * @param Model $model + * @param string $attribute + * @return string + * @throws \yii\base\InvalidParamException + */ + public static function getInputName($model, $attribute) + { + $formName = $model->formName(); + if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $prefix = $matches[1]; + $attribute = $matches[2]; + $suffix = $matches[3]; + if ($formName === '' && $prefix === '') { + return $attribute . $suffix; + } elseif ($formName !== '') { + return $formName . $prefix . "[$attribute]" . $suffix; + } else { + throw new InvalidParamException(get_class($model) . '::formName() cannot be empty for tabular inputs.'); + } + } + + public static function getInputId($model, $attribute) + { + $name = static::getInputName($model, $attribute); + return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); + } }