├── .bowerrc ├── .gitignore ├── .scrutinizer.yml ├── .tarignore ├── .travis.yml ├── LICENSE ├── README.md ├── Vagrantfile ├── ansible ├── development ├── group_vars │ ├── all │ ├── development │ └── frontends ├── playbook.yml ├── production └── roles │ ├── frontend │ ├── tasks │ │ └── main.yml │ └── templates │ │ └── parameters.yml.j2 │ ├── mysql │ ├── tasks │ │ └── main.yml │ └── templates │ │ └── my.cnf.j2 │ ├── nginx │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── nginx.conf.j2 │ │ └── sites │ │ └── frontend.conf.j2 │ ├── php-fpm │ ├── handlers │ │ └── main.yml │ ├── tasks │ │ └── main.yml │ └── templates │ │ ├── php-fpm.conf.j2 │ │ └── php.ini.j2 │ ├── redis │ └── tasks │ │ └── main.yml │ ├── skel │ └── tasks │ │ └── main.yml │ ├── supervisor │ └── tasks │ │ └── main.yml │ └── user │ └── tasks │ └── main.yml ├── app ├── AppCache.php ├── AppKernel.php ├── Resources │ ├── bin │ │ └── reload │ ├── docker │ │ └── init.sh │ ├── gif │ │ ├── README.md │ │ └── star.html │ ├── jwt │ │ ├── README.md │ │ ├── private.pem │ │ └── public.pem │ └── views │ │ ├── admin.html.twig │ │ ├── base.html.twig │ │ └── menu.html.twig ├── autoload.php ├── config │ ├── config.yml │ ├── config_dev.yml │ ├── config_prod.yml │ ├── config_test.yml │ ├── parameters.yml.dist │ ├── routing.yml │ ├── routing_dev.yml │ ├── security.yml │ └── service_aliases.yml ├── console ├── logs │ └── .gitkeep └── phpunit.xml.dist ├── assets ├── img │ ├── colors │ │ ├── txture.png │ │ ├── txture_lighter.png │ │ └── txture_trans.png │ ├── homepage │ │ ├── carousel │ │ │ ├── banner-1.jpg │ │ │ ├── banner-2.jpg │ │ │ └── banner-3.jpg │ │ └── testimonials │ │ │ ├── chewbacca.jpg │ │ │ ├── grievous.png │ │ │ ├── vader.jpg │ │ │ └── yoda.jpg │ └── icons │ │ ├── darth-vader-icon.jpeg │ │ ├── deathstar-ico-32.png │ │ ├── luke-skywalker-icon.png │ │ └── yoda-icon.jpg ├── js │ ├── admin │ │ └── scripts.js │ └── app │ │ └── scripts.js └── less │ ├── admin.less │ ├── admin │ ├── bootswatch.less │ ├── layout.less │ ├── navs.less │ ├── pagination.less │ ├── panels.less │ ├── tables.less │ └── variables.less │ ├── app.less │ └── app │ ├── colors.less │ ├── forms.less │ ├── pages │ └── homepage.less │ └── theme.less ├── behat.yml.dist ├── bower.json ├── composer.json ├── composer.lock ├── docker-compose.yml.dist ├── features ├── account │ ├── confirm.feature │ └── profile.feature ├── admin │ └── login.feature ├── api │ ├── authentication.feature │ ├── cms-blocks │ │ └── list.feature │ └── version.feature └── guest │ ├── login.feature │ └── signup.feature ├── gruntfile.js ├── package.json ├── src ├── AdminBundle │ ├── AdminBundle.php │ ├── Controller │ │ ├── AuditController.php │ │ ├── CmsBlockController.php │ │ ├── DashboardController.php │ │ ├── MailTemplateController.php │ │ └── UserController.php │ ├── DependencyInjection │ │ └── AdminExtension.php │ ├── Form │ │ ├── CmsBlockType.php │ │ ├── MailTemplateType.php │ │ └── UserType.php │ ├── Menu │ │ └── MenuBuilder.php │ ├── Resources │ │ ├── config │ │ │ ├── routing.yml │ │ │ └── twig.yml │ │ ├── translations │ │ │ ├── menu.en.yml │ │ │ └── messages.en.yml │ │ └── views │ │ │ ├── Audit │ │ │ ├── assoc.html.twig │ │ │ ├── associate.html.twig │ │ │ ├── blame.html.twig │ │ │ ├── diff.html.twig │ │ │ ├── dissociate.html.twig │ │ │ ├── index.html.twig │ │ │ ├── insert.html.twig │ │ │ ├── remove.html.twig │ │ │ └── update.html.twig │ │ │ ├── CmsBlock │ │ │ ├── edit.html.twig │ │ │ ├── index.html.twig │ │ │ └── new.html.twig │ │ │ ├── Dashboard │ │ │ └── index.html.twig │ │ │ ├── MailTemplate │ │ │ ├── edit.html.twig │ │ │ ├── index.html.twig │ │ │ └── new.html.twig │ │ │ ├── User │ │ │ ├── edit.html.twig │ │ │ ├── index.html.twig │ │ │ └── new.html.twig │ │ │ └── layout.html.twig │ └── Twig │ │ └── AuditExtension.php ├── ApiBundle │ ├── ApiBundle.php │ ├── Behat │ │ └── APIContext.php │ ├── Controller │ │ ├── CmsBlockController.php │ │ └── VersionController.php │ ├── DependencyInjection │ │ ├── ApiExtension.php │ │ └── Security │ │ │ └── Factory │ │ │ ├── JWTAuthFactory.php │ │ │ └── JWTFactory.php │ ├── EventListener │ │ └── ApiResponseListener.php │ ├── Resource │ │ ├── CmsBlock │ │ │ ├── ListResource.php │ │ │ └── SingleResource.php │ │ └── VersionResource.php │ ├── ResourceInterface.php │ ├── Resources │ │ └── config │ │ │ ├── listeners │ │ │ └── kernel.yml │ │ │ ├── routing.yml │ │ │ └── security.yml │ └── Security │ │ ├── Authentication │ │ ├── Provider │ │ │ └── JWTProvider.php │ │ └── Token │ │ │ └── JWTUserToken.php │ │ └── Firewall │ │ ├── JWTAuthListener.php │ │ └── JWTListener.php └── AppBundle │ ├── AppBundle.php │ ├── Behat │ ├── BaseContext.php │ ├── DatabaseContext.php │ ├── Doctrine │ │ └── PlaceholderListener.php │ ├── MailerContext.php │ ├── PageContext.php │ ├── PlaceholderContext.php │ ├── Swiftmailer │ │ └── MemorySpool.php │ └── UserContext.php │ ├── Cache │ ├── RedisCache.php │ └── Session │ │ └── RedisSessionHandler.php │ ├── Command │ ├── CacheClearCommand.php │ ├── FixturesCommand.php │ └── RouterAnonymousCommand.php │ ├── Composer.php │ ├── Controller │ ├── DoctrineController.php │ ├── HomeController.php │ └── UserController.php │ ├── DependencyInjection │ └── AppExtension.php │ ├── Entity │ ├── CmsBlock.php │ ├── Internal │ │ └── Fixture.php │ ├── MailTemplate.php │ └── User.php │ ├── EventListener │ ├── DoctrineExtensionsListener.php │ ├── FlushListener.php │ └── FlushSubscriber.php │ ├── Fixture │ ├── Cms │ │ └── LayoutBlocks.php │ ├── Mail │ │ └── RegistrationConfirm.php │ └── Users │ │ └── DevUsers.php │ ├── Form │ └── Type │ │ └── User │ │ ├── ConfirmType.php │ │ ├── ProfileType.php │ │ ├── ResetType.php │ │ └── SignupType.php │ ├── Mailer │ ├── ContactInterface.php │ └── Mailer.php │ ├── Menu │ ├── MenuBuilder.php │ └── RequestVoter.php │ ├── Migration │ └── Version20150813073404.php │ ├── Resources │ ├── config │ │ ├── cache │ │ │ ├── dev.yml │ │ │ ├── prod.yml │ │ │ └── test.yml │ │ ├── listeners │ │ │ ├── doctrine.yml │ │ │ └── kernel.yml │ │ ├── mailer.yml │ │ ├── menu.yml │ │ ├── routing.yml │ │ ├── security.yml │ │ └── twig.yml │ ├── translations │ │ ├── menu.en.yml │ │ ├── messages.en.yml │ │ └── time.en.yml │ └── views │ │ ├── Home │ │ ├── about.html.twig │ │ └── homepage.html.twig │ │ ├── Mail │ │ └── template.html.twig │ │ ├── User │ │ ├── confirm.html.twig │ │ ├── login.html.twig │ │ ├── profile.html.twig │ │ ├── reset.html.twig │ │ └── signup.html.twig │ │ ├── blocks │ │ └── layout │ │ │ └── footer.html.twig │ │ ├── flashes.html.twig │ │ ├── forms.html.twig │ │ └── layout.html.twig │ ├── Security │ └── Core │ │ └── Encoder │ │ └── BCryptPasswordEncoder.php │ ├── Tests │ └── Entity │ │ └── UserTest.php │ └── Twig │ ├── CMSBlockExtension.php │ └── TimeExtension.php └── web ├── app.php ├── app_dev.php ├── app_test.php ├── apple-touch-icon.png ├── favicon.ico └── robots.txt /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "assets/vendor" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vagrant 2 | /behat.yml 3 | /ansible/*.tar.gz 4 | /app/bootstrap.php.cache 5 | /app/cache/* 6 | /app/config/parameters.yml 7 | /app/logs/* 8 | !app/cache/.gitkeep 9 | !app/logs/.gitkeep 10 | /app/phpunit.xml 11 | /assets/vendor/ 12 | /bin/ 13 | /node_modules 14 | /vendor/ 15 | /web/build/ 16 | /web/bundles/ 17 | /docker-compose.yml 18 | /app/session 19 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | php: { } 3 | 4 | coding_style: 5 | php: 6 | indentation: 7 | switch: 8 | indent_case: false 9 | 10 | filter: 11 | paths: 12 | - src/* 13 | excluded_paths: 14 | - src/*/Behat/* 15 | -------------------------------------------------------------------------------- /.tarignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .drone.yml 3 | .travis.yml 4 | .jshintrc 5 | .travis.yml 6 | .tarignore 7 | .gitignore 8 | .gitmodules 9 | .bowerrc 10 | .editorconfig 11 | .vagrant 12 | *.tar.gz 13 | *.log 14 | */Behat 15 | */Tests 16 | ansible 17 | app/cache/* 18 | app/phpunit.* 19 | app/logs/* 20 | app/spool/* 21 | app/check.php 22 | app/SymfonyRequirements.php 23 | app/config/parameters.* 24 | app/config/routing_*.yml 25 | app/config/config_dev.yml 26 | app/config/config_test.yml 27 | app/Resources/views/*.dist.html.twig 28 | app/Resources/gif 29 | app/Resources/docker 30 | assets 31 | starwars.gif 32 | screenshot.png 33 | behat.yml* 34 | bin 35 | bower.json 36 | composer.* 37 | docker-compose.* 38 | features 39 | gruntfile.js 40 | node_modules 41 | package.json 42 | tests 43 | README.md 44 | Vagrantfile 45 | web/app_*.php 46 | web/bundles 47 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | 7 | services: 8 | - mysql 9 | - redis-server 10 | 11 | install: 12 | - composer install --prefer-dist --no-interaction 13 | - bin/reload test 14 | 15 | script: 16 | - bin/phpunit -c app 17 | - bin/behat -fprogress 18 | - app/console security:check 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 DATA-DOG TEAM 2 | 3 | The MIT license, reference http://www.opensource.org/licenses/mit-license.php 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby ts=2 sw=2 sts=2 : 3 | 4 | VAGRANTFILE_API_VERSION = "2" 5 | 6 | Vagrant.require_version '>= 1.7.0' 7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 8 | 9 | config.vm.define 'front1' do |front| 10 | front.vm.box = 'chef/centos-7.0' 11 | front.vm.hostname = 'deathstar' 12 | front.vm.network :private_network, ip: '192.168.50.100' 13 | end 14 | 15 | # Fix for slow external network connections 16 | config.vm.provider :virtualbox do |vb| 17 | vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'on'] 18 | vb.customize ['modifyvm', :id, '--natdnsproxy1', 'on'] 19 | vb.memory = 1024 20 | vb.cpus = 2 21 | end 22 | 23 | config.vm.provision :ansible do |ansible| 24 | ansible.playbook = 'ansible/playbook.yml' 25 | ansible.inventory_path = 'ansible/development' 26 | end 27 | end 28 | 29 | -------------------------------------------------------------------------------- /ansible/development: -------------------------------------------------------------------------------- 1 | # vi: set ft=dosini ts=2 sw=2 sts=2 : 2 | 3 | front1 ansible_ssh_host=192.168.50.100 ansible_ssh_user=vagrant ansible_ssh_private_key_file=.vagrant/machines/front1/virtualbox/private_key 4 | 5 | [frontends] 6 | front1 7 | 8 | [development:children] 9 | frontends 10 | -------------------------------------------------------------------------------- /ansible/group_vars/all: -------------------------------------------------------------------------------- 1 | # vi: set ft=yaml ts=2 sw=2 sts=2 : 2 | 3 | # user - is current application owner 4 | home: "/home/{{ user }}" 5 | # name - is an application currently played, like frontend 6 | app: "{{ home }}/{{ name }}" 7 | releases: "{{ home }}/{{ name }}/releases" 8 | current: "{{ home }}/{{ name }}/current" 9 | builds: "{{ home }}/{{ name }}/builds" 10 | shared: "{{ home }}/{{ name }}/shared" 11 | 12 | ansible_python_interpreter: "/usr/bin/python" 13 | -------------------------------------------------------------------------------- /ansible/group_vars/development: -------------------------------------------------------------------------------- 1 | # vi: set ft=yaml ts=2 sw=2 sts=2 : 2 | 3 | hostname: deathstar.dev 4 | https: false 5 | 6 | db_user: root 7 | db_pass: S3cretpassword 8 | db_name: deathstar 9 | 10 | secret_salt: 679khgkkj776iyi7i667765ftfhfcvbxxzss 11 | -------------------------------------------------------------------------------- /ansible/group_vars/frontends: -------------------------------------------------------------------------------- 1 | # vi: set ft=yaml ts=2 sw=2 sts=2 : 2 | 3 | user: www 4 | name: frontend 5 | 6 | -------------------------------------------------------------------------------- /ansible/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # given our server OS is centos, install package repositories 3 | - hosts: all 4 | sudo: yes 5 | tasks: 6 | - name: ensure epel repo present 7 | yum: pkg=epel-release state=installed 8 | 9 | - name: ensure remi repo present 10 | yum: name=http://rpms.famillecollet.com/enterprise/remi-release-7.rpm state=installed 11 | 12 | # provision and deploy frontends 13 | - hosts: frontends 14 | roles: 15 | - { role: user, sudo: yes } 16 | - { role: mysql, sudo: yes } 17 | - { role: redis, sudo: yes } 18 | - { role: php-fpm, sudo: yes } 19 | - { role: nginx, sudo: yes } 20 | - { role: skel, sudo: yes, sudo_user: "{{ user }}" } 21 | - { role: frontend, sudo: yes, sudo_user: "{{ user }}" } 22 | -------------------------------------------------------------------------------- /ansible/production: -------------------------------------------------------------------------------- 1 | # vi: set ft=dosini ts=2 sw=2 sts=2 : 2 | 3 | front1 ansible_ssh_host=85.67.77.66 ansible_ssh_user=deployer ansible_ssh_pass=S3cretpassword 4 | 5 | [frontends] 6 | front1 7 | 8 | [production:children] 9 | frontends 10 | -------------------------------------------------------------------------------- /ansible/roles/frontend/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ensure shared directories 3 | file: dest={{ shared }}/{{ item }} state=directory 4 | with_items: 5 | - logs 6 | tags: [ frontend ] 7 | 8 | - name: limit stored releases and builds by removing oldest 9 | shell: "ls -1dt {{ item.dir }}/* | tail -n +{{ item.keep }} | xargs rm -rf" 10 | with_items: 11 | - { dir: "{{ builds }}", keep: 3 } 12 | - { dir: "{{ releases }}", keep: 5 } 13 | tags: [ frontend ] 14 | 15 | - name: new build name 16 | shell: date '+%Y%m%d%H%M%S' 17 | register: next_release 18 | tags: [ frontend ] 19 | 20 | - name: previous release 21 | shell: "[ -d '{{ current }}' ] && readlink {{ current }} || echo ''" 22 | register: prev_release 23 | tags: [ frontend ] 24 | 25 | - name: prepare a release directory 26 | file: path={{ builds }}/{{ next_release.stdout }} state=directory 27 | tags: [ frontend ] 28 | 29 | - name: extract release archive 30 | unarchive: src={{ name }}.tar.gz dest={{ builds }}/{{ next_release.stdout }} 31 | tags: [ frontend ] 32 | 33 | - name: symlink logs 34 | file: src={{ shared }}/logs dest={{ builds }}/{{ next_release.stdout }}/app/logs state=link 35 | tags: [ frontend ] 36 | 37 | - name: copy parameters.yml to build 38 | template: src=parameters.yml.j2 dest={{ builds }}/{{ next_release.stdout }}/app/config/parameters.yml 39 | tags: [ frontend ] 40 | 41 | - name: ensure temp directories are available 42 | file: path={{ builds }}/{{ next_release.stdout }}/app/{{ item }} state=directory 43 | with_items: 44 | - cache 45 | tags: [ frontend ] 46 | 47 | - name: warmup cache 48 | shell: chdir={{ builds }}/{{ next_release.stdout }} php app/console cache:warmup --env=prod 49 | tags: [ frontend ] 50 | 51 | - name: run migrations 52 | shell: chdir={{ builds }}/{{ next_release.stdout }} php app/console doctrine:migrations:migrate --no-interaction --env=prod 53 | tags: [ frontend ] 54 | 55 | - name: run fixtures 56 | shell: chdir={{ builds }}/{{ next_release.stdout }} php app/console app:fixtures --env=prod 57 | tags: [ frontend ] 58 | 59 | - name: move successful build to releases 60 | shell: mv {{ builds }}/{{ next_release.stdout }} {{ releases }}/{{ next_release.stdout }} 61 | tags: [ frontend ] 62 | 63 | - name: link successful release 64 | file: src={{ releases }}/{{ next_release.stdout }} dest={{ current }} state=link force=yes 65 | tags: [ frontend ] 66 | 67 | - name: clear previous release cache 68 | shell: chdir={{ prev_release.stdout.strip() }} php app/console app:cache:clear --env=prod 69 | when: prev_release.stdout.strip() != "" 70 | ignore_errors: true 71 | tags: [ frontend ] 72 | 73 | -------------------------------------------------------------------------------- /ansible/roles/frontend/templates/parameters.yml.j2: -------------------------------------------------------------------------------- 1 | # vi: set ft=yaml ts=2 sw=2 sts=2 : 2 | 3 | parameters: 4 | database_driver: pdo_mysql 5 | database_host: 127.0.0.1 6 | database_port: null 7 | database_name: "{{ db_name }}" 8 | database_user: "{{ db_user }}" 9 | database_password: "{{ db_pass }}" 10 | 11 | locale: en 12 | secret: "{{ secret_salt }}" 13 | cache_namespace: "{{ next_release.stdout }}" 14 | 15 | mailer_transport: smtp 16 | mailer_host: mailtrap.io 17 | mailer_user: 33434104a686874df 18 | mailer_password: 70905ac4f06417 19 | mailer_sender: { noreply@deathstart.dev: "Death Star" } 20 | mailer_auth_mode: cram-md5 21 | mailer_port: 2525 22 | 23 | redis_host: 127.0.0.1 24 | redis_port: 6379 25 | -------------------------------------------------------------------------------- /ansible/roles/mysql/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install MySQL 3 | yum: pkg={{ item }} state=installed 4 | with_items: 5 | - mariadb-server 6 | - mysql 7 | - MySQL-python 8 | tags: [ mysql ] 9 | 10 | - name: ensure mysql is enabled on boot and started 11 | service: name=mariadb state=started enabled=yes 12 | tags: [ mysql ] 13 | 14 | - name: update mysql root passwd 15 | mysql_user: name=root host={{ item }} password={{ db_pass }} 16 | with_items: 17 | - 127.0.0.1 18 | - ::1 19 | - localhost 20 | tags: [ mysql ] 21 | 22 | - name: copy user my.cnf file with root passwd credentials 23 | template: src=my.cnf.j2 dest=/root/.my.cnf owner=root group=root mode=0600 24 | tags: [ mysql ] 25 | 26 | - name: root from any host 27 | mysql_user: name=root password={{ db_pass }} host="%" priv=*.*:ALL,GRANT state=present 28 | tags: [ mysql ] 29 | 30 | - name: delete anonymous mysql user 31 | mysql_user: name="" state=absent 32 | tags: [ mysql ] 33 | 34 | - name: remove mysql test database 35 | mysql_db: name=test state=absent 36 | tags: [ mysql ] 37 | 38 | - name: create default database 39 | mysql_db: name={{ db_name }} state=present 40 | tags: [ mysql ] 41 | -------------------------------------------------------------------------------- /ansible/roles/mysql/templates/my.cnf.j2: -------------------------------------------------------------------------------- 1 | [client] 2 | user=root 3 | password={{ db_pass }} 4 | -------------------------------------------------------------------------------- /ansible/roles/nginx/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: nginx reload 3 | service: name=nginx state=reloaded 4 | 5 | - name: nginx restart 6 | service: name=nginx state=restarted 7 | -------------------------------------------------------------------------------- /ansible/roles/nginx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install nginx 3 | yum: name=http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm state=installed 4 | tags: [ nginx ] 5 | 6 | - name: install nginx 7 | yum: name=nginx state=latest 8 | tags: [ nginx ] 9 | 10 | - name: ensure nginx is running and enabled on boot 11 | service: name=nginx.service enabled=yes state=started 12 | tags: [ nginx ] 13 | 14 | - name: ensure nginx has sites-available/enabled directories 15 | file: dest=/etc/nginx/{{ item }} state=directory owner=root mode=0755 16 | with_items: 17 | - sites-available 18 | - sites-enabled 19 | tags: [ nginx ] 20 | 21 | - name: ensure main nginx configuration file is present 22 | template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf owner=root mode=0644 backup=yes 23 | notify: 24 | - nginx restart 25 | tags: [ nginx ] 26 | 27 | - name: ensure that nginx log dir is writable by user 28 | file: path=/var/log/nginx state=directory owner={{ user }} group={{ user }} 29 | tags: [ nginx ] 30 | 31 | - name: install nginx site 32 | template: src="sites/{{ name }}.conf.j2" 33 | dest=/etc/nginx/sites-available/{{ name }}.conf 34 | owner=root mode=0644 backup=yes 35 | tags: [ nginx ] 36 | 37 | - name: ensure nginx vhost enabled 38 | file: src=/etc/nginx/sites-available/{{ name }}.conf 39 | dest=/etc/nginx/sites-enabled/{{ name }}.conf 40 | owner=root state=link 41 | notify: 42 | - nginx restart 43 | tags: [ nginx ] 44 | -------------------------------------------------------------------------------- /ansible/roles/nginx/templates/nginx.conf.j2: -------------------------------------------------------------------------------- 1 | user {{ user }}; 2 | worker_processes 2; # number of cpus * 2 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | include /etc/nginx/mime.types; 13 | default_type application/octet-stream; 14 | 15 | # max request header size (file uploads needs more space) 16 | # seems it is ignored on old nginx versions (putting to server as well) 17 | client_max_body_size 32M; 18 | 19 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 20 | '$status $body_bytes_sent "$http_referer" ' 21 | '"$http_user_agent" "$http_x_forwarded_for"'; 22 | 23 | log_format time_combined '$remote_addr - $remote_user [$time_local] "$request" ' 24 | '$status $body_bytes_sent "$http_referer" ' 25 | '"$http_user_agent" "$http_x_forwarded_for" ' 26 | '$request_time $upstream_response_time $pipe'; 27 | 28 | access_log /var/log/nginx/access.log main; 29 | 30 | sendfile on; 31 | 32 | keepalive_timeout 65; 33 | 34 | gzip on; 35 | gzip_disable "MSIE [1-6]\.(?!.*SV1)"; 36 | 37 | ## 38 | # Virtual Hosts 39 | ## 40 | include /etc/nginx/sites-enabled/*.conf; 41 | 42 | include /etc/nginx/conf.d/*.conf; 43 | } 44 | -------------------------------------------------------------------------------- /ansible/roles/nginx/templates/sites/frontend.conf.j2: -------------------------------------------------------------------------------- 1 | server { 2 | listen {% if https %}443{% else %}80{% endif %}; 3 | 4 | server_name {{ hostname }}; 5 | root {{ current }}/web; 6 | 7 | # max header size for uploads need more space 8 | client_max_body_size 32M; 9 | 10 | add_header X-Frame-Options SAMEORIGIN; 11 | 12 | location / { 13 | try_files $uri @handler; 14 | expires 1h; # cache static files 15 | } 16 | 17 | location @handler { 18 | rewrite ^(.*)$ /app.php/$1 last; 19 | } 20 | 21 | location ~ ^/app\.php(/|$) { 22 | fastcgi_pass 127.0.0.1:9000; 23 | fastcgi_split_path_info ^(.+\.php)(/.*)$; 24 | include fastcgi_params; 25 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 26 | fastcgi_param DOCUMENT_ROOT $realpath_root; 27 | fastcgi_param HTTPS {% if https %}on{% else %}off{% endif %}; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ansible/roles/php-fpm/handlers/main.yml: -------------------------------------------------------------------------------- 1 | - name: php-fpm restart 2 | service: name=php-fpm state=restarted 3 | -------------------------------------------------------------------------------- /ansible/roles/php-fpm/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ensure remi repo is enabled 3 | ini_file: dest=/etc/yum.repos.d/remi.repo section=remi option=enabled value=1 4 | tags: [ php ] 5 | 6 | - name: ensure php56 remi repo is enabled 7 | ini_file: dest=/etc/yum.repos.d/remi.repo section=remi-php56 option=enabled value=1 8 | tags: [ php ] 9 | 10 | - name: ensure php modules are installed 11 | action: yum name={{ item }} state=latest 12 | with_items: 13 | - php-fpm 14 | - php-cli 15 | - php-pdo 16 | - php-mysql 17 | - php-redis 18 | - php-mcrypt 19 | - php-bcmath 20 | - php-mbstring 21 | - php-intl 22 | - php-gd 23 | - php-opcache 24 | notify: 25 | - php-fpm restart 26 | tags: [ php ] 27 | 28 | - name: unsure php-fpm is started and enabled on boot 29 | service: name=php-fpm enabled=yes state=started 30 | tags: [ php ] 31 | 32 | - name: ensure php configuration files are present 33 | template: src={{ item.source }} dest={{ item.destination }} owner=root mode=0644 backup=yes 34 | with_items: 35 | - { source: 'php.ini.j2', destination: '/etc/php.ini' } 36 | - { source: 'php-fpm.conf.j2', destination: '/etc/php-fpm.d/www.conf' } 37 | notify: 38 | - php-fpm restart 39 | tags: [ php ] 40 | 41 | - name: ensure php session and wsdl directories are writable 42 | file: dest=/var/lib/php/{{ item }} state=directory owner={{ user }} mode=0770 43 | with_items: 44 | - session 45 | - wsdlcache 46 | tags: [ php ] 47 | 48 | - name: ensure that php-fpm log dir is writable by wwwdata 49 | file: path=/var/log/php-fpm state=directory owner={{ user }} group={{ user }} 50 | tags: [ php ] 51 | -------------------------------------------------------------------------------- /ansible/roles/php-fpm/templates/php-fpm.conf.j2: -------------------------------------------------------------------------------- 1 | ; vi: set ft=dosini ts=2 sw=2 sts=2 : 2 | 3 | [www] 4 | listen = 127.0.0.1:9000 5 | listen.allowed_clients = 127.0.0.1 6 | 7 | user = {{ user }} 8 | group = {{ user }} 9 | 10 | pm = dynamic 11 | pm.max_children = 50 12 | pm.start_servers = 2 13 | pm.min_spare_servers = 2 14 | pm.max_spare_servers = 2 15 | 16 | php_admin_value[error_log] = /var/log/php-fpm/www-error.log 17 | php_admin_flag[log_errors] = on 18 | 19 | ; Set session path to a directory owned by process user 20 | ;php_value[session.save_handler] = redis 21 | php_value[session.save_path] = /var/lib/php/session 22 | php_value[soap.wsdl_cache_dir] = /var/lib/php/wsdlcache 23 | -------------------------------------------------------------------------------- /ansible/roles/php-fpm/templates/php.ini.j2: -------------------------------------------------------------------------------- 1 | ; vi: set ft=dosini ts=2 sw=2 sts=2 : 2 | 3 | [PHP] 4 | 5 | ; Only override settings which may be different from defaults 6 | 7 | max_execution_time = 30 8 | max_input_time = 60 9 | memory_limit = 256M 10 | 11 | error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT 12 | display_errors = Off 13 | display_startup_errors = Off 14 | log_errors = On 15 | 16 | variables_order = "GPCS" 17 | request_order = "GP" 18 | 19 | post_max_size = 32M 20 | upload_max_filesize = 32M 21 | max_file_uploads = 10 22 | 23 | default_socket_timeout = 60 24 | 25 | ; COOKIES 26 | session.cookie_secure = On 27 | session.cookie_httponly = On 28 | 29 | ;;;;;;;;;;;;;;;;; 30 | ; Miscellaneous ; 31 | ;;;;;;;;;;;;;;;;; 32 | 33 | ; Decides whether PHP may expose the fact that it is installed on the server 34 | ; (e.g. by adding its signature to the Web server header). It is no security 35 | ; threat in any way, but it makes it possible to determine whether you use PHP 36 | ; on your server or not. 37 | ; http://php.net/expose-php 38 | expose_php = Off 39 | 40 | [Date] 41 | date.timezone=UTC 42 | 43 | [opcache] 44 | opcache.enable=1 45 | opcache.memory_consumption=128 46 | opcache.interned_strings_buffer=8 47 | opcache.max_accelerated_files=4000 48 | opcache.revalidate_freq=0 49 | opcache.fast_shutdown=1 50 | opcache.enable_cli=1 51 | -------------------------------------------------------------------------------- /ansible/roles/redis/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install redis 3 | yum: name=redis state=latest 4 | tags: [ redis ] 5 | 6 | - name: ensure redis is started 7 | service: name=redis enabled=yes state=started 8 | tags: [ redis ] 9 | -------------------------------------------------------------------------------- /ansible/roles/skel/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ensure app directories 3 | file: dest={{ item }} state=directory owner={{ user }} group={{ user }} mode=0755 4 | with_items: 5 | - "{{ app }}" 6 | - "{{ releases }}" 7 | - "{{ current }}" 8 | - "{{ shared }}" 9 | tags: [ skel ] 10 | -------------------------------------------------------------------------------- /ansible/roles/supervisor/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: install supervisor 3 | yum: name=supervisor state=latest 4 | tags: [ supervisor ] 5 | 6 | - name: ensure supervisord is started 7 | service: name=supervisord enabled=yes state=started 8 | tags: [ supervisor ] 9 | 10 | -------------------------------------------------------------------------------- /ansible/roles/user/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: ensure user is present 3 | user: name={{ user }} shell=/bin/bash comment="{{ app }} user" state=present 4 | tags: [ user ] 5 | -------------------------------------------------------------------------------- /app/AppCache.php: -------------------------------------------------------------------------------- 1 | getEnvironment(), ['dev', 'test'])) { 29 | $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle(); 30 | $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); 31 | $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); 32 | $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); 33 | } 34 | 35 | return $bundles; 36 | } 37 | 38 | public function registerContainerConfiguration(LoaderInterface $loader) 39 | { 40 | $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Resources/bin/reload: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # takes argument as environment 4 | 5 | DIR="$( cd "$( dirname "$0" )" && pwd )" 6 | 7 | ROOT="$DIR/.." 8 | CLI="$ROOT/app/console" 9 | if [ ! -f "$CLI" ]; then 10 | echo "Can be run only from sf-project-root/bin directory. Run composer install first." 11 | exit 1 12 | fi 13 | 14 | ENV=${1:-"dev"} 15 | 16 | # Bootstrap cache 17 | php $ROOT/vendor/sensio/distribution-bundle/Sensio/Bundle/DistributionBundle/Resources/bin/build_bootstrap.php 18 | 19 | # Symfony cache 20 | rm -rf $ROOT/app/cache/* # force cache cleanup - yeah yeah, sometimes it may not work as expected 21 | php $CLI app:cache:clear --env=$ENV --ansi -vvv 22 | php $CLI cache:clear --env=$ENV --ansi 23 | php $CLI cache:warmup --env=$ENV --ansi 24 | 25 | # Database 26 | php $CLI doctrine:database:drop --force --quiet --env=$ENV --ansi 27 | php $CLI doctrine:database:create --env=$ENV --ansi 28 | php $CLI doctrine:migrations:migrate --no-interaction --env=$ENV --ansi 29 | echo "" 30 | 31 | # Fixtures 32 | php $CLI app:fixtures --env=$ENV --ansi 33 | echo "" 34 | -------------------------------------------------------------------------------- /app/Resources/docker/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "[info] copy default parameters.yml" 4 | cp -n /var/www/app/config/parameters.yml.dist /var/www/app/config/parameters.yml 5 | 6 | sed -i "s/database_host:.*$/database_host: mysql/" /var/www/app/config/parameters.yml 7 | sed -i "s/database_user:.*$/database_user: $MYSQL_ENV_MYSQL_USER/" /var/www/app/config/parameters.yml 8 | sed -i "s/database_password:.*$/database_password: $MYSQL_ENV_MYSQL_PASSWORD/" /var/www/app/config/parameters.yml 9 | sed -i "s/database_port:.*$/database_port: 3306/" /var/www/app/config/parameters.yml 10 | sed -i "s/redis_host:.*$/redis_host: redis/" /var/www/app/config/parameters.yml 11 | 12 | echo "[info] Running composer" 13 | composer install --optimize-autoloader --working-dir=/var/www 14 | 15 | echo "[info] Changing permissions for storage/" 16 | chmod -R 777 /var/www/app/cache /var/www/app/logs /var/www/app/session 17 | 18 | echo "[info] Waiting for mysql" 19 | sleep 10 20 | 21 | echo "[info] Migrating database" 22 | php /var/www/app/console cache:clear 23 | php /var/www/app/console doctrine:schema:update --force 24 | 25 | chown -R www:www /var/www 26 | -------------------------------------------------------------------------------- /app/Resources/gif/README.md: -------------------------------------------------------------------------------- 1 | # How to create starwars gif 2 | 3 | You will need: `recordmydesktop ffmpeg` 4 | 5 | - run `recordmydesktop` in shell, keep it running.. 6 | - open **star.html** in your browser: `chromium star.html` make it fullscreen and refresh to run through. 7 | - cancel `recordmydesktop` CTRL+C 8 | - convert ogv to gif: `ffmpeg -ss 00:00:05.20 -i out.ogv -r 65 -s 640x480 starwars.gif` 9 | 10 | **NOTE:** the `-ss` option sets the start time offset. 11 | 12 | You may download latest ffmpeg from here: 13 | 14 | wget http://ffmpeg.gusari.org/static/64bit/ffmpeg.static.64bit.latest.tar.gz 15 | tar -xzf ffmpeg.static.64bit.latest.tar.gz 16 | -------------------------------------------------------------------------------- /app/Resources/jwt/README.md: -------------------------------------------------------------------------------- 1 | # JWT keys 2 | 3 | To regenerate: 4 | 5 | openssl genrsa -out private.pem -aes256 4096 6 | openssl rsa -pubout -in private.pem -out public.pem 7 | -------------------------------------------------------------------------------- /app/Resources/jwt/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-256-CBC,C053F8A95528470B0D4A9C4E778BD01B 4 | 5 | l6sHgBBWP4c/lT2N8OD8fUgdLT6LbWW2Bb3MpYwiRXbu2T1jyUf9NjewFtzunCsR 6 | 3r6AwSuJB4Z0KWYPHZs8pNHn1M3eqhKuStJclfJDJ4G5t8EqquoZaWHsTGO/FZU/ 7 | JMt3C0MgmpZckkH1jiHaiLPC0O/CDx7eAG7OjTD6feZEJJGj+w23RomB6816J+AY 8 | UQNCn8QZfAr7Z+UY2bnjDx/Y7pO8eUknf8PTshbkrNw7b+/agwQ/O9t4Qibo4vJ5 9 | QDYhJsikDDUtQF+a1w5QNNtgbtTZMoQnEb+ZkfLFKJJVLctELYjSQ4tqKmmQcHtL 10 | Dg+chaB0BxABinh6f/yx1rRQCyWPKo6IKu4aExdHP1qNk7pPcvBp/dbQk1w8J6Le 11 | GcWqX2eNBLyp363Bu8RU5Cnqxd+VZ+GRnia4RO+Ow9NkuHDs0YsKZJ/uz59lxMTh 12 | Msws/wwb+ItuKHjvUNB5cB+li7apOcOi7VpzrP7BwgvQ6ZZZRBA0BdyFIEUnMwdk 13 | EjyBuVUNUA+gwg565Y+odZm5yY920O1UJW2RPBX0ZXjaS/hSxsI1GzlaxWg8wzOe 14 | lZ3K2tmWg2KRq4kBtIUZmUMJu0rbf7NtYK5PFBg01kF2dSV+LE/EfUXIkIY5flo/ 15 | IamIUUHBnj2S2CrrR0VK7gZ13/z5p0AXeO3L7aCJsBmMpSgdsfj30sUpSj7JASZ+ 16 | LUMS6iQUCZuL8DTIZf5W1Va1RfJklcw0hkBd/YDGUo6uk6hCQFNcuZnsEmn6lkh7 17 | cgsrOnCzEtFMG/t1T7792Wb+DdjHq6Fm0oUUK66+hcHrAVvtdxe85Sagxk0SLILy 18 | Zo7LjulET6DrmrhG9nHKcfMREVD2ZrHcRhXJlQKUW3TQ+a/Ab0WamuaEa8+Vbjgi 19 | CHktxEnFJgcmeZK31u/ryTatXlepByIlEH/YnAqaPZWfBlQcMyAriHmw4WHc5n3B 20 | +FTBaODhaB/LOg6zYmPlXYZ4+BOaI2puDIqWUV62Z6HYHZ5AN0xCMSJegTKqbhl9 21 | NLHx5xmc4Hdz5awM8tDDKHifw2v1YBNusjBPORdMpNoE4W+TQjML4wFqm/JwXwdx 22 | Qdy6M/CBdnHhN3JFpFVHaXz2NsvfBvyO37NRutEZfow3WRf8FYyYaq6Z3Se/TfII 23 | XjctHTnWgUEiqPEybxTvESId+RR9FR3fVsS7vnasylEunYtC2aPn2R1iBjR2nYyj 24 | uKnKsUWjMfpUKLZ+DPSIjR2u7X3BAnlPHd7DdTQv7DSVB76uoXxg8FN0KhLg9So3 25 | jo3o/j1vhC79ehpp6S6sRRh6swXQo+r3229sgixVknisiwehGkSUABZeWJkS2Npe 26 | pffcgnzqzvU4ug9BYeP2lQk8S8fNJL6qAtwN6589qE+Jgdw//uO0W0dd9MA5zyJ3 27 | TTidV0aYC8gGN+fdEQBRl7tlL0Eg1ueto/TKLXL5Z2+z4Gtb2utqpndVyU1xaqeU 28 | xJWtTpotZUU6cwL+VR4J3UOa/9p5SCCcZIk5R3Fam5pPErzJXXOoCaigB1MMxddk 29 | WjYJHfZvSWW+xbhXsPDuKc+cI7o1Uhc8IULaYf9OmKjDmzp1AL4ZC8xNxigdvs25 30 | OZLZJv2A71wjlqkyKO9DU7an843O3rL3Uj0Gl/cX8NlOXVXQ/zZ515gizndwNrMC 31 | Z9yZn/2QeHeeJ2Eetj+mVQ8Gxjkn+WpH54wCiCTJR4vgQb6YoPskFYqXISgcQ3F1 32 | jlmTkusRDkyTHjklJ3FgWfgmvMuv1yfMrHUkAgurNvg+0pfy6LMvGwA2A7dz3M4N 33 | e+eh9crMUXuT8VTUgbj2kzwUhhWlRDSVFe7YYGwCdC/+YLGYEPYt11w6WQKu0HW5 34 | Ju4Lak0H5j6qXi4VY+C1NBwffsmt/pI0cM1hO6AuPuYOsfW6Wq2AB4VDZbpBTdL1 35 | Uvenc/1hHbRrIPnikjyNFKkIR32Z3L9L1FLk9ClsUDuPcfOTlk4HQ7DcSaXQ2jBA 36 | ndCCh0zVWv/Wm5Iu/HLD9f9q69Mj8SIQxgAfxSRiikLUFAUguetyklcHkXjDqbcT 37 | I2jGhcGwj8baO4o2SQUk0nB4AhydxfMGPzcjpGubRokS/b/4L1QXUC5pPUUmrTtJ 38 | BbDLIKh0undyf7FL0io2BHQd6BA7J/lU2eGYNf6e2Wj03/Qi3j6vBCSQtbeY1A/3 39 | aMALQtJWdR9txKUJaY9qdgVNPvJy2TsvX6coXuA9f45RkEhGqGjqKxp8+sRBnKW0 40 | ipdCSu1qklP6E7czyRZCBnwd5LNsHIRgQI8RAO7yQRpLHOS5OAZf5caMFJXhxgrP 41 | FykeA4sntvVoeYGSHqLw8w3Fb9l2bof2pGl1Js0Y/VhZgtFEAyMQIYIFpf+D/Bp/ 42 | tjwfNwuu8BAbZeWggLGg5s9+c1f/g6LtGszDVPMg9prgTd1LWC836FgrEpmvD/GA 43 | 5pmuptMWRrIxek9OLJeU06r7QhEEZ//zSKaMtW4w1WI9yEcbOsdyfo0SRoW42SeM 44 | bGsG6kSCj+lFLwfFMuoMimNQ5w+N/munbnyP7XmE5Ij8dccup8djINwkrvtR/42z 45 | r0bbcjOK7Sn8NlEFw+4TqHHoYHONo1hgQOPILq6Knh/T9fxokGcd8l9S+DLoFRNb 46 | qXkjxNr/u4+iW1OwzA4BT2UwhlWzp0bJGfuO4oYhnKDDS9DgZdW5qNQpFD4EVJFf 47 | UQnRBny8ExX+cgXn6BLPUwrP7U/rlp410hIkHNfnexzoYC7NDH2Wn7iNUep/zpgQ 48 | w1TslkpsFuKTs8dS6aVcRhwEsN5w80z1LOU/skviX3MYQOw1m/uIU5dPDnDzPJ/r 49 | KSn2tDbLOD3qCIom5C6X2O9QrokF1jCKlm2ck+uxanjXx8s4GYjI0+7mgi+6OJiK 50 | w6SIfD3v+EOjkigUEMG4pyo2ByK4BNs6Nx4bnpRKgsLlaKYvXRvi+w6FDshgn2Oc 51 | wSorluiFMofyg+GtAbuutD9/0FF3Ln14b4efGltRhdI+RqTOP6Xbe9rdDpz6qdGZ 52 | DZf9uZsdAvwTbugX+s3ieznXZdvfWoPLqqAyv12HCuG8qTxjkEhlzlB1OA9K1Zkf 53 | cHYMIKx2LU85iKt6cjIA+VTuPY38QZE3DUEW82vpkOPv3vLuD9K7YCK2eQQ4Q38Z 54 | -----END RSA PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /app/Resources/jwt/public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyWl2FFvRGBJ/Kbz8mfLT 3 | AQ9NWzKpBQGGfoYuPhIaEj9V/sf7bjwQ1SXprLvoG8PItds81nbOoFStPOMwEvY2 4 | x8RliZEGWwq+HCsveFgcYOjbq/rT3gkOn82FZ2lLpfA1QaPWH97CH4dP1aV2ZsiA 5 | 8lHGXvRTUhQTdUZdHgk7VFqft+xkcDztHYr1LG/T1UfnDZgVY/pvlrslgV273PSw 6 | FT1nEPUKQUWkALvkHZfIffaqdDUEKNTavKeYFcLyvCbTttkgvgdEa6a33Eef5Y6p 7 | UeXxB0ZJw8JJx3XyHqlkIxAnn3YK+3aX27ux4G/1gaFrDi5atY/A+4bEs8yt+gzp 8 | eypWAAcoZSUiBijWpHdCVwX0e6niKxyld/NZ0Ub2u0kjLkGxtjZJXC+wVJ4os+Sy 9 | 2klLAyawdJMjMDvBdxH1iE9+NhrpzcgYQkt1kg8GBRUDDRUxleKcuJmscjlY7qHX 10 | lEOG94cErd2Mt34x4i6oGPLDQpriVi3nCqMo28UAbLilfcjmYKsTc9uNuNdsDp8F 11 | mPgTZ1NcMKU7ocs7TOFwEPARfqryLOPsf9yfdvGQH7CzLnztDDCybjTlqi9lwexF 12 | dmG7FKnZPf9vxCnH1+9x4j6bBmKMujypFSxuRbhbMysu9Q4HhFR0OFyN7ChgN9xy 13 | bz1VJUXrL/GaK/bXkPASojMCAwEAAQ== 14 | -----END PUBLIC KEY----- 15 | -------------------------------------------------------------------------------- /app/Resources/views/admin.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% if title is defined %}{{ title }}{% else %}Symfony2 application skeleton{% endif %} | Deathstar 8 | 9 | 10 | {% block css %}{% endblock %} 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | {% block body %}{% endblock %} 24 | 25 | 26 | 27 | {% block js %}{% endblock %} 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/Resources/views/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% if title is defined %}{{ title }}{% else %}Symfony Force Edition{% endif %} 8 | 9 | 10 | {% block css %}{% endblock %} 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | {% block body %}{% endblock %} 25 | 26 | 27 | 28 | {% block js %}{% endblock %} 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/autoload.php: -------------------------------------------------------------------------------- 1 | getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev'); 19 | $debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod'; 20 | 21 | if ($debug) { 22 | Debug::enable(); 23 | } 24 | 25 | $kernel = new AppKernel($env, $debug); 26 | $application = new Application($kernel); 27 | $application->run($input); 28 | -------------------------------------------------------------------------------- /app/logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/app/logs/.gitkeep -------------------------------------------------------------------------------- /app/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | ../src/*/*Bundle/Tests 13 | ../src/*/Bundle/*Bundle/Tests 14 | ../src/*Bundle/Tests 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | ../src 27 | 28 | ../src/*Bundle/Resources 29 | ../src/*Bundle/Tests 30 | ../src/*/*Bundle/Resources 31 | ../src/*/*Bundle/Tests 32 | ../src/*/Bundle/*Bundle/Resources 33 | ../src/*/Bundle/*Bundle/Tests 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /assets/img/colors/txture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/colors/txture.png -------------------------------------------------------------------------------- /assets/img/colors/txture_lighter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/colors/txture_lighter.png -------------------------------------------------------------------------------- /assets/img/colors/txture_trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/colors/txture_trans.png -------------------------------------------------------------------------------- /assets/img/homepage/carousel/banner-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/homepage/carousel/banner-1.jpg -------------------------------------------------------------------------------- /assets/img/homepage/carousel/banner-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/homepage/carousel/banner-2.jpg -------------------------------------------------------------------------------- /assets/img/homepage/carousel/banner-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/homepage/carousel/banner-3.jpg -------------------------------------------------------------------------------- /assets/img/homepage/testimonials/chewbacca.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/homepage/testimonials/chewbacca.jpg -------------------------------------------------------------------------------- /assets/img/homepage/testimonials/grievous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/homepage/testimonials/grievous.png -------------------------------------------------------------------------------- /assets/img/homepage/testimonials/vader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/homepage/testimonials/vader.jpg -------------------------------------------------------------------------------- /assets/img/homepage/testimonials/yoda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/homepage/testimonials/yoda.jpg -------------------------------------------------------------------------------- /assets/img/icons/darth-vader-icon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/icons/darth-vader-icon.jpeg -------------------------------------------------------------------------------- /assets/img/icons/deathstar-ico-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/icons/deathstar-ico-32.png -------------------------------------------------------------------------------- /assets/img/icons/luke-skywalker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/icons/luke-skywalker-icon.png -------------------------------------------------------------------------------- /assets/img/icons/yoda-icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/img/icons/yoda-icon.jpg -------------------------------------------------------------------------------- /assets/js/admin/scripts.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $('[data-toggle="tooltip"]').tooltip(); 3 | 4 | $('a.js-confirm').click(function(e) { 5 | e.preventDefault(); 6 | 7 | if (confirm('Are you sure?')) { 8 | window.location.href = this.href; 9 | } 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /assets/js/app/scripts.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/assets/js/app/scripts.js -------------------------------------------------------------------------------- /assets/less/admin.less: -------------------------------------------------------------------------------- 1 | @import "../vendor/bootstrap/less/bootstrap"; 2 | @import "../vendor/fontawesome/less/font-awesome"; 3 | @import "admin/layout"; 4 | @import "admin/pagination"; 5 | @import "admin/tables"; 6 | @import "admin/bootswatch"; 7 | @import "admin/variables"; 8 | @import "admin/panels"; 9 | @import "admin/navs"; 10 | 11 | @fa-font-path: "/build/fonts"; 12 | -------------------------------------------------------------------------------- /assets/less/admin/layout.less: -------------------------------------------------------------------------------- 1 | .container.container-content { 2 | margin-top: @navbar-height; 3 | } 4 | .container-content { 5 | > h1:first-child { 6 | margin-bottom: 25px; 7 | margin-top: 30px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/less/admin/navs.less: -------------------------------------------------------------------------------- 1 | @navbar-inverse-link-color: #fff; 2 | @navbar-inverse-link-active-bg: transparent; 3 | @navbar-link-active-border-width: 3px; 4 | 5 | .navbar { 6 | box-shadow: 0 0 11px #444; 7 | border: 0; 8 | 9 | &.navbar-inverse { 10 | #gradient > .horizontal(@brand-success, #B5AC49, 10%, 90%); 11 | 12 | .nav li { 13 | &.active a { 14 | 15 | border-bottom: @navbar-link-active-border-width solid rgba(255, 255, 255, 0.4); 16 | padding-bottom: @navbar-padding-vertical - @navbar-link-active-border-width; 17 | } 18 | a:hover { 19 | transition: background .3s; 20 | background: rgba(255, 255, 255, 0.13); 21 | } 22 | } 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /assets/less/admin/pagination.less: -------------------------------------------------------------------------------- 1 | .panel-footer { 2 | .pagination { 3 | float: right; 4 | margin: 0; 5 | } 6 | .pagination-summary { 7 | // btn height + padding + border 8 | line-height: @line-height-computed + @padding-base-vertical*2 + 2; 9 | margin: 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/less/admin/panels.less: -------------------------------------------------------------------------------- 1 | 2 | 3 | .panel { 4 | box-shadow: 1px 1px 3px #aaa; 5 | border: 0; 6 | } 7 | 8 | 9 | .panel { 10 | > .panel-heading { 11 | color: #fff; 12 | min-height: @padding-base-vertical*4+@line-height-computed + 2; 13 | .panel-title { 14 | line-height: 22px; 15 | } 16 | 17 | .toolbar { 18 | float: right; 19 | margin-top: -3px; 20 | .btn { 21 | .btn-sm(); 22 | } 23 | .btn-default { 24 | background: rgba(255, 255, 255, 0.45); 25 | color: #fff; 26 | transition: background .5s; 27 | &:hover { 28 | background: rgba(255, 255, 255, 0.65); 29 | } 30 | } 31 | } 32 | 33 | } 34 | 35 | &-primary > .panel-heading { 36 | border-bottom: 4px solid lighten(@brand-primary, 10%); 37 | #gradient > .horizontal(@brand-primary, @brand-primary-light, 10%, 90%); 38 | } 39 | &-success > .panel-heading { 40 | border-bottom: 4px solid lighten(@brand-success-light, 15%); 41 | #gradient > .horizontal(@brand-success, @brand-success-light, 10%, 90%); 42 | } 43 | &-danger > .panel-heading { 44 | border-bottom: 4px solid lighten(@brand-danger-light, 15%); 45 | #gradient > .horizontal(@brand-danger, @brand-danger-light, 10%, 90%); 46 | } 47 | &-info > .panel-heading { 48 | border-bottom: 4px solid lighten(@brand-info-light, 15%); 49 | #gradient > .horizontal(@brand-info, @brand-info-light, 10%, 90%); 50 | } 51 | &-warning > .panel-heading { 52 | border-bottom: 4px solid lighten(@brand-warning-light, 15%); 53 | #gradient > .horizontal(@brand-warning, @brand-warning-light, 10%, 90%); 54 | } 55 | &-default > .panel-heading { 56 | border-bottom: 4px solid #ccc; 57 | #gradient > .horizontal(#aaa, #ddd, 10%, 70%); 58 | } 59 | 60 | > .table { 61 | thead th { 62 | 63 | background: #f7f7f7; 64 | a { 65 | text-decoration: none; 66 | color: @gray-light; 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /assets/less/admin/tables.less: -------------------------------------------------------------------------------- 1 | .table { 2 | tr.filters { 3 | .btn-group-justified { 4 | 5 | } 6 | } 7 | td.col-min { 8 | width: 1%; 9 | white-space: nowrap; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/less/admin/variables.less: -------------------------------------------------------------------------------- 1 | @body-bg: #eee; 2 | @font-family-base: 'Open Sans', sans-serif; 3 | 4 | @brand-primary: #3a7bd5; 5 | @brand-primary-light: #00B1D7; 6 | @brand-success: #3CA55C; 7 | @brand-info: #56B4D3; 8 | @brand-info-light: #348F50; 9 | @brand-warning: #F09819; 10 | @brand-warning-light: #EDDE5D; 11 | @brand-danger: #f83600; 12 | @brand-danger-light: #fe8c00; 13 | @brand-success: #3CA55C; 14 | @brand-success-light: #B5AC49; 15 | 16 | @panel-border-radius: @border-radius-small; 17 | 18 | @alert-primary-bg: @brand-primary; 19 | @alert-primary-border: @brand-primary-light; 20 | @alert-primary-text: #fff; 21 | 22 | @alert-success-bg: @brand-success; 23 | @alert-success-border: @brand-success-light; 24 | @alert-success-text: #fff; 25 | 26 | @alert-warning-bg: @brand-warning; 27 | @alert-warning-border: @brand-warning-light; 28 | @alert-warning-text: #fff; 29 | 30 | @alert-info-bg: @brand-info; 31 | @alert-info-border: @brand-info-light; 32 | @alert-info-text: #fff; 33 | 34 | @alert-danger-bg: @brand-danger; 35 | @alert-danger-border: @brand-danger-light; 36 | @alert-danger-text: #fff; 37 | 38 | 39 | @panel-default-border: @table-border-color; 40 | -------------------------------------------------------------------------------- /assets/less/app.less: -------------------------------------------------------------------------------- 1 | @import "../vendor/bootstrap/less/bootstrap"; 2 | @import "../vendor/fontawesome/less/font-awesome"; 3 | @import "app/colors"; 4 | @import "app/forms"; 5 | @import "app/theme"; 6 | @import "app/pages/homepage"; 7 | 8 | @fa-font-path: "/build/fonts"; 9 | -------------------------------------------------------------------------------- /assets/less/app/colors.less: -------------------------------------------------------------------------------- 1 | .yellow { 2 | background-color: #f1c40f; 3 | color: #fff; 4 | background-image: url(../img/colors/txture_trans.png); 5 | 6 | &.btn:hover { 7 | background-color: #f4d313; 8 | color: #fff; 9 | border-color: #f4d313; 10 | } 11 | } 12 | 13 | .red { 14 | background-color: #e74c3c; 15 | color: #fff; 16 | background-image: url(../img/colors/txture_trans.png); 17 | 18 | &.btn:hover { 19 | background-color: #ec7063; 20 | color: #fff; 21 | border-color: #ec7063; 22 | } 23 | } 24 | 25 | .green { 26 | background-color: #1abc9c; 27 | color: #fff; 28 | background-image: url(../img/colors/txture_trans.png); 29 | 30 | &.btn:hover { 31 | background-color: #58d68d; 32 | color: #fff; 33 | border-color: #58d68d; 34 | } 35 | } 36 | 37 | .blue { 38 | background-color: #3498db; 39 | color: #fff; 40 | background-image: url(../img/colors/txture_trans.png); 41 | 42 | &.btn:hover { 43 | background-color: #5dade2; 44 | color: #fff; 45 | border-color: #5dade2; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /assets/less/app/forms.less: -------------------------------------------------------------------------------- 1 | .form-group .control-label.required:after { 2 | color: #d00; 3 | content: "*"; 4 | position: absolute; 5 | margin-left: 3px; 6 | top: 7px; 7 | } 8 | -------------------------------------------------------------------------------- /assets/less/app/pages/homepage.less: -------------------------------------------------------------------------------- 1 | 2 | #carousel { 3 | .carousel-inner img { 4 | height: 350px; 5 | width: 100%; 6 | object-fit: cover; 7 | } 8 | } 9 | 10 | #testimonials { 11 | 12 | margin-top: 25px; 13 | 14 | blockquote { 15 | border-left: none; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /assets/less/app/theme.less: -------------------------------------------------------------------------------- 1 | // theme can be removed or changed completely 2 | .header { 3 | margin-bottom: 30px; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /behat.yml.dist: -------------------------------------------------------------------------------- 1 | # vi: set ft=yaml ts=2 sw=2 sts=2 : 2 | 3 | default: 4 | formatters: 5 | pretty: 6 | verbose: true 7 | paths: true 8 | snippets: true 9 | 10 | extensions: 11 | Behat\MinkExtension: 12 | sessions: 13 | default: 14 | symfony2: ~ 15 | Behat\Symfony2Extension: 16 | kernel: 17 | env: test 18 | debug: false 19 | 20 | suites: 21 | default: 22 | contexts: 23 | - AppBundle\Behat\PlaceholderContext 24 | - AppBundle\Behat\DatabaseContext 25 | - AppBundle\Behat\MailerContext 26 | - AppBundle\Behat\PageContext 27 | - AppBundle\Behat\UserContext 28 | - Behat\MinkExtension\Context\MinkContext 29 | filters: 30 | tags: @user 31 | 32 | api: 33 | contexts: 34 | - ApiBundle\Behat\APIContext 35 | - AppBundle\Behat\PlaceholderContext 36 | - AppBundle\Behat\DatabaseContext 37 | - AppBundle\Behat\UserContext 38 | filters: 39 | role: "api user" 40 | 41 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony2-skeleton", 3 | "private": true, 4 | "dependencies": { 5 | "bootstrap": "~3.3.5", 6 | "fontawesome": "~4.4.0" 7 | }, 8 | "devDependencies": {} 9 | } 10 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data-dog/symfony-force", 3 | "license": "MIT", 4 | "type": "project", 5 | "description": "Symfony force edition. Pragmatic symfony2 application bootstrap guide.", 6 | 7 | "autoload": { 8 | "psr-0": { "": "src/" } 9 | }, 10 | 11 | "autoload-dev": {}, 12 | 13 | "require": { 14 | "php": ">=5.6.0", 15 | "ext-curl": "*", 16 | "ext-mbstring": "*", 17 | "ext-mcrypt": "*", 18 | "ext-pdo": "*", 19 | "ext-pdo_mysql": "*", 20 | "ext-intl": "*", 21 | "ext-openssl": "*", 22 | 23 | "symfony/symfony": "~2.7.0", 24 | "symfony/monolog-bundle": "~2.7", 25 | "symfony/swiftmailer-bundle": "~2.3", 26 | 27 | "sensio/distribution-bundle": "~4.0.0", 28 | "sensio/framework-extra-bundle": "~3.0", 29 | 30 | "twig/extensions": "~1.0", 31 | "incenteev/composer-parameter-handler": "~2.0", 32 | "knplabs/knp-menu-bundle": "~2.0.0", 33 | "excelwebzone/recaptcha-bundle": "^1.4", 34 | 35 | "doctrine/orm": "~2.5.0", 36 | "doctrine/doctrine-bundle": "~1.4", 37 | "doctrine/doctrine-migrations-bundle": "~1.1.0", 38 | "doctrine/migrations": "~1.2.2", 39 | "doctrine/data-fixtures": "~1.0", 40 | "data-dog/pager-bundle": "^0.2.0", 41 | "data-dog/audit-bundle": "^0.1.0", 42 | "gedmo/doctrine-extensions": "~2.4.0", 43 | 44 | "predis/predis": "^1.0" 45 | }, 46 | 47 | "require-dev": { 48 | "sensio/generator-bundle": "~2.3", 49 | "fzaninotto/faker": "~1.4.0", 50 | 51 | "phpunit/phpunit": "^4.7", 52 | 53 | "behat/behat": "~3.0.0", 54 | "behat/symfony2-extension": "~2.0.0", 55 | "behat/mink-extension": "~2.0.0", 56 | "behat/mink-browserkit-driver": "~1.2.0", 57 | "behat/mink": "~1.6.0" 58 | }, 59 | 60 | "scripts": { 61 | "post-install-cmd": [ 62 | "AppBundle\\Composer::misc", 63 | "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", 64 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", 65 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", 66 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets" 67 | ], 68 | "post-update-cmd": [ 69 | "AppBundle\\Composer::misc", 70 | "Incenteev\\ParameterHandler\\ScriptHandler::buildParameters", 71 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap", 72 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::clearCache", 73 | "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::installAssets" 74 | ] 75 | }, 76 | 77 | "config": { 78 | "bin-dir": "bin" 79 | }, 80 | 81 | "extra": { 82 | "symfony-app-dir": "app", 83 | "symfony-web-dir": "web", 84 | "symfony-assets-install": "relative", 85 | "incenteev-parameters": { 86 | "file": "app/config/parameters.yml" 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /docker-compose.yml.dist: -------------------------------------------------------------------------------- 1 | php: 2 | image: datadoglt/php56 3 | links: [mysql, redis] 4 | volumes: [ ".:/var/www", "./app/Resources/docker/init.sh:/init.sh", "./app/session:/var/lib/php/session" ] 5 | 6 | web: 7 | image: datadoglt/nginx-sf 8 | volumes: [".:/var/www"] 9 | ports: [80] 10 | links: [php] 11 | environment: { VIRTUAL_HOST: symfony.dev,admin.symfony.dev } 12 | 13 | redis: 14 | image: redis 15 | 16 | mysql: 17 | image: mysql 18 | hostname: mysql 19 | ports: ["3306"] 20 | environment: 21 | MYSQL_DATABASE: symfony2 22 | MYSQL_USER: symfony2 23 | MYSQL_PASSWORD: symfony2 24 | MYSQL_ROOT_PASSWORD: mysecretpw 25 | -------------------------------------------------------------------------------- /features/account/confirm.feature: -------------------------------------------------------------------------------- 1 | @user 2 | Feature: Confirming an account 3 | In order to use application 4 | As an unconfirmed user 5 | I need to be able to confirm my account 6 | 7 | Scenario: should be able to confirm an account 8 | Given I have signed up as "luke.skywalker@datadog.lt" 9 | When I follow the confirmation link in my email 10 | Then I should see "Account details" on page headline 11 | # Then my account "luke.skywalker@datadog.lt" should be confirmed 12 | 13 | Scenario: confirm an account 14 | Given I have signed up as "luke.skywalker@datadog.lt" 15 | When I follow the confirmation link in my email 16 | And I fill in my personal details 17 | Then I should see success notification "The user Luke Skywalker was successfully confirmed" 18 | -------------------------------------------------------------------------------- /features/account/profile.feature: -------------------------------------------------------------------------------- 1 | @user 2 | Feature: Profile management 3 | In order to keep account up to date 4 | As a confirmed user 5 | I need to be able to update my profile 6 | 7 | Background: 8 | Given confirmed user named "General Grievous" 9 | And I'm logged in as "general.grievous@datadog.lt" 10 | 11 | Scenario: can update without filling in password 12 | Given I am on "profile" page 13 | When I fill in "First name" with "Dark" 14 | And I press "Update" 15 | Then I should see success notification "Your profile was updated successfully" 16 | 17 | Scenario: can change password 18 | Given I am on "profile" page 19 | When I fill in "Password" with "S3cretpass" 20 | And I fill in "Repeat password" with "S3cretpass" 21 | And I press "Update" 22 | Then I should see success notification "Your profile was updated successfully" 23 | -------------------------------------------------------------------------------- /features/admin/login.feature: -------------------------------------------------------------------------------- 1 | @user 2 | Feature: Logging in to administrator panel 3 | In order to use manage site 4 | As a administrator 5 | I need to be able to login 6 | 7 | Scenario: can't login with unprivileged user 8 | Given confirmed user named "Chewbacca Wookiee" 9 | And I'm logged in as "chewbacca.wookiee@datadog.lt" 10 | When I visit "admin" page 11 | Then the response code should be 403 12 | 13 | Scenario: admin user is able to login 14 | Given confirmed admin named "Chewbacca Wookiee" 15 | And I'm logged in as "chewbacca.wookiee@datadog.lt" 16 | When I visit "admin" page 17 | Then I should see "Deathstar:Admin" 18 | 19 | -------------------------------------------------------------------------------- /features/api/authentication.feature: -------------------------------------------------------------------------------- 1 | Feature: protected API resources 2 | 3 | In order to protect resources 4 | As an api user 5 | I should not be able to access certain API resources without authentication 6 | 7 | Scenario: request without an authentication token should result in error 8 | When I send GET request to "/api/cms-blocks" 9 | Then the response code should be 401 10 | 11 | Scenario: authentication should allow only POST method 12 | When I send GET request to "/api/authenticate" 13 | Then the response code should be 405 14 | 15 | Scenario: authentication requires credentials 16 | When I send POST request to "/api/authenticate" with: 17 | """ 18 | { 19 | "please": "log me in" 20 | } 21 | """ 22 | Then the response code should be 401 23 | And there should be an error message "Username or password is not valid." in response 24 | 25 | Scenario: try to authenticate when there is no such user 26 | When I try to authenticate as "unavailable@datadog.lt" 27 | Then the response code should be 401 28 | 29 | Scenario: should be able to authenticate a confirmed user 30 | Given confirmed user named "John Doe" 31 | When I try to authenticate as "john.doe@datadog.lt" 32 | Then the response code should be 200 33 | And the response should contain json: 34 | """ 35 | { 36 | "id": %john.doe@datadog.lt%, 37 | "roles": ["ROLE_USER"], 38 | "token": "/(.+)/" 39 | } 40 | """ 41 | 42 | Scenario: should not be able to authenticate an unconfirmed user 43 | Given unconfirmed user named "John Doe" 44 | When I try to authenticate as "john.doe@datadog.lt" 45 | Then the response code should be 401 46 | -------------------------------------------------------------------------------- /features/api/cms-blocks/list.feature: -------------------------------------------------------------------------------- 1 | Feature: get cms blocks 2 | 3 | In order to see cms blocks 4 | As an api user 5 | I should be able to get list of cms blocks 6 | 7 | Scenario: api resource is protected 8 | When I send GET request to "/api/cms-blocks" 9 | Then the response code should be 401 10 | 11 | Scenario: can list cms blocks 12 | Given confirmed user named "Chewbacca Wookiee" 13 | And I'm authenticated as "chewbacca.wookiee@datadog.lt" 14 | When I send GET request to "/api/cms-blocks" 15 | Then the response code should be 200 16 | And the response should contain json: 17 | """ 18 | { 19 | "blocks": [ 20 | { 21 | "id": 1, 22 | "name": "Footer" 23 | }, 24 | { 25 | "id": 2, 26 | "name": "Css" 27 | }, 28 | { 29 | "id": 3, 30 | "name": "Js" 31 | } 32 | ] 33 | } 34 | """ 35 | 36 | Scenario: can get paged cms blocks 37 | Given confirmed user named "Chewbacca Wookiee" 38 | And I'm authenticated as "chewbacca.wookiee@datadog.lt" 39 | When I send GET request to "/api/cms-blocks?page=2&limit=1" 40 | Then the response code should be 200 41 | And the response should match json: 42 | """ 43 | { 44 | "blocks": [ 45 | { 46 | "id": 2, 47 | "alias": "css", 48 | "name": "Css", 49 | "content": "\/* cms block for css *\/", 50 | "createdAt": "/(.+)/", 51 | "updatedAt": "/(.+)/" 52 | } 53 | ] 54 | } 55 | """ 56 | -------------------------------------------------------------------------------- /features/api/version.feature: -------------------------------------------------------------------------------- 1 | Feature: see api version 2 | 3 | In order to know api version 4 | As an api user 5 | I should be able to get version details anonymously 6 | 7 | Scenario: anonymous user can view api version details 8 | When I send GET request to "/api/version" 9 | Then the response code should be 200 10 | And the response should contain json: 11 | """ 12 | { 13 | "name": "symfony-force", 14 | "version": "0.1.0", 15 | "description": "Pragmatic symfony2 application bootstrap guide." 16 | } 17 | """ 18 | -------------------------------------------------------------------------------- /features/guest/login.feature: -------------------------------------------------------------------------------- 1 | @user 2 | Feature: Logging in 3 | In order to use application and manage private resources 4 | As a registered user 5 | I need to be able to login 6 | 7 | Background: 8 | Given confirmed user named "Chewbacca Wookiee" 9 | 10 | Scenario: can't login with incorrect credentials 11 | Given I am on "login" page 12 | When I try to login as "chewbacca.wookiee@datadog.lt" using password "any" 13 | Then I should see error notification "Email or password is incorrect" 14 | 15 | Scenario: confirmed user is able to login 16 | Given I am on "login" page 17 | When I try to login as "chewbacca.wookiee@datadog.lt" using password "S3cretpassword" 18 | Then I should see "Symfony Force Edition" on page headline 19 | And I should be logged in 20 | 21 | Scenario: unconfirmed user cannot login 22 | Given unconfirmed user named "Darth Vader" 23 | And I am on "login" page 24 | When I try to login as "darth.vader@datadog.lt" using password "S3cretpassword" 25 | Then I should see error notification "Email or password is incorrect" 26 | -------------------------------------------------------------------------------- /features/guest/signup.feature: -------------------------------------------------------------------------------- 1 | @user 2 | Feature: Signing up 3 | In order to use application 4 | As an anonymous user 5 | I need to be able to signup 6 | 7 | Scenario: signup with valid email 8 | Given I am on "signup" page 9 | When I signup as "luke.skywalker@datadog.lt" 10 | Then I should see success notification "You should receive confirmation email shortly" 11 | And I should see "Login" on page headline 12 | And I should receive an email to "luke.skywalker@datadog.lt" 13 | 14 | Scenario: cannot signup with a confirmed email address 15 | Given confirmed user named "Luke Skywalker" 16 | And I am on "signup" page 17 | When I attempt to signup as "luke.skywalker@datadog.lt" 18 | Then I should see a form field error "Email luke.skywalker@datadog.lt is already confirmed" 19 | 20 | Scenario: signing up with unconfirmed email resends confirmation token 21 | Given unconfirmed user named "Luke Skywalker" 22 | And I am on "signup" page 23 | When I attempt to signup as "luke.skywalker@datadog.lt" 24 | Then I should see info notification "Confirmation email was successfully resent to luke.skywalker@datadog.lt" 25 | 26 | Scenario: try to signup with invalid email 27 | Given I am on "signup" page 28 | When I signup as "luke" 29 | Then I should see a form field error "Email address is not valid" 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony-force", 3 | "version": "0.1.0", 4 | "description": "Pragmatic symfony2 application bootstrap guide.", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/DATA-DOG/symfony-force.git" 9 | }, 10 | "dependencies": { 11 | "grunt": "~0.4.0", 12 | "grunt-contrib-clean": "^0.6.0", 13 | "grunt-contrib-concat": "^0.5.0", 14 | "grunt-contrib-copy": "^0.7.0", 15 | "grunt-contrib-less": "^1.0.1", 16 | "grunt-contrib-uglify": "^0.6.0", 17 | "grunt-contrib-watch": "^0.6.1", 18 | "grunt-remove-logging": "^0.2.0", 19 | "grunt-shell": "^1.1.2", 20 | "load-grunt-tasks": "^1.0.0", 21 | "time-grunt": "^1.0.0" 22 | }, 23 | "devDependencies": {} 24 | } 25 | -------------------------------------------------------------------------------- /src/AdminBundle/AdminBundle.php: -------------------------------------------------------------------------------- 1 | load('twig.yml'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/AdminBundle/Form/CmsBlockType.php: -------------------------------------------------------------------------------- 1 | add('alias', 'text', ['label' => 'cms_block.label.alias']); 29 | $builder->add('name', 'text', ['label' => 'cms_block.label.name']); 30 | $builder->add('content', 'textarea', ['label' => 'cms_block.label.content']); 31 | } 32 | 33 | /** 34 | * Configures the options for this type. 35 | * 36 | * @param OptionsResolver $resolver The resolver for the options. 37 | */ 38 | public function configureOptions(OptionsResolver $resolver) 39 | { 40 | $resolver->setDefaults([ 41 | 'data_class' => CmsBlock::class 42 | ]); 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/AdminBundle/Form/MailTemplateType.php: -------------------------------------------------------------------------------- 1 | add('alias', 'text', [ 30 | 'label' => 'mail_template.label.alias' 31 | ]) 32 | ->add('subject', 'text', [ 33 | 'label' => 'mail_template.label.subject' 34 | ]) 35 | ->add('content', 'textarea', [ 36 | 'label' => 'mail_template.label.content' 37 | ]) 38 | ; 39 | } 40 | 41 | /** 42 | * Configures the options for this type. 43 | * 44 | * @param OptionsResolver $resolver The resolver for the options. 45 | */ 46 | public function configureOptions(OptionsResolver $resolver) 47 | { 48 | $resolver->setDefaults([ 49 | 'data_class' => MailTemplate::class 50 | ]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/AdminBundle/Form/UserType.php: -------------------------------------------------------------------------------- 1 | add('email', 'email', [ 20 | 'label' => 'user.label.email', 21 | ]) 22 | ->add('firstname', 'text', [ 23 | 'label' => 'user.label.firstname', 24 | ]) 25 | ->add('lastname', 'text', [ 26 | 'label' => 'user.label.lastname', 27 | ]) 28 | ->add('plainPassword', 'repeated', [ 29 | 'type' => 'password', 30 | 'first_options' => ['label' => 'user.label.password'], 31 | 'second_options' => ['label' => 'user.label.repeat_password'], 32 | ]) 33 | ; 34 | } 35 | 36 | /** 37 | * Configures the options for this type. 38 | * 39 | * @param OptionsResolver $resolver The resolver for the options. 40 | */ 41 | public function configureOptions(OptionsResolver $resolver) 42 | { 43 | $resolver->setDefaults([ 44 | 'data_class' => User::class, 45 | ]); 46 | } 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getName() 52 | { 53 | return 'user'; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/AdminBundle/Menu/MenuBuilder.php: -------------------------------------------------------------------------------- 1 | createItem('root'); 21 | $menu->setChildrenAttribute('class', 'nav navbar-nav pull-right'); 22 | 23 | $child = function($label, $route) use($menu) { 24 | $attributes = ['role' => 'presentation']; 25 | $menu->addChild($this->container->get('translator')->trans($label, [], 'menu'), compact('route', 'attributes')); 26 | }; 27 | 28 | if ($this->getToken()->getUser() instanceof UserInterface) { 29 | $child($this->getToken()->getUser(), 'app_user_profile'); 30 | $child('logout', 'app_user_logout'); 31 | } 32 | 33 | return $menu; 34 | } 35 | 36 | /** 37 | * @param FactoryInterface $factory 38 | * @return \Knp\Menu\ItemInterface 39 | */ 40 | public function main(FactoryInterface $factory) 41 | { 42 | $menu = $factory->createItem('root'); 43 | $menu->setChildrenAttribute('class', 'nav navbar-nav'); 44 | 45 | $child = function($label, $route) use($menu) { 46 | $attributes = ['role' => 'presentation']; 47 | $menu->addChild($this->container->get('translator')->trans($label, [], 'menu'), compact('route', 'attributes')); 48 | }; 49 | 50 | $child('users', 'admin_user_index'); 51 | $child('templates', 'admin_mailtemplate_index'); 52 | $child('audit', 'admin_audit_index'); 53 | $child('cms', 'admin_cmsblock_index'); 54 | 55 | return $menu; 56 | } 57 | 58 | /** 59 | * Get Security Token Storage. 60 | * @return TokenInterface 61 | */ 62 | private function getToken() 63 | { 64 | if (!$this->container->has('security.token_storage')) { 65 | throw new \LogicException('The SecurityBundle is not registered in your application.'); 66 | } 67 | 68 | $token = $this->container->get('security.token_storage')->getToken(); 69 | if (!$token instanceof TokenInterface) { 70 | return null; 71 | } 72 | 73 | return $token; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/config/routing.yml: -------------------------------------------------------------------------------- 1 | admin: 2 | resource: "@AdminBundle/Controller/" 3 | type: annotation 4 | prefix: / 5 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/config/twig.yml: -------------------------------------------------------------------------------- 1 | services: 2 | twig.audit.extension: 3 | class: AdminBundle\Twig\AuditExtension 4 | tags: 5 | - { name: twig.extension } 6 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/translations/menu.en.yml: -------------------------------------------------------------------------------- 1 | users: "Users" 2 | templates: "Templates" 3 | audit: "Audit" 4 | cms: "CMS" 5 | logout: "Logout" 6 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/translations/messages.en.yml: -------------------------------------------------------------------------------- 1 | admin_project_name: "Deathstar:Admin" 2 | 3 | title: 4 | edit: "Edit" 5 | delete: 'Delete' 6 | 7 | button: 8 | save: "Save" 9 | cancel: "Cancel" 10 | 11 | user: 12 | label: 13 | email: 'Email' 14 | name: 'Name' 15 | registered_on: 'Registered on' 16 | firstname: 'First name' 17 | lastname: 'Last name' 18 | password: 'Password' 19 | repeat_password: 'Repeat password' 20 | new: 21 | title: 'Create new user' 22 | enter_details: 'Enter user details' 23 | index: 24 | title: 'Users' 25 | new: 'Add new user' 26 | edit: 27 | title: 'Edit user' 28 | flash: 29 | created: 'User created successfully' 30 | updated: 'User updated successfully' 31 | removed: 'User removed successfully' 32 | 33 | mail_template: 34 | label: 35 | alias: "Alias" 36 | subject: "Subject" 37 | content: "Content" 38 | last_updated: 'Last updated' 39 | new: 40 | title: 'New email template' 41 | enter_details: 'Enter template details' 42 | index: 43 | title: 'Email templates' 44 | new: 'Add new template' 45 | edit: 46 | title: 'Edit template' 47 | flash: 48 | created: 'Mail template created successfully' 49 | updated: 'Mail template updated successfully' 50 | removed: 'Mail template removed successfully' 51 | 52 | cms_block: 53 | label: 54 | alias: "Alias" 55 | name: "Name" 56 | content: "Content" 57 | last_update: 'Last update' 58 | new: 59 | title: 'Create CMS Block' 60 | enter_details: 'Enter block info' 61 | edit: 62 | title: 'Edit CMS block' 63 | index: 64 | title: 'CMS blocks' 65 | new: 'Add new' 66 | flash: 67 | created: 'CMS block created successfully' 68 | updated: 'CMS block updated successfully' 69 | removed: 'CMS block removed successfully' 70 | 71 | audit: 72 | index: 73 | title: 'Audit' 74 | search_by_pk: 'Search by primary key..' 75 | show_history: 'Show all resource history' 76 | was_associated: 'was associated with' 77 | was_disassociated: 'was dissociated from' 78 | was_updated: 'was updated' 79 | was_removed: 'was removed' 80 | was_inserted: 'was inserted' 81 | by_user: 'by' 82 | by_system: 'by power user or task runner.' 83 | diff: 'audit diff' 84 | field: 'Field' 85 | old: 'Old' 86 | new: 'New' 87 | pagination: 88 | any_source: 'Any source class' 89 | any_user: 'Any user' 90 | unknown: 'Unknown' 91 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/Audit/assoc.html.twig: -------------------------------------------------------------------------------- 1 | 2 | {{ assoc.label }} 3 | 4 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/Audit/associate.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ log.source.typLabel }} {{ audit_assoc(log.source) }} {{ 'audit.index.was_associated'|trans }} 7 | 8 | {{ log.target.typLabel }} {{ audit_assoc(log.target) }}, {{ audit_blame(log.blame) }} 9 | 10 | {{ time_diff(log.loggedAt) }} 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/Audit/blame.html.twig: -------------------------------------------------------------------------------- 1 | {% if blame %} 2 | {{ 'audit.index.by_user'|trans }} {{ audit_assoc(blame) }} 3 | {% else %} 4 | {{ 'audit.index.by_system'|trans|raw }} 5 | {% endif %} 6 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/Audit/diff.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "AdminBundle::layout.html.twig" %} 2 | 3 | {% block content %} 4 |

