├── .bowerrc ├── .gitignore ├── LICENSE.md ├── README.md ├── Vagrantfile ├── assets └── AppAsset.php ├── codeception.yml ├── commands └── HelloController.php ├── composer.json ├── composer.lock ├── config ├── console.php ├── db.php ├── params.php ├── test.php ├── test_db.php └── web.php ├── controllers ├── ArticleController.php └── SiteController.php ├── docker-compose.yml ├── mail └── layouts │ └── html.php ├── models ├── Article.php ├── ArticleSearch.php ├── ContactForm.php ├── LoginForm.php ├── SignupForm.php ├── User.php └── query │ └── ArticleQuery.php ├── requirements.php ├── runtime └── .gitignore ├── tests ├── _bootstrap.php ├── _data │ └── .gitkeep ├── _output │ └── .gitignore ├── _support │ ├── AcceptanceTester.php │ ├── FunctionalTester.php │ └── UnitTester.php ├── acceptance.suite.yml.example ├── acceptance │ ├── AboutCest.php │ ├── ContactCest.php │ ├── HomeCest.php │ ├── LoginCest.php │ └── _bootstrap.php ├── bin │ ├── yii │ └── yii.bat ├── functional.suite.yml ├── functional │ ├── ContactFormCest.php │ ├── LoginFormCest.php │ └── _bootstrap.php ├── unit.suite.yml └── unit │ ├── _bootstrap.php │ └── models │ ├── ContactFormTest.php │ ├── LoginFormTest.php │ └── UserTest.php ├── vagrant ├── config │ ├── .gitignore │ └── vagrant-local.example.yml ├── nginx │ ├── app.conf │ └── log │ │ └── .gitignore └── provision │ ├── always-as-root.sh │ ├── once-as-root.sh │ └── once-as-vagrant.sh ├── views ├── article │ ├── _article_item.php │ ├── _form.php │ ├── _search.php │ ├── create.php │ ├── index.php │ ├── update.php │ └── view.php ├── layouts │ └── main.php └── site │ ├── about.php │ ├── contact.php │ ├── error.php │ ├── index.php │ ├── login.php │ └── register.php ├── web ├── .htaccess ├── assets │ └── .gitignore ├── css │ └── site.css ├── favicon.ico ├── index-test.php ├── index.php └── robots.txt ├── widgets └── Alert.php ├── yii └── yii.bat /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory" : "vendor/bower-asset" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | tests/_output/* 30 | tests/_support/_generated 31 | 32 | #vagrant folder 33 | /.vagrant -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Yii Software LLC nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yii2 minimalistic blog application 2 | 3 | The project is created along with the [Youtube tutorial](https://youtu.be/sRJ6GYiCwkI) 4 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'fileutils' 3 | 4 | required_plugins = %w( vagrant-hostmanager vagrant-vbguest ) 5 | required_plugins.each do |plugin| 6 | exec "vagrant plugin install #{plugin}" unless Vagrant.has_plugin? plugin 7 | end 8 | 9 | domains = { 10 | app: 'yii2basic.test' 11 | } 12 | 13 | vagrantfile_dir_path = File.dirname(__FILE__) 14 | 15 | config = { 16 | local: vagrantfile_dir_path + '/vagrant/config/vagrant-local.yml', 17 | example: vagrantfile_dir_path + '/vagrant/config/vagrant-local.example.yml' 18 | } 19 | 20 | # copy config from example if local config not exists 21 | FileUtils.cp config[:example], config[:local] unless File.exist?(config[:local]) 22 | # read config 23 | options = YAML.load_file config[:local] 24 | 25 | # check github token 26 | if options['github_token'].nil? || options['github_token'].to_s.length != 40 27 | puts "You must place REAL GitHub token into configuration:\n/yii2-app-basic/vagrant/config/vagrant-local.yml" 28 | exit 29 | end 30 | 31 | # vagrant configurate 32 | Vagrant.configure(2) do |config| 33 | # select the box 34 | config.vm.box = 'bento/ubuntu-16.04' 35 | 36 | # should we ask about box updates? 37 | config.vm.box_check_update = options['box_check_update'] 38 | 39 | config.vm.provider 'virtualbox' do |vb| 40 | # machine cpus count 41 | vb.cpus = options['cpus'] 42 | # machine memory size 43 | vb.memory = options['memory'] 44 | # machine name (for VirtualBox UI) 45 | vb.name = options['machine_name'] 46 | end 47 | 48 | # machine name (for vagrant console) 49 | config.vm.define options['machine_name'] 50 | 51 | # machine name (for guest machine console) 52 | config.vm.hostname = options['machine_name'] 53 | 54 | # network settings 55 | config.vm.network 'private_network', ip: options['ip'] 56 | 57 | # sync: folder 'yii2-app-advanced' (host machine) -> folder '/app' (guest machine) 58 | config.vm.synced_folder './', '/app', owner: 'vagrant', group: 'vagrant' 59 | 60 | # disable folder '/vagrant' (guest machine) 61 | config.vm.synced_folder '.', '/vagrant', disabled: true 62 | 63 | # hosts settings (host machine) 64 | config.vm.provision :hostmanager 65 | config.hostmanager.enabled = true 66 | config.hostmanager.manage_host = true 67 | config.hostmanager.ignore_private_ip = false 68 | config.hostmanager.include_offline = true 69 | config.hostmanager.aliases = domains.values 70 | 71 | # quick fix for failed guest additions installations 72 | # config.vbguest.auto_update = false 73 | 74 | # provisioners 75 | config.vm.provision 'shell', path: './vagrant/provision/once-as-root.sh', args: [options['timezone']] 76 | config.vm.provision 'shell', path: './vagrant/provision/once-as-vagrant.sh', args: [options['github_token']], privileged: false 77 | config.vm.provision 'shell', path: './vagrant/provision/always-as-root.sh', run: 'always' 78 | 79 | # post-install message (vagrant console) 80 | config.vm.post_up_message = "App URL: http://#{domains[:app]}" 81 | end 82 | -------------------------------------------------------------------------------- /assets/AppAsset.php: -------------------------------------------------------------------------------- 1 | 16 | * @since 2.0 17 | */ 18 | class AppAsset extends AssetBundle 19 | { 20 | public $basePath = '@webroot'; 21 | public $baseUrl = '@web'; 22 | public $css = [ 23 | 'css/site.css', 24 | ]; 25 | public $js = [ 26 | ]; 27 | public $depends = [ 28 | 'yii\web\YiiAsset', 29 | 'yii\bootstrap\BootstrapAsset', 30 | ]; 31 | } 32 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | paths: 3 | tests: tests 4 | log: tests/_output 5 | data: tests/_data 6 | helpers: tests/_support 7 | settings: 8 | bootstrap: _bootstrap.php 9 | memory_limit: 1024M 10 | colors: true 11 | modules: 12 | config: 13 | Yii2: 14 | configFile: 'config/test.php' 15 | 16 | # To enable code coverage: 17 | #coverage: 18 | # #c3_url: http://localhost:8080/index-test.php/ 19 | # enabled: true 20 | # #remote: true 21 | # #remote_config: '../codeception.yml' 22 | # whitelist: 23 | # include: 24 | # - models/* 25 | # - controllers/* 26 | # - commands/* 27 | # - mail/* 28 | # blacklist: 29 | # include: 30 | # - assets/* 31 | # - config/* 32 | # - runtime/* 33 | # - vendor/* 34 | # - views/* 35 | # - web/* 36 | # - tests/* 37 | -------------------------------------------------------------------------------- /commands/HelloController.php: -------------------------------------------------------------------------------- 1 | 19 | * @since 2.0 20 | */ 21 | class HelloController extends Controller 22 | { 23 | /** 24 | * This command echoes what you have entered as the message. 25 | * @param string $message the message to be echoed. 26 | * @return int Exit code 27 | */ 28 | public function actionIndex($message = 'hello world') 29 | { 30 | echo $message . "\n"; 31 | 32 | return ExitCode::OK; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiisoft/yii2-app-basic", 3 | "description": "Yii 2 Basic Project Template", 4 | "keywords": ["yii2", "framework", "basic", "project template"], 5 | "homepage": "http://www.yiiframework.com/", 6 | "type": "project", 7 | "license": "BSD-3-Clause", 8 | "support": { 9 | "issues": "https://github.com/yiisoft/yii2/issues?state=open", 10 | "forum": "http://www.yiiframework.com/forum/", 11 | "wiki": "http://www.yiiframework.com/wiki/", 12 | "irc": "irc://irc.freenode.net/yii", 13 | "source": "https://github.com/yiisoft/yii2" 14 | }, 15 | "minimum-stability": "stable", 16 | "require": { 17 | "php": ">=5.4.0", 18 | "yiisoft/yii2": "~2.0.14", 19 | "yiisoft/yii2-bootstrap": "~2.0.0", 20 | "yiisoft/yii2-swiftmailer": "~2.0.0 || ~2.1.0" 21 | }, 22 | "require-dev": { 23 | "yiisoft/yii2-debug": "~2.1.0", 24 | "yiisoft/yii2-gii": "~2.1.0", 25 | "yiisoft/yii2-faker": "~2.0.0", 26 | 27 | "codeception/base": "~2.3.0", 28 | "codeception/verify": "~0.4.0", 29 | "codeception/specify": "~0.4.6", 30 | "symfony/browser-kit": ">=2.7 <=4.2.4" 31 | }, 32 | "config": { 33 | "process-timeout": 1800, 34 | "fxp-asset": { 35 | "enabled": false 36 | } 37 | }, 38 | "scripts": { 39 | "post-install-cmd": [ 40 | "yii\\composer\\Installer::postInstall" 41 | ], 42 | "post-create-project-cmd": [ 43 | "yii\\composer\\Installer::postCreateProject", 44 | "yii\\composer\\Installer::postInstall" 45 | ] 46 | }, 47 | "extra": { 48 | "yii\\composer\\Installer::postCreateProject": { 49 | "setPermission": [ 50 | { 51 | "runtime": "0777", 52 | "web/assets": "0777", 53 | "yii": "0755" 54 | } 55 | ] 56 | }, 57 | "yii\\composer\\Installer::postInstall": { 58 | "generateCookieValidationKey": [ 59 | "config/web.php" 60 | ] 61 | } 62 | }, 63 | "repositories": [ 64 | { 65 | "type": "composer", 66 | "url": "https://asset-packagist.org" 67 | } 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /config/console.php: -------------------------------------------------------------------------------- 1 | 'basic-console', 8 | 'basePath' => dirname(__DIR__), 9 | 'bootstrap' => ['log'], 10 | 'controllerNamespace' => 'app\commands', 11 | 'aliases' => [ 12 | '@bower' => '@vendor/bower-asset', 13 | '@npm' => '@vendor/npm-asset', 14 | '@tests' => '@app/tests', 15 | ], 16 | 'components' => [ 17 | 'cache' => [ 18 | 'class' => 'yii\caching\FileCache', 19 | ], 20 | 'log' => [ 21 | 'targets' => [ 22 | [ 23 | 'class' => 'yii\log\FileTarget', 24 | 'levels' => ['error', 'warning'], 25 | ], 26 | ], 27 | ], 28 | 'db' => $db, 29 | ], 30 | 'params' => $params, 31 | /* 32 | 'controllerMap' => [ 33 | 'fixture' => [ // Fixture generation command line. 34 | 'class' => 'yii\faker\FixtureController', 35 | ], 36 | ], 37 | */ 38 | ]; 39 | 40 | if (YII_ENV_DEV) { 41 | // configuration adjustments for 'dev' environment 42 | $config['bootstrap'][] = 'gii'; 43 | $config['modules']['gii'] = [ 44 | 'class' => 'yii\gii\Module', 45 | ]; 46 | } 47 | 48 | return $config; 49 | -------------------------------------------------------------------------------- /config/db.php: -------------------------------------------------------------------------------- 1 | 'yii\db\Connection', 5 | 'dsn' => 'mysql:host=localhost;dbname=yii2_crash_course', 6 | 'username' => 'root', 7 | 'password' => '', 8 | 'charset' => 'utf8', 9 | 10 | // Schema cache options (for production environment) 11 | //'enableSchemaCache' => true, 12 | //'schemaCacheDuration' => 60, 13 | //'schemaCache' => 'cache', 14 | ]; 15 | -------------------------------------------------------------------------------- /config/params.php: -------------------------------------------------------------------------------- 1 | 'admin@example.com', 5 | 'senderEmail' => 'noreply@example.com', 6 | 'senderName' => 'Example.com mailer', 7 | ]; 8 | -------------------------------------------------------------------------------- /config/test.php: -------------------------------------------------------------------------------- 1 | 'basic-tests', 10 | 'basePath' => dirname(__DIR__), 11 | 'aliases' => [ 12 | '@bower' => '@vendor/bower-asset', 13 | '@npm' => '@vendor/npm-asset', 14 | ], 15 | 'language' => 'en-US', 16 | 'components' => [ 17 | 'db' => $db, 18 | 'mailer' => [ 19 | 'useFileTransport' => true, 20 | ], 21 | 'assetManager' => [ 22 | 'basePath' => __DIR__ . '/../web/assets', 23 | ], 24 | 'urlManager' => [ 25 | 'showScriptName' => true, 26 | ], 27 | 'user' => [ 28 | 'identityClass' => 'app\models\User', 29 | ], 30 | 'request' => [ 31 | 'cookieValidationKey' => 'test', 32 | 'enableCsrfValidation' => false, 33 | // but if you absolutely need it set cookie domain to localhost 34 | /* 35 | 'csrfCookie' => [ 36 | 'domain' => 'localhost', 37 | ], 38 | */ 39 | ], 40 | ], 41 | 'params' => $params, 42 | ]; 43 | -------------------------------------------------------------------------------- /config/test_db.php: -------------------------------------------------------------------------------- 1 | 'basic', 8 | 'basePath' => dirname(__DIR__), 9 | 'bootstrap' => ['log'], 10 | 'defaultRoute' => 'article/index', 11 | 'aliases' => [ 12 | '@bower' => '@vendor/bower-asset', 13 | '@npm' => '@vendor/npm-asset', 14 | ], 15 | 'components' => [ 16 | 'request' => [ 17 | // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation 18 | 'cookieValidationKey' => '4zbYHZ2nlRQomO10X7sZnBT2f8Ji7Kiz', 19 | ], 20 | 'cache' => [ 21 | 'class' => 'yii\caching\FileCache', 22 | ], 23 | 'user' => [ 24 | 'identityClass' => 'app\models\User', 25 | 'enableAutoLogin' => true, 26 | ], 27 | 'errorHandler' => [ 28 | 'errorAction' => 'site/error', 29 | ], 30 | 'mailer' => [ 31 | 'class' => 'yii\swiftmailer\Mailer', 32 | // send all mails to a file by default. You have to set 33 | // 'useFileTransport' to false and configure a transport 34 | // for the mailer to send real emails. 35 | 'useFileTransport' => true, 36 | ], 37 | 'log' => [ 38 | 'traceLevel' => YII_DEBUG ? 3 : 0, 39 | 'targets' => [ 40 | [ 41 | 'class' => 'yii\log\FileTarget', 42 | 'levels' => ['error', 'warning'], 43 | ], 44 | ], 45 | ], 46 | 'db' => $db, 47 | 48 | 'urlManager' => [ 49 | 'enablePrettyUrl' => true, 50 | 'showScriptName' => false, 51 | 'rules' => [ 52 | 'article/' => 'article/', 53 | 'article/' => 'article/view' 54 | ], 55 | ], 56 | 'formatter' => [ 57 | 'datetimeFormat' => 'short' 58 | ] 59 | 60 | ], 61 | 'params' => $params, 62 | ]; 63 | 64 | if (YII_ENV_DEV) { 65 | // configuration adjustments for 'dev' environment 66 | $config['bootstrap'][] = 'debug'; 67 | $config['modules']['debug'] = [ 68 | 'class' => 'yii\debug\Module', 69 | // uncomment the following to add your IP if you are not connecting from localhost. 70 | //'allowedIPs' => ['127.0.0.1', '::1'], 71 | ]; 72 | 73 | $config['bootstrap'][] = 'gii'; 74 | $config['modules']['gii'] = [ 75 | 'class' => 'yii\gii\Module', 76 | // uncomment the following to add your IP if you are not connecting from localhost. 77 | //'allowedIPs' => ['127.0.0.1', '::1'], 78 | ]; 79 | } 80 | 81 | return $config; 82 | -------------------------------------------------------------------------------- /controllers/ArticleController.php: -------------------------------------------------------------------------------- 1 | [ 26 | 'class' => AccessControl::class, 27 | 'only' => ['create', 'update', 'delete'], 28 | 'rules' => [ 29 | [ 30 | 'actions' => ['create', 'update', 'delete'], 31 | 'allow' => true, 32 | 'roles' => ['@'] 33 | ] 34 | ] 35 | ], 36 | 'verbs' => [ 37 | 'class' => VerbFilter::className(), 38 | 'actions' => [ 39 | 'delete' => ['POST'], 40 | ], 41 | ], 42 | ]; 43 | } 44 | 45 | /** 46 | * Lists all Article models. 47 | * @return mixed 48 | */ 49 | public function actionIndex() 50 | { 51 | $searchModel = new ArticleSearch(); 52 | $dataProvider = $searchModel->search(Yii::$app->request->queryParams); 53 | 54 | return $this->render('index', [ 55 | 'searchModel' => $searchModel, 56 | 'dataProvider' => $dataProvider, 57 | ]); 58 | } 59 | 60 | /** 61 | * Displays a single Article model. 62 | * @param integer $id 63 | * @return mixed 64 | * @throws NotFoundHttpException if the model cannot be found 65 | */ 66 | public function actionView($slug) 67 | { 68 | return $this->render('view', [ 69 | 'model' => $this->findModel($slug), 70 | ]); 71 | } 72 | 73 | /** 74 | * Creates a new Article model. 75 | * If creation is successful, the browser will be redirected to the 'view' page. 76 | * @return mixed 77 | */ 78 | public function actionCreate() 79 | { 80 | $model = new Article(); 81 | 82 | if ($model->load(Yii::$app->request->post()) && $model->save()) { 83 | return $this->redirect(['view', 'slug' => $model->slug]); 84 | } 85 | 86 | return $this->render('create', [ 87 | 'model' => $model, 88 | ]); 89 | } 90 | 91 | /** 92 | * Updates an existing Article model. 93 | * If update is successful, the browser will be redirected to the 'view' page. 94 | * @param integer $id 95 | * @return mixed 96 | * @throws NotFoundHttpException if the model cannot be found 97 | */ 98 | public function actionUpdate($slug) 99 | { 100 | $model = $this->findModel($slug); 101 | if ($model->created_by !== Yii::$app->user->id){ 102 | throw new ForbiddenHttpException("You do not have permission to edit this article"); 103 | } 104 | 105 | if ($model->load(Yii::$app->request->post()) && $model->save()) { 106 | return $this->redirect(['view', 'slug' => $model->slug]); 107 | } 108 | 109 | return $this->render('update', [ 110 | 'model' => $model, 111 | ]); 112 | } 113 | 114 | /** 115 | * Deletes an existing Article model. 116 | * If deletion is successful, the browser will be redirected to the 'index' page. 117 | * @param integer $id 118 | * @return mixed 119 | * @throws NotFoundHttpException if the model cannot be found 120 | */ 121 | public function actionDelete($id) 122 | { 123 | $model = $this->findModel($id); 124 | if ($model->created_by !== Yii::$app->user->id){ 125 | throw new ForbiddenHttpException("You do not have permission to delete this article"); 126 | } 127 | $model->delete(); 128 | 129 | return $this->redirect(['index']); 130 | } 131 | 132 | /** 133 | * Finds the Article model based on its primary key value. 134 | * If the model is not found, a 404 HTTP exception will be thrown. 135 | * @param integer $id 136 | * @return Article the loaded model 137 | * @throws NotFoundHttpException if the model cannot be found 138 | */ 139 | protected function findModel($slug) 140 | { 141 | if (($model = Article::findOne(['slug' => $slug])) !== null) { 142 | return $model; 143 | } 144 | 145 | throw new NotFoundHttpException('The requested page does not exist.'); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /controllers/SiteController.php: -------------------------------------------------------------------------------- 1 | [ 24 | 'class' => AccessControl::className(), 25 | 'only' => ['logout'], 26 | 'rules' => [ 27 | [ 28 | 'actions' => ['logout'], 29 | 'allow' => true, 30 | 'roles' => ['@'], 31 | ], 32 | ], 33 | ], 34 | 'verbs' => [ 35 | 'class' => VerbFilter::className(), 36 | 'actions' => [ 37 | 'logout' => ['post'], 38 | ], 39 | ], 40 | ]; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function actions() 47 | { 48 | return [ 49 | 'error' => [ 50 | 'class' => 'yii\web\ErrorAction', 51 | ], 52 | 'captcha' => [ 53 | 'class' => 'yii\captcha\CaptchaAction', 54 | 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, 55 | ], 56 | ]; 57 | } 58 | 59 | /** 60 | * Displays homepage. 61 | * 62 | * @return string 63 | */ 64 | public function actionIndex() 65 | { 66 | return $this->render('index'); 67 | } 68 | 69 | /** 70 | * Login action. 71 | * 72 | * @return Response|string 73 | */ 74 | public function actionLogin() 75 | { 76 | if (!Yii::$app->user->isGuest) { 77 | return $this->goHome(); 78 | } 79 | 80 | $model = new LoginForm(); 81 | if ($model->load(Yii::$app->request->post()) && $model->login()) { 82 | return $this->goBack(); 83 | } 84 | 85 | $model->password = ''; 86 | return $this->render('login', [ 87 | 'model' => $model, 88 | ]); 89 | } 90 | 91 | public function actionRegister() 92 | { 93 | $model = new SignupForm(); 94 | 95 | if ($model->load(Yii::$app->request->post()) && $model->signup()){ 96 | Yii::$app->session->addFlash('SIGNUP', 'You have successfully registered'); 97 | return $this->redirect(Yii::$app->homeUrl); 98 | } 99 | 100 | return $this->render('register', [ 101 | 'model' => $model 102 | ]); 103 | } 104 | 105 | /** 106 | * Logout action. 107 | * 108 | * @return Response 109 | */ 110 | public function actionLogout() 111 | { 112 | Yii::$app->user->logout(); 113 | 114 | return $this->goHome(); 115 | } 116 | 117 | /** 118 | * Displays contact page. 119 | * 120 | * @return Response|string 121 | */ 122 | public function actionContact() 123 | { 124 | $model = new ContactForm(); 125 | if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) { 126 | Yii::$app->session->setFlash('contactFormSubmitted'); 127 | 128 | return $this->refresh(); 129 | } 130 | return $this->render('contact', [ 131 | 'model' => $model, 132 | ]); 133 | } 134 | 135 | /** 136 | * Displays about page. 137 | * 138 | * @return string 139 | */ 140 | public function actionAbout() 141 | { 142 | return $this->render('about'); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | php: 4 | image: yiisoftware/yii2-php:7.1-apache 5 | volumes: 6 | - ~/.composer-docker/cache:/root/.composer/cache:delegated 7 | - ./:/app:delegated 8 | ports: 9 | - '8000:80' -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /models/Article.php: -------------------------------------------------------------------------------- 1 | SluggableBehavior::class, 40 | 'attribute' => 'title', 41 | ], 42 | TimestampBehavior::class, 43 | BlameableBehavior::class 44 | ]; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function rules() 51 | { 52 | return [ 53 | [['title', 'body'], 'required'], 54 | [['body'], 'string'], 55 | [['created_at', 'updated_at', 'created_by', 'updated_by'], 'integer'], 56 | [['title', 'slug'], 'string', 'max' => 1024], 57 | [['created_by'], 'exist', 'skipOnError' => true, 'targetClass' => User::className(), 'targetAttribute' => ['created_by' => 'id']], 58 | [['updated_by'], 'exist', 'skipOnError' => true, 'targetClass' => User::className(), 'targetAttribute' => ['updated_by' => 'id']], 59 | ]; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function attributeLabels() 66 | { 67 | return [ 68 | 'id' => 'ID', 69 | 'title' => 'Title', 70 | 'slug' => 'Slug', 71 | 'body' => 'Body', 72 | 'created_at' => 'Created At', 73 | 'updated_at' => 'Updated At', 74 | 'created_by' => 'Created By', 75 | 'updated_by' => 'Updated By', 76 | ]; 77 | } 78 | 79 | /** 80 | * @return \yii\db\ActiveQuery 81 | */ 82 | public function getCreatedBy() 83 | { 84 | return $this->hasOne(User::className(), ['id' => 'created_by']); 85 | } 86 | 87 | /** 88 | * @return \yii\db\ActiveQuery 89 | */ 90 | public function getUpdatedBy() 91 | { 92 | return $this->hasOne(User::className(), ['id' => 'updated_by']); 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | * @return \app\models\query\ArticleQuery the active query used by this AR class. 98 | */ 99 | public static function find() 100 | { 101 | return new \app\models\query\ArticleQuery(get_called_class()); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /models/ArticleSearch.php: -------------------------------------------------------------------------------- 1 | $query, 49 | ]); 50 | 51 | $this->load($params); 52 | 53 | if (!$this->validate()) { 54 | // uncomment the following line if you do not want to return any records when validation fails 55 | // $query->where('0=1'); 56 | return $dataProvider; 57 | } 58 | 59 | // grid filtering conditions 60 | $query->andFilterWhere([ 61 | 'id' => $this->id, 62 | 'created_at' => $this->created_at, 63 | 'updated_at' => $this->updated_at, 64 | 'created_by' => $this->created_by, 65 | 'updated_by' => $this->updated_by, 66 | ]); 67 | 68 | $query->andFilterWhere(['like', 'title', $this->title]) 69 | ->andFilterWhere(['like', 'slug', $this->slug]) 70 | ->andFilterWhere(['like', 'body', $this->body]); 71 | 72 | return $dataProvider; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /models/ContactForm.php: -------------------------------------------------------------------------------- 1 | 'Verification Code', 42 | ]; 43 | } 44 | 45 | /** 46 | * Sends an email to the specified email address using the information collected by this model. 47 | * @param string $email the target email address 48 | * @return bool whether the model passes validation 49 | */ 50 | public function contact($email) 51 | { 52 | if ($this->validate()) { 53 | Yii::$app->mailer->compose() 54 | ->setTo($email) 55 | ->setFrom([Yii::$app->params['senderEmail'] => Yii::$app->params['senderName']]) 56 | ->setReplyTo([$this->email => $this->name]) 57 | ->setSubject($this->subject) 58 | ->setTextBody($this->body) 59 | ->send(); 60 | 61 | return true; 62 | } 63 | return false; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /models/LoginForm.php: -------------------------------------------------------------------------------- 1 | hasErrors()) { 48 | $user = $this->getUser(); 49 | 50 | if (!$user || !$user->validatePassword($this->password)) { 51 | $this->addError($attribute, 'Incorrect username or password.'); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * Logs in a user using the provided username and password. 58 | * @return bool whether the user is logged in successfully 59 | */ 60 | public function login() 61 | { 62 | if ($this->validate()) { 63 | return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); 64 | } 65 | return false; 66 | } 67 | 68 | /** 69 | * Finds user by [[username]] 70 | * 71 | * @return User|null 72 | */ 73 | public function getUser() 74 | { 75 | if ($this->_user === false) { 76 | $this->_user = User::findByUsername($this->username); 77 | } 78 | 79 | return $this->_user; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /models/SignupForm.php: -------------------------------------------------------------------------------- 1 | 18 | * @package app\models 19 | */ 20 | class SignupForm extends Model 21 | { 22 | public $username; 23 | public $password; 24 | public $password_repeat; 25 | 26 | public function rules() 27 | { 28 | return [ 29 | [['username', 'password', 'password_repeat'], 'required'], 30 | ['username', 'string', 'min' => 4, 'max' => 16], 31 | [['password', 'password_repeat'], 'string', 'min' => 8, 'max' => 32], 32 | [['password_repeat'], 'compare', 'compareAttribute' => 'password'] 33 | ]; 34 | } 35 | 36 | public function signup() 37 | { 38 | $user = new User(); 39 | $user->username = $this->username; 40 | $user->auth_key = \Yii::$app->security->generateRandomString(); 41 | $user->access_token = \Yii::$app->security->generateRandomString(); 42 | $user->password = \Yii::$app->security->generatePasswordHash($this->password); 43 | 44 | if ($user->save()){ 45 | return true; 46 | } 47 | 48 | \Yii::error("User was not saved: ".VarDumper::dumpAsString($user->errors)); 49 | return false; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /models/User.php: -------------------------------------------------------------------------------- 1 | 55], 30 | [['password', 'auth_key', 'access_token'], 'string', 'max' => 255], 31 | ]; 32 | } 33 | 34 | public function attributeLabels() 35 | { 36 | return [ 37 | 'id' => 'ID', 38 | 'username' => 'Username', 39 | 'password' => 'Password', 40 | 'auth_key' => 'Auth Key', 41 | 'access_token' => 'Access Token', 42 | ]; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public static function findIdentity($id) 49 | { 50 | return self::findOne($id); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public static function findIdentityByAccessToken($token, $type = null) 57 | { 58 | return self::find()->where(['access_token' => $token]); 59 | } 60 | 61 | /** 62 | * Finds user by username 63 | * 64 | * @param string $username 65 | * @return static|null 66 | */ 67 | public static function findByUsername($username) 68 | { 69 | return User::find()->where(['username' => $username])->one(); 70 | } 71 | 72 | /** 73 | * {@inheritdoc} 74 | */ 75 | public function getId() 76 | { 77 | return $this->id; 78 | } 79 | 80 | /** 81 | * {@inheritdoc} 82 | */ 83 | public function getAuthKey() 84 | { 85 | return $this->auth_key; 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function validateAuthKey($authKey) 92 | { 93 | return $this->auth_key === $authKey; 94 | } 95 | 96 | /** 97 | * Validates password 98 | * 99 | * @param string $password password to validate 100 | * @return bool if password provided is valid for current user 101 | */ 102 | public function validatePassword($password) 103 | { 104 | return \Yii::$app->security->validatePassword($password, $this->password); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /models/query/ArticleQuery.php: -------------------------------------------------------------------------------- 1 | andWhere('[[status]]=1'); 15 | }*/ 16 | 17 | /** 18 | * {@inheritdoc} 19 | * @return \app\models\Article[]|array 20 | */ 21 | public function all($db = null) 22 | { 23 | return parent::all($db); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | * @return \app\models\Article|array|null 29 | */ 30 | public function one($db = null) 31 | { 32 | return parent::one($db); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /requirements.php: -------------------------------------------------------------------------------- 1 | Error\n\n" 33 | . "

