From 8e962ebe66c674d0068e06737bd5ac47f8b53a8d Mon Sep 17 00:00:00 2001 From: Prabowo Murti Date: Thu, 30 Jun 2016 06:10:45 +0700 Subject: [PATCH 1/9] Implement beautified URL on frontend --- frontend/web/.htaccess | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 frontend/web/.htaccess diff --git a/frontend/web/.htaccess b/frontend/web/.htaccess new file mode 100644 index 0000000..50fc2ac --- /dev/null +++ b/frontend/web/.htaccess @@ -0,0 +1,12 @@ +Options +FollowSymLinks +IndexIgnore */* + +RewriteEngine on + +# if a directory or a file exists, use it directly +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d + +# otherwise forward it to index.php +RewriteRule . index.php + From bdb423c5f2d16f605e7ced814144675d6dfb4c93 Mon Sep 17 00:00:00 2001 From: Prabowo Murti Date: Thu, 30 Jun 2016 16:55:19 +0700 Subject: [PATCH 2/9] Fix field error on login form --- frontend/views/site/login.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/views/site/login.php b/frontend/views/site/login.php index 56ea98e..90e81c4 100644 --- a/frontend/views/site/login.php +++ b/frontend/views/site/login.php @@ -19,7 +19,7 @@
'login-form']); ?> - field($model, 'username')->textInput(['autofocus' => true]) ?> + field($model, 'email')->textInput(['autofocus' => true]) ?> field($model, 'password')->passwordInput() ?> From ff24ea463c21a73482efd9c1b542710cf97780f9 Mon Sep 17 00:00:00 2001 From: Prabowo Murti Date: Thu, 14 Jul 2016 17:25:05 +0700 Subject: [PATCH 3/9] Load jQuery at the beginning of the page --- backend/assets/AppAsset.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/assets/AppAsset.php b/backend/assets/AppAsset.php index 940c0af..b290729 100644 --- a/backend/assets/AppAsset.php +++ b/backend/assets/AppAsset.php @@ -16,6 +16,9 @@ class AppAsset extends AssetBundle ]; public $js = [ ]; + public $jsOptions = [ + 'position' => \yii\web\View::POS_HEAD + ]; public $depends = [ 'yii\web\YiiAsset', 'yii\bootstrap\BootstrapAsset', From b02ff30ec2489108817fc68d8872c662dea28701 Mon Sep 17 00:00:00 2001 From: Prabowo Murti Date: Sat, 16 Jul 2016 10:38:43 +0700 Subject: [PATCH 4/9] Add custom template for CRUD --- .../layouts/js/_multipledelete_script.php | 32 +++ .../my_templates/crud/default/controller.php | 208 ++++++++++++++++++ common/my_templates/crud/default/search.php | 87 ++++++++ .../my_templates/crud/default/views/_form.php | 43 ++++ .../crud/default/views/_search.php | 44 ++++ .../crud/default/views/create.php | 30 +++ .../my_templates/crud/default/views/index.php | 104 +++++++++ .../crud/default/views/update.php | 32 +++ .../my_templates/crud/default/views/view.php | 65 ++++++ 9 files changed, 645 insertions(+) create mode 100644 backend/views/layouts/js/_multipledelete_script.php create mode 100644 common/my_templates/crud/default/controller.php create mode 100644 common/my_templates/crud/default/search.php create mode 100644 common/my_templates/crud/default/views/_form.php create mode 100644 common/my_templates/crud/default/views/_search.php create mode 100644 common/my_templates/crud/default/views/create.php create mode 100644 common/my_templates/crud/default/views/index.php create mode 100644 common/my_templates/crud/default/views/update.php create mode 100644 common/my_templates/crud/default/views/view.php diff --git a/backend/views/layouts/js/_multipledelete_script.php b/backend/views/layouts/js/_multipledelete_script.php new file mode 100644 index 0000000..521fc41 --- /dev/null +++ b/backend/views/layouts/js/_multipledelete_script.php @@ -0,0 +1,32 @@ + \ No newline at end of file diff --git a/common/my_templates/crud/default/controller.php b/common/my_templates/crud/default/controller.php new file mode 100644 index 0000000..e116fb5 --- /dev/null +++ b/common/my_templates/crud/default/controller.php @@ -0,0 +1,208 @@ +controllerClass); +$modelClass = StringHelper::basename($generator->modelClass); +$searchModelClass = StringHelper::basename($generator->modelClass); // prev : $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 common\models\User; + +use modelClass, '\\') ?>; +searchModelClass)): ?> +// use searchModelClass, '\\') . (isset($searchModelAlias) ? " as $searchModelAlias" : "") ?>; +use common\models\search\; + +use yii\data\ActiveDataProvider; + +use baseControllerClass, '\\') ?>; +use yii\web\NotFoundHttpException; +use yii\filters\VerbFilter; +use yii\filters\AccessControl; + +/** + * implements the CRUD actions for model. + */ +class extends baseControllerClass) . "\n" ?> +{ + /** + * @inheritdoc + */ + public function behaviors() + { + return [ + 'access' => [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'actions' => ['index', 'view', 'edit', 'create', 'update', 'delete', 'multipledelete'], + 'allow' => true, + 'roles' => ['@'], + 'matchCallback' => function ($rule, $action) + { + return Yii::$app->user->identity['role'] == User::ROLE_ADMIN; + } + ], + ], + ], + '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']); + } + + /** + * Delete multiple IDs + * @return mixed + */ + public function actionMultipledelete() + { + if (Yii::$app->request->isAjax) + { + $selected_ids = Yii::$app->request->post('selectedItems'); + foreach ($selected_ids as $id) + $this->findModel($id)->delete(); + } + } + + /** + * 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/common/my_templates/crud/default/search.php b/common/my_templates/crud/default/search.php new file mode 100644 index 0000000..88edbb1 --- /dev/null +++ b/common/my_templates/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/common/my_templates/crud/default/views/_form.php b/common/my_templates/crud/default/views/_form.php new file mode 100644 index 0000000..e048b91 --- /dev/null +++ b/common/my_templates/crud/default/views/_form.php @@ -0,0 +1,43 @@ +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)) { + if ($attribute != 'created_at' && $attribute != 'updated_at') + 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/common/my_templates/crud/default/views/_search.php b/common/my_templates/crud/default/views/_search.php new file mode 100644 index 0000000..fc45d2e --- /dev/null +++ b/common/my_templates/crud/default/views/_search.php @@ -0,0 +1,44 @@ + + +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +/* @var $this yii\web\View */ +/* @var $model searchModelClass, '\\') ?> */ +/* @var $form yii\widgets\ActiveForm */ +?> + + diff --git a/common/my_templates/crud/default/views/create.php b/common/my_templates/crud/default/views/create.php new file mode 100644 index 0000000..10b00b9 --- /dev/null +++ b/common/my_templates/crud/default/views/create.php @@ -0,0 +1,30 @@ + + +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/common/my_templates/crud/default/views/index.php b/common/my_templates/crud/default/views/index.php new file mode 100644 index 0000000..142b407 --- /dev/null +++ b/common/my_templates/crud/default/views/index.php @@ -0,0 +1,104 @@ +generateUrlParams(); +$nameAttribute = $generator->getNameAttribute(); + +echo " + +use yii\helpers\Html; +use indexWidgetType === 'grid' ? "yii\\grid\\GridView" : "yii\\widgets\\ListView" ?>; +enablePjax ? 'use yii\widgets\Pjax;' : '' ?> + +/* @var $this yii\web\View */ +searchModelClass) ? "/* @var \$searchModel " . ltrim($generator->searchModelClass, '\\') . " */\n" : '' ?> +/* @var $dataProvider yii\data\ActiveDataProvider */ + +$this->title = generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>; +$this->params['breadcrumbs'][] = $this->title; +?> +
+ +

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

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

