├── .gitignore ├── README.md ├── TODO.md ├── Vagrantfile ├── app ├── assets │ ├── AnimateAsset.php │ ├── AppAsset.php │ ├── BootstrapNotifyAsset.php │ ├── CheckAsset.php │ ├── Select2Asset.php │ ├── TimePickerAsset.php │ └── TypeAheadAsset.php ├── base │ ├── Action.php │ ├── Composer.php │ ├── ConsoleApplication.php │ ├── Controller.php │ ├── Formatter.php │ ├── MailTrait.php │ ├── Migration.php │ ├── ModelUrlTrait.php │ ├── Module.php │ ├── ModuleApplicationTrait.php │ ├── ModuleMigrateException.php │ ├── RbacMigration.php │ ├── WebApplication.php │ ├── actions │ │ ├── Settings.php │ │ └── user │ │ │ ├── Login.php │ │ │ ├── Logout.php │ │ │ ├── PasswordRequest.php │ │ │ ├── PasswordReset.php │ │ │ ├── Profile.php │ │ │ └── Register.php │ ├── behaviors │ │ ├── LinkedBehavior.php │ │ ├── SerializableBehavior.php │ │ └── StatusBehavior.php │ ├── console │ │ └── Controller.php │ ├── gii │ │ └── ModuleGenerator.php │ └── grid │ │ ├── CheckboxColumn.php │ │ ├── DeleteColumn.php │ │ └── EditColumn.php ├── commands │ ├── MigrateController.php │ ├── ModuleController.php │ ├── RbacController.php │ ├── SqlController.php │ └── UserController.php ├── components │ ├── Menu.php │ └── Param.php ├── config │ ├── common.php │ ├── console.php │ └── web.php ├── controllers │ ├── SiteController.php │ └── UserController.php ├── forms │ ├── ContactForm.php │ └── user │ │ ├── Login.php │ │ ├── PasswordRequest.php │ │ ├── PasswordReset.php │ │ ├── Profile.php │ │ └── Register.php ├── helpers │ ├── Icon.php │ └── UserHelper.php ├── mail │ ├── accountCreated-html.php │ ├── accountCreated-text.php │ ├── layouts │ │ ├── html.php │ │ └── text.php │ ├── passwordRequest-html.php │ └── passwordRequest-text.php ├── migrations │ ├── m140506_102106_rbac_init.php │ ├── m160314_212231_user.php │ ├── m160818_075724_config.php │ ├── m160830_073241_param_default_role.php │ ├── m160831_094735_module.php │ ├── m161108_083707_config_perms.php │ └── m180130_201810_param_site_name.php ├── models │ ├── Config.php │ └── User.php ├── views │ ├── layouts │ │ ├── content.php │ │ ├── header.php │ │ ├── left.php │ │ ├── main-login.php │ │ └── main.php │ ├── site │ │ ├── about.php │ │ ├── contact.php │ │ ├── error.php │ │ └── index.php │ ├── templates │ │ ├── confirmation.php │ │ ├── gii-module │ │ │ ├── controller.php │ │ │ ├── module.php │ │ │ └── view.php │ │ └── migration.php │ └── user │ │ ├── _create_modal.php │ │ ├── _profile_account.php │ │ ├── _profile_admin.php │ │ ├── index.php │ │ ├── login.php │ │ ├── passwordRequest.php │ │ ├── passwordReset.php │ │ ├── profile.php │ │ └── register.php └── widgets │ ├── ActiveForm.php │ ├── Box.php │ ├── Check.php │ ├── GridView.php │ ├── InputClear.php │ ├── InputGroup.php │ ├── ItemList.php │ ├── Modal.php │ ├── Notify.php │ ├── Pjax.php │ ├── ProgressBar.php │ ├── ProgressBarGroup.php │ ├── Select2.php │ ├── Tabs.php │ ├── TimePicker.php │ ├── Timeline.php │ └── TypeAhead.php ├── bin ├── requirements └── yii ├── composer.json ├── composer.lock ├── config-sample.php ├── docs └── Param.md ├── modules ├── README.md └── wiki │ ├── DiffRendererHtmlInline.php │ ├── RuleOwnWiki.php │ ├── WikiModule.php │ ├── assets │ ├── DiffAsset.php │ ├── MarkdownEditorAsset.php │ └── diff.css │ ├── controllers │ └── PageController.php │ ├── forms │ ├── DeleteWiki.php │ └── Editor.php │ ├── helpers │ └── DiffHelper.php │ ├── migrations │ ├── m160901_090402_wiki.php │ ├── m160902_065120_wiki_change.php │ └── m160902_073325_auth.php │ ├── models │ ├── History.php │ └── Wiki.php │ ├── views │ └── page │ │ ├── _editor.php │ │ ├── _history.php │ │ ├── create.php │ │ ├── delete.php │ │ ├── index.php │ │ ├── update.php │ │ └── view.php │ └── widgets │ └── MarkdownEditor.php ├── runtime └── .gitignore ├── screenshot.png ├── tests ├── README.md ├── codeception.yml └── codeception │ ├── .gitignore │ ├── _bootstrap.php │ ├── _output │ └── .gitignore │ ├── _pages │ ├── AboutPage.php │ ├── ContactPage.php │ └── LoginPage.php │ ├── _support │ ├── AcceptanceTester.php │ ├── FunctionalTester.php │ ├── UnitTester.php │ └── _generated │ │ ├── AcceptanceTesterActions.php │ │ ├── FunctionalTesterActions.php │ │ └── UnitTesterActions.php │ ├── acceptance.suite.yml │ ├── acceptance │ ├── AboutCept.php │ ├── ContactCept.php │ ├── HomeCept.php │ ├── LoginCept.php │ └── _bootstrap.php │ ├── bin │ ├── _bootstrap.php │ ├── yii │ └── yii.bat │ ├── config │ ├── acceptance.php │ ├── functional.php │ └── unit.php │ ├── fixtures │ └── .gitignore │ ├── functional.suite.yml │ ├── functional │ ├── AboutCept.php │ ├── ContactCept.php │ ├── HomeCept.php │ ├── LoginCept.php │ └── _bootstrap.php │ ├── templates │ └── .gitignore │ ├── unit.suite.yml │ └── unit │ ├── _bootstrap.php │ ├── fixtures │ ├── .gitkeep │ └── data │ │ └── .gitkeep │ ├── models │ ├── ContactFormTest.php │ ├── LoginFormTest.php │ └── UserTest.php │ └── templates │ └── fixtures │ └── .gitkeep └── web ├── .htaccess ├── assets └── .gitignore ├── css ├── admin.css └── site.css ├── images └── avatars │ ├── avatar1.png │ ├── avatar2.png │ ├── avatar3.png │ ├── avatar4.png │ └── avatar5.png ├── index-test.php ├── index.php └── js └── admin.js /.gitignore: -------------------------------------------------------------------------------- 1 | # phpstorm project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # composer vendor dir 16 | /vendor 17 | 18 | # composer itself is not needed 19 | composer.phar 20 | 21 | # Mac DS_Store Files 22 | .DS_Store 23 | 24 | # phpunit itself is not needed 25 | phpunit.phar 26 | # local phpunit config 27 | /phpunit.xml 28 | 29 | # Editor backups 30 | *~ 31 | .*~ 32 | 33 | # local application config 34 | config.php 35 | 36 | 37 | tests/_output/* 38 | 39 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | Things to be done 2 | ----------------- 3 | 4 | 1. Fork dmstr/yii2-adminlte-asset and break dependency from cebe/yii2-gravatar 5 | 2. Make use account avatars (adminlte has some pictures). 6 | 3. User profile page. +++ 7 | 4. Console command for database backup. +++ (via sql console command) 8 | 5. Session messages (after logout, register and so on). +++ 9 | 6. Global translation function t() wrapper for Yii::t(). +++ 10 | 7. Wrapper for session->setFlash in base Controller. +++ 11 | 8. New "Settings" tab in user profile with preferred language and timezone. 12 | 9. Option for enable/disable social login in user login action. 13 | 14 | Features for 0.2 release: 15 | 16 | 1. Users management page (related with no.3). +++ 17 | 2. Configuration editor (avoid of manual editing params in config/web.php). +++ 18 | 3. Simple RBAC with "Administrator" and "Registered" roles. +++ 19 | 4. Modal with remote content. +++ 20 | 5. Modules integration (consider module as subapplication). +++ 21 | 22 | Features for 0.3 release: 23 | 24 | 1. Send email after registration: 25 | - email text 26 | - setting for enable/disable sending 27 | - add checkbox in admin user create form for sending email 28 | 2. Localization. 29 | 3. System log page. 30 | 4. User activation by email (levearage of 'Pending' status). 31 | 5. Role editor (allow to add/delete permissions to role). 32 | 6. Modules administation. 33 | 7. Server info (info about server and php) module. 34 | 35 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # For a complete reference, please see the online documentation at 2 | # https://docs.vagrantup.com. 3 | 4 | # In case of "rejecting i/o input from offline devices" error in 5 | # guest machine change: 6 | # - kernel type from linux to ubuntu64 7 | # - change cpu count to 1 8 | # - disable I/O APIC feature 9 | 10 | # In case out of memory create swapfile: 11 | # sudo fallocate -l 1G /swapfile 12 | # sudo chmod 600 /swapfile 13 | # sudo mkswap /swapfile 14 | # sudo swapon /swapfile 15 | # echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab 16 | 17 | # MySQL root's password is "pw" 18 | 19 | Vagrant.configure("2") do |config| 20 | 21 | config.vm.box = "ubuntu/xenial64" 22 | 23 | config.vm.boot_timeout = 600 24 | config.vm.network "forwarded_port", guest: 80, host: 8080 25 | config.vm.provider "virtualbox" do |vb| 26 | vb.memory = "800" 27 | end 28 | 29 | # Permit write to temporary folders by web server. 30 | #config.vm.synced_folder "web/sites/default/files", "/vagrant/web/sites/default/files", owner: "www-data", group: "www-data" 31 | 32 | config.vm.provision "shell", inline: <<-SHELL 33 | apt-get update 34 | apt-get install -y apache2 libapache2-mod-php php-zip php-curl php-mysql php-xdebug php-gd php-xml git 35 | debconf-set-selections <<< "mysql-server mysql-server/root_password password pw" 36 | debconf-set-selections <<< "mysql-server mysql-server/root_password_again password pw" 37 | apt-get install -y --no-install-recommends mysql-server mysql-client 38 | a2enmod rewrite 39 | if ! [ -L /var/www/html ]; then 40 | rm -rf /var/www/html 41 | ln -sf /vagrant /var/www/html 42 | fi 43 | cd /home/ubuntu || exit 44 | curl -sS https://getcomposer.org/installer | php 45 | mkdir -p .local/bin 46 | mv composer.phar .local/bin/composer 47 | chmod +x .local/bin/composer 48 | chown ubuntu.ubuntu -R .local 49 | ( grep .local/bin .profile >/dev/null ) || \ 50 | ( echo 'PATH="$PATH:$HOME/.local/bin"' >> .profile ) 51 | echo 'PATH="$HOME/.config/composer/vendor/bin:$PATH"' >> .profile 52 | cd /etc/php/7.0/mods-available || exit 53 | echo 'xdebug.remote_enable=1' >> xdebug.ini 54 | echo 'xdebug.remote_connect_back=1' >> xdebug.ini 55 | echo 'xdebug.remote_port=9000' >> xdebug.ini 56 | echo 'xdebug.idekey=netbeans-xdebug' >> xdebug.ini 57 | phpdismod -s cli xdebug 58 | service apache2 restart 59 | SHELL 60 | end 61 | -------------------------------------------------------------------------------- /app/assets/AnimateAsset.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\assets; 9 | 10 | use yii\web\AssetBundle; 11 | 12 | /** 13 | * AnimateAsset 14 | * 15 | * @author skoro 16 | */ 17 | class AnimateAsset extends AssetBundle 18 | { 19 | 20 | public $sourcePath = '@bower/animate.css'; 21 | 22 | public $css = [ 23 | 'animate.min.css', 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /app/assets/AppAsset.php: -------------------------------------------------------------------------------- 1 | 14 | * @since 2.0 15 | */ 16 | class AppAsset extends AssetBundle 17 | { 18 | public $basePath = '@webroot'; 19 | public $baseUrl = '@web'; 20 | public $css = [ 21 | 'css/site.css', 22 | 'css/admin.css', 23 | ]; 24 | public $js = [ 25 | 'js/admin.js', 26 | ]; 27 | public $depends = [ 28 | 'yii\web\YiiAsset', 29 | 'yii\bootstrap\BootstrapAsset', 30 | ]; 31 | } 32 | -------------------------------------------------------------------------------- /app/assets/BootstrapNotifyAsset.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\assets; 9 | 10 | use yii\web\AssetBundle; 11 | 12 | /** 13 | * GrowlAsset 14 | * 15 | * @author skoro 16 | */ 17 | class BootstrapNotifyAsset extends AssetBundle 18 | { 19 | 20 | public $sourcePath = '@bower/remarkable-bootstrap-notify'; 21 | 22 | public $js = [ 23 | 'dist/bootstrap-notify.min.js', 24 | ]; 25 | 26 | public $depends = [ 27 | 'yii\bootstrap\BootstrapAsset', 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /app/assets/CheckAsset.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\assets; 9 | 10 | use yii\web\AssetBundle; 11 | 12 | /** 13 | * iCheck check/radion plugin. 14 | * 15 | * @author skoro 16 | */ 17 | class CheckAsset extends AssetBundle 18 | { 19 | 20 | public $sourcePath = '@vendor/almasaeed2010/adminlte/plugins/iCheck'; 21 | 22 | public $js = [ 23 | 'icheck.min.js', 24 | ]; 25 | 26 | public $css = [ 27 | 'all.css', 28 | ]; 29 | 30 | public $depends = [ 31 | 'yii\web\JqueryAsset', 32 | ]; 33 | 34 | /** 35 | * @var string checkbox style name. 36 | */ 37 | public $style; 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | public function registerAssetFiles($view) 43 | { 44 | $this->css[] = $this->style . '/_all.css'; 45 | parent::registerAssetFiles($view); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/assets/Select2Asset.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\assets; 9 | 10 | use yii\web\AssetBundle; 11 | 12 | /** 13 | * Select2Asset 14 | * 15 | * @author skoro 16 | */ 17 | class Select2Asset extends AssetBundle 18 | { 19 | 20 | public $sourcePath = '@vendor/almasaeed2010/adminlte/bower_components/select2/dist'; 21 | 22 | public $css = [ 23 | 'css/select2.css', 24 | ]; 25 | 26 | public $js = [ 27 | 'js/select2.js', 28 | ]; 29 | 30 | public $depends = [ 31 | 'yii\web\JqueryAsset', 32 | ]; 33 | 34 | /** 35 | * Select2 localization language. 36 | * @var string 37 | */ 38 | public $language; 39 | 40 | /** 41 | * Register language script. 42 | */ 43 | public function registerAssetFiles($view) 44 | { 45 | if ($this->language) { 46 | $this->js[] = "i18n/{$this->language}.js"; 47 | } 48 | parent::registerAssetFiles($view); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/assets/TimePickerAsset.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\assets; 9 | 10 | use yii\web\AssetBundle; 11 | 12 | /** 13 | * TimePickerAsset 14 | * 15 | * @author skoro 16 | */ 17 | class TimePickerAsset extends AssetBundle 18 | { 19 | 20 | public $sourcePath = '@vendor/almasaeed2010/adminlte/plugins/timepicker'; 21 | 22 | public $css = [ 23 | 'bootstrap-timepicker.min.css', 24 | ]; 25 | 26 | public $js = [ 27 | 'bootstrap-timepicker.min.js', 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /app/assets/TypeAheadAsset.php: -------------------------------------------------------------------------------- 1 | 15 | * @since 2.0 16 | */ 17 | class TypeAheadAsset extends AssetBundle 18 | { 19 | public $sourcePath = '@bower/typeahead.js/dist'; 20 | public $js = [ 21 | 'typeahead.bundle.js', 22 | ]; 23 | public $depends = [ 24 | 'yii\bootstrap\BootstrapAsset', 25 | 'yii\bootstrap\BootstrapPluginAsset', 26 | ]; 27 | } 28 | -------------------------------------------------------------------------------- /app/base/Action.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base; 9 | 10 | /** 11 | * Parent of controller actions. 12 | * 13 | * @author skoro 14 | */ 15 | class Action extends \yii\base\Action 16 | { 17 | /** 18 | * @var string view layout 19 | */ 20 | public $layout; 21 | 22 | /** 23 | * @var string action view 24 | */ 25 | public $view; 26 | 27 | /** 28 | * Render action. 29 | * @param array $params view parameters 30 | * @return string 31 | */ 32 | protected function render(array $params = []) 33 | { 34 | if ($this->layout) { 35 | $this->controller->layout = $this->layout; 36 | } 37 | return $this->controller->render($this->view, $params); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/base/ConsoleApplication.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace app\base; 9 | 10 | /** 11 | * ConsoleApplication 12 | * 13 | * @author skoro 14 | */ 15 | class ConsoleApplication extends \yii\console\Application 16 | { 17 | use ModuleApplicationTrait; 18 | } 19 | -------------------------------------------------------------------------------- /app/base/Formatter.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base; 9 | 10 | use app\models\User; 11 | use app\widgets\ProgressBar; 12 | use Yii; 13 | use yii\helpers\Html; 14 | 15 | /** 16 | * Formatter 17 | * 18 | * @author skoro 19 | */ 20 | class Formatter extends \yii\i18n\Formatter 21 | { 22 | 23 | /** 24 | * Markdown parsers. 25 | * @see Formatter::asMarkdown() 26 | */ 27 | const MARKDOWN_PARSER_TRADITIONAL = '\cebe\markdown\Markdown'; 28 | const MARKDOWN_PARSER_GITHUB = '\cebe\markdown\GithubMarkdown'; 29 | const MARKDOWN_PARSER_EXTRA = '\cebe\markdown\MarkdownExtra'; 30 | 31 | /** 32 | * Format value as progress bar widget. 33 | * @param integer $value progress value 34 | * @param array $options widget options 35 | * @return string 36 | */ 37 | public function asProgressBar($value, $options = []) 38 | { 39 | $options['value'] = $value; 40 | return ProgressBar::widget($options); 41 | } 42 | 43 | /** 44 | * Converts Markdown to html. 45 | * @param string $text markdown source. 46 | * @param string $parserClass markdown parser class. 47 | * @return string 48 | */ 49 | public function asMarkdown($text, $parserClass = self::MARKDOWN_PARSER_EXTRA) 50 | { 51 | $parser = new $parserClass(); 52 | return $parser->parse($text); 53 | } 54 | 55 | /** 56 | * Output user as link to their profile. 57 | * User must has 'viewAnyUser' permission to link to user profiles otherwise 58 | * outputs as simple text. 59 | * @param User $user 60 | * @param array $options link options 61 | * @return string 62 | */ 63 | public function asUserlink($user, array $options = []) 64 | { 65 | if (empty($user)) { 66 | return $this->nullDisplay; 67 | } 68 | 69 | $username = Html::encode($user->name); 70 | if (Yii::$app->user->id == $user->id) { 71 | $link = ['/user/profile']; 72 | } elseif (Yii::$app->user->can('viewAnyUser')) { 73 | $link = ['/user/profile', 'id' => $user->id]; 74 | } else { 75 | return $username; 76 | } 77 | 78 | return Html::a($username, $link, $options); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/base/MailTrait.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.3 6 | */ 7 | 8 | namespace app\base; 9 | 10 | use app\components\Param; 11 | use Yii; 12 | use yii\helpers\ArrayHelper; 13 | 14 | /** 15 | * Wrapper for mailer compose. 16 | * 17 | * Send email to destination address from site admin email. 18 | * 19 | * @author skoro 20 | */ 21 | trait MailTrait 22 | { 23 | 24 | /** 25 | * Send email. 26 | * @param string $view mail template. 27 | * @param string $to destination email address. 28 | * @param array $params view parameters. Special parameter 'subject' 29 | * used for mail subject rest parameters passed to view. 30 | * @return boolean 31 | */ 32 | public function mail($view, $to, array $params) 33 | { 34 | $views = [ 35 | 'html' => $view . '-html', 36 | 'text' => $view . '-text', 37 | ]; 38 | 39 | $subject = ArrayHelper::remove($params, 'subject'); 40 | 41 | $compose = Yii::$app->mailer->compose($views, $params); 42 | if (!empty($subject)) { 43 | $compose->setSubject($subject); 44 | } 45 | $compose->setTo($to) 46 | ->setFrom(Param::value('Site.adminEmail')); 47 | 48 | return $compose->send(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/base/Migration.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base; 9 | 10 | /** 11 | * Migration 12 | * 13 | * @author skoro 14 | */ 15 | class Migration extends \yii\db\Migration 16 | { 17 | /** 18 | * @var string default database table options. 19 | */ 20 | protected $tableOptions = null; 21 | 22 | /** 23 | * Is MS SQL Server database driver used ? 24 | * @return bool 25 | */ 26 | protected function isMSSQL() 27 | { 28 | return $this->db->driverName === 'mssql' || $this->db->driverName === 'sqlsrv' || $this->db->driverName === 'dblib'; 29 | } 30 | 31 | /** 32 | * Is SQLite database driver used ? 33 | * @return bool 34 | */ 35 | protected function isSqlite() 36 | { 37 | return $this->db->driverName === 'sqlite'; 38 | } 39 | 40 | /** 41 | * Get specific table options for database driver. 42 | * @return string 43 | */ 44 | protected function getTableOptions() 45 | { 46 | if ($this->tableOptions === null) { 47 | switch ($this->db->driverName) { 48 | case 'mysql': 49 | // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci 50 | $this->tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci'; 51 | break; 52 | 53 | default: 54 | $this->tableOptions = ''; 55 | } 56 | } 57 | 58 | return $this->tableOptions; 59 | } 60 | 61 | /** 62 | * @inheritdoc 63 | */ 64 | public function createTable($table, $columns, $options = null) 65 | { 66 | parent::createTable($table, $columns, $options === null ? $this->getTableOptions() : $options); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/base/ModelUrlTrait.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace app\base; 9 | 10 | use Closure; 11 | 12 | /** 13 | * This trait is helper for generate model url. 14 | * 15 | * @author skoro 16 | */ 17 | trait ModelUrlTrait 18 | { 19 | /** 20 | * @var string|array|Closure delete url. 21 | */ 22 | public $url; 23 | 24 | /** 25 | * Returns model's url. 26 | * @param mixed $model 27 | * @param string|array $default default url 28 | * @return array|string 29 | */ 30 | protected function getModelUrl($model, $default) 31 | { 32 | if ($this->url instanceof Closure) { 33 | $url = call_user_func($this->url, $model); 34 | } 35 | else { 36 | $url = empty($this->url) ? $default : $this->url; 37 | if (is_array($url) && isset($model->id)) { 38 | $url['id'] = $model->id; 39 | } 40 | } 41 | 42 | return $url; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/base/Module.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace app\base; 9 | 10 | use Yii; 11 | 12 | /** 13 | * Application module. 14 | * 15 | * @author skoro 16 | */ 17 | class Module extends \yii\base\Module 18 | { 19 | /** 20 | * Module statuses. 21 | */ 22 | const STATUS_INSTALLED = 1; 23 | const STATUS_NOTINSTALLED = 0; 24 | 25 | /** 26 | * Events. 27 | */ 28 | const EVENT_MODULE_INSTALLED = 'eventModuleInstalled'; 29 | const EVENT_MODULE_UNINSTALLED = 'eventModuleUninstalled'; 30 | 31 | /** 32 | * @var string required, module name. 33 | */ 34 | public $moduleName; 35 | 36 | /** 37 | * @var string 38 | */ 39 | public $moduleDescription = ''; 40 | 41 | /** 42 | * Emit event when module is installed. 43 | */ 44 | public function install() 45 | { 46 | $this->trigger(self::EVENT_MODULE_INSTALLED); 47 | } 48 | 49 | /** 50 | * Emit event when module is uninstalled. 51 | */ 52 | public function uninstall() 53 | { 54 | $this->trigger(self::EVENT_MODULE_UNINSTALLED); 55 | } 56 | 57 | /** 58 | * Adds menu entries. 59 | */ 60 | public function addMenu($menu, array $items) 61 | { 62 | try { 63 | Yii::$app->menu->insertItems($menu, $items); 64 | } catch (\Exception $e) { 65 | // Menu does not available in console applications. 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/base/ModuleMigrateException.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace app\base; 9 | 10 | /** 11 | * Exception occurs when module migrations failed (up/down). 12 | * @author skoro 13 | */ 14 | class ModuleMigrateException extends \Exception 15 | { 16 | /** 17 | * @var string 18 | */ 19 | public $moduleId; 20 | 21 | /** 22 | * @var array 23 | */ 24 | public $migrations; 25 | 26 | /** 27 | * @var string migration command output. 28 | */ 29 | public $output = ''; 30 | 31 | /** 32 | * @param string $moduleId 33 | * @param array $migrations list of failed migrations. 34 | * @param string $output migration command output 35 | */ 36 | public function __construct($moduleId, array $migrations, $output = '') { 37 | $this->moduleId = $moduleId; 38 | $this->migrations = $migrations; 39 | $this->output = $output; 40 | parent::__construct('Cannot apply module migrations.'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/base/WebApplication.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace app\base; 9 | 10 | /** 11 | * Web application. 12 | * 13 | * @author skoro 14 | */ 15 | class WebApplication extends \yii\web\Application 16 | { 17 | use ModuleApplicationTrait; 18 | } 19 | -------------------------------------------------------------------------------- /app/base/actions/user/Login.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\actions\user; 9 | 10 | use app\base\Action; 11 | use app\base\Controller; 12 | use app\components\Param; 13 | use Yii; 14 | use yii\web\Response; 15 | use yii\widgets\ActiveForm; 16 | 17 | /** 18 | * User login action. 19 | * 20 | * @todo enable/disable social links. 21 | * @author skoro 22 | */ 23 | class Login extends Action 24 | { 25 | 26 | /** 27 | * @var string class name for Login form. 28 | */ 29 | public $modelClass = 'app\forms\user\Login'; 30 | 31 | /** 32 | * @var string 33 | */ 34 | public $view = 'login'; 35 | 36 | /** 37 | * @inheritdoc 38 | */ 39 | public function run() 40 | { 41 | if (!Yii::$app->user->isGuest) { 42 | return $this->controller->goBack(); 43 | } 44 | 45 | $model = new $this->modelClass; 46 | if (Yii::$app->request->isPost) { 47 | if ($model->load(Yii::$app->request->post()) && $model->login()) { 48 | return $this->controller->goBack(); 49 | } else { 50 | $this->controller->addFlash(Controller::FLASH_ERROR, Yii::t('app', 'Login to your account failed.')); 51 | $model->password = ''; 52 | } 53 | } 54 | 55 | if (!Yii::$app->request->isPjax && Yii::$app->request->isAjax) { 56 | Yii::$app->response->format = Response::FORMAT_JSON; 57 | return ActiveForm::validate($model); 58 | } 59 | 60 | return $this->render([ 61 | 'model' => $model, 62 | 'disableUserRegister' => Param::value('User.disableUserRegister'), 63 | ]); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/base/actions/user/Logout.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\actions\user; 9 | 10 | use Yii; 11 | use app\base\Action; 12 | 13 | /** 14 | * User logout action. 15 | * 16 | * @author skoro 17 | */ 18 | class Logout extends Action 19 | { 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | public function run() 25 | { 26 | Yii::$app->user->logout(); 27 | return $this->controller->goHome(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/base/actions/user/PasswordRequest.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\actions\user; 9 | 10 | use Yii; 11 | use app\base\Action; 12 | use app\base\Controller; 13 | 14 | /** 15 | * Request user password. 16 | * 17 | * @author skoro 18 | */ 19 | class PasswordRequest extends Action 20 | { 21 | 22 | /** 23 | * @var string class name for Password Request form. 24 | */ 25 | public $modelClass = 'app\forms\user\PasswordRequest'; 26 | 27 | /** 28 | * @var string 29 | */ 30 | public $view = 'passwordRequest'; 31 | 32 | /** 33 | * @inheritdoc 34 | */ 35 | public function run() 36 | { 37 | $model = new $this->modelClass; 38 | 39 | if (Yii::$app->request->isPost) { 40 | if ($model->load(Yii::$app->request->post()) && $model->validate()) { 41 | if ($model->sendEmail()) { 42 | $this->controller->addFlash(Controller::FLASH_INFO, Yii::t('app', 'Check your email for further instructions.')); 43 | return $this->controller->redirect(['user/login']); 44 | } else { 45 | $this->controller->addFlash(Controller::FLASH_ERROR, Yii::t('app', 'Sorry, we are unable to reset password for provided email.')); 46 | } 47 | } 48 | } 49 | 50 | return $this->render([ 51 | 'model' => $model, 52 | ]); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/base/actions/user/PasswordReset.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\actions\user; 9 | 10 | use Yii; 11 | use app\base\Action; 12 | use app\base\Controller; 13 | 14 | /** 15 | * User password reset. 16 | * 17 | * @author skoro 18 | */ 19 | class PasswordReset extends Action 20 | { 21 | /** 22 | * @var string class name for Login form. 23 | */ 24 | public $modelClass = 'app\forms\user\PasswordReset'; 25 | 26 | /** 27 | * @var string 28 | */ 29 | public $view = 'passwordReset'; 30 | 31 | /** 32 | * @inheritdoc 33 | */ 34 | public function run($token) 35 | { 36 | try { 37 | $model = new $this->modelClass($token); 38 | } 39 | catch(\yii\base\InvalidParamException $e) { 40 | $this->controller->addFlash(Controller::FLASH_ERROR, $e->getMessage()); 41 | return $this->controller->goHome(); 42 | } 43 | 44 | if (Yii::$app->request->isPost) { 45 | if ($model->load(Yii::$app->request->post()) && $model->validate()) { 46 | if ($model->resetPassword()) { 47 | $this->controller->addFlash(Controller::FLASH_SUCCESS, Yii::t('app', 'Password has been changed. Now you may login.')); 48 | return $this->controller->redirect(['user/login']); 49 | } 50 | else { 51 | $this->controller->addFlash(Controller::FLASH_ERROR, Yii::t('app', 'Unable to change password.')); 52 | } 53 | } 54 | } 55 | 56 | return $this->render([ 57 | 'model' => $model, 58 | ]); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/base/actions/user/Profile.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\actions\user; 9 | 10 | use app\base\Action; 11 | use app\base\Controller; 12 | use app\models\User; 13 | use Yii; 14 | use yii\web\Response; 15 | use yii\widgets\ActiveForm; 16 | 17 | /** 18 | * User profile. 19 | * 20 | * @author skoro 21 | */ 22 | class Profile extends Action 23 | { 24 | 25 | /** 26 | * @var string class name for Register form. 27 | */ 28 | public $modelClass = 'app\forms\user\Profile'; 29 | 30 | /** 31 | * @var string 32 | */ 33 | public $view = 'profile'; 34 | 35 | /** 36 | * @inheritdoc 37 | */ 38 | public function run($id = null, $tab = 'account') 39 | { 40 | if (Yii::$app->user->isGuest) { 41 | return $this->controller->redirect(['user/login']); 42 | } 43 | 44 | if ($id === null) { 45 | $user = Yii::$app->user->getIdentity(); 46 | } elseif (Yii::$app->user->can('updateAnyUser')) { 47 | $user = $this->controller->findModel(User::className(), $id); 48 | } else { 49 | throw new \yii\web\ForbiddenHttpException(); 50 | } 51 | 52 | $model = new $this->modelClass($user); 53 | 54 | if (Yii::$app->request->isPost) { 55 | if (Yii::$app->user->can('updateAnyUser')) { 56 | $model->setScenario('admin'); 57 | } 58 | if ($model->load(Yii::$app->request->post()) && $model->save()) { 59 | $this->controller->addFlash(Controller::FLASH_INFO, Yii::t('app', 'Changes has saved.')); 60 | $model->reset(); 61 | } 62 | } 63 | 64 | if (!Yii::$app->request->isPjax && Yii::$app->request->isAjax) { 65 | Yii::$app->response->format = Response::FORMAT_JSON; 66 | return ActiveForm::validate($model); 67 | } 68 | 69 | return $this->render([ 70 | 'model' => $model, 71 | 'tab' => $tab, 72 | ]); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/base/actions/user/Register.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\actions\user; 9 | 10 | use app\base\Action; 11 | use app\components\Param; 12 | use Yii; 13 | use yii\helpers\Url; 14 | use yii\web\NotFoundHttpException; 15 | use yii\web\Response; 16 | use yii\widgets\ActiveForm; 17 | 18 | /** 19 | * User register action. 20 | * 21 | * To disable user registration, put to the app's config 22 | * parameter `disableUserRegister => true`. 23 | * 24 | * @author skoro 25 | */ 26 | class Register extends Action 27 | { 28 | 29 | /** 30 | * @var string class name for Register form. 31 | */ 32 | public $modelClass = 'app\forms\user\Register'; 33 | 34 | /** 35 | * @var string 36 | */ 37 | public $view = 'register'; 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | public function run() 43 | { 44 | if (Param::value('User.disableUserRegister', false)) { 45 | throw new NotFoundHttpException(); 46 | } 47 | 48 | if (!Yii::$app->user->isGuest) { 49 | return $this->controller->goBack(); 50 | } 51 | 52 | $model = new $this->modelClass; 53 | if (Yii::$app->request->isPost) { 54 | if ($model->load(Yii::$app->request->post()) && $model->register()) { 55 | $this->controller->addFlash('info', 56 | Yii::t('app', 'Registration successful. Now you can login.', [ 57 | // FIXME: use app's login route. 58 | 'login' => Url::to(['user/login']) 59 | ]) 60 | ); 61 | return $this->controller->goHome(); 62 | } 63 | } 64 | 65 | if (!Yii::$app->request->isPjax && Yii::$app->request->isAjax) { 66 | Yii::$app->response->format = Response::FORMAT_JSON; 67 | return ActiveForm::validate($model); 68 | } 69 | 70 | return $this->render([ 71 | 'model' => $model, 72 | ]); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/base/behaviors/LinkedBehavior.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\behaviors; 9 | 10 | use Yii; 11 | use yii\base\Behavior; 12 | use yii\db\Query; 13 | 14 | /** 15 | * LinkedBehavior 16 | * 17 | * This behavior links two tables via helper table (many-to-many relation). 18 | * 19 | * @author skoro 20 | */ 21 | class LinkedBehavior extends Behavior 22 | { 23 | 24 | /** 25 | * @var string helper table name. 26 | */ 27 | protected $_table; 28 | 29 | /** 30 | * @var string linked model attribute in helper table. 31 | */ 32 | protected $_linkAttribute; 33 | 34 | /** 35 | * @var string which module to link to owner. 36 | */ 37 | protected $_linkModel; 38 | 39 | /** 40 | * @var string owner attribute in helper table. 41 | */ 42 | protected $_sourceAttribute; 43 | 44 | /** 45 | * @var string 46 | */ 47 | protected $_idAttribute = 'id'; 48 | 49 | /** 50 | * Relation to linked models. 51 | * @return \yii\db\ActiveQuery 52 | */ 53 | protected function getRelation() 54 | { 55 | return $this->owner->hasMany($this->_linkModel, [$this->_idAttribute => $this->_linkAttribute]) 56 | ->viaTable($this->_table, [$this->_sourceAttribute => $this->_idAttribute]); 57 | } 58 | 59 | /** 60 | * Link model with owner. 61 | * @param Model $model 62 | * @return boolean 63 | * @throws InvalidCallException 64 | */ 65 | protected function addLinked($model) 66 | { 67 | if ($model->isNewRecord) { 68 | throw new InvalidCallException('Model must be saved before adding.'); 69 | } 70 | if ($this->owner->isNewRecord) { 71 | throw new InvalidCallException('Save the model before linking with other.'); 72 | } 73 | $params = [ 74 | $this->_sourceAttribute => $this->owner->id, 75 | $this->_linkAttribute => $model->id, 76 | ]; 77 | $exists = (new Query())->from($this->_table) 78 | ->where($params) 79 | ->exists(); 80 | if ($exists) { 81 | return false; 82 | } 83 | return Yii::$app->db->createCommand() 84 | ->insert($this->_table, $params) 85 | ->execute(); 86 | 87 | } 88 | 89 | /** 90 | * Break relation between linking and owner. 91 | * @param Model $model linked model 92 | * @return integer 93 | */ 94 | protected function removeLinked($model) 95 | { 96 | return Yii::$app->db->createCommand() 97 | ->delete($this->_table, [ 98 | $this->_sourceAttribute => $this->owner->id, 99 | $this->_linkAttribute => $model->id, 100 | ]) 101 | ->execute(); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /app/base/behaviors/SerializableBehavior.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace app\base\behaviors; 9 | 10 | use yii\base\Behavior; 11 | use yii\db\ActiveRecord; 12 | 13 | /** 14 | * SerializableBehavior 15 | * 16 | * @author skoro 17 | */ 18 | class SerializableBehavior extends Behavior 19 | { 20 | 21 | /** 22 | * @var array list of attribute name to be serialize. 23 | */ 24 | public $attributes = []; 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public function events() 30 | { 31 | return [ 32 | ActiveRecord::EVENT_AFTER_FIND => 'onAfterFind', 33 | ActiveRecord::EVENT_BEFORE_INSERT => 'onBeforeSave', 34 | ActiveRecord::EVENT_BEFORE_UPDATE => 'onBeforeSave', 35 | ActiveRecord::EVENT_AFTER_INSERT => 'onAfterSave', 36 | ActiveRecord::EVENT_AFTER_UPDATE => 'onAfterSave', 37 | ]; 38 | } 39 | 40 | public function onAfterFind() 41 | { 42 | $this->unserializeAttributes(); 43 | } 44 | 45 | public function onBeforeSave() 46 | { 47 | $this->serializeAttributes(); 48 | } 49 | 50 | public function onAfterSave() 51 | { 52 | $this->unserializeAttributes(); 53 | } 54 | 55 | protected function serializeAttributes() 56 | { 57 | foreach ($this->attributes as $attribute) { 58 | $this->owner->$attribute = serialize($this->owner->$attribute); 59 | } 60 | } 61 | 62 | protected function unserializeAttributes() 63 | { 64 | foreach ($this->attributes as $attribute) { 65 | $this->owner->$attribute = unserialize($this->owner->$attribute); 66 | $this->owner->setOldAttribute($attribute, $this->owner->$attribute); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/base/behaviors/StatusBehavior.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\behaviors; 9 | 10 | use Yii; 11 | use ReflectionClass; 12 | use yii\base\Behavior; 13 | 14 | /** 15 | * StatusBehavior 16 | * 17 | * This behavior extracts STATUS_ constant from a model and makes appropriate 18 | * labels. For example, model has: 19 | * ```php 20 | * class Order extends Model { 21 | * const STATUS_ACTIVE = 0; 22 | * const STATUS_COMPLETED = 1; 23 | * const STATUS_CANCELLED = 2; 24 | * 25 | * public $status; 26 | * } 27 | * $order = new Order(); 28 | * $order->getStatusLabels(); // Will returns: 0 => Active, 1 => Completed, ... 29 | * $order->getStatusLabel(); // Will returns current model status based on 30 | * // 'status' model's attribute. 31 | * ``` 32 | * 33 | * 34 | * @author skoro 35 | */ 36 | class StatusBehavior extends Behavior 37 | { 38 | 39 | /** 40 | * @var string[] a list of status labels. 41 | */ 42 | protected $_statuses; 43 | 44 | /** 45 | * @var string 46 | */ 47 | public $statusAttribute = 'status'; 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | public function attach($owner) 53 | { 54 | parent::attach($owner); 55 | $reflection = new ReflectionClass($this->owner); 56 | $consts = $reflection->getConstants(); 57 | foreach ($consts as $name => $value) { 58 | if (strpos($name, 'STATUS_') === 0) { 59 | $this->_statuses[$value] = $this->createLabel($name); 60 | } 61 | } 62 | } 63 | 64 | /** 65 | * Returns model's all status labels. 66 | * @return array 67 | */ 68 | public function getStatusLabels() 69 | { 70 | return $this->_statuses; 71 | } 72 | 73 | /** 74 | * Returns model's status field label. 75 | * @return string 76 | */ 77 | public function getStatusLabel() 78 | { 79 | $status = $this->owner->{$this->statusAttribute}; 80 | return $this->_statuses[$status]; 81 | } 82 | 83 | /** 84 | * Create label from constant name. 85 | * @param string $const 86 | * @return string 87 | */ 88 | protected function createLabel($const) 89 | { 90 | $label = str_replace('STATUS_', '', $const); 91 | $label = ucfirst(strtolower($label)); 92 | return Yii::t('app', $label); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/base/console/Controller.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\console; 9 | 10 | use Yii; 11 | use yii\helpers\Console; 12 | 13 | /** 14 | * Controller 15 | * 16 | * Parent for console controllers. 17 | * 18 | * @todo Helper method for output tabular data. 19 | * @author skoro 20 | */ 21 | class Controller extends \yii\console\Controller 22 | { 23 | 24 | /** 25 | * Prints translated message. 26 | * @param string $message 27 | * @param array $params 28 | */ 29 | public function p($message, array $params = []) 30 | { 31 | $this->stdout(Yii::t('app', $message, $params) . PHP_EOL); 32 | } 33 | 34 | /** 35 | * Prints error message. 36 | * @param string $message 37 | * @param array $params 38 | */ 39 | public function err($message, array $params = []) 40 | { 41 | $this->stderr(Yii::t('app', $message, $params) . PHP_EOL, Console::FG_RED); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/base/gii/ModuleGenerator.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace app\base\gii; 9 | 10 | /** 11 | * Application module generator. 12 | * 13 | * Parameter moduleID is required only. Template 'app-module' used by default. 14 | * moduleClass parameter generated automatically from moduleID property. 15 | * 16 | * Use on command line: 17 | * ``` 18 | * ./bin/yii gii/module --moduleID=my-orders 19 | * ``` 20 | * 21 | * Will create module `MyOrdersModule` in `modules\\my_orders` folder. 22 | * 23 | * @author skoro 24 | */ 25 | class ModuleGenerator extends \yii\gii\generators\module\Generator 26 | { 27 | /** 28 | * @var string the name of the code template that the user has selected. 29 | * The value of this property is internally managed by this class. 30 | */ 31 | public $template = 'app-module'; 32 | 33 | /** 34 | * @inheritdoc 35 | */ 36 | public function rules() 37 | { 38 | return [ 39 | ['moduleID', 'required'], 40 | ['moduleID', 'filter', 'filter' => 'trim'], 41 | ['moduleID', 'match', 'pattern' => '/^[a-zA-Z]+[a-zA-Z-_]+$/', 'message' => 'Only word characters and dashes are allowed.'], 42 | ['moduleID', 'validateModuleID'], 43 | 44 | ['template', 'required', 'message' => 'A code template must be selected.'], 45 | ['template', 'validateTemplate'], 46 | ]; 47 | } 48 | 49 | /** 50 | * Not actual validation but class generator for moduleClass property. 51 | */ 52 | public function validateModuleID() 53 | { 54 | $id = str_replace('-', '_', strtolower($this->moduleID)); 55 | $class = ucwords(str_replace('_', ' ', $id)) . 'Module'; 56 | $class = str_replace(' ', '', $class); 57 | $this->moduleClass = 'modules\\' . $id . '\\' . $class; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/base/grid/CheckboxColumn.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.3 6 | */ 7 | 8 | namespace app\base\grid; 9 | 10 | use app\widgets\Check; 11 | use yii\helpers\Json; 12 | 13 | /** 14 | * Checkbox column with iChecks. 15 | * 16 | * @author skoro 17 | */ 18 | class CheckboxColumn extends \yii\grid\CheckboxColumn 19 | { 20 | 21 | /** 22 | * @var string control style. 23 | */ 24 | public $style = Check::STYLE_FLAT; 25 | 26 | /** 27 | * @var string style color. 28 | */ 29 | public $color = Check::COLOR_GREEN; 30 | 31 | /** 32 | * @inheritdoc 33 | */ 34 | public function registerClientScript() 35 | { 36 | $id = $this->grid->options['id']; 37 | $options = Json::encode([ 38 | 'name' => $this->name, 39 | 'class' => $this->cssClass, 40 | 'multiple' => $this->multiple, 41 | 'checkAll' => $this->grid->showHeader ? $this->getHeaderCheckBoxName() : null, 42 | 'checkboxClass' => Check::createStyleName(Check::TYPE_CHECKBOX, $this->style, $this->color), 43 | ]); 44 | //$this->grid->getView()->registerJs("jQuery('#$id input[type=checkbox').iCheck($options);"); 45 | $this->grid->getView()->registerJs("Admin.Grid.initSelectionColumn('#$id', $options);"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/base/grid/DeleteColumn.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\grid; 9 | 10 | use app\helpers\Icon; 11 | use Closure; 12 | use Yii; 13 | use yii\grid\Column; 14 | use yii\helpers\Html; 15 | use app\base\ModelUrlTrait; 16 | 17 | /** 18 | * DeleteColumn 19 | * 20 | * @author skoro 21 | */ 22 | class DeleteColumn extends Column 23 | { 24 | 25 | use ModelUrlTrait; 26 | 27 | /** 28 | * @var string|Closure 29 | */ 30 | public $confirm; 31 | 32 | /** 33 | * @inheritdoc 34 | */ 35 | protected function renderDataCellContent($model, $key, $index) 36 | { 37 | if ($this->confirm instanceof Closure) { 38 | $confirm = call_user_func($this->confirm, $model); 39 | } 40 | elseif (is_string($this->confirm)) { 41 | $confirm = $this->confirm; 42 | } 43 | else { 44 | $confirm = Yii::t('app', 'Are you sure ?'); 45 | } 46 | $confirm = Html::encode($confirm); 47 | 48 | $url = $this->getModelUrl($model, ['delete']); 49 | 50 | return Html::a(Icon::TRASH, $url, ['class' => 'confirm', 'data-confirm' => $confirm]); 51 | } 52 | 53 | /** 54 | * @inheritdoc 55 | */ 56 | protected function getHeaderCellLabel() 57 | { 58 | return Yii::t('app', 'Delete'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/base/grid/EditColumn.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\base\grid; 9 | 10 | use app\helpers\Icon; 11 | use Yii; 12 | use yii\grid\Column; 13 | use yii\helpers\Html; 14 | use app\base\ModelUrlTrait; 15 | 16 | /** 17 | * EditColumn 18 | * 19 | * @author skoro 20 | */ 21 | class EditColumn extends Column 22 | { 23 | 24 | use ModelUrlTrait; 25 | 26 | /** 27 | * @var array url options. 28 | */ 29 | public $options = []; 30 | 31 | /** 32 | * @inheritdoc 33 | */ 34 | protected function renderDataCellContent($model, $key, $index) 35 | { 36 | $url = $this->getModelUrl($model, ['update']); 37 | return Html::a(Icon::EDIT, $url, $this->options); 38 | } 39 | 40 | /** 41 | * @inheritdoc 42 | */ 43 | protected function getHeaderCellLabel() 44 | { 45 | return Yii::t('app', 'Edit'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/commands/MigrateController.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace app\commands; 9 | 10 | use app\base\ModuleMigrateException; 11 | use Yii; 12 | 13 | /** 14 | * Apply/revert module migration hooks. 15 | * 16 | * @author skoro 17 | */ 18 | class MigrateController extends \yii\console\controllers\MigrateController 19 | { 20 | 21 | /** 22 | * Get new migrations for module. 23 | * @param string $moduleId 24 | * @return array migrations list 25 | */ 26 | public function getModuleNewMigrations($moduleId) 27 | { 28 | if (!is_dir($dir = $this->getModuleMigrationsDir($moduleId))) { 29 | return []; 30 | } 31 | $this->migrationPath = $dir; 32 | return $this->getNewMigrations(); 33 | } 34 | 35 | /** 36 | * Apply module migrations. 37 | * @param string $moduleId 38 | * @throws ModuleMigrateException 39 | */ 40 | public function moduleMigrateUp($moduleId) 41 | { 42 | $failed = []; 43 | $migrations = $this->getModuleNewMigrations($moduleId); 44 | ob_start(); 45 | foreach ($migrations as $migration) { 46 | if (!$this->migrateUp($migration)) { 47 | $failed[] = $migration; 48 | } 49 | } 50 | $output = ob_get_clean(); 51 | if ($failed) { 52 | throw new ModuleMigrateException($moduleId, $failed, $output); 53 | } 54 | } 55 | 56 | /** 57 | * Revert module migrations. 58 | * @param string $moduleId 59 | * @param array $migrations 60 | * @throws ModuleMigrateException 61 | */ 62 | public function moduleMigrateDown($moduleId, $migrations) 63 | { 64 | $this->migrationPath = $this->getModuleMigrationsDir($moduleId); 65 | $failed = []; 66 | ob_start(); 67 | // Start revert migrations from reverse order. 68 | $migrations = array_reverse($migrations); 69 | foreach ($migrations as $migration) { 70 | if (!$this->migrateDown($migration)) { 71 | $failed[] = $migration; 72 | } 73 | } 74 | $output = ob_get_clean(); 75 | if ($failed) { 76 | throw new ModuleMigrateException($moduleId, $failed, $output); 77 | } 78 | } 79 | 80 | /** 81 | * 82 | * @param string $moduleId 83 | * @return string 84 | */ 85 | protected function getModuleMigrationsDir($moduleId) 86 | { 87 | return Yii::getAlias('@modules') . DIRECTORY_SEPARATOR . $moduleId . 88 | DIRECTORY_SEPARATOR . 'migrations'; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/commands/SqlController.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace app\commands; 9 | 10 | use app\base\console\Controller; 11 | use Yii; 12 | use yii\helpers\Console; 13 | 14 | /** 15 | * SqlController 16 | * 17 | * @author skoro 18 | */ 19 | class SqlController extends Controller 20 | { 21 | 22 | /** 23 | * Launch database SQL client on application's database. 24 | */ 25 | public function actionIndex() 26 | { 27 | $db = Yii::$app->db; 28 | switch ($driver = $db->getDriverName()) { 29 | case 'sqlite': 30 | $file = preg_replace('/^sqlite:/', '', $db->dsn); 31 | $cmd = 'sqlite3 ' . Yii::getAlias($file); 32 | break; 33 | 34 | case 'mysql': 35 | if (!($dsn = $this->parseMysqlDsn())) { 36 | $this->stderr('Cannot parse DSN: ' . $dsn, Console::FG_RED); 37 | return 1; 38 | } 39 | $cmd = sprintf('mysql -u %s -p%s -h %s %s', 40 | $dsn['user'], 41 | $dsn['password'], 42 | $dsn['host'], 43 | $dsn['db'] 44 | ); 45 | break; 46 | 47 | default: 48 | $this->stderr('Not implemented for driver: ' . $driver, Console::FG_RED); 49 | return 1; 50 | } 51 | 52 | $this->procOpen($cmd); 53 | } 54 | 55 | /** 56 | * Generate dump of application database to stdout. 57 | */ 58 | public function actionDump() 59 | { 60 | $db = Yii::$app->db; 61 | if ($db->getDriverName() !== 'mysql') { 62 | $this->stderr('Dump generation implemented only for MySQL yet.', Console::FG_RED); 63 | return 1; 64 | } 65 | 66 | if (!($dsn = $this->parseMysqlDsn())) { 67 | $this->stderr('Cannot parse DSN: ' . $dsn, Console::FG_RED); 68 | return 1; 69 | } 70 | 71 | $cmd = sprintf('mysqldump -u %s -p%s -h %s %s', 72 | $dsn['user'], 73 | $dsn['password'], 74 | $dsn['host'], 75 | $dsn['db'] 76 | ); 77 | 78 | $this->procOpen($cmd); 79 | } 80 | 81 | /** 82 | * Open process. 83 | * @param string $cmd command line 84 | * @return integer command exit status 85 | */ 86 | protected function procOpen($cmd) 87 | { 88 | $process = proc_open($cmd, [0 => STDIN, 1 => STDOUT, 2 => STDERR], $pipes); 89 | $proc_status = proc_get_status($process); 90 | $exit_code = proc_close($process); 91 | return ($proc_status['running'] ? $exit_code : $proc_status['exitcode']); 92 | } 93 | 94 | /** 95 | * Parse MySQL dsn string. 96 | * @return array parsed elements: host, db, user, password. 97 | */ 98 | protected function parseMysqlDsn() 99 | { 100 | $dsn = Yii::$app->db->dsn; 101 | if (!preg_match('/^mysql:host=(.*?);dbname=(.*)/', $dsn, $matches)) { 102 | return false; 103 | } 104 | return [ 105 | 'host' => $matches[1], 106 | 'db' => $matches[2], 107 | 'user' => Yii::$app->db->username, 108 | 'password' => Yii::$app->db->password, 109 | ]; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/config/common.php: -------------------------------------------------------------------------------- 1 | APPROOT_DIR . '/app', 15 | 'vendorPath' => APPROOT_DIR . '/vendor', 16 | 'runtimePath' => APPROOT_DIR . '/runtime', 17 | 'components' => [ 18 | 'authManager' => [ 19 | 'class' => 'yii\rbac\DbManager', 20 | ], 21 | ], 22 | ]; 23 | 24 | if (YII_ENV_DEV) { 25 | // configuration adjustments for 'dev' environment 26 | $config['bootstrap'][] = 'debug'; 27 | $config['modules']['debug'] = [ 28 | 'class' => 'yii\debug\Module', 29 | ]; 30 | 31 | $config['bootstrap'][] = 'gii'; 32 | $config['modules']['gii'] = [ 33 | 'class' => 'yii\gii\Module', 34 | 'newFileMode' => 0664, 35 | 'newDirMode' => 0775, 36 | 'generators' => [ 37 | 'module' => [ 38 | 'class' => 'app\base\gii\ModuleGenerator', 39 | 'templates' => [ 40 | 'app-module' => '@app/views/templates/gii-module', 41 | ], 42 | ], 43 | ], 44 | ]; 45 | 46 | // Link assets instead of copy them (useful for development environment). 47 | $config['components']['assetManager']['linkAssets'] = true; 48 | } 49 | 50 | return $config; 51 | }); 52 | -------------------------------------------------------------------------------- /app/config/console.php: -------------------------------------------------------------------------------- 1 | 'backend-console', 7 | 'bootstrap' => ['log'], 8 | 'controllerNamespace' => 'app\commands', 9 | 'components' => [ 10 | 'cache' => [ 11 | 'class' => 'yii\caching\FileCache', 12 | ], 13 | 'log' => [ 14 | 'targets' => [ 15 | [ 16 | 'class' => 'yii\log\FileTarget', 17 | 'levels' => ['error', 'warning'], 18 | ], 19 | ], 20 | ], 21 | ], 22 | 'controllerMap' => [ 23 | 'migrate' => [ 24 | 'class' => 'yii\console\controllers\MigrateController', 25 | 'templateFile' => '@app/views/templates/migration.php', 26 | ], 27 | 'serve' => [ 28 | 'class' => 'yii\console\controllers\ServeController', 29 | 'docroot' => APPROOT_DIR . '/web', 30 | ], 31 | # 'fixture' => [ // Fixture generation command line. 32 | # 'class' => 'yii\faker\FixtureController', 33 | # ], 34 | ], 35 | ]; 36 | 37 | return yii\helpers\ArrayHelper::merge(require APPROOT_DIR . '/app/config/common.php', $config); 38 | -------------------------------------------------------------------------------- /app/controllers/SiteController.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'class' => 'yii\web\ErrorAction', 17 | ], 18 | 'captcha' => [ 19 | 'class' => 'yii\captcha\CaptchaAction', 20 | 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, 21 | ], 22 | 'settings' => [ 23 | 'class' => 'app\base\actions\Settings', 24 | ], 25 | ]; 26 | } 27 | 28 | public function actionIndex() 29 | { 30 | return $this->render('index'); 31 | } 32 | 33 | public function actionContact() 34 | { 35 | $model = new ContactForm(); 36 | if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) { 37 | Yii::$app->session->setFlash('contactFormSubmitted'); 38 | 39 | return $this->refresh(); 40 | } 41 | return $this->render('contact', [ 42 | 'model' => $model, 43 | ]); 44 | } 45 | 46 | public function actionAbout() 47 | { 48 | return $this->render('about'); 49 | } 50 | 51 | public function actionModalRemote() 52 | { 53 | return '

