├── .bowerrc ├── .gitignore ├── LICENSE.md ├── README.md ├── assets └── AppAsset.php ├── commands └── HelloController.php ├── composer.json ├── composer.lock ├── config ├── console.php ├── db.php ├── params.php └── web.php ├── controllers ├── ReceiptController.php └── SiteController.php ├── mail └── layouts │ └── html.php ├── migrations └── m150204_103949_create_receipt_tables.php ├── models ├── ContactForm.php ├── LoginForm.php ├── Receipt.php ├── ReceiptDetail.php └── User.php ├── requirements.php ├── runtime └── .gitignore ├── tests ├── README.md ├── codeception.yml └── codeception │ ├── .gitignore │ ├── _bootstrap.php │ ├── _output │ └── .gitignore │ ├── _pages │ ├── AboutPage.php │ ├── ContactPage.php │ ├── LoginPage.php │ └── ReceiptPage.php │ ├── acceptance.suite.yml │ ├── acceptance │ ├── AboutCept.php │ ├── ContactCept.php │ ├── HomeCept.php │ ├── LoginCept.php │ ├── ReceiptCept.php │ └── _bootstrap.php │ ├── bin │ ├── _bootstrap.php │ ├── yii │ └── yii.bat │ ├── config │ ├── acceptance.php │ ├── config.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 ├── views ├── layouts │ └── main.php ├── receipt │ ├── _form.php │ ├── create.php │ ├── index.php │ ├── update.php │ └── view.php └── site │ ├── about.php │ ├── contact.php │ ├── error.php │ ├── index.php │ └── login.php ├── web ├── assets │ └── .gitignore ├── css │ └── site.css ├── favicon.ico ├── index-test.php ├── index.php └── robots.txt ├── yii └── yii.bat /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory" : "vendor/bower" 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 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The Yii framework is free software. It is released under the terms of 2 | the following BSD License. 3 | 4 | Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions 9 | are met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | * Neither the name of Yii Software LLC nor the names of its 18 | contributors may be used to endorse or promote products derived 19 | from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii2 Dynamic Tabular Form App 2 | ================================ 3 | 4 | This is a Demonstration on how to implement dynamic tabular forms. 5 | This application is built on top of [Yii2 Basic application template](https://github.com/yiisoft/yii2-app-basic). 6 | Please refer to the basic app for more installation details and requirements such as composer 7 | The dynamic addition of rows in this app does not actually implement javascript and instead uses a `addRow` button to refresh the page with a new row 8 | 9 | 10 | Receipt Controller 11 | ------------ 12 | 13 | After installation: 14 | - run the migration using 15 | ``` 16 | php yii migrate 17 | ``` 18 | - Access the receipt CRUD in 19 | ``` 20 | http://localhost/yii2-dynamic-tabular-form-app/web/?r=receipt 21 | ``` 22 | 23 | ![Form](http://snag.gy/dMPxm.jpg) 24 | 25 | Running Tests 26 | ----------- 27 | Requirements 28 | - Firefox 29 | - Selenium 30 | 31 | Configuration 32 | modify the `tests/codeception/acceptance.suite.yml` 33 | ``` 34 | config: 35 | WebDriver: 36 | url: 'http://127.0.0.1/yii2-dynamic-tabular-form-app/web/' 37 | browser: firefox 38 | restart: true 39 | clear_cookies: true 40 | ``` 41 | with your necessary settings 42 | -------------------------------------------------------------------------------- /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 | ]; 23 | public $js = [ 24 | ]; 25 | public $depends = [ 26 | 'yii\web\YiiAsset', 27 | 'yii\bootstrap\BootstrapAsset', 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /commands/HelloController.php: -------------------------------------------------------------------------------- 1 | 18 | * @since 2.0 19 | */ 20 | class HelloController extends Controller 21 | { 22 | /** 23 | * This command echoes what you have entered as the message. 24 | * @param string $message the message to be echoed. 25 | */ 26 | public function actionIndex($message = 'hello world') 27 | { 28 | echo $message . "\n"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiisoft/yii2-app-basic", 3 | "description": "Yii 2 Basic Application Template", 4 | "keywords": ["yii2", "framework", "basic", "application 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": "dev", 16 | "require": { 17 | "php": ">=5.4.0", 18 | "yiisoft/yii2": "*", 19 | "yiisoft/yii2-bootstrap": "*", 20 | "yiisoft/yii2-swiftmailer": "*" 21 | }, 22 | "require-dev": { 23 | "yiisoft/yii2-codeception": "*", 24 | "yiisoft/yii2-debug": "*", 25 | "yiisoft/yii2-gii": "*", 26 | "yiisoft/yii2-faker": "*" 27 | }, 28 | "config": { 29 | "process-timeout": 1800 30 | }, 31 | "scripts": { 32 | "post-create-project-cmd": [ 33 | "yii\\composer\\Installer::postCreateProject" 34 | ] 35 | }, 36 | "extra": { 37 | "yii\\composer\\Installer::postCreateProject": { 38 | "setPermission": [ 39 | { 40 | "runtime": "0777", 41 | "web/assets": "0777", 42 | "yii": "0755" 43 | } 44 | ], 45 | "generateCookieValidationKey": [ 46 | "config/web.php" 47 | ] 48 | }, 49 | "asset-installer-paths": { 50 | "npm-asset-library": "vendor/npm", 51 | "bower-asset-library": "vendor/bower" 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "10d8d3b92d960bec820ae06a5e92ecc8", 8 | "packages": [ 9 | { 10 | "name": "bower-asset/bootstrap", 11 | "version": "v3.3.2", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/twbs/bootstrap.git", 15 | "reference": "bcf7dd38b5ab180256e2e4fb5da0369551b3f082" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/twbs/bootstrap/zipball/bcf7dd38b5ab180256e2e4fb5da0369551b3f082", 20 | "reference": "bcf7dd38b5ab180256e2e4fb5da0369551b3f082", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "bower-asset/jquery": ">=1.9.1" 25 | }, 26 | "type": "bower-asset-library", 27 | "extra": { 28 | "bower-asset-main": [ 29 | "less/bootstrap.less", 30 | "dist/css/bootstrap.css", 31 | "dist/js/bootstrap.js", 32 | "dist/fonts/glyphicons-halflings-regular.eot", 33 | "dist/fonts/glyphicons-halflings-regular.svg", 34 | "dist/fonts/glyphicons-halflings-regular.ttf", 35 | "dist/fonts/glyphicons-halflings-regular.woff" 36 | ], 37 | "bower-asset-ignore": [ 38 | "/.*", 39 | "_config.yml", 40 | "CNAME", 41 | "composer.json", 42 | "CONTRIBUTING.md", 43 | "docs", 44 | "js/tests", 45 | "test-infra" 46 | ] 47 | }, 48 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 49 | "keywords": [ 50 | "css", 51 | "framework", 52 | "front-end", 53 | "js", 54 | "less", 55 | "mobile-first", 56 | "responsive", 57 | "web" 58 | ] 59 | }, 60 | { 61 | "name": "bower-asset/jquery", 62 | "version": "2.1.3", 63 | "source": { 64 | "type": "git", 65 | "url": "https://github.com/jquery/jquery.git", 66 | "reference": "8f2a9d9272d6ed7f32d3a484740ab342c02541e0" 67 | }, 68 | "dist": { 69 | "type": "zip", 70 | "url": "https://api.github.com/repos/jquery/jquery/zipball/8f2a9d9272d6ed7f32d3a484740ab342c02541e0", 71 | "reference": "8f2a9d9272d6ed7f32d3a484740ab342c02541e0", 72 | "shasum": "" 73 | }, 74 | "require-dev": { 75 | "bower-asset/qunit": "1.14.0", 76 | "bower-asset/requirejs": "2.1.10", 77 | "bower-asset/sinon": "1.8.1", 78 | "bower-asset/sizzle": "2.1.1-patch2" 79 | }, 80 | "type": "bower-asset-library", 81 | "extra": { 82 | "bower-asset-main": "dist/jquery.js", 83 | "bower-asset-ignore": [ 84 | "**/.*", 85 | "build", 86 | "speed", 87 | "test", 88 | "*.md", 89 | "AUTHORS.txt", 90 | "Gruntfile.js", 91 | "package.json" 92 | ] 93 | }, 94 | "license": [ 95 | "MIT" 96 | ], 97 | "keywords": [ 98 | "javascript", 99 | "jquery", 100 | "library" 101 | ] 102 | }, 103 | { 104 | "name": "bower-asset/jquery.inputmask", 105 | "version": "3.1.61", 106 | "source": { 107 | "type": "git", 108 | "url": "https://github.com/RobinHerbots/jquery.inputmask.git", 109 | "reference": "f2c086411d2557fc485c47afb3cecfa6c1de9ee2" 110 | }, 111 | "dist": { 112 | "type": "zip", 113 | "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/f2c086411d2557fc485c47afb3cecfa6c1de9ee2", 114 | "reference": "f2c086411d2557fc485c47afb3cecfa6c1de9ee2", 115 | "shasum": "" 116 | }, 117 | "require": { 118 | "bower-asset/jquery": ">=1.7" 119 | }, 120 | "type": "bower-asset-library", 121 | "extra": { 122 | "bower-asset-main": [ 123 | "./dist/inputmask/jquery.inputmask.js", 124 | "./dist/inputmask/jquery.inputmask.extensions.js", 125 | "./dist/inputmask/jquery.inputmask.date.extensions.js", 126 | "./dist/inputmask/jquery.inputmask.numeric.extensions.js", 127 | "./dist/inputmask/jquery.inputmask.phone.extensions.js", 128 | "./dist/inputmask/jquery.inputmask.regex.extensions.js" 129 | ], 130 | "bower-asset-ignore": [ 131 | "**/.*", 132 | "qunit/", 133 | "nuget/", 134 | "tools/", 135 | "js/", 136 | "*.md", 137 | "build.properties", 138 | "build.xml", 139 | "jquery.inputmask.jquery.json" 140 | ] 141 | }, 142 | "license": [ 143 | "http://opensource.org/licenses/mit-license.php" 144 | ], 145 | "description": "jquery.inputmask is a jquery plugin which create an input mask.", 146 | "keywords": [ 147 | "form", 148 | "input", 149 | "inputmask", 150 | "jquery", 151 | "mask", 152 | "plugins" 153 | ] 154 | }, 155 | { 156 | "name": "bower-asset/punycode", 157 | "version": "v1.3.2", 158 | "source": { 159 | "type": "git", 160 | "url": "https://github.com/bestiejs/punycode.js.git", 161 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" 162 | }, 163 | "dist": { 164 | "type": "zip", 165 | "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", 166 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", 167 | "shasum": "" 168 | }, 169 | "type": "bower-asset-library", 170 | "extra": { 171 | "bower-asset-main": "punycode.js", 172 | "bower-asset-ignore": [ 173 | "coverage", 174 | "tests", 175 | ".*", 176 | "component.json", 177 | "Gruntfile.js", 178 | "node_modules", 179 | "package.json" 180 | ] 181 | } 182 | }, 183 | { 184 | "name": "bower-asset/yii2-pjax", 185 | "version": "dev-master", 186 | "source": { 187 | "type": "git", 188 | "url": "https://github.com/yiisoft/jquery-pjax.git", 189 | "reference": "b7491aec282bfd2e78faf33b18df865299b88d36" 190 | }, 191 | "dist": { 192 | "type": "zip", 193 | "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/b7491aec282bfd2e78faf33b18df865299b88d36", 194 | "reference": "b7491aec282bfd2e78faf33b18df865299b88d36", 195 | "shasum": "" 196 | }, 197 | "require": { 198 | "bower-asset/jquery": ">=1.8" 199 | }, 200 | "type": "bower-asset-library", 201 | "extra": { 202 | "bower-asset-main": "./jquery.pjax.js", 203 | "bower-asset-ignore": [ 204 | ".travis.yml", 205 | "Gemfile", 206 | "Gemfile.lock", 207 | "vendor/", 208 | "script/", 209 | "test/" 210 | ] 211 | }, 212 | "license": [ 213 | "MIT" 214 | ] 215 | }, 216 | { 217 | "name": "cebe/markdown", 218 | "version": "dev-master", 219 | "source": { 220 | "type": "git", 221 | "url": "https://github.com/cebe/markdown.git", 222 | "reference": "f89dc1da1fc6823f0286d6cad736a642efd0f59e" 223 | }, 224 | "dist": { 225 | "type": "zip", 226 | "url": "https://api.github.com/repos/cebe/markdown/zipball/f89dc1da1fc6823f0286d6cad736a642efd0f59e", 227 | "reference": "f89dc1da1fc6823f0286d6cad736a642efd0f59e", 228 | "shasum": "" 229 | }, 230 | "require": { 231 | "lib-pcre": "*", 232 | "php": ">=5.4.0" 233 | }, 234 | "require-dev": { 235 | "cebe/indent": "*", 236 | "facebook/xhprof": "*@dev", 237 | "phpunit/phpunit": "3.7.*" 238 | }, 239 | "bin": [ 240 | "bin/markdown" 241 | ], 242 | "type": "library", 243 | "extra": { 244 | "branch-alias": { 245 | "dev-master": "1.0.x-dev" 246 | } 247 | }, 248 | "autoload": { 249 | "psr-4": { 250 | "cebe\\markdown\\": "" 251 | } 252 | }, 253 | "notification-url": "https://packagist.org/downloads/", 254 | "license": [ 255 | "MIT" 256 | ], 257 | "authors": [ 258 | { 259 | "name": "Carsten Brandt", 260 | "email": "mail@cebe.cc", 261 | "homepage": "http://cebe.cc/", 262 | "role": "Creator" 263 | } 264 | ], 265 | "description": "A super fast, highly extensible markdown parser for PHP", 266 | "homepage": "https://github.com/cebe/markdown#readme", 267 | "keywords": [ 268 | "extensible", 269 | "fast", 270 | "gfm", 271 | "markdown", 272 | "markdown-extra" 273 | ], 274 | "time": "2014-12-18 00:45:32" 275 | }, 276 | { 277 | "name": "ezyang/htmlpurifier", 278 | "version": "v4.6.0", 279 | "source": { 280 | "type": "git", 281 | "url": "https://github.com/ezyang/htmlpurifier.git", 282 | "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd" 283 | }, 284 | "dist": { 285 | "type": "zip", 286 | "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/6f389f0f25b90d0b495308efcfa073981177f0fd", 287 | "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd", 288 | "shasum": "" 289 | }, 290 | "require": { 291 | "php": ">=5.2" 292 | }, 293 | "type": "library", 294 | "autoload": { 295 | "psr-0": { 296 | "HTMLPurifier": "library/" 297 | }, 298 | "files": [ 299 | "library/HTMLPurifier.composer.php" 300 | ] 301 | }, 302 | "notification-url": "https://packagist.org/downloads/", 303 | "license": [ 304 | "LGPL" 305 | ], 306 | "authors": [ 307 | { 308 | "name": "Edward Z. Yang", 309 | "email": "admin@htmlpurifier.org", 310 | "homepage": "http://ezyang.com" 311 | } 312 | ], 313 | "description": "Standards compliant HTML filter written in PHP", 314 | "homepage": "http://htmlpurifier.org/", 315 | "keywords": [ 316 | "html" 317 | ], 318 | "time": "2013-11-30 08:25:19" 319 | }, 320 | { 321 | "name": "swiftmailer/swiftmailer", 322 | "version": "dev-master", 323 | "source": { 324 | "type": "git", 325 | "url": "https://github.com/swiftmailer/swiftmailer.git", 326 | "reference": "db95cfa68a8bc91d1c54f75c416f481c9a8bd100" 327 | }, 328 | "dist": { 329 | "type": "zip", 330 | "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/db95cfa68a8bc91d1c54f75c416f481c9a8bd100", 331 | "reference": "db95cfa68a8bc91d1c54f75c416f481c9a8bd100", 332 | "shasum": "" 333 | }, 334 | "require": { 335 | "php": ">=5.3.3" 336 | }, 337 | "require-dev": { 338 | "mockery/mockery": "~0.9.1" 339 | }, 340 | "type": "library", 341 | "extra": { 342 | "branch-alias": { 343 | "dev-master": "5.3-dev" 344 | } 345 | }, 346 | "autoload": { 347 | "files": [ 348 | "lib/swift_required.php" 349 | ] 350 | }, 351 | "notification-url": "https://packagist.org/downloads/", 352 | "license": [ 353 | "MIT" 354 | ], 355 | "authors": [ 356 | { 357 | "name": "Chris Corbyn" 358 | }, 359 | { 360 | "name": "Fabien Potencier", 361 | "email": "fabien@symfony.com" 362 | } 363 | ], 364 | "description": "Swiftmailer, free feature-rich PHP mailer", 365 | "homepage": "http://swiftmailer.org", 366 | "keywords": [ 367 | "mail", 368 | "mailer" 369 | ], 370 | "time": "2015-01-18 13:49:36" 371 | }, 372 | { 373 | "name": "yiisoft/yii2", 374 | "version": "dev-master", 375 | "source": { 376 | "type": "git", 377 | "url": "https://github.com/yiisoft/yii2-framework.git", 378 | "reference": "b95c0de6191b2c438eecad4e6cffca0fe4c7c640" 379 | }, 380 | "dist": { 381 | "type": "zip", 382 | "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/b95c0de6191b2c438eecad4e6cffca0fe4c7c640", 383 | "reference": "b95c0de6191b2c438eecad4e6cffca0fe4c7c640", 384 | "shasum": "" 385 | }, 386 | "require": { 387 | "bower-asset/jquery": "2.1.*@stable | 1.11.*@stable", 388 | "bower-asset/jquery.inputmask": "3.1.*", 389 | "bower-asset/punycode": "1.3.*", 390 | "bower-asset/yii2-pjax": ">=2.0.1", 391 | "cebe/markdown": "~1.0.0", 392 | "ext-mbstring": "*", 393 | "ext-mcrypt": "*", 394 | "ezyang/htmlpurifier": "4.6.*", 395 | "lib-pcre": "*", 396 | "php": ">=5.4.0", 397 | "yiisoft/yii2-composer": "*" 398 | }, 399 | "bin": [ 400 | "yii" 401 | ], 402 | "type": "library", 403 | "extra": { 404 | "branch-alias": { 405 | "dev-master": "2.0.x-dev" 406 | } 407 | }, 408 | "autoload": { 409 | "psr-4": { 410 | "yii\\": "" 411 | } 412 | }, 413 | "notification-url": "https://packagist.org/downloads/", 414 | "license": [ 415 | "BSD-3-Clause" 416 | ], 417 | "authors": [ 418 | { 419 | "name": "Qiang Xue", 420 | "email": "qiang.xue@gmail.com", 421 | "homepage": "http://www.yiiframework.com/", 422 | "role": "Founder and project lead" 423 | }, 424 | { 425 | "name": "Alexander Makarov", 426 | "email": "sam@rmcreative.ru", 427 | "homepage": "http://rmcreative.ru/", 428 | "role": "Core framework development" 429 | }, 430 | { 431 | "name": "Maurizio Domba", 432 | "homepage": "http://mdomba.info/", 433 | "role": "Core framework development" 434 | }, 435 | { 436 | "name": "Carsten Brandt", 437 | "email": "mail@cebe.cc", 438 | "homepage": "http://cebe.cc/", 439 | "role": "Core framework development" 440 | }, 441 | { 442 | "name": "Timur Ruziev", 443 | "email": "resurtm@gmail.com", 444 | "homepage": "http://resurtm.com/", 445 | "role": "Core framework development" 446 | }, 447 | { 448 | "name": "Paul Klimov", 449 | "email": "klimov.paul@gmail.com", 450 | "role": "Core framework development" 451 | } 452 | ], 453 | "description": "Yii PHP Framework Version 2", 454 | "homepage": "http://www.yiiframework.com/", 455 | "keywords": [ 456 | "framework", 457 | "yii2" 458 | ], 459 | "time": "2015-02-05 14:24:17" 460 | }, 461 | { 462 | "name": "yiisoft/yii2-bootstrap", 463 | "version": "dev-master", 464 | "source": { 465 | "type": "git", 466 | "url": "https://github.com/yiisoft/yii2-bootstrap.git", 467 | "reference": "d6daff0dc635c87659754b16107843501355eae3" 468 | }, 469 | "dist": { 470 | "type": "zip", 471 | "url": "https://api.github.com/repos/yiisoft/yii2-bootstrap/zipball/d6daff0dc635c87659754b16107843501355eae3", 472 | "reference": "d6daff0dc635c87659754b16107843501355eae3", 473 | "shasum": "" 474 | }, 475 | "require": { 476 | "bower-asset/bootstrap": "3.3.* | 3.2.* | 3.1.*", 477 | "yiisoft/yii2": "*" 478 | }, 479 | "type": "yii2-extension", 480 | "extra": { 481 | "branch-alias": { 482 | "dev-master": "2.0.x-dev" 483 | } 484 | }, 485 | "autoload": { 486 | "psr-4": { 487 | "yii\\bootstrap\\": "" 488 | } 489 | }, 490 | "notification-url": "https://packagist.org/downloads/", 491 | "license": [ 492 | "BSD-3-Clause" 493 | ], 494 | "authors": [ 495 | { 496 | "name": "Qiang Xue", 497 | "email": "qiang.xue@gmail.com" 498 | } 499 | ], 500 | "description": "The Twitter Bootstrap extension for the Yii framework", 501 | "keywords": [ 502 | "bootstrap", 503 | "yii2" 504 | ], 505 | "time": "2015-01-20 08:53:09" 506 | }, 507 | { 508 | "name": "yiisoft/yii2-composer", 509 | "version": "dev-master", 510 | "source": { 511 | "type": "git", 512 | "url": "https://github.com/yiisoft/yii2-composer.git", 513 | "reference": "7f21221ee85862c83f60c8f74a267e29c9dc6336" 514 | }, 515 | "dist": { 516 | "type": "zip", 517 | "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7f21221ee85862c83f60c8f74a267e29c9dc6336", 518 | "reference": "7f21221ee85862c83f60c8f74a267e29c9dc6336", 519 | "shasum": "" 520 | }, 521 | "require": { 522 | "composer-plugin-api": "1.0.0" 523 | }, 524 | "type": "composer-plugin", 525 | "extra": { 526 | "class": "yii\\composer\\Plugin", 527 | "branch-alias": { 528 | "dev-master": "2.0.x-dev" 529 | } 530 | }, 531 | "autoload": { 532 | "psr-4": { 533 | "yii\\composer\\": "" 534 | } 535 | }, 536 | "notification-url": "https://packagist.org/downloads/", 537 | "license": [ 538 | "BSD-3-Clause" 539 | ], 540 | "authors": [ 541 | { 542 | "name": "Qiang Xue", 543 | "email": "qiang.xue@gmail.com" 544 | } 545 | ], 546 | "description": "The composer plugin for Yii extension installer", 547 | "keywords": [ 548 | "composer", 549 | "extension installer", 550 | "yii2" 551 | ], 552 | "time": "2015-01-11 03:59:50" 553 | }, 554 | { 555 | "name": "yiisoft/yii2-swiftmailer", 556 | "version": "dev-master", 557 | "source": { 558 | "type": "git", 559 | "url": "https://github.com/yiisoft/yii2-swiftmailer.git", 560 | "reference": "e702187bb958f759c849684222ac98c4c1b08dba" 561 | }, 562 | "dist": { 563 | "type": "zip", 564 | "url": "https://api.github.com/repos/yiisoft/yii2-swiftmailer/zipball/e702187bb958f759c849684222ac98c4c1b08dba", 565 | "reference": "e702187bb958f759c849684222ac98c4c1b08dba", 566 | "shasum": "" 567 | }, 568 | "require": { 569 | "swiftmailer/swiftmailer": "*", 570 | "yiisoft/yii2": "*" 571 | }, 572 | "type": "yii2-extension", 573 | "extra": { 574 | "branch-alias": { 575 | "dev-master": "2.0.x-dev" 576 | } 577 | }, 578 | "autoload": { 579 | "psr-4": { 580 | "yii\\swiftmailer\\": "" 581 | } 582 | }, 583 | "notification-url": "https://packagist.org/downloads/", 584 | "license": [ 585 | "BSD-3-Clause" 586 | ], 587 | "authors": [ 588 | { 589 | "name": "Paul Klimov", 590 | "email": "klimov.paul@gmail.com" 591 | } 592 | ], 593 | "description": "The SwiftMailer integration for the Yii framework", 594 | "keywords": [ 595 | "email", 596 | "mail", 597 | "mailer", 598 | "swift", 599 | "swiftmailer", 600 | "yii2" 601 | ], 602 | "time": "2015-01-11 03:59:50" 603 | } 604 | ], 605 | "packages-dev": [ 606 | { 607 | "name": "bower-asset/typeahead.js", 608 | "version": "v0.10.5", 609 | "source": { 610 | "type": "git", 611 | "url": "https://github.com/twitter/typeahead.js.git", 612 | "reference": "5f198b87d1af845da502ea9df93a5e84801ce742" 613 | }, 614 | "dist": { 615 | "type": "zip", 616 | "url": "https://api.github.com/repos/twitter/typeahead.js/zipball/5f198b87d1af845da502ea9df93a5e84801ce742", 617 | "reference": "5f198b87d1af845da502ea9df93a5e84801ce742", 618 | "shasum": "" 619 | }, 620 | "require": { 621 | "bower-asset/jquery": ">=1.7" 622 | }, 623 | "require-dev": { 624 | "bower-asset/jasmine-ajax": ">=1.3.1,<1.4", 625 | "bower-asset/jasmine-jquery": ">=1.5.2,<1.6", 626 | "bower-asset/jquery": ">=1.7,<1.8" 627 | }, 628 | "type": "bower-asset-library", 629 | "extra": { 630 | "bower-asset-main": "dist/typeahead.bundle.js" 631 | } 632 | }, 633 | { 634 | "name": "fzaninotto/faker", 635 | "version": "dev-master", 636 | "source": { 637 | "type": "git", 638 | "url": "https://github.com/fzaninotto/Faker.git", 639 | "reference": "81e4eccea302300ac0c69c7ed04c94e29f580633" 640 | }, 641 | "dist": { 642 | "type": "zip", 643 | "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/81e4eccea302300ac0c69c7ed04c94e29f580633", 644 | "reference": "81e4eccea302300ac0c69c7ed04c94e29f580633", 645 | "shasum": "" 646 | }, 647 | "require": { 648 | "php": ">=5.3.3" 649 | }, 650 | "require-dev": { 651 | "phpunit/phpunit": "~4.0", 652 | "squizlabs/php_codesniffer": "~1.5" 653 | }, 654 | "suggest": { 655 | "ext-intl": "*" 656 | }, 657 | "type": "library", 658 | "extra": { 659 | "branch-alias": { 660 | "dev-master": "1.5.x-dev" 661 | } 662 | }, 663 | "autoload": { 664 | "psr-4": { 665 | "Faker\\": "src/Faker/" 666 | } 667 | }, 668 | "notification-url": "https://packagist.org/downloads/", 669 | "license": [ 670 | "MIT" 671 | ], 672 | "authors": [ 673 | { 674 | "name": "François Zaninotto" 675 | } 676 | ], 677 | "description": "Faker is a PHP library that generates fake data for you.", 678 | "keywords": [ 679 | "data", 680 | "faker", 681 | "fixtures" 682 | ], 683 | "time": "2015-01-27 08:16:46" 684 | }, 685 | { 686 | "name": "phpspec/php-diff", 687 | "version": "dev-master", 688 | "source": { 689 | "type": "git", 690 | "url": "https://github.com/phpspec/php-diff.git", 691 | "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a" 692 | }, 693 | "dist": { 694 | "type": "zip", 695 | "url": "https://api.github.com/repos/phpspec/php-diff/zipball/30e103d19519fe678ae64a60d77884ef3d71b28a", 696 | "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a", 697 | "shasum": "" 698 | }, 699 | "type": "library", 700 | "autoload": { 701 | "psr-0": { 702 | "Diff": "lib/" 703 | } 704 | }, 705 | "notification-url": "https://packagist.org/downloads/", 706 | "license": [ 707 | "BSD-3-Clause" 708 | ], 709 | "authors": [ 710 | { 711 | "name": "Chris Boulton", 712 | "homepage": "http://github.com/chrisboulton", 713 | "role": "Original developer" 714 | } 715 | ], 716 | "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", 717 | "time": "2013-11-01 13:02:21" 718 | }, 719 | { 720 | "name": "yiisoft/yii2-codeception", 721 | "version": "dev-master", 722 | "source": { 723 | "type": "git", 724 | "url": "https://github.com/yiisoft/yii2-codeception.git", 725 | "reference": "036ec922a1d080f2d7e81bece0b499717a6562d6" 726 | }, 727 | "dist": { 728 | "type": "zip", 729 | "url": "https://api.github.com/repos/yiisoft/yii2-codeception/zipball/036ec922a1d080f2d7e81bece0b499717a6562d6", 730 | "reference": "036ec922a1d080f2d7e81bece0b499717a6562d6", 731 | "shasum": "" 732 | }, 733 | "require": { 734 | "yiisoft/yii2": "*" 735 | }, 736 | "type": "yii2-extension", 737 | "extra": { 738 | "branch-alias": { 739 | "dev-master": "2.0.x-dev" 740 | } 741 | }, 742 | "autoload": { 743 | "psr-4": { 744 | "yii\\codeception\\": "" 745 | } 746 | }, 747 | "notification-url": "https://packagist.org/downloads/", 748 | "license": [ 749 | "BSD-3-Clause" 750 | ], 751 | "authors": [ 752 | { 753 | "name": "Mark Jebri", 754 | "email": "mark.github@yandex.ru" 755 | } 756 | ], 757 | "description": "The Codeception integration for the Yii framework", 758 | "keywords": [ 759 | "codeception", 760 | "yii2" 761 | ], 762 | "time": "2015-01-24 08:47:11" 763 | }, 764 | { 765 | "name": "yiisoft/yii2-debug", 766 | "version": "dev-master", 767 | "source": { 768 | "type": "git", 769 | "url": "https://github.com/yiisoft/yii2-debug.git", 770 | "reference": "fa92dcf066205df00614adf38cf3ec9188f9a9c8" 771 | }, 772 | "dist": { 773 | "type": "zip", 774 | "url": "https://api.github.com/repos/yiisoft/yii2-debug/zipball/fa92dcf066205df00614adf38cf3ec9188f9a9c8", 775 | "reference": "fa92dcf066205df00614adf38cf3ec9188f9a9c8", 776 | "shasum": "" 777 | }, 778 | "require": { 779 | "yiisoft/yii2": "*", 780 | "yiisoft/yii2-bootstrap": "*" 781 | }, 782 | "type": "yii2-extension", 783 | "extra": { 784 | "branch-alias": { 785 | "dev-master": "2.0.x-dev" 786 | } 787 | }, 788 | "autoload": { 789 | "psr-4": { 790 | "yii\\debug\\": "" 791 | } 792 | }, 793 | "notification-url": "https://packagist.org/downloads/", 794 | "license": [ 795 | "BSD-3-Clause" 796 | ], 797 | "authors": [ 798 | { 799 | "name": "Qiang Xue", 800 | "email": "qiang.xue@gmail.com" 801 | } 802 | ], 803 | "description": "The debugger extension for the Yii framework", 804 | "keywords": [ 805 | "debug", 806 | "debugger", 807 | "yii2" 808 | ], 809 | "time": "2015-01-20 18:08:25" 810 | }, 811 | { 812 | "name": "yiisoft/yii2-faker", 813 | "version": "dev-master", 814 | "source": { 815 | "type": "git", 816 | "url": "https://github.com/yiisoft/yii2-faker.git", 817 | "reference": "fc93dd57b8fcff21ecb68c76b539d78a1cd80c55" 818 | }, 819 | "dist": { 820 | "type": "zip", 821 | "url": "https://api.github.com/repos/yiisoft/yii2-faker/zipball/fc93dd57b8fcff21ecb68c76b539d78a1cd80c55", 822 | "reference": "fc93dd57b8fcff21ecb68c76b539d78a1cd80c55", 823 | "shasum": "" 824 | }, 825 | "require": { 826 | "fzaninotto/faker": "*", 827 | "yiisoft/yii2": "*" 828 | }, 829 | "type": "yii2-extension", 830 | "extra": { 831 | "branch-alias": { 832 | "dev-master": "2.0.x-dev" 833 | } 834 | }, 835 | "autoload": { 836 | "psr-4": { 837 | "yii\\faker\\": "" 838 | } 839 | }, 840 | "notification-url": "https://packagist.org/downloads/", 841 | "license": [ 842 | "BSD-3-Clause" 843 | ], 844 | "authors": [ 845 | { 846 | "name": "Mark Jebri", 847 | "email": "mark.github@yandex.ru" 848 | } 849 | ], 850 | "description": "Fixture generator. The Faker integration for the Yii framework.", 851 | "keywords": [ 852 | "Fixture", 853 | "faker", 854 | "yii2" 855 | ], 856 | "time": "2015-01-11 03:59:50" 857 | }, 858 | { 859 | "name": "yiisoft/yii2-gii", 860 | "version": "dev-master", 861 | "source": { 862 | "type": "git", 863 | "url": "https://github.com/yiisoft/yii2-gii.git", 864 | "reference": "f8b3959383b99697835e7a5887da7c5c0a5eee7d" 865 | }, 866 | "dist": { 867 | "type": "zip", 868 | "url": "https://api.github.com/repos/yiisoft/yii2-gii/zipball/f8b3959383b99697835e7a5887da7c5c0a5eee7d", 869 | "reference": "f8b3959383b99697835e7a5887da7c5c0a5eee7d", 870 | "shasum": "" 871 | }, 872 | "require": { 873 | "bower-asset/typeahead.js": "0.10.*", 874 | "phpspec/php-diff": ">=1.0.2", 875 | "yiisoft/yii2": "*", 876 | "yiisoft/yii2-bootstrap": "*" 877 | }, 878 | "type": "yii2-extension", 879 | "extra": { 880 | "branch-alias": { 881 | "dev-master": "2.0.x-dev" 882 | } 883 | }, 884 | "autoload": { 885 | "psr-4": { 886 | "yii\\gii\\": "" 887 | } 888 | }, 889 | "notification-url": "https://packagist.org/downloads/", 890 | "license": [ 891 | "BSD-3-Clause" 892 | ], 893 | "authors": [ 894 | { 895 | "name": "Qiang Xue", 896 | "email": "qiang.xue@gmail.com" 897 | } 898 | ], 899 | "description": "The Gii extension for the Yii framework", 900 | "keywords": [ 901 | "code generator", 902 | "gii", 903 | "yii2" 904 | ], 905 | "time": "2015-01-24 14:34:00" 906 | } 907 | ], 908 | "aliases": [], 909 | "minimum-stability": "dev", 910 | "stability-flags": [], 911 | "prefer-stable": false, 912 | "prefer-lowest": false, 913 | "platform": { 914 | "php": ">=5.4.0" 915 | }, 916 | "platform-dev": [] 917 | } 918 | -------------------------------------------------------------------------------- /config/console.php: -------------------------------------------------------------------------------- 1 | 'basic-console', 10 | 'basePath' => dirname(__DIR__), 11 | 'bootstrap' => ['log', 'gii'], 12 | 'controllerNamespace' => 'app\commands', 13 | 'modules' => [ 14 | 'gii' => 'yii\gii\Module', 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 | -------------------------------------------------------------------------------- /config/db.php: -------------------------------------------------------------------------------- 1 | 'yii\db\Connection', 5 | 'dsn' => 'mysql:host=localhost;dbname=yii2dynamicformtut', 6 | 'username' => 'root', 7 | 'password' => '', 8 | 'charset' => 'utf8', 9 | ]; 10 | -------------------------------------------------------------------------------- /config/params.php: -------------------------------------------------------------------------------- 1 | 'admin@example.com', 5 | ]; 6 | -------------------------------------------------------------------------------- /config/web.php: -------------------------------------------------------------------------------- 1 | 'basic', 7 | 'basePath' => dirname(__DIR__), 8 | 'bootstrap' => ['log'], 9 | 'components' => [ 10 | 'request' => [ 11 | // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation 12 | 'cookieValidationKey' => 'ocRLnh9uZ54fMiYXnFvp6r803zAnWZwP', 13 | ], 14 | 'cache' => [ 15 | 'class' => 'yii\caching\FileCache', 16 | ], 17 | 'user' => [ 18 | 'identityClass' => 'app\models\User', 19 | 'enableAutoLogin' => true, 20 | ], 21 | 'errorHandler' => [ 22 | 'errorAction' => 'site/error', 23 | ], 24 | 'mailer' => [ 25 | 'class' => 'yii\swiftmailer\Mailer', 26 | // send all mails to a file by default. You have to set 27 | // 'useFileTransport' to false and configure a transport 28 | // for the mailer to send real emails. 29 | 'useFileTransport' => true, 30 | ], 31 | 'log' => [ 32 | 'traceLevel' => YII_DEBUG ? 3 : 0, 33 | 'targets' => [ 34 | [ 35 | 'class' => 'yii\log\FileTarget', 36 | 'levels' => ['error', 'warning'], 37 | ], 38 | ], 39 | ], 40 | 'db' => require(__DIR__ . '/db.php'), 41 | ], 42 | 'params' => $params, 43 | ]; 44 | 45 | if (YII_ENV_DEV) { 46 | // configuration adjustments for 'dev' environment 47 | $config['bootstrap'][] = 'debug'; 48 | $config['modules']['debug'] = 'yii\debug\Module'; 49 | 50 | $config['bootstrap'][] = 'gii'; 51 | $config['modules']['gii'] = 'yii\gii\Module'; 52 | } 53 | 54 | return $config; 55 | -------------------------------------------------------------------------------- /controllers/ReceiptController.php: -------------------------------------------------------------------------------- 1 | [ 23 | 'class' => VerbFilter::className(), 24 | 'actions' => [ 25 | 'delete' => ['post'], 26 | ], 27 | ], 28 | ]; 29 | } 30 | 31 | /** 32 | * Lists all Receipt models. 33 | * @return mixed 34 | */ 35 | public function actionIndex() 36 | { 37 | $dataProvider = new ActiveDataProvider([ 38 | 'query' => Receipt::find(), 39 | ]); 40 | 41 | return $this->render('index', [ 42 | 'dataProvider' => $dataProvider, 43 | ]); 44 | } 45 | 46 | /** 47 | * Displays a single Receipt model. 48 | * @param integer $id 49 | * @return mixed 50 | */ 51 | public function actionView($id) 52 | { 53 | return $this->render('view', [ 54 | 'model' => $this->findModel($id), 55 | ]); 56 | } 57 | 58 | /** 59 | * Creates a new Receipt model. 60 | * If creation is successful, the browser will be redirected to the 'view' page. 61 | * @return mixed 62 | */ 63 | public function actionCreate() 64 | { 65 | $model = new Receipt(); 66 | $modelDetails = []; 67 | 68 | $formDetails = Yii::$app->request->post('ReceiptDetail', []); 69 | foreach ($formDetails as $i => $formDetail) { 70 | $modelDetail = new ReceiptDetail(['scenario' => ReceiptDetail::SCENARIO_BATCH_UPDATE]); 71 | $modelDetail->setAttributes($formDetail); 72 | $modelDetails[] = $modelDetail; 73 | } 74 | 75 | //handling if the addRow button has been pressed 76 | if (Yii::$app->request->post('addRow') == 'true') { 77 | $model->load(Yii::$app->request->post()); 78 | $modelDetails[] = new ReceiptDetail(['scenario' => ReceiptDetail::SCENARIO_BATCH_UPDATE]); 79 | return $this->render('create', [ 80 | 'model' => $model, 81 | 'modelDetails' => $modelDetails 82 | ]); 83 | } 84 | 85 | if ($model->load(Yii::$app->request->post())) { 86 | if (Model::validateMultiple($modelDetails) && $model->validate()) { 87 | $model->save(); 88 | foreach($modelDetails as $modelDetail) { 89 | $modelDetail->receipt_id = $model->id; 90 | $modelDetail->save(); 91 | } 92 | return $this->redirect(['view', 'id' => $model->id]); 93 | } 94 | } 95 | 96 | return $this->render('create', [ 97 | 'model' => $model, 98 | 'modelDetails' => $modelDetails 99 | ]); 100 | } 101 | 102 | /** 103 | * Updates an existing Receipt model. 104 | * If update is successful, the browser will be redirected to the 'view' page. 105 | * @param integer $id 106 | * @return mixed 107 | */ 108 | public function actionUpdate($id) 109 | { 110 | $model = $this->findModel($id); 111 | $modelDetails = $model->receiptDetails; 112 | 113 | $formDetails = Yii::$app->request->post('ReceiptDetail', []); 114 | foreach ($formDetails as $i => $formDetail) { 115 | //loading the models if they are not new 116 | if (isset($formDetail['id']) && isset($formDetail['updateType']) && $formDetail['updateType'] != ReceiptDetail::UPDATE_TYPE_CREATE) { 117 | //making sure that it is actually a child of the main model 118 | $modelDetail = ReceiptDetail::findOne(['id' => $formDetail['id'], 'receipt_id' => $model->id]); 119 | $modelDetail->setScenario(ReceiptDetail::SCENARIO_BATCH_UPDATE); 120 | $modelDetail->setAttributes($formDetail); 121 | $modelDetails[$i] = $modelDetail; 122 | //validate here if the modelDetail loaded is valid, and if it can be updated or deleted 123 | } else { 124 | $modelDetail = new ReceiptDetail(['scenario' => ReceiptDetail::SCENARIO_BATCH_UPDATE]); 125 | $modelDetail->setAttributes($formDetail); 126 | $modelDetails[] = $modelDetail; 127 | } 128 | 129 | } 130 | 131 | //handling if the addRow button has been pressed 132 | if (Yii::$app->request->post('addRow') == 'true') { 133 | $modelDetails[] = new ReceiptDetail(['scenario' => ReceiptDetail::SCENARIO_BATCH_UPDATE]); 134 | return $this->render('update', [ 135 | 'model' => $model, 136 | 'modelDetails' => $modelDetails 137 | ]); 138 | } 139 | 140 | if ($model->load(Yii::$app->request->post())) { 141 | if (Model::validateMultiple($modelDetails) && $model->validate()) { 142 | $model->save(); 143 | foreach($modelDetails as $modelDetail) { 144 | //details that has been flagged for deletion will be deleted 145 | if ($modelDetail->updateType == ReceiptDetail::UPDATE_TYPE_DELETE) { 146 | $modelDetail->delete(); 147 | } else { 148 | //new or updated records go here 149 | $modelDetail->receipt_id = $model->id; 150 | $modelDetail->save(); 151 | } 152 | } 153 | return $this->redirect(['view', 'id' => $model->id]); 154 | } 155 | } 156 | 157 | 158 | return $this->render('update', [ 159 | 'model' => $model, 160 | 'modelDetails' => $modelDetails 161 | ]); 162 | 163 | } 164 | 165 | /** 166 | * Deletes an existing Receipt model. 167 | * If deletion is successful, the browser will be redirected to the 'index' page. 168 | * @param integer $id 169 | * @return mixed 170 | */ 171 | public function actionDelete($id) 172 | { 173 | $model = $this->findModel($id); 174 | 175 | foreach ($model->receiptDetails as $modelDetail) { 176 | $modelDetail->delete(); 177 | } 178 | 179 | $model->delete(); 180 | 181 | return $this->redirect(['index']); 182 | } 183 | 184 | /** 185 | * Finds the Receipt model based on its primary key value. 186 | * If the model is not found, a 404 HTTP exception will be thrown. 187 | * @param integer $id 188 | * @return Receipt the loaded model 189 | * @throws NotFoundHttpException if the model cannot be found 190 | */ 191 | protected function findModel($id) 192 | { 193 | if (($model = Receipt::findOne($id)) !== null) { 194 | return $model; 195 | } else { 196 | throw new NotFoundHttpException('The requested page does not exist.'); 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /controllers/SiteController.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'class' => AccessControl::className(), 19 | 'only' => ['logout'], 20 | 'rules' => [ 21 | [ 22 | 'actions' => ['logout'], 23 | 'allow' => true, 24 | 'roles' => ['@'], 25 | ], 26 | ], 27 | ], 28 | 'verbs' => [ 29 | 'class' => VerbFilter::className(), 30 | 'actions' => [ 31 | 'logout' => ['post'], 32 | ], 33 | ], 34 | ]; 35 | } 36 | 37 | public function actions() 38 | { 39 | return [ 40 | 'error' => [ 41 | 'class' => 'yii\web\ErrorAction', 42 | ], 43 | 'captcha' => [ 44 | 'class' => 'yii\captcha\CaptchaAction', 45 | 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, 46 | ], 47 | ]; 48 | } 49 | 50 | public function actionIndex() 51 | { 52 | return $this->render('index'); 53 | } 54 | 55 | public function actionLogin() 56 | { 57 | if (!\Yii::$app->user->isGuest) { 58 | return $this->goHome(); 59 | } 60 | 61 | $model = new LoginForm(); 62 | if ($model->load(Yii::$app->request->post()) && $model->login()) { 63 | return $this->goBack(); 64 | } else { 65 | return $this->render('login', [ 66 | 'model' => $model, 67 | ]); 68 | } 69 | } 70 | 71 | public function actionLogout() 72 | { 73 | Yii::$app->user->logout(); 74 | 75 | return $this->goHome(); 76 | } 77 | 78 | public function actionContact() 79 | { 80 | $model = new ContactForm(); 81 | if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) { 82 | Yii::$app->session->setFlash('contactFormSubmitted'); 83 | 84 | return $this->refresh(); 85 | } else { 86 | return $this->render('contact', [ 87 | 'model' => $model, 88 | ]); 89 | } 90 | } 91 | 92 | public function actionAbout() 93 | { 94 | return $this->render('about'); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /migrations/m150204_103949_create_receipt_tables.php: -------------------------------------------------------------------------------- 1 | createTable('receipt', [ 11 | 'id' => 'pk', 12 | 'title' => 'string' 13 | ]); 14 | 15 | $this->createTable('receipt_detail', [ 16 | 'id' => 'pk', 17 | 'receipt_id' => 'int', 18 | 'item_name' => 'string' 19 | ]); 20 | 21 | $this->addForeignKey('receipt_detail_receipt_fk', 'receipt_detail', 'receipt_id', 'receipt', 'id', 'RESTRICT', 'RESTRICT'); 22 | 23 | } 24 | 25 | public function down() 26 | { 27 | $this->dropTable('receipt_detail'); 28 | $this->dropTable('receipt'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /models/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 | } else { 61 | return false; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /models/LoginForm.php: -------------------------------------------------------------------------------- 1 | hasErrors()) { 45 | $user = $this->getUser(); 46 | 47 | if (!$user || !$user->validatePassword($this->password)) { 48 | $this->addError($attribute, 'Incorrect username or password.'); 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * Logs in a user using the provided username and password. 55 | * @return boolean whether the user is logged in successfully 56 | */ 57 | public function login() 58 | { 59 | if ($this->validate()) { 60 | return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); 61 | } else { 62 | return false; 63 | } 64 | } 65 | 66 | /** 67 | * Finds user by [[username]] 68 | * 69 | * @return User|null 70 | */ 71 | public function getUser() 72 | { 73 | if ($this->_user === false) { 74 | $this->_user = User::findByUsername($this->username); 75 | } 76 | 77 | return $this->_user; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /models/Receipt.php: -------------------------------------------------------------------------------- 1 | 255] 33 | ]; 34 | } 35 | 36 | /** 37 | * @inheritdoc 38 | */ 39 | public function attributeLabels() 40 | { 41 | return [ 42 | 'id' => 'ID', 43 | 'title' => 'Title', 44 | ]; 45 | } 46 | 47 | /** 48 | * @return \yii\db\ActiveQuery 49 | */ 50 | public function getReceiptDetails() 51 | { 52 | return $this->hasMany(ReceiptDetail::className(), ['receipt_id' => 'id']); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /models/ReceiptDetail.php: -------------------------------------------------------------------------------- 1 | _updateType)) { 33 | if ($this->isNewRecord) { 34 | $this->_updateType = self::UPDATE_TYPE_CREATE; 35 | } else { 36 | $this->_updateType = self::UPDATE_TYPE_UPDATE; 37 | } 38 | } 39 | 40 | return $this->_updateType; 41 | } 42 | 43 | public function setUpdateType($value) 44 | { 45 | $this->_updateType = $value; 46 | } 47 | /** 48 | * @inheritdoc 49 | */ 50 | public static function tableName() 51 | { 52 | return 'receipt_detail'; 53 | } 54 | 55 | /** 56 | * @inheritdoc 57 | */ 58 | public function rules() 59 | { 60 | return [ 61 | ['updateType', 'required', 'on' => self::SCENARIO_BATCH_UPDATE], 62 | ['updateType', 63 | 'in', 64 | 'range' => [self::UPDATE_TYPE_CREATE, self::UPDATE_TYPE_UPDATE, self::UPDATE_TYPE_DELETE], 65 | 'on' => self::SCENARIO_BATCH_UPDATE] 66 | , 67 | ['item_name', 'required'], 68 | //allowing it to be empty because it will be filled by the ReceiptController 69 | ['receipt_id', 'required', 'except' => self::SCENARIO_BATCH_UPDATE], 70 | [['receipt_id'], 'integer'], 71 | [['item_name'], 'string', 'max' => 255] 72 | ]; 73 | } 74 | 75 | /** 76 | * @inheritdoc 77 | */ 78 | public function attributeLabels() 79 | { 80 | return [ 81 | 'id' => 'ID', 82 | 'receipt_id' => 'Receipt ID', 83 | 'item_name' => 'Item Name', 84 | ]; 85 | } 86 | 87 | /** 88 | * @return \yii\db\ActiveQuery 89 | */ 90 | public function getReceipt() 91 | { 92 | return $this->hasOne(Receipt::className(), ['id' => 'receipt_id']); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /models/User.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'id' => '100', 16 | 'username' => 'admin', 17 | 'password' => 'admin', 18 | 'authKey' => 'test100key', 19 | 'accessToken' => '100-token', 20 | ], 21 | '101' => [ 22 | 'id' => '101', 23 | 'username' => 'demo', 24 | 'password' => 'demo', 25 | 'authKey' => 'test101key', 26 | 'accessToken' => '101-token', 27 | ], 28 | ]; 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | public static function findIdentity($id) 34 | { 35 | return isset(self::$users[$id]) ? new static(self::$users[$id]) : null; 36 | } 37 | 38 | /** 39 | * @inheritdoc 40 | */ 41 | public static function findIdentityByAccessToken($token, $type = null) 42 | { 43 | foreach (self::$users as $user) { 44 | if ($user['accessToken'] === $token) { 45 | return new static($user); 46 | } 47 | } 48 | 49 | return null; 50 | } 51 | 52 | /** 53 | * Finds user by username 54 | * 55 | * @param string $username 56 | * @return static|null 57 | */ 58 | public static function findByUsername($username) 59 | { 60 | foreach (self::$users as $user) { 61 | if (strcasecmp($user['username'], $username) === 0) { 62 | return new static($user); 63 | } 64 | } 65 | 66 | return null; 67 | } 68 | 69 | /** 70 | * @inheritdoc 71 | */ 72 | public function getId() 73 | { 74 | return $this->id; 75 | } 76 | 77 | /** 78 | * @inheritdoc 79 | */ 80 | public function getAuthKey() 81 | { 82 | return $this->authKey; 83 | } 84 | 85 | /** 86 | * @inheritdoc 87 | */ 88 | public function validateAuthKey($authKey) 89 | { 90 | return $this->authKey === $authKey; 91 | } 92 | 93 | /** 94 | * Validates password 95 | * 96 | * @param string $password password to validate 97 | * @return boolean if password provided is valid for current user 98 | */ 99 | public function validatePassword($password) 100 | { 101 | return $this->password === $password; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /requirements.php: -------------------------------------------------------------------------------- 1 | Error'; 18 | echo '

The path to yii framework seems to be incorrect.

'; 19 | echo '

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

'; 20 | echo '

Please refer to the README on how to install Yii.

'; 21 | } 22 | 23 | require_once($frameworkPath . '/requirements/YiiRequirementChecker.php'); 24 | $requirementsChecker = new YiiRequirementChecker(); 25 | 26 | $gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.'; 27 | $gdOK = $imagickOK = false; 28 | 29 | if (extension_loaded('imagick')) { 30 | $imagick = new Imagick(); 31 | $imagickFormats = $imagick->queryFormats('PNG'); 32 | if (in_array('PNG', $imagickFormats)) { 33 | $imagickOK = true; 34 | } else { 35 | $imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.'; 36 | } 37 | } 38 | 39 | if (extension_loaded('gd')) { 40 | $gdInfo = gd_info(); 41 | if (!empty($gdInfo['FreeType Support'])) { 42 | $gdOK = true; 43 | } else { 44 | $gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.'; 45 | } 46 | } 47 | 48 | /** 49 | * Adjust requirements according to your application specifics. 50 | */ 51 | $requirements = array( 52 | // Database : 53 | array( 54 | 'name' => 'PDO extension', 55 | 'mandatory' => true, 56 | 'condition' => extension_loaded('pdo'), 57 | 'by' => 'All DB-related classes', 58 | ), 59 | array( 60 | 'name' => 'PDO SQLite extension', 61 | 'mandatory' => false, 62 | 'condition' => extension_loaded('pdo_sqlite'), 63 | 'by' => 'All DB-related classes', 64 | 'memo' => 'Required for SQLite database.', 65 | ), 66 | array( 67 | 'name' => 'PDO MySQL extension', 68 | 'mandatory' => false, 69 | 'condition' => extension_loaded('pdo_mysql'), 70 | 'by' => 'All DB-related classes', 71 | 'memo' => 'Required for MySQL database.', 72 | ), 73 | array( 74 | 'name' => 'PDO PostgreSQL extension', 75 | 'mandatory' => false, 76 | 'condition' => extension_loaded('pdo_pgsql'), 77 | 'by' => 'All DB-related classes', 78 | 'memo' => 'Required for PostgreSQL database.', 79 | ), 80 | // Cache : 81 | array( 82 | 'name' => 'Memcache extension', 83 | 'mandatory' => false, 84 | 'condition' => extension_loaded('memcache') || extension_loaded('memcached'), 85 | 'by' => 'MemCache', 86 | 'memo' => extension_loaded('memcached') ? 'To use memcached set MemCache::useMemcached to true.' : '' 87 | ), 88 | array( 89 | 'name' => 'APC extension', 90 | 'mandatory' => false, 91 | 'condition' => extension_loaded('apc'), 92 | 'by' => 'ApcCache', 93 | ), 94 | // CAPTCHA: 95 | array( 96 | 'name' => 'GD PHP extension with FreeType support', 97 | 'mandatory' => false, 98 | 'condition' => $gdOK, 99 | 'by' => 'Captcha', 100 | 'memo' => $gdMemo, 101 | ), 102 | array( 103 | 'name' => 'ImageMagick PHP extension with PNG support', 104 | 'mandatory' => false, 105 | 'condition' => $imagickOK, 106 | 'by' => 'Captcha', 107 | 'memo' => $imagickMemo, 108 | ), 109 | // PHP ini : 110 | 'phpExposePhp' => array( 111 | 'name' => 'Expose PHP', 112 | 'mandatory' => false, 113 | 'condition' => $requirementsChecker->checkPhpIniOff("expose_php"), 114 | 'by' => 'Security reasons', 115 | 'memo' => '"expose_php" should be disabled at php.ini', 116 | ), 117 | 'phpAllowUrlInclude' => array( 118 | 'name' => 'PHP allow url include', 119 | 'mandatory' => false, 120 | 'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"), 121 | 'by' => 'Security reasons', 122 | 'memo' => '"allow_url_include" should be disabled at php.ini', 123 | ), 124 | 'phpSmtp' => array( 125 | 'name' => 'PHP mail SMTP', 126 | 'mandatory' => false, 127 | 'condition' => strlen(ini_get('SMTP'))>0, 128 | 'by' => 'Email sending', 129 | 'memo' => 'PHP mail SMTP server required', 130 | ), 131 | ); 132 | $requirementsChecker->checkYii()->check($requirements)->render(); 133 | -------------------------------------------------------------------------------- /runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | This directory contains various tests for the basic application. 2 | 3 | Tests in `codeception` directory are developed with [Codeception PHP Testing Framework](http://codeception.com/). 4 | 5 | After creating the basic application, follow these steps to prepare for the tests: 6 | 7 | 1. Install Codeception if it's not yet installed: 8 | 9 | ``` 10 | composer global require "codeception/codeception=2.0.*" 11 | composer global require "codeception/specify=*" 12 | composer global require "codeception/verify=*" 13 | ``` 14 | 15 | If you've never used Composer for global packages run `composer global status`. It should output: 16 | 17 | ``` 18 | Changed current directory to 19 | ``` 20 | 21 | Then add `/vendor/bin` to you `PATH` environment variable. Now we're able to use `codecept` from command 22 | line globally. 23 | 24 | 2. Install faker extension by running the following from template root directory where `composer.json` is: 25 | 26 | ``` 27 | composer require --dev yiisoft/yii2-faker:* 28 | ``` 29 | 30 | 3. Create `yii2_basic_tests` database and update it by applying migrations: 31 | 32 | ``` 33 | codeception/bin/yii migrate 34 | ``` 35 | 36 | 4. Build the test suites: 37 | 38 | ``` 39 | codecept build 40 | ``` 41 | 42 | 5. In order to be able to run acceptance tests you need to start a webserver. The simplest way is to use PHP built in 43 | webserver. In the `web` directory execute the following: 44 | 45 | ``` 46 | php -S localhost:8080 47 | ``` 48 | 49 | 6. Now you can run the tests with the following commands: 50 | 51 | ``` 52 | # run all available tests 53 | codecept run 54 | # run acceptance tests 55 | codecept run acceptance 56 | # run functional tests 57 | codecept run functional 58 | # run unit tests 59 | codecept run unit 60 | ``` 61 | 62 | Code coverage support 63 | --------------------- 64 | 65 | By default, code coverage is disabled in `codeception.yml` configuration file, you should uncomment needed rows to be able 66 | to collect code coverage. You can run your tests and collect coverage with the following command: 67 | 68 | ``` 69 | #collect coverage for all tests 70 | codecept run --coverage-html --coverage-xml 71 | 72 | #collect coverage only for unit tests 73 | codecept run unit --coverage-html --coverage-xml 74 | 75 | #collect coverage for unit and functional tests 76 | codecept run functional,unit --coverage-html --coverage-xml 77 | ``` 78 | 79 | You can see code coverage output under the `tests/_output` directory. 80 | 81 | ###Remote code coverage 82 | 83 | When you run your tests not in the same process where code coverage is collected, then you should uncomment `remote` option and its 84 | related options, to be able to collect code coverage correctly. To setup remote code coverage you should follow [instructions](http://codeception.com/docs/11-Codecoverage) 85 | from codeception site. 86 | 87 | 1. install `Codeception c3` remote support `composer require "codeception/c3:*"`; 88 | 89 | 2. copy `c3.php` file under your `web` directory; 90 | 91 | 3. include `c3.php` file in your `index-test.php` file before application run, so it can catch needed requests. 92 | 93 | Configuration options that are used by remote code coverage: 94 | 95 | - c3_url: url pointing to entry script that includes `c3.php` file, so `Codeception` will be able to produce code coverage; 96 | - remote: whether to enable remote code coverage or not; 97 | - remote_config: path to the `codeception.yml` configuration file, from the directory where `c3.php` file is located. This is needed 98 | so that `Codeception` can create itself instance and collect code coverage correctly. 99 | 100 | By default `c3_url` and `remote_config` setup correctly, you only need to copy and include `c3.php` file in your `index-test.php` 101 | 102 | After that you should be able to collect code coverage from tests that run through `PhpBrowser` or `WebDriver` with same command 103 | as for other tests: 104 | 105 | ``` 106 | #collect coverage from remote 107 | codecept run acceptance --coverage-html --coverage-xml 108 | ``` 109 | 110 | Please refer to [Codeception tutorial](http://codeception.com/docs/01-Introduction) for 111 | more details about writing and running acceptance, functional and unit tests. 112 | -------------------------------------------------------------------------------- /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="LoginForm[username]"]', $username); 22 | $this->actor->fillField('input[name="LoginForm[password]"]', $password); 23 | $this->actor->click('login-button'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/codeception/_pages/ReceiptPage.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 adress 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('My Company'); 9 | $I->seeLink('About'); 10 | $I->click('About'); 11 | $I->see('This is the About page.'); 12 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/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 | if (method_exists($I, 'wait')) { 17 | $I->wait(3); // only for selenium 18 | } 19 | $I->expectTo('see validations errors'); 20 | $I->see('Username 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('Incorrect username or password.'); 30 | 31 | $I->amGoingTo('try to login with correct credentials'); 32 | $loginPage->login('admin', 'admin'); 33 | if (method_exists($I, 'wait')) { 34 | $I->wait(3); // only for selenium 35 | } 36 | $I->expectTo('see user info'); 37 | $I->see('Logout (admin)'); 38 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/ReceiptCept.php: -------------------------------------------------------------------------------- 1 | wantTo('test create'); 8 | ReceiptPage::openBy($I); 9 | $I->click('Create Receipt'); 10 | $I->fillField('input[name="Receipt[title]"]', 'Sandwich'); 11 | $I->click('Add row'); 12 | $I->wait(3); 13 | $I->click('Add row'); 14 | $I->wait(3); 15 | $I->fillField('input[name="ReceiptDetail[1][item_name]"]', 'Ham'); 16 | $I->fillField('input[name="ReceiptDetail[0][item_name]"]', 'Bread'); 17 | $I->click('Add row'); 18 | $I->wait(3); 19 | $I->fillField('input[name="ReceiptDetail[2][item_name]"]', 'Lettuce'); 20 | $I->click('Add row'); 21 | $I->wait(3); 22 | $I->fillField('input[name="ReceiptDetail[3][item_name]"]', 'Truffles'); 23 | $I->wait(3); 24 | 25 | //deleting an entry 26 | $I->amGoingTo('Remove truffles'); 27 | //using XPath here 28 | $I->click('//*[@id="w0"]/div[5]/div[2]/button'); 29 | $I->click('Create'); 30 | $I->wait(3); 31 | $I->see('Sandwich'); 32 | $I->wait(3); 33 | 34 | $I->see('Sandwich'); 35 | $I->see('Ham'); 36 | $I->see('Bread'); 37 | $I->see('Lettuce'); 38 | $I->dontSee('Truffles'); 39 | 40 | $I->wantTo('Test update'); 41 | $I->click('Update'); 42 | 43 | $I->amGoingTo('Replace Lettuce with tomatoes'); 44 | $I->fillField('input[name="ReceiptDetail[2][item_name]"]', 'Tomatoes'); 45 | $I->amGoingTo('Add bacon'); 46 | $I->click('Add row'); 47 | $I->wait(3); 48 | $I->fillField('input[name="ReceiptDetail[3][item_name]"]', 'Bacon'); 49 | $I->amGoingTo('Remove Bread'); 50 | $I->click('//*[@id="w0"]/div[2]/div[2]/button'); 51 | 52 | $I->click('Update'); 53 | $I->wait(3); 54 | $I->amGoingTo('We should end up with these items: Ham, Tomatoes, Bacon'); 55 | $I->see('Sandwich'); 56 | $I->see('Ham'); 57 | $I->see('Tomatoes'); 58 | $I->see('Bacon'); 59 | $I->dontSee('Bread'); 60 | 61 | $I->amGoingTo('Delete sandwich'); 62 | $I->click('Delete'); 63 | $I->acceptPopup(); 64 | $I->wait(3); 65 | $I->see('Create Receipt'); 66 | -------------------------------------------------------------------------------- /tests/codeception/acceptance/_bootstrap.php: -------------------------------------------------------------------------------- 1 | run(); 20 | exit($exitCode); 21 | -------------------------------------------------------------------------------- /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 | [ 7 | 'fixture' => [ 8 | 'class' => 'yii\faker\FixtureController', 9 | 'fixtureDataPath' => '@tests/codeception/fixtures', 10 | 'templatePath' => '@tests/codeception/templates', 11 | 'namespace' => 'tests\codeception\fixtures', 12 | ], 13 | ], 14 | 'components' => [ 15 | 'db' => [ 16 | 'dsn' => 'mysql:host=localhost;dbname=yii2_basic_tests', 17 | ], 18 | 'mailer' => [ 19 | 'useFileTransport' => true, 20 | ], 21 | 'urlManager' => [ 22 | 'showScriptName' => true, 23 | ], 24 | ], 25 | ]; 26 | -------------------------------------------------------------------------------- /tests/codeception/config/functional.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'request' => [ 14 | // it's not recommended to run functional tests with CSRF validation enabled 15 | 'enableCsrfValidation' => false, 16 | // but if you absolutely need it set cookie domain to localhost 17 | /* 18 | 'csrfCookie' => [ 19 | 'domain' => 'localhost', 20 | ], 21 | */ 22 | ], 23 | ], 24 | ] 25 | ); 26 | -------------------------------------------------------------------------------- /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 adress 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/fernandezekiel/yii2-dynamic-tabular-form-app/1d4f46abb498f5f77a517b048f21bfddfc4c85db/tests/codeception/unit/templates/fixtures/.gitkeep -------------------------------------------------------------------------------- /views/layouts/main.php: -------------------------------------------------------------------------------- 1 | 13 | beginPage() ?> 14 | 15 | 16 | 17 | 18 | 19 | 20 | <?= Html::encode($this->title) ?> 21 | head() ?> 22 | 23 | 24 | 25 | beginBody() ?> 26 |
27 | 'My Company', 30 | 'brandUrl' => Yii::$app->homeUrl, 31 | 'options' => [ 32 | 'class' => 'navbar-inverse navbar-fixed-top', 33 | ], 34 | ]); 35 | echo Nav::widget([ 36 | 'options' => ['class' => 'navbar-nav navbar-right'], 37 | 'items' => [ 38 | ['label' => 'Home', 'url' => ['/site/index']], 39 | ['label' => 'About', 'url' => ['/site/about']], 40 | ['label' => 'Contact', 'url' => ['/site/contact']], 41 | ['label' => 'Receipt', 'url' => ['/receipt/index']], 42 | Yii::$app->user->isGuest ? 43 | ['label' => 'Login', 'url' => ['/site/login']] : 44 | ['label' => 'Logout (' . Yii::$app->user->identity->username . ')', 45 | 'url' => ['/site/logout'], 46 | 'linkOptions' => ['data-method' => 'post']], 47 | ], 48 | ]); 49 | NavBar::end(); 50 | ?> 51 | 52 |
53 | isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], 55 | ]) ?> 56 | 57 |
58 |
59 | 60 |
61 |
62 |

