From 3f76e39a66d7aa314bc8cb9fd7b10b465551c53a Mon Sep 17 00:00:00 2001 From: Wolfgang Mattis Date: Sat, 19 Nov 2016 13:15:21 +0100 Subject: [PATCH] On branch master Initial commit new file: LICENSE new file: README.md new file: YasheenaGiiAsset.php new file: assets/yasheenaGii.css new file: assets/yasheenaGii.js new file: composer.json new file: generators/crud/Generator.php new file: generators/crud/default/_controller.php new file: generators/crud/default/controller.php new file: generators/crud/default/search.php new file: generators/crud/default/views/_form.php new file: generators/crud/default/views/_index.php new file: generators/crud/default/views/create.php new file: generators/crud/default/views/index.php new file: generators/crud/default/views/update.php new file: generators/crud/default/views/view.php new file: generators/crud/form.php new file: generators/crud/valuestore.php --- LICENSE | 21 + README.md | 35 + YasheenaGiiAsset.php | 19 + assets/yasheenaGii.css | 78 ++ assets/yasheenaGii.js | 46 ++ composer.json | 22 + generators/crud/Generator.php | 932 +++++++++++++++++++++++ generators/crud/default/_controller.php | 96 +++ generators/crud/default/controller.php | 176 +++++ generators/crud/default/search.php | 87 +++ generators/crud/default/views/_form.php | 42 + generators/crud/default/views/_index.php | 305 ++++++++ generators/crud/default/views/create.php | 30 + generators/crud/default/views/index.php | 44 ++ generators/crud/default/views/update.php | 32 + generators/crud/default/views/view.php | 60 ++ generators/crud/form.php | 93 +++ generators/crud/valuestore.php | 147 ++++ 18 files changed, 2265 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 YasheenaGiiAsset.php create mode 100644 assets/yasheenaGii.css create mode 100644 assets/yasheenaGii.js create mode 100644 composer.json create mode 100644 generators/crud/Generator.php create mode 100644 generators/crud/default/_controller.php create mode 100644 generators/crud/default/controller.php create mode 100644 generators/crud/default/search.php create mode 100644 generators/crud/default/views/_form.php create mode 100644 generators/crud/default/views/_index.php create mode 100644 generators/crud/default/views/create.php create mode 100644 generators/crud/default/views/index.php create mode 100644 generators/crud/default/views/update.php create mode 100644 generators/crud/default/views/view.php create mode 100644 generators/crud/form.php create mode 100644 generators/crud/valuestore.php diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ce58870 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Wolfgang Mattis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..251ac04 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +CRUD Generator for repeated use +=============================== + +This packet contains a CRUD generator for the framework YII2. + +This generator generates a controller and views that implement CRUD operations for the specified data model using the great modules of kartik-v. + +Many parameters of the Grid can be defined and this data is stored for reusing the generator to update the Grid settings till final version. This settings inculdes the handling of foreign keys, search fields, column formats, withs, visibility, order, labels and alignment and also additional buttons (including glyphicons) in the footer area for actions and mass actions. + +The template is splitted into two parts, so you can write already code in one part and update settings of the grid in the other part by using the generator. + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require --prefer-dist yasheena/yii2-gii "*" +``` + +or add + +``` +"yasheena/yii2-gii": "*" +``` + +to the require section of your `composer.json` file. + + +Usage +----- + +Once the extension is installed, you can find a new CRUD generator in your generator list. diff --git a/YasheenaGiiAsset.php b/YasheenaGiiAsset.php new file mode 100644 index 0000000..64a233c --- /dev/null +++ b/YasheenaGiiAsset.php @@ -0,0 +1,19 @@ + true, + ]; +} \ No newline at end of file diff --git a/assets/yasheenaGii.css b/assets/yasheenaGii.css new file mode 100644 index 0000000..d369120 --- /dev/null +++ b/assets/yasheenaGii.css @@ -0,0 +1,78 @@ + +.coltab { + border: 1px solid gray; + width: -moz-max-content; +} + +.coltab td,th { + border: 1px solid gray; + padding: 10px 10px 0px 10px; +} + +.col_order { + width: 80px; +} + +.col_name { +} + +.col_visible { + width: 120px; +} + +.col_align { + width: 110px; +} + +.col_replace { +} + +.col_width { + width: 95px; +} + +.col_format { + width: 135px; +} + +.col_search { + width: 105px; +} + +.col_label { +} + +.container { + margin-left: 0px !important; +} + +.btn_label { +} + +.btn_url { + width: 350px; +} + +.btn_icon { +} + +.btn_icon_pic { + width: 35px; +} + +.btn_hint { + width: 500px; +} + +.glyphicon { + display: block !important; +} + +.field-generator-viewpath .generator-loadview { + float: left; +} + +.field-generator-basecontrollerclass { + clear: left; +} +. \ No newline at end of file diff --git a/assets/yasheenaGii.js b/assets/yasheenaGii.js new file mode 100644 index 0000000..6a619d5 --- /dev/null +++ b/assets/yasheenaGii.js @@ -0,0 +1,46 @@ +yii.yasheenaGii = (function ($) { + return { + init: function () { + // hide left button definition when checkbox column is disabled + $('form #generator-dat_use_checkboxcolumn').change(function () { + $('form .field-generator-dat_lft_button').toggle($(this).is(':checked')); + $('form #generator-dat_lft_button').trigger('change'); + }).change(); + // update form depending on the left button settings + $('form #generator-dat_lft_button').change(function () { + // if left button is set to none, hide left button entries table + $('form .field-generator-lft_table').toggle( + !$('input[name="Generator[dat_lft_button]"][value="None"]').is(':checked') + && $('#generator-dat_use_checkboxcolumn').is(':checked') + ); + // if it is not a dropdown, hide line for defining dropdown button + $('form #generator-lft-line-0').toggle( + $('input[name="Generator[dat_lft_button]"][value="Dropdown"]').is(':checked') + ); + // if left button is not set to none, hide datafield selection of checkboxcolumn + $('form .field-generator-checkbox-field').toggle( + $('input[name="Generator[dat_lft_button]"][value="None"]').is(':checked') + && $('#generator-dat_use_checkboxcolumn').is(':checked') + ); + }).change(); + // if right button is set to none, hide right button entries table + $('form #generator-dat_rgt_button').change(function () { + $('form .field-generator-rgt_table').toggle( + !$('input[name="Generator[dat_rgt_button]"][value="None"]').is(':checked') + ); + // if it is not a dropdown, hide line for defining dropdown button + $('form #generator-rgt-line-0').toggle( + $('input[name="Generator[dat_rgt_button]"][value="Dropdown"]').is(':checked') + ); + }).change(); + // update the icon depending on dropdown list for the left und right button entries + $('form .generator-button-dropdown').change(function () { + var id = $(this).attr('id'); + $('form #' + id + '-icon').removeClass(); + if ($(this).val() != '---') { + $('form #' + id + '-icon').toggleClass('glyphicon glyphicon-' + $(this).val(), true); + } + }).change(); + } + } +})(jQuery); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4d1a945 --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "yasheena/yii2-gii", + "description": "This is a CRUD generator for the framework YII2, which is highly configurable and allows reusing the generator till final version.", + "type": "yii2-extension", + "keywords": ["yii2","extension","gii","generator"], + "license": "MIT", + "authors": [ + { + "name": "Wolfgang Mattis", + "email": "git@gedankenspieler.de" + } + ], + "require": { + "yiisoft/yii2": "*", + "yasheena/view": "*" + }, + "autoload": { + "psr-4": { + "yasheena\\gii\\": "" + } + } +} diff --git a/generators/crud/Generator.php b/generators/crud/Generator.php new file mode 100644 index 0000000..43b5894 --- /dev/null +++ b/generators/crud/Generator.php @@ -0,0 +1,932 @@ + + * @since 2.0 + */ +class Generator extends \yii\gii\Generator +{ + public $modelClass; + public $controllerClass; + public $viewPath; + public $baseControllerClass = 'yii\web\Controller'; + public $indexWidgetType = 'grid'; + public $searchModelClass = ''; + public $generated = false; + public $enablePjax = true; + private $store; + + public function init() + { + $this->enableI18N = true; + $this->store = new valuestore(); + $this->attachBehavior('valuestore', $this->store); + parent::init(); + } + + /** + * @inheritdoc + */ + public function getName() + { + return 'Yasheena CRUD Generator'; + } + + /** + * @inheritdoc + */ + public function getDescription() + { + return 'This generator generates a controller and views that implement CRUD + operations for the specified data model using the great modules of kartik-v.

+ Many parameters of the Grid can be defined and this data is stored for + reusing the generator to update the Grid settings till final version. This + settings inculdes the handling of foreign keys, search fields, column + formats, withs, visibility, order, labels and alignment and also additional + buttons (including glyphicons) in the footer area for actions and mass actions.

+ The template is splitted into two parts, so you can write already code in one + part and update settings of the grid in the other part by using the generator.'; + } + + /** + * @inheritdoc + */ + public function rules() + { + return array_merge(parent::rules(), [ + [['controllerClass', 'modelClass', 'searchModelClass', 'baseControllerClass'], 'filter', 'filter' => 'trim'], + [['modelClass', 'controllerClass', 'baseControllerClass', 'indexWidgetType'], 'required'], + [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], + [['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], + [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]], + [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], + [['controllerClass'], 'match', 'pattern' => '/(^|\\\\)[A-Z][^\\\\]+Controller$/', 'message' => 'Controller class name must start with an uppercase letter.'], + [['controllerClass', 'searchModelClass'], 'validateNewClass'], + [['indexWidgetType'], 'in', 'range' => ['grid', 'list']], + [['modelClass'], 'validateModelClass'], + [['enableI18N', 'enablePjax'], 'boolean'], + [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], + [['viewPath'], 'safe'], + ], $this->store->rules()); + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), [ + 'modelClass' => 'Model Class', + 'controllerClass' => 'Controller Class', + 'viewPath' => 'View Path', + 'baseControllerClass' => 'Base Controller Class', + 'indexWidgetType' => 'Widget Used in Index Page', + 'searchModelClass' => 'Search Model Class', + 'enablePjax' => 'Enable Pjax', + 'dat_headerInfo' => 'List Header (optional text)', + 'dat_footerInfo' => 'List Footer (optional text)', + 'dat_use_serialcolumn' => 'Use a serial column', + 'dat_use_checkboxcolumn' => 'Use a checkbox column', + 'dat_checkboxcolumn_field' => 'Field for direct update:', + 'dat_lft_button' => 'Left buttion in List Footer', + 'dat_rgt_button' => 'Right buttion in List Footer', +]); + } + + /** + * @inheritdoc + */ + public function hints() + { + return array_merge(parent::hints(), [ + '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.', + '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), + and class name should be in CamelCase with an uppercase first letter. Make sure the class + is using the same namespace as specified by your application\'s controllerNamespace property.', + 'viewPath' => 'Specify the directory for storing the view scripts for the controller. You may use path alias here, e.g., + /var/www/basic/controllers/views/post, @app/views/post. If not set, it will default + to @app/views/ControllerID', + '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', + 'searchModelClass' => 'This is the name of the search model class to be generated. You should provide a fully + qualified namespaced class name, e.g., app\models\PostSearch.', + 'enablePjax' => 'This indicates whether the generator should wrap the GridView or ListView + widget on the index page with yii\widgets\Pjax widget. Set this to true if you want to get + sorting, filtering and pagination without page refreshing.', + 'dat_headerInfo' => 'This text will be displayed in the header area of the table.', + 'dat_footerInfo' => 'This text will be displayed in the footer area of the table.', + 'dat_use_serialcolumn' => 'Add a column with serial numbering of the rows', + 'dat_use_checkboxcolumn' => 'Add a column with checkboxes to allow the user to select multiple rows for a mass action or for direct marking in database', + 'dat_checkboxcolumn_field' => 'Datebase field to update directly with the checkbox value (true/false). Make sure this field has no unique index. Using \'---\' will execute the controller function \'actionCheckboxChanged\' instead of updating the database. For setting default values to the checkboxes in that case define a function \'getCheckboxDefaultValue($key, $index, $widget)\' in the model and rerun the generator to update \'_index.php\'.', + 'dat_lft_button' => 'Add button(s) or a dropdown in the left footer area to handle selected rows in a mass action:
None: Immediate action on changing a checkbox
Buttons: One or more single buttons
Dropdown: Actions listed in a dropdown list', + 'dat_rgt_button' => 'Add button(s) or a dropdown in the right footer area:
None: No button at all
Buttons: One or more single buttons
Dropdown: Actions listed in a dropdown list', +]); + } + + /** + * @inheritdoc + */ + public function requiredTemplates() + { + return ['controller.php']; + } + + /** + * @inheritdoc + */ + public function stickyAttributes() + { + return array_merge(parent::stickyAttributes(), ['baseControllerClass', 'indexWidgetType']); + } + + /** + * Checks if model class is valid + */ + public function validateModelClass() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pk = $class::primaryKey(); + if (empty($pk)) { + $this->addError('modelClass', "The table associated with $class must have primary key(s)."); + } + } + + /** + * @inheritdoc + */ + public function generate() + { + $viewPath = $this->getViewPath(); + + $storefile = "$viewPath/.generator"; + if (file_exists($storefile)) { + if(count($this->store->col_name) == 0) { + @$this->store->setJSON(file_get_contents($storefile)); + $this->enableI18N = $this->store->dat_enableI18N; + $this->enablePjax = $this->store->dat_enablePjax; + $this->messageCategory = $this->dat_messageCategory; + } + } + if ($this->enableI18N == null) { + $this->enableI18N = true; + } + if ($this->enablePjax == null) { + $this->enablePjax = true; + } + if ($this->messageCategory == null) { + $this->messageCategory = 'app'; + } + if ($this->store->dat_use_serialcolumn == null) { + $this->store->dat_use_serialcolumn = true; + } + + foreach(['lft', 'rgt'] as $idx) { + $_btn = 'dat_' . $idx . '_button'; + $_lbl = $idx . '_label'; + $_url = $idx . '_url'; + $_ico = $idx . '_icon'; + $_hnt = $idx . '_hint'; + if ($this->store->$_btn == null) { + $this->store->$_btn = 'None'; + } + $labels = $this->store->$_lbl; + $urls = $this->store->$_url; + $icons = $this->store->$_ico; + $hints = $this->store->$_hnt; + foreach ($labels as $index => $lbl) { + if (($lbl == '') && ($icons[$index] == '---')) { + unset($labels[$index]); + unset($urls[$index]); + unset($icons[$index]); + unset($hints[$index]); + } else { + if ($index > 0) { + if ($urls[$index] == '') { + $urls[$index] = $this->createUrl(($lbl == '') ? ($idx . '-action-' . $index) : $lbl); + } + } + } + } + $labels[] = ''; + $urls[] = ''; + $icons[] = '---'; + $hints[] = ''; + $this->store->$_lbl = array_values($labels); + $this->store->$_url = array_values($urls); + $this->store->$_ico = array_values($icons); + $this->store->$_hnt = array_values($hints); + } + + $tableSchema = $this->getTableSchema(); + $model = new $this->modelClass; + $db = $model->getDb(); + foreach ($this->getColumnNames() as $columnName) { + $index = $this->store->getIndexOfName($columnName); + $replacenames = null; + $format = 'text'; + $visible = 'visible'; + $align = 'left'; + if ($tableSchema) { + foreach ($tableSchema->foreignKeys as $index2 => $key) { + if (array_key_exists($columnName, $key)) { + $replacenames = array('---'); + $tableSchemaRepl = $db->getTableSchema($key[0]); + foreach ($tableSchemaRepl->columns as $index3 => $column) { + if($index3 != $key[$columnName]) { + $replacenames[] = $key[0] . '.' . $index3; + } + } + $replacenames = $this->createDropdownList($replacenames); + break; + } + } + $type = $tableSchema->columns[$columnName]->dbType; + if (false !== ($n = strpos($type, '('))) { + $type = substr($type, 0, $n); + } + switch($type) { + case 'int': + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'bigint': + $format = 'integer'; + $align = 'right'; + break; + case 'decimal': + case 'float': + case 'double': + case 'real': + $format = 'decimal'; + $align = 'right'; + break; + case 'boolean': + $format = 'boolean'; + $align = 'center'; + break; + case 'varchar': + case 'char': + case 'year': + break; + case 'text': + case 'tinytext': + case 'mediumtext': + case 'longtext': + $format = 'ntext1line'; + break; + case 'date': + $format = 'date'; + break; + case 'timestamp': + case 'datetime': + $format = 'datetime'; + break; + case 'time': + $format = 'time'; + break; + case 'bit': + break; + case 'enum': + break; + case 'set': + break; + case 'binary': + case 'varbinary': + $visible = 'hidden'; + break; + case 'blob': + case 'tinyblob': + case 'mediumblob': + case 'longblob': + $visible = 'hidden'; + break; + case 'point': + case 'multipoint': + case 'polygon': + case 'multipolygon': + case 'linestring': + case 'multilinestring': + case 'geometry': + case 'geometrycollection': + $visible = 'hidden'; + break; + default: + $visible = 'hidden'; + break; + } + } + $this->store->set('col_replacename', $index, $replacenames); + if (!$this->store->is_set('col_format', $index)) { + $this->store->set('col_format', $index, $format); + } + if (!$this->store->is_set('col_align', $index)) { + $this->store->set('col_align', $index, $align); + } + if (!$this->store->is_set('col_visible', $index)) { + if (strtolower($columnName) == 'id') { + $this->store->set('col_visible', $index, 'invisible'); + } else { + $this->store->set('col_visible', $index, $visible); + } + } + } + $this->store->removeUntouchedNames(); + $this->store->dat_enableI18N = $this->enableI18N; + $this->store->dat_enablePjax = $this->enablePjax; + $this->dat_messageCategory = $this->messageCategory; + $file = new CodeFile($storefile, $this->store->getJSON()); + if ($file->operation == 'overwrite') { + $file->operation = 'create'; + } + $files = [$file]; + + $controllerClass = str_replace('\\', '/', ltrim($this->controllerClass, '\\')); + $controllerFile = Yii::getAlias('@' . $controllerClass . '.php'); + $files[] = new CodeFile($controllerFile, $this->render('controller.php')); + + $controllerFile2 = Yii::getAlias('@' . $this->underscoreIt($controllerClass) . '.php'); + $file = new CodeFile($controllerFile2, $this->render('_controller.php')); + if ($file->operation == 'overwrite') { + $file->operation = 'create'; + } + $files[] = $file; + + if (!empty($this->searchModelClass)) { + $searchModel = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->searchModelClass, '\\') . '.php')); + $files[] = new CodeFile($searchModel, $this->render('search.php')); + } + + $templatePath = $this->getTemplatePath() . '/views'; + foreach (scandir($templatePath) as $filename) { + if (empty($this->searchModelClass) && $filename === '_search.php') { + continue; + } + if (is_file($templatePath . '/' . $filename) && pathinfo($filename, PATHINFO_EXTENSION) === 'php') { + $file = new CodeFile("$viewPath/$filename", $this->render("views/$filename")); + if ($filename == '_index.php') { + if ($file->operation == 'overwrite') { + $file->operation = 'create'; + } + } + $files[] = $file; + } + } + + $this->generated = true; + return $files; + } + + /** + * @return string the controller ID (without the module ID prefix) + */ + public function getControllerID() + { + $pos = strrpos($this->controllerClass, '\\'); + $class = substr(substr($this->controllerClass, $pos + 1), 0, -10); + + return Inflector::camel2id($class); + } + + /** + * @return string the controller view path + */ + public function getViewPath() + { + if (empty($this->viewPath)) { + return Yii::getAlias('@app/views/' . $this->getControllerID()); + } else { + return Yii::getAlias($this->viewPath); + } + } + + public function getNameAttribute() + { + foreach ($this->getColumnNames() as $name) { + if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) { + return $name; + } + } + /* @var $class \yii\db\ActiveRecord */ + $class = $this->modelClass; + $pk = $class::primaryKey(); + + return $pk[0]; + } + + /** + * Generates code for active field + * @param string $attribute + * @return string + */ + public function generateActiveField($attribute) + { + $tableSchema = $this->getTableSchema(); + if ($tableSchema === false || !isset($tableSchema->columns[$attribute])) { + if (preg_match('/^(password|pass|passwd|passcode)$/i', $attribute)) { + return "\$form->field(\$model, '$attribute')->passwordInput()"; + } else { + 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(['rows' => 6])"; + } else { + if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) { + $input = 'passwordInput'; + } else { + $input = 'textInput'; + } + if (is_array($column->enumValues) && count($column->enumValues) > 0) { + $dropDownOptions = []; + foreach ($column->enumValues as $enumValue) { + $dropDownOptions[$enumValue] = Inflector::humanize($enumValue); + } + return "\$form->field(\$model, '$attribute')->dropDownList(" + . preg_replace("/\n\s*/", ' ', VarDumper::export($dropDownOptions)).", ['prompt' => ''])"; + } elseif ($column->phpType !== 'string' || $column->size === null) { + return "\$form->field(\$model, '$attribute')->$input()"; + } else { + return "\$form->field(\$model, '$attribute')->$input(['maxlength' => true])"; + } + } + } + + /** + * Generates code for active search field + * @param string $attribute + * @return string + */ + public function generateActiveSearchField($attribute) + { + $tableSchema = $this->getTableSchema(); + if ($tableSchema === false) { + return "\$form->field(\$model, '$attribute')"; + } + $column = $tableSchema->columns[$attribute]; + if ($column->phpType === 'boolean') { + return "\$form->field(\$model, '$attribute')->checkbox()"; + } else { + return "\$form->field(\$model, '$attribute')"; + } + } + + /** + * Generates column format + * @param \yii\db\ColumnSchema $column + * @return string + */ + public function generateColumnFormat($column) + { + if ($column->phpType === 'boolean') { + return 'boolean'; + } elseif ($column->type === 'text') { + return 'ntext1line'; + } 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'; + } + } + + /** + * Generates validation rules for the search model. + * @return array the generated validation rules + */ + public function generateSearchRules() + { + if (($table = $this->getTableSchema()) === false) { + return ["[['" . implode("', '", $this->getColumnNames()) . "'], 'safe']"]; + } + $types = []; + foreach ($table->columns as $column) { + if ($this->store->hasSearchField($column->name)) { + 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_DOUBLE: + 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 = []; + foreach ($types as $type => $columns) { + $rules[] = "[['" . implode("', '", $columns) . "'], '$type']"; + } + + return $rules; + } + + /** + * @return array searchable attributes + */ + public function getSearchAttributes() + { + $list = array(); + foreach ($this->getColumnNames() as $col) { + if ($this->store->hasSearchField($col)) { + $list[] = $col; + } + } + return $list; + } + + /** + * Generates the attribute labels for the search model. + * @return array the generated attribute labels (name => label) + */ + public function generateSearchLabels() + { + /* @var $model \yii\base\Model */ + $model = new $this->modelClass(); + $attributeLabels = $model->attributeLabels(); + $labels = []; + foreach ($this->getColumnNames() as $name) { + if ($this->store->hasSearchField($name)) { + if (isset($attributeLabels[$name])) { + $labels[$name] = $attributeLabels[$name]; + } else { + if (!strcasecmp($name, 'id')) { + $labels[$name] = 'ID'; + } else { + $label = Inflector::camel2words($name); + if (!empty($label) && substr_compare($label, ' id', -3, 3, true) === 0) { + $label = substr($label, 0, -3) . ' ID'; + } + $labels[$name] = $label; + } + } + } + } + + return $labels; + } + + /** + * Generates search conditions + * @return array + */ + public function generateSearchConditions() + { + $columns = []; + if (($table = $this->getTableSchema()) === false) { + $class = $this->modelClass; + /* @var $model \yii\base\Model */ + $model = new $class(); + foreach ($model->attributes() as $attribute) { + $columns[$attribute] = 'unknown'; + } + } else { + foreach ($table->columns as $column) { + if ($this->store->hasSearchField($column->name)) { + $columns[$column->name] = $column->type; + } + } + } + + $likeConditions = []; + $hashConditions = []; + foreach ($columns as $column => $type) { + switch ($type) { + case Schema::TYPE_SMALLINT: + case Schema::TYPE_INTEGER: + case Schema::TYPE_BIGINT: + case Schema::TYPE_BOOLEAN: + case Schema::TYPE_FLOAT: + case Schema::TYPE_DOUBLE: + case Schema::TYPE_DECIMAL: + case Schema::TYPE_MONEY: + case Schema::TYPE_DATE: + case Schema::TYPE_TIME: + case Schema::TYPE_DATETIME: + case Schema::TYPE_TIMESTAMP: + $hashConditions[] = "'{$column}' => \$this->{$column},"; + break; + default: + $likeConditions[] = "->andFilterWhere(['like', '{$column}', \$this->{$column}])"; + break; + } + } + + $conditions = []; + if (!empty($hashConditions)) { + $conditions[] = "\$query->andFilterWhere([\n" + . str_repeat(' ', 12) . implode("\n" . str_repeat(' ', 12), $hashConditions) + . "\n" . str_repeat(' ', 8) . "]);\n"; + } + if (!empty($likeConditions)) { + $conditions[] = "\$query" . implode("\n" . str_repeat(' ', 12), $likeConditions) . ";\n"; + } + + return $conditions; + } + + /** + * Generates URL parameters + * @return string + */ + public function generateUrlParams() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pks = $class::primaryKey(); + if (count($pks) === 1) { + if (is_subclass_of($class, 'yii\mongodb\ActiveRecord')) { + return "'id' => (string)\$model->{$pks[0]}"; + } else { + return "'id' => \$model->{$pks[0]}"; + } + } else { + $params = []; + foreach ($pks as $pk) { + if (is_subclass_of($class, 'yii\mongodb\ActiveRecord')) { + $params[] = "'$pk' => (string)\$model->$pk"; + } else { + $params[] = "'$pk' => \$model->$pk"; + } + } + + return implode(', ', $params); + } + } + + /** + * Generates action parameters + * @return string + */ + public function generateActionParams() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pks = $class::primaryKey(); + if (count($pks) === 1) { + return '$id'; + } else { + return '$' . implode(', $', $pks); + } + } + + /** + * Generates parameter tags for phpdoc + * @return array parameter tags for phpdoc + */ + public function generateActionParamComments() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pks = $class::primaryKey(); + if (($table = $this->getTableSchema()) === false) { + $params = []; + foreach ($pks as $pk) { + $params[] = '@param ' . (substr(strtolower($pk), -2) == 'id' ? 'integer' : 'string') . ' $' . $pk; + } + + return $params; + } + if (count($pks) === 1) { + return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id']; + } else { + $params = []; + foreach ($pks as $pk) { + $params[] = '@param ' . $table->columns[$pk]->phpType . ' $' . $pk; + } + + return $params; + } + } + + /** + * Returns table schema for current model class or false if it is not an active record + * @return boolean|\yii\db\TableSchema + */ + public function getTableSchema() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + if (is_subclass_of($class, 'yii\db\ActiveRecord')) { + return $class::getTableSchema(); + } else { + return false; + } + } + + /** + * @return array model column names + */ + public function getColumnNames() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + if (is_subclass_of($class, 'yii\db\ActiveRecord')) { + return $class::getTableSchema()->getColumnNames(); + } else { + /* @var $model \yii\base\Model */ + $model = new $class(); + + return $model->attributes(); + } + } + + /** + * Modified original function "generateString" to handle variablenames in placeholders + * The variablenames must be enclosed by '', i.e. generateString('Hello {0}!', '$name') + * @return string + */ + public function generateString($string = '', $placeholders = []) + { + if ($string == '') { + return "''"; + } + $s = parent::generateString($string, $placeholders); + $s = preg_replace('/\'(\$[a-zA-Z0-9->\$\(\)\[\]_\\\:]+)\'/', '$1', $s); + return $s; + } + + /** + * Use "generateString" with the messageCatergory of the Yasheena CRUD generator + * @param string $string + * @param unknown $placeholders + * @return unknown + */ + public function generateMyString($string = '', $placeholders = []) + { + $tmp = $this->messageCategory; + $this->messageCategory = 'yashgen'; + $result = $this->generateString($string, $placeholders); + $this->messageCategory = $tmp; + return $result; + } + + public function createUrl($name) + { + $name = str_replace([' ', '_', '#', '*', '+', '/'], ['-'], trim(strtolower($name))); + preg_match_all('/[-a-z0-9]/', $name, $tmp); + $name = preg_replace('/[-]+/', '-', implode($tmp[0])); + $name = ltrim($name, '-'); + return $name; + } + + public function createDropdownList($values, $prefix = '') + { + $list = []; + foreach ($values as $value) { + $list[$value] = $prefix . $value; + } + return $list; + } + + public function getListButtonType() + { + return $this->createDropdownList(['None', 'Buttons', 'Dropdown']); + } + + public function getListVisible() + { + return $this->createDropdownList(['visible', 'invisible', 'hidden']); + } + + public function getListAlign() + { + return $this->createDropdownList(['left', 'center', 'right']); + } + + public function getListFormat() + { + return $this->createDropdownList(['text', 'ntext', 'ntext1line', 'ntextshort', 'boolean', 'integer', 'decimal', 'currency', 'percent', 'timestamp', 'datetime', 'date', 'time', 'email', 'url', 'html', 'image', 'spellout', 'size']); + } + + public function getListSearch() + { + return $this->createDropdownList(['text', 'list', 'none']); + } + + public function getGlyphiconNames() + { + return [ + 'asterisk', 'plus', 'minus', 'euro', 'cloud', 'envelope', 'pencil', 'glass', 'music', 'search', 'heart', 'star', 'star-empty', 'user', 'film', 'th-large', 'th', 'th-list', + 'ok', 'remove', 'zoom-in', 'zoom-out', 'off', 'signal', 'cog', 'trash', 'home', 'file', 'time', 'road', 'download-alt', 'download', 'upload', 'inbox', 'play-circle', + 'repeat', 'refresh', 'list-alt', 'lock', 'flag', 'headphones', 'volume-off', 'volume-down', 'volume-up', 'qrcode', 'barcode', 'tag', 'tags', 'book', 'bookmark', 'print', + 'camera', 'font', 'bold', 'italic', 'text-height', 'text-width', 'align-left', 'align-center', 'align-right', 'align-justify', 'list', 'indent-left', 'indent-right', + 'facetime-video', 'picture', 'map-marker', 'adjust', 'tint', 'edit', 'share', 'check', 'move', 'step-backward', 'fast-backward', 'backward', 'play', 'pause', 'stop', + 'forward', 'fast-forward', 'step-forward', 'eject', 'chevron-left', 'chevron-right', 'plus-sign', 'minus-sign', 'remove-sign', 'ok-sign', 'question-sign', 'info-sign', + 'screenshot', 'remove-circle', 'ok-circle', 'ban-circle', 'arrow-left', 'arrow-right', 'arrow-up', 'arrow-down', 'share-alt', 'resize-full', 'resize-small', 'exclamation-sign', + 'gift', 'leaf', 'fire', 'eye-open', 'eye-close', 'warning-sign', 'plane', 'calendar', 'random', 'comment', 'magnet', 'chevron-up', 'chevron-down', 'retweet', 'shopping-cart', + 'folder-close', 'folder-open', 'resize-vertical', 'resize-horizontal', 'hdd', 'bullhorn', 'bell', 'certificate', 'thumbs-up', 'thumbs-down', 'hand-right', 'hand-left', + 'hand-up', 'hand-down', 'circle-arrow-right', 'circle-arrow-left', 'circle-arrow-up', 'circle-arrow-down', 'globe', 'wrench', 'tasks', 'filter', 'briefcase', 'fullscreen', + 'dashboard', 'paperclip', 'heart-empty', 'link', 'phone', 'pushpin', 'usd', 'gbp', 'sort', 'sort-by-alphabet', 'sort-by-alphabet-alt', 'sort-by-order', 'sort-by-order-alt', + 'sort-by-attributes', 'sort-by-attributes-alt', 'unchecked', 'expand', 'collapse-down', 'collapse-up', 'log-in', 'flash', 'log-out', 'new-window', 'record', 'save', 'open', + 'saved', 'import', 'export', 'send', 'floppy-disk', 'floppy-saved', 'floppy-remove', 'floppy-save', 'floppy-open', 'credit-card', 'transfer', 'cutlery', 'header', 'compressed', + 'earphone', 'phone-alt', 'tower', 'stats', 'sd-video', 'hd-video', 'subtitles', 'sound-stereo', 'sound-dolby', 'sound-5-1', 'sound-6-1', 'sound-7-1', 'copyright-mark', + 'registration-mark', 'cloud-download', 'cloud-upload', 'tree-conifer', 'tree-deciduous', 'cd', 'save-file', 'open-file', 'level-up', 'copy', 'paste', 'alert', 'equalizer', + 'king', 'queen', 'pawn', 'bishop', 'knight', 'baby-formula', 'tent', 'blackboard', 'bed', 'apple', 'erase', 'hourglass', 'lamp', 'duplicate', 'piggy-bank', 'scissors', + 'bitcoin', 'yen', 'ruble', 'scale', 'ice-lolly', 'ice-lolly-tasted', 'education', 'option-horizontal', 'option-vertical', 'menu-hamburger', 'modal-window', 'oil', 'grain', + 'sunglasses', 'text-size', 'text-color', 'text-background', 'object-align-top', 'object-align-bottom', 'object-align-horizontal', 'object-align-left', 'object-align-vertical', + 'object-align-right', 'triangle-right', 'triangle-left', 'triangle-bottom', 'triangle-top', 'superscript', 'subscript', 'menu-left', 'menu-right', 'menu-down', 'menu-up' + ]; + } + + public function getListIcons() + { + return $this->createDropdownList(array_merge(['---'], $this->getGlyphiconNames()), ' '); + } + + public function getCheckboxcolumnField() + { + $list = ['---']; + if (($table = $this->getTableSchema()) !== false) { + foreach ($table->columns as $column) { + switch ($column->type) { + case 'int': + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'bigint': + case 'boolean': + case 'bit': + $list[] = $column->name; + break; + } + } + } + return $this->createDropdownList($list); + } + + public function getGlyphiconOptions() + { + $list = []; + foreach ($this->getGlyphiconNames() as $name) { + $list[$name] = ['class' => 'glyphicon glyphicon-' . $name]; + } + return $list; + } + + public function colLabel($label, $hint) + { + return '
' .$hint . '
'; + } + + public function underscoreIt($classname) + { + if ((false === ($n = strrpos($classname, '/'))) && (false === ($n = strrpos($classname, '\\')))) { + return '_' . $classname; + } + return substr($classname, 0, $n + 1) . '_' . substr($classname, $n + 1); + } +} diff --git a/generators/crud/default/_controller.php b/generators/crud/default/_controller.php new file mode 100644 index 0000000..c9db3b5 --- /dev/null +++ b/generators/crud/default/_controller.php @@ -0,0 +1,96 @@ +underscoreIt($generator->controllerClass)); +$childControllerClass = StringHelper::basename($generator->controllerClass); +$modelClass = StringHelper::basename($generator->modelClass); +$buttonList = ['rgt' => 'right', 'lft' => 'left']; + +echo " + +namespace controllerClass, '\\')) ?>; + +use baseControllerClass, '\\') ?>; + +/** + * implements the additional actions for model depending on the variable parts of the generated form. + */ +class extends baseControllerClass) . "\n" ?> +{ + $name) { + $_btn = 'dat_' . $src . '_button'; + $_url = $src . '_url'; + $urls = $generator->$_url; + array_pop($urls); + if (($generator->$_btn != 'None') && (count($urls)) > 0) { + $_lbl = $src . '_label'; + $lbls = $generator->$_lbl; + $_icn = $src . '_icon'; + $icns = $generator->$_icn; + $type = ($generator->$_btn == 'Dropdown') ? 'dropdown entry' : 'button'; + foreach ($urls as $index => $url) { + if ($index == 0) continue; + if (false !== strpos($url, '/')) continue; + $url = explode('-', $url); + array_walk($url, function(&$value) { $value = ucfirst($value); }); + $url = implode($url); + if (in_array($url, $reserved)) continue; + if ($lbls[$index] != '') { + $lbl = $lbls[$index]; + } else { + $lbl = $icns[$index] . '-icon'; + } + $lbl = $name . ' ' . $type . ' "' . $lbl . '"'; + if (array_key_exists($url, $list)) { + if (!array_key_exists($lbl, $list[$url])) { + $list[$url][] = $lbl; + } + } else { + $list[$url] = array($lbl); + } + } + } +} +foreach ($list as $url => $info) { +?> + /** + * Action of + + * Overwrite this function in "". + * @return mixed + */ + public function action() + { + return "action is still not implemented in !"; + } + +dat_use_checkboxcolumn) { + if ($generator->dat_lft_button == 'None') { +?> + /** + * Update the database depending on the changed state of the checkbox + * @return mixed + */ + public function actionCheckboxchanged() + { + return 'ok'; + } + + +} diff --git a/generators/crud/default/controller.php b/generators/crud/default/controller.php new file mode 100644 index 0000000..85862ad --- /dev/null +++ b/generators/crud/default/controller.php @@ -0,0 +1,176 @@ +controllerClass); +$modelClass = StringHelper::basename($generator->modelClass); +$searchModelClass = StringHelper::basename($generator->searchModelClass); +if ($modelClass === $searchModelClass) { + $searchModelAlias = $searchModelClass . 'Search'; +} + +/* @var $class ActiveRecordInterface */ +$class = $generator->modelClass; +$pks = $class::primaryKey(); +$urlParams = $generator->generateUrlParams(); +$actionParams = $generator->generateActionParams(); +$actionParamComments = $generator->generateActionParamComments(); + +echo " + +namespace controllerClass, '\\')) ?>; + +use Yii; +use modelClass, '\\') ?>; +searchModelClass)): ?> +use searchModelClass, '\\') . (isset($searchModelAlias) ? " as $searchModelAlias" : "") ?>; + +use yii\data\ActiveDataProvider; + +use underscoreIt($generator->controllerClass), '\\') ?>; +use yii\web\NotFoundHttpException; +use yii\filters\VerbFilter; + +/** + * implements the CRUD actions for model. + */ +class extends underscoreIt($generator->controllerClass)) . "\n" ?> +{ + /** + * @inheritdoc + */ + public function behaviors() + { + return [ + 'verbs' => [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + ]; + } + + /** + * Lists all models. + * @return mixed + */ + public function actionIndex() + { +searchModelClass)): ?> + $searchModel = new (); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + + $dataProvider = new ActiveDataProvider([ + 'query' => ::find(), + ]); + + return $this->render('index', [ + 'dataProvider' => $dataProvider, + ]); + + } + + /** + * Displays a single model. + * + * @return mixed + */ + public function actionView() + { + return $this->render('view', [ + 'model' => $this->findModel(), + ]); + } + + /** + * Creates a new model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new (); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', ]); + } else { + return $this->render('create', [ + 'model' => $model, + ]); + } + } + + /** + * Updates an existing model. + * If update is successful, the browser will be redirected to the 'view' page. + * + * @return mixed + */ + public function actionUpdate() + { + $model = $this->findModel(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', ]); + } else { + return $this->render('update', [ + 'model' => $model, + ]); + } + } + + /** + * Deletes an existing model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * + * @return mixed + */ + public function actionDelete() + { + $this->findModel()->delete(); + + return $this->redirect(['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 NotFoundHttpException if the model cannot be found + */ + protected function findModel() + { + \$$pk"; + } + $condition = '[' . implode(', ', $condition) . ']'; +} +?> + if (($model = ::findOne()) !== null) { + return $model; + } else { + throw new NotFoundHttpException('The requested page does not exist.'); + } + } +} diff --git a/generators/crud/default/search.php b/generators/crud/default/search.php new file mode 100644 index 0000000..88edbb1 --- /dev/null +++ b/generators/crud/default/search.php @@ -0,0 +1,87 @@ +modelClass); +$searchModelClass = StringHelper::basename($generator->searchModelClass); +if ($modelClass === $searchModelClass) { + $modelAlias = $modelClass . 'Model'; +} +$rules = $generator->generateSearchRules(); +$labels = $generator->generateSearchLabels(); +$searchAttributes = $generator->getSearchAttributes(); +$searchConditions = $generator->generateSearchConditions(); + +echo " + +namespace searchModelClass, '\\')) ?>; + +use Yii; +use yii\base\Model; +use yii\data\ActiveDataProvider; +use modelClass, '\\') . (isset($modelAlias) ? " as $modelAlias" : "") ?>; + +/** + * represents the model behind the search form about `modelClass ?>`. + */ +class extends + +{ + /** + * @inheritdoc + */ + public function rules() + { + return [ + , + ]; + } + + /** + * @inheritdoc + */ + public function scenarios() + { + // bypass scenarios() implementation in the parent class + return Model::scenarios(); + } + + /** + * Creates data provider instance with search query applied + * + * @param array $params + * + * @return ActiveDataProvider + */ + public function search($params) + { + $query = ::find(); + + // add conditions that should always apply here + + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + ]); + + $this->load($params); + + if (!$this->validate()) { + // uncomment the following line if you do not want to return any records when validation fails + // $query->where('0=1'); + return $dataProvider; + } + + // grid filtering conditions + + + return $dataProvider; + } +} diff --git a/generators/crud/default/views/_form.php b/generators/crud/default/views/_form.php new file mode 100644 index 0000000..b600266 --- /dev/null +++ b/generators/crud/default/views/_form.php @@ -0,0 +1,42 @@ +modelClass(); +$safeAttributes = $model->safeAttributes(); +if (empty($safeAttributes)) { + $safeAttributes = $model->attributes(); +} + +echo " + +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ +/* @var $form yii\widgets\ActiveForm */ +?> + +
+ + $form = ActiveForm::begin(); ?> + +getColumnNames() as $attribute) { + if (in_array($attribute, $safeAttributes)) { + echo " generateActiveField($attribute) . " ?>\n\n"; + } +} ?> +
+ Html::submitButton($model->isNewRecord ? generateString('Create') ?> : generateString('Update') ?>, ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> +
+ + ActiveForm::end(); ?> + +
diff --git a/generators/crud/default/views/_index.php b/generators/crud/default/views/_index.php new file mode 100644 index 0000000..c4bcd18 --- /dev/null +++ b/generators/crud/default/views/_index.php @@ -0,0 +1,305 @@ + 'right', 'lft' => 'left']; + +echo " + +/** + * Do not modify this file if you want to reuse the generator to update settings. + * Use the file index.php instead to modify the functionality. + */ + +use yii\helpers\Html; +use kartik\grid\GridView; +dat_use_checkboxcolumn && ($generator->dat_lft_button == 'None')) { + echo "use yii\helpers\Url;\n"; +} +foreach ($generator->col_order as $index => $value) { + if ($generator->col_search[$index] == 'list') { + echo 'use yii\helpers\ArrayHelper;' . "\n"; + break; + } +} + +foreach ($buttonList as $src => $name) { + $_btn = 'dat_' . $src . '_button'; + if ($generator->$_btn == 'Dropdown') { + echo "use yii\bootstrap\ButtonDropdown;\n"; + break; + } +} + +echo "\n\$columns = [\n"; +if ($generator->dat_use_serialcolumn) { + echo "\t['class' => 'kartik\\grid\\SerialColumn', 'order' => 'fixleft'],\n"; +} +if ($generator->dat_use_checkboxcolumn) { + $_btn = 'dat_lft_button'; + echo "\t['class' => 'kartik\\grid\\CheckboxColumn', 'order' => 'fixleft'"; + if ($generator->$_btn == 'None') { + echo ", 'multiple' => false"; + echo ", 'checkboxOptions' => function(\$model, \$key, \$index, \$widget) {return ['onclick' => 'js:checkboxChanged(this.value, this.checked)'"; + if ($generator->dat_checkboxcolumn_field == '---') { + $class = new $generator->modelClass; + if (method_exists($class, 'getCheckboxDefaultValue')) { + echo ", 'checked' => \$model->getCheckboxDefaultValue(\$key, \$index, \$widget)"; + } + } else { + echo ", 'checked' => \$model->" . $generator->dat_checkboxcolumn_field; + } + echo "];}"; + } else { + echo ", 'checkboxOptions' => ['onclick' => 'js:checkboxChanged(this.value, this.checked)']"; + } + echo "],\n"; +} +$extraFormatter = false; +foreach ($generator->getBehavior('valuestore')->sort() as $index) { + $name = $generator->col_name[$index]; + $visible = $generator->col_visible[$index]; + $align = $generator->col_align[$index]; + $replace = $generator->col_replace[$index]; + $width = $generator->col_width[$index]; + $format = $generator->col_format[$index]; + $search = $generator->col_search[$index]; + $label = $generator->col_label[$index]; + if ($visible != 'hidden') { + if (($visible == 'visible') && ($align == 'left') && ($replace == null) && ($width == '') && ($search == 'text') && ($label == '') + && !in_array($format, ['decimal', 'ntextshort'])) { + echo " '" . $name . (($format == 'text') ? '' : (':' . $format)) . "',\n"; + } else { + echo " [\n"; + if (($replace == null) || ($replace == '---')) { + echo " 'attribute' => '" . $name . "',\n"; + } else { + $parts = explode('.', $replace); + echo " 'attribute' => '" . $parts[0] . "',\n"; + echo " 'value' => '" . $replace ."',\n"; + if ($label == '') { + $class = new $generator->modelClass; + $origLabels = $class->attributeLabels(); + echo " 'label' => '" . $origLabels[$name] . "',\n"; + } + } + switch($format) { + case 'text': + break; + case 'decimal': + echo " 'format' => ['" . $format . "', 2],\n"; + break; + case 'ntextshort': + echo " 'format' => ['ntext1line', 30],\n"; + break; + default: + echo " 'format' => '" . $format . "',\n"; + break; + } + if ($label != '') { + echo " 'label' => '" . $label . "',\n"; + } + if ($width != '') { + echo " 'width' => '" . $width . "',\n"; + } + if ($align != 'left') { + echo " 'hAlign' => '" . $align . "',\n"; + } + if ($visible == 'invisible') { + echo " 'visible' => false,\n"; + } + if ($search == 'list') { + if ($replace == null) { + echo " 'filter' => ArrayHelper::map(" . $generator->modelClass . "::find()->asArray()->all(), '" . $name . "', '" . $name . "'),\n"; + } else { + echo " 'filter' => ArrayHelper::map(app\\models\\Stock::find()->asArray()->all(), 'id', 'name'),\n"; + } + } + echo " ],\n"; + } + if (in_array($format, ['ntext1line', 'ntextshort'])) { + $extraFormatter = true; + } + } +} +?> + ['class' => 'kartik\grid\ActionColumn', 'order' => 'fixright'], +]; + + $name) { + $_btn = 'dat_' . $src . '_button'; + $_lbl = $src . '_label'; + $_icn = $src . '_icon'; + $_url = $src . '_url'; + $_hnt = $src . '_hint'; + $icons = $generator->$_icn; + array_pop($icons); + $tmp = $icons; + array_shift($tmp); + $tmp = array_flip($tmp); + $useIcons = (count($tmp) > 1) || (array_pop($tmp) != '---'); + $labels = $generator->$_lbl; + $urls = $generator->$_url; + $hints = $generator->$_hnt; + if (count($icons) > 0) { + echo '$' . $name . 'ButtonItems = [' . "\n"; + foreach ($icons as $index => $icon) { + if ($index == 0) continue; + $label = $generator->generateString(Html::encode($labels[$index])); + $hint = $generator->generateString($hints[$index]); + switch ($generator->$_btn) { + case 'Dropdown': + echo " ['label' => "; + if ($useIcons) { + echo '\''; + if ($label != "''") { + echo ' \' . ' . $label; + } else { + echo "'"; + } + } else { + echo $label; + } + echo ", 'url' => ['" . $urls[$index] . "']" + . (($hints[$index] == '') ? '' : (", 'options' => ['title' => " . $hint) . "]") + . "],\n"; + break; + case 'Buttons': + echo " Html::a("; + if ($icon != '---') { + echo "'"; + if ($label != "''") { + echo ' \'. ' . $label; + } else { + echo "'"; + } + } else { + echo $label; + } + echo ", ['" . $urls[$index] . "'], ['class'=>'btn btn-default'" + . (($hints[$index] == '') ? '' : ", 'title' => " . $hint) + . "]),\n"; + break; + } + } + echo "];\n\n"; + } +} +?> + +$gridOptions = [ + 'dataProvider' => $dataProvider, + 'filterModel' => $searchModel, + 'panel' => [ + 'type' => GridView::TYPE_PRIMARY, + 'heading' => $this->title, + 'before' => '
' . generateString($generator->dat_headerInfo) ?> . '
', + 'after' => $name) { + $_btn = 'dat_' . $src . '_button'; + $_lbl = $src . '_label'; + $labels = $generator->$_lbl; + array_pop($labels); + if (($generator->$_btn != 'None') && (count($labels)) > 0) { + $_ico = $src . '_icon'; + $icons = $generator->$_ico; + $_url = $src . '_url'; + $urls = $generator->$_url; + $_hnt = $src . '_hint'; + $hints = $generator->$_hnt; + echo "'
'\n"; + switch ($generator->$_btn) { + case 'Buttons': + echo " . implode(' ', \$" . $name . "ButtonItems)\n"; + break; + case 'Dropdown': + echo " . ButtonDropdown::widget([\n" + . " 'label' => '" . (($icons[0] != '---') ? " " : '') . Html::encode($labels[0]) . "',\n" + . " 'options' => ['class' => 'btn-default'" + . (($hints[0] == '') ? '' : ", 'title' => " . $generator->generateMyString($hints[0])) + . "],\n" + . " 'dropdown' => ['items' => \$" . $name . "ButtonItems, 'encodeLabels' => false],\n" + . " 'encodeLabel' => false,\n" + . " ])\n"; + break; + } + echo " . '
'\n . "; + } +} +?> +'
dat_lft_button != 'None') ? '   ' : '' ?>' . generateString($generator->dat_footerInfo) ?> . '
', + ], + new \yasheena\view\YFormatter(),' : '' ?> + + 'responsive' => true, + 'hover' => true, + 'persistResize' => true, + 'toolbar' => [ + [ + 'content'=> + Html::a(' ' . generateMyString('Create {0}', '$elementName') ?>, + ['create'], ['class'=>'btn btn-success'] + ) . ' ' . + Html::a('', [''], [ + 'class' => 'btn btn-default', + 'title' => generateMyString('Reset filter/sorting') ?> + + ]) . ' {dynagridFilter}{dynagridSort}{dynagrid}', + ], + '{export}', + '{toggleData}' + ], + 'exportConfig' => [ + GridView::PDF => [], + GridView::EXCEL => [], + GridView::CSV => [], + GridView::TEXT => [], + ], +]; + +dat_use_checkboxcolumn) { + if ($generator->dat_lft_button == 'None') : ?> +$this->registerJs(" +function restoreCheckbox(value){ + var ele = $('.kv-row-checkbox[name=\"selection[]\"][value=' + value + ']'); + ele.prop('checked', !ele.prop('checked')).change(); +} +function checkboxChanged(value, checked){ + \$('*').css('cursor', 'progress'); + \$.ajax({ + url: '" . Url::to(['checkboxchanged']) . "', + method: 'post', + dataType: 'text', + data: {vakue:value, checked:checked} + }) + .done(function(result) {\$('*').css('cursor', 'default');if(result!='ok'){restoreCheckbox(value);alert(result);}}) + .error(function() {\$('*').css('cursor', 'default');restoreCheckbox(value); alert('" . generateMyString('Error solving checkbox change!') ?> . "')}); +} +", 1); // 1 => View::POS_HEAD + +$this->registerJs(" +function checkboxChanged() { + var sel = 'dat_lft_button == 'Buttons') ? 'div.left-footer-btn a' : 'div.left-footer-btn button' ?>'; + if ($('.kv-row-checkbox[name=\"selection[]\"]:checked').length > 0) { + $(sel).off('click').css('opacity', '1'); + } else { + $(sel).click(function() { return false; }).css('opacity', '0.5'); + } +}; +", 1); // 1 => View::POS_HEAD +$this->registerJs(" +$(document).on('ready pjax:success', function(){ + checkboxChanged(); +}); +checkboxChanged(); +"); + + +use yii\helpers\Html; + + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ + +$this->title = generateString('Create ' . Inflector::camel2words(StringHelper::basename($generator->modelClass))) ?>; +$this->params['breadcrumbs'][] = ['label' => generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>, 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

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

+ + $this->render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/generators/crud/default/views/index.php b/generators/crud/default/views/index.php new file mode 100644 index 0000000..0ea43d3 --- /dev/null +++ b/generators/crud/default/views/index.php @@ -0,0 +1,44 @@ +generateUrlParams(); +$nameAttribute = $generator->getNameAttribute(); + +echo " + +use kartik\dynagrid\DynaGrid; +enablePjax ? 'use yii\widgets\Pjax;' : '' ?> + + +/* @var $this yii\web\View */ +searchModelClass) ? "/* @var \$searchModel " . ltrim($generator->searchModelClass, '\\') . " */\n" : '' ?> +/* @var $dataProvider yii\data\ActiveDataProvider */ + +$elementName = generateString(Inflector::camel2words(StringHelper::basename($generator->modelClass))) ?>; + +include '_index.php'; + +// You may modify here the structures defined in _index.php ($gridOptions, $colums, ...) + +$this->title = generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>; +$this->params['breadcrumbs'][] = $this->title; +?> + +
+enablePjax ? ' ' : '' ?> + + DynaGrid::widget([ + 'options' => ['id' => 'dg-modelClass) ?>-index'], + 'allowThemeSetting' => false, + 'gridOptions' => $gridOptions, + 'columns' => $columns, + ]); ?> +enablePjax ? ' ' : '' ?> + +
diff --git a/generators/crud/default/views/update.php b/generators/crud/default/views/update.php new file mode 100644 index 0000000..ce12cd6 --- /dev/null +++ b/generators/crud/default/views/update.php @@ -0,0 +1,32 @@ +generateUrlParams(); + +echo " + +use yii\helpers\Html; + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ + +$this->title = generateString('Update {modelClass}: ', ['modelClass' => Inflector::camel2words(StringHelper::basename($generator->modelClass))]) ?> . $model->getNameAttribute() ?>; +$this->params['breadcrumbs'][] = ['label' => generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>, 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->getNameAttribute() ?>, 'url' => ['view', ]]; +$this->params['breadcrumbs'][] = generateString('Update') ?>; +?> +
+ +

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

+ + $this->render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/generators/crud/default/views/view.php b/generators/crud/default/views/view.php new file mode 100644 index 0000000..070b129 --- /dev/null +++ b/generators/crud/default/views/view.php @@ -0,0 +1,60 @@ +generateUrlParams(); + +echo " + +use yii\helpers\Html; +use yii\widgets\DetailView; + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ + +$this->title = $model->getNameAttribute() ?>; +$this->params['breadcrumbs'][] = ['label' => generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>, 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

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

+ +

+ Html::a(generateString('Update') ?>, ['update', ], ['class' => 'btn btn-primary']) ?> + Html::a(generateString('Delete') ?>, ['delete', ], [ + 'class' => 'btn btn-danger', + 'data' => [ + 'confirm' => generateString('Are you sure you want to delete this item?') ?>, + 'method' => 'post', + ], + ]) ?> +

+ + DetailView::widget([ + 'model' => $model, + 'attributes' => [ +getTableSchema()) === false) { + foreach ($generator->getColumnNames() as $name) { + echo " '" . $name . "',\n"; + } +} else { + foreach ($generator->getTableSchema()->columns as $column) { + $format = $generator->generateColumnFormat($column); + if (substr($format, 0, 5) == 'ntext') { + $format = 'ntext'; + } + echo " '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; + } +} +?> + ], + ]) ?> + +
diff --git a/generators/crud/form.php b/generators/crud/form.php new file mode 100644 index 0000000..f95a27d --- /dev/null +++ b/generators/crud/form.php @@ -0,0 +1,93 @@ +field($generator, 'modelClass'); +echo $form->field($generator, 'searchModelClass'); +echo $form->field($generator, 'controllerClass'); +echo $form->field($generator, 'viewPath'); +echo Html::a('Load', ['load'], ['class' => 'btn btn-primary generator-loadview']); +echo $form->field($generator, 'baseControllerClass'); +if ($generator->generated) { + yasheena\gii\YasheenaGiiAsset::register($this); + echo $form->field($generator, 'enableI18N')->checkbox(); + echo $form->field($generator, 'enablePjax')->checkbox(); + echo $form->field($generator, 'messageCategory'); + echo '

Configuration of the index page:


'; + echo $form->field($generator, 'dat_headerInfo'); + echo $form->field($generator, 'dat_footerInfo'); + echo $form->field($generator, 'dat_use_serialcolumn')->checkbox(); + echo "
\n"; + echo $form->field($generator, 'dat_use_checkboxcolumn')->checkbox(); + echo $form->field($generator, 'dat_lft_button')->radioList($generator->getListButtonType()); + echo "
\n" + . $generator->colLabel('Text', 'Left buttons: Text on button
Left dropdown: 1st line: text for dropdown button
2nd and following lines: text for dropdown list

(Click on "Preview" to get additional rows)
(Leave "Text" empty, set "Icon" to "---" and click on "Preview" to remove thise row)') + . $generator->colLabel('Url', 'Left buttons / dropdown:
<name>   =>   Action name of actual controller
/<path>   =>   Path within this application
http://<url>   =>   Path to external url') + . $generator->colLabel('Icon', 'Left buttons: Icon to use for the button
Left dropdown: 1st line: icon to use for the dropdown button
2nd and following lines: icons to use for or dropdown list entries') + . "" + . $generator->colLabel('Hint', 'Left buttons / dropdown: Hint text for this entry') + . "\n"; + foreach ($generator->lft_label as $index => $label) { + echo '\n"; + } + echo "
' . $form->field($generator, "lft_label[$index]", ['template' => "{input}\n{hint}\n{error}"]) + . '' . (($index == 0) ? $form->field($generator, "lft_url[$index]")->hiddenInput()->label(false) : $form->field($generator, "lft_url[$index]", ['template' => "{input}\n{hint}\n{error}"])) + . '' . $form->field($generator, "lft_icon[$index]", ['template' => "{input}\n{hint}\n{error}"])->dropDownList($generator->getListIcons(), ['class' => 'generator-button-dropdown', 'id' => 'generator-lft-button-' . $index, 'options' => $generator->getGlyphiconOptions()]) + . '' + . '' . $form->field($generator, "lft_hint[$index]", ['template' => "{input}\n{hint}\n{error}"]) + . "
\n"; + echo $form->field($generator, 'dat_checkboxcolumn_field')->dropDownList($generator->getCheckboxcolumnField(), ['class' => 'generator-button-dropdown', 'id' => 'generator-checkbox-field']); + echo "
\n"; + echo $form->field($generator, 'dat_rgt_button')->radioList($generator->getListButtonType()); + echo "
\n" + . $generator->colLabel('Text', 'Right buttons: Text on button
Right dropdown: 1st line: text for dropdown button
2nd and following lines: text for dropdown list

(Click on "Preview" to get additional rows)
(Leave "Text" empty, set "Icon" to "---" and click on "Preview" to remove thise row)') + . $generator->colLabel('Url', 'Right buttons / dropdown:
<name>   =>   Action name of actual controller
/<path>   =>   Path within this application
http://<url>   =>   Path to external url') + . $generator->colLabel('Icon', 'Right buttons: Icon to use for the button
Right dropdown: 1st line: icon to use for the dropdown button
2nd and following lines: icons to use for or dropdown list entries') + . "" + . $generator->colLabel('Hint', 'Right buttons / dropdown: Hint text for this entry') + . "\n"; + foreach ($generator->rgt_label as $index => $label) { + echo '\n"; + } + echo "
' . $form->field($generator, "rgt_label[$index]", ['template' => "{input}\n{hint}\n{error}"]) + . '' . (($index == 0) ? $form->field($generator, "rgt_url[$index]")->hiddenInput()->label(false) : $form->field($generator, "rgt_url[$index]", ['template' => "{input}\n{hint}\n{error}"])) + . '' . $form->field($generator, "rgt_icon[$index]", ['template' => "{input}\n{hint}\n{error}"])->dropDownList($generator->getListIcons(), ['class' => 'generator-button-dropdown', 'id' => 'generator-rgt-button-' . $index, 'options' => $generator->getGlyphiconOptions()]) + . '' + . '' . $form->field($generator, "rgt_hint[$index]", ['template' => "{input}\n{hint}\n{error}"]) + . "

\n"; + echo "

Column configuration:


\n" + . $generator->colLabel('Order', 'Modify the numbers to get a new order of the fields in the resulting view.') + . $generator->colLabel('Database field', 'List of all fields in the given model.') + . $generator->colLabel('Visibility', 'visible: Column is visible on default
invisible: Column is invisible on default
hidden: Column will never be visible') + . $generator->colLabel('Alignment', 'Set the alignment for the column content.') + . $generator->colLabel('Width', 'Optional: Use px or % to define the default column width, i.e. "120px" or "15%".') + . $generator->colLabel('Format', 'text: Default view as text (\n and \r are ignored)
ntext: Show multiline values as multiple lines
ntext1line: Multiline values => view first line only
' + . 'ntextshort: Like ntext1line but shortened to 30 chars
boolean: View "yes" and "no" instead of number
' + . 'integer: Show as integer
decimal:Show as decimal (default: 2 decimal places)
currency: Format with actual currency settings
percent: Show as percent (1 => 100 %)
' + . 'timestamp: Formats value as UNIX timestamp (float)
datetime: Show integer as date and time
date: Show date part of integer value only
time: Show time part of integer value only
' + . 'email: Format as direct clickable email address
url: Format as direct clickable link
html: Use the HtmlPurifier (to avoid XSS attacks)
' + . 'image: Show image with the value as url
spellout: Show the number as written text
size: Formats the value in a human readable byte format') + . $generator->colLabel('Searchfield', 'text: Default search field (enter text)
list: Select one entry outof a list with all column values
none: Remove search area for this column') + . $generator->colLabel('Replace column by', 'Replace this column with the column of the connected table (foreign key).') + . $generator->colLabel('Label', 'Optional: Replaces the original label text.') + . "\n"; + foreach ($generator->getBehavior('valuestore')->sort() as $index) { + $replList = $generator->col_replacename[$index]; + echo '\n"; + } + echo "
' . $form->field($generator, "col_order[$index]", ['template' => "{input}\n{hint}\n{error}"]) + . '' . $form->field($generator, "col_name[$index]", ['template' => "{input}\n{hint}\n{error}"])->textInput(['readonly' => true]) + . '' . $form->field($generator, "col_visible[$index]", ['template' => "{input}\n{hint}\n{error}"])->dropDownList($generator->getListVisible()) + . '' . $form->field($generator, "col_align[$index]", ['template' => "{input}\n{hint}\n{error}"])->dropDownList($generator->getListAlign()) + . '' . $form->field($generator, "col_width[$index]", ['template' => "{input}\n{hint}\n{error}"]) + . '' . $form->field($generator, "col_format[$index]", ['template' => "{input}\n{hint}\n{error}"])->dropDownList($generator->getListFormat()) + . '' . ($replList == null + ? $form->field($generator, "col_replace[$index]", ['template' => "{input}\n{hint}\n{error}"])->hiddenInput() + : $form->field($generator, "col_replace[$index]", ['template' => "{input}\n{hint}\n{error}"])->dropDownList($replList)) + . '' . $form->field($generator, "col_label[$index]", ['template' => "{input}\n{hint}\n{error}"]) + . "

\n"; +} diff --git a/generators/crud/valuestore.php b/generators/crud/valuestore.php new file mode 100644 index 0000000..9b44677 --- /dev/null +++ b/generators/crud/valuestore.php @@ -0,0 +1,147 @@ + ['visible', 'align', 'replace', 'replacename', 'width', 'search', 'label', 'order', 'name', 'format'], + 'dat_' => ['headerInfo', 'footerInfo', 'use_serialcolumn', 'use_checkboxcolumn', 'checkboxcolumn_field', + 'enableI18N', 'enablePjax', 'messageCategory', 'lft_button', 'rgt_button'], + 'lft_' => ['label', 'url', 'icon', 'hint'], + 'rgt_' => ['label', 'url', 'icon', 'hint'], + ]; + protected $initDat = [ + 'col_' => [], + 'dat_' => null, + 'lft_' => [], + 'rgt_' => [], + ]; + + protected $ruleBoolean = ['use_serialcolumn', 'use_checkboxcolumn']; + + public function canSetProperty($name, $checkVars = true) + { + $pre = substr($name, 0, 4); + return array_key_exists($pre, $this->names) && in_array(substr($name, 4), $this->names[$pre]); + } + + public function canGetProperty($name, $checkVars = true) + { + $pre = substr($name, 0, 4); + return array_key_exists($pre, $this->names) && in_array(substr($name, 4), $this->names[$pre]); + } + + public function __get($name) + { + $this->force($name); + return $this->vars[$name]; + } + + public function __set($name, $value) + { + $this->force($name); + $this->vars[$name] = $value; + } + + public function set($name, $index, $value) + { + $this->force($name); + $this->vars[$name][$index] = $value; + } + + public function is_set($name, $index) + { + $this->force($name); + return array_key_exists($index, $this->vars[$name]) && ($this->vars[$name][$index] != null); + } + + private function force($name) + { + if (!array_key_exists($name, $this->vars)) { + $this->vars[$name] = $this->initDat[substr($name, 0, 4)]; + } + } + + // Returns an array with indexes sorted on the values of the col_order array. + // Additional the values of col_order array are normalized. + public function sort() + { + asort($this->vars['col_order']); + $n = 0; + foreach ($this->vars['col_order'] as $index => $value) { + $n += 10; + $this->vars['col_order'][$index] = $n; + } + return array_keys($this->vars['col_order']); + } + + public function getIndexOfName($name) + { + if (false === ($index = array_search($name, $this->vars['col_name']))) { + $index = count($this->vars['col_name']); + foreach ($this->names['col_'] as $nam) { + $this->vars['col_' . $nam][$index] = null; + } + $this->vars['col_name'][$index] = $name; + $this->vars['col_order'][$index] = 10000000 + $index; + } + $this->touched[$index] = true; + return $index; + } + + public function hasSearchField($name) + { + if (false === ($index = array_search($name, $this->vars['col_name']))) { + return false; + } + return ($this->vars['col_visible'][$index] != 'hidden') && ($this->vars['col_search'][$index] != 'none'); + } + + public function getJSON() + { + $this->sort(); + return json_encode($this->vars); + } + + public function setJSON($str) + { + $this->vars = json_decode($str, true); + } + + public function removeUntouchedNames() + { + $touched = array_keys($this->touched); + sort($touched); + foreach($this->names['col_'] as $name) { + $in = $this->vars['col_' . $name]; + $out = []; + foreach ($touched as $index) { + $out[] = $in[$index]; + } + $this->vars['col_' . $name] = $out; + } + $this->touched = []; + } + + public function rules() + { + $safe = []; + $boolean = []; + foreach ($this->names as $pre => $names) { + foreach ($names as $name) { + $nam = $pre . $name; + if (in_array($nam, $this->ruleBoolean)) { + $boolean[] = $nam; + } else { + $safe[] = $nam; + } + } + } + return [[$safe, 'safe'], [$boolean, 'boolean']]; + } +} +