├── .gitignore ├── .php_cs ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── Vagrantfile ├── bin ├── .gitignore ├── clear-cache ├── include │ └── functions.php ├── init └── requirements ├── codeception.yml ├── composer.json ├── config ├── .gitignore ├── application-test.config.php ├── application.config.php ├── autoload │ ├── .gitignore │ └── global.php ├── constants.php └── modules.config.php ├── data ├── images │ └── logo.png ├── migrations │ └── .gitkeep └── runtime │ └── .gitignore ├── doc └── screenshot.png ├── env ├── config.php └── dev │ ├── bin │ ├── console │ └── console-test │ ├── config │ └── autoload │ │ └── local.php │ └── public │ ├── index-test.php │ └── index.php ├── module ├── Cli │ ├── config │ │ └── module.config.php │ └── src │ │ ├── Manager │ │ ├── CommandManager.php │ │ ├── CommandManagerFactory.php │ │ └── Provider │ │ │ └── CommandManagerProviderInterface.php │ │ ├── Module.php │ │ └── Options │ │ ├── ModuleOptions.php │ │ └── ModuleOptionsFactory.php ├── Core │ ├── assets │ │ ├── css │ │ │ ├── 404.sass │ │ │ ├── base.sass │ │ │ └── style.sass │ │ ├── img │ │ │ ├── 404.png │ │ │ └── logo.png │ │ └── js │ │ │ └── navbar.js │ ├── config │ │ ├── assets.config.php │ │ ├── doctrine.config.php │ │ ├── module.config.php │ │ ├── navigation.config.php │ │ ├── rbac.config.php │ │ ├── router.config.php │ │ └── twig.config.php │ ├── language │ │ ├── en.php │ │ └── ru.php │ ├── logs │ │ └── .gitignore │ ├── src │ │ ├── Controller │ │ │ ├── ActionControllerAbstract.php │ │ │ └── Plugin │ │ │ │ ├── TranslatePlugin.php │ │ │ │ └── TranslatePluginFactory.php │ │ ├── Module.php │ │ └── View │ │ │ └── Helper │ │ │ ├── AlertMessageHelper.php │ │ │ ├── LocaleHelper.php │ │ │ └── LocaleHelperFactory.php │ └── view │ │ ├── error │ │ ├── 404.twig │ │ └── index.twig │ │ ├── layout │ │ ├── breadcrumbs.twig │ │ ├── layout.twig │ │ ├── navigation.twig │ │ └── paginator.twig │ │ └── macro │ │ ├── component.twig │ │ ├── form.twig │ │ ├── icon.twig │ │ └── table.twig ├── DataProvider │ ├── config │ │ └── module.config.php │ └── src │ │ ├── DataProviderInterface.php │ │ ├── Module.php │ │ └── QueryDataProvider.php ├── ExAssetic │ ├── config │ │ ├── cli.config.php │ │ ├── module.config.php │ │ └── rbac.config.php │ └── src │ │ ├── Cache │ │ └── CacheBusterFactory.php │ │ ├── Command │ │ ├── BuildCommandFactory.php │ │ └── SetupCommandFactory.php │ │ ├── Filter │ │ ├── Sass │ │ │ └── SassFilterFactory.php │ │ ├── UglifyJs2FilterFactory.php │ │ └── Yui │ │ │ └── CssCompressorFilterFactory.php │ │ ├── Module.php │ │ └── Options │ │ ├── ModuleOptions.php │ │ └── ModuleOptionsFactory.php ├── ExCodeception │ └── src │ │ ├── BaseFixture.php │ │ ├── Connector │ │ └── ZF3.php │ │ └── Module │ │ ├── Fixture.php │ │ └── ZF3.php ├── ExDebugBar │ ├── assets │ │ └── .gitkeep │ ├── config │ │ ├── assets.config.php │ │ └── module.config.php │ └── src │ │ └── Module.php ├── ExDoctrine │ ├── config │ │ ├── module.config.php │ │ └── rbac.config.php │ └── src │ │ ├── Hydrator │ │ └── ObjectHydratorFactory.php │ │ ├── Module.php │ │ ├── Repository │ │ └── RepositoryInvokableFactory.php │ │ └── Type │ │ └── DateTimeType.php ├── ExPaginator │ └── src │ │ └── Adapter │ │ └── DoctrineAdapter.php ├── ExRbac │ └── src │ │ └── Module.php ├── ExTwig │ ├── config │ │ └── module.config.php │ └── src │ │ ├── Extension │ │ └── ClassExtension.php │ │ └── Module.php ├── ExValidate │ ├── config │ │ └── module.config.php │ ├── language │ │ ├── en.php │ │ └── ru.php │ └── src │ │ ├── Delegator │ │ └── TranslatorDelegatorFactory.php │ │ └── Module.php ├── Mail │ ├── config │ │ └── module.config.php │ └── src │ │ ├── Hydrator │ │ └── ModelHydrator.php │ │ ├── Message │ │ ├── MessageBuilder.php │ │ └── MessageBuilderFactory.php │ │ ├── Module.php │ │ ├── Options │ │ ├── ModuleOptions.php │ │ └── ModuleOptionsFactory.php │ │ └── Service │ │ ├── MailService.php │ │ └── MailServiceFactory.php └── User │ ├── codeception.yml │ ├── config │ ├── doctrine.config.php │ ├── mapping │ │ └── User.Entity.User.dcm.xml │ ├── module.config.php │ ├── navigation.config.php │ ├── rbac.config.php │ └── router.config.php │ ├── language │ ├── en.php │ └── ru.php │ ├── src │ ├── Adapter │ │ ├── AuthAdapter.php │ │ └── AuthAdapterFactory.php │ ├── Auth │ │ └── Result.php │ ├── Controller │ │ ├── AccessController.php │ │ ├── AccessControllerFactory.php │ │ ├── AuthController.php │ │ ├── AuthControllerFactory.php │ │ ├── ConfirmEmailController.php │ │ ├── ConfirmEmailControllerFactory.php │ │ ├── IndexController.php │ │ ├── SignupController.php │ │ └── SignupControllerFactory.php │ ├── Entity │ │ └── User.php │ ├── Form │ │ ├── AgainConfirmForm.php │ │ ├── ForgotPassForm.php │ │ ├── RestorePassForm.php │ │ ├── SignInForm.php │ │ ├── SignUpForm.php │ │ ├── SignUpFormFactory.php │ │ └── UserSearchForm.php │ ├── Module.php │ ├── Repository │ │ └── UserRepository.php │ ├── Search │ │ └── UserSearch.php │ └── Service │ │ ├── AccessService.php │ │ ├── AccessServiceFactory.php │ │ ├── AuthServiceFactory.php │ │ ├── ConfirmEmailService.php │ │ ├── SignUpService.php │ │ └── SignUpServiceFactory.php │ ├── test │ ├── _bootstrap.php │ ├── _output │ │ └── .gitignore │ ├── _support │ │ ├── FunctionalTester.php │ │ ├── Helper │ │ │ ├── Functional.php │ │ │ └── Unit.php │ │ ├── UnitTester.php │ │ └── _generated │ │ │ └── .gitignore │ ├── functional.suite.yml │ ├── functional │ │ ├── SignInCest.php │ │ └── _bootstrap.php │ ├── unit.suite.yml │ └── unit │ │ └── _bootstrap.php │ └── view │ ├── mail │ ├── confirm-request.twig │ └── restore-pass.twig │ └── user │ ├── access │ ├── forgot-pass.twig │ └── restore-pass.twig │ ├── auth │ └── signin.twig │ ├── confirm-email │ └── again.twig │ ├── index │ ├── index.twig │ └── row.twig │ └── signup │ └── signup.twig ├── package.json ├── public ├── .gitignore ├── assets │ └── .gitignore └── favicon.ico ├── test ├── _data │ └── user.php └── _fixture │ └── UserFixture.php ├── travis ├── before-install.sh └── before-script.sh └── workenv ├── config ├── .gitignore └── vagrant-local.yml.dist ├── nginx ├── log │ └── .gitignore └── site.conf └── provision ├── always-as-root.sh ├── once-as-root.sh └── once-as-vagrant.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | /vendor 4 | /.vagrant 5 | /nbproject 6 | /*.sublime-project 7 | /*.sublime-workspace 8 | /.idea 9 | /.php_cs.cache 10 | /_codeceptOutput 11 | /yarn-error.log 12 | /node_modules 13 | 14 | # delete before to create own project 15 | /yarn.lock 16 | /composer.lock 17 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | in(__DIR__ . '/bin') 8 | ->in(__DIR__ . '/config') 9 | ->in(__DIR__ . '/env') 10 | ->in(__DIR__ . '/module') 11 | ->in(__DIR__ . '/public'); 12 | return Config::create()->setRules([ 13 | '@PSR2' => true, 14 | 'array_syntax' => [ 15 | 'syntax' => 'short', 16 | ], 17 | ]) 18 | ->setFinder($finder); 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '7.1' 4 | - '7.2' 5 | 6 | dist: trusty 7 | 8 | services: 9 | - postgresql 10 | 11 | addons: 12 | postgresql: "9.6" 13 | 14 | before_install: sudo bash ./travis/before-install.sh 15 | 16 | before_script: bash ./travis/before-script.sh 17 | 18 | script: 19 | # run tests 20 | - composer test:build 21 | - composer test:run-with-cov 22 | 23 | after_script: 24 | - vendor/bin/php-coveralls -v --coverage_clover=_codeceptOutput/coverage.xml --json_path=data/runtime/coveralls-upload.json 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | zf-app-blank 2 | ============ 3 | 4 | 1.6.5 5 | ----- 6 | 7 | - Enh: Disabled generate proxies for not debug mode. 8 | 9 | 1.6.4 [2018-01-14] 10 | ------------------ 11 | 12 | - Add: Integration with Travis CI. 13 | - Add: Integration with Coveralls. 14 | 15 | 1.6.3 [2018-01-13] 16 | ------------------ 17 | 18 | - Fix: Link in README. 19 | - Enh: Upgrade `widmogrod/zf2-assetic-module` to 2.4. 20 | - Enh: Small refactoring of `ExAssetic`. 21 | - Rem: Mailgun config from the global config file. 22 | 23 | 1.6.2 [2018-01-12] 24 | ------------------ 25 | 26 | - Enh: Add CLI and Web configuration of XDebug. 27 | - Fix: Small fixes. 28 | 29 | 1.6.1 [2018-01-11] 30 | ------------------ 31 | 32 | - Fix: Requirements checker. 33 | - Fix: Email templates. 34 | - Fix: User demo list. 35 | 36 | 1.6.0 [2018-01-11] 37 | ------------------ 38 | 39 | - Fix: Symlinks. 40 | - Rem: Less. 41 | - Add: Sass. 42 | - Rem: Bootstrap 3. 43 | - Add: Bootstrap 4. 44 | - Enh: 2 intent instead of 4 for Twig, HTML, CSS, JS files. 45 | - Enh: Refactoring. 46 | 47 | 48 | 1.5.1 [2018-01-10] 49 | ------------------ 50 | 51 | - Enh: Upgrade packages. 52 | - Enh: Refactoring composer.json. 53 | - Rem: Assets packagist repository. 54 | - Add: Yarn package manager. 55 | - Add: `Bupy7\Zf\TimeZone` module. 56 | - Enh: Replaced `Application` name module to `Core`. 57 | - Enh: Small refactoring. 58 | - Add: List users. 59 | 60 | 1.5.0 [2017-09-13] 61 | ------------------ 62 | 63 | - Enh: Rename Composer command `fix` to `cs:fix`. 64 | - Add: Composer command `cs:check`. 65 | - Add: Composer command `test:run-with-cov`. 66 | - Fix: Package versions. 67 | - Add: #15. 68 | - Fix: #14. 69 | - Fix: #13. 70 | - Enh: #12. 71 | - Fix: #10. 72 | - Add: Authentication user on test. 73 | - Add: Autoload fixtures on each test. 74 | - Add: Signin, signup and login action tests. 75 | 76 | 1.4.0-beta.1 [2017-08-15] 77 | ------------------------- 78 | 79 | - Enh: Moved tests to each module. 80 | - Fix (#8): Doctrine proxy config. 81 | - Add (#9): CLI. 82 | - Fix: Small bugs. 83 | - Rem: DI. 84 | - Rem: Local constants. 85 | - Enh: Small changes. 86 | - Add: Clear chache Bash command. 87 | - Add: Error logs to `module/Application/logs`. 88 | 89 | 1.3.0 [2017-07-23] 90 | ------------------ 91 | 92 | - Fix (#4): Revert local constants. 93 | - Rem: JS framework integration. 94 | - Fix: Small bugs. 95 | 96 | 1.2.0 [2017-06-16] 97 | ----------------- 98 | - Add: Mailgun. 99 | - Add: Confirm email action. 100 | - Add: Recovery password action. 101 | - Add: Asset packagist. 102 | - Add: Codeception. 103 | - Add: XDebug. 104 | - Add: DI. 105 | - Add: Markdown in flash messages. 106 | - Enh: Composer packages. 107 | - Enh: PHP to 7.1. 108 | - Enh: Vagrant configuration. 109 | - Enh: Form. 110 | - Rep: Backbone validation package. 111 | - Rem: Ajax Email validation. 112 | - Rem: jQuery dependency. 113 | - Rem: Bower. 114 | 115 | 1.1.2 [2017-05-26] 116 | ------------------ 117 | 118 | - Fix: Provision. 119 | 120 | 1.1.1 [2017-03-30] 121 | ------------------ 122 | 123 | - Enh #3: Update the requirements script. 124 | 125 | 1.1.0 [2017-03-29] 126 | ------------------ 127 | 128 | - Fix: Added PHPCSFixer package to composer.json. 129 | - Enh: Rewrite Vagrant configuration. 130 | - New: Added MySQL. 131 | 132 | 1.0.0 [2016-11-15] 133 | ------------ 134 | 135 | - First release. 136 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Vasilij Belosludcev 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 5 | following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 8 | disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 22 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | zf-app-blank 2 | ============ 3 | 4 | [![Latest Stable Version](https://poser.pugx.org/bupy7/zf-app-blank/v/stable)](https://packagist.org/packages/bupy7/zf-app-blank) 5 | [![Latest Unstable Version](https://poser.pugx.org/bupy7/zf-app-blank/v/unstable)](https://packagist.org/packages/bupy7/zf-app-blank) 6 | [![License](https://poser.pugx.org/bupy7/zf-app-blank/license)](https://packagist.org/packages/bupy7/zf-app-blank) 7 | [![Build Status](https://travis-ci.org/bupy7/zf-app-blank.svg?branch=dev)](https://travis-ci.org/bupy7/zf-app-blank) 8 | [![Coverage Status](https://coveralls.io/repos/github/bupy7/zf-app-blank/badge.svg?branch=master)](https://coveralls.io/github/bupy7/zf-app-blank?branch=master) 9 | 10 | Autoloading standart [PSR-4](http://www.php-fig.org/psr/psr-4/). Coding standart [PSR-2](http://www.php-fig.org/psr/psr-2/). 11 | 12 | ![zf-app-blank](doc/screenshot.png) 13 | 14 | TODO 15 | ---- 16 | 17 | - Refactoring code to PHP 7.1. 18 | - Add PHP tests. 19 | 20 | Features 21 | -------- 22 | 23 | - PHP 7.1 24 | - [Zend Framework 3](https://github.com/zendframework/zendframework) 25 | - [Twitter Bootstrap 4](http://getbootstrap.com/) 26 | - [Doctrine ORM 2](http://www.doctrine-project.org/) 27 | - [Debug Bar](https://github.com/bupy7/zf-php-debug-bar) 28 | - [Twig](http://twig.sensiolabs.org/) 29 | - [Assetic Management](https://github.com/kriswallsmith/assetic) 30 | - [RBAC](https://github.com/ZF-Commons/zfc-rbac) 31 | - [Flexible Form Builder](https://github.com/bupy7/zf-form) 32 | - [Support Vagrant](https://www.vagrantup.com/) 33 | - [Support Composer](https://getcomposer.org/) 34 | - [Support Yarn](https://yarnpkg.com/) 35 | - [Database is PostgreSQL](https://www.postgresql.org/) 36 | - [Database is MySQL](https://www.mysql.com/) 37 | - [YUI Comressor](https://github.com/yui/yuicompressor) 38 | - [UglifyJS2](https://github.com/mishoo/UglifyJS2) 39 | - [PHP Coding Standarts Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) 40 | - [XDebug](https://xdebug.org/) 41 | - [Mailgun](https://www.mailgun.com/) 42 | - Multilanguage (English and Russian). 43 | - Simple example application: 44 | - Sign in 45 | - Sign up 46 | - Log out 47 | - Confirm Email address 48 | - Recovery password 49 | - [Symfony CLI](https://github.com/symfony/console) 50 | - [Codeception](http://codeception.com/) 51 | - [Sass](http://sass-lang.com/) 52 | 53 | Installation 54 | ------------ 55 | 56 | - Download and unpack the repository. 57 | 58 | - [Install Vagrant](https://www.vagrantup.com/docs/installation/) 59 | 60 | - Install plugins for Vagrant: 61 | 62 | ``` 63 | $ vagrant plugin install vagrant-vbguest 64 | $ vagrant plugin install vagrant-hostmanager 65 | ``` 66 | 67 | - Run install the work environment: 68 | 69 | ``` 70 | $ vagrant up 71 | ``` 72 | 73 | - Paste GitHub token in `/workenv/config/vagrant-local.yml` 74 | 75 | - Run again: 76 | 77 | ``` 78 | $ vagrant up 79 | ``` 80 | 81 | - Configure Mailgun in `/config/autoload/local.php`: 82 | 83 | You should [create Mailgun account](https://www.mailgun.com/) if you didn't do it before. 84 | Also, create [Postbin](http://bin.mailgun.net/). 85 | 86 | ```php 87 | 'mailgun' => [ 88 | 'key' => 'key-somekey', 89 | 'endpoint' => 'http://bin.mailgun.net/somekey', 90 | ], 91 | 'mail' => [ 92 | 'domain' => 'somesudomain.mailgun.org', 93 | ], 94 | ``` 95 | 96 | - Create scheme: 97 | 98 | ``` 99 | $ vagrant ssh -c 'php bin/console orm:schema-tool:create' 100 | ``` 101 | 102 | - Done. 103 | 104 | Testing 105 | ------- 106 | 107 | Run tests: 108 | 109 | ``` 110 | $ vagrant ssh -c 'composer test:build' 111 | $ vagrant ssh -c 'composer test:run' 112 | ``` 113 | 114 | License 115 | ------- 116 | 117 | zf-app-blank is released under the BSD 3-Clause License. 118 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # Required plugins 2 | # ----------------- 3 | # vagrant plugin install vagrant-vbguest 4 | # vagrant plugin install vagrant-hostmanager 5 | 6 | require 'yaml' 7 | require 'fileutils' 8 | 9 | domains = { 10 | main: 'zf-app-blank.local', 11 | } 12 | 13 | config = { 14 | local: './workenv/config/vagrant-local.yml', 15 | dist: './workenv/config/vagrant-local.yml.dist' 16 | } 17 | 18 | # copy config from example if local config not exists 19 | FileUtils.cp config[:dist], config[:local] unless File.exist?(config[:local]) 20 | # read config 21 | options = YAML.load_file config[:local] 22 | 23 | # check github token 24 | if options['github_token'].nil? || options['github_token'].to_s.length != 40 25 | puts "You must place REAL GitHub token into configuration:\n/workenv/config/vagrant-local.yml" 26 | exit 27 | end 28 | 29 | # vagrant configurate 30 | Vagrant.configure(2) do |config| 31 | # select the box 32 | config.vm.box = 'debian/jessie64' 33 | 34 | # should we ask about box updates? 35 | config.vm.box_check_update = options['box_check_update'] 36 | 37 | config.vm.provider 'virtualbox' do |vb| 38 | # machine cpus count 39 | vb.cpus = options['server_cpus'] 40 | # machine memory size 41 | vb.memory = options['server_memory'] 42 | # machine name (for VirtualBox UI) 43 | vb.name = options['server_name'] 44 | end 45 | 46 | # machine name (for vagrant console) 47 | config.vm.define options['server_name'] 48 | 49 | # machine name (for guest machine console) 50 | config.vm.hostname = options['server_name'] 51 | 52 | # network settings 53 | config.vm.network 'private_network', ip: options['ip'] 54 | 55 | # sync: folder of project (host machine) -> folder '/vagrant' (guest machine) 56 | config.vm.synced_folder ".", "/vagrant", type: "nfs" 57 | 58 | # hosts settings (host machine) 59 | config.vm.provision :hostmanager 60 | config.hostmanager.enabled = true 61 | config.hostmanager.manage_host = true 62 | config.hostmanager.ignore_private_ip = false 63 | config.hostmanager.include_offline = true 64 | config.hostmanager.aliases = domains.values 65 | 66 | # provisioners 67 | config.vm.provision 'shell' do |s| 68 | s.path = './workenv/provision/once-as-root.sh' 69 | s.args = [ 70 | options['server_time_zone'], 71 | options['mysql_db'], 72 | options['mysql_user'], 73 | options['mysql_pass'], 74 | options['php_time_zone'], 75 | options['php_memory_limit'], 76 | options['php_execution_time'], 77 | options['php_input_time'], 78 | options['server_locale'], 79 | options['pgsql_db'], 80 | options['pgsql_user'], 81 | options['pgsql_pass'], 82 | options['pgsql_locale'], 83 | options['db_type'], 84 | options['xdebug_idekey'], 85 | options['ip'] 86 | ] 87 | end 88 | config.vm.provision 'shell' do |s| 89 | s.path = './workenv/provision/once-as-vagrant.sh' 90 | s.args = [options['github_token']] 91 | s.privileged = false 92 | end 93 | config.vm.provision 'shell', path: './workenv/provision/always-as-root.sh', 94 | run: 'always', args: [options['db_type']] 95 | 96 | # post-install message (vagrant console) 97 | config.vm.post_up_message = "Main URL: http://#{domains[:main]}" 98 | end 99 | -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- 1 | /console* 2 | -------------------------------------------------------------------------------- /bin/clear-cache: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROOT_PATH=$(cd $(dirname $0) && pwd); 4 | 5 | if [ -f $ROOT_PATH/../data/runtime/module-classmap-cache.module_map.php ]; then 6 | echo '>>> Removing "module-classmap-cache.module_map.php"' 7 | rm $ROOT_PATH/../data/runtime/module-classmap-cache.module_map.php 8 | fi 9 | if [ -f $ROOT_PATH/../data/runtime/module-config-cache.app_config.php ]; then 10 | echo '>>> Removing "module-config-cache.app_config.php"' 11 | rm $ROOT_PATH/../data/runtime/module-config-cache.app_config.php 12 | fi 13 | if [ -d $ROOT_PATH/../data/runtime/DoctrineORMModule ]; then 14 | echo '>>> Removing "DoctrineORMModule"' 15 | rm -rf $ROOT_PATH/../data/runtime/DoctrineORMModule 16 | fi 17 | echo 'OK' 18 | -------------------------------------------------------------------------------- /bin/include/functions.php: -------------------------------------------------------------------------------- 1 | 31, 16 | 'green' => 32, 17 | 'yellow' => 33, 18 | 'blue' => 34, 19 | 'magenta' => 35, 20 | 'cyan' => 36, 21 | 'white' => 37, 22 | ]; 23 | return $styles[$name]; 24 | } 25 | -------------------------------------------------------------------------------- /bin/requirements: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 'php-bcmath', 15 | 'condition' => extension_loaded('bcmath'), 16 | ], 17 | [ 18 | 'name' => 'php-curl', 19 | 'condition' => extension_loaded('curl'), 20 | ], 21 | [ 22 | 'name' => 'php-intl', 23 | 'condition' => extension_loaded('intl'), 24 | ], 25 | [ 26 | 'name' => 'php-json', 27 | 'condition' => extension_loaded('json'), 28 | ], 29 | [ 30 | 'name' => 'php-mbstring', 31 | 'condition' => extension_loaded('mbstring'), 32 | ], 33 | [ 34 | 'name' => 'php-pdo-(pgsql|mysql)', 35 | 'condition' => extension_loaded('pdo_pgsql') || extension_loaded('pdo_mysql'), 36 | ], 37 | [ 38 | 'name' => 'php-mcrypt', 39 | 'condition' => extension_loaded('mcrypt'), 40 | ], 41 | [ 42 | 'name' => 'php-bz2', 43 | 'condition' => extension_loaded('bz2'), 44 | ], 45 | [ 46 | 'name' => 'php-zip', 47 | 'condition' => extension_loaded('zip'), 48 | ], 49 | [ 50 | 'name' => 'php-xml', 51 | 'condition' => extension_loaded('xml'), 52 | ], 53 | [ 54 | 'name' => 'sass', 55 | 'condition' => shellCommandExists('sass'), 56 | ], 57 | [ 58 | 'name' => 'yui-compressor', 59 | 'condition' => shellCommandExists('yuicompressor'), 60 | ], 61 | [ 62 | 'name' => 'uglify-js2', 63 | 'condition' => shellCommandExists('uglifyjs'), 64 | ], 65 | [ 66 | 'name' => 'java', 67 | 'condition' => shellCommandExists('java'), 68 | ], 69 | [ 70 | 'name' => 'ruby', 71 | 'condition' => shellCommandExists('ruby'), 72 | ], 73 | ]; 74 | 75 | $errors = 0; 76 | foreach ($checkList as $checkItem) { 77 | $errors += !$checkItem['condition']; 78 | $result = $checkItem['condition'] ? colorStr('OK', 'green') : colorStr('FAIL', 'red'); 79 | echo sprintf("%s: %s\n", $result, $checkItem['name']); 80 | } 81 | echo "---\n"; 82 | if ($errors) { 83 | echo colorStr(sprintf("%d Error(s)\n", $errors), 'red'); 84 | } else { 85 | echo colorStr("OK\n", 'green'); 86 | } 87 | 88 | function shellCommandExists($cmd) 89 | { 90 | $exec = shell_exec(sprintf('which %s', $cmd)); 91 | return !empty($exec); 92 | } 93 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - module/User 3 | paths: 4 | output: _codeceptOutput 5 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bupy7/zf-app-blank", 3 | "description": "A blank application Zend Framework 3.", 4 | "version": "1.6.4", 5 | "type": "project", 6 | "license": "BSD-3-Clause", 7 | "keywoards": [ 8 | "zend", 9 | "zf", 10 | "zf3", 11 | "framework", 12 | "skeleton", 13 | "blank", 14 | "application", 15 | "app" 16 | ], 17 | "authors": [ 18 | { 19 | "name": "Vasily Belosludcev", 20 | "email": "bupy765@gmail.com" 21 | } 22 | ], 23 | "require": { 24 | "php": "^7.1", 25 | "zendframework/zend-mvc": "~3.1.0", 26 | "zendframework/zend-mvc-i18n": "~1.0.0", 27 | "zendframework/zend-navigation": "~2.8.2", 28 | "zendframework/zend-validator": "~2.9.1", 29 | "zendframework/zend-crypt": "~3.2.0", 30 | "zendframework/zend-authentication": "~2.5.3", 31 | "zendframework/zend-mvc-plugin-flashmessenger": "~1.0.0", 32 | "zendframework/zend-mvc-plugin-identity": "~1.0.0", 33 | "zendframework/zend-mvc-plugin-prg": "~1.0.0", 34 | "zendframework/zend-mvc-plugin-fileprg": "~1.0.0", 35 | "zendframework/zend-json": "~3.0.0", 36 | "widmogrod/zf2-assetic-module": "~2.4", 37 | "zf-commons/zfc-rbac": "~2.6.3", 38 | "doctrine/doctrine-orm-module": "~1.1.0", 39 | "bupy7/zf-mailgun": "~1.0.0", 40 | "cebe/markdown": "~1.1.1", 41 | "bupy7/zf-form": "~1.2.0", 42 | "kokspflanze/zfc-twig": "~2.1", 43 | "symfony/console": "~3.3", 44 | "zendframework/zend-log": "~2.9", 45 | "doctrine/migrations": "~1.6", 46 | "bupy7/zf-time-zone": "~1.0" 47 | }, 48 | "require-dev": { 49 | "friendsofphp/php-cs-fixer": "~2.6", 50 | "codeception/codeception": "~2.3", 51 | "doctrine/data-fixtures": "~1.3", 52 | "bupy7/zf-php-debug-bar": "~0.13", 53 | "php-coveralls/php-coveralls": "~2.0" 54 | }, 55 | "autoload": { 56 | "psr-4": { 57 | "Core\\": "module/Core/src", 58 | "ExAssetic\\": "module/ExAssetic/src", 59 | "ExDebugBar\\": "module/ExDebugBar/src", 60 | "ExDoctrine\\": "module/ExDoctrine/src", 61 | "ExRbac\\": "module/ExRbac/src", 62 | "ExTwig\\": "module/ExTwig/src", 63 | "ExValidate\\": "module/ExValidate/src", 64 | "ExCodeception\\": "module/ExCodeception/src", 65 | "User\\": "module/User/src", 66 | "Mail\\": "module/Mail/src", 67 | "Cli\\": "module/Cli/src", 68 | "DataProvider\\": "module/DataProvider/src", 69 | "ExPaginator\\": "module/ExPaginator/src" 70 | } 71 | }, 72 | "autoload-dev": { 73 | "psr-4": { 74 | "User\\Test\\": "module/User/test" 75 | } 76 | }, 77 | "scripts": { 78 | "cs:fix": "./vendor/bin/php-cs-fixer fix", 79 | "cs:check": "./vendor/bin/php-cs-fixer fix --dry-run --diff", 80 | "test:run": "./vendor/bin/codecept run --ansi", 81 | "test:run-with-cov": "./vendor/bin/codecept run --ansi --coverage-html --coverage-xml", 82 | "test:build": [ 83 | "@db:test:drop", 84 | "@db:test:create", 85 | "@codecept:build" 86 | ], 87 | "db:test:drop": "bin/console-test orm:schema-tool:drop --force", 88 | "db:test:create": "bin/console-test orm:schema-tool:create", 89 | "db:test:update": "bin/console-test orm:schema-tool:update --force", 90 | "codecept:build": "./vendor/bin/codecept build --ansi" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | /constants-local.php 2 | -------------------------------------------------------------------------------- /config/application-test.config.php: -------------------------------------------------------------------------------- 1 | require __DIR__ . '/modules.config.php', 9 | 'module_listener_options' => [ 10 | 'config_glob_paths' => [ 11 | 'config/autoload/{{,*.}global,{,*.}local}.php', 12 | ], 13 | 'config_cache_enabled' => APP_ENV_PROD, 14 | 'config_cache_key' => 'app_config', 15 | 'module_map_cache_enabled' => APP_ENV_PROD, 16 | 'module_map_cache_key' => 'module_map', 17 | 'cache_dir' => 'data/runtime', 18 | 'check_dependencies' => APP_ENV_DEV || APP_ENV_TEST, 19 | ], 20 | ]; 21 | -------------------------------------------------------------------------------- /config/autoload/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore the local configuration files 2 | local.php 3 | -------------------------------------------------------------------------------- /config/autoload/global.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'display_exceptions' => APP_DEBUG, 11 | 'display_not_found_reason' => APP_DEBUG, 12 | ], 13 | // The ZfcTwig module configurations. 14 | 'zfctwig' => [ 15 | 'environment_options' => [ 16 | 'debug' => APP_DEBUG, 17 | ], 18 | ], 19 | // The ZfSnapPhpDebugBar moduleconfigurations. 20 | 'php-debug-bar' => [ 21 | 'enabled' => APP_ENV_DEV, 22 | ], 23 | // Doctrine 24 | 'doctrine' => [ 25 | 'configuration' => [ 26 | 'orm_default' => [ 27 | 'generate_proxies' => APP_DEBUG, 28 | ], 29 | ], 30 | ], 31 | ]; 32 | -------------------------------------------------------------------------------- /config/constants.php: -------------------------------------------------------------------------------- 1 | [ 10 | * 'path' => 'directory storing the local files', 11 | * 'skipFiles' => [ 12 | * // list of files that should only copied once and skipped if they already exist 13 | * ], 14 | * 'setWritable' => [ 15 | * // list of directories that should be set writable 16 | * ], 17 | * 'setExecutable' => [ 18 | * // list of files that should be set executable 19 | * ], 20 | * 'relativeSymlinks' => 'Whether need to create relative symlinks (true/false)? By default: false.', 21 | * 'createSymlink' => [ 22 | * // list of symlinks to be created. Keys are symlinks, and values are the targets. 23 | * ], 24 | * ], 25 | * ]; 26 | */ 27 | return [ 28 | 'dev' => [ 29 | 'path' => 'dev', 30 | 'setWritable' => [ 31 | 'public/assets', 32 | 'data/cache', 33 | ], 34 | 'setExecutable' => [ 35 | 'bin/console', 36 | 'bin/console-test', 37 | ], 38 | 'relativeSymlinks' => true, 39 | 'createSymlinks' => [], 40 | ], 41 | ]; 42 | -------------------------------------------------------------------------------- /env/dev/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | name, $composerConfig->version); 24 | 25 | /** @var \Zend\ServiceManager\ServiceManager $sm */ 26 | $sm = $app->getServiceManager(); 27 | 28 | // adding commands 29 | foreach ($sm->get('Cli\Options\ModuleOptions')->getCommands() as $command) { 30 | $cli->add($sm->get('CommandManager')->get($command)); 31 | } 32 | 33 | // adding doctrine commands 34 | /** @var Cli $doctrineCli */ 35 | $doctrineCli = $sm->get('doctrine.cli'); 36 | $cli->setHelperSet($doctrineCli->getHelperSet()); 37 | foreach ($doctrineCli->all() as $command) { 38 | $cli->add($command); 39 | } 40 | 41 | // run cli 42 | $cli->run(); 43 | -------------------------------------------------------------------------------- /env/dev/bin/console-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | name, $composerConfig->version); 24 | 25 | /** @var \Zend\ServiceManager\ServiceManager $sm */ 26 | $sm = $app->getServiceManager(); 27 | 28 | // adding commands 29 | foreach ($sm->get('Cli\Options\ModuleOptions')->getCommands() as $command) { 30 | $cli->add($sm->get('CommandManager')->get($command)); 31 | } 32 | 33 | // adding doctrine commands 34 | /** @var Cli $doctrineCli */ 35 | $doctrineCli = $sm->get('doctrine.cli'); 36 | $cli->setHelperSet($doctrineCli->getHelperSet()); 37 | foreach ($doctrineCli->all() as $command) { 38 | $cli->add($command); 39 | } 40 | 41 | // run cli 42 | $cli->run(); 43 | -------------------------------------------------------------------------------- /env/dev/config/autoload/local.php: -------------------------------------------------------------------------------- 1 | [ 12 | 'connection' => [ 13 | 'orm_default' => [ 14 | // 'driverClass' => PgSqlDriver::class, 15 | 'driverClass' => MySqlDriver::class, 16 | 'params' => [ 17 | 'host' => '127.0.0.1', 18 | // 'port' => 5432, 19 | 'port' => 3306, 20 | 'user' => 'zf_app_blank', 21 | 'password' => '1234', 22 | 'dbname' => 'zf_app_blank' . (APP_ENV_TEST ? '_test' : ''), 23 | ], 24 | ], 25 | ], 26 | ], 27 | 'ex_assetic' => [ 28 | 'node_bin' => '/usr/bin/node', 29 | 'yui_path' => '/usr/lib/node_modules/yuicompressor/build/yuicompressor-2.4.8.jar', 30 | 'java_path' => '/usr/bin/java', 31 | 'uglify_js2_path' => '/usr/bin/uglifyjs', 32 | 'sass_path' => '/usr/local/bin/sass', 33 | ], 34 | 'mailgun' => [ 35 | 'debug' => true, 36 | 'key' => 'key-example', // example key-dfdfh87d765s45ra7s65a4sasdas76253e 37 | 'endpoint' => 'endpoint-example', // example http://bin.mailgun.net/sd7gs4wsd 38 | ], 39 | 'mail' => [ 40 | 'domain' => 'example.mailgun.org', // example mail.my-site.com 41 | ], 42 | ]; 43 | -------------------------------------------------------------------------------- /env/dev/public/index-test.php: -------------------------------------------------------------------------------- 1 | run(); 21 | -------------------------------------------------------------------------------- /env/dev/public/index.php: -------------------------------------------------------------------------------- 1 | run(); 21 | -------------------------------------------------------------------------------- /module/Cli/config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'commands' => [], 8 | ], 9 | 'command_manager' => [], 10 | 'service_manager' => [ 11 | 'factories' => [ 12 | Manager\CommandManager::class => Manager\CommandManagerFactory::class, 13 | Options\ModuleOptions::class => Options\ModuleOptionsFactory::class, 14 | ], 15 | 'aliases' => [ 16 | 'CommandManager' => Manager\CommandManager::class, 17 | ], 18 | ], 19 | ]; 20 | -------------------------------------------------------------------------------- /module/Cli/src/Manager/CommandManager.php: -------------------------------------------------------------------------------- 1 | addInitializer([$this, 'injectEventManager']); 18 | parent::__construct($configOrContainerInstance, $config); 19 | } 20 | 21 | public function injectEventManager(ContainerInterface $container, Command $command) 22 | { 23 | if (!$command instanceof EventManagerAwareInterface) { 24 | return; 25 | } 26 | $events = $command->getEventManager(); 27 | if (!$events || !$events->getSharedManager() instanceof SharedEventManagerInterface) { 28 | $command->setEventManager($container->get('EventManager')); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /module/Cli/src/Manager/CommandManagerFactory.php: -------------------------------------------------------------------------------- 1 | getEvent(); 19 | $container = $event->getParam('ServiceManager'); 20 | /** @var \Zend\ModuleManager\Listener\ServiceListener $serviceListener */ 21 | $serviceListener = $container->get('ServiceListener'); 22 | $serviceListener->addServiceManager( 23 | 'CommandManager', 24 | 'command_manager', 25 | 'Cli\Manager\Provider\CommandManagerProviderInterface', 26 | 'getCommandManagerConfig' 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /module/Cli/src/Options/ModuleOptions.php: -------------------------------------------------------------------------------- 1 | commands = $commands; 17 | return $this; 18 | } 19 | 20 | public function getCommands(): array 21 | { 22 | return $this->commands; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /module/Cli/src/Options/ModuleOptionsFactory.php: -------------------------------------------------------------------------------- 1 | get('config')['cli']); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /module/Core/assets/css/404.sass: -------------------------------------------------------------------------------- 1 | @import url("//fonts.googleapis.com/css?family=Comfortaa&subset=latin,cyrillic") 2 | 3 | .page-404 4 | h1 5 | font-family: 'Comfortaa', cursive 6 | 7 | -------------------------------------------------------------------------------- /module/Core/assets/css/base.sass: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | height: 100% 4 | 5 | .wrap 6 | height: auto 7 | margin: 0 auto -60px 8 | min-height: 100% 9 | padding-bottom: 60px 10 | 11 | > .container 12 | padding: 15px 20px 13 | 14 | footer 15 | height: 60px 16 | background-color: #f5f5f5 17 | border-top: 1px solid #ddd 18 | padding-top: 20px 19 | 20 | .navbar-brand 21 | img 22 | margin-top: -3px 23 | margin-right: 5px 24 | 25 | .form-group 26 | &.required label::after 27 | content: " *" 28 | color: $danger 29 | 30 | .help-block 31 | font-size: $small-font-size 32 | -------------------------------------------------------------------------------- /module/Core/assets/css/style.sass: -------------------------------------------------------------------------------- 1 | // libraries 2 | @import "../../../../node_modules/bootstrap/scss/bootstrap" 3 | @import "../../../../node_modules/font-awesome/scss/font-awesome" 4 | 5 | // core styles 6 | @import "404" 7 | @import "base" 8 | -------------------------------------------------------------------------------- /module/Core/assets/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bupy7/zf-app-blank/6ffb30bcfca4c0ab135c6bdd5469fbaac273a63b/module/Core/assets/img/404.png -------------------------------------------------------------------------------- /module/Core/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bupy7/zf-app-blank/6ffb30bcfca4c0ab135c6bdd5469fbaac273a63b/module/Core/assets/img/logo.png -------------------------------------------------------------------------------- /module/Core/assets/js/navbar.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var mainNavbar = document.querySelector('#main-navbar'); 3 | 4 | new Collapse(mainNavbar.querySelector('.navbar-toggler')); 5 | })(); 6 | -------------------------------------------------------------------------------- /module/Core/config/assets.config.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'acceptableErrors' => [ 14 | GuardInterface::GUARD_UNAUTHORIZED 15 | ], 16 | 'debug' => APP_DEBUG, 17 | 'buildOnRequest' => APP_ENV_DEV, 18 | 'webPath' => getcwd() . '/public/assets', 19 | 'basePath' => 'assets', 20 | 'default' => [ 21 | 'assets' => [ 22 | '@library_js', 23 | 24 | '@app_css', 25 | '@app_js', 26 | ], 27 | 'options' => [ 28 | 'mixin' => true, 29 | ], 30 | ], 31 | 'modules' => [ 32 | 'Core' => [ 33 | 'root_path' => __DIR__ . '/../assets', 34 | 'collections' => [ 35 | 'app_css' => [ 36 | 'assets' => [ 37 | 'css/style.sass', 38 | ], 39 | 'filters' => [ 40 | 'SassFilter', 41 | '?CssCompressorFilter', 42 | ], 43 | 'options' => [ 44 | 'output' => 'css/app.min.css', 45 | ], 46 | ], 47 | 'app_js' => [ 48 | 'assets' => [ 49 | 'js/navbar.js', 50 | ], 51 | 'filters' => [ 52 | '?UglifyJs2Filter', 53 | ], 54 | 'options' => [ 55 | 'output' => 'js/app.min.js', 56 | ], 57 | ], 58 | 'library_js' => [ 59 | 'assets' => [ 60 | $npmPath . '/bootstrap.native/dist/bootstrap-native-v4.js', 61 | ], 62 | 'filters' => [ 63 | '?UglifyJs2Filter', 64 | ], 65 | 'options' => [ 66 | 'output' => 'js/library.min.js', 67 | ], 68 | ], 69 | 'app_images' => [ 70 | 'assets' => [ 71 | 'img/*', 72 | ], 73 | 'options' => [ 74 | 'move_raw' => true, 75 | ], 76 | ], 77 | 'font_awesome_fonts' => [ 78 | 'assets' => [ 79 | $npmPath . '/font-awesome/fonts/*', 80 | ], 81 | 'options' => [ 82 | 'move_raw' => true, 83 | 'targetPath' => 'fonts', 84 | 'disable_source_path' => true, 85 | ], 86 | ], 87 | ], 88 | ], 89 | ], 90 | ], 91 | ]; 92 | -------------------------------------------------------------------------------- /module/Core/config/doctrine.config.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'migrations_configuration' => [ 10 | 'orm_default' => [ 11 | 'directory' => getcwd() . '/data/migrations', 12 | 'name' => 'Doctrine Database Migrations', 13 | 'namespace' => 'DoctrineORMModule\Migrations', 14 | 'table' => 'migration', 15 | 'column' => 'version', 16 | ], 17 | ], 18 | 'configuration' => [ 19 | 'orm_default' => [ 20 | 'proxy_dir' => 'data/runtime/DoctrineORMModule/Proxy' 21 | ], 22 | ], 23 | ], 24 | ]; 25 | -------------------------------------------------------------------------------- /module/Core/config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'time_zone' => 'Europe/London', 18 | ], 19 | 'service_manager' => [ 20 | 'abstract_factories' => [ 21 | StorageCacheAbstractServiceFactory::class, 22 | LoggerAbstractServiceFactory::class, 23 | NavigationAbstractServiceFactory::class, 24 | ], 25 | 'factories' => [ 26 | 'translator' => TranslatorFactory::class, 27 | ], 28 | ], 29 | 'translator' => [ 30 | 'locale' => 'en', 31 | 'translation_file_patterns' => [ 32 | [ 33 | 'type' => 'phparray', 34 | 'base_dir' => __DIR__ . '/../language', 35 | 'pattern' => '%s.php', 36 | 'text_domain' => 'Core', 37 | ], 38 | ], 39 | ], 40 | 'controller_plugins' => [ 41 | 'factories' => [ 42 | 'translate' => TranslatePluginFactory::class, 43 | ], 44 | ], 45 | 'view_manager' => [ 46 | 'doctype' => 'HTML5', 47 | 'not_found_template' => 'error/404', 48 | 'exception_template' => 'error/index', 49 | 'template_path_stack' => [ 50 | __DIR__ . '/../view', 51 | ], 52 | 'strategies' => [ 53 | 'ViewJsonStrategy', 54 | ], 55 | ], 56 | 'view_helpers' => [ 57 | 'factories' => [ 58 | 'locale' => LocaleHelperFactory::class, 59 | ], 60 | 'invokables' => [ 61 | 'alertMessage' => AlertMessageHelper::class, 62 | ], 63 | ], 64 | ]; 65 | -------------------------------------------------------------------------------- /module/Core/config/navigation.config.php: -------------------------------------------------------------------------------- 1 | [ 9 | // for guest 10 | 'guestright' => [], 11 | // for auth 12 | 'authright' => [], 13 | // default 14 | 'default' => [], 15 | ], 16 | ]; 17 | -------------------------------------------------------------------------------- /module/Core/config/rbac.config.php: -------------------------------------------------------------------------------- 1 | [ 13 | /** 14 | * Key that is used to fetch the identity provider 15 | * 16 | * Please note that when an identity is found, it MUST implements the ZfcRbac\Identity\IdentityProviderInterface 17 | * interface, otherwise it will throw an exception. 18 | */ 19 | // 'identity_provider' => 'ZfcRbac\Identity\AuthenticationIdentityProvider', 20 | 21 | /** 22 | * Set the guest role 23 | * 24 | * This role is used by the authorization service when the authentication service returns no identity 25 | */ 26 | 'guest_role' => 'guest', 27 | /** 28 | * Set the guards 29 | * 30 | * You must comply with the various options of guards. The format must be of the following format: 31 | * 32 | * 'guards' => [ 33 | * 'ZfcRbac\Guard\RouteGuard' => [ 34 | * // options 35 | * ] 36 | * ] 37 | */ 38 | 'guards' => [ 39 | RoutePermissionsGuard::class => [], 40 | ], 41 | /** 42 | * As soon as one rule for either route or controller is specified, a guard will be automatically 43 | * created and will start to hook into the MVC loop. 44 | * 45 | * If the protection policy is set to DENY, then any route/controller will be denied by 46 | * default UNLESS it is explicitly added as a rule. On the other hand, if it is set to ALLOW, then 47 | * not specified route/controller will be implicitly approved. 48 | * 49 | * DENY is the most secure way, but it is more work for the developer 50 | */ 51 | 'protection_policy' => GuardInterface::POLICY_DENY, 52 | /** 53 | * Configuration for role provider 54 | * 55 | * It must be an array that contains configuration for the role provider. The provider config 56 | * must follow the following format: 57 | * 58 | * 'ZfcRbac\Role\InMemoryRoleProvider' => [ 59 | * 'role1' => [ 60 | * 'children' => ['children1', 'children2'], // OPTIONAL 61 | * 'permissions' => ['edit', 'read'] // OPTIONAL 62 | * ] 63 | * ] 64 | * 65 | * Supported options depend of the role provider, so please refer to the official documentation 66 | */ 67 | 'role_provider' => [ 68 | InMemoryRoleProvider::class => [ 69 | 'registered' => [ 70 | 'children' => ['guest'], 71 | ], 72 | 'guest' => [], 73 | ], 74 | ], 75 | /** 76 | * Configure the unauthorized strategy. It is used to render a template whenever a user is unauthorized 77 | */ 78 | 'unauthorized_strategy' => [ 79 | /** 80 | * Set the template name to render 81 | */ 82 | //'template' => 'error/403' 83 | ], 84 | /** 85 | * Configure the redirect strategy. It is used to redirect the user to another route when a user is 86 | * unauthorized 87 | */ 88 | 'redirect_strategy' => [ 89 | /** 90 | * Enable redirection when the user is connected 91 | */ 92 | 'redirect_when_connected' => true, 93 | /** 94 | * Set the route to redirect when user is connected (of course, it must exist!) 95 | */ 96 | 'redirect_to_route_connected' => 'home', 97 | /** 98 | * Set the route to redirect when user is disconnected (of course, it must exist!) 99 | */ 100 | 'redirect_to_route_disconnected' => 'signin', 101 | /** 102 | * If a user is unauthorized and redirected to another route (login, for instance), should we 103 | * append the previous URI (the one that was unauthorized) in the query params? 104 | */ 105 | 'append_previous_uri' => false, 106 | /** 107 | * If append_previous_uri option is set to true, this option set the query key to use when 108 | * the previous uri is appended 109 | */ 110 | 'previous_uri_query_key' => 'redirectTo' 111 | ], 112 | /** 113 | * Various plugin managers for guards and role providers. Each of them must follow a common 114 | * plugin manager config format, and can be used to create your custom objects 115 | */ 116 | // 'guard_manager' => [], 117 | // 'role_provider_manager' => [] 118 | ] 119 | ]; 120 | -------------------------------------------------------------------------------- /module/Core/config/router.config.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'routes' => [ 10 | 'home' => [ 11 | 'type' => 'literal', 12 | 'options' => [ 13 | 'route' => '/', 14 | ], 15 | ], 16 | ], 17 | ], 18 | ]; 19 | -------------------------------------------------------------------------------- /module/Core/config/twig.config.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'environment_options' => [ 14 | 'autoescape' => false, 15 | 'strict_variables' => true, 16 | ], 17 | 'extensions' => [ 18 | 'debug' => Twig_Extension_Debug::class, 19 | ], 20 | 'helper_manager' => [ 21 | 'invokables' => [ 22 | 'partial' => Partial::class, 23 | 'paginationControl' => \Zend\View\Helper\PaginationControl::class, 24 | ], 25 | 'factories' => [ 26 | FormBuilderHelper::class => FormBuilderHelperFactory::class, 27 | ], 28 | 'shared' => [ 29 | 'formBuilder' => false, 30 | FormBuilderHelper::class => false, 31 | ], 32 | 'aliases' => [ 33 | 'formBuilder' => FormBuilderHelper::class, 34 | ], 35 | ], 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /module/Core/language/en.php: -------------------------------------------------------------------------------- 1 | 'All right reserved', 5 | 6 | 'TITLE_HOMEPAGE' => 'App-Blank', 7 | 'TITLE_404' => '404 - Not found', 8 | 'TITLE_FATAL_ERROR' => 'Fatal error', 9 | 10 | 'DESC_404' => 'The requested page could not be found', 11 | 'EXCEPTION_USER_MESSAGE' => 'For more information contact to the site administrator.', 12 | 13 | 'LABEL_NOTHING_FOUND' => 'Nothing found', 14 | 'LABEL_PAGINATION_INFO' => 'Records %d-%d of %d', 15 | 'LABEL_ERROR_FILE' => 'File', 16 | 'LABEL_ERROR_MESSAGE' => 'Message', 17 | 'LABEL_ERROR_STACK_TRACE' => 'Call stack', 18 | 'LABEL_ERROR_EXCEPTION' => 'Exception', 19 | ]; 20 | -------------------------------------------------------------------------------- /module/Core/language/ru.php: -------------------------------------------------------------------------------- 1 | 'Все права защищены', 5 | 6 | 'TITLE_HOMEPAGE' => 'App-Blank', 7 | 'TITLE_404' => '404 - Ничего не найдено', 8 | 'TITLE_FATAL_ERROR' => 'Произошла непоправимая ошибка', 9 | 10 | 'DESC_404' => 'Запрашиваемая страница не найдена', 11 | 12 | 'LABEL_ERROR_FILE' => 'Файл', 13 | 'LABEL_ERROR_MESSAGE' => 'Сообщение', 14 | 'LABEL_ERROR_STACK_TRACE' => 'Стек вызовов', 15 | 'LABEL_ERROR_EXCEPTION' => 'Исключение', 16 | 'LABEL_NOTHING_FOUND' => 'Ничего не найдено', 17 | 'LABEL_PAGINATION_INFO' => 'Показаны записи %d-%d из %d', 18 | 19 | 'EXCEPTION_USER_MESSAGE' => 'Обратитесь к администратору сайта за дополнительной информацией.', 20 | ]; 21 | -------------------------------------------------------------------------------- /module/Core/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !/.gitignore 3 | -------------------------------------------------------------------------------- /module/Core/src/Controller/ActionControllerAbstract.php: -------------------------------------------------------------------------------- 1 | getResponse()->setStatusCode($code); 20 | return new JsonModel($data); 21 | } 22 | 23 | public function asView(array $data = [], array $options = []): ViewModel 24 | { 25 | return new ViewModel(count($data) > 0 ? $data : null, count($options) > 0 ? $options : null); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /module/Core/src/Controller/Plugin/TranslatePlugin.php: -------------------------------------------------------------------------------- 1 | translator = $translator; 25 | } 26 | 27 | /** 28 | * Translate a message. 29 | * 30 | * @param string $message 31 | * @param string $textDomain 32 | * @param string $locale 33 | * @return string 34 | */ 35 | public function __invoke($message, $textDomain = null, $locale = null) 36 | { 37 | if ($textDomain === null) { 38 | $textDomain = $this->translatorTextDomain; 39 | } 40 | return $this->translator->translate($message, $textDomain, $locale); 41 | } 42 | 43 | /** 44 | * @param string $textDomain 45 | */ 46 | public function setTranslatorTextDomain($textDomain) 47 | { 48 | $this->translatorTextDomain = $textDomain; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /module/Core/src/Controller/Plugin/TranslatePluginFactory.php: -------------------------------------------------------------------------------- 1 | get('translator')); 16 | return $translatePlg; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /module/Core/src/Module.php: -------------------------------------------------------------------------------- 1 | getApplication()->getEventManager()->getSharedManager(); 33 | $logger = $this->getLogger(); 34 | $sharedManager->attach('Zend\Mvc\Application', 'dispatch.error', function ($e) use ($logger) { 35 | if ($e->getParam('exception')) { 36 | $logger->crit($e->getParam('exception')); 37 | } 38 | }); 39 | Logger::registerFatalErrorShutdownFunction($logger); 40 | } 41 | 42 | protected function getLogger(): Logger 43 | { 44 | $logger = new Logger; 45 | $writer = new Stream(self::ERROR_LOG_FILE); 46 | return $logger->addWriter($writer); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /module/Core/src/View/Helper/AlertMessageHelper.php: -------------------------------------------------------------------------------- 1 | setFlashMessenger(); 45 | return $this->getFlashMessages(); 46 | } 47 | 48 | /** 49 | * Constructor method. 50 | */ 51 | public function __construct() 52 | { 53 | $this->setFlashMessenger(); 54 | $this->markdown = new GithubMarkdown(); 55 | } 56 | 57 | /** 58 | * Add a new message 59 | * 60 | * @param string $message 61 | * @param bool $dismissable 62 | */ 63 | public function addMessage($message, $dismissable = true) 64 | { 65 | $this->addMessageByType(self::MESSAGE_TYPE_DEFAULT, $message, $dismissable); 66 | } 67 | 68 | /** 69 | * Add a new success message 70 | * 71 | * @param string $successMessage 72 | * @param bool $dismissable 73 | */ 74 | public function addSuccessMessage($successMessage, $dismissable = true) 75 | { 76 | $this->addMessageByType(self::MESSAGE_TYPE_SUCCESS, $successMessage, $dismissable); 77 | } 78 | 79 | /** 80 | * Add a new info message 81 | * 82 | * @param string $infoMessage 83 | * @param bool $dismissable 84 | */ 85 | public function addInfoMessage($infoMessage, $dismissable = true) 86 | { 87 | $this->addMessageByType(self::MESSAGE_TYPE_INFO, $infoMessage, $dismissable); 88 | } 89 | 90 | /** 91 | * Add a new warning message 92 | * 93 | * @param string $warningMessage 94 | * @param bool $dismissable 95 | */ 96 | public function addWarningMessage($warningMessage, $dismissable = false) 97 | { 98 | $this->addMessageByType(self::MESSAGE_TYPE_WARNING, $warningMessage, $dismissable); 99 | } 100 | 101 | /** 102 | * Add a new error message 103 | * 104 | * @param string $errorMessage 105 | * @param bool $dismissable 106 | */ 107 | public function addErrorMessage($errorMessage, $dismissable = false) 108 | { 109 | $this->addMessageByType(self::MESSAGE_TYPE_ERROR, $errorMessage, $dismissable); 110 | } 111 | 112 | /** 113 | * Set a instance of the FlashMessenger. 114 | */ 115 | public function setFlashMessenger() 116 | { 117 | if ($this->flashMessenger === null) { 118 | $this->flashMessenger = new FlashMessenger(); 119 | } 120 | } 121 | 122 | /** 123 | * Add a message by type 124 | * 125 | * @param string $type 126 | * @param string $message 127 | * @param bool $dismissable 128 | */ 129 | protected function addMessageByType($type, $message, $dismissable = true) 130 | { 131 | self::$flashMessages[$type][] = [ 132 | 'message' => $this->markdown->parseParagraph($message), 133 | 'dismissable' => $dismissable, 134 | ]; 135 | } 136 | 137 | /** 138 | * Get all flash messages from the plugin 139 | */ 140 | protected function getFlashMessages() 141 | { 142 | if ($this->flashMessenger->hasMessages()) { 143 | foreach ($this->flashMessenger->getMessages() as $message) { 144 | $this->addMessageByType(self::MESSAGE_TYPE_DEFAULT, $message); 145 | } 146 | } 147 | if ($this->flashMessenger->hasSuccessMessages()) { 148 | foreach ($this->flashMessenger->getSuccessMessages() as $successMessages) { 149 | $this->addMessageByType(self::MESSAGE_TYPE_SUCCESS, $successMessages); 150 | } 151 | } 152 | if ($this->flashMessenger->hasInfoMessages()) { 153 | foreach ($this->flashMessenger->getInfoMessages() as $infoMessages) { 154 | $this->addMessageByType(self::MESSAGE_TYPE_INFO, $infoMessages); 155 | } 156 | } 157 | if ($this->flashMessenger->hasWarningMessages()) { 158 | foreach ($this->flashMessenger->getWarningMessages() as $warningMessages) { 159 | $this->addMessageByType(self::MESSAGE_TYPE_WARNING, $warningMessages); 160 | } 161 | } 162 | if ($this->flashMessenger->hasErrorMessages()) { 163 | foreach ($this->flashMessenger->getErrorMessages() as $errorMessages) { 164 | $this->addMessageByType(self::MESSAGE_TYPE_ERROR, $errorMessages); 165 | } 166 | } 167 | return self::$flashMessages; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /module/Core/src/View/Helper/LocaleHelper.php: -------------------------------------------------------------------------------- 1 | _locale = $locale; 23 | } 24 | 25 | /** 26 | * @return string 27 | */ 28 | public function __invoke() 29 | { 30 | return $this->_locale; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /module/Core/src/View/Helper/LocaleHelperFactory.php: -------------------------------------------------------------------------------- 1 | get('config')['translator']['locale']); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /module/Core/view/error/404.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout/layout.twig' %} 2 | 3 | {% do headTitle(translate('TITLE_404', 'Core')) %} 4 | 5 | {% block content %} 6 |
7 |
8 |