© My Company

63 |

64 |
65 |
66 | 67 | endBody() ?> 68 | 69 | 70 | endPage() ?> 71 | -------------------------------------------------------------------------------- /views/receipt/_form.php: -------------------------------------------------------------------------------- 1 | 13 | registerJs(" 14 | $('.delete-button').click(function() { 15 | var detail = $(this).closest('.receipt-detail'); 16 | var updateType = detail.find('.update-type'); 17 | if (updateType.val() === " . json_encode(ReceiptDetail::UPDATE_TYPE_UPDATE) . ") { 18 | //marking the row for deletion 19 | updateType.val(" . json_encode(ReceiptDetail::UPDATE_TYPE_DELETE) . "); 20 | detail.hide(); 21 | } else { 22 | //if the row is a new row, delete the row 23 | detail.remove(); 24 | } 25 | 26 | }); 27 | "); 28 | ?> 29 |
30 | 31 | false 33 | ]); ?> 34 | 35 | field($model, 'title')->textInput(['maxlength' => 255]) ?> 36 | Details"?> 37 | 38 | $modelDetail) : ?> 39 |
40 |
41 | 42 | 'update-type']) ?> 43 | field($modelDetail, "[$i]item_name") ?> 44 |
45 |
46 | 'delete-button btn btn-danger', 'data-target' => "receipt-detail-$i"]) ?> 47 |
48 |
49 | 50 | 51 |
52 | isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> 53 | 'addRow', 'value' => 'true', 'class' => 'btn btn-info']) ?> 54 |
55 | 56 | 57 | 58 |
59 | -------------------------------------------------------------------------------- /views/receipt/create.php: -------------------------------------------------------------------------------- 1 | title = 'Create Receipt'; 11 | $this->params['breadcrumbs'][] = ['label' => 'Receipts', 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = $this->title; 13 | ?> 14 |
15 | 16 |

title) ?>

