diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b1ffdc1..9a1cdd8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -15,7 +15,7 @@ test:
- docker-compose up -d
- docker-compose run --rm phpfpm setup.sh
- docker-compose run --rm phpfpm codecept clean
- - docker-compose run --rm -e YII_ENV=test phpfpm codecept run --html=_report.html unit,acceptance; TESTS_EXIT_CODE=$?
+ - docker-compose run --rm -e YII_ENV=test phpfpm codecept run --html=_report.html acceptance,unit; TESTS_EXIT_CODE=$?
- set -e
- mv _output /tmp/${BUILD_PREFIX}
- exit $TESTS_EXIT_CODE
diff --git a/Bootstrap.php b/Bootstrap.php
index 99c3387..4849fb8 100644
--- a/Bootstrap.php
+++ b/Bootstrap.php
@@ -43,7 +43,7 @@ public function bootstrap($app)
$app->urlManager->addRules(
[
// pages default page route
- '
-Create root-node for {$language} +Create root-node for {$language}
HTML; diff --git a/controllers/api/DefaultController.php b/controllers/api/DefaultController.php index 0f7987a..ffddc45 100644 --- a/controllers/api/DefaultController.php +++ b/controllers/api/DefaultController.php @@ -27,7 +27,7 @@ public function actions() * Supported $_GET params for /pages/api/default/index * * @param dmstr\modules\pages\models\Tree::ATTR_ID - * @param dmstr\modules\pages\models\Tree::ATTR_NAME_ID + * @param dmstr\modules\pages\models\Tree::ATTR_DOMAIN_ID * @param dmstr\modules\pages\models\Tree::ATTR_ROOT * @param dmstr\modules\pages\models\Tree::ATTR_ACCESS_DOMAIN */ @@ -45,15 +45,17 @@ public function actions() if (isset($_GET[$modelClass::ATTR_ID])) { $query->andFilterWhere([$modelClass::ATTR_ID => $_GET[$modelClass::ATTR_ID]]); } - if (isset($_GET[$modelClass::ATTR_NAME_ID])) { - $query->andFilterWhere([$modelClass::ATTR_NAME_ID => $_GET[$modelClass::ATTR_NAME_ID]]); + if (isset($_GET[$modelClass::ATTR_DOMAIN_ID]) && isset($_GET[$modelClass::ATTR_ACCESS_DOMAIN])) { + $query->andFilterWhere( + [$modelClass::ATTR_DOMAIN_ID => $_GET[$modelClass::ATTR_DOMAIN_ID]] + ); + $query->andFilterWhere( + [$modelClass::ATTR_ACCESS_DOMAIN => $_GET[$modelClass::ATTR_ACCESS_DOMAIN]] + ); } if (isset($_GET[$modelClass::ATTR_ROOT])) { $query->andFilterWhere([$modelClass::ATTR_ROOT => $_GET[$modelClass::ATTR_ROOT]]); } - if (isset($_GET[$modelClass::ATTR_ACCESS_DOMAIN])) { - $query->andFilterWhere([$modelClass::ATTR_ACCESS_DOMAIN => $_GET[$modelClass::ATTR_ACCESS_DOMAIN]]); - } return new \yii\data\ActiveDataProvider( [ diff --git a/migrations/m160411_082658_rename_name_id_column.php b/migrations/m160411_082658_rename_name_id_column.php new file mode 100644 index 0000000..d4edf53 --- /dev/null +++ b/migrations/m160411_082658_rename_name_id_column.php @@ -0,0 +1,21 @@ +dropIndex('name_id_UNIQUE', 'dmstr_page'); + $this->renameColumn('dmstr_page', 'name_id', 'domain_id'); + $this->execute("ALTER TABLE dmstr_page MODIFY COLUMN domain_id VARCHAR(255) NOT NULL;"); + $this->execute( + "ALTER TABLE `dmstr_page` ADD UNIQUE INDEX `name_id_UNIQUE` (`domain_id`, `access_domain`);" + ); + } + + public function down() + { + return false; + } +} diff --git a/migrations/m160411_111111_name_id_to_domain_id_renamer.php b/migrations/m160411_111111_name_id_to_domain_id_renamer.php new file mode 100644 index 0000000..2952e81 --- /dev/null +++ b/migrations/m160411_111111_name_id_to_domain_id_renamer.php @@ -0,0 +1,29 @@ + 0 && $languages[0] !== '') { + foreach ($languages as $language) { + + $this->execute( + " + UPDATE dmstr_page SET domain_id = REPLACE (domain_id, '_$language', '') WHERE domain_id LIKE '%_$language'; + " + ); + } + return true; + } + return false; + } + + public function down() + { + return false; + } +} diff --git a/migrations/workbench/dmstr_yii2-pages-module.mwb b/migrations/workbench/dmstr_yii2-pages-module.mwb index aa91d56..814c5f4 100644 Binary files a/migrations/workbench/dmstr_yii2-pages-module.mwb and b/migrations/workbench/dmstr_yii2-pages-module.mwb differ diff --git a/models/Tree.php b/models/Tree.php index 4a738a0..9173dd9 100755 --- a/models/Tree.php +++ b/models/Tree.php @@ -21,6 +21,7 @@ * * @property string $page_title * @property string $name_id + * @property string $domain_id * @property string $slug * @property string $route * @property string $view @@ -62,13 +63,17 @@ class Tree extends \kartik\tree\models\Tree const COLLAPSED = 1; const NOT_COLLAPSED = 0; + /** + * The root node domain_id prefix and level identifier + */ + const ROOT_NODE_PREFIX = 'root'; const ROOT_NODE_LVL = 0; /** * Attribute names */ const ATTR_ID = 'id'; - const ATTR_NAME_ID = 'name_id'; + const ATTR_DOMAIN_ID = 'domain_id'; const ATTR_ACCESS_DOMAIN = 'access_domain'; const ATTR_ROOT = 'root'; const ATTR_ROUTE = 'route'; @@ -83,6 +88,12 @@ class Tree extends \kartik\tree\models\Tree const ATTR_VISIBLE = 'visible'; const ATTR_COLLAPSED = 'collapsed'; + /** + * Virtual attribute generated from "domain_id"_"access_domain" + * @var $string + */ + public $name_id; + /** * @inheritdoc */ @@ -103,10 +114,10 @@ public function behaviors() parent::behaviors(), [ [ - 'class' => TimestampBehavior::className(), + 'class' => TimestampBehavior::className(), 'createdAtAttribute' => 'created_at', 'updatedAtAttribute' => 'updated_at', - 'value' => new Expression('NOW()'), + 'value' => new Expression('NOW()'), ] ] ); @@ -120,21 +131,27 @@ public function rules() return ArrayHelper::merge( parent::rules(), [ + [ + ['domain_id', 'access_domain'], + 'unique', + 'targetAttribute' => ['domain_id', 'access_domain'], + 'message' => \Yii::t('app', 'Combination domain_id and access_domain must be unique!') + ], [ [ - 'name_id', + 'domain_id', ], - 'unique' + 'validateNoSpecialChars' ], [ [ - 'name_id', + 'domain_id', ], 'required' ], [ [ - 'name_id', + 'domain_id', 'page_title', 'slug', 'route', @@ -162,17 +179,24 @@ public function rules() 'string', 'max' => 8 ], + [ + [ + 'access_domain', + ], + 'default', + 'value' => mb_strtolower(\Yii::$app->language) + ], [ [ 'root', 'access_owner', ], 'integer', - 'max' => 11 + 'integerOnly' => true ], [ [ - 'name_id', + 'domain_id', 'page_title', 'slug', 'route', @@ -195,16 +219,36 @@ public function rules() } /** + * @inheritdoc + */ + public function afterFind() + { + parent::afterFind(); + $this->setNameId($this->domain_id . '_' . $this->access_domain); + } + + /** + * @param $attribute + * @param $params + */ + public function validateNoSpecialChars($attribute, $params) + { + // Check for whitespaces + if (preg_match("/^[a-z0-9_\\/\\'\"]+$/", $this->domain_id) == 0) { + $this->addError( + $attribute, + \Yii::t('app', '{0} should not contain any uppercase and special chars!', [$attribute]) + ); + } + } + + /**^ * Override isDisabled method if you need as shown in the * example below. You can override similarly other methods * like isActive, isMovable etc. */ public function isDisabled() { - //if (Yii::$app->user->id !== 'admin') { - //return true; - //} - return parent::isDisabled(); } @@ -213,7 +257,7 @@ public function isDisabled() */ public static function optsAccessDomain() { - $availableLanguages[Yii::$app->language] = Yii::$app->language; + $availableLanguages[mb_strtolower(Yii::$app->language)] = Yii::$app->language; return $availableLanguages; } @@ -244,10 +288,9 @@ public static function optsRoute() public function createUrl($additionalParams = []) { $route = [ - '/'.$this->route, - - 'id' => $this->id, - 'pageName' => Inflector::slug($this->page_title), + '/' . $this->route, + 'id' => $this->id, + 'pageName' => Inflector::slug($this->page_title), 'parentLeave' => $this->parents(1)->one() ? $this->parents(1)->one()->page_title : null, ]; @@ -261,14 +304,15 @@ public function createUrl($additionalParams = []) /** * @return active and visible menu nodes for the current application language * - * @param $rootName the name of the root node + * @param $domainId the domain id of the root node * * @return array */ - public static function getMenuItems($rootName, $checkUserPermissions = false) + public static function getMenuItems($domainId, $checkUserPermissions = false) { - // Get root node by name - $rootCondition['name_id'] = $rootName; + // Get root node by domain id + $rootCondition['domain_id'] = $domainId; + $rootCondition['access_domain'] = mb_strtolower(\Yii::$app->language); if (!Yii::$app->user->can('pages')) { $rootCondition[Tree::ATTR_DISABLED] = Tree::NOT_DISABLED; } @@ -285,8 +329,8 @@ public static function getMenuItems($rootName, $checkUserPermissions = false) // Get all leaves from this root node $leavesQuery = $rootNode->children()->andWhere( [ - Tree::ATTR_ACTIVE => Tree::ACTIVE, - Tree::ATTR_VISIBLE => Tree::VISIBLE, + Tree::ATTR_ACTIVE => Tree::ACTIVE, + Tree::ATTR_VISIBLE => Tree::VISIBLE, Tree::ATTR_ACCESS_DOMAIN => \Yii::$app->language, ] ); @@ -306,7 +350,7 @@ public static function getMenuItems($rootName, $checkUserPermissions = false) // tree mapping and leave stack $treeMap = []; - $stack = []; + $stack = []; if (count($leaves) > 0) { @@ -315,18 +359,18 @@ public static function getMenuItems($rootName, $checkUserPermissions = false) // prepare node identifiers $pageOptions = [ 'data-page-id' => $page->id, - 'data-lvl' => $page->lvl, + 'data-lvl' => $page->lvl, ]; $itemTemplate = [ - 'label' => ($page->icon) ? ' '.$page->name : $page->name, - 'url' => $page->createUrl(), + 'label' => ($page->icon) ? ' ' . $page->name : $page->name, + 'url' => $page->createUrl(), 'linkOptions' => $pageOptions, - 'visible' => ($checkUserPermissions) ? + 'visible' => ($checkUserPermissions) ? Yii::$app->user->can(substr(str_replace('/', '_', $page->route), 1), ['route' => true]) : true, ]; - $item = $itemTemplate; + $item = $itemTemplate; // Count items in stack $counter = count($stack); @@ -340,20 +384,39 @@ public static function getMenuItems($rootName, $checkUserPermissions = false) // Stack is now empty (check root again) if ($counter == 0) { // assign root node - $i = count($treeMap); + $i = count($treeMap); $treeMap[$i] = $item; - $stack[] = &$treeMap[$i]; + $stack[] = &$treeMap[$i]; } else { if (!isset($stack[$counter - 1]['items'])) { $stack[$counter - 1]['items'] = []; } // add the node to parent node - $i = count($stack[$counter - 1]['items']); + $i = count($stack[$counter - 1]['items']); $stack[$counter - 1]['items'][$i] = $item; - $stack[] = &$stack[$counter - 1]['items'][$i]; + $stack[] = &$stack[$counter - 1]['items'][$i]; } } } return array_filter($treeMap); } + + /** + * Get virtual name_id + * @return string + */ + public function getNameId() + { + return $this->name_id; + } + + /** + * Generate and Set virtual attribute name_id + * + * @param mixed $name_id + */ + public function setNameId($name_id) + { + $this->name_id = $name_id; + } } diff --git a/tests/_support/_generated/AcceptanceTesterActions.php b/tests/_support/_generated/AcceptanceTesterActions.php index 247a1a4..3b7d97c 100644 --- a/tests/_support/_generated/AcceptanceTesterActions.php +++ b/tests/_support/_generated/AcceptanceTesterActions.php @@ -1,4 +1,4 @@ -see('General'); $I->see('Route'); -$I->fillField('#tree-name_id','root_en'); -$I->fillField('#tree-name','root_en'); +$I->fillField('#tree-domain_id','root'); +$I->fillField('#tree-name','Home'); $I->click('Save'); $I->wait(2); @@ -37,7 +37,6 @@ $I->click('.kv-create'); $I->wait(2); -$I->fillField('#tree-name_id',uniqid('node-')); $I->fillField('#tree-name',uniqid('node-')); $I->click('Save'); $I->wait(2); diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 7ed8b66..9c9ae2b 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -21,6 +21,7 @@ services: - seleniumfirefox environment: APP_NAME: pages + APP_MIGRATION_LOOKUP: '@vendor/dmstr/yii2-pages-module/tests/migrations' APP_TITLE: 'dmstr/yii2-pages TESTING v2' YII_ENV: 'prod' YII_DEBUG: 'false' diff --git a/tests/migrations/m160415_095116_add_root_node.php b/tests/migrations/m160415_095116_add_root_node.php new file mode 100644 index 0000000..cd62b44 --- /dev/null +++ b/tests/migrations/m160415_095116_add_root_node.php @@ -0,0 +1,26 @@ +execute( + " +INSERT INTO `dmstr_page` (`id`, `root`, `lft`, `rgt`, `lvl`, `page_title`, `name`, `domain_id`, `slug`, `route`, `view`, `default_meta_keywords`, `default_meta_description`, `request_params`, `owner`, `access_owner`, `access_domain`, `access_read`, `access_update`, `access_delete`, `icon`, `icon_type`, `active`, `selected`, `disabled`, `readonly`, `visible`, `collapsed`, `movable_u`, `movable_d`, `movable_l`, `movable_r`, `removable`, `removable_all`, `created_at`, `updated_at`) +VALUES + (1, 1, 1, 2, 0, '', 'Startseite', 'root', NULL, '', '', '', '', '{}', NULL, NULL, 'de', NULL, NULL, NULL, '', 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, '2016-04-15 09:53:03', '2016-04-15 09:53:03'); + +" + ); + } + + public function down() + { + echo "m160415_095116_add_root_node cannot be reverted.\n"; + + return false; + } +} diff --git a/tests/unit/ModelTest.php b/tests/unit/ModelTest.php index 2d929c5..148c771 100644 --- a/tests/unit/ModelTest.php +++ b/tests/unit/ModelTest.php @@ -17,8 +17,31 @@ public function testInit() public function testMenuItems() { - $tree = Tree::getMenuItems('root_en'); + $tree = Tree::getMenuItems(Tree::ROOT_NODE_PREFIX); Debug::debug($tree); } + /** + * Test the virtual name_id attribute setter and getter for 'de' and 'en' root pages + * @return mixed + */ + public function testNameId() + { + $pages = Tree::findAll( + [ + Tree::ATTR_DOMAIN_ID => Tree::ROOT_NODE_PREFIX, + Tree::ATTR_ACTIVE => Tree::ACTIVE, + Tree::ATTR_VISIBLE => Tree::VISIBLE, + ] + ); + if ($pages) { + foreach ($pages as $page) { + $buildNameId = $page->domain_id . '_' . $page->access_domain; + $this->assertSame($buildNameId, $page->name_id, 'NameID was not set proberly'); + } + } else { + return $this->assertNotEmpty($pages, 'No Page not found!'); + } + } + } \ No newline at end of file diff --git a/views/treeview/_form.php b/views/treeview/_form.php index a7f7115..e28e6d3 100755 --- a/views/treeview/_form.php +++ b/views/treeview/_form.php @@ -116,7 +116,7 @@ [ 'addon' => ['prepend' => ['content' => 'Name ID']] ] - )->textInput()->label(false) ?> + )->textInput(['value' => $node->getNameId(), 'disabled' => 'disabled'])->label(false) ?>