+ ```
+
+7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
+ with a clear title and description.
+
+**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
+license your work under the same license as that used by the project.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..96e283b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 LoveOrigami
+
+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.
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..96e283b
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 LoveOrigami
+
+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.
\ No newline at end of file
diff --git a/Module.php b/Module.php
new file mode 100644
index 0000000..594d3d5
--- /dev/null
+++ b/Module.php
@@ -0,0 +1,36 @@
+i18n->translations['plugin'])) {
+ \Yii::$app->i18n->translations['plugin'] = [
+ 'class' => 'yii\i18n\PhpMessageSource',
+ 'sourceLanguage' => 'en',
+ 'basePath' => '@lo/plugins/messages'
+ ];
+ }
+
+ //user did not define the Navbar?
+ if (!$this->pluginsDir) {
+ $this->pluginsDir = [
+ '@lo/plugins/plugins'
+ ];
+ }
+ }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d482ddb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,98 @@
+# Getting started with Yii2-plugins-system
+
+Yii2-plugins-system is designed to work out of the box. It means that installation requires
+minimal steps. Only one configuration step should be taken and you are ready to
+have plugin system on your Yii2 website.
+
+!["Plugins"](docs/img/tab_plugins.jpg)
+
+### 1. Download
+
+Yii2-plugins-system can be installed using composer. Run following command to download and
+install Yii2-plugins-system:
+
+```bash
+composer require "loveorigami/yii2-plugins-system": "*"
+```
+
+### 2. Update database schema
+
+The last thing you need to do is updating your database schema by applying the
+migrations. Make sure that you have properly configured `db` application component
+and run the following command:
+
+```bash
+$ php yii migrate/up --migrationPath=@vendor/loveorigami/yii2-plugins-system/migrations
+```
+
+### 3. Configure application
+
+Let's start with defining module in `@backend/config/main.php`:
+
+```php
+'modules' => [
+ 'plugins' => [
+ 'class' => 'lo\plugins\Module',
+ 'pluginsDir'=>[
+ '@lo/plugins/plugins', // default dir with core plugins
+ // '@common/plugins', // dir with our plugins
+ ]
+ ],
+],
+```
+That's all, now you have module installed and configured in advanced template.
+
+Next, open `@frontend/config/main.php` and add following:
+
+```
+'bootstrap' => ['log', 'plugins'],
+...
+'components' => [
+ 'plugins' => [
+ 'class' => 'lo\plugins\components\EventBootstrap',
+ 'appId' => 'frontend'
+ ],
+ ...
+]
+```
+
+Also do the same thing with `@backend/config/main.php`:
+
+```
+'bootstrap' => ['log', 'plugins'],
+...
+'components' => [
+ 'plugins' => [
+ 'class' => 'lo\plugins\components\EventBootstrap',
+ 'appId' => 'backend'
+ ],
+ ...
+]
+```
+
+## Core plugins (examples)
+
+* [Hello world!] (plugins/helloworld)
+* [Code Highlighting] (plugins/code)
+* [Http Authentication] (plugins/httpauth)
+
+## Your plugins
+
+* [Create] (docs/create_plugin.md)
+* [Install] (docs/install_plugin.md)
+
+## Contributing to this project
+
+Anyone and everyone is welcome to contribute. Please take a moment to
+review the [guidelines for contributing](CONTRIBUTING.md).
+
+## License
+
+Yii2-plugins-system is released under the MIT License. See the bundled [LICENSE.md](LICENSE.md)
+for details.
+
+## Thanks
+
+* Bariew for [event manager] (https://github.com/bariew/yii2-event-component) component
+* Troxa for [yii2-shortcodes] (https://github.com/tpoxa/yii2-shortcodes) component
+* ElisDn for great webinar about [yii2-events] (http://www.elisdn.ru/blog/74/events-on-yii2-and-javascript) (by ru)
\ No newline at end of file
diff --git a/components/EventBootstrap.php b/components/EventBootstrap.php
new file mode 100644
index 0000000..3acd5f3
--- /dev/null
+++ b/components/EventBootstrap.php
@@ -0,0 +1,84 @@
+, modify Loveorigami
+ */
+class EventBootstrap implements BootstrapInterface
+{
+ /**
+ * Application id for category plugins.
+ * Support values: frontend, backend, common
+ * Default: frontend
+ * @var appId string
+ */
+ public static $appId = 'frontend';
+
+ /**
+ * @var EventManager EventManager memory storage for getEventManager method
+ */
+ protected static $_eventManager = [];
+
+ /**
+ * @inheritdoc
+ */
+ public function bootstrap($app)
+ {
+ self::getEventManager($app);
+ }
+
+ /**
+ * finds and creates app event manager from its settings
+ * @param Application $app yii app
+ * @return EventManager app event manager component
+ * @throws Exception Define event manager
+ */
+ public static function getEventManager($app)
+ {
+ if (self::$_eventManager) {
+ return self::$_eventManager;
+ }
+
+ foreach ($app->components as $name => $config) {
+ $class = is_string($config) ? $config : @$config['class'];
+
+ // if eventManager component in config
+ if ($class == str_replace('Bootstrap', 'Manager', get_called_class())) {
+ self::$_eventManager = $app->$name->events;
+ }
+
+ // this class. set $appId from config
+ if ($class == str_replace('Manager', 'Bootstrap', get_called_class())) {
+ if($app->$name->appId){
+ self::$appId = $app->$name->appId;
+ };
+ }
+ }
+
+ $events = ModelEvent::eventList(self::$appId);
+
+ // merge config events with plugins
+ self::$_eventManager = array_merge_recursive($events, self::$_eventManager);
+
+ $app->setComponents([
+ 'eventManager' => [
+ 'class' => 'lo\plugins\components\EventManager',
+ 'events' => self::$_eventManager
+ ],
+ ]);
+
+ return self::$_eventManager = $app->eventManager;
+ }
+}
diff --git a/components/EventManager.php b/components/EventManager.php
new file mode 100644
index 0000000..c186356
--- /dev/null
+++ b/components/EventManager.php
@@ -0,0 +1,67 @@
+ [
+ * $eventName => [
+ * [$handlerClassName, $handlerMethodName]
+ * ]
+ * ]
+ * ]
+ *
+ * @since 1.3.0 handler can also keep additional data and $append boolean as for Event::on() method eg:
+ * ... [[$handlerClassName, $handlerMethodName], ['myData'], false]
+ *
+ * @var array events settings
+ */
+ public $events = [];
+
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ // var_dump($this->events);
+ $this->attachEvents($this->events);
+ }
+
+ /**
+ * attaches all events to all classNames
+ * @param array $eventConfig commonly $this->events config
+ */
+ public function attachEvents($eventConfig)
+ {
+ foreach ($eventConfig as $className => $events) {
+ foreach ($events as $eventName => $handlers) {
+ foreach ($handlers as $handler) {
+ if (is_array($handler) && is_callable($handler[0])) {
+ $data = isset($handler[1]) ? array_pop($handler) : null;
+ $append = isset($handler[2]) ? array_pop($handler) : null;
+ Event::on($className, $eventName, $handler[0], $data, $append);
+ } else if (is_callable($handler)) {
+ Event::on($className, $eventName, $handler);
+ }
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/components/Shortcode.php b/components/Shortcode.php
new file mode 100644
index 0000000..74cbd10
--- /dev/null
+++ b/components/Shortcode.php
@@ -0,0 +1,12 @@
+=2.0.6",
+ "components/highlightjs":"*",
+ "loveorigami/yii2-jsoneditor": "*",
+ "tpoxa/shortcodes": "dev-master"
+ },
+ "autoload": {
+ "psr-4": {
+ "lo\\plugins\\": ""
+ }
+ }
+}
diff --git a/controllers/EventController.php b/controllers/EventController.php
new file mode 100644
index 0000000..fe2bc59
--- /dev/null
+++ b/controllers/EventController.php
@@ -0,0 +1,121 @@
+ [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['post'],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Lists all Event models.
+ * @return mixed
+ */
+ public function actionIndex()
+ {
+ $searchModel = new EventSearch();
+ $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
+
+ return $this->render('index', [
+ 'searchModel' => $searchModel,
+ 'dataProvider' => $dataProvider,
+ ]);
+ }
+
+ /**
+ * Displays a single Event model.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionView($id)
+ {
+ return $this->render('view', [
+ 'model' => $this->findModel($id),
+ ]);
+ }
+
+ /**
+ * Creates a new Event model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ * @return mixed
+ */
+ public function _actionCreate()
+ {
+ $model = new Event();
+
+ if ($model->load(Yii::$app->request->post()) && $model->save()) {
+ return $this->redirect(['view', 'id' => $model->id]);
+ } else {
+ return $this->render('create', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Updates an existing Event 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('index');
+ } else {
+ return $this->render('update', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Deletes an existing Event 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']);
+ }
+
+ /**
+ * Finds the Event model based on its primary key value.
+ * If the model is not found, a 404 HTTP exception will be thrown.
+ * @param integer $id
+ * @return Event the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel($id)
+ {
+ if (($model = Event::findOne($id)) !== null) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+ }
+}
diff --git a/controllers/ItemController.php b/controllers/ItemController.php
new file mode 100644
index 0000000..03e49bf
--- /dev/null
+++ b/controllers/ItemController.php
@@ -0,0 +1,511 @@
+ [
+ 'class' => VerbFilter::className(),
+ 'actions' => [
+ 'delete' => ['post'],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * Lists all Item models.
+ * @return mixed
+ */
+ public function actionIndex()
+ {
+ $searchModel = new ItemSearch();
+ $dataProvider = $searchModel->search(Yii::$app->request->queryParams);
+
+ return $this->render('index', [
+ 'searchModel' => $searchModel,
+ 'dataProvider' => $dataProvider,
+ ]);
+ }
+
+ /**
+ * Displays a info page
+ * @return mixed
+ */
+ public function actionFind()
+ {
+ // Find all plugins
+ $this->findPlugins();
+
+ // Get all activated plugins
+ $this->getActivatedPlugins();
+
+ // Include and update plugins
+ $this->includePlugins();
+
+ $data['plugins'] = self::$plugins_pool;
+ $data['active'] = self::$plugins_active;
+
+ $dataProvider = new ArrayDataProvider([
+ 'allModels' => array_diff_key(self::$plugins_pool, self::$plugins_active),
+ 'pagination' => [
+ 'pageSize' => 20,
+ ],
+ ]);
+
+ return $this->render('find', compact('data', 'dataProvider'));
+ }
+
+ /**
+ * Displays a info page
+ * @return mixed
+ */
+ public function actionInstall($id)
+ {
+ // Find all plugins
+ $this->findPlugins();
+ if (self::$plugins_pool) {
+ foreach (self::$plugins_pool AS $handlerClass => $value) {
+
+ if (md5($handlerClass) == $id) {
+
+ // The plugin information being added to the database
+ $data['Item'] = [
+ "handler_class" => $handlerClass,
+ "name" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['name']),
+ "url" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['url']),
+ "version" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['version']),
+ "text" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['text']),
+ "author" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['author']),
+ "author_url" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['author_url']),
+ "status" => Item::STATUS_ACTIVE
+ ];
+
+ $model = new Item();
+
+ if ($model->load($data) && $model->save()) {
+ // here install events to Event
+ $this->includeEvents($model->id, $handlerClass);
+ } else {
+ Yii::$app->session->setFlash('error', Yii::t('plugin', 'This plugin alredy installed'));
+ }
+ }
+ }
+ }
+ return $this->redirect(Yii::$app->request->referrer);
+ }
+
+ /**
+ * Displays a info page
+ * @return mixed
+ */
+ public function actionInfo()
+ {
+ return $this->render('info');
+ }
+
+
+ /**
+ * Displays a single Item model.
+ * @param integer $id
+ * @return mixed
+ */
+ public function actionView($id)
+ {
+ return $this->render('view', [
+ 'model' => $this->findModel($id),
+ ]);
+ }
+
+ /**
+ * Creates a new Item model.
+ * If creation is successful, the browser will be redirected to the 'view' page.
+ * @return mixed
+ */
+ public function _actionCreate()
+ {
+ $model = new Item();
+
+ if ($model->load(Yii::$app->request->post()) && $model->save()) {
+ return $this->redirect(['view', 'id' => $model->id]);
+ } else {
+ return $this->render('create', [
+ 'model' => $model,
+ ]);
+ }
+ }
+
+ /**
+ * Updates an existing Item 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,
+ ]);
+ }
+ }
+
+ /**
+ * Deletes an existing Item 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']);
+ }
+
+ /**
+ * Finds the Item model based on its primary key value.
+ * If the model is not found, a 404 HTTP exception will be thrown.
+ * @param integer $id
+ * @return Item the loaded model
+ * @throws NotFoundHttpException if the model cannot be found
+ */
+ protected function findModel($id)
+ {
+ if (($model = Item::findOne($id)) !== null) {
+ return $model;
+ } else {
+ throw new NotFoundHttpException('The requested page does not exist.');
+ }
+ }
+
+ /**
+ * Find all plugins from $plugins_dir
+ * @param var $plugin
+ * @return $plugins_pool
+ */
+ protected function findPlugins()
+ {
+ if (!is_array($this->module->pluginsDir)) {
+ throw new \yii\base\InvalidConfigException("Plugins directory is not array.");
+ }
+
+ foreach ($this->module->pluginsDir as $path) {
+ $dir = Yii::getAlias($path);
+ if (!file_exists($dir)) {
+ return $result;
+ }
+ $plugins = array_diff(scandir($dir), ['.', '..']);
+
+ foreach ($plugins AS $key => $name) {
+
+ $basePath = $dir . DIRECTORY_SEPARATOR . $name;
+ if (!is_dir($basePath)) {
+ continue;
+ }
+
+ // Make sure a valid plugin file by the same name as the folder exists
+ $file = $basePath . DIRECTORY_SEPARATOR . ucfirst($name) . ".php";
+
+ if (file_exists($file)) {
+
+ // Register the plugin
+ $handlerClass = Crawler::getNamespace($file) . '\\' . ucfirst($name);
+
+ // If the plugin hasn't already been added and isn't a file
+ if (!isset(self::$plugins_pool[$handlerClass]) AND !stripos($name, ".")) {
+
+ self::$plugins_pool[$handlerClass] = [
+ 'plugin' => $name,
+ 'class' => $handlerClass
+ ];
+
+ // else may be plugin make as inactive
+ if (is_callable([$handlerClass, 'events'])) {
+ self::$plugins_pool[$handlerClass]['events'] = $handlerClass::events();
+ } else {
+ self::$plugins_pool[$handlerClass]['events'] = [];
+ }
+
+ // add info to pool
+ $this->getInfo($handlerClass);
+ }
+
+ } else {
+ // self::$errors[$name][] = "Plugin file " . $name . ".php does not exist.";
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Get Activated Plugins
+ * Get all activated plugins from the database
+ *
+ */
+ protected function getActivatedPlugins()
+ {
+ // Only plugins in the database are active ones
+ $plugins = Item::find()->all();
+
+ if ($plugins) {
+ // For every plugin, store it
+ //var_dump($plugins);
+ foreach ($plugins AS $plugin) {
+ self::$plugins_active[$plugin->handler_class] = [
+ 'plugin' => $plugin->name,
+ 'class' => $plugin->handler_class
+ ];
+
+ if ($plugin->events) {
+ foreach ($plugin->events AS $event) {
+ if ($event->data) {
+ self::$plugins_active[$plugin->handler_class]['events'][$event->trigger_class] = [
+ $event->trigger_event => [$event->handler_method, json_decode($event->data, true)]
+ ];
+ } else {
+ self::$plugins_active[$plugin->handler_class]['events'][$event->trigger_class] = [
+ $event->trigger_event => $event->handler_method
+ ];
+ }
+ }
+ }
+ }
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Include Plugins
+ * Include all active plugins that are in the database
+ *
+ */
+ protected function includePlugins()
+ {
+ if (self::$plugins_active AND !empty(self::$plugins_active)) {
+ // Validate and include our found plugins
+ foreach (self::$plugins_active AS $handlerClass => $value) {
+ // The plugin information being added to the database
+ $data['Item'] = [
+ "handler_class" => $handlerClass,
+ "name" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['name']),
+ "url" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['url']),
+ "version" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['version']),
+ "text" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['text']),
+ "author" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['author']),
+ "author_url" => trim(self::$plugins_pool[$handlerClass]['plugin_info']['author_url'])
+ ];
+
+ $model = Item::findOne(['handler_class' => $handlerClass]);
+
+ if ($model->load($data) && $model->save()) {
+ // here install events to Event
+ $this->includeEvents($model->id, $handlerClass);
+ }
+ }
+ }
+ }
+
+ /**
+ * Include events from $handlerClass
+ * @param int $plugin_id
+ * @param var $handlerClass
+ * @return bool
+ */
+ protected function includeEvents($plugin_id, $handlerClass)
+ {
+ if (isset(self::$plugins_active[$handlerClass]['events']) && isset(self::$plugins_pool[$handlerClass]['events'])) {
+ // here remove all active_events from db, if is not in $plugins_pool
+ foreach (self::$plugins_active[$handlerClass]['events'] as $className => $events) {
+
+ foreach ($events as $eventName => $handler) {
+
+ $data1['plugin_id'] = $plugin_id;
+ $data1['trigger_event'] = $eventName;
+
+ if (isset(self::$plugins_pool[$handlerClass]['events'][$className][$eventName])) {
+
+ $handlerPool = self::$plugins_pool[$handlerClass]['events'][$className][$eventName];
+ $handlerMethodPool = is_array($handlerPool) ? $handlerPool[0] : $handlerPool;
+ $handlerMethodActive = is_array($handler) ? $handler[0] : $handler;
+
+ $data2['plugin_id'] = $plugin_id;
+ $data2['trigger_event'] = $eventName;
+ $data2['trigger_class'] = $className;
+ $data2['handler_method'] = $handlerMethodActive;
+
+ if ($handlerMethodPool == $handlerMethodActive) {
+ // plugin event is 'in pool'
+ return true;
+ } else {
+ $this->deleteEvent($data2);
+ }
+ } else {
+ $this->deleteEvent($data1);
+ }
+ }
+ }
+ }
+ if (isset(self::$plugins_pool[$handlerClass]['events'])) {
+ // get all events from plugin
+ foreach (self::$plugins_pool[$handlerClass]['events'] as $className => $events) {
+ foreach ($events as $eventName => $handler) {
+
+ $handlerActive = self::$plugins_active[$handlerClass]['events'][$className][$eventName];
+ $handlerMethodActive = is_array($handlerActive) ? $handlerActive[0] : $handlerActive;
+ $handlerMethodPool = is_array($handler) ? $handler[0] : $handler;
+
+ $data['app_id'] = (int)$this->checkAppId($handlerClass);
+ $data['plugin_id'] = (int)$plugin_id;
+ $data['trigger_class'] = $className;
+ $data['trigger_event'] = $eventName;
+ $data['handler_method'] = $handlerMethodPool;
+ $data['data'] = isset($handler[1]) ? json_encode($handler[1]) : '';
+ $data['status'] = Event::STATUS_INACTIVE;
+
+ if ($handlerMethodPool != $handlerMethodActive) {
+ self::$plugins_active[$handlerClass]['install'][] = $data;
+ // echo 'install';
+ // var_dump($data);
+ $this->installEvent($data);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Convert var AppId to int app_id
+ * @param var $handlerClass
+ * @return int $app_id
+ */
+ protected function checkAppId($handlerClass)
+ {
+ if (!isset($handlerClass::$appId)) return App::APP_FRONTEND;
+ switch ($handlerClass::$appId) {
+ case 'backend':
+ return App::APP_BACKEND;
+ break;
+ case 'common':
+ return App::APP_COMMON;
+ break;
+ default:
+ return App::APP_FRONTEND;
+ }
+ }
+
+ /**
+ * Install event from config
+ * @param var $data
+ * @return bool
+ */
+ protected function installEvent($event)
+ {
+ $data['Event'] = $event;
+ $model = new Event();
+ if ($model->load($data) && $model->save()) {
+ Yii::$app->session->setFlash('success', Yii::t('plugin', 'New event installed'));
+ } else {
+ return ['error' => $model->errors];
+ // var_dump($model->errors);
+ }
+ }
+
+ /**
+ * Delete events
+ * @param array $data
+ * @return bool
+ */
+ protected function deleteEvent($data)
+ {
+ foreach (Event::find()->where($data)->all() as $event) {
+ $event->delete();
+ }
+ }
+
+ /**
+ * Get info about plugin from Htdoc
+ * @param var $plugin
+ * @return $plugins_pool
+ */
+ protected static function getInfo($handlerClass)
+ {
+
+ $plugin_data = Crawler::getDoc($handlerClass);
+
+ preg_match('|Plugin Name:(.*)$|mi', $plugin_data, $name);
+ preg_match('|Plugin URI:(.*)$|mi', $plugin_data, $uri);
+ preg_match('|Version:(.*)|i', $plugin_data, $version);
+ preg_match('|Description:(.*)$|mi', $plugin_data, $description);
+ preg_match('|Author:(.*)$|mi', $plugin_data, $author_name);
+ preg_match('|Author URI:(.*)$|mi', $plugin_data, $author_uri);
+
+ if (isset($name[1])) {
+ $arr['name'] = trim($name[1]);
+ }
+
+ if (isset($uri[1])) {
+ $arr['url'] = trim($uri[1]);
+ }
+
+ if (isset($version[1])) {
+ $arr['version'] = trim($version[1]);
+ }
+
+ if (isset($description[1])) {
+ $arr['text'] = trim($description[1]);
+ }
+
+ if (isset($author_name[1])) {
+ $arr['author'] = trim($author_name[1]);
+ }
+
+ if (isset($author_uri[1])) {
+ $arr['author_url'] = trim($author_uri[1]);
+ }
+
+ $arr['handler_class'] = trim($handlerClass);
+
+ // For every plugin header item
+ foreach ($arr AS $k => $v) {
+ // If the key doesn't exist or the value is not the same, update the array
+ if (!isset(self::$plugins_pool[$handlerClass]['plugin_info'][$k]) OR self::$plugins_pool[$handlerClass]['plugin_info'][$k] != $v) {
+ self::$plugins_pool[$handlerClass]['plugin_info'][$k] = trim($v);
+ } else {
+ return true;
+ }
+ }
+
+ }
+
+}
diff --git a/docs/create_plugin.md b/docs/create_plugin.md
new file mode 100644
index 0000000..14b4baf
--- /dev/null
+++ b/docs/create_plugin.md
@@ -0,0 +1,106 @@
+# Create your plugin
+
+To create your plugin you need to run the following required steps
+
+### 1. Create in dir with our plugins `@common\plugins` new folder:
+* For example: `test`
+
+### 2. In this folder create:
+* `README.md` with usage instruction for this plugin
+* New named as folder class `Test`, with information about plugin
+
+```php
+ 'Hello, world!',
+ ];
+```
+
+* Then, assign a template events
+
+```php
+ public static function events()
+ {
+ return [
+ $eventSenderClassName => [
+ $eventName => [$handlerMethodName, self::$config]
+ ],
+ ];
+ }
+```
+
+for example:
+
+```php
+ public static function events()
+ {
+ return [
+ 'yii\base\View' => [
+ 'afterRender' => ['foo', self::$config]
+ ],
+ ];
+ }
+```
+more about `$eventSenderClassName` and `$eventName` you can be found on the info tab of this module
+
+!["Info tab"](img/tab_info.jpg)
+
+* Create a handler method `foo` with the necessary logic
+
+```php
+ /**
+ * handler method foo
+ */
+ public function foo($event)
+ {
+ $term = ($event->data['term']) ? $event->data['term'] : self::$config['term'];
+
+ if (isset($event->output)) {
+ $content = $event->output;
+ $event->output = str_replace($term,"$term
", $content);
+ }
+
+ return true;
+ }
+```
+
+* That's all. Then you have to [install](install_plugin.md) this plugin
\ No newline at end of file
diff --git a/docs/img/demo_events.jpg b/docs/img/demo_events.jpg
new file mode 100644
index 0000000..0f8f1a3
Binary files /dev/null and b/docs/img/demo_events.jpg differ
diff --git a/docs/img/event_edit.jpg b/docs/img/event_edit.jpg
new file mode 100644
index 0000000..ea03717
Binary files /dev/null and b/docs/img/event_edit.jpg differ
diff --git a/docs/img/tab_events.jpg b/docs/img/tab_events.jpg
new file mode 100644
index 0000000..e40715a
Binary files /dev/null and b/docs/img/tab_events.jpg differ
diff --git a/docs/img/tab_info.jpg b/docs/img/tab_info.jpg
new file mode 100644
index 0000000..233c7e1
Binary files /dev/null and b/docs/img/tab_info.jpg differ
diff --git a/docs/img/tab_install.jpg b/docs/img/tab_install.jpg
new file mode 100644
index 0000000..f8b081c
Binary files /dev/null and b/docs/img/tab_install.jpg differ
diff --git a/docs/img/tab_plugins.jpg b/docs/img/tab_plugins.jpg
new file mode 100644
index 0000000..ba0870a
Binary files /dev/null and b/docs/img/tab_plugins.jpg differ
diff --git a/docs/install_plugin.md b/docs/install_plugin.md
new file mode 100644
index 0000000..7924719
--- /dev/null
+++ b/docs/install_plugin.md
@@ -0,0 +1,31 @@
+# Install plugin
+
+After [creating](create_plugin.md) your `Test` plugin first, you must make sure that the our directory plugins included in the module configuration
+
+```php
+'modules' => [
+ 'plugins' => [
+ 'class' => 'lo\plugins\Module',
+ 'pluginsDir'=>[
+ '@lo/plugins/plugins', // default dir with core plugins
+ '@common/plugins', // dir with our plugins
+ ]
+ ],
+],
+```
+
+* Then go to the install tab and press button
+
+!["Install tab"](img/tab_install.jpg)
+
+* Go to the events tab for enabled and configure plugin event
+
+!["Events tab"](img/tab_events.jpg)
+
+* If you want, change configuration, update...
+
+!["Event edit"](img/event_edit.jpg)
+
+* Go to the website to see the result
+
+!["Result"](img/demo_events.jpg)
\ No newline at end of file
diff --git a/helpers/Crawler.php b/helpers/Crawler.php
new file mode 100644
index 0000000..ae16aa7
--- /dev/null
+++ b/helpers/Crawler.php
@@ -0,0 +1,174 @@
+getConstants() as $name => $value) {
+ if (!preg_match('/^EVENT/', $name)) {
+ continue;
+ }
+ $result[$name] = $value;
+ }
+ } catch (\Exception $e) {
+ echo $className;
+ exit;
+ }
+
+
+ return $result;
+ }
+
+ public static function getEventHandlerMethodNames($className)
+ {
+ $result = [];
+ if (!$reflection = self::getReflection($className)) {
+ return $result;
+ }
+ foreach ($reflection->getMethods(\ReflectionMethod::IS_STATIC) as $method) {
+ if (!$method->isPublic()) {
+ continue;
+ }
+ if ((!$params = $method->getParameters()) || ($params[0]->name != 'event')) {
+ continue;
+ }
+ $result[$method->name] = $method->name;
+ }
+
+ return $result;
+ }
+
+ public static function getMethodTriggeredEvents($className, $methodName)
+ {
+ $result = [];
+ if (!$reflection = self::getReflection($className)) {
+ return $result;
+ }
+ if (!$reflection->hasMethod($methodName)) {
+ return $result;
+ }
+ $method = $reflection->getMethod($methodName);
+ $body = self::getReflectionBody($method);
+ $events = array_flip(self::extractTriggeredEvents($body));
+ foreach ($events as $name => $trash) {
+ $events[$name] = $reflection->getConstant($name);
+ }
+ return $events;
+ }
+
+ public static function getAllClasses()
+ {
+ if (self::$_allClasses !== null) {
+ return self::$_allClasses;
+ }
+ $result = [];
+ foreach (self::getAllAliases() as $alias) {
+ $path = Yii::getAlias($alias);
+ if (!file_exists($path) || is_file($path)) {
+ continue;
+ }
+ $files = BaseFileHelper::findFiles($path, ['except' => ['/yii2-gii/', 'Yii.php']]);
+ foreach ($files as $filePath) {
+ if (!preg_match('/.*\/[A-Z]\w+\.php/', $filePath)) {
+ continue;
+ }
+ $className = str_replace([$path, '.php', '/', '@'], [$alias, '', '\\', ''], $filePath);
+ $result[] = $className;
+ }
+ }
+
+ return self::$_allClasses = $result;
+ }
+
+ public static function getAllAliases()
+ {
+ $result = [];
+ foreach (\Yii::$aliases as $aliases) {
+ foreach (array_keys((array)$aliases) as $alias) {
+ if (!$alias) {
+ continue;
+ }
+ $result[] = $alias;
+ }
+ }
+ return $result;
+ }
+
+ protected static function getReflection($className)
+ {
+ try {
+ if (in_array($className, ['yii\requirements\YiiRequirementChecker', 'yii\helpers\Markdown'])) {
+ return false;
+ }
+ $reflection = new \ReflectionClass($className);
+ } catch (\Exception $e) {
+ return false;
+ }
+ return $reflection;
+ }
+
+ protected static function extractTriggeredEvents($string)
+ {
+ $string = preg_replace('/\s/', '', $string);
+ return preg_match_all('/\-\>trigger\(self\:\:(EVENT_[\w\_]+)/', $string, $matches)
+ ? $matches[1] : [];
+ }
+
+ public static function getDoc($className)
+ {
+ $result = '';
+
+ try {
+ if (!$reflection = self::getReflection($className)) {
+ return $result;
+ }
+ $result = $reflection->getDocComment();
+
+ } catch (\Exception $e) {
+ echo $className;
+ exit;
+ }
+ return $result;
+
+ }
+
+
+ /**
+ * @param $reflection
+ * @return string
+ * @author http://stackoverflow.com/questions/7026690/reconstruct-get-code-of-php-function
+ */
+ protected static function getReflectionBody($reflection)
+ {
+ $filename = $reflection->getFileName();
+ $start_line = $reflection->getStartLine() - 1; // it's actually - 1, otherwise you wont get the function() block
+ $end_line = $reflection->getEndLine();
+ $length = $end_line - $start_line;
+ $source = file($filename);
+ return implode("", array_slice($source, $start_line, $length));
+ }
+
+ // Makes many assumptions on file format:
+ // namespace is declared on its own line, starting with "namespace" (no spaces).
+ public static function getNamespace($filename)
+ {
+ $lines = file($filename);
+ $namespaceLine = array_shift(preg_grep('/^namespace /', $lines));
+ $match = array();
+ preg_match('/^namespace (.*);/', $namespaceLine, $match);
+ $fullNamespace = trim(array_pop($match));
+ return $fullNamespace;
+ }
+}
\ No newline at end of file
diff --git a/helpers/JsonValidator.php b/helpers/JsonValidator.php
new file mode 100644
index 0000000..aa16604
--- /dev/null
+++ b/helpers/JsonValidator.php
@@ -0,0 +1,51 @@
+
+ */
+class JsonValidator extends Validator
+{
+ /**
+ * @inheritdoc
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ $this->message = Yii::t('common', '"{attribute}" must be a valid JSON');
+ }
+ }
+ /**
+ * @inheritdoc
+ */
+ public function validateValue($value)
+ {
+ if(!$value) return true;
+
+ if (!@json_decode($value)) {
+ return [$this->message, []];
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function clientValidateAttribute($model, $attribute, $view)
+ {
+ $message = Yii::$app->getI18n()->format($this->message, [
+ 'attribute' => $model->getAttributeLabel($attribute)
+ ], Yii::$app->language);
+ return <<<"JS"
+ try {
+ if(value) JSON.parse(value);
+ } catch (e) {
+ messages.push('{$message}')
+ }
+JS;
+ }
+}
diff --git a/messages/en/plugin.php b/messages/en/plugin.php
new file mode 100644
index 0000000..2837d67
--- /dev/null
+++ b/messages/en/plugin.php
@@ -0,0 +1,61 @@
+ 'Are you sure to delete this item?',
+ 'App' => 'App',
+ 'App Id' => 'App Id',
+ 'Author' => 'Author',
+ 'Author Url' => 'Author Url',
+
+ 'Create' => 'Create',
+ 'Create Item' => 'Create Plugin',
+
+ 'Data' => 'Data',
+ 'Delete' => 'Delete',
+ 'Disabled' => 'Disabled',
+
+ 'Enabled' => 'Enabled',
+ 'Events' => 'Events',
+
+ 'Handler Class' => 'Handler Class',
+ 'Handler Method' => 'Handler Method',
+
+ 'ID' => 'ID',
+ 'Info' => 'Info',
+ 'Install' => 'Install',
+ 'Items' => 'Plugins',
+
+ 'Name' => 'Name',
+ 'New event installed' => 'New event installed',
+
+ 'Plugin ID' => 'Plugin ID',
+ 'Position' => 'Position',
+
+ 'Status' => 'Status',
+
+ 'Text' => 'Text',
+ 'This plugin alredy installed' => 'This plugin alredy installed',
+ 'Trigger Class' => 'Trigger Class',
+ 'Trigger Event' => 'Trigger Event',
+
+ 'Update' => 'Update',
+ 'Url' => 'Url',
+
+ 'Version' => 'Version',
+];
diff --git a/messages/ru/plugin.php b/messages/ru/plugin.php
new file mode 100644
index 0000000..138e6df
--- /dev/null
+++ b/messages/ru/plugin.php
@@ -0,0 +1,36 @@
+ 'Уверены, что хотите удалить эту запись?',
+ 'Author' => 'Автор',
+ 'Author Url' => 'Url автора',
+ 'Create' => 'Добавить',
+ 'Create Item' => 'Создать плагин',
+ 'Delete' => 'Удалить',
+ 'Events' => 'События',
+ 'ID' => 'ID',
+ 'Info' => 'Информация',
+ 'Install' => 'Установить',
+ 'Items' => 'Плагины',
+ 'Name' => 'Название',
+ 'Status' => 'Статус',
+ 'Text' => 'Описание',
+ 'Url' => 'Url',
+ 'Version' => 'Версия',
+];
diff --git a/migrations/m150720_090901_plugin_table.php b/migrations/m150720_090901_plugin_table.php
new file mode 100644
index 0000000..f959959
--- /dev/null
+++ b/migrations/m150720_090901_plugin_table.php
@@ -0,0 +1,45 @@
+db->driverName === 'mysql') {
+ $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
+ }
+
+ $this->createTable("{{%plugins__item}}", [
+ 'id' => Schema::TYPE_PK,
+ 'handler_class' => Schema::TYPE_STRING,
+ 'name' => Schema::TYPE_STRING,
+ 'url' => Schema::TYPE_STRING,
+ 'version' => Schema::TYPE_STRING,
+ 'text'=> Schema::TYPE_TEXT,
+ 'author'=> Schema::TYPE_STRING,
+ 'author_url'=> Schema::TYPE_STRING,
+ 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
+ ], $tableOptions);
+ }
+
+ public function down()
+ {
+ echo "m150720_090901_plugin_table cannot be reverted.\n";
+
+ return false;
+ }
+
+ /*
+ // Use safeUp/safeDown to run migration code within a transaction
+ public function safeUp()
+ {
+ }
+
+ public function safeDown()
+ {
+ }
+ */
+}
diff --git a/migrations/m150720_090905_app_table.php b/migrations/m150720_090905_app_table.php
new file mode 100644
index 0000000..57b0e73
--- /dev/null
+++ b/migrations/m150720_090905_app_table.php
@@ -0,0 +1,55 @@
+db->driverName === 'mysql') {
+ $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
+ }
+
+ $this->createTable("{{%plugins__app}}", [
+ 'id' => Schema::TYPE_PK,
+ 'name' => Schema::TYPE_STRING,
+ ], $tableOptions);
+
+ $this->insert('{{%plugins__app}}', [
+ 'id' => 1,
+ 'name' => 'frontend',
+ ]);
+
+ $this->insert('{{%plugins__app}}', [
+ 'id' => 2,
+ 'name' => 'common',
+ ]);
+
+ $this->insert('{{%plugins__app}}', [
+ 'id' => 3,
+ 'name' => 'backend',
+ ]);
+
+
+ }
+
+ public function down()
+ {
+ echo "m150720_090905_app_table cannot be reverted.\n";
+
+ return false;
+ }
+
+ /*
+ // Use safeUp/safeDown to run migration code within a transaction
+ public function safeUp()
+ {
+ }
+
+ public function safeDown()
+ {
+ }
+ */
+}
diff --git a/migrations/m150720_091726_event_table.php b/migrations/m150720_091726_event_table.php
new file mode 100644
index 0000000..d37463b
--- /dev/null
+++ b/migrations/m150720_091726_event_table.php
@@ -0,0 +1,91 @@
+db->driverName === 'mysql') {
+ $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
+ }
+
+ $this->createTable("{{%plugins__event}}", [
+ 'id' => Schema::TYPE_PK,
+ 'app_id' => Schema::TYPE_INTEGER . ' NOT NULL DEFAULT 1',
+ 'plugin_id' => Schema::TYPE_INTEGER . ' NOT NULL',
+ 'trigger_class' => Schema::TYPE_STRING,
+ 'trigger_event' => Schema::TYPE_STRING,
+ 'handler_method'=> Schema::TYPE_STRING,
+ 'data'=> Schema::TYPE_TEXT,
+ 'pos'=> Schema::TYPE_INTEGER . ' NOT NULL DEFAULT 0',
+ 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',
+ ], $tableOptions);
+
+ $this->addForeignKey(
+ 'fk_plugins_event_plugins_item',
+ '{{%plugins__event}}',
+ 'plugin_id',
+ '{{%plugins__item}}',
+ 'id',
+ 'cascade',
+ 'cascade'
+ );
+
+ $this->addForeignKey(
+ 'fk_plugins_event_plugins_app',
+ '{{%plugins__event}}',
+ 'app_id',
+ '{{%plugins__app}}',
+ 'id',
+ 'cascade',
+ 'cascade'
+ );
+
+ $this->insert('{{%plugins__item}}', [
+ 'id' => 1,
+ 'handler_class' => 'lo\plugins\plugins\code\Code',
+ 'name' => 'Code Highlighting plugin',
+ 'status' => 1,
+ ]);
+
+ $this->insert('{{%plugins__event}}', [
+ 'id' => 1,
+ 'app_id' => 1, // frontend
+ 'plugin_id' => 1, // Code Highlighting
+ 'trigger_class' => 'yii\base\View',
+ 'trigger_event' => 'afterRender',
+ 'handler_method' => 'shortCode',
+ 'data' => '{"style":"github","lang":"php"}',
+ 'status' => 1,
+ ]);
+ }
+
+ public function down()
+ {
+ $this->dropForeignKey(
+ 'fk_plugins_event_plugins_item',
+ '{{%plugins__event}}'
+ );
+
+ $this->dropForeignKey(
+ 'fk_plugins_event_plugins_app',
+ '{{%plugins__event}}'
+ );
+
+ $this->dropTable('{{%plugins__event}}');
+ }
+
+ /*
+ // Use safeUp/safeDown to run migration code within a transaction
+ public function safeUp()
+ {
+ }
+
+ public function safeDown()
+ {
+ }
+ */
+}
diff --git a/models/App.php b/models/App.php
new file mode 100644
index 0000000..0774171
--- /dev/null
+++ b/models/App.php
@@ -0,0 +1,56 @@
+ 255],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'id' => Yii::t('plugin', 'ID'),
+ 'name' => Yii::t('plugin', 'Name'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getEvents()
+ {
+ return $this->hasMany(Event::className(), ['app_id' => 'id']);
+ }
+
+}
diff --git a/models/Event.php b/models/Event.php
new file mode 100644
index 0000000..3c0a4f4
--- /dev/null
+++ b/models/Event.php
@@ -0,0 +1,130 @@
+ 255],
+ [['data'], JsonValidator::className()],
+ //[['plugin_id'], 'exist', 'skipOnError' => true, 'targetClass' => Item::className(), 'targetAttribute' => ['id']],
+ //[['app_id'], 'exist', 'skipOnError' => true, 'targetClass' => App::className(), 'targetAttribute' => ['id']],
+ ];
+
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'id' => Yii::t('plugin', 'ID'),
+ 'app_id' => Yii::t('plugin', 'App ID'),
+ 'plugin_id' => Yii::t('plugin', 'Plugin ID'),
+ 'trigger_class' => Yii::t('plugin', 'Trigger Class'),
+ 'trigger_event' => Yii::t('plugin', 'Trigger Event'),
+ 'handler_method' => Yii::t('plugin', 'Handler Method'),
+ 'data' => Yii::t('plugin', 'Data'),
+ 'pos' => Yii::t('plugin', 'Position'),
+ 'status' => Yii::t('plugin', 'Status'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getApp()
+ {
+ return $this->hasOne(App::className(), ['id' => 'app_id']);
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getPlugin()
+ {
+ return $this->hasOne(Item::className(), ['id' => 'plugin_id']);
+ }
+
+ /**
+ * @inheritdoc
+ * @return EventQuery the active query used by this AR class.
+ */
+ public static function find()
+ {
+ return new EventQuery(get_called_class());
+ }
+
+ /**
+ * eventList for BootstrapManager
+ * @return array
+ */
+ public static function eventList($appId = 'frontend')
+ {
+ // (frontentd and common) or (backend and common) events
+ $cond = ($appId == 'backend') ? '>=' : '<=';
+ $attributes = ['trigger_class', 'trigger_event', 'plugin_id', 'pos', 'handler_method']; // handler_class
+ $order = array_combine($attributes, array_fill(0, count($attributes), SORT_ASC));
+
+ $allEvents = self::find()
+ ->select('t.*')
+ ->from(self::tableName() . 'AS t')
+ ->joinWith(['plugin'])
+ ->where([
+ 't.status' => self::STATUS_ACTIVE,
+ Item::tableName() . '.status' => Item::STATUS_ACTIVE,
+ ])
+ ->andWhere([$cond, 't.app_id', App::APP_COMMON])
+ ->orderBy($order)
+ ->all();
+
+ $result = [];
+
+ foreach ($allEvents as $data) {
+ if ($data->data) {
+ $handler = [[$data->plugin->handler_class, $data->handler_method], json_decode($data->data, true)];
+ } else {
+ $handler = [$data->plugin->handler_class, $data->handler_method];
+ }
+ $result[$data->trigger_class][$data->trigger_event][] = $handler;
+ }
+
+ return $result;
+ }
+}
diff --git a/models/EventQuery.php b/models/EventQuery.php
new file mode 100644
index 0000000..7c38530
--- /dev/null
+++ b/models/EventQuery.php
@@ -0,0 +1,35 @@
+andWhere('[[status]]=1');
+ return $this;
+ }*/
+
+ /**
+ * @inheritdoc
+ * @return Event[]|array
+ */
+ public function all($db = null)
+ {
+ return parent::all($db);
+ }
+
+ /**
+ * @inheritdoc
+ * @return Event|array|null
+ */
+ public function one($db = null)
+ {
+ return parent::one($db);
+ }
+}
\ No newline at end of file
diff --git a/models/EventSearch.php b/models/EventSearch.php
new file mode 100644
index 0000000..999e6ca
--- /dev/null
+++ b/models/EventSearch.php
@@ -0,0 +1,71 @@
+ $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;
+ }
+
+ $query->andFilterWhere([
+ 'id' => $this->id,
+ 'plugin_id' => $this->plugin_id,
+ 'app_id' => $this->app_id,
+ 'status' => $this->status,
+ ]);
+
+ $query->andFilterWhere(['like', 'trigger_class', $this->trigger_class])
+ ->andFilterWhere(['like', 'trigger_event', $this->trigger_event])
+ ->andFilterWhere(['like', 'handler_method', $this->handler_method]);
+
+ return $dataProvider;
+ }
+}
diff --git a/models/Item.php b/models/Item.php
new file mode 100644
index 0000000..06c05ac
--- /dev/null
+++ b/models/Item.php
@@ -0,0 +1,83 @@
+ 255],
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function attributeLabels()
+ {
+ return [
+ 'id' => Yii::t('plugin', 'ID'),
+ 'handler_class' => Yii::t('plugin', 'Handler Class'),
+ 'name' => Yii::t('plugin', 'Name'),
+ 'url' => Yii::t('plugin', 'Url'),
+ 'version' => Yii::t('plugin', 'Version'),
+ 'text' => Yii::t('plugin', 'Text'),
+ 'author' => Yii::t('plugin', 'Author'),
+ 'author_url' => Yii::t('plugin', 'Author Url'),
+ 'status' => Yii::t('plugin', 'Status'),
+ ];
+ }
+
+ /**
+ * @return \yii\db\ActiveQuery
+ */
+ public function getEvents()
+ {
+ return $this->hasMany(Event::className(), ['plugin_id' => 'id']);
+ }
+
+ /**
+ * @inheritdoc
+ * @return ItemQuery the active query used by this AR class.
+ */
+ public static function find()
+ {
+ return new ItemQuery(get_called_class());
+ }
+}
diff --git a/models/ItemQuery.php b/models/ItemQuery.php
new file mode 100644
index 0000000..7de381c
--- /dev/null
+++ b/models/ItemQuery.php
@@ -0,0 +1,35 @@
+andWhere('[[status]]=1');
+ return $this;
+ }*/
+
+ /**
+ * @inheritdoc
+ * @return Item[]|array
+ */
+ public function all($db = null)
+ {
+ return parent::all($db);
+ }
+
+ /**
+ * @inheritdoc
+ * @return Item|array|null
+ */
+ public function one($db = null)
+ {
+ return parent::one($db);
+ }
+}
\ No newline at end of file
diff --git a/models/ItemSearch.php b/models/ItemSearch.php
new file mode 100644
index 0000000..a72147b
--- /dev/null
+++ b/models/ItemSearch.php
@@ -0,0 +1,73 @@
+ $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;
+ }
+
+ $query->andFilterWhere([
+ 'id' => $this->id,
+ 'status' => $this->status,
+ ]);
+
+ $query->andFilterWhere(['like', 'name', $this->name])
+ ->andFilterWhere(['like', 'handler_class', $this->handler_class])
+ ->andFilterWhere(['like', 'url', $this->url])
+ ->andFilterWhere(['like', 'version', $this->version])
+ ->andFilterWhere(['like', 'text', $this->text])
+ ->andFilterWhere(['like', 'author', $this->author])
+ ->andFilterWhere(['like', 'author_url', $this->author_url]);
+
+ return $dataProvider;
+ }
+}
diff --git a/plugins/code/Code.php b/plugins/code/Code.php
new file mode 100644
index 0000000..3ff0e47
--- /dev/null
+++ b/plugins/code/Code.php
@@ -0,0 +1,69 @@
+ 'github', 'lang' => 'php'];
+
+ public static function events()
+ {
+ return [
+ 'yii\base\View' => [
+ 'afterRender' => ['shortCode', self::$config]
+ ],
+ ];
+ }
+
+ /**
+ * Parse shortcode [code], more styles you can find in https://highlightjs.org
+ */
+ public function shortCode($event)
+ {
+ $view = $event->sender;
+
+ $style = ($event->data['style']) ? $event->data['style'] : self::$config['style'];
+ $lang = ($event->data['lang']) ? $event->data['lang'] : self::$config['lang'];
+
+ CodeAsset::register($view, ['style' => $style]);
+
+ $view->registerJs("hljs.initHighlightingOnLoad();");
+
+ if (isset($event->output)) {
+
+ $shortcode = new Shortcode();
+
+ $shortcode->callbacks = [
+ 'code' => function ($attrs, $content, $tag) use ($lang) {
+ $lg = isset($attrs['lang']) ? $attrs['lang'] : $lang;
+ return '' . htmlspecialchars($content) . '
';
+ },
+ ];
+
+ $event->output = $shortcode->parse($event->output);
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/plugins/code/CodeAsset.php b/plugins/code/CodeAsset.php
new file mode 100644
index 0000000..02ce0cf
--- /dev/null
+++ b/plugins/code/CodeAsset.php
@@ -0,0 +1,24 @@
+getAssetManager()->getBundle(__CLASS__);
+
+ $thisBundle->css[] = sprintf('styles/%s.css', $config['style']);
+
+ return parent::register($view);
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/code/README.md b/plugins/code/README.md
new file mode 100644
index 0000000..78fc92c
--- /dev/null
+++ b/plugins/code/README.md
@@ -0,0 +1,29 @@
+# Code Highlighting
+
+### Config
+
+After installiation in plugins system, you can change default config
+
+```php
+ [
+ 'style' => 'github',
+ 'lang' => 'php'
+ ];
+```
+
+### Usage
+
+All the blocks of text enclosed in the shortcode [code], will be highlighted.
+For example:
+
+```php
+ [code] ... our text ... [/code]
+```
+or, if the language of highlighting is different from the by default `php`
+ ```php
+ [code lang="html"] ... our text ... [/code]
+ ```
+
+ ### Links
+
+ * More langs and styles you can find in [https://highlightjs.org] (https://highlightjs.org)
\ No newline at end of file
diff --git a/plugins/helloworld/Helloworld.php b/plugins/helloworld/Helloworld.php
new file mode 100644
index 0000000..9c6c4c4
--- /dev/null
+++ b/plugins/helloworld/Helloworld.php
@@ -0,0 +1,55 @@
+ 'Hello, world!',
+ 'replace' => 'Hello, Yii!',
+ 'color' => '#FFDB51'
+ ];
+
+ public static function events()
+ {
+ return [
+ 'yii\base\View' => [
+ 'afterRender' => ['hello', self::$config]
+ ]
+ ];
+ }
+
+ /**
+ * Plugin action for event
+ */
+ public static function hello($event)
+ {
+ $search = ($event->data['search']) ? $event->data['search'] : self::$config['search'];
+ $replace = ($event->data['replace']) ? $event->data['replace'] : self::$config['replace'];
+ $color = ($event->data['color']) ? $event->data['color'] : self::$config['color'];
+
+ if (isset($event->output)) {
+ $content = $event->output;
+ $event->output = str_replace($search,"$replace", $content);
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/plugins/helloworld/README.md b/plugins/helloworld/README.md
new file mode 100644
index 0000000..6ef6a08
--- /dev/null
+++ b/plugins/helloworld/README.md
@@ -0,0 +1,17 @@
+# Hello World plugin
+
+### Config
+
+After installiation in plugins system, you can change default config
+
+```php
+ [
+ 'search' => 'Hello, world!',
+ 'replace' => 'Hello, Yii!',
+ 'color' => '#FFDB51'
+ ];
+```
+
+### Usage
+
+After enabled this plugin, all phrases `search` will be replaced on `replace`, and highlighted `color`
\ No newline at end of file
diff --git a/plugins/httpauth/Httpauth.php b/plugins/httpauth/Httpauth.php
new file mode 100644
index 0000000..3f21959
--- /dev/null
+++ b/plugins/httpauth/Httpauth.php
@@ -0,0 +1,100 @@
+ ['127.0.0.1', '127.0.0.2'],
+ 'users' => [
+ 'admin' => '123456',
+ ]
+ ];
+
+ public static function events()
+ {
+ return [
+ 'yii\base\Application' => [
+ 'beforeRequest' => ['login', self::$config]
+ ],
+ ];
+ }
+
+ /**
+ * @var array Username and password pairs.
+ */
+ private static $_users = [];
+
+ /**
+ * @var array the list of IPs that are allowed to access this application.
+ */
+ private static $_allowedIps = [];
+
+ /**
+ * Logining
+ */
+ public static function login($event)
+ {
+ self::$_allowedIps = ($event->data['allowedIps']) ? $event->data['allowedIps'] : self::$config['allowedIps'];
+ self::$_users = ($event->data['users']) ? $event->data['users'] : self::$config['users'];
+
+ if (Yii::$app->request->isConsoleRequest || self::_checkAllowedIps() || self::_checkHttpAuthentication()) {
+ return;
+ }
+
+ Yii::$app->response->headers->add('WWW-Authenticate', 'Basic realm="HTTP authentication"');
+ throw new UnauthorizedHttpException(Yii::t('yii', 'You are not allowed to perform this action.'), 401);
+
+ return false;
+ }
+
+ /**
+ * @return boolean Whether the application can be accessed by the current user.
+ */
+ private function _checkAllowedIps()
+ {
+ if (in_array(Yii::$app->request->getUserIP(), self::$_allowedIps)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return boolean Whether the application can be accessed by the current user.
+ */
+ private function _checkHttpAuthentication()
+ {
+ $username = Yii::$app->request->getAuthUser();
+ $password = Yii::$app->request->getAuthPassword();
+ if (
+ isset(self::$_users[$username]) &&
+ (
+ $password == self::$_users[$username] ||
+ md5($password) == self::$_users[$username])
+ ) {
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/plugins/httpauth/README.md b/plugins/httpauth/README.md
new file mode 100644
index 0000000..98496ac
--- /dev/null
+++ b/plugins/httpauth/README.md
@@ -0,0 +1,18 @@
+# Http Authentication plugin
+
+### Config
+
+After installiation in plugins system, you can change default config
+
+```php
+ [
+ 'allowedIps' => ['127.0.0.1', '127.0.0.2'],
+ 'users' => [
+ 'admin' => '123456',
+ ]
+ ];
+```
+
+### Thanks
+
+* Lajax for idea from [Yii2 Http Authentication] (https://github.com/lajax/yii2-http-auth) extension
\ No newline at end of file
diff --git a/views/_menu.php b/views/_menu.php
new file mode 100644
index 0000000..ddc887a
--- /dev/null
+++ b/views/_menu.php
@@ -0,0 +1,35 @@
+
+
+= Nav::widget([
+ 'options' => [
+ 'class' => 'nav-tabs',
+ 'style' => 'margin-bottom: 15px'
+ ],
+ 'items' => [
+ [
+ 'label' => Yii::t('plugin', 'Items'),
+ 'url' => ['/plugins/item/index'],
+ ],
+ [
+ 'label' => Yii::t('plugin', 'Events'),
+ 'url' => ['/plugins/event/index'],
+ ],
+ [
+ 'label' => Yii::t('plugin', 'Install'),
+ 'url' => ['/plugins/item/find'],
+ ],
+ [
+ 'label' => Yii::t('plugin', 'Info'),
+ 'url' => ['/plugins/item/info'],
+ ],
+ ]
+])
+?>
diff --git a/views/event/_form.php b/views/event/_form.php
new file mode 100644
index 0000000..bc06581
--- /dev/null
+++ b/views/event/_form.php
@@ -0,0 +1,59 @@
+
+
+
diff --git a/views/event/_search.php b/views/event/_search.php
new file mode 100644
index 0000000..3cc3186
--- /dev/null
+++ b/views/event/_search.php
@@ -0,0 +1,39 @@
+
+
+
+
+ ['index'],
+ 'method' => 'get',
+ ]); ?>
+
+ = $form->field($model, 'id') ?>
+
+ = $form->field($model, 'plugin_id') ?>
+
+ = $form->field($model, 'trigger_class') ?>
+
+ = $form->field($model, 'trigger_event') ?>
+
+ = $form->field($model, 'handler_class') ?>
+
+ field($model, 'handler_method') ?>
+
+ field($model, 'status') ?>
+
+
+ = Html::submitButton(Yii::t('plugin', 'Search'), ['class' => 'btn btn-primary']) ?>
+ = Html::resetButton(Yii::t('plugin', 'Reset'), ['class' => 'btn btn-default']) ?>
+
+
+
+
+
diff --git a/views/event/create.php b/views/event/create.php
new file mode 100644
index 0000000..77a6cee
--- /dev/null
+++ b/views/event/create.php
@@ -0,0 +1,21 @@
+title = Yii::t('plugin', 'Create Event');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('plugin', 'Events'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+
diff --git a/views/event/index.php b/views/event/index.php
new file mode 100644
index 0000000..0541f74
--- /dev/null
+++ b/views/event/index.php
@@ -0,0 +1,93 @@
+title = Yii::t('plugin', 'Events');
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+ = $this->render('/_menu') ?>
+
+ = GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'filterModel' => $searchModel,
+ 'columns' => [
+ ['class' => 'yii\grid\SerialColumn'],
+ [
+ 'attribute' => 'app_id',
+ 'label' => Yii::t('plugin','App'),
+ 'options' => ['style' => 'width: 25px; align: center;'],
+ 'value' => function ($model, $key, $index, $column) {
+ switch ($model->app_id) {
+ case 1:
+ return 'F';
+ break;
+ case 2:
+ return 'C';
+ break;
+ case 3:
+ return 'B';
+ break;
+ }
+ },
+ 'filter' => [
+ 1 => Yii::t('plugin', 'Frontend'),
+ 2 => Yii::t('plugin', 'Common'),
+ 3 => Yii::t('plugin', 'Backend')
+ ],
+ 'format' => "raw"
+ ],
+ 'trigger_class',
+ 'trigger_event',
+ 'plugin.handler_class',
+ 'handler_method',
+ [
+ 'attribute' => 'status',
+ 'options' => ['style' => 'width: 75px; align: center;'],
+ 'value' => function ($model, $key, $index, $column) {
+ return $model->status == $model::STATUS_ACTIVE ? 'Enabled' : 'Disabled';
+ },
+ 'filter' => [
+ 1 => Yii::t('plugin', 'Enabled'),
+ 0 => Yii::t('plugin', 'Disabled')
+ ],
+ 'format' => "raw"
+ ],
+ [
+ 'class' => 'yii\grid\ActionColumn',
+ 'template' => '{update} {view} {delete}',
+ 'options' => ['style' => 'width: 100px;'],
+ 'buttons' => [
+ 'update' => function ($url, $model) {
+ return Html::a('', $url, [
+ 'class' => 'btn btn-xs btn-primary',
+ 'title' => Yii::t('plugin', 'Update'),
+ ]);
+ },
+ 'view' => function ($url, $model) {
+ return Html::a('', $url, [
+ 'class' => 'btn btn-xs btn-warning',
+ 'title' => Yii::t('plugin', 'View'),
+ ]);
+ },
+ 'delete' => function ($url, $model) {
+ return Html::a('', $url, [
+ 'class' => 'btn btn-xs btn-danger',
+ 'data-method' => 'post',
+ 'data-confirm' => Yii::t('plugin', 'Are you sure to delete this item?'),
+ 'title' => Yii::t('plugin', 'Delete'),
+ ]);
+ },
+ ]
+ ],
+ ],
+ ]); ?>
+
+
diff --git a/views/event/update.php b/views/event/update.php
new file mode 100644
index 0000000..d6ed727
--- /dev/null
+++ b/views/event/update.php
@@ -0,0 +1,23 @@
+title = Yii::t('plugin', 'Update {modelClass}: ', [
+ 'modelClass' => 'Event',
+]) . ' ' . $model->plugin->name;
+$this->params['breadcrumbs'][] = ['label' => Yii::t('plugin', 'Events'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = ['label' => $model->id, 'url' => ['view', 'id' => $model->id]];
+$this->params['breadcrumbs'][] = Yii::t('plugin', 'Update');
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+
diff --git a/views/event/view.php b/views/event/view.php
new file mode 100644
index 0000000..ddd976b
--- /dev/null
+++ b/views/event/view.php
@@ -0,0 +1,43 @@
+title = $model->id;
+$this->params['breadcrumbs'][] = ['label' => Yii::t('plugin', 'Events'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+
+ = Html::a(Yii::t('plugin', 'Update'), ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?>
+ = Html::a(Yii::t('plugin', 'Delete'), ['delete', 'id' => $model->id], [
+ 'class' => 'btn btn-danger',
+ 'data' => [
+ 'confirm' => Yii::t('plugin', 'Are you sure you want to delete this item?'),
+ 'method' => 'post',
+ ],
+ ]) ?>
+
+
+ = DetailView::widget([
+ 'model' => $model,
+ 'attributes' => [
+ 'id',
+ 'plugin_id',
+ 'trigger_class',
+ 'trigger_event',
+ 'plugin.handler_class',
+ 'handler_method',
+ 'data',
+ 'pos',
+ 'status',
+ ],
+ ]) ?>
+
+
diff --git a/views/item/_form.php b/views/item/_form.php
new file mode 100644
index 0000000..1ccdb5d
--- /dev/null
+++ b/views/item/_form.php
@@ -0,0 +1,38 @@
+
+
+
diff --git a/views/item/_item.php b/views/item/_item.php
new file mode 100644
index 0000000..e5ec2f5
--- /dev/null
+++ b/views/item/_item.php
@@ -0,0 +1,11 @@
+
+
+
+ =$model['plugin_info']['name'] ?> |
+ =$model['plugin_info']['version'] ?> |
+ =$model['plugin_info']['author'] ?> |
+ =$model['plugin_info']['text'] ?> |
+ =Html::a('Install', ['item/install', 'id' => md5($key)], ['class' => 'btn btn-primary']);?> |
+
\ No newline at end of file
diff --git a/views/item/_search.php b/views/item/_search.php
new file mode 100644
index 0000000..65d6fb8
--- /dev/null
+++ b/views/item/_search.php
@@ -0,0 +1,43 @@
+
+
+
+
+ ['index'],
+ 'method' => 'get',
+ ]); ?>
+
+ = $form->field($model, 'id') ?>
+
+ = $form->field($model, 'name') ?>
+
+ = $form->field($model, 'url') ?>
+
+ = $form->field($model, 'version') ?>
+
+ = $form->field($model, 'text') ?>
+
+ field($model, 'author') ?>
+
+ field($model, 'author_url') ?>
+
+ field($model, 'hash') ?>
+
+ field($model, 'status') ?>
+
+
+ = Html::submitButton(Yii::t('plugin', 'Search'), ['class' => 'btn btn-primary']) ?>
+ = Html::resetButton(Yii::t('plugin', 'Reset'), ['class' => 'btn btn-default']) ?>
+
+
+
+
+
diff --git a/views/item/create.php b/views/item/create.php
new file mode 100644
index 0000000..6148a47
--- /dev/null
+++ b/views/item/create.php
@@ -0,0 +1,21 @@
+title = Yii::t('plugin', 'Create Item');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('plugin', 'Items'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+
diff --git a/views/item/find.php b/views/item/find.php
new file mode 100644
index 0000000..da10a7b
--- /dev/null
+++ b/views/item/find.php
@@ -0,0 +1,52 @@
+title = Yii::t('plugin', 'Install');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('plugin', 'Items'), 'url' => ['info']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+ = $this->render('/_menu') ?>
+
+
+ Plugin name |
+ Ver. |
+ Author |
+ Plugin description |
+ |
+
+ ';
+ ?>
+ = ListView::widget([
+ 'dataProvider' => $dataProvider,
+ 'layout' => "$thead{items}",
+ 'options' => [
+ 'tag' => 'table',
+ 'class' => 'table table-bordered table-striped',
+ ],
+ 'itemOptions' => [
+ 'tag' => false,
+ ],
+ 'itemOptions' => ['class' => 'item'],
+ 'itemView' => '_item',
+ /*'itemView' => function ($model, $key, $index, $widget) use ($transportRun) {
+ // return print_r($model, true);
+ return $key;
+ },*/
+ ]) ?>
+
+ $dataProvider->pagination,
+ ]); ?>
+
+
+
+
diff --git a/views/item/index.php b/views/item/index.php
new file mode 100644
index 0000000..7546469
--- /dev/null
+++ b/views/item/index.php
@@ -0,0 +1,71 @@
+title = Yii::t('plugin', 'Items');
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+ = $this->render('/_menu') ?>
+
+ = GridView::widget([
+ 'dataProvider' => $dataProvider,
+ 'filterModel' => $searchModel,
+ 'columns' => [
+ ['class' => 'yii\grid\SerialColumn'],
+ 'handler_class',
+ 'name',
+ 'url:url',
+ 'version',
+ 'text:ntext',
+ [
+ 'attribute' => 'status',
+ 'options' => ['style'=>'width: 75px; align: center;'],
+ 'value' => function ($model, $key, $index, $column) {
+ return $model->status == $model::STATUS_ACTIVE ? 'Enabled' : 'Disabled';
+ },
+ 'filter' => [
+ 1 => Yii::t('plugin', 'Enabled'),
+ 0 => Yii::t('plugin', 'Disabled')
+ ],
+ 'format'=>"raw"
+ ],
+
+ [
+ 'class' => 'yii\grid\ActionColumn',
+ 'template' => '{update} {view} {delete}',
+ 'options' => ['style'=>'width: 100px;'],
+ 'buttons' => [
+ 'update' => function ($url, $model) {
+ return Html::a('', $url, [
+ 'class' => 'btn btn-xs btn-primary',
+ 'title' => Yii::t('plugin', 'Update'),
+ ]);
+ },
+ 'view' => function ($url, $model) {
+ return Html::a('', $url, [
+ 'class' => 'btn btn-xs btn-warning',
+ 'title' => Yii::t('plugin', 'View'),
+ ]);
+ },
+ 'delete' => function ($url, $model) {
+ return Html::a('', $url, [
+ 'class' => 'btn btn-xs btn-danger',
+ 'data-method' => 'post',
+ 'data-confirm' => Yii::t('plugin', 'Are you sure to delete this item?'),
+ 'title' => Yii::t('plugin', 'Delete'),
+ ]);
+ },
+ ]
+ ],
+ ],
+ ]); ?>
+
+
diff --git a/views/item/info.php b/views/item/info.php
new file mode 100644
index 0000000..73dfe1c
--- /dev/null
+++ b/views/item/info.php
@@ -0,0 +1,227 @@
+title = Yii::t('plugin', 'Info');
+$this->params['breadcrumbs'][] = ['label' => Yii::t('plugin', 'Items'), 'url' => ['info']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
+
= Html::encode($this->title) ?>
+ = $this->render('/_menu') ?>
+
+
+
+
+
MVC
+
+
+ Controller or Module |
+
+
+ EVENT_BEFORE_ACTION |
+ beforeAction |
+
+
+ EVENT_AFTER_ACTION |
+ afterAction |
+
+
+ Model |
+
+
+ EVENT_BEFORE_VALIDATE |
+ beforeValidate |
+
+
+ EVENT_AFTER_VALIDATE |
+ afterValidate |
+
+
+ yii\base\View |
+
+
+ EVENT_BEGIN_PAGE |
+ beginPage |
+
+
+ EVENT_END_PAGE |
+ endPage |
+
+
+ EVENT_BEFORE_RENDER |
+ beforeRender |
+
+
+ EVENT_AFTER_RENDER |
+ afterRender |
+
+
+ yii\web\View |
+
+
+ EVENT_BEGIN_BODY |
+ beginBody |
+
+
+ EVENT_END_BODY |
+ endBody |
+
+
+
+
Components
+
+
+ MessageSource |
+
+
+ EVENT_MISSING_TRANSLATION |
+ missingTranslation |
+
+
+ BaseMailer |
+
+
+ EVENT_BEFORE_SEND |
+ beforeSend |
+
+
+ EVENT_AFTER_SEND |
+ afterSend |
+
+
+ User |
+
+
+ EVENT_BEFORE_LOGIN |
+ beforeLogin |
+
+
+ EVENT_AFTER_LOGIN |
+ afterLogin |
+
+
+ EVENT_BEFORE_LOGOUT |
+ beforeLogout |
+
+
+ EVENT_AFTER_LOGOUT |
+ afterLogout |
+
+
+
+
+
+
Database
+
+
+ BaseActiveRecord |
+
+
+ EVENT_INIT
+
+ |
+ init |
+
+
+ EVENT_AFTER_FIND |
+ afterFind |
+
+
+ EVENT_BEFORE_INSERT |
+ beforeInsert |
+
+
+ EVENT_AFTER_INSERT |
+ afterInsert |
+
+
+ EVENT_BEFORE_UPDATE |
+ beforeUpdate |
+
+
+ EVENT_AFTER_UPDATE |
+ afterUpdate |
+
+
+ EVENT_BEFORE_DELETE |
+ beforeDelete |
+
+
+ EVENT_AFTER_DELETE |
+ afterDelete |
+
+
+ ActiveQuery |
+
+
+ EVENT_INIT |
+ init |
+
+
+ Connection |
+
+
+ EVENT_AFTER_OPEN |
+ afterOpen |
+
+
+ EVENT_BEGIN_TRANSACTION |
+ beginTransaction |
+
+
+ EVENT_COMMIT_TRANSACTION |
+ commitTransaction |
+
+
+ EVENT_ROLLBACK_TRANSACTION |
+ rollbackTransaction |
+
+
+
+
Request
+
+
+ yii\base\Application |
+
+
+ EVENT_BEFORE_REQUEST |
+ beforeRequest |
+
+
+ EVENT_AFTER_REQUEST |
+ afterRequest |
+
+
+ Response |
+
+
+ EVENT_BEFORE_SEND |
+ beforeSend |
+
+
+ EVENT_AFTER_SEND |
+ afterSend |
+
+
+ EVENT_AFTER_PREPARE |
+ afterPrepare |
+
+
+
+
+
+
+
+
+
diff --git a/views/item/update.php b/views/item/update.php
new file mode 100644
index 0000000..a541381
--- /dev/null
+++ b/views/item/update.php
@@ -0,0 +1,23 @@
+title = Yii::t('plugin', 'Update {modelClass}: ', [
+ 'modelClass' => 'Item',
+]) . ' ' . $model->name;
+$this->params['breadcrumbs'][] = ['label' => Yii::t('plugin', 'Items'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]];
+$this->params['breadcrumbs'][] = Yii::t('plugin', 'Update');
+?>
+
+
+
= Html::encode($this->title) ?>
+
+ = $this->render('_form', [
+ 'model' => $model,
+ ]) ?>
+
+
diff --git a/views/item/view.php b/views/item/view.php
new file mode 100644
index 0000000..03b1e2b
--- /dev/null
+++ b/views/item/view.php
@@ -0,0 +1,42 @@
+title = $model->name;
+$this->params['breadcrumbs'][] = ['label' => Yii::t('plugin', 'Items'), 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+
+
= Html::encode($this->title) ?>
+
+
+ = Html::a(Yii::t('plugin', 'Update'), ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?>
+ = Html::a(Yii::t('plugin', 'Delete'), ['delete', 'id' => $model->id], [
+ 'class' => 'btn btn-danger',
+ 'data' => [
+ 'confirm' => Yii::t('plugin', 'Are you sure you want to delete this item?'),
+ 'method' => 'post',
+ ],
+ ]) ?>
+
+
+ = DetailView::widget([
+ 'model' => $model,
+ 'attributes' => [
+ 'id',
+ 'name',
+ 'url:url',
+ 'version',
+ 'text:ntext',
+ 'author',
+ 'author_url:url',
+ 'status',
+ ],
+ ]) ?>
+
+