From 3f2e7fa604a1af8a852780d2e9cf06b3baf35b18 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 2 Sep 2013 20:25:39 -0400 Subject: [PATCH 001/157] crud wip --- framework/yii/gii/generators/crud/Generator.php | 22 ++- framework/yii/gii/generators/crud/form.php | 6 + .../gii/generators/crud/templates/controller.php | 182 ++++++++++++++++++++- .../yii/gii/generators/crud/templates/model.php | 8 - .../yii/gii/generators/crud/templates/search.php | 8 + .../gii/generators/crud/templates/views/_form.php | 49 ++++++ .../generators/crud/templates/views/_search.php | 38 +++++ .../gii/generators/crud/templates/views/_view.php | 31 ++++ .../gii/generators/crud/templates/views/create.php | 27 +++ .../generators/crud/templates/views/index-grid.php | 73 +++++++++ .../gii/generators/crud/templates/views/index.php | 29 ++++ .../gii/generators/crud/templates/views/update.php | 31 ++++ .../gii/generators/crud/templates/views/view.php | 39 +++++ framework/yii/gii/views/default/view.php | 1 + 14 files changed, 527 insertions(+), 17 deletions(-) delete mode 100644 framework/yii/gii/generators/crud/templates/model.php create mode 100644 framework/yii/gii/generators/crud/templates/search.php create mode 100644 framework/yii/gii/generators/crud/templates/views/_form.php create mode 100644 framework/yii/gii/generators/crud/templates/views/_search.php create mode 100644 framework/yii/gii/generators/crud/templates/views/_view.php create mode 100644 framework/yii/gii/generators/crud/templates/views/create.php create mode 100644 framework/yii/gii/generators/crud/templates/views/index-grid.php create mode 100644 framework/yii/gii/generators/crud/templates/views/index.php create mode 100644 framework/yii/gii/generators/crud/templates/views/update.php create mode 100644 framework/yii/gii/generators/crud/templates/views/view.php diff --git a/framework/yii/gii/generators/crud/Generator.php b/framework/yii/gii/generators/crud/Generator.php index 2b6697d..7029e7c 100644 --- a/framework/yii/gii/generators/crud/Generator.php +++ b/framework/yii/gii/generators/crud/Generator.php @@ -7,6 +7,7 @@ namespace yii\gii\generators\crud; +use yii\base\Model; use yii\db\ActiveRecord; use yii\gii\CodeFile; use yii\web\Controller; @@ -21,6 +22,9 @@ class Generator extends \yii\gii\Generator public $modelClass; public $controllerID; public $baseControllerClass = 'yii\web\Controller'; + public $indexWidgetType = 'grid'; + public $enableSearch = true; + public $searchModelClass; public function getName() { @@ -36,9 +40,9 @@ class Generator extends \yii\gii\Generator public function rules() { return array_merge(parent::rules(), array( - array('modelClass, controllerID, baseControllerClass', 'filter', 'filter' => 'trim'), - array('modelClass, controllerID, baseControllerClass', 'required'), - array('modelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), + array('modelClass, searchModelClass, controllerID, baseControllerClass', 'filter', 'filter' => 'trim'), + array('modelClass, searchModelClass, controllerID, baseControllerClass', 'required'), + array('modelClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), array('modelClass', 'validateClass', 'params' => array('extends' => ActiveRecord::className())), array('controllerID', 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'), array('baseControllerClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), @@ -52,6 +56,9 @@ class Generator extends \yii\gii\Generator 'modelClass' => 'Model Class', 'controllerID' => 'Controller ID', 'baseControllerClass' => 'Base Controller Class', + 'indexWidgetType' => 'Widget Used in Index Page', + 'enableSearch' => 'Enable Search', + 'searchModelClass' => 'Search Model Class', )); } @@ -72,6 +79,13 @@ class Generator extends \yii\gii\Generator ', 'baseControllerClass' => 'This is the class that the new CRUD controller class will extend from. You should provide a fully qualified class name, e.g., yii\web\Controller.', + 'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models. + You may choose either GridView or ListView', + 'enableSearch' => 'Whether to enable the search functionality on the index page. When search is enabled, + a search form will be displayed on the index page, and the index page will display the search results.', + 'searchModelClass' => 'This is the class representing the data being collecting in the search form. + A fully qualified namespaced class name is required, e.g., app\models\PostSearchForm. + This is only used when search is enabled.', ); } @@ -87,7 +101,7 @@ class Generator extends \yii\gii\Generator */ public function stickyAttributes() { - return array('baseControllerClass'); + return array('baseControllerClass', 'indexWidgetType', 'enableSearch'); } /** diff --git a/framework/yii/gii/generators/crud/form.php b/framework/yii/gii/generators/crud/form.php index 0951695..095791f 100644 --- a/framework/yii/gii/generators/crud/form.php +++ b/framework/yii/gii/generators/crud/form.php @@ -8,3 +8,9 @@ echo $form->field($generator, 'modelClass'); echo $form->field($generator, 'controllerID'); echo $form->field($generator, 'baseControllerClass'); +echo $form->field($generator, 'indexWidgetType')->dropDownList(array( + 'grid' => 'GridView', + 'list' => 'ListView', +)); +echo $form->field($generator, 'enableSearch')->checkbox(); +echo $form->field($generator, 'searchModelClass'); diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/framework/yii/gii/generators/crud/templates/controller.php index f372629..47c193f 100644 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ b/framework/yii/gii/generators/crud/templates/controller.php @@ -1,8 +1,180 @@ + + +class controllerClass; ?> extends baseControllerClass."\n"; ?> +{ + /** + * @var string the default layout for the views. Defaults to '//layouts/column2', meaning + * using two-column layout. See 'protected/views/layouts/column2.php'. + */ + public $layout='//layouts/column2'; + + /** + * @return array action filters + */ + public function filters() + { + return array( + 'accessControl', // perform access control for CRUD operations + 'postOnly + delete', // we only allow deletion via POST request + ); + } + + /** + * Specifies the access control rules. + * This method is used by the 'accessControl' filter. + * @return array access control rules + */ + public function accessRules() + { + return array( + array('allow', // allow all users to perform 'index' and 'view' actions + 'actions'=>array('index','view'), + 'users'=>array('*'), + ), + array('allow', // allow authenticated user to perform 'create' and 'update' actions + 'actions'=>array('create','update'), + 'users'=>array('@'), + ), + array('allow', // allow admin user to perform 'admin' and 'delete' actions + 'actions'=>array('admin','delete'), + 'users'=>array('admin'), + ), + array('deny', // deny all users + 'users'=>array('*'), + ), + ); + } + + /** + * Displays a particular model. + * @param integer $id the ID of the model to be displayed + */ + public function actionView($id) + { + $this->render('view',array( + 'model'=>$this->loadModel($id), + )); + } + + /** + * Creates a new model. + * If creation is successful, the browser will be redirected to the 'view' page. + */ + public function actionCreate() + { + $model=new modelClass; ?>; + + // Uncomment the following line if AJAX validation is needed + // $this->performAjaxValidation($model); + + if(isset($_POST['modelClass; ?>'])) + { + $model->attributes=$_POST['modelClass; ?>']; + if($model->save()) + $this->redirect(array('view','id'=>$model->tableSchema->primaryKey; ?>)); + } + + $this->render('create',array( + 'model'=>$model, + )); + } + + /** + * Updates a particular model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id the ID of the model to be updated + */ + public function actionUpdate($id) + { + $model=$this->loadModel($id); + + // Uncomment the following line if AJAX validation is needed + // $this->performAjaxValidation($model); + + if(isset($_POST['modelClass; ?>'])) + { + $model->attributes=$_POST['modelClass; ?>']; + if($model->save()) + $this->redirect(array('view','id'=>$model->tableSchema->primaryKey; ?>)); + } + + $this->render('update',array( + 'model'=>$model, + )); + } + + /** + * Deletes a particular model. + * If deletion is successful, the browser will be redirected to the 'admin' page. + * @param integer $id the ID of the model to be deleted + */ + public function actionDelete($id) + { + $this->loadModel($id)->delete(); + + // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser + if(!isset($_GET['ajax'])) + $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin')); + } + + /** + * Lists all models. + */ + public function actionIndex() + { + $dataProvider=new CActiveDataProvider('modelClass; ?>'); + $this->render('index',array( + 'dataProvider'=>$dataProvider, + )); + } + + /** + * Manages all models. + */ + public function actionAdmin() + { + $model=new modelClass; ?>('search'); + $model->unsetAttributes(); // clear any default values + if(isset($_GET['modelClass; ?>'])) + $model->attributes=$_GET['modelClass; ?>']; + + $this->render('admin',array( + 'model'=>$model, + )); + } + + /** + * Returns the data model based on the primary key given in the GET variable. + * If the data model is not found, an HTTP exception will be raised. + * @param integer $id the ID of the model to be loaded + * @return modelClass; ?> the loaded model + * @throws CHttpException + */ + public function loadModel($id) + { + $model=modelClass; ?>::model()->findByPk($id); + if($model===null) + throw new CHttpException(404,'The requested page does not exist.'); + return $model; + } + + /** + * Performs the AJAX validation. + * @param modelClass; ?> $model the model to be validated + */ + protected function performAjaxValidation($model) + { + if(isset($_POST['ajax']) && $_POST['ajax']==='class2id($this->modelClass); ?>-form') + { + echo CActiveForm::validate($model); + Yii::app()->end(); + } + } +} diff --git a/framework/yii/gii/generators/crud/templates/model.php b/framework/yii/gii/generators/crud/templates/model.php deleted file mode 100644 index f372629..0000000 --- a/framework/yii/gii/generators/crud/templates/model.php +++ /dev/null @@ -1,8 +0,0 @@ - + +/* @var $this getControllerClass(); ?> */ +/* @var $model getModelClass(); ?> */ +/* @var $form CActiveForm */ +?> + +
+ +beginWidget('CActiveForm', array( + 'id'=>'".$this->class2id($this->modelClass)."-form', + // Please note: When you enable ajax validation, make sure the corresponding + // controller action is handling ajax validation correctly. + // There is a call to performAjaxValidation() commented in generated controller code. + // See class documentation of CActiveForm for details on this. + 'enableAjaxValidation'=>false, +)); ?>\n"; ?> + +

Fields with * are required.

+ + errorSummary(\$model); ?>\n"; ?> + +tableSchema->columns as $column) +{ + if($column->autoIncrement) + continue; +?> +
+ generateActiveLabel($this->modelClass,$column)."; ?>\n"; ?> + generateActiveField($this->modelClass,$column)."; ?>\n"; ?> + error(\$model,'{$column->name}'); ?>\n"; ?> +
+ + +
+ isNewRecord ? 'Create' : 'Save'); ?>\n"; ?> +
+ +endWidget(); ?>\n"; ?> + +
\ No newline at end of file diff --git a/framework/yii/gii/generators/crud/templates/views/_search.php b/framework/yii/gii/generators/crud/templates/views/_search.php new file mode 100644 index 0000000..a9679f1 --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/_search.php @@ -0,0 +1,38 @@ + + +/* @var $this getControllerClass(); ?> */ +/* @var $model getModelClass(); ?> */ +/* @var $form CActiveForm */ +?> + +
+ +beginWidget('CActiveForm', array( + 'action'=>Yii::app()->createUrl(\$this->route), + 'method'=>'get', +)); ?>\n"; ?> + +tableSchema->columns as $column): ?> +generateInputField($this->modelClass,$column); + if(strpos($field,'password')!==false) + continue; +?> +
+ label(\$model,'{$column->name}'); ?>\n"; ?> + generateActiveField($this->modelClass,$column)."; ?>\n"; ?> +
+ + +
+ \n"; ?> +
+ +endWidget(); ?>\n"; ?> + +
\ No newline at end of file diff --git a/framework/yii/gii/generators/crud/templates/views/_view.php b/framework/yii/gii/generators/crud/templates/views/_view.php new file mode 100644 index 0000000..0f11051 --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/_view.php @@ -0,0 +1,31 @@ + + +/* @var $this getControllerClass(); ?> */ +/* @var $data getModelClass(); ?> */ +?> + +
+ +getAttributeLabel('{$this->tableSchema->primaryKey}')); ?>:\n"; +echo "\t{$this->tableSchema->primaryKey}), array('view', 'id'=>\$data->{$this->tableSchema->primaryKey})); ?>\n\t
\n\n"; +$count=0; +foreach($this->tableSchema->columns as $column) +{ + if($column->isPrimaryKey) + continue; + if(++$count==7) + echo "\tgetAttributeLabel('{$column->name}')); ?>:\n"; + echo "\t{$column->name}); ?>\n\t
\n\n"; +} +if($count>=7) + echo "\t*/ ?>\n"; +?> + +
\ No newline at end of file diff --git a/framework/yii/gii/generators/crud/templates/views/create.php b/framework/yii/gii/generators/crud/templates/views/create.php new file mode 100644 index 0000000..9fd3ccf --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/create.php @@ -0,0 +1,27 @@ + + +/* @var $this getControllerClass(); ?> */ +/* @var $model getModelClass(); ?> */ + +pluralize($this->class2name($this->modelClass)); +echo "\$this->breadcrumbs=array( + '$label'=>array('index'), + 'Create', +);\n"; +?> + +$this->menu=array( + array('label'=>'List modelClass; ?>', 'url'=>array('index')), + array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), +); +?> + +

Create modelClass; ?>

+ +renderPartial('_form', array('model'=>\$model)); ?>"; ?> diff --git a/framework/yii/gii/generators/crud/templates/views/index-grid.php b/framework/yii/gii/generators/crud/templates/views/index-grid.php new file mode 100644 index 0000000..1884515 --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/index-grid.php @@ -0,0 +1,73 @@ + + +/* @var $this getControllerClass(); ?> */ +/* @var $model getModelClass(); ?> */ + +pluralize($this->class2name($this->modelClass)); +echo "\$this->breadcrumbs=array( + '$label'=>array('index'), + 'Manage', +);\n"; +?> + +$this->menu=array( + array('label'=>'List modelClass; ?>', 'url'=>array('index')), + array('label'=>'Create modelClass; ?>', 'url'=>array('create')), +); + +Yii::app()->clientScript->registerScript('search', " +$('.search-button').click(function(){ + $('.search-form').toggle(); + return false; +}); +$('.search-form form').submit(function(){ + $('#class2id($this->modelClass); ?>-grid').yiiGridView('update', { + data: $(this).serialize() + }); + return false; +}); +"); +?> + +

Manage pluralize($this->class2name($this->modelClass)); ?>

+ +

+You may optionally enter a comparison operator (<, <=, >, >=, <> +or =) at the beginning of each of your search values to specify how the comparison should be done. +

+ +'search-button')); ?>"; ?> + + + + $this->widget('zii.widgets.grid.CGridView', array( + 'id'=>'class2id($this->modelClass); ?>-grid', + 'dataProvider'=>$model->search(), + 'filter'=>$model, + 'columns'=>array( +tableSchema->columns as $column) +{ + if(++$count==7) + echo "\t\t/*\n"; + echo "\t\t'".$column->name."',\n"; +} +if($count>=7) + echo "\t\t*/\n"; +?> + array( + 'class'=>'CButtonColumn', + ), + ), +)); ?> diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php new file mode 100644 index 0000000..a115251 --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -0,0 +1,29 @@ + + +/* @var $this getControllerClass(); ?> */ +/* @var $dataProvider CActiveDataProvider */ + +pluralize($this->class2name($this->modelClass)); +echo "\$this->breadcrumbs=array( + '$label', +);\n"; +?> + +$this->menu=array( + array('label'=>'Create modelClass; ?>', 'url'=>array('create')), + array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), +); +?> + +

+ + $this->widget('zii.widgets.CListView', array( + 'dataProvider'=>$dataProvider, + 'itemView'=>'_view', +)); ?> diff --git a/framework/yii/gii/generators/crud/templates/views/update.php b/framework/yii/gii/generators/crud/templates/views/update.php new file mode 100644 index 0000000..4adc72d --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/update.php @@ -0,0 +1,31 @@ + + +/* @var $this getControllerClass(); ?> */ +/* @var $model getModelClass(); ?> */ + +guessNameColumn($this->tableSchema->columns); +$label=$this->pluralize($this->class2name($this->modelClass)); +echo "\$this->breadcrumbs=array( + '$label'=>array('index'), + \$model->{$nameColumn}=>array('view','id'=>\$model->{$this->tableSchema->primaryKey}), + 'Update', +);\n"; +?> + +$this->menu=array( + array('label'=>'List modelClass; ?>', 'url'=>array('index')), + array('label'=>'Create modelClass; ?>', 'url'=>array('create')), + array('label'=>'View modelClass; ?>', 'url'=>array('view', 'id'=>$model->tableSchema->primaryKey; ?>)), + array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), +); +?> + +

Update modelClass." {$this->tableSchema->primaryKey}; ?>"; ?>

+ +renderPartial('_form', array('model'=>\$model)); ?>"; ?> \ No newline at end of file diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php new file mode 100644 index 0000000..3363130 --- /dev/null +++ b/framework/yii/gii/generators/crud/templates/views/view.php @@ -0,0 +1,39 @@ + + +/* @var $this getControllerClass(); ?> */ +/* @var $model getModelClass(); ?> */ + +guessNameColumn($this->tableSchema->columns); +$label=$this->pluralize($this->class2name($this->modelClass)); +echo "\$this->breadcrumbs=array( + '$label'=>array('index'), + \$model->{$nameColumn}, +);\n"; +?> + +$this->menu=array( + array('label'=>'List modelClass; ?>', 'url'=>array('index')), + array('label'=>'Create modelClass; ?>', 'url'=>array('create')), + array('label'=>'Update modelClass; ?>', 'url'=>array('update', 'id'=>$model->tableSchema->primaryKey; ?>)), + array('label'=>'Delete modelClass; ?>', 'url'=>'#', 'linkOptions'=>array('submit'=>array('delete','id'=>$model->tableSchema->primaryKey; ?>),'confirm'=>'Are you sure you want to delete this item?')), + array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), +); +?> + +

View modelClass." #{$this->tableSchema->primaryKey}; ?>"; ?>

+ + $this->widget('zii.widgets.CDetailView', array( + 'data'=>$model, + 'attributes'=>array( +tableSchema->columns as $column) + echo "\t\t'".$column->name."',\n"; +?> + ), +)); ?> diff --git a/framework/yii/gii/views/default/view.php b/framework/yii/gii/views/default/view.php index c61d5d0..bf05e84 100644 --- a/framework/yii/gii/views/default/view.php +++ b/framework/yii/gii/views/default/view.php @@ -30,6 +30,7 @@ foreach ($generator->templates as $name => $path) { "$id-generator", + 'successCssClass' => '', 'fieldConfig' => array('class' => ActiveField::className()), )); ?>
From 7f4e02cb4599cfc17f4f3c3a64e74f452a3994f7 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 3 Sep 2013 19:36:23 -0400 Subject: [PATCH 002/157] crud generator WIP --- .../yii/gii/generators/controller/Generator.php | 2 +- framework/yii/gii/generators/crud/Generator.php | 85 +++++++++++++++++++--- .../generators/crud/templates/views/index-grid.php | 73 ------------------- 3 files changed, 77 insertions(+), 83 deletions(-) delete mode 100644 framework/yii/gii/generators/crud/templates/views/index-grid.php diff --git a/framework/yii/gii/generators/controller/Generator.php b/framework/yii/gii/generators/controller/Generator.php index c57b2b2..9de9c17 100644 --- a/framework/yii/gii/generators/controller/Generator.php +++ b/framework/yii/gii/generators/controller/Generator.php @@ -78,7 +78,7 @@ class Generator extends \yii\gii\Generator 'baseClass' => 'Base Class', 'controller' => 'Controller ID', 'actions' => 'Action IDs', - 'ns' => 'Namespace', + 'ns' => 'Controller Namespace', ); } diff --git a/framework/yii/gii/generators/crud/Generator.php b/framework/yii/gii/generators/crud/Generator.php index 7029e7c..983dbc7 100644 --- a/framework/yii/gii/generators/crud/Generator.php +++ b/framework/yii/gii/generators/crud/Generator.php @@ -7,9 +7,11 @@ namespace yii\gii\generators\crud; +use Yii; use yii\base\Model; use yii\db\ActiveRecord; use yii\gii\CodeFile; +use yii\helpers\Inflector; use yii\web\Controller; /** @@ -41,12 +43,15 @@ class Generator extends \yii\gii\Generator { return array_merge(parent::rules(), array( array('modelClass, searchModelClass, controllerID, baseControllerClass', 'filter', 'filter' => 'trim'), - array('modelClass, searchModelClass, controllerID, baseControllerClass', 'required'), + array('modelClass, controllerID, baseControllerClass', 'required'), array('modelClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), array('modelClass', 'validateClass', 'params' => array('extends' => ActiveRecord::className())), array('controllerID', 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'), array('baseControllerClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), array('baseControllerClass', 'validateClass', 'params' => array('extends' => Controller::className())), + array('enableSearch', 'boolean'), + array('indexWidgetType', 'in', 'range' => array('grid', 'list')), + array('searchModelClass', 'validateSearchModelClass'), )); } @@ -104,6 +109,13 @@ class Generator extends \yii\gii\Generator return array('baseControllerClass', 'indexWidgetType', 'enableSearch'); } + public function validateSearchModelClass() + { + if ($this->enableSearch && empty($this->searchModelClass)) { + $this->addError('searchModelClass', 'Search Model Class cannot be empty.'); + } + } + /** * @inheritdoc */ @@ -111,20 +123,75 @@ class Generator extends \yii\gii\Generator { $files = array(); $files[] = new CodeFile( - $this->controllerFile, + $this->getControllerFile(), $this->render('controller.php') ); + $viewPath = $this->getViewPath(); - $files = scandir($this->getTemplatePath()); - foreach ($files as $file) { - if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php' && $file !== 'controller.php') { - $files[] = new CodeFile( - $this->viewPath . DIRECTORY_SEPARATOR . $file, - $this->render($templatePath . '/' . $file) - ); + $templatePath = $this->getTemplatePath() . '/views'; + foreach (scandir($templatePath) as $file) { + if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') { + $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file")); } } + if ($this->enableSearch) { + + } + return $files; } + + + /** + * @return string the controller class name without the namespace part. + */ + public function getControllerClass() + { + return Inflector::id2camel($this->getControllerID()) . 'Controller'; + } + + /** + * @return string the controller ID (without the module ID prefix) + */ + public function getControllerID() + { + if (($pos = strrpos($this->controllerID, '/')) !== false) { + return substr($this->controllerID, $pos + 1); + } else { + return $this->controllerID; + } + } + + /** + * @return \yii\base\Module the module that the new controller belongs to + */ + public function getModule() + { + if (($pos = strpos($this->controllerID, '/')) !== false) { + $id = substr($this->controllerID, 0, $pos); + if (($module = Yii::$app->getModule($id)) !== null) { + return $module; + } + } + return Yii::$app; + } + + /** + * @return string the controller class file path + */ + public function getControllerFile() + { + $module = $this->getModule(); + return $module->getControllerPath() . '/' . $this->getControllerClass() . '.php'; + } + + /** + * @return string the action view file path + */ + public function getViewPath() + { + $module = $this->getModule(); + return $module->getViewPath() . '/' . $this->getControllerID() ; + } } diff --git a/framework/yii/gii/generators/crud/templates/views/index-grid.php b/framework/yii/gii/generators/crud/templates/views/index-grid.php deleted file mode 100644 index 1884515..0000000 --- a/framework/yii/gii/generators/crud/templates/views/index-grid.php +++ /dev/null @@ -1,73 +0,0 @@ - - -/* @var $this getControllerClass(); ?> */ -/* @var $model getModelClass(); ?> */ - -pluralize($this->class2name($this->modelClass)); -echo "\$this->breadcrumbs=array( - '$label'=>array('index'), - 'Manage', -);\n"; -?> - -$this->menu=array( - array('label'=>'List modelClass; ?>', 'url'=>array('index')), - array('label'=>'Create modelClass; ?>', 'url'=>array('create')), -); - -Yii::app()->clientScript->registerScript('search', " -$('.search-button').click(function(){ - $('.search-form').toggle(); - return false; -}); -$('.search-form form').submit(function(){ - $('#class2id($this->modelClass); ?>-grid').yiiGridView('update', { - data: $(this).serialize() - }); - return false; -}); -"); -?> - -

Manage pluralize($this->class2name($this->modelClass)); ?>

- -

-You may optionally enter a comparison operator (<, <=, >, >=, <> -or =) at the beginning of each of your search values to specify how the comparison should be done. -

- -'search-button')); ?>"; ?> - - - - $this->widget('zii.widgets.grid.CGridView', array( - 'id'=>'class2id($this->modelClass); ?>-grid', - 'dataProvider'=>$model->search(), - 'filter'=>$model, - 'columns'=>array( -tableSchema->columns as $column) -{ - if(++$count==7) - echo "\t\t/*\n"; - echo "\t\t'".$column->name."',\n"; -} -if($count>=7) - echo "\t\t*/\n"; -?> - array( - 'class'=>'CButtonColumn', - ), - ), -)); ?> From 8c7d0aebe108c4bda29a31be6d1e578257c2b728 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 4 Sep 2013 15:44:31 +0400 Subject: [PATCH 003/157] Added note about the fact that PHP SSL extension is required to install all Composer dependencies --- apps/advanced/README.md | 2 ++ apps/basic/README.md | 2 ++ apps/benchmark/README.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index 7055360..6860c11 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -74,6 +74,8 @@ You can then install the application using the following command: php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-advanced ~~~ +Note that in order to install some dependencies you must have `php_openssl` extension enabled. + ### Install from an Archive File diff --git a/apps/basic/README.md b/apps/basic/README.md index b5e1ec2..aaa7fba 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -56,6 +56,8 @@ php composer.phar create-project --stability=dev yiisoft/yii2-app-basic yii-basi Now you should be able to access the application using the URL `http://localhost/yii-basic/web/`, assuming `yii-basic` is directly under the document root of your Web server. +Note that in order to install some dependencies you must have `php_openssl` extension enabled. + ### Install from an Archive File diff --git a/apps/benchmark/README.md b/apps/benchmark/README.md index 2aeb0ae..2d5871a 100644 --- a/apps/benchmark/README.md +++ b/apps/benchmark/README.md @@ -54,3 +54,5 @@ http://localhost/yii-benchmark/index.php/site/hello In the above, we assume `yii-benchmark` is directly under the document root of your Web server. +Note that in order to install some dependencies you must have `php_openssl` extension enabled. + From f877de144b273ca0f4278e8cfcda61b05feaf334 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 4 Sep 2013 15:55:32 +0400 Subject: [PATCH 004/157] Removed DebugTarget reference from advanced app template configs --- apps/advanced/environments/dev/backend/config/main-local.php | 9 --------- apps/advanced/environments/dev/frontend/config/main-local.php | 9 --------- apps/advanced/frontend/config/main.php | 1 + 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/apps/advanced/environments/dev/backend/config/main-local.php b/apps/advanced/environments/dev/backend/config/main-local.php index fdc131d..6ad664a 100644 --- a/apps/advanced/environments/dev/backend/config/main-local.php +++ b/apps/advanced/environments/dev/backend/config/main-local.php @@ -5,13 +5,4 @@ return array( // 'class' => 'yii\debug\Module', // ), ), - 'components' => array( - 'log' => array( - 'targets' => array( -// array( -// 'class' => 'yii\log\DebugTarget', -// ) - ), - ), - ), ); diff --git a/apps/advanced/environments/dev/frontend/config/main-local.php b/apps/advanced/environments/dev/frontend/config/main-local.php index f7d77e3..5ad1e61 100644 --- a/apps/advanced/environments/dev/frontend/config/main-local.php +++ b/apps/advanced/environments/dev/frontend/config/main-local.php @@ -5,13 +5,4 @@ return array( // 'class' => 'yii\debug\Module', // ), ), - 'components' => array( - 'log' => array( - 'targets' => array( -// array( -// 'class' => 'yii\log\DebugTarget', -// ) - ), - ), - ), ); diff --git a/apps/advanced/frontend/config/main.php b/apps/advanced/frontend/config/main.php index b9bfdae..ca4a734 100644 --- a/apps/advanced/frontend/config/main.php +++ b/apps/advanced/frontend/config/main.php @@ -14,6 +14,7 @@ return array( 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'controllerNamespace' => 'frontend\controllers', 'modules' => array( + 'gii' => 'yii\gii\Module' ), 'components' => array( 'db' => $params['components.db'], From df85be3622c64d887ff43f0f239eef1b13d465c7 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 4 Sep 2013 16:18:15 +0400 Subject: [PATCH 005/157] Added debug to preload in advanced app template --- apps/advanced/environments/dev/backend/config/main-local.php | 3 +++ apps/advanced/environments/dev/frontend/config/main-local.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/apps/advanced/environments/dev/backend/config/main-local.php b/apps/advanced/environments/dev/backend/config/main-local.php index 6ad664a..2689ed1 100644 --- a/apps/advanced/environments/dev/backend/config/main-local.php +++ b/apps/advanced/environments/dev/backend/config/main-local.php @@ -1,5 +1,8 @@ array( + //'debug', + ), 'modules' => array( // 'debug' => array( // 'class' => 'yii\debug\Module', diff --git a/apps/advanced/environments/dev/frontend/config/main-local.php b/apps/advanced/environments/dev/frontend/config/main-local.php index 5ad1e61..35d10ed 100644 --- a/apps/advanced/environments/dev/frontend/config/main-local.php +++ b/apps/advanced/environments/dev/frontend/config/main-local.php @@ -1,5 +1,8 @@ array( + //'debug', + ), 'modules' => array( // 'debug' => array( // 'class' => 'yii\debug\Module', From 8c753676db71adaa62ad552cd0b0a8a8ba2ff125 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 4 Sep 2013 14:27:20 +0200 Subject: [PATCH 006/157] Exception about missing class in class file only when YII_DEBUG See discussion about exceptions in autoloader here: https://groups.google.com/forum/?fromgroups#!topic/php-fig/kRTVRSIJ0qE --- framework/yii/YiiBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/YiiBase.php b/framework/yii/YiiBase.php index c96a969..f04903d 100644 --- a/framework/yii/YiiBase.php +++ b/framework/yii/YiiBase.php @@ -335,7 +335,7 @@ class YiiBase include($classFile); - if (!class_exists($className, false) && !interface_exists($className, false) && + if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && (!function_exists('trait_exists') || !trait_exists($className, false))) { throw new UnknownClassException("Unable to find '$className' in file: $classFile"); } From b77ea4dcb0f9270ff353bfe2c8e0fcc67af757e9 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 4 Sep 2013 21:03:10 -0400 Subject: [PATCH 007/157] Allow generator to define actions. --- .../yii/gii/controllers/DefaultController.php | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/framework/yii/gii/controllers/DefaultController.php b/framework/yii/gii/controllers/DefaultController.php index 969096b..305ef35 100644 --- a/framework/yii/gii/controllers/DefaultController.php +++ b/framework/yii/gii/controllers/DefaultController.php @@ -7,6 +7,7 @@ namespace yii\gii\controllers; +use Yii; use yii\web\Controller; use yii\web\HttpException; @@ -86,6 +87,26 @@ class DefaultController extends Controller throw new HttpException(404, "Code file not found: $file"); } + /** + * Runs an action defined in the generator. + * Given an action named "xyz", the method "actionXyz()" in the generator will be called. + * If the method does not exist, a 400 HTTP exception will be thrown. + * @param string $id the ID of the generator + * @param string $name the action name + * @return mixed the result of the action. + * @throws HttpException if the action method does not exist. + */ + public function actionAction($id, $name) + { + $generator = $this->loadGenerator($id); + $method = 'action' . $name; + if (method_exists($generator, $method)) { + return $generator->$method(); + } else { + throw new HttpException(400, "Unknown generator action: $name"); + } + } + public function createUrl($route, $params = array()) { if (!isset($params['id']) && $this->generator !== null) { @@ -99,6 +120,18 @@ class DefaultController extends Controller return parent::createUrl($route, $params); } + public function createActionUrl($name, $params = array()) + { + foreach ($this->module->generators as $id => $generator) { + if ($generator === $this->generator) { + $params['id'] = $id; + break; + } + } + $params['name'] = $name; + return parent::createUrl('action', $params); + } + /** * Loads the generator with the specified ID. * @param string $id the ID of the generator to be loaded. From c306fccd46d9f37dbcfeb1673f42ccdcf96ea146 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 5 Sep 2013 12:36:38 +0200 Subject: [PATCH 008/157] fixed FileValidator after UploadedFile refactoring fixes #845 --- framework/yii/validators/FileValidator.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/framework/yii/validators/FileValidator.php b/framework/yii/validators/FileValidator.php index fbd432d..e2880af 100644 --- a/framework/yii/validators/FileValidator.php +++ b/framework/yii/validators/FileValidator.php @@ -135,7 +135,7 @@ class FileValidator extends Validator return; } foreach ($files as $i => $file) { - if (!$file instanceof UploadedFile || $file->getError() == UPLOAD_ERR_NO_FILE) { + if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) { unset($files[$i]); } } @@ -152,7 +152,7 @@ class FileValidator extends Validator } } else { $file = $object->$attribute; - if ($file instanceof UploadedFile && $file->getError() != UPLOAD_ERR_NO_FILE) { + if ($file instanceof UploadedFile && $file->error != UPLOAD_ERR_NO_FILE) { $this->validateFile($object, $attribute, $file); } else { $this->addError($object, $attribute, $this->uploadRequired); @@ -168,37 +168,37 @@ class FileValidator extends Validator */ protected function validateFile($object, $attribute, $file) { - switch ($file->getError()) { + switch ($file->error) { 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->maxSize !== null && $file->size > $this->maxSize) { + $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->name, '{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 ($this->minSize !== null && $file->size < $this->minSize) { + $this->addError($object, $attribute, $this->tooSmall, array('{file}' => $file->name, '{limit}' => $this->minSize)); } - if (!empty($this->types) && !in_array(strtolower(pathinfo($file->getName(), PATHINFO_EXTENSION)), $this->types, true)) { - $this->addError($object, $attribute, $this->wrongType, array('{file}' => $file->getName(), '{extensions}' => implode(', ', $this->types))); + if (!empty($this->types) && !in_array(strtolower(pathinfo($file->name, PATHINFO_EXTENSION)), $this->types, true)) { + $this->addError($object, $attribute, $this->wrongType, array('{file}' => $file->name, '{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())); + $this->addError($object, $attribute, $this->tooBig, array('{file}' => $file->name, '{limit}' => $this->getSizeLimit())); break; case UPLOAD_ERR_PARTIAL: $this->addError($object, $attribute, $this->message); - Yii::warning('File was only partially uploaded: ' . $file->getName(), __METHOD__); + Yii::warning('File was only partially uploaded: ' . $file->name, __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__); + Yii::warning('Missing the temporary folder to store the uploaded file: ' . $file->name, __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__); + Yii::warning('Failed to write the uploaded file to disk: ' . $file->name, __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__); + Yii::warning('File upload was stopped by some PHP extension: ' . $file->name, __METHOD__); break; default: break; From d4995ddc57554a836563cc1545263c1987384546 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 5 Sep 2013 12:40:16 +0200 Subject: [PATCH 009/157] updated php doc --- framework/yii/db/DataReader.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/db/DataReader.php b/framework/yii/db/DataReader.php index d18de4c..f2990c1 100644 --- a/framework/yii/db/DataReader.php +++ b/framework/yii/db/DataReader.php @@ -40,7 +40,7 @@ use yii\base\InvalidCallException; * for more details about possible fetch mode. * * @property integer $columnCount The number of columns in the result set. This property is read-only. - * @property mixed $fetchMode Fetch mode. This property is write-only. + * @property integer $fetchMode Fetch mode. This property is write-only. * @property boolean $isClosed Whether the reader is closed or not. This property is read-only. * @property integer $rowCount Number of rows contained in the result. This property is read-only. * From 8976b7cbd877473501455bf3aed4ffa40739817e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 5 Sep 2013 12:48:52 +0200 Subject: [PATCH 010/157] moved MSSQL specific property to mssql TableSchema --- framework/yii/db/TableSchema.php | 6 ------ framework/yii/db/mssql/Schema.php | 2 +- framework/yii/db/mssql/TableSchema.php | 23 +++++++++++++++++++++++ 3 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 framework/yii/db/mssql/TableSchema.php diff --git a/framework/yii/db/TableSchema.php b/framework/yii/db/TableSchema.php index d599388..910061d 100644 --- a/framework/yii/db/TableSchema.php +++ b/framework/yii/db/TableSchema.php @@ -21,12 +21,6 @@ use yii\base\InvalidParamException; class TableSchema extends Object { /** - * @var string name of the catalog (database) that this table belongs to. - * Defaults to null, meaning no catalog (or the current database). - * This property is only meaningful for MSSQL. - */ - public $catalogName; - /** * @var string name of the schema that this table belongs to. */ public $schemaName; diff --git a/framework/yii/db/mssql/Schema.php b/framework/yii/db/mssql/Schema.php index ad0f7d4..4048fe2 100644 --- a/framework/yii/db/mssql/Schema.php +++ b/framework/yii/db/mssql/Schema.php @@ -7,7 +7,7 @@ namespace yii\db\mssql; -use yii\db\TableSchema; +use yii\db\mssql\TableSchema; use yii\db\ColumnSchema; /** diff --git a/framework/yii/db/mssql/TableSchema.php b/framework/yii/db/mssql/TableSchema.php new file mode 100644 index 0000000..67ad85c --- /dev/null +++ b/framework/yii/db/mssql/TableSchema.php @@ -0,0 +1,23 @@ + + * @since 2.0 + */ +class TableSchema extends \yii\db\TableSchema +{ + /** + * @var string name of the catalog (database) that this table belongs to. + * Defaults to null, meaning no catalog (or the current database). + */ + public $catalogName; +} From 326e98a41ca81edc902dc0a7919f507f4ad88e78 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 5 Sep 2013 08:31:04 -0400 Subject: [PATCH 011/157] doc fix. --- framework/yii/base/Component.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php index cc3525e..a7ddd29 100644 --- a/framework/yii/base/Component.php +++ b/framework/yii/base/Component.php @@ -40,6 +40,7 @@ class Component extends Object * @param string $name the property name * @return mixed the property value or the value of a behavior's property * @throws UnknownPropertyException if the property is not defined + * @throws InvalidCallException if the property is write-only. * @see __set */ public function __get($name) From 267f2d784946230a275a8735fd2750aa6d3e0b0c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 5 Sep 2013 16:47:05 +0200 Subject: [PATCH 012/157] fixed docs in db Schema classes --- framework/yii/db/Schema.php | 2 +- framework/yii/db/mssql/Schema.php | 4 +--- framework/yii/db/mysql/Schema.php | 4 +--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/framework/yii/db/Schema.php b/framework/yii/db/Schema.php index 4fd1cd1..7b19066 100644 --- a/framework/yii/db/Schema.php +++ b/framework/yii/db/Schema.php @@ -216,7 +216,7 @@ abstract class Schema extends Object * This method should be overridden by child classes in order to support this feature * because the default implementation simply throws an exception. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * @return array all table names in the database. The names have NO the schema name prefix. + * @return array all table names in the database. The names have NO schema name prefix. * @throws NotSupportedException if this method is called */ protected function findTableNames($schema = '') diff --git a/framework/yii/db/mssql/Schema.php b/framework/yii/db/mssql/Schema.php index 4048fe2..5f53d8e 100644 --- a/framework/yii/db/mssql/Schema.php +++ b/framework/yii/db/mssql/Schema.php @@ -332,10 +332,8 @@ SQL; /** * Returns all table names in the database. - * This method should be overridden by child classes in order to support this feature - * because the default implementation simply throws an exception. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * @return array all table names in the database. The names have NO the schema name prefix. + * @return array all table names in the database. The names have NO schema name prefix. */ protected function findTableNames($schema = '') { diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php index 225ef38..998f49a 100644 --- a/framework/yii/db/mysql/Schema.php +++ b/framework/yii/db/mysql/Schema.php @@ -236,10 +236,8 @@ class Schema extends \yii\db\Schema /** * Returns all table names in the database. - * This method should be overridden by child classes in order to support this feature - * because the default implementation simply throws an exception. * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. - * @return array all table names in the database. The names have NO the schema name prefix. + * @return array all table names in the database. The names have NO schema name prefix. */ protected function findTableNames($schema = '') { From be67559fe155bedff60fdf194e066dac10de778b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 5 Sep 2013 16:49:11 +0200 Subject: [PATCH 013/157] added CUBIRD DB Schema class --- framework/yii/db/Connection.php | 5 +- framework/yii/db/cubrid/Schema.php | 222 +++++++++++++++++++++++++++++++++++++ 2 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 framework/yii/db/cubrid/Schema.php diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index 342fa15..69bf6a5 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -201,7 +201,7 @@ class Connection extends Component public $queryCache = 'cache'; /** * @var string the charset used for database connection. The property is only used - * for MySQL and PostgreSQL databases. Defaults to null, meaning using default charset + * for MySQL, PostgreSQL and CUBRID databases. Defaults to null, meaning using default charset * as specified by the database. * * Note that if you're using GBK or BIG5 then it's highly recommended to @@ -244,6 +244,7 @@ class Connection extends Component 'oci' => 'yii\db\oci\Schema', // Oracle driver 'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts 'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts + 'cubrid' => 'yii\db\cubrid\Schema', // CUBRID ); /** * @var Transaction the currently active transaction @@ -361,7 +362,7 @@ class Connection extends Component if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) { $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare); } - if ($this->charset !== null && in_array($this->getDriverName(), array('pgsql', 'mysql', 'mysqli'))) { + if ($this->charset !== null && in_array($this->getDriverName(), array('pgsql', 'mysql', 'mysqli', 'cubrid'))) { $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset)); } $this->trigger(self::EVENT_AFTER_OPEN); diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php new file mode 100644 index 0000000..3b60b64 --- /dev/null +++ b/framework/yii/db/cubrid/Schema.php @@ -0,0 +1,222 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * @var array mapping from physical column types (keys) to abstract column types (values) + * Please refer to [CUBRID manual](http://www.cubrid.org/manual/91/en/sql/datatype.html) for + * details on data types. + */ + public $typeMap = array( + // Numeric data types + 'short' => self::TYPE_SMALLINT, + 'smallint' => self::TYPE_SMALLINT, + 'int' => self::TYPE_INTEGER, + 'integer' => self::TYPE_INTEGER, + 'bigint' => self::TYPE_BIGINT, + 'numeric' => self::TYPE_DECIMAL, + 'decimal' => self::TYPE_DECIMAL, + 'float' => self::TYPE_FLOAT, + 'real' => self::TYPE_FLOAT, + 'double' => self::TYPE_FLOAT, + 'double precision' => self::TYPE_FLOAT, + 'monetary' => self::TYPE_MONEY, + // Date/Time data types + 'date' => self::TYPE_DATE, + 'time' => self::TYPE_TIME, + 'timestamp' => self::TYPE_TIMESTAMP, + 'datetime' => self::TYPE_DATETIME, + // Bit string data types +// 'bit' => self::TYPE_BINARY, // TODO +// 'bit varying' => self::TYPE_BINARY, + // String data types + 'char' => self::TYPE_STRING, + 'varchar' => self::TYPE_STRING, + 'char varying' => self::TYPE_STRING, + 'nchar' => self::TYPE_STRING, + 'nchar varying' => self::TYPE_STRING, + 'string' => self::TYPE_STRING, + // BLOB/CLOB data types + 'blob' => self::TYPE_BINARY, + 'clob' => self::TYPE_BINARY, + // Collection data types (TODO are considered strings for now, naybe support conversion?) +// 'set' => self::TYPE_STRING, +// 'multiset' => self::TYPE_STRING, +// 'list' => self::TYPE_STRING, +// 'sequence' => self::TYPE_STRING, +// 'enum' => self::TYPE_STRING, + ); + + /** + * Quotes a table name for use in a query. + * A simple table name has no schema prefix. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) + { + return strpos($name, "`") !== false ? $name : "`" . $name . "`"; + } + + /** + * Quotes a column name for use in a query. + * A simple column name has no prefix. + * @param string $name column name + * @return string the properly quoted column name + */ + public function quoteSimpleColumnName($name) + { + return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`'; + } + + /** + * Creates a query builder for the CUBRID database. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->db); + } + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema driver dependent table metadata. Null if the table does not exist. + */ + protected function loadTableSchema($name) + { + $this->db->open(); + $tableInfo = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE, $name); + + if (isset($tableInfo[0]['NAME'])) { + $table = new TableSchema; + $table->name = $tableInfo[0]['NAME']; + + $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); + $columns = $this->db->createCommand($sql)->queryAll(); + + foreach ($columns as $info) { + $column = $this->loadColumnSchema($info); + $table->columns[$column->name] = $column; + if ($column->isPrimaryKey) { + $table->primaryKey[] = $column->name; + if ($column->autoIncrement) { + $table->sequenceName = ''; + } + } + } + + $foreignKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $table->name); + foreach($foreignKeys as $key) { + $table->foreignKeys[] = array( + $key['PKTABLE_NAME'], + $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME'] + // TODO support composite foreign keys + ); + } + + return $table; + } else { + return null; + } + } + + /** + * Loads the column information into a [[ColumnSchema]] object. + * @param array $info column information + * @return ColumnSchema the column schema object + */ + protected function loadColumnSchema($info) + { + $column = new ColumnSchema; + + $column->name = $info['Field']; + $column->allowNull = $info['Null'] === 'YES'; + $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false; + $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false; + + $column->dbType = strtolower($info['Type']); + $column->unsigned = strpos($column->dbType, 'unsigned') !== false; + + $column->type = self::TYPE_STRING; + if (preg_match('/^([\w ]+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { + $type = $matches[1]; + if (isset($this->typeMap[$type])) { + $column->type = $this->typeMap[$type]; + } + if (!empty($matches[2])) { + if ($type === 'enum') { + $values = explode(',', $matches[2]); + foreach ($values as $i => $value) { + $values[$i] = trim($value, "'"); + } + $column->enumValues = $values; + } else { + $values = explode(',', $matches[2]); + $column->size = $column->precision = (int)$values[0]; + if (isset($values[1])) { + $column->scale = (int)$values[1]; + } + if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { + $column->type = 'boolean'; + } elseif ($type === 'bit' || $type === 'bit varying') { + if ($column->size > 32) { + $column->type = 'bigint'; + } elseif ($column->size === 32) { + $column->type = 'integer'; + } + } + } + } + } + + $column->phpType = $this->getColumnPhpType($column); + + if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP' || + $column->type === 'datetime' && $info['Default'] === 'SYS_DATETIME' || + $column->type === 'date' && $info['Default'] === 'SYS_DATE' || + $column->type === 'time' && $info['Default'] === 'SYS_TIME') { + $column->defaultValue = new Expression($info['Default']); + } else { + $column->defaultValue = $column->typecast($info['Default']); + } + + return $column; + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @return array all table names in the database. The names have NO schema name prefix. + */ + protected function findTableNames($schema = '') + { + $this->db->open(); + $tables = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE); + $tableNames = array(); + foreach($tables as $table) { + // do not list system tables + if ($table['TYPE'] !== 0) { + $tableNames[] = $table['NAME']; + } + } + return $tableNames; + } +} From f4fb2d94efef724c7aac1f098f13d8ee103eef2b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 5 Sep 2013 17:29:47 +0200 Subject: [PATCH 014/157] finalizing cubrid Schema --- framework/yii/db/Command.php | 2 +- framework/yii/db/QueryBuilder.php | 2 +- framework/yii/db/cubrid/Schema.php | 22 +++++++++++----------- framework/yii/db/mysql/QueryBuilder.php | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php index 460cd46..c439775 100644 --- a/framework/yii/db/Command.php +++ b/framework/yii/db/Command.php @@ -472,7 +472,7 @@ class Command extends \yii\base\Component * ))->execute(); * ~~~ * - * Not that the values in each row must match the corresponding column names. + * Note that the values in each row must match the corresponding column names. * * @param string $table the table that new rows will be inserted into. * @param array $columns the column names diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index f67038c..00d21c0 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -134,7 +134,7 @@ class QueryBuilder extends \yii\base\Object * ))->execute(); * ~~~ * - * Not that the values in each row must match the corresponding column names. + * Note that the values in each row must match the corresponding column names. * * @param string $table the table that new rows will be inserted into. * @param array $columns the column names diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index 3b60b64..9242dc1 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -43,9 +43,6 @@ class Schema extends \yii\db\Schema 'time' => self::TYPE_TIME, 'timestamp' => self::TYPE_TIMESTAMP, 'datetime' => self::TYPE_DATETIME, - // Bit string data types -// 'bit' => self::TYPE_BINARY, // TODO -// 'bit varying' => self::TYPE_BINARY, // String data types 'char' => self::TYPE_STRING, 'varchar' => self::TYPE_STRING, @@ -56,6 +53,9 @@ class Schema extends \yii\db\Schema // BLOB/CLOB data types 'blob' => self::TYPE_BINARY, 'clob' => self::TYPE_BINARY, + // Bit string data types +// 'bit' => self::TYPE_STRING, +// 'bit varying' => self::TYPE_STRING, // Collection data types (TODO are considered strings for now, naybe support conversion?) // 'set' => self::TYPE_STRING, // 'multiset' => self::TYPE_STRING, @@ -106,7 +106,7 @@ class Schema extends \yii\db\Schema $tableInfo = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE, $name); if (isset($tableInfo[0]['NAME'])) { - $table = new TableSchema; + $table = new TableSchema(); $table->name = $tableInfo[0]['NAME']; $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); @@ -145,7 +145,7 @@ class Schema extends \yii\db\Schema */ protected function loadColumnSchema($info) { - $column = new ColumnSchema; + $column = new ColumnSchema(); $column->name = $info['Field']; $column->allowNull = $info['Null'] === 'YES'; @@ -174,13 +174,13 @@ class Schema extends \yii\db\Schema if (isset($values[1])) { $column->scale = (int)$values[1]; } - if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { - $column->type = 'boolean'; - } elseif ($type === 'bit' || $type === 'bit varying') { - if ($column->size > 32) { - $column->type = 'bigint'; + if ($type === 'bit' || $type === 'bit varying') { + if ($column->size === 1) { + $column->type = self::TYPE_BOOLEAN; + } elseif ($column->size > 32) { + $column->type = self::TYPE_BIGINT; } elseif ($column->size === 32) { - $column->type = 'integer'; + $column->type = self::TYPE_INTEGER; } } } diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index 0307abd..c7a4256 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -152,7 +152,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * ))->execute(); * ~~~ * - * Not that the values in each row must match the corresponding column names. + * Note that the values in each row must match the corresponding column names. * * @param string $table the table that new rows will be inserted into. * @param array $columns the column names From 57a91c842afe84f9651410cf7314f4c65f055b65 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 5 Sep 2013 17:30:18 +0200 Subject: [PATCH 015/157] CUBRID Query builder --- framework/yii/db/cubrid/QueryBuilder.php | 116 +++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 framework/yii/db/cubrid/QueryBuilder.php diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php new file mode 100644 index 0000000..28b6466 --- /dev/null +++ b/framework/yii/db/cubrid/QueryBuilder.php @@ -0,0 +1,116 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder +{ + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = array( + Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_TEXT => 'varchar', + Schema::TYPE_SMALLINT => 'smallint', + Schema::TYPE_INTEGER => 'int', + Schema::TYPE_BIGINT => 'bigint', + Schema::TYPE_FLOAT => 'float(7)', + Schema::TYPE_DECIMAL => 'decimal(10,0)', + Schema::TYPE_DATETIME => 'datetime', + Schema::TYPE_TIMESTAMP => 'timestamp', + Schema::TYPE_TIME => 'time', + Schema::TYPE_DATE => 'date', + Schema::TYPE_BINARY => 'blob', + Schema::TYPE_BOOLEAN => 'bit(1)', + Schema::TYPE_MONEY => 'decimal(19,4)', + ); + + /** + * Creates a SQL statement for resetting the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $tableName the name of the table whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + * @return string the SQL statement for resetting sequence + * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table. + */ + public function resetSequence($tableName, $value = null) + { + $table = $this->db->getTableSchema($tableName); + if ($table !== null && $table->sequenceName !== null) { + $tableName = $this->db->quoteTableName($tableName); + if ($value === null) { + $key = reset($table->primaryKey); + $value = (int)$this->db->createCommand("SELECT MAX(`$key`) FROM " . $this->db->schema->quoteTableName($tableName))->queryScalar() + 1; + } else { + $value = (int)$value; + } + return "ALTER TABLE " . $this->db->schema->quoteTableName($tableName) . " AUTO_INCREMENT=$value;"; + } elseif ($table === null) { + throw new InvalidParamException("Table not found: $tableName"); + } else { + throw new InvalidParamException("There is not sequence associated with table '$tableName'."); + } + } + + /** + * Generates a batch INSERT SQL statement. + * For example, + * + * ~~~ + * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array( + * array('Tom', 30), + * array('Jane', 20), + * array('Linda', 25), + * ))->execute(); + * ~~~ + * + * Note that the values in each row must match the corresponding column names. + * + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column names + * @param array $rows the rows to be batch inserted into the table + * @return string the batch INSERT SQL statement + */ + public function batchInsert($table, $columns, $rows) + { + if (($tableSchema = $this->db->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = array(); + } + + foreach ($columns as $i => $name) { + $columns[$i] = $this->db->quoteColumnName($name); + } + + $values = array(); + foreach ($rows as $row) { + $vs = array(); + foreach ($row as $i => $value) { + if (!is_array($value) && isset($columnSchemas[$columns[$i]])) { + $value = $columnSchemas[$columns[$i]]->typecast($value); + } + $vs[] = is_string($value) ? $this->db->quoteValue($value) : $value; + } + $values[] = '(' . implode(', ', $vs) . ')'; + } + + return 'INSERT INTO ' . $this->db->quoteTableName($table) + . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values); + } +} From 2387d003415647bcd20750c60b0ca7c93fcfb69e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 5 Sep 2013 18:22:24 +0200 Subject: [PATCH 016/157] CUBRID added exception about wrong implementation of quoteValue --- framework/yii/db/cubrid/QueryBuilder.php | 2 +- framework/yii/db/cubrid/Schema.php | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php index 28b6466..2132b58 100644 --- a/framework/yii/db/cubrid/QueryBuilder.php +++ b/framework/yii/db/cubrid/QueryBuilder.php @@ -21,7 +21,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'varchar', Schema::TYPE_SMALLINT => 'smallint', diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index 9242dc1..f0b909b 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -7,6 +7,7 @@ namespace yii\db\cubrid; +use yii\base\NotSupportedException; use yii\db\Expression; use yii\db\TableSchema; use yii\db\ColumnSchema; @@ -87,6 +88,29 @@ class Schema extends \yii\db\Schema } /** + * Quotes a string value for use in a query. + * Note that if the parameter is not a string, it will be returned without change. + * @param string $str string to be quoted + * @return string the properly quoted string + * @see http://www.php.net/manual/en/function.PDO-quote.php + */ + public function quoteValue($str) + { + throw new NotSupportedException('quoteValue is currently broken in cubrid PDO'); + // TODO implement workaround +/* if (!is_string($str)) { + return $str; + } + + $this->db->open(); + if (($value = $this->db->pdo->quote($str)) !== false) { + return $value; + } else { // the driver doesn't support quote (e.g. oci) + return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; + }*/ + } + + /** * Creates a query builder for the CUBRID database. * @return QueryBuilder query builder instance */ From 825258efb865a864d51ad7a0a9f655c80f3aa470 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 5 Sep 2013 18:23:25 +0200 Subject: [PATCH 017/157] cubrid unit tests WIP --- tests/unit/data/config.php | 6 ++ tests/unit/data/cubrid.sql | 100 +++++++++++++++++++++ tests/unit/framework/db/QueryBuilderTest.php | 3 + .../framework/db/cubrid/CubridActiveRecordTest.php | 13 +++ .../unit/framework/db/cubrid/CubridCommandTest.php | 13 +++ .../framework/db/cubrid/CubridConnectionTest.php | 13 +++ .../framework/db/cubrid/CubridQueryBuilderTest.php | 80 +++++++++++++++++ tests/unit/framework/db/cubrid/CubridQueryTest.php | 13 +++ 8 files changed, 241 insertions(+) create mode 100644 tests/unit/data/cubrid.sql create mode 100644 tests/unit/framework/db/cubrid/CubridActiveRecordTest.php create mode 100644 tests/unit/framework/db/cubrid/CubridCommandTest.php create mode 100644 tests/unit/framework/db/cubrid/CubridConnectionTest.php create mode 100644 tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php create mode 100644 tests/unit/framework/db/cubrid/CubridQueryTest.php diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index 8ead605..fda2be1 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -1,6 +1,12 @@ array( + 'cubrid' => array( + 'dsn' => 'cubrid:dbname=demodb;host=localhost;port=33000', + 'username' => 'dba', + 'password' => '', + 'fixture' => __DIR__ . '/cubrid.sql', + ), 'mysql' => array( 'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest', 'username' => 'travis', diff --git a/tests/unit/data/cubrid.sql b/tests/unit/data/cubrid.sql new file mode 100644 index 0000000..2cf7716 --- /dev/null +++ b/tests/unit/data/cubrid.sql @@ -0,0 +1,100 @@ +/** + * This is the database schema for testing CUBRID support of Yii DAO and Active Record. + * The database setup in config.php is required to perform then relevant tests: + */ + +DROP TABLE IF EXISTS tbl_order_item; +DROP TABLE IF EXISTS tbl_item; +DROP TABLE IF EXISTS tbl_order; +DROP TABLE IF EXISTS tbl_category; +DROP TABLE IF EXISTS tbl_customer; +DROP TABLE IF EXISTS tbl_type; +DROP TABLE IF EXISTS tbl_constraints; + +CREATE TABLE `tbl_constraints` +( + `id` integer not null, + `field1` varchar(255) +); + + +CREATE TABLE `tbl_customer` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `email` varchar(128) NOT NULL, + `name` varchar(128) NOT NULL, + `address` string, + `status` int (11) DEFAULT 0, + PRIMARY KEY (`id`) +); + +CREATE TABLE `tbl_category` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(128) NOT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE `tbl_item` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(128) NOT NULL, + `category_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_item_category_id` FOREIGN KEY (`category_id`) REFERENCES `tbl_category` (`id`) ON DELETE CASCADE +); + +CREATE TABLE `tbl_order` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `customer_id` int(11) NOT NULL, + `create_time` int(11) NOT NULL, + `total` decimal(10,0) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_order_customer_id` FOREIGN KEY (`customer_id`) REFERENCES `tbl_customer` (`id`) ON DELETE CASCADE +); + +CREATE TABLE `tbl_order_item` ( + `order_id` int(11) NOT NULL, + `item_id` int(11) NOT NULL, + `quantity` int(11) NOT NULL, + `subtotal` decimal(10,0) NOT NULL, + PRIMARY KEY (`order_id`,`item_id`), + CONSTRAINT `FK_order_item_order_id` FOREIGN KEY (`order_id`) REFERENCES `tbl_order` (`id`) ON DELETE CASCADE, + CONSTRAINT `FK_order_item_item_id` FOREIGN KEY (`item_id`) REFERENCES `tbl_item` (`id`) ON DELETE CASCADE +); + +CREATE TABLE `tbl_type` ( + `int_col` int(11) NOT NULL, + `int_col2` int(11) DEFAULT '1', + `char_col` char(100) NOT NULL, + `char_col2` varchar(100) DEFAULT 'something', + `char_col3` string, + `float_col` double NOT NULL, + `float_col2` double DEFAULT '1.23', + `blob_col` blob, + `numeric_col` decimal(5,2) DEFAULT '33.22', + `time` timestamp NOT NULL DEFAULT '2002-01-01 00:00:00', + `bool_col` bit(1) NOT NULL, + `bool_col2` bit(1) DEFAULT B'1' +); + +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user1@example.com', 'user1', 'address1', 1); +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1); +INSERT INTO tbl_customer (email, name, address, status) VALUES ('user3@example.com', 'user3', 'address3', 2); + +INSERT INTO tbl_category (name) VALUES ('Books'); +INSERT INTO tbl_category (name) VALUES ('Movies'); + +INSERT INTO tbl_item (name, category_id) VALUES ('Agile Web Application Development with Yii1.1 and PHP5', 1); +INSERT INTO tbl_item (name, category_id) VALUES ('Yii 1.1 Application Development Cookbook', 1); +INSERT INTO tbl_item (name, category_id) VALUES ('Ice Age', 2); +INSERT INTO tbl_item (name, category_id) VALUES ('Toy Story', 2); +INSERT INTO tbl_item (name, category_id) VALUES ('Cars', 2); + +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (1, 1325282384, 110.0); +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325334482, 33.0); +INSERT INTO tbl_order (customer_id, create_time, total) VALUES (2, 1325502201, 40.0); + +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 1, 1, 30.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (1, 2, 2, 40.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 4, 1, 10.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 5, 1, 15.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (2, 3, 1, 8.0); +INSERT INTO tbl_order_item (order_id, item_id, quantity, subtotal) VALUES (3, 2, 1, 40.0); diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php index e08ac87..81a8156 100644 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -8,6 +8,7 @@ use yii\db\mysql\QueryBuilder as MysqlQueryBuilder; use yii\db\sqlite\QueryBuilder as SqliteQueryBuilder; use yii\db\mssql\QueryBuilder as MssqlQueryBuilder; use yii\db\pgsql\QueryBuilder as PgsqlQueryBuilder; +use yii\db\cubrid\QueryBuilder as CubridQueryBuilder; class QueryBuilderTest extends DatabaseTestCase { @@ -32,6 +33,8 @@ class QueryBuilderTest extends DatabaseTestCase return new MssqlQueryBuilder($this->getConnection()); case 'pgsql': return new PgsqlQueryBuilder($this->getConnection()); + case 'cubrid': + return new CubridQueryBuilder($this->getConnection()); } throw new \Exception('Test is not implemented for ' . $this->driverName); } diff --git a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php new file mode 100644 index 0000000..b30880b --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php @@ -0,0 +1,13 @@ +driverName = 'cubrid'; + parent::setUp(); + } +} diff --git a/tests/unit/framework/db/cubrid/CubridCommandTest.php b/tests/unit/framework/db/cubrid/CubridCommandTest.php new file mode 100644 index 0000000..60e422f --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridCommandTest.php @@ -0,0 +1,13 @@ +driverName = 'cubrid'; + parent::setUp(); + } +} diff --git a/tests/unit/framework/db/cubrid/CubridConnectionTest.php b/tests/unit/framework/db/cubrid/CubridConnectionTest.php new file mode 100644 index 0000000..2937d22 --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridConnectionTest.php @@ -0,0 +1,13 @@ +driverName = 'cubrid'; + parent::setUp(); + } +} diff --git a/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php new file mode 100644 index 0000000..36967d7 --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php @@ -0,0 +1,80 @@ + 5)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), + array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), + array(Schema::TYPE_STRING, 'varchar(255)'), + array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), + array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'), + array(Schema::TYPE_TEXT, 'varchar'), + array(Schema::TYPE_TEXT . '(255)', 'varchar'), + array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'varchar CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'varchar CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . ' NOT NULL', 'varchar NOT NULL'), + array(Schema::TYPE_TEXT . '(255) NOT NULL', 'varchar NOT NULL'), + array(Schema::TYPE_SMALLINT, 'smallint'), + array(Schema::TYPE_SMALLINT . '(8)', 'smallint'), + array(Schema::TYPE_INTEGER, 'int'), + array(Schema::TYPE_INTEGER . '(8)', 'int'), + array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . ' NOT NULL', 'int NOT NULL'), + array(Schema::TYPE_BIGINT, 'bigint'), + array(Schema::TYPE_BIGINT . '(8)', 'bigint'), + array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'), + array(Schema::TYPE_FLOAT, 'float(7)'), + array(Schema::TYPE_FLOAT . '(16)', 'float(16)'), + array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float(7) CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . '(16) CHECK (value > 5.6)', 'float(16) CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . ' NOT NULL', 'float(7) NOT NULL'), + array(Schema::TYPE_DECIMAL, 'decimal(10,0)'), + array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'), + array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'), + array(Schema::TYPE_DATETIME, 'datetime'), + array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'), + array(Schema::TYPE_TIMESTAMP, 'timestamp'), + array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'), + array(Schema::TYPE_TIME, 'time'), + array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"), + array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'), + array(Schema::TYPE_DATE, 'date'), + array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), + array(Schema::TYPE_BINARY, 'blob'), + array(Schema::TYPE_BOOLEAN, 'bit(1)'), + array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'bit(1) NOT NULL DEFAULT 1'), + array(Schema::TYPE_MONEY, 'decimal(19,4)'), + array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'), + array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'), + ); + } + +} diff --git a/tests/unit/framework/db/cubrid/CubridQueryTest.php b/tests/unit/framework/db/cubrid/CubridQueryTest.php new file mode 100644 index 0000000..0503fbc --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridQueryTest.php @@ -0,0 +1,13 @@ +driverName = 'cubrid'; + parent::setUp(); + } +} From fccd8185b5ad32c3932a118635aea97e6f557888 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 5 Sep 2013 22:39:40 -0400 Subject: [PATCH 018/157] Enhanced the default implementation of Model::scenarios() to return all scenarios found in rules(). --- framework/yii/base/Model.php | 47 ++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php index a195acd..8cfc4d4 100644 --- a/framework/yii/base/Model.php +++ b/framework/yii/base/Model.php @@ -45,7 +45,7 @@ use yii\validators\Validator; * property is read-only. * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is * read-only. - * @property string $scenario The scenario that this model is in. Defaults to 'default'. + * @property string $scenario The scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]]. * @property ArrayObject $validators All the validators declared in the model. This property is read-only. * * @author Qiang Xue @@ -54,6 +54,11 @@ use yii\validators\Validator; class Model extends Component implements IteratorAggregate, ArrayAccess { /** + * The name of the default scenario. + */ + const DEFAULT_SCENARIO = 'default'; + + /** * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set * [[ModelEvent::isValid]] to be false to stop the validation. */ @@ -74,7 +79,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess /** * @var string current scenario */ - private $_scenario = 'default'; + private $_scenario = self::DEFAULT_SCENARIO; /** * Returns the validation rules for attributes. @@ -159,23 +164,39 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * If an attribute should NOT be massively assigned (thus considered unsafe), * please prefix the attribute with an exclamation character (e.g. '!rank'). * - * The default implementation of this method will return a 'default' scenario - * which corresponds to all attributes listed in the validation rules applicable - * to the 'default' scenario. + * The default implementation of this method will all scenarios found in the [[rules()]] + * declaration. A special scenario named [[DEFAULT_SCENARIO]] will contain all attributes + * found in the [[rules()]]. Each scenario will be associated with the attributes that + * are being validated by the validation rules that apply to the scenario. * * @return array a list of scenarios and the corresponding active attributes. */ public function scenarios() { - $attributes = array(); - foreach ($this->getActiveValidators() as $validator) { - foreach ($validator->attributes as $name) { - $attributes[$name] = true; + $scenarios = array(); + $defaults = array(); + /** @var $validator Validator */ + foreach ($this->getValidators() as $validator) { + if (empty($validator->on)) { + foreach ($validator->attributes as $attribute) { + $defaults[$attribute] = true; + } + } else { + foreach ($validator->on as $scenario) { + foreach ($validator->attributes as $attribute) { + $scenarios[$scenario][$attribute] = true; + } + } + } + } + foreach ($scenarios as $scenario => $attributes) { + foreach (array_keys($defaults) as $attribute) { + $attributes[$attribute] = true; } + $scenarios[$scenario] = array_keys($attributes); } - return array( - 'default' => array_keys($attributes), - ); + $scenarios[self::DEFAULT_SCENARIO] = array_keys($defaults); + return $scenarios; } /** @@ -593,7 +614,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * Scenario affects how validation is performed and which attributes can * be massively assigned. * - * @return string the scenario that this model is in. Defaults to 'default'. + * @return string the scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]]. */ public function getScenario() { From b16bf0691ace16c1d83864b11292a0a7ce63419e Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 5 Sep 2013 23:37:15 -0400 Subject: [PATCH 019/157] fixed typo --- framework/yii/base/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php index 8cfc4d4..9d81d83 100644 --- a/framework/yii/base/Model.php +++ b/framework/yii/base/Model.php @@ -164,7 +164,7 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * If an attribute should NOT be massively assigned (thus considered unsafe), * please prefix the attribute with an exclamation character (e.g. '!rank'). * - * The default implementation of this method will all scenarios found in the [[rules()]] + * The default implementation of this method will return all scenarios found in the [[rules()]] * declaration. A special scenario named [[DEFAULT_SCENARIO]] will contain all attributes * found in the [[rules()]]. Each scenario will be associated with the attributes that * are being validated by the validation rules that apply to the scenario. From d6ff097cbbc952d643fdf16777bba72d04adf755 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 6 Sep 2013 10:28:10 +0200 Subject: [PATCH 020/157] avoid hanging test on bindValue problems (close db connection in tearDown) --- tests/unit/framework/db/DatabaseTestCase.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/unit/framework/db/DatabaseTestCase.php b/tests/unit/framework/db/DatabaseTestCase.php index 1fd2d56..1dd8cfc 100644 --- a/tests/unit/framework/db/DatabaseTestCase.php +++ b/tests/unit/framework/db/DatabaseTestCase.php @@ -1,12 +1,16 @@ db) { + $this->db->close(); + } + } + /** * @param bool $reset whether to clean up the test database * @param bool $open whether to open and populate test database From e446a118640fae9ecad414022ad5d2799d4dab0b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 6 Sep 2013 11:38:54 +0200 Subject: [PATCH 021/157] added CURBID to the docs --- docs/guide/database-basics.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/guide/database-basics.md b/docs/guide/database-basics.md index 71510f4..310e3f5 100644 --- a/docs/guide/database-basics.md +++ b/docs/guide/database-basics.md @@ -2,8 +2,14 @@ Database basics =============== Yii has a database access layer built on top of PHP's [PDO](http://www.php.net/manual/en/ref.pdo.php). It provides -uniform API and solves some inconsistencies between different DBMS. By default Yii supports MySQL, SQLite, PostgreSQL, -Oracle and MSSQL. +uniform API and solves some inconsistencies between different DBMS. By default Yii supports the following DBMS: + +- [MySQL](http://www.mysql.com/) +- [SQLite](http://sqlite.org/) +- [PostgreSQL](http://www.postgresql.org/) +- [CUBRID](http://www.cubrid.org/) (version 9.1.0 and higher). +- Oracle +- MSSQL Configuration @@ -22,6 +28,7 @@ return array( 'dsn' => 'mysql:host=localhost;dbname=mydatabase', // MySQL, MariaDB //'dsn' => 'sqlite:/path/to/database/file', // SQLite //'dsn' => 'pgsql:host=localhost;port=5432;dbname=mydatabase', // PostgreSQL + //'dsn' => 'cubrid:dbname=demodb;host=localhost;port=33000', // CUBRID //'dsn' => 'sqlsrv:Server=localhost;Database=mydatabase', // MS SQL Server, sqlsrv driver //'dsn' => 'dblib:host=localhost;dbname=mydatabase', // MS SQL Server, dblib driver //'dsn' => 'mssql:host=localhost;dbname=mydatabase', // MS SQL Server, mssql driver @@ -34,8 +41,10 @@ return array( // ... ); ``` +Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) for more details +on the format of the DSN string. -After the component is configured you can access it using the following syntax: +After the connection component is configured you can access it using the following syntax: ```php $connection = \Yii::$app->db; From c6ef7ec9d5acd00720cb306018960244dcd08938 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 6 Sep 2013 11:40:29 +0200 Subject: [PATCH 022/157] moved Command::getPdoType() to Schema this allows different implementation in different DBMS CUBRID does not supprt PDO::TYPE_BOOL, so we use STRING here which will be casted by the DBMS --- framework/yii/db/Command.php | 25 +++---------------------- framework/yii/db/Schema.php | 19 +++++++++++++++++++ framework/yii/db/cubrid/QueryBuilder.php | 2 +- framework/yii/db/cubrid/Schema.php | 19 +++++++++++++++++++ 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php index c439775..7f2d81d 100644 --- a/framework/yii/db/Command.php +++ b/framework/yii/db/Command.php @@ -181,7 +181,7 @@ class Command extends \yii\base\Component { $this->prepare(); if ($dataType === null) { - $this->pdoStatement->bindParam($name, $value, $this->getPdoType($value)); + $this->pdoStatement->bindParam($name, $value, $this->db->schema->getPdoType($value)); } elseif ($length === null) { $this->pdoStatement->bindParam($name, $value, $dataType); } elseif ($driverOptions === null) { @@ -208,7 +208,7 @@ class Command extends \yii\base\Component { $this->prepare(); if ($dataType === null) { - $this->pdoStatement->bindValue($name, $value, $this->getPdoType($value)); + $this->pdoStatement->bindValue($name, $value, $this->db->schema->getPdoType($value)); } else { $this->pdoStatement->bindValue($name, $value, $dataType); } @@ -236,7 +236,7 @@ class Command extends \yii\base\Component $type = $value[1]; $value = $value[0]; } else { - $type = $this->getPdoType($value); + $type = $this->db->schema->getPdoType($value); } $this->pdoStatement->bindValue($name, $value, $type); $this->_params[$name] = $value; @@ -246,25 +246,6 @@ class Command extends \yii\base\Component } /** - * Determines the PDO type for the give PHP data value. - * @param mixed $data the data whose PDO type is to be determined - * @return integer the PDO type - * @see http://www.php.net/manual/en/pdo.constants.php - */ - private function getPdoType($data) - { - static $typeMap = array( - 'boolean' => \PDO::PARAM_BOOL, - 'integer' => \PDO::PARAM_INT, - 'string' => \PDO::PARAM_STR, - 'resource' => \PDO::PARAM_LOB, - 'NULL' => \PDO::PARAM_NULL, - ); - $type = gettype($data); - return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; - } - - /** * Executes the SQL statement. * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. * No result set will be returned. diff --git a/framework/yii/db/Schema.php b/framework/yii/db/Schema.php index 4fd1cd1..9839144 100644 --- a/framework/yii/db/Schema.php +++ b/framework/yii/db/Schema.php @@ -376,4 +376,23 @@ abstract class Schema extends Object return 'string'; } } + + /** + * Determines the PDO type for the give PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($data) + { + static $typeMap = array( // php type => PDO type + 'boolean' => \PDO::PARAM_BOOL, + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ); + $type = gettype($data); + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } } diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php index 2132b58..4e5121a 100644 --- a/framework/yii/db/cubrid/QueryBuilder.php +++ b/framework/yii/db/cubrid/QueryBuilder.php @@ -10,7 +10,7 @@ namespace yii\db\cubrid; use yii\base\InvalidParamException; /** - * QueryBuilder is the query builder for CUBRID databases. + * QueryBuilder is the query builder for CUBRID databases (version 9.1.x and higher). * * @author Carsten Brandt * @since 2.0 diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index f0b909b..a423296 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -243,4 +243,23 @@ class Schema extends \yii\db\Schema } return $tableNames; } + + /** + * Determines the PDO type for the give PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($data) + { + static $typeMap = array( + 'boolean' => \PDO::PARAM_STR, // CUBRID PDO does not support PARAM_BOOL + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ); + $type = gettype($data); + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } } From 791f9d3f4e815244cc67c6cc2e928d57d884c76b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 6 Sep 2013 13:21:35 +0200 Subject: [PATCH 023/157] better use int for boolean representation bit has special syntax for storing and retreiving so we'd need a converter for that. Storing 1 bit will result in one byte left padded on the disc so the result of a query for boolean 0 will be 0x00 and for boolean 1 will be 0x80. --- framework/yii/db/cubrid/QueryBuilder.php | 2 +- framework/yii/db/cubrid/Schema.php | 11 +---------- tests/unit/data/cubrid.sql | 4 ++-- tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php | 4 ++-- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php index 4e5121a..935b3d3 100644 --- a/framework/yii/db/cubrid/QueryBuilder.php +++ b/framework/yii/db/cubrid/QueryBuilder.php @@ -34,7 +34,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_TIME => 'time', Schema::TYPE_DATE => 'date', Schema::TYPE_BINARY => 'blob', - Schema::TYPE_BOOLEAN => 'bit(1)', + Schema::TYPE_BOOLEAN => 'smallint', Schema::TYPE_MONEY => 'decimal(19,4)', ); diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index a423296..c07602f 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -198,15 +198,6 @@ class Schema extends \yii\db\Schema if (isset($values[1])) { $column->scale = (int)$values[1]; } - if ($type === 'bit' || $type === 'bit varying') { - if ($column->size === 1) { - $column->type = self::TYPE_BOOLEAN; - } elseif ($column->size > 32) { - $column->type = self::TYPE_BIGINT; - } elseif ($column->size === 32) { - $column->type = self::TYPE_INTEGER; - } - } } } } @@ -253,7 +244,7 @@ class Schema extends \yii\db\Schema public function getPdoType($data) { static $typeMap = array( - 'boolean' => \PDO::PARAM_STR, // CUBRID PDO does not support PARAM_BOOL + 'boolean' => \PDO::PARAM_INT, // CUBRID PDO does not support PARAM_BOOL 'integer' => \PDO::PARAM_INT, 'string' => \PDO::PARAM_STR, 'resource' => \PDO::PARAM_LOB, diff --git a/tests/unit/data/cubrid.sql b/tests/unit/data/cubrid.sql index 2cf7716..5525418 100644 --- a/tests/unit/data/cubrid.sql +++ b/tests/unit/data/cubrid.sql @@ -71,8 +71,8 @@ CREATE TABLE `tbl_type` ( `blob_col` blob, `numeric_col` decimal(5,2) DEFAULT '33.22', `time` timestamp NOT NULL DEFAULT '2002-01-01 00:00:00', - `bool_col` bit(1) NOT NULL, - `bool_col2` bit(1) DEFAULT B'1' + `bool_col` smallint NOT NULL, + `bool_col2` smallint DEFAULT 1 ); INSERT INTO tbl_customer (email, name, address, status) VALUES ('user1@example.com', 'user1', 'address1', 1); diff --git a/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php index 36967d7..94a002e 100644 --- a/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php +++ b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php @@ -67,8 +67,8 @@ class CubridQueryBuilderTest extends QueryBuilderTest array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), array(Schema::TYPE_BINARY, 'blob'), - array(Schema::TYPE_BOOLEAN, 'bit(1)'), - array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'bit(1) NOT NULL DEFAULT 1'), + array(Schema::TYPE_BOOLEAN, 'smallint'), + array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'smallint NOT NULL DEFAULT 1'), array(Schema::TYPE_MONEY, 'decimal(19,4)'), array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'), array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'), From 58f8293b8455d242d47c3b1894351371624ad2cd Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 6 Sep 2013 13:24:36 +0200 Subject: [PATCH 024/157] custom bindValue test for cubrid --- tests/unit/data/cubrid.sql | 1 + .../unit/framework/db/cubrid/CubridCommandTest.php | 63 ++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/tests/unit/data/cubrid.sql b/tests/unit/data/cubrid.sql index 5525418..8ea20a8 100644 --- a/tests/unit/data/cubrid.sql +++ b/tests/unit/data/cubrid.sql @@ -66,6 +66,7 @@ CREATE TABLE `tbl_type` ( `char_col` char(100) NOT NULL, `char_col2` varchar(100) DEFAULT 'something', `char_col3` string, + `enum_col` enum('a', 'b'), `float_col` double NOT NULL, `float_col2` double DEFAULT '1.23', `blob_col` blob, diff --git a/tests/unit/framework/db/cubrid/CubridCommandTest.php b/tests/unit/framework/db/cubrid/CubridCommandTest.php index 60e422f..6f10113 100644 --- a/tests/unit/framework/db/cubrid/CubridCommandTest.php +++ b/tests/unit/framework/db/cubrid/CubridCommandTest.php @@ -10,4 +10,67 @@ class CubridCommandTest extends CommandTest $this->driverName = 'cubrid'; parent::setUp(); } + + public function testBindParamValue() + { + $db = $this->getConnection(); + + // bindParam + $sql = 'INSERT INTO tbl_customer(email, name, address) VALUES (:email, :name, :address)'; + $command = $db->createCommand($sql); + $email = 'user4@example.com'; + $name = 'user4'; + $address = 'address4'; + $command->bindParam(':email', $email); + $command->bindParam(':name', $name); + $command->bindParam(':address', $address); + $command->execute(); + + $sql = 'SELECT name FROM tbl_customer WHERE email=:email'; + $command = $db->createCommand($sql); + $command->bindParam(':email', $email); + $this->assertEquals($name, $command->queryScalar()); + + $sql = "INSERT INTO tbl_type (int_col, char_col, char_col2, enum_col, float_col, blob_col, numeric_col, bool_col, bool_col2) VALUES (:int_col, '', :char_col, :enum_col, :float_col, CHAR_TO_BLOB(:blob_col), :numeric_col, :bool_col, :bool_col2)"; + $command = $db->createCommand($sql); + $intCol = 123; + $charCol = 'abc'; + $enumCol = 'a'; + $floatCol = 1.23; + $blobCol = "\x10\x11\x12"; + $numericCol = '1.23'; + $boolCol = false; + $boolCol2 = true; + $command->bindParam(':int_col', $intCol); + $command->bindParam(':char_col', $charCol); + $command->bindParam(':enum_col', $enumCol); + $command->bindParam(':float_col', $floatCol); + $command->bindParam(':blob_col', $blobCol); + $command->bindParam(':numeric_col', $numericCol); + $command->bindParam(':bool_col', $boolCol); + $command->bindParam(':bool_col2', $boolCol2); + $this->assertEquals(1, $command->execute()); + + $sql = 'SELECT * FROM tbl_type'; + $row = $db->createCommand($sql)->queryOne(); + $this->assertEquals($intCol, $row['int_col']); + $this->assertEquals($enumCol, $row['enum_col']); + $this->assertEquals($charCol, $row['char_col2']); + $this->assertEquals($floatCol, $row['float_col']); + $this->assertEquals($blobCol, fread($row['blob_col'], 3)); + $this->assertEquals($numericCol, $row['numeric_col']); + $this->assertEquals($boolCol, $row['bool_col']); + $this->assertEquals($boolCol2, $row['bool_col2']); + + // bindValue + $sql = 'INSERT INTO tbl_customer(email, name, address) VALUES (:email, \'user5\', \'address5\')'; + $command = $db->createCommand($sql); + $command->bindValue(':email', 'user5@example.com'); + $command->execute(); + + $sql = 'SELECT email FROM tbl_customer WHERE name=:name'; + $command = $db->createCommand($sql); + $command->bindValue(':name', 'user5'); + $this->assertEquals('user5@example.com', $command->queryScalar()); + } } From e996f3dfd579eed51f24eaa6415b367f7cb1b1f7 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 6 Sep 2013 13:53:33 +0200 Subject: [PATCH 025/157] Workaround for broken PDO::quote() in CUBRID 9.1.0 http://jira.cubrid.org/browse/APIS-658 --- framework/yii/db/cubrid/Schema.php | 13 ++++++------- tests/unit/framework/db/cubrid/CubridConnectionTest.php | 8 ++++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index c07602f..d1fdc10 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -96,18 +96,17 @@ class Schema extends \yii\db\Schema */ public function quoteValue($str) { - throw new NotSupportedException('quoteValue is currently broken in cubrid PDO'); - // TODO implement workaround -/* if (!is_string($str)) { + if (!is_string($str)) { return $str; } $this->db->open(); - if (($value = $this->db->pdo->quote($str)) !== false) { - return $value; - } else { // the driver doesn't support quote (e.g. oci) + // workaround for broken PDO::quote() implementation in CUBRID 9.1.0 http://jira.cubrid.org/browse/APIS-658 + if (version_compare($this->db->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION), '9.1.0', '<=')) { return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; - }*/ + } else { + return $this->db->pdo->quote($str); + } } /** diff --git a/tests/unit/framework/db/cubrid/CubridConnectionTest.php b/tests/unit/framework/db/cubrid/CubridConnectionTest.php index 2937d22..bfd2c90 100644 --- a/tests/unit/framework/db/cubrid/CubridConnectionTest.php +++ b/tests/unit/framework/db/cubrid/CubridConnectionTest.php @@ -10,4 +10,12 @@ class CubridConnectionTest extends ConnectionTest $this->driverName = 'cubrid'; parent::setUp(); } + + public function testQuoteValue() + { + $connection = $this->getConnection(false); + $this->assertEquals(123, $connection->quoteValue(123)); + $this->assertEquals("'string'", $connection->quoteValue('string')); + $this->assertEquals("'It''s interesting'", $connection->quoteValue("It's interesting")); + } } From 4e1ab6e5a5f7e80b4ae25d7b6f5ccbf682cf9e6b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 6 Sep 2013 08:40:16 -0400 Subject: [PATCH 026/157] Fixes #848: Added hidden field for Html::activeFileInput(). --- framework/yii/helpers/HtmlBase.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/yii/helpers/HtmlBase.php b/framework/yii/helpers/HtmlBase.php index cfff8f5..a93c93e 100644 --- a/framework/yii/helpers/HtmlBase.php +++ b/framework/yii/helpers/HtmlBase.php @@ -1049,7 +1049,10 @@ class HtmlBase */ public static function activeFileInput($model, $attribute, $options = array()) { - return static::activeInput('file', $model, $attribute, $options); + // add a hidden field so that if a model only has a file field, we can + // still use isset($_POST[$modelClass]) to detect if the input is submitted + return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) + . static::activeInput('file', $model, $attribute, $options); } /** From 95c2674609c2d61c9dfe0c56c4f1d8a96278b34f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 6 Sep 2013 08:40:44 -0400 Subject: [PATCH 027/157] hint fix. --- framework/yii/gii/generators/model/Generator.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/framework/yii/gii/generators/model/Generator.php b/framework/yii/gii/generators/model/Generator.php index 7aa601a..b9c8f23 100644 --- a/framework/yii/gii/generators/model/Generator.php +++ b/framework/yii/gii/generators/model/Generator.php @@ -93,8 +93,11 @@ class Generator extends \yii\gii\Generator 'db' => 'This is the ID of the DB application component.', 'tableName' => 'This is the name of the DB table that the new ActiveRecord class is associated with, e.g. tbl_post. The table name may consist of the DB schema part if needed, e.g. public.tbl_post. - The table name may contain an asterisk at the end to match multiple table names, e.g. tbl_*. - In this case, multiple ActiveRecord classes will be generated, one for each matching table name.', + The table name may contain an asterisk to match multiple table names, e.g. tbl_* + will match tables who name starts with tbl_. In this case, multiple ActiveRecord classes + will be generated, one for each matching table name; and the class names will be generated from + the matching characters. For example, table tbl_post will generate Post + class.', 'modelClass' => 'This is the name of the ActiveRecord class to be generated. The class name should not contain the namespace part as it is specified in "Namespace". You do not need to specify the class name if "Table Name" contains an asterisk at the end, in which case multiple ActiveRecord classes will be generated.', From 544e412af824487bba1911b509a25f1d7ee99108 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 6 Sep 2013 15:03:25 +0200 Subject: [PATCH 028/157] unit test cleanup --- tests/unit/framework/db/CommandTest.php | 6 ------ tests/unit/framework/db/ConnectionTest.php | 6 ------ tests/unit/framework/db/DatabaseTestCase.php | 3 ++- tests/unit/framework/db/QueryBuilderTest.php | 6 ------ tests/unit/framework/db/QueryTest.php | 6 ------ tests/unit/framework/db/cubrid/CubridActiveRecordTest.php | 6 +----- tests/unit/framework/db/cubrid/CubridCommandTest.php | 6 +----- tests/unit/framework/db/cubrid/CubridConnectionTest.php | 6 +----- tests/unit/framework/db/cubrid/CubridQueryTest.php | 6 +----- tests/unit/framework/db/mssql/MssqlActiveRecordTest.php | 6 +----- tests/unit/framework/db/mssql/MssqlCommandTest.php | 6 +----- tests/unit/framework/db/mssql/MssqlConnectionTest.php | 6 +----- tests/unit/framework/db/mssql/MssqlQueryTest.php | 6 +----- tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php | 6 +----- tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php | 6 +----- tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php | 2 -- tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php | 6 +----- tests/unit/framework/db/sqlite/SqliteCommandTest.php | 6 +----- tests/unit/framework/db/sqlite/SqliteConnectionTest.php | 6 +----- tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php | 2 +- tests/unit/framework/db/sqlite/SqliteQueryTest.php | 6 +----- 21 files changed, 17 insertions(+), 98 deletions(-) diff --git a/tests/unit/framework/db/CommandTest.php b/tests/unit/framework/db/CommandTest.php index 52fd046..229545d 100644 --- a/tests/unit/framework/db/CommandTest.php +++ b/tests/unit/framework/db/CommandTest.php @@ -9,12 +9,6 @@ use yii\db\DataReader; class CommandTest extends DatabaseTestCase { - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - public function testConstruct() { $db = $this->getConnection(false); diff --git a/tests/unit/framework/db/ConnectionTest.php b/tests/unit/framework/db/ConnectionTest.php index 42b470b..f2895f1 100644 --- a/tests/unit/framework/db/ConnectionTest.php +++ b/tests/unit/framework/db/ConnectionTest.php @@ -6,12 +6,6 @@ use yii\db\Connection; class ConnectionTest extends DatabaseTestCase { - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - public function testConstruct() { $connection = $this->getConnection(false); diff --git a/tests/unit/framework/db/DatabaseTestCase.php b/tests/unit/framework/db/DatabaseTestCase.php index 1dd8cfc..d8d2916 100644 --- a/tests/unit/framework/db/DatabaseTestCase.php +++ b/tests/unit/framework/db/DatabaseTestCase.php @@ -16,7 +16,6 @@ abstract class DatabaseTestCase extends TestCase protected function setUp() { parent::setUp(); - $this->mockApplication(); $databases = $this->getParam('databases'); $this->database = $databases[$this->driverName]; $pdo_database = 'pdo_'.$this->driverName; @@ -24,6 +23,7 @@ abstract class DatabaseTestCase extends TestCase if (!extension_loaded('pdo') || !extension_loaded($pdo_database)) { $this->markTestSkipped('pdo and pdo_'.$pdo_database.' extension are required.'); } + $this->mockApplication(); } protected function tearDown() @@ -31,6 +31,7 @@ abstract class DatabaseTestCase extends TestCase if ($this->db) { $this->db->close(); } + $this->destroyApplication(); } /** diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php index 81a8156..a2815e8 100644 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -12,12 +12,6 @@ use yii\db\cubrid\QueryBuilder as CubridQueryBuilder; class QueryBuilderTest extends DatabaseTestCase { - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - /** * @throws \Exception * @return QueryBuilder diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php index 5d1f1a9..dfb6c9f 100644 --- a/tests/unit/framework/db/QueryTest.php +++ b/tests/unit/framework/db/QueryTest.php @@ -9,12 +9,6 @@ use yii\db\DataReader; class QueryTest extends DatabaseTestCase { - protected function setUp() - { - parent::setUp(); - $this->mockApplication(); - } - public function testSelect() { // default diff --git a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php index b30880b..6633e11 100644 --- a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php +++ b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php @@ -5,9 +5,5 @@ use yiiunit\framework\db\ActiveRecordTest; class CubridActiveRecordTest extends ActiveRecordTest { - protected function setUp() - { - $this->driverName = 'cubrid'; - parent::setUp(); - } + public $driverName = 'cubrid'; } diff --git a/tests/unit/framework/db/cubrid/CubridCommandTest.php b/tests/unit/framework/db/cubrid/CubridCommandTest.php index 6f10113..5fe8848 100644 --- a/tests/unit/framework/db/cubrid/CubridCommandTest.php +++ b/tests/unit/framework/db/cubrid/CubridCommandTest.php @@ -5,11 +5,7 @@ use yiiunit\framework\db\CommandTest; class CubridCommandTest extends CommandTest { - protected function setUp() - { - $this->driverName = 'cubrid'; - parent::setUp(); - } + public $driverName = 'cubrid'; public function testBindParamValue() { diff --git a/tests/unit/framework/db/cubrid/CubridConnectionTest.php b/tests/unit/framework/db/cubrid/CubridConnectionTest.php index bfd2c90..6924883 100644 --- a/tests/unit/framework/db/cubrid/CubridConnectionTest.php +++ b/tests/unit/framework/db/cubrid/CubridConnectionTest.php @@ -5,11 +5,7 @@ use yiiunit\framework\db\ConnectionTest; class CubridConnectionTest extends ConnectionTest { - protected function setUp() - { - $this->driverName = 'cubrid'; - parent::setUp(); - } + public $driverName = 'cubrid'; public function testQuoteValue() { diff --git a/tests/unit/framework/db/cubrid/CubridQueryTest.php b/tests/unit/framework/db/cubrid/CubridQueryTest.php index 0503fbc..2c68a35 100644 --- a/tests/unit/framework/db/cubrid/CubridQueryTest.php +++ b/tests/unit/framework/db/cubrid/CubridQueryTest.php @@ -5,9 +5,5 @@ use yiiunit\framework\db\QueryTest; class CubridQueryTest extends QueryTest { - protected function setUp() - { - $this->driverName = 'cubrid'; - parent::setUp(); - } + public $driverName = 'cubrid'; } diff --git a/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php b/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php index 5647411..92e4da2 100644 --- a/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php +++ b/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php @@ -6,9 +6,5 @@ use yiiunit\framework\db\ActiveRecordTest; class MssqlActiveRecordTest extends ActiveRecordTest { - protected function setUp() - { - $this->driverName = 'sqlsrv'; - parent::setUp(); - } + protected $driverName = 'sqlsrv'; } diff --git a/tests/unit/framework/db/mssql/MssqlCommandTest.php b/tests/unit/framework/db/mssql/MssqlCommandTest.php index e27fcee..1908f65 100644 --- a/tests/unit/framework/db/mssql/MssqlCommandTest.php +++ b/tests/unit/framework/db/mssql/MssqlCommandTest.php @@ -6,11 +6,7 @@ use yiiunit\framework\db\CommandTest; class MssqlCommandTest extends CommandTest { - public function setUp() - { - $this->driverName = 'sqlsrv'; - parent::setUp(); - } + protected $driverName = 'sqlsrv'; public function testAutoQuoting() { diff --git a/tests/unit/framework/db/mssql/MssqlConnectionTest.php b/tests/unit/framework/db/mssql/MssqlConnectionTest.php index 7bbaae7..854c81f 100644 --- a/tests/unit/framework/db/mssql/MssqlConnectionTest.php +++ b/tests/unit/framework/db/mssql/MssqlConnectionTest.php @@ -6,11 +6,7 @@ use yiiunit\framework\db\ConnectionTest; class MssqlConnectionTest extends ConnectionTest { - public function setUp() - { - $this->driverName = 'sqlsrv'; - parent::setUp(); - } + protected $driverName = 'sqlsrv'; public function testQuoteValue() { diff --git a/tests/unit/framework/db/mssql/MssqlQueryTest.php b/tests/unit/framework/db/mssql/MssqlQueryTest.php index 4f37c14..e07ac1d 100644 --- a/tests/unit/framework/db/mssql/MssqlQueryTest.php +++ b/tests/unit/framework/db/mssql/MssqlQueryTest.php @@ -6,9 +6,5 @@ use yiiunit\framework\db\QueryTest; class MssqlQueryTest extends QueryTest { - public function setUp() - { - $this->driverName = 'sqlsrv'; - parent::setUp(); - } + protected $driverName = 'sqlsrv'; } diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php index 2836223..aef09fd 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php @@ -6,9 +6,5 @@ use yiiunit\framework\db\ActiveRecordTest; class PostgreSQLActiveRecordTest extends ActiveRecordTest { - protected function setUp() - { - $this->driverName = 'pgsql'; - parent::setUp(); - } + protected $driverName = 'pgsql'; } diff --git a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php index 7ac65c5..cd76a97 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php @@ -5,11 +5,7 @@ use yiiunit\framework\db\ConnectionTest; class PostgreSQLConnectionTest extends ConnectionTest { - public function setUp() - { - $this->driverName = 'pgsql'; - parent::setUp(); - } + protected $driverName = 'pgsql'; public function testConnection() { diff --git a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php index 7c31190..bf2dd73 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php @@ -2,13 +2,11 @@ namespace yiiunit\framework\db\pgsql; -use yii\base\NotSupportedException; use yii\db\pgsql\Schema; use yiiunit\framework\db\QueryBuilderTest; class PostgreSQLQueryBuilderTest extends QueryBuilderTest { - public $driverName = 'pgsql'; public function columnTypes() diff --git a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php index 2e76162..1088280 100644 --- a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php +++ b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php @@ -5,9 +5,5 @@ use yiiunit\framework\db\ActiveRecordTest; class SqliteActiveRecordTest extends ActiveRecordTest { - protected function setUp() - { - $this->driverName = 'sqlite'; - parent::setUp(); - } + protected $driverName = 'sqlite'; } diff --git a/tests/unit/framework/db/sqlite/SqliteCommandTest.php b/tests/unit/framework/db/sqlite/SqliteCommandTest.php index e330c7e..af9a323 100644 --- a/tests/unit/framework/db/sqlite/SqliteCommandTest.php +++ b/tests/unit/framework/db/sqlite/SqliteCommandTest.php @@ -5,11 +5,7 @@ use yiiunit\framework\db\CommandTest; class SqliteCommandTest extends CommandTest { - protected function setUp() - { - $this->driverName = 'sqlite'; - parent::setUp(); - } + protected $driverName = 'sqlite'; public function testAutoQuoting() { diff --git a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php index 2065200..f1d5b94 100644 --- a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php +++ b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php @@ -5,11 +5,7 @@ use yiiunit\framework\db\ConnectionTest; class SqliteConnectionTest extends ConnectionTest { - protected function setUp() - { - $this->driverName = 'sqlite'; - parent::setUp(); - } + protected $driverName = 'sqlite'; public function testConstruct() { diff --git a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php index 3291187..2e3a615 100644 --- a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php +++ b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php @@ -8,7 +8,7 @@ use yiiunit\framework\db\QueryBuilderTest; class SqliteQueryBuilderTest extends QueryBuilderTest { - public $driverName = 'sqlite'; + protected $driverName = 'sqlite'; public function columnTypes() { diff --git a/tests/unit/framework/db/sqlite/SqliteQueryTest.php b/tests/unit/framework/db/sqlite/SqliteQueryTest.php index ebbb1bb..3f112dc 100644 --- a/tests/unit/framework/db/sqlite/SqliteQueryTest.php +++ b/tests/unit/framework/db/sqlite/SqliteQueryTest.php @@ -5,9 +5,5 @@ use yiiunit\framework\db\QueryTest; class SqliteQueryTest extends QueryTest { - protected function setUp() - { - $this->driverName = 'sqlite'; - parent::setUp(); - } + protected $driverName = 'sqlite'; } From 8abeed03bd827a3684350f4775a23f5e92e7c317 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 6 Sep 2013 15:03:57 +0200 Subject: [PATCH 029/157] added SchemaTest --- framework/yii/db/cubrid/Schema.php | 2 +- tests/unit/framework/db/SchemaTest.php | 70 ++++++++++++++++++++++ .../unit/framework/db/cubrid/CubridSchemaTest.php | 31 ++++++++++ .../unit/framework/db/sqlite/SqliteSchemaTest.php | 9 +++ 4 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 tests/unit/framework/db/SchemaTest.php create mode 100644 tests/unit/framework/db/cubrid/CubridSchemaTest.php create mode 100644 tests/unit/framework/db/sqlite/SqliteSchemaTest.php diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index d1fdc10..d221073 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -227,7 +227,7 @@ class Schema extends \yii\db\Schema $tableNames = array(); foreach($tables as $table) { // do not list system tables - if ($table['TYPE'] !== 0) { + if ($table['TYPE'] != 0) { $tableNames[] = $table['NAME']; } } diff --git a/tests/unit/framework/db/SchemaTest.php b/tests/unit/framework/db/SchemaTest.php new file mode 100644 index 0000000..66aa410 --- /dev/null +++ b/tests/unit/framework/db/SchemaTest.php @@ -0,0 +1,70 @@ +getConnection()->schema; + + foreach($values as $value) { + $this->assertEquals($value[1], $schema->getPdoType($value[0])); + } + fclose($fp); + } + + public function testFindTableNames() + { + /** @var Schema $schema */ + $schema = $this->getConnection()->schema; + + $tables = $schema->getTableNames(); + $this->assertTrue(in_array('tbl_customer', $tables)); + $this->assertTrue(in_array('tbl_category', $tables)); + $this->assertTrue(in_array('tbl_item', $tables)); + $this->assertTrue(in_array('tbl_order', $tables)); + $this->assertTrue(in_array('tbl_order_item', $tables)); + $this->assertTrue(in_array('tbl_type', $tables)); + } + + public function testGetTableSchemas() + { + /** @var Schema $schema */ + $schema = $this->getConnection()->schema; + + $tables = $schema->getTableSchemas(); + $this->assertEquals(count($schema->getTableNames()), count($tables)); + foreach($tables as $table) { + $this->assertInstanceOf('yii\db\TableSchema', $table); + } + } + + public function testSchemaCache() + { + /** @var Schema $schema */ + $schema = $this->getConnection()->schema; + + $schema->db->enableSchemaCache = true; + $schema->db->schemaCache = new FileCache(); + $noCacheTable = $schema->getTableSchema('tbl_type', true); + $cachedTable = $schema->getTableSchema('tbl_type', true); + $this->assertEquals($noCacheTable, $cachedTable); + } +} diff --git a/tests/unit/framework/db/cubrid/CubridSchemaTest.php b/tests/unit/framework/db/cubrid/CubridSchemaTest.php new file mode 100644 index 0000000..d235b40 --- /dev/null +++ b/tests/unit/framework/db/cubrid/CubridSchemaTest.php @@ -0,0 +1,31 @@ + \PDO::PARAM_NULL, + '' => \PDO::PARAM_STR, + 'hello' => \PDO::PARAM_STR, + 0 => \PDO::PARAM_INT, + 1 => \PDO::PARAM_INT, + 1337 => \PDO::PARAM_INT, + true => \PDO::PARAM_INT, // CUBRID PDO does not support PARAM_BOOL + false => \PDO::PARAM_INT, // CUBRID PDO does not support PARAM_BOOL + ); + + $schema = $this->getConnection()->schema; + + foreach($values as $value => $type) { + $this->assertEquals($type, $schema->getPdoType($value)); + } + $this->assertEquals(\PDO::PARAM_LOB, $schema->getPdoType($fp=fopen(__FILE__, 'rb'))); + fclose($fp); + } +} diff --git a/tests/unit/framework/db/sqlite/SqliteSchemaTest.php b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php new file mode 100644 index 0000000..f3f6b60 --- /dev/null +++ b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php @@ -0,0 +1,9 @@ + Date: Fri, 6 Sep 2013 15:32:33 +0200 Subject: [PATCH 030/157] Add support for composite FK to cubrid --- framework/yii/db/cubrid/Schema.php | 14 +++++++++----- tests/unit/data/cubrid.sql | 9 +++++++++ tests/unit/data/mysql.sql | 9 +++++++++ tests/unit/data/sqlite.sql | 9 +++++++++ tests/unit/framework/db/SchemaTest.php | 19 +++++++++++++++++++ tests/unit/framework/db/sqlite/SqliteSchemaTest.php | 5 +++++ 6 files changed, 60 insertions(+), 5 deletions(-) diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index d221073..e3b125b 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -148,12 +148,16 @@ class Schema extends \yii\db\Schema $foreignKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $table->name); foreach($foreignKeys as $key) { - $table->foreignKeys[] = array( - $key['PKTABLE_NAME'], - $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME'] - // TODO support composite foreign keys - ); + if (isset($table->foreignKeys[$key['FK_NAME']])) { + $table->foreignKeys[$key['FK_NAME']][$key['FKCOLUMN_NAME']] = $key['PKCOLUMN_NAME']; + } else { + $table->foreignKeys[$key['FK_NAME']] = array( + $key['PKTABLE_NAME'], + $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME'] + ); + } } + $table->foreignKeys = array_values($table->foreignKeys); return $table; } else { diff --git a/tests/unit/data/cubrid.sql b/tests/unit/data/cubrid.sql index 8ea20a8..bfaf85c 100644 --- a/tests/unit/data/cubrid.sql +++ b/tests/unit/data/cubrid.sql @@ -3,6 +3,7 @@ * The database setup in config.php is required to perform then relevant tests: */ +DROP TABLE IF EXISTS tbl_composite_fk; DROP TABLE IF EXISTS tbl_order_item; DROP TABLE IF EXISTS tbl_item; DROP TABLE IF EXISTS tbl_order; @@ -76,6 +77,14 @@ CREATE TABLE `tbl_type` ( `bool_col2` smallint DEFAULT 1 ); +CREATE TABLE `tbl_composite_fk` ( + `id` int(11) NOT NULL, + `order_id` int(11) NOT NULL, + `item_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_composite_fk_order_item` FOREIGN KEY (`order_id`,`item_id`) REFERENCES `tbl_order_item` (`order_id`,`item_id`) ON DELETE CASCADE +); + INSERT INTO tbl_customer (email, name, address, status) VALUES ('user1@example.com', 'user1', 'address1', 1); INSERT INTO tbl_customer (email, name, address, status) VALUES ('user2@example.com', 'user2', 'address2', 1); INSERT INTO tbl_customer (email, name, address, status) VALUES ('user3@example.com', 'user3', 'address3', 2); diff --git a/tests/unit/data/mysql.sql b/tests/unit/data/mysql.sql index 2e9458e..133348d 100644 --- a/tests/unit/data/mysql.sql +++ b/tests/unit/data/mysql.sql @@ -3,6 +3,7 @@ * The database setup in config.php is required to perform then relevant tests: */ +DROP TABLE IF EXISTS tbl_composite_fk CASCADE; DROP TABLE IF EXISTS tbl_order_item CASCADE; DROP TABLE IF EXISTS tbl_item CASCADE; DROP TABLE IF EXISTS tbl_order CASCADE; @@ -62,6 +63,14 @@ CREATE TABLE `tbl_order_item` ( CONSTRAINT `FK_order_item_item_id` FOREIGN KEY (`item_id`) REFERENCES `tbl_item` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `tbl_composite_fk` ( + `id` int(11) NOT NULL, + `order_id` int(11) NOT NULL, + `item_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_composite_fk_order_item` FOREIGN KEY (`order_id`,`item_id`) REFERENCES `tbl_order_item` (`order_id`,`item_id`) ON DELETE CASCADE +); + CREATE TABLE `tbl_type` ( `int_col` int(11) NOT NULL, `int_col2` int(11) DEFAULT '1', diff --git a/tests/unit/data/sqlite.sql b/tests/unit/data/sqlite.sql index f75bfa6..f031ac3 100644 --- a/tests/unit/data/sqlite.sql +++ b/tests/unit/data/sqlite.sql @@ -3,6 +3,7 @@ * The database setup in config.php is required to perform then relevant tests: */ +DROP TABLE IF EXISTS tbl_composite_fk; DROP TABLE IF EXISTS tbl_order_item; DROP TABLE IF EXISTS tbl_item; DROP TABLE IF EXISTS tbl_order; @@ -48,6 +49,14 @@ CREATE TABLE tbl_order_item ( PRIMARY KEY (order_id, item_id) ); +CREATE TABLE `tbl_composite_fk` ( + `id` int(11) NOT NULL, + `order_id` int(11) NOT NULL, + `item_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_composite_fk_order_item` FOREIGN KEY (`order_id`,`item_id`) REFERENCES `tbl_order_item` (`order_id`,`item_id`) ON DELETE CASCADE +); + CREATE TABLE tbl_type ( int_col INTEGER NOT NULL, int_col2 INTEGER DEFAULT '1', diff --git a/tests/unit/framework/db/SchemaTest.php b/tests/unit/framework/db/SchemaTest.php index 66aa410..5f504e1 100644 --- a/tests/unit/framework/db/SchemaTest.php +++ b/tests/unit/framework/db/SchemaTest.php @@ -56,6 +56,11 @@ class SchemaTest extends DatabaseTestCase } } + public function testGetNonExistingTableSchema() + { + $this->assertNull($this->getConnection()->schema->getTableSchema('nonexisting_table')); + } + public function testSchemaCache() { /** @var Schema $schema */ @@ -67,4 +72,18 @@ class SchemaTest extends DatabaseTestCase $cachedTable = $schema->getTableSchema('tbl_type', true); $this->assertEquals($noCacheTable, $cachedTable); } + + public function testCompositeFk() + { + /** @var Schema $schema */ + $schema = $this->getConnection()->schema; + + $table = $schema->getTableSchema('tbl_composite_fk'); + + $this->assertCount(1, $table->foreignKeys); + $this->assertTrue(isset($table->foreignKeys[0])); + $this->assertEquals('tbl_order_item', $table->foreignKeys[0][0]); + $this->assertEquals('order_id', $table->foreignKeys[0]['order_id']); + $this->assertEquals('item_id', $table->foreignKeys[0]['item_id']); + } } diff --git a/tests/unit/framework/db/sqlite/SqliteSchemaTest.php b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php index f3f6b60..9b17a1d 100644 --- a/tests/unit/framework/db/sqlite/SqliteSchemaTest.php +++ b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php @@ -6,4 +6,9 @@ use yiiunit\framework\db\SchemaTest; class SqliteSchemaTest extends SchemaTest { protected $driverName = 'sqlite'; + + public function testCompositeFk() + { + $this->markTestSkipped('sqlite does not allow getting enough information about composite FK.'); + } } From 23b858a2ccd0e3eb7ad468ba011aeaf7eab98c86 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 6 Sep 2013 15:51:30 +0200 Subject: [PATCH 031/157] removed comments --- framework/yii/db/cubrid/Schema.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index e3b125b..fae932f 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -55,14 +55,14 @@ class Schema extends \yii\db\Schema 'blob' => self::TYPE_BINARY, 'clob' => self::TYPE_BINARY, // Bit string data types -// 'bit' => self::TYPE_STRING, -// 'bit varying' => self::TYPE_STRING, - // Collection data types (TODO are considered strings for now, naybe support conversion?) -// 'set' => self::TYPE_STRING, -// 'multiset' => self::TYPE_STRING, -// 'list' => self::TYPE_STRING, -// 'sequence' => self::TYPE_STRING, -// 'enum' => self::TYPE_STRING, + 'bit' => self::TYPE_STRING, + 'bit varying' => self::TYPE_STRING, + // Collection data types (considered strings for now, may add support for them later) + 'set' => self::TYPE_STRING, + 'multiset' => self::TYPE_STRING, + 'list' => self::TYPE_STRING, + 'sequence' => self::TYPE_STRING, + 'enum' => self::TYPE_STRING, ); /** From 6d25d3aef6dab835294f4147c64bdd0eaa3a82e2 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 6 Sep 2013 18:44:02 +0400 Subject: [PATCH 032/157] Added info about custom validator method signature to Model::rules phpdoc --- framework/yii/base/Model.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php index 9d81d83..93b5a6b 100644 --- a/framework/yii/base/Model.php +++ b/framework/yii/base/Model.php @@ -116,6 +116,10 @@ class Model extends Component implements IteratorAggregate, ArrayAccess * function validatorName($attribute, $params) * ~~~ * + * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of + * validator configuration options such as `max` in case of `length` validator. Currently validate attribute value + * can be accessed as `$this->[$attribute]`. + * * Yii also provides a set of [[Validator::builtInValidators|built-in validators]]. * They each has an alias name which can be used when specifying a validation rule. * From 3766e1b22f5144800dbd5efe15dae5e26df584b9 Mon Sep 17 00:00:00 2001 From: Larry Ullman Date: Mon, 26 Aug 2013 13:50:55 -0400 Subject: [PATCH 033/157] Edited the first half --- docs/guide/active-record.md | 52 +++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/docs/guide/active-record.md b/docs/guide/active-record.md index bb39115..0c93fad 100644 --- a/docs/guide/active-record.md +++ b/docs/guide/active-record.md @@ -1,17 +1,15 @@ Active Record ============= -ActiveRecord implements the [Active Record design pattern](http://en.wikipedia.org/wiki/Active_record). -The idea is that an [[ActiveRecord]] object is associated with a row in a database table and its attributes are mapped -to the columns of the corresponding table columns. Reading an ActiveRecord attribute is equivalent to accessing -the corresponding table column. For example, a `Customer` object is associated with a row in the -`tbl_customer` table, and its `name` attribute is mapped to the `name` column in the `tbl_customer` table. -To get the value of the `name` column in the table row, you can simply use the expression `$customer->name`, -just like reading an object property. - -Instead of writing raw SQL statements to perform database queries, you can call intuitive methods provided -by ActiveRecord to achieve the same goals. For example, calling [[ActiveRecord::save()|save()]] would -insert or update a row in the associated table of the ActiveRecord class: +Active Record implements the [Active Record design pattern](http://en.wikipedia.org/wiki/Active_record). +The premise behind Active Record is that an individual [[ActiveRecord]] object is associated with a specific row in a database table. The object's attributes are mapped to the columns of the corresponding table. Therefore, referencing an Active Record attribute is equivalent to accessing +the corresponding table column for that record. + +For example, say that the `Customer` ActiveRecord class is associated with the +`tbl_customer` table. This would mean that the class's `name` attribute is automatically mapped to the `name` column in `tbl_customer`. +Thanks to Active Record, assuming the variable `$customer` is an object of type `Customer`, to get the value of the `name` column for the table row, you can simply use the expression `$customer->name`. In this example, Active Record is providing an object-oriented way to access data stored in the database, just as you would access any object property. But Active Record provides much more functionality than this. + +With Active Record, instead of writing raw SQL statements to perform database queries, you can call intuitive methods to achieve the same goals. For example, calling [[ActiveRecord::save()|save()]] would perform an INSERT or UPDATE query, creating or updating a row in the associated table of the ActiveRecord class: ```php $customer = new Customer(); @@ -24,7 +22,7 @@ Declaring ActiveRecord Classes ------------------------------ To declare an ActiveRecord class you need to extend [[\yii\db\ActiveRecord]] and -implement the `tableName` method like the following: +implement the `tableName` method: ```php use yii\db\ActiveRecord; @@ -41,13 +39,12 @@ class Customer extends ActiveRecord } ``` -Connecting to Database +Connecting to the Database ---------------------- ActiveRecord relies on a [[Connection|DB connection]] to perform the underlying DB operations. -By default, it assumes that there is an application component named `db` which gives the needed -[[Connection]] instance. Usually this component is configured via application configuration -like the following: +By default, ActiveRecord assumes that there is an application component named `db` which provides the needed +[[Connection]] instance. Usually this component is configured in application configuration file: ```php return array( @@ -65,13 +62,10 @@ return array( Please read the [Database basics](database-basics.md) section to learn more on how to configure and use database connections. -> Tip: To use a different database connection, you may override the [[ActiveRecord::getDb()]] method. -You may create a base ActiveRecord class and override its [[ActiveRecord::getDb()]] method. You -then extend from this base class for all those ActiveRecord classes that need to use the same -DB connection. +> Tip: To use a different database connection, you need to override the [[ActiveRecord::getDb()]] method. To do that, create a base class that extends ActiveRecord. In the base class, override the [[ActiveRecord::getDb()]] method. Then extend your new base class for all of your ActiveRecord classes that need to use the same alternative database connection. -Querying Data from Database +Querying Data from the Database --------------------------- There are two ActiveRecord methods for querying data from database: @@ -79,8 +73,8 @@ There are two ActiveRecord methods for querying data from database: - [[ActiveRecord::find()]] - [[ActiveRecord::findBySql()]] -They both return an [[ActiveQuery]] instance which extends from [[Query]] and thus supports -the same set of flexible and powerful DB query methods. The followings are some examples, +Both methods return an [[ActiveQuery]] instance, which extends from [[Query]] thus supporting +the same set of flexible and powerful DB query methods. The followings examples demonstrate some of the possibilities. ```php // to retrieve all *active* customers and order them by their ID: @@ -122,10 +116,10 @@ Accessing Column Data --------------------- ActiveRecord maps each column of the corresponding database table row to an *attribute* in the ActiveRecord -object. An attribute is like a regular object property whose name is the same as the corresponding column -name and is case-sensitive. +object. The attribute behaves like any regular object public property. The attribute's name will be the same as the corresponding column +name, and is case-sensitive. -To read the value of a column, you can use the following expression: +To read the value of a column, you can use the following syntax: ```php // "id" is the name of a column in the table associated with $customer ActiveRecord object @@ -141,7 +135,7 @@ $values = $customer->attributes; ``` -Manipulating Data in Database +Manipulating Data in the Database ----------------------------- ActiveRecord provides the following methods to insert, update and delete data in the database: @@ -157,9 +151,7 @@ ActiveRecord provides the following methods to insert, update and delete data in Note that [[ActiveRecord::updateAll()|updateAll()]], [[ActiveRecord::updateAllCounters()|updateAllCounters()]] and [[ActiveRecord::deleteAll()|deleteAll()]] are static methods and apply to the whole database -table, while the rest of the methods only apply to the row associated with the ActiveRecord object. - -The followings are some examples: +table, while the other methods only apply to the row associated with the ActiveRecord object through which the method is being called. ```php // to insert a new customer record From 746a250e3f85ffe42600d8bf1ea18973e3ce556a Mon Sep 17 00:00:00 2001 From: Larry Ullman Date: Mon, 26 Aug 2013 13:51:40 -0400 Subject: [PATCH 034/157] Ignoring .DS_Store --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 13fcf4a..f2915a9 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ Thumbs.db # composer itself is not needed composer.phar + +# Mac DS_Store Files +.DS_Store From a9991d4e916e23360195b1a091c33d4cf0ab7e96 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 7 Sep 2013 08:18:41 -0400 Subject: [PATCH 035/157] CRUD WIP --- framework/yii/base/Module.php | 5 +- framework/yii/gii/Generator.php | 22 ++ framework/yii/gii/assets/gii.js | 5 + framework/yii/gii/generators/crud/Generator.php | 103 +++++---- framework/yii/gii/generators/crud/form.php | 7 +- .../gii/generators/crud/templates/controller.php | 236 +++++++++------------ .../gii/generators/crud/templates/views/create.php | 40 ++-- .../gii/generators/crud/templates/views/update.php | 44 ++-- .../gii/generators/crud/templates/views/view.php | 48 ++--- 9 files changed, 256 insertions(+), 254 deletions(-) diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index 1b7bf5b..de75ce7 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -338,10 +338,9 @@ abstract class Module extends Component /** * Retrieves the named module. - * @param string $id module ID (case-sensitive) + * @param string $id module ID (case-sensitive). * @param boolean $load whether to load the module if it is not yet loaded. - * @return Module|null the module instance, null if the module - * does not exist. + * @return Module|null the module instance, null if the module does not exist. * @see hasModule() */ public function getModule($id, $load = true) diff --git a/framework/yii/gii/Generator.php b/framework/yii/gii/Generator.php index f79b428..eb5b8b4 100644 --- a/framework/yii/gii/Generator.php +++ b/framework/yii/gii/Generator.php @@ -325,6 +325,28 @@ abstract class Generator extends Model } /** + * An inline validator that checks if the attribute value refers to a valid namespaced class name. + * The validator will check if the directory containing the new class file exist or not. + * @param string $attribute the attribute being validated + * @param array $params the validation options + */ + public function validateNewClass($attribute, $params) + { + $class = ltrim($this->$attribute, '\\'); + if (($pos = strrpos($class, '\\')) === false) { + $this->addError($attribute, "The class name must contain fully qualified namespace name."); + } else { + $ns = substr($class, 0, $pos); + $path = Yii::getAlias('@' . str_replace('\\', '/', $ns), false); + if ($path === false) { + $this->addError($attribute, "The class namespace is invalid: $ns"); + } elseif (!is_dir($path)) { + $this->addError($attribute, "Please make sure the directory containing this class exists: $path"); + } + } + } + + /** * @param string $value the attribute to be validated * @return boolean whether the value is a reserved PHP keyword. */ diff --git a/framework/yii/gii/assets/gii.js b/framework/yii/gii/assets/gii.js index b581d3b..45951a1 100644 --- a/framework/yii/gii/assets/gii.js +++ b/framework/yii/gii/assets/gii.js @@ -81,6 +81,11 @@ yii.gii = (function ($) { $('#model-generator .field-generator-modelclass').toggle($(this).val().indexOf('*') == -1); }).change(); + // crud generator: hide Search Model Class input if search is not enabled + $('#crud-generator #generator-enablesearch').on('change', function () { + $('#crud-generator .field-generator-searchmodelclass').toggle(this.checked); + }).change(); + // hide Generate button if any input is changed $('.default-view .form-group input,select,textarea').change(function () { $('.default-view-results,.default-view-files').hide(); diff --git a/framework/yii/gii/generators/crud/Generator.php b/framework/yii/gii/generators/crud/Generator.php index 983dbc7..3ed51c1 100644 --- a/framework/yii/gii/generators/crud/Generator.php +++ b/framework/yii/gii/generators/crud/Generator.php @@ -22,7 +22,8 @@ use yii\web\Controller; class Generator extends \yii\gii\Generator { public $modelClass; - public $controllerID; + public $moduleID; + public $controllerClass; public $baseControllerClass = 'yii\web\Controller'; public $indexWidgetType = 'grid'; public $enableSearch = true; @@ -42,16 +43,18 @@ class Generator extends \yii\gii\Generator public function rules() { return array_merge(parent::rules(), array( - array('modelClass, searchModelClass, controllerID, baseControllerClass', 'filter', 'filter' => 'trim'), - array('modelClass, controllerID, baseControllerClass', 'required'), - array('modelClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), + array('moduleID, controllerClass, modelClass, searchModelClass, baseControllerClass', 'filter', 'filter' => 'trim'), + array('modelClass, controllerClass, baseControllerClass, indexWidgetType', 'required'), + array('modelClass, controllerClass, baseControllerClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), array('modelClass', 'validateClass', 'params' => array('extends' => ActiveRecord::className())), - array('controllerID', 'match', 'pattern' => '/^[a-z\\-\\/]*$/', 'message' => 'Only a-z, dashes (-) and slashes (/) are allowed.'), - array('baseControllerClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), array('baseControllerClass', 'validateClass', 'params' => array('extends' => Controller::className())), + array('controllerClass', 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'), + array('controllerClass, searchModelClass', 'validateNewClass'), array('enableSearch', 'boolean'), array('indexWidgetType', 'in', 'range' => array('grid', 'list')), + array('modelClass', 'validateModelClass'), array('searchModelClass', 'validateSearchModelClass'), + array('moduleID', 'validateModuleID'), )); } @@ -59,7 +62,8 @@ class Generator extends \yii\gii\Generator { return array_merge(parent::attributeLabels(), array( 'modelClass' => 'Model Class', - 'controllerID' => 'Controller ID', + 'moduleID' => 'Module ID', + 'controllerClass' => 'Controller Class', 'baseControllerClass' => 'Base Controller Class', 'indexWidgetType' => 'Widget Used in Index Page', 'enableSearch' => 'Enable Search', @@ -75,15 +79,12 @@ class Generator extends \yii\gii\Generator return array( 'modelClass' => 'This is the ActiveRecord class associated with the table that CRUD will be built upon. You should provide a fully qualified class name, e.g., app\models\Post.', - 'controllerID' => 'CRUD controllers are often named after the model class name that they are dealing with. - Controller ID should be in lower case and may contain module ID(s) separated by slashes. For example: -
    -
  • order generates OrderController.php
  • -
  • order-item generates OrderItemController.php
  • -
  • admin/user generates UserController.php within the admin module.
  • -
', + 'controllerClass' => 'This is the name of the controller class to be generated. You should + provide a fully qualified namespaced class, .e.g, app\controllers\PostController.', 'baseControllerClass' => 'This is the class that the new CRUD controller class will extend from. You should provide a fully qualified class name, e.g., yii\web\Controller.', + 'moduleID' => 'This is the ID of the module that the generated controller will belong to. + If not set, it means the controller will belong to the application.', 'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models. You may choose either GridView or ListView', 'enableSearch' => 'Whether to enable the search functionality on the index page. When search is enabled, @@ -106,7 +107,17 @@ class Generator extends \yii\gii\Generator */ public function stickyAttributes() { - return array('baseControllerClass', 'indexWidgetType', 'enableSearch'); + return array('baseControllerClass', 'moduleID', 'indexWidgetType', 'enableSearch'); + } + + public function validateModelClass() + { + /** @var ActiveRecord $class */ + $class = $this->modelClass; + $pk = $class::primaryKey(); + if (empty($pk)) { + $this->addError('modelClass', "The table associated with $class must have primary key(s)."); + } } public function validateSearchModelClass() @@ -116,6 +127,16 @@ class Generator extends \yii\gii\Generator } } + public function validateModuleID() + { + if (!empty($this->moduleID)) { + $module = Yii::$app->getModule($this->moduleID); + if ($module === null) { + $this->addError('moduleID', "Module '{$this->moduleID}' does not exist."); + } + } + } + /** * @inheritdoc */ @@ -130,6 +151,9 @@ class Generator extends \yii\gii\Generator $templatePath = $this->getTemplatePath() . '/views'; foreach (scandir($templatePath) as $file) { + if (!in_array($file, array('create.php', 'update.php', 'view.php'))) { + continue; + } if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') { $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file")); } @@ -142,39 +166,14 @@ class Generator extends \yii\gii\Generator return $files; } - - /** - * @return string the controller class name without the namespace part. - */ - public function getControllerClass() - { - return Inflector::id2camel($this->getControllerID()) . 'Controller'; - } - /** * @return string the controller ID (without the module ID prefix) */ public function getControllerID() { - if (($pos = strrpos($this->controllerID, '/')) !== false) { - return substr($this->controllerID, $pos + 1); - } else { - return $this->controllerID; - } - } - - /** - * @return \yii\base\Module the module that the new controller belongs to - */ - public function getModule() - { - if (($pos = strpos($this->controllerID, '/')) !== false) { - $id = substr($this->controllerID, 0, $pos); - if (($module = Yii::$app->getModule($id)) !== null) { - return $module; - } - } - return Yii::$app; + $pos = strrpos($this->controllerClass, '\\'); + $class = substr(substr($this->controllerClass, $pos + 1), 0, -10); + return Inflector::camel2id($class); } /** @@ -182,8 +181,7 @@ class Generator extends \yii\gii\Generator */ public function getControllerFile() { - $module = $this->getModule(); - return $module->getControllerPath() . '/' . $this->getControllerClass() . '.php'; + return Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php'); } /** @@ -191,7 +189,20 @@ class Generator extends \yii\gii\Generator */ public function getViewPath() { - $module = $this->getModule(); + $module = empty($this->moduleID) ? Yii::$app : Yii::$app->getModule($this->moduleID); return $module->getViewPath() . '/' . $this->getControllerID() ; } + + public function getNameAttribute() + { + /** @var \yii\db\ActiveRecord $class */ + $class = $this->modelClass; + foreach ($class::getTableSchema()->columnNames as $name) { + if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) { + return $name; + } + } + $pk = $class::primaryKey(); + return $pk[0]; + } } diff --git a/framework/yii/gii/generators/crud/form.php b/framework/yii/gii/generators/crud/form.php index 095791f..ee4771b 100644 --- a/framework/yii/gii/generators/crud/form.php +++ b/framework/yii/gii/generators/crud/form.php @@ -6,11 +6,12 @@ */ echo $form->field($generator, 'modelClass'); -echo $form->field($generator, 'controllerID'); +echo $form->field($generator, 'controllerClass'); echo $form->field($generator, 'baseControllerClass'); +echo $form->field($generator, 'moduleID'); +echo $form->field($generator, 'enableSearch')->checkbox(); +echo $form->field($generator, 'searchModelClass'); echo $form->field($generator, 'indexWidgetType')->dropDownList(array( 'grid' => 'GridView', 'list' => 'ListView', )); -echo $form->field($generator, 'enableSearch')->checkbox(); -echo $form->field($generator, 'searchModelClass'); diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/framework/yii/gii/generators/crud/templates/controller.php index 47c193f..7b3cce8 100644 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ b/framework/yii/gii/generators/crud/templates/controller.php @@ -1,180 +1,152 @@ - - -class controllerClass; ?> extends baseControllerClass."\n"; ?> -{ - /** - * @var string the default layout for the views. Defaults to '//layouts/column2', meaning - * using two-column layout. See 'protected/views/layouts/column2.php'. - */ - public $layout='//layouts/column2'; - /** - * @return array action filters - */ - public function filters() - { - return array( - 'accessControl', // perform access control for CRUD operations - 'postOnly + delete', // we only allow deletion via POST request - ); +$pos = strrpos($generator->controllerClass, '\\'); +$ns = ltrim(substr($generator->controllerClass, 0, $pos), '\\'); +$controllerClass = substr($generator->controllerClass, $pos + 1); +$pos = strrpos($generator->modelClass, '\\'); +$modelClass = $pos === false ? $generator->modelClass : substr($generator->modelClass, $pos + 1); + +/** @var \yii\db\ActiveRecord $class */ +$class = $generator->modelClass; +$pks = $class::primaryKey(); +$schema = $class::getTableSchema(); +if (count($pks) === 1) { + $ids = '$id'; + $params = "array('id' => \$model->{$pks[0]})"; + $paramComments = '@param ' . $schema->columns[$pks[0]]->phpType . ' $id'; +} else { + $ids = '$' . implode(', $', $pks); + $params = array(); + $paramComments = array(); + foreach ($pks as $pk) { + $paramComments[] = '@param ' . $schema->columns[$pk]->phpType . ' $' . $pk; + $params[] = "'$pk' => \$model->$pk"; } + $params = implode(', ', $params); + $paramComments = implode("\n\t * ", $paramComments); +} - /** - * Specifies the access control rules. - * This method is used by the 'accessControl' filter. - * @return array access control rules - */ - public function accessRules() - { - return array( - array('allow', // allow all users to perform 'index' and 'view' actions - 'actions'=>array('index','view'), - 'users'=>array('*'), - ), - array('allow', // allow authenticated user to perform 'create' and 'update' actions - 'actions'=>array('create','update'), - 'users'=>array('@'), - ), - array('allow', // allow admin user to perform 'admin' and 'delete' actions - 'actions'=>array('admin','delete'), - 'users'=>array('admin'), - ), - array('deny', // deny all users - 'users'=>array('*'), - ), - ); - } +echo " + +namespace ; + +use modelClass, '\\'); ?>; +use yii\data\ActiveDataProvider; +use baseControllerClass, '\\'); ?>; +use yii\web\HttpException; +/** + * implements the CRUD actions for model. + */ +class extends baseControllerClass) . "\n"; ?> +{ /** - * Displays a particular model. - * @param integer $id the ID of the model to be displayed + * Displays a single model. + * + * @return mixed */ - public function actionView($id) + public function actionView() { - $this->render('view',array( - 'model'=>$this->loadModel($id), + return $this->render('view', array( + 'model' => $this->findModel(), )); } /** - * Creates a new model. + * Creates a new model. * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed */ public function actionCreate() { - $model=new modelClass; ?>; - - // Uncomment the following line if AJAX validation is needed - // $this->performAjaxValidation($model); - - if(isset($_POST['modelClass; ?>'])) - { - $model->attributes=$_POST['modelClass; ?>']; - if($model->save()) - $this->redirect(array('view','id'=>$model->tableSchema->primaryKey; ?>)); + $model = new ; + + if ($model->load($_POST) && $model->save()) { + return $this->redirect(array('view', )); + } else { + return $this->render('create', array( + 'model' => $model, + )); } - - $this->render('create',array( - 'model'=>$model, - )); } /** - * Updates a particular model. + * Updates an existing model. * If update is successful, the browser will be redirected to the 'view' page. - * @param integer $id the ID of the model to be updated + * + * @return mixed */ - public function actionUpdate($id) + public function actionUpdate() { - $model=$this->loadModel($id); - - // Uncomment the following line if AJAX validation is needed - // $this->performAjaxValidation($model); - - if(isset($_POST['modelClass; ?>'])) - { - $model->attributes=$_POST['modelClass; ?>']; - if($model->save()) - $this->redirect(array('view','id'=>$model->tableSchema->primaryKey; ?>)); + $model = $this->findModel(); + + if ($model->load($_POST) && $model->save()) { + return $this->redirect(array('view', )); + } else { + return $this->render('update', array( + 'model' => $model, + )); } - - $this->render('update',array( - 'model'=>$model, - )); } /** - * Deletes a particular model. - * If deletion is successful, the browser will be redirected to the 'admin' page. - * @param integer $id the ID of the model to be deleted + * Deletes an existing model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * + * @return mixed */ - public function actionDelete($id) + public function actionDelete() { - $this->loadModel($id)->delete(); - - // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser - if(!isset($_GET['ajax'])) - $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin')); + $this->findModel()->delete(); + return $this->redirect(array('index')); } /** * Lists all models. + * @return mixed */ public function actionIndex() { - $dataProvider=new CActiveDataProvider('modelClass; ?>'); - $this->render('index',array( - 'dataProvider'=>$dataProvider, + $dataProvider = new ActiveDataProvider(''); + return $this->render('index', array( + 'dataProvider' => $dataProvider, )); } /** - * Manages all models. + * Returns the data model based on its primary key value. + * If the data model is not found, a 404 HTTP exception will be thrown. + * + * @return the loaded model + * @throws HttpException if the model cannot be found */ - public function actionAdmin() + protected function findModel() { - $model=new modelClass; ?>('search'); - $model->unsetAttributes(); // clear any default values - if(isset($_GET['modelClass; ?>'])) - $model->attributes=$_GET['modelClass; ?>']; - - $this->render('admin',array( - 'model'=>$model, - )); - } - - /** - * Returns the data model based on the primary key given in the GET variable. - * If the data model is not found, an HTTP exception will be raised. - * @param integer $id the ID of the model to be loaded - * @return modelClass; ?> the loaded model - * @throws CHttpException - */ - public function loadModel($id) - { - $model=modelClass; ?>::model()->findByPk($id); - if($model===null) - throw new CHttpException(404,'The requested page does not exist.'); - return $model; + \$$pk"; } - - /** - * Performs the AJAX validation. - * @param modelClass; ?> $model the model to be validated - */ - protected function performAjaxValidation($model) - { - if(isset($_POST['ajax']) && $_POST['ajax']==='class2id($this->modelClass); ?>-form') - { - echo CActiveForm::validate($model); - Yii::app()->end(); + $condition = 'array(' . implode(', ', $condition) . ')'; +} +?> + $model = ::find(); + if ($model === null) { + throw new HttpException(404, 'The requested page does not exist.'); } + return $model; } } diff --git a/framework/yii/gii/generators/crud/templates/views/create.php b/framework/yii/gii/generators/crud/templates/views/create.php index 9fd3ccf..48a2318 100644 --- a/framework/yii/gii/generators/crud/templates/views/create.php +++ b/framework/yii/gii/generators/crud/templates/views/create.php @@ -1,27 +1,31 @@ - -/* @var $this getControllerClass(); ?> */ -/* @var $model getModelClass(); ?> */ -pluralize($this->class2name($this->modelClass)); -echo "\$this->breadcrumbs=array( - '$label'=>array('index'), - 'Create', -);\n"; +echo " -$this->menu=array( - array('label'=>'List modelClass; ?>', 'url'=>array('index')), - array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), -); +use yii\helpers\Html; + +/** + * @var yii\base\View $this + * @var modelClass, '\\'); ?> $model + */ + +$this->title = 'Create modelClass)); ?>'; ?> +
+ +

echo Html::encode($this->title); ?>

-

Create modelClass; ?>

+ echo $this->render('_form', array( + 'model' => $model, + )); ?> -renderPartial('_form', array('model'=>\$model)); ?>"; ?> +
diff --git a/framework/yii/gii/generators/crud/templates/views/update.php b/framework/yii/gii/generators/crud/templates/views/update.php index 4adc72d..8dc3c73 100644 --- a/framework/yii/gii/generators/crud/templates/views/update.php +++ b/framework/yii/gii/generators/crud/templates/views/update.php @@ -1,31 +1,31 @@ - -/* @var $this getControllerClass(); ?> */ -/* @var $model getModelClass(); ?> */ -guessNameColumn($this->tableSchema->columns); -$label=$this->pluralize($this->class2name($this->modelClass)); -echo "\$this->breadcrumbs=array( - '$label'=>array('index'), - \$model->{$nameColumn}=>array('view','id'=>\$model->{$this->tableSchema->primaryKey}), - 'Update', -);\n"; +echo " -$this->menu=array( - array('label'=>'List modelClass; ?>', 'url'=>array('index')), - array('label'=>'Create modelClass; ?>', 'url'=>array('create')), - array('label'=>'View modelClass; ?>', 'url'=>array('view', 'id'=>$model->tableSchema->primaryKey; ?>)), - array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), -); +use yii\helpers\Html; + +/** +* @var yii\base\View $this +* @var modelClass, '\\'); ?> $model +*/ + +$this->title = 'Modify modelClass)); ?>: ' . $model->getNameAttribute(); ?>; ?> +
+ +

echo Html::encode($this->title); ?>

-

Update modelClass." {$this->tableSchema->primaryKey}; ?>"; ?>

+ echo $this->render('_form', array( + 'model' => $model, + )); ?> -renderPartial('_form', array('model'=>\$model)); ?>"; ?> \ No newline at end of file +
diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php index 3363130..baca46f 100644 --- a/framework/yii/gii/generators/crud/templates/views/view.php +++ b/framework/yii/gii/generators/crud/templates/views/view.php @@ -1,39 +1,27 @@ - -/* @var $this getControllerClass(); ?> */ -/* @var $model getModelClass(); ?> */ -guessNameColumn($this->tableSchema->columns); -$label=$this->pluralize($this->class2name($this->modelClass)); -echo "\$this->breadcrumbs=array( - '$label'=>array('index'), - \$model->{$nameColumn}, -);\n"; +echo " -$this->menu=array( - array('label'=>'List modelClass; ?>', 'url'=>array('index')), - array('label'=>'Create modelClass; ?>', 'url'=>array('create')), - array('label'=>'Update modelClass; ?>', 'url'=>array('update', 'id'=>$model->tableSchema->primaryKey; ?>)), - array('label'=>'Delete modelClass; ?>', 'url'=>'#', 'linkOptions'=>array('submit'=>array('delete','id'=>$model->tableSchema->primaryKey; ?>),'confirm'=>'Are you sure you want to delete this item?')), - array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), -); -?> +use yii\helpers\Html; -

View modelClass." #{$this->tableSchema->primaryKey}; ?>"; ?>

+/** +* @var yii\base\View $this +* @var modelClass, '\\'); ?> $model +*/ - $this->widget('zii.widgets.CDetailView', array( - 'data'=>$model, - 'attributes'=>array( -tableSchema->columns as $column) - echo "\t\t'".$column->name."',\n"; +$this->title = $model->getNameAttribute(); ?>; ?> - ), -)); ?> +
+ +

echo Html::encode($this->title); ?>

+ +
From 409500000a65d8fc970e56bc41286aa3c246b6c2 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 8 Sep 2013 02:26:35 +0400 Subject: [PATCH 036/157] Validation reference, validators phpdoc fixes --- docs/guide/validation.md | 188 +++++++++++++++++++-- .../yii/validators/RegularExpressionValidator.php | 2 - framework/yii/validators/StringValidator.php | 2 +- 3 files changed, 172 insertions(+), 20 deletions(-) diff --git a/docs/guide/validation.md b/docs/guide/validation.md index 7bfeb96..0322573 100644 --- a/docs/guide/validation.md +++ b/docs/guide/validation.md @@ -7,22 +7,176 @@ In order to learn model validation basics please refer to [Model, Validation sub Standard Yii validators ----------------------- -- `boolean`: [[BooleanValidator]] -- `captcha`: [[CaptchaValidator]] -- `compare`: [[CompareValidator]] -- `date`: [[DateValidator]] -- `default`: [[DefaultValueValidator]] -- `double`: [[NumberValidator]] -- `email`: [[EmailValidator]] -- `exist`: [[ExistValidator]] -- `file`: [[FileValidator]] -- `filter`: [[FilterValidator]] -- `in`: [[RangeValidator]] -- `integer`: [[NumberValidator]] -- `match`: [[RegularExpressionValidator]] -- `required`: [[RequiredValidator]] -- `string`: [[StringValidator]] -- `unique`: [[UniqueValidator]] -- `url`: [[UrlValidator]] +Standard Yii validators could be specified using aliases instead of referring to class names. Here's the list of all +validators budled with Yii with their most useful properties: + +### `boolean`: [[BooleanValidator]] + +Checks if the attribute value is a boolean value. + +- `trueValue`, the value representing true status. _(1)_ +- `falseValue`, the value representing false status. _(0)_ +- `strict`, whether to compare the type of the value and `trueValue`/`falseValue`. _(false)_ + +### `captcha`: [[CaptchaValidator]] + +Validates that the attribute value is the same as the verification code displayed in the CAPTCHA. Should be used together +with [[CaptchaAction]]. + +- `caseSensitive` whether the comparison is case sensitive. _(false)_ +- `captchaAction` the route of the controller action that renders the CAPTCHA image. _('site/captcha')_ + +### `compare`: [[CompareValidator]] + +Compares the specified attribute value with another value and validates if they are equal. + +- `compareAttribute` the name of the attribute to be compared with. _(currentAttribute_repeat)_ +- `compareValue` the constant value to be compared with. +- `operator` the operator for comparison. _('==')_ + +### `date`: [[DateValidator]] + +Verifies if the attribute represents a date, time or datetime in a proper format. + +- `format` the date format that the value being validated should follow accodring to [[http://www.php.net/manual/en/datetime.createfromformat.php]]. _('Y-m-d')_ +- `timestampAttribute` the name of the attribute to receive the parsing result. + +### `default`: [[DefaultValueValidator]] + +Sets the attribute to be the specified default value. + +- `value` the default value to be set to the specified attributes. + +### `double`: [[NumberValidator]] + +Validates that the attribute value is a number. + +- `max` limit of the number. _(null)_ +- `min` lower limit of the number. _(null)_ + +### `email`: [[EmailValidator]] + +Validates that the attribute value is a valid email address. + +- `allowName` whether to allow name in the email address (e.g. `John Smith `). _(false)_. +- `checkMX` whether to check the MX record for the email address. _(false)_ +- `checkPort` whether to check port 25 for the email address. _(false)_ +- `enableIDN` whether validation process should take into account IDN (internationalized domain names). _(false)_ + +### `exist`: [[ExistValidator]] + +Validates that the attribute value exists in a table. + +- `className` the ActiveRecord class name or alias of the class that should be used to look for the attribute value being + validated. _(ActiveRecord class of the attribute being validated)_ +- `attributeName` the ActiveRecord attribute name that should be used to look for the attribute value being validated. + _(name of the attribute being validated)_ + +### `file`: [[FileValidator]] + +Verifies if an attribute is receiving a valid uploaded file. + +- `types` a list of file name extensions that are allowed to be uploaded. _(any)_ +- `minSize` the minimum number of bytes required for the uploaded file. +- `maxSize` the maximum number of bytes required for the uploaded file. +- `maxFiles` the maximum file count the given attribute can hold. _(1)_ + +### `filter`: [[FilterValidator]] + +Converts the attribute value according to a filter. + +- `filter` PHP callback that defines a filter. + +Typically a callback is either the name of PHP function: + +```php +array('password', 'filter', 'filter' => 'trim'), +``` + +Or an anonymous function: + +```php +array('text', 'filter', 'filter' => function ($value) { + // here we are removing all swear words from text + return $newValue; +}), +``` + +### `in`: [[RangeValidator]] + +Validates that the attribute value is among a list of values. + +- `range` list of valid values that the attribute value should be among. +- `strict` whether the comparison is strict (both type and value must be the same). _(false)_ +- `not` whether to invert the validation logic. _(false)_ + +### `integer`: [[NumberValidator]] + +Validates that the attribute value is an integer number. + +- `max` limit of the number. _(null)_ +- `min` lower limit of the number. _(null)_ + +### `match`: [[RegularExpressionValidator]] + +Validates that the attribute value matches the specified pattern defined by regular expression. + +- `pattern` the regular expression to be matched with. +- `not` whether to invert the validation logic. _(false)_ + +### `required`: [[RequiredValidator]] + +Validates that the specified attribute does not have null or empty value. + +- `requiredValue` the desired value that the attribute must have. _(any)_ +- `strict` whether the comparison between the attribute value and [[requiredValue]] is strict. _(false)_ + +### `safe`: [[SafeValidator]] + +Serves as a dummy validator whose main purpose is to mark the attributes to be safe for massive assignment. + +### `string`: [[StringValidator]] + +Validates that the attribute value is of certain length. + +- `length` specifies the length limit of the value to be validated. Can be `exactly X`, `array(min X)`, `array(min X, max Y)`. +- `max` maximum length. If not set, it means no maximum length limit. +- `min` minimum length. If not set, it means no minimum length limit. +- `encoding` the encoding of the string value to be validated. _([[\yii\base\Application::charset]])_ + +### `unique`: [[UniqueValidator]] + +Validates that the attribute value is unique in the corresponding database table. + +- `className` the ActiveRecord class name or alias of the class that should be used to look for the attribute value being + validated. _(ActiveRecord class of the attribute being validated)_ +- `attributeName` the ActiveRecord attribute name that should be used to look for the attribute value being validated. + _(name of the attribute being validated)_ + +### `url`: [[UrlValidator]] + +Validates that the attribute value is a valid http or https URL. + +- `validSchemes` list of URI schemes which should be considered valid. _array('http', 'https')_ +- `defaultScheme` the default URI scheme. If the input doesn't contain the scheme part, the default scheme will be + prepended to it. _(null)_ +- `enableIDN` whether validation process should take into account IDN (internationalized domain names). _(false)_ + +Validating values out of model context +-------------------------------------- + +Sometimes you need to validate a value that is not bound to any model such as email. In Yii `Validator` class has +`validateValue` method that can help you with it. Not all validator classes have it implemented but the ones that can +operate without model do. In our case to validate an email we can do the following: + +```php +$email = 'test@example.com'; +$validator = new yii\validators\EmailValidator(); +if ($validator->validateValue($email)) { + echo 'Email is valid.'; +} else { + echo 'Email is not valid.' +} +``` TBD: refer to http://www.yiiframework.com/wiki/56/ for the format \ No newline at end of file diff --git a/framework/yii/validators/RegularExpressionValidator.php b/framework/yii/validators/RegularExpressionValidator.php index 4ae2099..df57e7d 100644 --- a/framework/yii/validators/RegularExpressionValidator.php +++ b/framework/yii/validators/RegularExpressionValidator.php @@ -30,7 +30,6 @@ class RegularExpressionValidator extends Validator /** * @var boolean whether to invert the validation logic. Defaults to false. If set to true, * the regular expression defined via [[pattern]] should NOT match the attribute value. - * @throws InvalidConfigException if the "pattern" is not a valid regular expression **/ public $not = false; @@ -82,7 +81,6 @@ class RegularExpressionValidator extends Validator * @param \yii\base\View $view the view object that is going to be used to render views or view files * containing a model form with this validator applied. * @return string the client-side validation script. - * @throws InvalidConfigException if the "pattern" is not a valid regular expression */ public function clientValidateAttribute($object, $attribute, $view) { diff --git a/framework/yii/validators/StringValidator.php b/framework/yii/validators/StringValidator.php index 2cbab6c..946ca6e 100644 --- a/framework/yii/validators/StringValidator.php +++ b/framework/yii/validators/StringValidator.php @@ -52,7 +52,7 @@ class StringValidator extends Validator */ public $tooLong; /** - * @var string user-defined error message used when the length of the value is not equal to [[is]]. + * @var string user-defined error message used when the length of the value is not equal to [[length]]. */ public $notEqual; /** From a12f8da1ff960b84d4eabdb9aa17e517fefbc0a3 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 7 Sep 2013 23:37:15 -0400 Subject: [PATCH 037/157] Fixed debugger style. --- framework/yii/debug/assets/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/yii/debug/assets/main.css b/framework/yii/debug/assets/main.css index 6cb65dd..7953873 100644 --- a/framework/yii/debug/assets/main.css +++ b/framework/yii/debug/assets/main.css @@ -131,6 +131,7 @@ ul.trace { margin: 2px 0 0 0; padding: 0; list-style: none; + white-space: normal; } .callout-danger { From 263dbd264de3fcc8d788ef00b31e17e8eed6dcea Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 7 Sep 2013 23:37:36 -0400 Subject: [PATCH 038/157] Changed default pagination size to 20. --- framework/yii/data/Pagination.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/data/Pagination.php b/framework/yii/data/Pagination.php index 1625fde..04af828 100644 --- a/framework/yii/data/Pagination.php +++ b/framework/yii/data/Pagination.php @@ -98,10 +98,10 @@ class Pagination extends Object */ public $validatePage = true; /** - * @var integer number of items on each page. Defaults to 10. + * @var integer number of items on each page. Defaults to 20. * If it is less than 1, it means the page size is infinite, and thus a single page contains all items. */ - public $pageSize = 10; + public $pageSize = 20; /** * @var integer total number of items. */ From 02e3b451beb07fb3d30ac79168f48f8f85f2c783 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 7 Sep 2013 23:43:26 -0400 Subject: [PATCH 039/157] Removed sorter from the default display of ListView. --- framework/yii/grid/GridView.php | 10 ---------- framework/yii/widgets/ListViewBase.php | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/framework/yii/grid/GridView.php b/framework/yii/grid/GridView.php index 60d325d..a783a75 100644 --- a/framework/yii/grid/GridView.php +++ b/framework/yii/grid/GridView.php @@ -124,16 +124,6 @@ class GridView extends ListViewBase * Both "format" and "label" are optional. They will take default values if absent. */ public $columns = array(); - /** - * @var string the layout that determines how different sections of the list view should be organized. - * The following tokens will be replaced with the corresponding section contents: - * - * - `{summary}`: the summary section. See [[renderSummary()]]. - * - `{items}`: the list items. See [[renderItems()]]. - * - `{sorter}`: the sorter. See [[renderSorter()]]. - * - `{pager}`: the pager. See [[renderPager()]]. - */ - public $layout = "{items}\n{summary}\n{pager}"; public $emptyCell = ' '; /** * @var \yii\base\Model the model that keeps the user-entered filter data. When this property is set, diff --git a/framework/yii/widgets/ListViewBase.php b/framework/yii/widgets/ListViewBase.php index 6be704d..8c2f8f4 100644 --- a/framework/yii/widgets/ListViewBase.php +++ b/framework/yii/widgets/ListViewBase.php @@ -66,7 +66,7 @@ abstract class ListViewBase extends Widget * - `{sorter}`: the sorter. See [[renderSorter()]]. * - `{pager}`: the pager. See [[renderPager()]]. */ - public $layout = "{summary}\n{sorter}\n{items}\n{pager}"; + public $layout = "{summary}\n{items}\n{pager}"; /** From 4edd842558745018d603df568451b8a892f2ab16 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 7 Sep 2013 23:43:47 -0400 Subject: [PATCH 040/157] Added LinkSorter::attributes. --- framework/yii/widgets/LinkSorter.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/framework/yii/widgets/LinkSorter.php b/framework/yii/widgets/LinkSorter.php index b555475..c8b30e5 100644 --- a/framework/yii/widgets/LinkSorter.php +++ b/framework/yii/widgets/LinkSorter.php @@ -28,6 +28,11 @@ class LinkSorter extends Widget */ public $sort; /** + * @var array list of the attributes that support sorting. If not set, it will be determined + * using [[Sort::attributes]]. + */ + public $attributes; + /** * @var array HTML attributes for the sorter container tag. */ public $options = array('class' => 'sorter'); @@ -58,8 +63,9 @@ class LinkSorter extends Widget */ protected function renderSortLinks() { + $attributes = empty($this->atttributes) ? array_keys($this->sort->attributes) : $this->attributes; $links = array(); - foreach (array_keys($this->sort->attributes) as $name) { + foreach ($attributes as $name) { $links[] = $this->sort->link($name); } return Html::ul($links, array('encode' => false)); From e683bd3ddb38ef24d427302d80ed23580aea2d3d Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 8 Sep 2013 08:05:49 -0400 Subject: [PATCH 041/157] CRUD WIP --- framework/yii/gii/generators/crud/Generator.php | 53 +++++++++++++++- .../gii/generators/crud/templates/controller.php | 36 ++++++----- .../gii/generators/crud/templates/views/_form.php | 72 ++++++++++------------ .../gii/generators/crud/templates/views/index.php | 65 +++++++++++++------ .../gii/generators/crud/templates/views/update.php | 8 +-- .../gii/generators/crud/templates/views/view.php | 23 ++++++- 6 files changed, 174 insertions(+), 83 deletions(-) diff --git a/framework/yii/gii/generators/crud/Generator.php b/framework/yii/gii/generators/crud/Generator.php index 3ed51c1..7f1bff0 100644 --- a/framework/yii/gii/generators/crud/Generator.php +++ b/framework/yii/gii/generators/crud/Generator.php @@ -151,7 +151,7 @@ class Generator extends \yii\gii\Generator $templatePath = $this->getTemplatePath() . '/views'; foreach (scandir($templatePath) as $file) { - if (!in_array($file, array('create.php', 'update.php', 'view.php'))) { + if (!in_array($file, array('index.php', 'create.php', 'update.php', 'view.php', '_form.php'))) { continue; } if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') { @@ -205,4 +205,55 @@ class Generator extends \yii\gii\Generator $pk = $class::primaryKey(); return $pk[0]; } + + /** + * @param ActiveRecord $model + * @param string $attribute + * @return string + */ + public function generateActiveField($model, $attribute) + { + $tableSchema = $model->getTableSchema(); + if (!isset($tableSchema->columns[$attribute])) { + return "\$form->field(\$model, '$attribute');"; + } + $column = $tableSchema->columns[$attribute]; + if ($column->phpType === 'boolean') { + return "\$form->field(\$model, '$attribute')->checkbox();"; + } elseif ($column->type === 'text') { + return "\$form->field(\$model, '$attribute')->textarea(array('rows' => 6));"; + } else { + if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) { + $input = 'passwordInput'; + } else { + $input = 'textInput'; + } + if ($column->phpType !== 'string' || $column->size === null) { + return "\$form->field(\$model, '$attribute')->$input();"; + } else { + return "\$form->field(\$model, '$attribute')->$input(array('maxlength' => $column->size));"; + } + } + } + + /** + * @param \yii\db\ColumnSchema $column + * @return string + */ + public function generateColumnFormat($column) + { + if ($column->phpType === 'boolean') { + return 'boolean'; + } elseif ($column->type === 'text') { + return 'ntext'; + } elseif (stripos($column->name, 'time') !== false && $column->phpType === 'integer') { + return 'datetime'; + } elseif (stripos($column->name, 'email') !== false) { + return 'email'; + } elseif (stripos($column->name, 'url') !== false) { + return 'url'; + } else { + return 'text'; + } + } } diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/framework/yii/gii/generators/crud/templates/controller.php index 7b3cce8..da915c8 100644 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ b/framework/yii/gii/generators/crud/templates/controller.php @@ -51,6 +51,20 @@ use yii\web\HttpException; class extends baseControllerClass) . "\n"; ?> { /** + * Lists all models. + * @return mixed + */ + public function actionIndex() + { + $dataProvider = new ActiveDataProvider(array( + 'query' => ::find(), + )); + return $this->render('index', array( + 'dataProvider' => $dataProvider, + )); + } + + /** * Displays a single model. * * @return mixed @@ -112,20 +126,8 @@ class extends '); - return $this->render('index', array( - 'dataProvider' => $dataProvider, - )); - } - - /** - * Returns the data model based on its primary key value. - * If the data model is not found, a 404 HTTP exception will be thrown. + * Finds the model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. * * @return the loaded model * @throws HttpException if the model cannot be found @@ -143,10 +145,10 @@ if (count($pks) === 1) { $condition = 'array(' . implode(', ', $condition) . ')'; } ?> - $model = ::find(); - if ($model === null) { + if (($model = ::find()) !== null) { + return $model; + } else { throw new HttpException(404, 'The requested page does not exist.'); } - return $model; } } diff --git a/framework/yii/gii/generators/crud/templates/views/_form.php b/framework/yii/gii/generators/crud/templates/views/_form.php index caf6f2a..6160e72 100644 --- a/framework/yii/gii/generators/crud/templates/views/_form.php +++ b/framework/yii/gii/generators/crud/templates/views/_form.php @@ -1,49 +1,45 @@ - -/* @var $this getControllerClass(); ?> */ -/* @var $model getModelClass(); ?> */ -/* @var $form CActiveForm */ -?> -
- -beginWidget('CActiveForm', array( - 'id'=>'".$this->class2id($this->modelClass)."-form', - // Please note: When you enable ajax validation, make sure the corresponding - // controller action is handling ajax validation correctly. - // There is a call to performAjaxValidation() commented in generated controller code. - // See class documentation of CActiveForm for details on this. - 'enableAjaxValidation'=>false, -)); ?>\n"; ?> +/** @var \yii\db\ActiveRecord $model */ +$class = $generator->modelClass; +$model = new $class; +$safeAttributes = $model->safeAttributes(); +if (empty($safeAttributes)) { + $safeAttributes = $model->getTableSchema()->columnNames; +} -

Fields with * are required.

+echo " - errorSummary(\$model); ?>\n"; ?> +use yii\helpers\Html; +use yii\widgets\ActiveForm; -tableSchema->columns as $column) -{ - if($column->autoIncrement) - continue; +/** + * @var yii\base\View $this + * @var modelClass, '\\'); ?> $model + * @var yii\widgets\ActiveForm $form + */ ?> -
- generateActiveLabel($this->modelClass,$column)."; ?>\n"; ?> - generateActiveField($this->modelClass,$column)."; ?>\n"; ?> - error(\$model,'{$column->name}'); ?>\n"; ?> -
- -
- isNewRecord ? 'Create' : 'Save'); ?>\n"; ?> -
+
+ + $form = ActiveForm::begin(); ?> + +generateActiveField($model, $attribute) . " ?>\n\n"; +} ?> +
+ echo Html::submitButton($model->isNewRecord ? 'Create' : 'Update', array('class' => 'btn btn-primary')); ?> +
-endWidget(); ?>\n"; ?> + ActiveForm::end(); ?> -
\ No newline at end of file +
diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index a115251..fbac7c3 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -1,29 +1,54 @@ - -/* @var $this getControllerClass(); ?> */ -/* @var $dataProvider CActiveDataProvider */ -pluralize($this->class2name($this->modelClass)); -echo "\$this->breadcrumbs=array( - '$label', -);\n"; +/** @var \yii\db\ActiveRecord $model */ +$class = $generator->modelClass; +$pks = $class::primaryKey(); +if (count($pks) === 1) { + $viewUrl = "array('view', 'id' => \$model->{$pks[0]})"; +} else { + $params = array(); + foreach ($pks as $pk) { + $params[] = "'$pk' => \$model->$pk"; + } + $viewUrl = "array('view', " . implode(', ', $params) . ')'; +} + +$nameAttribute = $generator->getNameAttribute(); + +echo " -$this->menu=array( - array('label'=>'Create modelClass; ?>', 'url'=>array('create')), - array('label'=>'Manage modelClass; ?>', 'url'=>array('admin')), -); +use yii\helpers\Html; +use indexWidgetType === 'grid' ? 'yii\grid\GridView' : 'yii\widgets\ListView'; ?>; + +/** + * @var yii\base\View $this + * @var yii\data\ActiveDataProvider $dataProvider + */ + +$this->title = 'modelClass))); ?>'; ?> +
+ +

echo Html::encode($this->title); ?>

+ +indexWidgetType === 'grid'): ?> -

+ + echo ListView::widget(array( + 'dataProvider' => $dataProvider, + 'itemView' => function ($model, $key, $index, $widget) { + return Html::a(Html::encode($model->), ); + }, + )); ?> + - $this->widget('zii.widgets.CListView', array( - 'dataProvider'=>$dataProvider, - 'itemView'=>'_view', -)); ?> +
diff --git a/framework/yii/gii/generators/crud/templates/views/update.php b/framework/yii/gii/generators/crud/templates/views/update.php index 8dc3c73..bacc1ec 100644 --- a/framework/yii/gii/generators/crud/templates/views/update.php +++ b/framework/yii/gii/generators/crud/templates/views/update.php @@ -14,11 +14,11 @@ echo "modelClass, '\\'); ?> $model -*/ + * @var yii\base\View $this + * @var modelClass, '\\'); ?> $model + */ -$this->title = 'Modify modelClass)); ?>: ' . $model->getNameAttribute(); ?>; +$this->title = 'Update modelClass)); ?>: ' . $model->getNameAttribute(); ?>; ?>
diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php index baca46f..214f10b 100644 --- a/framework/yii/gii/generators/crud/templates/views/view.php +++ b/framework/yii/gii/generators/crud/templates/views/view.php @@ -8,15 +8,20 @@ use yii\helpers\StringHelper; * @var yii\gii\generators\crud\Generator $generator */ +/** @var \yii\db\ActiveRecord $model */ +$class = $generator->modelClass; +$model = new $class; + echo " use yii\helpers\Html; +use yii\widgets\DetailView; /** -* @var yii\base\View $this -* @var modelClass, '\\'); ?> $model -*/ + * @var yii\base\View $this + * @var modelClass, '\\'); ?> $model + */ $this->title = $model->getNameAttribute(); ?>; ?> @@ -24,4 +29,16 @@ $this->title = $model->getNameAttribute(); ?>;

echo Html::encode($this->title); ?>

+ echo DetailView::widget(array( + 'model' => $model, + 'attributes' => array( +getTableSchema()->columns as $column) { + $format = $generator->generateColumnFormat($column); + echo "\t\t\t'" . $column->name . ($format === 'text' ? '' : ':' . $format) . "',\n"; +} +?> + ), + )); ?> +
From b6f07859c1c28954eee41d6931c04e6cdec7a143 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 8 Sep 2013 08:47:52 -0400 Subject: [PATCH 042/157] Added support for getting all tables for pgsql. --- framework/yii/db/pgsql/Schema.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 8acb7bd..9693dd6 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -129,6 +129,35 @@ class Schema extends \yii\db\Schema } /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * If not empty, the returned table names will be prefixed with the schema name. + * @return array all table names in the database. + */ + protected function findTableNames($schema = '') + { + if ($schema === '') { + $schema = $this->defaultSchema; + } + $sql = <<db->createCommand($sql); + $command->bindParam(':schema', $schema); + $rows = $command->queryAll(); + $names = array(); + foreach ($rows as $row) { + if ($schema === $this->defaultSchema) { + $names[] = $row['table_name']; + } else { + $names[] = $row['table_schema'] . '.' . $row['table_name']; + } + } + return $names; + } + + /** * Collects the foreign key column details for the given table. * @param TableSchema $table the table metadata */ From 851dfe6333daa069ebfa4c67d90d73611f4a6342 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 8 Sep 2013 09:58:28 -0400 Subject: [PATCH 043/157] Use horizontal form for login page. --- apps/basic/views/site/login.php | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/apps/basic/views/site/login.php b/apps/basic/views/site/login.php index 524b0cc..f1a02b5 100644 --- a/apps/basic/views/site/login.php +++ b/apps/basic/views/site/login.php @@ -15,20 +15,32 @@ $this->params['breadcrumbs'][] = $this->title;

Please fill out the following fields to login:

-
-
- 'login-form')); ?> - field($model, 'username'); ?> - field($model, 'password')->passwordInput(); ?> - field($model, 'rememberMe')->checkbox(); ?> -
- 'btn btn-primary')); ?> -
- -
-
- You may login with admin/admin or demo/demo.
- To modify the username/password, please check out the code app\models\User::$users. + 'login-form', + 'options' => array('class' => 'form-horizontal'), + 'fieldConfig' => array( + 'template' => "
{label}
\n
{input}
\n
{error}
", + ), + )); ?> + + field($model, 'username'); ?> + + field($model, 'password')->passwordInput(); ?> + + field($model, 'rememberMe', array( + 'template' => "
{input}
\n
{error}
", + ))->checkbox(); ?> + +
+
+ 'btn btn-primary')); ?> +
+ + + +
+ You may login with admin/admin or demo/demo.
+ To modify the username/password, please check out the code app\models\User::$users.
From ea561ed3e03d09d9b5e217db85f516782cbb7e28 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 9 Sep 2013 07:05:35 -0400 Subject: [PATCH 044/157] Fixes #861. --- framework/yii/widgets/ActiveField.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/yii/widgets/ActiveField.php b/framework/yii/widgets/ActiveField.php index bba1ead..6c06483 100644 --- a/framework/yii/widgets/ActiveField.php +++ b/framework/yii/widgets/ActiveField.php @@ -410,6 +410,7 @@ class ActiveField extends Component */ public function checkbox($options = array(), $enclosedByLabel = true) { + $options = array_merge($this->inputOptions, $options); if ($enclosedByLabel) { if (!isset($options['label'])) { $options['label'] = Html::encode($this->model->getAttributeLabel($this->attribute)); From 791905e8bbf925d7cd149f4473e863bfc1ec2ed5 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 9 Sep 2013 08:44:40 -0400 Subject: [PATCH 045/157] Reverted previous change as it breaks layout. --- framework/yii/widgets/ActiveField.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/framework/yii/widgets/ActiveField.php b/framework/yii/widgets/ActiveField.php index 6c06483..51f27ab 100644 --- a/framework/yii/widgets/ActiveField.php +++ b/framework/yii/widgets/ActiveField.php @@ -374,7 +374,6 @@ class ActiveField extends Component */ public function radio($options = array(), $enclosedByLabel = true) { - $options = array_merge($this->inputOptions, $options); if ($enclosedByLabel) { if (!isset($options['label'])) { $options['label'] = Html::encode($this->model->getAttributeLabel($this->attribute)); @@ -410,7 +409,6 @@ class ActiveField extends Component */ public function checkbox($options = array(), $enclosedByLabel = true) { - $options = array_merge($this->inputOptions, $options); if ($enclosedByLabel) { if (!isset($options['label'])) { $options['label'] = Html::encode($this->model->getAttributeLabel($this->attribute)); From 347e79a4674e655fd88161c6bd5c69467b8e2d65 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 9 Sep 2013 21:27:58 -0400 Subject: [PATCH 046/157] Fixes #863: adjusted horizontal form layout --- apps/basic/views/layouts/main.php | 2 +- apps/basic/views/site/login.php | 21 ++++++++------- .../gii/generators/crud/templates/views/_view.php | 31 ---------------------- 3 files changed, 12 insertions(+), 42 deletions(-) delete mode 100644 framework/yii/gii/generators/crud/templates/views/_view.php diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php index 04a2f33..240c2a3 100644 --- a/apps/basic/views/layouts/main.php +++ b/apps/basic/views/layouts/main.php @@ -36,7 +36,7 @@ app\config\AppAsset::register($this); array('label' => 'Contact', 'url' => array('/site/contact')), Yii::$app->user->isGuest ? array('label' => 'Login', 'url' => array('/site/login')) : - array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , 'url' => array('/site/logout')), + array('label' => 'Logout (' . Html::encode(Yii::$app->user->identity->username) .')' , 'url' => array('/site/logout')), ), )); NavBar::end(); diff --git a/apps/basic/views/site/login.php b/apps/basic/views/site/login.php index f1a02b5..f61d9d7 100644 --- a/apps/basic/views/site/login.php +++ b/apps/basic/views/site/login.php @@ -19,23 +19,24 @@ $this->params['breadcrumbs'][] = $this->title; 'id' => 'login-form', 'options' => array('class' => 'form-horizontal'), 'fieldConfig' => array( - 'template' => "
{label}
\n
{input}
\n
{error}
", + 'template' => "{label}\n
{input}
\n
{error}
", + 'labelOptions' => array('class' => 'col-lg-1 control-label'), ), )); ?> - field($model, 'username'); ?> + field($model, 'username'); ?> - field($model, 'password')->passwordInput(); ?> + field($model, 'password')->passwordInput(); ?> - field($model, 'rememberMe', array( - 'template' => "
{input}
\n
{error}
", - ))->checkbox(); ?> + field($model, 'rememberMe', array( + 'template' => "
{input}
\n
{error}
", + ))->checkbox(); ?> -
-
- 'btn btn-primary')); ?> -
+
+
+ 'btn btn-primary')); ?>
+
diff --git a/framework/yii/gii/generators/crud/templates/views/_view.php b/framework/yii/gii/generators/crud/templates/views/_view.php deleted file mode 100644 index 0f11051..0000000 --- a/framework/yii/gii/generators/crud/templates/views/_view.php +++ /dev/null @@ -1,31 +0,0 @@ - - -/* @var $this getControllerClass(); ?> */ -/* @var $data getModelClass(); ?> */ -?> - -
- -getAttributeLabel('{$this->tableSchema->primaryKey}')); ?>:\n"; -echo "\t{$this->tableSchema->primaryKey}), array('view', 'id'=>\$data->{$this->tableSchema->primaryKey})); ?>\n\t
\n\n"; -$count=0; -foreach($this->tableSchema->columns as $column) -{ - if($column->isPrimaryKey) - continue; - if(++$count==7) - echo "\tgetAttributeLabel('{$column->name}')); ?>:\n"; - echo "\t{$column->name}); ?>\n\t
\n\n"; -} -if($count>=7) - echo "\t*/ ?>\n"; -?> - -
\ No newline at end of file From e9a5b92dd9a3f584b20322fdca8d0c3cb995a376 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 9 Sep 2013 21:59:50 -0400 Subject: [PATCH 047/157] Added StringHelper::dirname() --- framework/yii/helpers/StringHelperBase.php | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/framework/yii/helpers/StringHelperBase.php b/framework/yii/helpers/StringHelperBase.php index 54dabda..cbb696e 100644 --- a/framework/yii/helpers/StringHelperBase.php +++ b/framework/yii/helpers/StringHelperBase.php @@ -47,8 +47,8 @@ class StringHelperBase /** * Returns the trailing name component of a path. - * This method does the same as the php function `basename()` except that it will - * always use \ and / as directory separators, independent of the operating system. + * This method is similar to the php function `basename()` except that it will + * treat both \ and / as directory separators, independent of the operating system. * This method was mainly created to work on php namespaces. When working with real * file paths, php's `basename()` should work fine for you. * Note: this method is not aware of the actual filesystem, or path components such as "..". @@ -70,6 +70,24 @@ class StringHelperBase } /** + * Returns parent directory's path. + * This method is similar to `dirname()` except that it will treat + * both \ and / as directory separators, independent of the operating system. + * @param string $path A path string. + * @return string the parent directory's path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function dirname($path) + { + $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); + if ($pos !== false) { + return mb_substr($path, 0, $pos); + } else { + return $path; + } + } + + /** * Compares two strings or string arrays, and return their differences. * This is a wrapper of the [phpspec/php-diff](https://packagist.org/packages/phpspec/php-diff) package. * @param string|array $lines1 the first string or string array to be compared. If it is a string, From db212f0586a873b208615443db7d3444037096e3 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 9 Sep 2013 22:49:01 -0400 Subject: [PATCH 048/157] Added $label parameter to ActiveField::label(). --- framework/yii/widgets/ActiveField.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/framework/yii/widgets/ActiveField.php b/framework/yii/widgets/ActiveField.php index 51f27ab..ea8aa1b 100644 --- a/framework/yii/widgets/ActiveField.php +++ b/framework/yii/widgets/ActiveField.php @@ -216,22 +216,19 @@ class ActiveField extends Component /** * Generates a label tag for [[attribute]]. - * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. + * @param string $label the label to use. If null, it will be generated via [[Model::getAttributeLabel()]]. + * Note that this will NOT be [[Html::encode()|encoded]]. * @param array $options the tag options in terms of name-value pairs. It will be merged with [[labelOptions]]. * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * The following options are specially handled: - * - * - label: this specifies the label to be displayed. Note that this will NOT be [[encoded()]]. - * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display - * (after encoding). - * * @return ActiveField the field object itself */ - public function label($options = array()) + public function label($label = null, $options = array()) { $options = array_merge($this->labelOptions, $options); + if ($label !== null) { + $options['label'] = $label; + } $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $options); return $this; } From 4cbdd7a6dd0d6e186d13e453257865506f5899c7 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 9 Sep 2013 23:36:44 -0400 Subject: [PATCH 049/157] crud WIP --- framework/yii/gii/assets/gii.js | 5 - framework/yii/gii/generators/crud/Generator.php | 208 +++++++++++++++++---- framework/yii/gii/generators/crud/form.php | 3 +- .../gii/generators/crud/templates/controller.php | 69 +++---- .../yii/gii/generators/crud/templates/search.php | 85 ++++++++- .../gii/generators/crud/templates/views/_form.php | 5 +- .../generators/crud/templates/views/_search.php | 59 +++--- .../gii/generators/crud/templates/views/create.php | 2 + .../gii/generators/crud/templates/views/index.php | 49 +++-- .../gii/generators/crud/templates/views/update.php | 5 + .../gii/generators/crud/templates/views/view.php | 13 +- framework/yii/gii/views/default/view.php | 2 +- 12 files changed, 362 insertions(+), 143 deletions(-) diff --git a/framework/yii/gii/assets/gii.js b/framework/yii/gii/assets/gii.js index 45951a1..b581d3b 100644 --- a/framework/yii/gii/assets/gii.js +++ b/framework/yii/gii/assets/gii.js @@ -81,11 +81,6 @@ yii.gii = (function ($) { $('#model-generator .field-generator-modelclass').toggle($(this).val().indexOf('*') == -1); }).change(); - // crud generator: hide Search Model Class input if search is not enabled - $('#crud-generator #generator-enablesearch').on('change', function () { - $('#crud-generator .field-generator-searchmodelclass').toggle(this.checked); - }).change(); - // hide Generate button if any input is changed $('.default-view .form-group input,select,textarea').change(function () { $('.default-view-results,.default-view-files').hide(); diff --git a/framework/yii/gii/generators/crud/Generator.php b/framework/yii/gii/generators/crud/Generator.php index 7f1bff0..9bce7ff 100644 --- a/framework/yii/gii/generators/crud/Generator.php +++ b/framework/yii/gii/generators/crud/Generator.php @@ -10,6 +10,7 @@ namespace yii\gii\generators\crud; use Yii; use yii\base\Model; use yii\db\ActiveRecord; +use yii\db\Schema; use yii\gii\CodeFile; use yii\helpers\Inflector; use yii\web\Controller; @@ -26,7 +27,6 @@ class Generator extends \yii\gii\Generator public $controllerClass; public $baseControllerClass = 'yii\web\Controller'; public $indexWidgetType = 'grid'; - public $enableSearch = true; public $searchModelClass; public function getName() @@ -44,16 +44,14 @@ class Generator extends \yii\gii\Generator { return array_merge(parent::rules(), array( array('moduleID, controllerClass, modelClass, searchModelClass, baseControllerClass', 'filter', 'filter' => 'trim'), - array('modelClass, controllerClass, baseControllerClass, indexWidgetType', 'required'), + array('modelClass, searchModelClass, controllerClass, baseControllerClass, indexWidgetType', 'required'), array('modelClass, controllerClass, baseControllerClass, searchModelClass', 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'), array('modelClass', 'validateClass', 'params' => array('extends' => ActiveRecord::className())), array('baseControllerClass', 'validateClass', 'params' => array('extends' => Controller::className())), array('controllerClass', 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'), array('controllerClass, searchModelClass', 'validateNewClass'), - array('enableSearch', 'boolean'), array('indexWidgetType', 'in', 'range' => array('grid', 'list')), array('modelClass', 'validateModelClass'), - array('searchModelClass', 'validateSearchModelClass'), array('moduleID', 'validateModuleID'), )); } @@ -66,7 +64,6 @@ class Generator extends \yii\gii\Generator 'controllerClass' => 'Controller Class', 'baseControllerClass' => 'Base Controller Class', 'indexWidgetType' => 'Widget Used in Index Page', - 'enableSearch' => 'Enable Search', 'searchModelClass' => 'Search Model Class', )); } @@ -87,11 +84,8 @@ class Generator extends \yii\gii\Generator If not set, it means the controller will belong to the application.', 'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models. You may choose either GridView or ListView', - 'enableSearch' => 'Whether to enable the search functionality on the index page. When search is enabled, - a search form will be displayed on the index page, and the index page will display the search results.', 'searchModelClass' => 'This is the class representing the data being collecting in the search form. - A fully qualified namespaced class name is required, e.g., app\models\PostSearchForm. - This is only used when search is enabled.', + A fully qualified namespaced class name is required, e.g., app\models\search\PostSearch.', ); } @@ -107,7 +101,7 @@ class Generator extends \yii\gii\Generator */ public function stickyAttributes() { - return array('baseControllerClass', 'moduleID', 'indexWidgetType', 'enableSearch'); + return array('baseControllerClass', 'moduleID', 'indexWidgetType'); } public function validateModelClass() @@ -120,13 +114,6 @@ class Generator extends \yii\gii\Generator } } - public function validateSearchModelClass() - { - if ($this->enableSearch && empty($this->searchModelClass)) { - $this->addError('searchModelClass', 'Search Model Class cannot be empty.'); - } - } - public function validateModuleID() { if (!empty($this->moduleID)) { @@ -142,26 +129,21 @@ class Generator extends \yii\gii\Generator */ public function generate() { - $files = array(); - $files[] = new CodeFile( - $this->getControllerFile(), - $this->render('controller.php') + $controllerFile = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php'); + $searchModel = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->searchModelClass, '\\') . '.php')); + $files = array( + new CodeFile($controllerFile, $this->render('controller.php')), + new CodeFile($searchModel, $this->render('search.php')), ); - $viewPath = $this->getViewPath(); + $viewPath = $this->getViewPath(); $templatePath = $this->getTemplatePath() . '/views'; foreach (scandir($templatePath) as $file) { - if (!in_array($file, array('index.php', 'create.php', 'update.php', 'view.php', '_form.php'))) { - continue; - } if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') { $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file")); } } - if ($this->enableSearch) { - - } return $files; } @@ -177,14 +159,6 @@ class Generator extends \yii\gii\Generator } /** - * @return string the controller class file path - */ - public function getControllerFile() - { - return Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php'); - } - - /** * @return string the action view file path */ public function getViewPath() @@ -207,13 +181,12 @@ class Generator extends \yii\gii\Generator } /** - * @param ActiveRecord $model * @param string $attribute * @return string */ - public function generateActiveField($model, $attribute) + public function generateActiveField($attribute) { - $tableSchema = $model->getTableSchema(); + $tableSchema = $this->getTableSchema(); if (!isset($tableSchema->columns[$attribute])) { return "\$form->field(\$model, '$attribute');"; } @@ -237,6 +210,21 @@ class Generator extends \yii\gii\Generator } /** + * @param string $attribute + * @return string + */ + public function generateActiveSearchField($attribute) + { + $tableSchema = $this->getTableSchema(); + $column = $tableSchema->columns[$attribute]; + if ($column->phpType === 'boolean') { + return "\$form->field(\$model, '$attribute')->checkbox();"; + } else { + return "\$form->field(\$model, '$attribute');"; + } + } + + /** * @param \yii\db\ColumnSchema $column * @return string */ @@ -256,4 +244,146 @@ class Generator extends \yii\gii\Generator return 'text'; } } + + /** + * Generates validation rules for the search model. + * @return array the generated validation rules + */ + public function generateSearchRules() + { + $table = $this->getTableSchema(); + $types = array(); + foreach ($table->columns as $column) { + switch ($column->type) { + case Schema::TYPE_SMALLINT: + case Schema::TYPE_INTEGER: + case Schema::TYPE_BIGINT: + $types['integer'][] = $column->name; + break; + case Schema::TYPE_BOOLEAN: + $types['boolean'][] = $column->name; + break; + case Schema::TYPE_FLOAT: + case Schema::TYPE_DECIMAL: + case Schema::TYPE_MONEY: + $types['number'][] = $column->name; + break; + case Schema::TYPE_DATE: + case Schema::TYPE_TIME: + case Schema::TYPE_DATETIME: + case Schema::TYPE_TIMESTAMP: + default: + $types['safe'][] = $column->name; + break; + } + } + + $rules = array(); + foreach ($types as $type => $columns) { + $rules[] = "array('" . implode(', ', $columns) . "', '$type')"; + } + + return $rules; + } + + public function getSearchAttributes() + { + return $this->getTableSchema()->getColumnNames(); + } + + /** + * Generates the attribute labels for the search model. + * @return array the generated attribute labels (name => label) + */ + public function generateSearchLabels() + { + $table = $this->getTableSchema(); + $labels = array(); + foreach ($table->columns as $column) { + if (!strcasecmp($column->name, 'id')) { + $labels[$column->name] = 'ID'; + } else { + $label = Inflector::camel2words($column->name); + if (strcasecmp(substr($label, -3), ' id') === 0) { + $label = substr($label, 0, -3) . ' ID'; + } + $labels[$column->name] = $label; + } + } + return $labels; + } + + public function generateSearchConditions() + { + $table = $this->getTableSchema(); + $conditions = array(); + foreach ($table->columns as $column) { + switch ($column->type) { + case Schema::TYPE_SMALLINT: + case Schema::TYPE_INTEGER: + case Schema::TYPE_BIGINT: + case Schema::TYPE_BOOLEAN: + case Schema::TYPE_FLOAT: + case Schema::TYPE_DECIMAL: + case Schema::TYPE_MONEY: + case Schema::TYPE_DATE: + case Schema::TYPE_TIME: + case Schema::TYPE_DATETIME: + case Schema::TYPE_TIMESTAMP: + $conditions[] = "\$this->addCondition(\$query, '{$column->name}');"; + break; + default: + $conditions[] = "\$this->addCondition(\$query, '{$column->name}', true);"; + break; + } + } + + return $conditions; + } + + public function generateUrlParams() + { + $pks = $this->getTableSchema()->primaryKey; + if (count($pks) === 1) { + return "'id' => \$model->{$pks[0]}"; + } else { + $params = array(); + foreach ($pks as $pk) { + $params[] = "'$pk' => \$model->$pk"; + } + return implode(', ', $params); + } + } + + public function generateActionParams() + { + $pks = $this->getTableSchema()->primaryKey; + if (count($pks) === 1) { + return '$id'; + } else { + return '$' . implode(', $', $pks); + } + } + + public function generateActionParamComments() + { + $table = $this->getTableSchema(); + $pks = $table->primaryKey; + if (count($pks) === 1) { + return array('@param ' . $table->columns[$pks[0]]->phpType . ' $id'); + } else { + $params = array(); + foreach ($pks as $pk) { + $params[] = '@param ' . $table->columns[$pk]->phpType . ' $' . $pk; + } + return $params; + } + } + + public function getTableSchema() + { + /** @var ActiveRecord $class */ + $class = $this->modelClass; + return $class::getTableSchema(); + } } diff --git a/framework/yii/gii/generators/crud/form.php b/framework/yii/gii/generators/crud/form.php index ee4771b..829b8a3 100644 --- a/framework/yii/gii/generators/crud/form.php +++ b/framework/yii/gii/generators/crud/form.php @@ -6,11 +6,10 @@ */ echo $form->field($generator, 'modelClass'); +echo $form->field($generator, 'searchModelClass'); echo $form->field($generator, 'controllerClass'); echo $form->field($generator, 'baseControllerClass'); echo $form->field($generator, 'moduleID'); -echo $form->field($generator, 'enableSearch')->checkbox(); -echo $form->field($generator, 'searchModelClass'); echo $form->field($generator, 'indexWidgetType')->dropDownList(array( 'grid' => 'GridView', 'list' => 'ListView', diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/framework/yii/gii/generators/crud/templates/controller.php index da915c8..fd33eda 100644 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ b/framework/yii/gii/generators/crud/templates/controller.php @@ -9,38 +9,22 @@ use yii\helpers\StringHelper; * @var yii\gii\generators\crud\Generator $generator */ -$pos = strrpos($generator->controllerClass, '\\'); -$ns = ltrim(substr($generator->controllerClass, 0, $pos), '\\'); -$controllerClass = substr($generator->controllerClass, $pos + 1); -$pos = strrpos($generator->modelClass, '\\'); -$modelClass = $pos === false ? $generator->modelClass : substr($generator->modelClass, $pos + 1); - -/** @var \yii\db\ActiveRecord $class */ -$class = $generator->modelClass; -$pks = $class::primaryKey(); -$schema = $class::getTableSchema(); -if (count($pks) === 1) { - $ids = '$id'; - $params = "array('id' => \$model->{$pks[0]})"; - $paramComments = '@param ' . $schema->columns[$pks[0]]->phpType . ' $id'; -} else { - $ids = '$' . implode(', $', $pks); - $params = array(); - $paramComments = array(); - foreach ($pks as $pk) { - $paramComments[] = '@param ' . $schema->columns[$pk]->phpType . ' $' . $pk; - $params[] = "'$pk' => \$model->$pk"; - } - $params = implode(', ', $params); - $paramComments = implode("\n\t * ", $paramComments); -} +$controllerClass = StringHelper::basename($generator->controllerClass); +$modelClass = StringHelper::basename($generator->modelClass); +$searchModelClass = StringHelper::basename($generator->searchModelClass); + +$pks = $generator->getTableSchema()->primaryKey; +$urlParams = $generator->generateUrlParams(); +$actionParams = $generator->generateActionParams(); +$actionParamComments = $generator->generateActionParamComments(); echo " -namespace ; +namespace controllerClass, '\\')); ?>; use modelClass, '\\'); ?>; +use searchModelClass, '\\'); ?>; use yii\data\ActiveDataProvider; use baseControllerClass, '\\'); ?>; use yii\web\HttpException; @@ -56,23 +40,24 @@ class extends ::find(), - )); + $searchModel = new ; + $dataProvider = $searchModel->search($_GET); + return $this->render('index', array( 'dataProvider' => $dataProvider, + 'searchModel' => $searchModel, )); } /** * Displays a single model. - * + * * @return mixed */ - public function actionView() + public function actionView() { return $this->render('view', array( - 'model' => $this->findModel(), + 'model' => $this->findModel(), )); } @@ -86,7 +71,7 @@ class extends ; if ($model->load($_POST) && $model->save()) { - return $this->redirect(array('view', )); + return $this->redirect(array('view', )); } else { return $this->render('create', array( 'model' => $model, @@ -97,15 +82,15 @@ class extends model. * If update is successful, the browser will be redirected to the 'view' page. - * + * * @return mixed */ - public function actionUpdate() + public function actionUpdate() { - $model = $this->findModel(); + $model = $this->findModel(); if ($model->load($_POST) && $model->save()) { - return $this->redirect(array('view', )); + return $this->redirect(array('view', )); } else { return $this->render('update', array( 'model' => $model, @@ -116,23 +101,23 @@ class extends model. * If deletion is successful, the browser will be redirected to the 'index' page. - * + * * @return mixed */ - public function actionDelete() + public function actionDelete() { - $this->findModel()->delete(); + $this->findModel()->delete(); return $this->redirect(array('index')); } /** * Finds the model based on its primary key value. * If the model is not found, a 404 HTTP exception will be thrown. - * + * * @return the loaded model * @throws HttpException if the model cannot be found */ - protected function findModel() + protected function findModel() { modelClass); +$searchModelClass = StringHelper::basename($generator->searchModelClass); +$rules = $generator->generateSearchRules(); +$labels = $generator->generateSearchLabels(); +$searchAttributes = $generator->getSearchAttributes(); +$searchConditions = $generator->generateSearchConditions(); + +echo " + +namespace searchModelClass, '\\')); ?>; + +use yii\base\Model; +use yii\data\ActiveDataProvider; +use modelClass, '\\'); ?>; + +/** + * represents the model behind the search form about . + */ +class extends Model +{ + public $; + + public function rules() + { + return array( + , + ); + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return array( + $label): ?> + '" . addslashes($label) . "',\n"; ?> + + ); + } + + public function search($params) + { + $query = ::find(); + $dataProvider = new ActiveDataProvider(array( + 'query' => $query, + )); + + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + + + return $dataProvider; + } + + protected function addCondition($query, $attribute, $partialMatch = false) + { + $value = $this->$attribute; + if (trim($value) === '') { + return; + } + if ($partialMatch) { + $value = '%' . strtr($value, array('%'=>'\%', '_'=>'\_', '\\'=>'\\\\')) . '%'; + $query->andWhere(array('like', $attribute, $value)); + } else { + $query->andWhere(array($attribute => $value)); + } + } +} diff --git a/framework/yii/gii/generators/crud/templates/views/_form.php b/framework/yii/gii/generators/crud/templates/views/_form.php index 6160e72..2d9d5dc 100644 --- a/framework/yii/gii/generators/crud/templates/views/_form.php +++ b/framework/yii/gii/generators/crud/templates/views/_form.php @@ -9,8 +9,7 @@ use yii\helpers\StringHelper; */ /** @var \yii\db\ActiveRecord $model */ -$class = $generator->modelClass; -$model = new $class; +$model = new $generator->modelClass; $safeAttributes = $model->safeAttributes(); if (empty($safeAttributes)) { $safeAttributes = $model->getTableSchema()->columnNames; @@ -34,7 +33,7 @@ use yii\widgets\ActiveForm; $form = ActiveForm::begin(); ?> generateActiveField($model, $attribute) . " ?>\n\n"; + echo "\t\tgenerateActiveField($attribute) . " ?>\n\n"; } ?>
echo Html::submitButton($model->isNewRecord ? 'Create' : 'Update', array('class' => 'btn btn-primary')); ?> diff --git a/framework/yii/gii/generators/crud/templates/views/_search.php b/framework/yii/gii/generators/crud/templates/views/_search.php index a9679f1..a649589 100644 --- a/framework/yii/gii/generators/crud/templates/views/_search.php +++ b/framework/yii/gii/generators/crud/templates/views/_search.php @@ -1,38 +1,45 @@ - -/* @var $this getControllerClass(); ?> */ -/* @var $model getModelClass(); ?> */ -/* @var $form CActiveForm */ + +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +/** + * @var yii\base\View $this + * @var searchModelClass, '\\'); ?> $model + * @var yii\widgets\ActiveForm $form + */ ?> -
+ \ No newline at end of file +
diff --git a/framework/yii/gii/generators/crud/templates/views/create.php b/framework/yii/gii/generators/crud/templates/views/create.php index 48a2318..669b99a 100644 --- a/framework/yii/gii/generators/crud/templates/views/create.php +++ b/framework/yii/gii/generators/crud/templates/views/create.php @@ -19,6 +19,8 @@ use yii\helpers\Html; */ $this->title = 'Create modelClass)); ?>'; +$this->params['breadcrumbs'][] = array('label' => 'modelClass))); ?>', 'url' => array('index')); +$this->params['breadcrumbs'][] = $this->title; ?>
diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index fbac7c3..8efa53a 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -8,19 +8,7 @@ use yii\helpers\StringHelper; * @var yii\gii\generators\crud\Generator $generator */ -/** @var \yii\db\ActiveRecord $model */ -$class = $generator->modelClass; -$pks = $class::primaryKey(); -if (count($pks) === 1) { - $viewUrl = "array('view', 'id' => \$model->{$pks[0]})"; -} else { - $params = array(); - foreach ($pks as $pk) { - $params[] = "'$pk' => \$model->$pk"; - } - $viewUrl = "array('view', " . implode(', ', $params) . ')'; -} - +$urlParams = $generator->generateUrlParams(); $nameAttribute = $generator->getNameAttribute(); echo "indexWidgetType === 'grid' ? 'yii\grid\GridView' : 'y /** * @var yii\base\View $this * @var yii\data\ActiveDataProvider $dataProvider + * @var searchModelClass, '\\'); ?> $searchModel */ $this->title = 'modelClass))); ?>'; +$this->params['breadcrumbs'][] = $this->title; ?>

echo Html::encode($this->title); ?>

-indexWidgetType === 'grid'): ?> + echo $this->render('_search', array('model' => $searchModel)); ?> +
+ +
+ echo Html::a('Create modelClass); ?>', array('create'), array('class' => 'btn btn-danger')); ?> +
+ +indexWidgetType === 'grid'): ?> + echo GridView::widget(array( + 'dataProvider' => $dataProvider, + 'filterModel' => $searchModel, + 'columns' => array( +getTableSchema()->columns as $column) { + $format = $generator->generateColumnFormat($column); + if (++$count < 6) { + echo "\t\t\t'" . $column->name . ($format === 'text' ? '' : ':' . $format) . "',\n"; + } else { + echo "\t\t\t// '" . $column->name . ($format === 'text' ? '' : ':' . $format) . "',\n"; + } +} +?> + ), + )); ?> - echo ListView::widget(array( + echo ListView::widget(array( 'dataProvider' => $dataProvider, + 'itemOptions' => array( + 'class' => 'item', + ), 'itemView' => function ($model, $key, $index, $widget) { - return Html::a(Html::encode($model->), ); + return Html::a(Html::encode($model->), array('view', ); }, )); ?> diff --git a/framework/yii/gii/generators/crud/templates/views/update.php b/framework/yii/gii/generators/crud/templates/views/update.php index bacc1ec..cb892c2 100644 --- a/framework/yii/gii/generators/crud/templates/views/update.php +++ b/framework/yii/gii/generators/crud/templates/views/update.php @@ -8,6 +8,8 @@ use yii\helpers\StringHelper; * @var yii\gii\generators\crud\Generator $generator */ +$urlParams = $generator->generateUrlParams(); + echo " @@ -19,6 +21,9 @@ use yii\helpers\Html; */ $this->title = 'Update modelClass)); ?>: ' . $model->getNameAttribute(); ?>; +$this->params['breadcrumbs'][] = array('label' => 'modelClass))); ?>', 'url' => array('index')); +$this->params['breadcrumbs'][] = array('label' => $model->getNameAttribute(); ?>, 'url' => array('view', )); +$this->params['breadcrumbs'][] = 'Update'; ?>
diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php index 214f10b..d08ec23 100644 --- a/framework/yii/gii/generators/crud/templates/views/view.php +++ b/framework/yii/gii/generators/crud/templates/views/view.php @@ -8,9 +8,7 @@ use yii\helpers\StringHelper; * @var yii\gii\generators\crud\Generator $generator */ -/** @var \yii\db\ActiveRecord $model */ -$class = $generator->modelClass; -$model = new $class; +$urlParams = $generator->generateUrlParams(); echo " @@ -24,16 +22,23 @@ use yii\widgets\DetailView; */ $this->title = $model->getNameAttribute(); ?>; +$this->params['breadcrumbs'][] = array('label' => 'modelClass))); ?>', 'url' => array('index')); +$this->params['breadcrumbs'][] = $this->title; ?>

echo Html::encode($this->title); ?>

+
+ echo Html::a('Update', array('update', ), array('class' => 'btn btn-danger')); ?> + echo Html::a('Delete', array('delete', ), array('class' => 'btn btn-danger')); ?> +
+ echo DetailView::widget(array( 'model' => $model, 'attributes' => array( getTableSchema()->columns as $column) { +foreach ($generator->getTableSchema()->columns as $column) { $format = $generator->generateColumnFormat($column); echo "\t\t\t'" . $column->name . ($format === 'text' ? '' : ':' . $format) . "',\n"; } diff --git a/framework/yii/gii/views/default/view.php b/framework/yii/gii/views/default/view.php index bf05e84..9754918 100644 --- a/framework/yii/gii/views/default/view.php +++ b/framework/yii/gii/views/default/view.php @@ -40,7 +40,7 @@ foreach ($generator->templates as $name => $path) { 'form' => $form, )); ?> field($generator, 'template')->sticky() - ->label(array('label' => 'Code Template')) + ->label('Code Template') ->dropDownList($templates)->hint(' Please select which set of the templates should be used to generated the code. '); ?> From 590121c4ff76b548b2bdb74f72ce247a806af415 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 10 Sep 2013 14:42:22 +0400 Subject: [PATCH 050/157] Zend Data Cache returns null when record doesn't exist --- framework/yii/caching/ZendDataCache.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/framework/yii/caching/ZendDataCache.php b/framework/yii/caching/ZendDataCache.php index 9ff2fd0..7e040f0 100644 --- a/framework/yii/caching/ZendDataCache.php +++ b/framework/yii/caching/ZendDataCache.php @@ -21,6 +21,25 @@ namespace yii\caching; class ZendDataCache extends Cache { /** + * Checks whether a specified key exists in the cache. + * This can be faster than getting the value from the cache if the data is big. + * In case a cache does not support this feature natively, this method will try to simulate it + * but has no performance improvement over getting it. + * Note that this method does not check whether the dependency associated + * with the cached data, if there is any, has changed. So a call to [[get]] + * may return false while exists returns true. + * @param mixed $key a key identifying the cached value. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @return boolean true if a value exists in cache, false if the value is not in the cache or expired. + */ + public function exists($key) + { + $key = $this->buildKey($key); + $value = $this->getValue($key); + return $value !== null; + } + + /** * Retrieves a value from cache with a specified key. * This is the implementation of the method declared in the parent class. * @param string $key a unique key identifying the cached value From 2e01c06a4b26662d578d563ad97186c87edf4e34 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 10 Sep 2013 08:15:46 -0400 Subject: [PATCH 051/157] Fixes #865. --- framework/yii/db/QueryBuilder.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index 00d21c0..7ae5369 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -912,11 +912,6 @@ class QueryBuilder extends \yii\base\Object protected function buildCompositeInCondition($operator, $columns, $values, &$params) { - foreach ($columns as $i => $column) { - if (strpos($column, '(') === false) { - $columns[$i] = $this->db->quoteColumnName($column); - } - } $vss = array(); foreach ($values as $value) { $vs = array(); @@ -931,6 +926,11 @@ class QueryBuilder extends \yii\base\Object } $vss[] = '(' . implode(', ', $vs) . ')'; } + foreach ($columns as $i => $column) { + if (strpos($column, '(') === false) { + $columns[$i] = $this->db->quoteColumnName($column); + } + } return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; } From 7303bae38212ed2e1c8a4080e2f465172795431b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 10 Sep 2013 08:24:14 -0400 Subject: [PATCH 052/157] Fixed foreign key generation bug for pgsql. --- framework/yii/db/pgsql/Schema.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 9693dd6..969b47a 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -200,7 +200,7 @@ SQL; } $citem = array($foreignTable); foreach ($columns as $idx => $column) { - $citem[] = array($fcolumns[$idx] => $column); + $citem[$fcolumns[$idx]] = $column; } $table->foreignKeys[] = $citem; } From d02e7d4004a3730950cf46f741d4e05d5b86fa6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20W=C3=B6ster?= Date: Tue, 10 Sep 2013 14:27:22 +0200 Subject: [PATCH 053/157] add checks for GET and OPTIONS requests --- framework/yii/web/Request.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index c6e1ce3..2b9da0a 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -137,6 +137,24 @@ class Request extends \yii\base\Request } /** + * Returns whether this is a GET request. + * @return boolean whether this is a GET request. + */ + public function getIsGet() + { + return $this->getMethod() === 'GET'; + } + + /** + * Returns whether this is an OPTIONS request. + * @return boolean whether this is a OPTIONS request. + */ + public function getIsOptions() + { + return $this->getMethod() === 'OPTIONS'; + } + + /** * Returns whether this is a POST request. * @return boolean whether this is a POST request. */ From 64d57b397ac38080ef7148811b71b6931b7e0ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20W=C3=B6ster?= Date: Tue, 10 Sep 2013 14:33:50 +0200 Subject: [PATCH 054/157] add checks for HEAD request --- framework/yii/web/Request.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 2b9da0a..1482633 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -155,6 +155,15 @@ class Request extends \yii\base\Request } /** + * Returns whether this is a HEAD request. + * @return boolean whether this is a HEAD request. + */ + public function getIsHead() + { + return $this->getMethod() === 'HEAD'; + } + + /** * Returns whether this is a POST request. * @return boolean whether this is a POST request. */ From c6f4dac2497d78269061e26077cba4af7bc4ffb6 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 11 Sep 2013 13:41:47 -0400 Subject: [PATCH 055/157] Refactored AR code. --- framework/yii/db/ActiveRecord.php | 52 +++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index 35d7305..a09c60a 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -749,21 +749,21 @@ class ActiveRecord extends Model return false; } $db = static::getDb(); - $transaction = $this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null ? $db->beginTransaction() : null; - try { - $result = $this->insertInternal($attributes); - if ($transaction !== null) { + if ($this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null) { + $transaction = $db->beginTransaction(); + try { + $result = $this->insertInternal($attributes); if ($result === false) { $transaction->rollback(); } else { $transaction->commit(); } - } - } catch (\Exception $e) { - if ($transaction !== null) { + } catch (\Exception $e) { $transaction->rollback(); + throw $e; } - throw $e; + } else { + $result = $this->insertInternal($attributes); } return $result; } @@ -859,21 +859,21 @@ class ActiveRecord extends Model return false; } $db = static::getDb(); - $transaction = $this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null ? $db->beginTransaction() : null; - try { - $result = $this->updateInternal($attributes); - if ($transaction !== null) { + if ($this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null) { + $transaction = $db->beginTransaction(); + try { + $result = $this->updateInternal($attributes); if ($result === false) { $transaction->rollback(); } else { $transaction->commit(); } - } - } catch (\Exception $e) { - if ($transaction !== null) { + } catch (\Exception $e) { $transaction->rollback(); + throw $e; } - throw $e; + } else { + $result = $this->updateInternal($attributes); } return $result; } @@ -1010,6 +1010,16 @@ class ActiveRecord extends Model } /** + * Sets the value indicating whether the record is new. + * @param boolean $value whether the record is new and should be inserted when calling [[save()]]. + * @see getIsNewRecord + */ + public function setIsNewRecord($value) + { + $this->_oldAttributes = $value ? null : $this->_attributes; + } + + /** * Initializes the object. * This method is called at the end of the constructor. * The default implementation will trigger an [[EVENT_INIT]] event. @@ -1034,16 +1044,6 @@ class ActiveRecord extends Model } /** - * Sets the value indicating whether the record is new. - * @param boolean $value whether the record is new and should be inserted when calling [[save()]]. - * @see getIsNewRecord - */ - public function setIsNewRecord($value) - { - $this->_oldAttributes = $value ? null : $this->_attributes; - } - - /** * This method is called at the beginning of inserting or updating a record. * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true, * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false. From 84dd19d76f729e82f4d609fad2313a35dc15f45d Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 11 Sep 2013 13:42:34 -0400 Subject: [PATCH 056/157] Fixed the issue that Object/Component doesn't support using anonymous function as normal property values. --- framework/yii/base/Component.php | 5 ++--- framework/yii/base/Object.php | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php index a7ddd29..f497a29 100644 --- a/framework/yii/base/Component.php +++ b/framework/yii/base/Component.php @@ -193,9 +193,8 @@ class Component extends Object */ public function __call($name, $params) { - $getter = 'get' . $name; - if (method_exists($this, $getter)) { - $func = $this->$getter(); + if ($this->canGetProperty($name)) { + $func = $this->$name; if ($func instanceof \Closure) { return call_user_func_array($func, $params); } diff --git a/framework/yii/base/Object.php b/framework/yii/base/Object.php index adbab9c..0af3131 100644 --- a/framework/yii/base/Object.php +++ b/framework/yii/base/Object.php @@ -155,9 +155,8 @@ class Object implements Arrayable */ public function __call($name, $params) { - $getter = 'get' . $name; - if (method_exists($this, $getter)) { - $func = $this->$getter(); + if ($this->canGetProperty($name)) { + $func = $this->$name; if ($func instanceof \Closure) { return call_user_func_array($func, $params); } From 62f5b47f1d5332654e67ba2477ac383c1e6e68a6 Mon Sep 17 00:00:00 2001 From: ninbopa Date: Wed, 11 Sep 2013 20:59:25 +0300 Subject: [PATCH 057/157] Update single row query function --- docs/guide/database-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/database-basics.md b/docs/guide/database-basics.md index 310e3f5..85bc042 100644 --- a/docs/guide/database-basics.md +++ b/docs/guide/database-basics.md @@ -88,7 +88,7 @@ When only a single row is returned: ```php $command = $connection->createCommand('SELECT * FROM tbl_post WHERE id=1'); -$post = $command->query(); +$post = $command->queryOne(); ``` When there are multiple values from the same column: From a2b946e4d3acc7c37a8e3f91f7a1405be24a1073 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 13 Sep 2013 00:49:06 +0400 Subject: [PATCH 058/157] Zend Data Cache returns null when record doesn't exist (reverted from commit 590121c4ff76b548b2bdb74f72ce247a806af415) --- framework/yii/caching/ZendDataCache.php | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/framework/yii/caching/ZendDataCache.php b/framework/yii/caching/ZendDataCache.php index 7e040f0..9ff2fd0 100644 --- a/framework/yii/caching/ZendDataCache.php +++ b/framework/yii/caching/ZendDataCache.php @@ -21,25 +21,6 @@ namespace yii\caching; class ZendDataCache extends Cache { /** - * Checks whether a specified key exists in the cache. - * This can be faster than getting the value from the cache if the data is big. - * In case a cache does not support this feature natively, this method will try to simulate it - * but has no performance improvement over getting it. - * Note that this method does not check whether the dependency associated - * with the cached data, if there is any, has changed. So a call to [[get]] - * may return false while exists returns true. - * @param mixed $key a key identifying the cached value. This can be a simple string or - * a complex data structure consisting of factors representing the key. - * @return boolean true if a value exists in cache, false if the value is not in the cache or expired. - */ - public function exists($key) - { - $key = $this->buildKey($key); - $value = $this->getValue($key); - return $value !== null; - } - - /** * Retrieves a value from cache with a specified key. * This is the implementation of the method declared in the parent class. * @param string $key a unique key identifying the cached value From 91b6e2945aef0bd4d4e23bea42dbab53b82e5b4c Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 12 Sep 2013 20:10:06 -0400 Subject: [PATCH 059/157] Removed the support for calling anonymous function returned as a property value. --- framework/yii/base/Component.php | 12 ++---------- framework/yii/base/Object.php | 8 -------- tests/unit/framework/base/ObjectTest.php | 5 ----- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php index f497a29..2ad2c94 100644 --- a/framework/yii/base/Component.php +++ b/framework/yii/base/Component.php @@ -179,9 +179,8 @@ class Component extends Object /** * Calls the named method which is not a class method. - * If the name refers to a component property whose value is - * an anonymous function, the method will execute the function. - * Otherwise, it will check if any attached behavior has + * + * This method will check if any attached behavior has * the named method and will execute it if available. * * Do not call this method directly as it is a PHP magic method that @@ -193,13 +192,6 @@ class Component extends Object */ public function __call($name, $params) { - if ($this->canGetProperty($name)) { - $func = $this->$name; - if ($func instanceof \Closure) { - return call_user_func_array($func, $params); - } - } - $this->ensureBehaviors(); foreach ($this->_behaviors as $object) { if ($object->hasMethod($name)) { diff --git a/framework/yii/base/Object.php b/framework/yii/base/Object.php index 0af3131..55754de 100644 --- a/framework/yii/base/Object.php +++ b/framework/yii/base/Object.php @@ -143,8 +143,6 @@ class Object implements Arrayable /** * Calls the named method which is not a class method. - * If the name refers to a component property whose value is - * an anonymous function, the method will execute the function. * * Do not call this method directly as it is a PHP magic method that * will be implicitly called when an unknown method is being invoked. @@ -155,12 +153,6 @@ class Object implements Arrayable */ public function __call($name, $params) { - if ($this->canGetProperty($name)) { - $func = $this->$name; - if ($func instanceof \Closure) { - return call_user_func_array($func, $params); - } - } throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()"); } diff --git a/tests/unit/framework/base/ObjectTest.php b/tests/unit/framework/base/ObjectTest.php index 933b721..d6b95e5 100644 --- a/tests/unit/framework/base/ObjectTest.php +++ b/tests/unit/framework/base/ObjectTest.php @@ -134,11 +134,6 @@ class ObjectTest extends TestCase $this->assertEquals('new text', $this->object->object->text); } - public function testAnonymousFunctionProperty() - { - $this->assertEquals(2, $this->object->execute(1)); - } - public function testConstruct() { $object = new NewObject(array('text' => 'test text')); From b05442d368e08c999d018fec538eaf1f63f1ba76 Mon Sep 17 00:00:00 2001 From: Larry Ullman Date: Fri, 13 Sep 2013 11:23:56 -0400 Subject: [PATCH 060/157] Doing that editing thing --- docs/guide/active-record.md | 64 +++++++++++++++++++++-------------------- docs/guide/bootstrap-widgets.md | 9 +++--- docs/guide/overview.md | 18 ++++++------ docs/guide/template.md | 3 +- 4 files changed, 46 insertions(+), 48 deletions(-) diff --git a/docs/guide/active-record.md b/docs/guide/active-record.md index 0c93fad..fc98f98 100644 --- a/docs/guide/active-record.md +++ b/docs/guide/active-record.md @@ -2,12 +2,12 @@ Active Record ============= Active Record implements the [Active Record design pattern](http://en.wikipedia.org/wiki/Active_record). -The premise behind Active Record is that an individual [[ActiveRecord]] object is associated with a specific row in a database table. The object's attributes are mapped to the columns of the corresponding table. Therefore, referencing an Active Record attribute is equivalent to accessing +The premise behind Active Record is that an individual [[ActiveRecord]] object is associated with a specific row in a database table. The object's attributes are mapped to the columns of the corresponding table. Referencing an Active Record attribute is equivalent to accessing the corresponding table column for that record. -For example, say that the `Customer` ActiveRecord class is associated with the +As an example, say that the `Customer` ActiveRecord class is associated with the `tbl_customer` table. This would mean that the class's `name` attribute is automatically mapped to the `name` column in `tbl_customer`. -Thanks to Active Record, assuming the variable `$customer` is an object of type `Customer`, to get the value of the `name` column for the table row, you can simply use the expression `$customer->name`. In this example, Active Record is providing an object-oriented way to access data stored in the database, just as you would access any object property. But Active Record provides much more functionality than this. +Thanks to Active Record, assuming the variable `$customer` is an object of type `Customer`, to get the value of the `name` column for the table row, you can use the expression `$customer->name`. In this example, Active Record is providing an object-oriented interface for accessing data stored in the database. But Active Record provides much more functionality than this. With Active Record, instead of writing raw SQL statements to perform database queries, you can call intuitive methods to achieve the same goals. For example, calling [[ActiveRecord::save()|save()]] would perform an INSERT or UPDATE query, creating or updating a row in the associated table of the ActiveRecord class: @@ -39,6 +39,13 @@ class Customer extends ActiveRecord } ``` +The `tableName` method only has to return the name of the database table associated with the class. + +Class instances are obtained in one of two ways: + +* Using the `new` operator to create a new, empty object +* Using a method to fetch an existing record (or records) from the database + Connecting to the Database ---------------------- @@ -59,11 +66,7 @@ return array( ); ``` -Please read the [Database basics](database-basics.md) section to learn more on how to configure -and use database connections. - -> Tip: To use a different database connection, you need to override the [[ActiveRecord::getDb()]] method. To do that, create a base class that extends ActiveRecord. In the base class, override the [[ActiveRecord::getDb()]] method. Then extend your new base class for all of your ActiveRecord classes that need to use the same alternative database connection. - +Please read the [Database basics](database-basics.md) section to learn more on how to configure and use database connections. Querying Data from the Database --------------------------- @@ -73,8 +76,8 @@ There are two ActiveRecord methods for querying data from database: - [[ActiveRecord::find()]] - [[ActiveRecord::findBySql()]] -Both methods return an [[ActiveQuery]] instance, which extends from [[Query]] thus supporting -the same set of flexible and powerful DB query methods. The followings examples demonstrate some of the possibilities. +Both methods return an [[ActiveQuery]] instance, which extends [[Query]], and thus supports +the same set of flexible and powerful DB query methods. The following examples demonstrate some of the possibilities. ```php // to retrieve all *active* customers and order them by their ID: @@ -115,25 +118,24 @@ $customers = Customer::find()->indexBy('id')->all(); Accessing Column Data --------------------- -ActiveRecord maps each column of the corresponding database table row to an *attribute* in the ActiveRecord +ActiveRecord maps each column of the corresponding database table row to an attribute in the ActiveRecord object. The attribute behaves like any regular object public property. The attribute's name will be the same as the corresponding column name, and is case-sensitive. To read the value of a column, you can use the following syntax: ```php -// "id" is the name of a column in the table associated with $customer ActiveRecord object +// "id" and "email" are the names of columns in the table associated with $customer ActiveRecord object $id = $customer->id; -// or alternatively, -$id = $customer->getAttribute('id'); +$email = $customer->email; ``` -You can get all column values through the [[ActiveRecord::attributes]] property: +To change the value of a column, assign a new value to the associated property and save the object: -```php -$values = $customer->attributes; ``` - +$customer->email = 'jane@example.com'; +$customer->save(); +``` Manipulating Data in the Database ----------------------------- @@ -150,8 +152,8 @@ ActiveRecord provides the following methods to insert, update and delete data in - [[ActiveRecord::deleteAll()|deleteAll()]] Note that [[ActiveRecord::updateAll()|updateAll()]], [[ActiveRecord::updateAllCounters()|updateAllCounters()]] -and [[ActiveRecord::deleteAll()|deleteAll()]] are static methods and apply to the whole database -table, while the other methods only apply to the row associated with the ActiveRecord object through which the method is being called. +and [[ActiveRecord::deleteAll()|deleteAll()]] are static methods that apply to the whole database +table. The other methods only apply to the row associated with the ActiveRecord object through which the method is being called. ```php // to insert a new customer record @@ -173,12 +175,21 @@ $customer->delete(); Customer::updateAllCounters(array('age' => 1)); ``` +Notice that you can always use the `save` method, and ActiveRecord will automatically perform an INSERT for new records and an UPDATE for existing ones. + +Data Input and Validation +------------------------- + +ActiveRecord inherits data validation and data input features from [[\yii\base\Model]]. Data validation is called +automatically when `save()` is performed. If data validation fails, the saving operation will be cancelled. + +For more details refer to the [Model](model.md) section of this guide. Querying Relational Data ------------------------ -You can use ActiveRecord to query the relational data of a table. The relational data returned can -be accessed like a property of the ActiveRecord object associated with the primary table. +You can use ActiveRecord to also query a table's relational data (i.e., selection of data from Table A can also pull in related data from Table B). Thanks to ActiveRecord, the relational data returned can be accessed like a property of the ActiveRecord object associated with the primary table. + For example, with an appropriate relation declaration, by accessing `$customer->orders` you may obtain an array of `Order` objects which represent the orders placed by the specified customer. @@ -397,15 +408,6 @@ The [[link()]] call above will set the `customer_id` of the order to be the prim value of `$customer` and then call [[save()]] to save the order into database. -Data Input and Validation -------------------------- - -ActiveRecord inherits data validation and data input features from [[\yii\base\Model]]. Data validation is called -automatically when `save()` is performed. If data validation fails, the saving operation will be cancelled. - -For more details refer to the [Model](model.md) section of this guide. - - Life Cycles of an ActiveRecord Object ------------------------------------- diff --git a/docs/guide/bootstrap-widgets.md b/docs/guide/bootstrap-widgets.md index 0739847..3f56839 100644 --- a/docs/guide/bootstrap-widgets.md +++ b/docs/guide/bootstrap-widgets.md @@ -1,13 +1,12 @@ Bootstrap widgets ================= -Yii includes support of [Bootstrap 3](http://getbootstrap.com/) markup and components framework out of the box. It is an -excellent framework that allows you to speed up development a lot. +Yii includes support for the [Bootstrap 3](http://getbootstrap.com/) markup and components framework out of the box. Bootstrap is an excellent, responsive framework that can greatly speed up your development process. -Bootstrap is generally about two parts: +The core of Bootstrap is represented by two parts: -- Basics such as grid system, typography, helper classes and responsive utilities. -- Ready to use components such as menus, pagination, modal boxes, tabs etc. +- CSS basics, such as grid layout system, typography, helper classes, and responsive utilities. +- Ready to use components, such as menus, pagination, modal boxes, tabs etc. Basics ------ diff --git a/docs/guide/overview.md b/docs/guide/overview.md index d2ccb19..ef71aa0 100644 --- a/docs/guide/overview.md +++ b/docs/guide/overview.md @@ -1,8 +1,7 @@ What is Yii =========== -Yii is a high-performance, component-based PHP framework for developing -large-scale Web applications rapidly. It enables maximum reusability in Web +Yii is a high-performance, component-based PHP framework for rapidly developing large-scale Web applications. Yii enables maximum reusability in Web programming and can significantly accelerate your Web application development process. The name Yii (pronounced `Yee` or `[ji:]`) is an acronym for **Yes It Is!**. @@ -12,7 +11,7 @@ Requirements ------------ To run a Yii-powered Web application, you need a Web server that supports -PHP 5.3.?. +PHP 5.3.? or greater. For developers who want to use Yii, understanding object-oriented programming (OOP) is very helpful, because Yii is a pure OOP framework. @@ -31,10 +30,9 @@ management systems (CMS), e-commerce systems, etc. How does Yii Compare with Other Frameworks? ------------------------------------------- -- Like most PHP frameworks, Yii is an MVC (Model-View-Controller) framework. -- It is a fullstack framework providing many solutions and components such as logging, session management, caching etc. -- It has a good balance of simplicity and features. -- Syntax and overall development usability are taken seriously. -- Performance is one of the key goals. -- We are constantly watching other web frameworks out there and getting the best ideas in. Initial Yii release was heavily - influenced by Ruby on Rails. Still, we aren't blindly copying anything. +- Like most PHP frameworks, Yii is uses the MVC (Model-View-Controller) design approach. +- Yii is a fullstack framework providing many solutions and components, such as logging, session management, caching etc. +- Yii strikes a good balance between simplicity and features. +- Syntax and overall development usability are taken seriously by the Yii development team. +- Performance is one of the key goals for the Yii framework. +- The Yii development team is constantly watching what other Web frameworks are doing to see what best practices and features should be incorporated into Yii. The initial Yii release was heavily influenced by Ruby on Rails. Still, no framework or feature is being blindly copied into Yii; all decisions are based upon what's best for Web developers and in keeping with Yii's philosophy. diff --git a/docs/guide/template.md b/docs/guide/template.md index f2a6fc4..6d2db88 100644 --- a/docs/guide/template.md +++ b/docs/guide/template.md @@ -1,8 +1,7 @@ Using template engines ====================== -By default Yii uses PHP as template language but you can configure it to be able -to render templates with special engines such as Twig or Smarty. +By default Yii uses PHP as template language, but you can configure it to support other rendering engines, such as [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/). The component responsible for rendering a view is called `view`. You can add a custom template engines as follows: From 3e5491e0896d80fd3ac797be5a1e1d021e3749c7 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 13 Sep 2013 17:45:55 +0200 Subject: [PATCH 061/157] changed cubrid table and column name quoting ` is a MySQL thing supported by cubrid. " is more common. --- framework/yii/db/cubrid/Schema.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index fae932f..e192c9f 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -57,7 +57,7 @@ class Schema extends \yii\db\Schema // Bit string data types 'bit' => self::TYPE_STRING, 'bit varying' => self::TYPE_STRING, - // Collection data types (considered strings for now, may add support for them later) + // Collection data types (considered strings for now) 'set' => self::TYPE_STRING, 'multiset' => self::TYPE_STRING, 'list' => self::TYPE_STRING, @@ -73,7 +73,7 @@ class Schema extends \yii\db\Schema */ public function quoteSimpleTableName($name) { - return strpos($name, "`") !== false ? $name : "`" . $name . "`"; + return strpos($name, '"') !== false ? $name : '"' . $name . '"'; } /** @@ -84,7 +84,7 @@ class Schema extends \yii\db\Schema */ public function quoteSimpleColumnName($name) { - return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`'; + return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"'; } /** From 0bf2daddd5d4987aebbc6c268ade6cfa5d968a1e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 13 Sep 2013 17:49:29 +0200 Subject: [PATCH 062/157] changed querybuilder typemap to lower case --- framework/yii/db/cubrid/QueryBuilder.php | 2 +- framework/yii/db/mssql/QueryBuilder.php | 2 +- framework/yii/db/mysql/QueryBuilder.php | 2 +- framework/yii/db/sqlite/QueryBuilder.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php index 935b3d3..0f9cd33 100644 --- a/framework/yii/db/cubrid/QueryBuilder.php +++ b/framework/yii/db/cubrid/QueryBuilder.php @@ -21,7 +21,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_PK => 'int not null auto_increment primary key', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'varchar', Schema::TYPE_SMALLINT => 'smallint', diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/yii/db/mssql/QueryBuilder.php index e7f8f80..26e8c28 100644 --- a/framework/yii/db/mssql/QueryBuilder.php +++ b/framework/yii/db/mssql/QueryBuilder.php @@ -21,7 +21,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY', + Schema::TYPE_PK => 'int identity primary key', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint(6)', diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index c7a4256..9c81c99 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -22,7 +22,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_PK => 'int(11) not null auto_increment primary key', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint(6)', diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php index be0275a..5094442 100644 --- a/framework/yii/db/sqlite/QueryBuilder.php +++ b/framework/yii/db/sqlite/QueryBuilder.php @@ -23,7 +23,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', + Schema::TYPE_PK => 'integer primary key autoincrement not null', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', From 6015312548a87cc6cd4eac4b65d5ff93e4e5c7ee Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 13 Sep 2013 17:55:01 +0200 Subject: [PATCH 063/157] code style --- framework/yii/db/cubrid/Schema.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index e192c9f..c7b6ad2 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -210,7 +210,8 @@ class Schema extends \yii\db\Schema if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP' || $column->type === 'datetime' && $info['Default'] === 'SYS_DATETIME' || $column->type === 'date' && $info['Default'] === 'SYS_DATE' || - $column->type === 'time' && $info['Default'] === 'SYS_TIME') { + $column->type === 'time' && $info['Default'] === 'SYS_TIME' + ) { $column->defaultValue = new Expression($info['Default']); } else { $column->defaultValue = $column->typecast($info['Default']); From 6fe152da7e81a2a599426a28d42a930400de32c9 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 13 Sep 2013 20:13:46 -0400 Subject: [PATCH 064/157] Reverted 0bf2dad: it's very common to use lower case for column types and upper case for other DB keywords. --- framework/yii/db/cubrid/QueryBuilder.php | 2 +- framework/yii/db/mssql/QueryBuilder.php | 2 +- framework/yii/db/mysql/QueryBuilder.php | 2 +- framework/yii/db/pgsql/QueryBuilder.php | 2 +- framework/yii/db/sqlite/QueryBuilder.php | 2 +- tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php | 1 - 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php index 0f9cd33..935b3d3 100644 --- a/framework/yii/db/cubrid/QueryBuilder.php +++ b/framework/yii/db/cubrid/QueryBuilder.php @@ -21,7 +21,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'int not null auto_increment primary key', + Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'varchar', Schema::TYPE_SMALLINT => 'smallint', diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/yii/db/mssql/QueryBuilder.php index 26e8c28..e7f8f80 100644 --- a/framework/yii/db/mssql/QueryBuilder.php +++ b/framework/yii/db/mssql/QueryBuilder.php @@ -21,7 +21,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'int identity primary key', + Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint(6)', diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index 9c81c99..c7a4256 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -22,7 +22,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'int(11) not null auto_increment primary key', + Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint(6)', diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 9701fd6..22d615e 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -21,7 +21,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'serial not null primary key', + Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php index 5094442..be0275a 100644 --- a/framework/yii/db/sqlite/QueryBuilder.php +++ b/framework/yii/db/sqlite/QueryBuilder.php @@ -23,7 +23,7 @@ class QueryBuilder extends \yii\db\QueryBuilder * @var array mapping from abstract column types (keys) to physical column types (values). */ public $typeMap = array( - Schema::TYPE_PK => 'integer primary key autoincrement not null', + Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', diff --git a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php index 2e3a615..1f99503 100644 --- a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php +++ b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php @@ -2,7 +2,6 @@ namespace yiiunit\framework\db\sqlite; -use yii\base\NotSupportedException; use yii\db\sqlite\Schema; use yiiunit\framework\db\QueryBuilderTest; From 02fd82c42a9623b28f970af7696bccd0540fa4cd Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 13 Sep 2013 20:19:50 -0400 Subject: [PATCH 065/157] Fixed build break. --- framework/yii/db/redis/Connection.php | 1 - tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/framework/yii/db/redis/Connection.php b/framework/yii/db/redis/Connection.php index ea59f22..68b40d3 100644 --- a/framework/yii/db/redis/Connection.php +++ b/framework/yii/db/redis/Connection.php @@ -13,7 +13,6 @@ use \yii\base\Component; use yii\base\InvalidConfigException; use \yii\db\Exception; use yii\helpers\Inflector; -use yii\helpers\StringHelper; /** * diff --git a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php index bf2dd73..c7bfeed 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php @@ -12,10 +12,10 @@ class PostgreSQLQueryBuilderTest extends QueryBuilderTest public function columnTypes() { return array( - array(Schema::TYPE_PK, 'serial not null primary key'), - array(Schema::TYPE_PK . '(8)', 'serial not null primary key'), - array(Schema::TYPE_PK . ' CHECK (value > 5)', 'serial not null primary key CHECK (value > 5)'), - array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'serial not null primary key CHECK (value > 5)'), + array(Schema::TYPE_PK, 'serial NOT NULL PRIMARY KEY'), + array(Schema::TYPE_PK . '(8)', 'serial NOT NULL PRIMARY KEY'), + array(Schema::TYPE_PK . ' CHECK (value > 5)', 'serial NOT NULL PRIMARY KEY CHECK (value > 5)'), + array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'serial NOT NULL PRIMARY KEY CHECK (value > 5)'), array(Schema::TYPE_STRING, 'varchar(255)'), array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), From 6c6cb3cd90b19a23ad202fcefc9617bfb5e59a76 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 13 Sep 2013 20:41:40 -0400 Subject: [PATCH 066/157] Fixes #853: Added composite FK support for SQLite. --- framework/yii/db/sqlite/Schema.php | 8 +++++++- tests/unit/framework/db/sqlite/SqliteSchemaTest.php | 5 ----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/framework/yii/db/sqlite/Schema.php b/framework/yii/db/sqlite/Schema.php index d4fb245..bca26c1 100644 --- a/framework/yii/db/sqlite/Schema.php +++ b/framework/yii/db/sqlite/Schema.php @@ -126,7 +126,13 @@ class Schema extends \yii\db\Schema $sql = "PRAGMA foreign_key_list(" . $this->quoteSimpleTableName($table->name) . ')'; $keys = $this->db->createCommand($sql)->queryAll(); foreach ($keys as $key) { - $table->foreignKeys[] = array($key['table'], $key['from'] => $key['to']); + $id = (int)$key['id']; + if (!isset($table->foreignKeys[$id])) { + $table->foreignKeys[$id] = array($key['table'], $key['from'] => $key['to']); + } else { + // composite FK + $table->foreignKeys[$id][$key['from']] = $key['to']; + } } } diff --git a/tests/unit/framework/db/sqlite/SqliteSchemaTest.php b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php index 9b17a1d..f3f6b60 100644 --- a/tests/unit/framework/db/sqlite/SqliteSchemaTest.php +++ b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php @@ -6,9 +6,4 @@ use yiiunit\framework\db\SchemaTest; class SqliteSchemaTest extends SchemaTest { protected $driverName = 'sqlite'; - - public function testCompositeFk() - { - $this->markTestSkipped('sqlite does not allow getting enough information about composite FK.'); - } } From 30907b6134f17f287d5b182ae006cda4cdf63580 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 13 Sep 2013 20:54:16 -0400 Subject: [PATCH 067/157] Fixes #826: cleaned up User::getReturnUrl(). --- framework/yii/web/User.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index e02559b..22b85e5 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -10,6 +10,7 @@ namespace yii\web; use Yii; use yii\base\Component; use yii\base\InvalidConfigException; +use yii\base\InvalidParamException; /** * User is the class for the "user" application component that manages the user authentication status. @@ -255,20 +256,34 @@ class User extends Component * This property is usually used by the login action. If the login is successful, * the action should read this property and use it to redirect the user browser. * @param string|array $defaultUrl the default return URL in case it was not set previously. - * If this is null, it means [[Application::homeUrl]] will be redirected to. - * Please refer to [[\yii\helpers\Html::url()]] on acceptable URL formats. + * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to. + * Please refer to [[setReturnUrl()]] on accepted format of the URL. * @return string the URL that the user should be redirected to after login. * @see loginRequired */ public function getReturnUrl($defaultUrl = null) { $url = Yii::$app->getSession()->get($this->returnUrlVar, $defaultUrl); + if (is_array($url)) { + if (isset($url[0])) { + $route = array_shift($url); + return Yii::$app->getUrlManager()->createUrl($route, $url); + } else { + $url = null; + } + } return $url === null ? Yii::$app->getHomeUrl() : $url; } /** * @param string|array $url the URL that the user should be redirected to after login. - * Please refer to [[\yii\helpers\Html::url()]] on acceptable URL formats. + * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL. + * The first element of the array should be the route, and the rest of + * the name-value pairs are GET parameters used to construct the URL. For example, + * + * ~~~ + * array('admin/index', 'ref' => 1) + * ~~~ */ public function setReturnUrl($url) { From d3740932a4a64774a6a62d856378ced43af79ddc Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 11:12:48 +0200 Subject: [PATCH 068/157] fixed broken cubrid tests fixed expected quoting --- .../unit/framework/db/cubrid/CubridCommandTest.php | 9 +++++++++ .../framework/db/cubrid/CubridConnectionTest.php | 23 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/tests/unit/framework/db/cubrid/CubridCommandTest.php b/tests/unit/framework/db/cubrid/CubridCommandTest.php index 5fe8848..4151f2b 100644 --- a/tests/unit/framework/db/cubrid/CubridCommandTest.php +++ b/tests/unit/framework/db/cubrid/CubridCommandTest.php @@ -69,4 +69,13 @@ class CubridCommandTest extends CommandTest $command->bindValue(':name', 'user5'); $this->assertEquals('user5@example.com', $command->queryScalar()); } + + public function testAutoQuoting() + { + $db = $this->getConnection(false); + + $sql = 'SELECT [[id]], [[t.name]] FROM {{tbl_customer}} t'; + $command = $db->createCommand($sql); + $this->assertEquals('SELECT "id", "t"."name" FROM "tbl_customer" t', $command->sql); + } } diff --git a/tests/unit/framework/db/cubrid/CubridConnectionTest.php b/tests/unit/framework/db/cubrid/CubridConnectionTest.php index 6924883..2ce1c7b 100644 --- a/tests/unit/framework/db/cubrid/CubridConnectionTest.php +++ b/tests/unit/framework/db/cubrid/CubridConnectionTest.php @@ -14,4 +14,27 @@ class CubridConnectionTest extends ConnectionTest $this->assertEquals("'string'", $connection->quoteValue('string')); $this->assertEquals("'It''s interesting'", $connection->quoteValue("It's interesting")); } + + public function testQuoteTableName() + { + $connection = $this->getConnection(false); + $this->assertEquals('"table"', $connection->quoteTableName('table')); + $this->assertEquals('"table"', $connection->quoteTableName('"table"')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema.table')); + $this->assertEquals('"schema"."table"', $connection->quoteTableName('schema."table"')); + $this->assertEquals('{{table}}', $connection->quoteTableName('{{table}}')); + $this->assertEquals('(table)', $connection->quoteTableName('(table)')); + } + + public function testQuoteColumnName() + { + $connection = $this->getConnection(false); + $this->assertEquals('"column"', $connection->quoteColumnName('column')); + $this->assertEquals('"column"', $connection->quoteColumnName('"column"')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table.column')); + $this->assertEquals('"table"."column"', $connection->quoteColumnName('table."column"')); + $this->assertEquals('[[column]]', $connection->quoteColumnName('[[column]]')); + $this->assertEquals('{{column}}', $connection->quoteColumnName('{{column}}')); + $this->assertEquals('(column)', $connection->quoteColumnName('(column)')); + } } From eb5bf2dabedd4b00b4d78a35d2468f1807837c2f Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 11:33:07 +0200 Subject: [PATCH 069/157] added cubrid dbms to travis-ci copied from https://github.com/CUBRID/node-cubrid/blob/056e734ce36bb3fd25f100c983beb3947e899c1c/.travis.yml --- .travis.yml | 21 +++++++++++++++++++++ tests/unit/data/cubrid-solo.rb | 5 +++++ 2 files changed, 26 insertions(+) create mode 100644 tests/unit/data/cubrid-solo.rb diff --git a/.travis.yml b/.travis.yml index c24c0b3..6078518 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,11 +5,32 @@ php: - 5.4 - 5.5 +env: + - CUBRID_VERSION=9.1.0 + before_script: - composer self-update && composer --version - composer require satooshi/php-coveralls 0.6.* - mysql -e 'CREATE DATABASE yiitest;'; - psql -U postgres -c 'CREATE DATABASE yiitest;'; + # + # install CUBRID DBMS https://github.com/CUBRID/node-cubrid/blob/056e734ce36bb3fd25f100c983beb3947e899c1c/.travis.yml + - sudo hostname localhost + # Update OS before installing prerequisites. + - sudo apt-get update + # Install Chef Solo prerequisites. + - sudo apt-get install ruby ruby-dev libopenssl-ruby rdoc ri irb build-essential ssl-cert + # Install Chef Solo. + # Chef Solo 11.4.4 is broken, so install a previous version. + # The bug is planned to be fixed in 11.4.5 which haven't been released yet. + - sudo gem install --version '<11.4.4' chef --no-rdoc --no-ri + # Make sure the target directory for cookbooks exists. + - mkdir -p /tmp/chef-solo + # Prepare a file with runlist for Chef Solo. + - echo '{"cubrid":{"version":"'$CUBRID_VERSION'"},"run_list":["cubrid::demodb"]}' > cubrid_chef.json + # Install CUBRID via Chef Solo. Download all cookbooks from a remote URL. + - sudo chef-solo -c tests/unit/data/cubrid-solo.rb -j cubrid_chef.json -r http://sourceforge.net/projects/cubrid/files/CUBRID-Demo-Virtual-Machines/Vagrant/chef-cookbooks.tar.gz/download + script: - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml diff --git a/tests/unit/data/cubrid-solo.rb b/tests/unit/data/cubrid-solo.rb new file mode 100644 index 0000000..f5f0004 --- /dev/null +++ b/tests/unit/data/cubrid-solo.rb @@ -0,0 +1,5 @@ +file_cache_path "/tmp/chef-solo" +data_bag_path "/tmp/chef-solo/data_bags" +encrypted_data_bag_secret "/tmp/chef-solo/data_bag_key" +cookbook_path [ "/tmp/chef-solo/cookbooks" ] +role_path "/tmp/chef-solo/roles" \ No newline at end of file From 1179cb2a1c5b65e93755f4d02fefc505e83ea5b3 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 11:41:23 +0200 Subject: [PATCH 070/157] made travis phpunit verbose to see why cubrid tests are skipped --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6078518..6aeb868 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ before_script: script: - - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml + - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose after_script: - php vendor/bin/coveralls \ No newline at end of file From 281e602850139afb6a79a82f34963d96429c15e2 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 11:53:46 +0200 Subject: [PATCH 071/157] added pecl install pdo_cubrid to travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6aeb868..8b0153e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ before_script: - echo '{"cubrid":{"version":"'$CUBRID_VERSION'"},"run_list":["cubrid::demodb"]}' > cubrid_chef.json # Install CUBRID via Chef Solo. Download all cookbooks from a remote URL. - sudo chef-solo -c tests/unit/data/cubrid-solo.rb -j cubrid_chef.json -r http://sourceforge.net/projects/cubrid/files/CUBRID-Demo-Virtual-Machines/Vagrant/chef-cookbooks.tar.gz/download - + - sudo pecl install pdo_cubrid script: - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose From 69a38d189545841161234a5f62313f45f40932c8 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 12:07:04 +0200 Subject: [PATCH 072/157] added cubrid_pdo to travis --- .travis.yml | 19 +------------------ tests/unit/data/cubrid-solo.rb | 5 ----- tests/unit/data/travis/cubrid-setup.sh | 32 ++++++++++++++++++++++++++++++++ tests/unit/data/travis/cubrid-solo.rb | 5 +++++ 4 files changed, 38 insertions(+), 23 deletions(-) delete mode 100644 tests/unit/data/cubrid-solo.rb create mode 100644 tests/unit/data/travis/cubrid-setup.sh create mode 100644 tests/unit/data/travis/cubrid-solo.rb diff --git a/.travis.yml b/.travis.yml index 8b0153e..3a38d05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,24 +13,7 @@ before_script: - composer require satooshi/php-coveralls 0.6.* - mysql -e 'CREATE DATABASE yiitest;'; - psql -U postgres -c 'CREATE DATABASE yiitest;'; - # - # install CUBRID DBMS https://github.com/CUBRID/node-cubrid/blob/056e734ce36bb3fd25f100c983beb3947e899c1c/.travis.yml - - sudo hostname localhost - # Update OS before installing prerequisites. - - sudo apt-get update - # Install Chef Solo prerequisites. - - sudo apt-get install ruby ruby-dev libopenssl-ruby rdoc ri irb build-essential ssl-cert - # Install Chef Solo. - # Chef Solo 11.4.4 is broken, so install a previous version. - # The bug is planned to be fixed in 11.4.5 which haven't been released yet. - - sudo gem install --version '<11.4.4' chef --no-rdoc --no-ri - # Make sure the target directory for cookbooks exists. - - mkdir -p /tmp/chef-solo - # Prepare a file with runlist for Chef Solo. - - echo '{"cubrid":{"version":"'$CUBRID_VERSION'"},"run_list":["cubrid::demodb"]}' > cubrid_chef.json - # Install CUBRID via Chef Solo. Download all cookbooks from a remote URL. - - sudo chef-solo -c tests/unit/data/cubrid-solo.rb -j cubrid_chef.json -r http://sourceforge.net/projects/cubrid/files/CUBRID-Demo-Virtual-Machines/Vagrant/chef-cookbooks.tar.gz/download - - sudo pecl install pdo_cubrid + - tests/unit/data/travis/cubrid-setup.sh script: - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose diff --git a/tests/unit/data/cubrid-solo.rb b/tests/unit/data/cubrid-solo.rb deleted file mode 100644 index f5f0004..0000000 --- a/tests/unit/data/cubrid-solo.rb +++ /dev/null @@ -1,5 +0,0 @@ -file_cache_path "/tmp/chef-solo" -data_bag_path "/tmp/chef-solo/data_bags" -encrypted_data_bag_secret "/tmp/chef-solo/data_bag_key" -cookbook_path [ "/tmp/chef-solo/cookbooks" ] -role_path "/tmp/chef-solo/roles" \ No newline at end of file diff --git a/tests/unit/data/travis/cubrid-setup.sh b/tests/unit/data/travis/cubrid-setup.sh new file mode 100644 index 0000000..af007ff --- /dev/null +++ b/tests/unit/data/travis/cubrid-setup.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# +# install CUBRID DBMS https://github.com/CUBRID/node-cubrid/blob/056e734ce36bb3fd25f100c983beb3947e899c1c/.travis.yml + +sudo hostname localhost +# Update OS before installing prerequisites. +sudo apt-get update +# Install Chef Solo prerequisites. +sudo apt-get install ruby ruby-dev libopenssl-ruby rdoc ri irb build-essential ssl-cert +# Install Chef Solo. +# Chef Solo 11.4.4 is broken, so install a previous version. +# The bug is planned to be fixed in 11.4.5 which haven't been released yet. +sudo gem install --version '<11.4.4' chef --no-rdoc --no-ri +# Make sure the target directory for cookbooks exists. +mkdir -p /tmp/chef-solo +# Prepare a file with runlist for Chef Solo. +echo '{"cubrid":{"version":"'$CUBRID_VERSION'"},"run_list":["cubrid::demodb"]}' > cubrid_chef.json +# Install CUBRID via Chef Solo. Download all cookbooks from a remote URL. +sudo chef-solo -c tests/unit/data/travis/cubrid-solo.rb -j cubrid_chef.json -r http://sourceforge.net/projects/cubrid/files/CUBRID-Demo-Virtual-Machines/Vagrant/chef-cookbooks.tar.gz/download + + +install_pdo_cubrid() { + wget "http://pecl.php.net/get/PDO_CUBRID-9.1.0.0003.tgz" && + tar -zxf "PDO_CUBRID-9.1.0.0003.tgz" && + sh -c "cd PDO_CUBRID-9.1.0.0003 && phpize && ./configure && make && sudo make install" + + echo "extension=pdo_cubrid.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + + return $? +} + +install_pdo_cubrid > ~/pdo_cubrid.log || ( echo "=== PDO CUBRID BUILD FAILED ==="; cat ~/pdo_cubrid.log ) \ No newline at end of file diff --git a/tests/unit/data/travis/cubrid-solo.rb b/tests/unit/data/travis/cubrid-solo.rb new file mode 100644 index 0000000..f5f0004 --- /dev/null +++ b/tests/unit/data/travis/cubrid-solo.rb @@ -0,0 +1,5 @@ +file_cache_path "/tmp/chef-solo" +data_bag_path "/tmp/chef-solo/data_bags" +encrypted_data_bag_secret "/tmp/chef-solo/data_bag_key" +cookbook_path [ "/tmp/chef-solo/cookbooks" ] +role_path "/tmp/chef-solo/roles" \ No newline at end of file From f6f2522ad664b2a5d227979ccec0f274355a987a Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 12:12:20 +0200 Subject: [PATCH 073/157] added travis README from yii 1.1 --- tests/unit/data/travis/README.md | 10 ++++++++++ tests/unit/data/travis/cubrid-setup.sh | 0 2 files changed, 10 insertions(+) create mode 100644 tests/unit/data/travis/README.md mode change 100644 => 100755 tests/unit/data/travis/cubrid-setup.sh diff --git a/tests/unit/data/travis/README.md b/tests/unit/data/travis/README.md new file mode 100644 index 0000000..e87ebe4 --- /dev/null +++ b/tests/unit/data/travis/README.md @@ -0,0 +1,10 @@ +This directory contains scripts for automated test runs via the [Travis CI](http://travis-ci.org) build service. They are used for the preparation of worker instances by setting up needed extensions and configuring database access. + +These scripts might be used to configure your own system for test runs. But since their primary purpose remains to support Travis in running the test cases, you would be best advised to stick to the setup notes in the tests themselves. + +The scripts are: + + - [`memcache-setup.sh`](memcache-setup.sh) + Compiles and installs the [memcache pecl extension](http://pecl.php.net/package/memcache) + - [`cubrid-setup.sh`](cubrid-setup.sh) + Prepares the [CUBRID](http://www.cubrid.org/) server instance by installing the server and PHP PDO driver diff --git a/tests/unit/data/travis/cubrid-setup.sh b/tests/unit/data/travis/cubrid-setup.sh old mode 100644 new mode 100755 From 41bd9ab7a8d0b096076c267c8a680e6d1a6233f4 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 12:23:06 +0200 Subject: [PATCH 074/157] added memcache and apc to travis --- tests/unit/data/travis/README.md | 2 ++ tests/unit/data/travis/apc-setup.sh | 2 ++ tests/unit/data/travis/memcache-setup.sh | 16 ++++++++++++++++ 3 files changed, 20 insertions(+) create mode 100755 tests/unit/data/travis/apc-setup.sh create mode 100755 tests/unit/data/travis/memcache-setup.sh diff --git a/tests/unit/data/travis/README.md b/tests/unit/data/travis/README.md index e87ebe4..c86497e 100644 --- a/tests/unit/data/travis/README.md +++ b/tests/unit/data/travis/README.md @@ -4,6 +4,8 @@ These scripts might be used to configure your own system for test runs. But sinc The scripts are: + - [`apc-setup.sh`](apc-setup.sh) + Installs and configures the [apc pecl extension](http://pecl.php.net/package/apc) - [`memcache-setup.sh`](memcache-setup.sh) Compiles and installs the [memcache pecl extension](http://pecl.php.net/package/memcache) - [`cubrid-setup.sh`](cubrid-setup.sh) diff --git a/tests/unit/data/travis/apc-setup.sh b/tests/unit/data/travis/apc-setup.sh new file mode 100755 index 0000000..3355f8f --- /dev/null +++ b/tests/unit/data/travis/apc-setup.sh @@ -0,0 +1,2 @@ +echo "extension = .so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini +echo "apc.enable_cli = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file diff --git a/tests/unit/data/travis/memcache-setup.sh b/tests/unit/data/travis/memcache-setup.sh new file mode 100755 index 0000000..d0a9888 --- /dev/null +++ b/tests/unit/data/travis/memcache-setup.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +install_memcache() { + if [ "$(expr "$TRAVIS_PHP_VERSION" ">=" "5.5")" -eq 1 ]; then + MEMCACHE_VERSION="2.2.7" + wget "http://pecl.php.net/get/memcache-$MEMCACHE_VERSION.tgz" && + tar -zxf "memcache-$MEMCACHE_VERSION.tgz" && + sh -c "cd memcache-$MEMCACHE_VERSION && phpize && ./configure --enable-memcache && make && sudo make install" + fi + + echo "extension=memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + + return $? +} + +install_memcache > ~/memcache.log || ( echo "=== MEMCACHE BUILD FAILED ==="; cat ~/memcache.log ) \ No newline at end of file From 949dda5fdb2e584101bf438dfbcf58359e8c197e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 12:49:47 +0200 Subject: [PATCH 075/157] tagged tests with @group tags --- .travis.yml | 2 +- tests/unit/framework/YiiBaseTest.php | 1 + tests/unit/framework/base/BehaviorTest.php | 3 +++ tests/unit/framework/base/ComponentTest.php | 3 +++ tests/unit/framework/base/FormatterTest.php | 4 +--- tests/unit/framework/base/ModelTest.php | 2 +- tests/unit/framework/base/ObjectTest.php | 2 +- tests/unit/framework/behaviors/AutoTimestampTest.php | 2 ++ tests/unit/framework/caching/ApcCacheTest.php | 2 ++ tests/unit/framework/caching/DbCacheTest.php | 2 ++ tests/unit/framework/caching/FileCacheTest.php | 1 + tests/unit/framework/caching/MemCacheTest.php | 2 ++ tests/unit/framework/caching/MemCachedTest.php | 2 ++ tests/unit/framework/caching/RedisCacheTest.php | 2 ++ tests/unit/framework/caching/WinCacheTest.php | 2 ++ tests/unit/framework/caching/XCacheTest.php | 2 ++ tests/unit/framework/caching/ZendDataCacheTest.php | 2 ++ tests/unit/framework/console/controllers/AssetControllerTest.php | 2 ++ tests/unit/framework/console/controllers/MessageControllerTest.php | 2 ++ tests/unit/framework/data/ActiveDataProviderTest.php | 2 ++ tests/unit/framework/data/SortTest.php | 2 ++ tests/unit/framework/db/ActiveRecordTest.php | 4 ++++ tests/unit/framework/db/CommandTest.php | 4 ++++ tests/unit/framework/db/ConnectionTest.php | 4 ++++ tests/unit/framework/db/QueryBuilderTest.php | 4 ++++ tests/unit/framework/db/QueryTest.php | 4 ++++ tests/unit/framework/db/SchemaTest.php | 4 ++++ tests/unit/framework/db/cubrid/CubridActiveRecordTest.php | 4 ++++ tests/unit/framework/db/cubrid/CubridCommandTest.php | 4 ++++ tests/unit/framework/db/cubrid/CubridConnectionTest.php | 4 ++++ tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php | 4 ++++ tests/unit/framework/db/cubrid/CubridQueryTest.php | 4 ++++ tests/unit/framework/db/cubrid/CubridSchemaTest.php | 4 ++++ tests/unit/framework/db/mssql/MssqlActiveRecordTest.php | 4 ++++ tests/unit/framework/db/mssql/MssqlCommandTest.php | 4 ++++ tests/unit/framework/db/mssql/MssqlConnectionTest.php | 4 ++++ tests/unit/framework/db/mssql/MssqlQueryTest.php | 4 ++++ tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php | 4 ++++ tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php | 4 ++++ tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php | 4 ++++ tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php | 4 ++++ tests/unit/framework/db/sqlite/SqliteCommandTest.php | 4 ++++ tests/unit/framework/db/sqlite/SqliteConnectionTest.php | 4 ++++ tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php | 4 ++++ tests/unit/framework/db/sqlite/SqliteQueryTest.php | 4 ++++ tests/unit/framework/db/sqlite/SqliteSchemaTest.php | 4 ++++ tests/unit/framework/helpers/ArrayHelperTest.php | 3 +++ tests/unit/framework/helpers/ConsoleTest.php | 4 ++++ tests/unit/framework/helpers/FileHelperTest.php | 1 + tests/unit/framework/helpers/HtmlTest.php | 3 +++ tests/unit/framework/helpers/InflectorTest.php | 3 +++ tests/unit/framework/helpers/JsonTest.php | 3 +++ tests/unit/framework/helpers/StringHelperTest.php | 1 + tests/unit/framework/helpers/VarDumperTest.php | 3 +++ tests/unit/framework/i18n/FormatterTest.php | 1 + tests/unit/framework/i18n/GettextMessageSourceTest.php | 3 +++ tests/unit/framework/i18n/GettextMoFileTest.php | 3 +++ tests/unit/framework/i18n/GettextPoFileTest.php | 3 +++ tests/unit/framework/rbac/PhpManagerTest.php | 3 +++ tests/unit/framework/requirements/YiiRequirementCheckerTest.php | 1 + tests/unit/framework/validators/EmailValidatorTest.php | 1 + tests/unit/framework/web/ResponseTest.php | 3 +++ tests/unit/framework/web/UrlManagerTest.php | 3 +++ tests/unit/framework/web/UrlRuleTest.php | 3 +++ tests/unit/framework/web/XmlResponseFormatterTest.php | 2 ++ tests/unit/framework/widgets/SpacelessTest.php | 3 +++ 66 files changed, 188 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3a38d05..d8200f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ before_script: - tests/unit/data/travis/cubrid-setup.sh script: - - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose + - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata after_script: - php vendor/bin/coveralls \ No newline at end of file diff --git a/tests/unit/framework/YiiBaseTest.php b/tests/unit/framework/YiiBaseTest.php index e256b2b..72b5f24 100644 --- a/tests/unit/framework/YiiBaseTest.php +++ b/tests/unit/framework/YiiBaseTest.php @@ -6,6 +6,7 @@ use yiiunit\TestCase; /** * YiiBaseTest + * @group base */ class YiiBaseTest extends TestCase { diff --git a/tests/unit/framework/base/BehaviorTest.php b/tests/unit/framework/base/BehaviorTest.php index e596ea8..b6eda09 100644 --- a/tests/unit/framework/base/BehaviorTest.php +++ b/tests/unit/framework/base/BehaviorTest.php @@ -46,6 +46,9 @@ class BarBehavior extends Behavior } } +/** + * @group base + */ class BehaviorTest extends TestCase { protected function setUp() diff --git a/tests/unit/framework/base/ComponentTest.php b/tests/unit/framework/base/ComponentTest.php index 79fb7db..98786e2 100644 --- a/tests/unit/framework/base/ComponentTest.php +++ b/tests/unit/framework/base/ComponentTest.php @@ -17,6 +17,9 @@ function globalEventHandler2($event) $event->handled = true; } +/** + * @group base + */ class ComponentTest extends TestCase { /** diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php index 01dd682..ae71a5c 100644 --- a/tests/unit/framework/base/FormatterTest.php +++ b/tests/unit/framework/base/FormatterTest.php @@ -10,9 +10,7 @@ use yii\base\Formatter; use yiiunit\TestCase; /** - * - * @author Qiang Xue - * @since 2.0 + * @group base */ class FormatterTest extends TestCase { diff --git a/tests/unit/framework/base/ModelTest.php b/tests/unit/framework/base/ModelTest.php index e4d8976..b338c17 100644 --- a/tests/unit/framework/base/ModelTest.php +++ b/tests/unit/framework/base/ModelTest.php @@ -9,7 +9,7 @@ use yiiunit\data\base\Singer; use yiiunit\data\base\InvalidRulesModel; /** - * ModelTest + * @group base */ class ModelTest extends TestCase { diff --git a/tests/unit/framework/base/ObjectTest.php b/tests/unit/framework/base/ObjectTest.php index d6b95e5..0bd9b1d 100644 --- a/tests/unit/framework/base/ObjectTest.php +++ b/tests/unit/framework/base/ObjectTest.php @@ -5,7 +5,7 @@ use yii\base\Object; use yiiunit\TestCase; /** - * ObjectTest + * @group base */ class ObjectTest extends TestCase { diff --git a/tests/unit/framework/behaviors/AutoTimestampTest.php b/tests/unit/framework/behaviors/AutoTimestampTest.php index 0e17a39..c26d912 100644 --- a/tests/unit/framework/behaviors/AutoTimestampTest.php +++ b/tests/unit/framework/behaviors/AutoTimestampTest.php @@ -11,6 +11,8 @@ use yii\behaviors\AutoTimestamp; /** * Unit test for [[\yii\behaviors\AutoTimestamp]]. * @see AutoTimestamp + * + * @group behaviors */ class AutoTimestampTest extends TestCase { diff --git a/tests/unit/framework/caching/ApcCacheTest.php b/tests/unit/framework/caching/ApcCacheTest.php index 1d07498..adda151 100644 --- a/tests/unit/framework/caching/ApcCacheTest.php +++ b/tests/unit/framework/caching/ApcCacheTest.php @@ -5,6 +5,8 @@ use yii\caching\ApcCache; /** * Class for testing APC cache backend + * @group apc + * @group caching */ class ApcCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/DbCacheTest.php b/tests/unit/framework/caching/DbCacheTest.php index 969b034..1e94d58 100644 --- a/tests/unit/framework/caching/DbCacheTest.php +++ b/tests/unit/framework/caching/DbCacheTest.php @@ -6,6 +6,8 @@ use yii\caching\DbCache; /** * Class for testing file cache backend + * @group db + * @group caching */ class DbCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/FileCacheTest.php b/tests/unit/framework/caching/FileCacheTest.php index 0bdbc86..263ecb4 100644 --- a/tests/unit/framework/caching/FileCacheTest.php +++ b/tests/unit/framework/caching/FileCacheTest.php @@ -5,6 +5,7 @@ use yii\caching\FileCache; /** * Class for testing file cache backend + * @group caching */ class FileCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php index d54d807..32374b5 100644 --- a/tests/unit/framework/caching/MemCacheTest.php +++ b/tests/unit/framework/caching/MemCacheTest.php @@ -5,6 +5,8 @@ use yii\caching\MemCache; /** * Class for testing memcache cache backend + * @group memcache + * @group caching */ class MemCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php index faf0d56..f39ed17 100644 --- a/tests/unit/framework/caching/MemCachedTest.php +++ b/tests/unit/framework/caching/MemCachedTest.php @@ -5,6 +5,8 @@ use yii\caching\MemCache; /** * Class for testing memcached cache backend + * @group memcached + * @group caching */ class MemCachedTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/RedisCacheTest.php b/tests/unit/framework/caching/RedisCacheTest.php index 0924d0f..d02773d 100644 --- a/tests/unit/framework/caching/RedisCacheTest.php +++ b/tests/unit/framework/caching/RedisCacheTest.php @@ -6,6 +6,8 @@ use yiiunit\TestCase; /** * Class for testing redis cache backend + * @group redis + * @group caching */ class RedisCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/WinCacheTest.php b/tests/unit/framework/caching/WinCacheTest.php index b6f2425..1bce102 100644 --- a/tests/unit/framework/caching/WinCacheTest.php +++ b/tests/unit/framework/caching/WinCacheTest.php @@ -5,6 +5,8 @@ use yii\caching\WinCache; /** * Class for testing wincache backend + * @group wincache + * @group caching */ class WinCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/XCacheTest.php b/tests/unit/framework/caching/XCacheTest.php index 7ee0a0e..989765d 100644 --- a/tests/unit/framework/caching/XCacheTest.php +++ b/tests/unit/framework/caching/XCacheTest.php @@ -5,6 +5,8 @@ use yii\caching\XCache; /** * Class for testing xcache backend + * @group xcache + * @group caching */ class XCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/caching/ZendDataCacheTest.php b/tests/unit/framework/caching/ZendDataCacheTest.php index 2d68bfd..96354cd 100644 --- a/tests/unit/framework/caching/ZendDataCacheTest.php +++ b/tests/unit/framework/caching/ZendDataCacheTest.php @@ -6,6 +6,8 @@ use yii\caching\ZendDataCache; /** * Class for testing Zend cache backend + * @group zenddata + * @group caching */ class ZendDataCacheTest extends CacheTestCase { diff --git a/tests/unit/framework/console/controllers/AssetControllerTest.php b/tests/unit/framework/console/controllers/AssetControllerTest.php index aaf5ca9..67dbdaa 100644 --- a/tests/unit/framework/console/controllers/AssetControllerTest.php +++ b/tests/unit/framework/console/controllers/AssetControllerTest.php @@ -6,6 +6,8 @@ use yii\console\controllers\AssetController; /** * Unit test for [[\yii\console\controllers\AssetController]]. * @see AssetController + * + * @group console */ class AssetControllerTest extends TestCase { diff --git a/tests/unit/framework/console/controllers/MessageControllerTest.php b/tests/unit/framework/console/controllers/MessageControllerTest.php index cdd38b8..b0c697c 100644 --- a/tests/unit/framework/console/controllers/MessageControllerTest.php +++ b/tests/unit/framework/console/controllers/MessageControllerTest.php @@ -6,6 +6,8 @@ use yii\console\controllers\MessageController; /** * Unit test for [[\yii\console\controllers\MessageController]]. * @see MessageController + * + * @group console */ class MessageControllerTest extends TestCase { diff --git a/tests/unit/framework/data/ActiveDataProviderTest.php b/tests/unit/framework/data/ActiveDataProviderTest.php index 2699a52..3f65ebb 100644 --- a/tests/unit/framework/data/ActiveDataProviderTest.php +++ b/tests/unit/framework/data/ActiveDataProviderTest.php @@ -16,6 +16,8 @@ use yiiunit\data\ar\Order; /** * @author Qiang Xue * @since 2.0 + * + * @group data */ class ActiveDataProviderTest extends DatabaseTestCase { diff --git a/tests/unit/framework/data/SortTest.php b/tests/unit/framework/data/SortTest.php index 9891ad1..c4cc6aa 100644 --- a/tests/unit/framework/data/SortTest.php +++ b/tests/unit/framework/data/SortTest.php @@ -14,6 +14,8 @@ use yii\data\Sort; /** * @author Qiang Xue * @since 2.0 + * + * @group data */ class SortTest extends TestCase { diff --git a/tests/unit/framework/db/ActiveRecordTest.php b/tests/unit/framework/db/ActiveRecordTest.php index 7df4159..2f9b345 100644 --- a/tests/unit/framework/db/ActiveRecordTest.php +++ b/tests/unit/framework/db/ActiveRecordTest.php @@ -9,6 +9,10 @@ use yiiunit\data\ar\OrderItem; use yiiunit\data\ar\Order; use yiiunit\data\ar\Item; +/** + * @group db + * @group mysql + */ class ActiveRecordTest extends DatabaseTestCase { protected function setUp() diff --git a/tests/unit/framework/db/CommandTest.php b/tests/unit/framework/db/CommandTest.php index 229545d..7b16c76 100644 --- a/tests/unit/framework/db/CommandTest.php +++ b/tests/unit/framework/db/CommandTest.php @@ -7,6 +7,10 @@ use yii\db\Command; use yii\db\Query; use yii\db\DataReader; +/** + * @group db + * @group mysql + */ class CommandTest extends DatabaseTestCase { public function testConstruct() diff --git a/tests/unit/framework/db/ConnectionTest.php b/tests/unit/framework/db/ConnectionTest.php index f2895f1..04c5d53 100644 --- a/tests/unit/framework/db/ConnectionTest.php +++ b/tests/unit/framework/db/ConnectionTest.php @@ -4,6 +4,10 @@ namespace yiiunit\framework\db; use yii\db\Connection; +/** + * @group db + * @group mysql + */ class ConnectionTest extends DatabaseTestCase { public function testConstruct() diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php index a2815e8..d43c901 100644 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -10,6 +10,10 @@ use yii\db\mssql\QueryBuilder as MssqlQueryBuilder; use yii\db\pgsql\QueryBuilder as PgsqlQueryBuilder; use yii\db\cubrid\QueryBuilder as CubridQueryBuilder; +/** + * @group db + * @group mysql + */ class QueryBuilderTest extends DatabaseTestCase { /** diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php index dfb6c9f..a275afd 100644 --- a/tests/unit/framework/db/QueryTest.php +++ b/tests/unit/framework/db/QueryTest.php @@ -7,6 +7,10 @@ use yii\db\Command; use yii\db\Query; use yii\db\DataReader; +/** + * @group db + * @group mysql + */ class QueryTest extends DatabaseTestCase { public function testSelect() diff --git a/tests/unit/framework/db/SchemaTest.php b/tests/unit/framework/db/SchemaTest.php index 5f504e1..dce6e20 100644 --- a/tests/unit/framework/db/SchemaTest.php +++ b/tests/unit/framework/db/SchemaTest.php @@ -5,6 +5,10 @@ namespace yiiunit\framework\db; use yii\caching\FileCache; use yii\db\Schema; +/** + * @group db + * @group mysql + */ class SchemaTest extends DatabaseTestCase { diff --git a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php index 6633e11..dd48f44 100644 --- a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php +++ b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\cubrid; use yiiunit\framework\db\ActiveRecordTest; +/** + * @group db + * @group cubrid + */ class CubridActiveRecordTest extends ActiveRecordTest { public $driverName = 'cubrid'; diff --git a/tests/unit/framework/db/cubrid/CubridCommandTest.php b/tests/unit/framework/db/cubrid/CubridCommandTest.php index 4151f2b..895f548 100644 --- a/tests/unit/framework/db/cubrid/CubridCommandTest.php +++ b/tests/unit/framework/db/cubrid/CubridCommandTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\cubrid; use yiiunit\framework\db\CommandTest; +/** + * @group db + * @group cubrid + */ class CubridCommandTest extends CommandTest { public $driverName = 'cubrid'; diff --git a/tests/unit/framework/db/cubrid/CubridConnectionTest.php b/tests/unit/framework/db/cubrid/CubridConnectionTest.php index 2ce1c7b..4cd6e20 100644 --- a/tests/unit/framework/db/cubrid/CubridConnectionTest.php +++ b/tests/unit/framework/db/cubrid/CubridConnectionTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\cubrid; use yiiunit\framework\db\ConnectionTest; +/** + * @group db + * @group cubrid + */ class CubridConnectionTest extends ConnectionTest { public $driverName = 'cubrid'; diff --git a/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php index 94a002e..107b73b 100644 --- a/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php +++ b/tests/unit/framework/db/cubrid/CubridQueryBuilderTest.php @@ -6,6 +6,10 @@ use yii\base\NotSupportedException; use yii\db\sqlite\Schema; use yiiunit\framework\db\QueryBuilderTest; +/** + * @group db + * @group cubrid + */ class CubridQueryBuilderTest extends QueryBuilderTest { public $driverName = 'cubrid'; diff --git a/tests/unit/framework/db/cubrid/CubridQueryTest.php b/tests/unit/framework/db/cubrid/CubridQueryTest.php index 2c68a35..b7c9009 100644 --- a/tests/unit/framework/db/cubrid/CubridQueryTest.php +++ b/tests/unit/framework/db/cubrid/CubridQueryTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\cubrid; use yiiunit\framework\db\QueryTest; +/** + * @group db + * @group cubrid + */ class CubridQueryTest extends QueryTest { public $driverName = 'cubrid'; diff --git a/tests/unit/framework/db/cubrid/CubridSchemaTest.php b/tests/unit/framework/db/cubrid/CubridSchemaTest.php index d235b40..6a1f6a2 100644 --- a/tests/unit/framework/db/cubrid/CubridSchemaTest.php +++ b/tests/unit/framework/db/cubrid/CubridSchemaTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\cubrid; use yiiunit\framework\db\SchemaTest; +/** + * @group db + * @group cubrid + */ class CubridSchemaTest extends SchemaTest { public $driverName = 'cubrid'; diff --git a/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php b/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php index 92e4da2..c21efc6 100644 --- a/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php +++ b/tests/unit/framework/db/mssql/MssqlActiveRecordTest.php @@ -4,6 +4,10 @@ namespace yiiunit\framework\db\mssql; use yiiunit\framework\db\ActiveRecordTest; +/** + * @group db + * @group mssql + */ class MssqlActiveRecordTest extends ActiveRecordTest { protected $driverName = 'sqlsrv'; diff --git a/tests/unit/framework/db/mssql/MssqlCommandTest.php b/tests/unit/framework/db/mssql/MssqlCommandTest.php index 1908f65..86f7f45 100644 --- a/tests/unit/framework/db/mssql/MssqlCommandTest.php +++ b/tests/unit/framework/db/mssql/MssqlCommandTest.php @@ -4,6 +4,10 @@ namespace yiiunit\framework\db\mssql; use yiiunit\framework\db\CommandTest; +/** + * @group db + * @group mssql + */ class MssqlCommandTest extends CommandTest { protected $driverName = 'sqlsrv'; diff --git a/tests/unit/framework/db/mssql/MssqlConnectionTest.php b/tests/unit/framework/db/mssql/MssqlConnectionTest.php index 854c81f..6531f83 100644 --- a/tests/unit/framework/db/mssql/MssqlConnectionTest.php +++ b/tests/unit/framework/db/mssql/MssqlConnectionTest.php @@ -4,6 +4,10 @@ namespace yiiunit\framework\db\mssql; use yiiunit\framework\db\ConnectionTest; +/** + * @group db + * @group mssql + */ class MssqlConnectionTest extends ConnectionTest { protected $driverName = 'sqlsrv'; diff --git a/tests/unit/framework/db/mssql/MssqlQueryTest.php b/tests/unit/framework/db/mssql/MssqlQueryTest.php index e07ac1d..a2cb019 100644 --- a/tests/unit/framework/db/mssql/MssqlQueryTest.php +++ b/tests/unit/framework/db/mssql/MssqlQueryTest.php @@ -4,6 +4,10 @@ namespace yiiunit\framework\db\mssql; use yiiunit\framework\db\QueryTest; +/** + * @group db + * @group mssql + */ class MssqlQueryTest extends QueryTest { protected $driverName = 'sqlsrv'; diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php index aef09fd..1fffad7 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php @@ -4,6 +4,10 @@ namespace yiiunit\framework\db\pgsql; use yiiunit\framework\db\ActiveRecordTest; +/** + * @group db + * @group pgsql + */ class PostgreSQLActiveRecordTest extends ActiveRecordTest { protected $driverName = 'pgsql'; diff --git a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php index cd76a97..26ac0e0 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLConnectionTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\pgsql; use yiiunit\framework\db\ConnectionTest; +/** + * @group db + * @group pgsql + */ class PostgreSQLConnectionTest extends ConnectionTest { protected $driverName = 'pgsql'; diff --git a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php index c7bfeed..3ef329e 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php @@ -5,6 +5,10 @@ namespace yiiunit\framework\db\pgsql; use yii\db\pgsql\Schema; use yiiunit\framework\db\QueryBuilderTest; +/** + * @group db + * @group pgsql + */ class PostgreSQLQueryBuilderTest extends QueryBuilderTest { public $driverName = 'pgsql'; diff --git a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php index 1088280..a689e5d 100644 --- a/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php +++ b/tests/unit/framework/db/sqlite/SqliteActiveRecordTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\sqlite; use yiiunit\framework\db\ActiveRecordTest; +/** + * @group db + * @group sqlite + */ class SqliteActiveRecordTest extends ActiveRecordTest { protected $driverName = 'sqlite'; diff --git a/tests/unit/framework/db/sqlite/SqliteCommandTest.php b/tests/unit/framework/db/sqlite/SqliteCommandTest.php index af9a323..1f9ddc2 100644 --- a/tests/unit/framework/db/sqlite/SqliteCommandTest.php +++ b/tests/unit/framework/db/sqlite/SqliteCommandTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\sqlite; use yiiunit\framework\db\CommandTest; +/** + * @group db + * @group sqlite + */ class SqliteCommandTest extends CommandTest { protected $driverName = 'sqlite'; diff --git a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php index f1d5b94..e1a2961 100644 --- a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php +++ b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\sqlite; use yiiunit\framework\db\ConnectionTest; +/** + * @group db + * @group sqlite + */ class SqliteConnectionTest extends ConnectionTest { protected $driverName = 'sqlite'; diff --git a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php index 1f99503..b20acad 100644 --- a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php +++ b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php @@ -5,6 +5,10 @@ namespace yiiunit\framework\db\sqlite; use yii\db\sqlite\Schema; use yiiunit\framework\db\QueryBuilderTest; +/** + * @group db + * @group sqlite + */ class SqliteQueryBuilderTest extends QueryBuilderTest { protected $driverName = 'sqlite'; diff --git a/tests/unit/framework/db/sqlite/SqliteQueryTest.php b/tests/unit/framework/db/sqlite/SqliteQueryTest.php index 3f112dc..f1db36b 100644 --- a/tests/unit/framework/db/sqlite/SqliteQueryTest.php +++ b/tests/unit/framework/db/sqlite/SqliteQueryTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\sqlite; use yiiunit\framework\db\QueryTest; +/** + * @group db + * @group sqlite + */ class SqliteQueryTest extends QueryTest { protected $driverName = 'sqlite'; diff --git a/tests/unit/framework/db/sqlite/SqliteSchemaTest.php b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php index f3f6b60..260bb4c 100644 --- a/tests/unit/framework/db/sqlite/SqliteSchemaTest.php +++ b/tests/unit/framework/db/sqlite/SqliteSchemaTest.php @@ -3,6 +3,10 @@ namespace yiiunit\framework\db\sqlite; use yiiunit\framework\db\SchemaTest; +/** + * @group db + * @group sqlite + */ class SqliteSchemaTest extends SchemaTest { protected $driverName = 'sqlite'; diff --git a/tests/unit/framework/helpers/ArrayHelperTest.php b/tests/unit/framework/helpers/ArrayHelperTest.php index 92a3971..ca8c95a 100644 --- a/tests/unit/framework/helpers/ArrayHelperTest.php +++ b/tests/unit/framework/helpers/ArrayHelperTest.php @@ -35,6 +35,9 @@ class Post3 extends Object } } +/** + * @group helpers + */ class ArrayHelperTest extends TestCase { public function testToArray() diff --git a/tests/unit/framework/helpers/ConsoleTest.php b/tests/unit/framework/helpers/ConsoleTest.php index f1d3b3a..4b983f8 100644 --- a/tests/unit/framework/helpers/ConsoleTest.php +++ b/tests/unit/framework/helpers/ConsoleTest.php @@ -6,6 +6,10 @@ use Yii; use yii\helpers\Console; use yiiunit\TestCase; +/** + * @group helpers + * @group console + */ class ConsoleTest extends TestCase { public function testStripAnsiFormat() diff --git a/tests/unit/framework/helpers/FileHelperTest.php b/tests/unit/framework/helpers/FileHelperTest.php index 2212072..05bd7af 100644 --- a/tests/unit/framework/helpers/FileHelperTest.php +++ b/tests/unit/framework/helpers/FileHelperTest.php @@ -6,6 +6,7 @@ use yii\test\TestCase; /** * Unit test for [[yii\helpers\FileHelper]] * @see FileHelper + * @group helpers */ class FileHelperTest extends TestCase { diff --git a/tests/unit/framework/helpers/HtmlTest.php b/tests/unit/framework/helpers/HtmlTest.php index 6d725de..857f9c2 100644 --- a/tests/unit/framework/helpers/HtmlTest.php +++ b/tests/unit/framework/helpers/HtmlTest.php @@ -6,6 +6,9 @@ use Yii; use yii\helpers\Html; use yiiunit\TestCase; +/** + * @group helpers + */ class HtmlTest extends TestCase { protected function setUp() diff --git a/tests/unit/framework/helpers/InflectorTest.php b/tests/unit/framework/helpers/InflectorTest.php index 732c10d..de7fe01 100644 --- a/tests/unit/framework/helpers/InflectorTest.php +++ b/tests/unit/framework/helpers/InflectorTest.php @@ -6,6 +6,9 @@ use Yii; use yii\helpers\Inflector; use yiiunit\TestCase; +/** + * @group helpers + */ class InflectorTest extends TestCase { public function testPluralize() diff --git a/tests/unit/framework/helpers/JsonTest.php b/tests/unit/framework/helpers/JsonTest.php index 3734744..df2ca5f 100644 --- a/tests/unit/framework/helpers/JsonTest.php +++ b/tests/unit/framework/helpers/JsonTest.php @@ -7,6 +7,9 @@ use yii\helpers\Json; use yii\test\TestCase; use yii\web\JsExpression; +/** + * @group helpers + */ class JsonTest extends TestCase { public function testEncode() diff --git a/tests/unit/framework/helpers/StringHelperTest.php b/tests/unit/framework/helpers/StringHelperTest.php index fe6a96c..8af731d 100644 --- a/tests/unit/framework/helpers/StringHelperTest.php +++ b/tests/unit/framework/helpers/StringHelperTest.php @@ -6,6 +6,7 @@ use yii\test\TestCase; /** * StringHelperTest + * @group helpers */ class StringHelperTest extends TestCase { diff --git a/tests/unit/framework/helpers/VarDumperTest.php b/tests/unit/framework/helpers/VarDumperTest.php index 2b40b63..d41a69d 100644 --- a/tests/unit/framework/helpers/VarDumperTest.php +++ b/tests/unit/framework/helpers/VarDumperTest.php @@ -4,6 +4,9 @@ namespace yiiunit\framework\helpers; use \yii\helpers\VarDumper; use yii\test\TestCase; +/** + * @group helpers + */ class VarDumperTest extends TestCase { public function testDumpObject() diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index c13fff3..6966853 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -13,6 +13,7 @@ use yiiunit\TestCase; /** * @author Qiang Xue * @since 2.0 + * @group i18n */ class FormatterTest extends TestCase { diff --git a/tests/unit/framework/i18n/GettextMessageSourceTest.php b/tests/unit/framework/i18n/GettextMessageSourceTest.php index 7b499f4..d039629 100644 --- a/tests/unit/framework/i18n/GettextMessageSourceTest.php +++ b/tests/unit/framework/i18n/GettextMessageSourceTest.php @@ -5,6 +5,9 @@ namespace yiiunit\framework\i18n; use yii\i18n\GettextMessageSource; use yiiunit\TestCase; +/** + * @group i18n + */ class GettextMessageSourceTest extends TestCase { public function testLoadMessages() diff --git a/tests/unit/framework/i18n/GettextMoFileTest.php b/tests/unit/framework/i18n/GettextMoFileTest.php index 0aa22da..9b61145 100644 --- a/tests/unit/framework/i18n/GettextMoFileTest.php +++ b/tests/unit/framework/i18n/GettextMoFileTest.php @@ -5,6 +5,9 @@ namespace yiiunit\framework\i18n; use yii\i18n\GettextMoFile; use yiiunit\TestCase; +/** + * @group i18n + */ class GettextMoFileTest extends TestCase { public function testLoad() diff --git a/tests/unit/framework/i18n/GettextPoFileTest.php b/tests/unit/framework/i18n/GettextPoFileTest.php index 8dddb40..4165b81 100644 --- a/tests/unit/framework/i18n/GettextPoFileTest.php +++ b/tests/unit/framework/i18n/GettextPoFileTest.php @@ -5,6 +5,9 @@ namespace yiiunit\framework\i18n; use yii\i18n\GettextPoFile; use yiiunit\TestCase; +/** + * @group i18n + */ class GettextPoFileTest extends TestCase { public function testLoad() diff --git a/tests/unit/framework/rbac/PhpManagerTest.php b/tests/unit/framework/rbac/PhpManagerTest.php index b3b7c4f..8c5d366 100644 --- a/tests/unit/framework/rbac/PhpManagerTest.php +++ b/tests/unit/framework/rbac/PhpManagerTest.php @@ -5,6 +5,9 @@ namespace yiiunit\framework\rbac; use Yii; use yii\rbac\PhpManager; +/** + * @group rbac + */ class PhpManagerTest extends ManagerTestCase { protected function setUp() diff --git a/tests/unit/framework/requirements/YiiRequirementCheckerTest.php b/tests/unit/framework/requirements/YiiRequirementCheckerTest.php index 7554729..652d003 100644 --- a/tests/unit/framework/requirements/YiiRequirementCheckerTest.php +++ b/tests/unit/framework/requirements/YiiRequirementCheckerTest.php @@ -7,6 +7,7 @@ use yiiunit\TestCase; /** * Test case for [[YiiRequirementChecker]]. * @see YiiRequirementChecker + * @group requirements */ class YiiRequirementCheckerTest extends TestCase { diff --git a/tests/unit/framework/validators/EmailValidatorTest.php b/tests/unit/framework/validators/EmailValidatorTest.php index 5807aed..b33a809 100644 --- a/tests/unit/framework/validators/EmailValidatorTest.php +++ b/tests/unit/framework/validators/EmailValidatorTest.php @@ -6,6 +6,7 @@ use yiiunit\TestCase; /** * EmailValidatorTest + * @group validators */ class EmailValidatorTest extends TestCase { diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php index 41ed939..2a9b4bf 100644 --- a/tests/unit/framework/web/ResponseTest.php +++ b/tests/unit/framework/web/ResponseTest.php @@ -13,6 +13,9 @@ class MockResponse extends \yii\web\Response } } +/** + * @group web + */ class ResponseTest extends \yiiunit\TestCase { /** diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php index efa6695..a77a66d 100644 --- a/tests/unit/framework/web/UrlManagerTest.php +++ b/tests/unit/framework/web/UrlManagerTest.php @@ -5,6 +5,9 @@ use yii\web\Request; use yii\web\UrlManager; use yiiunit\TestCase; +/** + * @group web + */ class UrlManagerTest extends TestCase { protected function setUp() diff --git a/tests/unit/framework/web/UrlRuleTest.php b/tests/unit/framework/web/UrlRuleTest.php index d67dc58..0a0def4 100644 --- a/tests/unit/framework/web/UrlRuleTest.php +++ b/tests/unit/framework/web/UrlRuleTest.php @@ -7,6 +7,9 @@ use yii\web\UrlRule; use yii\web\Request; use yiiunit\TestCase; +/** + * @group web + */ class UrlRuleTest extends TestCase { public function testCreateUrl() diff --git a/tests/unit/framework/web/XmlResponseFormatterTest.php b/tests/unit/framework/web/XmlResponseFormatterTest.php index 590caef..e97962a 100644 --- a/tests/unit/framework/web/XmlResponseFormatterTest.php +++ b/tests/unit/framework/web/XmlResponseFormatterTest.php @@ -26,6 +26,8 @@ class Post extends Object /** * @author Qiang Xue * @since 2.0 + * + * @group web */ class XmlResponseFormatterTest extends \yiiunit\TestCase { diff --git a/tests/unit/framework/widgets/SpacelessTest.php b/tests/unit/framework/widgets/SpacelessTest.php index 6b2cf45..00f5a96 100644 --- a/tests/unit/framework/widgets/SpacelessTest.php +++ b/tests/unit/framework/widgets/SpacelessTest.php @@ -4,6 +4,9 @@ namespace yiiunit\framework\widgets; use yii\widgets\Spaceless; +/** + * @group widgets + */ class SpacelessTest extends \yiiunit\TestCase { public function testWidget() From 34ebe65cf5fffa8e36098cf2ee765e91873a130a Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 12:50:16 +0200 Subject: [PATCH 076/157] added apc,redis and memcache(d) to travis --- .travis.yml | 5 +++++ tests/unit/data/travis/memcache-setup.sh | 16 +++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index d8200f0..d5aa6c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,16 @@ php: env: - CUBRID_VERSION=9.1.0 +services: + - redis-server + before_script: - composer self-update && composer --version - composer require satooshi/php-coveralls 0.6.* - mysql -e 'CREATE DATABASE yiitest;'; - psql -U postgres -c 'CREATE DATABASE yiitest;'; + - tests/unit/data/travis/apc-setup.sh + - tests/unit/data/travis/memcache-setup.sh - tests/unit/data/travis/cubrid-setup.sh script: diff --git a/tests/unit/data/travis/memcache-setup.sh b/tests/unit/data/travis/memcache-setup.sh index d0a9888..4a0a311 100755 --- a/tests/unit/data/travis/memcache-setup.sh +++ b/tests/unit/data/travis/memcache-setup.sh @@ -1,16 +1,18 @@ #!/bin/sh install_memcache() { - if [ "$(expr "$TRAVIS_PHP_VERSION" ">=" "5.5")" -eq 1 ]; then - MEMCACHE_VERSION="2.2.7" - wget "http://pecl.php.net/get/memcache-$MEMCACHE_VERSION.tgz" && - tar -zxf "memcache-$MEMCACHE_VERSION.tgz" && - sh -c "cd memcache-$MEMCACHE_VERSION && phpize && ./configure --enable-memcache && make && sudo make install" - fi +# if [ "$(expr "$TRAVIS_PHP_VERSION" ">=" "5.5")" -eq 1 ]; then +# MEMCACHE_VERSION="2.2.7" +# wget "http://pecl.php.net/get/memcache-$MEMCACHE_VERSION.tgz" && +# tar -zxf "memcache-$MEMCACHE_VERSION.tgz" && +# sh -c "cd memcache-$MEMCACHE_VERSION && phpize && ./configure --enable-memcache && make && sudo make install" +# fi echo "extension=memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini return $? } -install_memcache > ~/memcache.log || ( echo "=== MEMCACHE BUILD FAILED ==="; cat ~/memcache.log ) \ No newline at end of file +install_memcache > ~/memcache.log || ( echo "=== MEMCACHE BUILD FAILED ==="; cat ~/memcache.log ) + +echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini From a1af321d38d3df38e6068b64674e4477c32bdea2 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 12:56:10 +0200 Subject: [PATCH 077/157] fixed typo in apc install script --- tests/unit/data/travis/apc-setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/data/travis/apc-setup.sh b/tests/unit/data/travis/apc-setup.sh index 3355f8f..e5e8734 100755 --- a/tests/unit/data/travis/apc-setup.sh +++ b/tests/unit/data/travis/apc-setup.sh @@ -1,2 +1,2 @@ -echo "extension = .so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini +echo "extension = apc.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini echo "apc.enable_cli = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini \ No newline at end of file From 0a5b1a935ea6d5bb677bcc828f14a4e97ba50cfb Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sat, 14 Sep 2013 12:57:45 +0200 Subject: [PATCH 078/157] simplified memcache installation on travis --- tests/unit/data/travis/memcache-setup.sh | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/tests/unit/data/travis/memcache-setup.sh b/tests/unit/data/travis/memcache-setup.sh index 4a0a311..6b623d6 100755 --- a/tests/unit/data/travis/memcache-setup.sh +++ b/tests/unit/data/travis/memcache-setup.sh @@ -1,18 +1,4 @@ #!/bin/sh -install_memcache() { -# if [ "$(expr "$TRAVIS_PHP_VERSION" ">=" "5.5")" -eq 1 ]; then -# MEMCACHE_VERSION="2.2.7" -# wget "http://pecl.php.net/get/memcache-$MEMCACHE_VERSION.tgz" && -# tar -zxf "memcache-$MEMCACHE_VERSION.tgz" && -# sh -c "cd memcache-$MEMCACHE_VERSION && phpize && ./configure --enable-memcache && make && sudo make install" -# fi - - echo "extension=memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - - return $? -} - -install_memcache > ~/memcache.log || ( echo "=== MEMCACHE BUILD FAILED ==="; cat ~/memcache.log ) - +echo "extension=memcache.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini From a2b4ef0f8245d2bcca2880a8b01314d5a5749ab9 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 14 Sep 2013 08:35:37 -0400 Subject: [PATCH 079/157] Fixes #876. --- framework/yii/db/ActiveRecord.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index a09c60a..e1c4b4f 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -889,6 +889,7 @@ class ActiveRecord extends Model } $values = $this->getDirtyAttributes($attributes); if (empty($values)) { + $this->afterSave(false); return 0; } $condition = $this->getOldPrimaryKey(true); From 523a63f5763c05c341c481fd473c1e54c63d2f14 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 15 Sep 2013 15:08:43 +0400 Subject: [PATCH 080/157] Added TYPE_BIGPK to Schema --- framework/yii/db/QueryBuilder.php | 1 + framework/yii/db/Schema.php | 1 + framework/yii/db/cubrid/QueryBuilder.php | 1 + framework/yii/db/mssql/QueryBuilder.php | 1 + framework/yii/db/mysql/QueryBuilder.php | 2 ++ framework/yii/db/pgsql/QueryBuilder.php | 1 + framework/yii/db/pgsql/Schema.php | 2 +- framework/yii/db/sqlite/QueryBuilder.php | 1 + 8 files changed, 9 insertions(+), 1 deletion(-) diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index 7ae5369..3cc7971 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -491,6 +491,7 @@ class QueryBuilder extends \yii\base\Object * physical types): * * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY" + * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY" * - `string`: string type, will be converted into "varchar(255)" * - `text`: a long string type, will be converted into "text" * - `smallint`: a small integer type, will be converted into "smallint(6)" diff --git a/framework/yii/db/Schema.php b/framework/yii/db/Schema.php index fad91fa..396e944 100644 --- a/framework/yii/db/Schema.php +++ b/framework/yii/db/Schema.php @@ -35,6 +35,7 @@ abstract class Schema extends Object * The followings are the supported abstract column data types. */ const TYPE_PK = 'pk'; + const TYPE_BIGPK = 'bigpk'; const TYPE_STRING = 'string'; const TYPE_TEXT = 'text'; const TYPE_SMALLINT = 'smallint'; diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php index 935b3d3..4b7ef43 100644 --- a/framework/yii/db/cubrid/QueryBuilder.php +++ b/framework/yii/db/cubrid/QueryBuilder.php @@ -22,6 +22,7 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = array( Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigint NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'varchar', Schema::TYPE_SMALLINT => 'smallint', diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/yii/db/mssql/QueryBuilder.php index e7f8f80..aeb5be8 100644 --- a/framework/yii/db/mssql/QueryBuilder.php +++ b/framework/yii/db/mssql/QueryBuilder.php @@ -22,6 +22,7 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = array( Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint(6)', diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index c7a4256..d4f4497 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -9,6 +9,7 @@ namespace yii\db\mysql; use yii\db\Exception; use yii\base\InvalidParamException; +use yii\db\sqlite\Schema; /** * QueryBuilder is the query builder for MySQL databases. @@ -23,6 +24,7 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = array( Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint(6)', diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php index 22d615e..33c7bf6 100644 --- a/framework/yii/db/pgsql/QueryBuilder.php +++ b/framework/yii/db/pgsql/QueryBuilder.php @@ -22,6 +22,7 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = array( Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigserial NOT NULL PRIMARY KEY', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 969b47a..d131342 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/framework/yii/db/pgsql/Schema.php @@ -255,7 +255,7 @@ SELECT information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t)) AS numeric ) AS size, - a.attnum = any (ct.conkey) as is_pkey + a.attnum = any (ct.conkey) as is_pkey FROM pg_class c LEFT JOIN pg_attribute a ON a.attrelid = c.oid diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php index be0275a..4e210f8 100644 --- a/framework/yii/db/sqlite/QueryBuilder.php +++ b/framework/yii/db/sqlite/QueryBuilder.php @@ -24,6 +24,7 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public $typeMap = array( Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', + Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', Schema::TYPE_SMALLINT => 'smallint', From 4a9efc9e756f443319b9da013cdbb4336dd7c84e Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 15 Sep 2013 15:18:38 +0400 Subject: [PATCH 081/157] Corrected schema used for MySQL --- framework/yii/db/mysql/QueryBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index d4f4497..1b67cda 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -9,7 +9,7 @@ namespace yii\db\mysql; use yii\db\Exception; use yii\base\InvalidParamException; -use yii\db\sqlite\Schema; +use yii\db\mysql\Schema; /** * QueryBuilder is the query builder for MySQL databases. From 0284bc4a457a5dedb20ac13eff3034d406826f20 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 15 Sep 2013 16:34:19 +0400 Subject: [PATCH 082/157] Fixes #875: Security::generateRandomKey() can now be safely used in URLs --- framework/yii/helpers/SecurityBase.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/helpers/SecurityBase.php b/framework/yii/helpers/SecurityBase.php index 541b311..1cd9403 100644 --- a/framework/yii/helpers/SecurityBase.php +++ b/framework/yii/helpers/SecurityBase.php @@ -140,12 +140,12 @@ class SecurityBase public static function generateRandomKey($length = 32) { if (function_exists('openssl_random_pseudo_bytes')) { - $key = base64_encode(openssl_random_pseudo_bytes($length, $strong)); + $key = strtr(base64_encode(openssl_random_pseudo_bytes($length, $strong)), array('+' => '_', '/' => '~')); if ($strong) { return substr($key, 0, $length); } } - $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_~'; return substr(str_shuffle(str_repeat($chars, 5)), 0, $length); } From a8d21805f5b0535805c62e22d83b0d84a996fd05 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 16 Sep 2013 01:49:54 +0400 Subject: [PATCH 083/157] Security::generateRandomKey enhancements: - Equals sign is now replaced with dot. - Slash is now replaced with dash. - Better phpdoc. --- framework/yii/helpers/SecurityBase.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/helpers/SecurityBase.php b/framework/yii/helpers/SecurityBase.php index 1cd9403..0ecad0f 100644 --- a/framework/yii/helpers/SecurityBase.php +++ b/framework/yii/helpers/SecurityBase.php @@ -133,19 +133,19 @@ class SecurityBase } /** - * Generates a random key. + * Generates a random key. The key may contain uppercase and lowercase latin letters, digits, underscore, dash and dot. * @param integer $length the length of the key that should be generated * @return string the generated random key */ public static function generateRandomKey($length = 32) { if (function_exists('openssl_random_pseudo_bytes')) { - $key = strtr(base64_encode(openssl_random_pseudo_bytes($length, $strong)), array('+' => '_', '/' => '~')); + $key = strtr(base64_encode(openssl_random_pseudo_bytes($length, $strong)), array('+' => '_', '/' => '-', '=' => '.')); if ($strong) { return substr($key, 0, $length); } } - $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_~'; + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.'; return substr(str_shuffle(str_repeat($chars, 5)), 0, $length); } From f34d7064ead978973e03649dea0e5b9345764aa7 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 16 Sep 2013 02:08:29 +0400 Subject: [PATCH 084/157] Better phpdoc for AccessControl --- framework/yii/web/AccessControl.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/framework/yii/web/AccessControl.php b/framework/yii/web/AccessControl.php index 35d6cae..7f791b8 100644 --- a/framework/yii/web/AccessControl.php +++ b/framework/yii/web/AccessControl.php @@ -31,15 +31,17 @@ use yii\base\ActionFilter; * 'class' => \yii\web\AccessControl::className(), * 'only' => array('create', 'update'), * 'rules' => array( + * // deny all POST requests + * array( + * 'allow' => false, + * 'verbs' => array('POST') + * ), * // allow authenticated users * array( * 'allow' => true, * 'roles' => array('@'), * ), - * // deny all - * array( - * 'allow' => false, - * ), + * // everything else is denied * ), * ), * ); From 325f83f66b801605638ebbd0d7635d863ab29c3f Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 16 Sep 2013 02:41:19 +0400 Subject: [PATCH 085/157] Basic application enhancements. - Turned on CSRF validation by default. - Application params are now readed before config is defined to be able to use values from params when configuring. - Added access control for login and logout. --- apps/basic/config/web.php | 7 +++++-- apps/basic/controllers/SiteController.php | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/basic/config/web.php b/apps/basic/config/web.php index 1433a64..e7d9420 100644 --- a/apps/basic/config/web.php +++ b/apps/basic/config/web.php @@ -1,9 +1,12 @@ 'bootstrap', 'basePath' => dirname(__DIR__), 'components' => array( + 'request' => array( + 'enableCsrfValidation' => true, + ), 'cache' => array( 'class' => 'yii\caching\FileCache', ), @@ -23,7 +26,7 @@ $config = array( ), ), ), - 'params' => require(__DIR__ . '/params.php'), + 'params' => $params, ); if (YII_ENV_DEV) { diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index cd0b3fb..785eddf 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -9,6 +9,28 @@ use app\models\ContactForm; class SiteController extends Controller { + public function behaviors() + { + return array( + 'access' => array( + 'class' => \yii\web\AccessControl::className(), + 'only' => array('login', 'logout'), + 'rules' => array( + array( + 'actions' => array('login'), + 'allow' => true, + 'roles' => array('?'), + ), + array( + 'actions' => array('logout'), + 'allow' => true, + 'roles' => array('@'), + ), + ), + ), + ); + } + public function actions() { return array( From f5778b6bf0e9a39a65f3ce6a9788e2d3ff9676b1 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 16 Sep 2013 02:46:29 +0400 Subject: [PATCH 086/157] Advanced application enhancements. - Turned on CSRF validation by default. - Added access control for login, signup and logout for frontend application. - Added access control for login, logout and index for backend application. - YII_ENV is now defined for all applications. - No trace is writted to logs if debug is turned off. - Added default error view for frontend and backend. - In frontend application captcha will always ask for "testme" if YII_ENV is defined as "test". --- apps/advanced/backend/config/main.php | 8 +++++- .../backend/controllers/SiteController.php | 30 ++++++++++++++++++++++ apps/advanced/backend/views/site/error.php | 29 +++++++++++++++++++++ .../environments/dev/backend/web/index.php | 2 +- .../environments/dev/frontend/web/index.php | 3 +-- apps/advanced/environments/dev/yii | 1 + .../environments/prod/backend/web/index.php | 2 +- .../environments/prod/frontend/web/index.php | 3 +-- apps/advanced/environments/prod/yii | 1 + apps/advanced/frontend/config/main.php | 8 +++++- .../frontend/controllers/SiteController.php | 26 +++++++++++++++++++ apps/advanced/frontend/views/site/error.php | 29 +++++++++++++++++++++ 12 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 apps/advanced/backend/views/site/error.php create mode 100644 apps/advanced/frontend/views/site/error.php diff --git a/apps/advanced/backend/config/main.php b/apps/advanced/backend/config/main.php index 377d34c..30c1825 100644 --- a/apps/advanced/backend/config/main.php +++ b/apps/advanced/backend/config/main.php @@ -17,13 +17,16 @@ return array( 'modules' => array( ), 'components' => array( + 'request' => array( + 'enableCsrfValidation' => true, + ), 'db' => $params['components.db'], 'cache' => $params['components.cache'], 'user' => array( - 'class' => 'yii\web\User', 'identityClass' => 'common\models\User', ), 'log' => array( + 'traceLevel' => YII_DEBUG ? 3 : 0, 'targets' => array( array( 'class' => 'yii\log\FileTarget', @@ -31,6 +34,9 @@ return array( ), ), ), + 'errorHandler' => array( + 'errorAction' => 'site/error', + ), ), 'params' => $params, ); diff --git a/apps/advanced/backend/controllers/SiteController.php b/apps/advanced/backend/controllers/SiteController.php index 480406a..28f2310 100644 --- a/apps/advanced/backend/controllers/SiteController.php +++ b/apps/advanced/backend/controllers/SiteController.php @@ -8,6 +8,36 @@ use common\models\LoginForm; class SiteController extends Controller { + public function behaviors() + { + return array( + 'access' => array( + 'class' => \yii\web\AccessControl::className(), + 'rules' => array( + array( + 'actions' => array('login'), + 'allow' => true, + 'roles' => array('?'), + ), + array( + 'actions' => array('logout', 'index'), + 'allow' => true, + 'roles' => array('@'), + ), + ), + ), + ); + } + + public function actions() + { + return array( + 'error' => array( + 'class' => 'yii\web\ErrorAction', + ), + ); + } + public function actionIndex() { return $this->render('index'); diff --git a/apps/advanced/backend/views/site/error.php b/apps/advanced/backend/views/site/error.php new file mode 100644 index 0000000..024e27d --- /dev/null +++ b/apps/advanced/backend/views/site/error.php @@ -0,0 +1,29 @@ +title = $name; +?> +
+ +

title); ?>

+ +
+ +
+ +

+ The above error occurred while the Web server was processing your request. +

+

+ Please contact us if you think this is a server error. Thank you. +

+ +
diff --git a/apps/advanced/environments/dev/backend/web/index.php b/apps/advanced/environments/dev/backend/web/index.php index 7d47419..2113419 100644 --- a/apps/advanced/environments/dev/backend/web/index.php +++ b/apps/advanced/environments/dev/backend/web/index.php @@ -1,6 +1,6 @@ 'yii\gii\Module' ), 'components' => array( + 'request' => array( + 'enableCsrfValidation' => true, + ), 'db' => $params['components.db'], 'cache' => $params['components.cache'], 'user' => array( - 'class' => 'yii\web\User', 'identityClass' => 'common\models\User', ), 'log' => array( + 'traceLevel' => YII_DEBUG ? 3 : 0, 'targets' => array( array( 'class' => 'yii\log\FileTarget', @@ -31,6 +34,9 @@ return array( ), ), ), + 'errorHandler' => array( + 'errorAction' => 'site/error', + ), ), 'params' => $params, ); diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php index 0c1b2f5..be9a634 100644 --- a/apps/advanced/frontend/controllers/SiteController.php +++ b/apps/advanced/frontend/controllers/SiteController.php @@ -12,11 +12,37 @@ use yii\helpers\Security; class SiteController extends Controller { + public function behaviors() + { + return array( + 'access' => array( + 'class' => \yii\web\AccessControl::className(), + 'only' => array('login', 'logout', 'signup'), + 'rules' => array( + array( + 'actions' => array('login', 'signup'), + 'allow' => true, + 'roles' => array('?'), + ), + array( + 'actions' => array('logout'), + 'allow' => true, + 'roles' => array('@'), + ), + ), + ), + ); + } + public function actions() { return array( + 'error' => array( + 'class' => 'yii\web\ErrorAction', + ), 'captcha' => array( 'class' => 'yii\captcha\CaptchaAction', + 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, ), ); } diff --git a/apps/advanced/frontend/views/site/error.php b/apps/advanced/frontend/views/site/error.php new file mode 100644 index 0000000..024e27d --- /dev/null +++ b/apps/advanced/frontend/views/site/error.php @@ -0,0 +1,29 @@ +title = $name; +?> +
+ +

title); ?>

+ +
+ +
+ +

+ The above error occurred while the Web server was processing your request. +

+

+ Please contact us if you think this is a server error. Thank you. +

+ +
From 51c29e444dc8bbd1ce620f80320c13f998e99f7d Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 15 Sep 2013 18:54:26 -0400 Subject: [PATCH 087/157] renamed Request::csrfTokenName to csrfVar. added version, csrfVar and csrfToken to yii js module. --- framework/yii/assets/yii.js | 6 ++++++ framework/yii/helpers/HtmlBase.php | 2 +- framework/yii/web/Request.php | 22 +++++++++++----------- framework/yii/web/YiiAsset.php | 17 +++++++++++++++++ 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index 31a57d5..3859a55 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -43,7 +43,13 @@ */ yii = (function ($) { var pub = { + // version of Yii framework version: '2.0', + // CSRF token name and value. If this is set and a form is created and submitted using JavaScript + // via POST, the CSRF token should be submitted too to pass CSRF validation. + csrfVar: undefined, + csrfToken: undefined, + initModule: function (module) { if (module.isActive === undefined || module.isActive) { if ($.isFunction(module.init)) { diff --git a/framework/yii/helpers/HtmlBase.php b/framework/yii/helpers/HtmlBase.php index a93c93e..a5786cb 100644 --- a/framework/yii/helpers/HtmlBase.php +++ b/framework/yii/helpers/HtmlBase.php @@ -238,7 +238,7 @@ class HtmlBase $method = 'post'; } if ($request->enableCsrfValidation) { - $hiddenInputs[] = static::hiddenInput($request->csrfTokenName, $request->getCsrfToken()); + $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); } } diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 1482633..37aa4a8 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -73,16 +73,16 @@ class Request extends \yii\base\Request * from the same application. If not, a 400 HTTP exception will be raised. * * Note, this feature requires that the user client accepts cookie. Also, to use this feature, - * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfTokenName]]. + * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfVar]]. * You may use [[\yii\web\Html::beginForm()]] to generate his hidden input. * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery */ public $enableCsrfValidation = false; /** - * @var string the name of the token used to prevent CSRF. Defaults to 'YII_CSRF_TOKEN'. - * This property is effectively only when {@link enableCsrfValidation} is true. + * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'. + * This property is effectively only when [[enableCsrfValidation]] is true. */ - public $csrfTokenName = '_csrf'; + public $csrfVar = '_csrf'; /** * @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true. * @see Cookie @@ -975,7 +975,7 @@ class Request extends \yii\base\Request public function getCsrfToken() { if ($this->_csrfCookie === null) { - $this->_csrfCookie = $this->getCookies()->get($this->csrfTokenName); + $this->_csrfCookie = $this->getCookies()->get($this->csrfVar); if ($this->_csrfCookie === null) { $this->_csrfCookie = $this->createCsrfCookie(); Yii::$app->getResponse()->getCookies()->add($this->_csrfCookie); @@ -994,7 +994,7 @@ class Request extends \yii\base\Request protected function createCsrfCookie() { $options = $this->csrfCookie; - $options['name'] = $this->csrfTokenName; + $options['name'] = $this->csrfVar; $options['value'] = sha1(uniqid(mt_rand(), true)); return new Cookie($options); } @@ -1015,19 +1015,19 @@ class Request extends \yii\base\Request $cookies = $this->getCookies(); switch ($method) { case 'POST': - $token = $this->getPost($this->csrfTokenName); + $token = $this->getPost($this->csrfVar); break; case 'PUT': - $token = $this->getPut($this->csrfTokenName); + $token = $this->getPut($this->csrfVar); break; case 'PATCH': - $token = $this->getPatch($this->csrfTokenName); + $token = $this->getPatch($this->csrfVar); break; case 'DELETE': - $token = $this->getDelete($this->csrfTokenName); + $token = $this->getDelete($this->csrfVar); } - if (empty($token) || $cookies->getValue($this->csrfTokenName) !== $token) { + if (empty($token) || $cookies->getValue($this->csrfVar) !== $token) { throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.')); } } diff --git a/framework/yii/web/YiiAsset.php b/framework/yii/web/YiiAsset.php index 8a4d77a..3c843a1 100644 --- a/framework/yii/web/YiiAsset.php +++ b/framework/yii/web/YiiAsset.php @@ -7,6 +7,8 @@ namespace yii\web; +use Yii; + /** * @author Qiang Xue * @since 2.0 @@ -20,4 +22,19 @@ class YiiAsset extends AssetBundle public $depends = array( 'yii\web\JqueryAsset', ); + + /** + * @inheritdoc + */ + public function registerAssets($view) + { + parent::registerAssets($view); + $js[] = "yii.version = '" . Yii::getVersion() . "';"; + $request = Yii::$app->getRequest(); + if ($request instanceof Request && $request->enableCsrfValidation) { + $js[] = "yii.csrfVar = '{$request->csrfVar}';"; + $js[] = "yii.csrfToken = '{$request->csrfToken}';"; + } + $view->registerJs(implode("\n", $js)); + } } From ad479dd7f65d7701848968c9ba2169042b29623f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 15 Sep 2013 18:57:11 -0400 Subject: [PATCH 088/157] Modified js registration position. --- framework/yii/web/YiiAsset.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/framework/yii/web/YiiAsset.php b/framework/yii/web/YiiAsset.php index 3c843a1..7d82027 100644 --- a/framework/yii/web/YiiAsset.php +++ b/framework/yii/web/YiiAsset.php @@ -8,6 +8,7 @@ namespace yii\web; use Yii; +use yii\base\View; /** * @author Qiang Xue @@ -29,12 +30,12 @@ class YiiAsset extends AssetBundle public function registerAssets($view) { parent::registerAssets($view); - $js[] = "yii.version = '" . Yii::getVersion() . "';"; + $js[] = "yii.version='" . Yii::getVersion() . "';"; $request = Yii::$app->getRequest(); if ($request instanceof Request && $request->enableCsrfValidation) { - $js[] = "yii.csrfVar = '{$request->csrfVar}';"; - $js[] = "yii.csrfToken = '{$request->csrfToken}';"; + $js[] = "yii.csrfVar='{$request->csrfVar}';"; + $js[] = "yii.csrfToken='{$request->csrfToken}';"; } - $view->registerJs(implode("\n", $js)); + $view->registerJs(implode("\n", $js), View::POS_END); } } From bc9a6f3e16e304c5608eb336dbd8b68253a7fef2 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 16 Sep 2013 23:20:17 +0400 Subject: [PATCH 089/157] Better strtr arguments format --- framework/yii/helpers/SecurityBase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/helpers/SecurityBase.php b/framework/yii/helpers/SecurityBase.php index 0ecad0f..5b192de 100644 --- a/framework/yii/helpers/SecurityBase.php +++ b/framework/yii/helpers/SecurityBase.php @@ -140,7 +140,7 @@ class SecurityBase public static function generateRandomKey($length = 32) { if (function_exists('openssl_random_pseudo_bytes')) { - $key = strtr(base64_encode(openssl_random_pseudo_bytes($length, $strong)), array('+' => '_', '/' => '-', '=' => '.')); + $key = strtr(base64_encode(openssl_random_pseudo_bytes($length, $strong)), '+/=', '_-.'); if ($strong) { return substr($key, 0, $length); } From dd59dd1db7f3c634ecf334526bae599b36267d29 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 16 Sep 2013 22:13:32 -0400 Subject: [PATCH 090/157] doc fix. --- framework/yii/bootstrap/Nav.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index e19eae1..8d62ec5 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -56,7 +56,7 @@ class Nav extends Widget { /** * @var array list of items in the nav widget. Each array element represents a single - * menu item with the following structure: + * menu item which can be either a string or an array with the following structure: * * - label: string, required, the nav item label. * - url: optional, the item's URL. Defaults to "#". @@ -66,6 +66,8 @@ class Nav extends Widget * - active: boolean, optional, whether the item should be on active state or not. * - items: array|string, optional, the configuration array for creating a [[Dropdown]] widget, * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus. + * + * It a menu item is a string, it will be rendered directly without HTML encoding. */ public $items = array(); /** From 43d15155c60fc18d4137062ff5b5d0216527602e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 17 Sep 2013 10:01:01 +0200 Subject: [PATCH 091/157] cleanup db `use` statements --- framework/yii/db/cubrid/Schema.php | 1 - framework/yii/db/mssql/Schema.php | 1 - framework/yii/db/mysql/QueryBuilder.php | 1 - 3 files changed, 3 deletions(-) diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index c7b6ad2..99624f6 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -7,7 +7,6 @@ namespace yii\db\cubrid; -use yii\base\NotSupportedException; use yii\db\Expression; use yii\db\TableSchema; use yii\db\ColumnSchema; diff --git a/framework/yii/db/mssql/Schema.php b/framework/yii/db/mssql/Schema.php index 5f53d8e..9def3b4 100644 --- a/framework/yii/db/mssql/Schema.php +++ b/framework/yii/db/mssql/Schema.php @@ -7,7 +7,6 @@ namespace yii\db\mssql; -use yii\db\mssql\TableSchema; use yii\db\ColumnSchema; /** diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index 1b67cda..386de2f 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -9,7 +9,6 @@ namespace yii\db\mysql; use yii\db\Exception; use yii\base\InvalidParamException; -use yii\db\mysql\Schema; /** * QueryBuilder is the query builder for MySQL databases. From 240b42aa0c81928252b3802f7a016436f3367473 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 17 Sep 2013 20:48:20 +0400 Subject: [PATCH 092/157] fixed typo --- framework/yii/bootstrap/Nav.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index 8d62ec5..f24f729 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -67,7 +67,7 @@ class Nav extends Widget * - items: array|string, optional, the configuration array for creating a [[Dropdown]] widget, * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus. * - * It a menu item is a string, it will be rendered directly without HTML encoding. + * If a menu item is a string, it will be rendered directly without HTML encoding. */ public $items = array(); /** From f38c516ac5c2ffee681d0a4bfe5ae3c7b8f9c09f Mon Sep 17 00:00:00 2001 From: Panagiotis Moustafellos Date: Tue, 17 Sep 2013 20:20:18 +0300 Subject: [PATCH 093/157] Added memcached service in Travis YAML --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d5aa6c3..2add223 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: services: - redis-server + - memcached before_script: - composer self-update && composer --version @@ -24,4 +25,4 @@ script: - phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata after_script: - - php vendor/bin/coveralls \ No newline at end of file + - php vendor/bin/coveralls From 1aa836ffc74ba2788786fb32b2a939a7b54ae1b7 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 17 Sep 2013 21:00:19 -0400 Subject: [PATCH 094/157] use meta tags to pass CSRF token. --- framework/yii/assets/yii.js | 19 +++++++++++++------ framework/yii/base/View.php | 8 ++++++++ framework/yii/web/YiiAsset.php | 15 --------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index 3859a55..f2ac379 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -43,12 +43,19 @@ */ yii = (function ($) { var pub = { - // version of Yii framework - version: '2.0', - // CSRF token name and value. If this is set and a form is created and submitted using JavaScript - // via POST, the CSRF token should be submitted too to pass CSRF validation. - csrfVar: undefined, - csrfToken: undefined, + /** + * @return string|undefined the CSRF variable name. Undefined is returned is CSRF validation is not enabled. + */ + getCsrfVar: function() { + return $('meta[name=csrf-var]').attr('content'); + }, + + /** + * @return string|undefined the CSRF token. Undefined is returned is CSRF validation is not enabled. + */ + getCsrfToken: function() { + return $('meta[name=csrf-token]').attr('content'); + }, initModule: function (module) { if (module.isActive === undefined || module.isActive) { diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php index 4d3d996..77d0e5c 100644 --- a/framework/yii/base/View.php +++ b/framework/yii/base/View.php @@ -12,6 +12,7 @@ use yii\helpers\FileHelper; use yii\helpers\Html; use yii\web\JqueryAsset; use yii\web\AssetBundle; +use yii\web\Request; use yii\widgets\Block; use yii\widgets\ContentDecorator; use yii\widgets\FragmentCache; @@ -708,6 +709,13 @@ class View extends Component if (!empty($this->metaTags)) { $lines[] = implode("\n", $this->metaTags); } + + $request = Yii::$app->getRequest(); + if ($request instanceof Request && $request->enableCsrfValidation) { + $lines[] = Html::tag('meta', '', array('name' => 'csrf-var', 'content' => $request->csrfVar)); + $lines[] = Html::tag('meta', '', array('name' => 'csrf-token', 'content' => $request->getCsrfToken())); + } + if (!empty($this->linkTags)) { $lines[] = implode("\n", $this->linkTags); } diff --git a/framework/yii/web/YiiAsset.php b/framework/yii/web/YiiAsset.php index 7d82027..2ad5384 100644 --- a/framework/yii/web/YiiAsset.php +++ b/framework/yii/web/YiiAsset.php @@ -23,19 +23,4 @@ class YiiAsset extends AssetBundle public $depends = array( 'yii\web\JqueryAsset', ); - - /** - * @inheritdoc - */ - public function registerAssets($view) - { - parent::registerAssets($view); - $js[] = "yii.version='" . Yii::getVersion() . "';"; - $request = Yii::$app->getRequest(); - if ($request instanceof Request && $request->enableCsrfValidation) { - $js[] = "yii.csrfVar='{$request->csrfVar}';"; - $js[] = "yii.csrfToken='{$request->csrfToken}';"; - } - $view->registerJs(implode("\n", $js), View::POS_END); - } } From 2db91187db2033eec943b44b402a1fbe8838a888 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 17 Sep 2013 21:02:48 -0400 Subject: [PATCH 095/157] Use .prop() instead .attr(). --- framework/yii/assets/yii.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index f2ac379..eb5ecf6 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -47,14 +47,14 @@ yii = (function ($) { * @return string|undefined the CSRF variable name. Undefined is returned is CSRF validation is not enabled. */ getCsrfVar: function() { - return $('meta[name=csrf-var]').attr('content'); + return $('meta[name=csrf-var]').prop('content'); }, /** * @return string|undefined the CSRF token. Undefined is returned is CSRF validation is not enabled. */ getCsrfToken: function() { - return $('meta[name=csrf-token]').attr('content'); + return $('meta[name=csrf-token]').prop('content'); }, initModule: function (module) { From 2deff126cfdba665b9ab8654d963298ee0616f96 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 17 Sep 2013 21:43:03 -0400 Subject: [PATCH 096/157] Supports sending CSRF token via HTTP header. --- framework/yii/web/Request.php | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 37aa4a8..eb1652c 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -68,19 +68,28 @@ use yii\helpers\Security; class Request extends \yii\base\Request { /** + * The name of the HTTP header for sending CSRF token. + */ + const CSRF_HEADER = 'X-CSRF-TOKEN'; + + /** * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false. - * By setting this property to true, forms submitted to an Yii Web application must be originated + * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated * from the same application. If not, a 400 HTTP exception will be raised. * * Note, this feature requires that the user client accepts cookie. Also, to use this feature, * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfVar]]. * You may use [[\yii\web\Html::beginForm()]] to generate his hidden input. + * + * In JavaScript, you may get the values of [[csrfVar]] and [[csrfToken]] via `yii.getCsrfVar()` and + * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered. + * * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery */ public $enableCsrfValidation = false; /** * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'. - * This property is effectively only when [[enableCsrfValidation]] is true. + * This property is used only when [[enableCsrfValidation]] is true. */ public $csrfVar = '_csrf'; /** @@ -986,6 +995,14 @@ class Request extends \yii\base\Request } /** + * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent. + */ + public function getCsrfTokenFromHeader() + { + return isset($_SERVER[self::CSRF_HEADER]) ? $_SERVER[self::CSRF_HEADER] : null; + } + + /** * Creates a cookie with a randomly generated CSRF token. * Initial values specified in [[csrfCookie]] will be applied to the generated cookie. * @return Cookie the generated cookie @@ -1012,7 +1029,7 @@ class Request extends \yii\base\Request } $method = $this->getMethod(); if ($method === 'POST' || $method === 'PUT' || $method === 'PATCH' || $method === 'DELETE') { - $cookies = $this->getCookies(); + $trueToken = $this->getCookies()->getValue($this->csrfVar); switch ($method) { case 'POST': $token = $this->getPost($this->csrfVar); @@ -1027,7 +1044,8 @@ class Request extends \yii\base\Request $token = $this->getDelete($this->csrfVar); } - if (empty($token) || $cookies->getValue($this->csrfVar) !== $token) { + $valid = !empty($token) && $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken; + if (!$valid) { throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.')); } } From 9836b28ed38428e9f7aa84e95fff3d05638550b8 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 18 Sep 2013 00:35:31 -0400 Subject: [PATCH 097/157] Fixes #884. --- framework/yii/base/View.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php index 77d0e5c..df0b2b2 100644 --- a/framework/yii/base/View.php +++ b/framework/yii/base/View.php @@ -12,7 +12,6 @@ use yii\helpers\FileHelper; use yii\helpers\Html; use yii\web\JqueryAsset; use yii\web\AssetBundle; -use yii\web\Request; use yii\widgets\Block; use yii\widgets\ContentDecorator; use yii\widgets\FragmentCache; @@ -711,7 +710,7 @@ class View extends Component } $request = Yii::$app->getRequest(); - if ($request instanceof Request && $request->enableCsrfValidation) { + if ($request instanceof \yii\web\Request && $request->enableCsrfValidation) { $lines[] = Html::tag('meta', '', array('name' => 'csrf-var', 'content' => $request->csrfVar)); $lines[] = Html::tag('meta', '', array('name' => 'csrf-token', 'content' => $request->getCsrfToken())); } From 0e261fba5ad7ebb020485f803939b99ac115b49d Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 16:00:55 +0200 Subject: [PATCH 098/157] moved redis out of yii\db namespace --- framework/yii/caching/RedisCache.php | 10 +- framework/yii/db/redis/Connection.php | 428 --------------------------------- framework/yii/db/redis/Transaction.php | 93 ------- framework/yii/redis/Connection.php | 428 +++++++++++++++++++++++++++++++++ framework/yii/redis/Transaction.php | 93 +++++++ 5 files changed, 525 insertions(+), 527 deletions(-) delete mode 100644 framework/yii/db/redis/Connection.php delete mode 100644 framework/yii/db/redis/Transaction.php create mode 100644 framework/yii/redis/Connection.php create mode 100644 framework/yii/redis/Transaction.php diff --git a/framework/yii/caching/RedisCache.php b/framework/yii/caching/RedisCache.php index 09ce599..5c778fc 100644 --- a/framework/yii/caching/RedisCache.php +++ b/framework/yii/caching/RedisCache.php @@ -7,7 +7,7 @@ namespace yii\caching; -use yii\db\redis\Connection; +use yii\redis\Connection; /** * RedisCache implements a cache application component based on [redis](http://redis.io/). @@ -39,7 +39,7 @@ use yii\db\redis\Connection; * ) * ~~~ * - * @property \yii\db\redis\Connection $connection This property is read-only. + * @property Connection $connection The redis connection object. This property is read-only. * * @author Carsten Brandt * @since 2.0 @@ -71,7 +71,7 @@ class RedisCache extends Cache */ public $dataTimeout = null; /** - * @var \yii\db\redis\Connection the redis connection + * @var Connection the redis connection */ private $_connection; @@ -88,9 +88,7 @@ class RedisCache extends Cache /** * Returns the redis connection object. * Establishes a connection to the redis server if it does not already exists. - * - * TODO throw exception on error - * @return \yii\db\redis\Connection + * @return Connection the redis connection object. */ public function getConnection() { diff --git a/framework/yii/db/redis/Connection.php b/framework/yii/db/redis/Connection.php deleted file mode 100644 index 68b40d3..0000000 --- a/framework/yii/db/redis/Connection.php +++ /dev/null @@ -1,428 +0,0 @@ - - * @since 2.0 - */ -class Connection extends Component -{ - /** - * @event Event an event that is triggered after a DB connection is established - */ - const EVENT_AFTER_OPEN = 'afterOpen'; - - /** - * @var string the Data Source Name, or DSN, contains the information required to connect to the database. - * DSN format: redis://server:port[/db] - * Where db is a zero based integer which refers to the DB to use. - * If no DB is given, ID 0 is used. - * - * Example: redis://localhost:6379/2 - */ - public $dsn; - /** - * @var string the password for establishing DB connection. Defaults to null meaning no AUTH command is send. - * See http://redis.io/commands/auth - */ - public $password; - /** - * @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout") - */ - public $connectionTimeout = null; - /** - * @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used. - */ - public $dataTimeout = null; - - /** - * @var array List of available redis commands http://redis.io/commands - */ - public $redisCommands = array( - 'BRPOP', // key [key ...] timeout Remove and get the last element in a list, or block until one is available - 'BRPOPLPUSH', // source destination timeout Pop a value from a list, push it to another list and return it; or block until one is available - 'CLIENT KILL', // ip:port Kill the connection of a client - 'CLIENT LIST', // Get the list of client connections - 'CLIENT GETNAME', // Get the current connection name - 'CLIENT SETNAME', // connection-name Set the current connection name - 'CONFIG GET', // parameter Get the value of a configuration parameter - 'CONFIG SET', // parameter value Set a configuration parameter to the given value - 'CONFIG RESETSTAT', // Reset the stats returned by INFO - 'DBSIZE', // Return the number of keys in the selected database - 'DEBUG OBJECT', // key Get debugging information about a key - 'DEBUG SEGFAULT', // Make the server crash - 'DECR', // key Decrement the integer value of a key by one - 'DECRBY', // key decrement Decrement the integer value of a key by the given number - 'DEL', // key [key ...] Delete a key - 'DISCARD', // Discard all commands issued after MULTI - 'DUMP', // key Return a serialized version of the value stored at the specified key. - 'ECHO', // message Echo the given string - 'EVAL', // script numkeys key [key ...] arg [arg ...] Execute a Lua script server side - 'EVALSHA', // sha1 numkeys key [key ...] arg [arg ...] Execute a Lua script server side - 'EXEC', // Execute all commands issued after MULTI - 'EXISTS', // key Determine if a key exists - 'EXPIRE', // key seconds Set a key's time to live in seconds - 'EXPIREAT', // key timestamp Set the expiration for a key as a UNIX timestamp - 'FLUSHALL', // Remove all keys from all databases - 'FLUSHDB', // Remove all keys from the current database - 'GET', // key Get the value of a key - 'GETBIT', // key offset Returns the bit value at offset in the string value stored at key - 'GETRANGE', // key start end Get a substring of the string stored at a key - 'GETSET', // key value Set the string value of a key and return its old value - 'HDEL', // key field [field ...] Delete one or more hash fields - 'HEXISTS', // key field Determine if a hash field exists - 'HGET', // key field Get the value of a hash field - 'HGETALL', // key Get all the fields and values in a hash - 'HINCRBY', // key field increment Increment the integer value of a hash field by the given number - 'HINCRBYFLOAT', // key field increment Increment the float value of a hash field by the given amount - 'HKEYS', // key Get all the fields in a hash - 'HLEN', // key Get the number of fields in a hash - 'HMGET', // key field [field ...] Get the values of all the given hash fields - 'HMSET', // key field value [field value ...] Set multiple hash fields to multiple values - 'HSET', // key field value Set the string value of a hash field - 'HSETNX', // key field value Set the value of a hash field, only if the field does not exist - 'HVALS', // key Get all the values in a hash - 'INCR', // key Increment the integer value of a key by one - 'INCRBY', // key increment Increment the integer value of a key by the given amount - 'INCRBYFLOAT', // key increment Increment the float value of a key by the given amount - 'INFO', // [section] Get information and statistics about the server - 'KEYS', // pattern Find all keys matching the given pattern - 'LASTSAVE', // Get the UNIX time stamp of the last successful save to disk - 'LINDEX', // key index Get an element from a list by its index - 'LINSERT', // key BEFORE|AFTER pivot value Insert an element before or after another element in a list - 'LLEN', // key Get the length of a list - 'LPOP', // key Remove and get the first element in a list - 'LPUSH', // key value [value ...] Prepend one or multiple values to a list - 'LPUSHX', // key value Prepend a value to a list, only if the list exists - 'LRANGE', // key start stop Get a range of elements from a list - 'LREM', // key count value Remove elements from a list - 'LSET', // key index value Set the value of an element in a list by its index - 'LTRIM', // key start stop Trim a list to the specified range - 'MGET', // key [key ...] Get the values of all the given keys - 'MIGRATE', // host port key destination-db timeout Atomically transfer a key from a Redis instance to another one. - 'MONITOR', // Listen for all requests received by the server in real time - 'MOVE', // key db Move a key to another database - 'MSET', // key value [key value ...] Set multiple keys to multiple values - 'MSETNX', // key value [key value ...] Set multiple keys to multiple values, only if none of the keys exist - 'MULTI', // Mark the start of a transaction block - 'OBJECT', // subcommand [arguments [arguments ...]] Inspect the internals of Redis objects - 'PERSIST', // key Remove the expiration from a key - 'PEXPIRE', // key milliseconds Set a key's time to live in milliseconds - 'PEXPIREAT', // key milliseconds-timestamp Set the expiration for a key as a UNIX timestamp specified in milliseconds - 'PING', // Ping the server - 'PSETEX', // key milliseconds value Set the value and expiration in milliseconds of a key - 'PSUBSCRIBE', // pattern [pattern ...] Listen for messages published to channels matching the given patterns - 'PTTL', // key Get the time to live for a key in milliseconds - 'PUBLISH', // channel message Post a message to a channel - 'PUNSUBSCRIBE', // [pattern [pattern ...]] Stop listening for messages posted to channels matching the given patterns - 'QUIT', // Close the connection - 'RANDOMKEY', // Return a random key from the keyspace - 'RENAME', // key newkey Rename a key - 'RENAMENX', // key newkey Rename a key, only if the new key does not exist - 'RESTORE', // key ttl serialized-value Create a key using the provided serialized value, previously obtained using DUMP. - 'RPOP', // key Remove and get the last element in a list - 'RPOPLPUSH', // source destination Remove the last element in a list, append it to another list and return it - 'RPUSH', // key value [value ...] Append one or multiple values to a list - 'RPUSHX', // key value Append a value to a list, only if the list exists - 'SADD', // key member [member ...] Add one or more members to a set - 'SAVE', // Synchronously save the dataset to disk - 'SCARD', // key Get the number of members in a set - 'SCRIPT EXISTS', // script [script ...] Check existence of scripts in the script cache. - 'SCRIPT FLUSH', // Remove all the scripts from the script cache. - 'SCRIPT KILL', // Kill the script currently in execution. - 'SCRIPT LOAD', // script Load the specified Lua script into the script cache. - 'SDIFF', // key [key ...] Subtract multiple sets - 'SDIFFSTORE', // destination key [key ...] Subtract multiple sets and store the resulting set in a key - 'SELECT', // index Change the selected database for the current connection - 'SET', // key value Set the string value of a key - 'SETBIT', // key offset value Sets or clears the bit at offset in the string value stored at key - 'SETEX', // key seconds value Set the value and expiration of a key - 'SETNX', // key value Set the value of a key, only if the key does not exist - 'SETRANGE', // key offset value Overwrite part of a string at key starting at the specified offset - 'SHUTDOWN', // [NOSAVE] [SAVE] Synchronously save the dataset to disk and then shut down the server - 'SINTER', // key [key ...] Intersect multiple sets - 'SINTERSTORE', // destination key [key ...] Intersect multiple sets and store the resulting set in a key - 'SISMEMBER', // key member Determine if a given value is a member of a set - 'SLAVEOF', // host port Make the server a slave of another instance, or promote it as master - 'SLOWLOG', // subcommand [argument] Manages the Redis slow queries log - 'SMEMBERS', // key Get all the members in a set - 'SMOVE', // source destination member Move a member from one set to another - 'SORT', // key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] Sort the elements in a list, set or sorted set - 'SPOP', // key Remove and return a random member from a set - 'SRANDMEMBER', // key [count] Get one or multiple random members from a set - 'SREM', // key member [member ...] Remove one or more members from a set - 'STRLEN', // key Get the length of the value stored in a key - 'SUBSCRIBE', // channel [channel ...] Listen for messages published to the given channels - 'SUNION', // key [key ...] Add multiple sets - 'SUNIONSTORE', // destination key [key ...] Add multiple sets and store the resulting set in a key - 'SYNC', // Internal command used for replication - 'TIME', // Return the current server time - 'TTL', // key Get the time to live for a key - 'TYPE', // key Determine the type stored at key - 'UNSUBSCRIBE', // [channel [channel ...]] Stop listening for messages posted to the given channels - 'UNWATCH', // Forget about all watched keys - 'WATCH', // key [key ...] Watch the given keys to determine execution of the MULTI/EXEC block - 'ZADD', // key score member [score member ...] Add one or more members to a sorted set, or update its score if it already exists - 'ZCARD', // key Get the number of members in a sorted set - 'ZCOUNT', // key min max Count the members in a sorted set with scores within the given values - 'ZINCRBY', // key increment member Increment the score of a member in a sorted set - 'ZINTERSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Intersect multiple sorted sets and store the resulting sorted set in a new key - 'ZRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index - 'ZRANGEBYSCORE', // key min max [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score - 'ZRANK', // key member Determine the index of a member in a sorted set - 'ZREM', // key member [member ...] Remove one or more members from a sorted set - 'ZREMRANGEBYRANK', // key start stop Remove all members in a sorted set within the given indexes - 'ZREMRANGEBYSCORE', // key min max Remove all members in a sorted set within the given scores - 'ZREVRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index, with scores ordered from high to low - 'ZREVRANGEBYSCORE', // key max min [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score, with scores ordered from high to low - 'ZREVRANK', // key member Determine the index of a member in a sorted set, with scores ordered from high to low - 'ZSCORE', // key member Get the score associated with the given member in a sorted set - 'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key - ); - /** - * @var Transaction the currently active transaction - */ - private $_transaction; - /** - * @var resource redis socket connection - */ - private $_socket; - - /** - * Closes the connection when this component is being serialized. - * @return array - */ - public function __sleep() - { - $this->close(); - return array_keys(get_object_vars($this)); - } - - /** - * Returns a value indicating whether the DB connection is established. - * @return boolean whether the DB connection is established - */ - public function getIsActive() - { - return $this->_socket !== null; - } - - /** - * Establishes a DB connection. - * It does nothing if a DB connection has already been established. - * @throws Exception if connection fails - */ - public function open() - { - if ($this->_socket === null) { - if (empty($this->dsn)) { - throw new InvalidConfigException('Connection.dsn cannot be empty.'); - } - $dsn = explode('/', $this->dsn); - $host = $dsn[2]; - if (strpos($host, ':')===false) { - $host .= ':6379'; - } - $db = isset($dsn[3]) ? $dsn[3] : 0; - - \Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__); - $this->_socket = @stream_socket_client( - $host, - $errorNumber, - $errorDescription, - $this->connectionTimeout ? $this->connectionTimeout : ini_get("default_socket_timeout") - ); - if ($this->_socket) { - if ($this->dataTimeout !== null) { - stream_set_timeout($this->_socket, $timeout=(int)$this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000)); - } - if ($this->password !== null) { - $this->executeCommand('AUTH', array($this->password)); - } - $this->executeCommand('SELECT', array($db)); - $this->initConnection(); - } else { - \Yii::error("Failed to open DB connection ({$this->dsn}): " . $errorNumber . ' - ' . $errorDescription, __CLASS__); - $message = YII_DEBUG ? 'Failed to open DB connection: ' . $errorNumber . ' - ' . $errorDescription : 'Failed to open DB connection.'; - throw new Exception($message, $errorDescription, (int)$errorNumber); - } - } - } - - /** - * Closes the currently active DB connection. - * It does nothing if the connection is already closed. - */ - public function close() - { - if ($this->_socket !== null) { - \Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__); - $this->executeCommand('QUIT'); - stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR); - $this->_socket = null; - $this->_transaction = null; - } - } - - /** - * Initializes the DB connection. - * This method is invoked right after the DB connection is established. - * The default implementation triggers an [[EVENT_AFTER_OPEN]] event. - */ - protected function initConnection() - { - $this->trigger(self::EVENT_AFTER_OPEN); - } - - /** - * Returns the currently active transaction. - * @return Transaction the currently active transaction. Null if no active transaction. - */ - public function getTransaction() - { - return $this->_transaction && $this->_transaction->isActive ? $this->_transaction : null; - } - - /** - * Starts a transaction. - * @return Transaction the transaction initiated - */ - public function beginTransaction() - { - $this->open(); - $this->_transaction = new Transaction(array( - 'db' => $this, - )); - $this->_transaction->begin(); - return $this->_transaction; - } - - /** - * Returns the name of the DB driver for the current [[dsn]]. - * @return string name of the DB driver - */ - public function getDriverName() - { - if (($pos = strpos($this->dsn, ':')) !== false) { - return strtolower(substr($this->dsn, 0, $pos)); - } else { - return 'redis'; - } - } - - /** - * - * @param string $name - * @param array $params - * @return mixed - */ - public function __call($name, $params) - { - $redisCommand = strtoupper(Inflector::camel2words($name, false)); - if (in_array($redisCommand, $this->redisCommands)) { - return $this->executeCommand($name, $params); - } else { - return parent::__call($name, $params); - } - } - - /** - * Executes a redis command. - * For a list of available commands and their parameters see http://redis.io/commands. - * - * @param string $name the name of the command - * @param array $params list of parameters for the command - * @return array|bool|null|string Dependend on the executed command this method - * will return different data types: - * - * - `true` for commands that return "status reply". - * - `string` for commands that return "integer reply" - * as the value is in the range of a signed 64 bit integer. - * - `string` or `null` for commands that return "bulk reply". - * - `array` for commands that return "Multi-bulk replies". - * - * See [redis protocol description](http://redis.io/topics/protocol) - * for details on the mentioned reply types. - * @trows Exception for commands that return [error reply](http://redis.io/topics/protocol#error-reply). - */ - public function executeCommand($name, $params=array()) - { - $this->open(); - - array_unshift($params, $name); - $command = '*' . count($params) . "\r\n"; - foreach($params as $arg) { - $command .= '$' . mb_strlen($arg, '8bit') . "\r\n" . $arg . "\r\n"; - } - - \Yii::trace("Executing Redis Command: {$name}", __CLASS__); - fwrite($this->_socket, $command); - - return $this->parseResponse(implode(' ', $params)); - } - - private function parseResponse($command) - { - if(($line = fgets($this->_socket)) === false) { - throw new Exception("Failed to read from socket.\nRedis command was: " . $command); - } - $type = $line[0]; - $line = mb_substr($line, 1, -2, '8bit'); - switch($type) - { - case '+': // Status reply - return true; - case '-': // Error reply - throw new Exception("Redis error: " . $line . "\nRedis command was: " . $command); - case ':': // Integer reply - // no cast to int as it is in the range of a signed 64 bit integer - return $line; - case '$': // Bulk replies - if ($line == '-1') { - return null; - } - $length = $line + 2; - $data = ''; - while ($length > 0) { - if(($block = fread($this->_socket, $line + 2)) === false) { - throw new Exception("Failed to read from socket.\nRedis command was: " . $command); - } - $data .= $block; - $length -= mb_strlen($block, '8bit'); - } - return mb_substr($data, 0, -2, '8bit'); - case '*': // Multi-bulk replies - $count = (int) $line; - $data = array(); - for($i = 0; $i < $count; $i++) { - $data[] = $this->parseResponse($command); - } - return $data; - default: - throw new Exception('Received illegal data from redis: ' . $line . "\nRedis command was: " . $command); - } - } -} diff --git a/framework/yii/db/redis/Transaction.php b/framework/yii/db/redis/Transaction.php deleted file mode 100644 index 024f821..0000000 --- a/framework/yii/db/redis/Transaction.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @since 2.0 - */ -class Transaction extends \yii\base\Object -{ - /** - * @var Connection the database connection that this transaction is associated with. - */ - public $db; - /** - * @var boolean whether this transaction is active. Only an active transaction - * can [[commit()]] or [[rollBack()]]. This property is set true when the transaction is started. - */ - private $_active = false; - - /** - * Returns a value indicating whether this transaction is active. - * @return boolean whether this transaction is active. Only an active transaction - * can [[commit()]] or [[rollBack()]]. - */ - public function getIsActive() - { - return $this->_active; - } - - /** - * Begins a transaction. - * @throws InvalidConfigException if [[connection]] is null - */ - public function begin() - { - if (!$this->_active) { - if ($this->db === null) { - throw new InvalidConfigException('Transaction::db must be set.'); - } - \Yii::trace('Starting transaction', __CLASS__); - $this->db->open(); - $this->db->createCommand('MULTI')->execute(); - $this->_active = true; - } - } - - /** - * Commits a transaction. - * @throws Exception if the transaction or the DB connection is not active. - */ - public function commit() - { - if ($this->_active && $this->db && $this->db->isActive) { - \Yii::trace('Committing transaction', __CLASS__); - $this->db->createCommand('EXEC')->execute(); - // TODO handle result of EXEC - $this->_active = false; - } else { - throw new Exception('Failed to commit transaction: transaction was inactive.'); - } - } - - /** - * Rolls back a transaction. - * @throws Exception if the transaction or the DB connection is not active. - */ - public function rollback() - { - if ($this->_active && $this->db && $this->db->isActive) { - \Yii::trace('Rolling back transaction', __CLASS__); - $this->db->pdo->commit(); - $this->_active = false; - } else { - throw new Exception('Failed to roll back transaction: transaction was inactive.'); - } - } -} diff --git a/framework/yii/redis/Connection.php b/framework/yii/redis/Connection.php new file mode 100644 index 0000000..848b408 --- /dev/null +++ b/framework/yii/redis/Connection.php @@ -0,0 +1,428 @@ + + * @since 2.0 + */ +class Connection extends Component +{ + /** + * @event Event an event that is triggered after a DB connection is established + */ + const EVENT_AFTER_OPEN = 'afterOpen'; + + /** + * @var string the Data Source Name, or DSN, contains the information required to connect to the database. + * DSN format: redis://server:port[/db] + * Where db is a zero based integer which refers to the DB to use. + * If no DB is given, ID 0 is used. + * + * Example: redis://localhost:6379/2 + */ + public $dsn; + /** + * @var string the password for establishing DB connection. Defaults to null meaning no AUTH command is send. + * See http://redis.io/commands/auth + */ + public $password; + /** + * @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout") + */ + public $connectionTimeout = null; + /** + * @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used. + */ + public $dataTimeout = null; + + /** + * @var array List of available redis commands http://redis.io/commands + */ + public $redisCommands = array( + 'BRPOP', // key [key ...] timeout Remove and get the last element in a list, or block until one is available + 'BRPOPLPUSH', // source destination timeout Pop a value from a list, push it to another list and return it; or block until one is available + 'CLIENT KILL', // ip:port Kill the connection of a client + 'CLIENT LIST', // Get the list of client connections + 'CLIENT GETNAME', // Get the current connection name + 'CLIENT SETNAME', // connection-name Set the current connection name + 'CONFIG GET', // parameter Get the value of a configuration parameter + 'CONFIG SET', // parameter value Set a configuration parameter to the given value + 'CONFIG RESETSTAT', // Reset the stats returned by INFO + 'DBSIZE', // Return the number of keys in the selected database + 'DEBUG OBJECT', // key Get debugging information about a key + 'DEBUG SEGFAULT', // Make the server crash + 'DECR', // key Decrement the integer value of a key by one + 'DECRBY', // key decrement Decrement the integer value of a key by the given number + 'DEL', // key [key ...] Delete a key + 'DISCARD', // Discard all commands issued after MULTI + 'DUMP', // key Return a serialized version of the value stored at the specified key. + 'ECHO', // message Echo the given string + 'EVAL', // script numkeys key [key ...] arg [arg ...] Execute a Lua script server side + 'EVALSHA', // sha1 numkeys key [key ...] arg [arg ...] Execute a Lua script server side + 'EXEC', // Execute all commands issued after MULTI + 'EXISTS', // key Determine if a key exists + 'EXPIRE', // key seconds Set a key's time to live in seconds + 'EXPIREAT', // key timestamp Set the expiration for a key as a UNIX timestamp + 'FLUSHALL', // Remove all keys from all databases + 'FLUSHDB', // Remove all keys from the current database + 'GET', // key Get the value of a key + 'GETBIT', // key offset Returns the bit value at offset in the string value stored at key + 'GETRANGE', // key start end Get a substring of the string stored at a key + 'GETSET', // key value Set the string value of a key and return its old value + 'HDEL', // key field [field ...] Delete one or more hash fields + 'HEXISTS', // key field Determine if a hash field exists + 'HGET', // key field Get the value of a hash field + 'HGETALL', // key Get all the fields and values in a hash + 'HINCRBY', // key field increment Increment the integer value of a hash field by the given number + 'HINCRBYFLOAT', // key field increment Increment the float value of a hash field by the given amount + 'HKEYS', // key Get all the fields in a hash + 'HLEN', // key Get the number of fields in a hash + 'HMGET', // key field [field ...] Get the values of all the given hash fields + 'HMSET', // key field value [field value ...] Set multiple hash fields to multiple values + 'HSET', // key field value Set the string value of a hash field + 'HSETNX', // key field value Set the value of a hash field, only if the field does not exist + 'HVALS', // key Get all the values in a hash + 'INCR', // key Increment the integer value of a key by one + 'INCRBY', // key increment Increment the integer value of a key by the given amount + 'INCRBYFLOAT', // key increment Increment the float value of a key by the given amount + 'INFO', // [section] Get information and statistics about the server + 'KEYS', // pattern Find all keys matching the given pattern + 'LASTSAVE', // Get the UNIX time stamp of the last successful save to disk + 'LINDEX', // key index Get an element from a list by its index + 'LINSERT', // key BEFORE|AFTER pivot value Insert an element before or after another element in a list + 'LLEN', // key Get the length of a list + 'LPOP', // key Remove and get the first element in a list + 'LPUSH', // key value [value ...] Prepend one or multiple values to a list + 'LPUSHX', // key value Prepend a value to a list, only if the list exists + 'LRANGE', // key start stop Get a range of elements from a list + 'LREM', // key count value Remove elements from a list + 'LSET', // key index value Set the value of an element in a list by its index + 'LTRIM', // key start stop Trim a list to the specified range + 'MGET', // key [key ...] Get the values of all the given keys + 'MIGRATE', // host port key destination-db timeout Atomically transfer a key from a Redis instance to another one. + 'MONITOR', // Listen for all requests received by the server in real time + 'MOVE', // key db Move a key to another database + 'MSET', // key value [key value ...] Set multiple keys to multiple values + 'MSETNX', // key value [key value ...] Set multiple keys to multiple values, only if none of the keys exist + 'MULTI', // Mark the start of a transaction block + 'OBJECT', // subcommand [arguments [arguments ...]] Inspect the internals of Redis objects + 'PERSIST', // key Remove the expiration from a key + 'PEXPIRE', // key milliseconds Set a key's time to live in milliseconds + 'PEXPIREAT', // key milliseconds-timestamp Set the expiration for a key as a UNIX timestamp specified in milliseconds + 'PING', // Ping the server + 'PSETEX', // key milliseconds value Set the value and expiration in milliseconds of a key + 'PSUBSCRIBE', // pattern [pattern ...] Listen for messages published to channels matching the given patterns + 'PTTL', // key Get the time to live for a key in milliseconds + 'PUBLISH', // channel message Post a message to a channel + 'PUNSUBSCRIBE', // [pattern [pattern ...]] Stop listening for messages posted to channels matching the given patterns + 'QUIT', // Close the connection + 'RANDOMKEY', // Return a random key from the keyspace + 'RENAME', // key newkey Rename a key + 'RENAMENX', // key newkey Rename a key, only if the new key does not exist + 'RESTORE', // key ttl serialized-value Create a key using the provided serialized value, previously obtained using DUMP. + 'RPOP', // key Remove and get the last element in a list + 'RPOPLPUSH', // source destination Remove the last element in a list, append it to another list and return it + 'RPUSH', // key value [value ...] Append one or multiple values to a list + 'RPUSHX', // key value Append a value to a list, only if the list exists + 'SADD', // key member [member ...] Add one or more members to a set + 'SAVE', // Synchronously save the dataset to disk + 'SCARD', // key Get the number of members in a set + 'SCRIPT EXISTS', // script [script ...] Check existence of scripts in the script cache. + 'SCRIPT FLUSH', // Remove all the scripts from the script cache. + 'SCRIPT KILL', // Kill the script currently in execution. + 'SCRIPT LOAD', // script Load the specified Lua script into the script cache. + 'SDIFF', // key [key ...] Subtract multiple sets + 'SDIFFSTORE', // destination key [key ...] Subtract multiple sets and store the resulting set in a key + 'SELECT', // index Change the selected database for the current connection + 'SET', // key value Set the string value of a key + 'SETBIT', // key offset value Sets or clears the bit at offset in the string value stored at key + 'SETEX', // key seconds value Set the value and expiration of a key + 'SETNX', // key value Set the value of a key, only if the key does not exist + 'SETRANGE', // key offset value Overwrite part of a string at key starting at the specified offset + 'SHUTDOWN', // [NOSAVE] [SAVE] Synchronously save the dataset to disk and then shut down the server + 'SINTER', // key [key ...] Intersect multiple sets + 'SINTERSTORE', // destination key [key ...] Intersect multiple sets and store the resulting set in a key + 'SISMEMBER', // key member Determine if a given value is a member of a set + 'SLAVEOF', // host port Make the server a slave of another instance, or promote it as master + 'SLOWLOG', // subcommand [argument] Manages the Redis slow queries log + 'SMEMBERS', // key Get all the members in a set + 'SMOVE', // source destination member Move a member from one set to another + 'SORT', // key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] Sort the elements in a list, set or sorted set + 'SPOP', // key Remove and return a random member from a set + 'SRANDMEMBER', // key [count] Get one or multiple random members from a set + 'SREM', // key member [member ...] Remove one or more members from a set + 'STRLEN', // key Get the length of the value stored in a key + 'SUBSCRIBE', // channel [channel ...] Listen for messages published to the given channels + 'SUNION', // key [key ...] Add multiple sets + 'SUNIONSTORE', // destination key [key ...] Add multiple sets and store the resulting set in a key + 'SYNC', // Internal command used for replication + 'TIME', // Return the current server time + 'TTL', // key Get the time to live for a key + 'TYPE', // key Determine the type stored at key + 'UNSUBSCRIBE', // [channel [channel ...]] Stop listening for messages posted to the given channels + 'UNWATCH', // Forget about all watched keys + 'WATCH', // key [key ...] Watch the given keys to determine execution of the MULTI/EXEC block + 'ZADD', // key score member [score member ...] Add one or more members to a sorted set, or update its score if it already exists + 'ZCARD', // key Get the number of members in a sorted set + 'ZCOUNT', // key min max Count the members in a sorted set with scores within the given values + 'ZINCRBY', // key increment member Increment the score of a member in a sorted set + 'ZINTERSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Intersect multiple sorted sets and store the resulting sorted set in a new key + 'ZRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index + 'ZRANGEBYSCORE', // key min max [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score + 'ZRANK', // key member Determine the index of a member in a sorted set + 'ZREM', // key member [member ...] Remove one or more members from a sorted set + 'ZREMRANGEBYRANK', // key start stop Remove all members in a sorted set within the given indexes + 'ZREMRANGEBYSCORE', // key min max Remove all members in a sorted set within the given scores + 'ZREVRANGE', // key start stop [WITHSCORES] Return a range of members in a sorted set, by index, with scores ordered from high to low + 'ZREVRANGEBYSCORE', // key max min [WITHSCORES] [LIMIT offset count] Return a range of members in a sorted set, by score, with scores ordered from high to low + 'ZREVRANK', // key member Determine the index of a member in a sorted set, with scores ordered from high to low + 'ZSCORE', // key member Get the score associated with the given member in a sorted set + 'ZUNIONSTORE', // destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] Add multiple sorted sets and store the resulting sorted set in a new key + ); + /** + * @var Transaction the currently active transaction + */ + private $_transaction; + /** + * @var resource redis socket connection + */ + private $_socket; + + /** + * Closes the connection when this component is being serialized. + * @return array + */ + public function __sleep() + { + $this->close(); + return array_keys(get_object_vars($this)); + } + + /** + * Returns a value indicating whether the DB connection is established. + * @return boolean whether the DB connection is established + */ + public function getIsActive() + { + return $this->_socket !== null; + } + + /** + * Establishes a DB connection. + * It does nothing if a DB connection has already been established. + * @throws Exception if connection fails + */ + public function open() + { + if ($this->_socket === null) { + if (empty($this->dsn)) { + throw new InvalidConfigException('Connection.dsn cannot be empty.'); + } + $dsn = explode('/', $this->dsn); + $host = $dsn[2]; + if (strpos($host, ':')===false) { + $host .= ':6379'; + } + $db = isset($dsn[3]) ? $dsn[3] : 0; + + \Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__); + $this->_socket = @stream_socket_client( + $host, + $errorNumber, + $errorDescription, + $this->connectionTimeout ? $this->connectionTimeout : ini_get("default_socket_timeout") + ); + if ($this->_socket) { + if ($this->dataTimeout !== null) { + stream_set_timeout($this->_socket, $timeout=(int)$this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000)); + } + if ($this->password !== null) { + $this->executeCommand('AUTH', array($this->password)); + } + $this->executeCommand('SELECT', array($db)); + $this->initConnection(); + } else { + \Yii::error("Failed to open DB connection ({$this->dsn}): " . $errorNumber . ' - ' . $errorDescription, __CLASS__); + $message = YII_DEBUG ? 'Failed to open DB connection: ' . $errorNumber . ' - ' . $errorDescription : 'Failed to open DB connection.'; + throw new Exception($message, $errorDescription, (int)$errorNumber); + } + } + } + + /** + * Closes the currently active DB connection. + * It does nothing if the connection is already closed. + */ + public function close() + { + if ($this->_socket !== null) { + \Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__); + $this->executeCommand('QUIT'); + stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR); + $this->_socket = null; + $this->_transaction = null; + } + } + + /** + * Initializes the DB connection. + * This method is invoked right after the DB connection is established. + * The default implementation triggers an [[EVENT_AFTER_OPEN]] event. + */ + protected function initConnection() + { + $this->trigger(self::EVENT_AFTER_OPEN); + } + + /** + * Returns the currently active transaction. + * @return Transaction the currently active transaction. Null if no active transaction. + */ + public function getTransaction() + { + return $this->_transaction && $this->_transaction->isActive ? $this->_transaction : null; + } + + /** + * Starts a transaction. + * @return Transaction the transaction initiated + */ + public function beginTransaction() + { + $this->open(); + $this->_transaction = new Transaction(array( + 'db' => $this, + )); + $this->_transaction->begin(); + return $this->_transaction; + } + + /** + * Returns the name of the DB driver for the current [[dsn]]. + * @return string name of the DB driver + */ + public function getDriverName() + { + if (($pos = strpos($this->dsn, ':')) !== false) { + return strtolower(substr($this->dsn, 0, $pos)); + } else { + return 'redis'; + } + } + + /** + * + * @param string $name + * @param array $params + * @return mixed + */ + public function __call($name, $params) + { + $redisCommand = strtoupper(Inflector::camel2words($name, false)); + if (in_array($redisCommand, $this->redisCommands)) { + return $this->executeCommand($name, $params); + } else { + return parent::__call($name, $params); + } + } + + /** + * Executes a redis command. + * For a list of available commands and their parameters see http://redis.io/commands. + * + * @param string $name the name of the command + * @param array $params list of parameters for the command + * @return array|bool|null|string Dependend on the executed command this method + * will return different data types: + * + * - `true` for commands that return "status reply". + * - `string` for commands that return "integer reply" + * as the value is in the range of a signed 64 bit integer. + * - `string` or `null` for commands that return "bulk reply". + * - `array` for commands that return "Multi-bulk replies". + * + * See [redis protocol description](http://redis.io/topics/protocol) + * for details on the mentioned reply types. + * @trows Exception for commands that return [error reply](http://redis.io/topics/protocol#error-reply). + */ + public function executeCommand($name, $params=array()) + { + $this->open(); + + array_unshift($params, $name); + $command = '*' . count($params) . "\r\n"; + foreach($params as $arg) { + $command .= '$' . mb_strlen($arg, '8bit') . "\r\n" . $arg . "\r\n"; + } + + \Yii::trace("Executing Redis Command: {$name}", __CLASS__); + fwrite($this->_socket, $command); + + return $this->parseResponse(implode(' ', $params)); + } + + private function parseResponse($command) + { + if(($line = fgets($this->_socket)) === false) { + throw new Exception("Failed to read from socket.\nRedis command was: " . $command); + } + $type = $line[0]; + $line = mb_substr($line, 1, -2, '8bit'); + switch($type) + { + case '+': // Status reply + return true; + case '-': // Error reply + throw new Exception("Redis error: " . $line . "\nRedis command was: " . $command); + case ':': // Integer reply + // no cast to int as it is in the range of a signed 64 bit integer + return $line; + case '$': // Bulk replies + if ($line == '-1') { + return null; + } + $length = $line + 2; + $data = ''; + while ($length > 0) { + if(($block = fread($this->_socket, $line + 2)) === false) { + throw new Exception("Failed to read from socket.\nRedis command was: " . $command); + } + $data .= $block; + $length -= mb_strlen($block, '8bit'); + } + return mb_substr($data, 0, -2, '8bit'); + case '*': // Multi-bulk replies + $count = (int) $line; + $data = array(); + for($i = 0; $i < $count; $i++) { + $data[] = $this->parseResponse($command); + } + return $data; + default: + throw new Exception('Received illegal data from redis: ' . $line . "\nRedis command was: " . $command); + } + } +} diff --git a/framework/yii/redis/Transaction.php b/framework/yii/redis/Transaction.php new file mode 100644 index 0000000..94cff7a --- /dev/null +++ b/framework/yii/redis/Transaction.php @@ -0,0 +1,93 @@ + + * @since 2.0 + */ +class Transaction extends \yii\base\Object +{ + /** + * @var Connection the database connection that this transaction is associated with. + */ + public $db; + /** + * @var boolean whether this transaction is active. Only an active transaction + * can [[commit()]] or [[rollBack()]]. This property is set true when the transaction is started. + */ + private $_active = false; + + /** + * Returns a value indicating whether this transaction is active. + * @return boolean whether this transaction is active. Only an active transaction + * can [[commit()]] or [[rollBack()]]. + */ + public function getIsActive() + { + return $this->_active; + } + + /** + * Begins a transaction. + * @throws InvalidConfigException if [[connection]] is null + */ + public function begin() + { + if (!$this->_active) { + if ($this->db === null) { + throw new InvalidConfigException('Transaction::db must be set.'); + } + \Yii::trace('Starting transaction', __CLASS__); + $this->db->open(); + $this->db->createCommand('MULTI')->execute(); + $this->_active = true; + } + } + + /** + * Commits a transaction. + * @throws Exception if the transaction or the DB connection is not active. + */ + public function commit() + { + if ($this->_active && $this->db && $this->db->isActive) { + \Yii::trace('Committing transaction', __CLASS__); + $this->db->createCommand('EXEC')->execute(); + // TODO handle result of EXEC + $this->_active = false; + } else { + throw new Exception('Failed to commit transaction: transaction was inactive.'); + } + } + + /** + * Rolls back a transaction. + * @throws Exception if the transaction or the DB connection is not active. + */ + public function rollback() + { + if ($this->_active && $this->db && $this->db->isActive) { + \Yii::trace('Rolling back transaction', __CLASS__); + $this->db->pdo->commit(); + $this->_active = false; + } else { + throw new Exception('Failed to roll back transaction: transaction was inactive.'); + } + } +} From cc09ef56b9f9a4b6409e98a15dfb2784df07f1df Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 16:01:29 +0200 Subject: [PATCH 099/157] updated @property annotations of web\Request --- framework/yii/web/Request.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index eb1652c..9e625f7 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -35,6 +35,9 @@ use yii\helpers\Security; * @property boolean $isDelete Whether this is a DELETE request. This property is read-only. * @property boolean $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is * read-only. + * @property boolean $isGet Whether this is a GET request. This property is read-only. + * @property boolean $isHead Whether this is a HEAD request. This property is read-only. + * @property boolean $isOptions Whether this is a OPTIONS request. This property is read-only. * @property boolean $isPatch Whether this is a PATCH request. This property is read-only. * @property boolean $isPost Whether this is a POST request. This property is read-only. * @property boolean $isPut Whether this is a PUT request. This property is read-only. From ef13a11f66094e53301910094ea8292cd9879226 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 16:29:16 +0200 Subject: [PATCH 100/157] moved getPDOType() back to Command to avoid dependency on Schema fixes #854 --- framework/yii/db/Command.php | 25 +++++++++++++++++++--- framework/yii/db/Schema.php | 19 ---------------- framework/yii/db/cubrid/Schema.php | 19 ---------------- tests/unit/data/cubrid.sql | 4 +--- tests/unit/framework/db/CommandTest.php | 23 ++++++++++++++++++++ tests/unit/framework/db/SchemaTest.php | 23 -------------------- .../framework/db/cubrid/CubridActiveRecordTest.php | 23 ++++++++++++++++++++ .../unit/framework/db/cubrid/CubridCommandTest.php | 8 +------ .../unit/framework/db/cubrid/CubridSchemaTest.php | 22 ------------------- 9 files changed, 70 insertions(+), 96 deletions(-) diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php index 7f2d81d..bfb8a26 100644 --- a/framework/yii/db/Command.php +++ b/framework/yii/db/Command.php @@ -181,7 +181,7 @@ class Command extends \yii\base\Component { $this->prepare(); if ($dataType === null) { - $this->pdoStatement->bindParam($name, $value, $this->db->schema->getPdoType($value)); + $this->pdoStatement->bindParam($name, $value, $this->getPdoType($value)); } elseif ($length === null) { $this->pdoStatement->bindParam($name, $value, $dataType); } elseif ($driverOptions === null) { @@ -208,7 +208,7 @@ class Command extends \yii\base\Component { $this->prepare(); if ($dataType === null) { - $this->pdoStatement->bindValue($name, $value, $this->db->schema->getPdoType($value)); + $this->pdoStatement->bindValue($name, $value, $this->getPdoType($value)); } else { $this->pdoStatement->bindValue($name, $value, $dataType); } @@ -236,7 +236,7 @@ class Command extends \yii\base\Component $type = $value[1]; $value = $value[0]; } else { - $type = $this->db->schema->getPdoType($value); + $type = $this->getPdoType($value); } $this->pdoStatement->bindValue($name, $value, $type); $this->_params[$name] = $value; @@ -246,6 +246,25 @@ class Command extends \yii\base\Component } /** + * Determines the PDO type for the given PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + private function getPdoType($data) + { + static $typeMap = array( // php type => PDO type + 'boolean' => \PDO::PARAM_BOOL, + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ); + $type = gettype($data); + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } + + /** * Executes the SQL statement. * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. * No result set will be returned. diff --git a/framework/yii/db/Schema.php b/framework/yii/db/Schema.php index 396e944..1d86616 100644 --- a/framework/yii/db/Schema.php +++ b/framework/yii/db/Schema.php @@ -377,23 +377,4 @@ abstract class Schema extends Object return 'string'; } } - - /** - * Determines the PDO type for the give PHP data value. - * @param mixed $data the data whose PDO type is to be determined - * @return integer the PDO type - * @see http://www.php.net/manual/en/pdo.constants.php - */ - public function getPdoType($data) - { - static $typeMap = array( // php type => PDO type - 'boolean' => \PDO::PARAM_BOOL, - 'integer' => \PDO::PARAM_INT, - 'string' => \PDO::PARAM_STR, - 'resource' => \PDO::PARAM_LOB, - 'NULL' => \PDO::PARAM_NULL, - ); - $type = gettype($data); - return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; - } } diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index 99624f6..ba7fcae 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/framework/yii/db/cubrid/Schema.php @@ -237,23 +237,4 @@ class Schema extends \yii\db\Schema } return $tableNames; } - - /** - * Determines the PDO type for the give PHP data value. - * @param mixed $data the data whose PDO type is to be determined - * @return integer the PDO type - * @see http://www.php.net/manual/en/pdo.constants.php - */ - public function getPdoType($data) - { - static $typeMap = array( - 'boolean' => \PDO::PARAM_INT, // CUBRID PDO does not support PARAM_BOOL - 'integer' => \PDO::PARAM_INT, - 'string' => \PDO::PARAM_STR, - 'resource' => \PDO::PARAM_LOB, - 'NULL' => \PDO::PARAM_NULL, - ); - $type = gettype($data); - return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; - } } diff --git a/tests/unit/data/cubrid.sql b/tests/unit/data/cubrid.sql index bfaf85c..3dcfa37 100644 --- a/tests/unit/data/cubrid.sql +++ b/tests/unit/data/cubrid.sql @@ -72,9 +72,7 @@ CREATE TABLE `tbl_type` ( `float_col2` double DEFAULT '1.23', `blob_col` blob, `numeric_col` decimal(5,2) DEFAULT '33.22', - `time` timestamp NOT NULL DEFAULT '2002-01-01 00:00:00', - `bool_col` smallint NOT NULL, - `bool_col2` smallint DEFAULT 1 + `time` timestamp NOT NULL DEFAULT '2002-01-01 00:00:00' ); CREATE TABLE `tbl_composite_fk` ( diff --git a/tests/unit/framework/db/CommandTest.php b/tests/unit/framework/db/CommandTest.php index 7b16c76..d9eb0e7 100644 --- a/tests/unit/framework/db/CommandTest.php +++ b/tests/unit/framework/db/CommandTest.php @@ -219,6 +219,29 @@ class CommandTest extends DatabaseTestCase $this->assertTrue(is_array($result) && isset($result[0])); } + // getPDOType is currently private +// public function testGetPDOType() +// { +// $values = array( +// array(null, \PDO::PARAM_NULL), +// array('', \PDO::PARAM_STR), +// array('hello', \PDO::PARAM_STR), +// array(0, \PDO::PARAM_INT), +// array(1, \PDO::PARAM_INT), +// array(1337, \PDO::PARAM_INT), +// array(true, \PDO::PARAM_BOOL), +// array(false, \PDO::PARAM_BOOL), +// array($fp=fopen(__FILE__, 'rb'), \PDO::PARAM_LOB), +// ); +// +// $command = $this->getConnection()->createCommand(); +// +// foreach($values as $value) { +// $this->assertEquals($value[1], $command->getPdoType($value[0])); +// } +// fclose($fp); +// } + public function testInsert() { } diff --git a/tests/unit/framework/db/SchemaTest.php b/tests/unit/framework/db/SchemaTest.php index dce6e20..2a3015d 100644 --- a/tests/unit/framework/db/SchemaTest.php +++ b/tests/unit/framework/db/SchemaTest.php @@ -11,29 +11,6 @@ use yii\db\Schema; */ class SchemaTest extends DatabaseTestCase { - - public function testGetPDOType() - { - $values = array( - array(null, \PDO::PARAM_NULL), - array('', \PDO::PARAM_STR), - array('hello', \PDO::PARAM_STR), - array(0, \PDO::PARAM_INT), - array(1, \PDO::PARAM_INT), - array(1337, \PDO::PARAM_INT), - array(true, \PDO::PARAM_BOOL), - array(false, \PDO::PARAM_BOOL), - array($fp=fopen(__FILE__, 'rb'), \PDO::PARAM_LOB), - ); - - $schema = $this->getConnection()->schema; - - foreach($values as $value) { - $this->assertEquals($value[1], $schema->getPdoType($value[0])); - } - fclose($fp); - } - public function testFindTableNames() { /** @var Schema $schema */ diff --git a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php index dd48f44..2d2db15 100644 --- a/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php +++ b/tests/unit/framework/db/cubrid/CubridActiveRecordTest.php @@ -1,6 +1,7 @@ name = 'boolean customer'; + $customer->email = 'mail@example.com'; + $customer->status = true; + $customer->save(false); + + $customer->refresh(); + $this->assertEquals(1, $customer->status); + + $customer->status = false; + $customer->save(false); + + $customer->refresh(); + $this->assertEquals(0, $customer->status); + } } diff --git a/tests/unit/framework/db/cubrid/CubridCommandTest.php b/tests/unit/framework/db/cubrid/CubridCommandTest.php index 895f548..45d3c1c 100644 --- a/tests/unit/framework/db/cubrid/CubridCommandTest.php +++ b/tests/unit/framework/db/cubrid/CubridCommandTest.php @@ -31,7 +31,7 @@ class CubridCommandTest extends CommandTest $command->bindParam(':email', $email); $this->assertEquals($name, $command->queryScalar()); - $sql = "INSERT INTO tbl_type (int_col, char_col, char_col2, enum_col, float_col, blob_col, numeric_col, bool_col, bool_col2) VALUES (:int_col, '', :char_col, :enum_col, :float_col, CHAR_TO_BLOB(:blob_col), :numeric_col, :bool_col, :bool_col2)"; + $sql = "INSERT INTO tbl_type (int_col, char_col, char_col2, enum_col, float_col, blob_col, numeric_col) VALUES (:int_col, '', :char_col, :enum_col, :float_col, CHAR_TO_BLOB(:blob_col), :numeric_col)"; $command = $db->createCommand($sql); $intCol = 123; $charCol = 'abc'; @@ -39,16 +39,12 @@ class CubridCommandTest extends CommandTest $floatCol = 1.23; $blobCol = "\x10\x11\x12"; $numericCol = '1.23'; - $boolCol = false; - $boolCol2 = true; $command->bindParam(':int_col', $intCol); $command->bindParam(':char_col', $charCol); $command->bindParam(':enum_col', $enumCol); $command->bindParam(':float_col', $floatCol); $command->bindParam(':blob_col', $blobCol); $command->bindParam(':numeric_col', $numericCol); - $command->bindParam(':bool_col', $boolCol); - $command->bindParam(':bool_col2', $boolCol2); $this->assertEquals(1, $command->execute()); $sql = 'SELECT * FROM tbl_type'; @@ -59,8 +55,6 @@ class CubridCommandTest extends CommandTest $this->assertEquals($floatCol, $row['float_col']); $this->assertEquals($blobCol, fread($row['blob_col'], 3)); $this->assertEquals($numericCol, $row['numeric_col']); - $this->assertEquals($boolCol, $row['bool_col']); - $this->assertEquals($boolCol2, $row['bool_col2']); // bindValue $sql = 'INSERT INTO tbl_customer(email, name, address) VALUES (:email, \'user5\', \'address5\')'; diff --git a/tests/unit/framework/db/cubrid/CubridSchemaTest.php b/tests/unit/framework/db/cubrid/CubridSchemaTest.php index 6a1f6a2..9a0c139 100644 --- a/tests/unit/framework/db/cubrid/CubridSchemaTest.php +++ b/tests/unit/framework/db/cubrid/CubridSchemaTest.php @@ -10,26 +10,4 @@ use yiiunit\framework\db\SchemaTest; class CubridSchemaTest extends SchemaTest { public $driverName = 'cubrid'; - - public function testGetPDOType() - { - $values = array( - null => \PDO::PARAM_NULL, - '' => \PDO::PARAM_STR, - 'hello' => \PDO::PARAM_STR, - 0 => \PDO::PARAM_INT, - 1 => \PDO::PARAM_INT, - 1337 => \PDO::PARAM_INT, - true => \PDO::PARAM_INT, // CUBRID PDO does not support PARAM_BOOL - false => \PDO::PARAM_INT, // CUBRID PDO does not support PARAM_BOOL - ); - - $schema = $this->getConnection()->schema; - - foreach($values as $value => $type) { - $this->assertEquals($type, $schema->getPdoType($value)); - } - $this->assertEquals(\PDO::PARAM_LOB, $schema->getPdoType($fp=fopen(__FILE__, 'rb'))); - fclose($fp); - } } From 3a347c3587d510e1e434ff169211bb72812ff239 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 16:30:00 +0200 Subject: [PATCH 101/157] added property docs to AR test classes --- tests/unit/data/ar/Customer.php | 9 +++++++++ tests/unit/data/ar/Item.php | 7 +++++++ tests/unit/data/ar/Order.php | 8 ++++++++ tests/unit/data/ar/OrderItem.php | 8 ++++++++ 4 files changed, 32 insertions(+) diff --git a/tests/unit/data/ar/Customer.php b/tests/unit/data/ar/Customer.php index 561d1ae..bbc0182 100644 --- a/tests/unit/data/ar/Customer.php +++ b/tests/unit/data/ar/Customer.php @@ -3,6 +3,15 @@ namespace yiiunit\data\ar; use yii\db\ActiveQuery; +/** + * Class Customer + * + * @property integer $id + * @property string $name + * @property string $email + * @property string $address + * @property integer $status + */ class Customer extends ActiveRecord { const STATUS_ACTIVE = 1; diff --git a/tests/unit/data/ar/Item.php b/tests/unit/data/ar/Item.php index 5d23378..e725be9 100644 --- a/tests/unit/data/ar/Item.php +++ b/tests/unit/data/ar/Item.php @@ -2,6 +2,13 @@ namespace yiiunit\data\ar; +/** + * Class Item + * + * @property integer $id + * @property string $name + * @property integer $category_id + */ class Item extends ActiveRecord { public static function tableName() diff --git a/tests/unit/data/ar/Order.php b/tests/unit/data/ar/Order.php index 119f332..063bb67 100644 --- a/tests/unit/data/ar/Order.php +++ b/tests/unit/data/ar/Order.php @@ -2,6 +2,14 @@ namespace yiiunit\data\ar; +/** + * Class Order + * + * @property integer $id + * @property integer $customer_id + * @property integer $create_time + * @property string $total + */ class Order extends ActiveRecord { public static function tableName() diff --git a/tests/unit/data/ar/OrderItem.php b/tests/unit/data/ar/OrderItem.php index 607133e..297432b 100644 --- a/tests/unit/data/ar/OrderItem.php +++ b/tests/unit/data/ar/OrderItem.php @@ -2,6 +2,14 @@ namespace yiiunit\data\ar; +/** + * Class OrderItem + * + * @property integer $order_id + * @property integer $item_id + * @property integer $quantity + * @property string $subtotal + */ class OrderItem extends ActiveRecord { public static function tableName() From 29394758bad59befe305f4f88009d25389935707 Mon Sep 17 00:00:00 2001 From: gsd Date: Wed, 18 Sep 2013 22:12:47 +0600 Subject: [PATCH 102/157] Update passwordResetToken.php --- apps/advanced/frontend/views/emails/passwordResetToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/advanced/frontend/views/emails/passwordResetToken.php b/apps/advanced/frontend/views/emails/passwordResetToken.php index 11aa8e9..1e7a855 100644 --- a/apps/advanced/frontend/views/emails/passwordResetToken.php +++ b/apps/advanced/frontend/views/emails/passwordResetToken.php @@ -13,4 +13,4 @@ Hello username)?>, Follow the link below to reset your password: - \ No newline at end of file + From 41e2c41002fc11defdf327b933a36f705de5cfe2 Mon Sep 17 00:00:00 2001 From: gsd Date: Wed, 18 Sep 2013 22:18:12 +0600 Subject: [PATCH 103/157] Update SiteController.php --- apps/advanced/frontend/controllers/SiteController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php index be9a634..a9413de 100644 --- a/apps/advanced/frontend/controllers/SiteController.php +++ b/apps/advanced/frontend/controllers/SiteController.php @@ -164,7 +164,7 @@ class SiteController extends Controller $headers = "From: $name <{$fromEmail}>\r\n" . "MIME-Version: 1.0\r\n" . "Content-type: text/plain; charset=UTF-8"; - return mail($fromEmail, $subject, $body, $headers); + return mail($email, $subject, $body, $headers); } return false; From c2a3aa3d99097752f7f79525aec1e722feb4aa06 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 18 Sep 2013 16:18:47 -0400 Subject: [PATCH 104/157] Fixes #885: removed NULLs from filtering by models. --- framework/yii/db/ActiveRelation.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/yii/db/ActiveRelation.php b/framework/yii/db/ActiveRelation.php index 0be4feb..f05c56a 100644 --- a/framework/yii/db/ActiveRelation.php +++ b/framework/yii/db/ActiveRelation.php @@ -279,7 +279,9 @@ class ActiveRelation extends ActiveQuery // single key $attribute = reset($this->link); foreach ($models as $model) { - $values[] = $model[$attribute]; + if (($value = $model[$attribute]) !== null) { + $values[] = $value; + } } } else { // composite keys From b8ffee655930a23cd616a2a4e781850efca534fa Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 22:38:53 +0200 Subject: [PATCH 105/157] moved CacheSession::init() parent call after init of cache component session autostart would fail otherwise. fixes #887 --- framework/yii/web/CacheSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/web/CacheSession.php b/framework/yii/web/CacheSession.php index bb387e1..b4ce2ae 100644 --- a/framework/yii/web/CacheSession.php +++ b/framework/yii/web/CacheSession.php @@ -42,13 +42,13 @@ class CacheSession extends Session */ public function init() { - parent::init(); if (is_string($this->cache)) { $this->cache = Yii::$app->getComponent($this->cache); } if (!$this->cache instanceof Cache) { throw new InvalidConfigException('CacheSession::cache must refer to the application component ID of a cache object.'); } + parent::init(); } /** From bd2404f1c6f53356e959a14455e7f4f9f3e96dc2 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 22:46:43 +0200 Subject: [PATCH 106/157] added simple unit test for CacheSession --- tests/unit/framework/web/CacheSessionTest.php | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/unit/framework/web/CacheSessionTest.php diff --git a/tests/unit/framework/web/CacheSessionTest.php b/tests/unit/framework/web/CacheSessionTest.php new file mode 100644 index 0000000..c80fa1c --- /dev/null +++ b/tests/unit/framework/web/CacheSessionTest.php @@ -0,0 +1,28 @@ +mockApplication(); + Yii::$app->setComponent('cache', new FileCache()); + } + + public function testCreate() + { + $session = new CacheSession(); + + $session->writeSession('test', 'sessionData'); + $this->assertEquals('sessionData', $session->readSession('test')); + } +} From 128ee07b78f83848f5ebeded650c6e89cb14675b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 22:54:11 +0200 Subject: [PATCH 107/157] 100% test coverage for CacheSession --- tests/unit/framework/web/CacheSessionTest.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/unit/framework/web/CacheSessionTest.php b/tests/unit/framework/web/CacheSessionTest.php index c80fa1c..e740596 100644 --- a/tests/unit/framework/web/CacheSessionTest.php +++ b/tests/unit/framework/web/CacheSessionTest.php @@ -18,11 +18,22 @@ class CacheSessionTest extends \yiiunit\TestCase Yii::$app->setComponent('cache', new FileCache()); } - public function testCreate() + public function testCacheSession() { $session = new CacheSession(); $session->writeSession('test', 'sessionData'); $this->assertEquals('sessionData', $session->readSession('test')); + $session->destroySession('test'); + $this->assertEquals('', $session->readSession('test')); + } + + public function testInvalidCache() + { + $this->setExpectedException('yii\base\InvalidConfigException'); + + $session = new CacheSession(array( + 'cache' => 'invalid', + )); } } From df6176edb6c0817ee6b1e5ffc2f63050d6c32feb Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 23:43:46 +0200 Subject: [PATCH 108/157] added unit test for Model::load() --- tests/unit/data/base/Speaker.php | 7 +++++++ tests/unit/framework/base/ModelTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/tests/unit/data/base/Speaker.php b/tests/unit/data/base/Speaker.php index 5668dad..b0acc6b 100644 --- a/tests/unit/data/base/Speaker.php +++ b/tests/unit/data/base/Speaker.php @@ -17,6 +17,13 @@ class Speaker extends Model protected $protectedProperty; private $_privateProperty; + public static $formName = 'Speaker'; + + public function formName() + { + return static::$formName; + } + public function attributeLabels() { return array( diff --git a/tests/unit/framework/base/ModelTest.php b/tests/unit/framework/base/ModelTest.php index b338c17..d500933 100644 --- a/tests/unit/framework/base/ModelTest.php +++ b/tests/unit/framework/base/ModelTest.php @@ -75,6 +75,32 @@ class ModelTest extends TestCase $this->assertEquals('Qiang', $speaker->firstName); } + public function testLoad() + { + $singer = new Singer(); + $this->assertEquals('Singer', $singer->formName()); + + $post = array('firstName' => 'Qiang'); + + Speaker::$formName = ''; + $model = new Speaker(); + $model->setScenario('test'); + $this->assertTrue($model->load($post)); + $this->assertEquals('Qiang', $model->firstName); + + Speaker::$formName = 'Speaker'; + $model = new Speaker(); + $model->setScenario('test'); + $this->assertTrue($model->load(array('Speaker' => $post))); + $this->assertEquals('Qiang', $model->firstName); + + Speaker::$formName = 'Speaker'; + $model = new Speaker(); + $model->setScenario('test'); + $this->assertFalse($model->load(array('Example' => array()))); + $this->assertEquals('', $model->firstName); + } + public function testActiveAttributes() { // by default mass assignment doesn't work at all From 1f8ab8106db89b177fe5b15975a7afcc61d84b3a Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 19 Sep 2013 00:21:12 +0200 Subject: [PATCH 109/157] updated classmap --- framework/yii/classes.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/framework/yii/classes.php b/framework/yii/classes.php index aee93c0..3880620 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -49,6 +49,7 @@ return array( 'yii\bootstrap\Alert' => YII_PATH . '/bootstrap/Alert.php', 'yii\bootstrap\BootstrapAsset' => YII_PATH . '/bootstrap/BootstrapAsset.php', 'yii\bootstrap\BootstrapPluginAsset' => YII_PATH . '/bootstrap/BootstrapPluginAsset.php', + 'yii\bootstrap\BootstrapThemeAsset' => YII_PATH . '/bootstrap/BootstrapThemeAsset.php', 'yii\bootstrap\Button' => YII_PATH . '/bootstrap/Button.php', 'yii\bootstrap\ButtonDropdown' => YII_PATH . '/bootstrap/ButtonDropdown.php', 'yii\bootstrap\ButtonGroup' => YII_PATH . '/bootstrap/ButtonGroup.php', @@ -60,7 +61,6 @@ return array( 'yii\bootstrap\NavBar' => YII_PATH . '/bootstrap/NavBar.php', 'yii\bootstrap\Progress' => YII_PATH . '/bootstrap/Progress.php', 'yii\bootstrap\Tabs' => YII_PATH . '/bootstrap/Tabs.php', - 'yii\bootstrap\Typeahead' => YII_PATH . '/bootstrap/Typeahead.php', 'yii\bootstrap\Widget' => YII_PATH . '/bootstrap/Widget.php', 'yii\caching\ApcCache' => YII_PATH . '/caching/ApcCache.php', 'yii\caching\Cache' => YII_PATH . '/caching/Cache.php', @@ -75,6 +75,7 @@ return array( 'yii\caching\GroupDependency' => YII_PATH . '/caching/GroupDependency.php', 'yii\caching\MemCache' => YII_PATH . '/caching/MemCache.php', 'yii\caching\MemCacheServer' => YII_PATH . '/caching/MemCacheServer.php', + 'yii\caching\RedisCache' => YII_PATH . '/caching/RedisCache.php', 'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php', 'yii\caching\XCache' => YII_PATH . '/caching/XCache.php', 'yii\caching\ZendDataCache' => YII_PATH . '/caching/ZendDataCache.php', @@ -104,10 +105,13 @@ return array( 'yii\db\StaleObjectException' => YII_PATH . '/db/StaleObjectException.php', 'yii\db\TableSchema' => YII_PATH . '/db/TableSchema.php', 'yii\db\Transaction' => YII_PATH . '/db/Transaction.php', + 'yii\db\cubrid\QueryBuilder' => YII_PATH . '/db/cubrid/QueryBuilder.php', + 'yii\db\cubrid\Schema' => YII_PATH . '/db/cubrid/Schema.php', 'yii\db\mssql\PDO' => YII_PATH . '/db/mssql/PDO.php', 'yii\db\mssql\QueryBuilder' => YII_PATH . '/db/mssql/QueryBuilder.php', 'yii\db\mssql\Schema' => YII_PATH . '/db/mssql/Schema.php', 'yii\db\mssql\SqlsrvPDO' => YII_PATH . '/db/mssql/SqlsrvPDO.php', + 'yii\db\mssql\TableSchema' => YII_PATH . '/db/mssql/TableSchema.php', 'yii\db\mysql\QueryBuilder' => YII_PATH . '/db/mysql/QueryBuilder.php', 'yii\db\mysql\Schema' => YII_PATH . '/db/mysql/Schema.php', 'yii\db\pgsql\QueryBuilder' => YII_PATH . '/db/pgsql/QueryBuilder.php', @@ -162,6 +166,8 @@ return array( 'yii\rbac\Item' => YII_PATH . '/rbac/Item.php', 'yii\rbac\Manager' => YII_PATH . '/rbac/Manager.php', 'yii\rbac\PhpManager' => YII_PATH . '/rbac/PhpManager.php', + 'yii\redis\Connection' => YII_PATH . '/redis/Connection.php', + 'yii\redis\Transaction' => YII_PATH . '/redis/Transaction.php', 'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php', 'yii\validators\BooleanValidator' => YII_PATH . '/validators/BooleanValidator.php', 'yii\validators\CompareValidator' => YII_PATH . '/validators/CompareValidator.php', @@ -177,6 +183,7 @@ return array( 'yii\validators\RangeValidator' => YII_PATH . '/validators/RangeValidator.php', 'yii\validators\RegularExpressionValidator' => YII_PATH . '/validators/RegularExpressionValidator.php', 'yii\validators\RequiredValidator' => YII_PATH . '/validators/RequiredValidator.php', + 'yii\validators\SafeValidator' => YII_PATH . '/validators/SafeValidator.php', 'yii\validators\StringValidator' => YII_PATH . '/validators/StringValidator.php', 'yii\validators\UniqueValidator' => YII_PATH . '/validators/UniqueValidator.php', 'yii\validators\UrlValidator' => YII_PATH . '/validators/UrlValidator.php', From 0fc423c74aa5ce4a4115bee63f14c058c90eeab5 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 18 Sep 2013 23:11:55 -0400 Subject: [PATCH 110/157] Added support for data-method and data-confirm. --- apps/basic/controllers/SiteController.php | 10 ++- apps/basic/views/layouts/main.php | 4 +- framework/yii/assets/yii.js | 100 +++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index 785eddf..1196280 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -3,7 +3,9 @@ namespace app\controllers; use Yii; +use yii\web\AccessControl; use yii\web\Controller; +use yii\web\VerbFilter; use app\models\LoginForm; use app\models\ContactForm; @@ -13,7 +15,7 @@ class SiteController extends Controller { return array( 'access' => array( - 'class' => \yii\web\AccessControl::className(), + 'class' => AccessControl::className(), 'only' => array('login', 'logout'), 'rules' => array( array( @@ -28,6 +30,12 @@ class SiteController extends Controller ), ), ), + 'verbs' => array( + 'class' => VerbFilter::className(), + 'actions' => array( + 'logout' => array('post'), + ), + ), ); } diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php index 240c2a3..1b7083d 100644 --- a/apps/basic/views/layouts/main.php +++ b/apps/basic/views/layouts/main.php @@ -36,7 +36,9 @@ app\config\AppAsset::register($this); array('label' => 'Contact', 'url' => array('/site/contact')), Yii::$app->user->isGuest ? array('label' => 'Login', 'url' => array('/site/login')) : - array('label' => 'Logout (' . Html::encode(Yii::$app->user->identity->username) .')' , 'url' => array('/site/logout')), + array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , + 'url' => array('/site/logout'), + 'linkOptions' => array('data-method' => 'post')), ), )); NavBar::end(); diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index eb5ecf6..b1fb0cb 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -37,26 +37,103 @@ * * Using this structure, you can define public and private functions/properties for a module. * Private functions/properties are only visible within the module, while public functions/properties - * may be accessed outside of the module. For example, you can access "yii.sample.init()". + * may be accessed outside of the module. For example, you can access "yii.sample.isActive". * * You must call "yii.initModule()" once for the root module of all your modules. */ yii = (function ($) { var pub = { /** + * The selector for links that support confirmation and form submission. + */ + linkClickSelector: 'a[data-confirm], a[data-method]', + + /** * @return string|undefined the CSRF variable name. Undefined is returned is CSRF validation is not enabled. */ - getCsrfVar: function() { + getCsrfVar: function () { return $('meta[name=csrf-var]').prop('content'); }, /** * @return string|undefined the CSRF token. Undefined is returned is CSRF validation is not enabled. */ - getCsrfToken: function() { + getCsrfToken: function () { return $('meta[name=csrf-token]').prop('content'); }, + /** + * Displays a confirmation dialog. + * The default implementation simply displays a js confirmation dialog. + * You may override this by setting `yii.confirm`. + * @param message the confirmation message. + * @return boolean whether the user confirms with the message in the dialog + */ + confirm: function (message) { + return confirm(message); + }, + + /** + * Returns a value indicating whether to allow executing the action defined for the specified element. + * @param $e the jQuery representation of the element + * @return boolean whether to allow executing the action defined for the specified element. + */ + allowAction: function ($e) { + var message = $e.data('confirm'); + if (!message) { + return true; + } + return pub.confirm(message); + }, + + /** + * Handles form submission triggered by elements with "method" data attribute. + * If the element is enclosed within an existing form, the form will be submitted. + * Otherwise, a new form will be created and submitted. The new form's method and action + * are determined using the element's "method" and "action" data attributes, respectively. + * If the "action" data attribute is not specified, it will try the "href" property and + * the current URL. + * @param $e the jQuery representation of the element + */ + handleSubmit: function ($e) { + var method = $e.data('method'); + if (method === undefined) { + return; + } + + var $form = $e.closest('form'); + if (!$form.length) { + var action = $e.data('action'); + if (action === undefined) { + action = $e.prop('href'); + if (action === undefined) { + action = window.location.href; + } + } + $form = $('
'); + var target = $e.prop('target'); + if (target) { + $form.attr('target', target); + } + if (!method.match(/(get|post)/i)) { + $form.append(''); + } + var csrfVar = pub.getCsrfVar(); + if (csrfVar) { + $form.append(''); + } + $form.hide().appendTo('body'); + } + + var activeFormData = $form.data('yiiActiveForm'); + if (activeFormData) { + // remember who triggers the form submission. This is used by yii.activeForm.js + activeFormData.submitObject = $e; + } + + $form.trigger('submit'); + }, + initModule: function (module) { if (module.isActive === undefined || module.isActive) { if ($.isFunction(module.init)) { @@ -68,6 +145,23 @@ yii = (function ($) { } }); } + }, + + init: function () { + var $document = $(document); + + $document.on('click.yii', pub.linkClickSelector, function () { + var $this = $(this); + if (!pub.allowAction($this)) { + $this.stopImmediatePropagation(); + return false; + } else { + if ($this.data('method')) { + pub.handleSubmit($this); + return false; + } + } + }); } }; return pub; From f9b957554f2b7f2b08747d4ecd3da0effd7bc51b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 18 Sep 2013 23:24:11 -0400 Subject: [PATCH 111/157] Added Controller::enableCsrfValidation to support turning on/off CSRF validation for particular actions. --- framework/yii/base/Controller.php | 2 ++ framework/yii/web/Controller.php | 18 ++++++++++++++++++ framework/yii/web/Request.php | 4 ++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 20f6e2b..3eebaa0 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -210,6 +210,7 @@ class Controller extends Component /** * This method is invoked right before an action is to be executed (after all possible filters.) * You may override this method to do last-minute preparation for the action. + * If you override this method, please make sure you call the parent implementation first. * @param Action $action the action to be executed. * @return boolean whether the action should continue to be executed. */ @@ -223,6 +224,7 @@ class Controller extends Component /** * This method is invoked right after an action is executed. * You may override this method to do some postprocessing for the action. + * If you override this method, please make sure you call the parent implementation first. * @param Action $action the action just executed. * @param mixed $result the action return result. */ diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index adb1b4d..9238063 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -20,6 +20,12 @@ use yii\helpers\Html; class Controller extends \yii\base\Controller { /** + * @var boolean whether to enable CSRF validation for the actions in this controller. + * CSRF validation is enabled only when both this property and [[Request::enableCsrfValidation]] are true. + */ + public $enableCsrfValidation = true; + + /** * Binds the parameters to the action. * This method is invoked by [[Action]] when it begins to run with the given parameters. * This method will check the parameter names that the action requires and return @@ -62,6 +68,18 @@ class Controller extends \yii\base\Controller } /** + * @inheritdoc + */ + public function beforeAction($action) + { + if (parent::beforeAction($action)) { + return !$this->enableCsrfValidation || Yii::$app->getRequest()->validateCsrfToken(); + } else { + return false; + } + } + + /** * Creates a URL using the given route and parameters. * * This method enhances [[UrlManager::createUrl()]] by supporting relative routes. diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 9e625f7..1186e05 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -87,6 +87,7 @@ class Request extends \yii\base\Request * In JavaScript, you may get the values of [[csrfVar]] and [[csrfToken]] via `yii.getCsrfVar()` and * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered. * + * @see Controller::enableCsrfValidation * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery */ public $enableCsrfValidation = false; @@ -122,8 +123,6 @@ class Request extends \yii\base\Request */ public function resolve() { - $this->validateCsrfToken(); - $result = Yii::$app->getUrlManager()->parseRequest($this); if ($result !== false) { list ($route, $params) = $result; @@ -1023,6 +1022,7 @@ class Request extends \yii\base\Request * Performs the CSRF validation. * The method will compare the CSRF token obtained from a cookie and from a POST field. * If they are different, a CSRF attack is detected and a 400 HTTP exception will be raised. + * This method is called in [[Controller::beforeAction()]]. * @throws HttpException if the validation fails */ public function validateCsrfToken() From 4f555a57517bfe7c6e59d4a24e574965ebffe942 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 19 Sep 2013 08:17:07 -0400 Subject: [PATCH 112/157] Fixed CSRF validation bug. --- framework/yii/web/Controller.php | 5 ++++- framework/yii/web/Request.php | 11 +++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 9238063..773e2de 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -73,7 +73,10 @@ class Controller extends \yii\base\Controller public function beforeAction($action) { if (parent::beforeAction($action)) { - return !$this->enableCsrfValidation || Yii::$app->getRequest()->validateCsrfToken(); + if ($this->enableCsrfValidation && !Yii::$app->getRequest()->validateCsrfToken()) { + throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.')); + } + return true; } else { return false; } diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 1186e05..6b805ea 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -1023,12 +1023,12 @@ class Request extends \yii\base\Request * The method will compare the CSRF token obtained from a cookie and from a POST field. * If they are different, a CSRF attack is detected and a 400 HTTP exception will be raised. * This method is called in [[Controller::beforeAction()]]. - * @throws HttpException if the validation fails + * @return boolean whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true. */ public function validateCsrfToken() { if (!$this->enableCsrfValidation) { - return; + return true; } $method = $this->getMethod(); if ($method === 'POST' || $method === 'PUT' || $method === 'PATCH' || $method === 'DELETE') { @@ -1047,10 +1047,9 @@ class Request extends \yii\base\Request $token = $this->getDelete($this->csrfVar); } - $valid = !empty($token) && $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken; - if (!$valid) { - throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.')); - } + return !empty($token) && $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken; + } else { + return true; } } } From 1aeb86df78148bd3793ebac2047150ec4e2c57ce Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 19 Sep 2013 08:24:06 -0400 Subject: [PATCH 113/157] refactored Request::validateCsrfToken(). --- framework/yii/web/Request.php | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 6b805ea..a07deaa 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -1027,29 +1027,25 @@ class Request extends \yii\base\Request */ public function validateCsrfToken() { - if (!$this->enableCsrfValidation) { - return true; - } $method = $this->getMethod(); - if ($method === 'POST' || $method === 'PUT' || $method === 'PATCH' || $method === 'DELETE') { - $trueToken = $this->getCookies()->getValue($this->csrfVar); - switch ($method) { - case 'POST': - $token = $this->getPost($this->csrfVar); - break; - case 'PUT': - $token = $this->getPut($this->csrfVar); - break; - case 'PATCH': - $token = $this->getPatch($this->csrfVar); - break; - case 'DELETE': - $token = $this->getDelete($this->csrfVar); - } - - return !empty($token) && $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken; - } else { + if (!$this->enableCsrfValidation || !in_array($method, array('POST', 'PUT', 'PATCH', 'DELETE'), true)) { return true; } + $trueToken = $this->getCookies()->getValue($this->csrfVar); + switch ($method) { + case 'PUT': + $token = $this->getPut($this->csrfVar); + break; + case 'PATCH': + $token = $this->getPatch($this->csrfVar); + break; + case 'DELETE': + $token = $this->getDelete($this->csrfVar); + break; + default: + $token = $this->getPost($this->csrfVar); + break; + } + return $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken; } } From 41f7a7d243a95fe879bfe2ff437e9e77e7d2f5aa Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 20 Sep 2013 14:57:41 -0400 Subject: [PATCH 114/157] Supports more elements to use data-confirm and data-method attributes. --- framework/yii/assets/yii.js | 75 ++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index b1fb0cb..99a314e 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -44,9 +44,13 @@ yii = (function ($) { var pub = { /** - * The selector for links that support confirmation and form submission. + * The selector for clickable elements that need to support confirmation and form submission. */ - linkClickSelector: 'a[data-confirm], a[data-method]', + clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], input[type="image"]', + /** + * The selector for changeable elements that need to support confirmation and form submission. + */ + changeableSelector: 'select, input, textarea', /** * @return string|undefined the CSRF variable name. Undefined is returned is CSRF validation is not enabled. @@ -75,40 +79,43 @@ yii = (function ($) { /** * Returns a value indicating whether to allow executing the action defined for the specified element. + * This method recognizes the `data-confirm` attribute of the element and uses it + * as the message in a confirmation dialog. The method will return true if this special attribute + * is not defined or if the user confirms the message. * @param $e the jQuery representation of the element * @return boolean whether to allow executing the action defined for the specified element. */ allowAction: function ($e) { var message = $e.data('confirm'); - if (!message) { - return true; - } - return pub.confirm(message); + return message === undefined || pub.confirm(message); }, /** - * Handles form submission triggered by elements with "method" data attribute. - * If the element is enclosed within an existing form, the form will be submitted. - * Otherwise, a new form will be created and submitted. The new form's method and action - * are determined using the element's "method" and "action" data attributes, respectively. - * If the "action" data attribute is not specified, it will try the "href" property and - * the current URL. + * Handles the action triggered by user. + * This method recognizes the `data-method` attribute of the element. If the attribute exists, + * the method will submit the form containing this element. If there is no containing form, a form + * will be created and submitted using the method given by this attribute value (e.g. "post", "put"). + * For hyperlinks, the form action will take the value of the "href" attribute of the link. + * For other elements, either the containing form action or the current page URL will be used + * as the form action URL. + * + * If the `data-method` attribute is not defined, the default element action will be performed. + * * @param $e the jQuery representation of the element + * @return boolean whether to execute the default action for the element. */ - handleSubmit: function ($e) { + handleAction: function ($e) { var method = $e.data('method'); if (method === undefined) { - return; + return true; } var $form = $e.closest('form'); - if (!$form.length) { - var action = $e.data('action'); - if (action === undefined) { - action = $e.prop('href'); - if (action === undefined) { - action = window.location.href; - } + var newForm = !$form.length; + if (newForm) { + var action = $e.prop('href'); + if (!action || !action.match(/(^\/|:\/\/)/)) { + action = window.location.href; } $form = $('
'); var target = $e.prop('target'); @@ -132,6 +139,12 @@ yii = (function ($) { } $form.trigger('submit'); + + if (newForm) { + $form.remove(); + } + + return false; }, initModule: function (module) { @@ -150,16 +163,22 @@ yii = (function ($) { init: function () { var $document = $(document); - $document.on('click.yii', pub.linkClickSelector, function () { + $document.on('click.yii', pub.clickableSelector, function (event) { var $this = $(this); - if (!pub.allowAction($this)) { - $this.stopImmediatePropagation(); + if (pub.allowAction($this)) { + return pub.handleAction($this); + } else { + event.stopImmediatePropagation(); return false; + } + }); + $document.on('change.yii', pub.changeableSelector, function (event) { + var $this = $(this); + if (pub.allowAction($this)) { + return pub.handleAction($this); } else { - if ($this.data('method')) { - pub.handleSubmit($this); - return false; - } + event.stopImmediatePropagation(); + return false; } }); } From 3acca93ad3b4d1d5340730c19f6e932fbb4549af Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 20 Sep 2013 15:04:28 -0400 Subject: [PATCH 115/157] Enable CSRF validation by default. --- framework/yii/assets/yii.js | 6 ++++++ framework/yii/web/Request.php | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index 99a314e..22f92a5 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -163,6 +163,12 @@ yii = (function ($) { init: function () { var $document = $(document); + $.ajaxPrefilter(function (options, originalOptions, xhr) { + if (!options.crossDomain && pub.getCsrfVar()) { + xhr.setRequestHeader('X-CSRF-TOKEN', pub.getCsrfToken()); + } + }); + $document.on('click.yii', pub.clickableSelector, function (event) { var $this = $(this); if (pub.allowAction($this)) { diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index a07deaa..76c8883 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -76,7 +76,7 @@ class Request extends \yii\base\Request const CSRF_HEADER = 'X-CSRF-TOKEN'; /** - * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false. + * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true. * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated * from the same application. If not, a 400 HTTP exception will be raised. * @@ -90,7 +90,7 @@ class Request extends \yii\base\Request * @see Controller::enableCsrfValidation * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery */ - public $enableCsrfValidation = false; + public $enableCsrfValidation = true; /** * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'. * This property is used only when [[enableCsrfValidation]] is true. From ae39324e5491f8e41bbc8e1b1d2a0d1e34fded54 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 20 Sep 2013 16:21:18 -0400 Subject: [PATCH 116/157] Support ajax redirection. --- framework/yii/assets/yii.js | 14 +++++++++++++- framework/yii/web/Controller.php | 3 +-- framework/yii/web/Request.php | 2 +- framework/yii/web/Response.php | 36 ++++++++++++++++++------------------ 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index 22f92a5..add3a02 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -163,12 +163,22 @@ yii = (function ($) { init: function () { var $document = $(document); + // automatically send CSRF token for all AJAX requests $.ajaxPrefilter(function (options, originalOptions, xhr) { if (!options.crossDomain && pub.getCsrfVar()) { - xhr.setRequestHeader('X-CSRF-TOKEN', pub.getCsrfToken()); + xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken()); } }); + // handle AJAX redirection + $document.ajaxComplete(function (event, xhr, settings) { + var url = xhr.getResponseHeader('X-Redirect'); + if (url) { + window.location = url; + } + }); + + // handle data-confirm and data-method for clickable elements $document.on('click.yii', pub.clickableSelector, function (event) { var $this = $(this); if (pub.allowAction($this)) { @@ -178,6 +188,8 @@ yii = (function ($) { return false; } }); + + // handle data-confirm and data-method for changeable elements $document.on('change.yii', pub.changeableSelector, function (event) { var $this = $(this); if (pub.allowAction($this)) { diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 773e2de..6b8afa4 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -126,8 +126,7 @@ class Controller extends \yii\base\Controller * Any relative URL will be converted into an absolute one by prepending it with the host info * of the current request. * - * @param integer $statusCode the HTTP status code. If null, it will use 302 - * for normal requests, and [[ajaxRedirectCode]] for AJAX requests. + * @param integer $statusCode the HTTP status code. If null, it will use 302. * See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]] * for details about HTTP status code * @return Response the current response object diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 76c8883..c76fd4e 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -73,7 +73,7 @@ class Request extends \yii\base\Request /** * The name of the HTTP header for sending CSRF token. */ - const CSRF_HEADER = 'X-CSRF-TOKEN'; + const CSRF_HEADER = 'X-CSRF-Token'; /** * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true. diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index cfbc537..979cce0 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -112,13 +112,6 @@ class Response extends \yii\base\Response */ public $charset; /** - * @var integer the HTTP status code that should be used when redirecting in AJAX mode. - * This is used by [[redirect()]]. A 2xx code should normally be used for this purpose - * so that the AJAX handler will treat the response as a success. - * @see redirect - */ - public $ajaxRedirectCode = 278; - /** * @var string */ public $statusText; @@ -565,17 +558,22 @@ class Response extends \yii\base\Response /** * Redirects the browser to the specified URL. + * * This method will send out a "Location" header to achieve the redirection. + * * In AJAX mode, this normally will not work as expected unless there are some * client-side JavaScript code handling the redirection. To help achieve this goal, - * this method will use [[ajaxRedirectCode]] as the HTTP status code when performing - * redirection in AJAX mode. The following JavaScript code may be used on the client - * side to handle the redirection response: + * this method will send out a "X-Redirect" header instead of "Location". + * + * If you use the "yii" JavaScript module, it will handle the AJAX redirection as + * described above. Otherwise, you should write the following JavaScript code to + * handle the redirection: * * ~~~ - * $(document).ajaxSuccess(function(event, xhr, settings) { - * if (xhr.status == 278) { - * window.location = xhr.getResponseHeader('Location'); + * $document.ajaxComplete(function (event, xhr, settings) { + * var url = xhr.getResponseHeader('X-Redirect'); + * if (url) { + * window.location = url; * } * }); * ~~~ @@ -597,8 +595,7 @@ class Response extends \yii\base\Response * Any relative URL will be converted into an absolute one by prepending it with the host info * of the current request. * - * @param integer $statusCode the HTTP status code. If null, it will use 302 - * for normal requests, and [[ajaxRedirectCode]] for AJAX requests. + * @param integer $statusCode the HTTP status code. If null, it will use 302. * See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]] * for details about HTTP status code * @return Response the response object itself @@ -613,11 +610,14 @@ class Response extends \yii\base\Response if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) { $url = Yii::$app->getRequest()->getHostInfo() . $url; } - if ($statusCode === null) { - $statusCode = Yii::$app->getRequest()->getIsAjax() ? $this->ajaxRedirectCode : 302; + + if (Yii::$app->getRequest()->getIsAjax()) { + $this->getHeaders()->set('X-Redirect', $url); + } else { + $this->getHeaders()->set('Location', $url); } - $this->getHeaders()->set('Location', $url); $this->setStatusCode($statusCode); + return $this; } From 463ff43b4feae6fe7c20cb2cd89f44d391a11225 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 20 Sep 2013 16:39:22 -0400 Subject: [PATCH 117/157] test break fix. --- tests/unit/framework/helpers/HtmlTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/framework/helpers/HtmlTest.php b/tests/unit/framework/helpers/HtmlTest.php index 857f9c2..88aa33a 100644 --- a/tests/unit/framework/helpers/HtmlTest.php +++ b/tests/unit/framework/helpers/HtmlTest.php @@ -19,6 +19,7 @@ class HtmlTest extends TestCase 'request' => array( 'class' => 'yii\web\Request', 'url' => '/test', + 'enableCsrfValidation' => false, ), 'response' => array( 'class' => 'yii\web\Response', From dde7d731a53310ff233efe29e2dcf81f3c868fea Mon Sep 17 00:00:00 2001 From: Larry Ullman Date: Fri, 20 Sep 2013 20:22:51 -0400 Subject: [PATCH 118/157] Doing more editing... --- docs/guide/bootstrap-widgets.md | 6 +++--- docs/guide/overview.md | 2 +- docs/guide/security.md | 7 ++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/guide/bootstrap-widgets.md b/docs/guide/bootstrap-widgets.md index 3f56839..432dcd8 100644 --- a/docs/guide/bootstrap-widgets.md +++ b/docs/guide/bootstrap-widgets.md @@ -1,17 +1,17 @@ Bootstrap widgets ================= -Yii includes support for the [Bootstrap 3](http://getbootstrap.com/) markup and components framework out of the box. Bootstrap is an excellent, responsive framework that can greatly speed up your development process. +Out of the box, Yii includes support for the [Bootstrap 3](http://getbootstrap.com/) markup and components framework (also known as "Twitter Bootstrap"). Bootstrap is an excellent, responsive framework that can greatly speed up the client-side of your development process. The core of Bootstrap is represented by two parts: -- CSS basics, such as grid layout system, typography, helper classes, and responsive utilities. +- CSS basics, such as a grid layout system, typography, helper classes, and responsive utilities. - Ready to use components, such as menus, pagination, modal boxes, tabs etc. Basics ------ -Yii doesn't wrap bootstrap basics into PHP code since HTML is very simple by itself in this case. You can find details +Yii doesn't wrap the bootstrap basics into PHP code since HTML is very simple by itself in this case. You can find details about using the basics at [bootstrap documentation website](http://getbootstrap.com/css/). Still Yii provides a convenient way to include bootstrap assets in your pages with a single line added to `AppAsset.php` located in your `config` directory: diff --git a/docs/guide/overview.md b/docs/guide/overview.md index ef71aa0..835c511 100644 --- a/docs/guide/overview.md +++ b/docs/guide/overview.md @@ -24,7 +24,7 @@ Yii is a generic Web programming framework that can be used for developing virtually any type of Web application. Because it is light-weight and equipped with sophisticated caching mechanisms, it is especially suited to high-traffic applications, such as portals, forums, content -management systems (CMS), e-commerce systems, etc. +management systems (CMS), e-commerce projects, etc. How does Yii Compare with Other Frameworks? diff --git a/docs/guide/security.md b/docs/guide/security.md index f9adf7c..af30e5b 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -4,12 +4,9 @@ Security Hashing and verifying passwords ------------------------------ -It is important not to store passwords in plain text but, contrary to popular belief, just using `md5` or `sha1` to -compute and verify hashes isn't a good way either. Modern hardware allows to brute force these very fast. +Most developers know that you cannot store passwords in plain text, but many believe it's safe to hash passwords using `md5` or `sha1`. There was a time when those hashing algorithms were sufficient, but modern hardware makes it possible to break those hashes very quickly using a brute force attack. -In order to truly secure user passwords even in case your database is leaked you need to use a function that is resistant -to brute-force such as bcrypt. In PHP it can be achieved by using [crypt function](http://php.net/manual/en/function.crypt.php) -but since usage isn't trivial and one can easily misuse it, Yii provides two helper functions for generating hash from +In order to truly secure user passwords, even in the worst case scenario (your database is broken into), you need to use a hashing algorithm that is resistant to brute force attacks. The best current choice is bcrypt. In PHP, you can create a bcrypt hash by using [crypt function](http://php.net/manual/en/function.crypt.php). However, this function is not easy to use properly, so Yii provides two helper functions for generating hash from password and verifying existing hash. When user sets his password we're taking password string from POST and then getting a hash: From 73cdbb37c0831a39340800ff7d5296c8c3e7a687 Mon Sep 17 00:00:00 2001 From: iJackUA Date: Sat, 21 Sep 2013 17:20:40 +0300 Subject: [PATCH 119/157] Fix parenthesis typo in CRUD index template it was causing blocking PHP error --- framework/yii/gii/generators/crud/templates/views/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index 8efa53a..98d35b3 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -63,7 +63,7 @@ foreach ($generator->getTableSchema()->columns as $column) { 'class' => 'item', ), 'itemView' => function ($model, $key, $index, $widget) { - return Html::a(Html::encode($model->), array('view', ); + return Html::a(Html::encode($model->), array('view', )); }, )); ?> From 2765bcccf8e5f9918a605423982f7889327e18ff Mon Sep 17 00:00:00 2001 From: Niko Wicaksono Date: Sun, 22 Sep 2013 00:49:12 +0700 Subject: [PATCH 120/157] Use str_replace() rather than implode-explode --- framework/yii/base/Module.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index a85385b..9b98a3c 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -615,7 +615,7 @@ abstract class Module extends Component if (isset($this->controllerMap[$id])) { $controller = Yii::createObject($this->controllerMap[$id], $id, $this); } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) { - $className = str_replace(' ', '', ucwords(implode(' ', explode('-', $id)))) . 'Controller'; + $className = str_replace(' ', '', ucwords(str_replace('-', ' ', $id))) . 'Controller'; $classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php'; if (!is_file($classFile)) { return false; From 93d5f5a3ec1b71b1ff6130f071d7d1f4927155e0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 22 Sep 2013 13:01:27 -0400 Subject: [PATCH 121/157] Fixes #897. --- framework/yii/base/ErrorHandler.php | 2 ++ framework/yii/web/Request.php | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 41fa7f9..40f5c37 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -93,6 +93,8 @@ class ErrorHandler extends Component $response->getHeaders()->removeAll(); if ($useErrorView && $this->errorAction !== null) { + // disable CSRF validation so that errorAction can run in case the error is caused by CSRF validation failure + Yii::$app->getRequest()->enableCsrfValidation = false; $result = Yii::$app->runAction($this->errorAction); if ($result instanceof Response) { $response = $result; diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index c76fd4e..4fb6257 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -1001,7 +1001,8 @@ class Request extends \yii\base\Request */ public function getCsrfTokenFromHeader() { - return isset($_SERVER[self::CSRF_HEADER]) ? $_SERVER[self::CSRF_HEADER] : null; + $key = 'HTTP_' . str_replace('-', '_', strtoupper(self::CSRF_HEADER)); + return isset($_SERVER[$key]) ? $_SERVER[$key] : null; } /** From f415193cdc62e1e2c9094a72bee7621e50b52232 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 22 Sep 2013 17:02:46 -0400 Subject: [PATCH 122/157] Fixes #898: supported different signature of MemCache::addServer(). --- framework/yii/caching/MemCache.php | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/framework/yii/caching/MemCache.php b/framework/yii/caching/MemCache.php index 53202f0..69a90b4 100644 --- a/framework/yii/caching/MemCache.php +++ b/framework/yii/caching/MemCache.php @@ -87,7 +87,14 @@ class MemCache extends Cache parent::init(); $servers = $this->getServers(); $cache = $this->getMemCache(); - if (count($servers)) { + if (empty($servers)) { + $cache->addServer('127.0.0.1', 11211); + } else { + if (!$this->useMemcached) { + // different version of memcache may have different number of parameters for the addServer method. + $class = new \ReflectionClass($cache); + $paramCount = $class->getMethod('addServer')->getNumberOfParameters(); + } foreach ($servers as $server) { if ($server->host === null) { throw new InvalidConfigException("The 'host' property must be specified for every memcache server."); @@ -97,15 +104,21 @@ class MemCache extends Cache } else { // $timeout is used for memcache versions that do not have timeoutms parameter $timeout = (int) ($server->timeout / 1000) + (($server->timeout % 1000 > 0) ? 1 : 0); - $cache->addServer( - $server->host, $server->port, $server->persistent, - $server->weight, $timeout, $server->retryInterval, - $server->status, $server->failureCallback, $server->timeout - ); + if ($paramCount === 9) { + $cache->addServer( + $server->host, $server->port, $server->persistent, + $server->weight, $timeout, $server->retryInterval, + $server->status, $server->failureCallback, $server->timeout + ); + } else { + $cache->addServer( + $server->host, $server->port, $server->persistent, + $server->weight, $timeout, $server->retryInterval, + $server->status, $server->failureCallback + ); + } } } - } else { - $cache->addServer('127.0.0.1', 11211); } } From 9861ed36cd5c7ef7e2a39f3f49011fd737742295 Mon Sep 17 00:00:00 2001 From: Larry Ullman Date: Sun, 22 Sep 2013 20:35:56 -0400 Subject: [PATCH 123/157] Edited introduction --- docs/guide/model.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/guide/model.md b/docs/guide/model.md index fec7ac3..b93fb7a 100644 --- a/docs/guide/model.md +++ b/docs/guide/model.md @@ -1,15 +1,15 @@ Model ===== -A model in Yii is intended for application data storage and has the following basic features: +In keeping with the MVC approach, a model in Yii is intended for storing or temporarily representing application data. Yii models have the following basic features: -- attribute declaration: a model defines what is considered an attribute. -- attribute labels: each attribute may be associated with a label for display purpose. -- massive attribute assignment. -- scenario-based data validation. +- Attribute declaration: a model defines what is considered an attribute. +- Attribute labels: each attribute may be associated with a label for display purpose. +- Massive attribute assignment: the ability to populate multiple model attributes in one step. +- Scenario-based data validation. -Models extending from [[\yii\base\Model]] class are typically used to hold data and corresponding validation rules of complex web forms. -The class is also a base for more advanced models with additional functionality such as [Active Record](active-record.md). +Models in Yii extend from the [[\yii\base\Model]] class. Models are typically used to both hold data and define the validation rules for that data. The validation rules greatly simply the generation of models from complex web forms. +The Model class is also the base for more advanced models with additional functionality such as [Active Record](active-record.md). Attributes ---------- From 43d392d8d2fe04271842e6ff64575ba0a78edebf Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 23 Sep 2013 07:18:42 -0400 Subject: [PATCH 124/157] porting the fix from https://github.com/yiisoft/yii/pull/2894 --- framework/yii/db/QueryBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index 3cc7971..f210f65 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -585,7 +585,7 @@ class QueryBuilder extends \yii\base\Object foreach ($tables as $i => $table) { if (strpos($table, '(') === false) { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/i', $table, $matches)) { // with alias + if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]); } else { $tables[$i] = $this->db->quoteTableName($table); @@ -619,7 +619,7 @@ class QueryBuilder extends \yii\base\Object // 0:join type, 1:table name, 2:on-condition $table = $join[1]; if (strpos($table, '(') === false) { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias + if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias $table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]); } else { $table = $this->db->quoteTableName($table); From 4d901acd8e0e307b73b040e2169d20fedaf8abb8 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 23 Sep 2013 18:01:13 +0400 Subject: [PATCH 125/157] Advanced application template: removed unused scenario from User model --- apps/advanced/common/models/User.php | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php index e4101e8..da8f067 100644 --- a/apps/advanced/common/models/User.php +++ b/apps/advanced/common/models/User.php @@ -123,7 +123,6 @@ class User extends ActiveRecord implements Identity { return array( 'signup' => array('username', 'email', 'password'), - 'login' => array('username', 'password'), 'resetPassword' => array('password'), 'requestPasswordResetToken' => array('email'), ); From c05477b14144f1bf3970f809356407afc4631063 Mon Sep 17 00:00:00 2001 From: Larry Ullman Date: Mon, 23 Sep 2013 17:03:15 -0400 Subject: [PATCH 126/157] Polished up the basic discussion of template alternatives --- docs/guide/template.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/guide/template.md b/docs/guide/template.md index 6d2db88..f9405ff 100644 --- a/docs/guide/template.md +++ b/docs/guide/template.md @@ -3,8 +3,8 @@ Using template engines By default Yii uses PHP as template language, but you can configure it to support other rendering engines, such as [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/). -The component responsible for rendering a view is called `view`. You can add -a custom template engines as follows: +The `view` component is responsible for rendering views. You can add +a custom template engines by reconfiguring this component's behavior: ```php array( @@ -26,15 +26,13 @@ array( ) ``` -Note that Smarty and Twig are not bundled with Yii and you have to download and -unpack these yourself and then specify `twigPath` and `smartyPath` respectively. +Note that the Smarty and Twig packages themselves are not bundled with Yii. You must download them yourself. Then unpack the packages and place the resulting files in a logical location, such as the application's `protected/vendor` folder. Finally, specify the correct `smartyPath` or `twigPath`, as in the code above (for Twig). Twig ---- -In order to use Twig you need to put you templates in files with extension `.twig` -(or another one if configured differently). -Also you need to specify this extension explicitly when calling `$this->render()` +To use Twig, you need to create templates in files with the `.twig` extension (or use another file extension but configure the component accordingly). +Unlike standard view files, when using Twig, you must include the extension when calling `$this->render()` or `$this->renderPartial()` from your controller: ```php @@ -43,25 +41,25 @@ echo $this->render('renderer.twig', array('username' => 'Alex')); ### Additional functions -Additionally to regular Twig syntax the following is available in Yii: +Yii adds the following construct to the standard Twig syntax: ```php {{ post.title }} ``` -path function calls `Html::url()` internally. +Internally, the `path()` function calls Yii's `Html::url()` method. ### Additional variables -- `app` = `\Yii::$app` -- `this` = current `View` object +Within Twig templates, you can also make use of these variables: + +- `app`, which equates to `\Yii::$app` +- `this`, which equates to the current `View` object Smarty ------ -In order to use Smarty you need to put you templates in files with extension `.tpl` -(or another one if configured differently). -Also you need to specify this extension explicitly when calling `$this->render()` +To use Smarty, you need to create templates in files with the `.tpl` extension (or use another file extension but configure the component accordingly). Unlike standard view files, when using Smarty, you must include the extension when calling `$this->render()` or `$this->renderPartial()` from your controller: ```php @@ -70,16 +68,18 @@ echo $this->render('renderer.tpl', array('username' => 'Alex')); ### Additional functions -Additionally to regular Smarty syntax the following is available in Yii: +Yii adds the following construct to the standard Smarty syntax: ```php {$post.title} ``` -path function calls `Html::url()` internally. +Internally, the `path()` function calls Yii's `Html::url()` method. ### Additional variables -- `$app` = `\Yii::$app` -- `$this` = current `View` object +Within Smarty templates, you can also make use of these variables: + +- `$app`, which equates to `\Yii::$app` +- `$this`, which equates to the current `View` object From 76154b97766f7a4ef77d6d13480ceeafea7205e9 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 23 Sep 2013 20:38:54 -0400 Subject: [PATCH 127/157] Fixes #901: Added $delete parameter to Session::getFlash(). --- framework/yii/web/Session.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/framework/yii/web/Session.php b/framework/yii/web/Session.php index fc9fe16..92ec3ad 100644 --- a/framework/yii/web/Session.php +++ b/framework/yii/web/Session.php @@ -582,12 +582,22 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co * A flash message is available only in the current request and the next request. * @param string $key the key identifying the flash message * @param mixed $defaultValue value to be returned if the flash message does not exist. + * @param boolean $delete whether to delete this flash message right after this method is called. + * If false, the flash message will be automatically deleted after the next request. * @return mixed the flash message */ - public function getFlash($key, $defaultValue = null) + public function getFlash($key, $defaultValue = null, $delete = false) { $counters = $this->get($this->flashVar, array()); - return isset($counters[$key]) ? $this->get($key, $defaultValue) : $defaultValue; + if (isset($counters[$key])) { + $value = $this->get($key, $defaultValue); + if ($delete) { + $this->removeFlash($key); + } + return $value; + } else { + return $defaultValue; + } } /** From e1061f19acc7ac083fee9c80984461313d67abdb Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 24 Sep 2013 12:07:41 +0400 Subject: [PATCH 128/157] Advanced application template: Delete flash message after it was displayed --- apps/advanced/frontend/widgets/Alert.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/advanced/frontend/widgets/Alert.php b/apps/advanced/frontend/widgets/Alert.php index 20f3372..b68bfb0 100644 --- a/apps/advanced/frontend/widgets/Alert.php +++ b/apps/advanced/frontend/widgets/Alert.php @@ -23,13 +23,13 @@ class Alert extends \yii\bootstrap\Alert private $_doNotRender = false; public function init() { - if ($this->body = \Yii::$app->getSession()->getFlash('error')) { + if ($this->body = \Yii::$app->getSession()->getFlash('error', null, true)) { Html::addCssClass($this->options, 'alert-danger'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('success')) { + } elseif ($this->body = \Yii::$app->getSession()->getFlash('success', null, true)) { Html::addCssClass($this->options, 'alert-success'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('info')) { + } elseif ($this->body = \Yii::$app->getSession()->getFlash('info', null, true)) { Html::addCssClass($this->options, 'alert-info'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('warning')) { + } elseif ($this->body = \Yii::$app->getSession()->getFlash('warning', null, true)) { Html::addCssClass($this->options, 'alert-warning'); } else { $this->_doNotRender = true; From e19d0dacf9992a08045bd8cdfcaaefc4bb1ca12c Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 24 Sep 2013 15:45:09 +0400 Subject: [PATCH 129/157] Fixes #823: consistent interface naming --- apps/advanced/common/models/User.php | 6 +- apps/basic/models/User.php | 2 +- docs/guide/upgrade-from-v1.md | 6 +- framework/yii/classes.php | 8 +-- framework/yii/data/DataProvider.php | 4 +- framework/yii/data/DataProviderInterface.php | 58 +++++++++++++++++ framework/yii/data/IDataProvider.php | 58 ----------------- framework/yii/web/AssetConverter.php | 2 +- framework/yii/web/AssetConverterInterface.php | 25 ++++++++ framework/yii/web/AssetManager.php | 8 +-- framework/yii/web/IAssetConverter.php | 25 -------- framework/yii/web/Identity.php | 81 ------------------------ framework/yii/web/IdentityInterface.php | 81 ++++++++++++++++++++++++ framework/yii/web/Response.php | 4 +- framework/yii/web/ResponseFormatter.php | 23 ------- framework/yii/web/ResponseFormatterInterface.php | 23 +++++++ framework/yii/web/User.php | 30 ++++----- framework/yii/web/UserEvent.php | 2 +- framework/yii/web/XmlResponseFormatter.php | 2 +- framework/yii/widgets/ListViewBase.php | 2 +- phpunit.xml.dist | 2 +- 21 files changed, 226 insertions(+), 226 deletions(-) create mode 100644 framework/yii/data/DataProviderInterface.php delete mode 100644 framework/yii/data/IDataProvider.php create mode 100644 framework/yii/web/AssetConverterInterface.php delete mode 100644 framework/yii/web/IAssetConverter.php delete mode 100644 framework/yii/web/Identity.php create mode 100644 framework/yii/web/IdentityInterface.php delete mode 100644 framework/yii/web/ResponseFormatter.php create mode 100644 framework/yii/web/ResponseFormatterInterface.php diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php index da8f067..62baf48 100644 --- a/apps/advanced/common/models/User.php +++ b/apps/advanced/common/models/User.php @@ -3,7 +3,7 @@ namespace common\models; use yii\db\ActiveRecord; use yii\helpers\Security; -use yii\web\Identity; +use yii\web\IdentityInterface; /** * Class User @@ -20,7 +20,7 @@ use yii\web\Identity; * @property integer $create_time * @property integer $update_time */ -class User extends ActiveRecord implements Identity +class User extends ActiveRecord implements IdentityInterface { /** * @var string the raw password. Used to collect password input and isn't saved in database @@ -49,7 +49,7 @@ class User extends ActiveRecord implements Identity * Finds an identity by the given ID. * * @param string|integer $id the ID to be looked for - * @return Identity|null the identity object that matches the given ID. + * @return IdentityInterface|null the identity object that matches the given ID. */ public static function findIdentity($id) { diff --git a/apps/basic/models/User.php b/apps/basic/models/User.php index afbf9f8..e1088a0 100644 --- a/apps/basic/models/User.php +++ b/apps/basic/models/User.php @@ -2,7 +2,7 @@ namespace app\models; -class User extends \yii\base\Object implements \yii\web\Identity +class User extends \yii\base\Object implements \yii\web\IdentityInterface { public $id; public $username; diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md index ee2a3d5..f174864 100644 --- a/docs/guide/upgrade-from-v1.md +++ b/docs/guide/upgrade-from-v1.md @@ -450,11 +450,11 @@ This feature is especially useful if you are developing an application that supp different DBMS. -User and Identity ------------------ +User and IdentityInterface +-------------------------- The `CWebUser` class in 1.1 is now replaced by `\yii\Web\User`, and there is no more -`CUserIdentity` class. Instead, you should implement the `Identity` interface which +`CUserIdentity` class. Instead, you should implement the `IdentityInterface` which is much more straightforward to implement. The bootstrap application provides such an example. diff --git a/framework/yii/classes.php b/framework/yii/classes.php index 3880620..40ca225 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -86,7 +86,7 @@ return array( 'yii\data\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php', 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php', 'yii\data\DataProvider' => YII_PATH . '/data/DataProvider.php', - 'yii\data\IDataProvider' => YII_PATH . '/data/IDataProvider.php', + 'yii\data\DataProviderInterface' => YII_PATH . '/data/DataProviderInterface.php', 'yii\data\Pagination' => YII_PATH . '/data/Pagination.php', 'yii\data\Sort' => YII_PATH . '/data/Sort.php', 'yii\db\ActiveQuery' => YII_PATH . '/db/ActiveQuery.php', @@ -204,15 +204,15 @@ return array( 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php', 'yii\web\HttpCache' => YII_PATH . '/web/HttpCache.php', 'yii\web\HttpException' => YII_PATH . '/web/HttpException.php', - 'yii\web\IAssetConverter' => YII_PATH . '/web/IAssetConverter.php', - 'yii\web\Identity' => YII_PATH . '/web/Identity.php', + 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php', + 'yii\web\IdentityInterface' => YII_PATH . '/web/IdentityInterface.php', 'yii\web\JqueryAsset' => YII_PATH . '/web/JqueryAsset.php', 'yii\web\JsExpression' => YII_PATH . '/web/JsExpression.php', 'yii\web\PageCache' => YII_PATH . '/web/PageCache.php', 'yii\web\Request' => YII_PATH . '/web/Request.php', 'yii\web\Response' => YII_PATH . '/web/Response.php', 'yii\web\ResponseEvent' => YII_PATH . '/web/ResponseEvent.php', - 'yii\web\ResponseFormatter' => YII_PATH . '/web/ResponseFormatter.php', + 'yii\web\ResponseFormatterInterface' => YII_PATH . '/web/ResponseFormatterInterface.php', 'yii\web\Session' => YII_PATH . '/web/Session.php', 'yii\web\SessionIterator' => YII_PATH . '/web/SessionIterator.php', 'yii\web\UploadedFile' => YII_PATH . '/web/UploadedFile.php', diff --git a/framework/yii/data/DataProvider.php b/framework/yii/data/DataProvider.php index 84491d6..b29f616 100644 --- a/framework/yii/data/DataProvider.php +++ b/framework/yii/data/DataProvider.php @@ -14,7 +14,7 @@ use yii\base\InvalidParamException; /** * DataProvider is the base class of data provider classes. * - * It implements the [[getPagination()]] and [[getSort()]] methods as specified by the [[IDataProvider]] interface. + * It implements the [[getPagination()]] and [[getSort()]] methods as specified by the [[DataProviderInterface]]. * * @property integer $count The number of data models in the current page. This property is read-only. * @property Pagination|boolean $pagination The pagination object. If this is false, it means the pagination @@ -26,7 +26,7 @@ use yii\base\InvalidParamException; * @author Qiang Xue * @since 2.0 */ -abstract class DataProvider extends Component implements IDataProvider +abstract class DataProvider extends Component implements DataProviderInterface { /** * @var string an ID that uniquely identifies the data provider among all data providers. diff --git a/framework/yii/data/DataProviderInterface.php b/framework/yii/data/DataProviderInterface.php new file mode 100644 index 0000000..f0bc39d --- /dev/null +++ b/framework/yii/data/DataProviderInterface.php @@ -0,0 +1,58 @@ + + * @since 2.0 + */ +interface DataProviderInterface +{ + /** + * Returns the number of data models in the current page. + * This is equivalent to `count($provider->getModels())`. + * When [[pagination]] is false, this is the same as [[totalCount]]. + * @return integer the number of data models in the current page. + */ + public function getCount(); + + /** + * Returns the total number of data models. + * When [[pagination]] is false, this is the same as [[count]]. + * @return integer total number of possible data models. + */ + public function getTotalCount(); + + /** + * Returns the data models in the current page. + * @return array the list of data models in the current page. + */ + public function getModels(); + + /** + * Returns the key values associated with the data models. + * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] + * is uniquely identified by the corresponding key value in this array. + */ + public function getKeys(); + + /** + * @return Sort the sorting object. If this is false, it means the sorting is disabled. + */ + public function getSort(); + + /** + * @return Pagination the pagination object. If this is false, it means the pagination is disabled. + */ + public function getPagination(); +} diff --git a/framework/yii/data/IDataProvider.php b/framework/yii/data/IDataProvider.php deleted file mode 100644 index 9ae5546..0000000 --- a/framework/yii/data/IDataProvider.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @since 2.0 - */ -interface IDataProvider -{ - /** - * Returns the number of data models in the current page. - * This is equivalent to `count($provider->getModels())`. - * When [[pagination]] is false, this is the same as [[totalCount]]. - * @return integer the number of data models in the current page. - */ - public function getCount(); - - /** - * Returns the total number of data models. - * When [[pagination]] is false, this is the same as [[count]]. - * @return integer total number of possible data models. - */ - public function getTotalCount(); - - /** - * Returns the data models in the current page. - * @return array the list of data models in the current page. - */ - public function getModels(); - - /** - * Returns the key values associated with the data models. - * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] - * is uniquely identified by the corresponding key value in this array. - */ - public function getKeys(); - - /** - * @return Sort the sorting object. If this is false, it means the sorting is disabled. - */ - public function getSort(); - - /** - * @return Pagination the pagination object. If this is false, it means the pagination is disabled. - */ - public function getPagination(); -} diff --git a/framework/yii/web/AssetConverter.php b/framework/yii/web/AssetConverter.php index cd931c9..420a5bc 100644 --- a/framework/yii/web/AssetConverter.php +++ b/framework/yii/web/AssetConverter.php @@ -16,7 +16,7 @@ use yii\base\Component; * @author Qiang Xue * @since 2.0 */ -class AssetConverter extends Component implements IAssetConverter +class AssetConverter extends Component implements AssetConverterInterface { /** * @var array the commands that are used to perform the asset conversion. diff --git a/framework/yii/web/AssetConverterInterface.php b/framework/yii/web/AssetConverterInterface.php new file mode 100644 index 0000000..51309c6 --- /dev/null +++ b/framework/yii/web/AssetConverterInterface.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +interface AssetConverterInterface +{ + /** + * Converts a given asset file into a CSS or JS file. + * @param string $asset the asset file path, relative to $basePath + * @param string $basePath the directory the $asset is relative to. + * @return string the converted asset file path, relative to $basePath. + */ + public function convert($asset, $basePath); +} diff --git a/framework/yii/web/AssetManager.php b/framework/yii/web/AssetManager.php index c6f7fea..500848b 100644 --- a/framework/yii/web/AssetManager.php +++ b/framework/yii/web/AssetManager.php @@ -16,7 +16,7 @@ use yii\helpers\FileHelper; /** * AssetManager manages asset bundles and asset publishing. * - * @property IAssetConverter $converter The asset converter. Note that the type of this property differs in + * @property AssetConverterInterface $converter The asset converter. Note that the type of this property differs in * getter and setter. See [[getConverter()]] and [[setConverter()]] for details. * * @author Qiang Xue @@ -116,7 +116,7 @@ class AssetManager extends Component /** * Returns the asset converter. - * @return IAssetConverter the asset converter. + * @return AssetConverterInterface the asset converter. */ public function getConverter() { @@ -130,8 +130,8 @@ class AssetManager extends Component /** * Sets the asset converter. - * @param array|IAssetConverter $value the asset converter. This can be either - * an object implementing the [[IAssetConverter]] interface, or a configuration + * @param array|AssetConverterInterface $value the asset converter. This can be either + * an object implementing the [[AssetConverterInterface]], or a configuration * array that can be used to create the asset converter object. */ public function setConverter($value) diff --git a/framework/yii/web/IAssetConverter.php b/framework/yii/web/IAssetConverter.php deleted file mode 100644 index 6021963..0000000 --- a/framework/yii/web/IAssetConverter.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -interface IAssetConverter -{ - /** - * Converts a given asset file into a CSS or JS file. - * @param string $asset the asset file path, relative to $basePath - * @param string $basePath the directory the $asset is relative to. - * @return string the converted asset file path, relative to $basePath. - */ - public function convert($asset, $basePath); -} diff --git a/framework/yii/web/Identity.php b/framework/yii/web/Identity.php deleted file mode 100644 index 101ecdb..0000000 --- a/framework/yii/web/Identity.php +++ /dev/null @@ -1,81 +0,0 @@ -id; - * } - * - * public function getAuthKey() - * { - * return $this->authKey; - * } - * - * public function validateAuthKey($authKey) - * { - * return $this->authKey === $authKey; - * } - * } - * ~~~ - * - * @author Qiang Xue - * @since 2.0 - */ -interface Identity -{ - /** - * Finds an identity by the given ID. - * @param string|integer $id the ID to be looked for - * @return Identity the identity object that matches the given ID. - * Null should be returned if such an identity cannot be found - * or the identity is not in an active state (disabled, deleted, etc.) - */ - public static function findIdentity($id); - /** - * Returns an ID that can uniquely identify a user identity. - * @return string|integer an ID that uniquely identifies a user identity. - */ - public function getId(); - /** - * Returns a key that can be used to check the validity of a given identity ID. - * - * The key should be unique for each individual user, and should be persistent - * so that it can be used to check the validity of the user identity. - * - * The space of such keys should be big enough to defeat potential identity attacks. - * - * This is required if [[User::enableAutoLogin]] is enabled. - * @return string a key that is used to check the validity of a given identity ID. - * @see validateAuthKey() - */ - public function getAuthKey(); - /** - * Validates the given auth key. - * - * This is required if [[User::enableAutoLogin]] is enabled. - * @param string $authKey the given auth key - * @return boolean whether the given auth key is valid. - * @see getAuthKey() - */ - public function validateAuthKey($authKey); -} diff --git a/framework/yii/web/IdentityInterface.php b/framework/yii/web/IdentityInterface.php new file mode 100644 index 0000000..c796b50 --- /dev/null +++ b/framework/yii/web/IdentityInterface.php @@ -0,0 +1,81 @@ +id; + * } + * + * public function getAuthKey() + * { + * return $this->authKey; + * } + * + * public function validateAuthKey($authKey) + * { + * return $this->authKey === $authKey; + * } + * } + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +interface IdentityInterface +{ + /** + * Finds an identity by the given ID. + * @param string|integer $id the ID to be looked for + * @return IdentityInterface the identity object that matches the given ID. + * Null should be returned if such an identity cannot be found + * or the identity is not in an active state (disabled, deleted, etc.) + */ + public static function findIdentity($id); + /** + * Returns an ID that can uniquely identify a user identity. + * @return string|integer an ID that uniquely identifies a user identity. + */ + public function getId(); + /** + * Returns a key that can be used to check the validity of a given identity ID. + * + * The key should be unique for each individual user, and should be persistent + * so that it can be used to check the validity of the user identity. + * + * The space of such keys should be big enough to defeat potential identity attacks. + * + * This is required if [[User::enableAutoLogin]] is enabled. + * @return string a key that is used to check the validity of a given identity ID. + * @see validateAuthKey() + */ + public function getAuthKey(); + /** + * Validates the given auth key. + * + * This is required if [[User::enableAutoLogin]] is enabled. + * @param string $authKey the given auth key + * @return boolean whether the given auth key is valid. + * @see getAuthKey() + */ + public function validateAuthKey($authKey); +} diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 979cce0..e6505fd 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -766,10 +766,10 @@ class Response extends \yii\base\Response if (!is_object($formatter)) { $formatter = Yii::createObject($formatter); } - if ($formatter instanceof ResponseFormatter) { + if ($formatter instanceof ResponseFormatterInterface) { $formatter->format($this); } else { - throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatter interface."); + throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface."); } } else { switch ($this->format) { diff --git a/framework/yii/web/ResponseFormatter.php b/framework/yii/web/ResponseFormatter.php deleted file mode 100644 index dc7c979..0000000 --- a/framework/yii/web/ResponseFormatter.php +++ /dev/null @@ -1,23 +0,0 @@ - - * @since 2.0 - */ -interface ResponseFormatter -{ - /** - * Formats the specified response. - * @param Response $response the response to be formatted. - */ - public function format($response); -} diff --git a/framework/yii/web/ResponseFormatterInterface.php b/framework/yii/web/ResponseFormatterInterface.php new file mode 100644 index 0000000..689ee1e --- /dev/null +++ b/framework/yii/web/ResponseFormatterInterface.php @@ -0,0 +1,23 @@ + + * @since 2.0 + */ +interface ResponseFormatterInterface +{ + /** + * Formats the specified response. + * @param Response $response the response to be formatted. + */ + public function format($response); +} diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index 22b85e5..f6a9bc8 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -18,12 +18,12 @@ use yii\base\InvalidParamException; * In particular, [[User::isGuest]] returns a value indicating whether the current user is a guest or not. * Through methods [[login()]] and [[logout()]], you can change the user authentication status. * - * User works with a class implementing the [[Identity]] interface. This class implements + * User works with a class implementing the [[IdentityInterface]]. This class implements * the actual user authentication logic and is often backed by a user database table. * * @property string|integer $id The unique identifier for the user. If null, it means the user is a guest. * This property is read-only. - * @property Identity $identity The identity object associated with the currently logged user. Null is + * @property IdentityInterface $identity The identity object associated with the currently logged user. Null is * returned if the user is not logged in (not authenticated). * @property boolean $isGuest Whether the current user is a guest. This property is read-only. * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type @@ -128,7 +128,7 @@ class User extends Component /** * Returns the identity object associated with the currently logged user. - * @return Identity the identity object associated with the currently logged user. + * @return IdentityInterface the identity object associated with the currently logged user. * Null is returned if the user is not logged in (not authenticated). * @see login * @see logout @@ -140,7 +140,7 @@ class User extends Component if ($id === null) { $this->_identity = null; } else { - /** @var $class Identity */ + /** @var $class IdentityInterface */ $class = $this->identityClass; $this->_identity = $class::findIdentity($id); } @@ -156,7 +156,7 @@ class User extends Component * You should normally update the user identity via methods [[login()]], [[logout()]] * or [[switchIdentity()]]. * - * @param Identity $identity the identity object associated with the currently logged user. + * @param IdentityInterface $identity the identity object associated with the currently logged user. */ public function setIdentity($identity) { @@ -171,7 +171,7 @@ class User extends Component * and [[enableAutoLogin]] is true, it will also send out an identity * cookie to support cookie-based login. * - * @param Identity $identity the user identity (which should already be authenticated) + * @param IdentityInterface $identity the user identity (which should already be authenticated) * @param integer $duration number of seconds that the user can remain in logged-in status. * Defaults to 0, meaning login till the user closes the browser or the session is manually destroyed. * If greater than 0 and [[enableAutoLogin]] is true, cookie-based login will be supported. @@ -200,7 +200,7 @@ class User extends Component $data = json_decode($value, true); if (count($data) === 3 && isset($data[0], $data[1], $data[2])) { list ($id, $authKey, $duration) = $data; - /** @var $class Identity */ + /** @var $class IdentityInterface */ $class = $this->identityClass; $identity = $class::findIdentity($id); if ($identity !== null && $identity->validateAuthKey($authKey)) { @@ -318,7 +318,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information * @param boolean $cookieBased whether the login is cookie-based * @return boolean whether the user should continue to be logged in */ @@ -337,7 +337,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information * @param boolean $cookieBased whether the login is cookie-based */ protected function afterLogin($identity, $cookieBased) @@ -353,7 +353,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information * @return boolean whether the user should continue to be logged out */ protected function beforeLogout($identity) @@ -370,7 +370,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information */ protected function afterLogout($identity) { @@ -402,9 +402,9 @@ class User extends Component /** * Sends an identity cookie. * This method is used when [[enableAutoLogin]] is true. - * It saves [[id]], [[Identity::getAuthKey()|auth key]], and the duration of cookie-based login + * It saves [[id]], [[IdentityInterface::getAuthKey()|auth key]], and the duration of cookie-based login * information in the cookie. - * @param Identity $identity + * @param IdentityInterface $identity * @param integer $duration number of seconds that the user can remain in logged-in status. * @see loginByCookie */ @@ -430,7 +430,7 @@ class User extends Component * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]] * when the current user needs to be associated with the corresponding identity information. * - * @param Identity $identity the identity information to be associated with the current user. + * @param IdentityInterface $identity the identity information to be associated with the current user. * If null, it means switching to be a guest. * @param integer $duration number of seconds that the user can remain in logged-in status. * This parameter is used only when `$identity` is not null. @@ -444,7 +444,7 @@ class User extends Component $this->setIdentity($identity); $session->remove($this->idVar); $session->remove($this->authTimeoutVar); - if ($identity instanceof Identity) { + if ($identity instanceof IdentityInterface) { $session->set($this->idVar, $identity->getId()); if ($this->authTimeout !== null) { $session->set($this->authTimeoutVar, time() + $this->authTimeout); diff --git a/framework/yii/web/UserEvent.php b/framework/yii/web/UserEvent.php index 3e403da..8577ef5 100644 --- a/framework/yii/web/UserEvent.php +++ b/framework/yii/web/UserEvent.php @@ -18,7 +18,7 @@ use yii\base\Event; class UserEvent extends Event { /** - * @var Identity the identity object associated with this event + * @var IdentityInterface the identity object associated with this event */ public $identity; /** diff --git a/framework/yii/web/XmlResponseFormatter.php b/framework/yii/web/XmlResponseFormatter.php index adf8807..737011d 100644 --- a/framework/yii/web/XmlResponseFormatter.php +++ b/framework/yii/web/XmlResponseFormatter.php @@ -20,7 +20,7 @@ use yii\helpers\StringHelper; * @author Qiang Xue * @since 2.0 */ -class XmlResponseFormatter extends Component implements ResponseFormatter +class XmlResponseFormatter extends Component implements ResponseFormatterInterface { /** * @var string the Content-Type header for the response diff --git a/framework/yii/widgets/ListViewBase.php b/framework/yii/widgets/ListViewBase.php index 8c2f8f4..33186ae 100644 --- a/framework/yii/widgets/ListViewBase.php +++ b/framework/yii/widgets/ListViewBase.php @@ -25,7 +25,7 @@ abstract class ListViewBase extends Widget */ public $options = array(); /** - * @var \yii\data\IDataProvider the data provider for the view. This property is required. + * @var \yii\data\DataProviderInterface the data provider for the view. This property is required. */ public $dataProvider; /** diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3100413..1f3056e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,7 +21,7 @@ framework/yii/helpers/ArrayHelper.php framework/yii/helpers/Console.php framework/yii/i18n/GettextFile.php - framework/yii/web/ResponseFormatter.php + framework/yii/web/ResponseFormatterInterface.php framework/yii/base framework/yii/db/mssql framework/yii/bootstrap From cd969509e6be6f89f2eda7b85727c073b06c1cd8 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 24 Sep 2013 21:46:31 -0400 Subject: [PATCH 130/157] Added ActionColumn. crud generator WIP. --- .../gii/generators/crud/templates/controller.php | 13 +++ .../gii/generators/crud/templates/views/index.php | 4 + .../gii/generators/crud/templates/views/view.php | 6 +- framework/yii/grid/ActionColumn.php | 102 +++++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 framework/yii/grid/ActionColumn.php diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/framework/yii/gii/generators/crud/templates/controller.php index fd33eda..f53f819 100644 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ b/framework/yii/gii/generators/crud/templates/controller.php @@ -28,12 +28,25 @@ use searchModelClass, '\\'); ?>; use yii\data\ActiveDataProvider; use baseControllerClass, '\\'); ?>; use yii\web\HttpException; +use yii\web\VerbFilter; /** * implements the CRUD actions for model. */ class extends baseControllerClass) . "\n"; ?> { + public function behaviors() + { + return array( + 'verbs' => array( + 'class' => VerbFilter::className(), + 'actions' => array( + 'delete' => array('post'), + ), + ), + ); + } + /** * Lists all models. * @return mixed diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index 98d35b3..5f55e84 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -54,6 +54,10 @@ foreach ($generator->getTableSchema()->columns as $column) { } } ?> + + array( + 'class' => 'yii\grid\ActionColumn', + ), ), )); ?> diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php index d08ec23..a3b6cad 100644 --- a/framework/yii/gii/generators/crud/templates/views/view.php +++ b/framework/yii/gii/generators/crud/templates/views/view.php @@ -31,7 +31,11 @@ $this->params['breadcrumbs'][] = $this->title;
echo Html::a('Update', array('update', ), array('class' => 'btn btn-danger')); ?> - echo Html::a('Delete', array('delete', ), array('class' => 'btn btn-danger')); ?> + echo Html::a('Delete', array('delete', ), array( + 'class' => 'btn btn-danger', + 'data-confirm' => Yii::t('app', 'Are you sure to delete this item?'), + 'data-method' => 'post', + )); ?>
echo DetailView::widget(array( diff --git a/framework/yii/grid/ActionColumn.php b/framework/yii/grid/ActionColumn.php new file mode 100644 index 0000000..72fafb6 --- /dev/null +++ b/framework/yii/grid/ActionColumn.php @@ -0,0 +1,102 @@ + + * @since 2.0 + */ +class ActionColumn extends Column +{ + public $template = '{view} {update} {delete}'; + public $buttons = array(); + public $urlCreator; + + public function init() + { + parent::init(); + $this->initDefaultButtons(); + } + + protected function initDefaultButtons() + { + if (!isset($this->buttons['view'])) { + $this->buttons['view'] = function ($model, $column) { + /** @var ActionColumn $column */ + $url = $column->createUrl($model, 'view'); + return Html::a('', $url, array( + 'title' => Yii::t('yii', 'View'), + )); + }; + } + if (!isset($this->buttons['update'])) { + $this->buttons['update'] = function ($model, $column) { + /** @var ActionColumn $column */ + $url = $column->createUrl($model, 'update'); + return Html::a('', $url, array( + 'title' => Yii::t('yii', 'Update'), + )); + }; + } + if (!isset($this->buttons['delete'])) { + $this->buttons['delete'] = function ($model, $column) { + /** @var ActionColumn $column */ + $url = $column->createUrl($model, 'delete'); + return Html::a('', $url, array( + 'title' => Yii::t('yii', 'Delete'), + 'data-confirm' => Yii::t('yii', 'Are you sure to delete this item?'), + 'data-method' => 'post', + )); + }; + } + } + + /** + * @param \yii\db\ActiveRecord $model + * @param string $action + * @return string + */ + public function createUrl($model, $action) + { + if ($this->urlCreator instanceof Closure) { + return call_user_func($this->urlCreator, $model, $action); + } else { + $route = Inflector::camel2id(StringHelper::basename(get_class($model))) . '/' . $action; + $params = $model->getPrimaryKey(true); + if (count($params) === 1) { + $params = array('id' => reset($params)); + } + return Yii::$app->getUrlManager()->createUrl($route, $params); + } + } + + /** + * Renders the data cell content. + * @param mixed $model the data model + * @param integer $index the zero-based index of the data model among the models array returned by [[dataProvider]]. + * @return string the rendering result + */ + protected function renderDataCellContent($model, $index) + { + $column = $this; + return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model, $column) { + $name = $matches[1]; + if (isset($column->buttons[$name])) { + return call_user_func($column->buttons[$name], $model, $column); + } else { + return ''; + } + }, $this->template); + } +} From 7d2d925dbe1809e42dcb0bd4e006c7e6919e0b94 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 24 Sep 2013 21:50:08 -0400 Subject: [PATCH 131/157] Added SerialColumn to crud generated code. --- framework/yii/gii/generators/crud/templates/views/index.php | 6 +++--- framework/yii/grid/SerialColumn.php | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index 5f55e84..df76581 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -43,6 +43,8 @@ $this->params['breadcrumbs'][] = $this->title; 'dataProvider' => $dataProvider, 'filterModel' => $searchModel, 'columns' => array( + array('class' => 'yii\grid\SerialColumn'), + getTableSchema()->columns as $column) { @@ -55,9 +57,7 @@ foreach ($generator->getTableSchema()->columns as $column) { } ?> - array( - 'class' => 'yii\grid\ActionColumn', - ), + array('class' => 'yii\grid\ActionColumn'), ), )); ?> diff --git a/framework/yii/grid/SerialColumn.php b/framework/yii/grid/SerialColumn.php index 2fdb770..6a875ae 100644 --- a/framework/yii/grid/SerialColumn.php +++ b/framework/yii/grid/SerialColumn.php @@ -15,6 +15,8 @@ namespace yii\grid; */ class SerialColumn extends Column { + public $header = '#'; + /** * Renders the data cell content. * @param mixed $model the data model From 9542fd24d4591ea392544952b26f9cfb9da6bf35 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 25 Sep 2013 12:58:27 +0200 Subject: [PATCH 132/157] try to fix: memcache testExpire fails randomly on travis issue #877 --- tests/unit/framework/caching/CacheTestCase.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php index 94894a3..dffcd67 100644 --- a/tests/unit/framework/caching/CacheTestCase.php +++ b/tests/unit/framework/caching/CacheTestCase.php @@ -13,6 +13,7 @@ function time() namespace yiiunit\framework\caching; +use yii\helpers\StringHelper; use yiiunit\TestCase; use yii\caching\Cache; @@ -147,7 +148,11 @@ abstract class CacheTestCase extends TestCase sleep(1); $this->assertEquals('expire_test', $cache->get('expire_test')); // wait a bit more than 2 sec to avoid random test failure - usleep(2500000); + if ($_ENV['TRAVIS'] && substr(StringHelper::basename(get_class($this)), 0, 8) == 'MemCache') { + sleep(3); // usleep with 2,5 seconds does not work well on travis and memcache + } else { + usleep(2500000); + } $this->assertFalse($cache->get('expire_test')); } From f3504f426dafb7b12e2f08238e15282cad3a29a6 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 25 Sep 2013 13:00:13 +0200 Subject: [PATCH 133/157] fix test fail when not on travis --- tests/unit/framework/caching/CacheTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php index dffcd67..480941f 100644 --- a/tests/unit/framework/caching/CacheTestCase.php +++ b/tests/unit/framework/caching/CacheTestCase.php @@ -148,7 +148,7 @@ abstract class CacheTestCase extends TestCase sleep(1); $this->assertEquals('expire_test', $cache->get('expire_test')); // wait a bit more than 2 sec to avoid random test failure - if ($_ENV['TRAVIS'] && substr(StringHelper::basename(get_class($this)), 0, 8) == 'MemCache') { + if (isset($_ENV['TRAVIS']) && substr(StringHelper::basename(get_class($this)), 0, 8) == 'MemCache') { sleep(3); // usleep with 2,5 seconds does not work well on travis and memcache } else { usleep(2500000); From 220db270cfd3efd0b6f89197de615f8354c18c70 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 25 Sep 2013 21:20:58 -0400 Subject: [PATCH 134/157] Improved crud generator. --- framework/yii/gii/generators/crud/templates/views/index.php | 8 +++----- framework/yii/gii/generators/crud/templates/views/view.php | 4 ++-- framework/yii/grid/DataColumn.php | 11 +++++++++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index df76581..1de548a 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -30,13 +30,11 @@ $this->params['breadcrumbs'][] = $this->title;

echo Html::encode($this->title); ?>

- echo $this->render('_search', array('model' => $searchModel)); ?> + indexWidgetType === 'grid' ? ' //' : ''); ?> echo $this->render('_search', array('model' => $searchModel)); ?> -
- -
+

echo Html::a('Create modelClass); ?>', array('create'), array('class' => 'btn btn-danger')); ?> -

+

indexWidgetType === 'grid'): ?> echo GridView::widget(array( diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php index a3b6cad..d25a851 100644 --- a/framework/yii/gii/generators/crud/templates/views/view.php +++ b/framework/yii/gii/generators/crud/templates/views/view.php @@ -29,14 +29,14 @@ $this->params['breadcrumbs'][] = $this->title;

echo Html::encode($this->title); ?>

-
+

echo Html::a('Update', array('update', ), array('class' => 'btn btn-danger')); ?> echo Html::a('Delete', array('delete', ), array( 'class' => 'btn btn-danger', 'data-confirm' => Yii::t('app', 'Are you sure to delete this item?'), 'data-method' => 'post', )); ?> -

+

echo DetailView::widget(array( 'model' => $model, diff --git a/framework/yii/grid/DataColumn.php b/framework/yii/grid/DataColumn.php index 295dece..4ebbb8f 100644 --- a/framework/yii/grid/DataColumn.php +++ b/framework/yii/grid/DataColumn.php @@ -68,6 +68,12 @@ class DataColumn extends Column * - If you don't want a filter for this data column, set this value to be false. */ public $filter; + /** + * @var array the HTML attributes for the filter input fields. This property is used in combination with + * the [[filter]] property. When [[filter]] is not set or is an array, this property will be used to + * render the HTML attributes for the generated filter input fields. + */ + public $filterInputOptions = array('class' => 'form-control'); protected function renderHeaderCellContent() @@ -111,9 +117,10 @@ class DataColumn extends Column return $this->filter; } elseif ($this->filter !== false && $this->grid->filterModel instanceof Model && $this->attribute !== null) { if (is_array($this->filter)) { - return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, array('prompt' => '')); + $options = array_merge(array('prompt' => ''), $this->filterInputOptions); + return Html::activeDropDownList($this->grid->filterModel, $this->attribute, $this->filter, $options); } else { - return Html::activeTextInput($this->grid->filterModel, $this->attribute); + return Html::activeTextInput($this->grid->filterModel, $this->attribute, $this->filterInputOptions); } } else { return parent::renderFilterCellContent(); From d9b256d73455ca771ca985efa7b23d7bfa8b7312 Mon Sep 17 00:00:00 2001 From: Jin Hu Date: Thu, 26 Sep 2013 20:29:59 +0800 Subject: [PATCH 135/157] Fixed pagination not working before data loaded --- framework/yii/data/DataProvider.php | 1 + tests/unit/framework/data/ActiveDataProviderTest.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/framework/yii/data/DataProvider.php b/framework/yii/data/DataProvider.php index b29f616..d75be6f 100644 --- a/framework/yii/data/DataProvider.php +++ b/framework/yii/data/DataProvider.php @@ -48,6 +48,7 @@ abstract class DataProvider extends Component implements DataProviderInterface if ($this->id !== null) { $this->_pagination->pageVar = $this->id . '-page'; } + $this->_pagination->totalCount = $this->getTotalCount(); } return $this->_pagination; } diff --git a/tests/unit/framework/data/ActiveDataProviderTest.php b/tests/unit/framework/data/ActiveDataProviderTest.php index 3f65ebb..79c0a39 100644 --- a/tests/unit/framework/data/ActiveDataProviderTest.php +++ b/tests/unit/framework/data/ActiveDataProviderTest.php @@ -85,4 +85,21 @@ class ActiveDataProviderTest extends DatabaseTestCase $provider->refresh(); $this->assertEquals(2, count($provider->getModels())); } + + public function testPaginationBeforeModels() + { + $query = new Query; + $provider = new ActiveDataProvider(array( + 'db' => $this->getConnection(), + 'query' => $query->from('tbl_order')->orderBy('id'), + )); + $pagination = $provider->getPagination(); + $this->assertEquals(1, $pagination->getPageCount()); + $this->assertCount(3, $provider->getModels()); + + $provider->getPagination()->pageSize = 2; + $this->assertEquals(3, count($provider->getModels())); + $provider->refresh(); + $this->assertEquals(2, count($provider->getModels())); + } } From b7948e48406f21a059431a619c901b3cd88a8ec5 Mon Sep 17 00:00:00 2001 From: Jin Hu Date: Thu, 26 Sep 2013 23:35:02 +0800 Subject: [PATCH 136/157] Removed unnecessary lines --- framework/yii/data/ActiveDataProvider.php | 1 - framework/yii/data/ArrayDataProvider.php | 1 - 2 files changed, 2 deletions(-) diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php index aaf71b2..2fe0efb 100644 --- a/framework/yii/data/ActiveDataProvider.php +++ b/framework/yii/data/ActiveDataProvider.php @@ -156,7 +156,6 @@ class ActiveDataProvider extends DataProvider throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.'); } if (($pagination = $this->getPagination()) !== false) { - $pagination->totalCount = $this->getTotalCount(); $this->query->limit($pagination->getLimit())->offset($pagination->getOffset()); } if (($sort = $this->getSort()) !== false) { diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php index d6eaca7..9534803 100644 --- a/framework/yii/data/ArrayDataProvider.php +++ b/framework/yii/data/ArrayDataProvider.php @@ -114,7 +114,6 @@ class ArrayDataProvider extends DataProvider } if (($pagination = $this->getPagination()) !== false) { - $pagination->totalCount = $this->getTotalCount(); $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); } From 45778a4bd7037e2797f29bc80d1c09cedbefffeb Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 27 Sep 2013 20:36:47 +0400 Subject: [PATCH 137/157] Added ->send() to redirect phpdoc example --- framework/yii/web/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index e6505fd..b353e7c 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -581,7 +581,7 @@ class Response extends \yii\base\Response * In a controller action you may use this method like this: * * ~~~ - * return Yii::$app->getResponse()->redirect($url); + * return Yii::$app->getResponse()->redirect($url)->send(); * ~~~ * * @param string|array $url the URL to be redirected to. This can be in one of the following formats: From 3e94fb479f1bb8cfe8669064e0a1489e3a508d88 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 27 Sep 2013 20:51:08 +0400 Subject: [PATCH 138/157] Fixed phpdoc --- framework/yii/helpers/Console.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/framework/yii/helpers/Console.php b/framework/yii/helpers/Console.php index eeadcbc..c0b7b32 100644 --- a/framework/yii/helpers/Console.php +++ b/framework/yii/helpers/Console.php @@ -8,11 +8,8 @@ namespace yii\helpers; /** - * TODO adjust phpdoc - * Console View is the base class for console view components - * - * A console view provides functionality to create rich console application by allowing to format output - * by adding color and font style to it. + * Console helper provides useful methods for command line related tasks such as getting input or formatting and coloring + * output. * * @author Carsten Brandt * @since 2.0 From 907d24fb9e07b2a4fdf344a76e984cfb7f734d97 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 01:01:49 +0400 Subject: [PATCH 139/157] Advanced application: fixed console return codes --- apps/advanced/environments/dev/yii | 3 ++- apps/advanced/environments/prod/yii | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/advanced/environments/dev/yii b/apps/advanced/environments/dev/yii index 9a257d7..e7d5f6c 100644 --- a/apps/advanced/environments/dev/yii +++ b/apps/advanced/environments/dev/yii @@ -24,4 +24,5 @@ $config = yii\helpers\ArrayHelper::merge( ); $application = new yii\console\Application($config); -return $application->run(); +$exitCode = $application->run(); +exit($exitCode); diff --git a/apps/advanced/environments/prod/yii b/apps/advanced/environments/prod/yii index 7f24a70..9e13eb2 100644 --- a/apps/advanced/environments/prod/yii +++ b/apps/advanced/environments/prod/yii @@ -24,4 +24,5 @@ $config = yii\helpers\ArrayHelper::merge( ); $application = new yii\console\Application($config); -return $application->run(); +$exitCode = $application->run(); +exit($exitCode); From 4ab2e986c6ccf6f1d89db1971e353b0c32d8df6d Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 01:10:38 +0400 Subject: [PATCH 140/157] Advanced application: init script can now be executed in non-interactive input mode Usage: init --env=Development init --env=Production --- apps/advanced/README.md | 3 ++- apps/advanced/init | 72 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index 6860c11..cdc10eb 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -103,7 +103,8 @@ GETTING STARTED After you install the application, you have to conduct the following steps to initialize the installed application. You only need to do these once for all. -1. Execute the `init` command and select `dev` as environment. +1. Execute the `init` command and select `dev` as environment. Alternatively you can execute it as `init --env=Development` +or `init --env=Production`. 2. Create a new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. 3. In `common/config/params.php` set your database details in `components.db` values. diff --git a/apps/advanced/init b/apps/advanced/init index 17ed854..3a8f6a6 100755 --- a/apps/advanced/init +++ b/apps/advanced/init @@ -1,27 +1,49 @@ #!/usr/bin/env php $name) { - echo " [$i] $name\n"; +echo "Yii Application Initialization Tool v1.0\n\n"; + +$envName = null; +if (empty($params['env'])) { + echo "Which environment do you want the application to be initialized in?\n\n"; + foreach ($envNames as $i => $name) { + echo " [$i] $name\n"; + } + echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; + $answer = trim(fgets(STDIN)); + + if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) { + echo "\n Quit initialization.\n"; + exit(1); + } + + if(isset($envNames[$answer])) { + $envName = $envNames[$answer]; + } +} +else { + $envName = $params['env']; } -echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; -$answer = trim(fgets(STDIN)); -if (!ctype_digit($answer) || !isset($envNames[$answer])) { - echo "\n Quit initialization.\n"; - return; + +if (!in_array($envName, $envNames)) { + $envsList = implode(', ', $envNames); + echo "\n $envName is not a valid environment. Try one of the following: $envsList. \n"; + exit(2); } -$env = $envs[$envNames[$answer]]; -echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; -$answer = trim(fgets(STDIN)); -if (strncasecmp($answer, 'y', 1)) { - echo "\n Quit initialization.\n"; - return; +$env = $envs[$envName]; + +if (empty($params['env'])) { + echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; + $answer = trim(fgets(STDIN)); + if (strncasecmp($answer, 'y', 1)) { + echo "\n Quit initialization.\n"; + exit(1); + } } echo "\n Start initialization ...\n\n"; @@ -110,3 +132,23 @@ function copyFile($root, $source, $target, &$all) file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); return true; } + +function getParams() +{ + $rawParams = array(); + if (isset($_SERVER['argv'])) { + $rawParams = $_SERVER['argv']; + array_shift($rawParams); + } + + $params = array(); + foreach ($rawParams as $param) { + if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { + $name = $matches[1]; + $params[$name] = isset($matches[3]) ? $matches[3] : true; + } else { + $params[] = $param; + } + } + return $params; +} From c4354d6be688f6323980c1d37c84fcfee86dfbc7 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 01:14:19 +0400 Subject: [PATCH 141/157] Advanced application: init is now automatically called when installing via Composer --- apps/advanced/composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index 2d5b987..4c0fced 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -20,7 +20,8 @@ }, "scripts": { "post-create-project-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" + "yii\\composer\\InstallHandler::setPermissions", + "./init" ] }, "extra": { From abd775e5a58ee641866d1aae201445faa78222a8 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 01:27:00 +0400 Subject: [PATCH 142/157] fixed typos --- extensions/composer/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/composer/README.md b/extensions/composer/README.md index 986fc6c..853d3c3 100644 --- a/extensions/composer/README.md +++ b/extensions/composer/README.md @@ -17,11 +17,11 @@ This is the yii2 composer installer. Installation ------------ -This extension offers you enhanced composer handling for your yii2-project. It will therefor require you to use composer. +This extension offers you enhanced Composer handling for your yii2-project. It will therefore require you to use Composer. -` +``` php composer.phar require yiisoft/yii2-composer "*" -` +``` *Note: You might have to run `php composer.phar selfupdate` before using this extension.* @@ -29,9 +29,9 @@ php composer.phar require yiisoft/yii2-composer "*" Usage & Documentation --------------------- -This extensions allows you to hook to certain composer events and prepare your yii2-app for usage. +This extension allows you to hook to certain composer events and automate preparing your Yii2 application for further usage. -After the package is installed, the composer.json file has to be modified to enable this extension. +After the package is installed, the `composer.json` file has to be modified to enable this extension. To see it in action take a look at the example apps in the repository: From f8ddb3d7be0c14ae362a1637baa9fee21042f3d5 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 01:27:23 +0400 Subject: [PATCH 143/157] Advanced application: added applying migrations to readme --- apps/advanced/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index cdc10eb..3903532 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -107,6 +107,7 @@ the installed application. You only need to do these once for all. or `init --env=Production`. 2. Create a new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. 3. In `common/config/params.php` set your database details in `components.db` values. +4. Apply migrations with `yii migrate`. Now you should be able to access: From 76cab3ea6449ef90bbfade7683b1c83aa2336a35 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 02:01:06 +0400 Subject: [PATCH 144/157] Renamed base helper classes --- framework/yii/classes.php | 22 +- framework/yii/helpers/AbstractArrayHelper.php | 451 +++++++ framework/yii/helpers/AbstractConsole.php | 835 +++++++++++++ framework/yii/helpers/AbstractFileHelper.php | 329 +++++ framework/yii/helpers/AbstractHtml.php | 1599 ++++++++++++++++++++++++ framework/yii/helpers/AbstractHtmlPurifier.php | 34 + framework/yii/helpers/AbstractInflector.php | 480 +++++++ framework/yii/helpers/AbstractJson.php | 112 ++ framework/yii/helpers/AbstractMarkdown.php | 44 + framework/yii/helpers/AbstractSecurity.php | 285 +++++ framework/yii/helpers/AbstractStringHelper.php | 138 ++ framework/yii/helpers/AbstractVarDumper.php | 127 ++ framework/yii/helpers/ArrayHelper.php | 2 +- framework/yii/helpers/ArrayHelperBase.php | 451 ------- framework/yii/helpers/Console.php | 2 +- framework/yii/helpers/ConsoleBase.php | 835 ------------- framework/yii/helpers/FileHelper.php | 2 +- framework/yii/helpers/FileHelperBase.php | 329 ----- framework/yii/helpers/Html.php | 2 +- framework/yii/helpers/HtmlBase.php | 1599 ------------------------ framework/yii/helpers/HtmlPurifier.php | 2 +- framework/yii/helpers/HtmlPurifierBase.php | 34 - framework/yii/helpers/Inflector.php | 2 +- framework/yii/helpers/InflectorBase.php | 480 ------- framework/yii/helpers/Json.php | 2 +- framework/yii/helpers/JsonBase.php | 112 -- framework/yii/helpers/Markdown.php | 2 +- framework/yii/helpers/MarkdownBase.php | 44 - framework/yii/helpers/Security.php | 2 +- framework/yii/helpers/SecurityBase.php | 285 ----- framework/yii/helpers/StringHelper.php | 2 +- framework/yii/helpers/StringHelperBase.php | 138 -- framework/yii/helpers/VarDumper.php | 2 +- framework/yii/helpers/VarDumperBase.php | 127 -- 34 files changed, 4456 insertions(+), 4456 deletions(-) create mode 100644 framework/yii/helpers/AbstractArrayHelper.php create mode 100644 framework/yii/helpers/AbstractConsole.php create mode 100644 framework/yii/helpers/AbstractFileHelper.php create mode 100644 framework/yii/helpers/AbstractHtml.php create mode 100644 framework/yii/helpers/AbstractHtmlPurifier.php create mode 100644 framework/yii/helpers/AbstractInflector.php create mode 100644 framework/yii/helpers/AbstractJson.php create mode 100644 framework/yii/helpers/AbstractMarkdown.php create mode 100644 framework/yii/helpers/AbstractSecurity.php create mode 100644 framework/yii/helpers/AbstractStringHelper.php create mode 100644 framework/yii/helpers/AbstractVarDumper.php delete mode 100644 framework/yii/helpers/ArrayHelperBase.php delete mode 100644 framework/yii/helpers/ConsoleBase.php delete mode 100644 framework/yii/helpers/FileHelperBase.php delete mode 100644 framework/yii/helpers/HtmlBase.php delete mode 100644 framework/yii/helpers/HtmlPurifierBase.php delete mode 100644 framework/yii/helpers/InflectorBase.php delete mode 100644 framework/yii/helpers/JsonBase.php delete mode 100644 framework/yii/helpers/MarkdownBase.php delete mode 100644 framework/yii/helpers/SecurityBase.php delete mode 100644 framework/yii/helpers/StringHelperBase.php delete mode 100644 framework/yii/helpers/VarDumperBase.php diff --git a/framework/yii/classes.php b/framework/yii/classes.php index 40ca225..22472d7 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -125,27 +125,27 @@ return array( 'yii\grid\GridViewAsset' => YII_PATH . '/grid/GridViewAsset.php', 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php', 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php', - 'yii\helpers\ArrayHelperBase' => YII_PATH . '/helpers/ArrayHelperBase.php', + 'yii\helpers\AbstractArrayHelper' => YII_PATH . '/helpers/AbstractArrayHelper.php', 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php', - 'yii\helpers\ConsoleBase' => YII_PATH . '/helpers/ConsoleBase.php', + 'yii\helpers\AbstractConsole' => YII_PATH . '/helpers/AbstractConsole.php', 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php', - 'yii\helpers\FileHelperBase' => YII_PATH . '/helpers/FileHelperBase.php', + 'yii\helpers\AbstractFileHelper' => YII_PATH . '/helpers/AbstractFileHelper.php', 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php', - 'yii\helpers\HtmlBase' => YII_PATH . '/helpers/HtmlBase.php', + 'yii\helpers\AbstractHtml' => YII_PATH . '/helpers/AbstractHtml.php', 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php', - 'yii\helpers\HtmlPurifierBase' => YII_PATH . '/helpers/HtmlPurifierBase.php', + 'yii\helpers\AbstractHtmlPurifier' => YII_PATH . '/helpers/AbstractHtmlPurifier.php', 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php', - 'yii\helpers\InflectorBase' => YII_PATH . '/helpers/InflectorBase.php', + 'yii\helpers\AbstractInflector' => YII_PATH . '/helpers/AbstractInflector.php', 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php', - 'yii\helpers\JsonBase' => YII_PATH . '/helpers/JsonBase.php', + 'yii\helpers\AbstractJson' => YII_PATH . '/helpers/AbstractJson.php', 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php', - 'yii\helpers\MarkdownBase' => YII_PATH . '/helpers/MarkdownBase.php', + 'yii\helpers\AbstractMarkdown' => YII_PATH . '/helpers/AbstractMarkdown.php', 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php', - 'yii\helpers\SecurityBase' => YII_PATH . '/helpers/SecurityBase.php', + 'yii\helpers\AbstractSecurity' => YII_PATH . '/helpers/AbstractSecurity.php', 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php', - 'yii\helpers\StringHelperBase' => YII_PATH . '/helpers/StringHelperBase.php', + 'yii\helpers\AbstractStringHelper' => YII_PATH . '/helpers/AbstractStringHelper.php', 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php', - 'yii\helpers\VarDumperBase' => YII_PATH . '/helpers/VarDumperBase.php', + 'yii\helpers\AbstractVarDumper' => YII_PATH . '/helpers/AbstractVarDumper.php', 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php', 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php', 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php', diff --git a/framework/yii/helpers/AbstractArrayHelper.php b/framework/yii/helpers/AbstractArrayHelper.php new file mode 100644 index 0000000..c26c1cd --- /dev/null +++ b/framework/yii/helpers/AbstractArrayHelper.php @@ -0,0 +1,451 @@ + + * @since 2.0 + */ +abstract class AbstractArrayHelper +{ + /** + * Converts an object or an array of objects into an array. + * @param object|array $object the object to be converted into an array + * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays. + * The properties specified for each class is an array of the following format: + * + * ~~~ + * array( + * 'app\models\Post' => array( + * 'id', + * 'title', + * // the key name in array result => property name + * 'createTime' => 'create_time', + * // the key name in array result => anonymous function + * 'length' => function ($post) { + * return strlen($post->content); + * }, + * ), + * ) + * ~~~ + * + * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: + * + * ~~~ + * array( + * 'id' => 123, + * 'title' => 'test', + * 'createTime' => '2013-01-01 12:00AM', + * 'length' => 301, + * ) + * ~~~ + * + * @param boolean $recursive whether to recursively converts properties which are objects into arrays. + * @return array the array representation of the object + */ + public static function toArray($object, $properties = array(), $recursive = true) + { + if (!empty($properties) && is_object($object)) { + $className = get_class($object); + if (!empty($properties[$className])) { + $result = array(); + foreach ($properties[$className] as $key => $name) { + if (is_int($key)) { + $result[$name] = $object->$name; + } else { + $result[$key] = static::getValue($object, $name); + } + } + return $result; + } + } + if ($object instanceof Arrayable) { + $object = $object->toArray(); + if (!$recursive) { + return $object; + } + } + $result = array(); + foreach ($object as $key => $value) { + if ($recursive && (is_array($value) || is_object($value))) { + $result[$key] = static::toArray($value, true); + } else { + $result[$key] = $value; + } + } + return $result; + } + + /** + * Merges two or more arrays into one recursively. + * If each array has an element with the same string key value, the latter + * will overwrite the former (different from array_merge_recursive). + * Recursive merging will be conducted if both arrays have an element of array + * type and are having the same key. + * For integer-keyed elements, the elements from the latter array will + * be appended to the former array. + * @param array $a array to be merged to + * @param array $b array to be merged from. You can specify additional + * arrays via third argument, fourth argument etc. + * @return array the merged array (the original arrays are not changed.) + */ + public static function merge($a, $b) + { + $args = func_get_args(); + $res = array_shift($args); + while (!empty($args)) { + $next = array_shift($args); + foreach ($next as $k => $v) { + if (is_integer($k)) { + isset($res[$k]) ? $res[] = $v : $res[$k] = $v; + } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { + $res[$k] = self::merge($res[$k], $v); + } else { + $res[$k] = $v; + } + } + } + return $res; + } + + /** + * Retrieves the value of an array element or object property with the given key or property name. + * If the key does not exist in the array, the default value will be returned instead. + * + * Below are some usage examples, + * + * ~~~ + * // working with array + * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); + * // working with object + * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); + * // working with anonymous function + * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) { + * return $user->firstName . ' ' . $user->lastName; + * }); + * ~~~ + * + * @param array|object $array array or object to extract value from + * @param string|\Closure $key key name of the array element, or property name of the object, + * or an anonymous function returning the value. The anonymous function signature should be: + * `function($array, $defaultValue)`. + * @param mixed $default the default value to be returned if the specified key does not exist + * @return mixed the value of the element if found, default value otherwise + */ + public static function getValue($array, $key, $default = null) + { + if ($key instanceof \Closure) { + return $key($array, $default); + } elseif (is_array($array)) { + return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default; + } else { + return $array->$key; + } + } + + /** + * Removes an item from an array and returns the value. If the key does not exist in the array, the default value + * will be returned instead. + * + * Usage examples, + * + * ~~~ + * // $array = array('type' => 'A', 'options' => array(1, 2)); + * // working with array + * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); + * // $array content + * // $array = array('options' => array(1, 2)); + * ~~~ + * + * @param array $array the array to extract value from + * @param string $key key name of the array element + * @param mixed $default the default value to be returned if the specified key does not exist + * @return mixed|null the value of the element if found, default value otherwise + */ + public static function remove(&$array, $key, $default = null) + { + if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { + $value = $array[$key]; + unset($array[$key]); + return $value; + } + return $default; + } + + /** + * Indexes an array according to a specified key. + * The input array should be multidimensional or an array of objects. + * + * The key can be a key name of the sub-array, a property name of object, or an anonymous + * function which returns the key value given an array element. + * + * If a key value is null, the corresponding array element will be discarded and not put in the result. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'data' => 'abc'), + * array('id' => '345', 'data' => 'def'), + * ); + * $result = ArrayHelper::index($array, 'id'); + * // the result is: + * // array( + * // '123' => array('id' => '123', 'data' => 'abc'), + * // '345' => array('id' => '345', 'data' => 'def'), + * // ) + * + * // using anonymous function + * $result = ArrayHelper::index($array, function ($element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array the array that needs to be indexed + * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array + * @return array the indexed array + */ + public static function index($array, $key) + { + $result = array(); + foreach ($array as $element) { + $value = static::getValue($element, $key); + $result[$value] = $element; + } + return $result; + } + + /** + * Returns the values of a specified column in an array. + * The input array should be multidimensional or an array of objects. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'data' => 'abc'), + * array('id' => '345', 'data' => 'def'), + * ); + * $result = ArrayHelper::getColumn($array, 'id'); + * // the result is: array( '123', '345') + * + * // using anonymous function + * $result = ArrayHelper::getColumn($array, function ($element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array + * @param string|\Closure $name + * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array + * will be re-indexed with integers. + * @return array the list of column values + */ + public static function getColumn($array, $name, $keepKeys = true) + { + $result = array(); + if ($keepKeys) { + foreach ($array as $k => $element) { + $result[$k] = static::getValue($element, $name); + } + } else { + foreach ($array as $element) { + $result[] = static::getValue($element, $name); + } + } + + return $result; + } + + /** + * Builds a map (key-value pairs) from a multidimensional array or an array of objects. + * The `$from` and `$to` parameters specify the key names or property names to set up the map. + * Optionally, one can further group the map according to a grouping field `$group`. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), + * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), + * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), + * ); + * + * $result = ArrayHelper::map($array, 'id', 'name'); + * // the result is: + * // array( + * // '123' => 'aaa', + * // '124' => 'bbb', + * // '345' => 'ccc', + * // ) + * + * $result = ArrayHelper::map($array, 'id', 'name', 'class'); + * // the result is: + * // array( + * // 'x' => array( + * // '123' => 'aaa', + * // '124' => 'bbb', + * // ), + * // 'y' => array( + * // '345' => 'ccc', + * // ), + * // ) + * ~~~ + * + * @param array $array + * @param string|\Closure $from + * @param string|\Closure $to + * @param string|\Closure $group + * @return array + */ + public static function map($array, $from, $to, $group = null) + { + $result = array(); + foreach ($array as $element) { + $key = static::getValue($element, $from); + $value = static::getValue($element, $to); + if ($group !== null) { + $result[static::getValue($element, $group)][$key] = $value; + } else { + $result[$key] = $value; + } + } + return $result; + } + + /** + * Sorts an array of objects or arrays (with the same structure) by one or several keys. + * @param array $array the array to be sorted. The array will be modified after calling this method. + * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array + * elements, a property name of the objects, or an anonymous function returning the values for comparison + * purpose. The anonymous function signature should be: `function($item)`. + * To sort by multiple keys, provide an array of keys here. + * @param boolean|array $descending whether to sort in descending or ascending order. When + * sorting by multiple keys with different descending orders, use an array of descending flags. + * @param integer|array $sortFlag the PHP sort flag. Valid values include + * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`. + * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php) + * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags. + * @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter + * is used only when `$sortFlag` is `SORT_STRING`. + * When sorting by multiple keys with different case sensitivities, use an array of boolean values. + * @throws InvalidParamException if the $descending or $sortFlag parameters do not have + * correct number of elements as that of $key. + */ + public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true) + { + $keys = is_array($key) ? $key : array($key); + if (empty($keys) || empty($array)) { + return; + } + $n = count($keys); + if (is_scalar($descending)) { + $descending = array_fill(0, $n, $descending); + } elseif (count($descending) !== $n) { + throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); + } + if (is_scalar($sortFlag)) { + $sortFlag = array_fill(0, $n, $sortFlag); + } elseif (count($sortFlag) !== $n) { + throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.'); + } + if (is_scalar($caseSensitive)) { + $caseSensitive = array_fill(0, $n, $caseSensitive); + } elseif (count($caseSensitive) !== $n) { + throw new InvalidParamException('The length of $caseSensitive parameter must be the same as that of $keys.'); + } + $args = array(); + foreach ($keys as $i => $key) { + $flag = $sortFlag[$i]; + $cs = $caseSensitive[$i]; + if (!$cs && ($flag === SORT_STRING)) { + if (defined('SORT_FLAG_CASE')) { + $flag = $flag | SORT_FLAG_CASE; + $args[] = static::getColumn($array, $key); + } else { + $column = array(); + foreach (static::getColumn($array, $key) as $k => $value) { + $column[$k] = mb_strtolower($value); + } + $args[] = $column; + } + } else { + $args[] = static::getColumn($array, $key); + } + $args[] = $descending[$i] ? SORT_DESC : SORT_ASC; + $args[] = $flag; + } + $args[] = &$array; + call_user_func_array('array_multisort', $args); + } + + /** + * Encodes special characters in an array of strings into HTML entities. + * Both the array keys and values will be encoded. + * If a value is an array, this method will also encode it recursively. + * @param array $data data to be encoded + * @param boolean $valuesOnly whether to encode array values only. If false, + * both the array keys and array values will be encoded. + * @param string $charset the charset that the data is using. If not set, + * [[\yii\base\Application::charset]] will be used. + * @return array the encoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function htmlEncode($data, $valuesOnly = true, $charset = null) + { + if ($charset === null) { + $charset = Yii::$app->charset; + } + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars($key, ENT_QUOTES, $charset); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); + } elseif (is_array($value)) { + $d[$key] = static::htmlEncode($value, $charset); + } + } + return $d; + } + + /** + * Decodes HTML entities into the corresponding characters in an array of strings. + * Both the array keys and values will be decoded. + * If a value is an array, this method will also decode it recursively. + * @param array $data data to be decoded + * @param boolean $valuesOnly whether to decode array values only. If false, + * both the array keys and array values will be decoded. + * @return array the decoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function htmlDecode($data, $valuesOnly = true) + { + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars_decode($key, ENT_QUOTES); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); + } elseif (is_array($value)) { + $d[$key] = static::htmlDecode($value); + } + } + return $d; + } +} diff --git a/framework/yii/helpers/AbstractConsole.php b/framework/yii/helpers/AbstractConsole.php new file mode 100644 index 0000000..8131aae --- /dev/null +++ b/framework/yii/helpers/AbstractConsole.php @@ -0,0 +1,835 @@ + + * @since 2.0 + */ +abstract class AbstractConsole +{ + const FG_BLACK = 30; + const FG_RED = 31; + const FG_GREEN = 32; + const FG_YELLOW = 33; + const FG_BLUE = 34; + const FG_PURPLE = 35; + const FG_CYAN = 36; + const FG_GREY = 37; + + const BG_BLACK = 40; + const BG_RED = 41; + const BG_GREEN = 42; + const BG_YELLOW = 43; + const BG_BLUE = 44; + const BG_PURPLE = 45; + const BG_CYAN = 46; + const BG_GREY = 47; + + const RESET = 0; + const NORMAL = 0; + const BOLD = 1; + const ITALIC = 3; + const UNDERLINE = 4; + const BLINK = 5; + const NEGATIVE = 7; + const CONCEALED = 8; + const CROSSED_OUT = 9; + const FRAMED = 51; + const ENCIRCLED = 52; + const OVERLINED = 53; + + /** + * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved up + */ + public static function moveCursorUp($rows = 1) + { + echo "\033[" . (int)$rows . 'A'; + } + + /** + * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved down + */ + public static function moveCursorDown($rows = 1) + { + echo "\033[" . (int)$rows . 'B'; + } + + /** + * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved forward + */ + public static function moveCursorForward($steps = 1) + { + echo "\033[" . (int)$steps . 'C'; + } + + /** + * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved backward + */ + public static function moveCursorBackward($steps = 1) + { + echo "\033[" . (int)$steps . 'D'; + } + + /** + * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. + * @param integer $lines number of lines the cursor should be moved down + */ + public static function moveCursorNextLine($lines = 1) + { + echo "\033[" . (int)$lines . 'E'; + } + + /** + * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. + * @param integer $lines number of lines the cursor should be moved up + */ + public static function moveCursorPrevLine($lines = 1) + { + echo "\033[" . (int)$lines . 'F'; + } + + /** + * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. + * @param integer $column 1-based column number, 1 is the left edge of the screen. + * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. + */ + public static function moveCursorTo($column, $row = null) + { + if ($row === null) { + echo "\033[" . (int)$column . 'G'; + } else { + echo "\033[" . (int)$row . ';' . (int)$column . 'H'; + } + } + + /** + * Scrolls whole page up by sending ANSI control code SU to the terminal. + * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. + * @param int $lines number of lines to scroll up + */ + public static function scrollUp($lines = 1) + { + echo "\033[" . (int)$lines . "S"; + } + + /** + * Scrolls whole page down by sending ANSI control code SD to the terminal. + * New lines are added at the top. This is not supported by ANSI.SYS used in windows. + * @param int $lines number of lines to scroll down + */ + public static function scrollDown($lines = 1) + { + echo "\033[" . (int)$lines . "T"; + } + + /** + * Saves the current cursor position by sending ANSI control code SCP to the terminal. + * Position can then be restored with {@link restoreCursorPosition}. + */ + public static function saveCursorPosition() + { + echo "\033[s"; + } + + /** + * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. + */ + public static function restoreCursorPosition() + { + echo "\033[u"; + } + + /** + * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. + * Use {@link showCursor} to bring it back. + * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. + */ + public static function hideCursor() + { + echo "\033[?25l"; + } + + /** + * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. + */ + public static function showCursor() + { + echo "\033[?25h"; + } + + /** + * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. + * Cursor position will not be changed. + * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. + */ + public static function clearScreen() + { + echo "\033[2J"; + } + + /** + * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenBeforeCursor() + { + echo "\033[1J"; + } + + /** + * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenAfterCursor() + { + echo "\033[0J"; + } + + /** + * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLine() + { + echo "\033[2K"; + } + + /** + * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineBeforeCursor() + { + echo "\033[1K"; + } + + /** + * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineAfterCursor() + { + echo "\033[0K"; + } + + /** + * Returns the ANSI format code. + * + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @return string The ANSI format code according to the given formatting constants. + */ + public static function ansiFormatCode($format) + { + return "\033[" . implode(';', $format) . 'm'; + } + + /** + * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards. + * + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @see ansiFormatCode() + * @see ansiFormatEnd() + */ + public static function beginAnsiFormat($format) + { + echo "\033[" . implode(';', $format) . 'm'; + } + + /** + * Resets any ANSI format set by previous method [[ansiFormatBegin()]] + * Any output after this will have default text format. + * This is equal to calling + * + * ```php + * echo Console::ansiFormatCode(array(Console::RESET)) + * ``` + */ + public static function endAnsiFormat() + { + echo "\033[0m"; + } + + /** + * Will return a string formatted with the given ANSI style + * + * @param string $string the string to be formatted + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @return string + */ + public static function ansiFormat($string, $format = array()) + { + $code = implode(';', $format); + return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; + } + + /** + * Returns the ansi format code for xterm foreground color. + * You can pass the return value of this to one of the formatting methods: + * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] + * + * @param integer $colorCode xterm color code + * @return string + * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + */ + public static function xtermFgColor($colorCode) + { + return '38;5;' . $colorCode; + } + + /** + * Returns the ansi format code for xterm background color. + * You can pass the return value of this to one of the formatting methods: + * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] + * + * @param integer $colorCode xterm color code + * @return string + * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + */ + public static function xtermBgColor($colorCode) + { + return '48;5;' . $colorCode; + } + + /** + * Strips ANSI control codes from a string + * + * @param string $string String to strip + * @return string + */ + public static function stripAnsiFormat($string) + { + return preg_replace('/\033\[[\d;?]*\w/', '', $string); + } + + /** + * Converts an ANSI formatted string to HTML + * @param $string + * @return mixed + */ + // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 + public static function ansiToHtml($string) + { + $tags = 0; + return preg_replace_callback( + '/\033\[[\d;]+m/', + function ($ansi) use (&$tags) { + $styleA = array(); + foreach (explode(';', $ansi) as $controlCode) { + switch ($controlCode) { + case self::FG_BLACK: + $style = array('color' => '#000000'); + break; + case self::FG_BLUE: + $style = array('color' => '#000078'); + break; + case self::FG_CYAN: + $style = array('color' => '#007878'); + break; + case self::FG_GREEN: + $style = array('color' => '#007800'); + break; + case self::FG_GREY: + $style = array('color' => '#787878'); + break; + case self::FG_PURPLE: + $style = array('color' => '#780078'); + break; + case self::FG_RED: + $style = array('color' => '#780000'); + break; + case self::FG_YELLOW: + $style = array('color' => '#787800'); + break; + case self::BG_BLACK: + $style = array('background-color' => '#000000'); + break; + case self::BG_BLUE: + $style = array('background-color' => '#000078'); + break; + case self::BG_CYAN: + $style = array('background-color' => '#007878'); + break; + case self::BG_GREEN: + $style = array('background-color' => '#007800'); + break; + case self::BG_GREY: + $style = array('background-color' => '#787878'); + break; + case self::BG_PURPLE: + $style = array('background-color' => '#780078'); + break; + case self::BG_RED: + $style = array('background-color' => '#780000'); + break; + case self::BG_YELLOW: + $style = array('background-color' => '#787800'); + break; + case self::BOLD: + $style = array('font-weight' => 'bold'); + break; + case self::ITALIC: + $style = array('font-style' => 'italic'); + break; + case self::UNDERLINE: + $style = array('text-decoration' => array('underline')); + break; + case self::OVERLINED: + $style = array('text-decoration' => array('overline')); + break; + case self::CROSSED_OUT: + $style = array('text-decoration' => array('line-through')); + break; + case self::BLINK: + $style = array('text-decoration' => array('blink')); + break; + case self::NEGATIVE: // ??? + case self::CONCEALED: + case self::ENCIRCLED: + case self::FRAMED: + // TODO allow resetting codes + break; + case 0: // ansi reset + $return = ''; + for ($n = $tags; $tags > 0; $tags--) { + $return .= ''; + } + return $return; + } + + $styleA = ArrayHelper::merge($styleA, $style); + } + $styleString[] = array(); + foreach ($styleA as $name => $content) { + if ($name === 'text-decoration') { + $content = implode(' ', $content); + } + $styleString[] = $name . ':' . $content; + } + $tags++; + return ' $value) { + echo " $key - $value\n"; + } + echo " ? - Show help\n"; + goto top; + } elseif (!in_array($input, array_keys($options))) { + goto top; + } + return $input; + } + + /** + * Displays and updates a simple progress bar on screen. + * + * @param integer $done the number of items that are completed + * @param integer $total the total value of items that are to be done + * @param integer $size the size of the status bar (optional) + * @see http://snipplr.com/view/29548/ + */ + public static function showProgress($done, $total, $size = 30) + { + static $start; + + // if we go over our bound, just ignore it + if ($done > $total) { + return; + } + + if (empty($start)) { + $start = time(); + } + + $now = time(); + + $percent = (double)($done / $total); + $bar = floor($percent * $size); + + $status = "\r["; + $status .= str_repeat("=", $bar); + if ($bar < $size) { + $status .= ">"; + $status .= str_repeat(" ", $size - $bar); + } else { + $status .= "="; + } + + $display = number_format($percent * 100, 0); + + $status .= "] $display% $done/$total"; + + $rate = ($now - $start) / $done; + $left = $total - $done; + $eta = round($rate * $left, 2); + + $elapsed = $now - $start; + + $status .= " remaining: " . number_format($eta) . " sec. elapsed: " . number_format($elapsed) . " sec."; + + static::stdout("$status "); + + flush(); + + // when done, send a newline + if ($done == $total) { + echo "\n"; + } + } +} diff --git a/framework/yii/helpers/AbstractFileHelper.php b/framework/yii/helpers/AbstractFileHelper.php new file mode 100644 index 0000000..5eab927 --- /dev/null +++ b/framework/yii/helpers/AbstractFileHelper.php @@ -0,0 +1,329 @@ + + * @author Alex Makarov + * @since 2.0 + */ +abstract class AbstractFileHelper +{ + /** + * Normalizes a file/directory path. + * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`, + * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux + * will be normalized as '/home/demo'. + * @param string $path the file/directory path to be normalized + * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`. + * @return string the normalized file/directory path + */ + public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) + { + return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds); + } + + /** + * Returns the localized version of a specified file. + * + * The searching is based on the specified language code. In particular, + * a file with the same name will be looked for under the subdirectory + * whose name is the same as the language code. For example, given the file "path/to/view.php" + * and language code "zh_CN", the localized file will be looked for as + * "path/to/zh_CN/view.php". If the file is not found, the original file + * will be returned. + * + * If the target and the source language codes are the same, + * the original file will be returned. + * + * @param string $file the original file + * @param string $language the target language that the file should be localized to. + * If not set, the value of [[\yii\base\Application::language]] will be used. + * @param string $sourceLanguage the language that the original file is in. + * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. + * @return string the matching localized file, or the original file if the localized version is not found. + * If the target and the source language codes are the same, the original file will be returned. + */ + public static function localize($file, $language = null, $sourceLanguage = null) + { + if ($language === null) { + $language = Yii::$app->language; + } + if ($sourceLanguage === null) { + $sourceLanguage = Yii::$app->sourceLanguage; + } + if ($language === $sourceLanguage) { + return $file; + } + $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file); + return is_file($desiredFile) ? $desiredFile : $file; + } + + /** + * Determines the MIME type of the specified file. + * This method will first try to determine the MIME type based on + * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will + * fall back to [[getMimeTypeByExtension()]]. + * @param string $file the file name. + * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`. + * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php). + * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case + * `finfo_open()` cannot determine it. + * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. + */ + public static function getMimeType($file, $magicFile = null, $checkExtension = true) + { + if (function_exists('finfo_open')) { + $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); + if ($info) { + $result = finfo_file($info, $file); + finfo_close($info); + if ($result !== false) { + return $result; + } + } + } + + return $checkExtension ? static::getMimeTypeByExtension($file) : null; + } + + /** + * Determines the MIME type based on the extension name of the specified file. + * This method will use a local map between extension names and MIME types. + * @param string $file the file name. + * @param string $magicFile the path of the file that contains all available MIME type information. + * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used. + * @return string the MIME type. Null is returned if the MIME type cannot be determined. + */ + public static function getMimeTypeByExtension($file, $magicFile = null) + { + static $mimeTypes = array(); + if ($magicFile === null) { + $magicFile = __DIR__ . '/mimeTypes.php'; + } + if (!isset($mimeTypes[$magicFile])) { + $mimeTypes[$magicFile] = require($magicFile); + } + if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { + $ext = strtolower($ext); + if (isset($mimeTypes[$magicFile][$ext])) { + return $mimeTypes[$magicFile][$ext]; + } + } + return null; + } + + /** + * Copies a whole directory as another one. + * The files and sub-directories will also be copied over. + * @param string $src the source directory + * @param string $dst the destination directory + * @param array $options options for directory copy. Valid options are: + * + * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775. + * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. + * - filter: callback, a PHP callback that is called for each directory or file. + * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. + * The callback can return one of the following values: + * + * * true: the directory or file will be copied (the "only" and "except" options will be ignored) + * * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored) + * * null: the "only" and "except" options will determine whether the directory or file should be copied + * + * - only: array, list of patterns that the file paths should match if they want to be copied. + * A path matches a pattern if it contains the pattern string at its end. + * For example, '.php' matches all file paths ending with '.php'. + * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. + * If a file path matches a pattern in both "only" and "except", it will NOT be copied. + * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. + * A path matches a pattern if it contains the pattern string at its end. + * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' + * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; + * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches + * both '/' and '\' in the paths. + * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. + * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file copied from, while `$to` is the copy target. + */ + public static function copyDirectory($src, $dst, $options = array()) + { + if (!is_dir($dst)) { + static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); + } + + $handle = opendir($src); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $from = $src . DIRECTORY_SEPARATOR . $file; + $to = $dst . DIRECTORY_SEPARATOR . $file; + if (static::filterPath($from, $options)) { + if (is_file($from)) { + copy($from, $to); + if (isset($options['fileMode'])) { + @chmod($to, $options['fileMode']); + } + } else { + static::copyDirectory($from, $to, $options); + } + if (isset($options['afterCopy'])) { + call_user_func($options['afterCopy'], $from, $to); + } + } + } + closedir($handle); + } + + /** + * Removes a directory (and all its content) recursively. + * @param string $dir the directory to be deleted recursively. + */ + public static function removeDirectory($dir) + { + if (!is_dir($dir) || !($handle = opendir($dir))) { + return; + } + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (is_file($path)) { + unlink($path); + } else { + static::removeDirectory($path); + } + } + closedir($handle); + rmdir($dir); + } + + /** + * Returns the files found under the specified directory and subdirectories. + * @param string $dir the directory under which the files will be looked for. + * @param array $options options for file searching. Valid options are: + * + * - filter: callback, a PHP callback that is called for each directory or file. + * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. + * The callback can return one of the following values: + * + * * true: the directory or file will be returned (the "only" and "except" options will be ignored) + * * false: the directory or file will NOT be returned (the "only" and "except" options will be ignored) + * * null: the "only" and "except" options will determine whether the directory or file should be returned + * + * - only: array, list of patterns that the file paths should match if they want to be returned. + * A path matches a pattern if it contains the pattern string at its end. + * For example, '.php' matches all file paths ending with '.php'. + * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. + * If a file path matches a pattern in both "only" and "except", it will NOT be returned. + * - except: array, list of patterns that the file paths or directory paths should match if they want to be excluded from the result. + * A path matches a pattern if it contains the pattern string at its end. + * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' + * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; + * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches + * both '/' and '\' in the paths. + * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. + * @return array files found under the directory. The file list is sorted. + */ + public static function findFiles($dir, $options = array()) + { + $list = array(); + $handle = opendir($dir); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (static::filterPath($path, $options)) { + if (is_file($path)) { + $list[] = $path; + } elseif (!isset($options['recursive']) || $options['recursive']) { + $list = array_merge($list, static::findFiles($path, $options)); + } + } + } + closedir($handle); + return $list; + } + + /** + * Checks if the given file path satisfies the filtering options. + * @param string $path the path of the file or directory to be checked + * @param array $options the filtering options. See [[findFiles()]] for explanations of + * the supported options. + * @return boolean whether the file or directory satisfies the filtering options. + */ + public static function filterPath($path, $options) + { + if (isset($options['filter'])) { + $result = call_user_func($options['filter'], $path); + if (is_bool($result)) { + return $result; + } + } + $path = str_replace('\\', '/', $path); + if ($isDir = is_dir($path)) { + $path .= '/'; + } + $n = StringHelper::strlen($path); + + if (!empty($options['except'])) { + foreach ($options['except'] as $name) { + if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { + return false; + } + } + } + + if (!$isDir && !empty($options['only'])) { + foreach ($options['only'] as $name) { + if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { + return true; + } + } + return false; + } + return true; + } + + /** + * Creates a new directory. + * + * This method is similar to the PHP `mkdir()` function except that + * it uses `chmod()` to set the permission of the created directory + * in order to avoid the impact of the `umask` setting. + * + * @param string $path path of the directory to be created. + * @param integer $mode the permission to be set for the created directory. + * @param boolean $recursive whether to create parent directories if they do not exist. + * @return boolean whether the directory is created successfully + */ + public static function createDirectory($path, $mode = 0775, $recursive = true) + { + if (is_dir($path)) { + return true; + } + $parentDir = dirname($path); + if ($recursive && !is_dir($parentDir)) { + static::createDirectory($parentDir, $mode, true); + } + $result = mkdir($path, $mode); + chmod($path, $mode); + return $result; + } +} diff --git a/framework/yii/helpers/AbstractHtml.php b/framework/yii/helpers/AbstractHtml.php new file mode 100644 index 0000000..37e926c --- /dev/null +++ b/framework/yii/helpers/AbstractHtml.php @@ -0,0 +1,1599 @@ + + * @since 2.0 + */ +abstract class AbstractHtml +{ + /** + * @var array list of void elements (element name => 1) + * @see http://www.w3.org/TR/html-markup/syntax.html#void-element + */ + public static $voidElements = array( + 'area' => 1, + 'base' => 1, + 'br' => 1, + 'col' => 1, + 'command' => 1, + 'embed' => 1, + 'hr' => 1, + 'img' => 1, + 'input' => 1, + 'keygen' => 1, + 'link' => 1, + 'meta' => 1, + 'param' => 1, + 'source' => 1, + 'track' => 1, + 'wbr' => 1, + ); + /** + * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes + * that are rendered by [[renderTagAttributes()]]. + */ + public static $attributeOrder = array( + 'type', + 'id', + 'class', + 'name', + 'value', + + 'href', + 'src', + 'action', + 'method', + + 'selected', + 'checked', + 'readonly', + 'disabled', + 'multiple', + + 'size', + 'maxlength', + 'width', + 'height', + 'rows', + 'cols', + + 'alt', + 'title', + 'rel', + 'media', + ); + + /** + * Encodes special characters into HTML entities. + * The [[yii\base\Application::charset|application charset]] will be used for encoding. + * @param string $content the content to be encoded + * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false, + * HTML entities in `$content` will not be further encoded. + * @return string the encoded content + * @see decode + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function encode($content, $doubleEncode = true) + { + return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset, $doubleEncode); + } + + /** + * Decodes special HTML entities back to the corresponding characters. + * This is the opposite of [[encode()]]. + * @param string $content the content to be decoded + * @return string the decoded content + * @see encode + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function decode($content) + { + return htmlspecialchars_decode($content, ENT_QUOTES); + } + + /** + * Generates a complete HTML tag. + * @param string $name the tag name + * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. + * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated HTML tag + * @see beginTag + * @see endTag + */ + public static function tag($name, $content = '', $options = array()) + { + $html = "<$name" . static::renderTagAttributes($options) . '>'; + return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content"; + } + + /** + * Generates a start tag. + * @param string $name the tag name + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated start tag + * @see endTag + * @see tag + */ + public static function beginTag($name, $options = array()) + { + return "<$name" . static::renderTagAttributes($options) . '>'; + } + + /** + * Generates an end tag. + * @param string $name the tag name + * @return string the generated end tag + * @see beginTag + * @see tag + */ + public static function endTag($name) + { + return ""; + } + + /** + * Generates a style tag. + * @param string $content the style content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/css" will be used. + * @return string the generated style tag + */ + public static function style($content, $options = array()) + { + return static::tag('style', $content, $options); + } + + /** + * Generates a script tag. + * @param string $content the script content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. + * @return string the generated script tag + */ + public static function script($content, $options = array()) + { + return static::tag('script', $content, $options); + } + + /** + * Generates a link tag that refers to an external CSS file. + * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated link tag + * @see url + */ + public static function cssFile($url, $options = array()) + { + $options['rel'] = 'stylesheet'; + $options['href'] = static::url($url); + return static::tag('link', '', $options); + } + + /** + * Generates a script tag that refers to an external JavaScript file. + * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated script tag + * @see url + */ + public static function jsFile($url, $options = array()) + { + $options['src'] = static::url($url); + return static::tag('script', '', $options); + } + + /** + * Generates a form start tag. + * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. + * @param string $method the form submission method, such as "post", "get", "put", "delete" (case-insensitive). + * Since most browsers only support "post" and "get", if other methods are given, they will + * be simulated using "post", and a hidden input will be added which contains the actual method type. + * See [[\yii\web\Request::restVar]] for more details. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated form start tag. + * @see endForm + */ + public static function beginForm($action = '', $method = 'post', $options = array()) + { + $action = static::url($action); + + $hiddenInputs = array(); + + $request = Yii::$app->getRequest(); + if ($request instanceof Request) { + if (strcasecmp($method, 'get') && strcasecmp($method, 'post')) { + // simulate PUT, DELETE, etc. via POST + $hiddenInputs[] = static::hiddenInput($request->restVar, $method); + $method = 'post'; + } + if ($request->enableCsrfValidation) { + $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); + } + } + + if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { + // query parameters in the action are ignored for GET method + // we use hidden fields to add them back + foreach (explode('&', substr($action, $pos + 1)) as $pair) { + if (($pos1 = strpos($pair, '=')) !== false) { + $hiddenInputs[] = static::hiddenInput( + urldecode(substr($pair, 0, $pos1)), + urldecode(substr($pair, $pos1 + 1)) + ); + } else { + $hiddenInputs[] = static::hiddenInput(urldecode($pair), ''); + } + } + $action = substr($action, 0, $pos); + } + + $options['action'] = $action; + $options['method'] = $method; + $form = static::beginTag('form', $options); + if (!empty($hiddenInputs)) { + $form .= "\n" . implode("\n", $hiddenInputs); + } + + return $form; + } + + /** + * Generates a form end tag. + * @return string the generated tag + * @see beginForm + */ + public static function endForm() + { + return ''; + } + + /** + * Generates a hyperlink tag. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]] + * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute + * will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated hyperlink + * @see url + */ + public static function a($text, $url = null, $options = array()) + { + if ($url !== null) { + $options['href'] = static::url($url); + } + return static::tag('a', $text, $options); + } + + /** + * Generates a mailto hyperlink. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param string $email email address. If this is null, the first parameter (link body) will be treated + * as the email address and used. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated mailto link + */ + public static function mailto($text, $email = null, $options = array()) + { + $options['href'] = 'mailto:' . ($email === null ? $text : $email); + return static::tag('a', $text, $options); + } + + /** + * Generates an image tag. + * @param string $src the image URL. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated image tag + */ + public static function img($src, $options = array()) + { + $options['src'] = static::url($src); + if (!isset($options['alt'])) { + $options['alt'] = ''; + } + return static::tag('img', '', $options); + } + + /** + * Generates a label tag. + * @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is is coming from end users, you should [[encode()]] + * it to prevent XSS attacks. + * @param string $for the ID of the HTML element that this label is associated with. + * If this is null, the "for" attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated label tag + */ + public static function label($content, $for = null, $options = array()) + { + $options['for'] = $for; + return static::tag('label', $content, $options); + } + + /** + * Generates a button tag. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function button($content = 'Button', $options = array()) + { + return static::tag('button', $content, $options); + } + + /** + * Generates a submit button tag. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated submit button tag + */ + public static function submitButton($content = 'Submit', $options = array()) + { + $options['type'] = 'submit'; + return static::button($content, $options); + } + + /** + * Generates a reset button tag. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated reset button tag + */ + public static function resetButton($content = 'Reset', $options = array()) + { + $options['type'] = 'reset'; + return static::button($content, $options); + } + + /** + * Generates an input type of the given type. + * @param string $type the type attribute. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated input tag + */ + public static function input($type, $name = null, $value = null, $options = array()) + { + $options['type'] = $type; + $options['name'] = $name; + $options['value'] = $value === null ? null : (string)$value; + return static::tag('input', '', $options); + } + + /** + * Generates an input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function buttonInput($label = 'Button', $options = array()) + { + $options['type'] = 'button'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a submit input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function submitInput($label = 'Submit', $options = array()) + { + $options['type'] = 'submit'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a reset input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated button tag + */ + public static function resetInput($label = 'Reset', $options = array()) + { + $options['type'] = 'reset'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a text input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function textInput($name, $value = null, $options = array()) + { + return static::input('text', $name, $value, $options); + } + + /** + * Generates a hidden input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function hiddenInput($name, $value = null, $options = array()) + { + return static::input('hidden', $name, $value, $options); + } + + /** + * Generates a password input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function passwordInput($name, $value = null, $options = array()) + { + return static::input('password', $name, $value, $options); + } + + /** + * Generates a file input field. + * To use a file input field, you should set the enclosing form's "enctype" attribute to + * be "multipart/form-data". After the form is submitted, the uploaded file information + * can be obtained via $_FILES[$name] (see PHP documentation). + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function fileInput($name, $value = null, $options = array()) + { + return static::input('file', $name, $value, $options); + } + + /** + * Generates a text area input. + * @param string $name the input name + * @param string $value the input value. Note that it will be encoded using [[encode()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated text area tag + */ + public static function textarea($name, $value = '', $options = array()) + { + $options['name'] = $name; + return static::tag('textarea', static::encode($value), $options); + } + + /** + * Generates a radio button input. + * @param string $name the name attribute. + * @param boolean $checked whether the radio button should be checked. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute + * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * When this option is specified, the radio button will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public static function radio($name, $checked = false, $options = array()) + { + $options['checked'] = (boolean)$checked; + $value = array_key_exists('value', $options) ? $options['value'] : '1'; + if (isset($options['uncheck'])) { + // add a hidden field so that if the radio button is not selected, it still submits a value + $hidden = static::hiddenInput($name, $options['uncheck']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + if (isset($options['label'])) { + $label = $options['label']; + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); + unset($options['label'], $options['labelOptions']); + $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); + return $hidden . static::tag('div', $content, array('class' => 'radio')); + } else { + return $hidden . static::input('radio', $name, $value, $options); + } + } + + /** + * Generates a checkbox input. + * @param string $name the name attribute. + * @param boolean $checked whether the checkbox should be checked. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute + * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * When this option is specified, the checkbox will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public static function checkbox($name, $checked = false, $options = array()) + { + $options['checked'] = (boolean)$checked; + $value = array_key_exists('value', $options) ? $options['value'] : '1'; + if (isset($options['uncheck'])) { + // add a hidden field so that if the checkbox is not selected, it still submits a value + $hidden = static::hiddenInput($name, $options['uncheck']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + if (isset($options['label'])) { + $label = $options['label']; + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); + unset($options['label'], $options['labelOptions']); + $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); + return $hidden . static::tag('div', $content, array('class' => 'checkbox')); + } else { + return $hidden . static::input('checkbox', $name, $value, $options); + } + } + + /** + * Generates a drop-down list. + * @param string $name the input name + * @param string $selection the selected value + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public static function dropDownList($name, $selection = null, $items = array(), $options = array()) + { + $options['name'] = $name; + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list box. + * @param string $name the input name + * @param string|array $selection the selected value(s) + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public static function listBox($name, $selection = null, $items = array(), $options = array()) + { + if (!isset($options['size'])) { + $options['size'] = 4; + } + if (!empty($options['multiple']) && substr($name, -2) !== '[]') { + $name .= '[]'; + } + $options['name'] = $name; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + if (substr($name, -2) === '[]') { + $name = substr($name, 0, -2); + } + $hidden = static::hiddenInput($name, $options['unselect']); + unset($options['unselect']); + } else { + $hidden = ''; + } + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * @param string $name the name attribute of each checkbox. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * @param array $options options (name => config) for the checkbox list container tag. + * The following options are specially handled: + * + * - tag: string, the tag name of the container element. + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. + * This option is ignored if `item` option is set. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input, respectively. + * @return string the generated checkbox list + */ + public static function checkboxList($name, $selection = null, $items = array(), $options = array()) + { + if (substr($name, -2) !== '[]') { + $name .= '[]'; + } + + $formatter = isset($options['item']) ? $options['item'] : null; + $encode = !isset($options['encode']) || $options['encode']; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::checkbox($name, $checked, array( + 'value' => $value, + 'label' => $encode ? static::encode($label) : $label, + )); + } + $index++; + } + + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; + $hidden = static::hiddenInput($name2, $options['unselect']); + } else { + $hidden = ''; + } + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); + + return $hidden . static::tag($tag, implode($separator, $lines), $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * @param string $name the name attribute of each radio button. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. + * This option is ignored if `item` option is set. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input, respectively. + * @return string the generated radio button list + */ + public static function radioList($name, $selection = null, $items = array(), $options = array()) + { + $encode = !isset($options['encode']) || $options['encode']; + $formatter = isset($options['item']) ? $options['item'] : null; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::radio($name, $checked, array( + 'value' => $value, + 'label' => $encode ? static::encode($label) : $label, + )); + } + $index++; + } + + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $hidden = static::hiddenInput($name, $options['unselect']); + } else { + $hidden = ''; + } + + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); + + return $hidden . static::tag($tag, implode($separator, $lines), $options); + } + + /** + * Generates an unordered list. + * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. + * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - encode: boolean, whether to HTML-encode the items. Defaults to true. + * This option is ignored if the `item` option is specified. + * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. + * - item: callable, a callback that is used to generate each individual list item. + * The signature of this callback must be: + * + * ~~~ + * function ($item, $index) + * ~~~ + * + * where $index is the array key corresponding to `$item` in `$items`. The callback should return + * the whole list item tag. + * + * @return string the generated unordered list. An empty string is returned if `$items` is empty. + */ + public static function ul($items, $options = array()) + { + if (empty($items)) { + return ''; + } + $tag = isset($options['tag']) ? $options['tag'] : 'ul'; + $encode = !isset($options['encode']) || $options['encode']; + $formatter = isset($options['item']) ? $options['item'] : null; + $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : array(); + unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); + $results = array(); + foreach ($items as $index => $item) { + if ($formatter !== null) { + $results[] = call_user_func($formatter, $item, $index); + } else { + $results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions); + } + } + return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options); + } + + /** + * Generates an ordered list. + * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. + * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - encode: boolean, whether to HTML-encode the items. Defaults to true. + * This option is ignored if the `item` option is specified. + * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. + * - item: callable, a callback that is used to generate each individual list item. + * The signature of this callback must be: + * + * ~~~ + * function ($item, $index) + * ~~~ + * + * where $index is the array key corresponding to `$item` in `$items`. The callback should return + * the whole list item tag. + * + * @return string the generated ordered list. An empty string is returned if `$items` is empty. + */ + public static function ol($items, $options = array()) + { + $options['tag'] = 'ol'; + return static::ul($items, $options); + } + + /** + * Generates a label tag for the given model attribute. + * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * The following options are specially handled: + * + * - label: this specifies the label to be displayed. Note that this will NOT be [[encoded()]]. + * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display + * (after encoding). + * + * @return string the generated label tag + */ + public static function activeLabel($model, $attribute, $options = array()) + { + $attribute = static::getAttributeName($attribute); + $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); + $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); + unset($options['label'], $options['for']); + return static::label($label, $for, $options); + } + + /** + * Generates a tag that contains the first validation error of the specified model attribute. + * Note that even if there is no validation error, this method will still return an empty error tag. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The values will be HTML-encoded + * using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * The following options are specially handled: + * + * - tag: this specifies the tag name. If not set, "div" will be used. + * + * @return string the generated label tag + */ + public static function error($model, $attribute, $options = array()) + { + $attribute = static::getAttributeName($attribute); + $error = $model->getFirstError($attribute); + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag']); + return Html::tag($tag, Html::encode($error), $options); + } + + /** + * Generates an input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param string $type the input type (e.g. 'text', 'password') + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeInput($type, $model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::input($type, $name, $value, $options); + } + + /** + * Generates a text input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeTextInput($model, $attribute, $options = array()) + { + return static::activeInput('text', $model, $attribute, $options); + } + + /** + * Generates a hidden input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeHiddenInput($model, $attribute, $options = array()) + { + return static::activeInput('hidden', $model, $attribute, $options); + } + + /** + * Generates a password input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activePasswordInput($model, $attribute, $options = array()) + { + return static::activeInput('password', $model, $attribute, $options); + } + + /** + * Generates a file input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeFileInput($model, $attribute, $options = array()) + { + // add a hidden field so that if a model only has a file field, we can + // still use isset($_POST[$modelClass]) to detect if the input is submitted + return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) + . static::activeInput('file', $model, $attribute, $options); + } + + /** + * Generates a textarea tag for the given model attribute. + * The model attribute value will be used as the content in the textarea. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated textarea tag + */ + public static function activeTextarea($model, $attribute, $options = array()) + { + $name = static::getInputName($model, $attribute); + $value = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::textarea($name, $value, $options); + } + + /** + * Generates a radio button tag for the given model attribute. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * When this option is specified, the radio button will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public static function activeRadio($model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('uncheck', $options)) { + $options['uncheck'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::radio($name, $checked, $options); + } + + /** + * Generates a checkbox tag for the given model attribute. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * When this option is specified, the checkbox will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public static function activeCheckbox($model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('uncheck', $options)) { + $options['uncheck'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::checkbox($name, $checked, $options); + } + + /** + * Generates a drop-down list for the given model attribute. + * The selection of the drop-down list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public static function activeDropDownList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::dropDownList($name, $checked, $items, $options); + } + + /** + * Generates a list box. + * The selection of the list box is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public static function activeListBox($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::listBox($name, $checked, $items, $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * The selection of the checkbox list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the checkbox list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input. + * @return string the generated checkbox list + */ + public static function activeCheckboxList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::checkboxList($name, $checked, $items, $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * The selection of the radio buttons is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the radio button list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input. + * @return string the generated radio button list + */ + public static function activeRadioList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::radioList($name, $checked, $items, $options); + } + + /** + * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. + * @param string|array $selection the selected value(s). This can be either a string for single selection + * or an array for multiple selections. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. + * This method will take out these elements, if any: "prompt", "options" and "groups". See more details + * in [[dropDownList()]] for the explanation of these elements. + * + * @return string the generated list options + */ + public static function renderSelectOptions($selection, $items, &$tagOptions = array()) + { + $lines = array(); + if (isset($tagOptions['prompt'])) { + $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); + $lines[] = static::tag('option', $prompt, array('value' => '')); + } + + $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); + $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); + unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); + + foreach ($items as $key => $value) { + if (is_array($value)) { + $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); + $groupAttrs['label'] = $key; + $attrs = array('options' => $options, 'groups' => $groups); + $content = static::renderSelectOptions($selection, $value, $attrs); + $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); + } else { + $attrs = isset($options[$key]) ? $options[$key] : array(); + $attrs['value'] = (string)$key; + $attrs['selected'] = $selection !== null && + (!is_array($selection) && !strcmp($key, $selection) + || is_array($selection) && in_array($key, $selection)); + $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs); + } + } + + return implode("\n", $lines); + } + + /** + * Renders the HTML tag attributes. + * Attributes whose values are of boolean type will be treated as + * [boolean attributes](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). + * And attributes whose values are null will not be rendered. + * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. + * @return string the rendering result. If the attributes are not empty, they will be rendered + * into a string with a leading white space (so that it can be directly appended to the tag name + * in a tag. If there is no attribute, an empty string will be returned. + */ + public static function renderTagAttributes($attributes) + { + if (count($attributes) > 1) { + $sorted = array(); + foreach (static::$attributeOrder as $name) { + if (isset($attributes[$name])) { + $sorted[$name] = $attributes[$name]; + } + } + $attributes = array_merge($sorted, $attributes); + } + + $html = ''; + foreach ($attributes as $name => $value) { + if (is_bool($value)) { + if ($value) { + $html .= " $name"; + } + } elseif ($value !== null) { + $html .= " $name=\"" . static::encode($value) . '"'; + } + } + return $html; + } + + /** + * Normalizes the input parameter to be a valid URL. + * + * If the input parameter + * + * - is an empty string: the currently requested URL will be returned; + * - is a non-empty string: it will first be processed by [[Yii::getAlias()]]. If the result + * is an absolute URL, it will be returned without any change further; Otherwise, the result + * will be prefixed with [[\yii\web\Request::baseUrl]] and returned. + * - is an array: the first array element is considered a route, while the rest of the name-value + * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. + * For example: `array('post/index', 'page' => 2)`, `array('index')`. + * In case there is no controller, [[\yii\web\UrlManager::createUrl()]] will be used. + * + * @param array|string $url the parameter to be used to generate a valid URL + * @return string the normalized URL + * @throws InvalidParamException if the parameter is invalid. + */ + public static function url($url) + { + if (is_array($url)) { + if (isset($url[0])) { + $route = $url[0]; + $params = array_splice($url, 1); + if (Yii::$app->controller instanceof \yii\web\Controller) { + return Yii::$app->controller->createUrl($route, $params); + } else { + return Yii::$app->getUrlManager()->createUrl($route, $params); + } + } else { + throw new InvalidParamException('The array specifying a URL must contain at least one element.'); + } + } elseif ($url === '') { + return Yii::$app->getRequest()->getUrl(); + } else { + $url = Yii::getAlias($url); + if ($url !== '' && ($url[0] === '/' || $url[0] === '#' || strpos($url, '://'))) { + return $url; + } else { + return Yii::$app->getRequest()->getBaseUrl() . '/' . $url; + } + } + } + + /** + * Adds a CSS class to the specified options. + * If the CSS class is already in the options, it will not be added again. + * @param array $options the options to be modified. + * @param string $class the CSS class to be added + */ + public static function addCssClass(&$options, $class) + { + if (isset($options['class'])) { + $classes = ' ' . $options['class'] . ' '; + if (($pos = strpos($classes, ' ' . $class . ' ')) === false) { + $options['class'] .= ' ' . $class; + } + } else { + $options['class'] = $class; + } + } + + /** + * Removes a CSS class from the specified options. + * @param array $options the options to be modified. + * @param string $class the CSS class to be removed + */ + public static function removeCssClass(&$options, $class) + { + if (isset($options['class'])) { + $classes = array_unique(preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY)); + if (($index = array_search($class, $classes)) !== false) { + unset($classes[$index]); + } + if (empty($classes)) { + unset($options['class']); + } else { + $options['class'] = implode(' ', $classes); + } + } + } + + /** + * Returns the real attribute name from the given attribute expression. + * + * An attribute expression is an attribute name prefixed and/or suffixed with array indexes. + * It is mainly used in tabular data input and/or input of array type. Below are some examples: + * + * - `[0]content` is used in tabular data input to represent the "content" attribute + * for the first model in tabular input; + * - `dates[0]` represents the first array element of the "dates" attribute; + * - `[0]dates[0]` represents the first array element of the "dates" attribute + * for the first model in tabular input. + * + * If `$attribute` has neither prefix nor suffix, it will be returned back without change. + * @param string $attribute the attribute name or expression + * @return string the attribute name without prefix and suffix. + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeName($attribute) + { + if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + return $matches[2]; + } else { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + } + + /** + * Returns the value of the specified attribute name or expression. + * + * For an attribute expression like `[0]dates[0]`, this method will return the value of `$model->dates[0]`. + * See [[getAttributeName()]] for more details about attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return mixed the corresponding attribute value + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeValue($model, $attribute) + { + if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $attribute = $matches[2]; + $index = $matches[3]; + if ($index === '') { + return $model->$attribute; + } else { + $value = $model->$attribute; + foreach (explode('][', trim($index, '[]')) as $id) { + if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { + $value = $value[$id]; + } else { + return null; + } + } + return $value; + } + } + + /** + * Generates an appropriate input name for the specified attribute name or expression. + * + * This method generates a name that can be used as the input name to collect user input + * for the specified attribute. The name is generated according to the [[Model::formName|form name]] + * of the model and the given attribute name. For example, if the form name of the `Post` model + * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. + * + * See [[getAttributeName()]] for explanation of attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return string the generated input name + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + 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.'); + } + } + + /** + * Generates an appropriate input ID for the specified attribute name or expression. + * + * This method converts the result [[getInputName()]] into a valid input ID. + * For example, if [[getInputName()]] returns `Post[content]`, this method will return `post-content`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for explanation of attribute expression. + * @return string the generated input ID + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getInputId($model, $attribute) + { + $name = strtolower(static::getInputName($model, $attribute)); + return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); + } +} diff --git a/framework/yii/helpers/AbstractHtmlPurifier.php b/framework/yii/helpers/AbstractHtmlPurifier.php new file mode 100644 index 0000000..221fc37 --- /dev/null +++ b/framework/yii/helpers/AbstractHtmlPurifier.php @@ -0,0 +1,34 @@ + + * @since 2.0 + */ +abstract class AbstractHtmlPurifier +{ + /** + * Passes markup through HTMLPurifier making it safe to output to end user + * + * @param string $content + * @param array|null $config + * @return string + */ + public static function process($content, $config = null) + { + $configInstance = \HTMLPurifier_Config::create($config); + $configInstance->autoFinalize = false; + $purifier=\HTMLPurifier::instance($configInstance); + $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); + return $purifier->purify($content); + } +} diff --git a/framework/yii/helpers/AbstractInflector.php b/framework/yii/helpers/AbstractInflector.php new file mode 100644 index 0000000..27ee4f7 --- /dev/null +++ b/framework/yii/helpers/AbstractInflector.php @@ -0,0 +1,480 @@ + + * @since 2.0 + */ +abstract class AbstractInflector +{ + /** + * @var array the rules for converting a word into its plural form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public static $plurals = array( + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(m)ove$/i' => '\1oves', + '/(f)oot$/i' => '\1eet', + '/(h)uman$/i' => '\1umans', + '/(s)tatus$/i' => '\1tatuses', + '/(s)taff$/i' => '\1taff', + '/(t)ooth$/i' => '\1eeth', + '/(quiz)$/i' => '\1zes', + '/^(ox)$/i' => '\1\2en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(p)erson$/i' => '\1eople', + '/(m)an$/i' => '\1en', + '/(c)hild$/i' => '\1hildren', + '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', + '/us$/i' => 'uses', + '/(alias)$/i' => '\1es', + '/(ax|cris|test)is$/i' => '\1es', + '/s$/' => 's', + '/^$/' => '', + '/$/' => 's', + ); + /** + * @var array the rules for converting a word into its singular form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public static $singulars = array( + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(s)tatuses$/i' => '\1tatus', + '/(f)eet$/i' => '\1oot', + '/(t)eeth$/i' => '\1ooth', + '/^(.*)(menu)s$/i' => '\1\2', + '/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias)(es)*$/i' => '\1', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(cris|ax|test)es$/i' => '\1is', + '/(shoe|slave)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/ouses$/' => 'ouse', + '/([^a])uses$/' => '\1us', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1\2ovie', + '/(s)eries$/i' => '\1\2eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/(drive)s$/i' => '\1', + '/([^fo])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(p)eople$/i' => '\1\2erson', + '/(m)en$/i' => '\1an', + '/(c)hildren$/i' => '\1\2hild', + '/(n)ews$/i' => '\1\2ews', + '/eaus$/' => 'eau', + '/^(.*us)$/' => '\\1', + '/s$/i' => '', + ); + /** + * @var array the special rules for converting a word between its plural form and singular form. + * The keys are the special words in singular form, and the values are the corresponding plural form. + */ + public static $specials = array( + 'atlas' => 'atlases', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'cafe' => 'cafes', + 'child' => 'children', + 'cookie' => 'cookies', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'curve' => 'curves', + 'foe' => 'foes', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hoof' => 'hoofs', + 'loaf' => 'loaves', + 'man' => 'men', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'penis' => 'penises', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'testis' => 'testes', + 'trilby' => 'trilbys', + 'turf' => 'turfs', + 'wave' => 'waves', + 'Amoyese' => 'Amoyese', + 'bison' => 'bison', + 'Borghese' => 'Borghese', + 'bream' => 'bream', + 'breeches' => 'breeches', + 'britches' => 'britches', + 'buffalo' => 'buffalo', + 'cantus' => 'cantus', + 'carp' => 'carp', + 'chassis' => 'chassis', + 'clippers' => 'clippers', + 'cod' => 'cod', + 'coitus' => 'coitus', + 'Congoese' => 'Congoese', + 'contretemps' => 'contretemps', + 'corps' => 'corps', + 'debris' => 'debris', + 'diabetes' => 'diabetes', + 'djinn' => 'djinn', + 'eland' => 'eland', + 'elk' => 'elk', + 'equipment' => 'equipment', + 'Faroese' => 'Faroese', + 'flounder' => 'flounder', + 'Foochowese' => 'Foochowese', + 'gallows' => 'gallows', + 'Genevese' => 'Genevese', + 'Genoese' => 'Genoese', + 'Gilbertese' => 'Gilbertese', + 'graffiti' => 'graffiti', + 'headquarters' => 'headquarters', + 'herpes' => 'herpes', + 'hijinks' => 'hijinks', + 'Hottentotese' => 'Hottentotese', + 'information' => 'information', + 'innings' => 'innings', + 'jackanapes' => 'jackanapes', + 'Kiplingese' => 'Kiplingese', + 'Kongoese' => 'Kongoese', + 'Lucchese' => 'Lucchese', + 'mackerel' => 'mackerel', + 'Maltese' => 'Maltese', + 'mews' => 'mews', + 'moose' => 'moose', + 'mumps' => 'mumps', + 'Nankingese' => 'Nankingese', + 'news' => 'news', + 'nexus' => 'nexus', + 'Niasese' => 'Niasese', + 'Pekingese' => 'Pekingese', + 'Piedmontese' => 'Piedmontese', + 'pincers' => 'pincers', + 'Pistoiese' => 'Pistoiese', + 'pliers' => 'pliers', + 'Portuguese' => 'Portuguese', + 'proceedings' => 'proceedings', + 'rabies' => 'rabies', + 'rice' => 'rice', + 'rhinoceros' => 'rhinoceros', + 'salmon' => 'salmon', + 'Sarawakese' => 'Sarawakese', + 'scissors' => 'scissors', + 'series' => 'series', + 'Shavese' => 'Shavese', + 'shears' => 'shears', + 'siemens' => 'siemens', + 'species' => 'species', + 'swine' => 'swine', + 'testes' => 'testes', + 'trousers' => 'trousers', + 'trout' => 'trout', + 'tuna' => 'tuna', + 'Vermontese' => 'Vermontese', + 'Wenchowese' => 'Wenchowese', + 'whiting' => 'whiting', + 'wildebeest' => 'wildebeest', + 'Yengeese' => 'Yengeese', + ); + /** + * @var array map of special chars and its translation. This is used by [[slug()]]. + */ + public static $transliteration = array( + '/ä|æ|ǽ/' => 'ae', + '/ö|œ/' => 'oe', + '/ü/' => 'ue', + '/Ä/' => 'Ae', + '/Ü/' => 'Ue', + '/Ö/' => 'Oe', + '/À|Á|Â|Ã|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A', + '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a', + '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', + '/ç|ć|ĉ|ċ|č/' => 'c', + '/Ð|Ď|Đ/' => 'D', + '/ð|ď|đ/' => 'd', + '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E', + '/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e', + '/Ĝ|Ğ|Ġ|Ģ/' => 'G', + '/ĝ|ğ|ġ|ģ/' => 'g', + '/Ĥ|Ħ/' => 'H', + '/ĥ|ħ/' => 'h', + '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I', + '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i', + '/Ĵ/' => 'J', + '/ĵ/' => 'j', + '/Ķ/' => 'K', + '/ķ/' => 'k', + '/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L', + '/ĺ|ļ|ľ|ŀ|ł/' => 'l', + '/Ñ|Ń|Ņ|Ň/' => 'N', + '/ñ|ń|ņ|ň|ʼn/' => 'n', + '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O', + '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o', + '/Ŕ|Ŗ|Ř/' => 'R', + '/ŕ|ŗ|ř/' => 'r', + '/Ś|Ŝ|Ş|Ș|Š/' => 'S', + '/ś|ŝ|ş|ș|š|ſ/' => 's', + '/Ţ|Ț|Ť|Ŧ/' => 'T', + '/ţ|ț|ť|ŧ/' => 't', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U', + '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u', + '/Ý|Ÿ|Ŷ/' => 'Y', + '/ý|ÿ|ŷ/' => 'y', + '/Ŵ/' => 'W', + '/ŵ/' => 'w', + '/Ź|Ż|Ž/' => 'Z', + '/ź|ż|ž/' => 'z', + '/Æ|Ǽ/' => 'AE', + '/ß/' => 'ss', + '/IJ/' => 'IJ', + '/ij/' => 'ij', + '/Œ/' => 'OE', + '/ƒ/' => 'f' + ); + + /** + * Converts a word to its plural form. + * Note that this is for English only! + * For example, 'apple' will become 'apples', and 'child' will become 'children'. + * @param string $word the word to be pluralized + * @return string the pluralized word + */ + public static function pluralize($word) + { + if (isset(self::$specials[$word])) { + return self::$specials[$word]; + } + foreach (static::$plurals as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + return $word; + } + + /** + * Returns the singular of the $word + * @param string $word the english word to singularize + * @return string Singular noun. + */ + public static function singularize($word) + { + $result = array_search($word, self::$specials, true); + if ($result !== false) { + return $result; + } + foreach (static::$singulars as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + return $word; + } + + /** + * Converts an underscored or CamelCase word into a English + * sentence. + * @param string $words + * @param bool $ucAll whether to set all words to uppercase + * @return string + */ + public static function titleize($words, $ucAll = false) + { + $words = static::humanize(static::underscore($words), $ucAll); + return $ucAll ? ucwords($words) : ucfirst($words); + } + + /** + * Returns given word as CamelCased + * Converts a word like "send_email" to "SendEmail". It + * will remove non alphanumeric character from the word, so + * "who's online" will be converted to "WhoSOnline" + * @see variablize + * @param string $word the word to CamelCase + * @return string + */ + public static function camelize($word) + { + return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word))); + } + + /** + * Converts a CamelCase name into space-separated words. + * For example, 'PostTag' will be converted to 'Post Tag'. + * @param string $name the string to be converted + * @param boolean $ucwords whether to capitalize the first letter in each word + * @return string the resulting words + */ + public static function camel2words($name, $ucwords = true) + { + $label = trim(strtolower(str_replace(array( + '-', + '_', + '.' + ), ' ', preg_replace('/(? ' ', + '/\\s+/' => $replacement, + '/(?<=[a-z])([A-Z])/' => $replacement . '\\1', + str_replace(':rep', preg_quote($replacement, '/'), '/^[:rep]+|[:rep]+$/') => '' + ); + return preg_replace(array_keys($map), array_values($map), $string); + } + + /** + * Converts a table name to its class name. For example, converts "people" to "Person" + * @param string $tableName + * @return string + */ + public static function classify($tableName) + { + return static::camelize(static::singularize($tableName)); + } + + /** + * Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd ... + * @param int $number the number to get its ordinal value + * @return string + */ + public static function ordinalize($number) + { + if (in_array(($number % 100), range(11, 13))) { + return $number . 'th'; + } + switch ($number % 10) { + case 1: return $number . 'st'; + case 2: return $number . 'nd'; + case 3: return $number . 'rd'; + default: return $number . 'th'; + } + } +} diff --git a/framework/yii/helpers/AbstractJson.php b/framework/yii/helpers/AbstractJson.php new file mode 100644 index 0000000..cda71a0 --- /dev/null +++ b/framework/yii/helpers/AbstractJson.php @@ -0,0 +1,112 @@ + + * @since 2.0 + */ +abstract class AbstractJson +{ + /** + * Encodes the given value into a JSON string. + * The method enhances `json_encode()` by supporting JavaScript expressions. + * In particular, the method will not encode a JavaScript expression that is + * represented in terms of a [[JsExpression]] object. + * @param mixed $value the data to be encoded + * @param integer $options the encoding options. For more details please refer to + * [[http://www.php.net/manual/en/function.json-encode.php]] + * @return string the encoding result + */ + public static function encode($value, $options = 0) + { + $expressions = array(); + $value = static::processData($value, $expressions, uniqid()); + $json = json_encode($value, $options); + return empty($expressions) ? $json : strtr($json, $expressions); + } + + /** + * Decodes the given JSON string into a PHP data structure. + * @param string $json the JSON string to be decoded + * @param boolean $asArray whether to return objects in terms of associative arrays. + * @return mixed the PHP data + * @throws InvalidParamException if there is any decoding error + */ + public static function decode($json, $asArray = true) + { + if (is_array($json)) { + throw new InvalidParamException('Invalid JSON data.'); + } + $decode = json_decode((string)$json, $asArray); + switch (json_last_error()) { + case JSON_ERROR_NONE: + break; + case JSON_ERROR_DEPTH: + throw new InvalidParamException('The maximum stack depth has been exceeded.'); + case JSON_ERROR_CTRL_CHAR: + throw new InvalidParamException('Control character error, possibly incorrectly encoded.'); + case JSON_ERROR_SYNTAX: + throw new InvalidParamException('Syntax error.'); + case JSON_ERROR_STATE_MISMATCH: + throw new InvalidParamException('Invalid or malformed JSON.'); + case JSON_ERROR_UTF8: + throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.'); + default: + throw new InvalidParamException('Unknown JSON decoding error.'); + } + + return $decode; + } + + /** + * Pre-processes the data before sending it to `json_encode()`. + * @param mixed $data the data to be processed + * @param array $expressions collection of JavaScript expressions + * @param string $expPrefix a prefix internally used to handle JS expressions + * @return mixed the processed data + */ + protected static function processData($data, &$expressions, $expPrefix) + { + if (is_array($data)) { + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $data[$key] = static::processData($value, $expressions, $expPrefix); + } + } + return $data; + } elseif (is_object($data)) { + if ($data instanceof JsExpression) { + $token = "!{[$expPrefix=" . count($expressions) . ']}!'; + $expressions['"' . $token . '"'] = $data->expression; + return $token; + } else { + $data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data); + $result = array(); + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $result[$key] = static::processData($value, $expressions, $expPrefix); + } else { + $result[$key] = $value; + } + } + return $result; + } + } else { + return $data; + } + } +} diff --git a/framework/yii/helpers/AbstractMarkdown.php b/framework/yii/helpers/AbstractMarkdown.php new file mode 100644 index 0000000..6b76b44 --- /dev/null +++ b/framework/yii/helpers/AbstractMarkdown.php @@ -0,0 +1,44 @@ + + * @since 2.0 + */ +abstract class AbstractMarkdown +{ + /** + * @var MarkdownExtra + */ + protected static $markdown; + + /** + * Converts markdown into HTML + * + * @param string $content + * @param array $config + * @return string + */ + public static function process($content, $config = array()) + { + if (static::$markdown === null) { + static::$markdown = new MarkdownExtra(); + } + foreach ($config as $name => $value) { + static::$markdown->{$name} = $value; + } + return static::$markdown->transform($content); + } +} diff --git a/framework/yii/helpers/AbstractSecurity.php b/framework/yii/helpers/AbstractSecurity.php new file mode 100644 index 0000000..d308b7f --- /dev/null +++ b/framework/yii/helpers/AbstractSecurity.php @@ -0,0 +1,285 @@ + + * @author Tom Worster + * @since 2.0 + */ +abstract class AbstractSecurity +{ + /** + * Encrypts data. + * @param string $data data to be encrypted. + * @param string $key the encryption secret key + * @return string the encrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see decrypt() + */ + public static function encrypt($data, $key) + { + $module = static::openCryptModule(); + // 192-bit (24 bytes) key size + $key = StringHelper::substr($key, 0, 24); + srand(); + $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); + mcrypt_generic_init($module, $key, $iv); + $encrypted = $iv . mcrypt_generic($module, $data); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return $encrypted; + } + + /** + * Decrypts data + * @param string $data data to be decrypted. + * @param string $key the decryption secret key + * @return string the decrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see encrypt() + */ + public static function decrypt($data, $key) + { + $module = static::openCryptModule(); + // 192-bit (24 bytes) key size + $key = StringHelper::substr($key, 0, 24); + $ivSize = mcrypt_enc_get_iv_size($module); + $iv = StringHelper::substr($data, 0, $ivSize); + mcrypt_generic_init($module, $key, $iv); + $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return rtrim($decrypted, "\0"); + } + + /** + * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. + * @param string $data the data to be protected + * @param string $key the secret key to be used for generating hash + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. + * @return string the data prefixed with the keyed hash + * @see validateData() + * @see getSecretKey() + */ + public static function hashData($data, $key, $algorithm = 'sha256') + { + return hash_hmac($algorithm, $data, $key) . $data; + } + + /** + * Validates if the given data is tampered. + * @param string $data the data to be validated. The data must be previously + * generated by [[hashData()]]. + * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. This must be the same + * as the value passed to [[hashData()]] when generating the hash for the data. + * @return string the real data with the hash stripped off. False if the data is tampered. + * @see hashData() + */ + public static function validateData($data, $key, $algorithm = 'sha256') + { + $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); + $n = StringHelper::strlen($data); + if ($n >= $hashSize) { + $hash = StringHelper::substr($data, 0, $hashSize); + $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); + return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; + } else { + return false; + } + } + + /** + * Returns a secret key associated with the specified name. + * If the secret key does not exist, a random key will be generated + * and saved in the file "keys.php" under the application's runtime directory + * so that the same secret key can be returned in future requests. + * @param string $name the name that is associated with the secret key + * @param integer $length the length of the key that should be generated if not exists + * @return string the secret key associated with the specified name + */ + public static function getSecretKey($name, $length = 32) + { + static $keys; + $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; + if ($keys === null) { + $keys = array(); + if (is_file($keyFile)) { + $keys = require($keyFile); + } + } + if (!isset($keys[$name])) { + $keys[$name] = static::generateRandomKey($length); + file_put_contents($keyFile, " 30) { + throw new InvalidParamException('Hash is invalid.'); + } + + $test = crypt($password, $hash); + $n = strlen($test); + if (strlen($test) < 32 || $n !== strlen($hash)) { + return false; + } + + // Use a for-loop to compare two strings to prevent timing attacks. See: + // http://codereview.stackexchange.com/questions/13512 + $check = 0; + for ($i = 0; $i < $n; ++$i) { + $check |= (ord($test[$i]) ^ ord($hash[$i])); + } + + return $check === 0; + } + + /** + * Generates a salt that can be used to generate a password hash. + * + * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function + * requires, for the Blowfish hash algorithm, a salt string in a specific format: + * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters + * from the alphabet "./0-9A-Za-z". + * + * @param integer $cost the cost parameter + * @return string the random salt value. + * @throws InvalidParamException if the cost parameter is not between 4 and 30 + */ + protected static function generateSalt($cost = 13) + { + $cost = (int)$cost; + if ($cost < 4 || $cost > 31) { + throw new InvalidParamException('Cost must be between 4 and 31.'); + } + + // Get 20 * 8bits of pseudo-random entropy from mt_rand(). + $rand = ''; + for ($i = 0; $i < 20; ++$i) { + $rand .= chr(mt_rand(0, 255)); + } + + // Add the microtime for a little more entropy. + $rand .= microtime(); + // Mix the bits cryptographically into a 20-byte binary string. + $rand = sha1($rand, true); + // Form the prefix that specifies Blowfish algorithm and cost parameter. + $salt = sprintf("$2y$%02d$", $cost); + // Append the random salt data in the required base64 format. + $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); + return $salt; + } +} diff --git a/framework/yii/helpers/AbstractStringHelper.php b/framework/yii/helpers/AbstractStringHelper.php new file mode 100644 index 0000000..050481e --- /dev/null +++ b/framework/yii/helpers/AbstractStringHelper.php @@ -0,0 +1,138 @@ + + * @author Alex Makarov + * @since 2.0 + */ +abstract class AbstractStringHelper +{ + /** + * Returns the number of bytes in the given string. + * This method ensures the string is treated as a byte array by using `mb_strlen()`. + * @param string $string the string being measured for length + * @return integer the number of bytes in the given string. + */ + public static function strlen($string) + { + return mb_strlen($string, '8bit'); + } + + /** + * Returns the portion of string specified by the start and length parameters. + * This method ensures the string is treated as a byte array by using `mb_substr()`. + * @param string $string the input string. Must be one character or longer. + * @param integer $start the starting position + * @param integer $length the desired portion length + * @return string the extracted part of string, or FALSE on failure or an empty string. + * @see http://www.php.net/manual/en/function.substr.php + */ + public static function substr($string, $start, $length) + { + return mb_substr($string, $start, $length, '8bit'); + } + + /** + * Returns the trailing name component of a path. + * This method is similar to the php function `basename()` except that it will + * treat both \ and / as directory separators, independent of the operating system. + * This method was mainly created to work on php namespaces. When working with real + * file paths, php's `basename()` should work fine for you. + * Note: this method is not aware of the actual filesystem, or path components such as "..". + * @param string $path A path string. + * @param string $suffix If the name component ends in suffix this will also be cut off. + * @return string the trailing name component of the given path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function basename($path, $suffix = '') + { + if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) == $suffix) { + $path = mb_substr($path, 0, -$len); + } + $path = rtrim(str_replace('\\', '/', $path), '/\\'); + if (($pos = mb_strrpos($path, '/')) !== false) { + return mb_substr($path, $pos + 1); + } + return $path; + } + + /** + * Returns parent directory's path. + * This method is similar to `dirname()` except that it will treat + * both \ and / as directory separators, independent of the operating system. + * @param string $path A path string. + * @return string the parent directory's path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function dirname($path) + { + $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); + if ($pos !== false) { + return mb_substr($path, 0, $pos); + } else { + return $path; + } + } + + /** + * Compares two strings or string arrays, and return their differences. + * This is a wrapper of the [phpspec/php-diff](https://packagist.org/packages/phpspec/php-diff) package. + * @param string|array $lines1 the first string or string array to be compared. If it is a string, + * it will be converted into a string array by breaking at newlines. + * @param string|array $lines2 the second string or string array to be compared. If it is a string, + * it will be converted into a string array by breaking at newlines. + * @param string $format the output format. It must be 'inline', 'unified', 'context', 'side-by-side', or 'array'. + * @return string|array the comparison result. An array is returned if `$format` is 'array'. For all other + * formats, a string is returned. + * @throws InvalidParamException if the format is invalid. + */ + public static function diff($lines1, $lines2, $format = 'inline') + { + if (!is_array($lines1)) { + $lines1 = explode("\n", $lines1); + } + if (!is_array($lines2)) { + $lines2 = explode("\n", $lines2); + } + foreach ($lines1 as $i => $line) { + $lines1[$i] = rtrim($line, "\r\n"); + } + foreach ($lines2 as $i => $line) { + $lines2[$i] = rtrim($line, "\r\n"); + } + switch ($format) { + case 'inline': + $renderer = new \Diff_Renderer_Html_Inline(); + break; + case 'array': + $renderer = new \Diff_Renderer_Html_Array(); + break; + case 'side-by-side': + $renderer = new \Diff_Renderer_Html_SideBySide(); + break; + case 'context': + $renderer = new \Diff_Renderer_Text_Context(); + break; + case 'unified': + $renderer = new \Diff_Renderer_Text_Unified(); + break; + default: + throw new InvalidParamException("Output format must be 'inline', 'side-by-side', 'array', 'context' or 'unified'."); + } + $diff = new \Diff($lines1, $lines2); + return $diff->render($renderer); + } +} diff --git a/framework/yii/helpers/AbstractVarDumper.php b/framework/yii/helpers/AbstractVarDumper.php new file mode 100644 index 0000000..2c9f194 --- /dev/null +++ b/framework/yii/helpers/AbstractVarDumper.php @@ -0,0 +1,127 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\helpers; + +/** + * AbstractVarDumper provides concrete implementation for [[VarDumper]]. + * + * Do not use AbstractVarDumper. Use [[VarDumper]] instead. + * + * @author Qiang Xue + * @since 2.0 + */ +abstract class AbstractVarDumper +{ + private static $_objects; + private static $_output; + private static $_depth; + + /** + * Displays a variable. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + */ + public static function dump($var, $depth = 10, $highlight = false) + { + echo static::dumpAsString($var, $depth, $highlight); + } + + /** + * Dumps a variable in terms of a string. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + * @return string the string representation of the variable + */ + public static function dumpAsString($var, $depth = 10, $highlight = false) + { + self::$_output = ''; + self::$_objects = array(); + self::$_depth = $depth; + self::dumpInternal($var, 0); + if ($highlight) { + $result = highlight_string("/', '', $result, 1); + } + return self::$_output; + } + + /** + * @param mixed $var variable to be dumped + * @param integer $level depth level + */ + private static function dumpInternal($var, $level) + { + switch (gettype($var)) { + case 'boolean': + self::$_output .= $var ? 'true' : 'false'; + break; + case 'integer': + self::$_output .= "$var"; + break; + case 'double': + self::$_output .= "$var"; + break; + case 'string': + self::$_output .= "'" . addslashes($var) . "'"; + break; + case 'resource': + self::$_output .= '{resource}'; + break; + case 'NULL': + self::$_output .= "null"; + break; + case 'unknown type': + self::$_output .= '{unknown}'; + break; + case 'array': + if (self::$_depth <= $level) { + self::$_output .= 'array(...)'; + } elseif (empty($var)) { + self::$_output .= 'array()'; + } else { + $keys = array_keys($var); + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "array\n" . $spaces . '('; + foreach ($keys as $key) { + self::$_output .= "\n" . $spaces . ' '; + self::dumpInternal($key, 0); + self::$_output .= ' => '; + self::dumpInternal($var[$key], $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + case 'object': + if (($id = array_search($var, self::$_objects, true)) !== false) { + self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; + } elseif (self::$_depth <= $level) { + self::$_output .= get_class($var) . '(...)'; + } else { + $id = array_push(self::$_objects, $var); + $className = get_class($var); + $members = (array)$var; + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "$className#$id\n" . $spaces . '('; + foreach ($members as $key => $value) { + $keyDisplay = strtr(trim($key), array("\0" => ':')); + self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; + self::dumpInternal($value, $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + } + } +} diff --git a/framework/yii/helpers/ArrayHelper.php b/framework/yii/helpers/ArrayHelper.php index 085104b..a63d3c2 100644 --- a/framework/yii/helpers/ArrayHelper.php +++ b/framework/yii/helpers/ArrayHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class ArrayHelper extends ArrayHelperBase +class ArrayHelper extends AbstractArrayHelper { } diff --git a/framework/yii/helpers/ArrayHelperBase.php b/framework/yii/helpers/ArrayHelperBase.php deleted file mode 100644 index 59129de..0000000 --- a/framework/yii/helpers/ArrayHelperBase.php +++ /dev/null @@ -1,451 +0,0 @@ - - * @since 2.0 - */ -class ArrayHelperBase -{ - /** - * Converts an object or an array of objects into an array. - * @param object|array $object the object to be converted into an array - * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays. - * The properties specified for each class is an array of the following format: - * - * ~~~ - * array( - * 'app\models\Post' => array( - * 'id', - * 'title', - * // the key name in array result => property name - * 'createTime' => 'create_time', - * // the key name in array result => anonymous function - * 'length' => function ($post) { - * return strlen($post->content); - * }, - * ), - * ) - * ~~~ - * - * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: - * - * ~~~ - * array( - * 'id' => 123, - * 'title' => 'test', - * 'createTime' => '2013-01-01 12:00AM', - * 'length' => 301, - * ) - * ~~~ - * - * @param boolean $recursive whether to recursively converts properties which are objects into arrays. - * @return array the array representation of the object - */ - public static function toArray($object, $properties = array(), $recursive = true) - { - if (!empty($properties) && is_object($object)) { - $className = get_class($object); - if (!empty($properties[$className])) { - $result = array(); - foreach ($properties[$className] as $key => $name) { - if (is_int($key)) { - $result[$name] = $object->$name; - } else { - $result[$key] = static::getValue($object, $name); - } - } - return $result; - } - } - if ($object instanceof Arrayable) { - $object = $object->toArray(); - if (!$recursive) { - return $object; - } - } - $result = array(); - foreach ($object as $key => $value) { - if ($recursive && (is_array($value) || is_object($value))) { - $result[$key] = static::toArray($value, true); - } else { - $result[$key] = $value; - } - } - return $result; - } - - /** - * Merges two or more arrays into one recursively. - * If each array has an element with the same string key value, the latter - * will overwrite the former (different from array_merge_recursive). - * Recursive merging will be conducted if both arrays have an element of array - * type and are having the same key. - * For integer-keyed elements, the elements from the latter array will - * be appended to the former array. - * @param array $a array to be merged to - * @param array $b array to be merged from. You can specify additional - * arrays via third argument, fourth argument etc. - * @return array the merged array (the original arrays are not changed.) - */ - public static function merge($a, $b) - { - $args = func_get_args(); - $res = array_shift($args); - while (!empty($args)) { - $next = array_shift($args); - foreach ($next as $k => $v) { - if (is_integer($k)) { - isset($res[$k]) ? $res[] = $v : $res[$k] = $v; - } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { - $res[$k] = self::merge($res[$k], $v); - } else { - $res[$k] = $v; - } - } - } - return $res; - } - - /** - * Retrieves the value of an array element or object property with the given key or property name. - * If the key does not exist in the array, the default value will be returned instead. - * - * Below are some usage examples, - * - * ~~~ - * // working with array - * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); - * // working with object - * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); - * // working with anonymous function - * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) { - * return $user->firstName . ' ' . $user->lastName; - * }); - * ~~~ - * - * @param array|object $array array or object to extract value from - * @param string|\Closure $key key name of the array element, or property name of the object, - * or an anonymous function returning the value. The anonymous function signature should be: - * `function($array, $defaultValue)`. - * @param mixed $default the default value to be returned if the specified key does not exist - * @return mixed the value of the element if found, default value otherwise - */ - public static function getValue($array, $key, $default = null) - { - if ($key instanceof \Closure) { - return $key($array, $default); - } elseif (is_array($array)) { - return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default; - } else { - return $array->$key; - } - } - - /** - * Removes an item from an array and returns the value. If the key does not exist in the array, the default value - * will be returned instead. - * - * Usage examples, - * - * ~~~ - * // $array = array('type' => 'A', 'options' => array(1, 2)); - * // working with array - * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); - * // $array content - * // $array = array('options' => array(1, 2)); - * ~~~ - * - * @param array $array the array to extract value from - * @param string $key key name of the array element - * @param mixed $default the default value to be returned if the specified key does not exist - * @return mixed|null the value of the element if found, default value otherwise - */ - public static function remove(&$array, $key, $default = null) - { - if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { - $value = $array[$key]; - unset($array[$key]); - return $value; - } - return $default; - } - - /** - * Indexes an array according to a specified key. - * The input array should be multidimensional or an array of objects. - * - * The key can be a key name of the sub-array, a property name of object, or an anonymous - * function which returns the key value given an array element. - * - * If a key value is null, the corresponding array element will be discarded and not put in the result. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); - * $result = ArrayHelper::index($array, 'id'); - * // the result is: - * // array( - * // '123' => array('id' => '123', 'data' => 'abc'), - * // '345' => array('id' => '345', 'data' => 'def'), - * // ) - * - * // using anonymous function - * $result = ArrayHelper::index($array, function ($element) { - * return $element['id']; - * }); - * ~~~ - * - * @param array $array the array that needs to be indexed - * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array - * @return array the indexed array - */ - public static function index($array, $key) - { - $result = array(); - foreach ($array as $element) { - $value = static::getValue($element, $key); - $result[$value] = $element; - } - return $result; - } - - /** - * Returns the values of a specified column in an array. - * The input array should be multidimensional or an array of objects. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); - * $result = ArrayHelper::getColumn($array, 'id'); - * // the result is: array( '123', '345') - * - * // using anonymous function - * $result = ArrayHelper::getColumn($array, function ($element) { - * return $element['id']; - * }); - * ~~~ - * - * @param array $array - * @param string|\Closure $name - * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array - * will be re-indexed with integers. - * @return array the list of column values - */ - public static function getColumn($array, $name, $keepKeys = true) - { - $result = array(); - if ($keepKeys) { - foreach ($array as $k => $element) { - $result[$k] = static::getValue($element, $name); - } - } else { - foreach ($array as $element) { - $result[] = static::getValue($element, $name); - } - } - - return $result; - } - - /** - * Builds a map (key-value pairs) from a multidimensional array or an array of objects. - * The `$from` and `$to` parameters specify the key names or property names to set up the map. - * Optionally, one can further group the map according to a grouping field `$group`. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), - * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), - * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), - * ); - * - * $result = ArrayHelper::map($array, 'id', 'name'); - * // the result is: - * // array( - * // '123' => 'aaa', - * // '124' => 'bbb', - * // '345' => 'ccc', - * // ) - * - * $result = ArrayHelper::map($array, 'id', 'name', 'class'); - * // the result is: - * // array( - * // 'x' => array( - * // '123' => 'aaa', - * // '124' => 'bbb', - * // ), - * // 'y' => array( - * // '345' => 'ccc', - * // ), - * // ) - * ~~~ - * - * @param array $array - * @param string|\Closure $from - * @param string|\Closure $to - * @param string|\Closure $group - * @return array - */ - public static function map($array, $from, $to, $group = null) - { - $result = array(); - foreach ($array as $element) { - $key = static::getValue($element, $from); - $value = static::getValue($element, $to); - if ($group !== null) { - $result[static::getValue($element, $group)][$key] = $value; - } else { - $result[$key] = $value; - } - } - return $result; - } - - /** - * Sorts an array of objects or arrays (with the same structure) by one or several keys. - * @param array $array the array to be sorted. The array will be modified after calling this method. - * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array - * elements, a property name of the objects, or an anonymous function returning the values for comparison - * purpose. The anonymous function signature should be: `function($item)`. - * To sort by multiple keys, provide an array of keys here. - * @param boolean|array $descending whether to sort in descending or ascending order. When - * sorting by multiple keys with different descending orders, use an array of descending flags. - * @param integer|array $sortFlag the PHP sort flag. Valid values include - * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`. - * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php) - * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags. - * @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter - * is used only when `$sortFlag` is `SORT_STRING`. - * When sorting by multiple keys with different case sensitivities, use an array of boolean values. - * @throws InvalidParamException if the $descending or $sortFlag parameters do not have - * correct number of elements as that of $key. - */ - public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true) - { - $keys = is_array($key) ? $key : array($key); - if (empty($keys) || empty($array)) { - return; - } - $n = count($keys); - if (is_scalar($descending)) { - $descending = array_fill(0, $n, $descending); - } elseif (count($descending) !== $n) { - throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); - } - if (is_scalar($sortFlag)) { - $sortFlag = array_fill(0, $n, $sortFlag); - } elseif (count($sortFlag) !== $n) { - throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.'); - } - if (is_scalar($caseSensitive)) { - $caseSensitive = array_fill(0, $n, $caseSensitive); - } elseif (count($caseSensitive) !== $n) { - throw new InvalidParamException('The length of $caseSensitive parameter must be the same as that of $keys.'); - } - $args = array(); - foreach ($keys as $i => $key) { - $flag = $sortFlag[$i]; - $cs = $caseSensitive[$i]; - if (!$cs && ($flag === SORT_STRING)) { - if (defined('SORT_FLAG_CASE')) { - $flag = $flag | SORT_FLAG_CASE; - $args[] = static::getColumn($array, $key); - } else { - $column = array(); - foreach (static::getColumn($array, $key) as $k => $value) { - $column[$k] = mb_strtolower($value); - } - $args[] = $column; - } - } else { - $args[] = static::getColumn($array, $key); - } - $args[] = $descending[$i] ? SORT_DESC : SORT_ASC; - $args[] = $flag; - } - $args[] = &$array; - call_user_func_array('array_multisort', $args); - } - - /** - * Encodes special characters in an array of strings into HTML entities. - * Both the array keys and values will be encoded. - * If a value is an array, this method will also encode it recursively. - * @param array $data data to be encoded - * @param boolean $valuesOnly whether to encode array values only. If false, - * both the array keys and array values will be encoded. - * @param string $charset the charset that the data is using. If not set, - * [[\yii\base\Application::charset]] will be used. - * @return array the encoded data - * @see http://www.php.net/manual/en/function.htmlspecialchars.php - */ - public static function htmlEncode($data, $valuesOnly = true, $charset = null) - { - if ($charset === null) { - $charset = Yii::$app->charset; - } - $d = array(); - foreach ($data as $key => $value) { - if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars($key, ENT_QUOTES, $charset); - } - if (is_string($value)) { - $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); - } elseif (is_array($value)) { - $d[$key] = static::htmlEncode($value, $charset); - } - } - return $d; - } - - /** - * Decodes HTML entities into the corresponding characters in an array of strings. - * Both the array keys and values will be decoded. - * If a value is an array, this method will also decode it recursively. - * @param array $data data to be decoded - * @param boolean $valuesOnly whether to decode array values only. If false, - * both the array keys and array values will be decoded. - * @return array the decoded data - * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php - */ - public static function htmlDecode($data, $valuesOnly = true) - { - $d = array(); - foreach ($data as $key => $value) { - if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars_decode($key, ENT_QUOTES); - } - if (is_string($value)) { - $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); - } elseif (is_array($value)) { - $d[$key] = static::htmlDecode($value); - } - } - return $d; - } -} diff --git a/framework/yii/helpers/Console.php b/framework/yii/helpers/Console.php index c0b7b32..9b0656e 100644 --- a/framework/yii/helpers/Console.php +++ b/framework/yii/helpers/Console.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Carsten Brandt * @since 2.0 */ -class Console extends ConsoleBase +class Console extends AbstractConsole { } diff --git a/framework/yii/helpers/ConsoleBase.php b/framework/yii/helpers/ConsoleBase.php deleted file mode 100644 index a985291..0000000 --- a/framework/yii/helpers/ConsoleBase.php +++ /dev/null @@ -1,835 +0,0 @@ - - * @since 2.0 - */ -class ConsoleBase -{ - const FG_BLACK = 30; - const FG_RED = 31; - const FG_GREEN = 32; - const FG_YELLOW = 33; - const FG_BLUE = 34; - const FG_PURPLE = 35; - const FG_CYAN = 36; - const FG_GREY = 37; - - const BG_BLACK = 40; - const BG_RED = 41; - const BG_GREEN = 42; - const BG_YELLOW = 43; - const BG_BLUE = 44; - const BG_PURPLE = 45; - const BG_CYAN = 46; - const BG_GREY = 47; - - const RESET = 0; - const NORMAL = 0; - const BOLD = 1; - const ITALIC = 3; - const UNDERLINE = 4; - const BLINK = 5; - const NEGATIVE = 7; - const CONCEALED = 8; - const CROSSED_OUT = 9; - const FRAMED = 51; - const ENCIRCLED = 52; - const OVERLINED = 53; - - /** - * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $rows number of rows the cursor should be moved up - */ - public static function moveCursorUp($rows = 1) - { - echo "\033[" . (int)$rows . 'A'; - } - - /** - * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $rows number of rows the cursor should be moved down - */ - public static function moveCursorDown($rows = 1) - { - echo "\033[" . (int)$rows . 'B'; - } - - /** - * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $steps number of steps the cursor should be moved forward - */ - public static function moveCursorForward($steps = 1) - { - echo "\033[" . (int)$steps . 'C'; - } - - /** - * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $steps number of steps the cursor should be moved backward - */ - public static function moveCursorBackward($steps = 1) - { - echo "\033[" . (int)$steps . 'D'; - } - - /** - * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. - * @param integer $lines number of lines the cursor should be moved down - */ - public static function moveCursorNextLine($lines = 1) - { - echo "\033[" . (int)$lines . 'E'; - } - - /** - * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. - * @param integer $lines number of lines the cursor should be moved up - */ - public static function moveCursorPrevLine($lines = 1) - { - echo "\033[" . (int)$lines . 'F'; - } - - /** - * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. - * @param integer $column 1-based column number, 1 is the left edge of the screen. - * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. - */ - public static function moveCursorTo($column, $row = null) - { - if ($row === null) { - echo "\033[" . (int)$column . 'G'; - } else { - echo "\033[" . (int)$row . ';' . (int)$column . 'H'; - } - } - - /** - * Scrolls whole page up by sending ANSI control code SU to the terminal. - * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. - * @param int $lines number of lines to scroll up - */ - public static function scrollUp($lines = 1) - { - echo "\033[" . (int)$lines . "S"; - } - - /** - * Scrolls whole page down by sending ANSI control code SD to the terminal. - * New lines are added at the top. This is not supported by ANSI.SYS used in windows. - * @param int $lines number of lines to scroll down - */ - public static function scrollDown($lines = 1) - { - echo "\033[" . (int)$lines . "T"; - } - - /** - * Saves the current cursor position by sending ANSI control code SCP to the terminal. - * Position can then be restored with {@link restoreCursorPosition}. - */ - public static function saveCursorPosition() - { - echo "\033[s"; - } - - /** - * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. - */ - public static function restoreCursorPosition() - { - echo "\033[u"; - } - - /** - * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. - * Use {@link showCursor} to bring it back. - * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. - */ - public static function hideCursor() - { - echo "\033[?25l"; - } - - /** - * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. - */ - public static function showCursor() - { - echo "\033[?25h"; - } - - /** - * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. - * Cursor position will not be changed. - * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. - */ - public static function clearScreen() - { - echo "\033[2J"; - } - - /** - * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. - * Cursor position will not be changed. - */ - public static function clearScreenBeforeCursor() - { - echo "\033[1J"; - } - - /** - * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. - * Cursor position will not be changed. - */ - public static function clearScreenAfterCursor() - { - echo "\033[0J"; - } - - /** - * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLine() - { - echo "\033[2K"; - } - - /** - * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLineBeforeCursor() - { - echo "\033[1K"; - } - - /** - * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLineAfterCursor() - { - echo "\033[0K"; - } - - /** - * Returns the ANSI format code. - * - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @return string The ANSI format code according to the given formatting constants. - */ - public static function ansiFormatCode($format) - { - return "\033[" . implode(';', $format) . 'm'; - } - - /** - * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards. - * - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @see ansiFormatCode() - * @see ansiFormatEnd() - */ - public static function beginAnsiFormat($format) - { - echo "\033[" . implode(';', $format) . 'm'; - } - - /** - * Resets any ANSI format set by previous method [[ansiFormatBegin()]] - * Any output after this will have default text format. - * This is equal to calling - * - * ```php - * echo Console::ansiFormatCode(array(Console::RESET)) - * ``` - */ - public static function endAnsiFormat() - { - echo "\033[0m"; - } - - /** - * Will return a string formatted with the given ANSI style - * - * @param string $string the string to be formatted - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @return string - */ - public static function ansiFormat($string, $format = array()) - { - $code = implode(';', $format); - return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; - } - - /** - * Returns the ansi format code for xterm foreground color. - * You can pass the return value of this to one of the formatting methods: - * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] - * - * @param integer $colorCode xterm color code - * @return string - * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors - */ - public static function xtermFgColor($colorCode) - { - return '38;5;' . $colorCode; - } - - /** - * Returns the ansi format code for xterm background color. - * You can pass the return value of this to one of the formatting methods: - * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] - * - * @param integer $colorCode xterm color code - * @return string - * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors - */ - public static function xtermBgColor($colorCode) - { - return '48;5;' . $colorCode; - } - - /** - * Strips ANSI control codes from a string - * - * @param string $string String to strip - * @return string - */ - public static function stripAnsiFormat($string) - { - return preg_replace('/\033\[[\d;?]*\w/', '', $string); - } - - /** - * Converts an ANSI formatted string to HTML - * @param $string - * @return mixed - */ - // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 - public static function ansiToHtml($string) - { - $tags = 0; - return preg_replace_callback( - '/\033\[[\d;]+m/', - function ($ansi) use (&$tags) { - $styleA = array(); - foreach (explode(';', $ansi) as $controlCode) { - switch ($controlCode) { - case self::FG_BLACK: - $style = array('color' => '#000000'); - break; - case self::FG_BLUE: - $style = array('color' => '#000078'); - break; - case self::FG_CYAN: - $style = array('color' => '#007878'); - break; - case self::FG_GREEN: - $style = array('color' => '#007800'); - break; - case self::FG_GREY: - $style = array('color' => '#787878'); - break; - case self::FG_PURPLE: - $style = array('color' => '#780078'); - break; - case self::FG_RED: - $style = array('color' => '#780000'); - break; - case self::FG_YELLOW: - $style = array('color' => '#787800'); - break; - case self::BG_BLACK: - $style = array('background-color' => '#000000'); - break; - case self::BG_BLUE: - $style = array('background-color' => '#000078'); - break; - case self::BG_CYAN: - $style = array('background-color' => '#007878'); - break; - case self::BG_GREEN: - $style = array('background-color' => '#007800'); - break; - case self::BG_GREY: - $style = array('background-color' => '#787878'); - break; - case self::BG_PURPLE: - $style = array('background-color' => '#780078'); - break; - case self::BG_RED: - $style = array('background-color' => '#780000'); - break; - case self::BG_YELLOW: - $style = array('background-color' => '#787800'); - break; - case self::BOLD: - $style = array('font-weight' => 'bold'); - break; - case self::ITALIC: - $style = array('font-style' => 'italic'); - break; - case self::UNDERLINE: - $style = array('text-decoration' => array('underline')); - break; - case self::OVERLINED: - $style = array('text-decoration' => array('overline')); - break; - case self::CROSSED_OUT: - $style = array('text-decoration' => array('line-through')); - break; - case self::BLINK: - $style = array('text-decoration' => array('blink')); - break; - case self::NEGATIVE: // ??? - case self::CONCEALED: - case self::ENCIRCLED: - case self::FRAMED: - // TODO allow resetting codes - break; - case 0: // ansi reset - $return = ''; - for ($n = $tags; $tags > 0; $tags--) { - $return .= ''; - } - return $return; - } - - $styleA = ArrayHelper::merge($styleA, $style); - } - $styleString[] = array(); - foreach ($styleA as $name => $content) { - if ($name === 'text-decoration') { - $content = implode(' ', $content); - } - $styleString[] = $name . ':' . $content; - } - $tags++; - return ' $value) { - echo " $key - $value\n"; - } - echo " ? - Show help\n"; - goto top; - } elseif (!in_array($input, array_keys($options))) { - goto top; - } - return $input; - } - - /** - * Displays and updates a simple progress bar on screen. - * - * @param integer $done the number of items that are completed - * @param integer $total the total value of items that are to be done - * @param integer $size the size of the status bar (optional) - * @see http://snipplr.com/view/29548/ - */ - public static function showProgress($done, $total, $size = 30) - { - static $start; - - // if we go over our bound, just ignore it - if ($done > $total) { - return; - } - - if (empty($start)) { - $start = time(); - } - - $now = time(); - - $percent = (double)($done / $total); - $bar = floor($percent * $size); - - $status = "\r["; - $status .= str_repeat("=", $bar); - if ($bar < $size) { - $status .= ">"; - $status .= str_repeat(" ", $size - $bar); - } else { - $status .= "="; - } - - $display = number_format($percent * 100, 0); - - $status .= "] $display% $done/$total"; - - $rate = ($now - $start) / $done; - $left = $total - $done; - $eta = round($rate * $left, 2); - - $elapsed = $now - $start; - - $status .= " remaining: " . number_format($eta) . " sec. elapsed: " . number_format($elapsed) . " sec."; - - static::stdout("$status "); - - flush(); - - // when done, send a newline - if ($done == $total) { - echo "\n"; - } - } -} diff --git a/framework/yii/helpers/FileHelper.php b/framework/yii/helpers/FileHelper.php index 9241025..919dc09 100644 --- a/framework/yii/helpers/FileHelper.php +++ b/framework/yii/helpers/FileHelper.php @@ -16,6 +16,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class FileHelper extends FileHelperBase +class FileHelper extends AbstractFileHelper { } diff --git a/framework/yii/helpers/FileHelperBase.php b/framework/yii/helpers/FileHelperBase.php deleted file mode 100644 index 0e480da..0000000 --- a/framework/yii/helpers/FileHelperBase.php +++ /dev/null @@ -1,329 +0,0 @@ - - * @author Alex Makarov - * @since 2.0 - */ -class FileHelperBase -{ - /** - * Normalizes a file/directory path. - * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`, - * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux - * will be normalized as '/home/demo'. - * @param string $path the file/directory path to be normalized - * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`. - * @return string the normalized file/directory path - */ - public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) - { - return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds); - } - - /** - * Returns the localized version of a specified file. - * - * The searching is based on the specified language code. In particular, - * a file with the same name will be looked for under the subdirectory - * whose name is the same as the language code. For example, given the file "path/to/view.php" - * and language code "zh_CN", the localized file will be looked for as - * "path/to/zh_CN/view.php". If the file is not found, the original file - * will be returned. - * - * If the target and the source language codes are the same, - * the original file will be returned. - * - * @param string $file the original file - * @param string $language the target language that the file should be localized to. - * If not set, the value of [[\yii\base\Application::language]] will be used. - * @param string $sourceLanguage the language that the original file is in. - * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. - * @return string the matching localized file, or the original file if the localized version is not found. - * If the target and the source language codes are the same, the original file will be returned. - */ - public static function localize($file, $language = null, $sourceLanguage = null) - { - if ($language === null) { - $language = Yii::$app->language; - } - if ($sourceLanguage === null) { - $sourceLanguage = Yii::$app->sourceLanguage; - } - if ($language === $sourceLanguage) { - return $file; - } - $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file); - return is_file($desiredFile) ? $desiredFile : $file; - } - - /** - * Determines the MIME type of the specified file. - * This method will first try to determine the MIME type based on - * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will - * fall back to [[getMimeTypeByExtension()]]. - * @param string $file the file name. - * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`. - * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php). - * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case - * `finfo_open()` cannot determine it. - * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. - */ - public static function getMimeType($file, $magicFile = null, $checkExtension = true) - { - if (function_exists('finfo_open')) { - $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); - if ($info) { - $result = finfo_file($info, $file); - finfo_close($info); - if ($result !== false) { - return $result; - } - } - } - - return $checkExtension ? static::getMimeTypeByExtension($file) : null; - } - - /** - * Determines the MIME type based on the extension name of the specified file. - * This method will use a local map between extension names and MIME types. - * @param string $file the file name. - * @param string $magicFile the path of the file that contains all available MIME type information. - * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used. - * @return string the MIME type. Null is returned if the MIME type cannot be determined. - */ - public static function getMimeTypeByExtension($file, $magicFile = null) - { - static $mimeTypes = array(); - if ($magicFile === null) { - $magicFile = __DIR__ . '/mimeTypes.php'; - } - if (!isset($mimeTypes[$magicFile])) { - $mimeTypes[$magicFile] = require($magicFile); - } - if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { - $ext = strtolower($ext); - if (isset($mimeTypes[$magicFile][$ext])) { - return $mimeTypes[$magicFile][$ext]; - } - } - return null; - } - - /** - * Copies a whole directory as another one. - * The files and sub-directories will also be copied over. - * @param string $src the source directory - * @param string $dst the destination directory - * @param array $options options for directory copy. Valid options are: - * - * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775. - * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. - * - filter: callback, a PHP callback that is called for each directory or file. - * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. - * The callback can return one of the following values: - * - * * true: the directory or file will be copied (the "only" and "except" options will be ignored) - * * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored) - * * null: the "only" and "except" options will determine whether the directory or file should be copied - * - * - only: array, list of patterns that the file paths should match if they want to be copied. - * A path matches a pattern if it contains the pattern string at its end. - * For example, '.php' matches all file paths ending with '.php'. - * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. - * If a file path matches a pattern in both "only" and "except", it will NOT be copied. - * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. - * A path matches a pattern if it contains the pattern string at its end. - * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' - * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; - * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches - * both '/' and '\' in the paths. - * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. - * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. - * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or - * file copied from, while `$to` is the copy target. - */ - public static function copyDirectory($src, $dst, $options = array()) - { - if (!is_dir($dst)) { - static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); - } - - $handle = opendir($src); - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $from = $src . DIRECTORY_SEPARATOR . $file; - $to = $dst . DIRECTORY_SEPARATOR . $file; - if (static::filterPath($from, $options)) { - if (is_file($from)) { - copy($from, $to); - if (isset($options['fileMode'])) { - @chmod($to, $options['fileMode']); - } - } else { - static::copyDirectory($from, $to, $options); - } - if (isset($options['afterCopy'])) { - call_user_func($options['afterCopy'], $from, $to); - } - } - } - closedir($handle); - } - - /** - * Removes a directory (and all its content) recursively. - * @param string $dir the directory to be deleted recursively. - */ - public static function removeDirectory($dir) - { - if (!is_dir($dir) || !($handle = opendir($dir))) { - return; - } - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $path = $dir . DIRECTORY_SEPARATOR . $file; - if (is_file($path)) { - unlink($path); - } else { - static::removeDirectory($path); - } - } - closedir($handle); - rmdir($dir); - } - - /** - * Returns the files found under the specified directory and subdirectories. - * @param string $dir the directory under which the files will be looked for. - * @param array $options options for file searching. Valid options are: - * - * - filter: callback, a PHP callback that is called for each directory or file. - * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. - * The callback can return one of the following values: - * - * * true: the directory or file will be returned (the "only" and "except" options will be ignored) - * * false: the directory or file will NOT be returned (the "only" and "except" options will be ignored) - * * null: the "only" and "except" options will determine whether the directory or file should be returned - * - * - only: array, list of patterns that the file paths should match if they want to be returned. - * A path matches a pattern if it contains the pattern string at its end. - * For example, '.php' matches all file paths ending with '.php'. - * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. - * If a file path matches a pattern in both "only" and "except", it will NOT be returned. - * - except: array, list of patterns that the file paths or directory paths should match if they want to be excluded from the result. - * A path matches a pattern if it contains the pattern string at its end. - * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' - * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; - * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches - * both '/' and '\' in the paths. - * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. - * @return array files found under the directory. The file list is sorted. - */ - public static function findFiles($dir, $options = array()) - { - $list = array(); - $handle = opendir($dir); - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $path = $dir . DIRECTORY_SEPARATOR . $file; - if (static::filterPath($path, $options)) { - if (is_file($path)) { - $list[] = $path; - } elseif (!isset($options['recursive']) || $options['recursive']) { - $list = array_merge($list, static::findFiles($path, $options)); - } - } - } - closedir($handle); - return $list; - } - - /** - * Checks if the given file path satisfies the filtering options. - * @param string $path the path of the file or directory to be checked - * @param array $options the filtering options. See [[findFiles()]] for explanations of - * the supported options. - * @return boolean whether the file or directory satisfies the filtering options. - */ - public static function filterPath($path, $options) - { - if (isset($options['filter'])) { - $result = call_user_func($options['filter'], $path); - if (is_bool($result)) { - return $result; - } - } - $path = str_replace('\\', '/', $path); - if ($isDir = is_dir($path)) { - $path .= '/'; - } - $n = StringHelper::strlen($path); - - if (!empty($options['except'])) { - foreach ($options['except'] as $name) { - if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { - return false; - } - } - } - - if (!$isDir && !empty($options['only'])) { - foreach ($options['only'] as $name) { - if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { - return true; - } - } - return false; - } - return true; - } - - /** - * Creates a new directory. - * - * This method is similar to the PHP `mkdir()` function except that - * it uses `chmod()` to set the permission of the created directory - * in order to avoid the impact of the `umask` setting. - * - * @param string $path path of the directory to be created. - * @param integer $mode the permission to be set for the created directory. - * @param boolean $recursive whether to create parent directories if they do not exist. - * @return boolean whether the directory is created successfully - */ - public static function createDirectory($path, $mode = 0775, $recursive = true) - { - if (is_dir($path)) { - return true; - } - $parentDir = dirname($path); - if ($recursive && !is_dir($parentDir)) { - static::createDirectory($parentDir, $mode, true); - } - $result = mkdir($path, $mode); - chmod($path, $mode); - return $result; - } -} diff --git a/framework/yii/helpers/Html.php b/framework/yii/helpers/Html.php index 8e4f1c9..0715c6c 100644 --- a/framework/yii/helpers/Html.php +++ b/framework/yii/helpers/Html.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class Html extends HtmlBase +class Html extends AbstractHtml { } diff --git a/framework/yii/helpers/HtmlBase.php b/framework/yii/helpers/HtmlBase.php deleted file mode 100644 index a5786cb..0000000 --- a/framework/yii/helpers/HtmlBase.php +++ /dev/null @@ -1,1599 +0,0 @@ - - * @since 2.0 - */ -class HtmlBase -{ - /** - * @var array list of void elements (element name => 1) - * @see http://www.w3.org/TR/html-markup/syntax.html#void-element - */ - public static $voidElements = array( - 'area' => 1, - 'base' => 1, - 'br' => 1, - 'col' => 1, - 'command' => 1, - 'embed' => 1, - 'hr' => 1, - 'img' => 1, - 'input' => 1, - 'keygen' => 1, - 'link' => 1, - 'meta' => 1, - 'param' => 1, - 'source' => 1, - 'track' => 1, - 'wbr' => 1, - ); - /** - * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes - * that are rendered by [[renderTagAttributes()]]. - */ - public static $attributeOrder = array( - 'type', - 'id', - 'class', - 'name', - 'value', - - 'href', - 'src', - 'action', - 'method', - - 'selected', - 'checked', - 'readonly', - 'disabled', - 'multiple', - - 'size', - 'maxlength', - 'width', - 'height', - 'rows', - 'cols', - - 'alt', - 'title', - 'rel', - 'media', - ); - - /** - * Encodes special characters into HTML entities. - * The [[yii\base\Application::charset|application charset]] will be used for encoding. - * @param string $content the content to be encoded - * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false, - * HTML entities in `$content` will not be further encoded. - * @return string the encoded content - * @see decode - * @see http://www.php.net/manual/en/function.htmlspecialchars.php - */ - public static function encode($content, $doubleEncode = true) - { - return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset, $doubleEncode); - } - - /** - * Decodes special HTML entities back to the corresponding characters. - * This is the opposite of [[encode()]]. - * @param string $content the content to be decoded - * @return string the decoded content - * @see encode - * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php - */ - public static function decode($content) - { - return htmlspecialchars_decode($content, ENT_QUOTES); - } - - /** - * Generates a complete HTML tag. - * @param string $name the tag name - * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. - * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated HTML tag - * @see beginTag - * @see endTag - */ - public static function tag($name, $content = '', $options = array()) - { - $html = "<$name" . static::renderTagAttributes($options) . '>'; - return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content"; - } - - /** - * Generates a start tag. - * @param string $name the tag name - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated start tag - * @see endTag - * @see tag - */ - public static function beginTag($name, $options = array()) - { - return "<$name" . static::renderTagAttributes($options) . '>'; - } - - /** - * Generates an end tag. - * @param string $name the tag name - * @return string the generated end tag - * @see beginTag - * @see tag - */ - public static function endTag($name) - { - return ""; - } - - /** - * Generates a style tag. - * @param string $content the style content - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "text/css" will be used. - * @return string the generated style tag - */ - public static function style($content, $options = array()) - { - return static::tag('style', $content, $options); - } - - /** - * Generates a script tag. - * @param string $content the script content - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. - * @return string the generated script tag - */ - public static function script($content, $options = array()) - { - return static::tag('script', $content, $options); - } - - /** - * Generates a link tag that refers to an external CSS file. - * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated link tag - * @see url - */ - public static function cssFile($url, $options = array()) - { - $options['rel'] = 'stylesheet'; - $options['href'] = static::url($url); - return static::tag('link', '', $options); - } - - /** - * Generates a script tag that refers to an external JavaScript file. - * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated script tag - * @see url - */ - public static function jsFile($url, $options = array()) - { - $options['src'] = static::url($url); - return static::tag('script', '', $options); - } - - /** - * Generates a form start tag. - * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. - * @param string $method the form submission method, such as "post", "get", "put", "delete" (case-insensitive). - * Since most browsers only support "post" and "get", if other methods are given, they will - * be simulated using "post", and a hidden input will be added which contains the actual method type. - * See [[\yii\web\Request::restVar]] for more details. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated form start tag. - * @see endForm - */ - public static function beginForm($action = '', $method = 'post', $options = array()) - { - $action = static::url($action); - - $hiddenInputs = array(); - - $request = Yii::$app->getRequest(); - if ($request instanceof Request) { - if (strcasecmp($method, 'get') && strcasecmp($method, 'post')) { - // simulate PUT, DELETE, etc. via POST - $hiddenInputs[] = static::hiddenInput($request->restVar, $method); - $method = 'post'; - } - if ($request->enableCsrfValidation) { - $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); - } - } - - if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { - // query parameters in the action are ignored for GET method - // we use hidden fields to add them back - foreach (explode('&', substr($action, $pos + 1)) as $pair) { - if (($pos1 = strpos($pair, '=')) !== false) { - $hiddenInputs[] = static::hiddenInput( - urldecode(substr($pair, 0, $pos1)), - urldecode(substr($pair, $pos1 + 1)) - ); - } else { - $hiddenInputs[] = static::hiddenInput(urldecode($pair), ''); - } - } - $action = substr($action, 0, $pos); - } - - $options['action'] = $action; - $options['method'] = $method; - $form = static::beginTag('form', $options); - if (!empty($hiddenInputs)) { - $form .= "\n" . implode("\n", $hiddenInputs); - } - - return $form; - } - - /** - * Generates a form end tag. - * @return string the generated tag - * @see beginForm - */ - public static function endForm() - { - return ''; - } - - /** - * Generates a hyperlink tag. - * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is coming from end users, you should consider [[encode()]] - * it to prevent XSS attacks. - * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]] - * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute - * will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated hyperlink - * @see url - */ - public static function a($text, $url = null, $options = array()) - { - if ($url !== null) { - $options['href'] = static::url($url); - } - return static::tag('a', $text, $options); - } - - /** - * Generates a mailto hyperlink. - * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is coming from end users, you should consider [[encode()]] - * it to prevent XSS attacks. - * @param string $email email address. If this is null, the first parameter (link body) will be treated - * as the email address and used. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated mailto link - */ - public static function mailto($text, $email = null, $options = array()) - { - $options['href'] = 'mailto:' . ($email === null ? $text : $email); - return static::tag('a', $text, $options); - } - - /** - * Generates an image tag. - * @param string $src the image URL. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated image tag - */ - public static function img($src, $options = array()) - { - $options['src'] = static::url($src); - if (!isset($options['alt'])) { - $options['alt'] = ''; - } - return static::tag('img', '', $options); - } - - /** - * Generates a label tag. - * @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is is coming from end users, you should [[encode()]] - * it to prevent XSS attacks. - * @param string $for the ID of the HTML element that this label is associated with. - * If this is null, the "for" attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated label tag - */ - public static function label($content, $for = null, $options = array()) - { - $options['for'] = $for; - return static::tag('label', $content, $options); - } - - /** - * Generates a button tag. - * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. - * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, - * you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function button($content = 'Button', $options = array()) - { - return static::tag('button', $content, $options); - } - - /** - * Generates a submit button tag. - * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. - * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, - * you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated submit button tag - */ - public static function submitButton($content = 'Submit', $options = array()) - { - $options['type'] = 'submit'; - return static::button($content, $options); - } - - /** - * Generates a reset button tag. - * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. - * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, - * you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated reset button tag - */ - public static function resetButton($content = 'Reset', $options = array()) - { - $options['type'] = 'reset'; - return static::button($content, $options); - } - - /** - * Generates an input type of the given type. - * @param string $type the type attribute. - * @param string $name the name attribute. If it is null, the name attribute will not be generated. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated input tag - */ - public static function input($type, $name = null, $value = null, $options = array()) - { - $options['type'] = $type; - $options['name'] = $name; - $options['value'] = $value === null ? null : (string)$value; - return static::tag('input', '', $options); - } - - /** - * Generates an input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function buttonInput($label = 'Button', $options = array()) - { - $options['type'] = 'button'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a submit input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function submitInput($label = 'Submit', $options = array()) - { - $options['type'] = 'submit'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a reset input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. - * Attributes whose value is null will be ignored and not put in the tag returned. - * @return string the generated button tag - */ - public static function resetInput($label = 'Reset', $options = array()) - { - $options['type'] = 'reset'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a text input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function textInput($name, $value = null, $options = array()) - { - return static::input('text', $name, $value, $options); - } - - /** - * Generates a hidden input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function hiddenInput($name, $value = null, $options = array()) - { - return static::input('hidden', $name, $value, $options); - } - - /** - * Generates a password input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function passwordInput($name, $value = null, $options = array()) - { - return static::input('password', $name, $value, $options); - } - - /** - * Generates a file input field. - * To use a file input field, you should set the enclosing form's "enctype" attribute to - * be "multipart/form-data". After the form is submitted, the uploaded file information - * can be obtained via $_FILES[$name] (see PHP documentation). - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function fileInput($name, $value = null, $options = array()) - { - return static::input('file', $name, $value, $options); - } - - /** - * Generates a text area input. - * @param string $name the input name - * @param string $value the input value. Note that it will be encoded using [[encode()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated text area tag - */ - public static function textarea($name, $value = '', $options = array()) - { - $options['name'] = $name; - return static::tag('textarea', static::encode($value), $options); - } - - /** - * Generates a radio button input. - * @param string $name the name attribute. - * @param boolean $checked whether the radio button should be checked. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute - * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * When this option is specified, the radio button will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated radio button tag - */ - public static function radio($name, $checked = false, $options = array()) - { - $options['checked'] = (boolean)$checked; - $value = array_key_exists('value', $options) ? $options['value'] : '1'; - if (isset($options['uncheck'])) { - // add a hidden field so that if the radio button is not selected, it still submits a value - $hidden = static::hiddenInput($name, $options['uncheck']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - if (isset($options['label'])) { - $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); - unset($options['label'], $options['labelOptions']); - $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . static::tag('div', $content, array('class' => 'radio')); - } else { - return $hidden . static::input('radio', $name, $value, $options); - } - } - - /** - * Generates a checkbox input. - * @param string $name the name attribute. - * @param boolean $checked whether the checkbox should be checked. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute - * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * When this option is specified, the checkbox will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated checkbox tag - */ - public static function checkbox($name, $checked = false, $options = array()) - { - $options['checked'] = (boolean)$checked; - $value = array_key_exists('value', $options) ? $options['value'] : '1'; - if (isset($options['uncheck'])) { - // add a hidden field so that if the checkbox is not selected, it still submits a value - $hidden = static::hiddenInput($name, $options['uncheck']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - if (isset($options['label'])) { - $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); - unset($options['label'], $options['labelOptions']); - $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . static::tag('div', $content, array('class' => 'checkbox')); - } else { - return $hidden . static::input('checkbox', $name, $value, $options); - } - } - - /** - * Generates a drop-down list. - * @param string $name the input name - * @param string $selection the selected value - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated drop-down list tag - */ - public static function dropDownList($name, $selection = null, $items = array(), $options = array()) - { - $options['name'] = $name; - $selectOptions = static::renderSelectOptions($selection, $items, $options); - return static::tag('select', "\n" . $selectOptions . "\n", $options); - } - - /** - * Generates a list box. - * @param string $name the input name - * @param string|array $selection the selected value(s) - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - unselect: string, the value that will be submitted when no option is selected. - * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple - * mode, we can still obtain the posted unselect value. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated list box tag - */ - public static function listBox($name, $selection = null, $items = array(), $options = array()) - { - if (!isset($options['size'])) { - $options['size'] = 4; - } - if (!empty($options['multiple']) && substr($name, -2) !== '[]') { - $name .= '[]'; - } - $options['name'] = $name; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - if (substr($name, -2) === '[]') { - $name = substr($name, 0, -2); - } - $hidden = static::hiddenInput($name, $options['unselect']); - unset($options['unselect']); - } else { - $hidden = ''; - } - $selectOptions = static::renderSelectOptions($selection, $items, $options); - return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); - } - - /** - * Generates a list of checkboxes. - * A checkbox list allows multiple selection, like [[listBox()]]. - * As a result, the corresponding submitted value is an array. - * @param string $name the name attribute of each checkbox. - * @param string|array $selection the selected value(s). - * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. - * @param array $options options (name => config) for the checkbox list container tag. - * The following options are specially handled: - * - * - tag: string, the tag name of the container element. - * - unselect: string, the value that should be submitted when none of the checkboxes is selected. - * By setting this option, a hidden input will be generated. - * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. - * This option is ignored if `item` option is set. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the checkbox in the whole list; $label - * is the label for the checkbox; and $name, $value and $checked represent the name, - * value and the checked status of the checkbox input, respectively. - * @return string the generated checkbox list - */ - public static function checkboxList($name, $selection = null, $items = array(), $options = array()) - { - if (substr($name, -2) !== '[]') { - $name .= '[]'; - } - - $formatter = isset($options['item']) ? $options['item'] : null; - $encode = !isset($options['encode']) || $options['encode']; - $lines = array(); - $index = 0; - foreach ($items as $value => $label) { - $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); - if ($formatter !== null) { - $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); - } else { - $lines[] = static::checkbox($name, $checked, array( - 'value' => $value, - 'label' => $encode ? static::encode($label) : $label, - )); - } - $index++; - } - - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; - $hidden = static::hiddenInput($name2, $options['unselect']); - } else { - $hidden = ''; - } - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); - - return $hidden . static::tag($tag, implode($separator, $lines), $options); - } - - /** - * Generates a list of radio buttons. - * A radio button list is like a checkbox list, except that it only allows single selection. - * @param string $name the name attribute of each radio button. - * @param string|array $selection the selected value(s). - * @param array $items the data item used to generate the radio buttons. - * The array keys are the labels, while the array values are the corresponding radio button values. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - unselect: string, the value that should be submitted when none of the radio buttons is selected. - * By setting this option, a hidden input will be generated. - * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. - * This option is ignored if `item` option is set. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the radio button in the whole list; $label - * is the label for the radio button; and $name, $value and $checked represent the name, - * value and the checked status of the radio button input, respectively. - * @return string the generated radio button list - */ - public static function radioList($name, $selection = null, $items = array(), $options = array()) - { - $encode = !isset($options['encode']) || $options['encode']; - $formatter = isset($options['item']) ? $options['item'] : null; - $lines = array(); - $index = 0; - foreach ($items as $value => $label) { - $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); - if ($formatter !== null) { - $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); - } else { - $lines[] = static::radio($name, $checked, array( - 'value' => $value, - 'label' => $encode ? static::encode($label) : $label, - )); - } - $index++; - } - - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $hidden = static::hiddenInput($name, $options['unselect']); - } else { - $hidden = ''; - } - - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); - - return $hidden . static::tag($tag, implode($separator, $lines), $options); - } - - /** - * Generates an unordered list. - * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. - * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - encode: boolean, whether to HTML-encode the items. Defaults to true. - * This option is ignored if the `item` option is specified. - * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. - * - item: callable, a callback that is used to generate each individual list item. - * The signature of this callback must be: - * - * ~~~ - * function ($item, $index) - * ~~~ - * - * where $index is the array key corresponding to `$item` in `$items`. The callback should return - * the whole list item tag. - * - * @return string the generated unordered list. An empty string is returned if `$items` is empty. - */ - public static function ul($items, $options = array()) - { - if (empty($items)) { - return ''; - } - $tag = isset($options['tag']) ? $options['tag'] : 'ul'; - $encode = !isset($options['encode']) || $options['encode']; - $formatter = isset($options['item']) ? $options['item'] : null; - $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : array(); - unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); - $results = array(); - foreach ($items as $index => $item) { - if ($formatter !== null) { - $results[] = call_user_func($formatter, $item, $index); - } else { - $results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions); - } - } - return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options); - } - - /** - * Generates an ordered list. - * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. - * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - encode: boolean, whether to HTML-encode the items. Defaults to true. - * This option is ignored if the `item` option is specified. - * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. - * - item: callable, a callback that is used to generate each individual list item. - * The signature of this callback must be: - * - * ~~~ - * function ($item, $index) - * ~~~ - * - * where $index is the array key corresponding to `$item` in `$items`. The callback should return - * the whole list item tag. - * - * @return string the generated ordered list. An empty string is returned if `$items` is empty. - */ - public static function ol($items, $options = array()) - { - $options['tag'] = 'ol'; - return static::ul($items, $options); - } - - /** - * Generates a label tag for the given model attribute. - * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * The following options are specially handled: - * - * - label: this specifies the label to be displayed. Note that this will NOT be [[encoded()]]. - * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display - * (after encoding). - * - * @return string the generated label tag - */ - public static function activeLabel($model, $attribute, $options = array()) - { - $attribute = static::getAttributeName($attribute); - $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); - $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); - unset($options['label'], $options['for']); - return static::label($label, $for, $options); - } - - /** - * Generates a tag that contains the first validation error of the specified model attribute. - * Note that even if there is no validation error, this method will still return an empty error tag. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The values will be HTML-encoded - * using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * The following options are specially handled: - * - * - tag: this specifies the tag name. If not set, "div" will be used. - * - * @return string the generated label tag - */ - public static function error($model, $attribute, $options = array()) - { - $attribute = static::getAttributeName($attribute); - $error = $model->getFirstError($attribute); - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag']); - return Html::tag($tag, Html::encode($error), $options); - } - - /** - * Generates an input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param string $type the input type (e.g. 'text', 'password') - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeInput($type, $model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::input($type, $name, $value, $options); - } - - /** - * Generates a text input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeTextInput($model, $attribute, $options = array()) - { - return static::activeInput('text', $model, $attribute, $options); - } - - /** - * Generates a hidden input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeHiddenInput($model, $attribute, $options = array()) - { - return static::activeInput('hidden', $model, $attribute, $options); - } - - /** - * Generates a password input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activePasswordInput($model, $attribute, $options = array()) - { - return static::activeInput('password', $model, $attribute, $options); - } - - /** - * Generates a file input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeFileInput($model, $attribute, $options = array()) - { - // add a hidden field so that if a model only has a file field, we can - // still use isset($_POST[$modelClass]) to detect if the input is submitted - return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) - . static::activeInput('file', $model, $attribute, $options); - } - - /** - * Generates a textarea tag for the given model attribute. - * The model attribute value will be used as the content in the textarea. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated textarea tag - */ - public static function activeTextarea($model, $attribute, $options = array()) - { - $name = static::getInputName($model, $attribute); - $value = static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::textarea($name, $value, $options); - } - - /** - * Generates a radio button tag for the given model attribute. - * This method will generate the "checked" tag attribute according to the model attribute value. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, - * it will take the default value '0'. This method will render a hidden input so that if the radio button - * is not checked and is submitted, the value of this attribute will still be submitted to the server - * via the hidden input. - * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * When this option is specified, the radio button will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated radio button tag - */ - public static function activeRadio($model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('uncheck', $options)) { - $options['uncheck'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::radio($name, $checked, $options); - } - - /** - * Generates a checkbox tag for the given model attribute. - * This method will generate the "checked" tag attribute according to the model attribute value. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, - * it will take the default value '0'. This method will render a hidden input so that if the radio button - * is not checked and is submitted, the value of this attribute will still be submitted to the server - * via the hidden input. - * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * When this option is specified, the checkbox will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated checkbox tag - */ - public static function activeCheckbox($model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('uncheck', $options)) { - $options['uncheck'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::checkbox($name, $checked, $options); - } - - /** - * Generates a drop-down list for the given model attribute. - * The selection of the drop-down list is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated drop-down list tag - */ - public static function activeDropDownList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::dropDownList($name, $checked, $items, $options); - } - - /** - * Generates a list box. - * The selection of the list box is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - unselect: string, the value that will be submitted when no option is selected. - * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple - * mode, we can still obtain the posted unselect value. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated list box tag - */ - public static function activeListBox($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::listBox($name, $checked, $items, $options); - } - - /** - * Generates a list of checkboxes. - * A checkbox list allows multiple selection, like [[listBox()]]. - * As a result, the corresponding submitted value is an array. - * The selection of the checkbox list is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. - * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the checkbox list. The following options are specially handled: - * - * - unselect: string, the value that should be submitted when none of the checkboxes is selected. - * By setting this option, a hidden input will be generated. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the checkbox in the whole list; $label - * is the label for the checkbox; and $name, $value and $checked represent the name, - * value and the checked status of the checkbox input. - * @return string the generated checkbox list - */ - public static function activeCheckboxList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::checkboxList($name, $checked, $items, $options); - } - - /** - * Generates a list of radio buttons. - * A radio button list is like a checkbox list, except that it only allows single selection. - * The selection of the radio buttons is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the data item used to generate the radio buttons. - * The array keys are the labels, while the array values are the corresponding radio button values. - * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the radio button list. The following options are specially handled: - * - * - unselect: string, the value that should be submitted when none of the radio buttons is selected. - * By setting this option, a hidden input will be generated. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the radio button in the whole list; $label - * is the label for the radio button; and $name, $value and $checked represent the name, - * value and the checked status of the radio button input. - * @return string the generated radio button list - */ - public static function activeRadioList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::radioList($name, $checked, $items, $options); - } - - /** - * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. - * @param string|array $selection the selected value(s). This can be either a string for single selection - * or an array for multiple selections. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. - * This method will take out these elements, if any: "prompt", "options" and "groups". See more details - * in [[dropDownList()]] for the explanation of these elements. - * - * @return string the generated list options - */ - public static function renderSelectOptions($selection, $items, &$tagOptions = array()) - { - $lines = array(); - if (isset($tagOptions['prompt'])) { - $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); - $lines[] = static::tag('option', $prompt, array('value' => '')); - } - - $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); - $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); - unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); - - foreach ($items as $key => $value) { - if (is_array($value)) { - $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); - $groupAttrs['label'] = $key; - $attrs = array('options' => $options, 'groups' => $groups); - $content = static::renderSelectOptions($selection, $value, $attrs); - $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); - } else { - $attrs = isset($options[$key]) ? $options[$key] : array(); - $attrs['value'] = (string)$key; - $attrs['selected'] = $selection !== null && - (!is_array($selection) && !strcmp($key, $selection) - || is_array($selection) && in_array($key, $selection)); - $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs); - } - } - - return implode("\n", $lines); - } - - /** - * Renders the HTML tag attributes. - * Attributes whose values are of boolean type will be treated as - * [boolean attributes](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). - * And attributes whose values are null will not be rendered. - * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. - * @return string the rendering result. If the attributes are not empty, they will be rendered - * into a string with a leading white space (so that it can be directly appended to the tag name - * in a tag. If there is no attribute, an empty string will be returned. - */ - public static function renderTagAttributes($attributes) - { - if (count($attributes) > 1) { - $sorted = array(); - foreach (static::$attributeOrder as $name) { - if (isset($attributes[$name])) { - $sorted[$name] = $attributes[$name]; - } - } - $attributes = array_merge($sorted, $attributes); - } - - $html = ''; - foreach ($attributes as $name => $value) { - if (is_bool($value)) { - if ($value) { - $html .= " $name"; - } - } elseif ($value !== null) { - $html .= " $name=\"" . static::encode($value) . '"'; - } - } - return $html; - } - - /** - * Normalizes the input parameter to be a valid URL. - * - * If the input parameter - * - * - is an empty string: the currently requested URL will be returned; - * - is a non-empty string: it will first be processed by [[Yii::getAlias()]]. If the result - * is an absolute URL, it will be returned without any change further; Otherwise, the result - * will be prefixed with [[\yii\web\Request::baseUrl]] and returned. - * - is an array: the first array element is considered a route, while the rest of the name-value - * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. - * For example: `array('post/index', 'page' => 2)`, `array('index')`. - * In case there is no controller, [[\yii\web\UrlManager::createUrl()]] will be used. - * - * @param array|string $url the parameter to be used to generate a valid URL - * @return string the normalized URL - * @throws InvalidParamException if the parameter is invalid. - */ - public static function url($url) - { - if (is_array($url)) { - if (isset($url[0])) { - $route = $url[0]; - $params = array_splice($url, 1); - if (Yii::$app->controller instanceof \yii\web\Controller) { - return Yii::$app->controller->createUrl($route, $params); - } else { - return Yii::$app->getUrlManager()->createUrl($route, $params); - } - } else { - throw new InvalidParamException('The array specifying a URL must contain at least one element.'); - } - } elseif ($url === '') { - return Yii::$app->getRequest()->getUrl(); - } else { - $url = Yii::getAlias($url); - if ($url !== '' && ($url[0] === '/' || $url[0] === '#' || strpos($url, '://'))) { - return $url; - } else { - return Yii::$app->getRequest()->getBaseUrl() . '/' . $url; - } - } - } - - /** - * Adds a CSS class to the specified options. - * If the CSS class is already in the options, it will not be added again. - * @param array $options the options to be modified. - * @param string $class the CSS class to be added - */ - public static function addCssClass(&$options, $class) - { - if (isset($options['class'])) { - $classes = ' ' . $options['class'] . ' '; - if (($pos = strpos($classes, ' ' . $class . ' ')) === false) { - $options['class'] .= ' ' . $class; - } - } else { - $options['class'] = $class; - } - } - - /** - * Removes a CSS class from the specified options. - * @param array $options the options to be modified. - * @param string $class the CSS class to be removed - */ - public static function removeCssClass(&$options, $class) - { - if (isset($options['class'])) { - $classes = array_unique(preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY)); - if (($index = array_search($class, $classes)) !== false) { - unset($classes[$index]); - } - if (empty($classes)) { - unset($options['class']); - } else { - $options['class'] = implode(' ', $classes); - } - } - } - - /** - * Returns the real attribute name from the given attribute expression. - * - * An attribute expression is an attribute name prefixed and/or suffixed with array indexes. - * It is mainly used in tabular data input and/or input of array type. Below are some examples: - * - * - `[0]content` is used in tabular data input to represent the "content" attribute - * for the first model in tabular input; - * - `dates[0]` represents the first array element of the "dates" attribute; - * - `[0]dates[0]` represents the first array element of the "dates" attribute - * for the first model in tabular input. - * - * If `$attribute` has neither prefix nor suffix, it will be returned back without change. - * @param string $attribute the attribute name or expression - * @return string the attribute name without prefix and suffix. - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getAttributeName($attribute) - { - if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - return $matches[2]; - } else { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - } - - /** - * Returns the value of the specified attribute name or expression. - * - * For an attribute expression like `[0]dates[0]`, this method will return the value of `$model->dates[0]`. - * See [[getAttributeName()]] for more details about attribute expression. - * - * @param Model $model the model object - * @param string $attribute the attribute name or expression - * @return mixed the corresponding attribute value - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getAttributeValue($model, $attribute) - { - if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - $attribute = $matches[2]; - $index = $matches[3]; - if ($index === '') { - return $model->$attribute; - } else { - $value = $model->$attribute; - foreach (explode('][', trim($index, '[]')) as $id) { - if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { - $value = $value[$id]; - } else { - return null; - } - } - return $value; - } - } - - /** - * Generates an appropriate input name for the specified attribute name or expression. - * - * This method generates a name that can be used as the input name to collect user input - * for the specified attribute. The name is generated according to the [[Model::formName|form name]] - * of the model and the given attribute name. For example, if the form name of the `Post` model - * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. - * - * See [[getAttributeName()]] for explanation of attribute expression. - * - * @param Model $model the model object - * @param string $attribute the attribute name or expression - * @return string the generated input name - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - 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.'); - } - } - - /** - * Generates an appropriate input ID for the specified attribute name or expression. - * - * This method converts the result [[getInputName()]] into a valid input ID. - * For example, if [[getInputName()]] returns `Post[content]`, this method will return `post-content`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for explanation of attribute expression. - * @return string the generated input ID - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getInputId($model, $attribute) - { - $name = strtolower(static::getInputName($model, $attribute)); - return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); - } -} diff --git a/framework/yii/helpers/HtmlPurifier.php b/framework/yii/helpers/HtmlPurifier.php index f7203e4..ca7e485 100644 --- a/framework/yii/helpers/HtmlPurifier.php +++ b/framework/yii/helpers/HtmlPurifier.php @@ -32,6 +32,6 @@ namespace yii\helpers; * @author Alexander Makarov * @since 2.0 */ -class HtmlPurifier extends HtmlPurifierBase +class HtmlPurifier extends AbstractHtmlPurifier { } diff --git a/framework/yii/helpers/HtmlPurifierBase.php b/framework/yii/helpers/HtmlPurifierBase.php deleted file mode 100644 index e89a589..0000000 --- a/framework/yii/helpers/HtmlPurifierBase.php +++ /dev/null @@ -1,34 +0,0 @@ - - * @since 2.0 - */ -class HtmlPurifierBase -{ - /** - * Passes markup through HTMLPurifier making it safe to output to end user - * - * @param string $content - * @param array|null $config - * @return string - */ - public static function process($content, $config = null) - { - $configInstance = \HTMLPurifier_Config::create($config); - $configInstance->autoFinalize = false; - $purifier=\HTMLPurifier::instance($configInstance); - $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); - return $purifier->purify($content); - } -} diff --git a/framework/yii/helpers/Inflector.php b/framework/yii/helpers/Inflector.php index ba9c069..71e7f05 100644 --- a/framework/yii/helpers/Inflector.php +++ b/framework/yii/helpers/Inflector.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Antonio Ramirez * @since 2.0 */ -class Inflector extends InflectorBase +class Inflector extends AbstractInflector { } diff --git a/framework/yii/helpers/InflectorBase.php b/framework/yii/helpers/InflectorBase.php deleted file mode 100644 index 87c1ff4..0000000 --- a/framework/yii/helpers/InflectorBase.php +++ /dev/null @@ -1,480 +0,0 @@ - - * @since 2.0 - */ -class InflectorBase -{ - /** - * @var array the rules for converting a word into its plural form. - * The keys are the regular expressions and the values are the corresponding replacements. - */ - public static $plurals = array( - '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1', - '/^(sea[- ]bass)$/i' => '\1', - '/(m)ove$/i' => '\1oves', - '/(f)oot$/i' => '\1eet', - '/(h)uman$/i' => '\1umans', - '/(s)tatus$/i' => '\1tatuses', - '/(s)taff$/i' => '\1taff', - '/(t)ooth$/i' => '\1eeth', - '/(quiz)$/i' => '\1zes', - '/^(ox)$/i' => '\1\2en', - '/([m|l])ouse$/i' => '\1ice', - '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', - '/(x|ch|ss|sh)$/i' => '\1es', - '/([^aeiouy]|qu)y$/i' => '\1ies', - '/(hive)$/i' => '\1s', - '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', - '/sis$/i' => 'ses', - '/([ti])um$/i' => '\1a', - '/(p)erson$/i' => '\1eople', - '/(m)an$/i' => '\1en', - '/(c)hild$/i' => '\1hildren', - '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes', - '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', - '/us$/i' => 'uses', - '/(alias)$/i' => '\1es', - '/(ax|cris|test)is$/i' => '\1es', - '/s$/' => 's', - '/^$/' => '', - '/$/' => 's', - ); - /** - * @var array the rules for converting a word into its singular form. - * The keys are the regular expressions and the values are the corresponding replacements. - */ - public static $singulars = array( - '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', - '/^(sea[- ]bass)$/i' => '\1', - '/(s)tatuses$/i' => '\1tatus', - '/(f)eet$/i' => '\1oot', - '/(t)eeth$/i' => '\1ooth', - '/^(.*)(menu)s$/i' => '\1\2', - '/(quiz)zes$/i' => '\\1', - '/(matr)ices$/i' => '\1ix', - '/(vert|ind)ices$/i' => '\1ex', - '/^(ox)en/i' => '\1', - '/(alias)(es)*$/i' => '\1', - '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', - '/([ftw]ax)es/i' => '\1', - '/(cris|ax|test)es$/i' => '\1is', - '/(shoe|slave)s$/i' => '\1', - '/(o)es$/i' => '\1', - '/ouses$/' => 'ouse', - '/([^a])uses$/' => '\1us', - '/([m|l])ice$/i' => '\1ouse', - '/(x|ch|ss|sh)es$/i' => '\1', - '/(m)ovies$/i' => '\1\2ovie', - '/(s)eries$/i' => '\1\2eries', - '/([^aeiouy]|qu)ies$/i' => '\1y', - '/([lr])ves$/i' => '\1f', - '/(tive)s$/i' => '\1', - '/(hive)s$/i' => '\1', - '/(drive)s$/i' => '\1', - '/([^fo])ves$/i' => '\1fe', - '/(^analy)ses$/i' => '\1sis', - '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', - '/([ti])a$/i' => '\1um', - '/(p)eople$/i' => '\1\2erson', - '/(m)en$/i' => '\1an', - '/(c)hildren$/i' => '\1\2hild', - '/(n)ews$/i' => '\1\2ews', - '/eaus$/' => 'eau', - '/^(.*us)$/' => '\\1', - '/s$/i' => '', - ); - /** - * @var array the special rules for converting a word between its plural form and singular form. - * The keys are the special words in singular form, and the values are the corresponding plural form. - */ - public static $specials = array( - 'atlas' => 'atlases', - 'beef' => 'beefs', - 'brother' => 'brothers', - 'cafe' => 'cafes', - 'child' => 'children', - 'cookie' => 'cookies', - 'corpus' => 'corpuses', - 'cow' => 'cows', - 'curve' => 'curves', - 'foe' => 'foes', - 'ganglion' => 'ganglions', - 'genie' => 'genies', - 'genus' => 'genera', - 'graffito' => 'graffiti', - 'hoof' => 'hoofs', - 'loaf' => 'loaves', - 'man' => 'men', - 'money' => 'monies', - 'mongoose' => 'mongooses', - 'move' => 'moves', - 'mythos' => 'mythoi', - 'niche' => 'niches', - 'numen' => 'numina', - 'occiput' => 'occiputs', - 'octopus' => 'octopuses', - 'opus' => 'opuses', - 'ox' => 'oxen', - 'penis' => 'penises', - 'sex' => 'sexes', - 'soliloquy' => 'soliloquies', - 'testis' => 'testes', - 'trilby' => 'trilbys', - 'turf' => 'turfs', - 'wave' => 'waves', - 'Amoyese' => 'Amoyese', - 'bison' => 'bison', - 'Borghese' => 'Borghese', - 'bream' => 'bream', - 'breeches' => 'breeches', - 'britches' => 'britches', - 'buffalo' => 'buffalo', - 'cantus' => 'cantus', - 'carp' => 'carp', - 'chassis' => 'chassis', - 'clippers' => 'clippers', - 'cod' => 'cod', - 'coitus' => 'coitus', - 'Congoese' => 'Congoese', - 'contretemps' => 'contretemps', - 'corps' => 'corps', - 'debris' => 'debris', - 'diabetes' => 'diabetes', - 'djinn' => 'djinn', - 'eland' => 'eland', - 'elk' => 'elk', - 'equipment' => 'equipment', - 'Faroese' => 'Faroese', - 'flounder' => 'flounder', - 'Foochowese' => 'Foochowese', - 'gallows' => 'gallows', - 'Genevese' => 'Genevese', - 'Genoese' => 'Genoese', - 'Gilbertese' => 'Gilbertese', - 'graffiti' => 'graffiti', - 'headquarters' => 'headquarters', - 'herpes' => 'herpes', - 'hijinks' => 'hijinks', - 'Hottentotese' => 'Hottentotese', - 'information' => 'information', - 'innings' => 'innings', - 'jackanapes' => 'jackanapes', - 'Kiplingese' => 'Kiplingese', - 'Kongoese' => 'Kongoese', - 'Lucchese' => 'Lucchese', - 'mackerel' => 'mackerel', - 'Maltese' => 'Maltese', - 'mews' => 'mews', - 'moose' => 'moose', - 'mumps' => 'mumps', - 'Nankingese' => 'Nankingese', - 'news' => 'news', - 'nexus' => 'nexus', - 'Niasese' => 'Niasese', - 'Pekingese' => 'Pekingese', - 'Piedmontese' => 'Piedmontese', - 'pincers' => 'pincers', - 'Pistoiese' => 'Pistoiese', - 'pliers' => 'pliers', - 'Portuguese' => 'Portuguese', - 'proceedings' => 'proceedings', - 'rabies' => 'rabies', - 'rice' => 'rice', - 'rhinoceros' => 'rhinoceros', - 'salmon' => 'salmon', - 'Sarawakese' => 'Sarawakese', - 'scissors' => 'scissors', - 'series' => 'series', - 'Shavese' => 'Shavese', - 'shears' => 'shears', - 'siemens' => 'siemens', - 'species' => 'species', - 'swine' => 'swine', - 'testes' => 'testes', - 'trousers' => 'trousers', - 'trout' => 'trout', - 'tuna' => 'tuna', - 'Vermontese' => 'Vermontese', - 'Wenchowese' => 'Wenchowese', - 'whiting' => 'whiting', - 'wildebeest' => 'wildebeest', - 'Yengeese' => 'Yengeese', - ); - /** - * @var array map of special chars and its translation. This is used by [[slug()]]. - */ - public static $transliteration = array( - '/ä|æ|ǽ/' => 'ae', - '/ö|œ/' => 'oe', - '/ü/' => 'ue', - '/Ä/' => 'Ae', - '/Ü/' => 'Ue', - '/Ö/' => 'Oe', - '/À|Á|Â|Ã|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A', - '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a', - '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', - '/ç|ć|ĉ|ċ|č/' => 'c', - '/Ð|Ď|Đ/' => 'D', - '/ð|ď|đ/' => 'd', - '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E', - '/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e', - '/Ĝ|Ğ|Ġ|Ģ/' => 'G', - '/ĝ|ğ|ġ|ģ/' => 'g', - '/Ĥ|Ħ/' => 'H', - '/ĥ|ħ/' => 'h', - '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I', - '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i', - '/Ĵ/' => 'J', - '/ĵ/' => 'j', - '/Ķ/' => 'K', - '/ķ/' => 'k', - '/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L', - '/ĺ|ļ|ľ|ŀ|ł/' => 'l', - '/Ñ|Ń|Ņ|Ň/' => 'N', - '/ñ|ń|ņ|ň|ʼn/' => 'n', - '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O', - '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o', - '/Ŕ|Ŗ|Ř/' => 'R', - '/ŕ|ŗ|ř/' => 'r', - '/Ś|Ŝ|Ş|Ș|Š/' => 'S', - '/ś|ŝ|ş|ș|š|ſ/' => 's', - '/Ţ|Ț|Ť|Ŧ/' => 'T', - '/ţ|ț|ť|ŧ/' => 't', - '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U', - '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u', - '/Ý|Ÿ|Ŷ/' => 'Y', - '/ý|ÿ|ŷ/' => 'y', - '/Ŵ/' => 'W', - '/ŵ/' => 'w', - '/Ź|Ż|Ž/' => 'Z', - '/ź|ż|ž/' => 'z', - '/Æ|Ǽ/' => 'AE', - '/ß/' => 'ss', - '/IJ/' => 'IJ', - '/ij/' => 'ij', - '/Œ/' => 'OE', - '/ƒ/' => 'f' - ); - - /** - * Converts a word to its plural form. - * Note that this is for English only! - * For example, 'apple' will become 'apples', and 'child' will become 'children'. - * @param string $word the word to be pluralized - * @return string the pluralized word - */ - public static function pluralize($word) - { - if (isset(self::$specials[$word])) { - return self::$specials[$word]; - } - foreach (static::$plurals as $rule => $replacement) { - if (preg_match($rule, $word)) { - return preg_replace($rule, $replacement, $word); - } - } - return $word; - } - - /** - * Returns the singular of the $word - * @param string $word the english word to singularize - * @return string Singular noun. - */ - public static function singularize($word) - { - $result = array_search($word, self::$specials, true); - if ($result !== false) { - return $result; - } - foreach (static::$singulars as $rule => $replacement) { - if (preg_match($rule, $word)) { - return preg_replace($rule, $replacement, $word); - } - } - return $word; - } - - /** - * Converts an underscored or CamelCase word into a English - * sentence. - * @param string $words - * @param bool $ucAll whether to set all words to uppercase - * @return string - */ - public static function titleize($words, $ucAll = false) - { - $words = static::humanize(static::underscore($words), $ucAll); - return $ucAll ? ucwords($words) : ucfirst($words); - } - - /** - * Returns given word as CamelCased - * Converts a word like "send_email" to "SendEmail". It - * will remove non alphanumeric character from the word, so - * "who's online" will be converted to "WhoSOnline" - * @see variablize - * @param string $word the word to CamelCase - * @return string - */ - public static function camelize($word) - { - return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word))); - } - - /** - * Converts a CamelCase name into space-separated words. - * For example, 'PostTag' will be converted to 'Post Tag'. - * @param string $name the string to be converted - * @param boolean $ucwords whether to capitalize the first letter in each word - * @return string the resulting words - */ - public static function camel2words($name, $ucwords = true) - { - $label = trim(strtolower(str_replace(array( - '-', - '_', - '.' - ), ' ', preg_replace('/(? ' ', - '/\\s+/' => $replacement, - '/(?<=[a-z])([A-Z])/' => $replacement . '\\1', - str_replace(':rep', preg_quote($replacement, '/'), '/^[:rep]+|[:rep]+$/') => '' - ); - return preg_replace(array_keys($map), array_values($map), $string); - } - - /** - * Converts a table name to its class name. For example, converts "people" to "Person" - * @param string $tableName - * @return string - */ - public static function classify($tableName) - { - return static::camelize(static::singularize($tableName)); - } - - /** - * Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd ... - * @param int $number the number to get its ordinal value - * @return string - */ - public static function ordinalize($number) - { - if (in_array(($number % 100), range(11, 13))) { - return $number . 'th'; - } - switch ($number % 10) { - case 1: return $number . 'st'; - case 2: return $number . 'nd'; - case 3: return $number . 'rd'; - default: return $number . 'th'; - } - } -} diff --git a/framework/yii/helpers/Json.php b/framework/yii/helpers/Json.php index 424de1f..8544a61 100644 --- a/framework/yii/helpers/Json.php +++ b/framework/yii/helpers/Json.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class Json extends JsonBase +class Json extends AbstractJson { } diff --git a/framework/yii/helpers/JsonBase.php b/framework/yii/helpers/JsonBase.php deleted file mode 100644 index fa3fb01..0000000 --- a/framework/yii/helpers/JsonBase.php +++ /dev/null @@ -1,112 +0,0 @@ - - * @since 2.0 - */ -class JsonBase -{ - /** - * Encodes the given value into a JSON string. - * The method enhances `json_encode()` by supporting JavaScript expressions. - * In particular, the method will not encode a JavaScript expression that is - * represented in terms of a [[JsExpression]] object. - * @param mixed $value the data to be encoded - * @param integer $options the encoding options. For more details please refer to - * [[http://www.php.net/manual/en/function.json-encode.php]] - * @return string the encoding result - */ - public static function encode($value, $options = 0) - { - $expressions = array(); - $value = static::processData($value, $expressions, uniqid()); - $json = json_encode($value, $options); - return empty($expressions) ? $json : strtr($json, $expressions); - } - - /** - * Decodes the given JSON string into a PHP data structure. - * @param string $json the JSON string to be decoded - * @param boolean $asArray whether to return objects in terms of associative arrays. - * @return mixed the PHP data - * @throws InvalidParamException if there is any decoding error - */ - public static function decode($json, $asArray = true) - { - if (is_array($json)) { - throw new InvalidParamException('Invalid JSON data.'); - } - $decode = json_decode((string)$json, $asArray); - switch (json_last_error()) { - case JSON_ERROR_NONE: - break; - case JSON_ERROR_DEPTH: - throw new InvalidParamException('The maximum stack depth has been exceeded.'); - case JSON_ERROR_CTRL_CHAR: - throw new InvalidParamException('Control character error, possibly incorrectly encoded.'); - case JSON_ERROR_SYNTAX: - throw new InvalidParamException('Syntax error.'); - case JSON_ERROR_STATE_MISMATCH: - throw new InvalidParamException('Invalid or malformed JSON.'); - case JSON_ERROR_UTF8: - throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.'); - default: - throw new InvalidParamException('Unknown JSON decoding error.'); - } - - return $decode; - } - - /** - * Pre-processes the data before sending it to `json_encode()`. - * @param mixed $data the data to be processed - * @param array $expressions collection of JavaScript expressions - * @param string $expPrefix a prefix internally used to handle JS expressions - * @return mixed the processed data - */ - protected static function processData($data, &$expressions, $expPrefix) - { - if (is_array($data)) { - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $data[$key] = static::processData($value, $expressions, $expPrefix); - } - } - return $data; - } elseif (is_object($data)) { - if ($data instanceof JsExpression) { - $token = "!{[$expPrefix=" . count($expressions) . ']}!'; - $expressions['"' . $token . '"'] = $data->expression; - return $token; - } else { - $data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data); - $result = array(); - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $result[$key] = static::processData($value, $expressions, $expPrefix); - } else { - $result[$key] = $value; - } - } - return $result; - } - } else { - return $data; - } - } -} diff --git a/framework/yii/helpers/Markdown.php b/framework/yii/helpers/Markdown.php index 690df5d..89f8801 100644 --- a/framework/yii/helpers/Markdown.php +++ b/framework/yii/helpers/Markdown.php @@ -30,6 +30,6 @@ namespace yii\helpers; * @author Alexander Makarov * @since 2.0 */ -class Markdown extends MarkdownBase +class Markdown extends AbstractMarkdown { } diff --git a/framework/yii/helpers/MarkdownBase.php b/framework/yii/helpers/MarkdownBase.php deleted file mode 100644 index 9db5b1e..0000000 --- a/framework/yii/helpers/MarkdownBase.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @since 2.0 - */ -class MarkdownBase -{ - /** - * @var MarkdownExtra - */ - protected static $markdown; - - /** - * Converts markdown into HTML - * - * @param string $content - * @param array $config - * @return string - */ - public static function process($content, $config = array()) - { - if (static::$markdown === null) { - static::$markdown = new MarkdownExtra(); - } - foreach ($config as $name => $value) { - static::$markdown->{$name} = $value; - } - return static::$markdown->transform($content); - } -} diff --git a/framework/yii/helpers/Security.php b/framework/yii/helpers/Security.php index d0ca2ed..e2c0314 100644 --- a/framework/yii/helpers/Security.php +++ b/framework/yii/helpers/Security.php @@ -24,6 +24,6 @@ namespace yii\helpers; * @author Tom Worster * @since 2.0 */ -class Security extends SecurityBase +class Security extends AbstractSecurity { } diff --git a/framework/yii/helpers/SecurityBase.php b/framework/yii/helpers/SecurityBase.php deleted file mode 100644 index 5b192de..0000000 --- a/framework/yii/helpers/SecurityBase.php +++ /dev/null @@ -1,285 +0,0 @@ - - * @author Tom Worster - * @since 2.0 - */ -class SecurityBase -{ - /** - * Encrypts data. - * @param string $data data to be encrypted. - * @param string $key the encryption secret key - * @return string the encrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - * @see decrypt() - */ - public static function encrypt($data, $key) - { - $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); - srand(); - $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); - mcrypt_generic_init($module, $key, $iv); - $encrypted = $iv . mcrypt_generic($module, $data); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return $encrypted; - } - - /** - * Decrypts data - * @param string $data data to be decrypted. - * @param string $key the decryption secret key - * @return string the decrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - * @see encrypt() - */ - public static function decrypt($data, $key) - { - $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); - $ivSize = mcrypt_enc_get_iv_size($module); - $iv = StringHelper::substr($data, 0, $ivSize); - mcrypt_generic_init($module, $key, $iv); - $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return rtrim($decrypted, "\0"); - } - - /** - * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. - * @param string $data the data to be protected - * @param string $key the secret key to be used for generating hash - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. - * @return string the data prefixed with the keyed hash - * @see validateData() - * @see getSecretKey() - */ - public static function hashData($data, $key, $algorithm = 'sha256') - { - return hash_hmac($algorithm, $data, $key) . $data; - } - - /** - * Validates if the given data is tampered. - * @param string $data the data to be validated. The data must be previously - * generated by [[hashData()]]. - * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. This must be the same - * as the value passed to [[hashData()]] when generating the hash for the data. - * @return string the real data with the hash stripped off. False if the data is tampered. - * @see hashData() - */ - public static function validateData($data, $key, $algorithm = 'sha256') - { - $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); - $n = StringHelper::strlen($data); - if ($n >= $hashSize) { - $hash = StringHelper::substr($data, 0, $hashSize); - $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); - return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; - } else { - return false; - } - } - - /** - * Returns a secret key associated with the specified name. - * If the secret key does not exist, a random key will be generated - * and saved in the file "keys.php" under the application's runtime directory - * so that the same secret key can be returned in future requests. - * @param string $name the name that is associated with the secret key - * @param integer $length the length of the key that should be generated if not exists - * @return string the secret key associated with the specified name - */ - public static function getSecretKey($name, $length = 32) - { - static $keys; - $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; - if ($keys === null) { - $keys = array(); - if (is_file($keyFile)) { - $keys = require($keyFile); - } - } - if (!isset($keys[$name])) { - $keys[$name] = static::generateRandomKey($length); - file_put_contents($keyFile, " 30) { - throw new InvalidParamException('Hash is invalid.'); - } - - $test = crypt($password, $hash); - $n = strlen($test); - if (strlen($test) < 32 || $n !== strlen($hash)) { - return false; - } - - // Use a for-loop to compare two strings to prevent timing attacks. See: - // http://codereview.stackexchange.com/questions/13512 - $check = 0; - for ($i = 0; $i < $n; ++$i) { - $check |= (ord($test[$i]) ^ ord($hash[$i])); - } - - return $check === 0; - } - - /** - * Generates a salt that can be used to generate a password hash. - * - * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function - * requires, for the Blowfish hash algorithm, a salt string in a specific format: - * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters - * from the alphabet "./0-9A-Za-z". - * - * @param integer $cost the cost parameter - * @return string the random salt value. - * @throws InvalidParamException if the cost parameter is not between 4 and 30 - */ - protected static function generateSalt($cost = 13) - { - $cost = (int)$cost; - if ($cost < 4 || $cost > 31) { - throw new InvalidParamException('Cost must be between 4 and 31.'); - } - - // Get 20 * 8bits of pseudo-random entropy from mt_rand(). - $rand = ''; - for ($i = 0; $i < 20; ++$i) { - $rand .= chr(mt_rand(0, 255)); - } - - // Add the microtime for a little more entropy. - $rand .= microtime(); - // Mix the bits cryptographically into a 20-byte binary string. - $rand = sha1($rand, true); - // Form the prefix that specifies Blowfish algorithm and cost parameter. - $salt = sprintf("$2y$%02d$", $cost); - // Append the random salt data in the required base64 format. - $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); - return $salt; - } -} diff --git a/framework/yii/helpers/StringHelper.php b/framework/yii/helpers/StringHelper.php index ef75790..e367c59 100644 --- a/framework/yii/helpers/StringHelper.php +++ b/framework/yii/helpers/StringHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class StringHelper extends StringHelperBase +class StringHelper extends AbstractStringHelper { } diff --git a/framework/yii/helpers/StringHelperBase.php b/framework/yii/helpers/StringHelperBase.php deleted file mode 100644 index cbb696e..0000000 --- a/framework/yii/helpers/StringHelperBase.php +++ /dev/null @@ -1,138 +0,0 @@ - - * @author Alex Makarov - * @since 2.0 - */ -class StringHelperBase -{ - /** - * Returns the number of bytes in the given string. - * This method ensures the string is treated as a byte array by using `mb_strlen()`. - * @param string $string the string being measured for length - * @return integer the number of bytes in the given string. - */ - public static function strlen($string) - { - return mb_strlen($string, '8bit'); - } - - /** - * Returns the portion of string specified by the start and length parameters. - * This method ensures the string is treated as a byte array by using `mb_substr()`. - * @param string $string the input string. Must be one character or longer. - * @param integer $start the starting position - * @param integer $length the desired portion length - * @return string the extracted part of string, or FALSE on failure or an empty string. - * @see http://www.php.net/manual/en/function.substr.php - */ - public static function substr($string, $start, $length) - { - return mb_substr($string, $start, $length, '8bit'); - } - - /** - * Returns the trailing name component of a path. - * This method is similar to the php function `basename()` except that it will - * treat both \ and / as directory separators, independent of the operating system. - * This method was mainly created to work on php namespaces. When working with real - * file paths, php's `basename()` should work fine for you. - * Note: this method is not aware of the actual filesystem, or path components such as "..". - * @param string $path A path string. - * @param string $suffix If the name component ends in suffix this will also be cut off. - * @return string the trailing name component of the given path. - * @see http://www.php.net/manual/en/function.basename.php - */ - public static function basename($path, $suffix = '') - { - if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) == $suffix) { - $path = mb_substr($path, 0, -$len); - } - $path = rtrim(str_replace('\\', '/', $path), '/\\'); - if (($pos = mb_strrpos($path, '/')) !== false) { - return mb_substr($path, $pos + 1); - } - return $path; - } - - /** - * Returns parent directory's path. - * This method is similar to `dirname()` except that it will treat - * both \ and / as directory separators, independent of the operating system. - * @param string $path A path string. - * @return string the parent directory's path. - * @see http://www.php.net/manual/en/function.basename.php - */ - public static function dirname($path) - { - $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); - if ($pos !== false) { - return mb_substr($path, 0, $pos); - } else { - return $path; - } - } - - /** - * Compares two strings or string arrays, and return their differences. - * This is a wrapper of the [phpspec/php-diff](https://packagist.org/packages/phpspec/php-diff) package. - * @param string|array $lines1 the first string or string array to be compared. If it is a string, - * it will be converted into a string array by breaking at newlines. - * @param string|array $lines2 the second string or string array to be compared. If it is a string, - * it will be converted into a string array by breaking at newlines. - * @param string $format the output format. It must be 'inline', 'unified', 'context', 'side-by-side', or 'array'. - * @return string|array the comparison result. An array is returned if `$format` is 'array'. For all other - * formats, a string is returned. - * @throws InvalidParamException if the format is invalid. - */ - public static function diff($lines1, $lines2, $format = 'inline') - { - if (!is_array($lines1)) { - $lines1 = explode("\n", $lines1); - } - if (!is_array($lines2)) { - $lines2 = explode("\n", $lines2); - } - foreach ($lines1 as $i => $line) { - $lines1[$i] = rtrim($line, "\r\n"); - } - foreach ($lines2 as $i => $line) { - $lines2[$i] = rtrim($line, "\r\n"); - } - switch ($format) { - case 'inline': - $renderer = new \Diff_Renderer_Html_Inline(); - break; - case 'array': - $renderer = new \Diff_Renderer_Html_Array(); - break; - case 'side-by-side': - $renderer = new \Diff_Renderer_Html_SideBySide(); - break; - case 'context': - $renderer = new \Diff_Renderer_Text_Context(); - break; - case 'unified': - $renderer = new \Diff_Renderer_Text_Unified(); - break; - default: - throw new InvalidParamException("Output format must be 'inline', 'side-by-side', 'array', 'context' or 'unified'."); - } - $diff = new \Diff($lines1, $lines2); - return $diff->render($renderer); - } -} diff --git a/framework/yii/helpers/VarDumper.php b/framework/yii/helpers/VarDumper.php index 50e543c..0aae16e 100644 --- a/framework/yii/helpers/VarDumper.php +++ b/framework/yii/helpers/VarDumper.php @@ -23,6 +23,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class VarDumper extends VarDumperBase +class VarDumper extends AbstractVarDumper { } diff --git a/framework/yii/helpers/VarDumperBase.php b/framework/yii/helpers/VarDumperBase.php deleted file mode 100644 index c7da208..0000000 --- a/framework/yii/helpers/VarDumperBase.php +++ /dev/null @@ -1,127 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\helpers; - -/** - * VarDumperBase provides concrete implementation for [[VarDumper]]. - * - * Do not use VarDumperBase. Use [[VarDumper]] instead. - * - * @author Qiang Xue - * @since 2.0 - */ -class VarDumperBase -{ - private static $_objects; - private static $_output; - private static $_depth; - - /** - * Displays a variable. - * This method achieves the similar functionality as var_dump and print_r - * but is more robust when handling complex objects such as Yii controllers. - * @param mixed $var variable to be dumped - * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. - * @param boolean $highlight whether the result should be syntax-highlighted - */ - public static function dump($var, $depth = 10, $highlight = false) - { - echo static::dumpAsString($var, $depth, $highlight); - } - - /** - * Dumps a variable in terms of a string. - * This method achieves the similar functionality as var_dump and print_r - * but is more robust when handling complex objects such as Yii controllers. - * @param mixed $var variable to be dumped - * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. - * @param boolean $highlight whether the result should be syntax-highlighted - * @return string the string representation of the variable - */ - public static function dumpAsString($var, $depth = 10, $highlight = false) - { - self::$_output = ''; - self::$_objects = array(); - self::$_depth = $depth; - self::dumpInternal($var, 0); - if ($highlight) { - $result = highlight_string("/', '', $result, 1); - } - return self::$_output; - } - - /** - * @param mixed $var variable to be dumped - * @param integer $level depth level - */ - private static function dumpInternal($var, $level) - { - switch (gettype($var)) { - case 'boolean': - self::$_output .= $var ? 'true' : 'false'; - break; - case 'integer': - self::$_output .= "$var"; - break; - case 'double': - self::$_output .= "$var"; - break; - case 'string': - self::$_output .= "'" . addslashes($var) . "'"; - break; - case 'resource': - self::$_output .= '{resource}'; - break; - case 'NULL': - self::$_output .= "null"; - break; - case 'unknown type': - self::$_output .= '{unknown}'; - break; - case 'array': - if (self::$_depth <= $level) { - self::$_output .= 'array(...)'; - } elseif (empty($var)) { - self::$_output .= 'array()'; - } else { - $keys = array_keys($var); - $spaces = str_repeat(' ', $level * 4); - self::$_output .= "array\n" . $spaces . '('; - foreach ($keys as $key) { - self::$_output .= "\n" . $spaces . ' '; - self::dumpInternal($key, 0); - self::$_output .= ' => '; - self::dumpInternal($var[$key], $level + 1); - } - self::$_output .= "\n" . $spaces . ')'; - } - break; - case 'object': - if (($id = array_search($var, self::$_objects, true)) !== false) { - self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; - } elseif (self::$_depth <= $level) { - self::$_output .= get_class($var) . '(...)'; - } else { - $id = array_push(self::$_objects, $var); - $className = get_class($var); - $members = (array)$var; - $spaces = str_repeat(' ', $level * 4); - self::$_output .= "$className#$id\n" . $spaces . '('; - foreach ($members as $key => $value) { - $keyDisplay = strtr(trim($key), array("\0" => ':')); - self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; - self::dumpInternal($value, $level + 1); - } - self::$_output .= "\n" . $spaces . ')'; - } - break; - } - } -} From b59812507acfac516b12dbc8364b67506406438f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 27 Sep 2013 18:39:33 -0400 Subject: [PATCH 145/157] reverted doc change: the original one is correct. --- framework/yii/web/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index b353e7c..e6505fd 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -581,7 +581,7 @@ class Response extends \yii\base\Response * In a controller action you may use this method like this: * * ~~~ - * return Yii::$app->getResponse()->redirect($url)->send(); + * return Yii::$app->getResponse()->redirect($url); * ~~~ * * @param string|array $url the URL to be redirected to. This can be in one of the following formats: From 558f499439fe9adfd9416dfde8944181ae35c3aa Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 02:48:54 +0400 Subject: [PATCH 146/157] Renamed YiiBase to AbstractYii --- build/build.xml | 2 +- build/controllers/ClassmapController.php | 2 +- build/controllers/PhpDocController.php | 2 +- docs/internals/autoloader.md | 2 +- framework/yii/AbstractYii.php | 574 +++++++++++++++++++++++++++++++ framework/yii/Yii.php | 6 +- framework/yii/YiiBase.php | 574 ------------------------------- framework/yii/log/Logger.php | 2 +- 8 files changed, 582 insertions(+), 582 deletions(-) create mode 100644 framework/yii/AbstractYii.php delete mode 100644 framework/yii/YiiBase.php diff --git a/build/build.xml b/build/build.xml index 9c18af0..85d5aa5 100644 --- a/build/build.xml +++ b/build/build.xml @@ -265,7 +265,7 @@ Please update yiisite/common/data/versions.php file with the following code: where <target name> can be one of the following: - - sync : synchronize yiilite.php and YiiBase.php + - sync : synchronize yiilite.php and AbstractYii.php - message : extract i18n messages of the framework - src : build source release - doc : build documentation release (Windows only) diff --git a/build/controllers/ClassmapController.php b/build/controllers/ClassmapController.php index 2a0483c..10eb134 100644 --- a/build/controllers/ClassmapController.php +++ b/build/controllers/ClassmapController.php @@ -45,7 +45,7 @@ class ClassmapController extends Controller 'only' => array('.php'), 'except' => array( 'Yii.php', - 'YiiBase.php', + 'AbstractYii.php', '/debug/', '/console/', '/test/', diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index 5aac7e5..4fed3b2 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -56,7 +56,7 @@ class PhpDocController extends Controller }, 'only' => array('.php'), 'except' => array( - 'YiiBase.php', + 'AbstractYii.php', 'Yii.php', '/debug/views/', '/requirements/', diff --git a/docs/internals/autoloader.md b/docs/internals/autoloader.md index b7696d7..067773d 100644 --- a/docs/internals/autoloader.md +++ b/docs/internals/autoloader.md @@ -16,4 +16,4 @@ PEAR-style libraries References ---------- -- YiiBase::autoload \ No newline at end of file +- AbstractYii::autoload \ No newline at end of file diff --git a/framework/yii/AbstractYii.php b/framework/yii/AbstractYii.php new file mode 100644 index 0000000..c40aad9 --- /dev/null +++ b/framework/yii/AbstractYii.php @@ -0,0 +1,574 @@ + + * @since 2.0 + */ +abstract class AbstractYii +{ + /** + * @var array class map used by the Yii autoloading mechanism. + * The array keys are the class names (without leading backslashes), and the array values + * are the corresponding class file paths (or path aliases). This property mainly affects + * how [[autoload()]] works. + * @see import + * @see autoload + */ + public static $classMap = array(); + /** + * @var \yii\console\Application|\yii\web\Application the application instance + */ + public static $app; + /** + * @var array registered path aliases + * @see getAlias + * @see setAlias + */ + public static $aliases = array('@yii' => __DIR__); + /** + * @var array initial property values that will be applied to objects newly created via [[createObject]]. + * The array keys are class names without leading backslashes "\", and the array values are the corresponding + * name-value pairs for initializing the created class instances. For example, + * + * ~~~ + * array( + * 'Bar' => array( + * 'prop1' => 'value1', + * 'prop2' => 'value2', + * ), + * 'mycompany\foo\Car' => array( + * 'prop1' => 'value1', + * 'prop2' => 'value2', + * ), + * ) + * ~~~ + * + * @see createObject + */ + public static $objectConfig = array(); + + + /** + * @return string the version of Yii framework + */ + public static function getVersion() + { + return '2.0-dev'; + } + + /** + * Imports a set of namespaces. + * + * By importing a namespace, the method will create an alias for the directory corresponding + * to the namespace. For example, if "foo\bar" is a namespace associated with the directory + * "path/to/foo/bar", then an alias "@foo/bar" will be created for this directory. + * + * This method is typically invoked in the bootstrap file to import the namespaces of + * the installed extensions. By default, Composer, when installing new extensions, will + * generate such a mapping file which can be loaded and passed to this method. + * + * @param array $namespaces the namespaces to be imported. The keys are the namespaces, + * and the values are the corresponding directories. + */ + public static function importNamespaces($namespaces) + { + foreach ($namespaces as $name => $path) { + if ($name !== '') { + $name = trim(strtr($name, array('\\' => '/', '_' => '/')), '/'); + if (is_array($path)) { + $path = reset($path); + } + static::setAlias('@' . $name, rtrim($path, '/\\') . '/' . $name); + } + } + } + + /** + * Translates a path alias into an actual path. + * + * The translation is done according to the following procedure: + * + * 1. If the given alias does not start with '@', it is returned back without change; + * 2. Otherwise, look for the longest registered alias that matches the beginning part + * of the given alias. If it exists, replace the matching part of the given alias with + * the corresponding registered path. + * 3. Throw an exception or return false, depending on the `$throwException` parameter. + * + * For example, by default '@yii' is registered as the alias to the Yii framework directory, + * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'. + * + * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' + * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. + * This is because the longest alias takes precedence. + * + * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced + * instead of '@foo/bar', because '/' serves as the boundary character. + * + * Note, this method does not check if the returned path exists or not. + * + * @param string $alias the alias to be translated. + * @param boolean $throwException whether to throw an exception if the given alias is invalid. + * If this is false and an invalid alias is given, false will be returned by this method. + * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. + * @throws InvalidParamException if the alias is invalid while $throwException is true. + * @see setAlias + */ + public static function getAlias($alias, $throwException = true) + { + if (strncmp($alias, '@', 1)) { + // not an alias + return $alias; + } + + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(self::$aliases[$root])) { + if (is_string(self::$aliases[$root])) { + return $pos === false ? self::$aliases[$root] : self::$aliases[$root] . substr($alias, $pos); + } else { + foreach (self::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $path . substr($alias, strlen($name)); + } + } + } + } + + if ($throwException) { + throw new InvalidParamException("Invalid path alias: $alias"); + } else { + return false; + } + } + + /** + * Returns the root alias part of a given alias. + * A root alias is an alias that has been registered via [[setAlias()]] previously. + * If a given alias matches multiple root aliases, the longest one will be returned. + * @param string $alias the alias + * @return string|boolean the root alias, or false if no root alias is found + */ + public static function getRootAlias($alias) + { + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(self::$aliases[$root])) { + if (is_string(self::$aliases[$root])) { + return $root; + } else { + foreach (self::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $name; + } + } + } + } + return false; + } + + /** + * Registers a path alias. + * + * A path alias is a short name representing a long path (a file path, a URL, etc.) + * For example, we use '@yii' as the alias of the path to the Yii framework directory. + * + * A path alias must start with the character '@' so that it can be easily differentiated + * from non-alias paths. + * + * Note that this method does not check if the given path exists or not. All it does is + * to associate the alias with the path. + * + * Any trailing '/' and '\' characters in the given path will be trimmed. + * + * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character. + * It may contain the forward slash '/' which serves as boundary character when performing + * alias translation by [[getAlias()]]. + * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters + * will be trimmed. This can be + * + * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) + * - a URL (e.g. `http://www.yiiframework.com`) + * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the + * actual path first by calling [[getAlias()]]. + * + * @throws InvalidParamException if $path is an invalid alias. + * @see getAlias + */ + public static function setAlias($alias, $path) + { + if (strncmp($alias, '@', 1)) { + $alias = '@' . $alias; + } + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + if ($path !== null) { + $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); + if (!isset(self::$aliases[$root])) { + if ($pos === false) { + self::$aliases[$root] = $path; + } else { + self::$aliases[$root] = array($alias => $path); + } + } elseif (is_string(self::$aliases[$root])) { + if ($pos === false) { + self::$aliases[$root] = $path; + } else { + self::$aliases[$root] = array( + $alias => $path, + $root => self::$aliases[$root], + ); + } + } else { + self::$aliases[$root][$alias] = $path; + krsort(self::$aliases[$root]); + } + } elseif (isset(self::$aliases[$root])) { + if (is_array(self::$aliases[$root])) { + unset(self::$aliases[$root][$alias]); + } elseif ($pos === false) { + unset(self::$aliases[$root]); + } + } + } + + /** + * Class autoload loader. + * This method is invoked automatically when PHP sees an unknown class. + * The method will attempt to include the class file according to the following procedure: + * + * 1. Search in [[classMap]]; + * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt + * to include the file associated with the corresponding path alias + * (e.g. `@yii/base/Component.php`); + * 3. If the class is named in PEAR style (e.g. `PHPUnit_Framework_TestCase`), + * it will attempt to include the file associated with the corresponding path alias + * (e.g. `@PHPUnit/Framework/TestCase.php`); + * + * This autoloader allows loading classes that follow the [PSR-0 standard](http://www.php-fig.org/psr/0/). + * Therefor a path alias has to be defined for each top-level namespace. + * + * @param string $className the fully qualified class name without a leading backslash "\" + * @throws UnknownClassException if the class does not exist in the class file + */ + public static function autoload($className) + { + if (isset(self::$classMap[$className])) { + $classFile = self::$classMap[$className]; + if ($classFile[0] === '@') { + $classFile = static::getAlias($classFile); + } + } else { + // follow PSR-0 to determine the class file + if (($pos = strrpos($className, '\\')) !== false) { + // namespaced class, e.g. yii\base\Component + $path = str_replace('\\', '/', substr($className, 0, $pos + 1)) + . str_replace('_', '/', substr($className, $pos + 1)) . '.php'; + } else { + $path = str_replace('_', '/', $className) . '.php'; + } + + // try loading via path alias + if (strpos($path, '/') === false) { + return; + } else { + $classFile = static::getAlias('@' . $path, false); + if ($classFile === false || !is_file($classFile)) { + return; + } + } + } + + include($classFile); + + if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && + (!function_exists('trait_exists') || !trait_exists($className, false))) { + throw new UnknownClassException("Unable to find '$className' in file: $classFile"); + } + } + + /** + * Creates a new object using the given configuration. + * + * The configuration can be either a string or an array. + * If a string, it is treated as the *object class*; if an array, + * it must contain a `class` element specifying the *object class*, and + * the rest of the name-value pairs in the array will be used to initialize + * the corresponding object properties. + * + * The object type can be either a class name or the [[getAlias()|alias]] of + * the class. For example, + * + * - `app\components\GoogleMap`: fully-qualified namespaced class. + * - `@app/components/GoogleMap`: an alias, used for non-namespaced class. + * + * Below are some usage examples: + * + * ~~~ + * $object = \Yii::createObject('@app/components/GoogleMap'); + * $object = \Yii::createObject(array( + * 'class' => '\app\components\GoogleMap', + * 'apiKey' => 'xyz', + * )); + * ~~~ + * + * This method can be used to create any object as long as the object's constructor is + * defined like the following: + * + * ~~~ + * public function __construct(..., $config = array()) { + * } + * ~~~ + * + * The method will pass the given configuration as the last parameter of the constructor, + * and any additional parameters to this method will be passed as the rest of the constructor parameters. + * + * @param string|array $config the configuration. It can be either a string representing the class name + * or an array representing the object configuration. + * @return mixed the created object + * @throws InvalidConfigException if the configuration is invalid. + */ + public static function createObject($config) + { + static $reflections = array(); + + if (is_string($config)) { + $class = $config; + $config = array(); + } elseif (isset($config['class'])) { + $class = $config['class']; + unset($config['class']); + } else { + throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); + } + + $class = ltrim($class, '\\'); + + if (isset(self::$objectConfig[$class])) { + $config = array_merge(self::$objectConfig[$class], $config); + } + + if (($n = func_num_args()) > 1) { + /** @var $reflection \ReflectionClass */ + if (isset($reflections[$class])) { + $reflection = $reflections[$class]; + } else { + $reflection = $reflections[$class] = new \ReflectionClass($class); + } + $args = func_get_args(); + array_shift($args); // remove $config + if (!empty($config)) { + $args[] = $config; + } + return $reflection->newInstanceArgs($args); + } else { + return empty($config) ? new $class : new $class($config); + } + } + + /** + * Logs a trace message. + * Trace messages are logged mainly for development purpose to see + * the execution work flow of some code. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function trace($message, $category = 'application') + { + if (YII_DEBUG) { + self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); + } + } + + /** + * Logs an error message. + * An error message is typically logged when an unrecoverable error occurs + * during the execution of an application. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function error($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); + } + + /** + * Logs a warning message. + * A warning message is typically logged when an error occurs while the execution + * can still continue. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function warning($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); + } + + /** + * Logs an informative message. + * An informative message is typically logged by an application to keep record of + * something important (e.g. an administrator logs in). + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function info($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); + } + + /** + * Marks the beginning of a code block for profiling. + * This has to be matched with a call to [[endProfile]] with the same category name. + * The begin- and end- calls must also be properly nested. For example, + * + * ~~~ + * \Yii::beginProfile('block1'); + * // some code to be profiled + * \Yii::beginProfile('block2'); + * // some other code to be profiled + * \Yii::endProfile('block2'); + * \Yii::endProfile('block1'); + * ~~~ + * @param string $token token for the code block + * @param string $category the category of this log message + * @see endProfile + */ + public static function beginProfile($token, $category = 'application') + { + self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); + } + + /** + * Marks the end of a code block for profiling. + * This has to be matched with a previous call to [[beginProfile]] with the same category name. + * @param string $token token for the code block + * @param string $category the category of this log message + * @see beginProfile + */ + public static function endProfile($token, $category = 'application') + { + self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); + } + + /** + * Returns an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information. + * @return string an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information + */ + public static function powered() + { + return 'Powered by Yii Framework'; + } + + /** + * Translates a message to the specified language. + * + * This is a shortcut method of [[\yii\i18n\I18N::translate()]]. + * + * The translation will be conducted according to the message category and the target language will be used. + * + * In case when a translated message has different plural forms (separated by "|"), this method + * will also attempt to choose an appropriate one according to a given numeric value which is + * specified as the first parameter (indexed by 0) in `$params`. + * + * For example, if a translated message is "I have an apple.|I have {n} apples.", and the first + * parameter is 2, the message returned will be "I have 2 apples.". Note that the placeholder "{n}" + * will be replaced with the given number. + * + * For more details on how plural rules are applied, please refer to: + * [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]] + * + * @param string $category the message category. + * @param string $message the message to be translated. + * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. + * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current + * [[\yii\base\Application::language|application language]] will be used. + * @return string the translated message. + */ + public static function t($category, $message, $params = array(), $language = null) + { + if (self::$app !== null) { + return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); + } else { + return is_array($params) ? strtr($message, $params) : $message; + } + } + + /** + * Configures an object with the initial property values. + * @param object $object the object to be configured + * @param array $properties the property initial values given in terms of name-value pairs. + */ + public static function configure($object, $properties) + { + foreach ($properties as $name => $value) { + $object->$name = $value; + } + } + + /** + * Returns the public member variables of an object. + * This method is provided such that we can get the public member variables of an object. + * It is different from "get_object_vars()" because the latter will return private + * and protected variables if it is called within the object itself. + * @param object $object the object to be handled + * @return array the public member variables of the object + */ + public static function getObjectVars($object) + { + return get_object_vars($object); + } +} diff --git a/framework/yii/Yii.php b/framework/yii/Yii.php index bde15cc..0406463 100644 --- a/framework/yii/Yii.php +++ b/framework/yii/Yii.php @@ -12,13 +12,13 @@ require(__DIR__ . '/YiiBase.php'); /** * Yii is a helper class serving common framework functionalities. * - * It extends from [[YiiBase]] which provides the actual implementation. - * By writing your own Yii class, you can customize some functionalities of [[YiiBase]]. + * It extends from [[AbstractYii]] which provides the actual implementation. + * By writing your own Yii class, you can customize some functionalities of [[AbstractYii]]. * * @author Qiang Xue * @since 2.0 */ -class Yii extends \yii\YiiBase +class Yii extends \yii\AbstractYii { } diff --git a/framework/yii/YiiBase.php b/framework/yii/YiiBase.php deleted file mode 100644 index f04903d..0000000 --- a/framework/yii/YiiBase.php +++ /dev/null @@ -1,574 +0,0 @@ - - * @since 2.0 - */ -class YiiBase -{ - /** - * @var array class map used by the Yii autoloading mechanism. - * The array keys are the class names (without leading backslashes), and the array values - * are the corresponding class file paths (or path aliases). This property mainly affects - * how [[autoload()]] works. - * @see import - * @see autoload - */ - public static $classMap = array(); - /** - * @var \yii\console\Application|\yii\web\Application the application instance - */ - public static $app; - /** - * @var array registered path aliases - * @see getAlias - * @see setAlias - */ - public static $aliases = array('@yii' => __DIR__); - /** - * @var array initial property values that will be applied to objects newly created via [[createObject]]. - * The array keys are class names without leading backslashes "\", and the array values are the corresponding - * name-value pairs for initializing the created class instances. For example, - * - * ~~~ - * array( - * 'Bar' => array( - * 'prop1' => 'value1', - * 'prop2' => 'value2', - * ), - * 'mycompany\foo\Car' => array( - * 'prop1' => 'value1', - * 'prop2' => 'value2', - * ), - * ) - * ~~~ - * - * @see createObject - */ - public static $objectConfig = array(); - - - /** - * @return string the version of Yii framework - */ - public static function getVersion() - { - return '2.0-dev'; - } - - /** - * Imports a set of namespaces. - * - * By importing a namespace, the method will create an alias for the directory corresponding - * to the namespace. For example, if "foo\bar" is a namespace associated with the directory - * "path/to/foo/bar", then an alias "@foo/bar" will be created for this directory. - * - * This method is typically invoked in the bootstrap file to import the namespaces of - * the installed extensions. By default, Composer, when installing new extensions, will - * generate such a mapping file which can be loaded and passed to this method. - * - * @param array $namespaces the namespaces to be imported. The keys are the namespaces, - * and the values are the corresponding directories. - */ - public static function importNamespaces($namespaces) - { - foreach ($namespaces as $name => $path) { - if ($name !== '') { - $name = trim(strtr($name, array('\\' => '/', '_' => '/')), '/'); - if (is_array($path)) { - $path = reset($path); - } - static::setAlias('@' . $name, rtrim($path, '/\\') . '/' . $name); - } - } - } - - /** - * Translates a path alias into an actual path. - * - * The translation is done according to the following procedure: - * - * 1. If the given alias does not start with '@', it is returned back without change; - * 2. Otherwise, look for the longest registered alias that matches the beginning part - * of the given alias. If it exists, replace the matching part of the given alias with - * the corresponding registered path. - * 3. Throw an exception or return false, depending on the `$throwException` parameter. - * - * For example, by default '@yii' is registered as the alias to the Yii framework directory, - * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'. - * - * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' - * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. - * This is because the longest alias takes precedence. - * - * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced - * instead of '@foo/bar', because '/' serves as the boundary character. - * - * Note, this method does not check if the returned path exists or not. - * - * @param string $alias the alias to be translated. - * @param boolean $throwException whether to throw an exception if the given alias is invalid. - * If this is false and an invalid alias is given, false will be returned by this method. - * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. - * @throws InvalidParamException if the alias is invalid while $throwException is true. - * @see setAlias - */ - public static function getAlias($alias, $throwException = true) - { - if (strncmp($alias, '@', 1)) { - // not an alias - return $alias; - } - - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - - if (isset(self::$aliases[$root])) { - if (is_string(self::$aliases[$root])) { - return $pos === false ? self::$aliases[$root] : self::$aliases[$root] . substr($alias, $pos); - } else { - foreach (self::$aliases[$root] as $name => $path) { - if (strpos($alias . '/', $name . '/') === 0) { - return $path . substr($alias, strlen($name)); - } - } - } - } - - if ($throwException) { - throw new InvalidParamException("Invalid path alias: $alias"); - } else { - return false; - } - } - - /** - * Returns the root alias part of a given alias. - * A root alias is an alias that has been registered via [[setAlias()]] previously. - * If a given alias matches multiple root aliases, the longest one will be returned. - * @param string $alias the alias - * @return string|boolean the root alias, or false if no root alias is found - */ - public static function getRootAlias($alias) - { - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - - if (isset(self::$aliases[$root])) { - if (is_string(self::$aliases[$root])) { - return $root; - } else { - foreach (self::$aliases[$root] as $name => $path) { - if (strpos($alias . '/', $name . '/') === 0) { - return $name; - } - } - } - } - return false; - } - - /** - * Registers a path alias. - * - * A path alias is a short name representing a long path (a file path, a URL, etc.) - * For example, we use '@yii' as the alias of the path to the Yii framework directory. - * - * A path alias must start with the character '@' so that it can be easily differentiated - * from non-alias paths. - * - * Note that this method does not check if the given path exists or not. All it does is - * to associate the alias with the path. - * - * Any trailing '/' and '\' characters in the given path will be trimmed. - * - * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character. - * It may contain the forward slash '/' which serves as boundary character when performing - * alias translation by [[getAlias()]]. - * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters - * will be trimmed. This can be - * - * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) - * - a URL (e.g. `http://www.yiiframework.com`) - * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the - * actual path first by calling [[getAlias()]]. - * - * @throws InvalidParamException if $path is an invalid alias. - * @see getAlias - */ - public static function setAlias($alias, $path) - { - if (strncmp($alias, '@', 1)) { - $alias = '@' . $alias; - } - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - if ($path !== null) { - $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); - if (!isset(self::$aliases[$root])) { - if ($pos === false) { - self::$aliases[$root] = $path; - } else { - self::$aliases[$root] = array($alias => $path); - } - } elseif (is_string(self::$aliases[$root])) { - if ($pos === false) { - self::$aliases[$root] = $path; - } else { - self::$aliases[$root] = array( - $alias => $path, - $root => self::$aliases[$root], - ); - } - } else { - self::$aliases[$root][$alias] = $path; - krsort(self::$aliases[$root]); - } - } elseif (isset(self::$aliases[$root])) { - if (is_array(self::$aliases[$root])) { - unset(self::$aliases[$root][$alias]); - } elseif ($pos === false) { - unset(self::$aliases[$root]); - } - } - } - - /** - * Class autoload loader. - * This method is invoked automatically when PHP sees an unknown class. - * The method will attempt to include the class file according to the following procedure: - * - * 1. Search in [[classMap]]; - * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt - * to include the file associated with the corresponding path alias - * (e.g. `@yii/base/Component.php`); - * 3. If the class is named in PEAR style (e.g. `PHPUnit_Framework_TestCase`), - * it will attempt to include the file associated with the corresponding path alias - * (e.g. `@PHPUnit/Framework/TestCase.php`); - * - * This autoloader allows loading classes that follow the [PSR-0 standard](http://www.php-fig.org/psr/0/). - * Therefor a path alias has to be defined for each top-level namespace. - * - * @param string $className the fully qualified class name without a leading backslash "\" - * @throws UnknownClassException if the class does not exist in the class file - */ - public static function autoload($className) - { - if (isset(self::$classMap[$className])) { - $classFile = self::$classMap[$className]; - if ($classFile[0] === '@') { - $classFile = static::getAlias($classFile); - } - } else { - // follow PSR-0 to determine the class file - if (($pos = strrpos($className, '\\')) !== false) { - // namespaced class, e.g. yii\base\Component - $path = str_replace('\\', '/', substr($className, 0, $pos + 1)) - . str_replace('_', '/', substr($className, $pos + 1)) . '.php'; - } else { - $path = str_replace('_', '/', $className) . '.php'; - } - - // try loading via path alias - if (strpos($path, '/') === false) { - return; - } else { - $classFile = static::getAlias('@' . $path, false); - if ($classFile === false || !is_file($classFile)) { - return; - } - } - } - - include($classFile); - - if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && - (!function_exists('trait_exists') || !trait_exists($className, false))) { - throw new UnknownClassException("Unable to find '$className' in file: $classFile"); - } - } - - /** - * Creates a new object using the given configuration. - * - * The configuration can be either a string or an array. - * If a string, it is treated as the *object class*; if an array, - * it must contain a `class` element specifying the *object class*, and - * the rest of the name-value pairs in the array will be used to initialize - * the corresponding object properties. - * - * The object type can be either a class name or the [[getAlias()|alias]] of - * the class. For example, - * - * - `app\components\GoogleMap`: fully-qualified namespaced class. - * - `@app/components/GoogleMap`: an alias, used for non-namespaced class. - * - * Below are some usage examples: - * - * ~~~ - * $object = \Yii::createObject('@app/components/GoogleMap'); - * $object = \Yii::createObject(array( - * 'class' => '\app\components\GoogleMap', - * 'apiKey' => 'xyz', - * )); - * ~~~ - * - * This method can be used to create any object as long as the object's constructor is - * defined like the following: - * - * ~~~ - * public function __construct(..., $config = array()) { - * } - * ~~~ - * - * The method will pass the given configuration as the last parameter of the constructor, - * and any additional parameters to this method will be passed as the rest of the constructor parameters. - * - * @param string|array $config the configuration. It can be either a string representing the class name - * or an array representing the object configuration. - * @return mixed the created object - * @throws InvalidConfigException if the configuration is invalid. - */ - public static function createObject($config) - { - static $reflections = array(); - - if (is_string($config)) { - $class = $config; - $config = array(); - } elseif (isset($config['class'])) { - $class = $config['class']; - unset($config['class']); - } else { - throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); - } - - $class = ltrim($class, '\\'); - - if (isset(self::$objectConfig[$class])) { - $config = array_merge(self::$objectConfig[$class], $config); - } - - if (($n = func_num_args()) > 1) { - /** @var $reflection \ReflectionClass */ - if (isset($reflections[$class])) { - $reflection = $reflections[$class]; - } else { - $reflection = $reflections[$class] = new \ReflectionClass($class); - } - $args = func_get_args(); - array_shift($args); // remove $config - if (!empty($config)) { - $args[] = $config; - } - return $reflection->newInstanceArgs($args); - } else { - return empty($config) ? new $class : new $class($config); - } - } - - /** - * Logs a trace message. - * Trace messages are logged mainly for development purpose to see - * the execution work flow of some code. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function trace($message, $category = 'application') - { - if (YII_DEBUG) { - self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); - } - } - - /** - * Logs an error message. - * An error message is typically logged when an unrecoverable error occurs - * during the execution of an application. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function error($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); - } - - /** - * Logs a warning message. - * A warning message is typically logged when an error occurs while the execution - * can still continue. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function warning($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); - } - - /** - * Logs an informative message. - * An informative message is typically logged by an application to keep record of - * something important (e.g. an administrator logs in). - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function info($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); - } - - /** - * Marks the beginning of a code block for profiling. - * This has to be matched with a call to [[endProfile]] with the same category name. - * The begin- and end- calls must also be properly nested. For example, - * - * ~~~ - * \Yii::beginProfile('block1'); - * // some code to be profiled - * \Yii::beginProfile('block2'); - * // some other code to be profiled - * \Yii::endProfile('block2'); - * \Yii::endProfile('block1'); - * ~~~ - * @param string $token token for the code block - * @param string $category the category of this log message - * @see endProfile - */ - public static function beginProfile($token, $category = 'application') - { - self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); - } - - /** - * Marks the end of a code block for profiling. - * This has to be matched with a previous call to [[beginProfile]] with the same category name. - * @param string $token token for the code block - * @param string $category the category of this log message - * @see beginProfile - */ - public static function endProfile($token, $category = 'application') - { - self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); - } - - /** - * Returns an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information. - * @return string an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information - */ - public static function powered() - { - return 'Powered by Yii Framework'; - } - - /** - * Translates a message to the specified language. - * - * This is a shortcut method of [[\yii\i18n\I18N::translate()]]. - * - * The translation will be conducted according to the message category and the target language will be used. - * - * In case when a translated message has different plural forms (separated by "|"), this method - * will also attempt to choose an appropriate one according to a given numeric value which is - * specified as the first parameter (indexed by 0) in `$params`. - * - * For example, if a translated message is "I have an apple.|I have {n} apples.", and the first - * parameter is 2, the message returned will be "I have 2 apples.". Note that the placeholder "{n}" - * will be replaced with the given number. - * - * For more details on how plural rules are applied, please refer to: - * [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]] - * - * @param string $category the message category. - * @param string $message the message to be translated. - * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. - * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current - * [[\yii\base\Application::language|application language]] will be used. - * @return string the translated message. - */ - public static function t($category, $message, $params = array(), $language = null) - { - if (self::$app !== null) { - return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); - } else { - return is_array($params) ? strtr($message, $params) : $message; - } - } - - /** - * Configures an object with the initial property values. - * @param object $object the object to be configured - * @param array $properties the property initial values given in terms of name-value pairs. - */ - public static function configure($object, $properties) - { - foreach ($properties as $name => $value) { - $object->$name = $value; - } - } - - /** - * Returns the public member variables of an object. - * This method is provided such that we can get the public member variables of an object. - * It is different from "get_object_vars()" because the latter will return private - * and protected variables if it is called within the object itself. - * @param object $object the object to be handled - * @return array the public member variables of the object - */ - public static function getObjectVars($object) - { - return get_object_vars($object); - } -} diff --git a/framework/yii/log/Logger.php b/framework/yii/log/Logger.php index 54f3a49..76354d4 100644 --- a/framework/yii/log/Logger.php +++ b/framework/yii/log/Logger.php @@ -220,7 +220,7 @@ class Logger extends Component * Returns the total elapsed time since the start of the current request. * This method calculates the difference between now and the timestamp * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning - * of [[YiiBase]] class file. + * of [[AbstractYii]] class file. * @return float the total elapsed time in seconds for current request. */ public function getElapsedTime() From b873f9f242b3b927fcc9fe9b34ab1ec5e48f69d0 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 16:25:43 +0400 Subject: [PATCH 147/157] Fixes #915: helper classes renamed again --- build/build.xml | 2 +- build/controllers/ClassmapController.php | 2 +- build/controllers/PhpDocController.php | 2 +- docs/internals/autoloader.md | 2 +- framework/yii/AbstractYii.php | 574 --------- framework/yii/BaseYii.php | 574 +++++++++ framework/yii/Yii.php | 8 +- framework/yii/classes.php | 22 +- framework/yii/helpers/AbstractArrayHelper.php | 451 ------- framework/yii/helpers/AbstractConsole.php | 835 ------------- framework/yii/helpers/AbstractFileHelper.php | 329 ----- framework/yii/helpers/AbstractHtml.php | 1599 ------------------------ framework/yii/helpers/AbstractHtmlPurifier.php | 34 - framework/yii/helpers/AbstractInflector.php | 480 ------- framework/yii/helpers/AbstractJson.php | 112 -- framework/yii/helpers/AbstractMarkdown.php | 44 - framework/yii/helpers/AbstractSecurity.php | 285 ----- framework/yii/helpers/AbstractStringHelper.php | 138 -- framework/yii/helpers/AbstractVarDumper.php | 127 -- framework/yii/helpers/ArrayHelper.php | 2 +- framework/yii/helpers/BaseArrayHelper.php | 451 +++++++ framework/yii/helpers/BaseConsole.php | 835 +++++++++++++ framework/yii/helpers/BaseFileHelper.php | 329 +++++ framework/yii/helpers/BaseHtml.php | 1599 ++++++++++++++++++++++++ framework/yii/helpers/BaseHtmlPurifier.php | 34 + framework/yii/helpers/BaseInflector.php | 480 +++++++ framework/yii/helpers/BaseJson.php | 112 ++ framework/yii/helpers/BaseMarkdown.php | 44 + framework/yii/helpers/BaseSecurity.php | 285 +++++ framework/yii/helpers/BaseStringHelper.php | 138 ++ framework/yii/helpers/BaseVarDumper.php | 127 ++ framework/yii/helpers/Console.php | 2 +- framework/yii/helpers/FileHelper.php | 2 +- framework/yii/helpers/Html.php | 2 +- framework/yii/helpers/HtmlPurifier.php | 2 +- framework/yii/helpers/Inflector.php | 2 +- framework/yii/helpers/Json.php | 2 +- framework/yii/helpers/Markdown.php | 2 +- framework/yii/helpers/Security.php | 2 +- framework/yii/helpers/StringHelper.php | 2 +- framework/yii/helpers/VarDumper.php | 2 +- framework/yii/log/Logger.php | 2 +- 42 files changed, 5039 insertions(+), 5039 deletions(-) delete mode 100644 framework/yii/AbstractYii.php create mode 100644 framework/yii/BaseYii.php delete mode 100644 framework/yii/helpers/AbstractArrayHelper.php delete mode 100644 framework/yii/helpers/AbstractConsole.php delete mode 100644 framework/yii/helpers/AbstractFileHelper.php delete mode 100644 framework/yii/helpers/AbstractHtml.php delete mode 100644 framework/yii/helpers/AbstractHtmlPurifier.php delete mode 100644 framework/yii/helpers/AbstractInflector.php delete mode 100644 framework/yii/helpers/AbstractJson.php delete mode 100644 framework/yii/helpers/AbstractMarkdown.php delete mode 100644 framework/yii/helpers/AbstractSecurity.php delete mode 100644 framework/yii/helpers/AbstractStringHelper.php delete mode 100644 framework/yii/helpers/AbstractVarDumper.php create mode 100644 framework/yii/helpers/BaseArrayHelper.php create mode 100644 framework/yii/helpers/BaseConsole.php create mode 100644 framework/yii/helpers/BaseFileHelper.php create mode 100644 framework/yii/helpers/BaseHtml.php create mode 100644 framework/yii/helpers/BaseHtmlPurifier.php create mode 100644 framework/yii/helpers/BaseInflector.php create mode 100644 framework/yii/helpers/BaseJson.php create mode 100644 framework/yii/helpers/BaseMarkdown.php create mode 100644 framework/yii/helpers/BaseSecurity.php create mode 100644 framework/yii/helpers/BaseStringHelper.php create mode 100644 framework/yii/helpers/BaseVarDumper.php diff --git a/build/build.xml b/build/build.xml index 85d5aa5..b0975dc 100644 --- a/build/build.xml +++ b/build/build.xml @@ -265,7 +265,7 @@ Please update yiisite/common/data/versions.php file with the following code: where <target name> can be one of the following: - - sync : synchronize yiilite.php and AbstractYii.php + - sync : synchronize yiilite.php and BaseYii.php - message : extract i18n messages of the framework - src : build source release - doc : build documentation release (Windows only) diff --git a/build/controllers/ClassmapController.php b/build/controllers/ClassmapController.php index 10eb134..6a5ac8c 100644 --- a/build/controllers/ClassmapController.php +++ b/build/controllers/ClassmapController.php @@ -45,7 +45,7 @@ class ClassmapController extends Controller 'only' => array('.php'), 'except' => array( 'Yii.php', - 'AbstractYii.php', + 'BaseYii.php', '/debug/', '/console/', '/test/', diff --git a/build/controllers/PhpDocController.php b/build/controllers/PhpDocController.php index 4fed3b2..cb574ff 100644 --- a/build/controllers/PhpDocController.php +++ b/build/controllers/PhpDocController.php @@ -56,7 +56,7 @@ class PhpDocController extends Controller }, 'only' => array('.php'), 'except' => array( - 'AbstractYii.php', + 'BaseYii.php', 'Yii.php', '/debug/views/', '/requirements/', diff --git a/docs/internals/autoloader.md b/docs/internals/autoloader.md index 067773d..76a545b 100644 --- a/docs/internals/autoloader.md +++ b/docs/internals/autoloader.md @@ -16,4 +16,4 @@ PEAR-style libraries References ---------- -- AbstractYii::autoload \ No newline at end of file +- BaseYii::autoload \ No newline at end of file diff --git a/framework/yii/AbstractYii.php b/framework/yii/AbstractYii.php deleted file mode 100644 index c40aad9..0000000 --- a/framework/yii/AbstractYii.php +++ /dev/null @@ -1,574 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractYii -{ - /** - * @var array class map used by the Yii autoloading mechanism. - * The array keys are the class names (without leading backslashes), and the array values - * are the corresponding class file paths (or path aliases). This property mainly affects - * how [[autoload()]] works. - * @see import - * @see autoload - */ - public static $classMap = array(); - /** - * @var \yii\console\Application|\yii\web\Application the application instance - */ - public static $app; - /** - * @var array registered path aliases - * @see getAlias - * @see setAlias - */ - public static $aliases = array('@yii' => __DIR__); - /** - * @var array initial property values that will be applied to objects newly created via [[createObject]]. - * The array keys are class names without leading backslashes "\", and the array values are the corresponding - * name-value pairs for initializing the created class instances. For example, - * - * ~~~ - * array( - * 'Bar' => array( - * 'prop1' => 'value1', - * 'prop2' => 'value2', - * ), - * 'mycompany\foo\Car' => array( - * 'prop1' => 'value1', - * 'prop2' => 'value2', - * ), - * ) - * ~~~ - * - * @see createObject - */ - public static $objectConfig = array(); - - - /** - * @return string the version of Yii framework - */ - public static function getVersion() - { - return '2.0-dev'; - } - - /** - * Imports a set of namespaces. - * - * By importing a namespace, the method will create an alias for the directory corresponding - * to the namespace. For example, if "foo\bar" is a namespace associated with the directory - * "path/to/foo/bar", then an alias "@foo/bar" will be created for this directory. - * - * This method is typically invoked in the bootstrap file to import the namespaces of - * the installed extensions. By default, Composer, when installing new extensions, will - * generate such a mapping file which can be loaded and passed to this method. - * - * @param array $namespaces the namespaces to be imported. The keys are the namespaces, - * and the values are the corresponding directories. - */ - public static function importNamespaces($namespaces) - { - foreach ($namespaces as $name => $path) { - if ($name !== '') { - $name = trim(strtr($name, array('\\' => '/', '_' => '/')), '/'); - if (is_array($path)) { - $path = reset($path); - } - static::setAlias('@' . $name, rtrim($path, '/\\') . '/' . $name); - } - } - } - - /** - * Translates a path alias into an actual path. - * - * The translation is done according to the following procedure: - * - * 1. If the given alias does not start with '@', it is returned back without change; - * 2. Otherwise, look for the longest registered alias that matches the beginning part - * of the given alias. If it exists, replace the matching part of the given alias with - * the corresponding registered path. - * 3. Throw an exception or return false, depending on the `$throwException` parameter. - * - * For example, by default '@yii' is registered as the alias to the Yii framework directory, - * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'. - * - * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' - * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. - * This is because the longest alias takes precedence. - * - * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced - * instead of '@foo/bar', because '/' serves as the boundary character. - * - * Note, this method does not check if the returned path exists or not. - * - * @param string $alias the alias to be translated. - * @param boolean $throwException whether to throw an exception if the given alias is invalid. - * If this is false and an invalid alias is given, false will be returned by this method. - * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. - * @throws InvalidParamException if the alias is invalid while $throwException is true. - * @see setAlias - */ - public static function getAlias($alias, $throwException = true) - { - if (strncmp($alias, '@', 1)) { - // not an alias - return $alias; - } - - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - - if (isset(self::$aliases[$root])) { - if (is_string(self::$aliases[$root])) { - return $pos === false ? self::$aliases[$root] : self::$aliases[$root] . substr($alias, $pos); - } else { - foreach (self::$aliases[$root] as $name => $path) { - if (strpos($alias . '/', $name . '/') === 0) { - return $path . substr($alias, strlen($name)); - } - } - } - } - - if ($throwException) { - throw new InvalidParamException("Invalid path alias: $alias"); - } else { - return false; - } - } - - /** - * Returns the root alias part of a given alias. - * A root alias is an alias that has been registered via [[setAlias()]] previously. - * If a given alias matches multiple root aliases, the longest one will be returned. - * @param string $alias the alias - * @return string|boolean the root alias, or false if no root alias is found - */ - public static function getRootAlias($alias) - { - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - - if (isset(self::$aliases[$root])) { - if (is_string(self::$aliases[$root])) { - return $root; - } else { - foreach (self::$aliases[$root] as $name => $path) { - if (strpos($alias . '/', $name . '/') === 0) { - return $name; - } - } - } - } - return false; - } - - /** - * Registers a path alias. - * - * A path alias is a short name representing a long path (a file path, a URL, etc.) - * For example, we use '@yii' as the alias of the path to the Yii framework directory. - * - * A path alias must start with the character '@' so that it can be easily differentiated - * from non-alias paths. - * - * Note that this method does not check if the given path exists or not. All it does is - * to associate the alias with the path. - * - * Any trailing '/' and '\' characters in the given path will be trimmed. - * - * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character. - * It may contain the forward slash '/' which serves as boundary character when performing - * alias translation by [[getAlias()]]. - * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters - * will be trimmed. This can be - * - * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) - * - a URL (e.g. `http://www.yiiframework.com`) - * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the - * actual path first by calling [[getAlias()]]. - * - * @throws InvalidParamException if $path is an invalid alias. - * @see getAlias - */ - public static function setAlias($alias, $path) - { - if (strncmp($alias, '@', 1)) { - $alias = '@' . $alias; - } - $pos = strpos($alias, '/'); - $root = $pos === false ? $alias : substr($alias, 0, $pos); - if ($path !== null) { - $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); - if (!isset(self::$aliases[$root])) { - if ($pos === false) { - self::$aliases[$root] = $path; - } else { - self::$aliases[$root] = array($alias => $path); - } - } elseif (is_string(self::$aliases[$root])) { - if ($pos === false) { - self::$aliases[$root] = $path; - } else { - self::$aliases[$root] = array( - $alias => $path, - $root => self::$aliases[$root], - ); - } - } else { - self::$aliases[$root][$alias] = $path; - krsort(self::$aliases[$root]); - } - } elseif (isset(self::$aliases[$root])) { - if (is_array(self::$aliases[$root])) { - unset(self::$aliases[$root][$alias]); - } elseif ($pos === false) { - unset(self::$aliases[$root]); - } - } - } - - /** - * Class autoload loader. - * This method is invoked automatically when PHP sees an unknown class. - * The method will attempt to include the class file according to the following procedure: - * - * 1. Search in [[classMap]]; - * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt - * to include the file associated with the corresponding path alias - * (e.g. `@yii/base/Component.php`); - * 3. If the class is named in PEAR style (e.g. `PHPUnit_Framework_TestCase`), - * it will attempt to include the file associated with the corresponding path alias - * (e.g. `@PHPUnit/Framework/TestCase.php`); - * - * This autoloader allows loading classes that follow the [PSR-0 standard](http://www.php-fig.org/psr/0/). - * Therefor a path alias has to be defined for each top-level namespace. - * - * @param string $className the fully qualified class name without a leading backslash "\" - * @throws UnknownClassException if the class does not exist in the class file - */ - public static function autoload($className) - { - if (isset(self::$classMap[$className])) { - $classFile = self::$classMap[$className]; - if ($classFile[0] === '@') { - $classFile = static::getAlias($classFile); - } - } else { - // follow PSR-0 to determine the class file - if (($pos = strrpos($className, '\\')) !== false) { - // namespaced class, e.g. yii\base\Component - $path = str_replace('\\', '/', substr($className, 0, $pos + 1)) - . str_replace('_', '/', substr($className, $pos + 1)) . '.php'; - } else { - $path = str_replace('_', '/', $className) . '.php'; - } - - // try loading via path alias - if (strpos($path, '/') === false) { - return; - } else { - $classFile = static::getAlias('@' . $path, false); - if ($classFile === false || !is_file($classFile)) { - return; - } - } - } - - include($classFile); - - if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && - (!function_exists('trait_exists') || !trait_exists($className, false))) { - throw new UnknownClassException("Unable to find '$className' in file: $classFile"); - } - } - - /** - * Creates a new object using the given configuration. - * - * The configuration can be either a string or an array. - * If a string, it is treated as the *object class*; if an array, - * it must contain a `class` element specifying the *object class*, and - * the rest of the name-value pairs in the array will be used to initialize - * the corresponding object properties. - * - * The object type can be either a class name or the [[getAlias()|alias]] of - * the class. For example, - * - * - `app\components\GoogleMap`: fully-qualified namespaced class. - * - `@app/components/GoogleMap`: an alias, used for non-namespaced class. - * - * Below are some usage examples: - * - * ~~~ - * $object = \Yii::createObject('@app/components/GoogleMap'); - * $object = \Yii::createObject(array( - * 'class' => '\app\components\GoogleMap', - * 'apiKey' => 'xyz', - * )); - * ~~~ - * - * This method can be used to create any object as long as the object's constructor is - * defined like the following: - * - * ~~~ - * public function __construct(..., $config = array()) { - * } - * ~~~ - * - * The method will pass the given configuration as the last parameter of the constructor, - * and any additional parameters to this method will be passed as the rest of the constructor parameters. - * - * @param string|array $config the configuration. It can be either a string representing the class name - * or an array representing the object configuration. - * @return mixed the created object - * @throws InvalidConfigException if the configuration is invalid. - */ - public static function createObject($config) - { - static $reflections = array(); - - if (is_string($config)) { - $class = $config; - $config = array(); - } elseif (isset($config['class'])) { - $class = $config['class']; - unset($config['class']); - } else { - throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); - } - - $class = ltrim($class, '\\'); - - if (isset(self::$objectConfig[$class])) { - $config = array_merge(self::$objectConfig[$class], $config); - } - - if (($n = func_num_args()) > 1) { - /** @var $reflection \ReflectionClass */ - if (isset($reflections[$class])) { - $reflection = $reflections[$class]; - } else { - $reflection = $reflections[$class] = new \ReflectionClass($class); - } - $args = func_get_args(); - array_shift($args); // remove $config - if (!empty($config)) { - $args[] = $config; - } - return $reflection->newInstanceArgs($args); - } else { - return empty($config) ? new $class : new $class($config); - } - } - - /** - * Logs a trace message. - * Trace messages are logged mainly for development purpose to see - * the execution work flow of some code. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function trace($message, $category = 'application') - { - if (YII_DEBUG) { - self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); - } - } - - /** - * Logs an error message. - * An error message is typically logged when an unrecoverable error occurs - * during the execution of an application. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function error($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); - } - - /** - * Logs a warning message. - * A warning message is typically logged when an error occurs while the execution - * can still continue. - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function warning($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); - } - - /** - * Logs an informative message. - * An informative message is typically logged by an application to keep record of - * something important (e.g. an administrator logs in). - * @param string $message the message to be logged. - * @param string $category the category of the message. - */ - public static function info($message, $category = 'application') - { - self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); - } - - /** - * Marks the beginning of a code block for profiling. - * This has to be matched with a call to [[endProfile]] with the same category name. - * The begin- and end- calls must also be properly nested. For example, - * - * ~~~ - * \Yii::beginProfile('block1'); - * // some code to be profiled - * \Yii::beginProfile('block2'); - * // some other code to be profiled - * \Yii::endProfile('block2'); - * \Yii::endProfile('block1'); - * ~~~ - * @param string $token token for the code block - * @param string $category the category of this log message - * @see endProfile - */ - public static function beginProfile($token, $category = 'application') - { - self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); - } - - /** - * Marks the end of a code block for profiling. - * This has to be matched with a previous call to [[beginProfile]] with the same category name. - * @param string $token token for the code block - * @param string $category the category of this log message - * @see beginProfile - */ - public static function endProfile($token, $category = 'application') - { - self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); - } - - /** - * Returns an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information. - * @return string an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information - */ - public static function powered() - { - return 'Powered by Yii Framework'; - } - - /** - * Translates a message to the specified language. - * - * This is a shortcut method of [[\yii\i18n\I18N::translate()]]. - * - * The translation will be conducted according to the message category and the target language will be used. - * - * In case when a translated message has different plural forms (separated by "|"), this method - * will also attempt to choose an appropriate one according to a given numeric value which is - * specified as the first parameter (indexed by 0) in `$params`. - * - * For example, if a translated message is "I have an apple.|I have {n} apples.", and the first - * parameter is 2, the message returned will be "I have 2 apples.". Note that the placeholder "{n}" - * will be replaced with the given number. - * - * For more details on how plural rules are applied, please refer to: - * [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]] - * - * @param string $category the message category. - * @param string $message the message to be translated. - * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. - * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current - * [[\yii\base\Application::language|application language]] will be used. - * @return string the translated message. - */ - public static function t($category, $message, $params = array(), $language = null) - { - if (self::$app !== null) { - return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); - } else { - return is_array($params) ? strtr($message, $params) : $message; - } - } - - /** - * Configures an object with the initial property values. - * @param object $object the object to be configured - * @param array $properties the property initial values given in terms of name-value pairs. - */ - public static function configure($object, $properties) - { - foreach ($properties as $name => $value) { - $object->$name = $value; - } - } - - /** - * Returns the public member variables of an object. - * This method is provided such that we can get the public member variables of an object. - * It is different from "get_object_vars()" because the latter will return private - * and protected variables if it is called within the object itself. - * @param object $object the object to be handled - * @return array the public member variables of the object - */ - public static function getObjectVars($object) - { - return get_object_vars($object); - } -} diff --git a/framework/yii/BaseYii.php b/framework/yii/BaseYii.php new file mode 100644 index 0000000..b586160 --- /dev/null +++ b/framework/yii/BaseYii.php @@ -0,0 +1,574 @@ + + * @since 2.0 + */ +class BaseYii +{ + /** + * @var array class map used by the Yii autoloading mechanism. + * The array keys are the class names (without leading backslashes), and the array values + * are the corresponding class file paths (or path aliases). This property mainly affects + * how [[autoload()]] works. + * @see import + * @see autoload + */ + public static $classMap = array(); + /** + * @var \yii\console\Application|\yii\web\Application the application instance + */ + public static $app; + /** + * @var array registered path aliases + * @see getAlias + * @see setAlias + */ + public static $aliases = array('@yii' => __DIR__); + /** + * @var array initial property values that will be applied to objects newly created via [[createObject]]. + * The array keys are class names without leading backslashes "\", and the array values are the corresponding + * name-value pairs for initializing the created class instances. For example, + * + * ~~~ + * array( + * 'Bar' => array( + * 'prop1' => 'value1', + * 'prop2' => 'value2', + * ), + * 'mycompany\foo\Car' => array( + * 'prop1' => 'value1', + * 'prop2' => 'value2', + * ), + * ) + * ~~~ + * + * @see createObject + */ + public static $objectConfig = array(); + + + /** + * @return string the version of Yii framework + */ + public static function getVersion() + { + return '2.0-dev'; + } + + /** + * Imports a set of namespaces. + * + * By importing a namespace, the method will create an alias for the directory corresponding + * to the namespace. For example, if "foo\bar" is a namespace associated with the directory + * "path/to/foo/bar", then an alias "@foo/bar" will be created for this directory. + * + * This method is typically invoked in the bootstrap file to import the namespaces of + * the installed extensions. By default, Composer, when installing new extensions, will + * generate such a mapping file which can be loaded and passed to this method. + * + * @param array $namespaces the namespaces to be imported. The keys are the namespaces, + * and the values are the corresponding directories. + */ + public static function importNamespaces($namespaces) + { + foreach ($namespaces as $name => $path) { + if ($name !== '') { + $name = trim(strtr($name, array('\\' => '/', '_' => '/')), '/'); + if (is_array($path)) { + $path = reset($path); + } + static::setAlias('@' . $name, rtrim($path, '/\\') . '/' . $name); + } + } + } + + /** + * Translates a path alias into an actual path. + * + * The translation is done according to the following procedure: + * + * 1. If the given alias does not start with '@', it is returned back without change; + * 2. Otherwise, look for the longest registered alias that matches the beginning part + * of the given alias. If it exists, replace the matching part of the given alias with + * the corresponding registered path. + * 3. Throw an exception or return false, depending on the `$throwException` parameter. + * + * For example, by default '@yii' is registered as the alias to the Yii framework directory, + * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'. + * + * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' + * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. + * This is because the longest alias takes precedence. + * + * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced + * instead of '@foo/bar', because '/' serves as the boundary character. + * + * Note, this method does not check if the returned path exists or not. + * + * @param string $alias the alias to be translated. + * @param boolean $throwException whether to throw an exception if the given alias is invalid. + * If this is false and an invalid alias is given, false will be returned by this method. + * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. + * @throws InvalidParamException if the alias is invalid while $throwException is true. + * @see setAlias + */ + public static function getAlias($alias, $throwException = true) + { + if (strncmp($alias, '@', 1)) { + // not an alias + return $alias; + } + + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(self::$aliases[$root])) { + if (is_string(self::$aliases[$root])) { + return $pos === false ? self::$aliases[$root] : self::$aliases[$root] . substr($alias, $pos); + } else { + foreach (self::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $path . substr($alias, strlen($name)); + } + } + } + } + + if ($throwException) { + throw new InvalidParamException("Invalid path alias: $alias"); + } else { + return false; + } + } + + /** + * Returns the root alias part of a given alias. + * A root alias is an alias that has been registered via [[setAlias()]] previously. + * If a given alias matches multiple root aliases, the longest one will be returned. + * @param string $alias the alias + * @return string|boolean the root alias, or false if no root alias is found + */ + public static function getRootAlias($alias) + { + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(self::$aliases[$root])) { + if (is_string(self::$aliases[$root])) { + return $root; + } else { + foreach (self::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $name; + } + } + } + } + return false; + } + + /** + * Registers a path alias. + * + * A path alias is a short name representing a long path (a file path, a URL, etc.) + * For example, we use '@yii' as the alias of the path to the Yii framework directory. + * + * A path alias must start with the character '@' so that it can be easily differentiated + * from non-alias paths. + * + * Note that this method does not check if the given path exists or not. All it does is + * to associate the alias with the path. + * + * Any trailing '/' and '\' characters in the given path will be trimmed. + * + * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character. + * It may contain the forward slash '/' which serves as boundary character when performing + * alias translation by [[getAlias()]]. + * @param string $path the path corresponding to the alias. Trailing '/' and '\' characters + * will be trimmed. This can be + * + * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) + * - a URL (e.g. `http://www.yiiframework.com`) + * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the + * actual path first by calling [[getAlias()]]. + * + * @throws InvalidParamException if $path is an invalid alias. + * @see getAlias + */ + public static function setAlias($alias, $path) + { + if (strncmp($alias, '@', 1)) { + $alias = '@' . $alias; + } + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + if ($path !== null) { + $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); + if (!isset(self::$aliases[$root])) { + if ($pos === false) { + self::$aliases[$root] = $path; + } else { + self::$aliases[$root] = array($alias => $path); + } + } elseif (is_string(self::$aliases[$root])) { + if ($pos === false) { + self::$aliases[$root] = $path; + } else { + self::$aliases[$root] = array( + $alias => $path, + $root => self::$aliases[$root], + ); + } + } else { + self::$aliases[$root][$alias] = $path; + krsort(self::$aliases[$root]); + } + } elseif (isset(self::$aliases[$root])) { + if (is_array(self::$aliases[$root])) { + unset(self::$aliases[$root][$alias]); + } elseif ($pos === false) { + unset(self::$aliases[$root]); + } + } + } + + /** + * Class autoload loader. + * This method is invoked automatically when PHP sees an unknown class. + * The method will attempt to include the class file according to the following procedure: + * + * 1. Search in [[classMap]]; + * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt + * to include the file associated with the corresponding path alias + * (e.g. `@yii/base/Component.php`); + * 3. If the class is named in PEAR style (e.g. `PHPUnit_Framework_TestCase`), + * it will attempt to include the file associated with the corresponding path alias + * (e.g. `@PHPUnit/Framework/TestCase.php`); + * + * This autoloader allows loading classes that follow the [PSR-0 standard](http://www.php-fig.org/psr/0/). + * Therefor a path alias has to be defined for each top-level namespace. + * + * @param string $className the fully qualified class name without a leading backslash "\" + * @throws UnknownClassException if the class does not exist in the class file + */ + public static function autoload($className) + { + if (isset(self::$classMap[$className])) { + $classFile = self::$classMap[$className]; + if ($classFile[0] === '@') { + $classFile = static::getAlias($classFile); + } + } else { + // follow PSR-0 to determine the class file + if (($pos = strrpos($className, '\\')) !== false) { + // namespaced class, e.g. yii\base\Component + $path = str_replace('\\', '/', substr($className, 0, $pos + 1)) + . str_replace('_', '/', substr($className, $pos + 1)) . '.php'; + } else { + $path = str_replace('_', '/', $className) . '.php'; + } + + // try loading via path alias + if (strpos($path, '/') === false) { + return; + } else { + $classFile = static::getAlias('@' . $path, false); + if ($classFile === false || !is_file($classFile)) { + return; + } + } + } + + include($classFile); + + if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && + (!function_exists('trait_exists') || !trait_exists($className, false))) { + throw new UnknownClassException("Unable to find '$className' in file: $classFile"); + } + } + + /** + * Creates a new object using the given configuration. + * + * The configuration can be either a string or an array. + * If a string, it is treated as the *object class*; if an array, + * it must contain a `class` element specifying the *object class*, and + * the rest of the name-value pairs in the array will be used to initialize + * the corresponding object properties. + * + * The object type can be either a class name or the [[getAlias()|alias]] of + * the class. For example, + * + * - `app\components\GoogleMap`: fully-qualified namespaced class. + * - `@app/components/GoogleMap`: an alias, used for non-namespaced class. + * + * Below are some usage examples: + * + * ~~~ + * $object = \Yii::createObject('@app/components/GoogleMap'); + * $object = \Yii::createObject(array( + * 'class' => '\app\components\GoogleMap', + * 'apiKey' => 'xyz', + * )); + * ~~~ + * + * This method can be used to create any object as long as the object's constructor is + * defined like the following: + * + * ~~~ + * public function __construct(..., $config = array()) { + * } + * ~~~ + * + * The method will pass the given configuration as the last parameter of the constructor, + * and any additional parameters to this method will be passed as the rest of the constructor parameters. + * + * @param string|array $config the configuration. It can be either a string representing the class name + * or an array representing the object configuration. + * @return mixed the created object + * @throws InvalidConfigException if the configuration is invalid. + */ + public static function createObject($config) + { + static $reflections = array(); + + if (is_string($config)) { + $class = $config; + $config = array(); + } elseif (isset($config['class'])) { + $class = $config['class']; + unset($config['class']); + } else { + throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); + } + + $class = ltrim($class, '\\'); + + if (isset(self::$objectConfig[$class])) { + $config = array_merge(self::$objectConfig[$class], $config); + } + + if (($n = func_num_args()) > 1) { + /** @var $reflection \ReflectionClass */ + if (isset($reflections[$class])) { + $reflection = $reflections[$class]; + } else { + $reflection = $reflections[$class] = new \ReflectionClass($class); + } + $args = func_get_args(); + array_shift($args); // remove $config + if (!empty($config)) { + $args[] = $config; + } + return $reflection->newInstanceArgs($args); + } else { + return empty($config) ? new $class : new $class($config); + } + } + + /** + * Logs a trace message. + * Trace messages are logged mainly for development purpose to see + * the execution work flow of some code. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function trace($message, $category = 'application') + { + if (YII_DEBUG) { + self::$app->getLog()->log($message, Logger::LEVEL_TRACE, $category); + } + } + + /** + * Logs an error message. + * An error message is typically logged when an unrecoverable error occurs + * during the execution of an application. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function error($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_ERROR, $category); + } + + /** + * Logs a warning message. + * A warning message is typically logged when an error occurs while the execution + * can still continue. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function warning($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_WARNING, $category); + } + + /** + * Logs an informative message. + * An informative message is typically logged by an application to keep record of + * something important (e.g. an administrator logs in). + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function info($message, $category = 'application') + { + self::$app->getLog()->log($message, Logger::LEVEL_INFO, $category); + } + + /** + * Marks the beginning of a code block for profiling. + * This has to be matched with a call to [[endProfile]] with the same category name. + * The begin- and end- calls must also be properly nested. For example, + * + * ~~~ + * \Yii::beginProfile('block1'); + * // some code to be profiled + * \Yii::beginProfile('block2'); + * // some other code to be profiled + * \Yii::endProfile('block2'); + * \Yii::endProfile('block1'); + * ~~~ + * @param string $token token for the code block + * @param string $category the category of this log message + * @see endProfile + */ + public static function beginProfile($token, $category = 'application') + { + self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); + } + + /** + * Marks the end of a code block for profiling. + * This has to be matched with a previous call to [[beginProfile]] with the same category name. + * @param string $token token for the code block + * @param string $category the category of this log message + * @see beginProfile + */ + public static function endProfile($token, $category = 'application') + { + self::$app->getLog()->log($token, Logger::LEVEL_PROFILE_END, $category); + } + + /** + * Returns an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information. + * @return string an HTML hyperlink that can be displayed on your Web page showing Powered by Yii" information + */ + public static function powered() + { + return 'Powered by Yii Framework'; + } + + /** + * Translates a message to the specified language. + * + * This is a shortcut method of [[\yii\i18n\I18N::translate()]]. + * + * The translation will be conducted according to the message category and the target language will be used. + * + * In case when a translated message has different plural forms (separated by "|"), this method + * will also attempt to choose an appropriate one according to a given numeric value which is + * specified as the first parameter (indexed by 0) in `$params`. + * + * For example, if a translated message is "I have an apple.|I have {n} apples.", and the first + * parameter is 2, the message returned will be "I have 2 apples.". Note that the placeholder "{n}" + * will be replaced with the given number. + * + * For more details on how plural rules are applied, please refer to: + * [[http://www.unicode.org/cldr/charts/supplemental/language_plural_rules.html]] + * + * @param string $category the message category. + * @param string $message the message to be translated. + * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. + * @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current + * [[\yii\base\Application::language|application language]] will be used. + * @return string the translated message. + */ + public static function t($category, $message, $params = array(), $language = null) + { + if (self::$app !== null) { + return self::$app->getI18N()->translate($category, $message, $params, $language ?: self::$app->language); + } else { + return is_array($params) ? strtr($message, $params) : $message; + } + } + + /** + * Configures an object with the initial property values. + * @param object $object the object to be configured + * @param array $properties the property initial values given in terms of name-value pairs. + */ + public static function configure($object, $properties) + { + foreach ($properties as $name => $value) { + $object->$name = $value; + } + } + + /** + * Returns the public member variables of an object. + * This method is provided such that we can get the public member variables of an object. + * It is different from "get_object_vars()" because the latter will return private + * and protected variables if it is called within the object itself. + * @param object $object the object to be handled + * @return array the public member variables of the object + */ + public static function getObjectVars($object) + { + return get_object_vars($object); + } +} diff --git a/framework/yii/Yii.php b/framework/yii/Yii.php index 0406463..232117f 100644 --- a/framework/yii/Yii.php +++ b/framework/yii/Yii.php @@ -7,18 +7,18 @@ * @license http://www.yiiframework.com/license/ */ -require(__DIR__ . '/YiiBase.php'); +require(__DIR__ . '/BaseYii.php'); /** * Yii is a helper class serving common framework functionalities. * - * It extends from [[AbstractYii]] which provides the actual implementation. - * By writing your own Yii class, you can customize some functionalities of [[AbstractYii]]. + * It extends from [[BaseYii]] which provides the actual implementation. + * By writing your own Yii class, you can customize some functionalities of [[BaseYii]]. * * @author Qiang Xue * @since 2.0 */ -class Yii extends \yii\AbstractYii +class Yii extends \yii\BaseYii { } diff --git a/framework/yii/classes.php b/framework/yii/classes.php index 22472d7..d4f304c 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -125,27 +125,27 @@ return array( 'yii\grid\GridViewAsset' => YII_PATH . '/grid/GridViewAsset.php', 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php', 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php', - 'yii\helpers\AbstractArrayHelper' => YII_PATH . '/helpers/AbstractArrayHelper.php', + 'yii\helpers\BaseArrayHelper' => YII_PATH . '/helpers/BaseArrayHelper.php', 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php', - 'yii\helpers\AbstractConsole' => YII_PATH . '/helpers/AbstractConsole.php', + 'yii\helpers\BaseConsole' => YII_PATH . '/helpers/BaseConsole.php', 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php', - 'yii\helpers\AbstractFileHelper' => YII_PATH . '/helpers/AbstractFileHelper.php', + 'yii\helpers\BaseFileHelper' => YII_PATH . '/helpers/BaseFileHelper.php', 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php', - 'yii\helpers\AbstractHtml' => YII_PATH . '/helpers/AbstractHtml.php', + 'yii\helpers\BaseHtml' => YII_PATH . '/helpers/BaseHtml.php', 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php', - 'yii\helpers\AbstractHtmlPurifier' => YII_PATH . '/helpers/AbstractHtmlPurifier.php', + 'yii\helpers\BaseHtmlPurifier' => YII_PATH . '/helpers/BaseHtmlPurifier.php', 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php', - 'yii\helpers\AbstractInflector' => YII_PATH . '/helpers/AbstractInflector.php', + 'yii\helpers\BaseInflector' => YII_PATH . '/helpers/BaseInflector.php', 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php', - 'yii\helpers\AbstractJson' => YII_PATH . '/helpers/AbstractJson.php', + 'yii\helpers\BaseJson' => YII_PATH . '/helpers/BaseJson.php', 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php', - 'yii\helpers\AbstractMarkdown' => YII_PATH . '/helpers/AbstractMarkdown.php', + 'yii\helpers\BaseMarkdown' => YII_PATH . '/helpers/BaseMarkdown.php', 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php', - 'yii\helpers\AbstractSecurity' => YII_PATH . '/helpers/AbstractSecurity.php', + 'yii\helpers\BaseSecurity' => YII_PATH . '/helpers/BaseSecurity.php', 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php', - 'yii\helpers\AbstractStringHelper' => YII_PATH . '/helpers/AbstractStringHelper.php', + 'yii\helpers\BaseStringHelper' => YII_PATH . '/helpers/BaseStringHelper.php', 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php', - 'yii\helpers\AbstractVarDumper' => YII_PATH . '/helpers/AbstractVarDumper.php', + 'yii\helpers\BaseVarDumper' => YII_PATH . '/helpers/BaseVarDumper.php', 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php', 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php', 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php', diff --git a/framework/yii/helpers/AbstractArrayHelper.php b/framework/yii/helpers/AbstractArrayHelper.php deleted file mode 100644 index c26c1cd..0000000 --- a/framework/yii/helpers/AbstractArrayHelper.php +++ /dev/null @@ -1,451 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractArrayHelper -{ - /** - * Converts an object or an array of objects into an array. - * @param object|array $object the object to be converted into an array - * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays. - * The properties specified for each class is an array of the following format: - * - * ~~~ - * array( - * 'app\models\Post' => array( - * 'id', - * 'title', - * // the key name in array result => property name - * 'createTime' => 'create_time', - * // the key name in array result => anonymous function - * 'length' => function ($post) { - * return strlen($post->content); - * }, - * ), - * ) - * ~~~ - * - * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: - * - * ~~~ - * array( - * 'id' => 123, - * 'title' => 'test', - * 'createTime' => '2013-01-01 12:00AM', - * 'length' => 301, - * ) - * ~~~ - * - * @param boolean $recursive whether to recursively converts properties which are objects into arrays. - * @return array the array representation of the object - */ - public static function toArray($object, $properties = array(), $recursive = true) - { - if (!empty($properties) && is_object($object)) { - $className = get_class($object); - if (!empty($properties[$className])) { - $result = array(); - foreach ($properties[$className] as $key => $name) { - if (is_int($key)) { - $result[$name] = $object->$name; - } else { - $result[$key] = static::getValue($object, $name); - } - } - return $result; - } - } - if ($object instanceof Arrayable) { - $object = $object->toArray(); - if (!$recursive) { - return $object; - } - } - $result = array(); - foreach ($object as $key => $value) { - if ($recursive && (is_array($value) || is_object($value))) { - $result[$key] = static::toArray($value, true); - } else { - $result[$key] = $value; - } - } - return $result; - } - - /** - * Merges two or more arrays into one recursively. - * If each array has an element with the same string key value, the latter - * will overwrite the former (different from array_merge_recursive). - * Recursive merging will be conducted if both arrays have an element of array - * type and are having the same key. - * For integer-keyed elements, the elements from the latter array will - * be appended to the former array. - * @param array $a array to be merged to - * @param array $b array to be merged from. You can specify additional - * arrays via third argument, fourth argument etc. - * @return array the merged array (the original arrays are not changed.) - */ - public static function merge($a, $b) - { - $args = func_get_args(); - $res = array_shift($args); - while (!empty($args)) { - $next = array_shift($args); - foreach ($next as $k => $v) { - if (is_integer($k)) { - isset($res[$k]) ? $res[] = $v : $res[$k] = $v; - } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { - $res[$k] = self::merge($res[$k], $v); - } else { - $res[$k] = $v; - } - } - } - return $res; - } - - /** - * Retrieves the value of an array element or object property with the given key or property name. - * If the key does not exist in the array, the default value will be returned instead. - * - * Below are some usage examples, - * - * ~~~ - * // working with array - * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); - * // working with object - * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); - * // working with anonymous function - * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) { - * return $user->firstName . ' ' . $user->lastName; - * }); - * ~~~ - * - * @param array|object $array array or object to extract value from - * @param string|\Closure $key key name of the array element, or property name of the object, - * or an anonymous function returning the value. The anonymous function signature should be: - * `function($array, $defaultValue)`. - * @param mixed $default the default value to be returned if the specified key does not exist - * @return mixed the value of the element if found, default value otherwise - */ - public static function getValue($array, $key, $default = null) - { - if ($key instanceof \Closure) { - return $key($array, $default); - } elseif (is_array($array)) { - return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default; - } else { - return $array->$key; - } - } - - /** - * Removes an item from an array and returns the value. If the key does not exist in the array, the default value - * will be returned instead. - * - * Usage examples, - * - * ~~~ - * // $array = array('type' => 'A', 'options' => array(1, 2)); - * // working with array - * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); - * // $array content - * // $array = array('options' => array(1, 2)); - * ~~~ - * - * @param array $array the array to extract value from - * @param string $key key name of the array element - * @param mixed $default the default value to be returned if the specified key does not exist - * @return mixed|null the value of the element if found, default value otherwise - */ - public static function remove(&$array, $key, $default = null) - { - if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { - $value = $array[$key]; - unset($array[$key]); - return $value; - } - return $default; - } - - /** - * Indexes an array according to a specified key. - * The input array should be multidimensional or an array of objects. - * - * The key can be a key name of the sub-array, a property name of object, or an anonymous - * function which returns the key value given an array element. - * - * If a key value is null, the corresponding array element will be discarded and not put in the result. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); - * $result = ArrayHelper::index($array, 'id'); - * // the result is: - * // array( - * // '123' => array('id' => '123', 'data' => 'abc'), - * // '345' => array('id' => '345', 'data' => 'def'), - * // ) - * - * // using anonymous function - * $result = ArrayHelper::index($array, function ($element) { - * return $element['id']; - * }); - * ~~~ - * - * @param array $array the array that needs to be indexed - * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array - * @return array the indexed array - */ - public static function index($array, $key) - { - $result = array(); - foreach ($array as $element) { - $value = static::getValue($element, $key); - $result[$value] = $element; - } - return $result; - } - - /** - * Returns the values of a specified column in an array. - * The input array should be multidimensional or an array of objects. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'data' => 'abc'), - * array('id' => '345', 'data' => 'def'), - * ); - * $result = ArrayHelper::getColumn($array, 'id'); - * // the result is: array( '123', '345') - * - * // using anonymous function - * $result = ArrayHelper::getColumn($array, function ($element) { - * return $element['id']; - * }); - * ~~~ - * - * @param array $array - * @param string|\Closure $name - * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array - * will be re-indexed with integers. - * @return array the list of column values - */ - public static function getColumn($array, $name, $keepKeys = true) - { - $result = array(); - if ($keepKeys) { - foreach ($array as $k => $element) { - $result[$k] = static::getValue($element, $name); - } - } else { - foreach ($array as $element) { - $result[] = static::getValue($element, $name); - } - } - - return $result; - } - - /** - * Builds a map (key-value pairs) from a multidimensional array or an array of objects. - * The `$from` and `$to` parameters specify the key names or property names to set up the map. - * Optionally, one can further group the map according to a grouping field `$group`. - * - * For example, - * - * ~~~ - * $array = array( - * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), - * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), - * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), - * ); - * - * $result = ArrayHelper::map($array, 'id', 'name'); - * // the result is: - * // array( - * // '123' => 'aaa', - * // '124' => 'bbb', - * // '345' => 'ccc', - * // ) - * - * $result = ArrayHelper::map($array, 'id', 'name', 'class'); - * // the result is: - * // array( - * // 'x' => array( - * // '123' => 'aaa', - * // '124' => 'bbb', - * // ), - * // 'y' => array( - * // '345' => 'ccc', - * // ), - * // ) - * ~~~ - * - * @param array $array - * @param string|\Closure $from - * @param string|\Closure $to - * @param string|\Closure $group - * @return array - */ - public static function map($array, $from, $to, $group = null) - { - $result = array(); - foreach ($array as $element) { - $key = static::getValue($element, $from); - $value = static::getValue($element, $to); - if ($group !== null) { - $result[static::getValue($element, $group)][$key] = $value; - } else { - $result[$key] = $value; - } - } - return $result; - } - - /** - * Sorts an array of objects or arrays (with the same structure) by one or several keys. - * @param array $array the array to be sorted. The array will be modified after calling this method. - * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array - * elements, a property name of the objects, or an anonymous function returning the values for comparison - * purpose. The anonymous function signature should be: `function($item)`. - * To sort by multiple keys, provide an array of keys here. - * @param boolean|array $descending whether to sort in descending or ascending order. When - * sorting by multiple keys with different descending orders, use an array of descending flags. - * @param integer|array $sortFlag the PHP sort flag. Valid values include - * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`. - * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php) - * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags. - * @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter - * is used only when `$sortFlag` is `SORT_STRING`. - * When sorting by multiple keys with different case sensitivities, use an array of boolean values. - * @throws InvalidParamException if the $descending or $sortFlag parameters do not have - * correct number of elements as that of $key. - */ - public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true) - { - $keys = is_array($key) ? $key : array($key); - if (empty($keys) || empty($array)) { - return; - } - $n = count($keys); - if (is_scalar($descending)) { - $descending = array_fill(0, $n, $descending); - } elseif (count($descending) !== $n) { - throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); - } - if (is_scalar($sortFlag)) { - $sortFlag = array_fill(0, $n, $sortFlag); - } elseif (count($sortFlag) !== $n) { - throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.'); - } - if (is_scalar($caseSensitive)) { - $caseSensitive = array_fill(0, $n, $caseSensitive); - } elseif (count($caseSensitive) !== $n) { - throw new InvalidParamException('The length of $caseSensitive parameter must be the same as that of $keys.'); - } - $args = array(); - foreach ($keys as $i => $key) { - $flag = $sortFlag[$i]; - $cs = $caseSensitive[$i]; - if (!$cs && ($flag === SORT_STRING)) { - if (defined('SORT_FLAG_CASE')) { - $flag = $flag | SORT_FLAG_CASE; - $args[] = static::getColumn($array, $key); - } else { - $column = array(); - foreach (static::getColumn($array, $key) as $k => $value) { - $column[$k] = mb_strtolower($value); - } - $args[] = $column; - } - } else { - $args[] = static::getColumn($array, $key); - } - $args[] = $descending[$i] ? SORT_DESC : SORT_ASC; - $args[] = $flag; - } - $args[] = &$array; - call_user_func_array('array_multisort', $args); - } - - /** - * Encodes special characters in an array of strings into HTML entities. - * Both the array keys and values will be encoded. - * If a value is an array, this method will also encode it recursively. - * @param array $data data to be encoded - * @param boolean $valuesOnly whether to encode array values only. If false, - * both the array keys and array values will be encoded. - * @param string $charset the charset that the data is using. If not set, - * [[\yii\base\Application::charset]] will be used. - * @return array the encoded data - * @see http://www.php.net/manual/en/function.htmlspecialchars.php - */ - public static function htmlEncode($data, $valuesOnly = true, $charset = null) - { - if ($charset === null) { - $charset = Yii::$app->charset; - } - $d = array(); - foreach ($data as $key => $value) { - if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars($key, ENT_QUOTES, $charset); - } - if (is_string($value)) { - $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); - } elseif (is_array($value)) { - $d[$key] = static::htmlEncode($value, $charset); - } - } - return $d; - } - - /** - * Decodes HTML entities into the corresponding characters in an array of strings. - * Both the array keys and values will be decoded. - * If a value is an array, this method will also decode it recursively. - * @param array $data data to be decoded - * @param boolean $valuesOnly whether to decode array values only. If false, - * both the array keys and array values will be decoded. - * @return array the decoded data - * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php - */ - public static function htmlDecode($data, $valuesOnly = true) - { - $d = array(); - foreach ($data as $key => $value) { - if (!$valuesOnly && is_string($key)) { - $key = htmlspecialchars_decode($key, ENT_QUOTES); - } - if (is_string($value)) { - $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); - } elseif (is_array($value)) { - $d[$key] = static::htmlDecode($value); - } - } - return $d; - } -} diff --git a/framework/yii/helpers/AbstractConsole.php b/framework/yii/helpers/AbstractConsole.php deleted file mode 100644 index 8131aae..0000000 --- a/framework/yii/helpers/AbstractConsole.php +++ /dev/null @@ -1,835 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractConsole -{ - const FG_BLACK = 30; - const FG_RED = 31; - const FG_GREEN = 32; - const FG_YELLOW = 33; - const FG_BLUE = 34; - const FG_PURPLE = 35; - const FG_CYAN = 36; - const FG_GREY = 37; - - const BG_BLACK = 40; - const BG_RED = 41; - const BG_GREEN = 42; - const BG_YELLOW = 43; - const BG_BLUE = 44; - const BG_PURPLE = 45; - const BG_CYAN = 46; - const BG_GREY = 47; - - const RESET = 0; - const NORMAL = 0; - const BOLD = 1; - const ITALIC = 3; - const UNDERLINE = 4; - const BLINK = 5; - const NEGATIVE = 7; - const CONCEALED = 8; - const CROSSED_OUT = 9; - const FRAMED = 51; - const ENCIRCLED = 52; - const OVERLINED = 53; - - /** - * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $rows number of rows the cursor should be moved up - */ - public static function moveCursorUp($rows = 1) - { - echo "\033[" . (int)$rows . 'A'; - } - - /** - * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $rows number of rows the cursor should be moved down - */ - public static function moveCursorDown($rows = 1) - { - echo "\033[" . (int)$rows . 'B'; - } - - /** - * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $steps number of steps the cursor should be moved forward - */ - public static function moveCursorForward($steps = 1) - { - echo "\033[" . (int)$steps . 'C'; - } - - /** - * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. - * If the cursor is already at the edge of the screen, this has no effect. - * @param integer $steps number of steps the cursor should be moved backward - */ - public static function moveCursorBackward($steps = 1) - { - echo "\033[" . (int)$steps . 'D'; - } - - /** - * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. - * @param integer $lines number of lines the cursor should be moved down - */ - public static function moveCursorNextLine($lines = 1) - { - echo "\033[" . (int)$lines . 'E'; - } - - /** - * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. - * @param integer $lines number of lines the cursor should be moved up - */ - public static function moveCursorPrevLine($lines = 1) - { - echo "\033[" . (int)$lines . 'F'; - } - - /** - * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. - * @param integer $column 1-based column number, 1 is the left edge of the screen. - * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. - */ - public static function moveCursorTo($column, $row = null) - { - if ($row === null) { - echo "\033[" . (int)$column . 'G'; - } else { - echo "\033[" . (int)$row . ';' . (int)$column . 'H'; - } - } - - /** - * Scrolls whole page up by sending ANSI control code SU to the terminal. - * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. - * @param int $lines number of lines to scroll up - */ - public static function scrollUp($lines = 1) - { - echo "\033[" . (int)$lines . "S"; - } - - /** - * Scrolls whole page down by sending ANSI control code SD to the terminal. - * New lines are added at the top. This is not supported by ANSI.SYS used in windows. - * @param int $lines number of lines to scroll down - */ - public static function scrollDown($lines = 1) - { - echo "\033[" . (int)$lines . "T"; - } - - /** - * Saves the current cursor position by sending ANSI control code SCP to the terminal. - * Position can then be restored with {@link restoreCursorPosition}. - */ - public static function saveCursorPosition() - { - echo "\033[s"; - } - - /** - * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. - */ - public static function restoreCursorPosition() - { - echo "\033[u"; - } - - /** - * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. - * Use {@link showCursor} to bring it back. - * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. - */ - public static function hideCursor() - { - echo "\033[?25l"; - } - - /** - * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. - */ - public static function showCursor() - { - echo "\033[?25h"; - } - - /** - * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. - * Cursor position will not be changed. - * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. - */ - public static function clearScreen() - { - echo "\033[2J"; - } - - /** - * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. - * Cursor position will not be changed. - */ - public static function clearScreenBeforeCursor() - { - echo "\033[1J"; - } - - /** - * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. - * Cursor position will not be changed. - */ - public static function clearScreenAfterCursor() - { - echo "\033[0J"; - } - - /** - * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLine() - { - echo "\033[2K"; - } - - /** - * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLineBeforeCursor() - { - echo "\033[1K"; - } - - /** - * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. - * Cursor position will not be changed. - */ - public static function clearLineAfterCursor() - { - echo "\033[0K"; - } - - /** - * Returns the ANSI format code. - * - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @return string The ANSI format code according to the given formatting constants. - */ - public static function ansiFormatCode($format) - { - return "\033[" . implode(';', $format) . 'm'; - } - - /** - * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards. - * - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @see ansiFormatCode() - * @see ansiFormatEnd() - */ - public static function beginAnsiFormat($format) - { - echo "\033[" . implode(';', $format) . 'm'; - } - - /** - * Resets any ANSI format set by previous method [[ansiFormatBegin()]] - * Any output after this will have default text format. - * This is equal to calling - * - * ```php - * echo Console::ansiFormatCode(array(Console::RESET)) - * ``` - */ - public static function endAnsiFormat() - { - echo "\033[0m"; - } - - /** - * Will return a string formatted with the given ANSI style - * - * @param string $string the string to be formatted - * @param array $format An array containing formatting values. - * You can pass any of the FG_*, BG_* and TEXT_* constants - * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. - * @return string - */ - public static function ansiFormat($string, $format = array()) - { - $code = implode(';', $format); - return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; - } - - /** - * Returns the ansi format code for xterm foreground color. - * You can pass the return value of this to one of the formatting methods: - * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] - * - * @param integer $colorCode xterm color code - * @return string - * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors - */ - public static function xtermFgColor($colorCode) - { - return '38;5;' . $colorCode; - } - - /** - * Returns the ansi format code for xterm background color. - * You can pass the return value of this to one of the formatting methods: - * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] - * - * @param integer $colorCode xterm color code - * @return string - * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors - */ - public static function xtermBgColor($colorCode) - { - return '48;5;' . $colorCode; - } - - /** - * Strips ANSI control codes from a string - * - * @param string $string String to strip - * @return string - */ - public static function stripAnsiFormat($string) - { - return preg_replace('/\033\[[\d;?]*\w/', '', $string); - } - - /** - * Converts an ANSI formatted string to HTML - * @param $string - * @return mixed - */ - // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 - public static function ansiToHtml($string) - { - $tags = 0; - return preg_replace_callback( - '/\033\[[\d;]+m/', - function ($ansi) use (&$tags) { - $styleA = array(); - foreach (explode(';', $ansi) as $controlCode) { - switch ($controlCode) { - case self::FG_BLACK: - $style = array('color' => '#000000'); - break; - case self::FG_BLUE: - $style = array('color' => '#000078'); - break; - case self::FG_CYAN: - $style = array('color' => '#007878'); - break; - case self::FG_GREEN: - $style = array('color' => '#007800'); - break; - case self::FG_GREY: - $style = array('color' => '#787878'); - break; - case self::FG_PURPLE: - $style = array('color' => '#780078'); - break; - case self::FG_RED: - $style = array('color' => '#780000'); - break; - case self::FG_YELLOW: - $style = array('color' => '#787800'); - break; - case self::BG_BLACK: - $style = array('background-color' => '#000000'); - break; - case self::BG_BLUE: - $style = array('background-color' => '#000078'); - break; - case self::BG_CYAN: - $style = array('background-color' => '#007878'); - break; - case self::BG_GREEN: - $style = array('background-color' => '#007800'); - break; - case self::BG_GREY: - $style = array('background-color' => '#787878'); - break; - case self::BG_PURPLE: - $style = array('background-color' => '#780078'); - break; - case self::BG_RED: - $style = array('background-color' => '#780000'); - break; - case self::BG_YELLOW: - $style = array('background-color' => '#787800'); - break; - case self::BOLD: - $style = array('font-weight' => 'bold'); - break; - case self::ITALIC: - $style = array('font-style' => 'italic'); - break; - case self::UNDERLINE: - $style = array('text-decoration' => array('underline')); - break; - case self::OVERLINED: - $style = array('text-decoration' => array('overline')); - break; - case self::CROSSED_OUT: - $style = array('text-decoration' => array('line-through')); - break; - case self::BLINK: - $style = array('text-decoration' => array('blink')); - break; - case self::NEGATIVE: // ??? - case self::CONCEALED: - case self::ENCIRCLED: - case self::FRAMED: - // TODO allow resetting codes - break; - case 0: // ansi reset - $return = ''; - for ($n = $tags; $tags > 0; $tags--) { - $return .= ''; - } - return $return; - } - - $styleA = ArrayHelper::merge($styleA, $style); - } - $styleString[] = array(); - foreach ($styleA as $name => $content) { - if ($name === 'text-decoration') { - $content = implode(' ', $content); - } - $styleString[] = $name . ':' . $content; - } - $tags++; - return ' $value) { - echo " $key - $value\n"; - } - echo " ? - Show help\n"; - goto top; - } elseif (!in_array($input, array_keys($options))) { - goto top; - } - return $input; - } - - /** - * Displays and updates a simple progress bar on screen. - * - * @param integer $done the number of items that are completed - * @param integer $total the total value of items that are to be done - * @param integer $size the size of the status bar (optional) - * @see http://snipplr.com/view/29548/ - */ - public static function showProgress($done, $total, $size = 30) - { - static $start; - - // if we go over our bound, just ignore it - if ($done > $total) { - return; - } - - if (empty($start)) { - $start = time(); - } - - $now = time(); - - $percent = (double)($done / $total); - $bar = floor($percent * $size); - - $status = "\r["; - $status .= str_repeat("=", $bar); - if ($bar < $size) { - $status .= ">"; - $status .= str_repeat(" ", $size - $bar); - } else { - $status .= "="; - } - - $display = number_format($percent * 100, 0); - - $status .= "] $display% $done/$total"; - - $rate = ($now - $start) / $done; - $left = $total - $done; - $eta = round($rate * $left, 2); - - $elapsed = $now - $start; - - $status .= " remaining: " . number_format($eta) . " sec. elapsed: " . number_format($elapsed) . " sec."; - - static::stdout("$status "); - - flush(); - - // when done, send a newline - if ($done == $total) { - echo "\n"; - } - } -} diff --git a/framework/yii/helpers/AbstractFileHelper.php b/framework/yii/helpers/AbstractFileHelper.php deleted file mode 100644 index 5eab927..0000000 --- a/framework/yii/helpers/AbstractFileHelper.php +++ /dev/null @@ -1,329 +0,0 @@ - - * @author Alex Makarov - * @since 2.0 - */ -abstract class AbstractFileHelper -{ - /** - * Normalizes a file/directory path. - * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`, - * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux - * will be normalized as '/home/demo'. - * @param string $path the file/directory path to be normalized - * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`. - * @return string the normalized file/directory path - */ - public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) - { - return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds); - } - - /** - * Returns the localized version of a specified file. - * - * The searching is based on the specified language code. In particular, - * a file with the same name will be looked for under the subdirectory - * whose name is the same as the language code. For example, given the file "path/to/view.php" - * and language code "zh_CN", the localized file will be looked for as - * "path/to/zh_CN/view.php". If the file is not found, the original file - * will be returned. - * - * If the target and the source language codes are the same, - * the original file will be returned. - * - * @param string $file the original file - * @param string $language the target language that the file should be localized to. - * If not set, the value of [[\yii\base\Application::language]] will be used. - * @param string $sourceLanguage the language that the original file is in. - * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. - * @return string the matching localized file, or the original file if the localized version is not found. - * If the target and the source language codes are the same, the original file will be returned. - */ - public static function localize($file, $language = null, $sourceLanguage = null) - { - if ($language === null) { - $language = Yii::$app->language; - } - if ($sourceLanguage === null) { - $sourceLanguage = Yii::$app->sourceLanguage; - } - if ($language === $sourceLanguage) { - return $file; - } - $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file); - return is_file($desiredFile) ? $desiredFile : $file; - } - - /** - * Determines the MIME type of the specified file. - * This method will first try to determine the MIME type based on - * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will - * fall back to [[getMimeTypeByExtension()]]. - * @param string $file the file name. - * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`. - * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php). - * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case - * `finfo_open()` cannot determine it. - * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. - */ - public static function getMimeType($file, $magicFile = null, $checkExtension = true) - { - if (function_exists('finfo_open')) { - $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); - if ($info) { - $result = finfo_file($info, $file); - finfo_close($info); - if ($result !== false) { - return $result; - } - } - } - - return $checkExtension ? static::getMimeTypeByExtension($file) : null; - } - - /** - * Determines the MIME type based on the extension name of the specified file. - * This method will use a local map between extension names and MIME types. - * @param string $file the file name. - * @param string $magicFile the path of the file that contains all available MIME type information. - * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used. - * @return string the MIME type. Null is returned if the MIME type cannot be determined. - */ - public static function getMimeTypeByExtension($file, $magicFile = null) - { - static $mimeTypes = array(); - if ($magicFile === null) { - $magicFile = __DIR__ . '/mimeTypes.php'; - } - if (!isset($mimeTypes[$magicFile])) { - $mimeTypes[$magicFile] = require($magicFile); - } - if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { - $ext = strtolower($ext); - if (isset($mimeTypes[$magicFile][$ext])) { - return $mimeTypes[$magicFile][$ext]; - } - } - return null; - } - - /** - * Copies a whole directory as another one. - * The files and sub-directories will also be copied over. - * @param string $src the source directory - * @param string $dst the destination directory - * @param array $options options for directory copy. Valid options are: - * - * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775. - * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. - * - filter: callback, a PHP callback that is called for each directory or file. - * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. - * The callback can return one of the following values: - * - * * true: the directory or file will be copied (the "only" and "except" options will be ignored) - * * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored) - * * null: the "only" and "except" options will determine whether the directory or file should be copied - * - * - only: array, list of patterns that the file paths should match if they want to be copied. - * A path matches a pattern if it contains the pattern string at its end. - * For example, '.php' matches all file paths ending with '.php'. - * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. - * If a file path matches a pattern in both "only" and "except", it will NOT be copied. - * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. - * A path matches a pattern if it contains the pattern string at its end. - * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' - * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; - * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches - * both '/' and '\' in the paths. - * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. - * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. - * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or - * file copied from, while `$to` is the copy target. - */ - public static function copyDirectory($src, $dst, $options = array()) - { - if (!is_dir($dst)) { - static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); - } - - $handle = opendir($src); - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $from = $src . DIRECTORY_SEPARATOR . $file; - $to = $dst . DIRECTORY_SEPARATOR . $file; - if (static::filterPath($from, $options)) { - if (is_file($from)) { - copy($from, $to); - if (isset($options['fileMode'])) { - @chmod($to, $options['fileMode']); - } - } else { - static::copyDirectory($from, $to, $options); - } - if (isset($options['afterCopy'])) { - call_user_func($options['afterCopy'], $from, $to); - } - } - } - closedir($handle); - } - - /** - * Removes a directory (and all its content) recursively. - * @param string $dir the directory to be deleted recursively. - */ - public static function removeDirectory($dir) - { - if (!is_dir($dir) || !($handle = opendir($dir))) { - return; - } - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $path = $dir . DIRECTORY_SEPARATOR . $file; - if (is_file($path)) { - unlink($path); - } else { - static::removeDirectory($path); - } - } - closedir($handle); - rmdir($dir); - } - - /** - * Returns the files found under the specified directory and subdirectories. - * @param string $dir the directory under which the files will be looked for. - * @param array $options options for file searching. Valid options are: - * - * - filter: callback, a PHP callback that is called for each directory or file. - * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. - * The callback can return one of the following values: - * - * * true: the directory or file will be returned (the "only" and "except" options will be ignored) - * * false: the directory or file will NOT be returned (the "only" and "except" options will be ignored) - * * null: the "only" and "except" options will determine whether the directory or file should be returned - * - * - only: array, list of patterns that the file paths should match if they want to be returned. - * A path matches a pattern if it contains the pattern string at its end. - * For example, '.php' matches all file paths ending with '.php'. - * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. - * If a file path matches a pattern in both "only" and "except", it will NOT be returned. - * - except: array, list of patterns that the file paths or directory paths should match if they want to be excluded from the result. - * A path matches a pattern if it contains the pattern string at its end. - * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' - * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; - * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches - * both '/' and '\' in the paths. - * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. - * @return array files found under the directory. The file list is sorted. - */ - public static function findFiles($dir, $options = array()) - { - $list = array(); - $handle = opendir($dir); - while (($file = readdir($handle)) !== false) { - if ($file === '.' || $file === '..') { - continue; - } - $path = $dir . DIRECTORY_SEPARATOR . $file; - if (static::filterPath($path, $options)) { - if (is_file($path)) { - $list[] = $path; - } elseif (!isset($options['recursive']) || $options['recursive']) { - $list = array_merge($list, static::findFiles($path, $options)); - } - } - } - closedir($handle); - return $list; - } - - /** - * Checks if the given file path satisfies the filtering options. - * @param string $path the path of the file or directory to be checked - * @param array $options the filtering options. See [[findFiles()]] for explanations of - * the supported options. - * @return boolean whether the file or directory satisfies the filtering options. - */ - public static function filterPath($path, $options) - { - if (isset($options['filter'])) { - $result = call_user_func($options['filter'], $path); - if (is_bool($result)) { - return $result; - } - } - $path = str_replace('\\', '/', $path); - if ($isDir = is_dir($path)) { - $path .= '/'; - } - $n = StringHelper::strlen($path); - - if (!empty($options['except'])) { - foreach ($options['except'] as $name) { - if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { - return false; - } - } - } - - if (!$isDir && !empty($options['only'])) { - foreach ($options['only'] as $name) { - if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { - return true; - } - } - return false; - } - return true; - } - - /** - * Creates a new directory. - * - * This method is similar to the PHP `mkdir()` function except that - * it uses `chmod()` to set the permission of the created directory - * in order to avoid the impact of the `umask` setting. - * - * @param string $path path of the directory to be created. - * @param integer $mode the permission to be set for the created directory. - * @param boolean $recursive whether to create parent directories if they do not exist. - * @return boolean whether the directory is created successfully - */ - public static function createDirectory($path, $mode = 0775, $recursive = true) - { - if (is_dir($path)) { - return true; - } - $parentDir = dirname($path); - if ($recursive && !is_dir($parentDir)) { - static::createDirectory($parentDir, $mode, true); - } - $result = mkdir($path, $mode); - chmod($path, $mode); - return $result; - } -} diff --git a/framework/yii/helpers/AbstractHtml.php b/framework/yii/helpers/AbstractHtml.php deleted file mode 100644 index 37e926c..0000000 --- a/framework/yii/helpers/AbstractHtml.php +++ /dev/null @@ -1,1599 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractHtml -{ - /** - * @var array list of void elements (element name => 1) - * @see http://www.w3.org/TR/html-markup/syntax.html#void-element - */ - public static $voidElements = array( - 'area' => 1, - 'base' => 1, - 'br' => 1, - 'col' => 1, - 'command' => 1, - 'embed' => 1, - 'hr' => 1, - 'img' => 1, - 'input' => 1, - 'keygen' => 1, - 'link' => 1, - 'meta' => 1, - 'param' => 1, - 'source' => 1, - 'track' => 1, - 'wbr' => 1, - ); - /** - * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes - * that are rendered by [[renderTagAttributes()]]. - */ - public static $attributeOrder = array( - 'type', - 'id', - 'class', - 'name', - 'value', - - 'href', - 'src', - 'action', - 'method', - - 'selected', - 'checked', - 'readonly', - 'disabled', - 'multiple', - - 'size', - 'maxlength', - 'width', - 'height', - 'rows', - 'cols', - - 'alt', - 'title', - 'rel', - 'media', - ); - - /** - * Encodes special characters into HTML entities. - * The [[yii\base\Application::charset|application charset]] will be used for encoding. - * @param string $content the content to be encoded - * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false, - * HTML entities in `$content` will not be further encoded. - * @return string the encoded content - * @see decode - * @see http://www.php.net/manual/en/function.htmlspecialchars.php - */ - public static function encode($content, $doubleEncode = true) - { - return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset, $doubleEncode); - } - - /** - * Decodes special HTML entities back to the corresponding characters. - * This is the opposite of [[encode()]]. - * @param string $content the content to be decoded - * @return string the decoded content - * @see encode - * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php - */ - public static function decode($content) - { - return htmlspecialchars_decode($content, ENT_QUOTES); - } - - /** - * Generates a complete HTML tag. - * @param string $name the tag name - * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. - * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated HTML tag - * @see beginTag - * @see endTag - */ - public static function tag($name, $content = '', $options = array()) - { - $html = "<$name" . static::renderTagAttributes($options) . '>'; - return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content"; - } - - /** - * Generates a start tag. - * @param string $name the tag name - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated start tag - * @see endTag - * @see tag - */ - public static function beginTag($name, $options = array()) - { - return "<$name" . static::renderTagAttributes($options) . '>'; - } - - /** - * Generates an end tag. - * @param string $name the tag name - * @return string the generated end tag - * @see beginTag - * @see tag - */ - public static function endTag($name) - { - return ""; - } - - /** - * Generates a style tag. - * @param string $content the style content - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "text/css" will be used. - * @return string the generated style tag - */ - public static function style($content, $options = array()) - { - return static::tag('style', $content, $options); - } - - /** - * Generates a script tag. - * @param string $content the script content - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. - * @return string the generated script tag - */ - public static function script($content, $options = array()) - { - return static::tag('script', $content, $options); - } - - /** - * Generates a link tag that refers to an external CSS file. - * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated link tag - * @see url - */ - public static function cssFile($url, $options = array()) - { - $options['rel'] = 'stylesheet'; - $options['href'] = static::url($url); - return static::tag('link', '', $options); - } - - /** - * Generates a script tag that refers to an external JavaScript file. - * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated script tag - * @see url - */ - public static function jsFile($url, $options = array()) - { - $options['src'] = static::url($url); - return static::tag('script', '', $options); - } - - /** - * Generates a form start tag. - * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. - * @param string $method the form submission method, such as "post", "get", "put", "delete" (case-insensitive). - * Since most browsers only support "post" and "get", if other methods are given, they will - * be simulated using "post", and a hidden input will be added which contains the actual method type. - * See [[\yii\web\Request::restVar]] for more details. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated form start tag. - * @see endForm - */ - public static function beginForm($action = '', $method = 'post', $options = array()) - { - $action = static::url($action); - - $hiddenInputs = array(); - - $request = Yii::$app->getRequest(); - if ($request instanceof Request) { - if (strcasecmp($method, 'get') && strcasecmp($method, 'post')) { - // simulate PUT, DELETE, etc. via POST - $hiddenInputs[] = static::hiddenInput($request->restVar, $method); - $method = 'post'; - } - if ($request->enableCsrfValidation) { - $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); - } - } - - if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { - // query parameters in the action are ignored for GET method - // we use hidden fields to add them back - foreach (explode('&', substr($action, $pos + 1)) as $pair) { - if (($pos1 = strpos($pair, '=')) !== false) { - $hiddenInputs[] = static::hiddenInput( - urldecode(substr($pair, 0, $pos1)), - urldecode(substr($pair, $pos1 + 1)) - ); - } else { - $hiddenInputs[] = static::hiddenInput(urldecode($pair), ''); - } - } - $action = substr($action, 0, $pos); - } - - $options['action'] = $action; - $options['method'] = $method; - $form = static::beginTag('form', $options); - if (!empty($hiddenInputs)) { - $form .= "\n" . implode("\n", $hiddenInputs); - } - - return $form; - } - - /** - * Generates a form end tag. - * @return string the generated tag - * @see beginForm - */ - public static function endForm() - { - return ''; - } - - /** - * Generates a hyperlink tag. - * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is coming from end users, you should consider [[encode()]] - * it to prevent XSS attacks. - * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]] - * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute - * will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated hyperlink - * @see url - */ - public static function a($text, $url = null, $options = array()) - { - if ($url !== null) { - $options['href'] = static::url($url); - } - return static::tag('a', $text, $options); - } - - /** - * Generates a mailto hyperlink. - * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is coming from end users, you should consider [[encode()]] - * it to prevent XSS attacks. - * @param string $email email address. If this is null, the first parameter (link body) will be treated - * as the email address and used. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated mailto link - */ - public static function mailto($text, $email = null, $options = array()) - { - $options['href'] = 'mailto:' . ($email === null ? $text : $email); - return static::tag('a', $text, $options); - } - - /** - * Generates an image tag. - * @param string $src the image URL. This parameter will be processed by [[url()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated image tag - */ - public static function img($src, $options = array()) - { - $options['src'] = static::url($src); - if (!isset($options['alt'])) { - $options['alt'] = ''; - } - return static::tag('img', '', $options); - } - - /** - * Generates a label tag. - * @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is is coming from end users, you should [[encode()]] - * it to prevent XSS attacks. - * @param string $for the ID of the HTML element that this label is associated with. - * If this is null, the "for" attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated label tag - */ - public static function label($content, $for = null, $options = array()) - { - $options['for'] = $for; - return static::tag('label', $content, $options); - } - - /** - * Generates a button tag. - * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. - * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, - * you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function button($content = 'Button', $options = array()) - { - return static::tag('button', $content, $options); - } - - /** - * Generates a submit button tag. - * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. - * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, - * you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated submit button tag - */ - public static function submitButton($content = 'Submit', $options = array()) - { - $options['type'] = 'submit'; - return static::button($content, $options); - } - - /** - * Generates a reset button tag. - * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. - * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, - * you should consider [[encode()]] it to prevent XSS attacks. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated reset button tag - */ - public static function resetButton($content = 'Reset', $options = array()) - { - $options['type'] = 'reset'; - return static::button($content, $options); - } - - /** - * Generates an input type of the given type. - * @param string $type the type attribute. - * @param string $name the name attribute. If it is null, the name attribute will not be generated. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated input tag - */ - public static function input($type, $name = null, $value = null, $options = array()) - { - $options['type'] = $type; - $options['name'] = $name; - $options['value'] = $value === null ? null : (string)$value; - return static::tag('input', '', $options); - } - - /** - * Generates an input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function buttonInput($label = 'Button', $options = array()) - { - $options['type'] = 'button'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a submit input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function submitInput($label = 'Submit', $options = array()) - { - $options['type'] = 'submit'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a reset input button. - * @param string $label the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. - * Attributes whose value is null will be ignored and not put in the tag returned. - * @return string the generated button tag - */ - public static function resetInput($label = 'Reset', $options = array()) - { - $options['type'] = 'reset'; - $options['value'] = $label; - return static::tag('input', '', $options); - } - - /** - * Generates a text input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function textInput($name, $value = null, $options = array()) - { - return static::input('text', $name, $value, $options); - } - - /** - * Generates a hidden input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function hiddenInput($name, $value = null, $options = array()) - { - return static::input('hidden', $name, $value, $options); - } - - /** - * Generates a password input field. - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function passwordInput($name, $value = null, $options = array()) - { - return static::input('password', $name, $value, $options); - } - - /** - * Generates a file input field. - * To use a file input field, you should set the enclosing form's "enctype" attribute to - * be "multipart/form-data". After the form is submitted, the uploaded file information - * can be obtained via $_FILES[$name] (see PHP documentation). - * @param string $name the name attribute. - * @param string $value the value attribute. If it is null, the value attribute will not be generated. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated button tag - */ - public static function fileInput($name, $value = null, $options = array()) - { - return static::input('file', $name, $value, $options); - } - - /** - * Generates a text area input. - * @param string $name the input name - * @param string $value the input value. Note that it will be encoded using [[encode()]]. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * @return string the generated text area tag - */ - public static function textarea($name, $value = '', $options = array()) - { - $options['name'] = $name; - return static::tag('textarea', static::encode($value), $options); - } - - /** - * Generates a radio button input. - * @param string $name the name attribute. - * @param boolean $checked whether the radio button should be checked. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute - * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * When this option is specified, the radio button will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated radio button tag - */ - public static function radio($name, $checked = false, $options = array()) - { - $options['checked'] = (boolean)$checked; - $value = array_key_exists('value', $options) ? $options['value'] : '1'; - if (isset($options['uncheck'])) { - // add a hidden field so that if the radio button is not selected, it still submits a value - $hidden = static::hiddenInput($name, $options['uncheck']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - if (isset($options['label'])) { - $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); - unset($options['label'], $options['labelOptions']); - $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . static::tag('div', $content, array('class' => 'radio')); - } else { - return $hidden . static::input('radio', $name, $value, $options); - } - } - - /** - * Generates a checkbox input. - * @param string $name the name attribute. - * @param boolean $checked whether the checkbox should be checked. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute - * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, - * the value of this attribute will still be submitted to the server via the hidden input. - * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * When this option is specified, the checkbox will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated checkbox tag - */ - public static function checkbox($name, $checked = false, $options = array()) - { - $options['checked'] = (boolean)$checked; - $value = array_key_exists('value', $options) ? $options['value'] : '1'; - if (isset($options['uncheck'])) { - // add a hidden field so that if the checkbox is not selected, it still submits a value - $hidden = static::hiddenInput($name, $options['uncheck']); - unset($options['uncheck']); - } else { - $hidden = ''; - } - if (isset($options['label'])) { - $label = $options['label']; - $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); - unset($options['label'], $options['labelOptions']); - $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); - return $hidden . static::tag('div', $content, array('class' => 'checkbox')); - } else { - return $hidden . static::input('checkbox', $name, $value, $options); - } - } - - /** - * Generates a drop-down list. - * @param string $name the input name - * @param string $selection the selected value - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated drop-down list tag - */ - public static function dropDownList($name, $selection = null, $items = array(), $options = array()) - { - $options['name'] = $name; - $selectOptions = static::renderSelectOptions($selection, $items, $options); - return static::tag('select', "\n" . $selectOptions . "\n", $options); - } - - /** - * Generates a list box. - * @param string $name the input name - * @param string|array $selection the selected value(s) - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - unselect: string, the value that will be submitted when no option is selected. - * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple - * mode, we can still obtain the posted unselect value. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated list box tag - */ - public static function listBox($name, $selection = null, $items = array(), $options = array()) - { - if (!isset($options['size'])) { - $options['size'] = 4; - } - if (!empty($options['multiple']) && substr($name, -2) !== '[]') { - $name .= '[]'; - } - $options['name'] = $name; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - if (substr($name, -2) === '[]') { - $name = substr($name, 0, -2); - } - $hidden = static::hiddenInput($name, $options['unselect']); - unset($options['unselect']); - } else { - $hidden = ''; - } - $selectOptions = static::renderSelectOptions($selection, $items, $options); - return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); - } - - /** - * Generates a list of checkboxes. - * A checkbox list allows multiple selection, like [[listBox()]]. - * As a result, the corresponding submitted value is an array. - * @param string $name the name attribute of each checkbox. - * @param string|array $selection the selected value(s). - * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. - * @param array $options options (name => config) for the checkbox list container tag. - * The following options are specially handled: - * - * - tag: string, the tag name of the container element. - * - unselect: string, the value that should be submitted when none of the checkboxes is selected. - * By setting this option, a hidden input will be generated. - * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. - * This option is ignored if `item` option is set. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the checkbox in the whole list; $label - * is the label for the checkbox; and $name, $value and $checked represent the name, - * value and the checked status of the checkbox input, respectively. - * @return string the generated checkbox list - */ - public static function checkboxList($name, $selection = null, $items = array(), $options = array()) - { - if (substr($name, -2) !== '[]') { - $name .= '[]'; - } - - $formatter = isset($options['item']) ? $options['item'] : null; - $encode = !isset($options['encode']) || $options['encode']; - $lines = array(); - $index = 0; - foreach ($items as $value => $label) { - $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); - if ($formatter !== null) { - $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); - } else { - $lines[] = static::checkbox($name, $checked, array( - 'value' => $value, - 'label' => $encode ? static::encode($label) : $label, - )); - } - $index++; - } - - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; - $hidden = static::hiddenInput($name2, $options['unselect']); - } else { - $hidden = ''; - } - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); - - return $hidden . static::tag($tag, implode($separator, $lines), $options); - } - - /** - * Generates a list of radio buttons. - * A radio button list is like a checkbox list, except that it only allows single selection. - * @param string $name the name attribute of each radio button. - * @param string|array $selection the selected value(s). - * @param array $items the data item used to generate the radio buttons. - * The array keys are the labels, while the array values are the corresponding radio button values. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - unselect: string, the value that should be submitted when none of the radio buttons is selected. - * By setting this option, a hidden input will be generated. - * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. - * This option is ignored if `item` option is set. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the radio button in the whole list; $label - * is the label for the radio button; and $name, $value and $checked represent the name, - * value and the checked status of the radio button input, respectively. - * @return string the generated radio button list - */ - public static function radioList($name, $selection = null, $items = array(), $options = array()) - { - $encode = !isset($options['encode']) || $options['encode']; - $formatter = isset($options['item']) ? $options['item'] : null; - $lines = array(); - $index = 0; - foreach ($items as $value => $label) { - $checked = $selection !== null && - (!is_array($selection) && !strcmp($value, $selection) - || is_array($selection) && in_array($value, $selection)); - if ($formatter !== null) { - $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); - } else { - $lines[] = static::radio($name, $checked, array( - 'value' => $value, - 'label' => $encode ? static::encode($label) : $label, - )); - } - $index++; - } - - $separator = isset($options['separator']) ? $options['separator'] : "\n"; - if (isset($options['unselect'])) { - // add a hidden field so that if the list box has no option being selected, it still submits a value - $hidden = static::hiddenInput($name, $options['unselect']); - } else { - $hidden = ''; - } - - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); - - return $hidden . static::tag($tag, implode($separator, $lines), $options); - } - - /** - * Generates an unordered list. - * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. - * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - encode: boolean, whether to HTML-encode the items. Defaults to true. - * This option is ignored if the `item` option is specified. - * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. - * - item: callable, a callback that is used to generate each individual list item. - * The signature of this callback must be: - * - * ~~~ - * function ($item, $index) - * ~~~ - * - * where $index is the array key corresponding to `$item` in `$items`. The callback should return - * the whole list item tag. - * - * @return string the generated unordered list. An empty string is returned if `$items` is empty. - */ - public static function ul($items, $options = array()) - { - if (empty($items)) { - return ''; - } - $tag = isset($options['tag']) ? $options['tag'] : 'ul'; - $encode = !isset($options['encode']) || $options['encode']; - $formatter = isset($options['item']) ? $options['item'] : null; - $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : array(); - unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); - $results = array(); - foreach ($items as $index => $item) { - if ($formatter !== null) { - $results[] = call_user_func($formatter, $item, $index); - } else { - $results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions); - } - } - return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options); - } - - /** - * Generates an ordered list. - * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. - * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. - * @param array $options options (name => config) for the radio button list. The following options are supported: - * - * - encode: boolean, whether to HTML-encode the items. Defaults to true. - * This option is ignored if the `item` option is specified. - * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. - * - item: callable, a callback that is used to generate each individual list item. - * The signature of this callback must be: - * - * ~~~ - * function ($item, $index) - * ~~~ - * - * where $index is the array key corresponding to `$item` in `$items`. The callback should return - * the whole list item tag. - * - * @return string the generated ordered list. An empty string is returned if `$items` is empty. - */ - public static function ol($items, $options = array()) - { - $options['tag'] = 'ol'; - return static::ul($items, $options); - } - - /** - * Generates a label tag for the given model attribute. - * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * If a value is null, the corresponding attribute will not be rendered. - * The following options are specially handled: - * - * - label: this specifies the label to be displayed. Note that this will NOT be [[encoded()]]. - * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display - * (after encoding). - * - * @return string the generated label tag - */ - public static function activeLabel($model, $attribute, $options = array()) - { - $attribute = static::getAttributeName($attribute); - $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); - $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); - unset($options['label'], $options['for']); - return static::label($label, $for, $options); - } - - /** - * Generates a tag that contains the first validation error of the specified model attribute. - * Note that even if there is no validation error, this method will still return an empty error tag. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The values will be HTML-encoded - * using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * The following options are specially handled: - * - * - tag: this specifies the tag name. If not set, "div" will be used. - * - * @return string the generated label tag - */ - public static function error($model, $attribute, $options = array()) - { - $attribute = static::getAttributeName($attribute); - $error = $model->getFirstError($attribute); - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag']); - return Html::tag($tag, Html::encode($error), $options); - } - - /** - * Generates an input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param string $type the input type (e.g. 'text', 'password') - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeInput($type, $model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::input($type, $name, $value, $options); - } - - /** - * Generates a text input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeTextInput($model, $attribute, $options = array()) - { - return static::activeInput('text', $model, $attribute, $options); - } - - /** - * Generates a hidden input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeHiddenInput($model, $attribute, $options = array()) - { - return static::activeInput('hidden', $model, $attribute, $options); - } - - /** - * Generates a password input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activePasswordInput($model, $attribute, $options = array()) - { - return static::activeInput('password', $model, $attribute, $options); - } - - /** - * Generates a file input tag for the given model attribute. - * This method will generate the "name" and "value" tag attributes automatically for the model attribute - * unless they are explicitly specified in `$options`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated input tag - */ - public static function activeFileInput($model, $attribute, $options = array()) - { - // add a hidden field so that if a model only has a file field, we can - // still use isset($_POST[$modelClass]) to detect if the input is submitted - return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) - . static::activeInput('file', $model, $attribute, $options); - } - - /** - * Generates a textarea tag for the given model attribute. - * The model attribute value will be used as the content in the textarea. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. These will be rendered as - * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. - * @return string the generated textarea tag - */ - public static function activeTextarea($model, $attribute, $options = array()) - { - $name = static::getInputName($model, $attribute); - $value = static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::textarea($name, $value, $options); - } - - /** - * Generates a radio button tag for the given model attribute. - * This method will generate the "checked" tag attribute according to the model attribute value. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, - * it will take the default value '0'. This method will render a hidden input so that if the radio button - * is not checked and is submitted, the value of this attribute will still be submitted to the server - * via the hidden input. - * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * When this option is specified, the radio button will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated radio button tag - */ - public static function activeRadio($model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('uncheck', $options)) { - $options['uncheck'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::radio($name, $checked, $options); - } - - /** - * Generates a checkbox tag for the given model attribute. - * This method will generate the "checked" tag attribute according to the model attribute value. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, - * it will take the default value '0'. This method will render a hidden input so that if the radio button - * is not checked and is submitted, the value of this attribute will still be submitted to the server - * via the hidden input. - * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass - * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. - * When this option is specified, the checkbox will be enclosed by a label tag. - * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated checkbox tag - */ - public static function activeCheckbox($model, $attribute, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('uncheck', $options)) { - $options['uncheck'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::checkbox($name, $checked, $options); - } - - /** - * Generates a drop-down list for the given model attribute. - * The selection of the drop-down list is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated drop-down list tag - */ - public static function activeDropDownList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::dropDownList($name, $checked, $items, $options); - } - - /** - * Generates a list box. - * The selection of the list box is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: - * - * - prompt: string, a prompt text to be displayed as the first option; - * - options: array, the attributes for the select option tags. The array keys must be valid option values, - * and the array values are the extra attributes for the corresponding option tags. For example, - * - * ~~~ - * array( - * 'value1' => array('disabled' => true), - * 'value2' => array('label' => 'value 2'), - * ); - * ~~~ - * - * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', - * except that the array keys represent the optgroup labels specified in $items. - * - unselect: string, the value that will be submitted when no option is selected. - * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple - * mode, we can still obtain the posted unselect value. - * - * The rest of the options will be rendered as the attributes of the resulting tag. The values will - * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. - * - * @return string the generated list box tag - */ - public static function activeListBox($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::listBox($name, $checked, $items, $options); - } - - /** - * Generates a list of checkboxes. - * A checkbox list allows multiple selection, like [[listBox()]]. - * As a result, the corresponding submitted value is an array. - * The selection of the checkbox list is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the data item used to generate the checkboxes. - * The array keys are the labels, while the array values are the corresponding checkbox values. - * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the checkbox list. The following options are specially handled: - * - * - unselect: string, the value that should be submitted when none of the checkboxes is selected. - * By setting this option, a hidden input will be generated. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the checkbox in the whole list; $label - * is the label for the checkbox; and $name, $value and $checked represent the name, - * value and the checked status of the checkbox input. - * @return string the generated checkbox list - */ - public static function activeCheckboxList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::checkboxList($name, $checked, $items, $options); - } - - /** - * Generates a list of radio buttons. - * A radio button list is like a checkbox list, except that it only allows single selection. - * The selection of the radio buttons is taken from the value of the model attribute. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format - * about attribute expression. - * @param array $items the data item used to generate the radio buttons. - * The array keys are the labels, while the array values are the corresponding radio button values. - * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the radio button list. The following options are specially handled: - * - * - unselect: string, the value that should be submitted when none of the radio buttons is selected. - * By setting this option, a hidden input will be generated. - * - separator: string, the HTML code that separates items. - * - item: callable, a callback that can be used to customize the generation of the HTML code - * corresponding to a single item in $items. The signature of this callback must be: - * - * ~~~ - * function ($index, $label, $name, $checked, $value) - * ~~~ - * - * where $index is the zero-based index of the radio button in the whole list; $label - * is the label for the radio button; and $name, $value and $checked represent the name, - * value and the checked status of the radio button input. - * @return string the generated radio button list - */ - public static function activeRadioList($model, $attribute, $items, $options = array()) - { - $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); - $checked = static::getAttributeValue($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = static::getInputId($model, $attribute); - } - return static::radioList($name, $checked, $items, $options); - } - - /** - * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. - * @param string|array $selection the selected value(s). This can be either a string for single selection - * or an array for multiple selections. - * @param array $items the option data items. The array keys are option values, and the array values - * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). - * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. - * If you have a list of data models, you may convert them into the format described above using - * [[\yii\helpers\ArrayHelper::map()]]. - * - * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in - * the labels will also be HTML-encoded. - * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. - * This method will take out these elements, if any: "prompt", "options" and "groups". See more details - * in [[dropDownList()]] for the explanation of these elements. - * - * @return string the generated list options - */ - public static function renderSelectOptions($selection, $items, &$tagOptions = array()) - { - $lines = array(); - if (isset($tagOptions['prompt'])) { - $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); - $lines[] = static::tag('option', $prompt, array('value' => '')); - } - - $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); - $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); - unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); - - foreach ($items as $key => $value) { - if (is_array($value)) { - $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); - $groupAttrs['label'] = $key; - $attrs = array('options' => $options, 'groups' => $groups); - $content = static::renderSelectOptions($selection, $value, $attrs); - $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); - } else { - $attrs = isset($options[$key]) ? $options[$key] : array(); - $attrs['value'] = (string)$key; - $attrs['selected'] = $selection !== null && - (!is_array($selection) && !strcmp($key, $selection) - || is_array($selection) && in_array($key, $selection)); - $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs); - } - } - - return implode("\n", $lines); - } - - /** - * Renders the HTML tag attributes. - * Attributes whose values are of boolean type will be treated as - * [boolean attributes](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). - * And attributes whose values are null will not be rendered. - * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. - * @return string the rendering result. If the attributes are not empty, they will be rendered - * into a string with a leading white space (so that it can be directly appended to the tag name - * in a tag. If there is no attribute, an empty string will be returned. - */ - public static function renderTagAttributes($attributes) - { - if (count($attributes) > 1) { - $sorted = array(); - foreach (static::$attributeOrder as $name) { - if (isset($attributes[$name])) { - $sorted[$name] = $attributes[$name]; - } - } - $attributes = array_merge($sorted, $attributes); - } - - $html = ''; - foreach ($attributes as $name => $value) { - if (is_bool($value)) { - if ($value) { - $html .= " $name"; - } - } elseif ($value !== null) { - $html .= " $name=\"" . static::encode($value) . '"'; - } - } - return $html; - } - - /** - * Normalizes the input parameter to be a valid URL. - * - * If the input parameter - * - * - is an empty string: the currently requested URL will be returned; - * - is a non-empty string: it will first be processed by [[Yii::getAlias()]]. If the result - * is an absolute URL, it will be returned without any change further; Otherwise, the result - * will be prefixed with [[\yii\web\Request::baseUrl]] and returned. - * - is an array: the first array element is considered a route, while the rest of the name-value - * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. - * For example: `array('post/index', 'page' => 2)`, `array('index')`. - * In case there is no controller, [[\yii\web\UrlManager::createUrl()]] will be used. - * - * @param array|string $url the parameter to be used to generate a valid URL - * @return string the normalized URL - * @throws InvalidParamException if the parameter is invalid. - */ - public static function url($url) - { - if (is_array($url)) { - if (isset($url[0])) { - $route = $url[0]; - $params = array_splice($url, 1); - if (Yii::$app->controller instanceof \yii\web\Controller) { - return Yii::$app->controller->createUrl($route, $params); - } else { - return Yii::$app->getUrlManager()->createUrl($route, $params); - } - } else { - throw new InvalidParamException('The array specifying a URL must contain at least one element.'); - } - } elseif ($url === '') { - return Yii::$app->getRequest()->getUrl(); - } else { - $url = Yii::getAlias($url); - if ($url !== '' && ($url[0] === '/' || $url[0] === '#' || strpos($url, '://'))) { - return $url; - } else { - return Yii::$app->getRequest()->getBaseUrl() . '/' . $url; - } - } - } - - /** - * Adds a CSS class to the specified options. - * If the CSS class is already in the options, it will not be added again. - * @param array $options the options to be modified. - * @param string $class the CSS class to be added - */ - public static function addCssClass(&$options, $class) - { - if (isset($options['class'])) { - $classes = ' ' . $options['class'] . ' '; - if (($pos = strpos($classes, ' ' . $class . ' ')) === false) { - $options['class'] .= ' ' . $class; - } - } else { - $options['class'] = $class; - } - } - - /** - * Removes a CSS class from the specified options. - * @param array $options the options to be modified. - * @param string $class the CSS class to be removed - */ - public static function removeCssClass(&$options, $class) - { - if (isset($options['class'])) { - $classes = array_unique(preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY)); - if (($index = array_search($class, $classes)) !== false) { - unset($classes[$index]); - } - if (empty($classes)) { - unset($options['class']); - } else { - $options['class'] = implode(' ', $classes); - } - } - } - - /** - * Returns the real attribute name from the given attribute expression. - * - * An attribute expression is an attribute name prefixed and/or suffixed with array indexes. - * It is mainly used in tabular data input and/or input of array type. Below are some examples: - * - * - `[0]content` is used in tabular data input to represent the "content" attribute - * for the first model in tabular input; - * - `dates[0]` represents the first array element of the "dates" attribute; - * - `[0]dates[0]` represents the first array element of the "dates" attribute - * for the first model in tabular input. - * - * If `$attribute` has neither prefix nor suffix, it will be returned back without change. - * @param string $attribute the attribute name or expression - * @return string the attribute name without prefix and suffix. - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getAttributeName($attribute) - { - if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - return $matches[2]; - } else { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - } - - /** - * Returns the value of the specified attribute name or expression. - * - * For an attribute expression like `[0]dates[0]`, this method will return the value of `$model->dates[0]`. - * See [[getAttributeName()]] for more details about attribute expression. - * - * @param Model $model the model object - * @param string $attribute the attribute name or expression - * @return mixed the corresponding attribute value - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getAttributeValue($model, $attribute) - { - if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - $attribute = $matches[2]; - $index = $matches[3]; - if ($index === '') { - return $model->$attribute; - } else { - $value = $model->$attribute; - foreach (explode('][', trim($index, '[]')) as $id) { - if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { - $value = $value[$id]; - } else { - return null; - } - } - return $value; - } - } - - /** - * Generates an appropriate input name for the specified attribute name or expression. - * - * This method generates a name that can be used as the input name to collect user input - * for the specified attribute. The name is generated according to the [[Model::formName|form name]] - * of the model and the given attribute name. For example, if the form name of the `Post` model - * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. - * - * See [[getAttributeName()]] for explanation of attribute expression. - * - * @param Model $model the model object - * @param string $attribute the attribute name or expression - * @return string the generated input name - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - 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.'); - } - } - - /** - * Generates an appropriate input ID for the specified attribute name or expression. - * - * This method converts the result [[getInputName()]] into a valid input ID. - * For example, if [[getInputName()]] returns `Post[content]`, this method will return `post-content`. - * @param Model $model the model object - * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for explanation of attribute expression. - * @return string the generated input ID - * @throws InvalidParamException if the attribute name contains non-word characters. - */ - public static function getInputId($model, $attribute) - { - $name = strtolower(static::getInputName($model, $attribute)); - return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); - } -} diff --git a/framework/yii/helpers/AbstractHtmlPurifier.php b/framework/yii/helpers/AbstractHtmlPurifier.php deleted file mode 100644 index 221fc37..0000000 --- a/framework/yii/helpers/AbstractHtmlPurifier.php +++ /dev/null @@ -1,34 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractHtmlPurifier -{ - /** - * Passes markup through HTMLPurifier making it safe to output to end user - * - * @param string $content - * @param array|null $config - * @return string - */ - public static function process($content, $config = null) - { - $configInstance = \HTMLPurifier_Config::create($config); - $configInstance->autoFinalize = false; - $purifier=\HTMLPurifier::instance($configInstance); - $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); - return $purifier->purify($content); - } -} diff --git a/framework/yii/helpers/AbstractInflector.php b/framework/yii/helpers/AbstractInflector.php deleted file mode 100644 index 27ee4f7..0000000 --- a/framework/yii/helpers/AbstractInflector.php +++ /dev/null @@ -1,480 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractInflector -{ - /** - * @var array the rules for converting a word into its plural form. - * The keys are the regular expressions and the values are the corresponding replacements. - */ - public static $plurals = array( - '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1', - '/^(sea[- ]bass)$/i' => '\1', - '/(m)ove$/i' => '\1oves', - '/(f)oot$/i' => '\1eet', - '/(h)uman$/i' => '\1umans', - '/(s)tatus$/i' => '\1tatuses', - '/(s)taff$/i' => '\1taff', - '/(t)ooth$/i' => '\1eeth', - '/(quiz)$/i' => '\1zes', - '/^(ox)$/i' => '\1\2en', - '/([m|l])ouse$/i' => '\1ice', - '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', - '/(x|ch|ss|sh)$/i' => '\1es', - '/([^aeiouy]|qu)y$/i' => '\1ies', - '/(hive)$/i' => '\1s', - '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', - '/sis$/i' => 'ses', - '/([ti])um$/i' => '\1a', - '/(p)erson$/i' => '\1eople', - '/(m)an$/i' => '\1en', - '/(c)hild$/i' => '\1hildren', - '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes', - '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', - '/us$/i' => 'uses', - '/(alias)$/i' => '\1es', - '/(ax|cris|test)is$/i' => '\1es', - '/s$/' => 's', - '/^$/' => '', - '/$/' => 's', - ); - /** - * @var array the rules for converting a word into its singular form. - * The keys are the regular expressions and the values are the corresponding replacements. - */ - public static $singulars = array( - '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', - '/^(sea[- ]bass)$/i' => '\1', - '/(s)tatuses$/i' => '\1tatus', - '/(f)eet$/i' => '\1oot', - '/(t)eeth$/i' => '\1ooth', - '/^(.*)(menu)s$/i' => '\1\2', - '/(quiz)zes$/i' => '\\1', - '/(matr)ices$/i' => '\1ix', - '/(vert|ind)ices$/i' => '\1ex', - '/^(ox)en/i' => '\1', - '/(alias)(es)*$/i' => '\1', - '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', - '/([ftw]ax)es/i' => '\1', - '/(cris|ax|test)es$/i' => '\1is', - '/(shoe|slave)s$/i' => '\1', - '/(o)es$/i' => '\1', - '/ouses$/' => 'ouse', - '/([^a])uses$/' => '\1us', - '/([m|l])ice$/i' => '\1ouse', - '/(x|ch|ss|sh)es$/i' => '\1', - '/(m)ovies$/i' => '\1\2ovie', - '/(s)eries$/i' => '\1\2eries', - '/([^aeiouy]|qu)ies$/i' => '\1y', - '/([lr])ves$/i' => '\1f', - '/(tive)s$/i' => '\1', - '/(hive)s$/i' => '\1', - '/(drive)s$/i' => '\1', - '/([^fo])ves$/i' => '\1fe', - '/(^analy)ses$/i' => '\1sis', - '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', - '/([ti])a$/i' => '\1um', - '/(p)eople$/i' => '\1\2erson', - '/(m)en$/i' => '\1an', - '/(c)hildren$/i' => '\1\2hild', - '/(n)ews$/i' => '\1\2ews', - '/eaus$/' => 'eau', - '/^(.*us)$/' => '\\1', - '/s$/i' => '', - ); - /** - * @var array the special rules for converting a word between its plural form and singular form. - * The keys are the special words in singular form, and the values are the corresponding plural form. - */ - public static $specials = array( - 'atlas' => 'atlases', - 'beef' => 'beefs', - 'brother' => 'brothers', - 'cafe' => 'cafes', - 'child' => 'children', - 'cookie' => 'cookies', - 'corpus' => 'corpuses', - 'cow' => 'cows', - 'curve' => 'curves', - 'foe' => 'foes', - 'ganglion' => 'ganglions', - 'genie' => 'genies', - 'genus' => 'genera', - 'graffito' => 'graffiti', - 'hoof' => 'hoofs', - 'loaf' => 'loaves', - 'man' => 'men', - 'money' => 'monies', - 'mongoose' => 'mongooses', - 'move' => 'moves', - 'mythos' => 'mythoi', - 'niche' => 'niches', - 'numen' => 'numina', - 'occiput' => 'occiputs', - 'octopus' => 'octopuses', - 'opus' => 'opuses', - 'ox' => 'oxen', - 'penis' => 'penises', - 'sex' => 'sexes', - 'soliloquy' => 'soliloquies', - 'testis' => 'testes', - 'trilby' => 'trilbys', - 'turf' => 'turfs', - 'wave' => 'waves', - 'Amoyese' => 'Amoyese', - 'bison' => 'bison', - 'Borghese' => 'Borghese', - 'bream' => 'bream', - 'breeches' => 'breeches', - 'britches' => 'britches', - 'buffalo' => 'buffalo', - 'cantus' => 'cantus', - 'carp' => 'carp', - 'chassis' => 'chassis', - 'clippers' => 'clippers', - 'cod' => 'cod', - 'coitus' => 'coitus', - 'Congoese' => 'Congoese', - 'contretemps' => 'contretemps', - 'corps' => 'corps', - 'debris' => 'debris', - 'diabetes' => 'diabetes', - 'djinn' => 'djinn', - 'eland' => 'eland', - 'elk' => 'elk', - 'equipment' => 'equipment', - 'Faroese' => 'Faroese', - 'flounder' => 'flounder', - 'Foochowese' => 'Foochowese', - 'gallows' => 'gallows', - 'Genevese' => 'Genevese', - 'Genoese' => 'Genoese', - 'Gilbertese' => 'Gilbertese', - 'graffiti' => 'graffiti', - 'headquarters' => 'headquarters', - 'herpes' => 'herpes', - 'hijinks' => 'hijinks', - 'Hottentotese' => 'Hottentotese', - 'information' => 'information', - 'innings' => 'innings', - 'jackanapes' => 'jackanapes', - 'Kiplingese' => 'Kiplingese', - 'Kongoese' => 'Kongoese', - 'Lucchese' => 'Lucchese', - 'mackerel' => 'mackerel', - 'Maltese' => 'Maltese', - 'mews' => 'mews', - 'moose' => 'moose', - 'mumps' => 'mumps', - 'Nankingese' => 'Nankingese', - 'news' => 'news', - 'nexus' => 'nexus', - 'Niasese' => 'Niasese', - 'Pekingese' => 'Pekingese', - 'Piedmontese' => 'Piedmontese', - 'pincers' => 'pincers', - 'Pistoiese' => 'Pistoiese', - 'pliers' => 'pliers', - 'Portuguese' => 'Portuguese', - 'proceedings' => 'proceedings', - 'rabies' => 'rabies', - 'rice' => 'rice', - 'rhinoceros' => 'rhinoceros', - 'salmon' => 'salmon', - 'Sarawakese' => 'Sarawakese', - 'scissors' => 'scissors', - 'series' => 'series', - 'Shavese' => 'Shavese', - 'shears' => 'shears', - 'siemens' => 'siemens', - 'species' => 'species', - 'swine' => 'swine', - 'testes' => 'testes', - 'trousers' => 'trousers', - 'trout' => 'trout', - 'tuna' => 'tuna', - 'Vermontese' => 'Vermontese', - 'Wenchowese' => 'Wenchowese', - 'whiting' => 'whiting', - 'wildebeest' => 'wildebeest', - 'Yengeese' => 'Yengeese', - ); - /** - * @var array map of special chars and its translation. This is used by [[slug()]]. - */ - public static $transliteration = array( - '/ä|æ|ǽ/' => 'ae', - '/ö|œ/' => 'oe', - '/ü/' => 'ue', - '/Ä/' => 'Ae', - '/Ü/' => 'Ue', - '/Ö/' => 'Oe', - '/À|Á|Â|Ã|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A', - '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a', - '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', - '/ç|ć|ĉ|ċ|č/' => 'c', - '/Ð|Ď|Đ/' => 'D', - '/ð|ď|đ/' => 'd', - '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E', - '/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e', - '/Ĝ|Ğ|Ġ|Ģ/' => 'G', - '/ĝ|ğ|ġ|ģ/' => 'g', - '/Ĥ|Ħ/' => 'H', - '/ĥ|ħ/' => 'h', - '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I', - '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i', - '/Ĵ/' => 'J', - '/ĵ/' => 'j', - '/Ķ/' => 'K', - '/ķ/' => 'k', - '/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L', - '/ĺ|ļ|ľ|ŀ|ł/' => 'l', - '/Ñ|Ń|Ņ|Ň/' => 'N', - '/ñ|ń|ņ|ň|ʼn/' => 'n', - '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O', - '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o', - '/Ŕ|Ŗ|Ř/' => 'R', - '/ŕ|ŗ|ř/' => 'r', - '/Ś|Ŝ|Ş|Ș|Š/' => 'S', - '/ś|ŝ|ş|ș|š|ſ/' => 's', - '/Ţ|Ț|Ť|Ŧ/' => 'T', - '/ţ|ț|ť|ŧ/' => 't', - '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U', - '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u', - '/Ý|Ÿ|Ŷ/' => 'Y', - '/ý|ÿ|ŷ/' => 'y', - '/Ŵ/' => 'W', - '/ŵ/' => 'w', - '/Ź|Ż|Ž/' => 'Z', - '/ź|ż|ž/' => 'z', - '/Æ|Ǽ/' => 'AE', - '/ß/' => 'ss', - '/IJ/' => 'IJ', - '/ij/' => 'ij', - '/Œ/' => 'OE', - '/ƒ/' => 'f' - ); - - /** - * Converts a word to its plural form. - * Note that this is for English only! - * For example, 'apple' will become 'apples', and 'child' will become 'children'. - * @param string $word the word to be pluralized - * @return string the pluralized word - */ - public static function pluralize($word) - { - if (isset(self::$specials[$word])) { - return self::$specials[$word]; - } - foreach (static::$plurals as $rule => $replacement) { - if (preg_match($rule, $word)) { - return preg_replace($rule, $replacement, $word); - } - } - return $word; - } - - /** - * Returns the singular of the $word - * @param string $word the english word to singularize - * @return string Singular noun. - */ - public static function singularize($word) - { - $result = array_search($word, self::$specials, true); - if ($result !== false) { - return $result; - } - foreach (static::$singulars as $rule => $replacement) { - if (preg_match($rule, $word)) { - return preg_replace($rule, $replacement, $word); - } - } - return $word; - } - - /** - * Converts an underscored or CamelCase word into a English - * sentence. - * @param string $words - * @param bool $ucAll whether to set all words to uppercase - * @return string - */ - public static function titleize($words, $ucAll = false) - { - $words = static::humanize(static::underscore($words), $ucAll); - return $ucAll ? ucwords($words) : ucfirst($words); - } - - /** - * Returns given word as CamelCased - * Converts a word like "send_email" to "SendEmail". It - * will remove non alphanumeric character from the word, so - * "who's online" will be converted to "WhoSOnline" - * @see variablize - * @param string $word the word to CamelCase - * @return string - */ - public static function camelize($word) - { - return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word))); - } - - /** - * Converts a CamelCase name into space-separated words. - * For example, 'PostTag' will be converted to 'Post Tag'. - * @param string $name the string to be converted - * @param boolean $ucwords whether to capitalize the first letter in each word - * @return string the resulting words - */ - public static function camel2words($name, $ucwords = true) - { - $label = trim(strtolower(str_replace(array( - '-', - '_', - '.' - ), ' ', preg_replace('/(? ' ', - '/\\s+/' => $replacement, - '/(?<=[a-z])([A-Z])/' => $replacement . '\\1', - str_replace(':rep', preg_quote($replacement, '/'), '/^[:rep]+|[:rep]+$/') => '' - ); - return preg_replace(array_keys($map), array_values($map), $string); - } - - /** - * Converts a table name to its class name. For example, converts "people" to "Person" - * @param string $tableName - * @return string - */ - public static function classify($tableName) - { - return static::camelize(static::singularize($tableName)); - } - - /** - * Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd ... - * @param int $number the number to get its ordinal value - * @return string - */ - public static function ordinalize($number) - { - if (in_array(($number % 100), range(11, 13))) { - return $number . 'th'; - } - switch ($number % 10) { - case 1: return $number . 'st'; - case 2: return $number . 'nd'; - case 3: return $number . 'rd'; - default: return $number . 'th'; - } - } -} diff --git a/framework/yii/helpers/AbstractJson.php b/framework/yii/helpers/AbstractJson.php deleted file mode 100644 index cda71a0..0000000 --- a/framework/yii/helpers/AbstractJson.php +++ /dev/null @@ -1,112 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractJson -{ - /** - * Encodes the given value into a JSON string. - * The method enhances `json_encode()` by supporting JavaScript expressions. - * In particular, the method will not encode a JavaScript expression that is - * represented in terms of a [[JsExpression]] object. - * @param mixed $value the data to be encoded - * @param integer $options the encoding options. For more details please refer to - * [[http://www.php.net/manual/en/function.json-encode.php]] - * @return string the encoding result - */ - public static function encode($value, $options = 0) - { - $expressions = array(); - $value = static::processData($value, $expressions, uniqid()); - $json = json_encode($value, $options); - return empty($expressions) ? $json : strtr($json, $expressions); - } - - /** - * Decodes the given JSON string into a PHP data structure. - * @param string $json the JSON string to be decoded - * @param boolean $asArray whether to return objects in terms of associative arrays. - * @return mixed the PHP data - * @throws InvalidParamException if there is any decoding error - */ - public static function decode($json, $asArray = true) - { - if (is_array($json)) { - throw new InvalidParamException('Invalid JSON data.'); - } - $decode = json_decode((string)$json, $asArray); - switch (json_last_error()) { - case JSON_ERROR_NONE: - break; - case JSON_ERROR_DEPTH: - throw new InvalidParamException('The maximum stack depth has been exceeded.'); - case JSON_ERROR_CTRL_CHAR: - throw new InvalidParamException('Control character error, possibly incorrectly encoded.'); - case JSON_ERROR_SYNTAX: - throw new InvalidParamException('Syntax error.'); - case JSON_ERROR_STATE_MISMATCH: - throw new InvalidParamException('Invalid or malformed JSON.'); - case JSON_ERROR_UTF8: - throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.'); - default: - throw new InvalidParamException('Unknown JSON decoding error.'); - } - - return $decode; - } - - /** - * Pre-processes the data before sending it to `json_encode()`. - * @param mixed $data the data to be processed - * @param array $expressions collection of JavaScript expressions - * @param string $expPrefix a prefix internally used to handle JS expressions - * @return mixed the processed data - */ - protected static function processData($data, &$expressions, $expPrefix) - { - if (is_array($data)) { - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $data[$key] = static::processData($value, $expressions, $expPrefix); - } - } - return $data; - } elseif (is_object($data)) { - if ($data instanceof JsExpression) { - $token = "!{[$expPrefix=" . count($expressions) . ']}!'; - $expressions['"' . $token . '"'] = $data->expression; - return $token; - } else { - $data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data); - $result = array(); - foreach ($data as $key => $value) { - if (is_array($value) || is_object($value)) { - $result[$key] = static::processData($value, $expressions, $expPrefix); - } else { - $result[$key] = $value; - } - } - return $result; - } - } else { - return $data; - } - } -} diff --git a/framework/yii/helpers/AbstractMarkdown.php b/framework/yii/helpers/AbstractMarkdown.php deleted file mode 100644 index 6b76b44..0000000 --- a/framework/yii/helpers/AbstractMarkdown.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @since 2.0 - */ -abstract class AbstractMarkdown -{ - /** - * @var MarkdownExtra - */ - protected static $markdown; - - /** - * Converts markdown into HTML - * - * @param string $content - * @param array $config - * @return string - */ - public static function process($content, $config = array()) - { - if (static::$markdown === null) { - static::$markdown = new MarkdownExtra(); - } - foreach ($config as $name => $value) { - static::$markdown->{$name} = $value; - } - return static::$markdown->transform($content); - } -} diff --git a/framework/yii/helpers/AbstractSecurity.php b/framework/yii/helpers/AbstractSecurity.php deleted file mode 100644 index d308b7f..0000000 --- a/framework/yii/helpers/AbstractSecurity.php +++ /dev/null @@ -1,285 +0,0 @@ - - * @author Tom Worster - * @since 2.0 - */ -abstract class AbstractSecurity -{ - /** - * Encrypts data. - * @param string $data data to be encrypted. - * @param string $key the encryption secret key - * @return string the encrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - * @see decrypt() - */ - public static function encrypt($data, $key) - { - $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); - srand(); - $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); - mcrypt_generic_init($module, $key, $iv); - $encrypted = $iv . mcrypt_generic($module, $data); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return $encrypted; - } - - /** - * Decrypts data - * @param string $data data to be decrypted. - * @param string $key the decryption secret key - * @return string the decrypted data - * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized - * @see encrypt() - */ - public static function decrypt($data, $key) - { - $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); - $ivSize = mcrypt_enc_get_iv_size($module); - $iv = StringHelper::substr($data, 0, $ivSize); - mcrypt_generic_init($module, $key, $iv); - $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); - mcrypt_generic_deinit($module); - mcrypt_module_close($module); - return rtrim($decrypted, "\0"); - } - - /** - * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. - * @param string $data the data to be protected - * @param string $key the secret key to be used for generating hash - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. - * @return string the data prefixed with the keyed hash - * @see validateData() - * @see getSecretKey() - */ - public static function hashData($data, $key, $algorithm = 'sha256') - { - return hash_hmac($algorithm, $data, $key) . $data; - } - - /** - * Validates if the given data is tampered. - * @param string $data the data to be validated. The data must be previously - * generated by [[hashData()]]. - * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. - * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" - * function to see the supported hashing algorithms on your system. This must be the same - * as the value passed to [[hashData()]] when generating the hash for the data. - * @return string the real data with the hash stripped off. False if the data is tampered. - * @see hashData() - */ - public static function validateData($data, $key, $algorithm = 'sha256') - { - $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); - $n = StringHelper::strlen($data); - if ($n >= $hashSize) { - $hash = StringHelper::substr($data, 0, $hashSize); - $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); - return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; - } else { - return false; - } - } - - /** - * Returns a secret key associated with the specified name. - * If the secret key does not exist, a random key will be generated - * and saved in the file "keys.php" under the application's runtime directory - * so that the same secret key can be returned in future requests. - * @param string $name the name that is associated with the secret key - * @param integer $length the length of the key that should be generated if not exists - * @return string the secret key associated with the specified name - */ - public static function getSecretKey($name, $length = 32) - { - static $keys; - $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; - if ($keys === null) { - $keys = array(); - if (is_file($keyFile)) { - $keys = require($keyFile); - } - } - if (!isset($keys[$name])) { - $keys[$name] = static::generateRandomKey($length); - file_put_contents($keyFile, " 30) { - throw new InvalidParamException('Hash is invalid.'); - } - - $test = crypt($password, $hash); - $n = strlen($test); - if (strlen($test) < 32 || $n !== strlen($hash)) { - return false; - } - - // Use a for-loop to compare two strings to prevent timing attacks. See: - // http://codereview.stackexchange.com/questions/13512 - $check = 0; - for ($i = 0; $i < $n; ++$i) { - $check |= (ord($test[$i]) ^ ord($hash[$i])); - } - - return $check === 0; - } - - /** - * Generates a salt that can be used to generate a password hash. - * - * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function - * requires, for the Blowfish hash algorithm, a salt string in a specific format: - * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters - * from the alphabet "./0-9A-Za-z". - * - * @param integer $cost the cost parameter - * @return string the random salt value. - * @throws InvalidParamException if the cost parameter is not between 4 and 30 - */ - protected static function generateSalt($cost = 13) - { - $cost = (int)$cost; - if ($cost < 4 || $cost > 31) { - throw new InvalidParamException('Cost must be between 4 and 31.'); - } - - // Get 20 * 8bits of pseudo-random entropy from mt_rand(). - $rand = ''; - for ($i = 0; $i < 20; ++$i) { - $rand .= chr(mt_rand(0, 255)); - } - - // Add the microtime for a little more entropy. - $rand .= microtime(); - // Mix the bits cryptographically into a 20-byte binary string. - $rand = sha1($rand, true); - // Form the prefix that specifies Blowfish algorithm and cost parameter. - $salt = sprintf("$2y$%02d$", $cost); - // Append the random salt data in the required base64 format. - $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); - return $salt; - } -} diff --git a/framework/yii/helpers/AbstractStringHelper.php b/framework/yii/helpers/AbstractStringHelper.php deleted file mode 100644 index 050481e..0000000 --- a/framework/yii/helpers/AbstractStringHelper.php +++ /dev/null @@ -1,138 +0,0 @@ - - * @author Alex Makarov - * @since 2.0 - */ -abstract class AbstractStringHelper -{ - /** - * Returns the number of bytes in the given string. - * This method ensures the string is treated as a byte array by using `mb_strlen()`. - * @param string $string the string being measured for length - * @return integer the number of bytes in the given string. - */ - public static function strlen($string) - { - return mb_strlen($string, '8bit'); - } - - /** - * Returns the portion of string specified by the start and length parameters. - * This method ensures the string is treated as a byte array by using `mb_substr()`. - * @param string $string the input string. Must be one character or longer. - * @param integer $start the starting position - * @param integer $length the desired portion length - * @return string the extracted part of string, or FALSE on failure or an empty string. - * @see http://www.php.net/manual/en/function.substr.php - */ - public static function substr($string, $start, $length) - { - return mb_substr($string, $start, $length, '8bit'); - } - - /** - * Returns the trailing name component of a path. - * This method is similar to the php function `basename()` except that it will - * treat both \ and / as directory separators, independent of the operating system. - * This method was mainly created to work on php namespaces. When working with real - * file paths, php's `basename()` should work fine for you. - * Note: this method is not aware of the actual filesystem, or path components such as "..". - * @param string $path A path string. - * @param string $suffix If the name component ends in suffix this will also be cut off. - * @return string the trailing name component of the given path. - * @see http://www.php.net/manual/en/function.basename.php - */ - public static function basename($path, $suffix = '') - { - if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) == $suffix) { - $path = mb_substr($path, 0, -$len); - } - $path = rtrim(str_replace('\\', '/', $path), '/\\'); - if (($pos = mb_strrpos($path, '/')) !== false) { - return mb_substr($path, $pos + 1); - } - return $path; - } - - /** - * Returns parent directory's path. - * This method is similar to `dirname()` except that it will treat - * both \ and / as directory separators, independent of the operating system. - * @param string $path A path string. - * @return string the parent directory's path. - * @see http://www.php.net/manual/en/function.basename.php - */ - public static function dirname($path) - { - $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); - if ($pos !== false) { - return mb_substr($path, 0, $pos); - } else { - return $path; - } - } - - /** - * Compares two strings or string arrays, and return their differences. - * This is a wrapper of the [phpspec/php-diff](https://packagist.org/packages/phpspec/php-diff) package. - * @param string|array $lines1 the first string or string array to be compared. If it is a string, - * it will be converted into a string array by breaking at newlines. - * @param string|array $lines2 the second string or string array to be compared. If it is a string, - * it will be converted into a string array by breaking at newlines. - * @param string $format the output format. It must be 'inline', 'unified', 'context', 'side-by-side', or 'array'. - * @return string|array the comparison result. An array is returned if `$format` is 'array'. For all other - * formats, a string is returned. - * @throws InvalidParamException if the format is invalid. - */ - public static function diff($lines1, $lines2, $format = 'inline') - { - if (!is_array($lines1)) { - $lines1 = explode("\n", $lines1); - } - if (!is_array($lines2)) { - $lines2 = explode("\n", $lines2); - } - foreach ($lines1 as $i => $line) { - $lines1[$i] = rtrim($line, "\r\n"); - } - foreach ($lines2 as $i => $line) { - $lines2[$i] = rtrim($line, "\r\n"); - } - switch ($format) { - case 'inline': - $renderer = new \Diff_Renderer_Html_Inline(); - break; - case 'array': - $renderer = new \Diff_Renderer_Html_Array(); - break; - case 'side-by-side': - $renderer = new \Diff_Renderer_Html_SideBySide(); - break; - case 'context': - $renderer = new \Diff_Renderer_Text_Context(); - break; - case 'unified': - $renderer = new \Diff_Renderer_Text_Unified(); - break; - default: - throw new InvalidParamException("Output format must be 'inline', 'side-by-side', 'array', 'context' or 'unified'."); - } - $diff = new \Diff($lines1, $lines2); - return $diff->render($renderer); - } -} diff --git a/framework/yii/helpers/AbstractVarDumper.php b/framework/yii/helpers/AbstractVarDumper.php deleted file mode 100644 index 2c9f194..0000000 --- a/framework/yii/helpers/AbstractVarDumper.php +++ /dev/null @@ -1,127 +0,0 @@ - - * @link http://www.yiiframework.com/ - * @copyright Copyright © 2008-2011 Yii Software LLC - * @license http://www.yiiframework.com/license/ - */ - -namespace yii\helpers; - -/** - * AbstractVarDumper provides concrete implementation for [[VarDumper]]. - * - * Do not use AbstractVarDumper. Use [[VarDumper]] instead. - * - * @author Qiang Xue - * @since 2.0 - */ -abstract class AbstractVarDumper -{ - private static $_objects; - private static $_output; - private static $_depth; - - /** - * Displays a variable. - * This method achieves the similar functionality as var_dump and print_r - * but is more robust when handling complex objects such as Yii controllers. - * @param mixed $var variable to be dumped - * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. - * @param boolean $highlight whether the result should be syntax-highlighted - */ - public static function dump($var, $depth = 10, $highlight = false) - { - echo static::dumpAsString($var, $depth, $highlight); - } - - /** - * Dumps a variable in terms of a string. - * This method achieves the similar functionality as var_dump and print_r - * but is more robust when handling complex objects such as Yii controllers. - * @param mixed $var variable to be dumped - * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. - * @param boolean $highlight whether the result should be syntax-highlighted - * @return string the string representation of the variable - */ - public static function dumpAsString($var, $depth = 10, $highlight = false) - { - self::$_output = ''; - self::$_objects = array(); - self::$_depth = $depth; - self::dumpInternal($var, 0); - if ($highlight) { - $result = highlight_string("/', '', $result, 1); - } - return self::$_output; - } - - /** - * @param mixed $var variable to be dumped - * @param integer $level depth level - */ - private static function dumpInternal($var, $level) - { - switch (gettype($var)) { - case 'boolean': - self::$_output .= $var ? 'true' : 'false'; - break; - case 'integer': - self::$_output .= "$var"; - break; - case 'double': - self::$_output .= "$var"; - break; - case 'string': - self::$_output .= "'" . addslashes($var) . "'"; - break; - case 'resource': - self::$_output .= '{resource}'; - break; - case 'NULL': - self::$_output .= "null"; - break; - case 'unknown type': - self::$_output .= '{unknown}'; - break; - case 'array': - if (self::$_depth <= $level) { - self::$_output .= 'array(...)'; - } elseif (empty($var)) { - self::$_output .= 'array()'; - } else { - $keys = array_keys($var); - $spaces = str_repeat(' ', $level * 4); - self::$_output .= "array\n" . $spaces . '('; - foreach ($keys as $key) { - self::$_output .= "\n" . $spaces . ' '; - self::dumpInternal($key, 0); - self::$_output .= ' => '; - self::dumpInternal($var[$key], $level + 1); - } - self::$_output .= "\n" . $spaces . ')'; - } - break; - case 'object': - if (($id = array_search($var, self::$_objects, true)) !== false) { - self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; - } elseif (self::$_depth <= $level) { - self::$_output .= get_class($var) . '(...)'; - } else { - $id = array_push(self::$_objects, $var); - $className = get_class($var); - $members = (array)$var; - $spaces = str_repeat(' ', $level * 4); - self::$_output .= "$className#$id\n" . $spaces . '('; - foreach ($members as $key => $value) { - $keyDisplay = strtr(trim($key), array("\0" => ':')); - self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; - self::dumpInternal($value, $level + 1); - } - self::$_output .= "\n" . $spaces . ')'; - } - break; - } - } -} diff --git a/framework/yii/helpers/ArrayHelper.php b/framework/yii/helpers/ArrayHelper.php index a63d3c2..9d428f5 100644 --- a/framework/yii/helpers/ArrayHelper.php +++ b/framework/yii/helpers/ArrayHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class ArrayHelper extends AbstractArrayHelper +class ArrayHelper extends BaseArrayHelper { } diff --git a/framework/yii/helpers/BaseArrayHelper.php b/framework/yii/helpers/BaseArrayHelper.php new file mode 100644 index 0000000..0ed584f --- /dev/null +++ b/framework/yii/helpers/BaseArrayHelper.php @@ -0,0 +1,451 @@ + + * @since 2.0 + */ +class BaseArrayHelper +{ + /** + * Converts an object or an array of objects into an array. + * @param object|array $object the object to be converted into an array + * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays. + * The properties specified for each class is an array of the following format: + * + * ~~~ + * array( + * 'app\models\Post' => array( + * 'id', + * 'title', + * // the key name in array result => property name + * 'createTime' => 'create_time', + * // the key name in array result => anonymous function + * 'length' => function ($post) { + * return strlen($post->content); + * }, + * ), + * ) + * ~~~ + * + * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: + * + * ~~~ + * array( + * 'id' => 123, + * 'title' => 'test', + * 'createTime' => '2013-01-01 12:00AM', + * 'length' => 301, + * ) + * ~~~ + * + * @param boolean $recursive whether to recursively converts properties which are objects into arrays. + * @return array the array representation of the object + */ + public static function toArray($object, $properties = array(), $recursive = true) + { + if (!empty($properties) && is_object($object)) { + $className = get_class($object); + if (!empty($properties[$className])) { + $result = array(); + foreach ($properties[$className] as $key => $name) { + if (is_int($key)) { + $result[$name] = $object->$name; + } else { + $result[$key] = static::getValue($object, $name); + } + } + return $result; + } + } + if ($object instanceof Arrayable) { + $object = $object->toArray(); + if (!$recursive) { + return $object; + } + } + $result = array(); + foreach ($object as $key => $value) { + if ($recursive && (is_array($value) || is_object($value))) { + $result[$key] = static::toArray($value, true); + } else { + $result[$key] = $value; + } + } + return $result; + } + + /** + * Merges two or more arrays into one recursively. + * If each array has an element with the same string key value, the latter + * will overwrite the former (different from array_merge_recursive). + * Recursive merging will be conducted if both arrays have an element of array + * type and are having the same key. + * For integer-keyed elements, the elements from the latter array will + * be appended to the former array. + * @param array $a array to be merged to + * @param array $b array to be merged from. You can specify additional + * arrays via third argument, fourth argument etc. + * @return array the merged array (the original arrays are not changed.) + */ + public static function merge($a, $b) + { + $args = func_get_args(); + $res = array_shift($args); + while (!empty($args)) { + $next = array_shift($args); + foreach ($next as $k => $v) { + if (is_integer($k)) { + isset($res[$k]) ? $res[] = $v : $res[$k] = $v; + } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { + $res[$k] = self::merge($res[$k], $v); + } else { + $res[$k] = $v; + } + } + } + return $res; + } + + /** + * Retrieves the value of an array element or object property with the given key or property name. + * If the key does not exist in the array, the default value will be returned instead. + * + * Below are some usage examples, + * + * ~~~ + * // working with array + * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); + * // working with object + * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); + * // working with anonymous function + * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) { + * return $user->firstName . ' ' . $user->lastName; + * }); + * ~~~ + * + * @param array|object $array array or object to extract value from + * @param string|\Closure $key key name of the array element, or property name of the object, + * or an anonymous function returning the value. The anonymous function signature should be: + * `function($array, $defaultValue)`. + * @param mixed $default the default value to be returned if the specified key does not exist + * @return mixed the value of the element if found, default value otherwise + */ + public static function getValue($array, $key, $default = null) + { + if ($key instanceof \Closure) { + return $key($array, $default); + } elseif (is_array($array)) { + return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default; + } else { + return $array->$key; + } + } + + /** + * Removes an item from an array and returns the value. If the key does not exist in the array, the default value + * will be returned instead. + * + * Usage examples, + * + * ~~~ + * // $array = array('type' => 'A', 'options' => array(1, 2)); + * // working with array + * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); + * // $array content + * // $array = array('options' => array(1, 2)); + * ~~~ + * + * @param array $array the array to extract value from + * @param string $key key name of the array element + * @param mixed $default the default value to be returned if the specified key does not exist + * @return mixed|null the value of the element if found, default value otherwise + */ + public static function remove(&$array, $key, $default = null) + { + if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { + $value = $array[$key]; + unset($array[$key]); + return $value; + } + return $default; + } + + /** + * Indexes an array according to a specified key. + * The input array should be multidimensional or an array of objects. + * + * The key can be a key name of the sub-array, a property name of object, or an anonymous + * function which returns the key value given an array element. + * + * If a key value is null, the corresponding array element will be discarded and not put in the result. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'data' => 'abc'), + * array('id' => '345', 'data' => 'def'), + * ); + * $result = ArrayHelper::index($array, 'id'); + * // the result is: + * // array( + * // '123' => array('id' => '123', 'data' => 'abc'), + * // '345' => array('id' => '345', 'data' => 'def'), + * // ) + * + * // using anonymous function + * $result = ArrayHelper::index($array, function ($element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array the array that needs to be indexed + * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array + * @return array the indexed array + */ + public static function index($array, $key) + { + $result = array(); + foreach ($array as $element) { + $value = static::getValue($element, $key); + $result[$value] = $element; + } + return $result; + } + + /** + * Returns the values of a specified column in an array. + * The input array should be multidimensional or an array of objects. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'data' => 'abc'), + * array('id' => '345', 'data' => 'def'), + * ); + * $result = ArrayHelper::getColumn($array, 'id'); + * // the result is: array( '123', '345') + * + * // using anonymous function + * $result = ArrayHelper::getColumn($array, function ($element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array + * @param string|\Closure $name + * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array + * will be re-indexed with integers. + * @return array the list of column values + */ + public static function getColumn($array, $name, $keepKeys = true) + { + $result = array(); + if ($keepKeys) { + foreach ($array as $k => $element) { + $result[$k] = static::getValue($element, $name); + } + } else { + foreach ($array as $element) { + $result[] = static::getValue($element, $name); + } + } + + return $result; + } + + /** + * Builds a map (key-value pairs) from a multidimensional array or an array of objects. + * The `$from` and `$to` parameters specify the key names or property names to set up the map. + * Optionally, one can further group the map according to a grouping field `$group`. + * + * For example, + * + * ~~~ + * $array = array( + * array('id' => '123', 'name' => 'aaa', 'class' => 'x'), + * array('id' => '124', 'name' => 'bbb', 'class' => 'x'), + * array('id' => '345', 'name' => 'ccc', 'class' => 'y'), + * ); + * + * $result = ArrayHelper::map($array, 'id', 'name'); + * // the result is: + * // array( + * // '123' => 'aaa', + * // '124' => 'bbb', + * // '345' => 'ccc', + * // ) + * + * $result = ArrayHelper::map($array, 'id', 'name', 'class'); + * // the result is: + * // array( + * // 'x' => array( + * // '123' => 'aaa', + * // '124' => 'bbb', + * // ), + * // 'y' => array( + * // '345' => 'ccc', + * // ), + * // ) + * ~~~ + * + * @param array $array + * @param string|\Closure $from + * @param string|\Closure $to + * @param string|\Closure $group + * @return array + */ + public static function map($array, $from, $to, $group = null) + { + $result = array(); + foreach ($array as $element) { + $key = static::getValue($element, $from); + $value = static::getValue($element, $to); + if ($group !== null) { + $result[static::getValue($element, $group)][$key] = $value; + } else { + $result[$key] = $value; + } + } + return $result; + } + + /** + * Sorts an array of objects or arrays (with the same structure) by one or several keys. + * @param array $array the array to be sorted. The array will be modified after calling this method. + * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array + * elements, a property name of the objects, or an anonymous function returning the values for comparison + * purpose. The anonymous function signature should be: `function($item)`. + * To sort by multiple keys, provide an array of keys here. + * @param boolean|array $descending whether to sort in descending or ascending order. When + * sorting by multiple keys with different descending orders, use an array of descending flags. + * @param integer|array $sortFlag the PHP sort flag. Valid values include + * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING` and `SORT_LOCALE_STRING`. + * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php) + * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags. + * @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter + * is used only when `$sortFlag` is `SORT_STRING`. + * When sorting by multiple keys with different case sensitivities, use an array of boolean values. + * @throws InvalidParamException if the $descending or $sortFlag parameters do not have + * correct number of elements as that of $key. + */ + public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true) + { + $keys = is_array($key) ? $key : array($key); + if (empty($keys) || empty($array)) { + return; + } + $n = count($keys); + if (is_scalar($descending)) { + $descending = array_fill(0, $n, $descending); + } elseif (count($descending) !== $n) { + throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); + } + if (is_scalar($sortFlag)) { + $sortFlag = array_fill(0, $n, $sortFlag); + } elseif (count($sortFlag) !== $n) { + throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.'); + } + if (is_scalar($caseSensitive)) { + $caseSensitive = array_fill(0, $n, $caseSensitive); + } elseif (count($caseSensitive) !== $n) { + throw new InvalidParamException('The length of $caseSensitive parameter must be the same as that of $keys.'); + } + $args = array(); + foreach ($keys as $i => $key) { + $flag = $sortFlag[$i]; + $cs = $caseSensitive[$i]; + if (!$cs && ($flag === SORT_STRING)) { + if (defined('SORT_FLAG_CASE')) { + $flag = $flag | SORT_FLAG_CASE; + $args[] = static::getColumn($array, $key); + } else { + $column = array(); + foreach (static::getColumn($array, $key) as $k => $value) { + $column[$k] = mb_strtolower($value); + } + $args[] = $column; + } + } else { + $args[] = static::getColumn($array, $key); + } + $args[] = $descending[$i] ? SORT_DESC : SORT_ASC; + $args[] = $flag; + } + $args[] = &$array; + call_user_func_array('array_multisort', $args); + } + + /** + * Encodes special characters in an array of strings into HTML entities. + * Both the array keys and values will be encoded. + * If a value is an array, this method will also encode it recursively. + * @param array $data data to be encoded + * @param boolean $valuesOnly whether to encode array values only. If false, + * both the array keys and array values will be encoded. + * @param string $charset the charset that the data is using. If not set, + * [[\yii\base\Application::charset]] will be used. + * @return array the encoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function htmlEncode($data, $valuesOnly = true, $charset = null) + { + if ($charset === null) { + $charset = Yii::$app->charset; + } + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars($key, ENT_QUOTES, $charset); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); + } elseif (is_array($value)) { + $d[$key] = static::htmlEncode($value, $charset); + } + } + return $d; + } + + /** + * Decodes HTML entities into the corresponding characters in an array of strings. + * Both the array keys and values will be decoded. + * If a value is an array, this method will also decode it recursively. + * @param array $data data to be decoded + * @param boolean $valuesOnly whether to decode array values only. If false, + * both the array keys and array values will be decoded. + * @return array the decoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function htmlDecode($data, $valuesOnly = true) + { + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars_decode($key, ENT_QUOTES); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); + } elseif (is_array($value)) { + $d[$key] = static::htmlDecode($value); + } + } + return $d; + } +} diff --git a/framework/yii/helpers/BaseConsole.php b/framework/yii/helpers/BaseConsole.php new file mode 100644 index 0000000..6796283 --- /dev/null +++ b/framework/yii/helpers/BaseConsole.php @@ -0,0 +1,835 @@ + + * @since 2.0 + */ +class BaseConsole +{ + const FG_BLACK = 30; + const FG_RED = 31; + const FG_GREEN = 32; + const FG_YELLOW = 33; + const FG_BLUE = 34; + const FG_PURPLE = 35; + const FG_CYAN = 36; + const FG_GREY = 37; + + const BG_BLACK = 40; + const BG_RED = 41; + const BG_GREEN = 42; + const BG_YELLOW = 43; + const BG_BLUE = 44; + const BG_PURPLE = 45; + const BG_CYAN = 46; + const BG_GREY = 47; + + const RESET = 0; + const NORMAL = 0; + const BOLD = 1; + const ITALIC = 3; + const UNDERLINE = 4; + const BLINK = 5; + const NEGATIVE = 7; + const CONCEALED = 8; + const CROSSED_OUT = 9; + const FRAMED = 51; + const ENCIRCLED = 52; + const OVERLINED = 53; + + /** + * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved up + */ + public static function moveCursorUp($rows = 1) + { + echo "\033[" . (int)$rows . 'A'; + } + + /** + * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved down + */ + public static function moveCursorDown($rows = 1) + { + echo "\033[" . (int)$rows . 'B'; + } + + /** + * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved forward + */ + public static function moveCursorForward($steps = 1) + { + echo "\033[" . (int)$steps . 'C'; + } + + /** + * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved backward + */ + public static function moveCursorBackward($steps = 1) + { + echo "\033[" . (int)$steps . 'D'; + } + + /** + * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. + * @param integer $lines number of lines the cursor should be moved down + */ + public static function moveCursorNextLine($lines = 1) + { + echo "\033[" . (int)$lines . 'E'; + } + + /** + * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. + * @param integer $lines number of lines the cursor should be moved up + */ + public static function moveCursorPrevLine($lines = 1) + { + echo "\033[" . (int)$lines . 'F'; + } + + /** + * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. + * @param integer $column 1-based column number, 1 is the left edge of the screen. + * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. + */ + public static function moveCursorTo($column, $row = null) + { + if ($row === null) { + echo "\033[" . (int)$column . 'G'; + } else { + echo "\033[" . (int)$row . ';' . (int)$column . 'H'; + } + } + + /** + * Scrolls whole page up by sending ANSI control code SU to the terminal. + * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. + * @param int $lines number of lines to scroll up + */ + public static function scrollUp($lines = 1) + { + echo "\033[" . (int)$lines . "S"; + } + + /** + * Scrolls whole page down by sending ANSI control code SD to the terminal. + * New lines are added at the top. This is not supported by ANSI.SYS used in windows. + * @param int $lines number of lines to scroll down + */ + public static function scrollDown($lines = 1) + { + echo "\033[" . (int)$lines . "T"; + } + + /** + * Saves the current cursor position by sending ANSI control code SCP to the terminal. + * Position can then be restored with {@link restoreCursorPosition}. + */ + public static function saveCursorPosition() + { + echo "\033[s"; + } + + /** + * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal. + */ + public static function restoreCursorPosition() + { + echo "\033[u"; + } + + /** + * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. + * Use {@link showCursor} to bring it back. + * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. + */ + public static function hideCursor() + { + echo "\033[?25l"; + } + + /** + * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal. + */ + public static function showCursor() + { + echo "\033[?25h"; + } + + /** + * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. + * Cursor position will not be changed. + * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. + */ + public static function clearScreen() + { + echo "\033[2J"; + } + + /** + * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenBeforeCursor() + { + echo "\033[1J"; + } + + /** + * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenAfterCursor() + { + echo "\033[0J"; + } + + /** + * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLine() + { + echo "\033[2K"; + } + + /** + * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineBeforeCursor() + { + echo "\033[1K"; + } + + /** + * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineAfterCursor() + { + echo "\033[0K"; + } + + /** + * Returns the ANSI format code. + * + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @return string The ANSI format code according to the given formatting constants. + */ + public static function ansiFormatCode($format) + { + return "\033[" . implode(';', $format) . 'm'; + } + + /** + * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards. + * + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @see ansiFormatCode() + * @see ansiFormatEnd() + */ + public static function beginAnsiFormat($format) + { + echo "\033[" . implode(';', $format) . 'm'; + } + + /** + * Resets any ANSI format set by previous method [[ansiFormatBegin()]] + * Any output after this will have default text format. + * This is equal to calling + * + * ```php + * echo Console::ansiFormatCode(array(Console::RESET)) + * ``` + */ + public static function endAnsiFormat() + { + echo "\033[0m"; + } + + /** + * Will return a string formatted with the given ANSI style + * + * @param string $string the string to be formatted + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @return string + */ + public static function ansiFormat($string, $format = array()) + { + $code = implode(';', $format); + return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; + } + + /** + * Returns the ansi format code for xterm foreground color. + * You can pass the return value of this to one of the formatting methods: + * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] + * + * @param integer $colorCode xterm color code + * @return string + * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + */ + public static function xtermFgColor($colorCode) + { + return '38;5;' . $colorCode; + } + + /** + * Returns the ansi format code for xterm background color. + * You can pass the return value of this to one of the formatting methods: + * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] + * + * @param integer $colorCode xterm color code + * @return string + * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + */ + public static function xtermBgColor($colorCode) + { + return '48;5;' . $colorCode; + } + + /** + * Strips ANSI control codes from a string + * + * @param string $string String to strip + * @return string + */ + public static function stripAnsiFormat($string) + { + return preg_replace('/\033\[[\d;?]*\w/', '', $string); + } + + /** + * Converts an ANSI formatted string to HTML + * @param $string + * @return mixed + */ + // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 + public static function ansiToHtml($string) + { + $tags = 0; + return preg_replace_callback( + '/\033\[[\d;]+m/', + function ($ansi) use (&$tags) { + $styleA = array(); + foreach (explode(';', $ansi) as $controlCode) { + switch ($controlCode) { + case self::FG_BLACK: + $style = array('color' => '#000000'); + break; + case self::FG_BLUE: + $style = array('color' => '#000078'); + break; + case self::FG_CYAN: + $style = array('color' => '#007878'); + break; + case self::FG_GREEN: + $style = array('color' => '#007800'); + break; + case self::FG_GREY: + $style = array('color' => '#787878'); + break; + case self::FG_PURPLE: + $style = array('color' => '#780078'); + break; + case self::FG_RED: + $style = array('color' => '#780000'); + break; + case self::FG_YELLOW: + $style = array('color' => '#787800'); + break; + case self::BG_BLACK: + $style = array('background-color' => '#000000'); + break; + case self::BG_BLUE: + $style = array('background-color' => '#000078'); + break; + case self::BG_CYAN: + $style = array('background-color' => '#007878'); + break; + case self::BG_GREEN: + $style = array('background-color' => '#007800'); + break; + case self::BG_GREY: + $style = array('background-color' => '#787878'); + break; + case self::BG_PURPLE: + $style = array('background-color' => '#780078'); + break; + case self::BG_RED: + $style = array('background-color' => '#780000'); + break; + case self::BG_YELLOW: + $style = array('background-color' => '#787800'); + break; + case self::BOLD: + $style = array('font-weight' => 'bold'); + break; + case self::ITALIC: + $style = array('font-style' => 'italic'); + break; + case self::UNDERLINE: + $style = array('text-decoration' => array('underline')); + break; + case self::OVERLINED: + $style = array('text-decoration' => array('overline')); + break; + case self::CROSSED_OUT: + $style = array('text-decoration' => array('line-through')); + break; + case self::BLINK: + $style = array('text-decoration' => array('blink')); + break; + case self::NEGATIVE: // ??? + case self::CONCEALED: + case self::ENCIRCLED: + case self::FRAMED: + // TODO allow resetting codes + break; + case 0: // ansi reset + $return = ''; + for ($n = $tags; $tags > 0; $tags--) { + $return .= ''; + } + return $return; + } + + $styleA = ArrayHelper::merge($styleA, $style); + } + $styleString[] = array(); + foreach ($styleA as $name => $content) { + if ($name === 'text-decoration') { + $content = implode(' ', $content); + } + $styleString[] = $name . ':' . $content; + } + $tags++; + return ' $value) { + echo " $key - $value\n"; + } + echo " ? - Show help\n"; + goto top; + } elseif (!in_array($input, array_keys($options))) { + goto top; + } + return $input; + } + + /** + * Displays and updates a simple progress bar on screen. + * + * @param integer $done the number of items that are completed + * @param integer $total the total value of items that are to be done + * @param integer $size the size of the status bar (optional) + * @see http://snipplr.com/view/29548/ + */ + public static function showProgress($done, $total, $size = 30) + { + static $start; + + // if we go over our bound, just ignore it + if ($done > $total) { + return; + } + + if (empty($start)) { + $start = time(); + } + + $now = time(); + + $percent = (double)($done / $total); + $bar = floor($percent * $size); + + $status = "\r["; + $status .= str_repeat("=", $bar); + if ($bar < $size) { + $status .= ">"; + $status .= str_repeat(" ", $size - $bar); + } else { + $status .= "="; + } + + $display = number_format($percent * 100, 0); + + $status .= "] $display% $done/$total"; + + $rate = ($now - $start) / $done; + $left = $total - $done; + $eta = round($rate * $left, 2); + + $elapsed = $now - $start; + + $status .= " remaining: " . number_format($eta) . " sec. elapsed: " . number_format($elapsed) . " sec."; + + static::stdout("$status "); + + flush(); + + // when done, send a newline + if ($done == $total) { + echo "\n"; + } + } +} diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/yii/helpers/BaseFileHelper.php new file mode 100644 index 0000000..2c911b0 --- /dev/null +++ b/framework/yii/helpers/BaseFileHelper.php @@ -0,0 +1,329 @@ + + * @author Alex Makarov + * @since 2.0 + */ +class BaseFileHelper +{ + /** + * Normalizes a file/directory path. + * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`, + * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux + * will be normalized as '/home/demo'. + * @param string $path the file/directory path to be normalized + * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`. + * @return string the normalized file/directory path + */ + public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) + { + return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds); + } + + /** + * Returns the localized version of a specified file. + * + * The searching is based on the specified language code. In particular, + * a file with the same name will be looked for under the subdirectory + * whose name is the same as the language code. For example, given the file "path/to/view.php" + * and language code "zh_CN", the localized file will be looked for as + * "path/to/zh_CN/view.php". If the file is not found, the original file + * will be returned. + * + * If the target and the source language codes are the same, + * the original file will be returned. + * + * @param string $file the original file + * @param string $language the target language that the file should be localized to. + * If not set, the value of [[\yii\base\Application::language]] will be used. + * @param string $sourceLanguage the language that the original file is in. + * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. + * @return string the matching localized file, or the original file if the localized version is not found. + * If the target and the source language codes are the same, the original file will be returned. + */ + public static function localize($file, $language = null, $sourceLanguage = null) + { + if ($language === null) { + $language = Yii::$app->language; + } + if ($sourceLanguage === null) { + $sourceLanguage = Yii::$app->sourceLanguage; + } + if ($language === $sourceLanguage) { + return $file; + } + $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file); + return is_file($desiredFile) ? $desiredFile : $file; + } + + /** + * Determines the MIME type of the specified file. + * This method will first try to determine the MIME type based on + * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will + * fall back to [[getMimeTypeByExtension()]]. + * @param string $file the file name. + * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`. + * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php). + * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case + * `finfo_open()` cannot determine it. + * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. + */ + public static function getMimeType($file, $magicFile = null, $checkExtension = true) + { + if (function_exists('finfo_open')) { + $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); + if ($info) { + $result = finfo_file($info, $file); + finfo_close($info); + if ($result !== false) { + return $result; + } + } + } + + return $checkExtension ? static::getMimeTypeByExtension($file) : null; + } + + /** + * Determines the MIME type based on the extension name of the specified file. + * This method will use a local map between extension names and MIME types. + * @param string $file the file name. + * @param string $magicFile the path of the file that contains all available MIME type information. + * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used. + * @return string the MIME type. Null is returned if the MIME type cannot be determined. + */ + public static function getMimeTypeByExtension($file, $magicFile = null) + { + static $mimeTypes = array(); + if ($magicFile === null) { + $magicFile = __DIR__ . '/mimeTypes.php'; + } + if (!isset($mimeTypes[$magicFile])) { + $mimeTypes[$magicFile] = require($magicFile); + } + if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { + $ext = strtolower($ext); + if (isset($mimeTypes[$magicFile][$ext])) { + return $mimeTypes[$magicFile][$ext]; + } + } + return null; + } + + /** + * Copies a whole directory as another one. + * The files and sub-directories will also be copied over. + * @param string $src the source directory + * @param string $dst the destination directory + * @param array $options options for directory copy. Valid options are: + * + * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775. + * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. + * - filter: callback, a PHP callback that is called for each directory or file. + * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. + * The callback can return one of the following values: + * + * * true: the directory or file will be copied (the "only" and "except" options will be ignored) + * * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored) + * * null: the "only" and "except" options will determine whether the directory or file should be copied + * + * - only: array, list of patterns that the file paths should match if they want to be copied. + * A path matches a pattern if it contains the pattern string at its end. + * For example, '.php' matches all file paths ending with '.php'. + * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. + * If a file path matches a pattern in both "only" and "except", it will NOT be copied. + * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. + * A path matches a pattern if it contains the pattern string at its end. + * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' + * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; + * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches + * both '/' and '\' in the paths. + * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. + * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file copied from, while `$to` is the copy target. + */ + public static function copyDirectory($src, $dst, $options = array()) + { + if (!is_dir($dst)) { + static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); + } + + $handle = opendir($src); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $from = $src . DIRECTORY_SEPARATOR . $file; + $to = $dst . DIRECTORY_SEPARATOR . $file; + if (static::filterPath($from, $options)) { + if (is_file($from)) { + copy($from, $to); + if (isset($options['fileMode'])) { + @chmod($to, $options['fileMode']); + } + } else { + static::copyDirectory($from, $to, $options); + } + if (isset($options['afterCopy'])) { + call_user_func($options['afterCopy'], $from, $to); + } + } + } + closedir($handle); + } + + /** + * Removes a directory (and all its content) recursively. + * @param string $dir the directory to be deleted recursively. + */ + public static function removeDirectory($dir) + { + if (!is_dir($dir) || !($handle = opendir($dir))) { + return; + } + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (is_file($path)) { + unlink($path); + } else { + static::removeDirectory($path); + } + } + closedir($handle); + rmdir($dir); + } + + /** + * Returns the files found under the specified directory and subdirectories. + * @param string $dir the directory under which the files will be looked for. + * @param array $options options for file searching. Valid options are: + * + * - filter: callback, a PHP callback that is called for each directory or file. + * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. + * The callback can return one of the following values: + * + * * true: the directory or file will be returned (the "only" and "except" options will be ignored) + * * false: the directory or file will NOT be returned (the "only" and "except" options will be ignored) + * * null: the "only" and "except" options will determine whether the directory or file should be returned + * + * - only: array, list of patterns that the file paths should match if they want to be returned. + * A path matches a pattern if it contains the pattern string at its end. + * For example, '.php' matches all file paths ending with '.php'. + * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. + * If a file path matches a pattern in both "only" and "except", it will NOT be returned. + * - except: array, list of patterns that the file paths or directory paths should match if they want to be excluded from the result. + * A path matches a pattern if it contains the pattern string at its end. + * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' + * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; + * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches + * both '/' and '\' in the paths. + * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. + * @return array files found under the directory. The file list is sorted. + */ + public static function findFiles($dir, $options = array()) + { + $list = array(); + $handle = opendir($dir); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (static::filterPath($path, $options)) { + if (is_file($path)) { + $list[] = $path; + } elseif (!isset($options['recursive']) || $options['recursive']) { + $list = array_merge($list, static::findFiles($path, $options)); + } + } + } + closedir($handle); + return $list; + } + + /** + * Checks if the given file path satisfies the filtering options. + * @param string $path the path of the file or directory to be checked + * @param array $options the filtering options. See [[findFiles()]] for explanations of + * the supported options. + * @return boolean whether the file or directory satisfies the filtering options. + */ + public static function filterPath($path, $options) + { + if (isset($options['filter'])) { + $result = call_user_func($options['filter'], $path); + if (is_bool($result)) { + return $result; + } + } + $path = str_replace('\\', '/', $path); + if ($isDir = is_dir($path)) { + $path .= '/'; + } + $n = StringHelper::strlen($path); + + if (!empty($options['except'])) { + foreach ($options['except'] as $name) { + if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { + return false; + } + } + } + + if (!$isDir && !empty($options['only'])) { + foreach ($options['only'] as $name) { + if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) { + return true; + } + } + return false; + } + return true; + } + + /** + * Creates a new directory. + * + * This method is similar to the PHP `mkdir()` function except that + * it uses `chmod()` to set the permission of the created directory + * in order to avoid the impact of the `umask` setting. + * + * @param string $path path of the directory to be created. + * @param integer $mode the permission to be set for the created directory. + * @param boolean $recursive whether to create parent directories if they do not exist. + * @return boolean whether the directory is created successfully + */ + public static function createDirectory($path, $mode = 0775, $recursive = true) + { + if (is_dir($path)) { + return true; + } + $parentDir = dirname($path); + if ($recursive && !is_dir($parentDir)) { + static::createDirectory($parentDir, $mode, true); + } + $result = mkdir($path, $mode); + chmod($path, $mode); + return $result; + } +} diff --git a/framework/yii/helpers/BaseHtml.php b/framework/yii/helpers/BaseHtml.php new file mode 100644 index 0000000..2baa679 --- /dev/null +++ b/framework/yii/helpers/BaseHtml.php @@ -0,0 +1,1599 @@ + + * @since 2.0 + */ +class BaseHtml +{ + /** + * @var array list of void elements (element name => 1) + * @see http://www.w3.org/TR/html-markup/syntax.html#void-element + */ + public static $voidElements = array( + 'area' => 1, + 'base' => 1, + 'br' => 1, + 'col' => 1, + 'command' => 1, + 'embed' => 1, + 'hr' => 1, + 'img' => 1, + 'input' => 1, + 'keygen' => 1, + 'link' => 1, + 'meta' => 1, + 'param' => 1, + 'source' => 1, + 'track' => 1, + 'wbr' => 1, + ); + /** + * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes + * that are rendered by [[renderTagAttributes()]]. + */ + public static $attributeOrder = array( + 'type', + 'id', + 'class', + 'name', + 'value', + + 'href', + 'src', + 'action', + 'method', + + 'selected', + 'checked', + 'readonly', + 'disabled', + 'multiple', + + 'size', + 'maxlength', + 'width', + 'height', + 'rows', + 'cols', + + 'alt', + 'title', + 'rel', + 'media', + ); + + /** + * Encodes special characters into HTML entities. + * The [[yii\base\Application::charset|application charset]] will be used for encoding. + * @param string $content the content to be encoded + * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false, + * HTML entities in `$content` will not be further encoded. + * @return string the encoded content + * @see decode + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function encode($content, $doubleEncode = true) + { + return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset, $doubleEncode); + } + + /** + * Decodes special HTML entities back to the corresponding characters. + * This is the opposite of [[encode()]]. + * @param string $content the content to be decoded + * @return string the decoded content + * @see encode + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function decode($content) + { + return htmlspecialchars_decode($content, ENT_QUOTES); + } + + /** + * Generates a complete HTML tag. + * @param string $name the tag name + * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. + * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated HTML tag + * @see beginTag + * @see endTag + */ + public static function tag($name, $content = '', $options = array()) + { + $html = "<$name" . static::renderTagAttributes($options) . '>'; + return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content"; + } + + /** + * Generates a start tag. + * @param string $name the tag name + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated start tag + * @see endTag + * @see tag + */ + public static function beginTag($name, $options = array()) + { + return "<$name" . static::renderTagAttributes($options) . '>'; + } + + /** + * Generates an end tag. + * @param string $name the tag name + * @return string the generated end tag + * @see beginTag + * @see tag + */ + public static function endTag($name) + { + return ""; + } + + /** + * Generates a style tag. + * @param string $content the style content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/css" will be used. + * @return string the generated style tag + */ + public static function style($content, $options = array()) + { + return static::tag('style', $content, $options); + } + + /** + * Generates a script tag. + * @param string $content the script content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. + * @return string the generated script tag + */ + public static function script($content, $options = array()) + { + return static::tag('script', $content, $options); + } + + /** + * Generates a link tag that refers to an external CSS file. + * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated link tag + * @see url + */ + public static function cssFile($url, $options = array()) + { + $options['rel'] = 'stylesheet'; + $options['href'] = static::url($url); + return static::tag('link', '', $options); + } + + /** + * Generates a script tag that refers to an external JavaScript file. + * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated script tag + * @see url + */ + public static function jsFile($url, $options = array()) + { + $options['src'] = static::url($url); + return static::tag('script', '', $options); + } + + /** + * Generates a form start tag. + * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. + * @param string $method the form submission method, such as "post", "get", "put", "delete" (case-insensitive). + * Since most browsers only support "post" and "get", if other methods are given, they will + * be simulated using "post", and a hidden input will be added which contains the actual method type. + * See [[\yii\web\Request::restVar]] for more details. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated form start tag. + * @see endForm + */ + public static function beginForm($action = '', $method = 'post', $options = array()) + { + $action = static::url($action); + + $hiddenInputs = array(); + + $request = Yii::$app->getRequest(); + if ($request instanceof Request) { + if (strcasecmp($method, 'get') && strcasecmp($method, 'post')) { + // simulate PUT, DELETE, etc. via POST + $hiddenInputs[] = static::hiddenInput($request->restVar, $method); + $method = 'post'; + } + if ($request->enableCsrfValidation) { + $hiddenInputs[] = static::hiddenInput($request->csrfVar, $request->getCsrfToken()); + } + } + + if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { + // query parameters in the action are ignored for GET method + // we use hidden fields to add them back + foreach (explode('&', substr($action, $pos + 1)) as $pair) { + if (($pos1 = strpos($pair, '=')) !== false) { + $hiddenInputs[] = static::hiddenInput( + urldecode(substr($pair, 0, $pos1)), + urldecode(substr($pair, $pos1 + 1)) + ); + } else { + $hiddenInputs[] = static::hiddenInput(urldecode($pair), ''); + } + } + $action = substr($action, 0, $pos); + } + + $options['action'] = $action; + $options['method'] = $method; + $form = static::beginTag('form', $options); + if (!empty($hiddenInputs)) { + $form .= "\n" . implode("\n", $hiddenInputs); + } + + return $form; + } + + /** + * Generates a form end tag. + * @return string the generated tag + * @see beginForm + */ + public static function endForm() + { + return ''; + } + + /** + * Generates a hyperlink tag. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]] + * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute + * will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated hyperlink + * @see url + */ + public static function a($text, $url = null, $options = array()) + { + if ($url !== null) { + $options['href'] = static::url($url); + } + return static::tag('a', $text, $options); + } + + /** + * Generates a mailto hyperlink. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param string $email email address. If this is null, the first parameter (link body) will be treated + * as the email address and used. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated mailto link + */ + public static function mailto($text, $email = null, $options = array()) + { + $options['href'] = 'mailto:' . ($email === null ? $text : $email); + return static::tag('a', $text, $options); + } + + /** + * Generates an image tag. + * @param string $src the image URL. This parameter will be processed by [[url()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated image tag + */ + public static function img($src, $options = array()) + { + $options['src'] = static::url($src); + if (!isset($options['alt'])) { + $options['alt'] = ''; + } + return static::tag('img', '', $options); + } + + /** + * Generates a label tag. + * @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is is coming from end users, you should [[encode()]] + * it to prevent XSS attacks. + * @param string $for the ID of the HTML element that this label is associated with. + * If this is null, the "for" attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated label tag + */ + public static function label($content, $for = null, $options = array()) + { + $options['for'] = $for; + return static::tag('label', $content, $options); + } + + /** + * Generates a button tag. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function button($content = 'Button', $options = array()) + { + return static::tag('button', $content, $options); + } + + /** + * Generates a submit button tag. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated submit button tag + */ + public static function submitButton($content = 'Submit', $options = array()) + { + $options['type'] = 'submit'; + return static::button($content, $options); + } + + /** + * Generates a reset button tag. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated reset button tag + */ + public static function resetButton($content = 'Reset', $options = array()) + { + $options['type'] = 'reset'; + return static::button($content, $options); + } + + /** + * Generates an input type of the given type. + * @param string $type the type attribute. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated input tag + */ + public static function input($type, $name = null, $value = null, $options = array()) + { + $options['type'] = $type; + $options['name'] = $name; + $options['value'] = $value === null ? null : (string)$value; + return static::tag('input', '', $options); + } + + /** + * Generates an input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function buttonInput($label = 'Button', $options = array()) + { + $options['type'] = 'button'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a submit input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function submitInput($label = 'Submit', $options = array()) + { + $options['type'] = 'submit'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a reset input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated button tag + */ + public static function resetInput($label = 'Reset', $options = array()) + { + $options['type'] = 'reset'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a text input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function textInput($name, $value = null, $options = array()) + { + return static::input('text', $name, $value, $options); + } + + /** + * Generates a hidden input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function hiddenInput($name, $value = null, $options = array()) + { + return static::input('hidden', $name, $value, $options); + } + + /** + * Generates a password input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function passwordInput($name, $value = null, $options = array()) + { + return static::input('password', $name, $value, $options); + } + + /** + * Generates a file input field. + * To use a file input field, you should set the enclosing form's "enctype" attribute to + * be "multipart/form-data". After the form is submitted, the uploaded file information + * can be obtained via $_FILES[$name] (see PHP documentation). + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated button tag + */ + public static function fileInput($name, $value = null, $options = array()) + { + return static::input('file', $name, $value, $options); + } + + /** + * Generates a text area input. + * @param string $name the input name + * @param string $value the input value. Note that it will be encoded using [[encode()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * @return string the generated text area tag + */ + public static function textarea($name, $value = '', $options = array()) + { + $options['name'] = $name; + return static::tag('textarea', static::encode($value), $options); + } + + /** + * Generates a radio button input. + * @param string $name the name attribute. + * @param boolean $checked whether the radio button should be checked. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute + * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * When this option is specified, the radio button will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public static function radio($name, $checked = false, $options = array()) + { + $options['checked'] = (boolean)$checked; + $value = array_key_exists('value', $options) ? $options['value'] : '1'; + if (isset($options['uncheck'])) { + // add a hidden field so that if the radio button is not selected, it still submits a value + $hidden = static::hiddenInput($name, $options['uncheck']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + if (isset($options['label'])) { + $label = $options['label']; + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); + unset($options['label'], $options['labelOptions']); + $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); + return $hidden . static::tag('div', $content, array('class' => 'radio')); + } else { + return $hidden . static::input('radio', $name, $value, $options); + } + } + + /** + * Generates a checkbox input. + * @param string $name the name attribute. + * @param boolean $checked whether the checkbox should be checked. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute + * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * When this option is specified, the checkbox will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public static function checkbox($name, $checked = false, $options = array()) + { + $options['checked'] = (boolean)$checked; + $value = array_key_exists('value', $options) ? $options['value'] : '1'; + if (isset($options['uncheck'])) { + // add a hidden field so that if the checkbox is not selected, it still submits a value + $hidden = static::hiddenInput($name, $options['uncheck']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + if (isset($options['label'])) { + $label = $options['label']; + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : array(); + unset($options['label'], $options['labelOptions']); + $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); + return $hidden . static::tag('div', $content, array('class' => 'checkbox')); + } else { + return $hidden . static::input('checkbox', $name, $value, $options); + } + } + + /** + * Generates a drop-down list. + * @param string $name the input name + * @param string $selection the selected value + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public static function dropDownList($name, $selection = null, $items = array(), $options = array()) + { + $options['name'] = $name; + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list box. + * @param string $name the input name + * @param string|array $selection the selected value(s) + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public static function listBox($name, $selection = null, $items = array(), $options = array()) + { + if (!isset($options['size'])) { + $options['size'] = 4; + } + if (!empty($options['multiple']) && substr($name, -2) !== '[]') { + $name .= '[]'; + } + $options['name'] = $name; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + if (substr($name, -2) === '[]') { + $name = substr($name, 0, -2); + } + $hidden = static::hiddenInput($name, $options['unselect']); + unset($options['unselect']); + } else { + $hidden = ''; + } + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * @param string $name the name attribute of each checkbox. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * @param array $options options (name => config) for the checkbox list container tag. + * The following options are specially handled: + * + * - tag: string, the tag name of the container element. + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. + * This option is ignored if `item` option is set. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input, respectively. + * @return string the generated checkbox list + */ + public static function checkboxList($name, $selection = null, $items = array(), $options = array()) + { + if (substr($name, -2) !== '[]') { + $name .= '[]'; + } + + $formatter = isset($options['item']) ? $options['item'] : null; + $encode = !isset($options['encode']) || $options['encode']; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::checkbox($name, $checked, array( + 'value' => $value, + 'label' => $encode ? static::encode($label) : $label, + )); + } + $index++; + } + + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; + $hidden = static::hiddenInput($name2, $options['unselect']); + } else { + $hidden = ''; + } + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); + + return $hidden . static::tag($tag, implode($separator, $lines), $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * @param string $name the name attribute of each radio button. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. + * This option is ignored if `item` option is set. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input, respectively. + * @return string the generated radio button list + */ + public static function radioList($name, $selection = null, $items = array(), $options = array()) + { + $encode = !isset($options['encode']) || $options['encode']; + $formatter = isset($options['item']) ? $options['item'] : null; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::radio($name, $checked, array( + 'value' => $value, + 'label' => $encode ? static::encode($label) : $label, + )); + } + $index++; + } + + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $hidden = static::hiddenInput($name, $options['unselect']); + } else { + $hidden = ''; + } + + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item']); + + return $hidden . static::tag($tag, implode($separator, $lines), $options); + } + + /** + * Generates an unordered list. + * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. + * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - encode: boolean, whether to HTML-encode the items. Defaults to true. + * This option is ignored if the `item` option is specified. + * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. + * - item: callable, a callback that is used to generate each individual list item. + * The signature of this callback must be: + * + * ~~~ + * function ($item, $index) + * ~~~ + * + * where $index is the array key corresponding to `$item` in `$items`. The callback should return + * the whole list item tag. + * + * @return string the generated unordered list. An empty string is returned if `$items` is empty. + */ + public static function ul($items, $options = array()) + { + if (empty($items)) { + return ''; + } + $tag = isset($options['tag']) ? $options['tag'] : 'ul'; + $encode = !isset($options['encode']) || $options['encode']; + $formatter = isset($options['item']) ? $options['item'] : null; + $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : array(); + unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); + $results = array(); + foreach ($items as $index => $item) { + if ($formatter !== null) { + $results[] = call_user_func($formatter, $item, $index); + } else { + $results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions); + } + } + return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options); + } + + /** + * Generates an ordered list. + * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. + * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - encode: boolean, whether to HTML-encode the items. Defaults to true. + * This option is ignored if the `item` option is specified. + * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. + * - item: callable, a callback that is used to generate each individual list item. + * The signature of this callback must be: + * + * ~~~ + * function ($item, $index) + * ~~~ + * + * where $index is the array key corresponding to `$item` in `$items`. The callback should return + * the whole list item tag. + * + * @return string the generated ordered list. An empty string is returned if `$items` is empty. + */ + public static function ol($items, $options = array()) + { + $options['tag'] = 'ol'; + return static::ul($items, $options); + } + + /** + * Generates a label tag for the given model attribute. + * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * The following options are specially handled: + * + * - label: this specifies the label to be displayed. Note that this will NOT be [[encoded()]]. + * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display + * (after encoding). + * + * @return string the generated label tag + */ + public static function activeLabel($model, $attribute, $options = array()) + { + $attribute = static::getAttributeName($attribute); + $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); + $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); + unset($options['label'], $options['for']); + return static::label($label, $for, $options); + } + + /** + * Generates a tag that contains the first validation error of the specified model attribute. + * Note that even if there is no validation error, this method will still return an empty error tag. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The values will be HTML-encoded + * using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * The following options are specially handled: + * + * - tag: this specifies the tag name. If not set, "div" will be used. + * + * @return string the generated label tag + */ + public static function error($model, $attribute, $options = array()) + { + $attribute = static::getAttributeName($attribute); + $error = $model->getFirstError($attribute); + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag']); + return Html::tag($tag, Html::encode($error), $options); + } + + /** + * Generates an input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param string $type the input type (e.g. 'text', 'password') + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeInput($type, $model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::input($type, $name, $value, $options); + } + + /** + * Generates a text input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeTextInput($model, $attribute, $options = array()) + { + return static::activeInput('text', $model, $attribute, $options); + } + + /** + * Generates a hidden input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeHiddenInput($model, $attribute, $options = array()) + { + return static::activeInput('hidden', $model, $attribute, $options); + } + + /** + * Generates a password input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activePasswordInput($model, $attribute, $options = array()) + { + return static::activeInput('password', $model, $attribute, $options); + } + + /** + * Generates a file input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeFileInput($model, $attribute, $options = array()) + { + // add a hidden field so that if a model only has a file field, we can + // still use isset($_POST[$modelClass]) to detect if the input is submitted + return static::activeHiddenInput($model, $attribute, array('id' => null, 'value' => '')) + . static::activeInput('file', $model, $attribute, $options); + } + + /** + * Generates a textarea tag for the given model attribute. + * The model attribute value will be used as the content in the textarea. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated textarea tag + */ + public static function activeTextarea($model, $attribute, $options = array()) + { + $name = static::getInputName($model, $attribute); + $value = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::textarea($name, $value, $options); + } + + /** + * Generates a radio button tag for the given model attribute. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * When this option is specified, the radio button will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public static function activeRadio($model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('uncheck', $options)) { + $options['uncheck'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::radio($name, $checked, $options); + } + + /** + * Generates a checkbox tag for the given model attribute. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * When this option is specified, the checkbox will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public static function activeCheckbox($model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('uncheck', $options)) { + $options['uncheck'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::checkbox($name, $checked, $options); + } + + /** + * Generates a drop-down list for the given model attribute. + * The selection of the drop-down list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public static function activeDropDownList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::dropDownList($name, $checked, $items, $options); + } + + /** + * Generates a list box. + * The selection of the list box is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public static function activeListBox($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::listBox($name, $checked, $items, $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * The selection of the checkbox list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the checkbox list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input. + * @return string the generated checkbox list + */ + public static function activeCheckboxList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::checkboxList($name, $checked, $items, $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * The selection of the radio buttons is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the radio button list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input. + * @return string the generated radio button list + */ + public static function activeRadioList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::radioList($name, $checked, $items, $options); + } + + /** + * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. + * @param string|array $selection the selected value(s). This can be either a string for single selection + * or an array for multiple selections. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. + * This method will take out these elements, if any: "prompt", "options" and "groups". See more details + * in [[dropDownList()]] for the explanation of these elements. + * + * @return string the generated list options + */ + public static function renderSelectOptions($selection, $items, &$tagOptions = array()) + { + $lines = array(); + if (isset($tagOptions['prompt'])) { + $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt'])); + $lines[] = static::tag('option', $prompt, array('value' => '')); + } + + $options = isset($tagOptions['options']) ? $tagOptions['options'] : array(); + $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array(); + unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); + + foreach ($items as $key => $value) { + if (is_array($value)) { + $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); + $groupAttrs['label'] = $key; + $attrs = array('options' => $options, 'groups' => $groups); + $content = static::renderSelectOptions($selection, $value, $attrs); + $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); + } else { + $attrs = isset($options[$key]) ? $options[$key] : array(); + $attrs['value'] = (string)$key; + $attrs['selected'] = $selection !== null && + (!is_array($selection) && !strcmp($key, $selection) + || is_array($selection) && in_array($key, $selection)); + $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs); + } + } + + return implode("\n", $lines); + } + + /** + * Renders the HTML tag attributes. + * Attributes whose values are of boolean type will be treated as + * [boolean attributes](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). + * And attributes whose values are null will not be rendered. + * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. + * @return string the rendering result. If the attributes are not empty, they will be rendered + * into a string with a leading white space (so that it can be directly appended to the tag name + * in a tag. If there is no attribute, an empty string will be returned. + */ + public static function renderTagAttributes($attributes) + { + if (count($attributes) > 1) { + $sorted = array(); + foreach (static::$attributeOrder as $name) { + if (isset($attributes[$name])) { + $sorted[$name] = $attributes[$name]; + } + } + $attributes = array_merge($sorted, $attributes); + } + + $html = ''; + foreach ($attributes as $name => $value) { + if (is_bool($value)) { + if ($value) { + $html .= " $name"; + } + } elseif ($value !== null) { + $html .= " $name=\"" . static::encode($value) . '"'; + } + } + return $html; + } + + /** + * Normalizes the input parameter to be a valid URL. + * + * If the input parameter + * + * - is an empty string: the currently requested URL will be returned; + * - is a non-empty string: it will first be processed by [[Yii::getAlias()]]. If the result + * is an absolute URL, it will be returned without any change further; Otherwise, the result + * will be prefixed with [[\yii\web\Request::baseUrl]] and returned. + * - is an array: the first array element is considered a route, while the rest of the name-value + * pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]]. + * For example: `array('post/index', 'page' => 2)`, `array('index')`. + * In case there is no controller, [[\yii\web\UrlManager::createUrl()]] will be used. + * + * @param array|string $url the parameter to be used to generate a valid URL + * @return string the normalized URL + * @throws InvalidParamException if the parameter is invalid. + */ + public static function url($url) + { + if (is_array($url)) { + if (isset($url[0])) { + $route = $url[0]; + $params = array_splice($url, 1); + if (Yii::$app->controller instanceof \yii\web\Controller) { + return Yii::$app->controller->createUrl($route, $params); + } else { + return Yii::$app->getUrlManager()->createUrl($route, $params); + } + } else { + throw new InvalidParamException('The array specifying a URL must contain at least one element.'); + } + } elseif ($url === '') { + return Yii::$app->getRequest()->getUrl(); + } else { + $url = Yii::getAlias($url); + if ($url !== '' && ($url[0] === '/' || $url[0] === '#' || strpos($url, '://'))) { + return $url; + } else { + return Yii::$app->getRequest()->getBaseUrl() . '/' . $url; + } + } + } + + /** + * Adds a CSS class to the specified options. + * If the CSS class is already in the options, it will not be added again. + * @param array $options the options to be modified. + * @param string $class the CSS class to be added + */ + public static function addCssClass(&$options, $class) + { + if (isset($options['class'])) { + $classes = ' ' . $options['class'] . ' '; + if (($pos = strpos($classes, ' ' . $class . ' ')) === false) { + $options['class'] .= ' ' . $class; + } + } else { + $options['class'] = $class; + } + } + + /** + * Removes a CSS class from the specified options. + * @param array $options the options to be modified. + * @param string $class the CSS class to be removed + */ + public static function removeCssClass(&$options, $class) + { + if (isset($options['class'])) { + $classes = array_unique(preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY)); + if (($index = array_search($class, $classes)) !== false) { + unset($classes[$index]); + } + if (empty($classes)) { + unset($options['class']); + } else { + $options['class'] = implode(' ', $classes); + } + } + } + + /** + * Returns the real attribute name from the given attribute expression. + * + * An attribute expression is an attribute name prefixed and/or suffixed with array indexes. + * It is mainly used in tabular data input and/or input of array type. Below are some examples: + * + * - `[0]content` is used in tabular data input to represent the "content" attribute + * for the first model in tabular input; + * - `dates[0]` represents the first array element of the "dates" attribute; + * - `[0]dates[0]` represents the first array element of the "dates" attribute + * for the first model in tabular input. + * + * If `$attribute` has neither prefix nor suffix, it will be returned back without change. + * @param string $attribute the attribute name or expression + * @return string the attribute name without prefix and suffix. + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeName($attribute) + { + if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + return $matches[2]; + } else { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + } + + /** + * Returns the value of the specified attribute name or expression. + * + * For an attribute expression like `[0]dates[0]`, this method will return the value of `$model->dates[0]`. + * See [[getAttributeName()]] for more details about attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return mixed the corresponding attribute value + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeValue($model, $attribute) + { + if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $attribute = $matches[2]; + $index = $matches[3]; + if ($index === '') { + return $model->$attribute; + } else { + $value = $model->$attribute; + foreach (explode('][', trim($index, '[]')) as $id) { + if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { + $value = $value[$id]; + } else { + return null; + } + } + return $value; + } + } + + /** + * Generates an appropriate input name for the specified attribute name or expression. + * + * This method generates a name that can be used as the input name to collect user input + * for the specified attribute. The name is generated according to the [[Model::formName|form name]] + * of the model and the given attribute name. For example, if the form name of the `Post` model + * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. + * + * See [[getAttributeName()]] for explanation of attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return string the generated input name + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + 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.'); + } + } + + /** + * Generates an appropriate input ID for the specified attribute name or expression. + * + * This method converts the result [[getInputName()]] into a valid input ID. + * For example, if [[getInputName()]] returns `Post[content]`, this method will return `post-content`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for explanation of attribute expression. + * @return string the generated input ID + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getInputId($model, $attribute) + { + $name = strtolower(static::getInputName($model, $attribute)); + return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); + } +} diff --git a/framework/yii/helpers/BaseHtmlPurifier.php b/framework/yii/helpers/BaseHtmlPurifier.php new file mode 100644 index 0000000..17d2122 --- /dev/null +++ b/framework/yii/helpers/BaseHtmlPurifier.php @@ -0,0 +1,34 @@ + + * @since 2.0 + */ +class BaseHtmlPurifier +{ + /** + * Passes markup through HTMLPurifier making it safe to output to end user + * + * @param string $content + * @param array|null $config + * @return string + */ + public static function process($content, $config = null) + { + $configInstance = \HTMLPurifier_Config::create($config); + $configInstance->autoFinalize = false; + $purifier=\HTMLPurifier::instance($configInstance); + $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); + return $purifier->purify($content); + } +} diff --git a/framework/yii/helpers/BaseInflector.php b/framework/yii/helpers/BaseInflector.php new file mode 100644 index 0000000..affd3dd --- /dev/null +++ b/framework/yii/helpers/BaseInflector.php @@ -0,0 +1,480 @@ + + * @since 2.0 + */ +class BaseInflector +{ + /** + * @var array the rules for converting a word into its plural form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public static $plurals = array( + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(m)ove$/i' => '\1oves', + '/(f)oot$/i' => '\1eet', + '/(h)uman$/i' => '\1umans', + '/(s)tatus$/i' => '\1tatuses', + '/(s)taff$/i' => '\1taff', + '/(t)ooth$/i' => '\1eeth', + '/(quiz)$/i' => '\1zes', + '/^(ox)$/i' => '\1\2en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(p)erson$/i' => '\1eople', + '/(m)an$/i' => '\1en', + '/(c)hild$/i' => '\1hildren', + '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', + '/us$/i' => 'uses', + '/(alias)$/i' => '\1es', + '/(ax|cris|test)is$/i' => '\1es', + '/s$/' => 's', + '/^$/' => '', + '/$/' => 's', + ); + /** + * @var array the rules for converting a word into its singular form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public static $singulars = array( + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(s)tatuses$/i' => '\1tatus', + '/(f)eet$/i' => '\1oot', + '/(t)eeth$/i' => '\1ooth', + '/^(.*)(menu)s$/i' => '\1\2', + '/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias)(es)*$/i' => '\1', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(cris|ax|test)es$/i' => '\1is', + '/(shoe|slave)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/ouses$/' => 'ouse', + '/([^a])uses$/' => '\1us', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1\2ovie', + '/(s)eries$/i' => '\1\2eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/(drive)s$/i' => '\1', + '/([^fo])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(p)eople$/i' => '\1\2erson', + '/(m)en$/i' => '\1an', + '/(c)hildren$/i' => '\1\2hild', + '/(n)ews$/i' => '\1\2ews', + '/eaus$/' => 'eau', + '/^(.*us)$/' => '\\1', + '/s$/i' => '', + ); + /** + * @var array the special rules for converting a word between its plural form and singular form. + * The keys are the special words in singular form, and the values are the corresponding plural form. + */ + public static $specials = array( + 'atlas' => 'atlases', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'cafe' => 'cafes', + 'child' => 'children', + 'cookie' => 'cookies', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'curve' => 'curves', + 'foe' => 'foes', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hoof' => 'hoofs', + 'loaf' => 'loaves', + 'man' => 'men', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'penis' => 'penises', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'testis' => 'testes', + 'trilby' => 'trilbys', + 'turf' => 'turfs', + 'wave' => 'waves', + 'Amoyese' => 'Amoyese', + 'bison' => 'bison', + 'Borghese' => 'Borghese', + 'bream' => 'bream', + 'breeches' => 'breeches', + 'britches' => 'britches', + 'buffalo' => 'buffalo', + 'cantus' => 'cantus', + 'carp' => 'carp', + 'chassis' => 'chassis', + 'clippers' => 'clippers', + 'cod' => 'cod', + 'coitus' => 'coitus', + 'Congoese' => 'Congoese', + 'contretemps' => 'contretemps', + 'corps' => 'corps', + 'debris' => 'debris', + 'diabetes' => 'diabetes', + 'djinn' => 'djinn', + 'eland' => 'eland', + 'elk' => 'elk', + 'equipment' => 'equipment', + 'Faroese' => 'Faroese', + 'flounder' => 'flounder', + 'Foochowese' => 'Foochowese', + 'gallows' => 'gallows', + 'Genevese' => 'Genevese', + 'Genoese' => 'Genoese', + 'Gilbertese' => 'Gilbertese', + 'graffiti' => 'graffiti', + 'headquarters' => 'headquarters', + 'herpes' => 'herpes', + 'hijinks' => 'hijinks', + 'Hottentotese' => 'Hottentotese', + 'information' => 'information', + 'innings' => 'innings', + 'jackanapes' => 'jackanapes', + 'Kiplingese' => 'Kiplingese', + 'Kongoese' => 'Kongoese', + 'Lucchese' => 'Lucchese', + 'mackerel' => 'mackerel', + 'Maltese' => 'Maltese', + 'mews' => 'mews', + 'moose' => 'moose', + 'mumps' => 'mumps', + 'Nankingese' => 'Nankingese', + 'news' => 'news', + 'nexus' => 'nexus', + 'Niasese' => 'Niasese', + 'Pekingese' => 'Pekingese', + 'Piedmontese' => 'Piedmontese', + 'pincers' => 'pincers', + 'Pistoiese' => 'Pistoiese', + 'pliers' => 'pliers', + 'Portuguese' => 'Portuguese', + 'proceedings' => 'proceedings', + 'rabies' => 'rabies', + 'rice' => 'rice', + 'rhinoceros' => 'rhinoceros', + 'salmon' => 'salmon', + 'Sarawakese' => 'Sarawakese', + 'scissors' => 'scissors', + 'series' => 'series', + 'Shavese' => 'Shavese', + 'shears' => 'shears', + 'siemens' => 'siemens', + 'species' => 'species', + 'swine' => 'swine', + 'testes' => 'testes', + 'trousers' => 'trousers', + 'trout' => 'trout', + 'tuna' => 'tuna', + 'Vermontese' => 'Vermontese', + 'Wenchowese' => 'Wenchowese', + 'whiting' => 'whiting', + 'wildebeest' => 'wildebeest', + 'Yengeese' => 'Yengeese', + ); + /** + * @var array map of special chars and its translation. This is used by [[slug()]]. + */ + public static $transliteration = array( + '/ä|æ|ǽ/' => 'ae', + '/ö|œ/' => 'oe', + '/ü/' => 'ue', + '/Ä/' => 'Ae', + '/Ü/' => 'Ue', + '/Ö/' => 'Oe', + '/À|Á|Â|Ã|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A', + '/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a', + '/Ç|Ć|Ĉ|Ċ|Č/' => 'C', + '/ç|ć|ĉ|ċ|č/' => 'c', + '/Ð|Ď|Đ/' => 'D', + '/ð|ď|đ/' => 'd', + '/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E', + '/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e', + '/Ĝ|Ğ|Ġ|Ģ/' => 'G', + '/ĝ|ğ|ġ|ģ/' => 'g', + '/Ĥ|Ħ/' => 'H', + '/ĥ|ħ/' => 'h', + '/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I', + '/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i', + '/Ĵ/' => 'J', + '/ĵ/' => 'j', + '/Ķ/' => 'K', + '/ķ/' => 'k', + '/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L', + '/ĺ|ļ|ľ|ŀ|ł/' => 'l', + '/Ñ|Ń|Ņ|Ň/' => 'N', + '/ñ|ń|ņ|ň|ʼn/' => 'n', + '/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O', + '/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o', + '/Ŕ|Ŗ|Ř/' => 'R', + '/ŕ|ŗ|ř/' => 'r', + '/Ś|Ŝ|Ş|Ș|Š/' => 'S', + '/ś|ŝ|ş|ș|š|ſ/' => 's', + '/Ţ|Ț|Ť|Ŧ/' => 'T', + '/ţ|ț|ť|ŧ/' => 't', + '/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U', + '/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u', + '/Ý|Ÿ|Ŷ/' => 'Y', + '/ý|ÿ|ŷ/' => 'y', + '/Ŵ/' => 'W', + '/ŵ/' => 'w', + '/Ź|Ż|Ž/' => 'Z', + '/ź|ż|ž/' => 'z', + '/Æ|Ǽ/' => 'AE', + '/ß/' => 'ss', + '/IJ/' => 'IJ', + '/ij/' => 'ij', + '/Œ/' => 'OE', + '/ƒ/' => 'f' + ); + + /** + * Converts a word to its plural form. + * Note that this is for English only! + * For example, 'apple' will become 'apples', and 'child' will become 'children'. + * @param string $word the word to be pluralized + * @return string the pluralized word + */ + public static function pluralize($word) + { + if (isset(self::$specials[$word])) { + return self::$specials[$word]; + } + foreach (static::$plurals as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + return $word; + } + + /** + * Returns the singular of the $word + * @param string $word the english word to singularize + * @return string Singular noun. + */ + public static function singularize($word) + { + $result = array_search($word, self::$specials, true); + if ($result !== false) { + return $result; + } + foreach (static::$singulars as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + return $word; + } + + /** + * Converts an underscored or CamelCase word into a English + * sentence. + * @param string $words + * @param bool $ucAll whether to set all words to uppercase + * @return string + */ + public static function titleize($words, $ucAll = false) + { + $words = static::humanize(static::underscore($words), $ucAll); + return $ucAll ? ucwords($words) : ucfirst($words); + } + + /** + * Returns given word as CamelCased + * Converts a word like "send_email" to "SendEmail". It + * will remove non alphanumeric character from the word, so + * "who's online" will be converted to "WhoSOnline" + * @see variablize + * @param string $word the word to CamelCase + * @return string + */ + public static function camelize($word) + { + return str_replace(' ', '', ucwords(preg_replace('/[^A-Z^a-z^0-9]+/', ' ', $word))); + } + + /** + * Converts a CamelCase name into space-separated words. + * For example, 'PostTag' will be converted to 'Post Tag'. + * @param string $name the string to be converted + * @param boolean $ucwords whether to capitalize the first letter in each word + * @return string the resulting words + */ + public static function camel2words($name, $ucwords = true) + { + $label = trim(strtolower(str_replace(array( + '-', + '_', + '.' + ), ' ', preg_replace('/(? ' ', + '/\\s+/' => $replacement, + '/(?<=[a-z])([A-Z])/' => $replacement . '\\1', + str_replace(':rep', preg_quote($replacement, '/'), '/^[:rep]+|[:rep]+$/') => '' + ); + return preg_replace(array_keys($map), array_values($map), $string); + } + + /** + * Converts a table name to its class name. For example, converts "people" to "Person" + * @param string $tableName + * @return string + */ + public static function classify($tableName) + { + return static::camelize(static::singularize($tableName)); + } + + /** + * Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd ... + * @param int $number the number to get its ordinal value + * @return string + */ + public static function ordinalize($number) + { + if (in_array(($number % 100), range(11, 13))) { + return $number . 'th'; + } + switch ($number % 10) { + case 1: return $number . 'st'; + case 2: return $number . 'nd'; + case 3: return $number . 'rd'; + default: return $number . 'th'; + } + } +} diff --git a/framework/yii/helpers/BaseJson.php b/framework/yii/helpers/BaseJson.php new file mode 100644 index 0000000..bd6ede5 --- /dev/null +++ b/framework/yii/helpers/BaseJson.php @@ -0,0 +1,112 @@ + + * @since 2.0 + */ +class BaseJson +{ + /** + * Encodes the given value into a JSON string. + * The method enhances `json_encode()` by supporting JavaScript expressions. + * In particular, the method will not encode a JavaScript expression that is + * represented in terms of a [[JsExpression]] object. + * @param mixed $value the data to be encoded + * @param integer $options the encoding options. For more details please refer to + * [[http://www.php.net/manual/en/function.json-encode.php]] + * @return string the encoding result + */ + public static function encode($value, $options = 0) + { + $expressions = array(); + $value = static::processData($value, $expressions, uniqid()); + $json = json_encode($value, $options); + return empty($expressions) ? $json : strtr($json, $expressions); + } + + /** + * Decodes the given JSON string into a PHP data structure. + * @param string $json the JSON string to be decoded + * @param boolean $asArray whether to return objects in terms of associative arrays. + * @return mixed the PHP data + * @throws InvalidParamException if there is any decoding error + */ + public static function decode($json, $asArray = true) + { + if (is_array($json)) { + throw new InvalidParamException('Invalid JSON data.'); + } + $decode = json_decode((string)$json, $asArray); + switch (json_last_error()) { + case JSON_ERROR_NONE: + break; + case JSON_ERROR_DEPTH: + throw new InvalidParamException('The maximum stack depth has been exceeded.'); + case JSON_ERROR_CTRL_CHAR: + throw new InvalidParamException('Control character error, possibly incorrectly encoded.'); + case JSON_ERROR_SYNTAX: + throw new InvalidParamException('Syntax error.'); + case JSON_ERROR_STATE_MISMATCH: + throw new InvalidParamException('Invalid or malformed JSON.'); + case JSON_ERROR_UTF8: + throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.'); + default: + throw new InvalidParamException('Unknown JSON decoding error.'); + } + + return $decode; + } + + /** + * Pre-processes the data before sending it to `json_encode()`. + * @param mixed $data the data to be processed + * @param array $expressions collection of JavaScript expressions + * @param string $expPrefix a prefix internally used to handle JS expressions + * @return mixed the processed data + */ + protected static function processData($data, &$expressions, $expPrefix) + { + if (is_array($data)) { + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $data[$key] = static::processData($value, $expressions, $expPrefix); + } + } + return $data; + } elseif (is_object($data)) { + if ($data instanceof JsExpression) { + $token = "!{[$expPrefix=" . count($expressions) . ']}!'; + $expressions['"' . $token . '"'] = $data->expression; + return $token; + } else { + $data = $data instanceof Arrayable ? $data->toArray() : get_object_vars($data); + $result = array(); + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $result[$key] = static::processData($value, $expressions, $expPrefix); + } else { + $result[$key] = $value; + } + } + return $result; + } + } else { + return $data; + } + } +} diff --git a/framework/yii/helpers/BaseMarkdown.php b/framework/yii/helpers/BaseMarkdown.php new file mode 100644 index 0000000..40a1dc4 --- /dev/null +++ b/framework/yii/helpers/BaseMarkdown.php @@ -0,0 +1,44 @@ + + * @since 2.0 + */ +class BaseMarkdown +{ + /** + * @var MarkdownExtra + */ + protected static $markdown; + + /** + * Converts markdown into HTML + * + * @param string $content + * @param array $config + * @return string + */ + public static function process($content, $config = array()) + { + if (static::$markdown === null) { + static::$markdown = new MarkdownExtra(); + } + foreach ($config as $name => $value) { + static::$markdown->{$name} = $value; + } + return static::$markdown->transform($content); + } +} diff --git a/framework/yii/helpers/BaseSecurity.php b/framework/yii/helpers/BaseSecurity.php new file mode 100644 index 0000000..3be07b4 --- /dev/null +++ b/framework/yii/helpers/BaseSecurity.php @@ -0,0 +1,285 @@ + + * @author Tom Worster + * @since 2.0 + */ +class BaseSecurity +{ + /** + * Encrypts data. + * @param string $data data to be encrypted. + * @param string $key the encryption secret key + * @return string the encrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see decrypt() + */ + public static function encrypt($data, $key) + { + $module = static::openCryptModule(); + // 192-bit (24 bytes) key size + $key = StringHelper::substr($key, 0, 24); + srand(); + $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); + mcrypt_generic_init($module, $key, $iv); + $encrypted = $iv . mcrypt_generic($module, $data); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return $encrypted; + } + + /** + * Decrypts data + * @param string $data data to be decrypted. + * @param string $key the decryption secret key + * @return string the decrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see encrypt() + */ + public static function decrypt($data, $key) + { + $module = static::openCryptModule(); + // 192-bit (24 bytes) key size + $key = StringHelper::substr($key, 0, 24); + $ivSize = mcrypt_enc_get_iv_size($module); + $iv = StringHelper::substr($data, 0, $ivSize); + mcrypt_generic_init($module, $key, $iv); + $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); + mcrypt_generic_deinit($module); + mcrypt_module_close($module); + return rtrim($decrypted, "\0"); + } + + /** + * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. + * @param string $data the data to be protected + * @param string $key the secret key to be used for generating hash + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. + * @return string the data prefixed with the keyed hash + * @see validateData() + * @see getSecretKey() + */ + public static function hashData($data, $key, $algorithm = 'sha256') + { + return hash_hmac($algorithm, $data, $key) . $data; + } + + /** + * Validates if the given data is tampered. + * @param string $data the data to be validated. The data must be previously + * generated by [[hashData()]]. + * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. + * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()" + * function to see the supported hashing algorithms on your system. This must be the same + * as the value passed to [[hashData()]] when generating the hash for the data. + * @return string the real data with the hash stripped off. False if the data is tampered. + * @see hashData() + */ + public static function validateData($data, $key, $algorithm = 'sha256') + { + $hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key)); + $n = StringHelper::strlen($data); + if ($n >= $hashSize) { + $hash = StringHelper::substr($data, 0, $hashSize); + $data2 = StringHelper::substr($data, $hashSize, $n - $hashSize); + return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false; + } else { + return false; + } + } + + /** + * Returns a secret key associated with the specified name. + * If the secret key does not exist, a random key will be generated + * and saved in the file "keys.php" under the application's runtime directory + * so that the same secret key can be returned in future requests. + * @param string $name the name that is associated with the secret key + * @param integer $length the length of the key that should be generated if not exists + * @return string the secret key associated with the specified name + */ + public static function getSecretKey($name, $length = 32) + { + static $keys; + $keyFile = Yii::$app->getRuntimePath() . '/keys.php'; + if ($keys === null) { + $keys = array(); + if (is_file($keyFile)) { + $keys = require($keyFile); + } + } + if (!isset($keys[$name])) { + $keys[$name] = static::generateRandomKey($length); + file_put_contents($keyFile, " 30) { + throw new InvalidParamException('Hash is invalid.'); + } + + $test = crypt($password, $hash); + $n = strlen($test); + if (strlen($test) < 32 || $n !== strlen($hash)) { + return false; + } + + // Use a for-loop to compare two strings to prevent timing attacks. See: + // http://codereview.stackexchange.com/questions/13512 + $check = 0; + for ($i = 0; $i < $n; ++$i) { + $check |= (ord($test[$i]) ^ ord($hash[$i])); + } + + return $check === 0; + } + + /** + * Generates a salt that can be used to generate a password hash. + * + * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function + * requires, for the Blowfish hash algorithm, a salt string in a specific format: + * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters + * from the alphabet "./0-9A-Za-z". + * + * @param integer $cost the cost parameter + * @return string the random salt value. + * @throws InvalidParamException if the cost parameter is not between 4 and 30 + */ + protected static function generateSalt($cost = 13) + { + $cost = (int)$cost; + if ($cost < 4 || $cost > 31) { + throw new InvalidParamException('Cost must be between 4 and 31.'); + } + + // Get 20 * 8bits of pseudo-random entropy from mt_rand(). + $rand = ''; + for ($i = 0; $i < 20; ++$i) { + $rand .= chr(mt_rand(0, 255)); + } + + // Add the microtime for a little more entropy. + $rand .= microtime(); + // Mix the bits cryptographically into a 20-byte binary string. + $rand = sha1($rand, true); + // Form the prefix that specifies Blowfish algorithm and cost parameter. + $salt = sprintf("$2y$%02d$", $cost); + // Append the random salt data in the required base64 format. + $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); + return $salt; + } +} diff --git a/framework/yii/helpers/BaseStringHelper.php b/framework/yii/helpers/BaseStringHelper.php new file mode 100644 index 0000000..e1622b9 --- /dev/null +++ b/framework/yii/helpers/BaseStringHelper.php @@ -0,0 +1,138 @@ + + * @author Alex Makarov + * @since 2.0 + */ +class BaseStringHelper +{ + /** + * Returns the number of bytes in the given string. + * This method ensures the string is treated as a byte array by using `mb_strlen()`. + * @param string $string the string being measured for length + * @return integer the number of bytes in the given string. + */ + public static function strlen($string) + { + return mb_strlen($string, '8bit'); + } + + /** + * Returns the portion of string specified by the start and length parameters. + * This method ensures the string is treated as a byte array by using `mb_substr()`. + * @param string $string the input string. Must be one character or longer. + * @param integer $start the starting position + * @param integer $length the desired portion length + * @return string the extracted part of string, or FALSE on failure or an empty string. + * @see http://www.php.net/manual/en/function.substr.php + */ + public static function substr($string, $start, $length) + { + return mb_substr($string, $start, $length, '8bit'); + } + + /** + * Returns the trailing name component of a path. + * This method is similar to the php function `basename()` except that it will + * treat both \ and / as directory separators, independent of the operating system. + * This method was mainly created to work on php namespaces. When working with real + * file paths, php's `basename()` should work fine for you. + * Note: this method is not aware of the actual filesystem, or path components such as "..". + * @param string $path A path string. + * @param string $suffix If the name component ends in suffix this will also be cut off. + * @return string the trailing name component of the given path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function basename($path, $suffix = '') + { + if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) == $suffix) { + $path = mb_substr($path, 0, -$len); + } + $path = rtrim(str_replace('\\', '/', $path), '/\\'); + if (($pos = mb_strrpos($path, '/')) !== false) { + return mb_substr($path, $pos + 1); + } + return $path; + } + + /** + * Returns parent directory's path. + * This method is similar to `dirname()` except that it will treat + * both \ and / as directory separators, independent of the operating system. + * @param string $path A path string. + * @return string the parent directory's path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function dirname($path) + { + $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); + if ($pos !== false) { + return mb_substr($path, 0, $pos); + } else { + return $path; + } + } + + /** + * Compares two strings or string arrays, and return their differences. + * This is a wrapper of the [phpspec/php-diff](https://packagist.org/packages/phpspec/php-diff) package. + * @param string|array $lines1 the first string or string array to be compared. If it is a string, + * it will be converted into a string array by breaking at newlines. + * @param string|array $lines2 the second string or string array to be compared. If it is a string, + * it will be converted into a string array by breaking at newlines. + * @param string $format the output format. It must be 'inline', 'unified', 'context', 'side-by-side', or 'array'. + * @return string|array the comparison result. An array is returned if `$format` is 'array'. For all other + * formats, a string is returned. + * @throws InvalidParamException if the format is invalid. + */ + public static function diff($lines1, $lines2, $format = 'inline') + { + if (!is_array($lines1)) { + $lines1 = explode("\n", $lines1); + } + if (!is_array($lines2)) { + $lines2 = explode("\n", $lines2); + } + foreach ($lines1 as $i => $line) { + $lines1[$i] = rtrim($line, "\r\n"); + } + foreach ($lines2 as $i => $line) { + $lines2[$i] = rtrim($line, "\r\n"); + } + switch ($format) { + case 'inline': + $renderer = new \Diff_Renderer_Html_Inline(); + break; + case 'array': + $renderer = new \Diff_Renderer_Html_Array(); + break; + case 'side-by-side': + $renderer = new \Diff_Renderer_Html_SideBySide(); + break; + case 'context': + $renderer = new \Diff_Renderer_Text_Context(); + break; + case 'unified': + $renderer = new \Diff_Renderer_Text_Unified(); + break; + default: + throw new InvalidParamException("Output format must be 'inline', 'side-by-side', 'array', 'context' or 'unified'."); + } + $diff = new \Diff($lines1, $lines2); + return $diff->render($renderer); + } +} diff --git a/framework/yii/helpers/BaseVarDumper.php b/framework/yii/helpers/BaseVarDumper.php new file mode 100644 index 0000000..f109125 --- /dev/null +++ b/framework/yii/helpers/BaseVarDumper.php @@ -0,0 +1,127 @@ + + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +namespace yii\helpers; + +/** + * BaseVarDumper provides concrete implementation for [[VarDumper]]. + * + * Do not use BaseVarDumper. Use [[VarDumper]] instead. + * + * @author Qiang Xue + * @since 2.0 + */ +class BaseVarDumper +{ + private static $_objects; + private static $_output; + private static $_depth; + + /** + * Displays a variable. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + */ + public static function dump($var, $depth = 10, $highlight = false) + { + echo static::dumpAsString($var, $depth, $highlight); + } + + /** + * Dumps a variable in terms of a string. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + * @return string the string representation of the variable + */ + public static function dumpAsString($var, $depth = 10, $highlight = false) + { + self::$_output = ''; + self::$_objects = array(); + self::$_depth = $depth; + self::dumpInternal($var, 0); + if ($highlight) { + $result = highlight_string("/', '', $result, 1); + } + return self::$_output; + } + + /** + * @param mixed $var variable to be dumped + * @param integer $level depth level + */ + private static function dumpInternal($var, $level) + { + switch (gettype($var)) { + case 'boolean': + self::$_output .= $var ? 'true' : 'false'; + break; + case 'integer': + self::$_output .= "$var"; + break; + case 'double': + self::$_output .= "$var"; + break; + case 'string': + self::$_output .= "'" . addslashes($var) . "'"; + break; + case 'resource': + self::$_output .= '{resource}'; + break; + case 'NULL': + self::$_output .= "null"; + break; + case 'unknown type': + self::$_output .= '{unknown}'; + break; + case 'array': + if (self::$_depth <= $level) { + self::$_output .= 'array(...)'; + } elseif (empty($var)) { + self::$_output .= 'array()'; + } else { + $keys = array_keys($var); + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "array\n" . $spaces . '('; + foreach ($keys as $key) { + self::$_output .= "\n" . $spaces . ' '; + self::dumpInternal($key, 0); + self::$_output .= ' => '; + self::dumpInternal($var[$key], $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + case 'object': + if (($id = array_search($var, self::$_objects, true)) !== false) { + self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; + } elseif (self::$_depth <= $level) { + self::$_output .= get_class($var) . '(...)'; + } else { + $id = array_push(self::$_objects, $var); + $className = get_class($var); + $members = (array)$var; + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "$className#$id\n" . $spaces . '('; + foreach ($members as $key => $value) { + $keyDisplay = strtr(trim($key), array("\0" => ':')); + self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; + self::dumpInternal($value, $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + } + } +} diff --git a/framework/yii/helpers/Console.php b/framework/yii/helpers/Console.php index 9b0656e..a34dc96 100644 --- a/framework/yii/helpers/Console.php +++ b/framework/yii/helpers/Console.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Carsten Brandt * @since 2.0 */ -class Console extends AbstractConsole +class Console extends BaseConsole { } diff --git a/framework/yii/helpers/FileHelper.php b/framework/yii/helpers/FileHelper.php index 919dc09..63954a4 100644 --- a/framework/yii/helpers/FileHelper.php +++ b/framework/yii/helpers/FileHelper.php @@ -16,6 +16,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class FileHelper extends AbstractFileHelper +class FileHelper extends BaseFileHelper { } diff --git a/framework/yii/helpers/Html.php b/framework/yii/helpers/Html.php index 0715c6c..f4fbbba 100644 --- a/framework/yii/helpers/Html.php +++ b/framework/yii/helpers/Html.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class Html extends AbstractHtml +class Html extends BaseHtml { } diff --git a/framework/yii/helpers/HtmlPurifier.php b/framework/yii/helpers/HtmlPurifier.php index ca7e485..e1511e4 100644 --- a/framework/yii/helpers/HtmlPurifier.php +++ b/framework/yii/helpers/HtmlPurifier.php @@ -32,6 +32,6 @@ namespace yii\helpers; * @author Alexander Makarov * @since 2.0 */ -class HtmlPurifier extends AbstractHtmlPurifier +class HtmlPurifier extends BaseHtmlPurifier { } diff --git a/framework/yii/helpers/Inflector.php b/framework/yii/helpers/Inflector.php index 71e7f05..ab4713e 100644 --- a/framework/yii/helpers/Inflector.php +++ b/framework/yii/helpers/Inflector.php @@ -13,6 +13,6 @@ namespace yii\helpers; * @author Antonio Ramirez * @since 2.0 */ -class Inflector extends AbstractInflector +class Inflector extends BaseInflector { } diff --git a/framework/yii/helpers/Json.php b/framework/yii/helpers/Json.php index 8544a61..8ca436a 100644 --- a/framework/yii/helpers/Json.php +++ b/framework/yii/helpers/Json.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class Json extends AbstractJson +class Json extends BaseJson { } diff --git a/framework/yii/helpers/Markdown.php b/framework/yii/helpers/Markdown.php index 89f8801..3dcc750 100644 --- a/framework/yii/helpers/Markdown.php +++ b/framework/yii/helpers/Markdown.php @@ -30,6 +30,6 @@ namespace yii\helpers; * @author Alexander Makarov * @since 2.0 */ -class Markdown extends AbstractMarkdown +class Markdown extends BaseMarkdown { } diff --git a/framework/yii/helpers/Security.php b/framework/yii/helpers/Security.php index e2c0314..0e3ee38 100644 --- a/framework/yii/helpers/Security.php +++ b/framework/yii/helpers/Security.php @@ -24,6 +24,6 @@ namespace yii\helpers; * @author Tom Worster * @since 2.0 */ -class Security extends AbstractSecurity +class Security extends BaseSecurity { } diff --git a/framework/yii/helpers/StringHelper.php b/framework/yii/helpers/StringHelper.php index e367c59..5ecd390 100644 --- a/framework/yii/helpers/StringHelper.php +++ b/framework/yii/helpers/StringHelper.php @@ -14,6 +14,6 @@ namespace yii\helpers; * @author Alex Makarov * @since 2.0 */ -class StringHelper extends AbstractStringHelper +class StringHelper extends BaseStringHelper { } diff --git a/framework/yii/helpers/VarDumper.php b/framework/yii/helpers/VarDumper.php index 0aae16e..1ac5aa7 100644 --- a/framework/yii/helpers/VarDumper.php +++ b/framework/yii/helpers/VarDumper.php @@ -23,6 +23,6 @@ namespace yii\helpers; * @author Qiang Xue * @since 2.0 */ -class VarDumper extends AbstractVarDumper +class VarDumper extends BaseVarDumper { } diff --git a/framework/yii/log/Logger.php b/framework/yii/log/Logger.php index 76354d4..2046ecc 100644 --- a/framework/yii/log/Logger.php +++ b/framework/yii/log/Logger.php @@ -220,7 +220,7 @@ class Logger extends Component * Returns the total elapsed time since the start of the current request. * This method calculates the difference between now and the timestamp * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning - * of [[AbstractYii]] class file. + * of [[BaseYii]] class file. * @return float the total elapsed time in seconds for current request. */ public function getElapsedTime() From fc75ab87be5008807c529e0cb28a7911a71261d7 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 22:42:42 +0400 Subject: [PATCH 148/157] =?UTF-8?q?Renamed=20ListViewBase=20=E2=86=92=20Ba?= =?UTF-8?q?seListView?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- framework/yii/classes.php | 2 +- framework/yii/grid/GridView.php | 4 +- framework/yii/widgets/BaseListView.php | 191 +++++++++++++++++++++++++++++++++ framework/yii/widgets/ListView.php | 2 +- framework/yii/widgets/ListViewBase.php | 191 --------------------------------- 5 files changed, 195 insertions(+), 195 deletions(-) create mode 100644 framework/yii/widgets/BaseListView.php delete mode 100644 framework/yii/widgets/ListViewBase.php diff --git a/framework/yii/classes.php b/framework/yii/classes.php index d4f304c..1469910 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -235,7 +235,7 @@ return array( 'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.php', 'yii\widgets\LinkSorter' => YII_PATH . '/widgets/LinkSorter.php', 'yii\widgets\ListView' => YII_PATH . '/widgets/ListView.php', - 'yii\widgets\ListViewBase' => YII_PATH . '/widgets/ListViewBase.php', + 'yii\widgets\BaseListView' => YII_PATH . '/widgets/BaseListView.php', 'yii\widgets\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php', 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php', 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php', diff --git a/framework/yii/grid/GridView.php b/framework/yii/grid/GridView.php index a783a75..f4433bc 100644 --- a/framework/yii/grid/GridView.php +++ b/framework/yii/grid/GridView.php @@ -14,13 +14,13 @@ use yii\base\InvalidConfigException; use yii\base\Widget; use yii\db\ActiveRecord; use yii\helpers\Html; -use yii\widgets\ListViewBase; +use yii\widgets\BaseListView; /** * @author Qiang Xue * @since 2.0 */ -class GridView extends ListViewBase +class GridView extends BaseListView { const FILTER_POS_HEADER = 'header'; const FILTER_POS_FOOTER = 'footer'; diff --git a/framework/yii/widgets/BaseListView.php b/framework/yii/widgets/BaseListView.php new file mode 100644 index 0000000..7268fbc --- /dev/null +++ b/framework/yii/widgets/BaseListView.php @@ -0,0 +1,191 @@ + + * @since 2.0 + */ +abstract class BaseListView extends Widget +{ + /** + * @var array the HTML attributes for the container tag of the list view. + * The "tag" element specifies the tag name of the container element and defaults to "div". + */ + public $options = array(); + /** + * @var \yii\data\DataProviderInterface the data provider for the view. This property is required. + */ + public $dataProvider; + /** + * @var array the configuration for the pager widget. By default, [[LinkPager]] will be + * used to render the pager. You can use a different widget class by configuring the "class" element. + */ + public $pager = array(); + /** + * @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be + * used to render the sorter. You can use a different widget class by configuring the "class" element. + */ + public $sorter = array(); + /** + * @var string the HTML content to be displayed as the summary of the list view. + * If you do not want to show the summary, you may set it with an empty string. + * + * The following tokens will be replaced with the corresponding values: + * + * - `{begin}`: the starting row number (1-based) currently being displayed + * - `{end}`: the ending row number (1-based) currently being displayed + * - `{count}`: the number of rows currently being displayed + * - `{totalCount}`: the total number of rows available + * - `{page}`: the page number (1-based) current being displayed + * - `{pageCount}`: the number of pages available + */ + public $summary; + /** + * @var string|boolean the HTML content to be displayed when [[dataProvider]] does not have any data. + * If false, the list view will still be displayed (without body content though). + */ + public $empty; + /** + * @var string the layout that determines how different sections of the list view should be organized. + * The following tokens will be replaced with the corresponding section contents: + * + * - `{summary}`: the summary section. See [[renderSummary()]]. + * - `{items}`: the list items. See [[renderItems()]]. + * - `{sorter}`: the sorter. See [[renderSorter()]]. + * - `{pager}`: the pager. See [[renderPager()]]. + */ + public $layout = "{summary}\n{items}\n{pager}"; + + + /** + * Renders the data models. + * @return string the rendering result. + */ + abstract public function renderItems(); + + /** + * Initializes the view. + */ + public function init() + { + if ($this->dataProvider === null) { + throw new InvalidConfigException('The "dataProvider" property must be set.'); + } + } + + /** + * Runs the widget. + */ + public function run() + { + if ($this->dataProvider->getCount() > 0 || $this->empty === false) { + $widget = $this; + $content = preg_replace_callback("/{\\w+}/", function ($matches) use ($widget) { + $content = $widget->renderSection($matches[0]); + return $content === false ? $matches[0] : $content; + }, $this->layout); + } else { + $content = '
' . ($this->empty === null ? Yii::t('yii', 'No results found.') : $this->empty) . '
'; + } + $tag = ArrayHelper::remove($this->options, 'tag', 'div'); + echo Html::tag($tag, $content, $this->options); + } + + /** + * Renders a section of the specified name. + * If the named section is not supported, false will be returned. + * @param string $name the section name, e.g., `{summary}`, `{items}`. + * @return string|boolean the rendering result of the section, or false if the named section is not supported. + */ + public function renderSection($name) + { + switch ($name) { + case '{summary}': + return $this->renderSummary(); + case '{items}': + return $this->renderItems(); + case '{pager}': + return $this->renderPager(); + case '{sorter}': + return $this->renderSorter(); + default: + return false; + } + } + + /** + * Renders the summary text. + */ + public function renderSummary() + { + $count = $this->dataProvider->getCount(); + if (($pagination = $this->dataProvider->getPagination()) !== false) { + $totalCount = $this->dataProvider->getTotalCount(); + $begin = $pagination->getPage() * $pagination->pageSize + 1; + $end = $begin + $count - 1; + $page = $pagination->getPage() + 1; + $pageCount = $pagination->pageCount; + if (($summaryContent = $this->summary) === null) { + $summaryContent = '
' . Yii::t('yii', 'Total 1 item.|Showing {begin}-{end} of {totalCount} items.', $totalCount) . '
'; + } + } else { + $begin = $page = $pageCount = 1; + $end = $totalCount = $count; + if (($summaryContent = $this->summary) === null) { + $summaryContent = '
' . Yii::t('yii', 'Total 1 item.|Total {count} items.', $count) . '
'; + } + } + return strtr($summaryContent, array( + '{begin}' => $begin, + '{end}' => $end, + '{count}' => $count, + '{totalCount}' => $totalCount, + '{page}' => $page, + '{pageCount}' => $pageCount, + )); + } + + /** + * Renders the pager. + * @return string the rendering result + */ + public function renderPager() + { + $pagination = $this->dataProvider->getPagination(); + if ($pagination === false || $this->dataProvider->getCount() <= 0) { + return ''; + } + /** @var LinkPager $class */ + $class = ArrayHelper::remove($this->pager, 'class', LinkPager::className()); + $this->pager['pagination'] = $pagination; + return $class::widget($this->pager); + } + + /** + * Renders the sorter. + * @return string the rendering result + */ + public function renderSorter() + { + $sort = $this->dataProvider->getSort(); + if ($sort === false || empty($sort->attributes) || $this->dataProvider->getCount() <= 0) { + return ''; + } + /** @var LinkSorter $class */ + $class = ArrayHelper::remove($this->sorter, 'class', LinkSorter::className()); + $this->sorter['sort'] = $sort; + return $class::widget($this->sorter); + } +} diff --git a/framework/yii/widgets/ListView.php b/framework/yii/widgets/ListView.php index c191389..1d8745d 100644 --- a/framework/yii/widgets/ListView.php +++ b/framework/yii/widgets/ListView.php @@ -16,7 +16,7 @@ use yii\helpers\Html; * @author Qiang Xue * @since 2.0 */ -class ListView extends ListViewBase +class ListView extends BaseListView { /** * @var array the HTML attributes for the container of the rendering result of each data model. diff --git a/framework/yii/widgets/ListViewBase.php b/framework/yii/widgets/ListViewBase.php deleted file mode 100644 index 33186ae..0000000 --- a/framework/yii/widgets/ListViewBase.php +++ /dev/null @@ -1,191 +0,0 @@ - - * @since 2.0 - */ -abstract class ListViewBase extends Widget -{ - /** - * @var array the HTML attributes for the container tag of the list view. - * The "tag" element specifies the tag name of the container element and defaults to "div". - */ - public $options = array(); - /** - * @var \yii\data\DataProviderInterface the data provider for the view. This property is required. - */ - public $dataProvider; - /** - * @var array the configuration for the pager widget. By default, [[LinkPager]] will be - * used to render the pager. You can use a different widget class by configuring the "class" element. - */ - public $pager = array(); - /** - * @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be - * used to render the sorter. You can use a different widget class by configuring the "class" element. - */ - public $sorter = array(); - /** - * @var string the HTML content to be displayed as the summary of the list view. - * If you do not want to show the summary, you may set it with an empty string. - * - * The following tokens will be replaced with the corresponding values: - * - * - `{begin}`: the starting row number (1-based) currently being displayed - * - `{end}`: the ending row number (1-based) currently being displayed - * - `{count}`: the number of rows currently being displayed - * - `{totalCount}`: the total number of rows available - * - `{page}`: the page number (1-based) current being displayed - * - `{pageCount}`: the number of pages available - */ - public $summary; - /** - * @var string|boolean the HTML content to be displayed when [[dataProvider]] does not have any data. - * If false, the list view will still be displayed (without body content though). - */ - public $empty; - /** - * @var string the layout that determines how different sections of the list view should be organized. - * The following tokens will be replaced with the corresponding section contents: - * - * - `{summary}`: the summary section. See [[renderSummary()]]. - * - `{items}`: the list items. See [[renderItems()]]. - * - `{sorter}`: the sorter. See [[renderSorter()]]. - * - `{pager}`: the pager. See [[renderPager()]]. - */ - public $layout = "{summary}\n{items}\n{pager}"; - - - /** - * Renders the data models. - * @return string the rendering result. - */ - abstract public function renderItems(); - - /** - * Initializes the view. - */ - public function init() - { - if ($this->dataProvider === null) { - throw new InvalidConfigException('The "dataProvider" property must be set.'); - } - } - - /** - * Runs the widget. - */ - public function run() - { - if ($this->dataProvider->getCount() > 0 || $this->empty === false) { - $widget = $this; - $content = preg_replace_callback("/{\\w+}/", function ($matches) use ($widget) { - $content = $widget->renderSection($matches[0]); - return $content === false ? $matches[0] : $content; - }, $this->layout); - } else { - $content = '
' . ($this->empty === null ? Yii::t('yii', 'No results found.') : $this->empty) . '
'; - } - $tag = ArrayHelper::remove($this->options, 'tag', 'div'); - echo Html::tag($tag, $content, $this->options); - } - - /** - * Renders a section of the specified name. - * If the named section is not supported, false will be returned. - * @param string $name the section name, e.g., `{summary}`, `{items}`. - * @return string|boolean the rendering result of the section, or false if the named section is not supported. - */ - public function renderSection($name) - { - switch ($name) { - case '{summary}': - return $this->renderSummary(); - case '{items}': - return $this->renderItems(); - case '{pager}': - return $this->renderPager(); - case '{sorter}': - return $this->renderSorter(); - default: - return false; - } - } - - /** - * Renders the summary text. - */ - public function renderSummary() - { - $count = $this->dataProvider->getCount(); - if (($pagination = $this->dataProvider->getPagination()) !== false) { - $totalCount = $this->dataProvider->getTotalCount(); - $begin = $pagination->getPage() * $pagination->pageSize + 1; - $end = $begin + $count - 1; - $page = $pagination->getPage() + 1; - $pageCount = $pagination->pageCount; - if (($summaryContent = $this->summary) === null) { - $summaryContent = '
' . Yii::t('yii', 'Total 1 item.|Showing {begin}-{end} of {totalCount} items.', $totalCount) . '
'; - } - } else { - $begin = $page = $pageCount = 1; - $end = $totalCount = $count; - if (($summaryContent = $this->summary) === null) { - $summaryContent = '
' . Yii::t('yii', 'Total 1 item.|Total {count} items.', $count) . '
'; - } - } - return strtr($summaryContent, array( - '{begin}' => $begin, - '{end}' => $end, - '{count}' => $count, - '{totalCount}' => $totalCount, - '{page}' => $page, - '{pageCount}' => $pageCount, - )); - } - - /** - * Renders the pager. - * @return string the rendering result - */ - public function renderPager() - { - $pagination = $this->dataProvider->getPagination(); - if ($pagination === false || $this->dataProvider->getCount() <= 0) { - return ''; - } - /** @var LinkPager $class */ - $class = ArrayHelper::remove($this->pager, 'class', LinkPager::className()); - $this->pager['pagination'] = $pagination; - return $class::widget($this->pager); - } - - /** - * Renders the sorter. - * @return string the rendering result - */ - public function renderSorter() - { - $sort = $this->dataProvider->getSort(); - if ($sort === false || empty($sort->attributes) || $this->dataProvider->getCount() <= 0) { - return ''; - } - /** @var LinkSorter $class */ - $class = ArrayHelper::remove($this->sorter, 'class', LinkSorter::className()); - $this->sorter['sort'] = $sort; - return $class::widget($this->sorter); - } -} From 40629ca49bef1a864b2d94593335eac7c927b084 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 28 Sep 2013 23:30:54 +0400 Subject: [PATCH 149/157] Added missing beforeCopy option to FileHelper::copyDirectory It was mentioned in AssetManager::publish phpdoc. --- framework/yii/helpers/BaseFileHelper.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/yii/helpers/BaseFileHelper.php index 2c911b0..c71a1a9 100644 --- a/framework/yii/helpers/BaseFileHelper.php +++ b/framework/yii/helpers/BaseFileHelper.php @@ -155,6 +155,11 @@ class BaseFileHelper * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches * both '/' and '\' in the paths. * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. + * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. + * This option is used only when publishing a directory. If the callback returns false, the copy + * operation for the sub-directory or file will be cancelled. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file to be copied from, while `$to` is the copy target. * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or * file copied from, while `$to` is the copy target. @@ -173,6 +178,9 @@ class BaseFileHelper $from = $src . DIRECTORY_SEPARATOR . $file; $to = $dst . DIRECTORY_SEPARATOR . $file; if (static::filterPath($from, $options)) { + if (isset($options['beforeCopy'])) { + call_user_func($options['beforeCopy'], $from, $to); + } if (is_file($from)) { copy($from, $to); if (isset($options['fileMode'])) { From ba1496cd508ca003bff8ffbae5e1d055a652e5b0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 28 Sep 2013 20:11:24 -0400 Subject: [PATCH 150/157] doc fix. --- framework/yii/web/Response.php | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index e6505fd..f0d506b 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -559,7 +559,20 @@ class Response extends \yii\base\Response /** * Redirects the browser to the specified URL. * - * This method will send out a "Location" header to achieve the redirection. + * This method adds a "Location" header to the current response. Note that it does not send out + * the header until [[send()]] is called. In a controller action you may use this method as follows: + * + * ~~~ + * return Yii::$app->getResponse()->redirect($url); + * ~~~ + * + * In other places, if you want to send out the "Location" header immediately, you should use + * the following code: + * + * ~~~ + * Yii::$app->getResponse()->redirect($url)->send(); + * return; + * ~~~ * * In AJAX mode, this normally will not work as expected unless there are some * client-side JavaScript code handling the redirection. To help achieve this goal, @@ -578,12 +591,6 @@ class Response extends \yii\base\Response * }); * ~~~ * - * In a controller action you may use this method like this: - * - * ~~~ - * return Yii::$app->getResponse()->redirect($url); - * ~~~ - * * @param string|array $url the URL to be redirected to. This can be in one of the following formats: * * - a string representing a URL (e.g. "http://example.com") From efef0e52ca41ed16f52eefae8c24a1103ee020f6 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 28 Sep 2013 20:13:48 -0400 Subject: [PATCH 151/157] Fixed beforeCopy option. --- framework/yii/helpers/BaseFileHelper.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/yii/helpers/BaseFileHelper.php index c71a1a9..caed59f 100644 --- a/framework/yii/helpers/BaseFileHelper.php +++ b/framework/yii/helpers/BaseFileHelper.php @@ -156,8 +156,7 @@ class BaseFileHelper * both '/' and '\' in the paths. * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. - * This option is used only when publishing a directory. If the callback returns false, the copy - * operation for the sub-directory or file will be cancelled. + * If the callback returns false, the copy operation for the sub-directory or file will be cancelled. * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or * file to be copied from, while `$to` is the copy target. * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. @@ -178,8 +177,8 @@ class BaseFileHelper $from = $src . DIRECTORY_SEPARATOR . $file; $to = $dst . DIRECTORY_SEPARATOR . $file; if (static::filterPath($from, $options)) { - if (isset($options['beforeCopy'])) { - call_user_func($options['beforeCopy'], $from, $to); + if (!isset($options['beforeCopy']) || !call_user_func($options['beforeCopy'], $from, $to)) { + continue; } if (is_file($from)) { copy($from, $to); From 6dc69e68b55e1eece655c4716f0969693b4aba0e Mon Sep 17 00:00:00 2001 From: ekerazha Date: Sun, 29 Sep 2013 17:21:41 +0200 Subject: [PATCH 152/157] Add data padding and key derivation. --- framework/yii/helpers/BaseSecurity.php | 85 ++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 9 deletions(-) diff --git a/framework/yii/helpers/BaseSecurity.php b/framework/yii/helpers/BaseSecurity.php index 3be07b4..41caade 100644 --- a/framework/yii/helpers/BaseSecurity.php +++ b/framework/yii/helpers/BaseSecurity.php @@ -24,20 +24,40 @@ use yii\base\InvalidParamException; class BaseSecurity { /** + * Uses AES, block size is 128-bit (16 bytes). + */ + const CRYPT_BLOCK_SIZE = 16; + + /** + * Uses AES-192, key size is 192-bit (24 bytes). + */ + const CRYPT_KEY_SIZE = 24; + + /** + * Uses SHA-256. + */ + const DERIVATION_HASH = 'sha256'; + + /** + * Uses 1000 iterations. + */ + const DERIVATION_ITERATIONS = 1000; + + /** * Encrypts data. * @param string $data data to be encrypted. - * @param string $key the encryption secret key + * @param string $password the encryption password * @return string the encrypted data * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized * @see decrypt() */ - public static function encrypt($data, $key) + public static function encrypt($data, $password) { $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); + $data = static::addPadding($data); srand(); $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND); + $key = static::deriveKey($password, $iv); mcrypt_generic_init($module, $key, $iv); $encrypted = $iv . mcrypt_generic($module, $data); mcrypt_generic_deinit($module); @@ -48,23 +68,70 @@ class BaseSecurity /** * Decrypts data * @param string $data data to be decrypted. - * @param string $key the decryption secret key + * @param string $password the decryption password * @return string the decrypted data * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized * @see encrypt() */ - public static function decrypt($data, $key) + public static function decrypt($data, $password) { $module = static::openCryptModule(); - // 192-bit (24 bytes) key size - $key = StringHelper::substr($key, 0, 24); $ivSize = mcrypt_enc_get_iv_size($module); $iv = StringHelper::substr($data, 0, $ivSize); + $key = static::deriveKey($password, $iv); mcrypt_generic_init($module, $key, $iv); $decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data))); mcrypt_generic_deinit($module); mcrypt_module_close($module); - return rtrim($decrypted, "\0"); + return static::stripPadding($decrypted); + } + + /** + * Adds a padding to the given data (PKCS #7). + * @param string $data the data to pad + * @return string the padded data + */ + protected static function addPadding($data) + { + $pad = self::CRYPT_BLOCK_SIZE - (StringHelper::strlen($data) % self::CRYPT_BLOCK_SIZE); + return $data . str_repeat(chr($pad), $pad); + } + + /** + * Strips the padding from the given data. + * @param string $data the data to trim + * @return string the trimmed data + */ + protected static function stripPadding($data) + { + $end = StringHelper::substr($data, -1); + $last = ord($end); + $n = StringHelper::strlen($data) - $last; + if (StringHelper::substr($data, $n) == str_repeat($end, $last)) { + return StringHelper::substr($data, 0, $n); + } + return false; + } + + /** + * Derives a key from the given password (PBKDF2). + * @param string $password the source password + * @param string $salt the random salt + * @param int $iterations the number of iterations + * @return string the derived key + */ + protected static function deriveKey($password, $salt) + { + if (function_exists('hash_pbkdf2')) { + return hash_pbkdf2(self::DERIVATION_HASH, $password, $salt, self::DERIVATION_ITERATIONS, self::CRYPT_KEY_SIZE, true); + } + $hmac = hash_hmac(self::DERIVATION_HASH, $salt . pack('N', 1), $password, true); + $xorsum = $hmac; + for ($i = 1; $i < self::DERIVATION_ITERATIONS; $i++) { + $hmac = hash_hmac(self::DERIVATION_HASH, $hmac, $password, true); + $xorsum ^= $hmac; + } + return substr($xorsum, 0, self::CRYPT_KEY_SIZE); } /** From 5d7c37bd7ea2d0e467fabeff312bd184c6216d7d Mon Sep 17 00:00:00 2001 From: Jin Hu Date: Sun, 29 Sep 2013 23:27:14 +0800 Subject: [PATCH 153/157] Incorrect array representation when has previous --- framework/yii/base/Exception.php | 8 ++++---- tests/unit/framework/base/ExceptionTest.php | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 tests/unit/framework/base/ExceptionTest.php diff --git a/framework/yii/base/Exception.php b/framework/yii/base/Exception.php index 4f66e53..b771490 100644 --- a/framework/yii/base/Exception.php +++ b/framework/yii/base/Exception.php @@ -41,10 +41,10 @@ class Exception extends \Exception implements Arrayable { if ($exception instanceof self) { $array = array( - 'type' => get_class($this), - 'name' => $this->getName(), - 'message' => $this->getMessage(), - 'code' => $this->getCode(), + 'type' => get_class($exception), + 'name' => $exception->getName(), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), ); } else { $array = array( diff --git a/tests/unit/framework/base/ExceptionTest.php b/tests/unit/framework/base/ExceptionTest.php new file mode 100644 index 0000000..af4293a --- /dev/null +++ b/tests/unit/framework/base/ExceptionTest.php @@ -0,0 +1,23 @@ +toArray(); + $this->assertEquals('bar', $array['message']); + $this->assertEquals('foo', $array['previous']['message']); + + $e = new InvalidCallException('bar', 0 ,new UserException('foo')); + $array = $e->toArray(); + $this->assertEquals('bar', $array['message']); + $this->assertEquals('foo', $array['previous']['message']); + } +} From cb7921b8a5ecd391076f869fd344b78f789adf70 Mon Sep 17 00:00:00 2001 From: ekerazha Date: Sun, 29 Sep 2013 17:59:31 +0200 Subject: [PATCH 154/157] Fix StringHelper::substr() call. --- framework/yii/helpers/BaseSecurity.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/helpers/BaseSecurity.php b/framework/yii/helpers/BaseSecurity.php index 41caade..ca42d37 100644 --- a/framework/yii/helpers/BaseSecurity.php +++ b/framework/yii/helpers/BaseSecurity.php @@ -104,10 +104,10 @@ class BaseSecurity */ protected static function stripPadding($data) { - $end = StringHelper::substr($data, -1); + $end = StringHelper::substr($data, -1, NULL); $last = ord($end); $n = StringHelper::strlen($data) - $last; - if (StringHelper::substr($data, $n) == str_repeat($end, $last)) { + if (StringHelper::substr($data, $n, NULL) == str_repeat($end, $last)) { return StringHelper::substr($data, 0, $n); } return false; From 9e9b3548db5544c2b3df47985601c95fe3de6011 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 29 Sep 2013 12:06:00 -0400 Subject: [PATCH 155/157] refactor Exception::toArrayRecursive(). --- framework/yii/base/Exception.php | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/framework/yii/base/Exception.php b/framework/yii/base/Exception.php index b771490..64f1d1b 100644 --- a/framework/yii/base/Exception.php +++ b/framework/yii/base/Exception.php @@ -39,21 +39,12 @@ class Exception extends \Exception implements Arrayable */ protected function toArrayRecursive($exception) { - if ($exception instanceof self) { - $array = array( - 'type' => get_class($exception), - 'name' => $exception->getName(), - 'message' => $exception->getMessage(), - 'code' => $exception->getCode(), - ); - } else { - $array = array( - 'type' => get_class($exception), - 'name' => 'Exception', - 'message' => $exception->getMessage(), - 'code' => $exception->getCode(), - ); - } + $array = array( + 'type' => get_class($exception), + 'name' => $exception instanceof self ? $exception->getName() : 'Exception', + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + ); if (($prev = $exception->getPrevious()) !== null) { $array['previous'] = $this->toArrayRecursive($prev); } From c4f4e52a5aaadd3369865ac2151dc965c675ecbf Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 29 Sep 2013 12:16:20 -0400 Subject: [PATCH 156/157] fixed test break. --- framework/yii/helpers/BaseFileHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/yii/helpers/BaseFileHelper.php index caed59f..7540168 100644 --- a/framework/yii/helpers/BaseFileHelper.php +++ b/framework/yii/helpers/BaseFileHelper.php @@ -177,7 +177,7 @@ class BaseFileHelper $from = $src . DIRECTORY_SEPARATOR . $file; $to = $dst . DIRECTORY_SEPARATOR . $file; if (static::filterPath($from, $options)) { - if (!isset($options['beforeCopy']) || !call_user_func($options['beforeCopy'], $from, $to)) { + if (isset($options['beforeCopy']) && !call_user_func($options['beforeCopy'], $from, $to)) { continue; } if (is_file($from)) { From 64a33e78498197796cbda9150ec0cf3d846801f8 Mon Sep 17 00:00:00 2001 From: ekerazha Date: Sun, 29 Sep 2013 18:18:59 +0200 Subject: [PATCH 157/157] Fix phpdoc. --- framework/yii/helpers/BaseSecurity.php | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/yii/helpers/BaseSecurity.php b/framework/yii/helpers/BaseSecurity.php index ca42d37..a1b0ec4 100644 --- a/framework/yii/helpers/BaseSecurity.php +++ b/framework/yii/helpers/BaseSecurity.php @@ -117,7 +117,6 @@ class BaseSecurity * Derives a key from the given password (PBKDF2). * @param string $password the source password * @param string $salt the random salt - * @param int $iterations the number of iterations * @return string the derived key */ protected static function deriveKey($password, $salt)