{{ audit_assoc(log.source) }}{{ 'audit.index.diff'|trans }}

5 | 6 | {% include 'AppBundle::flashes.html.twig' %} 7 | 8 |
9 |
10 |

{{ audit_assoc(log.source) }}

11 |
12 | 13 | {% if log.action == 'insert' %} 14 | 22 | {% else %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | {% for field, change in log.diff %} 30 | 31 | 32 | 33 | 34 | 35 | {% endfor %} 36 |
{{ 'audit.index.field'|trans }}{{ 'audit.index.old'|trans }}{{ 'audit.index.new'|trans }}
{{ field }}{{ audit_value(change.old) }}{{ audit_value(change.new) }}
37 | {% endif %} 38 | 39 |
40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/Audit/dissociate.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ log.source.typLabel }} {{ audit_assoc(log.source) }} {{ 'audit.index.was_disassociated'|trans }} 7 | {{ log.target.typLabel }} {{ audit_assoc(log.target) }}, {{ audit_blame(log.blame) }} 8 | 9 | {{ time_diff(log.loggedAt) }} 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/Audit/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "AdminBundle::layout.html.twig" %} 2 | 3 | {% block content %} 4 |

{{ 'audit.index.title'|trans }}

5 | 6 | {% include 'AppBundle::flashes.html.twig' %} 7 | 8 |
9 |
10 |