Loaded from remote source!

'; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/forms/ContactForm.php: -------------------------------------------------------------------------------- 1 | 'Verification Code', 41 | ]; 42 | } 43 | 44 | /** 45 | * Sends an email to the specified email address using the information collected by this model. 46 | * @param string $email the target email address 47 | * @return boolean whether the model passes validation 48 | */ 49 | public function contact($email) 50 | { 51 | if ($this->validate()) { 52 | Yii::$app->mailer->compose() 53 | ->setTo($email) 54 | ->setFrom([$this->email => $this->name]) 55 | ->setSubject($this->subject) 56 | ->setTextBody($this->body) 57 | ->send(); 58 | 59 | return true; 60 | } 61 | return false; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/forms/user/Login.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\forms\user; 9 | 10 | use Yii; 11 | use yii\base\Model; 12 | use app\models\User; 13 | 14 | /** 15 | * Login is the model behind the login form. 16 | */ 17 | class Login extends Model 18 | { 19 | public $email; 20 | public $password; 21 | public $rememberMe = true; 22 | 23 | private $_user = false; 24 | 25 | 26 | /** 27 | * @return array the validation rules. 28 | */ 29 | public function rules() 30 | { 31 | return [ 32 | [['email', 'password'], 'required'], 33 | ['email', 'email'], 34 | ['rememberMe', 'boolean'], 35 | ['password', 'validatePassword'], 36 | ]; 37 | } 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | public function attributeLabels() 43 | { 44 | return [ 45 | 'email' => Yii::t('app', 'Email'), 46 | 'password' => Yii::t('app', 'Password'), 47 | 'rememberMe' => Yii::t('app', 'Remember Me'), 48 | ]; 49 | } 50 | 51 | /** 52 | * Validates the password. 53 | * This method serves as the inline validation for password. 54 | * 55 | * @param string $attribute the attribute currently being validated 56 | * @param array $params the additional name-value pairs given in the rule 57 | */ 58 | public function validatePassword($attribute, $params) 59 | { 60 | if (!$this->hasErrors()) { 61 | $user = $this->getUser(); 62 | 63 | if (!$user || !$user->validatePassword($this->password)) { 64 | $this->addError($attribute, 'Incorrect email or password.'); 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Logs in a user using the provided email and password. 71 | * @return boolean whether the user is logged in successfully 72 | */ 73 | public function login() 74 | { 75 | if ($this->validate()) { 76 | $user = $this->getUser(); 77 | if ($user->status === User::STATUS_ENABLED) { 78 | return Yii::$app->user->login($user, $this->rememberMe ? 3600*24*30 : 0); 79 | } 80 | } 81 | return false; 82 | } 83 | 84 | /** 85 | * Finds user by [[email]] 86 | * 87 | * @return User|null 88 | */ 89 | public function getUser() 90 | { 91 | if ($this->_user === false) { 92 | $this->_user = User::findByEmail($this->email); 93 | } 94 | 95 | return $this->_user; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/forms/user/PasswordRequest.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\forms\user; 9 | 10 | use app\models\User; 11 | use app\base\MailTrait; 12 | use Yii; 13 | use yii\base\Model; 14 | 15 | /** 16 | * PasswordRequest 17 | * 18 | * @author skoro 19 | */ 20 | class PasswordRequest extends Model 21 | { 22 | 23 | use MailTrait; 24 | 25 | /** 26 | * @var string 27 | */ 28 | public $email; 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | public function rules() 34 | { 35 | return [ 36 | ['email', 'required'], 37 | ['email', 'email'], 38 | ['email', 'exist', 39 | 'targetClass' => Yii::$app->user->identityClass, 40 | 'filter' => ['status' => User::STATUS_ENABLED], 41 | 'message' => 'User with this email not found.', 42 | ], 43 | ]; 44 | } 45 | 46 | /** 47 | * Send password reset instructions. 48 | * @return boolean 49 | */ 50 | public function sendEmail() 51 | { 52 | $user = User::findByEmail($this->email); 53 | if ($user && $user->status === User::STATUS_ENABLED) { 54 | $user->generatePasswordResetToken(); 55 | if ($user->save()) { 56 | return $this->mail('passwordRequest', $this->email, [ 57 | 'subject' => Yii::t('app', 'Reset password information for {name} at {site}', [ 58 | 'name' => $user->name, 59 | 'site' => Yii::$app->name] 60 | ), 61 | 'user' => $user, 62 | ]); 63 | } 64 | } 65 | 66 | return false; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /app/forms/user/PasswordReset.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\forms\user; 9 | 10 | use Yii; 11 | use app\models\User; 12 | use yii\base\InvalidParamException; 13 | use yii\base\Model; 14 | 15 | /** 16 | * Password reset form 17 | */ 18 | class PasswordReset extends Model 19 | { 20 | 21 | public $password; 22 | public $password_repeat; 23 | 24 | /** 25 | * @var app\models\User 26 | */ 27 | private $_user; 28 | 29 | 30 | /** 31 | * Creates a form model given a token. 32 | * 33 | * @param string $token 34 | * @param array $config name-value pairs that will be used to initialize the object properties 35 | * @throws \yii\base\InvalidParamException if token is empty or not valid 36 | */ 37 | public function __construct($token, $config = []) 38 | { 39 | if (empty($token) || !is_string($token)) { 40 | throw new InvalidParamException('Password reset token cannot be blank.'); 41 | } 42 | $this->_user = User::findByResetToken($token); 43 | if (!$this->_user) { 44 | throw new InvalidParamException('Wrong password reset token.'); 45 | } 46 | parent::__construct($config); 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | public function rules() 53 | { 54 | return [ 55 | [['password', 'password_repeat'], 'required'], 56 | ['password', 'string', 'min' => 6], 57 | ['password', 'compare'], 58 | ]; 59 | } 60 | 61 | /** 62 | * @inheritdoc 63 | */ 64 | public function attributeLabels() 65 | { 66 | return [ 67 | 'password' => Yii::t('app', 'Password'), 68 | 'password_repeat' => Yii::t('app', 'Confirm password'), 69 | ]; 70 | } 71 | 72 | /** 73 | * Resets password. 74 | * 75 | * @return boolean if password was reset. 76 | */ 77 | public function resetPassword() 78 | { 79 | $user = $this->_user; 80 | $user->setPassword($this->password); 81 | $user->removeResetToken(); 82 | 83 | return $user->save(false); 84 | } 85 | 86 | /** 87 | * @return User 88 | */ 89 | public function getUser() 90 | { 91 | return $this->_user; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/helpers/Icon.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.1 6 | */ 7 | 8 | namespace app\helpers; 9 | 10 | /** 11 | * Icon 12 | * 13 | * @author skoro 14 | */ 15 | class Icon 16 | { 17 | 18 | /** 19 | * Well known Bootstrap icons. 20 | */ 21 | const OK = ''; 22 | const REMOVE = ''; 23 | const USER = ''; 24 | const STAR = ''; 25 | const PLUS = ''; 26 | const MINUS = ''; 27 | const TRASH = ''; 28 | const COG = ''; 29 | const EYE_OPEN = ''; 30 | const PAPERCLIP = ''; 31 | const TASKS = ''; 32 | const FILTER = ''; 33 | const DASHBOARD = ''; 34 | const SORT = ''; 35 | const EDIT = ''; 36 | const CHECK = ''; 37 | const UNCHECKED = ''; 38 | const STATS = ''; 39 | const FLASH = ''; 40 | const SEARCH = ''; 41 | const STAR_EMPTY = ''; 42 | const ENVELOPE = ''; 43 | 44 | /** 45 | * Returns icon html and optional (unencoded) text after icon. 46 | * @param string $ico icon class (for example, 'fa fa-circle'). 47 | * @param string $text optional text after icon. 48 | * @return string 49 | */ 50 | public static function icon($ico, $text = '') 51 | { 52 | return ' ' . $text; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/helpers/UserHelper.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace app\helpers; 9 | 10 | use app\models\User; 11 | use yii\helpers\Html; 12 | 13 | /** 14 | * Some view helpers for User model. 15 | * 16 | * @author skoro 17 | * @since 0.2 18 | */ 19 | class UserHelper 20 | { 21 | 22 | /** 23 | * Renders user status label. 24 | * @param User $user 25 | * @param array $options 26 | * @return string 27 | */ 28 | public static function status(User $user, array $options = []) 29 | { 30 | $defaults = ['class' => 'label']; 31 | switch ($user->status) { 32 | case User::STATUS_DISABLED: 33 | Html::addCssClass($defaults, 'label-danger'); 34 | break; 35 | case User::STATUS_PENDING: 36 | Html::addCssClass($defaults, 'label-warning'); 37 | break; 38 | default: 39 | Html::addCssClass($defaults, 'label-success'); 40 | 41 | } 42 | $options = array_merge($defaults, $options); 43 | return Html::tag('span', $user->getStatusLabel(), $options); 44 | } 45 | 46 | /** 47 | * Returns a link to user profile page suitable for use in Url::to(). 48 | * @param User $user 49 | * @param array $params optional, additional parameters to link. 50 | * @return array 51 | */ 52 | public static function getProfileUrl(User $user, array $params = []) 53 | { 54 | return array_merge(['/user/profile', 'id' => $user->id], $params); 55 | } 56 | 57 | /** 58 | * 59 | * @param User $user 60 | * @return string 61 | */ 62 | public static function userLink(User $user, array $options = [], array $linkParams = []) 63 | { 64 | return Html::a(Html::encode($user->name), static::getProfileUrl($user, $linkParams), $options); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/mail/accountCreated-html.php: -------------------------------------------------------------------------------- 1 | user->loginUrl, true); 12 | ?> 13 |
14 |