17 | 18 | render('_form', [ 19 | 'model' => $model, 20 | 'modelDetails' => $modelDetails 21 | ]) ?> 22 | 23 |
24 | -------------------------------------------------------------------------------- /views/receipt/index.php: -------------------------------------------------------------------------------- 1 | title = 'Receipts'; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 | 14 |

title) ?>

15 | 16 |

17 | 'btn btn-success']) ?> 18 |

19 | 20 | $dataProvider, 22 | 'columns' => [ 23 | ['class' => 'yii\grid\SerialColumn'], 24 | 25 | 'id', 26 | 'title', 27 | 28 | ['class' => 'yii\grid\ActionColumn'], 29 | ], 30 | ]); ?> 31 | 32 |
33 | -------------------------------------------------------------------------------- /views/receipt/update.php: -------------------------------------------------------------------------------- 1 | title = 'Update Receipt: ' . ' ' . $model->title; 10 | $this->params['breadcrumbs'][] = ['label' => 'Receipts', 'url' => ['index']]; 11 | $this->params['breadcrumbs'][] = ['label' => $model->title, 'url' => ['view', 'id' => $model->id]]; 12 | $this->params['breadcrumbs'][] = 'Update'; 13 | ?> 14 |
15 | 16 |

title) ?>

17 | 18 | render('_form', [ 19 | 'model' => $model, 20 | 'modelDetails' => $modelDetails 21 | ]) ?> 22 | 23 |
24 | -------------------------------------------------------------------------------- /views/receipt/view.php: -------------------------------------------------------------------------------- 1 | title = $model->title; 10 | $this->params['breadcrumbs'][] = ['label' => 'Receipts', 'url' => ['index']]; 11 | $this->params['breadcrumbs'][] = $this->title; 12 | ?> 13 |
14 | 15 |