{{ 'audit.index.title'|trans }}

11 |
12 | 13 | 14 | 15 | 16 | 30 | 31 | 32 | 33 | {% for log in logs %} 34 | {{ audit(log) }} 35 | {% endfor %} 36 | 37 |
17 |
18 |
19 |
20 | {{ filter_search(logs, "history", "audit.index.search_by_pk"|trans) }} 21 |
22 |
23 | {{ filter_dropdown(logs, "blamed", users) }} 24 |
25 |
26 | {{ filter_dropdown(logs, "class", sourceClasses) }} 27 |
28 |
29 |
38 | 39 | 42 |
43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/Audit/insert.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ log.source.typLabel }} {{ audit_assoc(log.source) }} {{ 'audit.index.was_inserted'|trans }}, 7 | {{ audit_blame(log.blame) }} 8 | 9 | {{ time_diff(log.loggedAt) }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/Audit/remove.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ log.source.typLabel }} {{ audit_assoc(log.source) }} {{ 'audit.index.was_removed'|trans }} 7 | {{ audit_blame(log.blame) }} 8 | 9 | {{ time_diff(log.loggedAt) }} 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/Audit/update.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ log.source.typLabel }} {{ audit_assoc(log.source) }} {{ 'audit.index.was_updated'|trans }}, 7 | {{ audit_blame(log.blame) }} 8 | 9 | {{ time_diff(log.loggedAt) }} 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/CmsBlock/edit.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AdminBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 |