{{ translate('DESC_404', 'Core') }}

9 |
10 |
11 | 12 |
13 |
14 | {% endblock content %} 15 | -------------------------------------------------------------------------------- /module/Core/view/error/index.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout/layout.twig' %} 2 | 3 | {% do headTitle(translate('TITLE_FATAL_ERROR', 'Core')) %} 4 | 5 | {% block content %} 6 | 14 | {% if display_exceptions %} 15 |

{{ translate('LABEL_ERROR_EXCEPTION', 'Core') }}: {{ get_class(exception) }}

16 |

{{ translate('LABEL_ERROR_FILE', 'Core') }}:

17 |
{{ exception.getFile() }}:{{ exception.getLine() }}
18 | {% if exception.getMessage %} 19 |

{{ translate('LABEL_ERROR_MESSAGE', 'Core') }}:

20 |
{{ exception.getMessage()|e }}
21 | {% endif %} 22 |

{{ translate('LABEL_ERROR_STACK_TRACE', 'Core') }}:

23 |
{{ exception.getTraceAsString()|e }}
24 | {% endif %} 25 | {% if not display_exceptions %} 26 |

{{ translate('EXCEPTION_USER_MESSAGE', 'Core') }}

27 | {% endif %} 28 | {% endblock content %} 29 | -------------------------------------------------------------------------------- /module/Core/view/layout/breadcrumbs.twig: -------------------------------------------------------------------------------- 1 | {% if pages is not empty %} 2 | 28 | {% endif %} 29 | -------------------------------------------------------------------------------- /module/Core/view/layout/layout.twig: -------------------------------------------------------------------------------- 1 | {% import 'macro/component.twig' as component %} 2 | 3 | {% set identity = identity() %} 4 | 5 | {{ doctype() }} 6 | 7 | 8 | 9 | 10 | {{ headTitle(translate('TITLE_HOMEPAGE', 'Core')).setSeparator(' - ') }} 11 | 12 | 13 | 14 | 15 | {% block meta %}{% endblock meta %} 16 | 17 | {{ headMeta() }} 18 | 19 | 20 | 21 | {{ headLink() }} 22 | 23 | {% block link %}{% endblock link %} 24 | 25 | {{ headScript() }} 26 | 27 | {% block script %}{% endblock script %} 28 | 29 | 30 |
31 | 63 | 64 |
65 | {{ component.alert(alertMessage()) }} 66 | {{ navigation('Zend\\Navigation\\Default') 67 | .breadcrumbs() 68 | .setPartial('layout/breadcrumbs.twig') }} 69 | {% block content %}{% endblock content %} 70 |
71 |
72 | 73 | 82 | 83 | {{ inlineScript() }} 84 | 85 | {% block inline %}{% endblock inline %} 86 | 87 | 88 | -------------------------------------------------------------------------------- /module/Core/view/layout/navigation.twig: -------------------------------------------------------------------------------- 1 | 70 | -------------------------------------------------------------------------------- /module/Core/view/layout/paginator.twig: -------------------------------------------------------------------------------- 1 | {% if pageCount > 1 %} 2 | {% set query = query | default({}) %} 3 | 38 | {% endif %} 39 | -------------------------------------------------------------------------------- /module/Core/view/macro/component.twig: -------------------------------------------------------------------------------- 1 | {% macro alert(items) %} 2 | {% set templates = { 3 | 'default': '
%s%s
', 4 | 'success': '
%s%s
', 5 | 'info': '
%s%s
', 6 | 'warning': '
%s%s
', 7 | 'error': '
%s%s
' 8 | } %} 9 | {% for type, messages in items %} 10 | {% for message in messages %} 11 | {% set dismissable = {class: '', button: ''} %} 12 | {% if message.dismissable %} 13 | {% set dismissable = { 14 | class: ' alert-dismisable', 15 | button: '' 16 | } %} 17 | {% endif %} 18 | {{ templates[type]|format(dismissable.class, dismissable.button, message.message) }} 19 | {% endfor %} 20 | {% endfor %} 21 | {% endmacro %} 22 | 23 | {% macro paginator(paginator, queryParams, part, displayTotal) %} 24 | {% set displayTotal = displayTotal is null ? true : displayTotal %} 25 | 26 |
27 |
28 | {{ paginationControl( 29 | paginator, 30 | 'sliding', 31 | ['layout', part | default(''), 'paginator.twig'] | join('/'), 32 | { 33 | query: queryParams | default({}) 34 | } 35 | ) }} 36 |
37 | {% if displayTotal %} 38 |
39 |
40 | {% set inc = 0 %} 41 | {% if paginator.getTotalItemCount() > 0 %} 42 | {% set inc = 1 %} 43 | {% endif %} 44 | 45 | {% set start = (paginator.getCurrentPageNumber() - 1) * paginator.getItemCountPerPage() + inc %} 46 | {% if paginator.getItemCountPerPage() != paginator.getCurrentItemCount() %} 47 | {% set diff = (paginator.getItemCountPerPage() - paginator.getCurrentItemCount()) | abs %} 48 | {% set end = paginator.getCurrentPageNumber() * paginator.getItemCountPerPage() - diff %} 49 | {% else %} 50 | {% set end = paginator.getCurrentPageNumber() * paginator.getItemCountPerPage() %} 51 | {% endif %} 52 | 53 | {% set total = paginator.getTotalItemCount() %} 54 | 55 | {{ translate('LABEL_PAGINATION_INFO', 'Core') | format(start, end, total) }} 56 |
57 |
58 | {% endif %} 59 |
60 | {% endmacro %} 61 | 62 | -------------------------------------------------------------------------------- /module/Core/view/macro/form.twig: -------------------------------------------------------------------------------- 1 | {% macro hasError(formBuilder, name) %} 2 | {{ formBuilder.hasError(name) ? ' is-invalid' : '' }} 3 | {% endmacro %} 4 | -------------------------------------------------------------------------------- /module/Core/view/macro/icon.twig: -------------------------------------------------------------------------------- 1 | {% macro fa(name) %} 2 | 3 | {% endmacro %} 4 | -------------------------------------------------------------------------------- /module/Core/view/macro/table.twig: -------------------------------------------------------------------------------- 1 | {% macro emptyRow(colspan) %} 2 | 3 | {{ translate('LABEL_NOTHING_FOUND', 'Core') }} 4 | 5 | {% endmacro %} 6 | -------------------------------------------------------------------------------- /module/DataProvider/config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'factories' => [ 8 | QueryDataProvider::class => \Zend\ServiceManager\AbstractFactory\ReflectionBasedAbstractFactory::class, 9 | ], 10 | 'shared' => [ 11 | QueryDataProvider::class => false, 12 | ], 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /module/DataProvider/src/DataProviderInterface.php: -------------------------------------------------------------------------------- 1 | pageRange = $pageRange; 72 | return $this; 73 | } 74 | 75 | public function setPage(int $page): DataProviderInterface 76 | { 77 | if ($page <= 0) { 78 | $page = self::PAGE_DEFAULT; 79 | } 80 | $this->page = $page; 81 | return $this; 82 | } 83 | 84 | public function setLimit(int $limit): DataProviderInterface 85 | { 86 | $this->limit = $limit; 87 | return $this; 88 | } 89 | 90 | public function getPaginator(): Paginator 91 | { 92 | if ($this->paginator === null) { 93 | $this->paginator = new Paginator(new DoctrineAdapter($this->getCollection(), $this->getCount())); 94 | $this->paginator->setItemCountPerPage($this->limit) 95 | ->setCurrentPageNumber($this->getValidPage()) 96 | ->setPageRange($this->pageRange); 97 | } 98 | return $this->paginator; 99 | } 100 | 101 | public function __construct(EntityManager $entityManager) 102 | { 103 | $this->entityManager = $entityManager; 104 | } 105 | 106 | public function getQueryBuilder(): QueryBuilder 107 | { 108 | if ($this->queryBuilder === null) { 109 | $this->queryBuilder = $this->entityManager->createQueryBuilder(); 110 | } 111 | return $this->queryBuilder; 112 | } 113 | 114 | public function getCountBuilder(): QueryBuilder 115 | { 116 | if ($this->countBuilder === null) { 117 | $this->countBuilder = clone $this->getQueryBuilder(); 118 | $this->countBuilder->select(sprintf('COUNT(%s)', self::MAIN_ENTITY_ALIAS)); 119 | } 120 | return $this->countBuilder; 121 | } 122 | 123 | public function asObject(): QueryDataProvider 124 | { 125 | $this->hydrationMode = AbstractQuery::HYDRATE_OBJECT; 126 | return $this; 127 | } 128 | 129 | public function asArray(): QueryDataProvider 130 | { 131 | $this->hydrationMode = AbstractQuery::HYDRATE_ARRAY; 132 | return $this; 133 | } 134 | 135 | public function setQueryParams(array $queryParams): QueryDataProvider 136 | { 137 | $this->queryParams = $queryParams; 138 | return $this; 139 | } 140 | 141 | public function getQueryParams(): array 142 | { 143 | return $this->queryParams; 144 | } 145 | 146 | /** 147 | * @return array|Collection 148 | */ 149 | protected function getCollection() 150 | { 151 | if ($this->collection === null) { 152 | $qb = clone $this->getQueryBuilder(); 153 | $qb->setFirstResult($this->getFirstResult()) 154 | ->setMaxResults($this->limit); 155 | $this->collection = $qb->getQuery()->getResult($this->hydrationMode); 156 | } 157 | return $this->collection; 158 | } 159 | 160 | protected function getCount(): int 161 | { 162 | if ($this->count === null) { 163 | $this->count = (int)$this->getCountBuilder()->getQuery()->getSingleScalarResult(); 164 | } 165 | return $this->count; 166 | } 167 | 168 | protected function getFirstResult(): int 169 | { 170 | $result = ($this->page - 1) * $this->limit; 171 | if ($this->getCount() > $result) { 172 | return $result; 173 | } 174 | return self::FIRST_RESULT_DEFAULT * $this->limit; 175 | } 176 | 177 | protected function getValidPage(): int 178 | { 179 | return $this->page > ceil($this->getCount() / $this->limit) ? self::PAGE_DEFAULT : $this->page; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /module/ExAssetic/config/cli.config.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'commands' => [ 8 | 'ExAssetic\Command\BuildCommand', 9 | 'ExAssetic\Command\SetupCommand', 10 | ], 11 | ], 12 | 'command_manager' => [ 13 | 'factories' => [ 14 | 'ExAssetic\Command\BuildCommand' => Command\BuildCommandFactory::class, 15 | 'ExAssetic\Command\SetupCommand' => Command\SetupCommandFactory::class, 16 | ], 17 | ], 18 | ]; 19 | -------------------------------------------------------------------------------- /module/ExAssetic/config/module.config.php: -------------------------------------------------------------------------------- 1 | [], 11 | 'service_manager' => [ 12 | 'factories' => [ 13 | \Assetic\Filter\Sass\SassFilter::class => Filter\Sass\SassFilterFactory::class, 14 | \Assetic\Filter\Yui\CssCompressorFilter::class => Filter\Yui\CssCompressorFilterFactory::class, 15 | \Assetic\Filter\UglifyJs2Filter::class => Filter\UglifyJs2FilterFactory::class, 16 | 17 | Options\ModuleOptions::class => Options\ModuleOptionsFactory::class, 18 | 19 | 'AsseticCacheBuster' => Cache\CacheBusterFactory::class, 20 | ], 21 | 'aliases' => [ 22 | 'CssCompressorFilter' => \Assetic\Filter\Yui\CssCompressorFilter::class, 23 | 'UglifyJs2Filter' => \Assetic\Filter\UglifyJs2Filter::class, 24 | 'SassFilter' => \Assetic\Filter\Sass\SassFilter::class, 25 | ], 26 | ], 27 | ]; 28 | -------------------------------------------------------------------------------- /module/ExAssetic/config/rbac.config.php: -------------------------------------------------------------------------------- 1 | [ 12 | 'guards' => [ 13 | RoutePermissionsGuard::class => [ 14 | 'assetic*' => ['assetic'], 15 | ], 16 | ], 17 | 'role_provider' => [ 18 | InMemoryRoleProvider::class => [ 19 | 'guest' => [ 20 | 'permissions' => [ 21 | 'assetic', 22 | ], 23 | ], 24 | ], 25 | ], 26 | ], 27 | ]; 28 | -------------------------------------------------------------------------------- /module/ExAssetic/src/Cache/CacheBusterFactory.php: -------------------------------------------------------------------------------- 1 | get('AsseticService')); 14 | return $cmd->setName('assetic:build'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /module/ExAssetic/src/Command/SetupCommandFactory.php: -------------------------------------------------------------------------------- 1 | get('AsseticService')); 14 | return $cmd->setName('assetic:setup'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /module/ExAssetic/src/Filter/Sass/SassFilterFactory.php: -------------------------------------------------------------------------------- 1 | get('ExAssetic\Options\ModuleOptions'); 14 | return new SassFilter($options->sassPath); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /module/ExAssetic/src/Filter/UglifyJs2FilterFactory.php: -------------------------------------------------------------------------------- 1 | get('ExAssetic\Options\ModuleOptions'); 17 | return new UglifyJs2Filter($options->uglifyJs2Path, $options->nodeBin); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /module/ExAssetic/src/Filter/Yui/CssCompressorFilterFactory.php: -------------------------------------------------------------------------------- 1 | get('ExAssetic\Options\ModuleOptions'); 17 | return new CssCompressorFilter($options->yuiPath, $options->javaPath); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /module/ExAssetic/src/Module.php: -------------------------------------------------------------------------------- 1 | nodeBin = $nodeBin; 33 | return $this; 34 | } 35 | 36 | public function getNodeBin(): string 37 | { 38 | return $this->nodeBin; 39 | } 40 | 41 | public function setYuiPath(string $yuiPath): ModuleOptions 42 | { 43 | $this->yuiPath = $yuiPath; 44 | return $this; 45 | } 46 | 47 | public function getYuiPath(): string 48 | { 49 | return $this->yuiPath; 50 | } 51 | 52 | public function setJavaPath(string $javaPath): ModuleOptions 53 | { 54 | $this->javaPath = $javaPath; 55 | return $this; 56 | } 57 | 58 | public function getJavaPath(): string 59 | { 60 | return $this->javaPath; 61 | } 62 | 63 | public function setUglifyJs2Path(string $uglifyJs2Path): ModuleOptions 64 | { 65 | $this->uglifyJs2Path = $uglifyJs2Path; 66 | return $this; 67 | } 68 | 69 | public function getUglifyJs2Path(): string 70 | { 71 | return $this->uglifyJs2Path; 72 | } 73 | 74 | public function setSassPath(string $sassPath): ModuleOptions 75 | { 76 | $this->sassPath = $sassPath; 77 | return $this; 78 | } 79 | 80 | public function getSassPath(): string 81 | { 82 | return $this->sassPath; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /module/ExAssetic/src/Options/ModuleOptionsFactory.php: -------------------------------------------------------------------------------- 1 | get('config')['ex_assetic']); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /module/ExCodeception/src/BaseFixture.php: -------------------------------------------------------------------------------- 1 | dataFile) { 33 | $this->items = require $this->dataFile; 34 | } 35 | 36 | foreach ($this->items as $item) { 37 | /** @var ClassMetadataInfo $metadata */ 38 | $metadata = $manager->getClassMetadata($this->entityClass); 39 | $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_AUTO); 40 | 41 | $entity = new $this->entityClass; 42 | 43 | foreach ($item as $name => $value) { 44 | $setValueMethodName = $this->createSetValueMethodName($name); 45 | 46 | if (method_exists($this, $setValueMethodName)) { 47 | $this->$setValueMethodName($entity, $item, $name); 48 | } else { 49 | $entity->$setValueMethodName($value); 50 | } 51 | 52 | if ($this->reference) { 53 | $this->setReference($this->reference . $item['id'], $entity); 54 | } 55 | } 56 | 57 | $manager->persist($entity); 58 | } 59 | $manager->flush(); 60 | } 61 | 62 | public function getOrder(): int 63 | { 64 | return 0; 65 | } 66 | 67 | protected function createSetValueMethodName(string $name): string 68 | { 69 | $ucfirstName = ucfirst($name); 70 | $setValueMethodName = 'set' . $ucfirstName; 71 | 72 | return $setValueMethodName; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /module/ExCodeception/src/Module/Fixture.php: -------------------------------------------------------------------------------- 1 | config['fixturePaths']; 27 | 28 | foreach ($fixturePaths as $path) { 29 | $fullPath = $this->createFullPath($path); 30 | if (is_dir($fullPath) === true) { 31 | $loader->loadFromDirectory($fullPath); 32 | } 33 | } 34 | 35 | $this->pushFixtures($loader->getFixtures()); 36 | } 37 | 38 | public function _getEntityManager(): EntityManagerInterface 39 | { 40 | return $this->getModule('\ExCodeception\Module\ZF3')->_getEntityManager(); 41 | } 42 | 43 | /** 44 | * @param \Doctrine\Common\DataFixtures\FixtureInterface[] $fixtures 45 | * @return void 46 | */ 47 | public function pushFixtures(array $fixtures): void 48 | { 49 | $purger = new ORMPurger(); 50 | $executor = new ORMExecutor($this->_getEntityManager(), $purger); 51 | $executor->execute($fixtures); 52 | } 53 | 54 | /** 55 | * @param string $path 56 | * @return string 57 | */ 58 | public function createFullPath(string $path): string 59 | { 60 | return Configuration::testsDir() . $path; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /module/ExDebugBar/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /module/ExDebugBar/config/assets.config.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'routes' => [ 12 | '.*' => [ 13 | '@ex_debugbar_css', 14 | '@ex_debugbar_js', 15 | ], 16 | ], 17 | 'modules' => [ 18 | 'ExDebugBar' => [ 19 | 'root_path' => __DIR__ . '/../assets', 20 | 'collections' => [ 21 | 'ex_debugbar_js' => [ 22 | 'assets' => [ 23 | getcwd() . '/node_modules/jquery/dist/jquery.min.js', 24 | $debugBarRes . '/vendor/highlightjs/highlight.pack.js', 25 | $debugBarRes . '/debugbar.js', 26 | $debugBarRes . '/widgets.js', 27 | $debugBarRes . '/openhandler.js', 28 | $debugBarRes . '/widgets/sqlqueries/widget.js', 29 | ], 30 | 'options' => [ 31 | 'output' => 'js/ex-debugbar.js', 32 | ], 33 | ], 34 | 'ex_debugbar_css' => [ 35 | 'assets' => [ 36 | $debugBarRes . '/vendor/font-awesome/css/font-awesome.min.css', 37 | $debugBarRes . '/vendor/highlightjs/styles/github.css', 38 | $debugBarRes . '/debugbar.css', 39 | $debugBarRes . '/widgets.css', 40 | $debugBarRes . '/openhandler.css', 41 | $debugBarRes . '/widgets/sqlqueries/widget.css', 42 | getcwd() . '/vendor/bupy7/zf-php-debug-bar/assets/zf-snap-php-debug-bar.css', 43 | ], 44 | 'options' => [ 45 | 'output' => 'css/ex-debugbar.css', 46 | ], 47 | ], 48 | ], 49 | ], 50 | ], 51 | ], 52 | ]; 53 | -------------------------------------------------------------------------------- /module/ExDebugBar/config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 12 | 'auto-append-assets' => false, 13 | 'collectors' => [ 14 | DoctrineCollector::class, 15 | ], 16 | ], 17 | 'service_manager' => [ 18 | 'delegators' => [ 19 | 'doctrine.configuration.orm_default' => [ 20 | DoctrineConfigurationDelegatorFactory::class, 21 | ], 22 | ], 23 | ], 24 | ]; 25 | -------------------------------------------------------------------------------- /module/ExDebugBar/src/Module.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'factories' => [ 12 | 'ExDoctrine\Hydrator\ObjectHydrator' => ObjectHydratorFactory::class, 13 | ], 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /module/ExDoctrine/config/rbac.config.php: -------------------------------------------------------------------------------- 1 | [ 12 | 'guards' => [ 13 | RoutePermissionsGuard::class => [ 14 | 'doctrine_cli' => ['doctrine'], 15 | ], 16 | ], 17 | 'role_provider' => [ 18 | InMemoryRoleProvider::class => [ 19 | 'guest' => [ 20 | 'permissions' => [ 21 | 'doctrine' 22 | ], 23 | ], 24 | ], 25 | ], 26 | ], 27 | ]; 28 | -------------------------------------------------------------------------------- /module/ExDoctrine/src/Hydrator/ObjectHydratorFactory.php: -------------------------------------------------------------------------------- 1 | get('Doctrine\ORM\EntityManager')); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /module/ExDoctrine/src/Module.php: -------------------------------------------------------------------------------- 1 | get('Doctrine\ORM\EntityManager'); 18 | $entityName = preg_replace(['/\\\\Repository/', '/Repository$/'], ['\\Entity', ''], $requestedName); 19 | return $em->getRepository($entityName); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /module/ExDoctrine/src/Type/DateTimeType.php: -------------------------------------------------------------------------------- 1 | setTimezone($this->getDbTz()); 17 | } 18 | return parent::convertToDatabaseValue($value, $platform); 19 | } 20 | 21 | public function convertToPHPValue($value, AbstractPlatform $platform) 22 | { 23 | if ($value === null || $value instanceof DateTime) { 24 | return $value; 25 | } 26 | $dt = DateTime::createFromFormat($platform->getDateTimeFormatString(), $value, $this->getDbTz()); 27 | if (!$dt) { 28 | throw ConversionException::conversionFailedFormat( 29 | $value, 30 | $this->getName(), 31 | $platform->getDateTimeFormatString() 32 | ); 33 | } 34 | $dt->setTimezone($this->getAppTz()); 35 | return $dt; 36 | } 37 | 38 | /** 39 | * @var DateTimeZone 40 | */ 41 | private static $dbTz; 42 | 43 | protected function getDbTz(): DateTimeZone 44 | { 45 | if (self::$dbTz === null) { 46 | self::$dbTz = new DateTimeZone('UTC'); 47 | } 48 | return self::$dbTz; 49 | } 50 | 51 | /** 52 | * @var DateTimeZone 53 | */ 54 | private static $appTz; 55 | 56 | protected function getAppTz(): DateTimeZone 57 | { 58 | if (self::$appTz === null) { 59 | self::$appTz = new DateTimeZone(date_default_timezone_get()); 60 | } 61 | return self::$appTz; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /module/ExPaginator/src/Adapter/DoctrineAdapter.php: -------------------------------------------------------------------------------- 1 | count = $collection->count(); 28 | } elseif (is_array($collection)) { 29 | $this->count = $count; 30 | } else { 31 | throw new InvalidArgumentException('Iterator must implement ' . Collection::class . ' or array'); 32 | } 33 | $this->collection = $collection; 34 | } 35 | 36 | /** 37 | * Returns an iterator of items for a page, or an empty array. 38 | * @param int $offset Page offset 39 | * @param int $itemCountPerPage Number of items per page 40 | * @return Collection|array 41 | */ 42 | public function getItems($offset, $itemCountPerPage) 43 | { 44 | if ($this->count == 0) { 45 | return []; 46 | } 47 | return $this->collection; 48 | } 49 | 50 | /** 51 | * Returns the total number of rows in the collection. 52 | */ 53 | public function count(): int 54 | { 55 | return $this->count; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /module/ExRbac/src/Module.php: -------------------------------------------------------------------------------- 1 | getApplication(); 20 | $services = $app->getServiceManager(); 21 | 22 | // redirect to 403 Forbidden 23 | if ($e->getRequest() instanceof HttpRequest) { 24 | $services->get('ZfcRbac\View\Strategy\RedirectStrategy')->attach($app->getEventManager()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /module/ExTwig/config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'extensions' => [ 8 | 'class' => Extension\ClassExtension::class, 9 | ], 10 | ], 11 | ]; 12 | -------------------------------------------------------------------------------- /module/ExTwig/src/Extension/ClassExtension.php: -------------------------------------------------------------------------------- 1 | new Twig_SimpleFunction('get_class', [$this, 'getClassName']), 26 | ]; 27 | } 28 | 29 | /** 30 | * {@inheritDoc} 31 | */ 32 | public function getTests() 33 | { 34 | return [ 35 | 'instanceof' => new Twig_SimpleTest('instanceof', [$this, 'isInstanceOf']), 36 | ]; 37 | } 38 | 39 | /** 40 | * @param object $object 41 | * @return string 42 | */ 43 | public function getClassName($object) 44 | { 45 | return get_class($object); 46 | } 47 | 48 | /** 49 | * @param object|string $actualClass 50 | * @param object|string $expectClass 51 | * @return boolean 52 | */ 53 | public function isInstanceOf($actualClass, $expectClass) 54 | { 55 | return $actualClass instanceof $expectClass; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /module/ExTwig/src/Module.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'delegators' => [ 12 | 'translator' => [ 13 | TranslatorDelegatorFactory::class, 14 | ], 15 | ], 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /module/ExValidate/src/Delegator/TranslatorDelegatorFactory.php: -------------------------------------------------------------------------------- 1 | addTranslationFilePattern('phparray', __DIR__ . '/../../language', '%s.php', 'ExValidate'); 20 | return $translator; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /module/ExValidate/src/Module.php: -------------------------------------------------------------------------------- 1 | getApplication()->getServiceManager()->get('translator'); 22 | AbstractValidator::setDefaultTranslator($translator, 'ExValidate'); 23 | } 24 | 25 | /** 26 | * {@inheritDoc} 27 | */ 28 | public function getConfig() 29 | { 30 | return require __DIR__ . '/../config/module.config.php'; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /module/Mail/config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'supportEmail' => 'no-reply@zf-app-blank.com', 8 | ], 9 | 'mailgun' => [ 10 | 'hydrator' => Hydrator\ModelHydrator::class, 11 | ], 12 | 'service_manager' => [ 13 | 'factories' => [ 14 | Service\MailService::class => Service\MailServiceFactory::class, 15 | Options\ModuleOptions::class => Options\ModuleOptionsFactory::class, 16 | Message\MessageBuilder::class => Message\MessageBuilderFactory::class, 17 | ], 18 | 'shared' => [ 19 | Message\MessageBuilder::class => false, 20 | ], 21 | ], 22 | ]; 23 | -------------------------------------------------------------------------------- /module/Mail/src/Hydrator/ModelHydrator.php: -------------------------------------------------------------------------------- 1 | getBody()->__toString(); 20 | $data = json_decode($body, true); 21 | if (JSON_ERROR_NONE !== json_last_error()) { 22 | throw new HydrationException(sprintf('Error (%d) when trying to json_decode response', json_last_error())); 23 | } 24 | if (is_subclass_of($class, ApiResponse::class)) { 25 | $object = call_user_func($class.'::create', $data); 26 | } else { 27 | $object = new $class($data); 28 | } 29 | return $object; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /module/Mail/src/Message/MessageBuilder.php: -------------------------------------------------------------------------------- 1 | translator = $translator; 24 | $this->viewRenderer = $viewRenderer; 25 | } 26 | 27 | public function setRenderHtmlBody(string $template, array $variables): string 28 | { 29 | $viewModel = new ViewModel($variables); 30 | $viewModel->setTemplate($template); 31 | return parent::setHtmlBody($this->viewRenderer->render($viewModel)); 32 | } 33 | 34 | public function setTranslateSubject(string $message, string $domain): string 35 | { 36 | return parent::setSubject($this->translator->translate($message, $domain)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /module/Mail/src/Message/MessageBuilderFactory.php: -------------------------------------------------------------------------------- 1 | get('translator'), $container->get('ZfcTwigRenderer')); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /module/Mail/src/Module.php: -------------------------------------------------------------------------------- 1 | supportEmail = $supportEmail; 25 | return $this; 26 | } 27 | 28 | /** 29 | * @return string 30 | */ 31 | public function getSupportEmail() 32 | { 33 | return $this->supportEmail; 34 | } 35 | 36 | /** 37 | * @param string $domain 38 | * @return static 39 | */ 40 | public function setDomain($domain) 41 | { 42 | $this->domain = $domain; 43 | return $this; 44 | } 45 | 46 | /** 47 | * @return string 48 | */ 49 | public function getDomain() 50 | { 51 | return $this->domain; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /module/Mail/src/Options/ModuleOptionsFactory.php: -------------------------------------------------------------------------------- 1 | get('config')['mail']); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /module/Mail/src/Service/MailService.php: -------------------------------------------------------------------------------- 1 | client = $client; 31 | $this->supportEmail = $supportEmail; 32 | $this->domain = $domain; 33 | $this->messageBuilder = $messageBuilder; 34 | } 35 | 36 | public function send(MessageBuilder $message): ApiResponse 37 | { 38 | $message->setFromAddress($this->supportEmail); 39 | return $this->client->messages()->send($this->domain, $message->getMessage()); 40 | } 41 | 42 | public function createMessageBuilder(): MessageBuilder 43 | { 44 | return clone $this->messageBuilder; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /module/Mail/src/Service/MailServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('Mail\Options\ModuleOptions'); 13 | return new MailService( 14 | $container->get('Bupy7\Mailgun\Service\MailgunService'), 15 | $options->getDomain(), 16 | $options->getSupportEmail(), 17 | $container->get('Mail\Message\MessageBuilder') 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /module/User/codeception.yml: -------------------------------------------------------------------------------- 1 | namespace: User\Test 2 | paths: 3 | tests: test 4 | output: test/_output 5 | data: test/_data 6 | support: test/_support 7 | envs: test/_envs 8 | actor_suffix: Tester 9 | extensions: 10 | enabled: 11 | - Codeception\Extension\RunFailed 12 | settings: 13 | bootstrap: _bootstrap.php 14 | colors: true 15 | memory_limit: 1024M 16 | coverage: 17 | enabled: true 18 | include: 19 | - src/* 20 | -------------------------------------------------------------------------------- /module/User/config/doctrine.config.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'authentication' => [ 14 | 'orm_default' => [ 15 | 'object_manager' => EntityManager::class, 16 | 'identity_class' => User::class, 17 | 'identity_property' => 'email', 18 | 'credential_property' => 'password', 19 | ], 20 | ], 21 | 'driver' => [ 22 | 'user_entity' => [ 23 | 'class' => 'Doctrine\ORM\Mapping\Driver\XmlDriver', 24 | 'paths' => __DIR__ . '/mapping', 25 | ], 26 | 'orm_default' => [ 27 | 'drivers' => [ 28 | 'User\Entity' => 'user_entity', 29 | ], 30 | ], 31 | ], 32 | 'fixtures' => [ 33 | 'User' => __DIR__ . '/../test/fixture', 34 | ], 35 | ], 36 | 'doctrine_factories' => [ 37 | 'authenticationadapter' => AuthAdapterFactory::class, 38 | ], 39 | ]; 40 | -------------------------------------------------------------------------------- /module/User/config/mapping/User.Entity.User.dcm.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /module/User/config/module.config.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'translation_file_patterns' => [ 12 | [ 13 | 'type' => 'phparray', 14 | 'base_dir' => __DIR__ . '/../language', 15 | 'pattern' => '%s.php', 16 | 'text_domain' => __NAMESPACE__, 17 | ], 18 | ], 19 | ], 20 | 'view_manager' => [ 21 | 'template_path_stack' => [ 22 | __DIR__ . '/../view', 23 | ], 24 | 'template_map' => [ 25 | 'user/mail/confirm-request' => __DIR__ . '/../view/mail/confirm-request.twig', 26 | 'user/mail/restore-pass' => __DIR__ . '/../view/mail/restore-pass.twig' 27 | ], 28 | ], 29 | 'controllers' => [ 30 | 'factories' => [ 31 | Controller\AuthController::class => Controller\AuthControllerFactory::class, 32 | Controller\SignupController::class => Controller\SignupControllerFactory::class, 33 | Controller\ConfirmEmailController::class => Controller\ConfirmEmailControllerFactory::class, 34 | Controller\AccessController::class => Controller\AccessControllerFactory::class, 35 | Controller\IndexController::class => LazyControllerAbstractFactory::class, 36 | ], 37 | ], 38 | 'service_manager' => [ 39 | 'factories' => [ 40 | Form\SignUpForm::class => Form\SignUpFormFactory::class, 41 | 42 | \Zend\Authentication\AuthenticationService::class => Service\AuthServiceFactory::class, 43 | Service\SignUpService::class => Service\SignUpServiceFactory::class, 44 | Service\ConfirmEmailService::class => ReflectionBasedAbstractFactory::class, 45 | Service\AccessService::class => Service\AccessServiceFactory::class, 46 | 47 | \Zend\Crypt\Password\BcryptSha::class => \Zend\ServiceManager\Factory\InvokableFactory::class, 48 | 49 | Search\UserSearch::class => ReflectionBasedAbstractFactory::class, 50 | 51 | Repository\UserRepository::class => RepositoryInvokableFactory::class, 52 | ], 53 | 'aliases' => [ 54 | 'User\Crypt\PasswordCrypt' => \Zend\Crypt\Password\BcryptSha::class, 55 | ], 56 | ], 57 | ]; 58 | -------------------------------------------------------------------------------- /module/User/config/navigation.config.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'guestright' => [ 10 | [ 11 | 'label' => 'MENU_SIGNIN', 12 | 'route' => 'signin', 13 | 'icon' => 'fa fa-sign-in', 14 | 'text_domain' => 'User', 15 | ], 16 | [ 17 | 'label' => 'MENU_SIGNUP', 18 | 'route' => 'signup', 19 | 'icon' => 'fa fa-user-plus', 20 | 'text_domain' => 'User', 21 | ], 22 | ], 23 | 'authright' => [ 24 | [ 25 | 'label' => 'MENU_LOGOUT', 26 | 'route' => 'logout', 27 | 'icon' => 'fa fa-sign-out', 28 | 'text_domain' => 'User', 29 | ], 30 | ], 31 | ], 32 | ]; 33 | -------------------------------------------------------------------------------- /module/User/config/rbac.config.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'role_provider' => [ 9 | InMemoryRoleProvider::class => [ 10 | 'registered' => [ 11 | 'permissions' => [ 12 | 'user/index/index', 13 | ], 14 | ], 15 | 'guest' => [ 16 | 'permissions' => [ 17 | 'user/auth/signin', 18 | 'user/auth/logout', 19 | 'user/signup/signup', 20 | 'user/confirm-email/confirm', 21 | 'user/confirm-again/again', 22 | 'user/access/forgot-pass', 23 | 'user/access/restore-pass', 24 | ], 25 | ], 26 | ], 27 | ], 28 | 'guards' => [ 29 | RoutePermissionsGuard::class => [ 30 | 'home' => 'user/index/index', 31 | 32 | 'signin' => 'user/auth/signin', 33 | 'logout' => 'user/auth/logout', 34 | 35 | 'signup' => 'user/signup/signup', 36 | 37 | 'confirm-email' => 'user/confirm-email/confirm', 38 | 39 | 'confirm-again' => 'user/confirm-again/again', 40 | 41 | 'forgot-pass' => 'user/access/forgot-pass', 42 | 'restore-pass' => 'user/access/restore-pass', 43 | ], 44 | ], 45 | ], 46 | ]; 47 | -------------------------------------------------------------------------------- /module/User/config/router.config.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'routes' => [ 14 | 'home' => [ 15 | 'type' => Literal::class, 16 | 'options' => [ 17 | 'route' => '/', 18 | 'defaults' => [ 19 | 'controller' => IndexController::class, 20 | 'action' => 'index', 21 | ], 22 | ], 23 | ], 24 | 'signin' => [ 25 | 'type' => Literal::class, 26 | 'options' => [ 27 | 'route' => '/signin', 28 | 'defaults' => [ 29 | 'controller' => AuthController::class, 30 | 'action' => 'signin', 31 | ], 32 | ], 33 | ], 34 | 'logout' => [ 35 | 'type' => Literal::class, 36 | 'options' => [ 37 | 'route' => '/logout', 38 | 'defaults' => [ 39 | 'controller' => AuthController::class, 40 | 'action' => 'logout', 41 | ], 42 | ], 43 | ], 44 | 'signup' => [ 45 | 'type' => Literal::class, 46 | 'options' => [ 47 | 'route' => '/signup', 48 | 'defaults' => [ 49 | 'controller' => SignupController::class, 50 | 'action' => 'signup', 51 | ], 52 | ], 53 | ], 54 | 'confirm-email' => [ 55 | 'type' => Segment::class, 56 | 'options' => [ 57 | 'route' => '/confirm-email/:e/:k', 58 | 'defaults' => [ 59 | 'controller' => ConfirmEmailController::class, 60 | 'action' => 'confirm', 61 | ], 62 | ], 63 | ], 64 | 'confirm-again' => [ 65 | 'type' => Literal::class, 66 | 'options' => [ 67 | 'route' => '/confirm-again', 68 | 'defaults' => [ 69 | 'controller' => ConfirmEmailController::class, 70 | 'action' => 'again', 71 | ], 72 | ], 73 | ], 74 | 'forgot-pass' => [ 75 | 'type' => Literal::class, 76 | 'options' => [ 77 | 'route' => '/forgot-pass', 78 | 'defaults' => [ 79 | 'controller' => AccessController::class, 80 | 'action' => 'forgot-pass', 81 | ], 82 | ], 83 | ], 84 | 'restore-pass' => [ 85 | 'type' => Segment::class, 86 | 'options' => [ 87 | 'route' => '/restore-pass/:e/:k', 88 | 'defaults' => [ 89 | 'controller' => AccessController::class, 90 | 'action' => 'restore-pass', 91 | ], 92 | ], 93 | ], 94 | ], 95 | ], 96 | ]; 97 | -------------------------------------------------------------------------------- /module/User/language/en.php: -------------------------------------------------------------------------------- 1 | 'Username', 5 | 'LABEL_EMAIL' => 'Email', 6 | 'LABEL_PASSWORD' => 'Password', 7 | 'LABEL_PERSON' => 'Name', 8 | 'LABEL_SIGNIN' => 'Sign in', 9 | 'LABEL_SIGNUP' => 'Sign up', 10 | 'LABEL_SEND_CONFIRM_KEY' => 'Send', 11 | 'LABEL_FORGOT_PASS' => 'Did you forget password?', 12 | 'LABEL_SEND_RESTORE_KEY' => 'Send', 13 | 'LABEL_RESTORE_PASS' => 'Save', 14 | 'LABEL_NEW_PASSWORD' => 'New password', 15 | 'LABEL_YES' => 'Yes', 16 | 'LABEL_NO' => 'No', 17 | 18 | 'PAGE_TITLE_SIGNUP' => 'Signup', 19 | 'PAGE_TITLE_SIGNIN' => 'Signin', 20 | 'PAGE_CONFIRM_AGAIN' => 'Email validation', 21 | 'PAGE_FORGOT_PASS' => 'Password recovery', 22 | 'PAGE_TITLE_USER_LIST' => 'Users', 23 | 24 | 'SUCCESS_SIGNUP' => 'You have registered successfully! Check your mail inbox in order to verify his ' 25 | . 'Email address.', 26 | 'SUCCESS_SIGNIN' => 'You have logged successfully!', 27 | 'SUCCESS_CONFIRM_EMAIL' => 'You have confirmed successfully his Email address.', 28 | 'SUCCESS_SENT_CONFIRM_KEY_AGAIN' => 'Key had been sent successfully to your Email address.', 29 | 'SUCCESS_SENT_RESTORE_KEY' => 'Key had been sent successfully to your Email address.', 30 | 'SUCCESS_RESTORE_PASS' => 'Password had been recovered successfully!', 31 | 32 | 'FAILED_SIGNIN' => 'Invalid login/password.', 33 | 'FAILED_SENT_CONFIRM_KEY_AGAIN' => 'The user not found either Email had been confirmed.', 34 | 'FAILED_SIGNIN_DIDNT_CONFIRM' => 'The user didn\'t still confirmed his Email. Send [confirm key again](%s) ' 35 | . 'if you didn\'t receive it.', 36 | 'FAILED_SEND_RESTORE_KEY' => 'The user not found.', 37 | 'FAILED_RESTORE_PASS' => 'The user not found either the waiting time recovery password had expired.', 38 | 39 | 'MENU_SIGNIN' => 'Sign in', 40 | 'MENU_SIGNUP' => 'Sign up', 41 | 'MENU_LOGOUT' => 'Log out', 42 | 43 | 'EMAIL_SUBJECT_CONFIRM_EMAIL' => 'Confirm Email address', 44 | 'EMAIL_BODY_CONFIRM_EMAIL' => 'For confirm Email address you should follow by link: %s', 45 | 'EMAIL_HELLO' => 'Hello, %s!', 46 | 'EMAIL_WITH_THE_RESPECT' => 'With respect, %s.', 47 | 'EMAIL_CONFIRM_EMAIL' => 'Confirm', 48 | 'EMAIL_SUBJECT_RESTORE_PASS' => 'Recovering password', 49 | 'EMAIL_RESTORE_PASS' => 'Recovery password', 50 | 'EMAIL_BODY_RESTORE_PASS' => 'For recovery password you should follow by link: %s', 51 | 52 | 'WARNING_CONFIRM_EMAIL_NOT_FOUND' => 'The user not found either Email has been confirmed.', 53 | 54 | 'COLUMN_USERNAME' => 'Username', 55 | 'COLUMN_EMAIL' => 'Email', 56 | 'COLUMN_ROLE' => 'Role', 57 | 'COLUMN_EMAIL_CONFIRM' => 'Confirmed Email', 58 | 'COLUMN_CREATED_AT' => 'Date/time', 59 | ]; 60 | -------------------------------------------------------------------------------- /module/User/language/ru.php: -------------------------------------------------------------------------------- 1 | 'Имя пользователя', 5 | 'LABEL_EMAIL' => 'Email', 6 | 'LABEL_PASSWORD' => 'Пароль', 7 | 'LABEL_PERSON' => 'Имя', 8 | 'LABEL_SIGNIN' => 'Войти', 9 | 'LABEL_SIGNUP' => 'Зарегистрироваться', 10 | 'LABEL_SEND_CONFIRM_KEY' => 'Отправить', 11 | 'LABEL_FORGOT_PASS' => 'Забыли пароль?', 12 | 'LABEL_SEND_RESTORE_KEY' => 'Отправить', 13 | 'LABEL_RESTORE_PASS' => 'Сохранить', 14 | 'LABEL_NEW_PASSWORD' => 'Новый пароль', 15 | 'LABEL_YES' => 'Да', 16 | 'LABEL_NO' => 'Нет', 17 | 18 | 'PAGE_TITLE_SIGNUP' => 'Регистрация', 19 | 'PAGE_TITLE_SIGNIN' => 'Форма входа', 20 | 'PAGE_CONFIRM_AGAIN' => 'Подтверждение Email адреса', 21 | 'PAGE_FORGOT_PASS' => 'Восстановление пароля', 22 | 'PAGE_TITLE_USER_LIST' => 'Пользователи', 23 | 24 | 'SUCCESS_SIGNUP' => 'Регистрация завершилась успешно! Проверьте Ваш почтовый ящик, чтобы подтвердить Email.', 25 | 'SUCCESS_SIGNIN' => 'Вы успешно вошли в систему!', 26 | 'SUCCESS_CONFIRM_EMAIL' => 'Вы успешно подтвердили Email адрес.', 27 | 'SUCCESS_SENT_CONFIRM_KEY_AGAIN' => 'Ключ для подтверждения Email успешно отправлен.', 28 | 'SUCCESS_SENT_RESTORE_KEY' => 'Ключ для восстановления пароля отправлен Вам на Email адрес.', 29 | 'SUCCESS_RESTORE_PASS' => 'Пароль был успешно восстановлен!', 30 | 31 | 'FAILED_SIGNIN' => 'Неверный логин/пароль.', 32 | 'FAILED_SENT_CONFIRM_KEY_AGAIN' => 'Пользователь не найден или Email уже был подтвержден.', 33 | 'FAILED_SIGNIN_DIDNT_CONFIRM' => 'Пользователь еще не подтвердил свой Email адрес. Если вы не получали письмо с ' 34 | . 'ключом подтверждения, то отправьте его [повторно](%s).', 35 | 'FAILED_SEND_RESTORE_KEY' => 'Пользователь с таким Email не найден.', 36 | 'FAILED_RESTORE_PASS' => 'Пользователь не найден или время ожидания сброса пароля истекло.', 37 | 38 | 'MENU_SIGNIN' => 'Войти', 39 | 'MENU_SIGNUP' => 'Регистрация', 40 | 'MENU_LOGOUT' => 'Выйти', 41 | 42 | 'EMAIL_SUBJECT_CONFIRM_EMAIL' => 'Подтверждение Email адреса', 43 | 'EMAIL_BODY_CONFIRM_EMAIL' => 'Для подтверждения Email адреса перейдите по следующей ссылке: %s', 44 | 'EMAIL_HELLO' => 'Здравствуйте, %s!', 45 | 'EMAIL_WITH_THE_RESPECT' => 'С ув., %s.', 46 | 'EMAIL_CONFIRM_EMAIL' => 'Подвердить', 47 | 'EMAIL_SUBJECT_RESTORE_PASS' => 'Восстановление пароля', 48 | 'EMAIL_RESTORE_PASS' => 'Восстановить пароль', 49 | 'EMAIL_BODY_RESTORE_PASS' => 'Для восстановления пароля перейдите по следующей ссылке: %s.', 50 | 51 | 'WARNING_CONFIRM_EMAIL_NOT_FOUND' => 'Пользователь не найден или Email уже был подтвержден.', 52 | 53 | 'COLUMN_USERNAME' => 'Имя', 54 | 'COLUMN_EMAIL' => 'Email', 55 | 'COLUMN_ROLE' => 'Роль', 56 | 'COLUMN_EMAIL_CONFIRM' => 'Подтвржд. Email', 57 | 'COLUMN_CREATED_AT' => 'Дата/время', 58 | ]; 59 | -------------------------------------------------------------------------------- /module/User/src/Adapter/AuthAdapter.php: -------------------------------------------------------------------------------- 1 | setup(); 14 | $options = $this->options; 15 | $identity = $options->getObjectRepository() 16 | ->findOneBy([$options->getIdentityProperty() => $this->identity]); 17 | if (!$identity) { 18 | $this->authenticationResultInfo['code'] = Result::FAILURE_IDENTITY_NOT_FOUND; 19 | $this->authenticationResultInfo['messages'][] = 'A record with the supplied identity could not be found.'; 20 | return $this->createAuthenticationResult(); 21 | } 22 | if (!$identity->getEmailConfirm()) { 23 | return new Result(Result::FAILURE_DIDNT_CONFIRM, $this->identity); 24 | } 25 | $authResult = $this->validateIdentity($identity); 26 | return $authResult; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /module/User/src/Adapter/AuthAdapterFactory.php: -------------------------------------------------------------------------------- 1 | getOptions($container, 'authentication'); 15 | if (is_string($objectManager = $options->getObjectManager())) { 16 | $options->setObjectManager($container->get($objectManager)); 17 | } 18 | return new AuthAdapter($options); 19 | } 20 | 21 | public function createService(ServiceLocatorInterface $serviceLocator): AuthAdapter 22 | { 23 | return $this($serviceLocator, ObjectRepository::class); 24 | } 25 | 26 | public function getOptionsClass(): string 27 | { 28 | return 'DoctrineModule\Options\Authentication'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /module/User/src/Auth/Result.php: -------------------------------------------------------------------------------- 1 | accessService = $accessService; 23 | } 24 | 25 | public function forgotPassAction() 26 | { 27 | $form = new ForgotPassForm; 28 | if ($this->getRequest()->isPost()) { 29 | if ($this->accessService->forgotPass($this->getRequest()->getPost()->toArray(), $form)) { 30 | $this->flashMessenger()->addSuccessMessage($this->translate('SUCCESS_SENT_RESTORE_KEY', 'User')); 31 | return $this->redirect()->refresh(); 32 | } else { 33 | $this->flashMessenger()->addWarningMessage($this->translate('FAILED_SEND_RESTORE_KEY', 'User')); 34 | } 35 | } 36 | return new ViewModel(['forgotPassForm' => $form]); 37 | } 38 | 39 | public function restorePassAction() 40 | { 41 | $email = $this->params()->fromRoute('e'); 42 | $key = $this->params()->fromRoute('k'); 43 | $entity = $this->accessService->findForRestore($email, $key); 44 | if (!$entity) { 45 | $this->flashMessenger()->addWarningMessage($this->translate('FAILED_RESTORE_PASS', 'User')); 46 | return $this->redirect()->toRoute(self::ROUTE_TO_SIGNIN); 47 | } 48 | $form = new RestorePassForm; 49 | if ($this->getRequest()->isPost()) { 50 | if ($this->accessService->restorePass($this->getRequest()->getPost()->toArray(), $entity, $form)) { 51 | $this->flashMessenger()->addSuccessMessage($this->translate('SUCCESS_RESTORE_PASS', 'User')); 52 | return $this->redirect()->toRoute(self::ROUTE_TO_SIGNIN); 53 | } 54 | } 55 | return new ViewModel([ 56 | 'restorePassForm' => $form, 57 | 'email' => $email, 58 | 'key' => $key, 59 | ]); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /module/User/src/Controller/AccessControllerFactory.php: -------------------------------------------------------------------------------- 1 | get('User\Service\AccessService')); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /module/User/src/Controller/AuthController.php: -------------------------------------------------------------------------------- 1 | authService = $authService; 30 | $this->signInForm = $signInForm; 31 | } 32 | 33 | /** 34 | * @return mixed 35 | */ 36 | public function signinAction() 37 | { 38 | if ($this->authService->getIdentity()) { 39 | return $this->redirect()->toRoute(self::ROUTE_TO_HOME); 40 | } 41 | $signInForm = $this->signInForm; 42 | if ($this->getRequest()->isPost()) { 43 | $signInForm->setValues($this->getRequest()->getPost()); 44 | if ($signInForm->isValid()) { 45 | $auth = $this->authService; 46 | $auth->getAdapter() 47 | ->setIdentity($signInForm->email) 48 | ->setCredential($signInForm->password); 49 | $result = $auth->authenticate(); 50 | if ($result->isValid()) { 51 | $this->flashMessenger()->addSuccessMessage($this->translate('SUCCESS_SIGNIN', 'User')); 52 | return $this->redirect()->toRoute(self::ROUTE_TO_HOME); 53 | } elseif ($result->getCode() == Result::FAILURE_DIDNT_CONFIRM) { 54 | $message = $this->translate('FAILED_SIGNIN_DIDNT_CONFIRM', 'User'); 55 | $message = sprintf($message, $this->url()->fromRoute(self::ROUTE_TO_CONFIRM_AGAIN)); 56 | $this->flashMessenger()->addWarningMessage($message); 57 | return $this->redirect()->toRoute(self::ROUTE_TO_HOME); 58 | } 59 | } 60 | $this->flashMessenger()->addErrorMessage($this->translate('FAILED_SIGNIN', 'User')); 61 | } 62 | return new ViewModel([ 63 | 'signInForm' => $signInForm, 64 | ]); 65 | } 66 | 67 | public function logoutAction(): Response 68 | { 69 | $this->authService->clearIdentity(); 70 | return $this->redirect()->toRoute(self::ROUTE_TO_SIGNIN); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /module/User/src/Controller/AuthControllerFactory.php: -------------------------------------------------------------------------------- 1 | get('Zend\Authentication\AuthenticationService'); 17 | return new AuthController($authService, new SignInForm); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /module/User/src/Controller/ConfirmEmailController.php: -------------------------------------------------------------------------------- 1 | confirmEmailService = $confirmEmailService; 27 | $this->userRepository = $userRepository; 28 | } 29 | 30 | public function confirmAction() 31 | { 32 | $email = $this->params()->fromRoute('e'); 33 | $key = $this->params()->fromRoute('k'); 34 | $userEntity = $this->userRepository->findNotConfirm($email, $key); 35 | if ($userEntity === null) { 36 | $this->flashMessenger() 37 | ->addWarningMessage($this->translate('WARNING_CONFIRM_EMAIL_NOT_FOUND', 'User')); 38 | } else { 39 | if ($this->confirmEmailService->confirm($userEntity)) { 40 | $this->flashMessenger() 41 | ->addSuccessMessage($this->translate('SUCCESS_CONFIRM_EMAIL', 'User')); 42 | } 43 | } 44 | return $this->redirect()->toRoute(self::ROUTE_TO_SIGNIN); 45 | } 46 | 47 | public function againAction() 48 | { 49 | $form = new AgainConfirmForm; 50 | if ($this->getRequest()->isPost()) { 51 | $result = $this->confirmEmailService->again($this->getRequest()->getPost()->toArray(), $form); 52 | if ($result) { 53 | $this->flashMessenger()->addSuccessMessage($this->translate('SUCCESS_SENT_CONFIRM_KEY_AGAIN', 'User')); 54 | return $this->redirect()->toRoute(self::ROUTE_TO_SIGNIN); 55 | } else { 56 | $this->flashMessenger()->addWarningMessage($this->translate('FAILED_SENT_CONFIRM_KEY_AGAIN', 'User')); 57 | } 58 | } 59 | return new ViewModel([ 60 | 'againForm' => $form, 61 | ]); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /module/User/src/Controller/ConfirmEmailControllerFactory.php: -------------------------------------------------------------------------------- 1 | get('User\Service\ConfirmEmailService'), 17 | $container->get('Doctrine\ORM\EntityManager')->getRepository('User\Entity\User') 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /module/User/src/Controller/IndexController.php: -------------------------------------------------------------------------------- 1 | userSearch = $userSearch; 20 | } 21 | 22 | public function indexAction(): ViewModel 23 | { 24 | $searchForm = new UserSearchForm(); 25 | $dataProvider = $this->userSearch->search((array)$this->params()->fromQuery(), $searchForm); 26 | 27 | return $this->asView([ 28 | 'dataProvider' => $dataProvider, 29 | ]); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /module/User/src/Controller/SignupController.php: -------------------------------------------------------------------------------- 1 | authService = $authService; 54 | $this->signUpForm = $signUpForm; 55 | $this->userEntity = $userEntity; 56 | $this->entityHydrator = $entityHydrator; 57 | $this->signUpService = $signUpService; 58 | $this->userRepository = $userRepository; 59 | } 60 | 61 | public function signupAction() 62 | { 63 | if ($this->authService->getIdentity()) { 64 | return $this->redirect()->toRoute(self::ROUTE_TO_HOME); 65 | } 66 | $signUpForm = $this->signUpForm; 67 | if ($this->getRequest()->isPost()) { 68 | $signUpForm->setValues($this->getRequest()->getPost()); 69 | if ($signUpForm->isValid()) { 70 | $values = $signUpForm->getValues(); 71 | $userEntity = $this->entityHydrator->hydrate($values, clone $this->userEntity); 72 | if ($this->signUpService->signup($userEntity)) { 73 | $this->flashMessenger() 74 | ->addSuccessMessage($this->translate('SUCCESS_SIGNUP', 'User')); 75 | return $this->redirect()->toRoute(self::ROUTE_TO_SIGNIN); 76 | } 77 | } 78 | } 79 | return new ViewModel([ 80 | 'signUpForm' => $signUpForm, 81 | ]); 82 | } 83 | 84 | public function confirmEmailAction(): Response 85 | { 86 | $email = $this->params()->fromRoute('e'); 87 | $key = $this->params()->fromRoute('k'); 88 | $userEntity = $this->userRepository->findNotConfirm($email, $key); 89 | if ($userEntity === null) { 90 | $this->flashMessenger() 91 | ->addWarningMessage($this->translate('WARNING_CONFIRM_EMAIL_NOT_FOUND', 'User')); 92 | } else { 93 | if ($this->signUpService->confirmEmail($userEntity)) { 94 | $this->flashMessenger() 95 | ->addSuccessMessage($this->translate('SUCCESS_CONFIRM_EMAIL', 'User')); 96 | } 97 | } 98 | return $this->redirect()->toRoute(self::ROUTE_TO_SIGNIN); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /module/User/src/Controller/SignupControllerFactory.php: -------------------------------------------------------------------------------- 1 | get('Zend\Authentication\AuthenticationService'); 13 | $signUpForm = $container->get('User\Form\SignUpForm'); 14 | $userRepository = $container->get('Doctrine\ORM\EntityManager')->getRepository('User\Entity\User'); 15 | $userEntityName = $userRepository->getClassName(); 16 | $entityHydrator = $container->get('ExDoctrine\Hydrator\ObjectHydrator'); 17 | $signUpService = $container->get('User\Service\SignUpService'); 18 | return new SignupController( 19 | $authService, 20 | $signUpForm, 21 | new $userEntityName, 22 | $entityHydrator, 23 | $signUpService, 24 | $userRepository 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /module/User/src/Entity/User.php: -------------------------------------------------------------------------------- 1 | 'guest', 14 | self::ROLE_REGISTERED => 'registered', 15 | ]; 16 | 17 | /** 18 | * @var integer 19 | */ 20 | private $id; 21 | /** 22 | * @var string 23 | */ 24 | private $person; 25 | /** 26 | * @var string 27 | */ 28 | private $email; 29 | /** 30 | * @var string 31 | */ 32 | private $password; 33 | /** 34 | * @var int 35 | */ 36 | private $roleId; 37 | /** 38 | * @var boolean 39 | */ 40 | private $emailConfirm = false; 41 | /** 42 | * @var string 43 | */ 44 | private $confirmKey; 45 | /** 46 | * @var DateTime 47 | */ 48 | private $createdAt; 49 | /** 50 | * @var string|null 51 | */ 52 | private $restoreKey; 53 | /** 54 | * @var DateTime|null 55 | */ 56 | private $restoreKeyExpire; 57 | 58 | /** 59 | * @return integer|null 60 | */ 61 | public function getId(): ?int 62 | { 63 | return $this->id; 64 | } 65 | 66 | public function setId(int $id): User 67 | { 68 | $this->id = $id; 69 | return $this; 70 | } 71 | 72 | public function getPerson(): string 73 | { 74 | return $this->person; 75 | } 76 | 77 | public function setPerson(string $person): User 78 | { 79 | $this->person = $person; 80 | return $this; 81 | } 82 | 83 | public function getEmail(): string 84 | { 85 | return $this->email; 86 | } 87 | 88 | public function setEmail(string $email): User 89 | { 90 | $this->email = $email; 91 | return $this; 92 | } 93 | 94 | public function getPassword(): string 95 | { 96 | return $this->password; 97 | } 98 | 99 | public function setPassword(string $password): User 100 | { 101 | $this->password = $password; 102 | return $this; 103 | } 104 | 105 | /** 106 | * @return int|null 107 | */ 108 | public function getRoleId() 109 | { 110 | return $this->roleId; 111 | } 112 | 113 | public function setRoleId(int $role): User 114 | { 115 | $this->roleId = $role; 116 | return $this; 117 | } 118 | 119 | public function getRoles(): array 120 | { 121 | return (array)(self::ROLE_MAP[$this->getRoleId()] ?? []); 122 | } 123 | 124 | public function getEmailConfirm(): bool 125 | { 126 | return $this->emailConfirm; 127 | } 128 | 129 | public function setEmailConfirm(bool $emailConfirm): User 130 | { 131 | $this->emailConfirm = $emailConfirm; 132 | return $this; 133 | } 134 | 135 | public function getConfirmKey(): string 136 | { 137 | return $this->confirmKey; 138 | } 139 | 140 | public function setConfirmKey(string $confirmKey): User 141 | { 142 | $this->confirmKey = $confirmKey; 143 | return $this; 144 | } 145 | 146 | public function setCreatedAt(DateTime $createdAt): User 147 | { 148 | $this->createdAt = $createdAt; 149 | return $this; 150 | } 151 | 152 | public function getCreatedAt(): DateTime 153 | { 154 | return $this->createdAt; 155 | } 156 | 157 | public function setRestoreKey(?string $restoreKey): User 158 | { 159 | $this->restoreKey = $restoreKey; 160 | return $this; 161 | } 162 | 163 | public function getRestoreKey(): ?string 164 | { 165 | return $this->restoreKey; 166 | } 167 | 168 | public function setRestoreKeyExpire(?DateTime $restoreKeyExpire): User 169 | { 170 | $this->restoreKeyExpire = $restoreKeyExpire; 171 | return $this; 172 | } 173 | 174 | public function getRestoreKeyExpire(): ?DateTime 175 | { 176 | return $this->restoreKeyExpire; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /module/User/src/Form/AgainConfirmForm.php: -------------------------------------------------------------------------------- 1 | 'email', 19 | 'required' => true, 20 | 'validators' => [ 21 | [ 22 | 'name' => 'EmailAddress', 23 | ], 24 | ], 25 | ], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /module/User/src/Form/ForgotPassForm.php: -------------------------------------------------------------------------------- 1 | 'email', 19 | 'required' => true, 20 | 'validators' => [ 21 | [ 22 | 'name' => 'EmailAddress', 23 | ], 24 | ], 25 | ], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /module/User/src/Form/RestorePassForm.php: -------------------------------------------------------------------------------- 1 | 'password', 19 | 'required' => true, 20 | 'validators' => [ 21 | [ 22 | 'name' => 'StringLength', 23 | 'options' => [ 24 | 'min' => 4, 25 | ], 26 | ] 27 | ], 28 | ], 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /module/User/src/Form/SignInForm.php: -------------------------------------------------------------------------------- 1 | 'email', 23 | 'required' => true, 24 | 'validators' => [ 25 | [ 26 | 'name' => 'EmailAddress', 27 | ], 28 | ], 29 | ], 30 | [ 31 | 'name' => 'password', 32 | 'required' => true, 33 | ], 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /module/User/src/Form/SignUpForm.php: -------------------------------------------------------------------------------- 1 | userRepository = $userRepository; 35 | } 36 | 37 | /** 38 | * {@inheritDoc} 39 | */ 40 | protected function inputs() 41 | { 42 | return [ 43 | [ 44 | 'name' => 'person', 45 | 'required' => true, 46 | 'validators' => [ 47 | [ 48 | 'name' => 'StringLength', 49 | 'options' => [ 50 | 'max' => 255, 51 | ], 52 | ], 53 | ], 54 | ], 55 | [ 56 | 'name' => 'email', 57 | 'required' => true, 58 | 'validators' => [ 59 | [ 60 | 'name' => 'EmailAddress', 61 | 'break_chain_on_failure' => true, 62 | ], 63 | [ 64 | 'name' => NoObjectExists::class, 65 | 'options' => [ 66 | 'object_repository' => $this->userRepository, 67 | 'fields' => 'email', 68 | ], 69 | ], 70 | ], 71 | ], 72 | [ 73 | 'name' => 'password', 74 | 'required' => true, 75 | 'validators' => [ 76 | [ 77 | 'name' => 'StringLength', 78 | 'options' => [ 79 | 'min' => 4, 80 | ], 81 | ] 82 | ], 83 | ], 84 | ]; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /module/User/src/Form/SignUpFormFactory.php: -------------------------------------------------------------------------------- 1 | get('Doctrine\ORM\EntityManager'); 16 | return new SignUpForm($entityManager->getRepository('User\Entity\User')); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /module/User/src/Form/UserSearchForm.php: -------------------------------------------------------------------------------- 1 | '_page', 19 | 'allow_empty' => true, 20 | 'validators' => [ 21 | [ 22 | 'name' => 'Digits', 23 | ], 24 | ], 25 | ], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /module/User/src/Module.php: -------------------------------------------------------------------------------- 1 | getApplication()->getServiceManager(); 25 | 26 | // confirm email request 27 | $confirmEmailService = $sm->get('User\Service\ConfirmEmailService'); 28 | $signUpEventManager = $sm->get('User\Service\SignUpService')->getEventManager(); 29 | $signUpEventManager->attach('signup', function (EventInterface $event) use ($confirmEmailService) { 30 | $confirmEmailService->request($event->getParam('userEntity')); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /module/User/src/Repository/UserRepository.php: -------------------------------------------------------------------------------- 1 | findOneBy(['email' => $email, 'confirmKey' => $confirmKey]); 16 | if ($entity === null || $entity->getEmailConfirm()) { 17 | return null; 18 | } 19 | return $entity; 20 | } 21 | 22 | public function findForRestore(string $email, string $restoreKey): ?User 23 | { 24 | return $this->findOneBy(['email' => $email, 'restoreKey' => $restoreKey]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /module/User/src/Search/UserSearch.php: -------------------------------------------------------------------------------- 1 | dataProvider = $dataProvider; 25 | $this->entityManager = $entityManager; 26 | } 27 | 28 | public function search(array $data, UserSearchForm $form): QueryDataProvider 29 | { 30 | // query 31 | $this->dataProvider->getQueryBuilder()->select('t') 32 | ->from('User\Entity\User', QueryDataProvider::MAIN_ENTITY_ALIAS) 33 | ->orderBy('t.id', 'DESC'); 34 | 35 | // limit 36 | $this->dataProvider->setLimit(self::RESULT_LIMIT_DEFAULT); 37 | 38 | // validation 39 | $form->setValues($data); 40 | if ($form->isValid()) { 41 | $this->dataProvider->setPage($form->_page); 42 | $this->dataProvider->setQueryParams($form->getValues()); 43 | } 44 | 45 | // counter 46 | $this->dataProvider->getCountBuilder()->resetDQLPart('orderBy'); 47 | 48 | return $this->dataProvider; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /module/User/src/Service/AccessService.php: -------------------------------------------------------------------------------- 1 | mailService = $mailService; 46 | $this->userRepository = $userRepository; 47 | $this->passwordHashService = $passwordHashService; 48 | $this->entityManager = $entityManager; 49 | } 50 | 51 | public function forgotPass(array $data, ForgotPassForm $form): bool 52 | { 53 | $form->setValues($data); 54 | if (!$form->isValid()) { 55 | return false; 56 | } 57 | 58 | $entity = $this->userRepository->findOneByEmail($form->email); 59 | if (!$entity) { 60 | return false; 61 | } 62 | 63 | // generate restore key 64 | $restoreKeyExpire = new DateTime; 65 | $restoreKeyExpire->add(new DateInterval(sprintf('PT%dS', self::RESTORE_KEY_DURATION))); 66 | $entity->setRestoreKey(Rand::getString(self::RESTORE_KEY_LENGTH, self::RESTORE_KEY_DICT)) 67 | ->setRestoreKeyExpire($restoreKeyExpire); 68 | 69 | $this->entityManager->persist($entity); 70 | $this->entityManager->flush(); 71 | 72 | // send email 73 | $message = $this->mailService->createMessageBuilder(); 74 | $message->addToRecipient($entity->getEmail(), ['full_name' => $entity->getPerson()]); 75 | $message->setTranslateSubject('EMAIL_SUBJECT_RESTORE_PASS', 'User'); 76 | $message->setRenderHtmlBody('user/mail/restore-pass', ['userEntity' => $entity]); 77 | $this->mailService->send($message); 78 | 79 | return true; 80 | } 81 | 82 | public function findForRestore(string $email, string $key): ?User 83 | { 84 | $entity = $this->userRepository->findForRestore($email, $key); 85 | if (!$entity || $entity->getRestoreKeyExpire() < new DateTime) { 86 | return null; 87 | } 88 | return $entity; 89 | } 90 | 91 | public function restorePass(array $data, User $entity, RestorePassForm $form): bool 92 | { 93 | $form->setValues($data); 94 | if (!$form->isValid()) { 95 | return false; 96 | } 97 | $hashPassword = $this->passwordHashService->create($form->password); 98 | $entity->setPassword($hashPassword) 99 | ->setRestoreKeyExpire(new DateTime); 100 | 101 | $this->entityManager->persist($entity); 102 | $this->entityManager->flush(); 103 | 104 | return true; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /module/User/src/Service/AccessServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('Mail\Service\MailService'), 14 | $container->get('Doctrine\ORM\EntityManager')->getRepository('User\Entity\User'), 15 | $container->get('User\Crypt\PasswordCrypt'), 16 | $container->get('Doctrine\ORM\EntityManager') 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /module/User/src/Service/AuthServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('doctrine.authenticationservice.orm_default'); 21 | $passHashService = $container->get('User\Crypt\PasswordCrypt'); 22 | $authService 23 | ->getAdapter() 24 | ->getOptions() 25 | ->setCredentialCallable(function (User $user, $password) use ($passHashService) { 26 | return $passHashService->verify($password, $user->getPassword()); 27 | }); 28 | return $authService; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /module/User/src/Service/ConfirmEmailService.php: -------------------------------------------------------------------------------- 1 | mailService = $mailService; 32 | $this->userRepository = $userRepository; 33 | $this->entityManager = $entityManager; 34 | } 35 | 36 | public function request(User $user): bool 37 | { 38 | $message = $this->mailService->createMessageBuilder(); 39 | $message->addToRecipient($user->getEmail(), [ 40 | 'full_name' => $user->getPerson(), 41 | ]); 42 | $message->setTranslateSubject('EMAIL_SUBJECT_CONFIRM_EMAIL', 'User'); 43 | $message->setRenderHtmlBody('user/mail/confirm-request', ['userEntity' => $user]); 44 | $this->mailService->send($message); 45 | return true; 46 | } 47 | 48 | public function confirm(User $user): bool 49 | { 50 | $user->setEmailConfirm(true); 51 | 52 | $this->entityManager->persist($user); 53 | $this->entityManager->flush(); 54 | 55 | return true; 56 | } 57 | 58 | public function again(array $data, AgainConfirmForm $form): bool 59 | { 60 | $form->setValues($data); 61 | if (!$form->isValid()) { 62 | return false; 63 | } 64 | 65 | $user = $this->userRepository->findOneByEmail($form->email); 66 | if ($user === null || $user->getEmailConfirm()) { 67 | return false; 68 | } 69 | 70 | return $this->request($user); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /module/User/src/Service/SignUpService.php: -------------------------------------------------------------------------------- 1 | setIdentifiers([__CLASS__, get_called_class()]); 41 | $this->eventManager = $eventManager; 42 | return $this; 43 | } 44 | 45 | public function getEventManager(): EventManagerInterface 46 | { 47 | if ($this->eventManager === null) { 48 | $this->setEventManager(new EventManager()); 49 | } 50 | return $this->eventManager; 51 | } 52 | 53 | public function __construct( 54 | UserRepository $userRepository, 55 | PasswordInterface $passwordHashService, 56 | EntityManager $entityManager 57 | ) { 58 | $this->userRepository = $userRepository; 59 | $this->passwordHashService = $passwordHashService; 60 | $this->entityManager = $entityManager; 61 | } 62 | 63 | public function signup(User $userEntity): bool 64 | { 65 | if ($userEntity->getRoleId() === null) { 66 | $userEntity->setRoleId(self::DEFAULT_ROLE); 67 | } 68 | $hashPassword = $this->passwordHashService->create($userEntity->getPassword()); 69 | $userEntity->setPassword($hashPassword) 70 | ->setConfirmKey(Rand::getString(self::LENGTH_CONFIRM_KEY, self::CONFIRM_KEY_DICT)) 71 | ->setCreatedAt(new DateTime); 72 | 73 | $this->entityManager->persist($userEntity); 74 | $this->entityManager->flush(); 75 | 76 | if ($userEntity->getId() === null) { 77 | return false; 78 | } 79 | $this->getEventManager()->trigger(__FUNCTION__, $this, ['userEntity' => $userEntity]); 80 | 81 | return true; 82 | } 83 | 84 | public function confirmEmail(User $userEntity): bool 85 | { 86 | $userEntity->setEmailConfirm(true); 87 | 88 | $this->entityManager->persist($userEntity); 89 | $this->entityManager->flush(); 90 | 91 | return true; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /module/User/src/Service/SignUpServiceFactory.php: -------------------------------------------------------------------------------- 1 | get('User\Repository\UserRepository'), 14 | $container->get('User\Crypt\PasswordCrypt'), 15 | $container->get('Doctrine\ORM\EntityManager') 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /module/User/test/_bootstrap.php: -------------------------------------------------------------------------------- 1 | amOnRoute('signin'); 12 | $I->seeResponseCodeIs(200); 13 | 14 | $I->submitForm('form', [ 15 | 'email' => 'vasily@zf-app-blank.com', 16 | 'password' => '1234', 17 | ]); 18 | $I->assertTrue($I->grabService('Zend\Authentication\AuthenticationService')->hasIdentity()); 19 | } 20 | 21 | public function logout(FunctionalTester $I) 22 | { 23 | $I->amAuthenticated('vasily@zf-app-blank.com', '1234'); 24 | 25 | $I->amOnRoute('home'); 26 | $I->seeResponseCodeIs(200); 27 | 28 | $I->seeLink('Log out', '/logout'); 29 | $I->click('Log out'); 30 | // cannot logout because of Codeception test release 31 | // we can check only status code after logout 32 | $I->seeResponseCodeIs(200); 33 | } 34 | 35 | public function signup(FunctionalTester $I) 36 | { 37 | $I->amOnRoute('signup'); 38 | 39 | /** @var \Bupy7\Mailgun\Options\ModuleOptions $mailConfig */ 40 | $mailConfig = $I->grabService('Bupy7\Mailgun\Options\ModuleOptions'); 41 | if (empty($mailConfig->getEndpoint())) { 42 | throw new Exception('You should setup "endpoint" for Mailgun in your local config file.'); 43 | } 44 | $mailConfig->setDebug(true); 45 | 46 | $I->submitForm('form', [ 47 | 'person' => 'Test User', 48 | 'email' => 'test@zf-app-blank.com', 49 | 'password' => '1234', 50 | ]); 51 | $I->seeResponseCodeIs(200); 52 | 53 | $I->canSeeInRepository('User\Entity\User', ['email' => 'test@zf-app-blank.com']); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /module/User/test/functional/_bootstrap.php: -------------------------------------------------------------------------------- 1 | {{ translate('EMAIL_HELLO', 'User')|format(userEntity.getPerson() | e) }}

3 | {% set link %} 4 | {{ translate('EMAIL_CONFIRM_EMAIL', 'User') }} 10 | {% endset %} 11 |

{{ translate('EMAIL_BODY_CONFIRM_EMAIL', 'User') | format(link) }}

12 | {% endblock content %} 13 | -------------------------------------------------------------------------------- /module/User/view/mail/restore-pass.twig: -------------------------------------------------------------------------------- 1 | {% block content %} 2 |

{{ translate('EMAIL_HELLO', 'User')|format(userEntity.getPerson() | e) }}

3 | {% set link %} 4 | {{ translate('EMAIL_RESTORE_PASS', 'User') }} 10 | {% endset %} 11 |

{{ translate('EMAIL_BODY_RESTORE_PASS', 'User') | format(link) }}

12 | {% endblock content %} 13 | -------------------------------------------------------------------------------- /module/User/view/user/access/forgot-pass.twig: -------------------------------------------------------------------------------- 1 | {% import 'macro/icon.twig' as icon %} 2 | {% import 'macro/form.twig' as form %} 3 | 4 | {% extends 'layout/layout.twig' %} 5 | 6 | {% set title = translate('PAGE_FORGOT_PASS', 'User') %} 7 | 8 | {% do headTitle(title) %} 9 | 10 | {% block content %} 11 |

{{ title | e }}

12 | 13 | {% set formBuilder = formBuilder(forgotPassForm) %} 14 | {{ formBuilder 15 | .open() 16 | .action(url('forgot-pass')) 17 | }} 18 |
19 | {{ formBuilder 20 | .label(translate('LABEL_EMAIL', 'User')) 21 | .forId('email') 22 | .addClass('control-label col-md-2') 23 | }} 24 |
25 | {{ formBuilder 26 | .email('email') 27 | .addClass('form-control') 28 | .addClass(form.hasError(formBuilder, 'email')) 29 | }} 30 |
31 | {{ formBuilder.getError('email') }} 32 |
33 |
34 |
35 | 36 |
37 |
38 | {{ formBuilder 39 | .button([icon.fa('check'), translate('LABEL_SEND_RESTORE_KEY', 'User')] | join(' ')) 40 | .attribute('type', 'submit') 41 | .addClass('btn btn-primary') }} 42 |
43 |
44 | {{ formBuilder.close() }} 45 | {% endblock content %} 46 | -------------------------------------------------------------------------------- /module/User/view/user/access/restore-pass.twig: -------------------------------------------------------------------------------- 1 | {% import 'macro/icon.twig' as icon %} 2 | {% import 'macro/form.twig' as form %} 3 | 4 | {% extends 'layout/layout.twig' %} 5 | 6 | {% set title = translate('PAGE_FORGOT_PASS', 'User') %} 7 | 8 | {% do headTitle(title) %} 9 | 10 | {% block content %} 11 |

{{ title | e }}

12 | 13 | {% set formBuilder = formBuilder(restorePassForm) %} 14 | {{ formBuilder 15 | .open() 16 | .action(url('restore-pass', {e: email, k: key})) 17 | }} 18 |
19 | {{ formBuilder 20 | .label(translate('LABEL_NEW_PASSWORD', 'User')) 21 | .forId('password') 22 | .addClass('control-label col-md-2') 23 | }} 24 |
25 | {{ formBuilder 26 | .password('password') 27 | .addClass('form-control') 28 | .addClass(form.hasError(formBuilder, 'password')) 29 | }} 30 |
31 | {{ formBuilder.getError('password') }} 32 |
33 |
34 |
35 |
36 |
37 | {{ formBuilder 38 | .button([icon.fa('check'), translate('LABEL_RESTORE_PASS', 'User')] | join(' ')) 39 | .attribute('type', 'submit') 40 | .addClass('btn btn-primary') 41 | }} 42 |
43 |
44 | {{ formBuilder.close() }} 45 | {% endblock content %} 46 | -------------------------------------------------------------------------------- /module/User/view/user/auth/signin.twig: -------------------------------------------------------------------------------- 1 | {% import 'macro/icon.twig' as icon %} 2 | {% import 'macro/form.twig' as form %} 3 | 4 | {% extends 'layout/layout.twig' %} 5 | 6 | {% set title = translate('PAGE_TITLE_SIGNIN', 'User') %} 7 | 8 | {% do headTitle(title) %} 9 | 10 | {% block content %} 11 |

{{ title | e }}

12 | 13 | {% set formBuilder = formBuilder(signInForm) %} 14 | {{ formBuilder 15 | .open() 16 | .action(url('signin')) 17 | }} 18 |
19 | {{ formBuilder 20 | .label(translate('LABEL_EMAIL', 'User')) 21 | .forId('email') 22 | .addClass('control-label col-md-2') 23 | }} 24 |
25 | {{ formBuilder 26 | .email('email') 27 | .addClass('form-control') 28 | .addClass(form.hasError(formBuilder, 'email')) 29 | }} 30 |
31 | {{ formBuilder.getError('email') }} 32 |
33 |
34 |
35 | 36 |
37 | {{ formBuilder 38 | .label(translate('LABEL_PASSWORD', 'User')) 39 | .forId('password') 40 | .addClass('control-label col-md-2') 41 | }} 42 |
43 | {{ formBuilder 44 | .password('password') 45 | .addClass('form-control') 46 | .addClass(form.hasError(formBuilder, 'password')) 47 | }} 48 |
49 | {{ formBuilder.getError('password')}} 50 |
51 |
52 |
53 | 54 |
55 |
56 | {{ formBuilder 57 | .button([icon.fa('sign-in'), translate('LABEL_SIGNIN', 'User')] | join(' ')) 58 | .attribute('type', 'submit') 59 | .addClass('btn btn-primary') 60 | }} 61 | 62 | {{ translate('LABEL_FORGOT_PASS', 'User') }} 63 | 64 |
65 |
66 | {{ formBuilder.close() }} 67 | {% endblock content %} -------------------------------------------------------------------------------- /module/User/view/user/confirm-email/again.twig: -------------------------------------------------------------------------------- 1 | {% import 'macro/icon.twig' as icon %} 2 | {% import 'macro/form.twig' as form %} 3 | 4 | {% extends 'layout/layout.twig' %} 5 | 6 | {% set title = translate('PAGE_CONFIRM_AGAIN', 'User') %} 7 | 8 | {% do headTitle(title) %} 9 | 10 | {% block content %} 11 |

{{ title | e }}

12 | 13 | {% set formBuilder = formBuilder(againForm) %} 14 | {{ formBuilder 15 | .open() 16 | .action(url('confirm-again')) 17 | }} 18 |
19 | {{ formBuilder 20 | .label(translate('LABEL_EMAIL', 'User')) 21 | .forId('email') 22 | .addClass('control-label col-md-2') 23 | }} 24 |
25 | {{ formBuilder 26 | .email('email') 27 | .addClass('form-control') 28 | .addClass(form.hasError(formBuilder, 'email')) 29 | }} 30 |
31 | {{ formBuilder.getError('email') }} 32 |
33 |
34 |
35 | 36 |
37 |
38 | {{ formBuilder 39 | .button([icon.fa('check'), translate('LABEL_SEND_CONFIRM_KEY', 'User')] | join(' ')) 40 | .attribute('type', 'submit') 41 | .addClass('btn btn-primary') 42 | }} 43 |
44 |
45 | {{ formBuilder.close() }} 46 | {% endblock content %} 47 | -------------------------------------------------------------------------------- /module/User/view/user/index/index.twig: -------------------------------------------------------------------------------- 1 | {% import 'macro/component.twig' as component %} 2 | {% import 'macro/table.twig' as table %} 3 | 4 | {% extends 'layout/layout.twig' %} 5 | 6 | {% block content %} 7 |

{{ translate('PAGE_TITLE_USER_LIST', 'User') | e }}

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% set paginator = dataProvider.getPaginator() %} 19 | {% set index = 1 %} 20 | {% if paginator | length > 0 %} 21 | {% for item in paginator %} 22 | {% include 'user/index/row.twig' %} 23 | {% set index = index + 1 %} 24 | {% endfor %} 25 | {% else %} 26 | {{ table.emptyRow(6) }} 27 | {% endif %} 28 | 29 |
#{{ translate('COLUMN_USERNAME', 'User') }}{{ translate('COLUMN_EMAIL', 'User') }}{{ translate('COLUMN_EMAIL_CONFIRM', 'User') }}{{ translate('COLUMN_CREATED_AT', 'User') }}
30 | 31 | {{ component.paginator(dataProvider.getPaginator(), dataProvider.getQueryParams()) }} 32 | {% endblock content %} 33 | -------------------------------------------------------------------------------- /module/User/view/user/index/row.twig: -------------------------------------------------------------------------------- 1 | 2 | {{ index }} 3 | {{ item.getPerson() | e }} 4 | {{ item.getEmail() | e }} 5 | {{ item.getEmailConfirm() ? translate('LABEL_YES', 'User') : translate('LABEL_NO', 'User') }} 6 | {{ item.getCreatedAt() | date('Y-m-d H:i') }} 7 | 8 | -------------------------------------------------------------------------------- /module/User/view/user/signup/signup.twig: -------------------------------------------------------------------------------- 1 | {% import 'macro/icon.twig' as icon %} 2 | {% import 'macro/form.twig' as form %} 3 | 4 | {% extends 'layout/layout.twig' %} 5 | 6 | {% set title = translate('PAGE_TITLE_SIGNUP', 'User') %} 7 | 8 | {% do headTitle(title) %} 9 | 10 | {% block content %} 11 |