The path to yii framework seems to be incorrect.

\n" 34 | . '

You need to install Yii framework via composer or adjust the framework path in file ' . basename(__FILE__) . ".

\n" 35 | . '

Please refer to the README on how to install Yii.

\n"; 36 | 37 | if (!empty($_SERVER['argv'])) { 38 | // do not print HTML when used in console mode 39 | echo strip_tags($message); 40 | } else { 41 | echo $message; 42 | } 43 | exit(1); 44 | } 45 | 46 | require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); 47 | $requirementsChecker = new YiiRequirementChecker(); 48 | 49 | $gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.'; 50 | $gdOK = $imagickOK = false; 51 | 52 | if (extension_loaded('imagick')) { 53 | $imagick = new Imagick(); 54 | $imagickFormats = $imagick->queryFormats('PNG'); 55 | if (in_array('PNG', $imagickFormats)) { 56 | $imagickOK = true; 57 | } else { 58 | $imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.'; 59 | } 60 | } 61 | 62 | if (extension_loaded('gd')) { 63 | $gdInfo = gd_info(); 64 | if (!empty($gdInfo['FreeType Support'])) { 65 | $gdOK = true; 66 | } else { 67 | $gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.'; 68 | } 69 | } 70 | 71 | /** 72 | * Adjust requirements according to your application specifics. 73 | */ 74 | $requirements = array( 75 | // Database : 76 | array( 77 | 'name' => 'PDO extension', 78 | 'mandatory' => true, 79 | 'condition' => extension_loaded('pdo'), 80 | 'by' => 'All DB-related classes', 81 | ), 82 | array( 83 | 'name' => 'PDO SQLite extension', 84 | 'mandatory' => false, 85 | 'condition' => extension_loaded('pdo_sqlite'), 86 | 'by' => 'All DB-related classes', 87 | 'memo' => 'Required for SQLite database.', 88 | ), 89 | array( 90 | 'name' => 'PDO MySQL extension', 91 | 'mandatory' => false, 92 | 'condition' => extension_loaded('pdo_mysql'), 93 | 'by' => 'All DB-related classes', 94 | 'memo' => 'Required for MySQL database.', 95 | ), 96 | array( 97 | 'name' => 'PDO PostgreSQL extension', 98 | 'mandatory' => false, 99 | 'condition' => extension_loaded('pdo_pgsql'), 100 | 'by' => 'All DB-related classes', 101 | 'memo' => 'Required for PostgreSQL database.', 102 | ), 103 | // Cache : 104 | array( 105 | 'name' => 'Memcache extension', 106 | 'mandatory' => false, 107 | 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), 108 | 'by' => 'MemCache', 109 | 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : '' 110 | ), 111 | // CAPTCHA: 112 | array( 113 | 'name' => 'GD PHP extension with FreeType support', 114 | 'mandatory' => false, 115 | 'condition' => $gdOK, 116 | 'by' => 'Captcha', 117 | 'memo' => $gdMemo, 118 | ), 119 | array( 120 | 'name' => 'ImageMagick PHP extension with PNG support', 121 | 'mandatory' => false, 122 | 'condition' => $imagickOK, 123 | 'by' => 'Captcha', 124 | 'memo' => $imagickMemo, 125 | ), 126 | // PHP ini : 127 | 'phpExposePhp' => array( 128 | 'name' => 'Expose PHP', 129 | 'mandatory' => false, 130 | 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), 131 | 'by' => 'Security reasons', 132 | 'memo' => '"expose_php" should be disabled at php.ini', 133 | ), 134 | 'phpAllowUrlInclude' => array( 135 | 'name' => 'PHP allow url include', 136 | 'mandatory' => false, 137 | 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), 138 | 'by' => 'Security reasons', 139 | 'memo' => '"allow_url_include" should be disabled at php.ini', 140 | ), 141 | 'phpSmtp' => array( 142 | 'name' => 'PHP mail SMTP', 143 | 'mandatory' => false, 144 | 'condition' => strlen(ini_get('SMTP')) > 0, 145 | 'by' => 'Email sending', 146 | 'memo' => 'PHP mail SMTP server required', 147 | ), 148 | ); 149 | 150 | // OPcache check 151 | if (!version_compare(phpversion(), '5.5', '>=')) { 152 | $requirements[] = array( 153 | 'name' => 'APC extension', 154 | 'mandatory' => false, 155 | 'condition' => extension_loaded('apc'), 156 | 'by' => 'ApcCache', 157 | ); 158 | } 159 | 160 | $result = $requirementsChecker->checkYii()->check($requirements)->getResult(); 161 | $requirementsChecker->render(); 162 | exit($result['summary']['errors'] === 0 ? 0 : 1); 163 | -------------------------------------------------------------------------------- /runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | amOnPage(Url::toRoute('/site/about')); 10 | $I->see('About', 'h1'); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/acceptance/ContactCest.php: -------------------------------------------------------------------------------- 1 | amOnPage(Url::toRoute('/site/contact')); 10 | } 11 | 12 | public function contactPageWorks(AcceptanceTester $I) 13 | { 14 | $I->wantTo('ensure that contact page works'); 15 | $I->see('Contact', 'h1'); 16 | } 17 | 18 | public function contactFormCanBeSubmitted(AcceptanceTester $I) 19 | { 20 | $I->amGoingTo('submit contact form with correct data'); 21 | $I->fillField('#contactform-name', 'tester'); 22 | $I->fillField('#contactform-email', 'tester@example.com'); 23 | $I->fillField('#contactform-subject', 'test subject'); 24 | $I->fillField('#contactform-body', 'test content'); 25 | $I->fillField('#contactform-verifycode', 'testme'); 26 | 27 | $I->click('contact-button'); 28 | 29 | $I->wait(2); // wait for button to be clicked 30 | 31 | $I->dontSeeElement('#contact-form'); 32 | $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/acceptance/HomeCest.php: -------------------------------------------------------------------------------- 1 | amOnPage(Url::toRoute('/site/index')); 10 | $I->see('My Company'); 11 | 12 | $I->seeLink('About'); 13 | $I->click('About'); 14 | $I->wait(2); // wait for page to be opened 15 | 16 | $I->see('This is the About page.'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/acceptance/LoginCest.php: -------------------------------------------------------------------------------- 1 | amOnPage(Url::toRoute('/site/login')); 10 | $I->see('Login', 'h1'); 11 | 12 | $I->amGoingTo('try to login with correct credentials'); 13 | $I->fillField('input[name="LoginForm[username]"]', 'admin'); 14 | $I->fillField('input[name="LoginForm[password]"]', 'admin'); 15 | $I->click('login-button'); 16 | $I->wait(2); // wait for button to be clicked 17 | 18 | $I->expectTo('see user info'); 19 | $I->see('Logout'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/acceptance/_bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 21 | 'db' => require __DIR__ . '/../../config/test_db.php' 22 | ] 23 | ] 24 | ); 25 | 26 | 27 | $application = new yii\console\Application($config); 28 | $exitCode = $application->run(); 29 | exit($exitCode); 30 | -------------------------------------------------------------------------------- /tests/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/functional.suite.yml: -------------------------------------------------------------------------------- 1 | # Codeception Test Suite Configuration 2 | 3 | # suite for functional (integration) tests. 4 | # emulate web requests and make application process them. 5 | # (tip: better to use with frameworks). 6 | 7 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. 8 | #basic/web/index.php 9 | class_name: FunctionalTester 10 | modules: 11 | enabled: 12 | - Filesystem 13 | - Yii2 14 | -------------------------------------------------------------------------------- /tests/functional/ContactFormCest.php: -------------------------------------------------------------------------------- 1 | amOnPage(['site/contact']); 8 | } 9 | 10 | public function openContactPage(\FunctionalTester $I) 11 | { 12 | $I->see('Contact', 'h1'); 13 | } 14 | 15 | public function submitEmptyForm(\FunctionalTester $I) 16 | { 17 | $I->submitForm('#contact-form', []); 18 | $I->expectTo('see validations errors'); 19 | $I->see('Contact', 'h1'); 20 | $I->see('Name cannot be blank'); 21 | $I->see('Email cannot be blank'); 22 | $I->see('Subject cannot be blank'); 23 | $I->see('Body cannot be blank'); 24 | $I->see('The verification code is incorrect'); 25 | } 26 | 27 | public function submitFormWithIncorrectEmail(\FunctionalTester $I) 28 | { 29 | $I->submitForm('#contact-form', [ 30 | 'ContactForm[name]' => 'tester', 31 | 'ContactForm[email]' => 'tester.email', 32 | 'ContactForm[subject]' => 'test subject', 33 | 'ContactForm[body]' => 'test content', 34 | 'ContactForm[verifyCode]' => 'testme', 35 | ]); 36 | $I->expectTo('see that email address is wrong'); 37 | $I->dontSee('Name cannot be blank', '.help-inline'); 38 | $I->see('Email is not a valid email address.'); 39 | $I->dontSee('Subject cannot be blank', '.help-inline'); 40 | $I->dontSee('Body cannot be blank', '.help-inline'); 41 | $I->dontSee('The verification code is incorrect', '.help-inline'); 42 | } 43 | 44 | public function submitFormSuccessfully(\FunctionalTester $I) 45 | { 46 | $I->submitForm('#contact-form', [ 47 | 'ContactForm[name]' => 'tester', 48 | 'ContactForm[email]' => 'tester@example.com', 49 | 'ContactForm[subject]' => 'test subject', 50 | 'ContactForm[body]' => 'test content', 51 | 'ContactForm[verifyCode]' => 'testme', 52 | ]); 53 | $I->seeEmailIsSent(); 54 | $I->dontSeeElement('#contact-form'); 55 | $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/functional/LoginFormCest.php: -------------------------------------------------------------------------------- 1 | amOnRoute('site/login'); 8 | } 9 | 10 | public function openLoginPage(\FunctionalTester $I) 11 | { 12 | $I->see('Login', 'h1'); 13 | 14 | } 15 | 16 | // demonstrates `amLoggedInAs` method 17 | public function internalLoginById(\FunctionalTester $I) 18 | { 19 | $I->amLoggedInAs(100); 20 | $I->amOnPage('/'); 21 | $I->see('Logout (admin)'); 22 | } 23 | 24 | // demonstrates `amLoggedInAs` method 25 | public function internalLoginByInstance(\FunctionalTester $I) 26 | { 27 | $I->amLoggedInAs(\app\models\User::findByUsername('admin')); 28 | $I->amOnPage('/'); 29 | $I->see('Logout (admin)'); 30 | } 31 | 32 | public function loginWithEmptyCredentials(\FunctionalTester $I) 33 | { 34 | $I->submitForm('#login-form', []); 35 | $I->expectTo('see validations errors'); 36 | $I->see('Username cannot be blank.'); 37 | $I->see('Password cannot be blank.'); 38 | } 39 | 40 | public function loginWithWrongCredentials(\FunctionalTester $I) 41 | { 42 | $I->submitForm('#login-form', [ 43 | 'LoginForm[username]' => 'admin', 44 | 'LoginForm[password]' => 'wrong', 45 | ]); 46 | $I->expectTo('see validations errors'); 47 | $I->see('Incorrect username or password.'); 48 | } 49 | 50 | public function loginSuccessfully(\FunctionalTester $I) 51 | { 52 | $I->submitForm('#login-form', [ 53 | 'LoginForm[username]' => 'admin', 54 | 'LoginForm[password]' => 'admin', 55 | ]); 56 | $I->see('Logout (admin)'); 57 | $I->dontSeeElement('form#login-form'); 58 | } 59 | } -------------------------------------------------------------------------------- /tests/functional/_bootstrap.php: -------------------------------------------------------------------------------- 1 | model = $this->getMockBuilder('app\models\ContactForm') 20 | ->setMethods(['validate']) 21 | ->getMock(); 22 | 23 | $this->model->expects($this->once()) 24 | ->method('validate') 25 | ->willReturn(true); 26 | 27 | $this->model->attributes = [ 28 | 'name' => 'Tester', 29 | 'email' => 'tester@example.com', 30 | 'subject' => 'very important letter subject', 31 | 'body' => 'body of current message', 32 | ]; 33 | 34 | expect_that($this->model->contact('admin@example.com')); 35 | 36 | // using Yii2 module actions to check email was sent 37 | $this->tester->seeEmailIsSent(); 38 | 39 | /** @var MessageInterface $emailMessage */ 40 | $emailMessage = $this->tester->grabLastSentEmail(); 41 | expect('valid email is sent', $emailMessage)->isInstanceOf('yii\mail\MessageInterface'); 42 | expect($emailMessage->getTo())->hasKey('admin@example.com'); 43 | expect($emailMessage->getFrom())->hasKey('noreply@example.com'); 44 | expect($emailMessage->getReplyTo())->hasKey('tester@example.com'); 45 | expect($emailMessage->getSubject())->equals('very important letter subject'); 46 | expect($emailMessage->toString())->contains('body of current message'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/unit/models/LoginFormTest.php: -------------------------------------------------------------------------------- 1 | user->logout(); 14 | } 15 | 16 | public function testLoginNoUser() 17 | { 18 | $this->model = new LoginForm([ 19 | 'username' => 'not_existing_username', 20 | 'password' => 'not_existing_password', 21 | ]); 22 | 23 | expect_not($this->model->login()); 24 | expect_that(\Yii::$app->user->isGuest); 25 | } 26 | 27 | public function testLoginWrongPassword() 28 | { 29 | $this->model = new LoginForm([ 30 | 'username' => 'demo', 31 | 'password' => 'wrong_password', 32 | ]); 33 | 34 | expect_not($this->model->login()); 35 | expect_that(\Yii::$app->user->isGuest); 36 | expect($this->model->errors)->hasKey('password'); 37 | } 38 | 39 | public function testLoginCorrect() 40 | { 41 | $this->model = new LoginForm([ 42 | 'username' => 'demo', 43 | 'password' => 'demo', 44 | ]); 45 | 46 | expect_that($this->model->login()); 47 | expect_not(\Yii::$app->user->isGuest); 48 | expect($this->model->errors)->hasntKey('password'); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /tests/unit/models/UserTest.php: -------------------------------------------------------------------------------- 1 | username)->equals('admin'); 13 | 14 | expect_not(User::findIdentity(999)); 15 | } 16 | 17 | public function testFindUserByAccessToken() 18 | { 19 | expect_that($user = User::findIdentityByAccessToken('100-token')); 20 | expect($user->username)->equals('admin'); 21 | 22 | expect_not(User::findIdentityByAccessToken('non-existing')); 23 | } 24 | 25 | public function testFindUserByUsername() 26 | { 27 | expect_that($user = User::findByUsername('admin')); 28 | expect_not(User::findByUsername('not-admin')); 29 | } 30 | 31 | /** 32 | * @depends testFindUserByUsername 33 | */ 34 | public function testValidateUser($user) 35 | { 36 | $user = User::findByUsername('admin'); 37 | expect_that($user->validateAuthKey('test100key')); 38 | expect_not($user->validateAuthKey('test102key')); 39 | 40 | expect_that($user->validatePassword('admin')); 41 | expect_not($user->validatePassword('123456')); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /vagrant/config/.gitignore: -------------------------------------------------------------------------------- 1 | # local configuration 2 | vagrant-local.yml -------------------------------------------------------------------------------- /vagrant/config/vagrant-local.example.yml: -------------------------------------------------------------------------------- 1 | # Your personal GitHub token 2 | github_token: 3 | # Read more: https://github.com/blog/1509-personal-api-tokens 4 | # You can generate it here: https://github.com/settings/tokens 5 | 6 | # Guest OS timezone 7 | timezone: Europe/London 8 | 9 | # Are we need check box updates for every 'vagrant up'? 10 | box_check_update: false 11 | 12 | # Virtual machine name 13 | machine_name: yii2basic 14 | 15 | # Virtual machine IP 16 | ip: 192.168.83.137 17 | 18 | # Virtual machine CPU cores number 19 | cpus: 1 20 | 21 | # Virtual machine RAM 22 | memory: 1024 23 | -------------------------------------------------------------------------------- /vagrant/nginx/app.conf: -------------------------------------------------------------------------------- 1 | server { 2 | charset utf-8; 3 | client_max_body_size 128M; 4 | sendfile off; 5 | 6 | listen 80; ## listen for ipv4 7 | #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 8 | 9 | server_name yii2basic.test; 10 | root /app/web/; 11 | index index.php; 12 | 13 | access_log /app/vagrant/nginx/log/yii2basic.access.log; 14 | error_log /app/vagrant/nginx/log/yii2basic.error.log; 15 | 16 | location / { 17 | # Redirect everything that isn't a real file to index.php 18 | try_files $uri $uri/ /index.php$is_args$args; 19 | } 20 | 21 | # uncomment to avoid processing of calls to non-existing static files by Yii 22 | #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { 23 | # try_files $uri =404; 24 | #} 25 | #error_page 404 /404.html; 26 | 27 | location ~ \.php$ { 28 | include fastcgi_params; 29 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 30 | #fastcgi_pass 127.0.0.1:9000; 31 | fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; 32 | try_files $uri =404; 33 | } 34 | 35 | location ~ /\.(ht|svn|git) { 36 | deny all; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vagrant/nginx/log/.gitignore: -------------------------------------------------------------------------------- 1 | #nginx logs 2 | yii2basic.access.log 3 | yii2basic.error.log -------------------------------------------------------------------------------- /vagrant/provision/always-as-root.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #== Bash helpers == 4 | 5 | function info { 6 | echo " " 7 | echo "--> $1" 8 | echo " " 9 | } 10 | 11 | #== Provision script == 12 | 13 | info "Provision-script user: `whoami`" 14 | 15 | info "Restart web-stack" 16 | service php7.0-fpm restart 17 | service nginx restart 18 | service mysql restart -------------------------------------------------------------------------------- /vagrant/provision/once-as-root.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #== Import script args == 4 | 5 | timezone=$(echo "$1") 6 | 7 | #== Bash helpers == 8 | 9 | function info { 10 | echo " " 11 | echo "--> $1" 12 | echo " " 13 | } 14 | 15 | #== Provision script == 16 | 17 | info "Provision-script user: `whoami`" 18 | 19 | export DEBIAN_FRONTEND=noninteractive 20 | 21 | info "Configure timezone" 22 | timedatectl set-timezone ${timezone} --no-ask-password 23 | 24 | info "Prepare root password for MySQL" 25 | debconf-set-selections <<< "mariadb-server-10.0 mysql-server/root_password password \"''\"" 26 | debconf-set-selections <<< "mariadb-server-10.0 mysql-server/root_password_again password \"''\"" 27 | echo "Done!" 28 | 29 | info "Update OS software" 30 | apt-get update 31 | apt-get upgrade -y 32 | 33 | info "Install additional software" 34 | apt-get install -y php7.0-curl php7.0-cli php7.0-intl php7.0-mysqlnd php7.0-gd php7.0-fpm php7.0-mbstring php7.0-xml unzip nginx mariadb-server-10.0 php.xdebug 35 | 36 | info "Configure MySQL" 37 | sed -i "s/.*bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/mariadb.conf.d/50-server.cnf 38 | mysql -uroot <<< "CREATE USER 'root'@'%' IDENTIFIED BY ''" 39 | mysql -uroot <<< "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'" 40 | mysql -uroot <<< "DROP USER 'root'@'localhost'" 41 | mysql -uroot <<< "FLUSH PRIVILEGES" 42 | echo "Done!" 43 | 44 | info "Configure PHP-FPM" 45 | sed -i 's/user = www-data/user = vagrant/g' /etc/php/7.0/fpm/pool.d/www.conf 46 | sed -i 's/group = www-data/group = vagrant/g' /etc/php/7.0/fpm/pool.d/www.conf 47 | sed -i 's/owner = www-data/owner = vagrant/g' /etc/php/7.0/fpm/pool.d/www.conf 48 | cat << EOF > /etc/php/7.0/mods-available/xdebug.ini 49 | zend_extension=xdebug.so 50 | xdebug.remote_enable=1 51 | xdebug.remote_connect_back=1 52 | xdebug.remote_port=9000 53 | xdebug.remote_autostart=1 54 | EOF 55 | echo "Done!" 56 | 57 | info "Configure NGINX" 58 | sed -i 's/user www-data/user vagrant/g' /etc/nginx/nginx.conf 59 | echo "Done!" 60 | 61 | info "Enabling site configuration" 62 | ln -s /app/vagrant/nginx/app.conf /etc/nginx/sites-enabled/app.conf 63 | echo "Done!" 64 | 65 | info "Removing default site configuration" 66 | rm /etc/nginx/sites-enabled/default 67 | echo "Done!" 68 | 69 | info "Initailize databases for MySQL" 70 | mysql -uroot <<< "CREATE DATABASE yii2basic" 71 | mysql -uroot <<< "CREATE DATABASE yii2basic_test" 72 | echo "Done!" 73 | 74 | info "Install composer" 75 | curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer -------------------------------------------------------------------------------- /vagrant/provision/once-as-vagrant.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #== Import script args == 4 | 5 | github_token=$(echo "$1") 6 | 7 | #== Bash helpers == 8 | 9 | function info { 10 | echo " " 11 | echo "--> $1" 12 | echo " " 13 | } 14 | 15 | #== Provision script == 16 | 17 | info "Provision-script user: `whoami`" 18 | 19 | info "Configure composer" 20 | composer config --global github-oauth.github.com ${github_token} 21 | echo "Done!" 22 | 23 | info "Install project dependencies" 24 | cd /app 25 | composer --no-progress --prefer-dist install 26 | 27 | info "Create bash-alias 'app' for vagrant user" 28 | echo 'alias app="cd /app"' | tee /home/vagrant/.bash_aliases 29 | 30 | info "Enabling colorized prompt for guest console" 31 | sed -i "s/#force_color_prompt=yes/force_color_prompt=yes/" /home/vagrant/.bashrc 32 | -------------------------------------------------------------------------------- /views/article/_article_item.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 12 |