Hello name) ?>,

15 | 16 |

A site administrator at name ?> has created an account for you.

17 | 18 |

You will be able to log in at using:

19 | 23 |
24 | -------------------------------------------------------------------------------- /app/mail/accountCreated-text.php: -------------------------------------------------------------------------------- 1 | user->loginUrl, true); 11 | ?> 12 | Hello name ?>, 13 | A site administrator at name ?> has created an account for you. 14 | 15 | You will be able to log in at using: 16 | 17 | Email: email ?> 18 | 19 | Password: password ?> 20 | -------------------------------------------------------------------------------- /app/mail/layouts/html.php: -------------------------------------------------------------------------------- 1 | 8 | beginPage() ?> 9 | 10 | 11 | 12 | 13 | <?= Html::encode($this->title) ?> 14 | head() ?> 15 | 16 | 17 | beginBody() ?> 18 | 19 | endBody() ?> 20 | 21 | 22 | endPage() ?> 23 | -------------------------------------------------------------------------------- /app/mail/layouts/text.php: -------------------------------------------------------------------------------- 1 | 8 | beginPage() ?> 9 | beginBody() ?> 10 | 11 | endBody() ?> 12 | endPage() ?> 13 | -------------------------------------------------------------------------------- /app/mail/passwordRequest-html.php: -------------------------------------------------------------------------------- 1 | urlManager->createAbsoluteUrl(['user/password-reset', 'token' => $user->reset_token]); 10 | ?> 11 |
12 |