{{ 'cms_block.edit.title'|trans }}

5 | 6 | {% include 'AppBundle::flashes.html.twig' %} 7 | 8 | {{ form_errors(form) }} 9 | {{ form_start(form) }} 10 |
11 |
12 |

{{ entity.alias }}

13 |
14 |
15 | {{ form_rest(form) }} 16 |
17 | 21 |
22 | {{ form_end(form) }} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/CmsBlock/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AdminBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 |

5 | {{ 'cms_block.index.title'|trans }} 6 | {{ 'cms_block.index.new'|trans }} 7 | 8 |

9 | 10 | {% include 'AppBundle::flashes.html.twig' %} 11 | 12 |
13 |
14 |

{{ 'cms_block.index.title'|trans }}

15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% for block in blocks %} 34 | 35 | 36 | 37 | 38 | 39 | 47 | 48 | {% endfor %} 49 | 50 |
#{{ sorter_link(blocks, 't.alias', 'cms_block.label.alias'|trans) }}{{ sorter_link(blocks, 't.name', 'cms_block.label.name'|trans) }}{{ sorter_link(blocks, 't.updatedAt', 'cms_block.label.last_update'|trans) }}
{{ filter_search(blocks, "t.alias") }}{{ filter_search(blocks, "t.name") }}{{ filter_search(blocks, "t.updatedAt") }}
{{ block.id }}{{ block.alias }}{{ block.name }}{{ block.createdAt|date }} 40 | 41 | 42 | 43 | 44 | 45 | 46 |
51 | 54 |
55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/CmsBlock/new.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AdminBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 |