title) ?>

13 |
14 |
15 | body), 40) ?> 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /views/article/_form.php: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 14 | 15 | field($model, 'title')->textInput(['maxlength' => true]) ?> 16 | 17 | field($model, 'slug')->textInput(['maxlength' => true]) ?> 18 | 19 | field($model, 'body')->textarea(['rows' => 6]) ?> 20 | 21 |
22 | 'btn btn-success']) ?> 23 |
24 | 25 | 26 | 27 |
28 | -------------------------------------------------------------------------------- /views/article/_search.php: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | ['index'], 15 | 'method' => 'get', 16 | ]); ?> 17 | 18 | field($model, 'id') ?> 19 | 20 | field($model, 'title') ?> 21 | 22 | field($model, 'slug') ?> 23 | 24 | field($model, 'body') ?> 25 | 26 | field($model, 'created_at') ?> 27 | 28 | field($model, 'updated_at') ?> 29 | 30 | field($model, 'created_by') ?> 31 | 32 | field($model, 'updated_by') ?> 33 | 34 |
35 | 'btn btn-primary']) ?> 36 | 'btn btn-outline-secondary']) ?> 37 |
38 | 39 | 40 | 41 |
42 | -------------------------------------------------------------------------------- /views/article/create.php: -------------------------------------------------------------------------------- 1 | title = 'Create Article'; 9 | $this->params['breadcrumbs'][] = ['label' => 'Articles', 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 | 14 |

title) ?>