Hello name) ?>,

13 | 14 |

A request to reset the password for your account has been made at name, Url::home(true)) ?>

15 |

Follow the link below to reset your password:

16 | 17 |

18 |
19 | -------------------------------------------------------------------------------- /app/mail/passwordRequest-text.php: -------------------------------------------------------------------------------- 1 | urlManager->createAbsoluteUrl(['user/password-reset', 'token' => $user->reset_token]); 7 | ?> 8 | Hello name ?>, 9 | A request to reset the password for your account has been made at name ?>. 10 | 11 | Follow the link below to reset your password: 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/migrations/m160314_212231_user.php: -------------------------------------------------------------------------------- 1 | createTable(User::tableName(), [ 12 | 'id' => $this->primaryKey(), 13 | 'name' => $this->string(64)->notNull()->unique(), 14 | 'email' => $this->string(64)->unique(), 15 | 'password_hash' => $this->string()->notNull(), 16 | 'reset_token' => $this->string()->notNull()->defaultValue(''), 17 | 'activate_token' => $this->string()->notNull()->defaultValue(''), 18 | 'auth_key' => $this->string()->notNull()->unique(), 19 | 'status' => $this->smallInteger()->unsigned()->defaultValue(0), 20 | 'created_at' => $this->integer()->unsigned(), 21 | 'logged_at' => $this->integer()->unsigned(), 22 | ]); 23 | 24 | $this->createIndex('idx_user_reset_token', User::tableName(), 'reset_token'); 25 | $this->createIndex('idx_user_activate_token', User::tableName(), 'activate_token'); 26 | } 27 | 28 | public function down() 29 | { 30 | $this->dropTable(User::tableName()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/migrations/m160818_075724_config.php: -------------------------------------------------------------------------------- 1 | createTable($this->table, [ 13 | 'id' => $this->primaryKey(), 14 | 'name' => $this->string(255)->notNull(), 15 | 'value' => $this->binary(), 16 | 'value_type' => $this->char(8)->notNull(), 17 | 'options' => $this->binary(), 18 | 'title' => $this->string(255)->notNull(), 19 | 'desc' => $this->text(), 20 | 'section' => $this->string(32)->notNull()->defaultValue('global'), 21 | 'required' => $this->boolean()->notNull()->defaultValue(0), 22 | ]); 23 | 24 | $this->createIndex('idx_config_name', $this->table, ['name', 'section'], true); 25 | 26 | echo 'Insert default parameters...'.PHP_EOL; 27 | $this->insertParam([ 28 | 'name' => 'passwordResetTokenExpire', 29 | 'title' => 'Password reset token expire', 30 | 'value' => 3600, 31 | 'value_type' => 'integer', 32 | 'section' => 'User', 33 | 'desc' => 'How long (in seconds) password reset token will be actual.', 34 | 'required' => true, 35 | ]); 36 | 37 | $this->insertParam([ 38 | 'name' => 'disableUserRegister', 39 | 'title' => 'Disable user registration', 40 | 'value' => false, 41 | 'value_type' => 'switch', 42 | 'section' => 'User', 43 | ]); 44 | 45 | $this->insertParam([ 46 | 'name' => 'noAvatarImage', 47 | 'title' => 'Default user avatar image', 48 | 'value' => '@web/images/avatars/avatar2.png', 49 | 'value_type' => 'text', 50 | 'section' => 'User', 51 | 'desc' => 'Default user avatar picture.', 52 | 'required' => true, 53 | ]); 54 | 55 | $this->insertParam([ 56 | 'name' => 'adminEmail', 57 | 'title' => 'Site email', 58 | 'value' => 'admin@example.com', 59 | 'value_type' => 'email', 60 | 'section' => 'Site', 61 | 'desc' => 'Email address used for replies.', 62 | 'required' => true, 63 | ]); 64 | 65 | // This is 'select' param demo: 66 | // $this->insertParam([ 67 | // 'name' => 'defaultUserRole', 68 | // 'title' => 'Default user role', 69 | // 'value' => 'Registered', 70 | // 'options' => [ 71 | // 'Administrator' => 'Administrator', 72 | // 'Registered' => 'Registerd', 73 | // 'Editor' => 'Editor', 74 | // 'Subscriber' => 'Subscriber', 75 | // ], 76 | // 'value_type' => 'select', 77 | // 'section' => 'User', 78 | // 'desc' => 'Assign newly registered users to specified role.', 79 | // ]); 80 | } 81 | 82 | public function down() 83 | { 84 | $this->dropTable($this->table); 85 | } 86 | 87 | protected function insertParam($data) 88 | { 89 | $data['value'] = serialize($data['value']); 90 | if (isset($data['options'])) { 91 | $data['options'] = serialize($data['options']); 92 | } 93 | Yii::$app->db->createCommand() 94 | ->insert($this->table, $data) 95 | ->execute(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/migrations/m160830_073241_param_default_role.php: -------------------------------------------------------------------------------- 1 | authManager->getRoles(), 'name', 'name'); 13 | $columns = [ 14 | 'section' => 'User', 15 | 'name' => 'defaultRole', 16 | 'title' => 'Default user role', 17 | 'value' => serialize('Registered'), 18 | 'value_type' => 'select', 19 | 'options' => serialize($roles), 20 | 'desc' => 'Assign newly registered users to specified role.', 21 | ]; 22 | Yii::$app->db->createCommand() 23 | ->insert($this->table, $columns) 24 | ->execute(); 25 | } 26 | 27 | public function down() 28 | { 29 | Yii::$app->db->createCommand() 30 | ->delete($this->table, [ 31 | 'section' => 'User', 32 | 'name' => 'defaultRole' 33 | ]) 34 | ->execute(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/migrations/m160831_094735_module.php: -------------------------------------------------------------------------------- 1 | createTable($this->table, [ 13 | 'module_id' => $this->string(32)->notNull()->unique(), 14 | 'name' => $this->string(255)->notNull()->defaultValue(''), 15 | 'installed' => $this->boolean()->notNull()->defaultValue(false), 16 | 'desc' => $this->text(), 17 | 'data' => $this->text(), 18 | ]); 19 | $this->createIndex('idx_module_status', $this->table, ['installed']); 20 | } 21 | 22 | public function down() 23 | { 24 | $this->dropTable($this->table); 25 | echo "\n\nWARNING!\n"; 26 | echo "Module is essential part of application and application cannot work without module table.\n"; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/migrations/m161108_083707_config_perms.php: -------------------------------------------------------------------------------- 1 | addColumn(Config::tableName(), 'perms', $this->binary()->defaultValue( 14 | serialize([ 15 | 'updateSettings', 16 | ]) 17 | )); 18 | } 19 | 20 | public function down() 21 | { 22 | if ($this->isSqlite()) { 23 | echo '!!! SQLite does not support drop columns.'.PHP_EOL; 24 | return; 25 | } 26 | $this->dropColumn(Config::tableName(), 'perms'); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/migrations/m180130_201810_param_site_name.php: -------------------------------------------------------------------------------- 1 | name = 'siteName'; 13 | $config->title = 'Site name'; 14 | $config->value = 'Admin template'; 15 | $config->section = 'Site'; 16 | $config->value_type = Config::TYPE_TEXT; 17 | $config->required = true; 18 | $config->save(); 19 | } 20 | 21 | public function down() 22 | { 23 | Config::deleteAll(['name' => 'siteName']); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/views/layouts/left.php: -------------------------------------------------------------------------------- 1 | 4 | 45 | -------------------------------------------------------------------------------- /app/views/layouts/main-login.php: -------------------------------------------------------------------------------- 1 | 11 | beginPage() ?> 12 | 13 | 14 | 15 | 16 | 17 | 18 | <?= Html::encode($this->title) ?> 19 | head() ?> 20 | 21 | 22 | 23 | beginBody() ?> 24 | 25 | 26 | 27 | 28 | 29 | endBody() ?> 30 | 31 | 32 | endPage() ?> 33 | -------------------------------------------------------------------------------- /app/views/layouts/main.php: -------------------------------------------------------------------------------- 1 | assetManager->getPublishedUrl('@vendor/almasaeed2010/adminlte/dist'); 18 | Notify::widget(); 19 | ?> 20 | beginPage() ?> 21 | 22 | 23 | 24 | 25 | 26 | 27 | <?= Html::encode($this->title) ?> 28 | head() ?> 29 | 30 | 31 | beginBody() ?> 32 |
33 | 34 | render( 35 | 'header.php', 36 | ['directoryAsset' => $directoryAsset] 37 | ) ?> 38 | 39 | render( 40 | 'left.php', 41 | ['directoryAsset' => $directoryAsset] 42 | ) 43 | ?> 44 | 45 | render( 46 | 'content.php', 47 | ['content' => $content, 'directoryAsset' => $directoryAsset] 48 | ) ?> 49 | 50 |
51 | 52 | endBody() ?> 53 | 54 | 55 | endPage() ?> -------------------------------------------------------------------------------- /app/views/site/about.php: -------------------------------------------------------------------------------- 1 | title = 'About'; 8 | $this->params['breadcrumbs'][] = $this->title; 9 | ?> 10 |
11 |

title) ?>

12 | 13 |

14 | This is the About page. You may modify the following file to customize its content: 15 |

16 | 17 | 18 |
19 | -------------------------------------------------------------------------------- /app/views/site/contact.php: -------------------------------------------------------------------------------- 1 | title = 'Contact'; 12 | $this->params['breadcrumbs'][] = $this->title; 13 | ?> 14 |
15 |

title) ?>

16 | 17 | session->hasFlash('contactFormSubmitted')): ?> 18 | 19 |
20 | Thank you for contacting us. We will respond to you as soon as possible. 21 |
22 | 23 |

24 | Note that if you turn on the Yii debugger, you should be able 25 | to view the mail message on the mail panel of the debugger. 26 | mailer->useFileTransport): ?> 27 | Because the application is in development mode, the email is not sent but saved as 28 | a file under mailer->fileTransportPath) ?>. 29 | Please configure the useFileTransport property of the mail 30 | application component to be false to enable email sending. 31 | 32 |

33 | 34 | 35 | 36 |

37 | If you have business inquiries or other questions, please fill out the following form to contact us. 38 | Thank you. 39 |

40 | 41 |
42 |
43 | 44 | 'contact-form']); ?> 45 | 46 | field($model, 'name')->textInput(['autofocus' => true]) ?> 47 | 48 | field($model, 'email') ?> 49 | 50 | field($model, 'subject') ?> 51 | 52 | field($model, 'body')->textArea(['rows' => 6]) ?> 53 | 54 | field($model, 'verifyCode')->widget(Captcha::className(), [ 55 | 'template' => '
{image}
{input}
', 56 | ]) ?> 57 | 58 |
59 | 'btn btn-primary', 'name' => 'contact-button']) ?> 60 |
61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 |
69 | -------------------------------------------------------------------------------- /app/views/site/error.php: -------------------------------------------------------------------------------- 1 | title = $name; 11 | ?> 12 | 13 |
14 | 15 |
16 |

17 | 18 |
19 |

20 | 21 |

22 | 23 |

24 | 25 |

26 | The above error occurred while the Web server was processing your request. 27 | Please contact us if you think this is a server error. Thank you. 28 | Meanwhile, you may return to dashboard or try using the search 29 | form. 30 |

31 | 32 |
33 |
34 | 35 | 36 |
37 | 39 |
40 |
41 |
42 |
43 |
44 | 45 |
46 | -------------------------------------------------------------------------------- /app/views/templates/confirmation.php: -------------------------------------------------------------------------------- 1 | title = $title; 19 | ?> 20 | 21 |
22 |
23 |

title ?>

24 |

25 |

26 |
27 |
28 | 29 |
30 |
31 | 32 | $actionUrl, 34 | ]) ?> 35 | 36 | $value): ?> 37 | 44 | 45 | 46 | $button, 48 | 'cancel' => [ 49 | 'url' => $cancelUrl, 50 | ], 51 | ]) ?> 52 | -------------------------------------------------------------------------------- /app/views/templates/gii-module/controller.php: -------------------------------------------------------------------------------- 1 | 11 | 12 | namespace getControllerNamespace() ?>; 13 | 14 | use app\base\Controller; 15 | 16 | /** 17 | * Default controller for the `moduleID ?>` module 18 | */ 19 | class DefaultController extends Controller 20 | { 21 | /** 22 | * Renders the index view for the module 23 | * @return string 24 | */ 25 | public function actionIndex() 26 | { 27 | return $this->render('index'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/views/templates/gii-module/module.php: -------------------------------------------------------------------------------- 1 | moduleClass; 10 | $pos = strrpos($className, '\\'); 11 | $ns = ltrim(substr($className, 0, $pos), '\\'); 12 | $className = substr($className, $pos + 1); 13 | 14 | echo " 16 | 17 | namespace ; 18 | 19 | /** 20 | * moduleID ?> module definition class 21 | */ 22 | class extends \app\base\Module 23 | { 24 | /** 25 | * @var string required, module name. 26 | */ 27 | public $moduleName = 'moduleID ?>'; 28 | 29 | /** 30 | * @var string 31 | */ 32 | public $moduleDescription = ''; 33 | 34 | /** 35 | * @inheritdoc 36 | */ 37 | public $controllerNamespace = 'getControllerNamespace() ?>'; 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | public function init() 43 | { 44 | parent::init(); 45 | 46 | // custom initialization code goes here 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/views/templates/gii-module/view.php: -------------------------------------------------------------------------------- 1 | 5 |
6 |

$this->context->action->uniqueId ?>

7 |

8 | This is the view content for action "$this->context->action->id ?>". 9 | The action belongs to the controller "get_class($this->context) ?>" 10 | in the "$this->context->module->id ?>" module. 11 |

12 |

13 | You may customize this page by editing the following file:
14 | __FILE__ ?> 15 |

16 |
17 | -------------------------------------------------------------------------------- /app/views/templates/migration.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | use app\base\Migration; 12 | 13 | class extends Migration 14 | { 15 | 16 | public $table = '{{%table}}'; 17 | 18 | public function up() 19 | { 20 | 21 | } 22 | 23 | public function down() 24 | { 25 | echo " cannot be reverted.\n"; 26 | 27 | return false; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/views/user/_create_modal.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | true, 11 | 'enableAjaxValidation' => true, 12 | ]) ?> 13 | field($register, 'name') ?> 14 | field($register, 'email') ?> 15 | field($register, 'password')->passwordInput() ?> 16 | field($register, 'password_repeat')->passwordInput() ?> 17 | field($register, 'sendmail') 18 | ->widget(Check::className()) 19 | ->label(false) ?> 20 | [ 22 | 'options' => ['data-dismiss' => 'modal'], 23 | ], 24 | ]) ?> -------------------------------------------------------------------------------- /app/views/user/_profile_account.php: -------------------------------------------------------------------------------- 1 | 17 | 'user-profile-form', 19 | 'pjax' => true, 20 | 'layout' => 'horizontal', 21 | 'enableClientValidation' => true, 22 | 'fieldConfig' => [ 23 | 'horizontalCssClasses' => [ 24 | 'label' => 'col-sm-2', 25 | 'wrapper' => 'col-sm-10', 26 | ], 27 | ], 28 | ]) ?> 29 | 30 | field($model, 'email')->widget(InputGroup::className(), [ 31 | 'inputOptions' => [ 32 | 'class' => 'form-control', 33 | 'disabled' => 'disabled', 34 | ], 35 | 'button' => true, 36 | 'addon' => Button::widget([ 37 | 'label' => Icon::icon('glyphicon glyphicon-copy'), 38 | 'encodeLabel' => false, 39 | 'options' => ['class' => 'btn btn-default btn-flat'], 40 | 'clientEvents' => [ 41 | 'click' => new JsExpression("function (ev) { 42 | ev.preventDefault(); 43 | $('#{$emailId}').removeAttr('disabled').select(); 44 | document.execCommand('copy'); 45 | $('#{$emailId}').attr('disabled', 'disabled'); 46 | }"), 47 | ], 48 | ]), 49 | ]) ?> 50 | field($model, 'name') ?> 51 | field($model, 'password')->passwordInput() ?> 52 | field($model, 'password_repeat')->passwordInput() ?> 53 | 54 | false, 56 | ]) ?> 57 | -------------------------------------------------------------------------------- /app/views/user/_profile_admin.php: -------------------------------------------------------------------------------- 1 | 11 | 'user-profile-admin-form', 13 | 'layout' => 'horizontal', 14 | 'fieldConfig' => [ 15 | 'horizontalCssClasses' => [ 16 | 'label' => 'col-sm-2', 17 | 'wrapper' => 'col-sm-10', 18 | ], 19 | ], 20 | 'action' => ['profile', 'id' => $model->getUser()->id, 'tab' => 'admin'], 21 | ]) ?> 22 | 23 | field($model, 'roles')->widget(Check::className(), [ 24 | 'items' => ArrayHelper::map(Yii::$app->authManager->getRoles(), 'name', 'name'), 25 | 'options' => ['class' => 'checkbox-list-vert'], 26 | ]) ?> 27 | 28 | field($model, 'status')->widget(Check::className(), [ 29 | 'type' => Check::TYPE_RADIO, 30 | 'items' => $model->getUser()->getStatusLabels(), 31 | 'options' => ['class' => 'checkbox-list-vert'], 32 | ]) ?> 33 | 34 | false, 36 | ]) ?> 37 | -------------------------------------------------------------------------------- /app/views/user/index.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Users'); 17 | $this->params['breadcrumbs'][] = $this->title; 18 | ?> 19 | 20 | 23 | true, 25 | ]) ?> 26 |
27 | user->can('createUser')): ?> 28 | '' . Yii::t('app', 'Create a new user') . '', 30 | 'toggleButton' => [ 31 | 'label' => Yii::t('app', 'Create'), 32 | 'class' => ['btn btn-flat btn-default'], 33 | ], 34 | ]) ?> 35 | render('_create_modal', ['register' => $register]) ?> 36 | 37 | 38 |
39 | $userProvider, 41 | 'bulk' => [ 42 | 'items' => [ 43 | 'enable' => Yii::t('app', 'Enable'), 44 | 'disable' => Yii::t('app', 'Disable'), 45 | 'delete' => Yii::t('app', 'Delete'), 46 | ], 47 | 'pjax' => true, 48 | 'visible' => Yii::$app->user->can('updateAnyUser') || Yii::$app->user->can('deleteAnyUser'), 49 | ], 50 | 'columns' => [ 51 | 'id', 52 | [ 53 | 'attribute' => 'name', 54 | 'format' => 'raw', 55 | 'value' => function ($model) { 56 | return UserHelper::userLink($model, ['data-pjax' => 0]); 57 | }, 58 | ], 59 | 'email', 60 | [ 61 | 'header' => Yii::t('app', 'Roles'), 62 | 'format' => 'html', 63 | 'value' => function ($user) { 64 | return Html::ul(ArrayHelper::getColumn($user->getRoles(), 'name')); 65 | }, 66 | ], 67 | [ 68 | 'attribute' => 'status', 69 | 'format' => 'html', 70 | 'value' => function ($model) { 71 | return UserHelper::status($model); 72 | }, 73 | ], 74 | 'created_at:relativeTime', 75 | 'logged_at:relativeTime', 76 | [ 77 | 'class' => DeleteColumn::className(), 78 | 'visible' => Yii::$app->user->can('deleteAnyUser'), 79 | ], 80 | ], 81 | ]) ?> 82 | 83 | 84 | -------------------------------------------------------------------------------- /app/views/user/login.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Sign In'); 15 | 16 | $fieldOptions = function ($icon) { 17 | return [ 18 | 'options' => ['class' => 'form-group has-feedback'], 19 | 'inputTemplate' => "{input}" 20 | ]; 21 | }; 22 | ?> 23 | 24 |
25 | 28 | 29 |
30 | 31 | 32 | 'login-form', 'enableClientValidation' => false]); ?> 33 | 34 | field($model, 'email', $fieldOptions('envelope')) 36 | ->label(false) 37 | ->textInput(['placeholder' => $model->getAttributeLabel('email')]) ?> 38 | 39 | field($model, 'password', $fieldOptions('lock')) 41 | ->label(false) 42 | ->passwordInput(['placeholder' => $model->getAttributeLabel('password')]) ?> 43 | 44 |
45 |
46 | field($model, 'rememberMe')->widget(Check::className())->label(false) ?> 47 |
48 | 49 |
50 | 'btn btn-primary btn-block btn-flat', 'name' => 'login-button']) ?> 51 |
52 | 53 |
54 | 55 | 56 | 57 | 64 | 65 | 66 | 72 | 73 |
74 | 75 |
76 | -------------------------------------------------------------------------------- /app/views/user/passwordRequest.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Request password'); 13 | $fieldOptions = function ($icon) { 14 | return [ 15 | 'options' => ['class' => 'form-group has-feedback'], 16 | 'inputTemplate' => "{input}" 17 | ]; 18 | }; 19 | ?> 20 |
21 | 24 | 25 |
26 | 27 | 28 | 'login-form', 'enableClientValidation' => false]); ?> 29 | 30 | field($model, 'email', $fieldOptions('envelope')) 32 | ->label(false) 33 | ->textInput(['placeholder' => $model->getAttributeLabel('email')]) ?> 34 | 35 |
36 |
37 | 'btn btn-primary btn-block btn-flat', 'name' => 'submit-button']) ?> 38 |
39 | 40 |
41 | 42 | 43 | 44 |
45 | 46 |
47 | -------------------------------------------------------------------------------- /app/views/user/passwordReset.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Reset password'); 13 | $fieldOptions = function ($icon) { 14 | return [ 15 | 'options' => ['class' => 'form-group has-feedback'], 16 | 'inputTemplate' => "{input}" 17 | ]; 18 | }; 19 | ?> 20 |
21 | 24 | 25 |
26 |