{{ 'cms_block.new.title'|trans }}

5 | 6 | {% include 'AppBundle::flashes.html.twig' %} 7 | 8 | {{ form_errors(form) }} 9 | {{ form_start(form) }} 10 |
11 |
12 |

{{ 'cms_block.new.enter_details'|trans }}

13 |
14 |
15 | {{ form_rest(form) }} 16 |
17 | 21 |
22 | {{ form_end(form) }} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/MailTemplate/edit.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AdminBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 |

{{ 'mail_template.edit.title'|trans }}

5 | 6 | {% include 'AppBundle::flashes.html.twig' %} 7 | 8 | {{ form_errors(form) }} 9 | {{ form_start(form) }} 10 |
11 |
12 |

{{ entity.alias }}

13 |
14 |
15 | {{ form_rest(form) }} 16 |
17 | 21 |
22 | {{ form_end(form) }} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/MailTemplate/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AdminBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 |

5 | {{ 'mail_template.index.title'|trans }} 6 | {{ 'mail_template.index.new'|trans }} 7 |

8 | 9 | {% include 'AppBundle::flashes.html.twig' %} 10 | 11 |
12 |
13 |

{{ 'mail_template.index.title'|trans }}

14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% for template in templates %} 33 | 34 | 35 | 36 | 37 | 38 | 46 | 47 | {% endfor %} 48 | 49 |
#{{ sorter_link(templates, 't.alias', 'mail_template.label.alias'|trans) }}{{ sorter_link(templates, 't.subject', 'mail_template.label.subject'|trans) }}{{ sorter_link(templates, 't.updatedAt', 'mail_template.label.last_updated'|trans) }}
{{ filter_search(templates, "t.alias") }}{{ filter_search(templates, "t.subject") }}{{ filter_search(templates, "t.updatedAt") }}
{{ template.id }}{{ template.alias }}{{ template.subject }}{{ template.createdAt|date }} 39 | 40 | 41 | 42 | 43 | 44 | 45 |
50 | 53 |
54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/MailTemplate/new.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AdminBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 |

{{ 'mail_template.new.title'|trans }}

5 | 6 | {% include 'AppBundle::flashes.html.twig' %} 7 | 8 | {{ form_errors(form) }} 9 | {{ form_start(form) }} 10 |
11 |
12 |

{{ 'mail_template.new.enter_details'|trans }}

13 |
14 |
15 | {{ form_rest(form) }} 16 |
17 | 21 |
22 | {{ form_end(form) }} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/User/edit.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AdminBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 |

{{ 'user.edit.title'|trans }}

5 | 6 | {% include 'AppBundle::flashes.html.twig' %} 7 | 8 | {{ form_errors(form) }} 9 | {{ form_start(form) }} 10 |
11 |
12 |

{{ entity.email }}

13 |
14 |
15 | {{ form_rest(form) }} 16 |
17 | 21 |
22 | {{ form_end(form) }} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/User/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AdminBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 |

5 | {{ 'user.index.title'|trans }} 6 | {{ 'user.index.new'|trans }} 7 |

8 | 9 | {% include 'AppBundle::flashes.html.twig' %} 10 | 11 |
12 |
13 |

{{ 'user.index.title'|trans }}

14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% for user in users %} 33 | 34 | 35 | 36 | 37 | 38 | 46 | 47 | {% endfor %} 48 | 49 |
#{{ sorter_link(users, 'u.email', 'user.label.email'|trans) }}{{ sorter_link(users, 'u.firstname', 'user.label.name'|trans) }}{{ sorter_link(users, 'u.createdAt', 'user.label.registered_on'|trans) }}
{{ filter_search(users, "u.email") }}{{ filter_search(users, "u.firstname") }}{{ filter_search(users, "u.createdAt") }}
{{ user.id }}{{ user.email }}{{ user.fullName }}{{ user.createdAt|date }} 39 | 40 | 41 | 42 | 43 | 44 | 45 |
50 | 53 |
54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/User/new.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AdminBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 |

{{ 'user.new.title'|trans }}

5 | 6 | {% include 'AppBundle::flashes.html.twig' %} 7 | 8 | {{ form_errors(form) }} 9 | {{ form_start(form) }} 10 |
11 |
12 |

{{ 'user.new.enter_details'|trans }}