title) ?>

16 | 17 |

18 | $model->id], ['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 | $model, 30 | 'attributes' => [ 31 | 'id', 32 | 'title', 33 | ], 34 | ]) ?> 35 |

Details

36 | 37 | 38 | 39 | 40 | 41 | receiptDetails as $receiptDetail) :?> 42 | 43 | 44 | 45 | 46 | 47 |
IDItem name
id ?>item_name ?>
48 | 49 |
50 | -------------------------------------------------------------------------------- /views/site/about.php: -------------------------------------------------------------------------------- 1 | title = 'About'; 6 | $this->params['breadcrumbs'][] = $this->title; 7 | ?> 8 |
9 |

title) ?>

10 | 11 |

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

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

title) ?>

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

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

32 | 33 | 34 | 35 |

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

38 | 39 |
40 |
41 | 'contact-form']); ?> 42 | field($model, 'name') ?> 43 | field($model, 'email') ?> 44 | field($model, 'subject') ?> 45 | field($model, 'body')->textArea(['rows' => 6]) ?> 46 | field($model, 'verifyCode')->widget(Captcha::className(), [ 47 | 'template' => '
{image}
{input}
', 48 | ]) ?> 49 |
50 | 'btn btn-primary', 'name' => 'contact-button']) ?> 51 |
52 | 53 |
54 |
55 | 56 | 57 |
58 | -------------------------------------------------------------------------------- /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'; 4 | ?> 5 |
6 | 7 |
8 |

Congratulations!

9 | 10 |

You have successfully created your Yii-powered application.

11 | 12 |

Get started with Yii

13 |
14 | 15 |
16 | 17 |
18 |
19 |

Heading

20 | 21 |

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

25 | 26 |

Yii Documentation »

27 |
28 |
29 |

Heading

30 | 31 |

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

35 | 36 |

Yii Forum »

37 |
38 |
39 |

Heading

40 | 41 |

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

45 | 46 |

Yii Extensions »

47 |
48 |
49 | 50 |
51 |
52 | -------------------------------------------------------------------------------- /views/site/login.php: -------------------------------------------------------------------------------- 1 | title = 'Login'; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 | 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fernandezekiel/yii2-dynamic-tabular-form-app/1d4f46abb498f5f77a517b048f21bfddfc4c85db/web/favicon.ico -------------------------------------------------------------------------------- /web/index-test.php: -------------------------------------------------------------------------------- 1 | run(); 17 | -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 | run(); 13 | -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: -------------------------------------------------------------------------------- /yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 24 | exit($exitCode); 25 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------