getUser()->name) ?>

27 | 28 | 29 | 'reset-password-form', 'enableClientValidation' => false]); ?> 30 | 31 | field($model, 'password', $fieldOptions('lock')) 33 | ->label(false) 34 | ->passwordInput(['placeholder' => $model->getAttributeLabel('password')]) ?> 35 | 36 | field($model, 'password_repeat', $fieldOptions('lock')) 38 | ->label(false) 39 | ->passwordInput(['placeholder' => $model->getAttributeLabel('password_repeat')]) ?> 40 | 41 |
42 |
43 | 'btn btn-primary btn-block btn-flat', 'name' => 'submit-button']) ?> 44 |
45 | 46 |
47 | 48 | 49 | 50 |
51 | 52 |
53 | -------------------------------------------------------------------------------- /app/views/user/profile.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'User Profile'); 15 | if (Yii::$app->user->can('viewAnyUser')) { 16 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Users'), 'url' => ['index']]; 17 | } 18 | $this->params['breadcrumbs'][] = $this->title; 19 | ?> 20 | 21 |
22 | 23 |
24 | Box::BOX_PRIMARY, 26 | 'bodyOptions' => ['class' => 'box-profile'], 27 | ]) ?> 28 | 'profile-user-img img-responsive img-circle']) ?> 29 |

30 | name) ?> 31 |

32 |

33 | Yii::$app->formatter->asDate(Yii::$app->user->identity->created_at)]) ?> 34 |

35 | [ 37 | [ 38 | 'title' => 'ID', 39 | 'value' => $model->getUser()->id, 40 | ], 41 | [ 42 | 'title' => Yii::t('app', 'Status'), 43 | 'value' => UserHelper::status($model->getUser()), 44 | ], 45 | [ 46 | 'title' => Yii::t('app', 'Last login'), 47 | 'value' => Yii::$app->formatter->asRelativeTime($model->getUser()->logged_at), 48 | ], 49 | ], 50 | ]) ?> 51 | 52 |
53 | 54 |
55 | 72 |
73 | 74 |
75 | 76 | -------------------------------------------------------------------------------- /app/views/user/register.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'User register'); 13 | $fieldOptions = function ($icon) { 14 | return [ 15 | 'options' => ['class' => 'form-group has-feedback'], 16 | 'inputTemplate' => "{input}" 17 | ]; 18 | }; 19 | ?> 20 | 21 |
22 | 25 | 26 |
27 |