+ Html::a(generateString('Create ' . Inflector::camel2words(StringHelper::basename($generator->modelClass))) ?>, ['create'], ['class' => 'btn btn-success']) ?> + + Delete Selected +

+enablePjax ? ' ' : '' ?> + +indexWidgetType === 'grid'): ?> + GridView::widget([ + 'dataProvider' => $dataProvider, + searchModelClass) ? "'filterModel' => \$searchModel,\n 'columns' => [\n" : "'columns' => [\n"; ?> + [ + 'class' => 'yii\grid\CheckboxColumn', + 'options' => ['width' => '32px'], + ], + ['class' => 'yii\grid\SerialColumn'], + +getTableSchema()) === false) { + foreach ($generator->getColumnNames() as $name) { + if (++$count < 6) { + if ($name == 'id') + { + echo " [\n"; + echo " 'attribute' => 'id',\n"; + echo " 'options' => ['width' => '70px'],\n"; + echo " ],\n"; + } + else + echo " '" . $name . "',\n"; + } else { + echo " // '" . $name . "',\n"; + } + } +} else { + foreach ($tableSchema->columns as $column) { + $format = $generator->generateColumnFormat($column); + if (++$count < 6) { + if ($column->name == 'id') + { + echo " [\n"; + echo " 'attribute' => 'id',\n"; + echo " 'options' => ['width' => '70px'],\n"; + echo " ],\n"; + } + else + echo " '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; + } else { + echo " // '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; + } + } +} +?> + + ['class' => 'yii\grid\ActionColumn'], + ], + ]); ?> + + ListView::widget([ + 'dataProvider' => $dataProvider, + 'itemOptions' => ['class' => 'item'], + 'itemView' => function ($model, $key, $index, $widget) { + return Html::a(Html::encode($model->), ['view', ]); + }, + ]) ?> + +enablePjax ? ' ' : '' ?> + +
+ + $this->render('//layouts/js/_multipledelete_script');?> diff --git a/common/my_templates/crud/default/views/update.php b/common/my_templates/crud/default/views/update.php new file mode 100644 index 0000000..0849e3a --- /dev/null +++ b/common/my_templates/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->getColumnNames()) ? 'label' : $generator->getNameAttribute() ?>; +$this->params['breadcrumbs'][] = ['label' => generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>, 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->getColumnNames()) ? 'label' : $generator->getNameAttribute() ?>, 'url' => ['view', ]]; +$this->params['breadcrumbs'][] = generateString('Update') ?>; +?> +
+ +

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

+ + $this->render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/common/my_templates/crud/default/views/view.php b/common/my_templates/crud/default/views/view.php new file mode 100644 index 0000000..b436bec --- /dev/null +++ b/common/my_templates/crud/default/views/view.php @@ -0,0 +1,65 @@ +generateUrlParams(); + +echo " + +use yii\helpers\Html; +use yii\widgets\DetailView; + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ + +$this->title = $model->getColumnNames()) ? 'label' : $generator->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 ($column->name == 'created_at' || $column->name == 'updated_at') + { + echo " [\n"; + echo " 'attribute' => '" . $column->name . "',\n"; + echo " 'value' => date('Y-m-d H:i:s', \$model->" . $column->name . ")\n"; + echo " ],\n"; + } + else + echo " '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; + } +} +?> + ], + ]) ?> + +
From 605dd6d1ef77074c4943e66e3f9dfe5a2f357526 Mon Sep 17 00:00:00 2001 From: Prabowo Murti Date: Sat, 16 Jul 2016 11:03:06 +0700 Subject: [PATCH 5/9] Add User CRUD --- backend/controllers/UserController.php | 203 +++++++++++++++++++++++++ backend/views/layouts/main.php | 13 +- backend/views/user/_form.php | 32 ++++ backend/views/user/_search.php | 45 ++++++ backend/views/user/create.php | 21 +++ backend/views/user/index.php | 47 ++++++ backend/views/user/profile.php | 43 ++++++ backend/views/user/update.php | 23 +++ backend/views/user/view.php | 50 ++++++ common/models/User.php | 56 ++++++- common/models/UserSearch.php | 77 ++++++++++ 11 files changed, 607 insertions(+), 3 deletions(-) create mode 100644 backend/controllers/UserController.php create mode 100644 backend/views/user/_form.php create mode 100644 backend/views/user/_search.php create mode 100644 backend/views/user/create.php create mode 100644 backend/views/user/index.php create mode 100644 backend/views/user/profile.php create mode 100644 backend/views/user/update.php create mode 100644 backend/views/user/view.php create mode 100644 common/models/UserSearch.php diff --git a/backend/controllers/UserController.php b/backend/controllers/UserController.php new file mode 100644 index 0000000..98d6cba --- /dev/null +++ b/backend/controllers/UserController.php @@ -0,0 +1,203 @@ + [ + 'class' => AccessControl::className(), + 'rules' => [ + [ + 'actions' => ['index', 'view', 'edit', 'create', 'update', 'delete', 'multipledelete', 'profile'], + 'allow' => true, + 'roles' => ['@'], + 'matchCallback' => function ($rule, $action) + { + return Yii::$app->user->identity['role'] == User::ROLE_SUPERADMIN; + } + ], + ], + ], + 'verbs' => [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['POST'], + ], + ], + ]; + } + + /** + * Lists all User models. + * @return mixed + */ + public function actionIndex() + { + $searchModel = new UserSearch(); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + } + + /** + * Displays a single User model. + * @param integer $id + * @return mixed + */ + public function actionView($id) + { + return $this->render('view', [ + 'model' => $this->findModel($id), + ]); + } + + /** + * Creates a new User model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new User(); + + if ($model->load(Yii::$app->request->post())) + { + if (strlen($model->password) < 6) + { + $model->addError('password', 'Password should contain at least 6 characters.'); + return $this->render('create', [ + 'model' => $model, + ]); + } + + $model->setPassword($model->password); + $model->generateAuthKey(); + + $post_user = Yii::$app->request->post('User'); + + if ($model->save()) + return $this->redirect(['view', 'id' => $model->id]); + else + { + return $this->render('create', [ + 'model' => $model, + ]); + } + + } + else + return $this->render('create', [ + 'model' => $model, + ]); + } + + /** + * Updates an existing User model. + * If update is successful, the browser will be redirected to the 'view' page. + * @param integer $id + * @return mixed + */ + public function actionUpdate($id) + { + $model = $this->findModel($id); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', 'id' => $model->id]); + } else { + return $this->render('update', [ + 'model' => $model, + ]); + } + } + + /** + * Updates the logged in user's profile + * @param boolean $success + * @return mixed + */ + public function actionProfile($success = false) + { + $model = $this->findModel(Yii::$app->user->id); + + if (Yii::$app->request->post()) + { + $post_user = Yii::$app->request->post('User'); + + $model->username = $post_user['username']; + + // update password + if ( ! empty($post_user['password'])) + $model->setPassword($post_user['password']); + + if ($model->save()) + return $this->redirect(['profile', 'success' => true]); + else + return $this->render('profile', ['model' => $model]); + } + else + return $this->render('profile', ['model' => $model, 'success' => $success]); + } + + /** + * Deletes an existing User model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * @param integer $id + * @return mixed + */ + public function actionDelete($id) + { + $this->findModel($id)->delete(); + + return $this->redirect(['index']); + } + + /** + * Delete multiplede IDs + * @return mixed + */ + public function actionMultipledelete() + { + if (Yii::$app->request->isAjax) + { + $selected_ids = Yii::$app->request->post('selectedItems'); + foreach ($selected_ids as $id) + $this->findModel($id)->delete(); + } + } + + /** + * Finds the User model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * @param integer $id + * @return User the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel($id) + { + if (($model = User::findOne($id)) !== null) { + return $model; + } else { + throw new NotFoundHttpException('The requested page does not exist.'); + } + } +} diff --git a/backend/views/layouts/main.php b/backend/views/layouts/main.php index 62d5992..842ce6c 100644 --- a/backend/views/layouts/main.php +++ b/backend/views/layouts/main.php @@ -3,6 +3,7 @@ /* @var $this \yii\web\View */ /* @var $content string */ +use common\models\User; use backend\assets\AppAsset; use yii\helpers\Html; use yii\bootstrap\Nav; @@ -37,9 +38,17 @@ $menuItems = [ ['label' => 'Home', 'url' => ['/site/index']], ]; - if (Yii::$app->user->isGuest) { + if (Yii::$app->user->isGuest) + { $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; - } else { + } + elseif (Yii::$app->user->identity['role'] == User::ROLE_SUPERADMIN) + { + $menuItems[] = [ + 'label' => 'User', + 'url' => ['/user/index'], + ]; + $menuItems[] = '
  • ' . Html::beginForm(['/site/logout'], 'post') . Html::submitButton( diff --git a/backend/views/user/_form.php b/backend/views/user/_form.php new file mode 100644 index 0000000..2d3d96b --- /dev/null +++ b/backend/views/user/_form.php @@ -0,0 +1,32 @@ + + +
    + + + + field($model, 'username')->textInput(['maxlength' => true]) ?> + + field($model, 'email')->input('email', ['maxlength' => true]) ?> + + field($model, 'password')->passwordInput(['placeholder' => 'Leave this field blank if you do not want to change the password', 'minlength' => 6]) ?> + + field($model, 'role')->dropDownList(User::getRoleAsArray()); ?> + + field($model, 'status')->dropDownList(User::getStatusAsArray()); ?> + +
    + isNewRecord ? Yii::t('app', 'Create') : Yii::t('app', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> +
    + + + +
    diff --git a/backend/views/user/_search.php b/backend/views/user/_search.php new file mode 100644 index 0000000..781acce --- /dev/null +++ b/backend/views/user/_search.php @@ -0,0 +1,45 @@ + + + diff --git a/backend/views/user/create.php b/backend/views/user/create.php new file mode 100644 index 0000000..52adf3c --- /dev/null +++ b/backend/views/user/create.php @@ -0,0 +1,21 @@ +title = Yii::t('app', 'Create User'); +$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Users'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
    + +

    title) ?>

    + + render('_form', [ + 'model' => $model, + ]) ?> + +
    diff --git a/backend/views/user/index.php b/backend/views/user/index.php new file mode 100644 index 0000000..c39cda0 --- /dev/null +++ b/backend/views/user/index.php @@ -0,0 +1,47 @@ +title = Yii::t('app', 'Users'); +$this->params['breadcrumbs'][] = $this->title; +?> +
    + +

    title) ?>

    + render('_search', ['model' => $searchModel]); ?> + +

    + 'btn btn-success btn-sm']) ?> + + Delete Selected +

    + $dataProvider, + 'filterModel' => $searchModel, + 'columns' => [ + ['class' => 'yii\grid\CheckboxColumn'], + 'id', + 'username', + // 'auth_key', + // 'password_hash', + // 'password_reset_token', + 'email:email', + [ + 'attribute' => 'role', + 'filter' => \common\models\User::getRoleAsArray() + ], + // 'status', + // 'created_at', + // 'updated_at', + + ['class' => 'yii\grid\ActionColumn'], + ], + ]); ?> +
    + +render('//layouts/js/_multipledelete_script') ?> diff --git a/backend/views/user/profile.php b/backend/views/user/profile.php new file mode 100644 index 0000000..960ee3d --- /dev/null +++ b/backend/views/user/profile.php @@ -0,0 +1,43 @@ +title = Yii::t('app', 'Update Profile'); +?> +
    + +

    title) ?>

    + [ + 'class' => 'alert-info', + ], + 'body' => 'Profile updated.', + ]) : ''?> + +
    + + + + field($model, 'username')->textInput(['maxlength' => true]) ?> + + field($model, 'password')->passwordInput(['placeholder' => 'Leave this field blank if you do not want to change the password', 'minlength' => 6]) ?> + +
    + isNewRecord ? Yii::t('app', 'Create') : Yii::t('app', 'Update'), ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> +
    + + + +
    + + +
    diff --git a/backend/views/user/update.php b/backend/views/user/update.php new file mode 100644 index 0000000..801d8b3 --- /dev/null +++ b/backend/views/user/update.php @@ -0,0 +1,23 @@ +title = Yii::t('app', 'Update {modelClass}: ', [ + 'modelClass' => 'User', +]) . $model->username; +$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Users'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->username, 'url' => ['view', 'id' => $model->id]]; +$this->params['breadcrumbs'][] = Yii::t('app', 'Update'); +?> +
    + +

    title) ?>

    + + render('_form', [ + 'model' => $model, + ]) ?> + +
    diff --git a/backend/views/user/view.php b/backend/views/user/view.php new file mode 100644 index 0000000..f9a06cf --- /dev/null +++ b/backend/views/user/view.php @@ -0,0 +1,50 @@ +title = $model->username; +$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Users'), 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
    + +

    title) ?>

    + +

    + $model->id], ['class' => 'btn btn-primary']) ?> + $model->id], [ + 'class' => 'btn btn-danger', + 'data' => [ + 'confirm' => Yii::t('app', 'Are you sure you want to delete this item?'), + 'method' => 'post', + ], + ]) ?> +

    + + $model, + 'attributes' => [ + 'id', + 'username', + // 'auth_key', + // 'password_hash', + // 'password_reset_token', + 'email:email', + 'role', + 'status', + [ + 'attribute' => 'created_at', + 'value' => date('Y-m-d H:i:s', $model->created_at) + ], + [ + 'attribute' => 'updated_at', + 'value' => date('Y-m-d H:i:s', $model->updated_at) + ], + ], + ]) ?> + +
    diff --git a/common/models/User.php b/common/models/User.php index d95ee47..3b6be6b 100644 --- a/common/models/User.php +++ b/common/models/User.php @@ -27,7 +27,10 @@ class User extends ActiveRecord implements IdentityInterface const STATUS_ACTIVE = 10; const ROLE_SUPERADMIN = 'superadmin'; - const ROLE_ADMIN = 'admin'; + const ROLE_ADMIN = 'admin'; + const ROLE_USER = 'user'; + + public $password; /** * @inheritdoc @@ -53,8 +56,37 @@ public function behaviors() public function rules() { return [ + [['username', 'email', 'auth_key', 'password_hash'], 'required'], + ['username', 'string', 'min' => 4], + ['email', 'email'], + [['status', 'created_at', 'updated_at'], 'integer'], ['status', 'default', 'value' => self::STATUS_ACTIVE], ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]], + [['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255], + [['auth_key'], 'string', 'max' => 32], + [['role'], 'string', 'max' => 50], + [['email'], 'unique'], + [['password_reset_token'], 'unique'], + ['password', 'string', 'min' => 6] + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'id' => Yii::t('app', 'ID'), + 'username' => Yii::t('app', 'Username'), + 'auth_key' => Yii::t('app', 'Auth Key'), + 'password_hash' => Yii::t('app', 'Password Hash'), + 'password_reset_token' => Yii::t('app', 'Password Reset Token'), + 'email' => Yii::t('app', 'Email'), + 'role' => Yii::t('app', 'Role'), + 'status' => Yii::t('app', 'Status'), + 'created_at' => Yii::t('app', 'Created At'), + 'updated_at' => Yii::t('app', 'Updated At'), ]; } @@ -199,4 +231,26 @@ public function removePasswordResetToken() { $this->password_reset_token = null; } + + /** + * Get `role` field as array + * @return array array of roles + */ + public static function getRoleAsArray() + { + return [ + static::ROLE_SUPERADMIN => 'Super Admin', + static::ROLE_ADMIN => 'Admin', + static::ROLE_USER => 'User', + ]; + } + + /** + * Get `status` field as array + * @return array array of status + */ + public static function getStatusAsArray() + { + return [static::STATUS_ACTIVE => 'Active', static::STATUS_DELETED => 'Deleted']; + } } diff --git a/common/models/UserSearch.php b/common/models/UserSearch.php new file mode 100644 index 0000000..41c15db --- /dev/null +++ b/common/models/UserSearch.php @@ -0,0 +1,77 @@ + $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 + $query->andFilterWhere([ + 'id' => $this->id, + 'status' => $this->status, + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, + ]); + + $query->andFilterWhere(['like', 'username', $this->username]) + ->andFilterWhere(['like', 'auth_key', $this->auth_key]) + ->andFilterWhere(['like', 'password_hash', $this->password_hash]) + ->andFilterWhere(['like', 'password_reset_token', $this->password_reset_token]) + ->andFilterWhere(['like', 'email', $this->email]) + ->andFilterWhere(['role' => $this->role]); + + return $dataProvider; + } +} From d69c18ed622b982e26467cc5807fe120c9fca6c0 Mon Sep 17 00:00:00 2001 From: Prabowo Murti Date: Sun, 17 Jul 2016 09:50:21 +0700 Subject: [PATCH 6/9] Modify update user functionality --- backend/controllers/UserController.php | 51 +++++++++++++++++++++----- backend/views/layouts/main.php | 5 +++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/backend/controllers/UserController.php b/backend/controllers/UserController.php index 98d6cba..0bc9512 100644 --- a/backend/controllers/UserController.php +++ b/backend/controllers/UserController.php @@ -30,7 +30,9 @@ public function behaviors() 'roles' => ['@'], 'matchCallback' => function ($rule, $action) { - return Yii::$app->user->identity['role'] == User::ROLE_SUPERADMIN; + return + Yii::$app->user->identity['role'] == User::ROLE_SUPERADMIN || + Yii::$app->user->identity['role'] == User::ROLE_ADMIN; } ], ], @@ -93,8 +95,6 @@ public function actionCreate() $model->setPassword($model->password); $model->generateAuthKey(); - $post_user = Yii::$app->request->post('User'); - if ($model->save()) return $this->redirect(['view', 'id' => $model->id]); else @@ -121,13 +121,36 @@ public function actionUpdate($id) { $model = $this->findModel($id); - if ($model->load(Yii::$app->request->post()) && $model->save()) { - return $this->redirect(['view', 'id' => $model->id]); - } else { - return $this->render('update', [ - 'model' => $model, - ]); + if (Yii::$app->request->post()) + { + $post_user = Yii::$app->request->post('User'); + + $model->username = $post_user['username']; + $model->email = $post_user['email']; + $model->role = $post_user['role']; + $model->status = $post_user['status']; + + // update password + if ( ! empty($post_user['password'])) + { + if (strlen($model->password) < 6) + { + $model->addError('password', 'Password should contain at least 6 characters.'); + return $this->render('update', [ + 'model' => $model, + ]); + } + + $model->setPassword($post_user['password']); + } + + if ($model->save()) + return $this->redirect(['view', 'id' => $model->id]); + else + return $this->render('update', ['model' => $model,]); } + else + return $this->render('update', ['model' => $model]); } /** @@ -147,7 +170,17 @@ public function actionProfile($success = false) // update password if ( ! empty($post_user['password'])) + { + if (strlen($model->password) < 6) + { + $model->addError('password', 'Password should contain at least 6 characters.'); + return $this->render('update', [ + 'model' => $model, + ]); + } + $model->setPassword($post_user['password']); + } if ($model->save()) return $this->redirect(['profile', 'success' => true]); diff --git a/backend/views/layouts/main.php b/backend/views/layouts/main.php index 842ce6c..4da32e5 100644 --- a/backend/views/layouts/main.php +++ b/backend/views/layouts/main.php @@ -44,6 +44,11 @@ } elseif (Yii::$app->user->identity['role'] == User::ROLE_SUPERADMIN) { + $menuItems[] = [ + 'label' => 'Profile', + 'url' => ['/user/profile'], + ]; + $menuItems[] = [ 'label' => 'User', 'url' => ['/user/index'], From 4140245172760c74f1899c2234b4012a5fce40e8 Mon Sep 17 00:00:00 2001 From: Prabowo Murti Date: Tue, 19 Jul 2016 06:55:16 +0700 Subject: [PATCH 7/9] Update composer, upgrade Yii version --- composer.json | 3 +- composer.lock | 76 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index 9ca5f9c..de6396e 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "php": ">=5.4.0", "yiisoft/yii2": ">=2.0.6", "yiisoft/yii2-bootstrap": "*", - "yiisoft/yii2-swiftmailer": "*" + "yiisoft/yii2-swiftmailer": "*", + "creocoder/yii2-nested-sets": "^0.9.0" }, "require-dev": { "yiisoft/yii2-codeception": "*", diff --git a/composer.lock b/composer.lock index a0e9104..352916f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "914f9194a4d021de88b285b03e683984", - "content-hash": "e1929a97e872bdbaacc8530468eccb67", + "hash": "cdd3966c677634ee1a9b9cfa9ad22b73", + "content-hash": "ecb439eaefbb1ccddd62962bdc2849bb", "packages": [ { "name": "bower-asset/bootstrap", @@ -58,16 +58,16 @@ }, { "name": "bower-asset/jquery", - "version": "2.2.3", + "version": "2.2.4", "source": { "type": "git", "url": "https://github.com/jquery/jquery-dist.git", - "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198" + "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/af22a351b2ea5801ffb1695abb3bb34d5bed9198", - "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198", + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/c0185ab7c75aab88762c5aae780b9d83b80eda72", + "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72", "shasum": "" }, "type": "bower-asset-library", @@ -255,6 +255,46 @@ ], "time": "2015-03-06 05:28:07" }, + { + "name": "creocoder/yii2-nested-sets", + "version": "0.9.0", + "source": { + "type": "git", + "url": "https://github.com/creocoder/yii2-nested-sets.git", + "reference": "cb8635a459b6246e5a144f096b992dcc30cf9954" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/creocoder/yii2-nested-sets/zipball/cb8635a459b6246e5a144f096b992dcc30cf9954", + "reference": "cb8635a459b6246e5a144f096b992dcc30cf9954", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "*" + }, + "type": "yii2-extension", + "autoload": { + "psr-4": { + "creocoder\\nestedsets\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Alexander Kochetov", + "email": "creocoder@gmail.com" + } + ], + "description": "The nested sets behavior for the Yii framework", + "keywords": [ + "nested sets", + "yii2" + ], + "time": "2015-01-27 10:53:51" + }, { "name": "ezyang/htmlpurifier", "version": "v4.7.0", @@ -301,23 +341,23 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v5.4.2", + "version": "v5.4.3", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "d8db871a54619458a805229a057ea2af33c753e8" + "reference": "4cc92842069c2bbc1f28daaaf1d2576ec4dfe153" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/d8db871a54619458a805229a057ea2af33c753e8", - "reference": "d8db871a54619458a805229a057ea2af33c753e8", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/4cc92842069c2bbc1f28daaaf1d2576ec4dfe153", + "reference": "4cc92842069c2bbc1f28daaaf1d2576ec4dfe153", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "mockery/mockery": "~0.9.1,<0.9.4" + "mockery/mockery": "~0.9.1" }, "type": "library", "extra": { @@ -350,24 +390,24 @@ "mail", "mailer" ], - "time": "2016-05-01 08:45:47" + "time": "2016-07-08 11:51:25" }, { "name": "yiisoft/yii2", - "version": "2.0.8", + "version": "2.0.9", "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "53992b136b993e32ca7b6f399cf42b143f8685a6" + "reference": "2b75151ea60e1fd820046416eee2e89c3dda1133" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/53992b136b993e32ca7b6f399cf42b143f8685a6", - "reference": "53992b136b993e32ca7b6f399cf42b143f8685a6", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2b75151ea60e1fd820046416eee2e89c3dda1133", + "reference": "2b75151ea60e1fd820046416eee2e89c3dda1133", "shasum": "" }, "require": { - "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable", + "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", "bower-asset/jquery.inputmask": "~3.2.2", "bower-asset/punycode": "1.3.*", "bower-asset/yii2-pjax": "~2.0.1", @@ -444,7 +484,7 @@ "framework", "yii2" ], - "time": "2016-04-28 14:50:20" + "time": "2016-07-11 13:36:42" }, { "name": "yiisoft/yii2-bootstrap", From 54fd6f17a946bc79648156bf9baef202974e9442 Mon Sep 17 00:00:00 2001 From: Prabowo Murti Date: Wed, 17 Aug 2016 18:40:48 +0700 Subject: [PATCH 8/9] Set default user's role to 'user' --- backend/views/user/index.php | 5 ++++- common/models/User.php | 3 ++- frontend/models/SignupForm.php | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/views/user/index.php b/backend/views/user/index.php index c39cda0..0bab8a1 100644 --- a/backend/views/user/index.php +++ b/backend/views/user/index.php @@ -25,7 +25,10 @@ 'filterModel' => $searchModel, 'columns' => [ ['class' => 'yii\grid\CheckboxColumn'], - 'id', + [ + 'attribute' => 'id', + 'options' => ['width' => '70px'] + ], 'username', // 'auth_key', // 'password_hash', diff --git a/common/models/User.php b/common/models/User.php index 3b6be6b..eef5006 100644 --- a/common/models/User.php +++ b/common/models/User.php @@ -65,6 +65,7 @@ public function rules() [['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255], [['auth_key'], 'string', 'max' => 32], [['role'], 'string', 'max' => 50], + [['role'], 'default', 'value' => self::ROLE_USER], [['email'], 'unique'], [['password_reset_token'], 'unique'], ['password', 'string', 'min' => 6] @@ -78,7 +79,7 @@ public function attributeLabels() { return [ 'id' => Yii::t('app', 'ID'), - 'username' => Yii::t('app', 'Username'), + 'username' => Yii::t('app', 'User Name'), 'auth_key' => Yii::t('app', 'Auth Key'), 'password_hash' => Yii::t('app', 'Password Hash'), 'password_reset_token' => Yii::t('app', 'Password Reset Token'), diff --git a/frontend/models/SignupForm.php b/frontend/models/SignupForm.php index f15f3a8..971294c 100644 --- a/frontend/models/SignupForm.php +++ b/frontend/models/SignupForm.php @@ -51,6 +51,7 @@ public function signup() $user->username = $this->username; $user->email = $this->email; $user->setPassword($this->password); + $user->role = User::ROLE_USER; $user->generateAuthKey(); return $user->save() ? $user : null; From 9ef0e9d53a6b171cc9458f0df13bec1f30b74e33 Mon Sep 17 00:00:00 2001 From: Prabowo Murti Date: Fri, 19 Aug 2016 11:37:56 +0700 Subject: [PATCH 9/9] Disallow ROLE_USER to login to backend --- backend/controllers/SiteController.php | 7 ++++++- common/models/LoginForm.php | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/backend/controllers/SiteController.php b/backend/controllers/SiteController.php index 01d9ad8..e1bc8e3 100644 --- a/backend/controllers/SiteController.php +++ b/backend/controllers/SiteController.php @@ -6,6 +6,7 @@ use yii\filters\VerbFilter; use yii\filters\AccessControl; use common\models\LoginForm; +use common\models\User; /** * Site controller @@ -29,6 +30,10 @@ public function behaviors() 'actions' => ['logout', 'index'], 'allow' => true, 'roles' => ['@'], + 'matchCallback' => function ($rule, $action) + { + return Yii::$app->user->identity['role'] == User::ROLE_ADMIN; + } ], ], ], @@ -65,7 +70,7 @@ public function actionLogin() } $model = new LoginForm(); - if ($model->load(Yii::$app->request->post()) && $model->login()) { + if ($model->load(Yii::$app->request->post()) && $model->backendLogin()) { return $this->goBack(); } else { return $this->render('login', [ diff --git a/common/models/LoginForm.php b/common/models/LoginForm.php index ea0e968..bba712f 100644 --- a/common/models/LoginForm.php +++ b/common/models/LoginForm.php @@ -62,6 +62,28 @@ public function login() } } + /** + * Logs in a backend user using the provided email and password. + * + * @return boolean whether the user is logged in successfully + */ + public function backendLogin() + { + if ($this->validate()) { + $user = $this->getUser(); + if ($user->role == User::ROLE_USER) + { + $this->_user = false; + $this->addError('email', 'Can not login as ' . User::ROLE_USER); + return false; + } + + return Yii::$app->user->login($user, $this->rememberMe ? 3600 * 24 * 30 : 0); + } else { + return false; + } + } + /** * Finds user by [[email]] *