13 |
14 |
15 | {{ form_rest(form) }} 16 |
17 | 21 |
22 | {{ form_end(form) }} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /src/AdminBundle/Resources/views/layout.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '::admin.html.twig' %} 2 | 3 | {% block body %} 4 | 15 | 16 |
17 | {% block content %}{% endblock %} 18 |
19 | 20 | {% endblock body %} 21 | 22 | {% block css %} 23 | 24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /src/AdminBundle/Twig/AuditExtension.php: -------------------------------------------------------------------------------- 1 | ['html'], 17 | 'needs_environment' => true, 18 | ]; 19 | 20 | return [ 21 | new \Twig_SimpleFunction('audit', [$this, 'audit'], $defaults), 22 | new \Twig_SimpleFunction('audit_value', [$this, 'value'], $defaults), 23 | new \Twig_SimpleFunction('audit_assoc', [$this, 'assoc'], $defaults), 24 | new \Twig_SimpleFunction('audit_blame', [$this, 'blame'], $defaults), 25 | ]; 26 | } 27 | 28 | public function audit(\Twig_Environment $twig, AuditLog $log) 29 | { 30 | return $twig->render("AdminBundle:Audit:{$log->getAction()}.html.twig", compact('log')); 31 | } 32 | 33 | public function assoc(\Twig_Environment $twig, $assoc) 34 | { 35 | return $twig->render("AdminBundle:Audit:assoc.html.twig", compact('assoc')); 36 | } 37 | 38 | public function blame(\Twig_Environment $twig, $blame) 39 | { 40 | return $twig->render("AdminBundle:Audit:blame.html.twig", compact('blame')); 41 | } 42 | 43 | public function value(\Twig_Environment $twig, $val) 44 | { 45 | switch (true) { 46 | case is_bool($val): 47 | return $val ? 'true' : 'false'; 48 | case is_array($val) && isset($val['fk']): 49 | return $this->assoc($twig, $val); 50 | case is_array($val): 51 | return json_encode($val); 52 | case is_string($val): 53 | return strlen($val) > 60 ? substr($val, 0, 60) . '...' : $val; 54 | case is_null($val): 55 | return 'NULL'; 56 | default: 57 | return $val; 58 | } 59 | } 60 | 61 | public function getName() 62 | { 63 | return 'admin_audit_extension'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ApiBundle/ApiBundle.php: -------------------------------------------------------------------------------- 1 | getExtension('security'); 15 | $extension->addSecurityListenerFactory(new JWTFactory()); 16 | $extension->addSecurityListenerFactory(new JWTAuthFactory()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ApiBundle/Controller/CmsBlockController.php: -------------------------------------------------------------------------------- 1 | repo('AppBundle:CmsBlock')->createQueryBuilder('t'); 30 | $paged = new Pagination($qb, $request); 31 | 32 | return new ListResource(iterator_to_array($paged, false)); 33 | } 34 | 35 | /** 36 | * @Route("/{id}") 37 | * @Method("GET") 38 | */ 39 | public function viewAction(Request $request, CmsBlock $block) 40 | { 41 | return new SingleResource($block); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ApiBundle/Controller/VersionController.php: -------------------------------------------------------------------------------- 1 | getParameter('kernel.root_dir') . '/../package.json'); 22 | if (!$pkg) { 23 | throw $this->createNotFoundException("package.json was not found in project root."); 24 | } 25 | 26 | return new VersionResource(json_decode(file_get_contents($pkg), true)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ApiBundle/DependencyInjection/ApiExtension.php: -------------------------------------------------------------------------------- 1 | load('listeners/kernel.yml'); 19 | $loader->load('security.yml'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/ApiBundle/DependencyInjection/Security/Factory/JWTAuthFactory.php: -------------------------------------------------------------------------------- 1 | setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.dao')) 21 | ->replaceArgument(0, new Reference($userProvider)) 22 | ->replaceArgument(2, $id) 23 | ; 24 | $listenerId = 'security.authentication.listener.jwt_auth.'.$id; 25 | $container 26 | ->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.jwt_auth')) 27 | ->replaceArgument(1, $id) 28 | ->replaceArgument(2, $config) 29 | ; 30 | return [$providerId, $listenerId, $defaultEntryPoint]; 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function getPosition() 37 | { 38 | return 'pre_auth'; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function getKey() 45 | { 46 | return 'jwt_auth'; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function addConfiguration(NodeDefinition $node) 53 | { 54 | $node 55 | ->children() 56 | ->scalarNode('priv_key')->defaultValue('')->end() 57 | ->scalarNode('passphrase')->defaultValue('')->end() 58 | ->end(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ApiBundle/DependencyInjection/Security/Factory/JWTFactory.php: -------------------------------------------------------------------------------- 1 | setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.jwt')) 21 | ->replaceArgument(0, new Reference($userProvider)) 22 | ->replaceArgument(1, $config); 23 | 24 | $listenerId = 'security.authentication.listener.jwt.' . $id; 25 | $container 26 | ->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.jwt')); 27 | 28 | return [$providerId, $listenerId, $defaultEntryPoint]; 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function getPosition() 35 | { 36 | return 'pre_auth'; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function getKey() 43 | { 44 | return 'jwt'; 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function addConfiguration(NodeDefinition $node) 51 | { 52 | $node 53 | ->children() 54 | ->scalarNode('pub_key')->defaultValue('')->end() 55 | ->end(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ApiBundle/Resource/CmsBlock/ListResource.php: -------------------------------------------------------------------------------- 1 | blocks = $blocks; 15 | } 16 | 17 | public function jsonSerialize() 18 | { 19 | $blocks = array_map(function(CmsBlock $block) { 20 | return (new SingleResource($block))->jsonSerialize(); 21 | }, $this->blocks); 22 | return compact('blocks'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ApiBundle/Resource/CmsBlock/SingleResource.php: -------------------------------------------------------------------------------- 1 | block = $block; 15 | } 16 | 17 | public function jsonSerialize() 18 | { 19 | return [ 20 | 'id' => $this->block->getId(), 21 | 'alias' => $this->block->getAlias(), 22 | 'name' => $this->block->getName(), 23 | 'content' => $this->block->getContent(), 24 | 'createdAt' => $this->block->getCreatedAt()->getTimestamp(), 25 | 'updatedAt' => $this->block->getUpdatedAt()->getTimestamp(), 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ApiBundle/Resource/VersionResource.php: -------------------------------------------------------------------------------- 1 | pkg = $pkg; 14 | } 15 | 16 | public function jsonSerialize() 17 | { 18 | return [ 19 | 'name' => $this->pkg['name'], 20 | 'version' => $this->pkg['version'], 21 | 'description' => $this->pkg['description'], 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ApiBundle/ResourceInterface.php: -------------------------------------------------------------------------------- 1 | userProvider = $userProvider; 26 | $this->pubkey = 'file://' . realpath($config['pub_key']); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function authenticate(TokenInterface $token) 33 | { 34 | $parts = explode('.', $token->getCredentials()); 35 | if (count($parts) != 3) { 36 | throw new AuthenticationException("Incorrect JWT token, does not match required number of parts"); 37 | } 38 | 39 | // base64 decode jwt token parts 40 | list($header, $payload, $signature) = array_map('base64_decode', $parts); 41 | 42 | // signable parts 43 | $signup = implode('.', [$parts[0], $parts[1]]); 44 | 45 | // init openssl public key resource 46 | $key = $key = openssl_pkey_get_public($this->pubkey); 47 | if (!is_resource($key)) { 48 | throw new AuthenticationException("Not valid pub key: {$this->pubkey}"); 49 | } 50 | 51 | // ensure key is valid RSA public key 52 | if (openssl_pkey_get_details($key)['type'] !== JWTUserToken::KEY_TYPE) { 53 | throw new AuthenticationException("Only RSA keys are supported."); 54 | } 55 | 56 | // verify signature 57 | if (!openssl_verify($signup, $signature, $key, JWTUserToken::ALGO)) { 58 | throw new AuthenticationException("Could not verify signature."); 59 | } 60 | 61 | // check expiration 62 | if (isset($payload['exp']) && is_numeric($payload['exp'])) { 63 | if ((new \DateTime('now'))->format('U') < $payload['exp']) { 64 | throw new AuthenticationException("Token has expired"); 65 | } 66 | } 67 | 68 | // decode payload and header json 69 | list($payload, $header) = array_map(function($json) { 70 | return json_decode($json, true); 71 | }, [$payload, $header]); 72 | 73 | // validate header if necessary 74 | // ... 75 | 76 | // load user 77 | if (!$user = $this->userProvider->loadUserByUsername($payload['username'])) { 78 | throw new AuthenticationException("user does not exist"); 79 | } 80 | 81 | $authToken = new JWTUserToken($user->getRoles()); 82 | $authToken->setUser($user); 83 | $authToken->setRawToken($token->getCredentials()); 84 | return $authToken; 85 | } 86 | 87 | /** 88 | * {@inheritdoc} 89 | */ 90 | public function supports(TokenInterface $token) 91 | { 92 | return $token instanceof JWTUserToken; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/ApiBundle/Security/Authentication/Token/JWTUserToken.php: -------------------------------------------------------------------------------- 1 | setAuthenticated(count($roles) > 0); 24 | } 25 | 26 | /** 27 | * @param string $rawToken 28 | */ 29 | public function setRawToken($rawToken) 30 | { 31 | $this->rawToken = $rawToken; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function getCredentials() 38 | { 39 | return $this->rawToken; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ApiBundle/Security/Firewall/JWTAuthListener.php: -------------------------------------------------------------------------------- 1 | authenticationManager = $authenticationManager; 34 | $this->providerKey = $providerKey; 35 | $this->privkey = 'file://' . realpath($config['priv_key']); 36 | $this->passphrase = $config['passphrase']; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function handle(GetResponseEvent $event) 43 | { 44 | $request = $event->getRequest(); 45 | if (!$request->isMethod('POST')) { 46 | throw new HttpException(405, "Only POST method is allowed for JWT authentication"); 47 | } 48 | 49 | $username = $request->request->get('username', null); 50 | $password = $request->request->get('password', null); 51 | 52 | try { 53 | $token = $this->authenticationManager->authenticate(new UsernamePasswordToken($username, $password, $this->providerKey)); 54 | } catch (\InvalidArgumentException $e) { 55 | // most probably failed to find user by these credentials 56 | // let other unexpected exceptions pass through 57 | throw new HttpException(JsonResponse::HTTP_UNAUTHORIZED, "Username or password is not valid.", $e); 58 | } 59 | 60 | $user = $token->getUser(); 61 | 62 | $header = []; 63 | 64 | // jwt token data 65 | $payload = [ 66 | 'username' => $user->getUsername(), 67 | 'exp' => (new \DateTime('+1 day'))->format('U'), 68 | 'iat' => (new \DateTime('now'))->format('U'), 69 | ]; 70 | 71 | // build jwt data to sign 72 | $toSign = implode('.', array_map('base64_encode', array_map('json_encode', [$header, $payload]))); 73 | 74 | // init openssl private key resource 75 | $key = openssl_pkey_get_private($this->privkey, $this->passphrase); 76 | if (!is_resource($key)) { 77 | throw new HttpException(500, "not valid private key, {$this->privkey}"); 78 | } 79 | 80 | // ensure key is valid RSA private key 81 | if (openssl_pkey_get_details($key)['type'] !== JWTUserToken::KEY_TYPE) { 82 | throw new HttpException(500, "Only RSA keys are supported."); 83 | } 84 | 85 | // create signature 86 | $signature = null; 87 | if (!openssl_sign($toSign, $signature, $key, JWTUserToken::ALGO)) { 88 | throw new HttpException(500, "could not sign JWT."); 89 | } 90 | 91 | // create jwt token 92 | $jwt = implode('.', [$toSign, base64_encode($signature)]); 93 | 94 | // finally create response 95 | $event->setResponse(new JsonResponse([ 96 | 'token' => $jwt, 97 | 'id' => $user->getId(), 98 | 'roles' => $user->getRoles(), 99 | ])); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/ApiBundle/Security/Firewall/JWTListener.php: -------------------------------------------------------------------------------- 1 | tokenStorage = $tokenStorage; 33 | $this->authenticationManager = $authenticationManager; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function handle(GetResponseEvent $event) 40 | { 41 | $request = $event->getRequest(); 42 | // note, if you want to allow token from query parameters or cookie, act accordingly 43 | if (!$request->headers->has('Authorization')) { 44 | throw new AuthenticationException("Authorization header is missing"); 45 | } 46 | 47 | // extract parts from authorization header: prefix - jwt 48 | $parts = explode(' ', $request->headers->get('Authorization')); 49 | if (count($parts) !== 2) { 50 | throw new AuthenticationException("Authorization header is not valid"); 51 | } 52 | 53 | // match authorization header prefix 54 | list($prefix, $jwt) = $parts; 55 | if (self::HEADER_PREFIX !== $prefix) { 56 | throw new AuthenticationException("Authorization header prefix is not valid"); 57 | } 58 | 59 | $token = new JWTUserToken(); 60 | $token->setRawToken($jwt); 61 | 62 | $authToken = $this->authenticationManager->authenticate($token); 63 | $this->tokenStorage->setToken($authToken); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/AppBundle/AppBundle.php: -------------------------------------------------------------------------------- 1 | getEnvironment()->getContext('AppBundle\Behat\PlaceholderContext'); 16 | $this->get('em')->getEventManager()->addEventSubscriber(new PlaceholderListener($placeholders)); 17 | 18 | $this->get('db')->beginTransaction(); 19 | } 20 | 21 | /** 22 | * @AfterScenario 23 | */ 24 | function rollback() 25 | { 26 | $this->get('db')->rollback(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/AppBundle/Behat/Doctrine/PlaceholderListener.php: -------------------------------------------------------------------------------- 1 | placeholders = $placeholders; 19 | } 20 | 21 | public function postPersist(LifecycleEventArgs $args) 22 | { 23 | $em = $args->getEntityManager(); 24 | $entity = $args->getEntity(); 25 | $meta = $em->getClassMetadata(get_class($entity)); 26 | 27 | $id = current(array_values($meta->getIdentifierValues($entity))); 28 | $this->placeholders->set($this->label($entity), $id); 29 | } 30 | 31 | public function getSubscribedEvents() 32 | { 33 | return [Events::postPersist]; 34 | } 35 | 36 | private function label($entity) 37 | { 38 | switch (true) { 39 | case $entity instanceof Entity\User: 40 | return $entity->getEmail(); 41 | case $entity instanceof Entity\MailTemplate: 42 | return $entity->getSubject(); 43 | case method_exists($entity, 'getName'): 44 | return $entity->getName(); 45 | case method_exists($entity, 'getTitle'): 46 | return $entity->getTitle(); 47 | case method_exists($entity, 'getLabel'): 48 | return $entity->getTitle(); 49 | case method_exists($entity, '__toString'): 50 | return (string) $entity; 51 | default: 52 | return spl_object_hash($entity) . '-' . get_class($entity); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/AppBundle/Behat/MailerContext.php: -------------------------------------------------------------------------------- 1 | get('mailer')->getTransport()->getSpool()->messages = []; 13 | } 14 | 15 | /** 16 | * @Then /^I should receive an email to "([^"]*)"$/ 17 | */ 18 | function iShouldHaveReceivedAnEmailTo($email) 19 | { 20 | foreach ($this->get('mailer')->getTransport()->getSpool()->messages as $message) { 21 | foreach ($message->getTo() as $address => $name) { 22 | if ($address === $email) { 23 | return; 24 | } 25 | } 26 | } 27 | throw new \Exception("An email to '$email' was never sent"); 28 | } 29 | 30 | /** 31 | * @When /^I follow the confirmation link in my email$/ 32 | */ 33 | function iFollowTheConfirmationLinkInMyEmail() 34 | { 35 | foreach ($this->get('mailer')->getTransport()->getSpool()->messages as $message) { 36 | if (preg_match('/href="([^"]+)/smi', $message->getBody(), $m)) { 37 | return $this->getSession()->visit($m[1]); 38 | } 39 | } 40 | 41 | throw new \Exception("A confirmation link was not found in any email"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/AppBundle/Behat/PageContext.php: -------------------------------------------------------------------------------- 1 | visit("homepage"); 16 | case "login": 17 | return $this->visit("app_user_login"); 18 | case "signup": 19 | return $this->visit("app_user_signup"); 20 | case "profile": 21 | return $this->visit("app_user_profile"); 22 | case "admin": 23 | return $this->visit("admin_dashboard_index"); 24 | default: 25 | throw new \InvalidArgumentException("Page: {$name} route is not defined yet."); 26 | } 27 | } 28 | 29 | /** 30 | * @Then /^I should see "([^"]+)" on page headline$/ 31 | * @Then /^I should see "([^"]+)" in page headline$/ 32 | */ 33 | function iShouldSeeTextOnPageHeadline($text) 34 | { 35 | $this->notNull( 36 | $this->find('xpath', '//h1[contains(., "' . $text . '")] | //h2[contains(., "' . $text . '")] | //h3[contains(., "' . $text . '")]'), 37 | "Text '$text' was not found on page headline" 38 | ); 39 | } 40 | 41 | /** 42 | * @Then /^the response code should be (\d+)$/ 43 | */ 44 | function theResponseCodeShouldBe($code) 45 | { 46 | $this->same(intval($code), $actual = $this->getSession()->getStatusCode(), "Invalid response code, expected $code, got $actual"); 47 | } 48 | 49 | /** 50 | * @Then /^I should see (error|danger|success|info|notice) notification "([^"]+)"$/ 51 | */ 52 | function iShouldSeeNotification($type, $text) 53 | { 54 | switch ($type) { 55 | case 'error': 56 | $type = 'danger'; 57 | break; 58 | case 'notice': 59 | $type = 'info'; 60 | break; 61 | } 62 | 63 | $q = '//div[contains(@class, "alert") and contains(@class, "alert-' . $type . '") and contains(., "' . $text . '")]'; 64 | $this->notNull($this->find('xpath', $q), "Notification of type '$type' with message '$text' was not found on page"); 65 | } 66 | 67 | /** 68 | * @Then /^I should see a form field error "([^"]+)"$/ 69 | */ 70 | function iShouldSeeAFormFieldError($text) 71 | { 72 | $q = '//div[contains(@class, "has-error")]//span[contains(@class, "help-block") and contains(., "' . $text . '")]'; 73 | $this->notNull($this->find('xpath', $q), "Form field error '$text' was not found on page"); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/AppBundle/Behat/PlaceholderContext.php: -------------------------------------------------------------------------------- 1 | placeholders = []; 19 | } 20 | 21 | /** 22 | * @AfterStep 23 | */ 24 | function showRecentPlaceholders(AfterStepScope $scope) 25 | { 26 | if ($scope->getTestResult()->getResultCode() == TestResult::FAILED) { 27 | if (!empty($this->placeholders)) { 28 | echo "\nPlaceholders:"; 29 | foreach ($this->placeholders as $name => $value) { 30 | echo sprintf("\n %s: %s", $name, $value); 31 | } 32 | echo "\n\n"; 33 | } 34 | } 35 | } 36 | 37 | public function all() 38 | { 39 | return $this->placeholders; 40 | } 41 | 42 | public function get($name) 43 | { 44 | if (array_key_exists($name, $this->placeholders)) { 45 | throw new \Exception("The placeholder: \"{$name}\" was not set.."); 46 | } 47 | return $this->placeholders[$name]; 48 | } 49 | 50 | public function set($name, $value) 51 | { 52 | if (array_key_exists($name, $this->placeholders)) { 53 | throw new \Exception("The placeholder: \"{$name}\" was already set.."); 54 | } 55 | $this->placeholders[$name] = (string)$value; 56 | } 57 | 58 | public function replace($text) 59 | { 60 | return preg_replace_callback("#%([^%]+)%#", function ($m) { 61 | return isset($this->placeholders[$m[1]]) ? $this->placeholders[$m[1]] : $m[0]; 62 | }, $text); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/AppBundle/Behat/Swiftmailer/MemorySpool.php: -------------------------------------------------------------------------------- 1 | messages[] = clone $message; 44 | return true; 45 | } 46 | 47 | /** 48 | * Sends messages using the given transport instance. 49 | * 50 | * @param Swift_Transport $transport A transport instance 51 | * @param string[] $failedRecipients An array of failures by-reference 52 | * 53 | * @return int The number of sent emails 54 | */ 55 | public function flushQueue(\Swift_Transport $transport, &$failedRecipients = null) 56 | { 57 | if (empty($this->messages)) { 58 | return 0; 59 | } 60 | 61 | if (!$transport->isStarted()) { 62 | $transport->start(); 63 | } 64 | 65 | $count = 0; 66 | while ($message = array_pop($this->messages)) { 67 | $count += $transport->send($message, $failedRecipients); 68 | } 69 | 70 | return $count; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/AppBundle/Behat/UserContext.php: -------------------------------------------------------------------------------- 1 | get('security.context')->setToken(null); 15 | } 16 | 17 | /** 18 | * @Given /^(confirmed|unconfirmed) (user|admin) named "([^"]+)"$/ 19 | */ 20 | function userNamed($status, $type, $name) 21 | { 22 | $names = explode(' ', $name); 23 | list ($firstname, $lastname) = $names; 24 | 25 | $em = $this->get('em'); 26 | $user = new User(); 27 | if ('confirmed' === $status) { 28 | $user->setFirstname($firstname); 29 | $user->setLastname($lastname); 30 | } 31 | $user->setEmail(strtolower(implode('.', $names)) . '@datadog.lt'); 32 | $user->setRoles($type == 'user' ? ['ROLE_USER'] : ['ROLE_ADMIN']); 33 | 34 | if ('unconfirmed' === $status) { 35 | $user->setConfirmationToken(implode('-', array_map('strtolower', $names)) . '-token'); 36 | } else { 37 | $encoder = $this->get('security.encoder_factory')->getEncoder($user); 38 | $user->setPassword($encoder->encodePassword('S3cretpassword', $user->getSalt())); 39 | } 40 | $em->persist($user); 41 | $em->flush(); 42 | return $user; 43 | } 44 | 45 | /** 46 | * @Given /^I have signed up as "([^"]*)"$/ 47 | */ 48 | function iHaveSignedUpAs($email) 49 | { 50 | $this->visit('app_user_signup'); 51 | $this->mink->fillField('Email', $email); 52 | $this->mink->pressButton('Signup'); 53 | } 54 | 55 | /** 56 | * @When /^I fill in my personal details$/ 57 | */ 58 | function iFillInMyPersonalDetails() 59 | { 60 | // look in placeholders 61 | $user = null; 62 | foreach ($this->placeholders->all() as $key => $id) { 63 | if (strpos($key, '@') !== false) { 64 | $user = $this->repo('AppBundle:User')->findOneById($id); 65 | } 66 | } 67 | $this->true($user instanceof User, "User must be on confirm page"); 68 | $name = substr($user->getEmail(), 0, strpos($user->getEmail(), '@')); 69 | list($first, $last) = array_map('ucfirst', explode('.', $name)); 70 | 71 | $this->mink->fillField('First name', $first); 72 | $this->mink->fillField('Last name', $last); 73 | $this->mink->fillField('Password', 'S3cretpassword'); 74 | $this->mink->fillField('Repeat password', 'S3cretpassword'); 75 | $this->mink->pressButton('Confirm'); 76 | } 77 | 78 | /** 79 | * @Given /^I'm logged in as "([^"]*)"$/ 80 | * @When /^I login as "([^"]*)" using password "([^"]*)"$/ 81 | * @When /^I try to login as "([^"]*)" using password "([^"]*)"$/ 82 | */ 83 | function iTryToLoginAsUsingPassword($email, $password = 'S3cretpassword') 84 | { 85 | $this->visit('app_user_login'); 86 | $this->mink->fillField("Username", $email); 87 | $this->mink->fillField("Password", $password); 88 | $this->mink->pressButton("Login"); 89 | } 90 | 91 | /** 92 | * @When /^I attempt to signup as "([^"]*)"$/ 93 | * @When /^I signup as "([^"]*)"$/ 94 | */ 95 | function iSignupAs($email) 96 | { 97 | $this->mink->fillField('Email', $email); 98 | $this->mink->pressButton('Signup'); 99 | } 100 | 101 | /** 102 | * @Then /^I should be logged in$/ 103 | */ 104 | function iShouldBeLoggedIn() 105 | { 106 | $this->true($this->get('security.context')->getToken()->getUser() instanceof User); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/AppBundle/Cache/RedisCache.php: -------------------------------------------------------------------------------- 1 | redis = $redis; 23 | $this->setNamespace($this->ns = $ns); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | protected function doFetch($id) 30 | { 31 | $result = $this->redis->get($id); 32 | 33 | return null === $result ? false : unserialize($result); 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | protected function doContains($id) 40 | { 41 | return (bool) $this->redis->exists($id); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected function doSave($id, $data, $lifeTime = false) 48 | { 49 | if (0 < $lifeTime) { 50 | $result = $this->redis->setex($id, (int) $lifeTime, serialize($data)); 51 | } else { 52 | $result = $this->redis->set($id, serialize($data)); 53 | } 54 | 55 | return (bool) $result; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | protected function doDelete($id) 62 | { 63 | return (bool) $this->redis->del($id); 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | protected function doFlush() 70 | { 71 | return (bool) $this->redis->flushDB(); 72 | } 73 | 74 | public function flushDB() 75 | { 76 | return (bool) $this->redis->flushDB(); 77 | } 78 | 79 | public function flushNamespacedKeys() 80 | { 81 | $params = $this->redis->getConnection()->getParameters(); 82 | // redis cli connection string 83 | $conn = sprintf("redis-cli -h %s -p %s -n %s --raw", $params->host, $params->port, $params->database); 84 | // remove namespaced keys only 85 | $proc = new Process(sprintf("%s KEYS '*%s*' | xargs --delim='\\n' %s DEL", $conn, $this->ns, $conn)); 86 | $proc->run(); 87 | return $proc->getOutput(); 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | */ 93 | protected function doGetStats() 94 | { 95 | $stats = $this->redis->info(); 96 | 97 | return array( 98 | Cache::STATS_HITS => isset($stats['keyspace_hits']) ? $stats['keyspace_hits'] : $stats['Stats']['keyspace_hits'], 99 | Cache::STATS_MISSES => isset($stats['keyspace_misses']) ? $stats['keyspace_misses'] : $stats['Stats']['keyspace_misses'], 100 | Cache::STATS_UPTIME => isset($stats['uptime_in_seconds']) ? $stats['uptime_in_seconds'] : $stats['Server']['uptime_in_seconds'], 101 | Cache::STATS_MEMORY_USAGE => isset($stats['used_memory']) ? $stats['used_memory'] : $stats['Memory']['used_memory'], 102 | Cache::STATS_MEMORY_AVAILIABLE => null, 103 | ); 104 | } 105 | 106 | /** 107 | * {@inheritdoc} 108 | */ 109 | public function has($class) 110 | { 111 | return $this->contains($class); 112 | } 113 | 114 | /** 115 | * {@inheritdoc} 116 | */ 117 | public function read($class) 118 | { 119 | return $this->fetch($class); 120 | } 121 | 122 | /** 123 | * {@inheritdoc} 124 | */ 125 | public function write(ClassMetadata $metadata) 126 | { 127 | $this->save($metadata->getClassName(), $metadata); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/AppBundle/Command/CacheClearCommand.php: -------------------------------------------------------------------------------- 1 | setName('app:cache:clear') 16 | ->setDescription('Clears application cache.') 17 | ->setHelp(<<%command.name% clears application cache. 19 | 20 | php %command.full_name% 21 | php %command.full_name% --env=prod 22 | 23 | EOT 24 | ); 25 | } 26 | 27 | protected function execute(InputInterface $input, OutputInterface $output) 28 | { 29 | $env = $this->getContainer()->getParameter('kernel.environment'); 30 | $output->writeLn("Clearing app cache for environment {$env}..."); 31 | 32 | $cache = $this->getContainer()->get('cache.default'); 33 | if ($cache instanceof RedisCache) { 34 | if ($env === 'test') { 35 | $cache->flushDB(); 36 | $output->writeln('Flushed all redis cache'); 37 | } else { 38 | // flush only namespaced keys for redis cache 39 | $output->writeLn(sprintf( 40 | "Flushed %s cache entries", 41 | trim($cache->flushNamespacedKeys()) 42 | )); 43 | } 44 | } else { 45 | // do not mind and remove all cache (array cache) 46 | $cache->flushAll(); 47 | } 48 | 49 | $output->writeLn("Cache was cleared."); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/AppBundle/Command/FixturesCommand.php: -------------------------------------------------------------------------------- 1 | setName('app:fixtures') 21 | ->setDescription('Load data fixtures to your database.') 22 | ->addOption('em', null, InputOption::VALUE_REQUIRED, 'The entity manager to use for this command.') 23 | ->setHelp(<<%command.name% command loads data fixtures from your bundles: 25 | It loads only ones which were not appended already. 26 | 27 | php %command.full_name% 28 | php %command.full_name% --env=prod 29 | 30 | EOT 31 | ); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output) 35 | { 36 | $doctrine = $this->getContainer()->get('doctrine'); 37 | $em = $doctrine->getManager($input->getOption('em')); 38 | 39 | $alreadyLoaded = $em->getRepository("AppBundle:Internal\Fixture")->findAll(); 40 | // may support more managers 41 | $loaded = $this->loadFixtures($output, $em, $alreadyLoaded); 42 | 43 | foreach ($loaded as $fixture) { 44 | $added = new Fixture(); 45 | $added->setName(get_class($fixture)); 46 | $em->persist($added); 47 | } 48 | $em->flush(); 49 | } 50 | 51 | private function loadFixtures(OutputInterface $output, EntityManager $em, array $loaded) 52 | { 53 | $logger = function($message) use ($output) { 54 | $output->writeln(sprintf(' > %s', $message)); 55 | }; 56 | $executor = new ORMExecutor($em); 57 | 58 | $paths = []; 59 | foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) { 60 | $paths[] = $bundle->getPath() . '/Fixture'; 61 | } 62 | 63 | $loader = new DataFixturesLoader($this->getContainer()); 64 | foreach ($paths as $path) { 65 | if (is_dir($path)) { 66 | $loader->loadFromDirectory($path); 67 | } 68 | } 69 | $env = $this->getContainer()->getParameter('kernel.environment'); 70 | $output->writeln("Loading fixtures..."); 71 | 72 | $has = array_map(function(Fixture $fixture) { 73 | return $fixture->getName(); 74 | }, $loaded); 75 | 76 | $fixtures = array_filter($loader->getFixtures(), function(FixtureInterface $fixture) use($has) { 77 | return !in_array(get_class($fixture), $has); 78 | }); 79 | 80 | if (!$fixtures) { 81 | $output->writeln(" Could not find any new fixtures to load.."); 82 | return []; 83 | } 84 | 85 | $executor->setLogger($logger); 86 | $executor->execute($fixtures, true); 87 | return $fixtures; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/AppBundle/Composer.php: -------------------------------------------------------------------------------- 1 | getIO()->write(sprintf('The "bin" directory was not found in %s.', getcwd())); 19 | return; 20 | } 21 | foreach (glob('app/Resources/bin/*') as $binary) { 22 | $src = '../app/Resources/bin/' . basename($binary); 23 | $dst = $dst = 'bin/' . basename($binary); 24 | @unlink($dst); 25 | if (@symlink($src, $dst) === false) { 26 | if (!file_exists($dst)) { 27 | $event->getIO()->write(sprintf('Failed to symlink %s from %s.', $src, $dst)); 28 | return; 29 | } 30 | continue; 31 | } 32 | $event->getIO()->write(sprintf('Installed binary %s.', $dst)); 33 | } 34 | } 35 | 36 | private static function spacingParametersYml(CommandEvent $event) 37 | { 38 | if (!file_exists($file = 'vendor/incenteev/composer-parameter-handler/Processor.php')) { 39 | return; 40 | } 41 | 42 | $content = file_get_contents($file); 43 | $matches = 0; 44 | $content = str_replace('Yaml::dump($actualValues, 99)', 'Yaml::dump($actualValues, 99, 2)', $content, $matches); 45 | if ($matches) { 46 | file_put_contents($file, $content, LOCK_EX); 47 | $event->getIO()->write('Updated spacing for incenteev parameters'); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/AppBundle/Controller/DoctrineController.php: -------------------------------------------------------------------------------- 1 | getDoctrine()->getManager()->persist($entity); 11 | } 12 | } 13 | 14 | protected function remove(...$entities) 15 | { 16 | foreach ($entities as $entity) { 17 | $this->getDoctrine()->getManager()->remove($entity); 18 | } 19 | } 20 | 21 | protected function flush($class = null) 22 | { 23 | $this->getDoctrine()->getManager()->flush($class); 24 | } 25 | 26 | /** 27 | * @param string $class 28 | */ 29 | protected function repo($class) 30 | { 31 | return $this->getDoctrine()->getManager()->getRepository($class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/AppBundle/Controller/HomeController.php: -------------------------------------------------------------------------------- 1 | load('security.yml'); 19 | $loader->load('twig.yml'); 20 | $loader->load('mailer.yml'); 21 | $loader->load('menu.yml'); 22 | $loader->load('listeners/doctrine.yml'); 23 | $loader->load('listeners/kernel.yml'); 24 | 25 | $loader->load(sprintf('cache/%s.yml', $container->getParameter('kernel.environment'))); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/AppBundle/Entity/Internal/Fixture.php: -------------------------------------------------------------------------------- 1 | name = $name; 22 | return $this; 23 | } 24 | 25 | public function getName() 26 | { 27 | return $this->name; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/AppBundle/Entity/MailTemplate.php: -------------------------------------------------------------------------------- 1 | id; 62 | } 63 | 64 | /** 65 | * Set alias 66 | * 67 | * @param string $alias 68 | * 69 | * @return MailTemplate 70 | */ 71 | public function setAlias($alias) 72 | { 73 | $this->alias = $alias; 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * Get alias 80 | * 81 | * @return string 82 | */ 83 | public function getAlias() 84 | { 85 | return $this->alias; 86 | } 87 | 88 | /** 89 | * @return \DateTime 90 | */ 91 | public function getCreatedAt() 92 | { 93 | return $this->createdAt; 94 | } 95 | 96 | /** 97 | * @param \DateTime $createdAt 98 | */ 99 | public function setCreatedAt($createdAt) 100 | { 101 | $this->createdAt = $createdAt; 102 | } 103 | 104 | /** 105 | * @return \DateTime 106 | */ 107 | public function getUpdatedAt() 108 | { 109 | return $this->updatedAt; 110 | } 111 | 112 | /** 113 | * @param \DateTime $updatedAt 114 | */ 115 | public function setUpdatedAt($updatedAt) 116 | { 117 | $this->updatedAt = $updatedAt; 118 | } 119 | 120 | /** 121 | * Set subject 122 | * 123 | * @param string $subject 124 | * 125 | * @return MailTemplate 126 | */ 127 | public function setSubject($subject) 128 | { 129 | $this->subject = $subject; 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * Get subject 136 | * 137 | * @return string 138 | */ 139 | public function getSubject() 140 | { 141 | return $this->subject; 142 | } 143 | 144 | /** 145 | * Set content 146 | * 147 | * @param string $content 148 | * 149 | * @return MailTemplate 150 | */ 151 | public function setContent($content) 152 | { 153 | $this->content = $content; 154 | 155 | return $this; 156 | } 157 | 158 | /** 159 | * Get content 160 | * 161 | * @return string 162 | */ 163 | public function getContent() 164 | { 165 | return $this->content; 166 | } 167 | } 168 | 169 | -------------------------------------------------------------------------------- /src/AppBundle/EventListener/DoctrineExtensionsListener.php: -------------------------------------------------------------------------------- 1 | translatable = $translatable; 15 | } 16 | 17 | public function onLateKernelRequest(GetResponseEvent $event) 18 | { 19 | $this->translatable->setTranslatableLocale($event->getRequest()->getLocale()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/AppBundle/EventListener/FlushListener.php: -------------------------------------------------------------------------------- 1 | em = $em; 17 | $this->sub = $sub; 18 | } 19 | 20 | public function onEarlyKernelRequest(GetResponseEvent $event) 21 | { 22 | if (!$event->isMasterRequest()) { 23 | return; 24 | } 25 | // reset flushed state on each request, since kernel may not be rebooted 26 | $this->sub->flushed = false; 27 | $this->sub->inRequest = true; 28 | } 29 | 30 | public function onLateKernelResponse(FilterResponseEvent $event) 31 | { 32 | if (!$event->isMasterRequest()) { 33 | return; 34 | } 35 | 36 | $this->sub->inRequest = false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/AppBundle/EventListener/FlushSubscriber.php: -------------------------------------------------------------------------------- 1 | inRequest) { 17 | // let console commands handle flushes anyway they want 18 | return; 19 | } 20 | 21 | $em = $args->getEntityManager(); 22 | if ($em->getConnection()->isTransactionActive()) { 23 | // the transaction is managed manually and was already started 24 | // probably it won't be handled since it is the end of response 25 | // but anyways, it won't cause trouble 26 | return; 27 | } 28 | 29 | if ($this->flushed) { 30 | throw new \BadMethodCallException("The flush can be run only once and is run automatically in the end of each request to prevent data inconsistencies and bad design."); 31 | } 32 | 33 | $this->flushed = true; 34 | } 35 | 36 | public function getSubscribedEvents() 37 | { 38 | return [Events::preFlush]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/AppBundle/Fixture/Cms/LayoutBlocks.php: -------------------------------------------------------------------------------- 1 | container = $container; 22 | } 23 | 24 | /** 25 | * {@inheritDoc} 26 | */ 27 | public function getOrder() 28 | { 29 | return 0; 30 | } 31 | 32 | private function resource($name) 33 | { 34 | $location = $this->container->get('kernel')->locateResource("@AppBundle/Resources/views/blocks/layout/{$name}.html.twig"); 35 | return file_get_contents($location); 36 | } 37 | 38 | public function load(ObjectManager $manager) 39 | { 40 | $blocks = [ 41 | 'footer', 42 | ]; 43 | 44 | foreach ($blocks as $alias) { 45 | $block = new CmsBlock(); 46 | $block->setAlias($alias); 47 | $block->setName(implode(' ', array_map('ucfirst', explode('_', $alias)))); 48 | $block->setContent($this->resource($alias)); 49 | $manager->persist($block); 50 | } 51 | 52 | foreach (['css', 'js'] as $alias) { 53 | $block = new CmsBlock(); 54 | $block->setAlias($alias); 55 | $block->setName(implode(' ', array_map('ucfirst', explode('_', $alias)))); 56 | $block->setContent("/* cms block for {$alias} */"); 57 | $manager->persist($block); 58 | } 59 | 60 | $manager->flush(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/AppBundle/Fixture/Mail/RegistrationConfirm.php: -------------------------------------------------------------------------------- 1 | [ 24 | 'subject'=>'Activate Email', 25 | 'content'=>'click here {{ link }}', 26 | ], 27 | ]; 28 | 29 | foreach ($emails as $alias => $emailData) { 30 | $email = new MailTemplate(); 31 | $email->setAlias($alias); 32 | $email->setSubject($emailData['subject']); 33 | $email->setContent($emailData['content']); 34 | $manager->persist($email); 35 | } 36 | 37 | $manager->flush(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/AppBundle/Fixture/Users/DevUsers.php: -------------------------------------------------------------------------------- 1 | container = $container; 23 | } 24 | 25 | /** 26 | * {@inheritDoc} 27 | */ 28 | public function getOrder() 29 | { 30 | return 10; // may need some groups or over related stuff created before 31 | } 32 | 33 | /** 34 | * @param ObjectManager $em 35 | */ 36 | public function load(ObjectManager $em) 37 | { 38 | if (!in_array($this->container->getParameter('kernel.environment'), ['dev'])) { 39 | return; // only for dev environment 40 | } 41 | 42 | $faker = Factory::create(); 43 | $users = [ 44 | 'yoda' => ['ROLE_ADMIN'], 45 | 'luke' => ['ROLE_USER'], 46 | ]; 47 | foreach ($users as $username => $roles) { 48 | $user = new User(); 49 | $user->setFirstname($faker->firstname); 50 | $user->setLastname($faker->lastname); 51 | $user->setEmail($username . '@datadog.lt'); 52 | $user->setRoles($roles); 53 | 54 | $encoder = $this->container->get('security.encoder_factory')->getEncoder($user); 55 | $user->setPassword($encoder->encodePassword('S3cretpassword', $user->getSalt())); 56 | 57 | $em->persist($user); 58 | } 59 | $em->flush(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/AppBundle/Form/Type/User/ConfirmType.php: -------------------------------------------------------------------------------- 1 | add('firstname', 'text', [ 16 | 'label' => 'user.label.firstname', 17 | 'required' => true, 18 | ]) 19 | ->add('lastname', 'text', [ 20 | 'label' => 'user.label.lastname', 21 | 'required' => true, 22 | ]) 23 | ->add('plainPassword', 'repeated', [ 24 | 'type' => 'password', 25 | 'invalid_message' => 'Passwords does not match', 26 | 'required' => true, 27 | 'first_options' => ['label' => 'user.label.password'], 28 | 'second_options' => ['label' => 'user.label.repeat_password'], 29 | ]); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function configureOptions(OptionsResolver $resolver) 36 | { 37 | $resolver->setDefaults([ 38 | 'data_class' => 'AppBundle\Entity\User', 39 | 'validation_groups' => 'confirm', 40 | 'intention' => 'confirm', 41 | ]); 42 | } 43 | 44 | public function getName() 45 | { 46 | return 'confirm'; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/AppBundle/Form/Type/User/ProfileType.php: -------------------------------------------------------------------------------- 1 | add('firstname', 'text', [ 16 | 'label' => 'user.label.firstname', 17 | 'required' => true, 18 | ]) 19 | ->add('lastname', 'text', [ 20 | 'label' => 'user.label.lastname', 21 | 'required' => true, 22 | ]) 23 | ->add('plainPassword', 'repeated', [ 24 | 'type' => 'password', 25 | 'invalid_message' => 'user.label.password_mismatch', 26 | 'required' => true, 27 | 'first_options' => ['label' => 'user.label.password'], 28 | 'second_options' => ['label' => 'user.label.repeat_password'], 29 | ]); 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function configureOptions(OptionsResolver $resolver) 36 | { 37 | $resolver->setDefaults([ 38 | 'data_class' => 'AppBundle\Entity\User', 39 | 'validation_groups' => 'profile', 40 | 'intention' => 'profile', 41 | ]); 42 | } 43 | 44 | public function getName() 45 | { 46 | return 'profile'; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/AppBundle/Form/Type/User/ResetType.php: -------------------------------------------------------------------------------- 1 | add('email', 'email', [ 19 | 'label' => 'user.label.email', 20 | 'required' => true, 21 | 'constraints' => [ 22 | new NotBlank(['message' => 'Email address cannot be empty']), 23 | new Email(['message' => 'Email address is not valid']), 24 | ], 25 | ]) 26 | ->add('captcha', 'ewz_recaptcha', [ 27 | 'label' => 'user.reset.verification', 28 | 'constraints' => [ 29 | new RecaptchaTrue(['message'=>'Invalid verification code']) 30 | ], 31 | ]) 32 | ; 33 | 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function configureOptions(OptionsResolver $resolver) 40 | { 41 | $resolver->setDefaults([ 42 | 'data_class' => null, 43 | 'intention' => 'reset_password', 44 | ]); 45 | } 46 | 47 | public function getName() 48 | { 49 | return 'reset'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/AppBundle/Form/Type/User/SignupType.php: -------------------------------------------------------------------------------- 1 | add('email', 'email', [ 15 | 'label' => 'user.label.email', 16 | 'required' => true, 17 | ]); 18 | } 19 | 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function configureOptions(OptionsResolver $resolver) 24 | { 25 | $resolver->setDefaults([ 26 | 'data_class' => 'AppBundle\Entity\User', 27 | 'validation_groups' => 'signup', 28 | 'intention' => 'signup', 29 | ]); 30 | } 31 | 32 | public function getName() 33 | { 34 | return 'signup'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/AppBundle/Mailer/ContactInterface.php: -------------------------------------------------------------------------------- 1 | mailer = $mailer; 43 | $this->twig = $twig; 44 | $this->em = $em; 45 | $this->sender = $sender; 46 | } 47 | 48 | /** 49 | * @param ContactInterface $contact 50 | * @param string $alias 51 | * @param array $data 52 | */ 53 | public function user(ContactInterface $contact, $alias, $data = []) 54 | { 55 | /** @var MailTemplate $template */ 56 | $template = $this->em->getRepository('AppBundle:MailTemplate')->findOneBy(['alias'=>$alias]); 57 | 58 | if (!$template) { 59 | throw new \InvalidArgumentException(sprintf("Template %s does not exist", $alias)); 60 | } 61 | 62 | $this->send([$contact->getEmail() => $contact->getFullName()], $template, ['user' => $contact] + $data); 63 | } 64 | 65 | /** 66 | * @param MailTemplate $template 67 | * @param array $data 68 | * @return string 69 | */ 70 | protected function render(MailTemplate $template, array $data) 71 | { 72 | return $this->twig->render("AppBundle:Mail:template.html.twig", compact('template') + $data); 73 | } 74 | 75 | /** 76 | * @param string|array $to 77 | * @param MailTemplate $template 78 | * @param array $data 79 | */ 80 | private function send($to, MailTemplate $template, array $data = []) 81 | { 82 | $body = $this->render($template, $data); 83 | 84 | $message = new \Swift_Message($template->getSubject(), $body, "text/html", "utf-8"); 85 | $message->setFrom($this->sender); 86 | $message->setTo($to); 87 | 88 | $this->mailer->send($message); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/AppBundle/Menu/MenuBuilder.php: -------------------------------------------------------------------------------- 1 | createItem('root'); 19 | $menu->setChildrenAttribute('class', 'nav navbar-nav pull-right'); 20 | 21 | // about 22 | $menu->addChild($this->trans('about'), ['route' => 'app_home_about', 'attributes' => [ 23 | 'role' => 'presentation', 24 | 'icon' => 'fa fa-book', 25 | ]]); 26 | $user = $this->getUser(); 27 | 28 | if ($user instanceof UserInterface) { 29 | // dropdown 30 | $dropdown = $menu->addChild($user, ['attributes' => [ 31 | 'role' => 'presentation', 32 | 'dropdown' => true, 33 | 'icon' => 'fa fa-user', 34 | ]]); 35 | // profile 36 | $dropdown->addChild($this->trans('profile'), ['route' => 'app_user_profile', 'attributes' => [ 37 | 'role' => 'presentation', 38 | 'icon' => 'fa fa-child', 39 | ]]); 40 | // administration 41 | if ($user->hasRole('ROLE_ADMIN')) { 42 | $dropdown->addChild($this->trans('admin'), ['route' => 'admin', 'attributes' => [ 43 | 'role' => 'presentation', 44 | 'icon' => 'fa fa-beer', 45 | ]]); 46 | } 47 | // logout 48 | $dropdown->addChild($this->trans('logout'), ['route' => 'app_user_logout', 'attributes' => [ 49 | 'role' => 'presentation', 50 | 'icon' => 'fa fa-sign-out', 51 | ]]); 52 | } 53 | 54 | if (!$user instanceof UserInterface) { 55 | // signin 56 | $menu->addChild($this->trans('login'), ['route' => 'app_user_login', 'attributes' => [ 57 | 'role' => 'presentation', 58 | 'icon' => 'fa fa-sign-in', 59 | ]]); 60 | // signup 61 | $menu->addChild($this->trans('sign_up'), ['route' => 'app_user_signup', 'attributes' => [ 62 | 'role' => 'presentation', 63 | 'icon' => 'fa fa-user-plus', 64 | ]]); 65 | } 66 | 67 | return $menu; 68 | } 69 | 70 | /** 71 | * @return UserInterface 72 | */ 73 | private function getUser() 74 | { 75 | if (!$this->container->has('security.token_storage')) { 76 | throw new \LogicException('The SecurityBundle is not registered in your application.'); 77 | } 78 | 79 | $token = $this->container->get('security.token_storage')->getToken(); 80 | if (!$token instanceof TokenInterface) { 81 | return null; 82 | } 83 | 84 | return $token->getUser(); 85 | } 86 | 87 | /** 88 | * @param string $label 89 | * @return string 90 | */ 91 | private function trans($label) 92 | { 93 | return $this->container->get('translator')->trans($label, [], 'menu'); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/AppBundle/Menu/RequestVoter.php: -------------------------------------------------------------------------------- 1 | isMasterRequest()) { 16 | return; 17 | } 18 | $this->request = $event->getRequest(); 19 | } 20 | 21 | public function matchItem(ItemInterface $item) 22 | { 23 | if (null === $this->request) { 24 | return null; 25 | } 26 | 27 | if ($item->getUri() === $this->request->getRequestUri()) { 28 | // URL's completely match 29 | return true; 30 | } 31 | 32 | if ($item->getUri() !== $this->request->getBaseUrl() . '/' && (substr($this->request->getRequestUri(), 0, strlen($item->getUri())) === $item->getUri())) { 33 | // URL isn't just "/" and the first part of the URL match 34 | return true; 35 | } 36 | 37 | return null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/config/cache/dev.yml: -------------------------------------------------------------------------------- 1 | services: 2 | cache.default: @doctrine.orm.default_query_cache # array cache for dev 3 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/config/cache/prod.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | redis_timeout: 5 3 | redis_db_default: 0 4 | redis_db_session: 1 5 | 6 | services: 7 | cache.connection.default: 8 | class: Predis\Client 9 | arguments: 10 | - { host: %redis_host%, port: %redis_port%, database: %redis_db_default% } 11 | 12 | cache.connection.session: 13 | class: Predis\Client 14 | arguments: 15 | - { host: %redis_host%, port: %redis_port%, database: %redis_db_session% } 16 | 17 | cache.default: 18 | class: AppBundle\Cache\RedisCache 19 | arguments: 20 | - @cache.connection.default 21 | - %cache_namespace% 22 | 23 | session.handler.redis: 24 | class: AppBundle\Cache\Session\RedisSessionHandler 25 | arguments: 26 | - @cache.connection.session 27 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/config/cache/test.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: prod.yml } 3 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/config/listeners/doctrine.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | doctrine.flush.listener: 4 | class: AppBundle\EventListener\FlushSubscriber 5 | tags: 6 | - { name: doctrine.event_subscriber, connection: default } 7 | 8 | doctrine.timestampable.listener: 9 | class: Gedmo\Timestampable\TimestampableListener 10 | tags: 11 | - { name: doctrine.event_subscriber, connection: default } 12 | calls: 13 | - [ setAnnotationReader, [ @annotation_reader ] ] 14 | 15 | # sluggable: 16 | # class: Gedmo\Sluggable\SluggableListener 17 | # tags: 18 | # - { name: doctrine.event_subscriber, connection: default } 19 | # calls: 20 | # - [ setAnnotationReader, [ @annotation_reader ] ] 21 | 22 | # doctrine_extensions.listener: 23 | # class: AppBundle\EventListener\DoctrineExtensionsListener 24 | # arguments: [ @translatable ] 25 | # tags: 26 | # # translatable sets locale after router processing 27 | # - { name: kernel.event_listener, event: kernel.request, method: onLateKernelRequest, priority: -10 } 28 | 29 | # translatable: 30 | # class: Gedmo\Translatable\TranslatableListener 31 | # tags: 32 | # - { name: doctrine.event_subscriber, connection: default } 33 | # calls: 34 | # - [ setAnnotationReader, [ @annotation_reader ] ] 35 | # - [ setDefaultLocale, [ %locale% ] ] 36 | # - [ setTranslatableLocale, [ %locale% ] ] 37 | # - [ setTranslationFallback, [ true ] ] 38 | # - [ setPersistDefaultLocaleTranslation, [false] ] 39 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/config/listeners/kernel.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | kernel.flush.listener: 4 | class: AppBundle\EventListener\FlushListener 5 | arguments: [@doctrine.orm.entity_manager, @doctrine.flush.listener] 6 | tags: 7 | - { name: kernel.event_listener, event: kernel.response, method: onLateKernelResponse, priority: -255 } 8 | - { name: kernel.event_listener, event: kernel.request, method: onEarlyKernelRequest, priority: 255 } 9 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/config/mailer.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mail: 3 | class: AppBundle\Mailer\Mailer 4 | arguments: 5 | - @mailer 6 | - @templating 7 | - @em 8 | - %mailer_sender% 9 | 10 | twig.extension.string_loader: 11 | class: Twig_Extension_StringLoader 12 | tags: 13 | - { name: twig.extension } 14 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/config/menu.yml: -------------------------------------------------------------------------------- 1 | services: 2 | bootstrap.menu.voter: 3 | class: AppBundle\Menu\RequestVoter 4 | tags: 5 | - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } 6 | - { name: knp_menu.voter } 7 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/config/routing.yml: -------------------------------------------------------------------------------- 1 | app: 2 | resource: @AppBundle/Controller/ 3 | type: annotation 4 | 5 | app_user_check: 6 | path: /login_check 7 | 8 | app_user_logout: 9 | path: /logout 10 | 11 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/config/security.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app.password_encoder: 3 | class: AppBundle\Security\Core\Encoder\BCryptPasswordEncoder 4 | arguments: [12] # cost 5 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/config/twig.yml: -------------------------------------------------------------------------------- 1 | services: 2 | twig.extension.cms_block: 3 | class: AppBundle\Twig\CMSBlockExtension 4 | arguments: [@doctrine.orm.entity_manager] 5 | tags: 6 | - { name: twig.extension } 7 | 8 | twig.extension.time_ago: 9 | class: AppBundle\Twig\TimeExtension 10 | arguments: [@translator] 11 | tags: 12 | - { name: twig.extension } 13 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/translations/menu.en.yml: -------------------------------------------------------------------------------- 1 | about: "About" 2 | profile: "Profile" 3 | admin: "Admin area" 4 | logout: "Logout" 5 | login: "Login" 6 | sign_up: "Sign-up" 7 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/translations/messages.en.yml: -------------------------------------------------------------------------------- 1 | project_name: "Symfony Force Edition" 2 | 3 | user: 4 | confirm: 5 | title: "Account details" 6 | submit: 'Confirm' 7 | 8 | login: 9 | title: "Login" 10 | username: "Username" 11 | password: "Password" 12 | forgot: "Forgot username or password?" 13 | submit: "Login" 14 | incorrect_credentials: 'Email or password is incorrect' 15 | account_disabled: 'Account is disabled' 16 | 17 | reset: 18 | title: "Reset password" 19 | submit: 'Reset' 20 | verification: 'Verification' 21 | confirmation_sent: 'Confirmation email was successfully resent to %email%' 22 | user_not_found: 'User with this email not found' 23 | flash: 24 | email_sent: 'You should receive confirmation email shortly' 25 | password_sent: 'Confirmation email was successfully resent to %email%' 26 | 27 | signup: 28 | title: 'Signup' 29 | submit: 'Signup' 30 | already_confirmed: 'Email %email% is already confirmed' 31 | 32 | profile: 33 | title: 'Account profile' 34 | change_password: 'Change password' 35 | submit: 'Update' 36 | details: 'Details' 37 | flash: 38 | updated: 'Your profile was updated successfully' 39 | 40 | label: 41 | password_mismatch: 'Passwords does not match' 42 | 43 | flash: 44 | confirmed: 'The user %user% was successfully confirmed' 45 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/translations/time.en.yml: -------------------------------------------------------------------------------- 1 | ago: 2 | year: "1 year ago|%count% years ago" 3 | month: "1 month ago|%count% months ago" 4 | day: "1 day ago|%count% days ago" 5 | hour: "1 hour ago|%count% hours ago" 6 | minute: "1 minute ago|%count% minutes ago" 7 | second: "1 second ago|%count% seconds ago" 8 | 9 | empty: now 10 | 11 | in: 12 | second: "in 1 second|in %count% seconds" 13 | minute: "in 1 minute|in %count% minutes" 14 | hour: "in 1 hour|in %count% hours" 15 | day: "in 1 day|in %count% days" 16 | month: "in 1 month|in %count% months" 17 | year: "in 1 year|in %count% years" 18 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/Home/about.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AppBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 | 5 | {% include 'AppBundle::flashes.html.twig' %} 6 | 7 |
8 |
9 |

Could not resist.. Want some background sound?

10 |
DATA-DOG team
11 |
12 | 13 |
14 | 15 |
16 |
17 | 18 |
19 | 20 | {% endblock %} 21 | 22 | {% block js %} 23 | 38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/Home/homepage.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AppBundle::layout.html.twig' %} 2 | 3 | {% block content %} 4 | 5 | {% include 'AppBundle::flashes.html.twig' %} 6 | 7 |
8 | 36 |
37 | 38 |
39 |
40 |
41 |
42 | 43 |
44 |
45 |

Making repository interfaces I was to run my Behat tests faster before. Without any change 85% faster 46 | now they run.

47 | Yoda 48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 |

it is not big enough for Deathstar enterprise software and it does not run on Windows!?

56 | Darth Vader 57 |
58 |
59 | 60 |
61 |
62 |
63 | 64 |
65 |
66 |
67 | 68 |
69 |
70 |

RRRAARRWHHGWWR sherlock. And there can be only a single main transaction during the request.

71 | Chewbacca 72 |
73 |
74 |
75 | 76 |
77 |
78 |
79 |

My drones cannot smuggle space heroin, all database changes are audited.

80 | General Grievous 81 |
82 |
83 | 84 |
85 |
86 |
87 |
88 | 89 |
90 | 91 | {% endblock %} 92 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/Mail/template.html.twig: -------------------------------------------------------------------------------- 1 |

{{ template.subject }}

2 | 3 | {% include(template_from_string(template.content)) %} 4 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/User/confirm.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AppBundle::layout.html.twig' %} 2 | 3 | {% set title = "user.confirm.title"|trans %} 4 | 5 | {% block content %} 6 | 7 | {% include 'AppBundle::flashes.html.twig' %} 8 | 9 |
10 | 11 | 14 | 15 | {{ form_start(form, {action: path('app_user_confirm', {token: token})}) }} 16 | {{ form_row(form.firstname) }} 17 | {{ form_row(form.lastname) }} 18 | {{ form_row(form.plainPassword) }} 19 | 20 |
21 |
22 | 23 |
24 |
25 | {{ form_end(form) }} 26 |
27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/User/login.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AppBundle::layout.html.twig' %} 2 | 3 | {% set title = 'user.login.title'|trans %} 4 | 5 | {% block content %} 6 | 7 | {% include 'AppBundle::flashes.html.twig' %} 8 | {% if error %} 9 |
10 | {{ error }} 11 |
12 | {% endif %} 13 | 14 |
15 | 16 | 19 | 20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 | 37 |
38 |
39 | 40 |
41 |
42 |
43 |
44 | {% endblock %} 45 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/User/profile.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AppBundle::layout.html.twig' %} 2 | 3 | {% set title = "user.profile.title"|trans %} 4 | 5 | {% block content %} 6 | 7 | {% include 'AppBundle::flashes.html.twig' %} 8 | 9 |
10 | 11 | 14 | 15 | {{ form_start(form, {action: path('app_user_profile')}) }} 16 |

{{ 'user.profile.details'|trans }}

17 |
18 | {{ form_row(form.firstname) }} 19 | {{ form_row(form.lastname) }} 20 | 21 |

{{ 'user.profile.change_password'|trans }}

22 |
23 | {{ form_row(form.plainPassword) }} 24 | 25 |
26 |
27 | 28 |
29 |
30 | {{ form_end(form) }} 31 |
32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/User/reset.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AppBundle::layout.html.twig' %} 2 | 3 | {% set title = 'user.reset.title'|trans %} 4 | 5 | {% block content %} 6 | 7 | {% include 'AppBundle::flashes.html.twig' %} 8 | 9 |
10 | 11 | 14 | 15 | {{ form_start(form, {action: path('app_user_reset')}) }} 16 | {{ form_row(form.email) }} 17 | {{ form_row(form.captcha) }} 18 | 19 |
20 |
21 | 22 |
23 |
24 | {{ form_end(form) }} 25 |
26 | {% endblock %} 27 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/User/signup.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'AppBundle::layout.html.twig' %} 2 | 3 | {% set title = "user.signup.title"|trans %} 4 | 5 | {% block content %} 6 | 7 | {% include 'AppBundle::flashes.html.twig' %} 8 | 9 |
10 | 11 | 14 | 15 | {{ form_start(form, {action: path('app_user_signup')}) }} 16 | {{ form_row(form.email) }} 17 | 18 |
19 |
20 | 21 |
22 |
23 | {{ form_end(form) }} 24 |
25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/blocks/layout/footer.html.twig: -------------------------------------------------------------------------------- 1 |

© {{ "now"|date("Y") }} DATA-DOG

2 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/flashes.html.twig: -------------------------------------------------------------------------------- 1 | {% set flashIconMap = {danger: 'exclamation-circle', success: 'check-circle', info: 'info-circle'} %} 2 | {% for type, bag in app.session.flashbag.all() %} 3 | {% for message in bag %} 4 |
5 | 6 | {{ message }} 7 |
8 | {% endfor %} 9 | {% endfor %} 10 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/forms.html.twig: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap_3_horizontal_layout.html.twig" %} 2 | 3 | {# adds novalidate to prevent html5 validation#} 4 | {% block form_start -%} 5 | {% set attr = attr|merge({novalidate: null}) %} 6 | {{- parent() -}} 7 | {%- endblock form_start %} 8 | 9 | -------------------------------------------------------------------------------- /src/AppBundle/Resources/views/layout.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '::base.html.twig' %} 2 | 3 | {% block body %} 4 |
5 |
6 | 7 |

{% trans %}project_name{% endtrans %}

8 |
9 | 10 | {% block content %}{% endblock %} 11 | 12 |
13 | {% include(cms_block('footer')) %} 14 |
15 |
16 | {% endblock body %} 17 | -------------------------------------------------------------------------------- /src/AppBundle/Security/Core/Encoder/BCryptPasswordEncoder.php: -------------------------------------------------------------------------------- 1 | assertSame([], $user->getRoles()); 16 | } 17 | 18 | /** 19 | * @test 20 | */ 21 | public function should_be_able_to_add_defined_role() 22 | { 23 | $user = new User; 24 | $user->addRole('ROLE_ADMIN'); 25 | $this->assertSame($user->getRoles(), ['ROLE_ADMIN']); 26 | } 27 | 28 | /** 29 | * @test 30 | */ 31 | public function should_skip_adding_undefined_role() 32 | { 33 | $user = new User; 34 | $user->addRole('ROLE_UNDEFINED'); 35 | $this->assertSame([], $user->getRoles()); 36 | } 37 | 38 | /** 39 | * @test 40 | */ 41 | public function should_be_able_to_remove_defined_role() 42 | { 43 | $user = new User; 44 | $user->setRoles(['ROLE_ADMIN', 'ROLE_USER']); 45 | $user->removeRole('ROLE_ADMIN'); 46 | 47 | $this->assertSame(['ROLE_USER'], $user->getRoles()); 48 | } 49 | 50 | /** 51 | * @test 52 | */ 53 | public function should_not_be_able_to_remove_undefined_role() 54 | { 55 | $user = new User; 56 | $user->setRoles(['ROLE_ADMIN', 'ROLE_USER']); 57 | $user->removeRole('ROLE_UNDEFINED'); 58 | 59 | $this->assertSame(['ROLE_USER', 'ROLE_ADMIN'], $user->getRoles()); 60 | } 61 | 62 | /** 63 | * @test 64 | */ 65 | public function should_add_user_role_when_confirmed() 66 | { 67 | $user = new User; 68 | $user->confirm(); 69 | $this->assertSame(['ROLE_USER'], $user->getRoles()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/AppBundle/Twig/CMSBlockExtension.php: -------------------------------------------------------------------------------- 1 | repo = $em->getRepository('AppBundle:CmsBlock'); 22 | } 23 | 24 | /** 25 | * Returns the name of the extension. 26 | * 27 | * @return string The extension name 28 | */ 29 | public function getName() 30 | { 31 | return 'cms_block'; 32 | } 33 | 34 | /** 35 | * Returns a list of functions to add to the existing list. 36 | * 37 | * @return array An array of functions 38 | */ 39 | public function getFunctions() 40 | { 41 | return [ 42 | new \Twig_SimpleFunction('cms_block', [$this, 'renderBlock'], ['needs_environment' => true]), 43 | ]; 44 | } 45 | 46 | /** 47 | * @param \Twig_Environment $twig 48 | * @param string $alias 49 | * @return \Twig_Template 50 | * @throws \InvalidArgumentException 51 | */ 52 | public function renderBlock(\Twig_Environment $twig, $alias) 53 | { 54 | $block = $this->repo->createQueryBuilder('b') 55 | ->where('b.alias = :alias') 56 | ->setParameters(compact('alias')) 57 | ->setMaxResults(1) 58 | ->getQuery() 59 | ->useResultCache(true) 60 | ->setResultCacheId('cms_block.' . $alias) 61 | ->getResult(); 62 | 63 | $block = current($block); 64 | if (!$block) { 65 | throw new \InvalidArgumentException(sprintf("CMS block '%s' could not be found", $alias)); 66 | } 67 | 68 | return $twig->createTemplate($block->getContent()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/AppBundle/Twig/TimeExtension.php: -------------------------------------------------------------------------------- 1 | translator = $translator; 19 | } 20 | 21 | public function getFunctions() 22 | { 23 | return [ 24 | new \Twig_SimpleFunction('time_diff', [$this, 'diff'], ['is_safe' => ['html']]) 25 | ]; 26 | } 27 | 28 | public function diff($since = null, $to = null) 29 | { 30 | foreach (['since', 'to'] as $var) { 31 | if ($$var instanceof \DateTime) { 32 | continue; 33 | } 34 | if (is_integer($$var)) { 35 | $$var = date('Y-m-d H:i:s', $$var); 36 | } 37 | $$var = new \DateTime($$var); 38 | } 39 | 40 | static $units = [ 41 | 'y' => 'year', 42 | 'm' => 'month', 43 | 'd' => 'day', 44 | 'h' => 'hour', 45 | 'i' => 'minute', 46 | 's' => 'second' 47 | ]; 48 | 49 | $diff = $to->diff($since); 50 | foreach ($units as $attr => $unit) { 51 | $count = $diff->{$attr}; 52 | if (0 !== $count) { 53 | $id = sprintf('%s.%s', $diff->invert ? 'ago' : 'in', $unit); 54 | return $this->translator->transChoice($id, $count, ['%count%' => $count], 'time'); 55 | } 56 | } 57 | return $this->translator->trans('empty', [], 'time'); 58 | } 59 | 60 | public function getName() 61 | { 62 | return 'time'; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /web/app.php: -------------------------------------------------------------------------------- 1 | unregister(); 15 | $apcLoader->register(true); 16 | */ 17 | 18 | require_once __DIR__.'/../app/AppKernel.php'; 19 | //require_once __DIR__.'/../app/AppCache.php'; 20 | 21 | $kernel = new AppKernel('prod', false); 22 | $kernel->loadClassCache(); 23 | //$kernel = new AppCache($kernel); 24 | 25 | // When using the HttpCache, you need to call the method in your front controller instead of relying on the configuration parameter 26 | //Request::enableHttpMethodParameterOverride(); 27 | $request = Request::createFromGlobals(); 28 | $response = $kernel->handle($request); 29 | $response->send(); 30 | $kernel->terminate($request, $response); 31 | -------------------------------------------------------------------------------- /web/app_dev.php: -------------------------------------------------------------------------------- 1 | loadClassCache(); 20 | $request = Request::createFromGlobals(); 21 | $response = $kernel->handle($request); 22 | $response->send(); 23 | $kernel->terminate($request, $response); 24 | -------------------------------------------------------------------------------- /web/app_test.php: -------------------------------------------------------------------------------- 1 | loadClassCache(); 15 | 16 | $request = Request::createFromGlobals(); 17 | $response = $kernel->handle($request); 18 | $response->send(); 19 | $kernel->terminate($request, $response); 20 | -------------------------------------------------------------------------------- /web/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/web/apple-touch-icon.png -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DATA-DOG/symfony-force/bbb8b070acdd2446e16672c2e491ff67a10fc3b6/web/favicon.ico -------------------------------------------------------------------------------- /web/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 3 | 4 | User-agent: * 5 | --------------------------------------------------------------------------------