15 | 16 | render('_form', [ 17 | 'model' => $model, 18 | ]) ?> 19 | 20 |
21 | -------------------------------------------------------------------------------- /views/article/index.php: -------------------------------------------------------------------------------- 1 | title = 'Articles'; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 | user->isGuest): ?> 18 |

19 | 'btn btn-success']) ?> 20 |

21 | 22 | 23 | render('_search', ['model' => $searchModel]); ?> 24 | 25 | $dataProvider, 27 | // 'filterModel' => $searchModel, 28 | 'itemView' => '_article_item' 29 | ]); ?> 30 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /views/article/update.php: -------------------------------------------------------------------------------- 1 | title = 'Update Article: ' . $model->title; 9 | $this->params['breadcrumbs'][] = ['label' => 'Articles', 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = ['label' => $model->title, 'url' => ['view', 'id' => $model->id]]; 11 | $this->params['breadcrumbs'][] = 'Update'; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 | render('_form', [ 18 | 'model' => $model, 19 | ]) ?> 20 | 21 |
22 | -------------------------------------------------------------------------------- /views/article/view.php: -------------------------------------------------------------------------------- 1 | title = $model->title; 10 | $this->params['breadcrumbs'][] = ['label' => 'Articles', 'url' => ['index']]; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | \yii\web\YiiAsset::register($this); 13 | ?> 14 |
15 | 16 | user->isGuest && Yii::$app->user->id === $model->created_by): ?> 17 |