{{ title | e }}

12 | 13 | {% set formBuilder = formBuilder(signUpForm) %} 14 | {{ formBuilder 15 | .open() 16 | .action(url('signup')) 17 | }} 18 |
19 | {{ formBuilder 20 | .label(translate('LABEL_PERSON', 'User')) 21 | .forId('person') 22 | .addClass('control-label col-md-2') 23 | }} 24 | 25 |
26 | {{ formBuilder 27 | .text('person') 28 | .addClass('form-control') 29 | .attribute('maxlength', 255) 30 | .addClass(form.hasError(formBuilder, 'person')) 31 | }} 32 | 33 |
34 | {{ formBuilder.getError('person') }} 35 |
36 |
37 |
38 | 39 |
40 | {{ formBuilder 41 | .label(translate('LABEL_EMAIL', 'User')) 42 | .forId('email') 43 | .addClass('control-label col-md-2') 44 | }} 45 |
46 | {{ formBuilder 47 | .email('email') 48 | .addClass('form-control') 49 | .addClass(form.hasError(formBuilder, 'email')) 50 | }} 51 | 52 |
53 | {{ formBuilder.getError('email') }} 54 |
55 |
56 |
57 | 58 |
59 | {{ formBuilder 60 | .label(translate('LABEL_PASSWORD', 'User')) 61 | .forId('password') 62 | .addClass('control-label col-md-2') 63 | }} 64 |
65 | {{ formBuilder 66 | .password('password') 67 | .addClass('form-control') 68 | .addClass(form.hasError(formBuilder, 'password')) 69 | }} 70 |
71 | {{ formBuilder.getError('password') }} 72 |
73 |
74 |
75 | 76 |
77 |
78 | {{ formBuilder 79 | .button([icon.fa('check'), translate('LABEL_SIGNUP', 'User')] | join(' ')) 80 | .attribute('type', 'submit') 81 | .addClass('btn btn-primary') 82 | }} 83 |
84 |
85 | {{ formBuilder.close() }} 86 | {% endblock content %} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zf-app-blank", 3 | "version": "1.6.4", 4 | "description": "A blank application Zend Framework 3.", 5 | "repository": "git@github.com:bupy7/zf-app-blank.git", 6 | "author": "Vasily Belosloodcev", 7 | "license": "BSD-3-Clause", 8 | "dependencies": { 9 | "bootstrap": "^4.0.0-beta.3", 10 | "bootstrap.native": "^2.0.21", 11 | "font-awesome": "~4.7" 12 | }, 13 | "devDependencies": { 14 | "jquery": "^3.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | /index*.php 2 | -------------------------------------------------------------------------------- /public/assets/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !/.gitignore -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bupy7/zf-app-blank/6ffb30bcfca4c0ab135c6bdd5469fbaac273a63b/public/favicon.ico -------------------------------------------------------------------------------- /test/_data/user.php: -------------------------------------------------------------------------------- 1 | 1, 6 | 'person' => 'Vasily', 7 | 'email' => 'vasily@zf-app-blank.com', 8 | 'password' => '$2y$10$tdYs2O82NjKo7w98Gs9yKOjiF43hv31LfehjjJFSysIL1qXC9IDpi', // 1234 9 | 'roleId' => 20, 10 | 'emailConfirm' => true, 11 | 'confirmKey' => 'ub_e-bbf6jbwx0qrp4', 12 | 'restoreKey' => null, 13 | 'restoreKeyExpire' => null, 14 | 'createdAt' => new DateTime(), 15 | ], 16 | ]; 17 | -------------------------------------------------------------------------------- /test/_fixture/UserFixture.php: -------------------------------------------------------------------------------- 1 | 3 | 4 | # https://wiki.debian.org/Locale 5 | server_locale: 'en_US.UTF-8 UTF-8' 6 | 7 | # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 8 | server_time_zone: Europe/London 9 | 10 | # are we need check box updates for every 'vagrant up'? 11 | box_check_update: false 12 | 13 | # virtual machine name 14 | server_name: zf-app-blank 15 | 16 | # virtual machine IP 17 | ip: 192.168.20.22 18 | 19 | # virtual machine CPU cores number 20 | server_cpus: 1 21 | 22 | # virtual machine RAM 23 | server_memory: 512 24 | 25 | # http://php.net/manual/ru/ini.core.php#ini.memory-limit 26 | php_memory_limit: 256M 27 | 28 | # http://php.net/manual/ru/info.configuration.php#ini.max-execution-time 29 | php_execution_time: 60 30 | 31 | # http://php.net/manual/ru/info.configuration.php#ini.max-input-time 32 | php_input_time: 120 33 | 34 | # http://php.net/manual/ru/timezones.php 35 | php_time_zone: UTC 36 | 37 | # https://xdebug.org/docs/all_settings#idekey 38 | xdebug_idekey: vagrant-xdebug 39 | 40 | # https://dev.mysql.com/doc/refman/5.7/en/adding-users.html 41 | # https://dev.mysql.com/doc/refman/5.7/en/creating-database.html 42 | mysql_db: zf_app_blank 43 | mysql_user: zf_app_blank 44 | mysql_pass: 1234 45 | 46 | # https://www.postgresql.org/docs/9.6/static/sql-createdatabase.html 47 | # https://www.postgresql.org/docs/9.6/static/sql-createuser.html 48 | # https://www.postgresql.org/docs/9.6/static/locale.html 49 | pgsql_db: zf_app_blank 50 | pgsql_user: zf_app_blank 51 | pgsql_pass: 1234 52 | pgsql_locale: 'en_US.utf8' 53 | 54 | # using database (mysql or pgsql) 55 | db_type: mysql 56 | -------------------------------------------------------------------------------- /workenv/nginx/log/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /workenv/nginx/site.conf: -------------------------------------------------------------------------------- 1 | server { 2 | server_name zf-app-blank.local; 3 | root /var/www/html/public; 4 | index index.php index.html index.htm; 5 | 6 | access_log /vagrant/workenv/nginx/log/access.log; 7 | error_log /vagrant/workenv/nginx/log/error.log; 8 | 9 | location / { 10 | try_files $uri $uri/ /index.php$is_args$args; 11 | location ~* ^.+\.(jpeg|jpg|png|gif|bmp|ico|svg|css|js)$ { 12 | expires max; 13 | } 14 | location ~ [^/]\.php(/|$) { 15 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 16 | if (!-f $document_root$fastcgi_script_name) { 17 | return 404; 18 | } 19 | fastcgi_pass unix:/var/run/php/php7.1-fpm.sock; 20 | fastcgi_index index.php; 21 | include /etc/nginx/fastcgi_params; 22 | } 23 | } 24 | 25 | location ~* "/\." { 26 | deny all; 27 | return 404; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /workenv/provision/always-as-root.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # import script args 4 | # ------------------ 5 | DB_TYPE=$(echo "$1") 6 | 7 | # restart services 8 | # ---------------- 9 | service php7.1-fpm restart 10 | service nginx restart 11 | case $DB_TYPE in 12 | mysql) 13 | service mysql restart 14 | ;; 15 | 16 | pgsql) 17 | service postgresql restart 18 | ;; 19 | esac 20 | -------------------------------------------------------------------------------- /workenv/provision/once-as-vagrant.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # import script args 4 | # ------------------ 5 | GITHUB_TOKEN=$(echo "$1") 6 | 7 | # common 8 | # ------ 9 | cd /vagrant 10 | 11 | # composer 12 | # -------- 13 | composer config --global github-oauth.github.com ${GITHUB_TOKEN} 14 | composer install 15 | 16 | # yarn 17 | # ---- 18 | yarn install 19 | 20 | # init project 21 | # ------------ 22 | php bin/init --env=dev --overwrite=y 23 | --------------------------------------------------------------------------------