28 | 29 | 'register-form', 'enableClientValidation' => false]); ?> 30 | 31 | field($model, 'name', $fieldOptions('user')) 33 | ->label(false) 34 | ->textInput(['placeholder' => $model->getAttributeLabel('name')]) ?> 35 | 36 | field($model, 'email', $fieldOptions('envelope')) 38 | ->label(false) 39 | ->textInput(['placeholder' => $model->getAttributeLabel('email')]) ?> 40 | 41 | field($model, 'password', $fieldOptions('lock')) 43 | ->label(false) 44 | ->passwordInput(['placeholder' => $model->getAttributeLabel('password')]) ?> 45 | 46 | field($model, 'password_repeat', $fieldOptions('lock')) 48 | ->label(false) 49 | ->passwordInput(['placeholder' => $model->getAttributeLabel('password_repeat')]) ?> 50 | 51 |
52 |
53 | 'btn btn-primary btn-block btn-flat', 'name' => 'register-button']) ?> 54 |
55 | 56 |
57 | 58 | 59 | 60 | 63 | 64 |
65 | 66 | 67 |
-------------------------------------------------------------------------------- /app/widgets/ActiveForm.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\widgets; 9 | 10 | use Yii; 11 | use yii\helpers\Html; 12 | use yii\helpers\ArrayHelper; 13 | 14 | /** 15 | * ActiveForm 16 | * 17 | * @author skoro 18 | */ 19 | class ActiveForm extends \yii\bootstrap\ActiveForm 20 | { 21 | 22 | /** 23 | * @var boolean on/off form's data-pjax attribute. 24 | */ 25 | public $pjax = false; 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function init() 31 | { 32 | if ($this->pjax) { 33 | $this->options['data-pjax'] = 1; 34 | } 35 | parent::init(); 36 | } 37 | 38 | /** 39 | * End form with action buttons: submit and cancel. 40 | * 41 | * @todo allow to add more than two internal buttons. 42 | * @param array $options button options. 43 | */ 44 | public static function endWithActions(array $options = []) 45 | { 46 | $defaults = [ 47 | 'options' => ['class' => 'actions'], 48 | 'save' => [ 49 | 'label' => Yii::t('app', 'Save'), 50 | 'options' => ['class' => 'btn btn-primary btn-flat'], 51 | ], 52 | 'cancel' => [ 53 | 'label' => Yii::t('app', 'Cancel'), 54 | 'options' => ['class' => 'btn btn-warning btn-flat pull-right'], 55 | ], 56 | ]; 57 | $options = ArrayHelper::merge($defaults, $options); 58 | $buttons = ''; 59 | if (!empty($options['save'])) { 60 | $buttons .= Html::submitButton( 61 | ArrayHelper::getValue($options, 'save.label'), 62 | ArrayHelper::getValue($options, 'save.options', []) 63 | ); 64 | } 65 | if (!empty($options['cancel'])) { 66 | if (!empty($options['cancel']['url'])) { 67 | $buttons .= Html::a( 68 | ArrayHelper::getValue($options, 'cancel.label'), 69 | $options['cancel']['url'], 70 | ArrayHelper::getValue($options, 'cancel.options', []) 71 | ); 72 | } else { 73 | $buttons .= Html::button( 74 | ArrayHelper::getValue($options, 'cancel.label'), 75 | ArrayHelper::getValue($options, 'cancel.options', []) 76 | ); 77 | } 78 | } 79 | echo Html::tag('div', $buttons, $options['options']); 80 | static::end(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /app/widgets/Check.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\widgets; 9 | 10 | use app\assets\CheckAsset; 11 | use yii\base\InvalidValueException; 12 | use yii\bootstrap\InputWidget; 13 | use yii\helpers\Html; 14 | 15 | /** 16 | * iCheck plugin 17 | * 18 | * @link https://github.com/fronteed/icheck 19 | * @author skoro 20 | */ 21 | class Check extends InputWidget 22 | { 23 | 24 | /** 25 | * Control type. 26 | */ 27 | const TYPE_CHECKBOX = 'checkbox'; 28 | const TYPE_RADIO = 'radio'; 29 | 30 | /** 31 | * Predefined styles. 32 | */ 33 | const STYLE_MINIMAL = 'minimal'; 34 | const STYLE_FLAT = 'flat'; 35 | const STYLE_LINE = 'line'; 36 | const STYLE_SQUARE = 'square'; 37 | 38 | /** 39 | * Style colors. 40 | */ 41 | const COLOR_RED = 'red'; 42 | const COLOR_GREEN = 'green'; 43 | const COLOR_BLUE = 'blue'; 44 | const COLOR_AERO = 'aero'; 45 | const COLOR_GREY = 'grey'; 46 | const COLOR_ORANGE = 'orange'; 47 | const COLOR_YELLOW = 'yellow'; 48 | const COLOR_PINK = 'pink'; 49 | const COLOR_PURPLE = 'purple'; 50 | 51 | /** 52 | * @var string control type. 53 | */ 54 | public $type = self::TYPE_CHECKBOX; 55 | 56 | /** 57 | * @var string control style. 58 | */ 59 | public $style = self::STYLE_FLAT; 60 | 61 | /** 62 | * @var string style color. 63 | */ 64 | public $color = self::COLOR_GREEN; 65 | 66 | /** 67 | * @var string 68 | */ 69 | public $label = ''; 70 | 71 | /** 72 | * @var array checkbox list 73 | */ 74 | public $items = []; 75 | 76 | /** 77 | * @inheritdoc 78 | */ 79 | public function run() 80 | { 81 | $this->registerPlugin('iCheck'); 82 | 83 | if ($this->label) { 84 | $this->options['label'] = $this->label; 85 | } 86 | 87 | if ($this->items) { 88 | if (!($this->items = array_filter($this->items))) { 89 | throw new InvalidValueException('Empty items list.'); 90 | } 91 | } 92 | 93 | if ($this->hasModel()) { 94 | if ($this->items) { 95 | $method = $this->type === static::TYPE_CHECKBOX ? 'activeCheckboxList' : 'activeRadioList'; 96 | $input = Html::$method($this->model, $this->attribute, $this->items, $this->options); 97 | } else { 98 | $input = Html::activeCheckbox($this->model, $this->attribute, $this->options); 99 | } 100 | } elseif ($this->items) { 101 | $method = $this->type === static::TYPE_CHECKBOX ? 'checkboxList' : 'radioList'; 102 | $input = Html::$method($this->name, (bool) $this->value, $this->items, $this->options); 103 | } else { 104 | $input = Html::checkbox($this->name, (bool) $this->value, $this->options); 105 | } 106 | 107 | return $input; 108 | } 109 | 110 | /** 111 | * @inheritdoc 112 | */ 113 | protected function registerPlugin($name) 114 | { 115 | $asset = CheckAsset::register($this->getView()); 116 | $asset->style = $this->style; 117 | 118 | $class = $this->type === static::TYPE_CHECKBOX ? 'checkboxClass' : 'radioClass'; 119 | $this->clientOptions[$class] = static::createStyleName($this->type, $this->style, $this->color); 120 | 121 | parent::registerPlugin($name); 122 | } 123 | 124 | /** 125 | * Create compound checkbox style name. 126 | * @param string $type 127 | * @param string $style 128 | * @param string $color 129 | * @return string 130 | */ 131 | public static function createStyleName($type, $style, $color) 132 | { 133 | return 'i' . $type . '_' . $style . '-' . $color; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/widgets/InputClear.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | * @since 0.1 7 | */ 8 | 9 | namespace app\widgets; 10 | 11 | use app\helpers\Icon; 12 | use yii\bootstrap\Button; 13 | use yii\web\JsExpression; 14 | 15 | /** 16 | * InputClear 17 | * 18 | * Adds to input button by pressing which input is cleared. 19 | * 20 | * @author skoro 21 | */ 22 | class InputClear extends InputGroup 23 | { 24 | 25 | /** 26 | * @var string 27 | */ 28 | public $icon = 'glyphicon glyphicon-erase'; 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | public $button = true; 34 | 35 | /** 36 | * @var array 37 | */ 38 | public $buttonOptions = ['class' => 'btn btn-default btn-flat']; 39 | 40 | /** 41 | * @inheritdoc 42 | */ 43 | public function run() 44 | { 45 | $this->addon = Button::widget([ 46 | 'label' => Icon::icon($this->icon), 47 | 'options' => $this->buttonOptions, 48 | 'encodeLabel' => false, 49 | 'clientEvents' => [ 50 | 'click' => new JsExpression(" 51 | function (ev) { 52 | ev.preventDefault(); 53 | jQuery('#{$this->inputOptions['id']}').val(''); 54 | } 55 | "), 56 | ], 57 | ]); 58 | return parent::run(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/widgets/InputGroup.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | * @since 0.1 7 | */ 8 | 9 | namespace app\widgets; 10 | 11 | use yii\bootstrap\InputWidget; 12 | use yii\helpers\Html; 13 | 14 | /** 15 | * InputGroup 16 | * 17 | * For example, put button labeled Go! to end of text input: 18 | * ```php 19 | * echo InputGroup::widget([ 20 | * 'model' => $model, 21 | * 'attribute' => 'name', 22 | * 'addon' => Html::a('Go!', ['go'], ['class' => 'btn btn-default']), 23 | * 'button' => true, 24 | * ]); 25 | * ``` 26 | * 27 | * @link http://getbootstrap.com/components/#input-groups 28 | * @author skoro 29 | */ 30 | class InputGroup extends InputWidget 31 | { 32 | 33 | const SIZE_LARGE = 'input-group-lg'; 34 | const SIZE_SMALL = 'input-group-sm'; 35 | 36 | /** 37 | * @var string input group size. 38 | */ 39 | public $size = ''; 40 | 41 | /** 42 | * @var boolean put addon on left or right. 43 | */ 44 | public $left = false; 45 | 46 | /** 47 | * @var string addon content. 48 | */ 49 | public $addon = ''; 50 | 51 | /** 52 | * @var boolean treat addon as a button. 53 | */ 54 | public $button = false; 55 | 56 | /** 57 | * @var array 58 | */ 59 | public $addonOptions = []; 60 | 61 | /** 62 | * @var array 63 | */ 64 | public $inputOptions = ['class' => 'form-control']; 65 | 66 | /** 67 | * @inheritdoc 68 | */ 69 | public function init() 70 | { 71 | parent::init(); 72 | $this->inputOptions['id'] = $this->options['id']; 73 | $this->options['id'] = $this->getId(); 74 | } 75 | 76 | /** 77 | * @inheritdoc 78 | */ 79 | public function run() 80 | { 81 | if ($this->hasModel()) { 82 | $input = Html::activeTextInput($this->model, $this->attribute, $this->inputOptions); 83 | } else { 84 | $input = Html::textInput($this->name, $this->value, $this->inputOptions); 85 | } 86 | 87 | $addon = $this->renderAddon(); 88 | 89 | Html::addCssClass($this->options, 'input-group'); 90 | Html::addCssClass($this->options, $this->size); 91 | 92 | $content = $this->left ? $addon . $input : $input . $addon; 93 | 94 | return Html::tag('div', $content, $this->options); 95 | } 96 | 97 | /** 98 | * Renders addon container. 99 | * @return string 100 | */ 101 | protected function renderAddon() 102 | { 103 | if ($this->button) { 104 | $tag = 'div'; 105 | Html::addCssClass($this->addonOptions, 'input-group-btn'); 106 | } 107 | else { 108 | $tag = 'span'; 109 | Html::addCssClass($this->addonOptions, 'input-group-addon'); 110 | } 111 | return Html::tag($tag, $this->addon, $this->addonOptions); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/widgets/ItemList.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\widgets; 9 | 10 | use yii\bootstrap\Widget; 11 | use yii\helpers\Html; 12 | use yii\helpers\ArrayHelper; 13 | 14 | /** 15 | * AdminLTE items list widget. 16 | * 17 | * @author skoro 18 | */ 19 | class ItemList extends Widget 20 | { 21 | 22 | /** 23 | * @var array 24 | */ 25 | public $items = []; 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function run() 31 | { 32 | $items = array_map(function ($item) { 33 | $options = ArrayHelper::getValue($item, 'options', []); 34 | Html::addCssClass($options, 'list-group-item'); 35 | $valueOptions = ArrayHelper::getValue($item, 'valueOptions', []); 36 | Html::addCssClass($valueOptions, 'pull-right'); 37 | $content = $item['title'] . Html::tag('span', $item['value'], $valueOptions); 38 | return Html::tag('li', $content, $options); 39 | }, $this->items); 40 | 41 | $options = $this->options; 42 | Html::addCssClass($options, 'list-group list-group-unbordered'); 43 | 44 | return Html::tag('ul', implode("\n", $items), $options); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/widgets/Modal.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\widgets; 9 | 10 | use yii\helpers\Url; 11 | 12 | /** 13 | * Modal widget. 14 | * 15 | * ~~~php 16 | * Modal::begin([ 17 | * 'header' => '

Hello world

', 18 | * 'toggleButton' => ['label' => 'click me'], 19 | * 'remote' => ['demo'], 20 | * ]); 21 | * 22 | * echo 'Say hello...'; 23 | * 24 | * Modal::end(); 25 | * 26 | * // In controller: 27 | * public function actionDemo() { 28 | * return 'Put content to modal.'; 29 | * } 30 | * ~~~ 31 | * 32 | * @author skoro 33 | */ 34 | class Modal extends \yii\bootstrap\Modal 35 | { 36 | 37 | /** 38 | * @var array|string fetch content from remote source. 39 | */ 40 | public $remote; 41 | 42 | /** 43 | * @inheritdoc 44 | */ 45 | public function run() 46 | { 47 | parent::run(); 48 | if ($this->remote) { 49 | $id = $this->options['id']; 50 | $url = Url::to($this->remote); 51 | $this->getView()->registerJs("Admin.Modal.remote('#$id', '$url');"); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/widgets/Notify.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\widgets; 9 | 10 | use app\assets\AnimateAsset; 11 | use app\assets\BootstrapNotifyAsset; 12 | use Yii; 13 | use yii\base\Widget; 14 | use yii\helpers\Json; 15 | 16 | /** 17 | * Bootstrap notify widget. 18 | * 19 | * @link http://bootstrap-notify.remabledesigns.com 20 | * @author skoro 21 | */ 22 | class Notify extends Widget 23 | { 24 | 25 | /** 26 | * @var array 27 | */ 28 | public $animate = [ 29 | 'enter' => 'animated fadeInRight', 30 | 'exit' => 'animated fadeOutRight', 31 | ]; 32 | 33 | /** 34 | * @inheritdoc 35 | */ 36 | public function run() 37 | { 38 | $session = Yii::$app->getSession(); 39 | $flashes = $session->getAllFlashes(true); 40 | 41 | if (empty($flashes)) { 42 | return; 43 | } 44 | 45 | $view = $this->getView(); 46 | BootstrapNotifyAsset::register($view); 47 | if ($this->animate) { 48 | // FIXME: in pjax responses animate.css does not included in body. 49 | AnimateAsset::register($view); 50 | } 51 | 52 | foreach ($flashes as $type => $messages) { 53 | $settings = []; 54 | switch ($type) { 55 | case 'success': case 'info': 56 | case 'warning': 57 | case 'danger': case 'error': 58 | $settings['type'] = $type; 59 | break; 60 | } 61 | 62 | if ($this->animate) { 63 | $settings['animate'] = $this->animate; 64 | } 65 | 66 | foreach ($messages as $message) { 67 | $settings = Json::encode($settings); 68 | $view->registerJs("\$.notify('$message', $settings);"); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/widgets/Pjax.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\widgets; 9 | 10 | /** 11 | * Pjax 12 | * 13 | * Modified Pjax widget with support of session notifications. 14 | * @author skoro 15 | */ 16 | class Pjax extends \yii\widgets\Pjax 17 | { 18 | 19 | /** 20 | * @var array 21 | */ 22 | public $notifyOptions = []; 23 | 24 | /** 25 | * @var boolean enable hacks when pjax container in modal. 26 | */ 27 | public $modal = false; 28 | 29 | /** 30 | * @inheritdoc 31 | */ 32 | public function run() 33 | { 34 | if ($this->requiresPjax()) { 35 | echo Notify::widget($this->notifyOptions); 36 | } 37 | $id = $this->options['id']; 38 | if ($this->modal) { 39 | $this->getView()->registerJs("Admin.Modal.pjax('#$id');"); 40 | } 41 | parent::run(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/widgets/ProgressBar.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\widgets; 9 | 10 | use yii\base\Widget; 11 | use yii\helpers\Html; 12 | 13 | /** 14 | * ProgressBar 15 | * 16 | * ```php 17 | * echo ProgressBar::widget([ 18 | * 'total' => 1200, 19 | * 'value' => 250, 20 | * ]); 21 | * ``` 22 | * 23 | * @author skoro 24 | */ 25 | class ProgressBar extends Widget 26 | { 27 | 28 | /** 29 | * Progress bar styles. 30 | */ 31 | const STYLE_DANGER = 'danger'; 32 | const STYLE_INFO = 'info'; 33 | const STYLE_PRIMARY = 'primary'; 34 | const STYLE_SUCCESS = 'success'; 35 | const STYLE_WARNING = 'warning'; 36 | 37 | /** 38 | * Progress sizes. 39 | */ 40 | const SIZE_XS = 'progress-xs'; 41 | const SIZE_XXS = 'progress-xxs'; 42 | const SIZE_SM = 'progress-sm'; 43 | 44 | /** 45 | * @var integer max progress 46 | */ 47 | public $total = 100; 48 | 49 | /** 50 | * @var integer current value of progress. 51 | */ 52 | public $value; 53 | 54 | /** 55 | * @var string progress bar style. 56 | */ 57 | public $style = self::STYLE_PRIMARY; 58 | 59 | /** 60 | * @var array 61 | */ 62 | public $options = []; 63 | 64 | /** 65 | * @var string progress bar height. 66 | */ 67 | public $size = ''; 68 | 69 | /** 70 | * @var boolean render a vertical progress bar. 71 | */ 72 | public $vertical = false; 73 | 74 | /** 75 | * @inheritdoc 76 | */ 77 | public function run() 78 | { 79 | $options = $this->options; 80 | Html::addCssClass($options, 'progress'); 81 | 82 | if ($this->size) { 83 | Html::addCssClass($options, $this->size); 84 | } 85 | 86 | if ($this->vertical) { 87 | Html::addCssClass($options, 'vertical'); 88 | } 89 | 90 | $bar = $this->renderBar(); 91 | 92 | return Html::tag('div', $bar, $options); 93 | } 94 | 95 | /** 96 | * Renders and calculate percent value. 97 | * @return string 98 | */ 99 | protected function renderBar() 100 | { 101 | $options = [ 102 | 'class' => 'progress-bar', 103 | 'role' => 'progressbar', 104 | 'aria-valuenow' => $this->value, 105 | 'aria-valuemin' => 0, 106 | 'aria-valuemax' => $this->total, 107 | ]; 108 | if ($this->style) { 109 | Html::addCssClass($options, 'progress-bar-' . $this->style); 110 | } 111 | 112 | if ($this->value === 0 || $this->value < 0) { 113 | $value = 0; 114 | } 115 | elseif ($this->value > 0 && $this->total > 0) { 116 | $value = (int)(($this->value * 100) / $this->total); 117 | } 118 | else { 119 | $value = $this->total == 0 ? 100 : 120 | ($this->total > 100 ? 100 : $this->total); 121 | } 122 | 123 | $percent = $value . '%'; 124 | if ($this->vertical) { 125 | Html::addCssStyle($options, ['height' => $percent]); 126 | } else { 127 | Html::addCssStyle($options, ['width' => $percent]); 128 | } 129 | 130 | $content = Html::tag('span', $percent, ['class' => 'sr-only']); 131 | return Html::tag('div', $content, $options); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/widgets/ProgressBarGroup.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\widgets; 9 | 10 | use yii\bootstrap\Widget; 11 | use yii\helpers\Html; 12 | 13 | /** 14 | * Extension to ProgressBar widget which add label and number. 15 | * 16 | * @author skoro 17 | */ 18 | class ProgressBarGroup extends Widget 19 | { 20 | /** 21 | * @var string progress bar label. 22 | */ 23 | public $label = ''; 24 | 25 | /** 26 | * @var boolean 27 | */ 28 | public $encodeLabel = true; 29 | 30 | /** 31 | * @var array 32 | */ 33 | public $options = []; 34 | 35 | /** 36 | * @var array 37 | */ 38 | public $labelOptions = []; 39 | 40 | /** 41 | * @var array 42 | */ 43 | public $numberOptions = []; 44 | 45 | /** 46 | * @var integer current value of progress. 47 | */ 48 | public $value; 49 | 50 | /** 51 | * @var integer max progress 52 | */ 53 | public $total = 100; 54 | 55 | /** 56 | * @var array progress bar options. 57 | */ 58 | public $progress = []; 59 | 60 | /** 61 | * @inheritdoc 62 | */ 63 | public function run() 64 | { 65 | $options = $this->options; 66 | Html::addCssClass($options, 'progress-group'); 67 | 68 | $label = $this->renderLabel(); 69 | $number = $this->renderNumber(); 70 | 71 | $this->progress['value'] = $this->value; 72 | $this->progress['total'] = $this->total; 73 | $progress = ProgressBar::widget($this->progress); 74 | 75 | return Html::tag('div', $label . "\n" . $number . "\n" . $progress, $options); 76 | } 77 | 78 | /** 79 | * Renders progress bar label. 80 | * @return string 81 | */ 82 | protected function renderLabel() 83 | { 84 | $options = $this->labelOptions; 85 | Html::addCssClass($options, 'progress-text'); 86 | $label = $this->encodeLabel ? Html::encode($this->label) : $this->label; 87 | return Html::tag('span', $label, $options); 88 | } 89 | 90 | /** 91 | * Renders progress number. 92 | * @return string 93 | */ 94 | protected function renderNumber() 95 | { 96 | $options = $this->numberOptions; 97 | Html::addCssClass($options, 'progress-number'); 98 | $number = '' . $this->value . '/' . $this->total; 99 | return Html::tag('span', $number, $options); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/widgets/TimePicker.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\widgets; 9 | 10 | use yii\helpers\Html; 11 | use yii\bootstrap\InputWidget; 12 | use app\assets\TimePickerAsset; 13 | 14 | /** 15 | * AdminLTE TimePicker widget. 16 | * 17 | * @author skoro 18 | */ 19 | class TimePicker extends InputWidget 20 | { 21 | 22 | const MODE_24H = '24h'; 23 | const MODE_12H = '12h'; 24 | 25 | /** 26 | * @var string 27 | */ 28 | public $mode = self::MODE_24H; 29 | 30 | /** 31 | * @var array 32 | */ 33 | public $containerOptions = []; 34 | 35 | /** 36 | * @inheritdoc 37 | */ 38 | public function run() 39 | { 40 | $this->registerScript(); 41 | 42 | $options = $this->options; 43 | Html::addCssClass($options, 'form-control'); 44 | 45 | if ($this->hasModel()) { 46 | $input = Html::activeTextInput($this->model, $this->attribute, $options); 47 | } else { 48 | $input = Html::textInput($this->name, $this->value, $options); 49 | } 50 | 51 | $input .= ''; 52 | 53 | $containerOptions = $this->containerOptions; 54 | $containerOptions['id'] = $this->getId(); 55 | Html::addCssClass($containerOptions, 'input-group bootstrap-timepicker timepicker'); 56 | 57 | return Html::tag('div', $input, $containerOptions); 58 | } 59 | 60 | /** 61 | * Include assets and enable plugin. 62 | */ 63 | protected function registerScript() 64 | { 65 | if ($this->mode === static::MODE_24H) { 66 | $this->clientOptions['showMeridian'] = false; 67 | } 68 | 69 | if ($this->hasModel() || $this->value) { 70 | $this->clientOptions['defaultTime'] = false; 71 | } 72 | 73 | TimePickerAsset::register($this->getView()); 74 | 75 | $this->registerPlugin('timepicker'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/widgets/TypeAhead.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace app\widgets; 9 | 10 | use yii\bootstrap\InputWidget; 11 | use yii\gii\TypeAheadAsset; 12 | use yii\helpers\Html; 13 | use yii\helpers\Json; 14 | use yii\helpers\Url; 15 | use yii\web\JsExpression; 16 | use yii\web\View; 17 | 18 | /** 19 | * Bootstrap TypeAhead widget. 20 | * 21 | * In view: 22 | * ```php 23 | * echo TypeAhead::widget([ 24 | * 'model' => $model, 25 | * 'attribute' => $name, 26 | * 'remote' => ['autocomplete', 'add' => 'that is additional parameter passed to action'], 27 | * ]); 28 | * ``` 29 | * 30 | * In controller: 31 | * ```php 32 | * public function actionAutocomplete($add) 33 | * { 34 | * Yii::$app->response->format = Response::FORMAT_JSON; 35 | * return [ 36 | * ['value' => 'Apple'], 37 | * ['value' => 'Orange'], 38 | * ['value' => 'Cherry'], 39 | * ]; 40 | * } 41 | * ``` 42 | * 43 | * @author skoro 44 | */ 45 | class TypeAhead extends InputWidget 46 | { 47 | 48 | /** 49 | * @var string|array fetch item from the remote source. 50 | */ 51 | public $remote; 52 | 53 | /** 54 | * @var array 55 | */ 56 | public $options = ['class' => 'form-control']; 57 | 58 | /** 59 | * @inheritdoc 60 | */ 61 | public function run() 62 | { 63 | $this->registerPlugin('typeahead'); 64 | if ($this->hasModel()) { 65 | return Html::activeTextInput($this->model, $this->attribute, $this->options); 66 | } else { 67 | return Html::textInput($this->name, $this->value, $this->options); 68 | } 69 | } 70 | 71 | protected function getDataSource() 72 | { 73 | $name = 'ds-' . $this->options['id']; 74 | $source = 'ds_' . str_replace('-', '_', $this->options['id']); 75 | $this->remote['q'] = '__QUERY'; 76 | $url = Url::to($this->remote); 77 | 78 | $js = <<getView()->registerJs($js, View::POS_READY); 89 | 90 | return [ 91 | 'name' => $name, 92 | 'display' => 'value', 93 | 'source' => new JsExpression($source), 94 | ]; 95 | } 96 | 97 | /** 98 | * @inheritdoc 99 | */ 100 | protected function registerPlugin($name) 101 | { 102 | $view = $this->getView(); 103 | 104 | TypeAheadAsset::register($view); 105 | 106 | $id = $this->options['id']; 107 | 108 | if ($this->clientOptions !== false) { 109 | $options = empty($this->clientOptions) ? 'null' : Json::htmlEncode($this->clientOptions); 110 | $dataSource = Json::htmlEncode($this->getDataSource()); 111 | $js = "jQuery('#$id').$name($options, $dataSource);"; 112 | $view->registerJs($js); 113 | } 114 | 115 | $this->registerClientEvents(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /bin/yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | [ 26 | 'db' => $local['components']['db'] 27 | ]] : [] 28 | ); 29 | 30 | $application = new app\base\ConsoleApplication($config); 31 | $exitCode = $application->run(); 32 | exit($exitCode); 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "skoro/yii2-admin-template", 3 | "description": "Yii 2 project template for backend projects", 4 | "keywords": ["yii2", "framework", "project template", "admin", "backend"], 5 | "license": "MIT", 6 | "type": "project", 7 | "authors": [ 8 | { 9 | "name": "Alexei Skorobogatko", 10 | "email": "skorobogatko.oleksii@gmail.com", 11 | "role": "Developer" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.4.0", 16 | "yiisoft/yii2": "^2.0", 17 | "yiisoft/yii2-bootstrap": "*", 18 | "yiisoft/yii2-swiftmailer": "*", 19 | "dmstr/yii2-adminlte-asset": "2.*", 20 | "bower-asset/remarkable-bootstrap-notify": "^3.1", 21 | "bower-asset/animate.css": "^3.5", 22 | "bower-asset/bootstrap-markdown": "^2.10" 23 | }, 24 | "require-dev": { 25 | "yiisoft/yii2-gii": "^2.0", 26 | "yiisoft/yii2-debug": "^2.0", 27 | "yiisoft/yii2-codeception": "*", 28 | "yiisoft/yii2-faker": "*" 29 | }, 30 | "minimum-stability": "stable", 31 | "autoload": { 32 | "psr-4": { 33 | "app\\": "app/", 34 | "modules\\": "modules/" 35 | } 36 | }, 37 | "scripts": { 38 | "post-create-project-cmd": "app\\base\\Composer::postCreateProjectCmd" 39 | }, 40 | "extra": { 41 | "asset-installer-paths": { 42 | "npm-asset-library": "vendor/npm", 43 | "bower-asset-library": "vendor/bower" 44 | }, 45 | "app\\base\\Composer::postCreateProjectCmd": { 46 | "createLocalConfig": ["config-sample.php", "config.php"], 47 | "generateCookieValidationKey": "config.php", 48 | "setPermissions": [ 49 | { 50 | "runtime": "0777", 51 | "runtime/cache": "0777", 52 | "runtime/logs": "0777", 53 | "web/assets": "0777" 54 | } 55 | ], 56 | "setDatabaseConfiguration": "config.php" 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /config-sample.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'request' => [ 16 | // !!! insert a secret key in the following (if it is empty) - 17 | // this is required by cookie validation 18 | 'cookieValidationKey' => '', 19 | ], 20 | // Database configuration. 21 | 'db' => [ 22 | 'class' => 'yii\db\Connection', 23 | 'dsn' => 'mysql:host=localhost;dbname=yii2basic', 24 | // SQLite3 example: 25 | // 'dsn' => 'sqlite:@runtime/data/db.sq3', 26 | 'username' => 'root', 27 | 'password' => '', 28 | 'charset' => 'utf8', 29 | ], 30 | ], 31 | // Configure your modules here: 32 | 'modules' => [ 33 | // 'debug' => [ 34 | // 'allowedIPs' => ['192.168.1.*'], 35 | // ], 36 | // 'gii' => [ 37 | // 'allowedIPs' => ['192.168.1.*'], 38 | // ], 39 | ], 40 | 'params' => [ 41 | // Application parameters. 42 | ], 43 | ]; 44 | -------------------------------------------------------------------------------- /docs/Param.md: -------------------------------------------------------------------------------- 1 | Управление параметрами 2 | ====================== 3 | 4 | Получить значение параметра: 5 | 6 | ```php 7 | $value = Param::value($key, 'default value'); 8 | ``` 9 | 10 | где, `$key` ключ параметра который состоит из: названия секции и названия 11 | параметра разделенных точкой. Например, `Site.adminEmail`. 12 | 13 | Поиск значения параметра происходит по такой схеме: 14 | - сначала в параметрах приложения `Yii::$app->params` (они задаются в файле 15 | `app/config/web.php` в массиве с индексом `params`) 16 | - среди закешированных (параметры которые уже были запрошены ранее) 17 | - и, наконец, в таблице `config`, после чего параметр кешируется 18 | 19 | Описание полей таблицы config 20 | ============================= 21 | 22 | Каждый параметр это запись в таблице `config` с такими полями: 23 | 24 | - **name** краткое название параметра (например, adminEmail как в примере выше) 25 | - **value** начальное значение параметра (сериализуется функцией `serialize()`) 26 | - **value_type** тип параметра (см. ниже доступные типы) 27 | - **options** если тип параметра `TYPE_SELECT` то содержит список доступных 28 | элементов для выпадающего списка (сериализуется функцией `serialize()`) 29 | - **title** заголовок параметра на странице настроек 30 | - **desc** описание параметра на странице настроек 31 | - **section** название секции (вкладки) к которой относится параметр 32 | - **required** признак что параметр является обязательным 33 | - **perms** список прав доступа к параметру (сериализуется функцией `serialize()`) 34 | 35 | Следует заметить, что связка **name** и **section** должна быть уникальной. 36 | Не может быть два одинаковых параметра в одной секции. 37 | 38 | Типы параметров 39 | =============== 40 | 41 | Тип параметра - это виджет который будет выводится на странице настроек **Settings**. 42 | - `TYPE_INT` поле ввода целого числа 43 | - `TYPE_NUM` поле ввода десятичного числа 44 | - `TYPE_EMAIL` поле ввода email адреса 45 | - `TYPE_URL` поле ввода ссылки 46 | - `TYPE_SWITCH` переключатель (чекбокс) 47 | - `TYPE_TEXT` текстовое поле ввода 48 | - `TYPE_EDITOR` редактор 49 | - `TYPE_SELECT` выпадающий список, варианты выбора содержатся в поле `options`. 50 | - `TYPE_PASSWORD` поле ввода пароля 51 | 52 | Страница настроек Settings 53 | ========================== 54 | 55 | Все параметры группируются по вкладкам (поле `section`). За работу с параметрами 56 | отвечает класс `app\base\actions\Settings` в котором происходит рендеринг и 57 | обработка страницы настроек. 58 | 59 | Права доступа 60 | ============= 61 | 62 | Поле `perms` содержит список прав доступа к параметру. Если у пользователя 63 | нет соответствующих прав, то такой параметр будет скрыт на странице 64 | настройек **Settings** для изменения. 65 | 66 | Право `updateSettings` доступно для администратора, модули могут добавлять 67 | свои права. 68 | 69 | Добавление нового параметра 70 | =========================== 71 | 72 | Добавление параметра - создание новой модели `Config`. 73 | 74 | ```php 75 | $test = new Config(); 76 | $test->name = 'key'; 77 | $test->title = 'Some key'; 78 | $test->section = 'MyModule'; 79 | $test->value_type = Config::TYPE_TEXT; 80 | $test->perms = ['MyModuleAdminSettings']; 81 | $test->save(); 82 | ``` 83 | 84 | Добавляется параметр `MyModule.key` который будет отображен на вкладке `MyModule` 85 | и который будет доступен пользователям с правом `MyModuleAdminSettings` и для 86 | остальных скрыт. 87 | 88 | Добавление параметра в миграциях возможно аналогичным созданием модели `Config`: 89 | ``` 90 | public function up() { 91 | $config = new \app\models\Config(); 92 | $config->name = 'fetchTime'; 93 | $config->title = 'Remote fetch time'; 94 | $config->section = 'Site'; 95 | $config->value_type = \app\models\Config::TYPE_TEXT; 96 | $config->save(); 97 | } 98 | public function down() { 99 | \app\models\Config::deleteAll([ 100 | 'name' => 'fetchTime', 101 | 'section' => 'Site', 102 | ]); 103 | } 104 | ``` 105 | -------------------------------------------------------------------------------- /modules/README.md: -------------------------------------------------------------------------------- 1 | What is modules 2 | --------------- 3 | 4 | Modules extend your site functionality beyond core. 5 | 6 | Place custom modules in this directory. It's better to use modules to 7 | extend your backend than direct change core files (files in `app` directory). 8 | 9 | How to develop modules 10 | ---------------------- 11 | 12 | Use console gii command: 13 | 14 | ``` 15 | ./bin/yii gii/module --moduleID=pages 16 | ``` 17 | 18 | This command will create `pages` directory under your project root `modules` 19 | directory. 20 | Inside `pages` directory is base module skeleton. Main module class 21 | is PagesModule. Please take attention to following properties in that class: 22 | 23 | ``` 24 | public $moduleName = ''; 25 | public $moduleDescription = ''; 26 | ``` 27 | 28 | These properties give information for others users/developers what module do. 29 | 30 | To create migration for `pages` module execute following command: 31 | 32 | ``` 33 | ./bin/yii migrate/create --migrationPath=@modules/pages/migrations schema 34 | ``` 35 | 36 | Module management 37 | ----------------- 38 | 39 | Yii's command line tool provide `module` command with followin subcommands: 40 | - index (by default) 41 | - install 42 | - uninstall 43 | - info 44 | 45 | Command `module/index` or just `module` will show list of all available 46 | modules (installed and not installed). 47 | 48 | `module/install` will install a module. This command requires module id 49 | argument. You can get module id from output from the `module` command. 50 | `module/uninstall` will uninstall a module and requires module id also. 51 | 52 | `info` command provides module information such: readable name, description, 53 | module status (installed/not installed) and contained migrations. 54 | -------------------------------------------------------------------------------- /modules/wiki/RuleOwnWiki.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace modules\wiki; 9 | 10 | /** 11 | * RuleOwnWiki 12 | * 13 | * @author skoro 14 | */ 15 | class RuleOwnWiki extends \yii\rbac\Rule 16 | { 17 | public $name = 'isOwnWiki'; 18 | 19 | /** 20 | * @param string|integer $user the user ID. 21 | * @param Item $item the role or permission that this rule is associated with 22 | * @param array $params parameters passed to ManagerInterface::checkAccess(). 23 | * @return boolean a value indicating whether the rule permits the role or permission it is associated with. 24 | */ 25 | public function execute($user, $item, $params) 26 | { 27 | return isset($params['wiki']) ? $params['wiki']->user_id == $user : false; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/wiki/WikiModule.php: -------------------------------------------------------------------------------- 1 | addMenu('main-nav', [ 33 | ['label' => 'Wiki', 'icon' => 'wikipedia-w', 'url' => '#', 'roles' => ['viewWiki'], 'items' => [ 34 | ['label' => 'Pages list', 'icon' => 'file-text', 'roles' => ['viewWiki'], 'url' => ['/wiki/page/index']], 35 | ]], 36 | ]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /modules/wiki/assets/DiffAsset.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | * @since 0.2 7 | */ 8 | 9 | namespace modules\wiki\assets; 10 | 11 | /** 12 | * DiffAsset 13 | * 14 | * @author skoro 15 | */ 16 | class DiffAsset extends \yii\web\AssetBundle 17 | { 18 | public $sourcePath = '@modules/wiki/assets'; 19 | 20 | public $css = [ 21 | 'diff.css', 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /modules/wiki/assets/MarkdownEditorAsset.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | namespace modules\wiki\assets; 9 | 10 | /** 11 | * MarkdownEditorAsset 12 | * 13 | * @author skoro 14 | */ 15 | class MarkdownEditorAsset extends \yii\web\AssetBundle 16 | { 17 | public $sourcePath = '@bower/bootstrap-markdown'; 18 | 19 | public $css = [ 20 | 'css/bootstrap-markdown.min.css', 21 | ]; 22 | 23 | public $js = [ 24 | 'js/bootstrap-markdown.js', 25 | ]; 26 | 27 | public $depends = [ 28 | 'yii\bootstrap\BootstrapAsset', 29 | ]; 30 | 31 | /** 32 | * Editor language. 33 | * @var string 34 | */ 35 | public $language; 36 | 37 | /** 38 | * Register language script. 39 | */ 40 | public function registerAssetFiles($view) 41 | { 42 | if ($this->language) { 43 | $this->js[] = 'locale/bootstrap-markdown.' . $this->language . '.js'; 44 | } 45 | parent::registerAssetFiles($view); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /modules/wiki/assets/diff.css: -------------------------------------------------------------------------------- 1 | .Differences { 2 | width: 100%; 3 | border-collapse: collapse; 4 | border-spacing: 0; 5 | empty-cells: show; 6 | } 7 | 8 | .Differences thead { 9 | display: none; 10 | } 11 | 12 | .Differences tbody th { 13 | text-align: right; 14 | background: #FAFAFA; 15 | padding: 1px 2px; 16 | border-right: 1px solid #eee; 17 | vertical-align: top; 18 | font-size: 13px; 19 | font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; 20 | font-weight: normal; 21 | color: #999; 22 | width: 5px; 23 | } 24 | 25 | .Differences td { 26 | padding: 1px 2px; 27 | font-size: 13px; 28 | font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; 29 | } 30 | 31 | .DifferencesSideBySide .ChangeInsert td.Left { 32 | background: #dfd; 33 | } 34 | 35 | .DifferencesSideBySide .ChangeInsert td.Right { 36 | background: #cfc; 37 | } 38 | 39 | .DifferencesSideBySide .ChangeDelete td.Left { 40 | background: #f88; 41 | } 42 | 43 | .DifferencesSideBySide .ChangeDelete td.Right { 44 | background: #faa; 45 | } 46 | 47 | .DifferencesSideBySide .ChangeReplace .Left { 48 | background: #fe9; 49 | } 50 | 51 | .DifferencesSideBySide .ChangeReplace .Right { 52 | background: #fd8; 53 | } 54 | 55 | .Differences ins, .Differences del { 56 | text-decoration: none; 57 | } 58 | 59 | .DifferencesSideBySide .ChangeReplace ins, .DifferencesSideBySide .ChangeReplace del { 60 | background: #fc0; 61 | } 62 | 63 | .Differences .Skipped { 64 | background: #f7f7f7; 65 | } 66 | 67 | .DifferencesInline .ChangeReplace .Left, 68 | .DifferencesInline .ChangeDelete .Left { 69 | background: #fdd; 70 | } 71 | 72 | .DifferencesInline .ChangeReplace .Right, 73 | .DifferencesInline .ChangeInsert .Right { 74 | background: #dfd; 75 | } 76 | 77 | .DifferencesInline .ChangeReplace ins { 78 | background: #9e9; 79 | } 80 | 81 | .DifferencesInline .ChangeReplace del { 82 | background: #e99; 83 | } 84 | 85 | .DifferencesInline th[data-line-number]:before { 86 | content: attr(data-line-number); 87 | } 88 | -------------------------------------------------------------------------------- /modules/wiki/forms/Editor.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace modules\wiki\forms; 9 | 10 | use modules\wiki\models\History; 11 | use modules\wiki\models\Wiki; 12 | use Yii; 13 | use yii\base\Model; 14 | 15 | /** 16 | * Editor 17 | * 18 | * @author skoro 19 | */ 20 | class Editor extends Model 21 | { 22 | 23 | const EVENT_BEFORE_UPDATE = 'beforeWikiUpdate'; 24 | const EVENT_AFTER_UPDATE = 'afterWikiUpdate'; 25 | 26 | /** 27 | * @var string 28 | */ 29 | public $title; 30 | 31 | /** 32 | * @var string 33 | */ 34 | public $slug; 35 | 36 | /** 37 | * @var string 38 | */ 39 | public $content; 40 | 41 | /** 42 | * @var string 43 | */ 44 | public $summary; 45 | 46 | /** 47 | * @var Wiki 48 | */ 49 | protected $_wiki; 50 | 51 | /** 52 | * @param Wiki $wiki 53 | * @param array $config 54 | */ 55 | public function __construct(Wiki $wiki, $config = array()) 56 | { 57 | $this->_wiki = $wiki; 58 | $this->title = $wiki->title; 59 | $this->slug = $wiki->slug; 60 | 61 | $this->content = $this->getHistoryContent(); 62 | 63 | parent::__construct($config); 64 | } 65 | 66 | 67 | /** 68 | * @inheritdoc 69 | */ 70 | public function rules() 71 | { 72 | return [ 73 | ['title', 'required'], 74 | ['title', 'string', 'max' => 255], 75 | ['title', 'filter', 'filter' => 'strip_tags'], 76 | ['title', 'filter', 'filter' => 'trim'], 77 | 78 | ['slug', 'string', 'max' => 255], 79 | 80 | ['summary', 'string', 'max' => 255], 81 | ['summary', 'default', 'value' => ''], 82 | 83 | ['content', 'string'], 84 | ]; 85 | } 86 | 87 | /** 88 | * Validate and save wiki page. 89 | * @return Wiki|false 90 | */ 91 | public function save() 92 | { 93 | $this->trigger(self::EVENT_BEFORE_UPDATE); 94 | 95 | if (!$this->validate()) { 96 | return false; 97 | } 98 | 99 | $transaction = Yii::$app->db->beginTransaction(); 100 | 101 | try { 102 | $isNew = $this->isNew(); // Preserve New status for later checking. 103 | $this->_wiki->title = $this->title; 104 | $this->_wiki->slug = $this->slug; 105 | if (!$this->_wiki->save()) { 106 | throw new \yii\db\Exception('Cannot save Wiki model.'); 107 | } 108 | 109 | // Don't save wiki if content not modified. 110 | if ($isNew || $this->content != $this->getHistoryContent()) { 111 | $history = new History(); 112 | $history->wiki_id = $this->_wiki->id; 113 | $history->content = $this->content; 114 | $history->host_ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; 115 | $history->summary = $this->summary; 116 | if (!$history->save()) { 117 | throw new \yii\db\Exception('Cannot save History model.'); 118 | } 119 | } 120 | 121 | $transaction->commit(); 122 | } catch (\Exception $e) { 123 | $transaction->rollBack(); 124 | return false; 125 | } 126 | 127 | $this->trigger(self::EVENT_AFTER_UPDATE); 128 | 129 | return $this->_wiki; 130 | } 131 | 132 | /** 133 | * @return boolean 134 | */ 135 | public function isNew() 136 | { 137 | return $this->_wiki->isNewRecord; 138 | } 139 | 140 | /** 141 | * Returns wiki instance. 142 | * @return Wiki 143 | */ 144 | public function getWiki() 145 | { 146 | return $this->_wiki; 147 | } 148 | 149 | /** 150 | * Returns recent actual page content. 151 | * @return string 152 | */ 153 | public function getHistoryContent() 154 | { 155 | $history = $this->_wiki->historyLatest; 156 | return $history ? $history->content : ''; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /modules/wiki/helpers/DiffHelper.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace modules\wiki\helpers; 9 | 10 | use Diff; 11 | use modules\wiki\DiffRendererHtmlInline; 12 | use modules\wiki\models\History; 13 | 14 | /** 15 | * Page differences helper. 16 | * 17 | * @author skoro 18 | */ 19 | class DiffHelper 20 | { 21 | 22 | /** 23 | * 24 | * @param History $history 25 | * @param array $diffOptions 26 | * @return string 27 | */ 28 | public static function diff(History $history, $diffOptions = []) 29 | { 30 | if (!($previous = $history->previous)) { 31 | return ''; 32 | } 33 | 34 | $content = explode("\n", $history->content); 35 | $prevContent = explode("\n", $previous->content); 36 | $diff = new Diff($content, $prevContent, $diffOptions); 37 | $renderer = new DiffRendererHtmlInline(); 38 | return $diff->render($renderer); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /modules/wiki/migrations/m160901_090402_wiki.php: -------------------------------------------------------------------------------- 1 | createTable($this->table, [ 13 | 'id' => $this->primaryKey(), 14 | 'user_id' => $this->integer(), 15 | 'title' => $this->string(255)->notNull(), 16 | 'slug' => $this->string(255)->notNull()->defaultValue(''), 17 | 'parent_id' => $this->integer(), 18 | 'created_at' => $this->integer(), 19 | ]); 20 | $this->createIndex('idx_wiki_slug', $this->table, ['slug']); 21 | $this->createIndex('idx_wiki_parent', $this->table, ['parent_id']); 22 | } 23 | 24 | public function down() 25 | { 26 | $this->dropTable($this->table); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /modules/wiki/migrations/m160902_065120_wiki_change.php: -------------------------------------------------------------------------------- 1 | createTable($this->table, [ 13 | 'id' => $this->primaryKey(), 14 | 'wiki_id' => $this->integer()->notNull(), 15 | 'user_id' => $this->integer(), 16 | 'content' => $this->text()->defaultValue(''), 17 | 'rev' => $this->integer()->notNull()->defaultValue(0), 18 | 'created_at' => $this->integer(), 19 | 'summary' => $this->string(255)->notNull()->defaultValue(''), 20 | 'host_ip' => $this->char(15)->notNull()->defaultValue(''), 21 | ]); 22 | 23 | if (!$this->isSqlite()) { 24 | $this->addForeignKey('fk_wiki_id', $this->table, 'wiki_id', '{{%wiki}}', 'id', 'CASCADE'); 25 | } 26 | $this->createIndex('idx_wiki_history_rev', $this->table, ['wiki_id', 'rev']); 27 | } 28 | 29 | public function down() 30 | { 31 | $this->dropTable($this->table); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /modules/wiki/migrations/m160902_073325_auth.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'createWiki' => 'Create a wiki', 10 | 'updateOwnWiki' => [ 11 | 'description' => 'Update only own wiki', 12 | 'rule' => 'modules\wiki\RuleOwnWiki', 13 | 'child' => 'updateWiki', 14 | ], 15 | 'updateWiki' => 'Update any wiki page', 16 | 'viewWiki' => 'View wiki', 17 | 'viewWikiHistory' => 'View wiki changes history', 18 | 'deleteOwnWiki' => [ 19 | 'description' => 'Delete only own wiki', 20 | 'rule' => 'modules\wiki\RuleOwnWiki', 21 | 'child' => 'deleteWiki', 22 | ], 23 | 'deleteWiki' => 'Delete any wiki page', 24 | ], 25 | 'roles' => [ 26 | 'WikiEditor' => ['createWiki', 'viewWiki', 'viewWikiHistory', 'updateOwnWiki', 'deleteOwnWiki'], 27 | 'WikiAdmin' => ['WikiEditor', 'updateWiki', 'deleteWiki'], 28 | ], 29 | ]; 30 | } 31 | -------------------------------------------------------------------------------- /modules/wiki/views/page/_editor.php: -------------------------------------------------------------------------------- 1 | getWiki(); 13 | if ($wiki->id) { 14 | $cancelUrl = ['page/view', 'id' => $wiki->id]; 15 | } elseif ($wiki->parent_id) { 16 | $cancelUrl = ['page/view', 'id' => $wiki->parent_id]; 17 | } else { 18 | $cancelUrl = ['page/index']; 19 | } 20 | ?> 21 | 22 | 23 | 24 | field($editor, 'title') ?> 25 | field($editor, 'content')->widget(MarkdownEditor::className(), [ 26 | 'previewUrl' => ['page/markdown-preview'], 27 | ]) ?> 28 | field($editor, 'summary')->textInput([ 29 | 'placeholder' => Yii::t('app', 'What did you change ?'), 30 | ]) ?> 31 | 32 | [ 34 | 'url' => $cancelUrl, 35 | ], 36 | ]) ?> 37 | -------------------------------------------------------------------------------- /modules/wiki/views/page/_history.php: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | $historyProvider, 17 | 'dateValue' => function ($model) { 18 | return Yii::$app->formatter->asDate($model->created_at); 19 | }, 20 | 'timeView' => function ($model) { 21 | return Yii::$app->formatter->asRelativeTime($model->created_at); 22 | }, 23 | 'itemHeaderView' => function ($model) { 24 | return Yii::$app->formatter->asUserlink($model->user) . ' ' . Html::tag('span', Html::encode($model->summary), ['class' => 'text-muted summary-change']); 25 | }, 26 | 'itemView' => function ($model) { 27 | return Html::tag('pre', DiffHelper::diff($model)); 28 | }, 29 | 'itemFooterView' => function ($model) { 30 | return Html::a(Yii::t('app', 'Edit'), ['page/update', 'id' => $model->wiki_id, 'rev' => $model->id], ['class' => 'btn btn-default btn-flat btn-xs', 'data-pjax' => 0]); 31 | }, 32 | ]) ?> 33 | 34 | -------------------------------------------------------------------------------- /modules/wiki/views/page/create.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Create a new page'); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Wiki'), 'url' => ['page/index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 | 13 | $this->title, 15 | ]) ?> 16 | render('_editor', ['editor' => $editor]) ?> 17 | 18 | -------------------------------------------------------------------------------- /modules/wiki/views/page/delete.php: -------------------------------------------------------------------------------- 1 | title = $delete->getWiki()->title; 13 | ?> 14 |
15 |

16 |

17 |
18 | 19 | 20 | 21 | isChildrenExists()): ?> 22 |

23 | 28 |

29 | field($delete, 'mode')->widget(Check::className(), [ 30 | 'type' => Check::TYPE_RADIO, 31 | 'options' => ['class' => 'checkbox-list-vert'], 32 | 'items' => $delete->getChoices(), 33 | ]) ?> 34 | field($delete, 'parentId')->widget(Select2::className(), [ 35 | 'hideSearch' => false, 36 | 'remote' => ['wiki-suggest', 'ign' => $delete->getWiki()->id], 37 | ])->label(false) ?> 38 |
39 | 40 | 41 | [ 43 | 'label' => Yii::t('app', 'DELETE'), 44 | 'options' => ['class' => 'btn btn-flat bg-red'], 45 | ], 46 | 'cancel' => [ 47 | 'url' => ['page/view', 'id' => $delete->getWiki()->id], 48 | ], 49 | ]) ?> 50 | -------------------------------------------------------------------------------- /modules/wiki/views/page/index.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Wiki'); 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 | 14 | 15 | 16 |
17 | user->can('createWiki')): ?> 18 | 'btn btn-flat btn-default']) ?> 19 | 20 |
21 | 22 | 23 | 24 | 25 | 32 | 33 | -------------------------------------------------------------------------------- /modules/wiki/views/page/update.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update page'); 10 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Wiki'), 'url' => ['page/index']]; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 | 14 | [ 16 | [ 17 | 'label' => Yii::t('app', 'Editor'), 18 | 'content' => $this->render('_editor', ['editor' => $editor]), 19 | ], 20 | [ 21 | 'label' => Yii::t('app', 'History'), 22 | 'content' => $this->render('_history', [ 23 | 'historyProvider' => $historyProvider, 24 | ]), 25 | 'visible' => Yii::$app->user->can('viewWikiHistory'), 26 | ], 27 | ], 28 | ]) ?> 29 | -------------------------------------------------------------------------------- /modules/wiki/views/page/view.php: -------------------------------------------------------------------------------- 1 | title = $wiki->title; 12 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Wiki'), 'url' => ['page/index']]; 13 | $this->params['breadcrumbs'][] = $this->title; 14 | ?> 15 | 16 | 17 |
18 | 'btn btn-flat btn-default']) ?> 19 | parent_id)): ?> 20 | $wiki->parent_id], ['class' => 'btn btn-flat btn-default']) ?> 21 | 22 | getChildren()->count()): ?> 23 | Yii::t('app', 'Pages'), 25 | 'tagName' => 'a', 26 | 'options' => ['class' => ['btn btn-flat btn-default']], 27 | 'dropdown' => [ 28 | 'items' => array_map(function ($child) { 29 | return [ 30 | 'label' => $child->title, 31 | 'url' => ['page/view', 'id' => $child->id], 32 | ]; 33 | }, $wiki->children), 34 | ], 35 | ]) ?> 36 | 37 | Yii::t('app', 'Actions'), 39 | 'tagName' => 'a', 40 | 'options' => ['class' => ['btn btn-flat btn-default']], 41 | 'dropdown' => [ 42 | 'items' => [ 43 | [ 44 | 'label' => Yii::t('app', 'Edit'), 45 | 'url' => ['page/update', 'id' => $wiki->id], 46 | 'visible' => Yii::$app->user->can('updateWiki', ['wiki' => $wiki]), 47 | ], 48 | [ 49 | 'label' => Yii::t('app', 'Create child page'), 50 | 'url' => ['page/create', 'id' => $wiki->id], 51 | 'visible' => Yii::$app->user->can('createWiki'), 52 | ], 53 | [ 54 | 'label' => Yii::t('app', 'View raw'), 55 | 'url' => ['page/raw', 'id' => $wiki->id], 56 | 'visible' => Yii::$app->user->can('viewWiki'), 57 | ], 58 | [ 59 | 'label' => Yii::t('app', 'Delete'), 60 | 'url' => ['page/delete', 'id' => $wiki->id], 61 | 'visible' => Yii::$app->user->can('deleteWiki', ['wiki' => $wiki]), 62 | ], 63 | ], 64 | ], 65 | ]) ?> 66 |
67 |
68 | 69 | Yii::$app->formatter->asUserlink($wiki->user), 71 | 'editor' => Yii::$app->formatter->asUserlink($wiki->historyLatest->user), 72 | 'time' => Yii::$app->formatter->asRelativeTime($wiki->historyLatest->created_at), 73 | ]) ?> 74 | 75 |
76 | formatter->asMarkdown($wiki->historyLatest->content) ?> 77 | 78 | 79 | getChildren()->count()): ?> 80 | Yii::t('app', 'Child pages'), 82 | ]) ?> 83 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /modules/wiki/widgets/MarkdownEditor.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @since 0.2 6 | */ 7 | 8 | namespace modules\wiki\widgets; 9 | 10 | use yii\widgets\InputWidget; 11 | use yii\helpers\Html; 12 | use yii\helpers\Json; 13 | use yii\helpers\Url; 14 | use modules\wiki\assets\MarkdownEditorAsset; 15 | 16 | /** 17 | * Bootstrap markdown editor. 18 | * 19 | * @link http://www.codingdrama.com/bootstrap-markdown/ 20 | * @author skoro 21 | */ 22 | class MarkdownEditor extends InputWidget 23 | { 24 | 25 | const RESIZE_NONE = 'none'; 26 | const RESIZE_BOTH = 'both'; 27 | const RESIZE_VERTICAL = 'vertical'; 28 | const RESIZE_HORIZONTAL = 'horizontal'; 29 | 30 | /** 31 | * @var array 32 | */ 33 | public $clientOptions = []; 34 | 35 | /** 36 | * @var string 37 | */ 38 | public $language; 39 | 40 | /** 41 | * @var boolean wrap editor by container with 'form-group' class. 42 | */ 43 | public $formGroup = true; 44 | 45 | /** 46 | * @var integer|false editor height. 47 | */ 48 | public $rows = 10; 49 | 50 | /** 51 | * @var string editor resize mode. 52 | */ 53 | public $resize = self::RESIZE_VERTICAL; 54 | 55 | /** 56 | * @var string|array|false 57 | */ 58 | public $previewUrl = false; 59 | 60 | /** 61 | * @inheritdoc 62 | */ 63 | public function run() 64 | { 65 | $this->registerClientScript(); 66 | 67 | if ($this->rows !== false) { 68 | $this->options['rows'] = $this->rows; 69 | } 70 | 71 | if ($this->hasModel()) { 72 | $editor = Html::activeTextarea($this->model, $this->attribute, $this->options); 73 | } else { 74 | $editor = Html::textarea($this->name, $this->value, $this->options); 75 | } 76 | 77 | if ($this->formGroup) { 78 | $editor = Html::tag('div', $editor, ['class' => 'form-group']); 79 | } 80 | 81 | return $editor; 82 | } 83 | 84 | /** 85 | * Register plugin script. 86 | */ 87 | protected function registerClientScript() 88 | { 89 | $view = $this->getView(); 90 | $id = $this->options['id']; 91 | if ($this->language) { 92 | $this->clientOptions['language'] = $this->language; 93 | } 94 | if ($this->resize !== self::RESIZE_NONE) { 95 | $this->clientOptions['resize'] = $this->resize; 96 | } 97 | if ($this->previewUrl !== false) { 98 | // $this->clientOptions['previewUrl'] = Url::to($this->previewUrl); 99 | $previewUrl = Url::to($this->previewUrl); 100 | $this->clientOptions['onPreview'] = new \yii\web\JsExpression("function (e) { 101 | var content = e.getContent(); 102 | if (content.trim().length === 0) { return ''; } 103 | $.ajax({ 104 | url: '{$previewUrl}', 105 | method: 'POST', 106 | cache: false, 107 | data: {content: content}, 108 | async: false, 109 | success: function (html) { content = html; } 110 | }); 111 | return content; 112 | }"); 113 | } 114 | $clientOptions = Json::encode($this->clientOptions); 115 | 116 | 117 | $view->registerJs("jQuery('#$id').markdown($clientOptions);"); 118 | $asset = MarkdownEditorAsset::register($view); 119 | if ($this->language) { 120 | $asset->language = $this->language; 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skoro/yii2-admin-template/832cf0f365d78b96879ddddfb3e17d9f239cf698/screenshot.png -------------------------------------------------------------------------------- /tests/codeception.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | #coverage: 3 | # #c3_url: http://localhost:8080/index-test.php/ 4 | # enabled: true 5 | # #remote: true 6 | # #remote_config: '../tests/codeception.yml' 7 | # white_list: 8 | # include: 9 | # - ../models/* 10 | # - ../controllers/* 11 | # - ../commands/* 12 | # - ../mail/* 13 | # blacklist: 14 | # include: 15 | # - ../assets/* 16 | # - ../config/* 17 | # - ../runtime/* 18 | # - ../vendor/* 19 | # - ../views/* 20 | # - ../web/* 21 | # - ../tests/* 22 | paths: 23 | tests: codeception 24 | log: codeception/_output 25 | data: codeception/_data 26 | helpers: codeception/_support 27 | settings: 28 | bootstrap: _bootstrap.php 29 | suite_class: \PHPUnit_Framework_TestSuite 30 | memory_limit: 1024M 31 | log: true 32 | colors: true 33 | config: 34 | # the entry script URL (with host info) for functional and acceptance tests 35 | # PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL 36 | test_entry_url: http://localhost:8080/index-test.php -------------------------------------------------------------------------------- /tests/codeception/.gitignore: -------------------------------------------------------------------------------- 1 | # these files are auto generated by codeception build 2 | /unit/UnitTester.php 3 | /functional/FunctionalTester.php 4 | /acceptance/AcceptanceTester.php 5 | -------------------------------------------------------------------------------- /tests/codeception/_bootstrap.php: -------------------------------------------------------------------------------- 1 | $value) { 21 | $inputType = $field === 'body' ? 'textarea' : 'input'; 22 | $this->actor->fillField($inputType . '[name="ContactForm[' . $field . ']"]', $value); 23 | } 24 | $this->actor->click('contact-button'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/codeception/_pages/LoginPage.php: -------------------------------------------------------------------------------- 1 | actor->fillField('input[name="Login[email]"]', $username); 22 | $this->actor->fillField('input[name="Login[password]"]', $password); 23 | $this->actor->click('login-button'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/codeception/_support/AcceptanceTester.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that about works'); 9 | AboutPage::openBy($I); 10 | $I->see('About', 'h1'); 11 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/ContactCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that contact works'); 9 | 10 | $contactPage = ContactPage::openBy($I); 11 | 12 | $I->see('Contact', 'h1'); 13 | 14 | $I->amGoingTo('submit contact form with no data'); 15 | $contactPage->submit([]); 16 | if (method_exists($I, 'wait')) { 17 | $I->wait(3); // only for selenium 18 | } 19 | $I->expectTo('see validations errors'); 20 | $I->see('Contact', 'h1'); 21 | $I->see('Name cannot be blank'); 22 | $I->see('Email cannot be blank'); 23 | $I->see('Subject cannot be blank'); 24 | $I->see('Body cannot be blank'); 25 | $I->see('The verification code is incorrect'); 26 | 27 | $I->amGoingTo('submit contact form with not correct email'); 28 | $contactPage->submit([ 29 | 'name' => 'tester', 30 | 'email' => 'tester.email', 31 | 'subject' => 'test subject', 32 | 'body' => 'test content', 33 | 'verifyCode' => 'testme', 34 | ]); 35 | if (method_exists($I, 'wait')) { 36 | $I->wait(3); // only for selenium 37 | } 38 | $I->expectTo('see that email address is wrong'); 39 | $I->dontSee('Name cannot be blank', '.help-inline'); 40 | $I->see('Email is not a valid email address.'); 41 | $I->dontSee('Subject cannot be blank', '.help-inline'); 42 | $I->dontSee('Body cannot be blank', '.help-inline'); 43 | $I->dontSee('The verification code is incorrect', '.help-inline'); 44 | 45 | $I->amGoingTo('submit contact form with correct data'); 46 | $contactPage->submit([ 47 | 'name' => 'tester', 48 | 'email' => 'tester@example.com', 49 | 'subject' => 'test subject', 50 | 'body' => 'test content', 51 | 'verifyCode' => 'testme', 52 | ]); 53 | if (method_exists($I, 'wait')) { 54 | $I->wait(3); // only for selenium 55 | } 56 | $I->dontSeeElement('#contact-form'); 57 | $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); 58 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/HomeCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that home page works'); 7 | $I->amOnPage(Yii::$app->homeUrl); 8 | $I->see('Congratulations!'); 9 | $I->seeLink('Login'); 10 | $I->seeLink('Register'); 11 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/LoginCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that login works'); 9 | 10 | $loginPage = LoginPage::openBy($I); 11 | 12 | $I->see('Sign in to start your session'); 13 | 14 | $I->amGoingTo('try to login with empty credentials'); 15 | $loginPage->login('', ''); 16 | if (method_exists($I, 'wait')) { 17 | $I->wait(3); // only for selenium 18 | } 19 | $I->expectTo('see validations errors'); 20 | $I->see('Email cannot be blank.'); 21 | $I->see('Password cannot be blank.'); 22 | 23 | $I->amGoingTo('try to login with wrong credentials'); 24 | $loginPage->login('admin', 'wrong'); 25 | if (method_exists($I, 'wait')) { 26 | $I->wait(3); // only for selenium 27 | } 28 | $I->expectTo('see validations errors'); 29 | $I->see('Email is not a valid email address.'); 30 | 31 | $I->amGoingTo('try to login with correct credentials'); 32 | $loginPage->login('login', 'password'); 33 | if (method_exists($I, 'wait')) { 34 | $I->wait(3); // only for selenium 35 | } 36 | $I->expectTo('see user info'); 37 | $I->seeLink('Logout'); 38 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/_bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'db' => $config['components']['db'] 20 | ]] : [], 21 | require(__DIR__ . '/../config/config.php') 22 | ); 23 | 24 | $application = new yii\console\Application($config); 25 | $exitCode = $application->run(); 26 | exit($exitCode); 27 | -------------------------------------------------------------------------------- /tests/codeception/bin/yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright (c) 2008 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 19 | 20 | @endlocal 21 | -------------------------------------------------------------------------------- /tests/codeception/config/acceptance.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'request' => [ 15 | // it's not recommended to run functional tests with CSRF validation enabled 16 | 'enableCsrfValidation' => false, 17 | // but if you absolutely need it set cookie domain to localhost 18 | /* 19 | 'csrfCookie' => [ 20 | 'domain' => 'localhost', 21 | ], 22 | */ 23 | ], 24 | ], 25 | ] 26 | ); 27 | -------------------------------------------------------------------------------- /tests/codeception/config/unit.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that about works'); 9 | AboutPage::openBy($I); 10 | $I->see('About', 'h1'); 11 | -------------------------------------------------------------------------------- /tests/codeception/functional/ContactCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that contact works'); 9 | 10 | $contactPage = ContactPage::openBy($I); 11 | 12 | $I->see('Contact', 'h1'); 13 | 14 | $I->amGoingTo('submit contact form with no data'); 15 | $contactPage->submit([]); 16 | $I->expectTo('see validations errors'); 17 | $I->see('Contact', 'h1'); 18 | $I->see('Name cannot be blank'); 19 | $I->see('Email cannot be blank'); 20 | $I->see('Subject cannot be blank'); 21 | $I->see('Body cannot be blank'); 22 | $I->see('The verification code is incorrect'); 23 | 24 | $I->amGoingTo('submit contact form with not correct email'); 25 | $contactPage->submit([ 26 | 'name' => 'tester', 27 | 'email' => 'tester.email', 28 | 'subject' => 'test subject', 29 | 'body' => 'test content', 30 | 'verifyCode' => 'testme', 31 | ]); 32 | $I->expectTo('see that email address is wrong'); 33 | $I->dontSee('Name cannot be blank', '.help-inline'); 34 | $I->see('Email is not a valid email address.'); 35 | $I->dontSee('Subject cannot be blank', '.help-inline'); 36 | $I->dontSee('Body cannot be blank', '.help-inline'); 37 | $I->dontSee('The verification code is incorrect', '.help-inline'); 38 | 39 | $I->amGoingTo('submit contact form with correct data'); 40 | $contactPage->submit([ 41 | 'name' => 'tester', 42 | 'email' => 'tester@example.com', 43 | 'subject' => 'test subject', 44 | 'body' => 'test content', 45 | 'verifyCode' => 'testme', 46 | ]); 47 | $I->dontSeeElement('#contact-form'); 48 | $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); 49 | -------------------------------------------------------------------------------- /tests/codeception/functional/HomeCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that home page works'); 7 | $I->amOnPage(Yii::$app->homeUrl); 8 | $I->see('My Company'); 9 | $I->seeLink('About'); 10 | $I->click('About'); 11 | $I->see('This is the About page.'); 12 | -------------------------------------------------------------------------------- /tests/codeception/functional/LoginCept.php: -------------------------------------------------------------------------------- 1 | wantTo('ensure that login works'); 9 | 10 | $loginPage = LoginPage::openBy($I); 11 | 12 | $I->see('Login', 'h1'); 13 | 14 | $I->amGoingTo('try to login with empty credentials'); 15 | $loginPage->login('', ''); 16 | $I->expectTo('see validations errors'); 17 | $I->see('Username cannot be blank.'); 18 | $I->see('Password cannot be blank.'); 19 | 20 | $I->amGoingTo('try to login with wrong credentials'); 21 | $loginPage->login('admin', 'wrong'); 22 | $I->expectTo('see validations errors'); 23 | $I->see('Incorrect username or password.'); 24 | 25 | $I->amGoingTo('try to login with correct credentials'); 26 | $loginPage->login('admin', 'admin'); 27 | $I->expectTo('see user info'); 28 | $I->see('Logout (admin)'); 29 | -------------------------------------------------------------------------------- /tests/codeception/functional/_bootstrap.php: -------------------------------------------------------------------------------- 1 | mailer->fileTransportCallback = function ($mailer, $message) { 17 | return 'testing_message.eml'; 18 | }; 19 | } 20 | 21 | protected function tearDown() 22 | { 23 | unlink($this->getMessageFile()); 24 | parent::tearDown(); 25 | } 26 | 27 | public function testContact() 28 | { 29 | $model = $this->getMock('app\models\ContactForm', ['validate']); 30 | $model->expects($this->once())->method('validate')->will($this->returnValue(true)); 31 | 32 | $model->attributes = [ 33 | 'name' => 'Tester', 34 | 'email' => 'tester@example.com', 35 | 'subject' => 'very important letter subject', 36 | 'body' => 'body of current message', 37 | ]; 38 | 39 | $model->contact('admin@example.com'); 40 | 41 | $this->specify('email should be send', function () { 42 | expect('email file should exist', file_exists($this->getMessageFile()))->true(); 43 | }); 44 | 45 | $this->specify('message should contain correct data', function () use ($model) { 46 | $emailMessage = file_get_contents($this->getMessageFile()); 47 | 48 | expect('email should contain user name', $emailMessage)->contains($model->name); 49 | expect('email should contain sender email', $emailMessage)->contains($model->email); 50 | expect('email should contain subject', $emailMessage)->contains($model->subject); 51 | expect('email should contain body', $emailMessage)->contains($model->body); 52 | }); 53 | } 54 | 55 | private function getMessageFile() 56 | { 57 | return Yii::getAlias(Yii::$app->mailer->fileTransportPath) . '/testing_message.eml'; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /tests/codeception/unit/models/LoginFormTest.php: -------------------------------------------------------------------------------- 1 | user->logout(); 17 | parent::tearDown(); 18 | } 19 | 20 | public function testLoginNoUser() 21 | { 22 | $model = new LoginForm([ 23 | 'username' => 'not_existing_username', 24 | 'password' => 'not_existing_password', 25 | ]); 26 | 27 | $this->specify('user should not be able to login, when there is no identity', function () use ($model) { 28 | expect('model should not login user', $model->login())->false(); 29 | expect('user should not be logged in', Yii::$app->user->isGuest)->true(); 30 | }); 31 | } 32 | 33 | public function testLoginWrongPassword() 34 | { 35 | $model = new LoginForm([ 36 | 'username' => 'demo', 37 | 'password' => 'wrong_password', 38 | ]); 39 | 40 | $this->specify('user should not be able to login with wrong password', function () use ($model) { 41 | expect('model should not login user', $model->login())->false(); 42 | expect('error message should be set', $model->errors)->hasKey('password'); 43 | expect('user should not be logged in', Yii::$app->user->isGuest)->true(); 44 | }); 45 | } 46 | 47 | public function testLoginCorrect() 48 | { 49 | $model = new LoginForm([ 50 | 'username' => 'demo', 51 | 'password' => 'demo', 52 | ]); 53 | 54 | $this->specify('user should be able to login with correct credentials', function () use ($model) { 55 | expect('model should login user', $model->login())->true(); 56 | expect('error message should not be set', $model->errors)->hasntKey('password'); 57 | expect('user should be logged in', Yii::$app->user->isGuest)->false(); 58 | }); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /tests/codeception/unit/models/UserTest.php: -------------------------------------------------------------------------------- 1 | loadFixtures(['user']); 14 | } 15 | 16 | // TODO add test methods here 17 | } 18 | -------------------------------------------------------------------------------- /tests/codeception/unit/templates/fixtures/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skoro/yii2-admin-template/832cf0f365d78b96879ddddfb3e17d9f239cf698/tests/codeception/unit/templates/fixtures/.gitkeep -------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | # use mod_rewrite for pretty URL support 2 | 3 | RewriteEngine on 4 | 5 | # If a directory or a file exists, use the request directly 6 | RewriteCond %{REQUEST_FILENAME} !-f 7 | RewriteCond %{REQUEST_FILENAME} !-d 8 | 9 | # Otherwise forward the request to index.php 10 | RewriteRule . index.php 11 | -------------------------------------------------------------------------------- /web/assets/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /web/css/admin.css: -------------------------------------------------------------------------------- 1 | /* 2 | Document : admin 3 | Created on : Mar 16, 2016, 10:21:04 AM 4 | Author : skoro 5 | Description: 6 | Backend styles. 7 | */ 8 | 9 | .navbar-nav > .user-menu > .dropdown-menu { 10 | padding: 0 0 0; 11 | } 12 | 13 | .login-box-body .password-request { 14 | text-align: left; 15 | padding: 0 0 15px 0; 16 | } 17 | 18 | .login-page .alert, .register-page .alert { 19 | margin: 10px; 20 | } 21 | 22 | .register-box .account-links { 23 | margin-top: 10px; 24 | } 25 | 26 | .checkbox-list-vert label { 27 | display: block; 28 | } 29 | 30 | ul.pagination { 31 | margin: 0; 32 | } 33 | -------------------------------------------------------------------------------- /web/css/site.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | .wrap { 7 | min-height: 100%; 8 | height: auto; 9 | margin: 0 auto -60px; 10 | padding: 0 0 60px; 11 | } 12 | 13 | .wrap > .container { 14 | padding: 70px 15px 20px; 15 | } 16 | 17 | .footer { 18 | height: 60px; 19 | background-color: #f5f5f5; 20 | border-top: 1px solid #ddd; 21 | padding-top: 20px; 22 | } 23 | 24 | .jumbotron { 25 | text-align: center; 26 | background-color: transparent; 27 | } 28 | 29 | .jumbotron .btn { 30 | font-size: 21px; 31 | padding: 14px 24px; 32 | } 33 | 34 | .not-set { 35 | color: #c55; 36 | font-style: italic; 37 | } 38 | 39 | /* add sorting icons to gridview sort links */ 40 | a.asc:after, a.desc:after { 41 | position: relative; 42 | top: 1px; 43 | display: inline-block; 44 | font-family: 'Glyphicons Halflings'; 45 | font-style: normal; 46 | font-weight: normal; 47 | line-height: 1; 48 | padding-left: 5px; 49 | } 50 | 51 | a.asc:after { 52 | content: /*"\e113"*/ "\e151"; 53 | } 54 | 55 | a.desc:after { 56 | content: /*"\e114"*/ "\e152"; 57 | } 58 | 59 | .sort-numerical a.asc:after { 60 | content: "\e153"; 61 | } 62 | 63 | .sort-numerical a.desc:after { 64 | content: "\e154"; 65 | } 66 | 67 | .sort-ordinal a.asc:after { 68 | content: "\e155"; 69 | } 70 | 71 | .sort-ordinal a.desc:after { 72 | content: "\e156"; 73 | } 74 | 75 | .grid-view th { 76 | white-space: nowrap; 77 | } 78 | 79 | .hint-block { 80 | display: block; 81 | margin-top: 5px; 82 | color: #999; 83 | } 84 | 85 | .error-summary { 86 | color: #a94442; 87 | background: #fdf7f7; 88 | border-left: 3px solid #eed3d7; 89 | padding: 10px 20px; 90 | margin: 0 0 15px 0; 91 | } 92 | -------------------------------------------------------------------------------- /web/images/avatars/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skoro/yii2-admin-template/832cf0f365d78b96879ddddfb3e17d9f239cf698/web/images/avatars/avatar1.png -------------------------------------------------------------------------------- /web/images/avatars/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skoro/yii2-admin-template/832cf0f365d78b96879ddddfb3e17d9f239cf698/web/images/avatars/avatar2.png -------------------------------------------------------------------------------- /web/images/avatars/avatar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skoro/yii2-admin-template/832cf0f365d78b96879ddddfb3e17d9f239cf698/web/images/avatars/avatar3.png -------------------------------------------------------------------------------- /web/images/avatars/avatar4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skoro/yii2-admin-template/832cf0f365d78b96879ddddfb3e17d9f239cf698/web/images/avatars/avatar4.png -------------------------------------------------------------------------------- /web/images/avatars/avatar5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skoro/yii2-admin-template/832cf0f365d78b96879ddddfb3e17d9f239cf698/web/images/avatars/avatar5.png -------------------------------------------------------------------------------- /web/index-test.php: -------------------------------------------------------------------------------- 1 | run(); 17 | -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 | 4 | * @copyright 2016 5 | * @version $Id$ 6 | */ 7 | 8 | define('APPROOT_DIR', dirname(__DIR__)); 9 | 10 | $localConfig = []; 11 | if (file_exists(APPROOT_DIR . '/config.php')) { 12 | $localConfig = require(APPROOT_DIR . '/config.php'); 13 | } 14 | else { 15 | die('config.php is missing.'); 16 | } 17 | 18 | require(APPROOT_DIR . '/vendor/autoload.php'); 19 | require(APPROOT_DIR . '/vendor/yiisoft/yii2/Yii.php'); 20 | 21 | $config = yii\helpers\ArrayHelper::merge( 22 | require(APPROOT_DIR . '/app/config/web.php'), 23 | $localConfig 24 | ); 25 | 26 | $app = new app\base\WebApplication($config); 27 | $app->run(); 28 | --------------------------------------------------------------------------------