18 | $model->slug], ['class' => 'btn btn-primary']) ?> 19 | $model->id], [ 20 | 'class' => 'btn btn-danger', 21 | 'data' => [ 22 | 'confirm' => 'Are you sure you want to delete this item?', 23 | 'method' => 'post', 24 | ], 25 | ]) ?> 26 |

27 | 28 | 29 |
30 |

title) ?>

31 |

32 | 33 | Created at: formatter->asRelativeTime($model->created_at) ?> 34 | By: createdBy->username ?> 35 | 36 |

37 |
38 | body); ?> 39 |
40 |
41 | 42 |
43 | -------------------------------------------------------------------------------- /views/layouts/main.php: -------------------------------------------------------------------------------- 1 | 15 | beginPage() ?> 16 | 17 | 18 | 19 | 20 | 21 | 22 | registerCsrfMetaTags() ?> 23 | <?= Html::encode($this->title) ?> 24 | head() ?> 25 | 26 | 27 | beginBody() ?> 28 | 29 |
30 | Yii::$app->name, 33 | 'brandUrl' => Yii::$app->homeUrl, 34 | 'options' => [ 35 | 'class' => 'navbar-inverse navbar-fixed-top', 36 | ], 37 | ]); 38 | echo Nav::widget([ 39 | 'options' => ['class' => 'navbar-nav navbar-right'], 40 | 'items' => [ 41 | ['label' => 'Home', 'url' => [Yii::$app->homeUrl]], 42 | Yii::$app->user->isGuest ? ( 43 | ['label' => 'Login', 'url' => ['/site/login']] 44 | ) : ( 45 | '
  • ' 46 | . Html::beginForm(['/site/logout'], 'post') 47 | . Html::submitButton( 48 | 'Logout (' . Yii::$app->user->identity->username . ')', 49 | ['class' => 'btn btn-link logout'] 50 | ) 51 | . Html::endForm() 52 | . '
  • ' 53 | ), 54 | Yii::$app->user->isGuest ? ( 55 | ['label' => 'Register', 'url' => ['/site/register']] 56 | ) : '' 57 | ], 58 | ]); 59 | NavBar::end(); 60 | ?> 61 | 62 |
    63 | isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], 65 | ]) ?> 66 | 67 | 68 |
    69 |
    70 | 71 |
    72 |
    73 |

    © My Company

    74 | 75 |

    76 |
    77 |
    78 | 79 | endBody() ?> 80 | 81 | 82 | endPage() ?> 83 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /views/site/error.php: -------------------------------------------------------------------------------- 1 | title = $name; 11 | ?> 12 |
    13 | 14 |

    title) ?>

    15 | 16 |
    17 | 18 |
    19 | 20 |

    21 | The above error occurred while the Web server was processing your request. 22 |

    23 |

    24 | Please contact us if you think this is a server error. Thank you. 25 |

    26 | 27 |
    28 | -------------------------------------------------------------------------------- /views/site/index.php: -------------------------------------------------------------------------------- 1 | title = 'My Yii Application'; 6 | ?> 7 |
    8 | 9 |
    10 |

    Congratulations!

    11 | 12 |

    You have successfully created your Yii-powered application.

    13 | 14 |

    Get started with Yii

    15 |
    16 | 17 |
    18 | 19 |
    20 |
    21 |

    Heading

    22 | 23 |

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et 24 | dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip 25 | ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 26 | fugiat nulla pariatur.

    27 | 28 |

    Yii Documentation »

    29 |
    30 |
    31 |

    Heading

    32 | 33 |

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et 34 | dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip 35 | ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 36 | fugiat nulla pariatur.

    37 | 38 |

    Yii Forum »

    39 |
    40 |
    41 |

    Heading

    42 | 43 |

    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et 44 | dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip 45 | ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 46 | fugiat nulla pariatur.

    47 | 48 |

    Yii Extensions »

    49 |
    50 |
    51 | 52 |
    53 |
    54 | -------------------------------------------------------------------------------- /views/site/login.php: -------------------------------------------------------------------------------- 1 | title = 'Login'; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 | 48 | -------------------------------------------------------------------------------- /views/site/register.php: -------------------------------------------------------------------------------- 1 | 12 | 13 |
    14 |

    title) ?>

    15 | 16 |

    Please fill out the following fields to login:

    17 | 18 | 'register-form', 20 | 'layout' => 'horizontal', 21 | 'fieldConfig' => [ 22 | 'template' => "{label}\n
    {input}
    \n
    {error}
    ", 23 | 'labelOptions' => ['class' => 'col-lg-2 control-label'], 24 | ], 25 | ]); ?> 26 | 27 | field($model, 'username')->textInput(['autofocus' => true]) ?> 28 | 29 | field($model, 'password')->passwordInput() ?> 30 | field($model, 'password_repeat')->passwordInput() ?> 31 | 32 |
    33 |
    34 | 'btn btn-primary', 'name' => 'login-button']) ?> 35 |
    36 |
    37 | 38 | 39 | 40 |
    41 | -------------------------------------------------------------------------------- /web/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | RewriteCond %{REQUEST_FILENAME} !-d 3 | RewriteCond %{REQUEST_FILENAME} !-f 4 | RewriteRule . index.php [L] 5 | -------------------------------------------------------------------------------- /web/assets/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /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 | 93 | /* align the logout "link" (button in form) of the navbar */ 94 | .nav li > form > button.logout { 95 | padding: 15px; 96 | border: none; 97 | } 98 | 99 | @media(max-width:767px) { 100 | .nav li > form > button.logout { 101 | display:block; 102 | text-align: left; 103 | width: 100%; 104 | padding: 10px 15px; 105 | } 106 | } 107 | 108 | .nav > li > form > button.logout:focus, 109 | .nav > li > form > button.logout:hover { 110 | text-decoration: none; 111 | } 112 | 113 | .nav > li > form > button.logout:focus { 114 | outline: none; 115 | } 116 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecodeholic/Yii2CrashCourse/421a62c5666b664b9452a8e23ccec3e0c21bcc92/web/favicon.ico -------------------------------------------------------------------------------- /web/index-test.php: -------------------------------------------------------------------------------- 1 | run(); 17 | -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 | run(); 13 | -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: -------------------------------------------------------------------------------- /widgets/Alert.php: -------------------------------------------------------------------------------- 1 | session->setFlash('error', 'This is the message'); 12 | * Yii::$app->session->setFlash('success', 'This is the message'); 13 | * Yii::$app->session->setFlash('info', 'This is the message'); 14 | * ``` 15 | * 16 | * Multiple messages could be set as follows: 17 | * 18 | * ```php 19 | * Yii::$app->session->setFlash('error', ['Error 1', 'Error 2']); 20 | * ``` 21 | * 22 | * @author Kartik Visweswaran 23 | * @author Alexander Makarov 24 | */ 25 | class Alert extends \yii\bootstrap\Widget 26 | { 27 | /** 28 | * @var array the alert types configuration for the flash messages. 29 | * This array is setup as $key => $value, where: 30 | * - key: the name of the session flash variable 31 | * - value: the bootstrap alert type (i.e. danger, success, info, warning) 32 | */ 33 | public $alertTypes = [ 34 | 'error' => 'alert-danger', 35 | 'danger' => 'alert-danger', 36 | 'success' => 'alert-success', 37 | 'info' => 'alert-info', 38 | 'warning' => 'alert-warning' 39 | ]; 40 | /** 41 | * @var array the options for rendering the close button tag. 42 | * Array will be passed to [[\yii\bootstrap\Alert::closeButton]]. 43 | */ 44 | public $closeButton = []; 45 | 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function run() 51 | { 52 | $session = Yii::$app->session; 53 | $flashes = $session->getAllFlashes(); 54 | $appendClass = isset($this->options['class']) ? ' ' . $this->options['class'] : ''; 55 | 56 | foreach ($flashes as $type => $flash) { 57 | if (!isset($this->alertTypes[$type])) { 58 | continue; 59 | } 60 | 61 | foreach ((array) $flash as $i => $message) { 62 | echo \yii\bootstrap\Alert::widget([ 63 | 'body' => $message, 64 | 'closeButton' => $this->closeButton, 65 | 'options' => array_merge($this->options, [ 66 | 'id' => $this->getId() . '-' . $type . '-' . $i, 67 | 'class' => $this->alertTypes[$type] . $appendClass, 68 | ]), 69 | ]); 70 | } 71 | 72 | $session->removeFlash($type); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 21 | exit($exitCode); 22 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------