Entri
Banyak pengembang web pemula dihadapkan dengan kebutuhan untuk membuat menu, direktori atau kategori untuk proyek Yii2 mereka, yang akan memiliki struktur hierarkis, tetapi pada saat yang sama mendukung multibahasa. Tugasnya cukup sederhana, tetapi tidak terlalu jelas dalam kerangka kerja kerangka ini. Ada sejumlah besar ekstensi yang sudah jadi untuk membuat struktur pohon (menu, direktori, dll.), Tetapi agak sulit untuk menemukan solusi yang akan mendukung pekerjaan penuh dengan beberapa bahasa. Selain itu, kami tidak berbicara tentang menerjemahkan antarmuka menggunakan alat kerangka kerja standar, tetapi tentang menyimpan data dalam database dalam beberapa bahasa. Juga cukup sulit untuk menemukan widget yang nyaman dan berfungsi penuh untuk mengelola pohon, yang juga bisa berfungsi dengan konten multibahasa tanpa manipulasi kode yang rumit.
Saya ingin berbagi resep tentang cara membuat modul serupa menggunakan contoh implementasi modul menu. Sebagai contoh, saya akan menggunakan templat aplikasi Yii2 App Basic, tetapi Anda dapat menyesuaikan semuanya dengan templat Anda jika berbeda dari yang dasar.
Persiapan
Untuk menyelesaikan tugas ini, kita membutuhkan beberapa ekstensi yang luar biasa, yaitu:
Pasang data ekstensi melalui komposer:
composer require paulzi/yii2-adjacency-list composer require execut/yii2-widget-bootstraptreeview composer require creocoder/yii2-translateable
Untuk mengimplementasikan menu sebagai modul, gunakan generator Gii (atau secara manual) buat modul menu baru dan sambungkan dalam pengaturan aplikasi.
Proyek juga harus memiliki mekanisme alih bahasa yang telah dikonfigurasi. Saya lebih suka menggunakan ekstensi ini untuk Yii2 .
Pembuatan model
Untuk menyimpan menu (atau entitas lain yang memiliki multibahasa) dalam database, kita perlu membuat dua tabel. Sebenarnya, metode yang berbeda dapat digunakan untuk menyimpan data multibahasa, tapi saya suka opsi dengan dua tabel, yang satu menyimpan esensi itu sendiri, dan yang kedua - variasi bahasanya, lebih dari yang lain. Lebih mudah menggunakan migrasi untuk membuat tabel. Berikut adalah contoh dari migrasi semacam itu:
m180819_083502_menu_init.php <?php use yii\db\Schema; use yii\db\Migration; class m180819_083502_menu_init extends Migration { public function init() { $this->db = 'db'; parent::init(); } public function safeUp() { $tableOptions = 'ENGINE=InnoDB'; $this->createTable('{{%menu}}', [ 'id'=> $this->primaryKey(11), 'parent_id'=> $this->integer(11)->null()->defaultValue(null), 'link'=> $this->string(255)->notNull()->defaultValue('#'), 'link_attributes'=> $this->text()->notNull(), 'icon_class'=> $this->string(255)->notNull(), 'sort'=> $this->integer(11)->notNull()->defaultValue(0), 'status'=> $this->tinyInteger(1)->notNull()->defaultValue(1), ], $tableOptions); $this->createIndex('parent_sort', '{{%menu}}', ['parent_id','sort'], false); $this->createTable('{{%menu_lang}}', [ 'owner_id'=> $this->integer(11)->notNull(), 'language'=> $this->string(2)->notNull(), 'name'=> $this->string(255)->notNull(), 'title'=> $this->text()->notNull(), ], $tableOptions); $this->addPrimaryKey('pk_on_menu_lang', '{{%menu_lang}}', ['owner_id','language']); $this->addForeignKey( 'fk_menu_lang_owner_id', '{{%menu_lang}}', 'owner_id', '{{%menu}}', 'id', 'CASCADE', 'CASCADE' ); // Insert sample data $this->batchInsert( '{{%menu}}', ['id', 'parent_id', 'link', 'link_attributes', 'icon_class', 'sort', 'status'], [ [ 'id' => '1', 'parent_id' => null, 'link' => '#', 'link_attributes' => '', 'icon_class' => '', 'sort' => '0', 'status' => '0', ], [ 'id' => '2', 'parent_id' => '1', 'link' => '/', 'link_attributes' => '', 'icon_class' => 'fa fa-home', 'sort' => '0', 'status' => '1', ], ] ); $this->batchInsert( '{{%menu_lang}}', ['owner_id', 'language', 'name', 'title'], [ [ 'owner_id' => '1', 'language' => 'ru', 'name' => ' ', 'title' => '', ], [ 'owner_id' => '1', 'language' => 'en', 'name' => 'Main menu', 'title' => '', ], [ 'owner_id' => '2', 'language' => 'ru', 'name' => '', 'title' => ' ', ], [ 'owner_id' => '2', 'language' => 'en', 'name' => 'Home', 'title' => 'Site homepage', ], ] ); } public function safeDown() { $this->truncateTable('{{%menu}} CASCADE'); $this->dropForeignKey('fk_menu_lang_owner_id', '{{%menu_lang}}'); $this->dropTable('{{%menu}}'); $this->dropPrimaryKey('pk_on_menu_lang', '{{%menu_lang}}'); $this->dropTable('{{%menu_lang}}'); } }
Letakkan file migrasi ini di folder / migrasi proyek kami, dan
jalankan perintah di konsol:
php yii migrate
Setelah kami membuat tabel yang diperlukan dan menambahkan menu baru ke mereka menggunakan migrasi, kita perlu membuat model. Karena multibahasa dan pohon dapat ditemukan tidak hanya di menu, tetapi juga di entitas lain (misalnya, halaman situs) dalam proyek, saya menyarankan agar metode yang menerapkan mekanisme multibahasa dan organisasi pohon dimasukkan ke dalam sifat yang terpisah sehingga kita dapat dengan mudah menggunakannya nanti mereka dalam model lain tanpa duplikasi kode. Buat folder ciri di root aplikasi (jika belum ada di sana) dan letakkan dua file di sana:
LangTrait.php <?php namespace app\traits; use Yii; use yii\behaviors\SluggableBehavior; use creocoder\translateable\TranslateableBehavior; trait LangTrait { public static function langClass() { return self::class . 'Lang'; } public static function langTableName() { return self::tableName() . '_lang'; } public function langBehaviors($translationAttributes) { return [ 'translateable' => [ 'class' => TranslateableBehavior::class, 'translationAttributes' => $translationAttributes, 'translationRelation' => 'translations', 'translationLanguageAttribute' => 'language', ], ]; } public function transactions() { return [ self::SCENARIO_DEFAULT => self::OP_INSERT | self::OP_UPDATE, ]; } public function getLang() { return $this->hasOne(self::langClass(), ['owner_id' => 'id'])->where([self::langTableName() . '.language' => Yii::$app->language]); } public function getTranslations() { return $this->hasMany(self::langClass(), ['owner_id' => 'id']); } }
TreeTrait.php <?php namespace app\traits; use Yii; use yii\helpers\Html; use paulzi\adjacencyList\AdjacencyListBehavior; trait TreeTrait { private static function getQueryClass() { return self::class . 'Query'; } public function treeBehaviors() { return [ 'tree' => [ 'class' => AdjacencyListBehavior::class, 'parentAttribute' => 'parent_id', 'sortable' => [ 'step' => 10, ], 'checkLoop' => false, 'parentsJoinLevels' => 5, 'childrenJoinLevels' => 5, ], ]; } public static function find() { $queryClass = self::getQueryClass(); return new $queryClass(get_called_class()); } public static function listTree($node = null, $level = 1, $nameAttribute = 'name', $prefix = '-->') { $result = []; if (!$node) { $node = self::find()->roots()->one()->populateTree(); } if ($node->isRoot()) { $result[$node['id']] = mb_strtoupper($node[$nameAttribute ?: 'slug']); } if ($node['children']) { foreach ($node['children'] as $child) { $result[$child['id']] = str_repeat($prefix, $level) . $child[$nameAttribute]; $result = $result + self::listTree($child, $level + 1, $nameAttribute); } } return $result; } public static function treeViewData($node = null) { if ($node === null) { $node = self::find()->roots()->one()->populateTree(); } $result = null; $items = null; $children = null; if ($node['children']) { foreach ($node['children'] as $child) { $items[] = self::treeViewData($child); } $children = call_user_func_array('array_merge', $items); } $result[] = [ 'text' => Html::a($node['lang']['name'] ?: $node['id'], ['update', 'id' => $node['id']], ['title' => Yii::t('app', ' ')]), 'tags' => [ Html::a( '<i class="glyphicon glyphicon-arrow-down"></i>', ['move-down', 'id' => $node['id']], ['title' => Yii::t('app', ' ')] ), Html::a( '<i class="glyphicon glyphicon-arrow-up"></i>', ['move-up', 'id' => $node['id']], ['title' => Yii::t('app', ' ')] ) ], 'backColor' => $node['status'] == 0 ? '#ccc' : '#fff', 'selectable' => false, 'nodes' => $children, ]; return $result; } }
Sekarang kita akan secara langsung membuat model sendiri untuk bekerja dengan menu, di mana kita menghubungkan ciri-ciri pohon dan multibahasa. Kami menempatkan model di / modules / menu / models:
Menu.php <?php namespace app\modules\menu\models; use Yii; class Menu extends \yii\db\ActiveRecord { use \app\traits\TreeTrait; use \app\traits\LangTrait; const STATUS_ACTIVE = 1; const STATUS_INACTIVE = 0; public function behaviors() { $behaviors = []; return array_merge( $behaviors, $this->treeBehaviors(), $this->langBehaviors(['name', 'title']) ); } public static function tableName() { return 'menu'; } public function rules() { return [ [['parent_id', 'sort', 'status'], 'integer'], [['link', 'icon_class'], 'string', 'max' => 255], [['link_attributes'], 'string'], [['link'], 'default', 'value' => '#'], [['link_attributes', 'icon_class'], 'default', 'value' => ''], [['parent_id'], 'exist', 'skipOnError' => true, 'targetClass' => self::class, 'targetAttribute' => ['parent_id' => 'id']], ]; } public function attributeLabels() { return [ 'id' => Yii::t('app', 'ID'), 'parent_id' => Yii::t('app', ''), 'link' => Yii::t('app', ''), 'link_attributes' => Yii::t('app', ' (JSON )'), 'icon_class' => Yii::t('app', ' '), 'sort' => Yii::t('app', ''), 'status' => Yii::t('app', ''), ]; } public static function menuItems($node = null) { if ($node === null) { $node = self::find()->roots()->one()->populateTree(); } $result = null; $items = null; $children = null; if ($node['children']) { foreach ($node['children'] as $child) { $items[] = self::menuItems($child); } $children = call_user_func_array('array_merge', $items); } $result[] = [ 'label' => ($node['icon_class'] ? '<i class="' . $node['icon_class'] . '"></i> ' . ($node['lang']['name'] ?: $node['id']) : ($node['lang']['name'] ?: $node['id'] )), 'encode' => ($node['icon_class'] ? false : true), 'url' => [$node['link'], 'language' => Yii::$app->language], 'active' => $node['link'] == Yii::$app->request->url ? true : false, 'linkOptions' => ($node['link_attributes'] ? array_merge(json_decode($node['link_attributes'], true), ['title' => ($node['lang']['title'] ?: $node['lang']['name'])]) : ['title' => ($node['lang']['title'] ?: $node['lang']['name'])]), 'items' => $children, ]; return $result; } }
MenuLang.php <?php namespace app\modules\menu\models; use Yii; class MenuLang extends \yii\db\ActiveRecord { public static function tableName() { return 'menu_lang'; } public function rules() { return [ [['name'], 'required'], [['name', 'title'], 'string', 'max' => 255], ]; } public function attributeLabels() { return [ 'owner_id' => Yii::t('app', ''), 'language' => Yii::t('app', ''), 'name' => Yii::t('app', ''), 'title' => Yii::t('app', ' '), ]; } public function getOwner() { return $this->hasOne(Menu::class, ['id' => 'owner_id']); } }
MenuQuery.php <?php namespace app\modules\menu\models; use paulzi\adjacencyList\AdjacencyListQueryTrait; class MenuQuery extends \yii\db\ActiveQuery { use AdjacencyListQueryTrait; }
MenuSearch.php <?php namespace app\modules\menu\models; use Yii; use yii\base\Model; use yii\data\ActiveDataProvider; use app\modules\menu\models\Menu; class MenuSearch extends Menu { public $name; public function rules() { return [ [['id', 'parent_id', 'sort', 'status'], 'integer'], [['link', 'link_attributes', 'icon_class'], 'safe'], [['name'], 'safe'], ]; } public function scenarios() { return Model::scenarios(); } public function search($params) { $query = parent::find()->joinWith(['lang']); $dataProvider = new ActiveDataProvider([ 'query' => $query, 'sort' => ['defaultOrder' => ['sort' => SORT_ASC]] ]); $dataProvider->sort->attributes['name'] = [ 'asc' => [ 'menu_lang.name' => SORT_ASC, ], 'desc' => [ 'menu_lang.name' => SORT_DESC, ], ]; $this->load($params); if (!$this->validate()) { return $dataProvider; } $query->andFilterWhere([ 'id' => $this->id, 'parent_id' => $this->parent_id, 'sort' => $this->sort, 'status' => $this->status, ]); $query->andFilterWhere(['like', 'link', $this->link]); $query->andFilterWhere(['like', 'link_attributes', $this->link_attributes]); $query->andFilterWhere(['like', 'icon_class', $this->icon_class]); $query->andFilterWhere(['like', 'name', $this->name]); return $dataProvider; } }
Membuat pengontrol
Untuk operasi CRUD pada pohon multibahasa, kita membutuhkan pengontrol. Untuk menyederhanakan kehidupan kita di masa depan, kita akan membuat satu pengendali dasar di mana akan ada semua tindakan yang diperlukan, dan untuk entitas yang berbeda, apakah itu menu, atau direktori, atau halaman, kita akan mewarisi darinya.
Kelas-kelas dari proyek kami yang akan kami gunakan sebagai kelas dasar akan ditempatkan di folder / base. Buat file /base/controllers/AdminLangTreeController.php. Pengontrol ini akan menjadi dasar untuk CRUD dari semua entitas di mana pohon dan multibahasa diimplementasikan:
AdminLangTreeController.php <?php namespace app\base\controllers; use Yii; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; use yii\helpers\Url; class AdminLangTreeController extends Controller { public $modelClass; public $modelClassSearch; public $modelName; public $modelNameLang; public function behaviors() { return [ 'verbs' => [ 'class' => VerbFilter::class, 'actions' => [ 'delete' => ['POST'], ], ], ]; } public function actionIndex() { // , if (count($this->modelClass::find()->roots()->all()) == 0) { $model = new $this->modelClass; $model->makeRoot()->save(); Yii::$app->session->setFlash('info', Yii::t('app', ' ')); return $this->redirect(['index']); } $searchModel = new $this->modelClassSearch; $dataProvider = $searchModel->search(Yii::$app->request->queryParams); $dataProvider->pagination = false; return $this->render('index', [ 'searchModel' => $searchModel, 'dataProvider' => $dataProvider, ]); } public function actionCreate() { // if (count($this->modelClass::find()->roots()->all()) == 0) { return $this->redirect(['index']); } // $model = new $this->modelClass; $root = $model::find()->roots()->one(); $model->parent_id = $root->id; // if ($model->load(Yii::$app->request->post()) && $model->validate()) { $parent = $model::findOne($model->parent_id); $model->appendTo($parent)->save(); // foreach (Yii::$app->request->post($this->modelNameLang, []) as $language => $data) { foreach ($data as $attribute => $translation) { $model->translate($language)->$attribute = $translation; } } $model->save(); Yii::$app->session->setFlash('success', Yii::t('app', ' ')); return $this->redirect(['update', 'id' => $model->id]); } else { return $this->render('create', [ 'model' => $model, ]); } } public function actionUpdate($id) { // $model = $this->modelClass::find()->with('translations')->where(['id' => $id])->one(); if ($model === null) { throw new NotFoundHttpException(Yii::t('app', ' ')); } // if ($model->load(Yii::$app->request->post()) && $model->save()) { foreach (Yii::$app->request->post($this->modelNameLang, []) as $language => $data) { foreach ($data as $attribute => $translation) { $model->translate($language)->$attribute = $translation; } } $model->save(); Yii::$app->session->setFlash('success', Yii::t('app', ' ')); if (Yii::$app->request->post('save') !== null) { return $this->redirect(['index']); } return $this->redirect(['update', 'id' => $model->id]); } else { return $this->render('update', [ 'model' => $model, ]); } } public function actionDelete($id) { $model = $this->findModel($id); // , if (count($model->children) > 0) { Yii::$app->session->setFlash('error', Yii::t('app', ' , . ')); return $this->redirect(['index']); } // if ($model->isRoot()) { Yii::$app->session->setFlash('error', Yii::t('app', ' ')); return $this->redirect(['index']); } // if ($model->delete()) { Yii::$app->session->setFlash('success', Yii::t('app', ' ')); } return $this->redirect(['index']); } public function actionMoveUp($id) { $model = $this->findModel($id); if ($prev = $model->getPrev()->one()) { $model->moveBefore($prev)->save(); $model->reorder(false); } else { Yii::$app->session->setFlash('error', Yii::t('app', ' ')); } return $this->redirect(Yii::$app->request->referrer); } public function actionMoveDown($id) { $model = $this->findModel($id); if ($next = $model->getNext()->one()) { $model->moveAfter($next)->save(); $model->reorder(false); } else { Yii::$app->session->setFlash('error', Yii::t('app', ' ')); } return $this->redirect(Yii::$app->request->referrer); } protected function findModel($id) { if (($model = $this->modelClass::findOne($id)) !== null) { return $model; } else { throw new NotFoundHttpException(Yii::t('app', ' ')); } } }
Sekarang di modul, buat file / modul / menu / controller / AdminController.php. Ini akan menjadi pengontrol utama untuk mengelola menu, dan karena ia mengimplementasikan pohon dan multibahasa, itu akan mewarisi dari basis yang sudah kita buat pada langkah sebelumnya:
AdminController.php <?php namespace app\modules\menu\controllers; use app\base\controllers\AdminLangTreeController as BaseController; class AdminController extends BaseController { public $modelClass = \app\modules\menu\models\Menu::class; public $modelClassSearch = \app\modules\menu\models\MenuSearch::class; public $modelName = 'Menu'; public $modelNameLang = 'MenuLang'; }
Seperti yang Anda lihat, kode controller ini hanya berisi nama-nama model dan kelas-kelasnya. Yaitu, untuk membuat kontroler CRUD untuk modul lain (katalog, rubrikator, dll.), Yang juga akan menggunakan pohon dan multibahasa, Anda dapat melakukan hal yang sama dengan cara yang sama - perluas pengendali dasar.
Membuat antarmuka untuk mengelola menu
Tahap terakhir adalah pembuatan antarmuka untuk mengelola pohon multibahasa. Ekstensi Bootstrap Treeview menangani tugas menampilkan pohon, yang dapat dikonfigurasi dengan cukup fleksibel dan mendukung banyak fungsi yang nyaman (misalnya, cari di pohon). Buat tampilan indeks untuk menampilkan pohon itu sendiri dan taruh di /modules/menu/views/admin/index.php:
index.php <?php use yii\helpers\Html; use yii\grid\GridView; use yii\widgets\ActiveForm; use execut\widget\TreeView; $this->title = Yii::t('app', ' '); $this->params['breadcrumbs'][] = $this->title; ?> <div class="row"> <div class="col-md-6"> <div class="panel panel-primary"> <div class="panel-heading"> <?= Html::a(Yii::t('app', ''), ['create'], ['class' => 'btn btn-success btn-flat']) ?> </div> <div class="panel-body"> <?= TreeView::widget([ 'id' => 'tree', 'data' => $searchModel::treeViewData($searchModel::find()->roots()->one()), 'header' => Yii::t('app', ' '), 'searchOptions' => [ 'inputOptions' => [ 'placeholder' => Yii::t('app', ' ') . '...' ], ], 'clientOptions' => [ 'selectedBackColor' => 'rgb(40, 153, 57)', 'borderColor' => '#fff', 'levels' => 10, 'showTags' => true, 'tagsClass' => 'badge', 'enableLinks' => true, ], ]) ?> </div> </div> </div> </div>
Jadi kami sampai pada tahap yang paling menarik dari kasus ini: bagaimana membuat formulir untuk membuat / mengedit data multibahasa. Buat tiga file di folder / modules / menu / views / admin:
create.php <?php use yii\helpers\Html; $this->title = Yii::t('app', ''); $this->params['breadcrumbs'][] = ['label' => Yii::t('app', ' '), 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title; echo $this->render('_form', [ 'model' => $model, ]);
update.php <?php use yii\helpers\Html; $this->title = Yii::t('app', '') . ': ' . $model->name; $this->params['breadcrumbs'][] = ['label' => Yii::t('app', ' '), 'url' => ['index']]; $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['update', 'id' => $model->id]]; $this->params['breadcrumbs'][] = Yii::t('app', ''); echo $this->render('_form', [ 'model' => $model, ]);
_form.php <?php use yii\helpers\Html; use yii\widgets\ActiveForm; if ($model->isNewRecord) { $model->status = true; } ?> <div class="panel panel-primary"> <?php $form = ActiveForm::begin(); ?> <div class="panel-body"> <fieldset> <legend><?= Yii::t('app', ' ') ?></legend> <div class="row"> <div class="col-md-4"> <?php if (!$model->isRoot()) { ?> <?= $form->field($model, 'parent_id')->dropDownList($model::listTree()) ?> <?php } ?> <?= $form->field($model, 'link')->textInput(['maxlength' => true]) ?> <?= $form->field($model, 'link_attributes')->textInput(['maxlength' => true]) ?> <?= $form->field($model, 'icon_class')->textInput(['maxlength' => true]) ?> <?= $form->field($model, 'status')->checkbox() ?> </div> </div> </fieldset> <fieldset> <legend><?= Yii::t('app', '') ?></legend> <ul class="nav nav-tabs" role="tablist"> <?php foreach (Yii::$app->urlManager->languages as $key => $language) { ?> <li role="presentation" <?= $key == 0 ? 'class="active"' : '' ?>> <a href="#tab-content-<?= $language ?>" aria-controls="tab-content-<?= $language ?>" role="tab" data-toggle="tab"><?= $language ?></a> </li> <?php } ?> </ul> <div class="tab-content"> <?php foreach (Yii::$app->urlManager->languages as $key => $language) { ?> <div role="tabpanel" class="tab-pane <?= $key == 0 ? 'active' : '' ?>" id="tab-content-<?= $language ?>"> <?= $form->field($model->translate($language), "[$language]name")->textInput() ?> <?= $form->field($model->translate($language), "[$language]title")->textInput() ?> </div> <?php } ?> </div> </fieldset> </div> <div class="box-footer"> <?= Html::submitButton($model->isNewRecord ? '<i class="fa fa-plus"></i> ' . Yii::t('app', '') : '<i class="fa fa-refresh"></i> ' . Yii::t('app', ''), ['class' => $model->isNewRecord ? 'btn btn-primary' : 'btn btn-success']) ?> <?= !$model->isNewRecord ? Html::submitButton('<i class="fa fa-save"></i> ' . Yii::t('app', ''), ['class' => 'btn btn-warning', 'name' => 'save']) : ''; ?> <?= !$model->isNewRecord ? Html::a('<i class="fa fa-trash"></i> ' . Yii::t('app', ''), ['delete', 'id' => $model->id], ['class' => 'btn btn-danger', 'data' => ['confirm' => Yii::t('app', ' , ?'), 'method' => 'post']]) : ''; ?> </div> <?php ActiveForm::end(); ?> </div>
Jangan lupa bahwa bahasa default harus ditentukan dalam aplikasi (parameter bahasa), dan dalam parameter UrlManager - array dengan daftar bahasa (bahasa) yang akan kita gunakan. Bahasa default harus menjadi yang pertama dalam array ini.
Kesimpulan
Akibatnya, kita harus mendapatkan yang berikut:
- Modul siap pakai untuk menu seperti pohon multibahasa dari sebuah situs dengan antarmuka yang nyaman dan dapat disesuaikan;
- Pengontrol CRUD dasar yang dapat diwarisi saat membuat modul lain yang menggunakan kayu dan multibahasa.
- Dua sifat (multi bahasa dan pohon) yang dapat dihubungkan ke model untuk mengimplementasikan fungsi yang sesuai.
Saya harap artikel ini bermanfaat dan akan membantu Anda dalam mengembangkan proyek baru yang bagus di Yii2.