Inital commit

This commit is contained in:
2020-11-19 15:36:28 +01:00
parent 71f32f83d3
commit 66ce4ee218
18077 changed files with 2166122 additions and 35184 deletions

56
.gitignore vendored Executable file → Normal file
View File

@@ -1,53 +1,3 @@
local/config/build.properties
local/config/config_db.php
local/config/build
local/config/database.yml
local/config/database.yml.sample.save
core/vendor
local/config/runtime-conf.xml
cache/*
log/*
bin/*
local/session/*
coverage
.idea
.buildpath
.project
.settings/
local/media/documents/*
local/media/images/*
web/assets/*
web/cache/*
web/tinymce
web/media
phpdoc*.log
xhprof/
phpunit.phar
.DS_Store
phpmyadmin
composer.phar
web/.htaccess
# Ignore everything in the "modules" directory, except the "default modules"
local/modules/*
!local/modules/Cheque/
!local/modules/Front/
!local/modules/TheliaDebugBar/
!local/modules/Tinymce/
!local/modules/Colissimo/
# Ignore everything in the "templates" directory, except the "default template"
templates/*
!templates/backOffice/
!templates/email/
!templates/frontOffice/
!templates/pdf/
#Ignore CodeKit
codekit-config.json
config.codekit
.codekit-cache
# Ignore casperjs screenshots
tests/functionnal/casperjs/screenshot/
/cache/
/log/
/web/cache/

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "local/modules/Klikandpay"]
path = local/modules/Klikandpay
url = https://github.com/thelia-modules/Klikandpay

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/thelia.iml" filepath="$PROJECT_DIR$/.idea/thelia.iml" />
</modules>
</component>
</project>

95
.idea/php.xml generated Normal file
View File

@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/core/vendor/phpunit/phpunit-mock-objects" />
<path value="$PROJECT_DIR$/core/vendor/symfony/security-acl" />
<path value="$PROJECT_DIR$/core/vendor/oyejorge/less.php" />
<path value="$PROJECT_DIR$/core/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/core/vendor/symfony/form" />
<path value="$PROJECT_DIR$/core/vendor/symfony/expression-language" />
<path value="$PROJECT_DIR$/core/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/core/vendor/symfony/options-resolver" />
<path value="$PROJECT_DIR$/core/vendor/symfony/security" />
<path value="$PROJECT_DIR$/core/vendor/symfony/class-loader" />
<path value="$PROJECT_DIR$/core/vendor/symfony/validator" />
<path value="$PROJECT_DIR$/core/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/core/vendor/phpunit/php-token-stream" />
<path value="$PROJECT_DIR$/core/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/core/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/core/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/core/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/core/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/core/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/core/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/core/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/core/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/core/vendor/fzaninotto/faker" />
<path value="$PROJECT_DIR$/core/vendor/phenx/php-font-lib" />
<path value="$PROJECT_DIR$/core/vendor/ptachoire/cssembed" />
<path value="$PROJECT_DIR$/core/vendor/psr/cache" />
<path value="$PROJECT_DIR$/core/vendor/simplepie/simplepie" />
<path value="$PROJECT_DIR$/core/vendor/psr/log" />
<path value="$PROJECT_DIR$/core/vendor/swiftmailer/swiftmailer" />
<path value="$PROJECT_DIR$/core/vendor/phpdocumentor/reflection-docblock" />
<path value="$PROJECT_DIR$/core/vendor/kriswallsmith/assetic" />
<path value="$PROJECT_DIR$/core/vendor/smarty/smarty" />
<path value="$PROJECT_DIR$/core/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/core/vendor/paragonie/random_compat" />
<path value="$PROJECT_DIR$/core/vendor/composer" />
<path value="$PROJECT_DIR$/core/vendor/maximebf/debugbar" />
<path value="$PROJECT_DIR$/core/vendor/thelia/installer" />
<path value="$PROJECT_DIR$/core/vendor/thelia/currency-converter" />
<path value="$PROJECT_DIR$/core/vendor/thelia/math-tools" />
<path value="$PROJECT_DIR$/core/vendor/leafo/lessphp" />
<path value="$PROJECT_DIR$/core/vendor/doctrine/cache" />
<path value="$PROJECT_DIR$/core/vendor/phpspec/prophecy" />
<path value="$PROJECT_DIR$/core/vendor/ircmaxell/password-compat" />
<path value="$PROJECT_DIR$/core/vendor/propel/propel" />
<path value="$PROJECT_DIR$/core/vendor/doctrine/collections" />
<path value="$PROJECT_DIR$/core/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/core/vendor/ramsey/array_column" />
<path value="$PROJECT_DIR$/core/vendor/ensepar/html2pdf" />
<path value="$PROJECT_DIR$/core/vendor/ensepar/tcpdf" />
<path value="$PROJECT_DIR$/core/vendor/stack/builder" />
<path value="$PROJECT_DIR$/core/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/core/vendor/symfony-cmf/routing" />
<path value="$PROJECT_DIR$/core/vendor/commerceguys/addressing" />
<path value="$PROJECT_DIR$/core/vendor/symfony/config" />
<path value="$PROJECT_DIR$/core/vendor/commerceguys/enum" />
<path value="$PROJECT_DIR$/core/vendor/symfony/process" />
<path value="$PROJECT_DIR$/core/vendor/symfony/polyfill-util" />
<path value="$PROJECT_DIR$/core/vendor/symfony/console" />
<path value="$PROJECT_DIR$/core/vendor/symfony/property-access" />
<path value="$PROJECT_DIR$/core/vendor/symfony/polyfill-php70" />
<path value="$PROJECT_DIR$/core/vendor/michelf/php-markdown" />
<path value="$PROJECT_DIR$/core/vendor/symfony/browser-kit" />
<path value="$PROJECT_DIR$/core/vendor/symfony/locale" />
<path value="$PROJECT_DIR$/core/vendor/imagine/imagine" />
<path value="$PROJECT_DIR$/core/vendor/symfony/serializer" />
<path value="$PROJECT_DIR$/core/vendor/symfony/routing" />
<path value="$PROJECT_DIR$/core/vendor/symfony/polyfill-intl-icu" />
<path value="$PROJECT_DIR$/core/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/core/vendor/symfony/polyfill-php56" />
<path value="$PROJECT_DIR$/core/vendor/symfony/debug" />
<path value="$PROJECT_DIR$/core/vendor/symfony/dependency-injection" />
<path value="$PROJECT_DIR$/core/vendor/symfony/http-foundation" />
<path value="$PROJECT_DIR$/core/vendor/symfony/intl" />
<path value="$PROJECT_DIR$/core/vendor/symfony/http-kernel" />
<path value="$PROJECT_DIR$/core/vendor/symfony/filesystem" />
<path value="$PROJECT_DIR$/core/vendor/symfony/icu" />
<path value="$PROJECT_DIR$/core/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/core/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/core/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/core/vendor/symfony/dom-crawler" />
<path value="$PROJECT_DIR$/core/vendor/symfony/polyfill-php55" />
<path value="$PROJECT_DIR$/core/vendor/symfony/polyfill-php54" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="5.5.0" />
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings configuration_file_path="$PROJECT_DIR$/phpunit.xml" custom_loader_path="$PROJECT_DIR$/core/vendor/autoload.php" use_configuration_file="true" />
</phpunit_settings>
</component>
</project>

12
.idea/phpunit.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PHPUnit">
<option name="directories">
<list>
<option value="$PROJECT_DIR$/tests" />
<option value="$PROJECT_DIR$/local/modules/TheliaSmarty/Tests" />
<option value="$PROJECT_DIR$/local/modules/FlatFeeDelivery/Tests" />
</list>
</option>
</component>
</project>

98
.idea/thelia.iml generated Normal file
View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/tests/phpunit/Thelia/Tests" isTestSource="true" packagePrefix="Thelia\Tests\" />
<sourceFolder url="file://$MODULE_DIR$/core/lib/Thelia" isTestSource="false" packagePrefix="Thelia\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/local/modules" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/local/modules/TheliaSmarty/Tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/local/modules/FlatFeeDelivery/Tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/commerceguys/addressing" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/commerceguys/enum" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/doctrine/cache" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/doctrine/collections" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/doctrine/instantiator" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/ensepar/html2pdf" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/ensepar/tcpdf" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/fzaninotto/faker" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/imagine/imagine" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/ircmaxell/password-compat" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/kriswallsmith/assetic" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/leafo/lessphp" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/maximebf/debugbar" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/michelf/php-markdown" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/oyejorge/less.php" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/paragonie/random_compat" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/phenx/php-font-lib" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/phpdocumentor/reflection-docblock" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/phpspec/prophecy" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/phpunit/php-code-coverage" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/phpunit/php-file-iterator" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/phpunit/php-text-template" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/phpunit/php-timer" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/phpunit/php-token-stream" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/phpunit/phpunit" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/phpunit/phpunit-mock-objects" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/propel/propel" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/psr/cache" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/psr/log" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/ptachoire/cssembed" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/ramsey/array_column" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/sebastian/diff" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/sebastian/environment" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/sebastian/exporter" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/sebastian/global-state" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/sebastian/recursion-context" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/sebastian/version" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/simplepie/simplepie" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/smarty/smarty" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/stack/builder" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/swiftmailer/swiftmailer" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony-cmf/routing" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/browser-kit" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/cache" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/class-loader" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/config" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/console" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/debug" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/dependency-injection" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/dom-crawler" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/expression-language" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/filesystem" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/finder" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/form" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/http-foundation" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/http-kernel" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/icu" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/intl" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/locale" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/options-resolver" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/polyfill-intl-icu" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/polyfill-php54" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/polyfill-php55" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/polyfill-php56" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/polyfill-php70" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/polyfill-util" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/process" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/property-access" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/routing" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/security" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/security-acl" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/serializer" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/translation" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/validator" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/symfony/yaml" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/thelia/currency-converter" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/thelia/installer" />
<excludeFolder url="file://$MODULE_DIR$/core/vendor/thelia/math-tools" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

7
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/local/modules/WireTransfer" vcs="Git" />
</component>
</project>

18241
.idea/workspace.xml generated Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

51
CONTRIBUTORS.md Normal file
View File

@@ -0,0 +1,51 @@
CONTRIBUTORS
============
If you contributes or contributed to this project and do not appear in this list below,
please email us (info@thelia.net) or fork this file on GitHub and send a pull-request.
- Manuel Raynaud (lunika)
- Franck Allimant (roadster31)
- Etienne Roudeix (etienneroudeix)
- Benjamin Perche (lovenunu)
- Julien Chanseaume (bibich)
- Christophe Laffont (touffies)
- Michaël Espeche (mespeche)
- Gilles Bourgeat (gillesbourgeat)
- Guillaume Morel (gmorel)
- Yochima (Yochima)
- Goleo Bruno (badsuricate)
- (zzuutt)
- Jérôme Billiras (bilhackmac)
- Emmanuel Nurit (enurit)
- (Asturyan)
- Etienne Perriere (Mertiozys)
- (griotteau)
- Alban Baixas (Alban-io)
- Christophe (InformatiqueProg)
- (jodeq)
- (nicolasleon)
- Chevrier (AnthonyMeedle)
- Romain Ducher (air-dex)
- Antony Penalver (Soldras)
- David Rimbault (Id4v)
- Stéphanie Pinet (stephaniepinet)
- Damien Foulhoux (Lucanis)
- Vincent Lopes-Vicente (lopes-vincent)
- (alex63530)
- Arnault Pachot (apachot)
- Cédric Sibaud (csibaud)
- Stéphane Calisti (csteph85)
- Alex Rock Ancelet (Pierstoval)
- Baptiste Cabarrou (bcabarrou)
- Adrien Bourroux (driou)
- (AdeDidou)
- DirtyF (DirtyF)
- (xockduo)
- Michaël Marinetti (Asenar)
- Mickaël (Kira-kyuukyoku)
- Quentin Dufour (superboum)
- Lespes (knjeurope)
- Tarun Garg (tarun1793)
- poum (poum)
- Luis Cordova (cordoval)

View File

@@ -1,4 +1,4 @@
THELIA
Copyright (C) 2005-2014 OpenStudio
Copyright (C) 2005-2015 OpenStudio
THELIA application uses externals components and libraries which are released under their own GPL compatible license terms.
THELIA application uses externals components and libraries which are released under their own LGPL compatible license terms.

122
Readme.md
View File

@@ -1,46 +1,42 @@
Readme
======
## Warning
### This is the development repository of Thelia. If you want to create a project, please take a look at [thelia/thelia-project](https://github.com/thelia/thelia-project)
Thelia
------
[![Build Status](https://travis-ci.org/thelia/thelia.png?branch=master)](https://travis-ci.org/thelia/thelia) [![License](https://poser.pugx.org/thelia/thelia/license.png)](https://packagist.org/packages/thelia/thelia) [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/thelia/thelia/badges/quality-score.png?s=61e3e04a69bffd71c29b08e5392080317a546716)](https://scrutinizer-ci.com/g/thelia/thelia/)
[![Build Status](https://travis-ci.org/thelia/thelia.png?branch=master)](https://travis-ci.org/thelia/thelia)
[![License](https://poser.pugx.org/thelia/thelia/license.png)](https://packagist.org/packages/thelia/thelia)
[![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/thelia/thelia/badges/quality-score.png?s=61e3e04a69bffd71c29b08e5392080317a546716)](https://scrutinizer-ci.com/g/thelia/thelia/)
[Thelia](http://thelia.net/) is an open source tool for creating e-business websites and managing online content. This software is published under LGPL.
[Thelia](https://thelia.net/) is an open source tool for creating e-business websites and managing online content. This software is published under LGPL.
This is the new major version of Thelia.
You can download this version and have a try or take a look at the source code (or anything you wish, respecting LGPL). See http://thelia.net/ web site for more information.
A repository containing all thelia modules is available at this address : https://github.com/thelia-modules
Requirements
------------
* php 5.4
* PHP 5.5
* Required extensions :
* PDO_Mysql
* mcrypt
* openssl
* intl
* gd
* curl
* calendar
* dom
* fileinfo
* safe_mode off
* memory_limit at least 128M, preferably 256.
* post_max_size 20M
* upload_max_filesize 2M
* apache 2
* mysql 5
* post\_max\_size 20M
* upload\_max\_filesize 2M
* date.timezone must be defined
* Web Server Apache 2 or Nginx
* MySQL 5
If you use Mac OSX, it still doesn't use php 5.4 as default php version... There are many solutions for you :
* use [phpbrew](https://github.com/c9s/phpbrew)
* use last MAMP version and put the php bin directory in your path:
```bash
export PATH=/Applications/MAMP/bin/php/php5.5.x/bin/:$PATH
```
* configure a complete development environment : http://php-osx.liip.ch/
* use a virtual machine with vagrant and puppet : https://puphpet.com/
### MySQL 5.6
@@ -72,6 +68,7 @@ Thelia's archive builder's needs external libraries.
For zip archives, you need PECL zip. See [PHP Doc](http://php.net/manual/en/zip.installation.php)
For tar archives, you need PECL phar. Moreover, you need to deactivate php.ini option "phar.readonly":
```ini
phar.readonly = Off
```
@@ -80,13 +77,30 @@ For tar.bz2 archives, you need tar's dependencies and the extension "bzip2". See
For tar.gz archives, you need tar's dependencies and the extension "zlib". See [PHP Doc](http://fr2.php.net/manual/fr/book.zlib.php)
## Download Thelia 2
## Download Thelia 2 and install its dependencies
You can get the sources from git and then let composer install dependencies, or use composer to install the whole thelia project into a specific directory
### Using git for download and composer for dependencies
``` bash
$ git clone --recursive https://github.com/thelia/thelia path
$ cd path
$ git checkout 2.3.5 (2.2.6 or 2.1.11)
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar install
```
### Using composer for both download and dependencies
``` bash
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar create-project thelia/thelia path/ 2.0.3-beta2
$ php composer.phar create-project thelia/thelia path/ 2.3.5 (2.2.6 or 2.1.11)
```
If something goes wrong during the install process, you can restart Thelia install wizard with
the following command : `php composer.phar run-script post-create-project-cmd`
## Install it
You can install Thelia by two different way
@@ -103,10 +117,10 @@ The install wizard in accessible with your favorite browser :
http://yourdomain.tld/[/subdomain_if_needed]/install
```
For example, I have thelia downloaded at http://thelia.net and my vhost is correctly configured, I have to reach this address :
For example, I have thelia downloaded at https://thelia.net and my vhost is correctly configured, I have to reach this address :
``` bash
http://thelia.net/install
https://thelia.net/install
```
### Using cli tools
@@ -115,20 +129,69 @@ http://thelia.net/install
$ php Thelia thelia:install
```
or if you use a Thelia project :
``` bash
$ php composer.phar run-script post-create-project-cmd
```
You just have to follow all instructions.
### Docker and docker compose
This repo contains all the configuration needed to run Thelia with docker and docker-compose.
It requires obviously [docker](https://docker.com/) and [docker-compose](http://docs.docker.com/compose/)
To install Thelia within Docker, run :
```
docker-compose up -d
docker-compose exec web composer install
docker-compose exec web php Thelia thelia:install
```
This will prompt you for database information. Enter the following :
* host : mariaDB
* port : 3306 (default)
* name : thelia
* login : root
* password : toor
tip : create an alias for docker-compose, it's boring to write it all the time
All the scripts can be launched through docker (or the corresponding `docker-compose exec web ...` command. For example :
```
docker exec -it thelia_web_1 composer install
docker exec -it thelia_web_1 php Thelia cache:clear
docker exec -it thelia_web_1 php setup/faker.php
docker exec -it thelia_web_1 unit-tests.sh
```
Once started, you can open your local Thelia website at [127.0.0.1:8080](http://127.0.0.1:8080) and your phpMyAdmin installation at [127.0.0.1:8081](http://127.0.0.1:8081).
What is missing :
* confguration for export compression (zip, gzip, etc)
Obviously you can modify all the configuration for your own case, for example the php version or add environment variable for the database configuration. Each time you modify the configuration, you have to rebuild it :
```
docker-compose build --no-cache
```
Documentation
-------------
Thelia documentation is available at http://doc.thelia.net
The documentation is also in beta version and some part can be obsolete cause to some refactor.
Roadmap
-------
The Roadmap is available at http://thelia.net/community/roadmap
The Roadmap is available at https://thelia.net/community/roadmap
Contribute
@@ -136,6 +199,8 @@ Contribute
see the documentation : http://doc.thelia.net/en/documentation/contribute.html
If you submit modifications that adds new data or change the structure of the database, take a look to http://doc.thelia.net/en/documentation/contribute.html#sql-scripts-modification
Usage
-----
@@ -150,4 +215,3 @@ $ phpunit
```
We still have lot of work to achieve but enjoy this part.

20
Thelia
View File

@@ -1,21 +1,5 @@
#!/usr/bin/env php
<?php
if (php_sapi_name() != 'cli') {
throw new \Exception('this script can only be launched with cli sapi');
}
set_time_limit(0);
require __DIR__ . '/core/bootstrap.php';
use Thelia\Core\Thelia;
use Thelia\Core\Application;
use Thelia\Command\Output\TheliaConsoleOutput;
use Symfony\Component\Console\Input\ArgvInput;
$input = new ArgvInput();
$env = $input->getParameterOption(array('--env', '-e'), getenv('THELIA_ENV') ?: 'dev');
$debug = getenv('THELIA_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod';
$thelia = new Thelia($env, $debug);
$application = new Application($thelia);
$application->run($input, new TheliaConsoleOutput());
$bootstrapFile = __DIR__ . "/core/vendor/autoload.php";
include __DIR__ . "/core/Thelia";

View File

@@ -1,8 +1,68 @@
#How to update your Thelia
# How to update your Thelia ?
- clear all caches running ```php Thelia cache:clear```
- copy all files from the thelia new version (local/modules/* files too)
- run ```php Thelia thelia:update```
- again clear all caches in all environment :
If you have already installed Thelia but a new version is available, you can update easily.
Before proceeding to the update, it's strongly recommended to backup your website (files and database).
You can backup your database with tools such as [phpmyadmin](http://www.phpmyadmin.net)
or [mysqldump](dev.mysql.com/doc/refman/5.6/en/mysqldump.html).
## 1. Update files
- Download the latest version of Thelia : <http://thelia.net/download/thelia.zip>
- Extract the zip in a temporary directory
- Then you should replace (not only copy) all the files from the new Thelia version :
- all files from root directory
- bin (*optional*)
- core (**mandatory**)
- setup (**mandatory**)
- Then, you have to merge (copy in your existent directories) these other directories. Normally,
you haven't modify files inside these directories (just created new ones - like your frontOffice template).
But If you have modified files, you should proceed carefully and try to report all your changes.
- local/config
- local/modules
- templates
- web
## 2. Update database
Then you have 2 different ways to proceed. In each method, a backup of your database can be automatically
performed if you want to. If an error is encountered, then your database will be restored.
But if your database is quite large, it's better to make a backup manually.
### 2.1. use the update script
In a command shell, go to the root directory of your installation, run and follow instructions :
```bash
php setup/update.php
```
### 2.2. use the update wizard
An update wizard is available in the ```web/install``` directory. It's the same directory used by the install wizard.
**You have to protect the web folder if your site is public (htaccess, List of allowed IP, ...).**
The update wizard in accessible with your favorite browser :
```bash
http://yourdomain.tld/[/subdomain_if_needed]/install
```
Note:
- the wizard is available only if your Thelia is not already in the latest version.
- at the end of the process, the install directory will be removed.
## 3. Clear cache
Once the update is done successfully, you have to clear all caches :
- clear all caches in all environment :
- ```php Thelia cache:clear```
- ```php Thelia cache:clear --env=prod```
If the command fails, you can do it manually. Just delete the content of
the ```cache``` and ```web/cache``` directories.

131
UPGRADE-2.3.md Normal file
View File

@@ -0,0 +1,131 @@
UPGRADE FROM 2.2 to 2.3
=======================
EventDispatcher
----------------
* The `getDispatcher()` and `getName()` methods from `Symfony\Component\EventDispatcher\Event`
are deprecated, the event dispatcher instance and event name can be received in the listener call instead.
Before:
```php
use Symfony\Component\EventDispatcher\Event;
class Foo
{
public function myFooListener(Event $event)
{
$dispatcher = $event->getDispatcher();
$eventName = $event->getName();
$dispatcher->dispatch('log', $event);
// ... more code
}
}
```
After:
```php
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class MyListenerClass
{
public function myListenerMethod(Event $event, $eventName, EventDispatcherInterface $dispatcher)
{
$dispatcher->dispatch('log', $event);
// ... more code
}
}
```
While this above is sufficient for most uses, **if your module must be compatible with versions less than 2.3, or if your module uses multiple EventDispatcher instances,** you might need to specifically inject a known instance of the `EventDispatcher` into your listeners. This could be done using constructor or setter injection as follows:
```php
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class MyListenerClass
{
protected $dispatcher = null;
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
}
```
Request and RequestStack
----------------
* The `Request` service are deprecated, you must now use the `RequestStack` service.
##### In your loops
The way to recover the request does not change.
To get the current request
```php
class MyLoopClass extends BaseLoop implements PropelSearchLoopInterface
{
public function buildModelCriteria()
{
// Get the current request
$request = $this->getCurrentRequest();
// Or
$request = $this->requestStack->getCurrentRequest();
// ... more code
}
}
```
##### In your controllers
It's not recommended to use `getRequest()` and `getSession()`, the Request instance can be received in the action method parameters.
However, the `getRequest()` method returns the current request.
**Warning !!** This is not compatible with Thelia 2.0, because it uses Symfony 2.2
To get the current request
```php
use Thelia\Core\HttpFoundation\Request;
class MyControllerClass extends ...
{
public function MyActionMethod(Request $request, $query_parameters ...)
{
$session = $request->getSession();
// ... more code
}
}
```
Container Scopes
----------------
* The "container scopes" concept no longer exists in Thelia 2.3.
For backward compatibility, the attributes `scope` is automatically removed of the xml configuration files.
**Warning !!** The attributes `scope` are always needed for your modules compatible with Thelia < 2.3.
[See the Symfony documentation for more information](http://symfony.com/doc/2.8/cookbook/service_container/scopes.html)
Unit Test
----------------
* The `SecurityContext`, `ParserContext`, `TokenProvider`, `TheliaFormFactory`, `TaxEngine` services are no longer dependent on "Request", but "RequestSTack".
This may break your unit tests.
For more information about the upgrade from Symfony 2.3 to Symfony 2.8
----------------
[Upgrade from Symfony 2.3 to 2.4](https://github.com/symfony/symfony/blob/2.8/UPGRADE-2.4.md)
[Upgrade from Symfony 2.4 to 2.5](https://github.com/symfony/symfony/blob/2.8/UPGRADE-2.5.md)
[Upgrade from Symfony 2.5 to 2.6](https://github.com/symfony/symfony/blob/2.8/UPGRADE-2.6.md)
[Upgrade from Symfony 2.6 to 2.7](https://github.com/symfony/symfony/blob/2.8/UPGRADE-2.7.md)
[Upgrade from Symfony 2.7 to 2.8](https://github.com/symfony/symfony/blob/2.8/UPGRADE-2.8.md)
[Upgrade from Symfony 2.8 to 3.0](https://github.com/symfony/symfony/blob/2.8/UPGRADE-3.0.md)

1
bin/lessc Symbolic link
View File

@@ -0,0 +1 @@
../core/vendor/oyejorge/less.php/bin/lessc

1
bin/phpunit Symbolic link
View File

@@ -0,0 +1 @@
../core/vendor/phpunit/phpunit/phpunit

1
bin/propel Symbolic link
View File

@@ -0,0 +1 @@
../core/vendor/propel/propel/bin/propel

BIN
commits-2.3.3.ods Normal file

Binary file not shown.

View File

@@ -3,46 +3,72 @@
"description": "Thelia is an ecommerce CMS.",
"license": "LGPL-3.0+",
"homepage": "http://thelia.net/",
"keywords": [
"ecommerce",
"cms",
"cms ecommerce",
"ecommerce cms"
],
"support": {
"forum": "http://thelia.net/forum",
"wiki": "http://doc.thelia.net"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/thelia/Propel2"
}
],
"require": {
"php": ">=5.4",
"php": ">=5.5",
"ircmaxell/password-compat": "1.0.*",
"propel/propel": "dev-master",
"psr/log": "1.0",
"symfony/class-loader": "2.2.*",
"symfony/config": "2.2.*",
"symfony/console": "2.2.*",
"symfony/dependency-injection": "2.2.*",
"symfony/event-dispatcher": "2.2.*",
"symfony/http-kernel": "2.2.*",
"symfony/routing": "2.2.*",
"symfony/filesystem": "2.2.*",
"symfony/yaml": "2.2.*",
"symfony/translation": "2.2.*",
"symfony-cmf/routing": "1.0.0",
"symfony/form": "2.2.*",
"symfony/validator": "2.3.*",
"smarty/smarty": "v3.1.14",
"kriswallsmith/assetic": "1.2.*@dev",
"leafo/lessphp": "0.4.*",
"ptachoire/cssembed": "1.0.*",
"doctrine/cache": "v1.3.0",
"simplepie/simplepie": "dev-master",
"imagine/imagine": "0.*",
"symfony/icu": "1.0",
"swiftmailer/swiftmailer": "5.2.*",
"symfony/serializer": "2.3.*",
"symfony/config": "2.8.*",
"symfony/console": "2.8.*",
"symfony/dependency-injection": "2.8.*",
"symfony/event-dispatcher": "2.8.*",
"symfony/http-kernel": "2.8.*",
"symfony/routing": "2.8.*",
"symfony/filesystem": "2.8.*",
"symfony/yaml": "2.8.*",
"symfony/translation": "2.8.*",
"symfony-cmf/routing": "1.3.*",
"symfony/validator": "2.8.*",
"symfony/options-resolver": "2.8.*",
"symfony/security": "2.8.*",
"symfony/expression-language": "2.8.*",
"symfony/process": "2.8.*",
"symfony/dom-crawler": "2.8.*",
"symfony/property-access": "2.8.*",
"symfony/serializer": "2.8.*",
"ensepar/html2pdf": "1.0.1",
"symfony/finder": "~2.2",
"symfony/browser-kit": "2.3.*"
"symfony/finder": "2.8.*",
"symfony/browser-kit": "2.8.*",
"symfony/http-foundation": "2.8.*",
"symfony/form": "2.8.*",
"symfony/class-loader": "2.8.*",
"symfony/icu": "1.0",
"stack/builder": "1.0.*",
"thelia/currency-converter": "~1.0",
"doctrine/cache": "1.5.*",
"kriswallsmith/assetic": "1.3.*",
"ptachoire/cssembed": "1.0.*",
"simplepie/simplepie": "1.3.*",
"imagine/imagine": "0.6.*",
"swiftmailer/swiftmailer": "5.4.*",
"oyejorge/less.php": "1.7.*",
"michelf/php-markdown": "1.6.*",
"smarty/smarty": "3.1.20",
"ramsey/array_column": "~1.1",
"propel/propel": "dev-thelia-2.3",
"commerceguys/addressing": "0.8.*",
"symfony/cache": "~3.1.0"
},
"require-dev": {
"phpunit/phpunit": "4.1.*",
"fzaninotto/faker": "dev-master",
"maximebf/debugbar": "dev-master"
"fzaninotto/faker": "1.5.*",
"thelia/hooktest-module": "~1.1",
"thelia/hooktest-template": "~1.1",
"phpunit/phpunit": "4.8.*"
},
"minimum-stability": "stable",
"config": {
@@ -50,37 +76,24 @@
"bin-dir": "bin"
},
"autoload": {
"psr-4": {
"Thelia\\": "core/lib/Thelia"
},
"psr-0": {
"": "local/modules/",
"Thelia": "core/lib/"
"": "local/modules/"
},
"files": [
"core/bootstrap.php"
]
},
"autoload-dev": {
"psr-4": {
"Thelia\\Tests\\" : "tests/phpunit/Thelia/Tests"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.1.0-dev"
}
},
"repositories": [
{
"type": "package",
"package": {
"name": "smarty/smarty",
"version": "3.1.14",
"dist": {
"url": "http://www.smarty.net/files/Smarty-3.1.14.zip",
"type": "zip"
},
"source": {
"url": "http://smarty-php.googlecode.com/svn/",
"type": "svn",
"reference": "tags/Smarty_3_1_14/distribution/"
},
"autoload": {
"classmap": [
"libs/"
]
"dev-master": "2.3-dev"
}
}
}
]
}

3994
composer.lock generated

File diff suppressed because it is too large Load Diff

165
core/LICENSE.txt Normal file
View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

38
core/Readme.md Normal file
View File

@@ -0,0 +1,38 @@
Readme
======
## This is the repository of Thelia core. All the pull requests on this repo will be ignored.
### If you want to create a project, please take a look at [thelia/thelia-project](https://github.com/thelia/thelia-project)
### If you want to contribute to Thelia, please take a look at [thelia/thelia](https://github.com/thelia/thelia)
Thelia
------
[![Build Status](https://travis-ci.org/thelia/thelia.png?branch=master)](https://travis-ci.org/thelia/thelia) [![License](https://poser.pugx.org/thelia/thelia/license.png)](https://packagist.org/packages/thelia/thelia) [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/thelia/thelia/badges/quality-score.png?s=61e3e04a69bffd71c29b08e5392080317a546716)](https://scrutinizer-ci.com/g/thelia/thelia/)
[Thelia](http://thelia.net/) is an open source tool for creating e-business websites and managing online content. This software is published under LGPL.
This is the new major version of Thelia.
You can download this version and have a try or take a look at the source code (or anything you wish, respecting LGPL). See http://thelia.net/ web site for more information.
A repository containing all thelia modules is available at this address : https://github.com/thelia-modules
Requirements
------------
* php 5.5
* Required extensions :
* PDO_Mysql
* openssl
* intl
* gd
* curl
* calendar
* dom
* fileinfo
* safe_mode off
* memory_limit at least 128M, preferably 256.
* post_max_size 20M
* upload_max_filesize 2M
* apache 2
* mysql

53
core/Thelia Executable file
View File

@@ -0,0 +1,53 @@
<?php
if (php_sapi_name() != 'cli') {
throw new \Exception('this script can only be launched with cli sapi');
}
set_time_limit(0);
// allow cache to be cleared by php client or web
umask(0002);
$bootstrapToggle = false;
$bootstraped = false;
// Autoload bootstrap
foreach ($argv as $arg) {
if ($arg === '-b') {
$bootstrapToggle = true;
continue;
}
if ($bootstrapToggle) {
require __DIR__ . DIRECTORY_SEPARATOR . $arg;
$bootstraped = true;
}
}
if (!$bootstraped) {
if (isset($bootstrapFile)) {
require $bootstrapFile;
} elseif (is_file($file = __DIR__ . '/vendor/autoload.php')) {
require $file;
} else {
echo "No autoload file found. Please use the -b argument to include yours";
exit(1);
}
}
use Thelia\Core\Thelia;
use Thelia\Core\Application;
use Thelia\Command\Output\TheliaConsoleOutput;
use Symfony\Component\Console\Input\ArgvInput;
$input = new ArgvInput();
$env = $input->getParameterOption(array('--env', '-e'), getenv('THELIA_ENV') ?: 'dev');
$debug = getenv('THELIA_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod';
$thelia = new Thelia($env, $debug);
$application = new Application($thelia);
$application->getContainer()->get('thelia.translator');
$application->run($input, new TheliaConsoleOutput());

View File

@@ -10,27 +10,83 @@
/* file that was distributed with this source code. */
/*************************************************************************************/
// Check php version
if (version_compare(phpversion(), "5.4", "<")) {
die(sprintf(
"Thelia needs at least php 5.4, but you are using php %s. Please upgrade before using Thelia.\n",
phpversion()
));
}
/**
* Thelia essential definitions
*/
define('DS' , DIRECTORY_SEPARATOR);
define('THELIA_ROOT' , rtrim(realpath(dirname(__DIR__)), DS) . DS);
define('THELIA_LOCAL_DIR' , THELIA_ROOT . 'local' . DS);
define('THELIA_CONF_DIR' , THELIA_LOCAL_DIR . 'config' . DS);
define('THELIA_MODULE_DIR' , THELIA_LOCAL_DIR . 'modules' . DS);
define('THELIA_WEB_DIR' , THELIA_ROOT . 'web' . DS);
define('THELIA_CACHE_DIR' , THELIA_ROOT . 'cache' . DS);
define('THELIA_LOG_DIR' , THELIA_ROOT . 'log' . DS);
define('THELIA_TEMPLATE_DIR' , THELIA_ROOT . 'templates' . DS);
$loader = require __DIR__ . "/vendor/autoload.php";
if (!defined('DS')) {
define('DS', DIRECTORY_SEPARATOR);
}
if (!defined('THELIA_ROOT')) {
define('THELIA_ROOT', rtrim(realpath(dirname(__DIR__)), DS) . DS);
}
if (!defined('THELIA_LIB')) {
define('THELIA_LIB', THELIA_ROOT . 'core' . DS . 'lib' . DS . 'Thelia' . DS);
}
if (!defined('THELIA_VENDOR')) {
define('THELIA_VENDOR', THELIA_ROOT . 'core' . DS . 'vendor' . DS);
}
if (!defined('THELIA_LOCAL_DIR')) {
define('THELIA_LOCAL_DIR', THELIA_ROOT . 'local' . DS);
}
if (!defined('THELIA_CONF_DIR')) {
define('THELIA_CONF_DIR', THELIA_LOCAL_DIR . 'config' . DS);
}
if (!defined('THELIA_MODULE_DIR')) {
define('THELIA_MODULE_DIR', THELIA_LOCAL_DIR . 'modules' . DS);
}
if (!defined('THELIA_WEB_DIR')) {
define('THELIA_WEB_DIR', THELIA_ROOT . 'web' . DS);
}
if (!defined('THELIA_CACHE_DIR')) {
define('THELIA_CACHE_DIR', THELIA_ROOT . 'cache' . DS);
}
if (!defined('THELIA_LOG_DIR')) {
define('THELIA_LOG_DIR', THELIA_ROOT . 'log' . DS);
}
if (!defined('THELIA_TEMPLATE_DIR')) {
define('THELIA_TEMPLATE_DIR', THELIA_ROOT . 'templates' . DS);
}
if (!defined('THELIA_SETUP_DIRECTORY')) {
define('THELIA_SETUP_DIRECTORY', THELIA_ROOT . 'setup' . DS);
}
if (!defined('THELIA_SETUP_WIZARD_DIRECTORY')) {
define('THELIA_SETUP_WIZARD_DIRECTORY', THELIA_ROOT . 'web' . DS . 'install' . DS);
}
if (!file_exists(THELIA_CONF_DIR . 'database.yml') && !defined('THELIA_INSTALL_MODE')) {
$sapi = php_sapi_name();
if (substr($sapi, 0, 3) == 'cli') {
define('THELIA_INSTALL_MODE', true);
} else {
} elseif (file_exists(THELIA_ROOT . DS . 'web' . DS . 'install')) {
$request = \Thelia\Core\HttpFoundation\Request::createFromGlobals();
header('Location: '.$request->getUriForPath('/install'));
} else {
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Thelia is not installed', true, 500);
die(sprintf(
"Thelia is not installed. <a href=\"%s\" target=\"_blank\">More information</a>\n",
"http://doc.thelia.net/en/documentation/installation/index.html#using-cli-tools"
));
}
}

79
core/composer.json Normal file
View File

@@ -0,0 +1,79 @@
{
"name": "thelia/core",
"description": "Core of Thelia ecommerce CMS.",
"license": "LGPL-3.0+",
"homepage": "http://thelia.net/",
"support": {
"forum": "http://thelia.net/forum",
"wiki": "http://doc.thelia.net"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/thelia/Propel2"
}
],
"require": {
"php": ">=5.4",
"ircmaxell/password-compat": "1.0.*",
"psr/log": "1.0",
"symfony/config": "2.8.*",
"symfony/console": "2.8.*",
"symfony/dependency-injection": "2.8.*",
"symfony/event-dispatcher": "2.8.*",
"symfony/http-kernel": "2.8.*",
"symfony/routing": "2.8.*",
"symfony/filesystem": "2.8.*",
"symfony/yaml": "2.8.*",
"symfony/translation": "2.8.*",
"symfony-cmf/routing": "1.3.*",
"symfony/validator": "2.8.*",
"symfony/options-resolver": "2.8.*",
"symfony/security": "2.8.*",
"symfony/expression-language": "2.8.*",
"symfony/process": "2.8.*",
"symfony/dom-crawler": "2.8.*",
"symfony/property-access": "2.8.*",
"symfony/serializer": "2.8.*",
"ensepar/html2pdf": "1.0.1",
"symfony/finder": "2.8.*",
"symfony/browser-kit": "2.8.*",
"symfony/http-foundation": "2.8.*",
"symfony/form": "2.8.*",
"symfony/class-loader": "2.8.*",
"symfony/icu": "1.0",
"stack/builder": "1.0.*",
"thelia/currency-converter": "~1.0",
"doctrine/cache": "1.5.*",
"kriswallsmith/assetic": "1.3.*",
"ptachoire/cssembed": "1.0.*",
"simplepie/simplepie": "1.3.*",
"imagine/imagine": "0.6.*",
"swiftmailer/swiftmailer": "5.4.*",
"oyejorge/less.php": "1.7.*",
"michelf/php-markdown": "1.6.*",
"smarty/smarty": "3.1.20",
"ramsey/array_column": "~1.1",
"propel/propel": "dev-thelia-2.3",
"commerceguys/addressing": "0.8.*",
"symfony/cache": "~3.1.0"
},
"require-dev": {
"fzaninotto/faker": "1.5.*",
"thelia/hooktest-module": "~1.1",
"thelia/hooktest-template": "~1.1",
"phpunit/phpunit": "4.8.*"
},
"bin": [
"Thelia"
],
"minimum-stability": "stable",
"autoload": {
"psr-4": {
"Thelia\\": "lib/Thelia/"
},
"files": [
"bootstrap.php"
]
}
}

View File

@@ -11,8 +11,10 @@
/*************************************************************************************/
namespace Thelia\Action;
use Propel\Runtime\Exception\PropelException;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Address\AddressCreateOrUpdateEvent;
use Thelia\Core\Event\Address\AddressEvent;
@@ -23,23 +25,22 @@ use Thelia\Model\Map\AddressTableMap;
/**
* Class Address
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Address extends BaseAction implements EventSubscriberInterface
{
public function create(AddressCreateOrUpdateEvent $event)
public function create(AddressCreateOrUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$address = new AddressModel();
$address->setCustomer($event->getCustomer());
$this->createOrUpdate($address, $event);
$this->createOrUpdate($address, $event, $dispatcher);
}
public function update(AddressCreateOrUpdateEvent $event)
public function update(AddressCreateOrUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$addressModel = $event->getAddress();
$this->createOrUpdate($addressModel, $event);
$this->createOrUpdate($addressModel, $event, $dispatcher);
}
public function delete(AddressEvent $event)
@@ -56,9 +57,9 @@ class Address extends BaseAction implements EventSubscriberInterface
$address->makeItDefault();
}
protected function createOrUpdate(AddressModel $addressModel, AddressCreateOrUpdateEvent $event)
protected function createOrUpdate(AddressModel $addressModel, AddressCreateOrUpdateEvent $event, $dispatcher)
{
$addressModel->setDispatcher($event->getDispatcher());
$addressModel->setDispatcher($dispatcher);
$con = Propel::getWriteConnection(AddressTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
@@ -73,6 +74,7 @@ class Address extends BaseAction implements EventSubscriberInterface
->setZipcode($event->getZipcode())
->setCity($event->getCity())
->setCountryId($event->getCountry())
->setStateId($event->getState())
->setCellphone($event->getCellphone())
->setPhone($event->getPhone())
->setCompany($event->getCompany())
@@ -85,33 +87,14 @@ class Address extends BaseAction implements EventSubscriberInterface
$event->setAddress($addressModel);
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{

View File

@@ -12,26 +12,45 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Administrator\AdministratorEvent;
use Thelia\Core\Event\Administrator\AdministratorUpdatePasswordEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Mailer\MailerFactory;
use Thelia\Model\Admin as AdminModel;
use Thelia\Model\AdminQuery;
use Thelia\Model\ConfigQuery;
use Thelia\Tools\TokenProvider;
class Administrator extends BaseAction implements EventSubscriberInterface
{
/** @var MailerFactory */
protected $mailer;
/** @var TokenProvider */
protected $tokenProvider;
public function __construct(MailerFactory $mailer, TokenProvider $tokenProvider)
{
$this->mailer = $mailer;
$this->tokenProvider = $tokenProvider;
}
/**
* @param AdministratorEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(AdministratorEvent $event)
public function create(AdministratorEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$administrator = new AdminModel();
$administrator
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setFirstname($event->getFirstname())
->setLastname($event->getLastname())
->setEmail($event->getEmail())
->setLogin($event->getLogin())
->setPassword($event->getPassword())
->setProfileId($event->getProfile())
@@ -45,16 +64,18 @@ class Administrator extends BaseAction implements EventSubscriberInterface
/**
* @param AdministratorEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(AdministratorEvent $event)
public function update(AdministratorEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $administrator = AdminQuery::create()->findPk($event->getId())) {
$administrator
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setFirstname($event->getFirstname())
->setLastname($event->getLastname())
->setLogin($event->getLogin())
->setEmail($event->getEmail())
->setProfileId($event->getProfile())
->setLocale($event->getLocale())
;
@@ -75,7 +96,6 @@ class Administrator extends BaseAction implements EventSubscriberInterface
public function delete(AdministratorEvent $event)
{
if (null !== $administrator = AdminQuery::create()->findPk($event->getId())) {
$administrator
->delete()
;
@@ -87,12 +107,40 @@ class Administrator extends BaseAction implements EventSubscriberInterface
public function updatePassword(AdministratorUpdatePasswordEvent $event)
{
$admin = $event->getAdmin();
$admin->setPassword($event->getPassword())
$admin
->setPassword($event->getPassword())
->setPasswordRenewToken(null)
->save();
}
public function createPassword(AdministratorEvent $event)
{
$admin = $event->getAdministrator();
$email = $admin->getEmail();
if (! empty($email)) {
$renewToken = $this->tokenProvider->getToken();
$admin
->setPasswordRenewToken($renewToken)
->save();
$this->mailer->sendEmailMessage(
'new_admin_password',
[ ConfigQuery::getStoreEmail() => ConfigQuery::getStoreName() ],
[ $email => $admin->getFirstname() . ' ' . $admin->getLastname() ],
[
'token' => $renewToken,
'admin' => $admin
]
);
}
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
@@ -100,7 +148,8 @@ class Administrator extends BaseAction implements EventSubscriberInterface
TheliaEvents::ADMINISTRATOR_CREATE => array('create', 128),
TheliaEvents::ADMINISTRATOR_UPDATE => array('update', 128),
TheliaEvents::ADMINISTRATOR_DELETE => array('delete', 128),
TheliaEvents::ADMINISTRATOR_UPDATEPASSWORD => array('updatePassword', 128)
TheliaEvents::ADMINISTRATOR_UPDATEPASSWORD => array('updatePassword', 128),
TheliaEvents::ADMINISTRATOR_CREATEPASSWORD => array('createPassword', 128)
);
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Api\ApiCreateEvent;
use Thelia\Core\Event\Api\ApiDeleteEvent;
use Thelia\Core\Event\Api\ApiUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\Api as ApiModel;
/**
* Class Api
* @package Thelia\Action
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Api extends BaseAction implements EventSubscriberInterface
{
public function createApi(ApiCreateEvent $event)
{
$api = new ApiModel();
$api->setLabel($event->getLabel())
->setProfileId($event->getProfile())
->save()
;
}
public function deleteApi(ApiDeleteEvent $event)
{
$api = $event->getApi();
$api->delete();
}
public function updateApi(ApiUpdateEvent $event)
{
$api = $event->getApi();
$api->setProfileId($event->getProfile())
->save();
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
TheliaEvents::API_CREATE => ['createApi', 128],
TheliaEvents::API_DELETE => ['deleteApi', 128],
TheliaEvents::API_UPDATE => ['updateApi', 128],
];
}
}

View File

@@ -11,52 +11,73 @@
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Area\AreaAddCountryEvent;
use Thelia\Core\Event\Area\AreaCreateEvent;
use Thelia\Core\Event\Area\AreaDeleteEvent;
use Thelia\Core\Event\Area\AreaRemoveCountryEvent;
use Thelia\Core\Event\Area\AreaUpdateEvent;
use Thelia\Core\Event\Area\AreaUpdatePostageEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\AreaQuery;
use Thelia\Model\CountryQuery;
use Thelia\Model\Area as AreaModel;
use Thelia\Model\AreaQuery;
use Thelia\Model\CountryArea;
use Thelia\Model\CountryAreaQuery;
/**
* Class Area
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Area extends BaseAction implements EventSubscriberInterface
{
public function addCountry(AreaAddCountryEvent $event)
{
if (null !== $country = CountryQuery::create()->findPk($event->getCountryId())) {
$country->setDispatcher($event->getDispatcher());
$country->setAreaId($event->getAreaId())
->save();
$countryIds = $event->getCountryId();
$event->setArea($country->getArea());
$areaId = $event->getAreaId();
foreach ($countryIds as $countryId) {
$countryArea = new CountryArea();
$country = explode('-', $countryId);
if (count($country) === 1) {
$country[1] = null;
}
if ($country[1] == 0) {
$country[1] = null;
}
$countryArea
->setAreaId($areaId)
->setCountryId($country[0])
->setStateId($country[1])
->save()
;
}
$event->setArea(AreaQuery::create()->findPk($areaId));
}
public function removeCountry(AreaRemoveCountryEvent $event)
{
if (null !== $country = CountryQuery::create()->findPk($event->getCountryId())) {
$event->setArea($country->getArea());
CountryAreaQuery::create()
->filterByCountryId($event->getCountryId())
->filterByStateId($event->getStateId())
->filterByAreaId($event->getAreaId())
->delete();
$country->setDispatcher($event->getDispatcher());
$country->setAreaId(null)
->save();
if (null !== $area = AreaQuery::create()->findPk($event->getAreaId())) {
$event->setArea($area);
}
}
public function updatePostage(AreaUpdatePostageEvent $event)
public function updatePostage(AreaUpdatePostageEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $area = AreaQuery::create()->findPk($event->getAreaId())) {
$area->setDispatcher($event->getDispatcher());
$area->setDispatcher($dispatcher);
$area
->setPostage($event->getPostage())
->save();
@@ -65,47 +86,42 @@ class Area extends BaseAction implements EventSubscriberInterface
}
}
public function delete(AreaDeleteEvent $event)
public function delete(AreaDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $area = AreaQuery::create()->findPk($event->getAreaId())) {
$area->setDispatcher($event->getDispatcher());
$area->setDispatcher($dispatcher);
$area->delete();
$event->setArea($area);
}
}
public function create(AreaCreateEvent $event)
public function create(AreaCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$area = new AreaModel();
$area
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setName($event->getAreaName())
->save();
$event->setArea($area);
}
public function update(AreaUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $area = AreaQuery::create()->findPk($event->getAreaId())) {
$area
->setDispatcher($dispatcher)
->setName($event->getAreaName())
->save();
$event->setArea($area);
}
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
@@ -114,7 +130,8 @@ class Area extends BaseAction implements EventSubscriberInterface
TheliaEvents::AREA_REMOVE_COUNTRY => array('removeCountry', 128),
TheliaEvents::AREA_POSTAGE_UPDATE => array('updatePostage', 128),
TheliaEvents::AREA_DELETE => array('delete', 128),
TheliaEvents::AREA_CREATE => array('create', 128)
TheliaEvents::AREA_CREATE => array('create', 128),
TheliaEvents::AREA_UPDATE => array('update', 128)
);
}
}

View File

@@ -12,13 +12,11 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Model\AttributeQuery;
use Thelia\Model\Attribute as AttributeModel;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Attribute\AttributeUpdateEvent;
use Thelia\Core\Event\Attribute\AttributeCreateEvent;
use Thelia\Core\Event\Attribute\AttributeDeleteEvent;
@@ -34,17 +32,17 @@ class Attribute extends BaseAction implements EventSubscriberInterface
* Create a new attribute entry
*
* @param AttributeCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(AttributeCreateEvent $event)
public function create(AttributeCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$attribute = new AttributeModel();
$attribute
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->save()
;
@@ -60,14 +58,14 @@ class Attribute extends BaseAction implements EventSubscriberInterface
* Change a product attribute
*
* @param AttributeUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(AttributeUpdateEvent $event)
public function update(AttributeUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $attribute = AttributeQuery::create()->findPk($event->getAttributeId())) {
$attribute
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setTitle($event->getTitle())
@@ -85,14 +83,14 @@ class Attribute extends BaseAction implements EventSubscriberInterface
* Delete a product attribute entry
*
* @param AttributeDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function delete(AttributeDeleteEvent $event)
public function delete(AttributeDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== ($attribute = AttributeQuery::create()->findPk($event->getAttributeId()))) {
$attribute
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->delete()
;
@@ -103,11 +101,13 @@ class Attribute extends BaseAction implements EventSubscriberInterface
/**
* Changes position, selecting absolute ou relative change.
*
* @param CategoryChangePositionEvent $event
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(AttributeQuery::create(), $event);
$this->genericUpdatePosition(AttributeQuery::create(), $event, $dispatcher);
}
protected function doAddToAllTemplates(AttributeModel $attribute)
@@ -115,7 +115,6 @@ class Attribute extends BaseAction implements EventSubscriberInterface
$templates = TemplateQuery::create()->find();
foreach ($templates as $template) {
$attribute_template = new AttributeTemplate();
if (null === AttributeTemplateQuery::create()->filterByAttribute($attribute)->filterByTemplate($template)->findOne()) {
@@ -140,7 +139,7 @@ class Attribute extends BaseAction implements EventSubscriberInterface
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{

View File

@@ -12,13 +12,11 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Model\AttributeAvQuery;
use Thelia\Model\AttributeAv as AttributeAvModel;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Attribute\AttributeAvUpdateEvent;
use Thelia\Core\Event\Attribute\AttributeAvCreateEvent;
use Thelia\Core\Event\Attribute\AttributeAvDeleteEvent;
@@ -30,13 +28,15 @@ class AttributeAv extends BaseAction implements EventSubscriberInterface
* Create a new attribute entry
*
* @param AttributeAvCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(AttributeAvCreateEvent $event)
public function create(AttributeAvCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$attribute = new AttributeAvModel();
$attribute
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setAttributeId($event->getAttributeId())
->setLocale($event->getLocale())
@@ -51,15 +51,15 @@ class AttributeAv extends BaseAction implements EventSubscriberInterface
/**
* Change a product attribute
*
* @param \Thelia\Core\Event\Attribute\AttributeAvUpdateEvent $event
* @param AttributeAvUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(AttributeAvUpdateEvent $event)
public function update(AttributeAvUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $attribute = AttributeAvQuery::create()->findPk($event->getAttributeAvId())) {
$attribute
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setTitle($event->getTitle())
@@ -77,14 +77,14 @@ class AttributeAv extends BaseAction implements EventSubscriberInterface
* Delete a product attribute entry
*
* @param AttributeAvDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function delete(AttributeAvDeleteEvent $event)
public function delete(AttributeAvDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== ($attribute = AttributeAvQuery::create()->findPk($event->getAttributeAvId()))) {
$attribute
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->delete()
;
@@ -95,15 +95,17 @@ class AttributeAv extends BaseAction implements EventSubscriberInterface
/**
* Changes position, selecting absolute ou relative change.
*
* @param CategoryChangePositionEvent $event
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(AttributeAvQuery::create(), $event);
}
/**
* {@inheritDoc}
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{

View File

@@ -12,12 +12,13 @@
namespace Thelia\Action;
use Propel\Runtime\ActiveQuery\ModelCriteria;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Thelia\Core\Event\ToggleVisibilityEvent;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Exception\UrlRewritingException;
use Thelia\Form\Exception\FormValidationException;
use Thelia\Model\ProductCategory;
class BaseAction
{
@@ -26,41 +27,75 @@ class BaseAction
*
* @param ModelCriteria $query
* @param UpdatePositionEvent $event
* @param EventDispatcherInterface $dispatcher
*
* @return null
*/
protected function genericUpdatePosition(ModelCriteria $query, UpdatePositionEvent $event)
protected function genericUpdatePosition(ModelCriteria $query, UpdatePositionEvent $event, EventDispatcherInterface $dispatcher = null)
{
if (null !== $object = $query->findPk($event->getObjectId())) {
if (!isset(class_uses($object)['Thelia\Model\Tools\PositionManagementTrait'])) {
throw new \InvalidArgumentException("Your model does not implement the PositionManagementTrait trait");
}
$object->setDispatcher($event->getDispatcher());
$object->setDispatcher($dispatcher !== null ? $dispatcher : $event->getDispatcher());
$mode = $event->getMode();
if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE)
if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE) {
$object->changeAbsolutePosition($event->getPosition());
else if ($mode == UpdatePositionEvent::POSITION_UP)
} elseif ($mode == UpdatePositionEvent::POSITION_UP) {
$object->movePositionUp();
else if ($mode == UpdatePositionEvent::POSITION_DOWN)
} elseif ($mode == UpdatePositionEvent::POSITION_DOWN) {
$object->movePositionDown();
}
}
}
/**
* @param ModelCriteria $query
* @param UpdatePositionEvent $event
* @param EventDispatcherInterface|null $dispatcher
*
* @since 2.3
*/
protected function genericUpdateDelegatePosition(ModelCriteria $query, UpdatePositionEvent $event, EventDispatcherInterface $dispatcher = null)
{
if (null !== $object = $query->findOne()) {
if (!isset(class_uses($object)['Thelia\Model\Tools\PositionManagementTrait'])) {
throw new \InvalidArgumentException("Your model does not implement the PositionManagementTrait trait");
}
//$object->setDispatcher($dispatcher !== null ? $dispatcher : $event->getDispatcher());
$mode = $event->getMode();
if ($mode == UpdatePositionEvent::POSITION_ABSOLUTE) {
$object->changeAbsolutePosition($event->getPosition());
} elseif ($mode == UpdatePositionEvent::POSITION_UP) {
$object->movePositionUp();
} elseif ($mode == UpdatePositionEvent::POSITION_DOWN) {
$object->movePositionDown();
}
}
}
/**
* Changes SEO Fields for an object.
*
* @param ModelCriteria $query
* @param UpdateSeoEvent $event
* @param EventDispatcherInterface $dispatcher
*
* @return mixed an SEOxxx object
* @throws FormValidationException if a rewritten URL cannot be created
*/
protected function genericUpdateSeo(ModelCriteria $query, UpdateSeoEvent $event)
protected function genericUpdateSeo(ModelCriteria $query, UpdateSeoEvent $event, EventDispatcherInterface $dispatcher = null)
{
if (null !== $object = $query->findPk($event->getObjectId())) {
$object
->setDispatcher($event->getDispatcher())
//for backward compatibility
->setDispatcher($dispatcher !== null ? $dispatcher : $event->getDispatcher())
->setLocale($event->getLocale())
->setMetaTitle($event->getMetaTitle())
@@ -82,4 +117,30 @@ class BaseAction
return $object;
}
/**
* Toggle visibility for an object
*
* @param ModelCriteria $query
* @param ToggleVisibilityEvent $event
* @param EventDispatcherInterface $dispatcher
*
* @return mixed
*/
public function genericToggleVisibility(ModelCriteria $query, ToggleVisibilityEvent $event, EventDispatcherInterface $dispatcher = null)
{
if (null !== $object = $query->findPk($event->getObjectId())) {
$newVisibility = !$object->getVisible();
$object
//for backward compatibility
->setDispatcher($dispatcher !== null ? $dispatcher : $event->getDispatcher())
->setVisible($newVisibility)
->save()
;
$event->setObject($object);
}
return $object;
}
}

View File

@@ -11,12 +11,16 @@
/*************************************************************************************/
namespace Thelia\Action;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Thelia\Core\Event\CachedFileEvent;
use Thelia\Core\Event\File\FileCreateOrUpdateEvent;
use Thelia\Core\Event\File\FileDeleteEvent;
use Thelia\Core\Event\File\FileToggleVisibilityEvent;
use Thelia\Core\Event\UpdateFilePositionEvent;
use Thelia\Exception\FileException;
use Thelia\Files\FileManager;
use Thelia\Model\Map\ProductImageTableMap;
use Thelia\Tools\URL;
/**
@@ -77,8 +81,9 @@ abstract class BaseCachedFile extends BaseAction
/** @var \DirectoryIterator $fileinfo */
foreach ($iterator as $fileinfo) {
if ($fileinfo->isDot()) continue;
if ($fileinfo->isDot()) {
continue;
}
if ($fileinfo->isFile() || $fileinfo->isLink()) {
@unlink($fileinfo->getPathname());
@@ -118,11 +123,12 @@ abstract class BaseCachedFile extends BaseAction
$safe_filename = preg_replace("[^:alnum:\-\._]", "-", strtolower(basename($filename)));
// Keep original safe name if no tranformations are applied
if ($forceOriginalFile || $hashed_options == null)
if ($forceOriginalFile || $hashed_options == null) {
return sprintf("%s/%s", $path, $safe_filename);
else
} else {
return sprintf("%s/%s-%s", $path, $hashed_options, $safe_filename);
}
}
/**
* Return the cache directory path relative to Web Root
@@ -138,8 +144,9 @@ abstract class BaseCachedFile extends BaseAction
$safe_subdir = basename($subdir);
$path = sprintf("%s/%s", $cache_dir_from_web_root, $safe_subdir);
} else
} else {
$path = $cache_dir_from_web_root;
}
// Check if path is valid, e.g. in the cache dir
return $path;
@@ -186,14 +193,18 @@ abstract class BaseCachedFile extends BaseAction
*
* @param FileCreateOrUpdateEvent $event Image event
*
* @throws \Thelia\Exception\FileException
* @throws \Thelia\Exception\FileException|\Exception
*
*/
public function saveFile(FileCreateOrUpdateEvent $event)
{
$model = $event->getModel();
$model->setFile(sprintf("tmp/%s", $event->getUploadedFile()->getFilename()));
$con = Propel::getWriteConnection(ProductImageTableMap::DATABASE_NAME);
$con->beginTransaction();
$nbModifiedLines = $model->save();
try {
$nbModifiedLines = $model->save($con);
$event->setModel($model);
if (!$nbModifiedLines) {
@@ -210,6 +221,12 @@ abstract class BaseCachedFile extends BaseAction
$newUploadedFile = $this->fileManager->copyUploadedFile($event->getModel(), $event->getUploadedFile());
$event->setUploadedFile($newUploadedFile);
$con->commit();
} catch (\Exception $e) {
$con->rollBack();
throw $e;
}
}
/**
@@ -247,9 +264,13 @@ abstract class BaseCachedFile extends BaseAction
$this->fileManager->deleteFile($event->getFileToDelete());
}
public function updatePosition(UpdateFilePositionEvent $event)
public function updatePosition(UpdateFilePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition($event->getQuery(), $event);
$this->genericUpdatePosition($event->getQuery(), $event, $dispatcher);
}
public function toggleVisibility(FileToggleVisibilityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericToggleVisibility($event->getQuery(), $event, $dispatcher);
}
}

View File

@@ -12,7 +12,9 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Thelia\Core\Event\Brand\BrandCreateEvent;
use Thelia\Core\Event\Brand\BrandDeleteEvent;
use Thelia\Core\Event\Brand\BrandToggleVisibilityEvent;
@@ -20,6 +22,7 @@ use Thelia\Core\Event\Brand\BrandUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Model\Brand as BrandModel;
use Thelia\Model\BrandQuery;
@@ -31,7 +34,6 @@ use Thelia\Model\BrandQuery;
*/
class Brand extends BaseAction implements EventSubscriberInterface
{
public function create(BrandCreateEvent $event)
{
$brand = new BrandModel();
@@ -50,11 +52,13 @@ class Brand extends BaseAction implements EventSubscriberInterface
* process update brand
*
* @param BrandUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(BrandUpdateEvent $event)
public function update(BrandUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $brand = BrandQuery::create()->findPk($event->getBrandId())) {
$brand->setDispatcher($event->getDispatcher());
$brand->setDispatcher($dispatcher);
$brand
->setVisible($event->getVisible())
@@ -75,13 +79,17 @@ class Brand extends BaseAction implements EventSubscriberInterface
* Toggle Brand visibility
*
* @param BrandToggleVisibilityEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
public function toggleVisibility(BrandToggleVisibilityEvent $event)
public function toggleVisibility(BrandToggleVisibilityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$brand = $event->getBrand();
$brand
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setVisible(!$brand->getVisible())
->save();
@@ -91,31 +99,62 @@ class Brand extends BaseAction implements EventSubscriberInterface
/**
* Change Brand SEO
*
* @param \Thelia\Core\Event\UpdateSeoEvent $event
*
* @return mixed
* @param UpdateSeoEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return Object
*/
public function updateSeo(UpdateSeoEvent $event)
public function updateSeo(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
return $this->genericUpdateSeo(BrandQuery::create(), $event);
return $this->genericUpdateSeo(BrandQuery::create(), $event, $dispatcher);
}
public function delete(BrandDeleteEvent $event)
public function delete(BrandDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $brand = BrandQuery::create()->findPk($event->getBrandId())) {
$brand->setDispatcher($event->getDispatcher())->delete();
$brand->setDispatcher($dispatcher)->delete();
$event->setBrand($brand);
}
}
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(BrandQuery::create(), $event);
$this->genericUpdatePosition(BrandQuery::create(), $event, $dispatcher);
}
/**
* @inheritdoc
* Check if is a brand view and if brand_id is visible
*
* @param ViewCheckEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function viewCheck(ViewCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->getView() == 'brand') {
$brand = BrandQuery::create()
->filterById($event->getViewId())
->filterByVisible(1)
->count();
if ($brand == 0) {
$dispatcher->dispatch(TheliaEvents::VIEW_BRAND_ID_NOT_VISIBLE, $event);
}
}
}
/**
* @param ViewCheckEvent $event
* @throws NotFoundHttpException
*/
public function viewBrandIdNotVisible(ViewCheckEvent $event)
{
throw new NotFoundHttpException();
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
@@ -128,6 +167,9 @@ class Brand extends BaseAction implements EventSubscriberInterface
TheliaEvents::BRAND_UPDATE_POSITION => array('updatePosition', 128),
TheliaEvents::BRAND_TOGGLE_VISIBILITY => array('toggleVisibility', 128),
TheliaEvents::VIEW_CHECK => array('viewCheck', 128),
TheliaEvents::VIEW_BRAND_ID_NOT_VISIBLE => array('viewBrandIdNotVisible', 128),
);
}
}

View File

@@ -11,6 +11,8 @@
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Filesystem\Filesystem;
use Thelia\Core\Event\Cache\CacheEvent;
@@ -19,39 +21,35 @@ use Thelia\Core\Event\TheliaEvents;
/**
* Class Cache
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Cache extends BaseAction implements EventSubscriberInterface
{
/** @var AdapterInterface */
protected $adapter;
/**
* CacheListener constructor.
* @param AdapterInterface $adapter
*/
public function __construct(AdapterInterface $adapter)
{
$this->adapter = $adapter;
}
public function cacheClear(CacheEvent $event)
{
// clear cache on thelia.cache service
$this->adapter->clear();
$dir = $event->getDir();
$fs = new Filesystem();
$fs->remove($dir);
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{

View File

@@ -14,15 +14,27 @@ namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Thelia\Core\Event\Cart\CartCreateEvent;
use Thelia\Core\Event\Cart\CartDuplicationEvent;
use Thelia\Core\Event\Cart\CartPersistEvent;
use Thelia\Core\Event\Cart\CartRestoreEvent;
use Thelia\Core\Event\Cart\CartEvent;
use Thelia\Core\Event\Currency\CurrencyChangeEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Exception\TheliaProcessException;
use Thelia\Model\Base\CustomerQuery;
use Thelia\Model\Base\ProductSaleElementsQuery;
use Thelia\Model\Currency as CurrencyModel;
use Thelia\Model\CartItem;
use Thelia\Model\Cart as CartModel;
use Thelia\Model\CartItemQuery;
use Thelia\Model\CartQuery;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Currency;
use Thelia\Model\ProductSaleElementsQuery;
use Thelia\Model\Customer as CustomerModel;
use Thelia\Model\ProductSaleElements;
use Thelia\Model\Tools\ProductPriceTools;
use Thelia\Tools\TokenProvider;
/**
*
@@ -30,19 +42,44 @@ use Thelia\Model\Tools\ProductPriceTools;
*
* Class Cart
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Cart extends BaseAction implements EventSubscriberInterface
{
/** @var RequestStack */
protected $requestStack;
/** @var TokenProvider */
protected $tokenProvider;
public function __construct(RequestStack $requestStack, TokenProvider $tokenProvider)
{
$this->requestStack = $requestStack;
$this->tokenProvider = $tokenProvider;
}
public function persistCart(CartPersistEvent $event)
{
$cart = $event->getCart();
if ($cart->isNew()) {
$cart
->setToken($this->generateCartCookieIdentifier())
->save();
$this->getSession()->setSessionCart($cart);
}
}
/**
*
* add an article in the current cart
*
* @param \Thelia\Core\Event\Cart\CartEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function addItem(CartEvent $event)
public function addItem(CartEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$cart = $event->getCart();
$newness = $event->getNewness();
$append = $event->getAppend();
@@ -51,6 +88,11 @@ class Cart extends BaseAction implements EventSubscriberInterface
$customer = $cart->getCustomer();
$discount = 0;
if ($cart->isNew()) {
$persistEvent = new CartPersistEvent($cart);
$dispatcher->dispatch(TheliaEvents::CART_PERSIST, $persistEvent);
}
if (null !== $customer && $customer->getDiscount() > 0) {
$discount = $customer->getDiscount();
}
@@ -58,29 +100,29 @@ class Cart extends BaseAction implements EventSubscriberInterface
$productSaleElementsId = $event->getProductSaleElementsId();
$productId = $event->getProduct();
$cartItem = $this->findItem($cart->getId(), $productId, $productSaleElementsId);
// Search for an identical item in the cart
$findItemEvent = clone $event;
$dispatcher->dispatch(TheliaEvents::CART_FINDITEM, $findItemEvent);
$cartItem = $findItemEvent->getCartItem();
if ($cartItem === null || $newness) {
$productSaleElements = ProductSaleElementsQuery::create()
->findPk($productSaleElementsId);
$productSaleElements = ProductSaleElementsQuery::create()->findPk($productSaleElementsId);
if (null !== $productSaleElements) {
$productPrices = $productSaleElements->getPricesByCurrency($currency, $discount);
$event->setCartItem(
$this->doAddItem($event->getDispatcher(), $cart, $productId, $productSaleElements, $quantity, $productPrices)
);
$cartItem = $this->doAddItem($dispatcher, $cart, $productId, $productSaleElements, $quantity, $productPrices);
} else {
// We did no find any PSE... Something is wrong with the DB, just throw an exception.
throw new TheliaProcessException("This item cannot be added to the cart: no matching product sale element was found.");
}
} elseif ($append && $cartItem !== null) {
$cartItem->addQuantity($quantity)->save();
}
if ($append && $cartItem !== null) {
$cartItem->addQuantity($quantity)
->save();
$event->setCartItem(
$cartItem
);
}
$event->setCartItem($cartItem);
}
/**
@@ -91,13 +133,16 @@ class Cart extends BaseAction implements EventSubscriberInterface
*/
public function deleteItem(CartEvent $event)
{
if (null !== $cartItemId = $event->getCartItem()) {
if (null !== $cartItemId = $event->getCartItemId()) {
$cart = $event->getCart();
CartItemQuery::create()
->filterByCartId($cart->getId())
->filterById($cartItemId)
->delete();
// Force an update of the Cart object to provide
// to other listeners an updated CartItem collection.
$cart->clearCartItems();
}
}
@@ -119,10 +164,12 @@ class Cart extends BaseAction implements EventSubscriberInterface
* don't use Form here just test the Request.
*
* @param \Thelia\Core\Event\Cart\CartEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function changeItem(CartEvent $event)
public function changeItem(CartEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ((null !== $cartItemId = $event->getCartItem()) && (null !== $quantity = $event->getQuantity())) {
if ((null !== $cartItemId = $event->getCartItemId()) && (null !== $quantity = $event->getQuantity())) {
$cart = $event->getCart();
$cartItem = CartItemQuery::create()
@@ -132,16 +179,16 @@ class Cart extends BaseAction implements EventSubscriberInterface
if ($cartItem) {
$event->setCartItem(
$this->updateQuantity($event->getDispatcher(), $cartItem, $quantity)
$this->updateQuantity($dispatcher, $cartItem, $quantity)
);
}
}
}
public function updateCart(CurrencyChangeEvent $event)
public function updateCart(CurrencyChangeEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$session = $event->getRequest()->getSession();
$cart = $session->getCart();
$cart = $event->getRequest()->getSession()->getSessionCart($dispatcher);
if (null !== $cart) {
$this->updateCartPrices($cart, $event->getCurrency());
}
@@ -154,9 +201,8 @@ class Cart extends BaseAction implements EventSubscriberInterface
* @param \Thelia\Model\Cart $cart
* @param \Thelia\Model\Currency $currency
*/
public function updateCartPrices(\Thelia\Model\Cart $cart, Currency $currency)
public function updateCartPrices(CartModel $cart, CurrencyModel $currency)
{
$customer = $cart->getCustomer();
$discount = 0;
@@ -180,47 +226,17 @@ class Cart extends BaseAction implements EventSubscriberInterface
// update the currency cart
$cart->setCurrencyId($currency->getId());
$cart->save();
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::CART_ADDITEM => array("addItem", 128),
TheliaEvents::CART_DELETEITEM => array("deleteItem", 128),
TheliaEvents::CART_UPDATEITEM => array("changeItem", 128),
TheliaEvents::CART_CLEAR => array("clear", 128),
TheliaEvents::CHANGE_DEFAULT_CURRENCY => array("updateCart", 128),
);
}
/**
* increase the quantity for an existing cartItem
*
* @param EventDispatcherInterface $dispatcher
* @param CartItem $cartItem
* @param float $quantity
*
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
* @return CartItem
*/
protected function updateQuantity(EventDispatcherInterface $dispatcher, CartItem $cartItem, $quantity)
@@ -235,17 +251,23 @@ class Cart extends BaseAction implements EventSubscriberInterface
/**
* try to attach a new item to an existing cart
*
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* @param EventDispatcherInterface $dispatcher
* @param \Thelia\Model\Cart $cart
* @param int $productId
* @param \Thelia\Model\ProductSaleElements $productSaleElements
* @param ProductSaleElements $productSaleElements
* @param float $quantity
* @param ProductPriceTools $productPrices
*
* @return CartItem
*/
protected function doAddItem(EventDispatcherInterface $dispatcher, \Thelia\Model\Cart $cart, $productId, \Thelia\Model\ProductSaleElements $productSaleElements, $quantity, ProductPriceTools $productPrices)
{
protected function doAddItem(
EventDispatcherInterface $dispatcher,
CartModel $cart,
$productId,
ProductSaleElements $productSaleElements,
$quantity,
ProductPriceTools $productPrices
) {
$cartItem = new CartItem();
$cartItem->setDisptacher($dispatcher);
$cartItem
@@ -269,7 +291,9 @@ class Cart extends BaseAction implements EventSubscriberInterface
* @param int $cartId
* @param int $productId
* @param int $productSaleElementsId
* @return ChildCartItem
* @return CartItem
*
* @deprecated this method is deprecated. Dispatch a TheliaEvents::CART_FINDITEM instead
*/
protected function findItem($cartId, $productId, $productSaleElementsId)
{
@@ -280,4 +304,247 @@ class Cart extends BaseAction implements EventSubscriberInterface
->findOne();
}
/**
* Find a specific record in CartItem table using the current CartEvent
*
* @param CartEvent $event the cart event
*/
public function findCartItem(CartEvent $event)
{
// Do not try to find a cartItem if one exists in the event, as previous event handlers
// mays have put it in th event.
if (null === $event->getCartItem() && null !== $foundItem = CartItemQuery::create()
->filterByCartId($event->getCart()->getId())
->filterByProductId($event->getProduct())
->filterByProductSaleElementsId($event->getProductSaleElementsId())
->findOne()) {
$event->setCartItem($foundItem);
}
}
/**
* Search if cart already exists in session. If not try to restore it from the cart cookie,
* or duplicate an old one.
*
* @param CartRestoreEvent $cartRestoreEvent
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function restoreCurrentCart(CartRestoreEvent $cartRestoreEvent, $eventName, EventDispatcherInterface $dispatcher)
{
$cookieName = ConfigQuery::read("cart.cookie_name", 'thelia_cart');
$persistentCookie = ConfigQuery::read("cart.use_persistent_cookie", 1);
$cart = null;
if ($this->requestStack->getCurrentRequest()->cookies->has($cookieName) && $persistentCookie) {
$cart = $this->managePersistentCart($cartRestoreEvent, $cookieName, $dispatcher);
} elseif (!$persistentCookie) {
$cart = $this->manageNonPersistentCookie($cartRestoreEvent, $dispatcher);
}
// Still no cart ? Create a new one.
if (null === $cart) {
$cart = $this->dispatchNewCart($dispatcher);
}
$cartRestoreEvent->setCart($cart);
}
/**
* The cart token is not saved in a cookie, if the cart is present in session, we just change the customer id
* if needed or create duplicate the current cart if the customer is not the same as customer already present in
* the cart.
*
* @param CartRestoreEvent $cartRestoreEvent
* @param EventDispatcherInterface $dispatcher
* @return CartModel
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
protected function manageNonPersistentCookie(CartRestoreEvent $cartRestoreEvent, EventDispatcherInterface $dispatcher)
{
$cart = $cartRestoreEvent->getCart();
if (null === $cart) {
$cart = $this->dispatchNewCart($dispatcher);
} else {
$cart = $this->manageCartDuplicationAtCustomerLogin($cart, $dispatcher);
}
return $cart;
}
/**
*
* The cart token is saved in a cookie so we try to retrieve it. Then the customer is checked.
*
* @param CartRestoreEvent $cartRestoreEvent
* @param $cookieName
* @param EventDispatcherInterface $dispatcher
* @return CartModel
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
protected function managePersistentCart(CartRestoreEvent $cartRestoreEvent, $cookieName, EventDispatcherInterface $dispatcher)
{
// The cart cookie exists -> get the cart token
$token = $this->requestStack->getCurrentRequest()->cookies->get($cookieName);
// Check if a cart exists for this token
if (null !== $cart = CartQuery::create()->findOneByToken($token)) {
$cart = $this->manageCartDuplicationAtCustomerLogin($cart, $dispatcher);
}
return $cart;
}
protected function manageCartDuplicationAtCustomerLogin(CartModel $cart, EventDispatcherInterface $dispatcher)
{
/** @var CustomerModel $customer */
if (null !== $customer = $this->getSession()->getCustomerUser()) {
// Check if we have to duplicate the existing cart.
$duplicateCart = true;
// A customer is logged in.
if (null === $cart->getCustomerId()) {
// If the customer has a discount, whe have to duplicate the cart,
// so that the discount will be applied to the products in cart.
if (0 === $customer->getDiscount() || 0 === $cart->countCartItems()) {
// If no discount, or an empty cart, there's no need to duplicate.
$duplicateCart = false;
}
}
if ($duplicateCart) {
// Duplicate the cart
$cart = $this->duplicateCart($dispatcher, $cart, $customer);
} else {
// No duplication required, just assign the cart to the customer
$cart->setCustomerId($customer->getId())->save();
}
} elseif ($cart->getCustomerId() != null) {
// The cart belongs to another user
if (0 === $cart->countCartItems()) {
// No items in cart, assign it to nobody.
$cart->setCustomerId(null)->save();
} else {
// Some itemls in cart, duplicate it without assigning a customer ID.
$cart = $this->duplicateCart($dispatcher, $cart);
}
}
return $cart;
}
/**
* @param EventDispatcherInterface $dispatcher
* @return CartModel
*/
protected function dispatchNewCart(EventDispatcherInterface $dispatcher)
{
$cartCreateEvent = new CartCreateEvent();
$dispatcher->dispatch(TheliaEvents::CART_CREATE_NEW, $cartCreateEvent);
return $cartCreateEvent->getCart();
}
/**
* Create a new, empty cart object, and assign it to the current customer, if any.
*
* @param CartCreateEvent $cartCreateEvent
*/
public function createEmptyCart(CartCreateEvent $cartCreateEvent)
{
$cart = new CartModel();
$cart->setCurrency($this->getSession()->getCurrency(true));
/** @var CustomerModel $customer */
if (null !== $customer = $this->getSession()->getCustomerUser()) {
$cart->setCustomer(CustomerQuery::create()->findPk($customer->getId()));
}
$this->getSession()->setSessionCart($cart);
if (ConfigQuery::read("cart.use_persistent_cookie", 1) == 1) {
// set cart_use_cookie to "" to remove the cart cookie
// see Thelia\Core\EventListener\ResponseListener
$this->getSession()->set("cart_use_cookie", "");
}
$cartCreateEvent->setCart($cart);
}
/**
* Duplicate an existing Cart. If a customer ID is provided the created cart will be attached to this customer.
*
* @param EventDispatcherInterface $dispatcher
* @param CartModel $cart
* @param CustomerModel $customer
* @return CartModel
*/
protected function duplicateCart(EventDispatcherInterface $dispatcher, CartModel $cart, CustomerModel $customer = null)
{
$newCart = $cart->duplicate(
$this->generateCartCookieIdentifier(),
$customer,
$this->getSession()->getCurrency(),
$dispatcher
);
$cartEvent = new CartDuplicationEvent($newCart, $cart);
$dispatcher->dispatch(TheliaEvents::CART_DUPLICATE, $cartEvent);
return $cartEvent->getDuplicatedCart();
}
/**
* Generate the cart cookie identifier, or return null if the cart is only managed in the session object,
* not in a client cookie.
*
* @return string
*/
protected function generateCartCookieIdentifier()
{
$id = null;
if (ConfigQuery::read("cart.use_persistent_cookie", 1) == 1) {
$id = $this->tokenProvider->getToken();
$this->getSession()->set('cart_use_cookie', $id);
}
return $id;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::CART_PERSIST => array("persistCart", 128),
TheliaEvents::CART_RESTORE_CURRENT => array("restoreCurrentCart", 128),
TheliaEvents::CART_CREATE_NEW => array("createEmptyCart", 128),
TheliaEvents::CART_ADDITEM => array("addItem", 128),
TheliaEvents::CART_FINDITEM => array("findCartItem", 128),
TheliaEvents::CART_DELETEITEM => array("deleteItem", 128),
TheliaEvents::CART_UPDATEITEM => array("changeItem", 128),
TheliaEvents::CART_CLEAR => array("clear", 128),
TheliaEvents::CHANGE_DEFAULT_CURRENCY => array("updateCart", 128),
);
}
/**
* Returns the session from the current request
*
* @return \Thelia\Core\HttpFoundation\Session\Session
*/
protected function getSession()
{
return $this->requestStack->getCurrentRequest()->getSession();
}
}

View File

@@ -12,14 +12,17 @@
namespace Thelia\Action;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Thelia\Core\Event\File\FileDeleteEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Model\CategoryDocumentQuery;
use Thelia\Model\CategoryImageQuery;
use Thelia\Model\CategoryQuery;
use Thelia\Model\Category as CategoryModel;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Category\CategoryUpdateEvent;
use Thelia\Core\Event\Category\CategoryCreateEvent;
use Thelia\Core\Event\Category\CategoryDeleteEvent;
@@ -27,8 +30,10 @@ use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\Category\CategoryToggleVisibilityEvent;
use Thelia\Core\Event\Category\CategoryAddContentEvent;
use Thelia\Core\Event\Category\CategoryDeleteContentEvent;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Model\CategoryAssociatedContent;
use Thelia\Model\CategoryAssociatedContentQuery;
use Thelia\Model\Map\CategoryTableMap;
class Category extends BaseAction implements EventSubscriberInterface
{
@@ -36,13 +41,15 @@ class Category extends BaseAction implements EventSubscriberInterface
* Create a new category entry
*
* @param \Thelia\Core\Event\Category\CategoryCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(CategoryCreateEvent $event)
public function create(CategoryCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$category = new CategoryModel();
$category
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setParent($event->getParent())
@@ -59,14 +66,15 @@ class Category extends BaseAction implements EventSubscriberInterface
* Change a category
*
* @param \Thelia\Core\Event\Category\CategoryUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(CategoryUpdateEvent $event)
public function update(CategoryUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $category = CategoryQuery::create()->findPk($event->getCategoryId())) {
$category
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setDefaultTemplateId($event->getDefaultTemplateId() == 0 ? null : $event->getDefaultTemplateId())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setDescription($event->getDescription())
@@ -85,44 +93,78 @@ class Category extends BaseAction implements EventSubscriberInterface
/**
* Change a Category SEO
*
* @param \Thelia\Core\Event\UpdateSeoEvent $event
*
* @return mixed
* @param UpdateSeoEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return Object
*/
public function updateSeo(UpdateSeoEvent $event)
public function updateSeo(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
return $this->genericUpdateSeo(CategoryQuery::create(), $event);
return $this->genericUpdateSeo(CategoryQuery::create(), $event, $dispatcher);
}
/**
* Delete a category entry
*
* @param \Thelia\Core\Event\Category\CategoryDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Exception
*/
public function delete(CategoryDeleteEvent $event)
public function delete(CategoryDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $category = CategoryQuery::create()->findPk($event->getCategoryId())) {
$con = Propel::getWriteConnection(CategoryTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$fileList = ['images' => [], 'documentList' => []];
// Get category's files to delete after category deletion
$fileList['images']['list'] = CategoryImageQuery::create()
->findByCategoryId($event->getCategoryId());
$fileList['images']['type'] = TheliaEvents::IMAGE_DELETE;
$fileList['documentList']['list'] = CategoryDocumentQuery::create()
->findByCategoryId($event->getCategoryId());
$fileList['documentList']['type'] = TheliaEvents::DOCUMENT_DELETE;
// Delete category
$category
->setDispatcher($event->getDispatcher())
->delete()
;
->setDispatcher($dispatcher)
->delete($con);
$event->setCategory($category);
// Dispatch delete category's files event
foreach ($fileList as $fileTypeList) {
foreach ($fileTypeList['list'] as $fileToDelete) {
$fileDeleteEvent = new FileDeleteEvent($fileToDelete);
$dispatcher->dispatch($fileTypeList['type'], $fileDeleteEvent);
}
}
$con->commit();
} catch (\Exception $e) {
$con->rollback();
throw $e;
}
}
}
/**
* Toggle category visibility. No form used here
*
* @param ActionEvent $event
* @param CategoryToggleVisibilityEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function toggleVisibility(CategoryToggleVisibilityEvent $event)
public function toggleVisibility(CategoryToggleVisibilityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$category = $event->getCategory();
$category
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setVisible($category->getVisible() ? false : true)
->save()
;
@@ -133,23 +175,24 @@ class Category extends BaseAction implements EventSubscriberInterface
/**
* Changes position, selecting absolute ou relative change.
*
* @param CategoryChangePositionEvent $event
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(CategoryQuery::create(), $event);
$this->genericUpdatePosition(CategoryQuery::create(), $event, $dispatcher);
}
public function addContent(CategoryAddContentEvent $event)
public function addContent(CategoryAddContentEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (CategoryAssociatedContentQuery::create()
->filterByContentId($event->getContentId())
->filterByCategory($event->getCategory())->count() <= 0) {
$content = new CategoryAssociatedContent();
$content
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setCategory($event->getCategory())
->setContentId($event->getContentId())
->save()
@@ -157,7 +200,7 @@ class Category extends BaseAction implements EventSubscriberInterface
}
}
public function removeContent(CategoryDeleteContentEvent $event)
public function removeContent(CategoryDeleteContentEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$content = CategoryAssociatedContentQuery::create()
->filterByContentId($event->getContentId())
@@ -166,11 +209,41 @@ class Category extends BaseAction implements EventSubscriberInterface
if ($content !== null) {
$content
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->delete();
}
}
/**
* Check if is a category view and if category_id is visible
*
* @param ViewCheckEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function viewCheck(ViewCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->getView() == 'category') {
$category = CategoryQuery::create()
->filterById($event->getViewId())
->filterByVisible(1)
->count();
if ($category == 0) {
$dispatcher->dispatch(TheliaEvents::VIEW_CATEGORY_ID_NOT_VISIBLE, $event);
}
}
}
/**
* @param ViewCheckEvent $event
* @throws NotFoundHttpException
*/
public function viewcategoryIdNotVisible(ViewCheckEvent $event)
{
throw new NotFoundHttpException();
}
/**
* {@inheritDoc}
*/
@@ -188,6 +261,8 @@ class Category extends BaseAction implements EventSubscriberInterface
TheliaEvents::CATEGORY_ADD_CONTENT => array("addContent", 128),
TheliaEvents::CATEGORY_REMOVE_CONTENT => array("removeContent", 128),
TheliaEvents::VIEW_CHECK => array('viewCheck', 128),
TheliaEvents::VIEW_CATEGORY_ID_NOT_VISIBLE => array('viewcategoryIdNotVisible', 128),
);
}
}

View File

@@ -11,6 +11,8 @@
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Config\ConfigCreateEvent;
use Thelia\Core\Event\Config\ConfigDeleteEvent;
@@ -25,12 +27,14 @@ class Config extends BaseAction implements EventSubscriberInterface
* Create a new configuration entry
*
* @param \Thelia\Core\Event\Config\ConfigCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(ConfigCreateEvent $event)
public function create(ConfigCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$config = new ConfigModel();
$config->setDispatcher($event->getDispatcher())
$config->setDispatcher($dispatcher)
->setName($event->getEventName())
->setValue($event->getValue())
->setLocale($event->getLocale())
@@ -46,15 +50,14 @@ class Config extends BaseAction implements EventSubscriberInterface
* Change a configuration entry value
*
* @param \Thelia\Core\Event\Config\ConfigUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function setValue(ConfigUpdateEvent $event)
public function setValue(ConfigUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $config = ConfigQuery::create()->findPk($event->getConfigId())) {
if ($event->getValue() !== $config->getValue()) {
$config->setDispatcher($event->getDispatcher())->setValue($event->getValue())->save();
$config->setDispatcher($dispatcher)->setValue($event->getValue())->save();
$event->setConfig($config);
}
@@ -65,13 +68,13 @@ class Config extends BaseAction implements EventSubscriberInterface
* Change a configuration entry
*
* @param \Thelia\Core\Event\Config\ConfigUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function modify(ConfigUpdateEvent $event)
public function modify(ConfigUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $config = ConfigQuery::create()->findPk($event->getConfigId())) {
$config->setDispatcher($event->getDispatcher())
$config->setDispatcher($dispatcher)
->setName($event->getEventName())
->setValue($event->getValue())
->setHidden($event->getHidden())
@@ -91,15 +94,14 @@ class Config extends BaseAction implements EventSubscriberInterface
* Delete a configuration entry
*
* @param \Thelia\Core\Event\Config\ConfigDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function delete(ConfigDeleteEvent $event)
public function delete(ConfigDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== ($config = ConfigQuery::create()->findPk($event->getConfigId()))) {
if (!$config->getSecured()) {
$config->setDispatcher($event->getDispatcher())->delete();
$config->setDispatcher($dispatcher)->delete();
$event->setConfig($config);
}

View File

@@ -12,34 +12,41 @@
namespace Thelia\Action;
use Propel\Runtime\Exception\PropelException;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Thelia\Core\Event\Content\ContentAddFolderEvent;
use Thelia\Core\Event\Content\ContentCreateEvent;
use Thelia\Core\Event\Content\ContentDeleteEvent;
use Thelia\Core\Event\Content\ContentRemoveFolderEvent;
use Thelia\Core\Event\Content\ContentToggleVisibilityEvent;
use Thelia\Core\Event\Content\ContentUpdateEvent;
use Thelia\Core\Event\File\FileDeleteEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Model\ContentDocumentQuery;
use Thelia\Model\ContentFolder;
use Thelia\Model\ContentFolderQuery;
use Thelia\Model\ContentImageQuery;
use Thelia\Model\ContentQuery;
use Thelia\Model\Content as ContentModel;
use Thelia\Model\Map\ContentTableMap;
/**
* Class Content
* @package Thelia\Action
* @author manuel raynaud <mraynaud@openstudio.fr>
* @author manuel raynaud <manu@raynaud.io>
*/
class Content extends BaseAction implements EventSubscriberInterface
{
public function create(ContentCreateEvent $event)
public function create(ContentCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$content = new ContentModel();
$content
$content = (new ContentModel)
->setDispatcher($dispatcher)
->setVisible($event->getVisible())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
@@ -53,12 +60,19 @@ class Content extends BaseAction implements EventSubscriberInterface
* process update content
*
* @param ContentUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws PropelException
* @throws \Exception
*/
public function update(ContentUpdateEvent $event)
public function update(ContentUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $content = ContentQuery::create()->findPk($event->getContentId())) {
$content->setDispatcher($event->getDispatcher());
$con = Propel::getWriteConnection(ContentTableMap::DATABASE_NAME);
$con->beginTransaction();
$content->setDispatcher($dispatcher);
try {
$content
->setVisible($event->getVisible())
->setLocale($event->getLocale())
@@ -66,55 +80,96 @@ class Content extends BaseAction implements EventSubscriberInterface
->setDescription($event->getDescription())
->setChapo($event->getChapo())
->setPostscriptum($event->getPostscriptum())
->save()
->save($con)
;
$content->updateDefaultFolder($event->getDefaultFolder());
$content->setDefaultFolder($event->getDefaultFolder());
$event->setContent($content);
$con->commit();
} catch (PropelException $e) {
$con->rollBack();
throw $e;
}
}
}
/**
* Change Content SEO
*
* @param \Thelia\Core\Event\UpdateSeoEvent $event
*
* @return mixed
* @param UpdateSeoEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return Object
*/
public function updateSeo(UpdateSeoEvent $event)
public function updateSeo(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
return $this->genericUpdateSeo(ContentQuery::create(), $event);
return $this->genericUpdateSeo(ContentQuery::create(), $event, $dispatcher);
}
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(ContentQuery::create(), $event);
$this->genericUpdateDelegatePosition(
ContentFolderQuery::create()
->filterByContentId($event->getObjectId())
->filterByFolderId($event->getReferrerId()),
$event,
$dispatcher
);
}
public function toggleVisibility(ContentToggleVisibilityEvent $event)
public function toggleVisibility(ContentToggleVisibilityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$content = $event->getContent();
$content
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setVisible(!$content->getVisible())
->save();
$event->setContent($content);
}
public function delete(ContentDeleteEvent $event)
public function delete(ContentDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $content = ContentQuery::create()->findPk($event->getContentId())) {
$con = Propel::getWriteConnection(ContentTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$fileList = ['images' => [], 'documentList' => []];
$defaultFolderId = $content->getDefaultFolderId();
$content->setDispatcher($event->getDispatcher())
->delete();
// Get content's files to delete after content deletion
$fileList['images']['list'] = ContentImageQuery::create()
->findByContentId($event->getContentId());
$fileList['images']['type'] = TheliaEvents::IMAGE_DELETE;
$fileList['documentList']['list'] = ContentDocumentQuery::create()
->findByContentId($event->getContentId());
$fileList['documentList']['type'] = TheliaEvents::DOCUMENT_DELETE;
// Delete content
$content->setDispatcher($dispatcher)
->delete($con);
$event->setDefaultFolderId($defaultFolderId);
$event->setContent($content);
// Dispatch delete content's files event
foreach ($fileList as $fileTypeList) {
foreach ($fileTypeList['list'] as $fileToDelete) {
$fileDeleteEvent = new FileDeleteEvent($fileToDelete);
$dispatcher->dispatch($fileTypeList['type'], $fileDeleteEvent);
}
}
$con->commit();
} catch (\Exception $e) {
$con->rollback();
throw $e;
}
}
}
@@ -126,17 +181,18 @@ class Content extends BaseAction implements EventSubscriberInterface
*/
public function addFolder(ContentAddFolderEvent $event)
{
if(ContentFolderQuery::create()
if (ContentFolderQuery::create()
->filterByContent($event->getContent())
->filterByFolderId($event->getFolderId())
->count() <= 0
) {
$contentFolder = new ContentFolder();
$contentFolder
$contentFolder = (new ContentFolder())
->setFolderId($event->getFolderId())
->setContent($event->getContent())
->setDefaultFolder(false)
->setDefaultFolder(false);
$contentFolder
->setPosition($contentFolder->getNextPosition())
->save();
}
}
@@ -154,24 +210,37 @@ class Content extends BaseAction implements EventSubscriberInterface
}
/**
* Returns an array of event names this subscriber wants to listen to.
* Check if is a content view and if content_id is visible
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* @param ViewCheckEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function viewCheck(ViewCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->getView() == 'content') {
$content = ContentQuery::create()
->filterById($event->getViewId())
->filterByVisible(1)
->count();
if ($content == 0) {
$dispatcher->dispatch(TheliaEvents::VIEW_CONTENT_ID_NOT_VISIBLE, $event);
}
}
}
/**
* @param ViewCheckEvent $event
* @throws NotFoundHttpException
*/
public function viewContentIdNotVisible(ViewCheckEvent $event)
{
throw new NotFoundHttpException();
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
@@ -186,7 +255,9 @@ class Content extends BaseAction implements EventSubscriberInterface
TheliaEvents::CONTENT_ADD_FOLDER => array('addFolder', 128),
TheliaEvents::CONTENT_REMOVE_FOLDER => array('removeFolder', 128),
TheliaEvents::VIEW_CHECK => array('viewCheck', 128),
TheliaEvents::VIEW_CONTENT_ID_NOT_VISIBLE => array('viewContentIdNotVisible', 128),
);
}
}

View File

@@ -12,10 +12,12 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Country\CountryCreateEvent;
use Thelia\Core\Event\Country\CountryDeleteEvent;
use Thelia\Core\Event\Country\CountryToggleDefaultEvent;
use Thelia\Core\Event\Country\CountryToggleVisibilityEvent;
use Thelia\Core\Event\Country\CountryUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\Country as CountryModel;
@@ -24,34 +26,38 @@ use Thelia\Model\CountryQuery;
/**
* Class Country
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Country extends BaseAction implements EventSubscriberInterface
{
public function create(CountryCreateEvent $event)
{
$country = new CountryModel();
$country
->setVisible($event->isVisible())
->setIsocode($event->getIsocode())
->setIsoalpha2($event->getIsoAlpha2())
->setIsoalpha3($event->getIsoAlpha3())
->setHasStates($event->isHasStates())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->save();
$event->setCountry($country);
}
public function update(CountryUpdateEvent $event)
{
if (null !== $country = CountryQuery::create()->findPk($event->getCountryId())) {
$country
->setVisible($event->isVisible())
->setIsocode($event->getIsocode())
->setIsoalpha2($event->getIsoAlpha2())
->setIsoalpha3($event->getIsoAlpha3())
->setHasStates($event->isHasStates())
->setNeedZipCode($event->isNeedZipCode())
->setZipCodeFormat($event->getZipCodeFormat())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setChapo($event->getChapo())
@@ -73,7 +79,7 @@ class Country extends BaseAction implements EventSubscriberInterface
public function toggleDefault(CountryToggleDefaultEvent $event)
{
if ( null !== $country = CountryQuery::create()->findPk($event->getCountryId())) {
if (null !== $country = CountryQuery::create()->findPk($event->getCountryId())) {
$country->toggleDefault();
$event->setCountry($country);
@@ -81,24 +87,26 @@ class Country extends BaseAction implements EventSubscriberInterface
}
/**
* Returns an array of event names this subscriber wants to listen to.
* Toggle Country visibility
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* @param CountryToggleVisibilityEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function toggleVisibility(CountryToggleVisibilityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$country = $event->getCountry();
$country
->setDispatcher($dispatcher)
->setVisible(!$country->getVisible())
->save();
$event->setCountry($country);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
@@ -106,7 +114,8 @@ class Country extends BaseAction implements EventSubscriberInterface
TheliaEvents::COUNTRY_CREATE => array('create', 128),
TheliaEvents::COUNTRY_UPDATE => array('update', 128),
TheliaEvents::COUNTRY_DELETE => array('delete', 128),
TheliaEvents::COUNTRY_TOGGLE_DEFAULT => array('toggleDefault', 128)
TheliaEvents::COUNTRY_TOGGLE_DEFAULT => array('toggleDefault', 128),
TheliaEvents::COUNTRY_TOGGLE_VISIBILITY => array('toggleVisibility', 128)
);
}
}

View File

@@ -13,15 +13,18 @@
namespace Thelia\Action;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Thelia\Condition\ConditionCollection;
use Thelia\Condition\ConditionFactory;
use Thelia\Condition\Implementation\ConditionInterface;
use Thelia\Core\Event\Coupon\CouponConsumeEvent;
use Thelia\Core\Event\Coupon\CouponCreateOrUpdateEvent;
use Thelia\Core\Event\Coupon\CouponDeleteEvent;
use Thelia\Core\Event\Order\OrderEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpFoundation\Request;
use Thelia\Coupon\CouponFactory;
use Thelia\Coupon\CouponManager;
use Thelia\Coupon\Type\CouponInterface;
@@ -35,6 +38,8 @@ use Thelia\Model\Map\OrderCouponTableMap;
use Thelia\Model\OrderCoupon;
use Thelia\Model\OrderCouponCountry;
use Thelia\Model\OrderCouponModule;
use Thelia\Model\OrderCouponQuery;
use Thelia\Model\OrderStatusQuery;
/**
* Process Coupon Events
@@ -45,10 +50,8 @@ use Thelia\Model\OrderCouponModule;
*/
class Coupon extends BaseAction implements EventSubscriberInterface
{
/**
* @var \Thelia\Core\HttpFoundation\Request
*/
protected $request;
/** @var RequestStack */
protected $requestStack;
/** @var CouponFactory $couponFactory */
protected $couponFactory;
@@ -62,11 +65,14 @@ class Coupon extends BaseAction implements EventSubscriberInterface
/** @var ConditionFactory $conditionFactory */
protected $conditionFactory;
public function __construct(Request $request,
CouponFactory $couponFactory, CouponManager $couponManager,
ConditionInterface $noConditionRule, ConditionFactory $conditionFactory)
{
$this->request = $request;
public function __construct(
RequestStack $requestStack,
CouponFactory $couponFactory,
CouponManager $couponManager,
ConditionInterface $noConditionRule,
ConditionFactory $conditionFactory
) {
$this->requestStack = $requestStack;
$this->couponFactory = $couponFactory;
$this->couponManager = $couponManager;
$this->noConditionRule = $noConditionRule;
@@ -77,57 +83,87 @@ class Coupon extends BaseAction implements EventSubscriberInterface
* Occurring when a Coupon is about to be created
*
* @param CouponCreateOrUpdateEvent $event Event creation or update Coupon
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(CouponCreateOrUpdateEvent $event)
public function create(CouponCreateOrUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$coupon = new CouponModel();
$this->createOrUpdate($coupon, $event);
$this->createOrUpdate($coupon, $event, $dispatcher);
}
/**
* Occurring when a Coupon is about to be updated
*
* @param CouponCreateOrUpdateEvent $event Event creation or update Coupon
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(CouponCreateOrUpdateEvent $event)
public function update(CouponCreateOrUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$coupon = $event->getCouponModel();
$this->createOrUpdate($coupon, $event);
$this->createOrUpdate($coupon, $event, $dispatcher);
}
public function delete(CouponDeleteEvent $event)
{
$coupon = $event->getCoupon();
if (null === $coupon) {
throw new \InvalidArgumentException(
sprintf(
"The coupon id '%d' doesn't exist",
$event->getCouponId()
)
);
}
$coupon->delete();
$event->setCoupon(null);
}
/**
* Occurring when a Coupon condition is about to be updated
*
* @param CouponCreateOrUpdateEvent $event Event creation or update Coupon condition
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updateCondition(CouponCreateOrUpdateEvent $event)
public function updateCondition(CouponCreateOrUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$modelCoupon = $event->getCouponModel();
$this->createOrUpdateCondition($modelCoupon, $event);
$this->createOrUpdateCondition($modelCoupon, $event, $dispatcher);
}
/**
* Clear all coupons in session.
*
* @param Event $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function clearAllCoupons()
public function clearAllCoupons(Event $event, $eventName, EventDispatcherInterface $dispatcher)
{
// Tell coupons to clear any data they may have stored
$this->couponManager->clear();
$this->request->getSession()->setConsumedCoupons(array());
$this->getSession()->setConsumedCoupons(array());
$this->updateOrderDiscount(null);
$this->updateOrderDiscount($event, $eventName, $dispatcher);
}
/**
* Occurring when a Coupon condition is about to be consumed
*
* @param CouponConsumeEvent $event Event consuming Coupon
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function consume(CouponConsumeEvent $event)
public function consume(CouponConsumeEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$totalDiscount = 0;
$isValid = false;
@@ -136,34 +172,18 @@ class Coupon extends BaseAction implements EventSubscriberInterface
$coupon = $this->couponFactory->buildCouponFromCode($event->getCode());
if ($coupon) {
$isValid = $coupon->isMatching();
if ($isValid) {
$consumedCoupons = $this->request->getSession()->getConsumedCoupons();
if (!isset($consumedCoupons) || !$consumedCoupons) {
$consumedCoupons = array();
}
if (!isset($consumedCoupons[$event->getCode()])) {
// Prevent accumulation of the same Coupon on a Checkout
$consumedCoupons[$event->getCode()] = $event->getCode();
$this->request->getSession()->setConsumedCoupons($consumedCoupons);
}
$this->couponManager->pushCouponInSession($event->getCode());
$totalDiscount = $this->couponManager->getDiscount();
$this->request
->getSession()
->getCart()
$this->getSession()
->getSessionCart($dispatcher)
->setDiscount($totalDiscount)
->save();
$this->request
->getSession()
$this->getSession()
->getOrder()
->setDiscount($totalDiscount)
;
@@ -174,18 +194,16 @@ class Coupon extends BaseAction implements EventSubscriberInterface
$event->setDiscount($totalDiscount);
}
public function updateOrderDiscount(/** @noinspection PhpUnusedParameterInspection */ $event)
public function updateOrderDiscount(Event $event, $eventName, EventDispatcherInterface $dispatcher)
{
$discount = $this->couponManager->getDiscount();
$this->request
->getSession()
->getCart()
$this->getSession()
->getSessionCart($dispatcher)
->setDiscount($discount)
->save();
$this->request
->getSession()
$this->getSession()
->getOrder()
->setDiscount($discount);
}
@@ -196,10 +214,11 @@ class Coupon extends BaseAction implements EventSubscriberInterface
*
* @param CouponModel $coupon Model to save
* @param CouponCreateOrUpdateEvent $event Event containing data
* @param EventDispatcherInterface $dispatcher
*/
protected function createOrUpdate(CouponModel $coupon, CouponCreateOrUpdateEvent $event)
protected function createOrUpdate(CouponModel $coupon, CouponCreateOrUpdateEvent $event, EventDispatcherInterface $dispatcher)
{
$coupon->setDispatcher($event->getDispatcher());
$coupon->setDispatcher($dispatcher);
// Set default condition if none found
/** @var ConditionInterface $noConditionRule */
@@ -229,7 +248,8 @@ class Coupon extends BaseAction implements EventSubscriberInterface
$event->getLocale(),
$event->getFreeShippingForCountries(),
$event->getFreeShippingForMethods(),
$event->getPerCustomerUsageCount()
$event->getPerCustomerUsageCount(),
$event->getStartDate()
);
$event->setCouponModel($coupon);
@@ -241,10 +261,11 @@ class Coupon extends BaseAction implements EventSubscriberInterface
*
* @param CouponModel $coupon Model to save
* @param CouponCreateOrUpdateEvent $event Event containing data
* @param EventDispatcherInterface $dispatcher
*/
protected function createOrUpdateCondition(CouponModel $coupon, CouponCreateOrUpdateEvent $event)
protected function createOrUpdateCondition(CouponModel $coupon, CouponCreateOrUpdateEvent $event, EventDispatcherInterface $dispatcher)
{
$coupon->setDispatcher($event->getDispatcher());
$coupon->setDispatcher($dispatcher);
/** @var ConditionFactory $conditionFactory */
$conditionFactory = $this->conditionFactory;
@@ -265,7 +286,6 @@ class Coupon extends BaseAction implements EventSubscriberInterface
$order = $event->getOrder();
if ($this->couponManager->isCouponRemovingPostage($order)) {
$order->setPostage(0);
$event->setOrder($order);
@@ -278,21 +298,23 @@ class Coupon extends BaseAction implements EventSubscriberInterface
* @param \Thelia\Core\Event\Order\OrderEvent $event
*
* @throws \Exception if something goes wrong.
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function afterOrder(OrderEvent $event)
public function afterOrder(OrderEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$consumedCoupons = $this->request->getSession()->getConsumedCoupons();
if (is_array($consumedCoupons)) {
/** @var CouponInterface[] $consumedCoupons */
$consumedCoupons = $this->couponManager->getCouponsKept();
if (is_array($consumedCoupons) && count($consumedCoupons) > 0) {
$con = Propel::getWriteConnection(OrderCouponTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
foreach ($consumedCoupons as $couponCode) {
$couponQuery = CouponQuery::create();
$couponModel = $couponQuery->findOneByCode($couponCode);
$couponModel->setLocale($this->request->getSession()->getLang()->getLocale());
$couponModel = $couponQuery->findOneByCode($couponCode->getCode());
$couponModel->setLocale($this->getSession()->getLang()->getLocale());
/* decrease coupon quantity */
$this->couponManager->decrementQuantity($couponModel, $event->getOrder()->getCustomerId());
@@ -302,12 +324,13 @@ class Coupon extends BaseAction implements EventSubscriberInterface
$orderCoupon->setOrder($event->getOrder())
->setCode($couponModel->getCode())
->setType($couponModel->getType())
->setAmount($couponModel->getAmount())
->setAmount($couponCode->exec())
->setTitle($couponModel->getTitle())
->setShortDescription($couponModel->getShortDescription())
->setDescription($couponModel->getDescription())
->setStartDate($couponModel->getStartDate())
->setExpirationDate($couponModel->getExpirationDate())
->setIsCumulative($couponModel->getIsCumulative())
->setIsRemovingPostage($couponModel->getIsRemovingPostage())
@@ -354,42 +377,90 @@ class Coupon extends BaseAction implements EventSubscriberInterface
}
// Clear all coupons.
$event->getDispatcher()->dispatch(TheliaEvents::COUPON_CLEAR_ALL);
$dispatcher->dispatch(TheliaEvents::COUPON_CLEAR_ALL);
}
/**
* Returns an array of event names this subscriber listens to.
* Cancels order coupons usage when order is canceled or refunded,
* or use canceled coupons again if the order is no longer canceled or refunded
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* @param OrderEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
public function orderStatusChange(OrderEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
// The order has been canceled or refunded ?
if ($event->getOrder()->isCancelled() || $event->getOrder()->isRefunded()) {
// Cancel usage of all coupons for this order
$usedCoupons = OrderCouponQuery::create()
->filterByUsageCanceled(false)
->findByOrderId($event->getOrder()->getId());
$customerId = $event->getOrder()->getCustomerId();
/** @var OrderCoupon $usedCoupon */
foreach ($usedCoupons as $usedCoupon) {
if (null !== $couponModel = CouponQuery::create()->findOneByCode($usedCoupon->getCode())) {
// If the coupon still exists, restore one usage to the usage count.
$this->couponManager->incrementQuantity($couponModel, $customerId);
}
// Mark coupon usage as canceled in the OrderCoupon table
$usedCoupon->setUsageCanceled(true)->save();
}
} else {
// Mark canceled coupons for this order as used again
$usedCoupons = OrderCouponQuery::create()
->filterByUsageCanceled(true)
->findByOrderId($event->getOrder()->getId());
$customerId = $event->getOrder()->getCustomerId();
/** @var OrderCoupon $usedCoupon */
foreach ($usedCoupons as $usedCoupon) {
if (null !== $couponModel = CouponQuery::create()->findOneByCode($usedCoupon->getCode())) {
// If the coupon still exists, mark the coupon as used
$this->couponManager->decrementQuantity($couponModel, $customerId);
}
// The coupon is no longer canceled
$usedCoupon->setUsageCanceled(false)->save();
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::COUPON_CREATE => array("create", 128),
TheliaEvents::COUPON_UPDATE => array("update", 128),
TheliaEvents::COUPON_DELETE => array("delete", 128),
TheliaEvents::COUPON_CONSUME => array("consume", 128),
TheliaEvents::COUPON_CLEAR_ALL => array("clearAllCoupons", 128),
TheliaEvents::COUPON_CONDITION_UPDATE => array("updateCondition", 128),
TheliaEvents::ORDER_SET_POSTAGE => array("testFreePostage", 132),
TheliaEvents::ORDER_BEFORE_PAYMENT => array("afterOrder", 128),
TheliaEvents::ORDER_UPDATE_STATUS => array("orderStatusChange", 10),
TheliaEvents::CART_ADDITEM => array("updateOrderDiscount", 10),
TheliaEvents::CART_UPDATEITEM => array("updateOrderDiscount", 10),
TheliaEvents::CART_DELETEITEM => array("updateOrderDiscount", 10),
TheliaEvents::CUSTOMER_LOGIN => array("updateOrderDiscount", 10)
);
}
/**
* Returns the session from the current request
*
* @return \Thelia\Core\HttpFoundation\Session\Session
*/
protected function getSession()
{
return $this->requestStack->getCurrentRequest()->getSession();
}
}

View File

@@ -12,40 +12,54 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\ActionEvent;
use Thelia\Model\CurrencyQuery;
use Thelia\Model\Currency as CurrencyModel;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Currency\CurrencyUpdateEvent;
use Thelia\Core\Event\Currency\CurrencyCreateEvent;
use Thelia\Core\Event\Currency\CurrencyDeleteEvent;
use Thelia\Model\ConfigQuery;
use Thelia\Core\Event\Currency\CurrencyUpdateEvent;
use Thelia\Core\Event\Currency\CurrencyUpdateRateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Translation\Translator;
use Thelia\CurrencyConverter\CurrencyConverter;
use Thelia\CurrencyConverter\Exception\CurrencyNotFoundException;
use Thelia\Log\Tlog;
use Thelia\Math\Number;
use Thelia\Model\Currency as CurrencyModel;
use Thelia\Model\CurrencyQuery;
class Currency extends BaseAction implements EventSubscriberInterface
{
/** @var CurrencyConverter */
protected $currencyConverter;
public function __construct(CurrencyConverter $currencyConverter)
{
$this->currencyConverter = $currencyConverter;
}
/**
* Create a new currencyuration entry
*
* @param \Thelia\Core\Event\Currency\CurrencyCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(CurrencyCreateEvent $event)
public function create(CurrencyCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$currency = new CurrencyModel();
$currency
->setDispatcher($event->getDispatcher())
$isDefault = CurrencyQuery::create()->count() === 0;
$currency
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setName($event->getCurrencyName())
->setSymbol($event->getSymbol())
->setFormat($event->getFormat())
->setRate($event->getRate())
->setCode(strtoupper($event->getCode()))
->setByDefault($isDefault)
->save()
;
@@ -56,17 +70,19 @@ class Currency extends BaseAction implements EventSubscriberInterface
* Change a currency
*
* @param \Thelia\Core\Event\Currency\CurrencyUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(CurrencyUpdateEvent $event)
public function update(CurrencyUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $currency = CurrencyQuery::create()->findPk($event->getCurrencyId())) {
$currency
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setName($event->getCurrencyName())
->setSymbol($event->getSymbol())
->setFormat($event->getFormat())
->setRate($event->getRate())
->setCode(strtoupper($event->getCode()))
@@ -80,36 +96,63 @@ class Currency extends BaseAction implements EventSubscriberInterface
* Set the default currency
*
* @param CurrencyUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function setDefault(CurrencyUpdateEvent $event)
public function setDefault(CurrencyUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $currency = CurrencyQuery::create()->findPk($event->getCurrencyId())) {
// Reset default status
CurrencyQuery::create()->filterByByDefault(true)->update(array('ByDefault' => false));
$currency
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setVisible($event->getVisible())
->setByDefault($event->getIsDefault())
->save()
;
// Update rates when setting a new default currency
if ($event->getIsDefault()) {
$updateRateEvent = new CurrencyUpdateRateEvent();
$dispatcher->dispatch(TheliaEvents::CURRENCY_UPDATE_RATES, $updateRateEvent);
}
$event->setCurrency($currency);
}
}
/**
* @param CurrencyUpdateEvent $event
*/
public function setVisible(CurrencyUpdateEvent $event)
{
if (null !== $currency = CurrencyQuery::create()->findPk($event->getCurrencyId())) {
if (!$currency->getByDefault()) {
$currency->setVisible($event->getVisible())->save();
}
}
}
/**
* Delete a currencyuration entry
*
* @param \Thelia\Core\Event\Currency\CurrencyDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function delete(CurrencyDeleteEvent $event)
public function delete(CurrencyDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== ($currency = CurrencyQuery::create()->findPk($event->getCurrencyId()))) {
if ($currency->getByDefault()) {
throw new \RuntimeException(
Translator::getInstance()->trans('It is not allowed to delete the default currency')
);
}
$currency
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->delete()
;
@@ -117,39 +160,45 @@ class Currency extends BaseAction implements EventSubscriberInterface
}
}
public function updateRates(ActionEvent $event)
public function updateRates(CurrencyUpdateRateEvent $event)
{
$rates_url = ConfigQuery::read('currency_rate_update_url', 'http://www.ecb.int/stats/eurofxref/eurofxref-daily.xml');
$rate_data = @file_get_contents($rates_url);
if ($rate_data && $sxe = new \SimpleXMLElement($rate_data)) {
foreach ($sxe->Cube[0]->Cube[0]->Cube as $last) {
$code = strtoupper($last["currency"]);
$rate = floatval($last['rate']);
if (null !== $currency = CurrencyQuery::create()->findOneByCode($code)) {
$currency
->setDispatcher($event->getDispatcher())
->setRate($rate)
->save()
;
if (null === $defaultCurrency = CurrencyQuery::create()->findOneByByDefault(true)) {
throw new \RuntimeException('Unable to find a default currency, please define a default currency.');
}
$defaultCurrency->setRate(1)->save();
$currencies = CurrencyQuery::create()->filterByByDefault(false);
$baseValue = new Number('1');
/** @var \Thelia\Model\Currency $currency */
foreach ($currencies as $currency) {
try {
$rate = $this->currencyConverter
->from($defaultCurrency->getCode())
->to($currency->getCode())
->convert($baseValue);
$currency->setRate($rate->getNumber(-1))->save();
} catch (CurrencyNotFoundException $ex) {
Tlog::getInstance()->addError(
sprintf("Unable to find exchange rate for currency %s, ID %d", $currency->getCode(), $currency->getId())
);
$event->addUndefinedRate($currency->getId());
}
} else {
throw new \RuntimeException(sprintf("Failed to get currency rates data from URL %s", $rates_url));
}
}
/**
* Changes position, selecting absolute ou relative change.
*
* @param CategoryChangePositionEvent $event
* @param UpdatePositionEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(CurrencyQuery::create(), $event);
$this->genericUpdatePosition(CurrencyQuery::create(), $event, $dispatcher);
}
/**
@@ -162,6 +211,7 @@ class Currency extends BaseAction implements EventSubscriberInterface
TheliaEvents::CURRENCY_UPDATE => array("update", 128),
TheliaEvents::CURRENCY_DELETE => array("delete", 128),
TheliaEvents::CURRENCY_SET_DEFAULT => array("setDefault", 128),
TheliaEvents::CURRENCY_SET_VISIBLE => array("setVisible", 128),
TheliaEvents::CURRENCY_UPDATE_RATES => array("updateRates", 128),
TheliaEvents::CURRENCY_UPDATE_POSITION => array("updatePosition", 128)
);

View File

@@ -12,23 +12,21 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\ActionEvent;
use Thelia\Core\Event\Customer\CustomerCreateOrUpdateEvent;
use Thelia\Core\Event\Customer\CustomerEvent;
use Thelia\Core\Event\Customer\CustomerLoginEvent;
use Thelia\Core\Event\LostPasswordEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Security\SecurityContext;
use Thelia\Core\Template\ParserInterface;
use Thelia\Core\Translation\Translator;
use Thelia\Exception\CustomerException;
use Thelia\Mailer\MailerFactory;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Customer as CustomerModel;
use Thelia\Core\Event\Customer\CustomerLoginEvent;
use Thelia\Model\CustomerQuery;
use Thelia\Model\LangQuery;
use Thelia\Model\MessageQuery;
use Thelia\Tools\Password;
/**
@@ -37,46 +35,77 @@ use Thelia\Tools\Password;
*
* Class Customer
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Customer extends BaseAction implements EventSubscriberInterface
{
/** @var SecurityContext */
protected $securityContext;
protected $parser;
/** @var MailerFactory */
protected $mailer;
public function __construct(SecurityContext $securityContext, ParserInterface $parser, MailerFactory $mailer)
public function __construct(SecurityContext $securityContext, MailerFactory $mailer)
{
$this->securityContext = $securityContext;
$this->mailer = $mailer;
$this->parser = $parser;
}
public function create(CustomerCreateOrUpdateEvent $event)
public function create(CustomerCreateOrUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$customer = new CustomerModel();
$this->createOrUpdateCustomer($customer, $event);
$plainPassword = $event->getPassword();
$this->createOrUpdateCustomer($customer, $event, $dispatcher);
if ($event->getNotifyCustomerOfAccountCreation()) {
$this->mailer->sendEmailToCustomer(
'customer_account_created',
$customer,
[ 'password' => $plainPassword ]
);
}
public function modify(CustomerCreateOrUpdateEvent $event)
{
$customer = $event->getCustomer();
$this->createOrUpdateCustomer($customer, $event);
$dispatcher->dispatch(
TheliaEvents::SEND_ACCOUNT_CONFIRMATION_EMAIL,
new CustomerEvent($customer)
);
}
public function updateProfile(CustomerCreateOrUpdateEvent $event)
public function customerConfirmationEmail(CustomerEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$customer = $event->getCustomer();
$customer->setDispatcher($event->getDispatcher());
if (ConfigQuery::isCustomerEmailConfirmationEnable() && $customer->getConfirmationToken() !== null && $customer !== null) {
$this->mailer->sendEmailToCustomer(
'customer_confirmation',
$customer,
['customer_id' => $customer->getId()]
);
}
}
public function modify(CustomerCreateOrUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$plainPassword = $event->getPassword();
$customer = $event->getCustomer();
$emailChanged = $customer->getEmail() !== $event->getEmail();
$this->createOrUpdateCustomer($customer, $event, $dispatcher);
if (! empty($plainPassword) || $emailChanged) {
$this->mailer->sendEmailToCustomer('customer_account_changed', $customer, ['password' => $plainPassword]);
}
}
public function updateProfile(CustomerCreateOrUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$customer = $event->getCustomer();
$customer->setDispatcher($dispatcher);
if ($event->getTitle() !== null) {
$customer->setTitleId($event->getTitle());
@@ -91,7 +120,7 @@ class Customer extends BaseAction implements EventSubscriberInterface
}
if ($event->getEmail() !== null) {
$customer->setEmail($event->getEmail());
$customer->setEmail($event->getEmail(), $event->getEmailUpdateAllowed());
}
if ($event->getPassword() !== null) {
@@ -118,7 +147,6 @@ class Customer extends BaseAction implements EventSubscriberInterface
public function delete(CustomerEvent $event)
{
if (null !== $customer = $event->getCustomer()) {
if (true === $customer->hasOrder()) {
throw new CustomerException(Translator::getInstance()->trans("Impossible to delete a customer who already have orders"));
}
@@ -127,9 +155,9 @@ class Customer extends BaseAction implements EventSubscriberInterface
}
}
private function createOrUpdateCustomer(CustomerModel $customer, CustomerCreateOrUpdateEvent $event)
private function createOrUpdateCustomer(CustomerModel $customer, CustomerCreateOrUpdateEvent $event, EventDispatcherInterface $dispatcher)
{
$customer->setDispatcher($event->getDispatcher());
$customer->setDispatcher($dispatcher);
$customer->createOrUpdate(
$event->getTitle(),
@@ -145,12 +173,14 @@ class Customer extends BaseAction implements EventSubscriberInterface
$event->getCountry(),
$event->getEmail(),
$event->getPassword(),
$event->getLang(),
$event->getLangId(),
$event->getReseller(),
$event->getSponsor(),
$event->getDiscount(),
$event->getCompany(),
$event->getRef()
$event->getRef(),
$event->getEmailUpdateAllowed(),
$event->getState()
);
$event->setCustomer($customer);
@@ -171,18 +201,14 @@ class Customer extends BaseAction implements EventSubscriberInterface
*
* @param ActionEvent $event
*/
public function logout(ActionEvent $event)
public function logout(/** @noinspection PhpUnusedParameterInspection */ ActionEvent $event)
{
$this->securityContext->clearCustomerUser();
}
public function lostPassword(LostPasswordEvent $event)
{
$contact_email = ConfigQuery::read('store_email');
if ($contact_email) {
if (null !== $customer = CustomerQuery::create()->filterByEmail($event->getEmail())->findOne()) {
$password = Password::generateRandom(8);
$customer
@@ -190,65 +216,12 @@ class Customer extends BaseAction implements EventSubscriberInterface
->save()
;
if ($customer->getLang() !== null) {
$lang = LangQuery::create()
->findPk($customer->getLang());
$locale = $lang->getLocale();
} else {
$lang = LangQuery::create()
->filterByByDefault(1)
->findOne();
$locale = $lang->getLocale();
}
$message = MessageQuery::create()
->filterByName('lost_password')
->findOne();
$message->setLocale($locale);
if (false === $message) {
throw new \Exception("Failed to load message 'order_confirmation'.");
}
$this->parser->assign('password', $password);
$instance = \Swift_Message::newInstance()
->addTo($customer->getEmail(), $customer->getFirstname()." ".$customer->getLastname())
->addFrom($contact_email, ConfigQuery::read('store_name'))
;
// Build subject and body
$message->buildMessage($this->parser, $instance);
$this->mailer->send($instance);
}
$this->mailer->sendEmailToCustomer('lost_password', $customer, ['password' => $password]);
}
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
@@ -259,7 +232,8 @@ class Customer extends BaseAction implements EventSubscriberInterface
TheliaEvents::CUSTOMER_LOGOUT => array('logout', 128),
TheliaEvents::CUSTOMER_LOGIN => array('login', 128),
TheliaEvents::CUSTOMER_DELETEACCOUNT => array('delete', 128),
TheliaEvents::LOST_PASSWORD => array('lostPassword', 128)
TheliaEvents::LOST_PASSWORD => array('lostPassword', 128),
TheliaEvents::SEND_ACCOUNT_CONFIRMATION_EMAIL => array('customerConfirmationEmail', 128)
);
}
}

View File

@@ -0,0 +1,111 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\CustomerTitle\CustomerTitleEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\CustomerTitle as CustomerTitleModel;
use Thelia\Model\Map\CustomerTitleTableMap;
/**
* Class CustomerTitle
* @package Thelia\Action
* @author Benjamin Perche <bperche@openstudio.fr>
*/
class CustomerTitle extends BaseAction implements EventSubscriberInterface
{
public function create(CustomerTitleEvent $event)
{
$this->createOrUpdate($event, new CustomerTitleModel());
}
public function update(CustomerTitleEvent $event)
{
$this->checkCustomerTitle($event);
$this->createOrUpdate($event, $event->getCustomerTitle());
}
public function delete(CustomerTitleEvent $event)
{
$this->checkCustomerTitle($event);
$con = Propel::getConnection(CustomerTitleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$event->getCustomerTitle()->delete();
$con->commit();
} catch (\Exception $e) {
$con->rollBack();
throw $e;
}
$event->setCustomerTitle(null);
}
protected function checkCustomerTitle(CustomerTitleEvent $event)
{
if (null === $event->getCustomerTitle()) {
throw new \LogicException(
"You must set the customer title before its update"
);
}
}
protected function createOrUpdate(CustomerTitleEvent $event, CustomerTitleModel $customerTitle)
{
$con = Propel::getConnection(CustomerTitleTableMap::DATABASE_NAME);
$con->beginTransaction();
$i18n = $customerTitle->getTranslation($event->getLocale(), $con);
try {
$i18n
->setShort($event->getShort())
->setLong($event->getLong())
;
$customerTitle->save($con);
if ($event->isDefault()) {
$customerTitle->toggleDefault($con);
$event->setDefault(false);
}
$con->commit();
} catch (\Exception $e) {
$con->rollBack();
throw $e;
}
$event->setCustomerTitle($customerTitle);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::CUSTOMER_TITLE_CREATE => array("create"),
TheliaEvents::CUSTOMER_TITLE_UPDATE => array("update"),
TheliaEvents::CUSTOMER_TITLE_DELETE => array("delete"),
);
}
}

View File

@@ -0,0 +1,66 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Delivery\DeliveryPostageEvent;
use Thelia\Core\Event\TheliaEvents;
/**
* Class Delivery
* @package Thelia\Action
* @author Julien Chanséaume <julien@thelia.net>
*/
class Delivery implements EventSubscriberInterface
{
/**
* Get postage from module using the classical module functions
*
* @param DeliveryPostageEvent $event
*/
public function getPostage(DeliveryPostageEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$module = $event->getModule();
// dispatch event to target specific module
$dispatcher->dispatch(
TheliaEvents::getModuleEvent(
TheliaEvents::MODULE_DELIVERY_GET_POSTAGE,
$module->getCode()
),
$event
);
if ($event->isPropagationStopped()) {
return;
}
// call legacy module method
$event->setValidModule($module->isValidDelivery($event->getCountry()));
if ($event->isValidModule()) {
$event->setPostage($module->getPostage($event->getCountry()));
}
}
/**
* @inheritdoc
*/
public static function getSubscribedEvents()
{
return [
TheliaEvents::MODULE_DELIVERY_GET_POSTAGE => ['getPostage', 128]
];
}
}

View File

@@ -41,12 +41,17 @@ use Thelia\Tools\URL;
*/
class Document extends BaseCachedFile implements EventSubscriberInterface
{
/**
* @var string Config key for document delivery mode
*/
const CONFIG_DELIVERY_MODE = 'original_document_delivery_mode';
/**
* @return string root of the document cache directory in web space
*/
protected function getCacheDirFromWebRoot()
{
return ConfigQuery::read('document_cache_dir_from_web_root', 'cache');
return ConfigQuery::read('document_cache_dir_from_web_root', 'cache' . DS . 'documents');
}
/**
@@ -74,20 +79,19 @@ class Document extends BaseCachedFile implements EventSubscriberInterface
$originalDocumentPathInCache = $this->getCacheFilePath($subdir, $sourceFile, true);
if (! file_exists($originalDocumentPathInCache)) {
if (! file_exists($sourceFile)) {
throw new DocumentException(sprintf("Source document file %s does not exists.", $sourceFile));
}
$mode = ConfigQuery::read('original_document_delivery_mode', 'symlink');
$mode = ConfigQuery::read(self::CONFIG_DELIVERY_MODE, 'symlink');
if ($mode == 'symlink') {
if (false == symlink($sourceFile, $originalDocumentPathInCache)) {
if (false === symlink($sourceFile, $originalDocumentPathInCache)) {
throw new DocumentException(sprintf("Failed to create symbolic link for %s in %s document cache directory", basename($sourceFile), $subdir));
}
} else {
// mode = 'copy'
if (false == @copy($sourceFile, $originalDocumentPathInCache)) {
if (false === @copy($sourceFile, $originalDocumentPathInCache)) {
throw new DocumentException(sprintf("Failed to copy %s in %s document cache directory", basename($sourceFile), $subdir));
}
}
@@ -101,6 +105,9 @@ class Document extends BaseCachedFile implements EventSubscriberInterface
$event->setDocumentUrl(URL::getInstance()->absoluteUrl($documentUrl, null, URL::PATH_TO_FILE));
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
@@ -112,6 +119,7 @@ class Document extends BaseCachedFile implements EventSubscriberInterface
TheliaEvents::DOCUMENT_SAVE => array("saveFile", 128),
TheliaEvents::DOCUMENT_UPDATE => array("updateFile", 128),
TheliaEvents::DOCUMENT_UPDATE_POSITION => array("updatePosition", 128),
TheliaEvents::DOCUMENT_TOGGLE_VISIBILITY => array("toggleVisibility", 128),
);
}
}

View File

@@ -11,77 +11,69 @@
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Cache\CacheEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Handler\ExportHandler;
use Thelia\Model\ExportCategoryQuery;
use Thelia\Model\ExportQuery;
/**
* Class Export
* @package Thelia\Action
* @author Benjamin Perche <bperche@openstudio.fr>
* @author Jérôme Billiras <jbilliras@openstudio.fr>
*/
class Export extends BaseAction implements EventSubscriberInterface
{
protected $environment;
/**
* @var \Thelia\Handler\ExportHandler The export handler
*/
protected $handler;
public function __construct($environment)
/**
* @param \Thelia\Handler\ExportHandler $exportHandler The export handler
*/
public function __construct(ExportHandler $exportHandler)
{
$this->environment = $environment;
$this->handler = $exportHandler;
}
public function changeCategoryPosition(UpdatePositionEvent $event)
public static function getSubscribedEvents()
{
$this->genericUpdatePosition(new ExportCategoryQuery(), $event);
$this->cacheClear($event->getDispatcher());
}
public function changeExportPosition(UpdatePositionEvent $event)
{
$this->genericUpdatePosition(new ExportQuery(), $event);
$this->cacheClear($event->getDispatcher());
}
protected function cacheClear(EventDispatcherInterface $dispatcher)
{
$cacheEvent = new CacheEvent(
$this->environment
);
$dispatcher->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent);
return [
TheliaEvents::EXPORT_CHANGE_POSITION => [
['exportChangePosition', 128]
],
TheliaEvents::EXPORT_CATEGORY_CHANGE_POSITION => [
['exportCategoryChangePosition', 128]
]
];
}
/**
* Returns an array of event names this subscriber wants to listen to.
* Handle export change position event
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* @param UpdatePositionEvent $updatePositionEvent
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public static function getSubscribedEvents()
public function exportChangePosition(UpdatePositionEvent $updatePositionEvent, $eventName, EventDispatcherInterface $dispatcher)
{
return array(
TheliaEvents::EXPORT_CATEGORY_CHANGE_POSITION => array("changeCategoryPosition", 128),
TheliaEvents::EXPORT_CHANGE_POSITION => array("changeExportPosition", 128),
);
$this->handler->getExport($updatePositionEvent->getObjectId(), true);
$this->genericUpdatePosition(new ExportQuery, $updatePositionEvent, $dispatcher);
}
/**
* Handle export category change position event
*
* @param UpdatePositionEvent $updatePositionEvent
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function exportCategoryChangePosition(UpdatePositionEvent $updatePositionEvent, $eventName, EventDispatcherInterface $dispatcher)
{
$this->handler->getCategory($updatePositionEvent->getObjectId(), true);
$this->genericUpdatePosition(new ExportCategoryQuery, $updatePositionEvent, $dispatcher);
}
}

View File

@@ -12,13 +12,11 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Model\FeatureQuery;
use Thelia\Model\Feature as FeatureModel;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Feature\FeatureUpdateEvent;
use Thelia\Core\Event\Feature\FeatureCreateEvent;
use Thelia\Core\Event\Feature\FeatureDeleteEvent;
@@ -34,13 +32,15 @@ class Feature extends BaseAction implements EventSubscriberInterface
* Create a new feature entry
*
* @param \Thelia\Core\Event\Feature\FeatureCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(FeatureCreateEvent $event)
public function create(FeatureCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$feature = new FeatureModel();
$feature
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setTitle($event->getTitle())
@@ -60,14 +60,14 @@ class Feature extends BaseAction implements EventSubscriberInterface
* Change a product feature
*
* @param \Thelia\Core\Event\Feature\FeatureUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(FeatureUpdateEvent $event)
public function update(FeatureUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $feature = FeatureQuery::create()->findPk($event->getFeatureId())) {
$feature
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setTitle($event->getTitle())
@@ -85,14 +85,14 @@ class Feature extends BaseAction implements EventSubscriberInterface
* Delete a product feature entry
*
* @param FeatureDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function delete(FeatureDeleteEvent $event)
public function delete(FeatureDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== ($feature = FeatureQuery::create()->findPk($event->getFeatureId()))) {
$feature
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->delete()
;
@@ -104,10 +104,12 @@ class Feature extends BaseAction implements EventSubscriberInterface
* Changes position, selecting absolute ou relative change.
*
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(FeatureQuery::create(), $event);
$this->genericUpdatePosition(FeatureQuery::create(), $event, $dispatcher);
}
protected function doAddToAllTemplates(FeatureModel $feature)
@@ -115,7 +117,6 @@ class Feature extends BaseAction implements EventSubscriberInterface
$templates = TemplateQuery::create()->find();
foreach ($templates as $template) {
$feature_template = new FeatureTemplate();
if (null === FeatureTemplateQuery::create()->filterByFeature($feature)->filterByTemplate($template)->findOne()) {

View File

@@ -12,13 +12,11 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Model\FeatureAvQuery;
use Thelia\Model\FeatureAv as FeatureAvModel;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Feature\FeatureAvUpdateEvent;
use Thelia\Core\Event\Feature\FeatureAvCreateEvent;
use Thelia\Core\Event\Feature\FeatureAvDeleteEvent;
@@ -30,13 +28,15 @@ class FeatureAv extends BaseAction implements EventSubscriberInterface
* Create a new feature entry
*
* @param FeatureAvCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(FeatureAvCreateEvent $event)
public function create(FeatureAvCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$feature = new FeatureAvModel();
$feature
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setFeatureId($event->getFeatureId())
->setLocale($event->getLocale())
@@ -52,14 +52,14 @@ class FeatureAv extends BaseAction implements EventSubscriberInterface
* Change a product feature
*
* @param FeatureAvUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(FeatureAvUpdateEvent $event)
public function update(FeatureAvUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $feature = FeatureAvQuery::create()->findPk($event->getFeatureAvId())) {
$feature
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setTitle($event->getTitle())
@@ -77,14 +77,14 @@ class FeatureAv extends BaseAction implements EventSubscriberInterface
* Delete a product feature entry
*
* @param FeatureAvDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function delete(FeatureAvDeleteEvent $event)
public function delete(FeatureAvDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== ($feature = FeatureAvQuery::create()->findPk($event->getFeatureAvId()))) {
$feature
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->delete()
;
@@ -96,10 +96,12 @@ class FeatureAv extends BaseAction implements EventSubscriberInterface
* Changes position, selecting absolute ou relative change.
*
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(FeatureAvQuery::create(), $event);
$this->genericUpdatePosition(FeatureAvQuery::create(), $event, $dispatcher);
}
/**

View File

@@ -0,0 +1,175 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Thelia\Core\Event\File\FileCreateOrUpdateEvent;
use Thelia\Core\Event\Product\ProductCloneEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Log\Tlog;
use Thelia\Model\ProductDocument;
use Thelia\Model\ProductDocumentI18n;
use Thelia\Model\ProductDocumentI18nQuery;
use Thelia\Model\ProductDocumentQuery;
use Thelia\Model\ProductImage;
use Thelia\Model\ProductImageI18nQuery;
use Thelia\Model\ProductImageQuery;
/**
* Class File
*
* @package Thelia\Action
* @author Etienne Perriere <eperriere@openstudio.fr>
*/
class File extends BaseAction implements EventSubscriberInterface
{
public function cloneFile(ProductCloneEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$originalProductId = $event->getOriginalProduct()->getId();
$clonedProduct = $event->getClonedProduct();
foreach ($event->getTypes() as $type) {
$originalProductFiles = [];
switch ($type) {
case 'images':
$originalProductFiles = ProductImageQuery::create()
->findByProductId($originalProductId);
break;
case 'documents':
$originalProductFiles = ProductDocumentQuery::create()
->findByProductId($originalProductId);
break;
}
// Set clone's files
/** @var ProductDocument|ProductImage $originalProductFile */
foreach ($originalProductFiles as $originalProductFile) {
$srcPath = $originalProductFile->getUploadDir() . DS . $originalProductFile->getFile();
if (file_exists($srcPath)) {
$ext = pathinfo($srcPath, PATHINFO_EXTENSION);
$clonedProductFile = [];
switch ($type) {
case 'images':
$fileName = $clonedProduct->getRef().'.'.$ext;
$clonedProductFile = new ProductImage();
break;
case 'documents':
$fileName = pathinfo($originalProductFile->getFile(), PATHINFO_FILENAME).'-'.$clonedProduct->getRef().'.'.$ext;
$clonedProductFile = new ProductDocument();
break;
}
// Copy a temporary file of the source file as it will be deleted by IMAGE_SAVE or DOCUMENT_SAVE event
$srcTmp = $srcPath.'.tmp';
copy($srcPath, $srcTmp);
// Get file mimeType
$finfo = new \finfo();
$fileMimeType = $finfo->file($srcPath, FILEINFO_MIME_TYPE);
// Get file event's parameters
$clonedProductFile
->setProductId($clonedProduct->getId())
->setVisible($originalProductFile->getVisible())
->setPosition($originalProductFile->getPosition())
->setLocale($clonedProduct->getLocale())
->setTitle($clonedProduct->getTitle());
$clonedProductCopiedFile = new UploadedFile($srcPath, $fileName, $fileMimeType, filesize($srcPath), null, true);
// Create and dispatch event
$clonedProductCreateFileEvent = new FileCreateOrUpdateEvent($clonedProduct->getId());
$clonedProductCreateFileEvent
->setModel($clonedProductFile)
->setUploadedFile($clonedProductCopiedFile)
->setParentName($clonedProduct->getTitle());
$originalProductFileI18ns = [];
switch ($type) {
case 'images':
$dispatcher->dispatch(TheliaEvents::IMAGE_SAVE, $clonedProductCreateFileEvent);
// Get original product image I18n
$originalProductFileI18ns = ProductImageI18nQuery::create()
->findById($originalProductFile->getId());
break;
case 'documents':
$dispatcher->dispatch(TheliaEvents::DOCUMENT_SAVE, $clonedProductCreateFileEvent);
// Get original product document I18n
$originalProductFileI18ns = ProductDocumentI18nQuery::create()
->findById($originalProductFile->getId());
break;
}
// Set temporary source file as original one
rename($srcTmp, $srcPath);
// Clone file's I18n
$this->cloneFileI18n($originalProductFileI18ns, $clonedProductFile, $type, $event, $dispatcher);
} else {
Tlog::getInstance()->addWarning("Failed to find media file $srcPath");
}
}
}
}
public function cloneFileI18n($originalProductFileI18ns, $clonedProductFile, $type, ProductCloneEvent $event, EventDispatcherInterface $dispatcher)
{
// Set clone files I18n
/** @var ProductDocumentI18n $originalProductFileI18n */
foreach ($originalProductFileI18ns as $originalProductFileI18n) {
// Update file with current I18n info. Update or create I18n according to existing or absent Locale in DB
$clonedProductFile
->setLocale($originalProductFileI18n->getLocale())
->setTitle($originalProductFileI18n->getTitle())
->setDescription($originalProductFileI18n->getDescription())
->setChapo($originalProductFileI18n->getChapo())
->setPostscriptum($originalProductFileI18n->getPostscriptum());
// Create and dispatch event
$clonedProductUpdateFileEvent = new FileCreateOrUpdateEvent($event->getClonedProduct()->getId());
$clonedProductUpdateFileEvent->setModel($clonedProductFile);
switch ($type) {
case 'images':
$dispatcher->dispatch(TheliaEvents::IMAGE_UPDATE, $clonedProductUpdateFileEvent);
break;
case 'documents':
$dispatcher->dispatch(TheliaEvents::DOCUMENT_UPDATE, $clonedProductUpdateFileEvent);
break;
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::FILE_CLONE => array("cloneFile", 128)
);
}
}

View File

@@ -11,7 +11,12 @@
/*************************************************************************************/
namespace Thelia\Action;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Thelia\Core\Event\File\FileDeleteEvent;
use Thelia\Core\Event\Folder\FolderCreateEvent;
use Thelia\Core\Event\Folder\FolderDeleteEvent;
use Thelia\Core\Event\Folder\FolderToggleVisibilityEvent;
@@ -19,21 +24,24 @@ use Thelia\Core\Event\Folder\FolderUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Model\FolderDocumentQuery;
use Thelia\Model\FolderImageQuery;
use Thelia\Model\FolderQuery;
use Thelia\Model\Folder as FolderModel;
use Thelia\Model\Map\FolderTableMap;
/**
* Class Folder
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Folder extends BaseAction implements EventSubscriberInterface
{
public function update(FolderUpdateEvent $event)
public function update(FolderUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $folder = FolderQuery::create()->findPk($event->getFolderId())) {
$folder->setDispatcher($event->getDispatcher());
$folder->setDispatcher($dispatcher);
$folder
->setParent($event->getParent())
@@ -53,32 +61,60 @@ class Folder extends BaseAction implements EventSubscriberInterface
/**
* Change Folder SEO
*
* @param \Thelia\Core\Event\UpdateSeoEvent $event
*
* @return mixed
* @param UpdateSeoEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return Object
*/
public function updateSeo(UpdateSeoEvent $event)
public function updateSeo(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
return $this->genericUpdateSeo(FolderQuery::create(), $event);
return $this->genericUpdateSeo(FolderQuery::create(), $event, $dispatcher);
}
public function delete(FolderDeleteEvent $event)
public function delete(FolderDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $folder = FolderQuery::create()->findPk($event->getFolderId())) {
$folder->setDispatcher($event->getDispatcher())
->delete();
$con = Propel::getWriteConnection(FolderTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$fileList = ['images' => [], 'documentList' => []];
// Get folder's files to delete after folder deletion
$fileList['images']['list'] = FolderImageQuery::create()
->findByFolderId($event->getFolderId());
$fileList['images']['type'] = TheliaEvents::IMAGE_DELETE;
$fileList['documentList']['list'] = FolderDocumentQuery::create()
->findByFolderId($event->getFolderId());
$fileList['documentList']['type'] = TheliaEvents::DOCUMENT_DELETE;
// Delete folder
$folder->setDispatcher($dispatcher)
->delete($con);
$event->setFolder($folder);
// Dispatch delete folder's files event
foreach ($fileList as $fileTypeList) {
foreach ($fileTypeList['list'] as $fileToDelete) {
$fileDeleteEvent = new FileDeleteEvent($fileToDelete);
$dispatcher->dispatch($fileTypeList['type'], $fileDeleteEvent);
}
}
/**
* @param FolderCreateEvent $event
*/
public function create(FolderCreateEvent $event)
$con->commit();
} catch (\Exception $e) {
$con->rollback();
throw $e;
}
}
}
public function create(FolderCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$folder = new FolderModel();
$folder->setDispatcher($event->getDispatcher());
$folder->setDispatcher($dispatcher);
$folder
->setParent($event->getParent())
@@ -90,23 +126,22 @@ class Folder extends BaseAction implements EventSubscriberInterface
$event->setFolder($folder);
}
public function toggleVisibility(FolderToggleVisibilityEvent $event)
public function toggleVisibility(FolderToggleVisibilityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$folder = $event->getFolder();
$folder
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setVisible(!$folder->getVisible())
->save();
$event->setFolder($folder);
}
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $folder = FolderQuery::create()->findPk($event->getObjectId())) {
$folder->setDispatcher($event->getDispatcher());
$folder->setDispatcher($dispatcher);
switch ($event->getMode()) {
case UpdatePositionEvent::POSITION_ABSOLUTE:
@@ -123,24 +158,37 @@ class Folder extends BaseAction implements EventSubscriberInterface
}
/**
* Returns an array of event names this subscriber wants to listen to.
* Check if is a folder view and if folder_id is visible
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* @param ViewCheckEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function viewCheck(ViewCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->getView() == 'folder') {
$folder = FolderQuery::create()
->filterById($event->getViewId())
->filterByVisible(1)
->count();
if ($folder == 0) {
$dispatcher->dispatch(TheliaEvents::VIEW_FOLDER_ID_NOT_VISIBLE, $event);
}
}
}
/**
* @param ViewCheckEvent $event
* @throws NotFoundHttpException
*/
public function viewFolderIdNotVisible(ViewCheckEvent $event)
{
throw new NotFoundHttpException();
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
@@ -151,7 +199,10 @@ class Folder extends BaseAction implements EventSubscriberInterface
TheliaEvents::FOLDER_TOGGLE_VISIBILITY => array("toggleVisibility", 128),
TheliaEvents::FOLDER_UPDATE_POSITION => array("updatePosition", 128),
TheliaEvents::FOLDER_UPDATE_SEO => array('updateSeo', 128)
TheliaEvents::FOLDER_UPDATE_SEO => array('updateSeo', 128),
TheliaEvents::VIEW_CHECK => array('viewCheck', 128),
TheliaEvents::VIEW_FOLDER_ID_NOT_VISIBLE => array('viewFolderIdNotVisible', 128),
);
}
}

View File

@@ -0,0 +1,168 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Cache\CacheEvent;
use Thelia\Core\Event\Hook\HookCreateAllEvent;
use Thelia\Core\Event\Hook\HookCreateEvent;
use Thelia\Core\Event\Hook\HookDeactivationEvent;
use Thelia\Core\Event\Hook\HookDeleteEvent;
use Thelia\Core\Event\Hook\HookToggleActivationEvent;
use Thelia\Core\Event\Hook\HookToggleNativeEvent;
use Thelia\Core\Event\Hook\HookUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\Hook as HookModel;
use Thelia\Model\HookQuery;
/**
* Class HookAction
* @package Thelia\Action
* @author Julien Chanséaume <jchanseaume@openstudio.fr>
*/
class Hook extends BaseAction implements EventSubscriberInterface
{
/** @var string */
protected $cacheDir;
public function __construct($cacheDir)
{
$this->cacheDir = $cacheDir;
}
public function create(HookCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$hook = new HookModel();
$hook
->setLocale($event->getLocale())
->setCode($event->getCode())
->setType($event->getType())
->setNative($event->getNative())
->setActivate($event->getActive())
->setTitle($event->getTitle())
->save();
$event->setHook($hook);
$this->cacheClear($dispatcher);
}
public function update(HookUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $hook = HookQuery::create()->findPk($event->getHookId())) {
$hook
->setLocale($event->getLocale())
->setCode($event->getCode())
->setType($event->getType())
->setNative($event->getNative())
->setActivate($event->getActive())
->setBlock($event->getBlock())
->setByModule($event->getByModule())
->setTitle($event->getTitle())
->setChapo($event->getChapo())
->setDescription($event->getDescription())
->save();
$event->setHook($hook);
$this->cacheClear($dispatcher);
}
}
public function delete(HookDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $hook = HookQuery::create()->findPk($event->getHookId())) {
$hook->delete();
$event->setHook($hook);
$this->cacheClear($dispatcher);
}
}
public function createAll(HookCreateAllEvent $event)
{
$hook = new HookModel();
$hook
->setLocale($event->getLocale())
->setCode($event->getCode())
->setType($event->getType())
->setNative($event->getNative())
->setActivate($event->getActive())
->setBlock($event->getBlock())
->setByModule($event->getByModule())
->setTitle($event->getTitle())
->setChapo($event->getChapo())
->setDescription($event->getDescription())
->save();
$event->setHook($hook);
}
public function deactivation(HookDeactivationEvent $event)
{
if (null !== $hook = HookQuery::create()->findPk($event->getHookId())) {
$hook
->setActivate(false)
->save();
$event->setHook($hook);
}
}
public function toggleNative(HookToggleNativeEvent $event)
{
if (null !== $hook = HookQuery::create()->findPk($event->getHookId())) {
$hook
->setNative(!$hook->getNative())
->save();
$event->setHook($hook);
}
}
public function toggleActivation(HookToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $hook = HookQuery::create()->findPk($event->getHookId())) {
$hook
->setActivate(!$hook->getActivate())
->save();
$event->setHook($hook);
$this->cacheClear($dispatcher);
}
}
protected function cacheClear(EventDispatcherInterface $dispatcher)
{
$cacheEvent = new CacheEvent($this->cacheDir);
$dispatcher->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::HOOK_CREATE => array('create', 128),
TheliaEvents::HOOK_UPDATE => array('update', 128),
TheliaEvents::HOOK_DELETE => array('delete', 128),
TheliaEvents::HOOK_TOGGLE_ACTIVATION => array('toggleActivation', 128),
TheliaEvents::HOOK_TOGGLE_NATIVE => array('toggleNative', 128),
TheliaEvents::HOOK_CREATE_ALL => array('createAll', 128),
TheliaEvents::HOOK_DEACTIVATION => array('deactivation', 128),
);
}
}

View File

@@ -13,27 +13,25 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException as BaseHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Thelia\Core\HttpFoundation\Response;
use Thelia\Core\Template\ParserInterface;
use Thelia\Exception\AdminAccessDenied;
use Thelia\Model\ConfigQuery;
use Thelia\Core\Template\TemplateHelper;
/**
*
* Class HttpException
* @package Thelia\Action
* @author Etienne Roudeix <eroudeix@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class HttpException extends BaseAction implements EventSubscriberInterface
{
/**
* @var ParserInterface
*/
/** @var ParserInterface */
protected $parser;
public function __construct(ParserInterface $parser)
@@ -48,29 +46,33 @@ class HttpException extends BaseAction implements EventSubscriberInterface
$this->display404($event);
}
if ($exception instanceof AccessDeniedHttpException) {
$this->display403($event);
}
if ($exception instanceof AdminAccessDenied) {
$this->displayAdminGeneralError($event);
}
if ($exception instanceof BaseHttpException && null === $event->getResponse()) {
$this->displayException($event);
}
}
protected function displayAdminGeneralError(GetResponseForExceptionEvent $event)
{
// Define the template thant shoud be used
$this->parser->setTemplateDefinition(TemplateHelper::getInstance()->getActiveAdminTemplate());
$this->parser->setTemplateDefinition(
$this->parser->getTemplateHelper()->getActiveAdminTemplate()
);
$message = $event->getException()->getMessage();
$response = Response::create(
$this->parser->render('general_error.html',
$this->parser->render(
'general_error.html',
array(
"error_message" => $message
)),
)
),
403
) ;
);
$event->setResponse($response);
}
@@ -78,42 +80,35 @@ class HttpException extends BaseAction implements EventSubscriberInterface
protected function display404(GetResponseForExceptionEvent $event)
{
// Define the template thant shoud be used
$this->parser->setTemplateDefinition(TemplateHelper::getInstance()->getActiveFrontTemplate());
$this->parser->setTemplateDefinition(
$this->parser->getTemplateHelper()->getActiveFrontTemplate()
);
$response = new Response($this->parser->render(ConfigQuery::getPageNotFoundView()), 404);
$event->setResponse($response);
}
protected function display403(GetResponseForExceptionEvent $event)
protected function displayException(GetResponseForExceptionEvent $event)
{
$event->setResponse(new Response("You don't have access to this resources", 403));
/** @var \Symfony\Component\HttpKernel\Exception\HttpException $exception */
$exception = $event->getException();
$event->setResponse(
new Response(
$exception->getMessage(),
$exception->getStatusCode(),
$exception->getHeaders()
)
);
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
KernelEvents::EXCEPTION => array("checkHttpException", 128),
KernelEvents::EXCEPTION => ["checkHttpException", 128],
);
}
}

View File

@@ -13,16 +13,21 @@
namespace Thelia\Action;
use Imagine\Image\Box;
use Imagine\Image\Color;
use Imagine\Image\ImageInterface;
use Imagine\Image\ImagineInterface;
use Imagine\Image\Palette\RGB;
use Imagine\Image\Point;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Image\ImageEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Exception\ImageException;
use Thelia\Files\FileManager;
use Thelia\Model\ConfigQuery;
use Thelia\Tools\URL;
use Imagine\Imagick\Imagine as ImagickImagine;
use Imagine\Gmagick\Imagine as GmagickImagine;
use Imagine\Gd\Imagine;
/**
*
@@ -70,7 +75,7 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
*/
protected function getCacheDirFromWebRoot()
{
return ConfigQuery::read('image_cache_dir_from_web_root', 'cache');
return ConfigQuery::read('image_cache_dir_from_web_root', 'cache' . DS . 'images');
}
/**
@@ -83,13 +88,14 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
* This method updates the cache_file_path and file_url attributes of the event
*
* @param ImageEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*
* @throws \Thelia\Exception\ImageException
* @throws \InvalidArgumentException
*/
public function processImage(ImageEvent $event)
public function processImage(ImageEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$subdir = $event->getCacheSubdirectory();
$source_file = $event->getSourceFilepath();
@@ -103,23 +109,22 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
$originalImagePathInCache = $this->getCacheFilePath($subdir, $source_file, true);
if (! file_exists($cacheFilePath)) {
if (! file_exists($source_file)) {
throw new ImageException(sprintf("Source image file %s does not exists.", $source_file));
}
// Create a chached version of the original image in the web space, if not exists
// Create a cached version of the original image in the web space, if not exists
if (! file_exists($originalImagePathInCache)) {
$mode = ConfigQuery::read('original_image_delivery_mode', 'symlink');
if ($mode == 'symlink') {
if (false == symlink($source_file, $originalImagePathInCache)) {
if (false === symlink($source_file, $originalImagePathInCache)) {
throw new ImageException(sprintf("Failed to create symbolic link for %s in %s image cache directory", basename($source_file), $subdir));
}
} else {// mode = 'copy'
if (false == @copy($source_file, $originalImagePathInCache)) {
} else {
// mode = 'copy'
if (false === @copy($source_file, $originalImagePathInCache)) {
throw new ImageException(sprintf("Failed to copy %s in %s image cache directory", basename($source_file), $subdir));
}
}
@@ -127,39 +132,49 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
// Process image only if we have some transformations to do.
if (! $event->isOriginalImage()) {
// We have to process the image.
$imagine = $this->createImagineInstance();
$image = $imagine->open($source_file);
if ($image) {
// Allow image pre-processing (watermarging, or other stuff...)
$event->setImageObject($image);
$event->getDispatcher()->dispatch(TheliaEvents::IMAGE_PREPROCESSING, $event);
$dispatcher->dispatch(TheliaEvents::IMAGE_PREPROCESSING, $event);
$image = $event->getImageObject();
$background_color = $event->getBackgroundColor();
$palette = new RGB();
if ($background_color != null) {
$bg_color = new Color($background_color);
} else
$bg_color = null;
$bg_color = $palette->color($background_color);
} else {
// Define a fully transparent white background color
$bg_color = $palette->color('fff', 0);
}
// Apply resize
$image = $this->applyResize($imagine, $image, $event->getWidth(), $event->getHeight(), $event->getResizeMode(), $bg_color);
$image = $this->applyResize(
$imagine,
$image,
$event->getWidth(),
$event->getHeight(),
$event->getResizeMode(),
$bg_color,
$event->getAllowZoom()
);
// Rotate if required
$rotation = intval($event->getRotation());
if ($rotation != 0)
if ($rotation != 0) {
$image->rotate($rotation, $bg_color);
}
// Flip
// Process each effects
foreach ($event->getEffects() as $effect) {
$effect = trim(strtolower($effect));
$params = explode(':', $effect);
@@ -182,7 +197,7 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
case 'vertical_flip':
case 'vflip':
$image-> flipVertically();
$image->flipVertically();
break;
case 'gamma':
@@ -197,7 +212,7 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
case 'colorize':
// Syntax: colorize:couleur. Exemple: colorize:#ff00cc
if (isset($params[1])) {
$the_color = new Color($params[1]);
$the_color = $palette->color($params[1]);
$image->effects()->colorize($the_color);
}
@@ -207,11 +222,13 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
$quality = $event->getQuality();
if (is_null($quality)) $quality = ConfigQuery::read('default_image_quality_percent', 75);
if (is_null($quality)) {
$quality = ConfigQuery::read('default_images_quality_percent', 75);
}
// Allow image post-processing (watermarging, or other stuff...)
$event->setImageObject($image);
$event->getDispatcher()->dispatch(TheliaEvents::IMAGE_POSTPROCESSING, $event);
$dispatcher->dispatch(TheliaEvents::IMAGE_POSTPROCESSING, $event);
$image = $event->getImageObject();
$image->save(
@@ -248,23 +265,35 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
* @param int $dest_height the required height
* @param int $resize_mode the resize mode (crop / bands / keep image ratio)p
* @param string $bg_color the bg_color used for bands
* @param bool $allow_zoom if true, image may be zoomed to matchrequired size. If false, image is not zoomed.
* @return ImageInterface the resized image.
*/
protected function applyResize(ImagineInterface $imagine, ImageInterface $image, $dest_width, $dest_height, $resize_mode, $bg_color)
{
protected function applyResize(
ImagineInterface $imagine,
ImageInterface $image,
$dest_width,
$dest_height,
$resize_mode,
$bg_color,
$allow_zoom = false
) {
if (! (is_null($dest_width) && is_null($dest_height))) {
$width_orig = $image->getSize()->getWidth();
$height_orig = $image->getSize()->getHeight();
if (is_null($dest_width))
$dest_width = $width_orig;
$ratio = $width_orig / $height_orig;
if (is_null($dest_height))
$dest_height = $height_orig;
if (is_null($dest_width)) {
$dest_width = $dest_height * $ratio;
}
if (is_null($resize_mode))
if (is_null($dest_height)) {
$dest_height = $dest_width / $ratio;
}
if (is_null($resize_mode)) {
$resize_mode = self::KEEP_IMAGE_RATIO;
}
$width_diff = $dest_width / $width_orig;
$height_diff = $dest_height / $height_orig;
@@ -272,45 +301,66 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
$delta_x = $delta_y = $border_width = $border_height = 0;
if ($width_diff > 1 && $height_diff > 1) {
// Set the default final size. If zoom is allowed, we will get the required
// image dimension. Otherwise, the final image may be smaller than required.
if ($allow_zoom) {
$resize_width = $dest_width;
$resize_height = $dest_height;
} else {
$resize_width = $width_orig;
$resize_height = $height_orig;
}
$next_width = $width_orig;
$next_height = $height_orig;
$dest_width = ($resize_mode == self::EXACT_RATIO_WITH_BORDERS ? $dest_width : $next_width);
$dest_height = ($resize_mode == self::EXACT_RATIO_WITH_BORDERS ? $dest_height : $next_height);
// When cropping, be sure to always generate an image which is
// not smaller than the required size, zooming it if required.
if ($resize_mode == self::EXACT_RATIO_WITH_CROP) {
if ($allow_zoom) {
if ($width_diff > $height_diff) {
$resize_width = $dest_width;
$resize_height = intval($height_orig * $dest_width / $width_orig);
$delta_y = ($resize_height - $dest_height) / 2;
} else {
$resize_height = $dest_height;
$resize_width = intval(($width_orig * $resize_height) / $height_orig);
$delta_x = ($resize_width - $dest_width) / 2;
}
} else {
// No zoom : final image may be smaller than the required size.
$dest_width = $resize_width;
$dest_height = $resize_height;
}
}
} elseif ($width_diff > $height_diff) {
// Image height > image width
$next_height = $dest_height;
$next_width = intval(($width_orig * $next_height) / $height_orig);
$resize_height = $dest_height;
$resize_width = intval(($width_orig * $resize_height) / $height_orig);
if ($resize_mode == self::EXACT_RATIO_WITH_CROP) {
$next_width = $dest_width;
$next_height = intval($height_orig * $dest_width / $width_orig);
$delta_y = ($next_height - $dest_height) / 2;
$resize_width = $dest_width;
$resize_height = intval($height_orig * $dest_width / $width_orig);
$delta_y = ($resize_height - $dest_height) / 2;
} elseif ($resize_mode != self::EXACT_RATIO_WITH_BORDERS) {
$dest_width = $next_width;
$dest_width = $resize_width;
}
} else {
// Image width > image height
$next_width = $dest_width;
$next_height = intval($height_orig * $dest_width / $width_orig);
$resize_width = $dest_width;
$resize_height = intval($height_orig * $dest_width / $width_orig);
if ($resize_mode == self::EXACT_RATIO_WITH_CROP) {
$next_height = $dest_height;
$next_width = intval(($width_orig * $next_height) / $height_orig);
$delta_x = ($next_width - $dest_width) / 2;
$resize_height = $dest_height;
$resize_width = intval(($width_orig * $resize_height) / $height_orig);
$delta_x = ($resize_width - $dest_width) / 2;
} elseif ($resize_mode != self::EXACT_RATIO_WITH_BORDERS) {
$dest_height = $next_height;
$dest_height = $resize_height;
}
}
$image->resize(new Box($next_width, $next_height));
$image->resize(new Box($resize_width, $resize_height));
if ($resize_mode == self::EXACT_RATIO_WITH_BORDERS) {
$border_width = intval(($dest_width - $next_width) / 2);
$border_height = intval(($dest_height - $next_height) / 2);
$border_width = intval(($dest_width - $resize_width) / 2);
$border_height = intval(($dest_height - $resize_height) / 2);
$canvas = new Box($dest_width, $dest_height);
@@ -337,22 +387,25 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
$driver = ConfigQuery::read("imagine_graphic_driver", "gd");
switch ($driver) {
case 'imagik':
$image = new \Imagine\Imagick\Imagine();
case 'imagick':
$image = new ImagickImagine();
break;
case 'gmagick':
$image = new \Imagine\Gmagick\Imagine();
$image = new GmagickImagine();
break;
case 'gd':
default:
$image = new \Imagine\Gd\Imagine();
$image = new Imagine();
}
return $image;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
@@ -364,6 +417,7 @@ class Image extends BaseCachedFile implements EventSubscriberInterface
TheliaEvents::IMAGE_SAVE => array("saveFile", 128),
TheliaEvents::IMAGE_UPDATE => array("updateFile", 128),
TheliaEvents::IMAGE_UPDATE_POSITION => array("updatePosition", 128),
TheliaEvents::IMAGE_TOGGLE_VISIBILITY => array("toggleVisibility", 128),
);
}
}

View File

@@ -11,77 +11,69 @@
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Cache\CacheEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Handler\ImportHandler;
use Thelia\Model\ImportCategoryQuery;
use Thelia\Model\ImportQuery;
/**
* Class Import
* @package Thelia\Action
* @author Benjamin Perche <bperche@openstudio.fr>
* @author Jérôme Billiras <jbilliras@openstudio.fr>
*/
class Import extends BaseAction implements EventSubscriberInterface
{
protected $environment;
/**
* @var \Thelia\Handler\ImportHandler The import handler
*/
protected $handler;
public function __construct($environment)
/**
* @param \Thelia\Handler\ImportHandler $importHandler The import handler
*/
public function __construct(ImportHandler $importHandler)
{
$this->environment = $environment;
$this->handler = $importHandler;
}
public function changeCategoryPosition(UpdatePositionEvent $event)
public static function getSubscribedEvents()
{
$this->genericUpdatePosition(new ImportCategoryQuery(), $event);
$this->cacheClear($event->getDispatcher());
}
public function changeImportPosition(UpdatePositionEvent $event)
{
$this->genericUpdatePosition(new ImportQuery(), $event);
$this->cacheClear($event->getDispatcher());
}
protected function cacheClear(EventDispatcherInterface $dispatcher)
{
$cacheEvent = new CacheEvent(
$this->environment
);
$dispatcher->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent);
return [
TheliaEvents::IMPORT_CHANGE_POSITION => [
['importChangePosition', 128]
],
TheliaEvents::IMPORT_CATEGORY_CHANGE_POSITION => [
['importCategoryChangePosition', 128]
]
];
}
/**
* Returns an array of event names this subscriber wants to listen to.
* Handle import change position event
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* @param UpdatePositionEvent $updatePositionEvent
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public static function getSubscribedEvents()
public function importChangePosition(UpdatePositionEvent $updatePositionEvent, $eventName, EventDispatcherInterface $dispatcher)
{
return array(
TheliaEvents::IMPORT_CATEGORY_CHANGE_POSITION => array("changeCategoryPosition", 128),
TheliaEvents::IMPORT_CHANGE_POSITION => array("changeImportPosition", 128),
);
$this->handler->getImport($updatePositionEvent->getObjectId(), true);
$this->genericUpdatePosition(new ImportQuery, $updatePositionEvent, $dispatcher);
}
/**
* Handle import category change position event
*
* @param UpdatePositionEvent $updatePositionEvent
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function importCategoryChangePosition(UpdatePositionEvent $updatePositionEvent, $eventName, EventDispatcherInterface $dispatcher)
{
$this->handler->getCategory($updatePositionEvent->getObjectId(), true);
$this->genericUpdatePosition(new ImportCategoryQuery, $updatePositionEvent, $dispatcher);
}
}

View File

@@ -11,13 +11,23 @@
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\RequestStack;
use Thelia\Core\Event\Lang\LangCreateEvent;
use Thelia\Core\Event\Lang\LangDefaultBehaviorEvent;
use Thelia\Core\Event\Lang\LangDeleteEvent;
use Thelia\Core\Event\Lang\LangEvent;
use Thelia\Core\Event\Lang\LangToggleActiveEvent;
use Thelia\Core\Event\Lang\LangToggleDefaultEvent;
use Thelia\Core\Event\Lang\LangToggleVisibleEvent;
use Thelia\Core\Event\Lang\LangUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpFoundation\Session\Session;
use Thelia\Core\Template\TemplateHelperInterface;
use Thelia\Core\Translation\Translator;
use Thelia\Form\Lang\LangUrlEvent;
use Thelia\Model\ConfigQuery;
use Thelia\Model\LangQuery;
@@ -26,19 +36,31 @@ use Thelia\Model\Lang as LangModel;
/**
* Class Lang
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Lang extends BaseAction implements EventSubscriberInterface
{
/** @var TemplateHelperInterface */
protected $templateHelper;
public function update(LangUpdateEvent $event)
/** @var RequestStack */
protected $requestStack;
public function __construct(TemplateHelperInterface $templateHelper, RequestStack $requestStack)
{
$this->templateHelper = $templateHelper;
$this->requestStack = $requestStack;
}
public function update(LangUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $lang = LangQuery::create()->findPk($event->getId())) {
$lang->setDispatcher($event->getDispatcher());
$lang->setDispatcher($dispatcher);
$lang->setTitle($event->getTitle())
->setLocale($event->getLocale())
->setCode($event->getCode())
->setDateTimeFormat($event->getDateTimeFormat())
->setDateFormat($event->getDateFormat())
->setTimeFormat($event->getTimeFormat())
->setDecimalSeparator($event->getDecimalSeparator())
@@ -50,10 +72,10 @@ class Lang extends BaseAction implements EventSubscriberInterface
}
}
public function toggleDefault(LangToggleDefaultEvent $event)
public function toggleDefault(LangToggleDefaultEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $lang = LangQuery::create()->findPk($event->getLangId())) {
$lang->setDispatcher($event->getDispatcher());
$lang->setDispatcher($dispatcher);
$lang->toggleDefault();
@@ -61,15 +83,58 @@ class Lang extends BaseAction implements EventSubscriberInterface
}
}
public function create(LangCreateEvent $event)
public function toggleActive(LangToggleActiveEvent $event)
{
if (null !== $lang = LangQuery::create()->findPk($event->getLangId())) {
if ($lang->getByDefault()) {
throw new \RuntimeException(
Translator::getInstance()->trans('Cannot disable the default language')
);
}
$lang->setActive($lang->getActive() ? 0 : 1);
if (!$lang->getActive()) {
$lang->setVisible(0);
}
$lang->save();
$event->setLang($lang);
}
}
public function toggleVisible(LangToggleVisibleEvent $event)
{
if (null !== $lang = LangQuery::create()->findPk($event->getLangId())) {
if ($lang->getByDefault()) {
throw new \RuntimeException(
Translator::getInstance()->trans('Cannot hide the default language')
);
}
$lang->setVisible($lang->getVisible() ? 0 : 1);
if (!$lang->getActive() && $lang->getVisible()) {
$lang->setActive(1);
}
$lang->save();
$event->setLang($lang);
}
}
public function create(LangCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$lang = new LangModel();
$lang
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setTitle($event->getTitle())
->setCode($event->getCode())
->setLocale($event->getLocale())
->setDateTimeFormat($event->getDateTimeFormat())
->setDateFormat($event->getDateFormat())
->setTimeFormat($event->getTimeFormat())
->setDecimalSeparator($event->getDecimalSeparator())
@@ -80,12 +145,31 @@ class Lang extends BaseAction implements EventSubscriberInterface
$event->setLang($lang);
}
public function delete(LangDeleteEvent $event)
public function delete(LangDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $lang = LangQuery::create()->findPk($event->getLangId())) {
$lang->setDispatcher($event->getDispatcher())
if ($lang->getByDefault()) {
throw new \RuntimeException(
Translator::getInstance()->trans('It is not allowed to delete the default language')
);
}
$lang->setDispatcher($dispatcher)
->delete();
/** @var Session $session */
$session = $this->requestStack->getCurrentRequest()->getSession();
// If we've just deleted the current admin edition language, set it to the default one.
if ($lang->getId() == $session->getAdminEditionLang()->getId()) {
$session->setAdminEditionLang(LangModel::getDefaultLanguage());
}
// If we've just deleted the current admin language, set it to the default one.
if ($lang->getId() == $session->getLang()->getId()) {
$session->setLang(LangModel::getDefaultLanguage());
}
$event->setLang($lang);
}
}
@@ -106,35 +190,49 @@ class Lang extends BaseAction implements EventSubscriberInterface
}
}
public function fixMissingFlag(LangEvent $event)
{
// Be sure that a lang have a flag, otherwise copy the
// "unknown" flag
$adminTemplate = $this->templateHelper->getActiveAdminTemplate();
$unknownFlag = ConfigQuery::getUnknownFlagPath();
$unknownFlagPath = $adminTemplate->getAbsolutePath().DS.$unknownFlag;
if (! file_exists($unknownFlagPath)) {
throw new \RuntimeException(
Translator::getInstance()->trans(
"The image which replaces an undefined country flag (%file) was not found. Please check unknown-flag-path configuration variable, and check that the image exists.",
array("%file" => $unknownFlag)
)
);
}
// Check if the country flag exists
$countryFlag = rtrim(dirname($unknownFlagPath), DS).DS.$event->getLang()->getCode().'.png';
if (! file_exists($countryFlag)) {
$fs = new Filesystem();
$fs->copy($unknownFlagPath, $countryFlag);
}
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::LANG_UPDATE => array('update', 128),
TheliaEvents::LANG_TOGGLEDEFAULT => array('toggleDefault', 128),
TheliaEvents::LANG_TOGGLEACTIVE => array('toggleActive', 128),
TheliaEvents::LANG_TOGGLEVISIBLE => array('toggleVisible', 128),
TheliaEvents::LANG_CREATE => array('create', 128),
TheliaEvents::LANG_DELETE => array('delete', 128),
TheliaEvents::LANG_DEFAULTBEHAVIOR => array('defaultBehavior', 128),
TheliaEvents::LANG_URL => array('langUrl', 128)
TheliaEvents::LANG_URL => array('langUrl', 128),
TheliaEvents::LANG_FIX_MISSING_FLAG => array('fixMissingFlag', 128)
);
}
}

View File

@@ -12,13 +12,11 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Model\MessageQuery;
use Thelia\Model\Message as MessageModel;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Message\MessageUpdateEvent;
use Thelia\Core\Event\Message\MessageCreateEvent;
use Thelia\Core\Event\Message\MessageDeleteEvent;
@@ -29,19 +27,19 @@ class Message extends BaseAction implements EventSubscriberInterface
* Create a new messageuration entry
*
* @param \Thelia\Core\Event\Message\MessageCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(MessageCreateEvent $event)
public function create(MessageCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$message = new MessageModel();
$message
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setName($event->getMessageName())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setSecured($event->getSecured())
->save()
@@ -54,14 +52,14 @@ class Message extends BaseAction implements EventSubscriberInterface
* Change a message
*
* @param \Thelia\Core\Event\Message\MessageUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function modify(MessageUpdateEvent $event)
public function modify(MessageUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $message = MessageQuery::create()->findPk($event->getMessageId())) {
$message
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setName($event->getMessageName())
->setSecured($event->getSecured())
@@ -89,14 +87,14 @@ class Message extends BaseAction implements EventSubscriberInterface
* Delete a messageuration entry
*
* @param \Thelia\Core\Event\Message\MessageDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function delete(MessageDeleteEvent $event)
public function delete(MessageDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== ($message = MessageQuery::create()->findPk($event->getMessageId()))) {
$message
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->delete()
;

View File

@@ -0,0 +1,75 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\MetaData\MetaDataCreateOrUpdateEvent;
use Thelia\Core\Event\MetaData\MetaDataDeleteEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\MetaData as MetaDataModel;
use Thelia\Model\MetaDataQuery;
/**
* Class MetaData
* @package Thelia\Action
* @author Julien Chanséaume <jchanseaume@openstudio.fr>
*/
class MetaData extends BaseAction implements EventSubscriberInterface
{
public function createOrUpdate(MetaDataCreateOrUpdateEvent $event)
{
$metaData = MetaDataQuery::create()
->filterByMetaKey($event->getMetaKey())
->filterByElementKey($event->getElementKey())
->filterByElementId($event->getElementId())
->findOne();
if (null === $metaData) {
$metaData = new MetaDataModel();
$metaData
->setMetaKey($event->getMetaKey())
->setElementKey($event->getElementkey())
->setElementId($event->getElementId());
}
$metaData->
setValue($event->getValue());
$metaData->save();
$event->setMetaData($metaData);
}
public function delete(MetaDataDeleteEvent $event)
{
$metaData = MetaDataQuery::create()
->filterByMetaKey($event->getMetaKey())
->filterByElementKey($event->getElementKey())
->filterByElementId($event->getElementId())
->findOne();
$event->setMetaData($metaData);
if (null !== $metaData) {
$metaData->delete();
}
}
/**
* @inheritdoc
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::META_DATA_CREATE => array('createOrUpdate', 128),
TheliaEvents::META_DATA_UPDATE => array('createOrUpdate', 128),
TheliaEvents::META_DATA_DELETE => array('delete', 128),
);
}
}

View File

@@ -11,35 +11,43 @@
/*************************************************************************************/
namespace Thelia\Action;
use Exception;
use Propel\Runtime\Propel;
use SplFileInfo;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\Response;
use Thelia\Core\Event\Cache\CacheEvent;
use Thelia\Core\Event\Module\ModuleDeleteEvent;
use Thelia\Core\Event\Module\ModuleEvent;
use Thelia\Core\Event\Module\ModuleInstallEvent;
use Thelia\Core\Event\Module\ModuleToggleActivationEvent;
use Thelia\Core\Event\Order\OrderPaymentEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Translation\Translator;
use Thelia\Exception\FileNotFoundException;
use Thelia\Exception\ModuleException;
use Thelia\Log\Tlog;
use Thelia\Model\Base\OrderQuery;
use Thelia\Model\Map\ModuleTableMap;
use Thelia\Model\ModuleQuery;
use Thelia\Module\BaseModule;
use Thelia\Module\ModuleManagement;
use Thelia\Module\Validator\ModuleValidator;
/**
* Class Module
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Module extends BaseAction implements EventSubscriberInterface
{
/**
* @var ContainerInterface
*/
/** @var ContainerInterface */
protected $container;
public function __construct(ContainerInterface $container)
@@ -47,13 +55,12 @@ class Module extends BaseAction implements EventSubscriberInterface
$this->container = $container;
}
public function toggleActivation(ModuleToggleActivationEvent $event)
public function toggleActivation(ModuleToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId())) {
$moduleInstance = $module->createInstance();
if ( method_exists($moduleInstance, 'setContainer')) {
if (method_exists($moduleInstance, 'setContainer')) {
$moduleInstance->setContainer($this->container);
if ($module->getActivate() == BaseModule::IS_ACTIVATED) {
$moduleInstance->deActivate($module);
@@ -64,28 +71,188 @@ class Module extends BaseAction implements EventSubscriberInterface
$event->setModule($module);
$this->cacheClear($event->getDispatcher());
$this->cacheClear($dispatcher);
}
}
public function delete(ModuleDeleteEvent $event)
public function checkToggleActivation(ModuleToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (true === $event->isNoCheck()) {
return;
}
if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId())) {
try {
if ($module->getActivate() == BaseModule::IS_ACTIVATED) {
if ($module->getMandatory() == BaseModule::IS_MANDATORY && $event->getAssumeDeactivate() === false) {
throw new \Exception(
Translator::getInstance()->trans('Can\'t deactivate a secure module')
);
}
if ($event->isRecursive()) {
$this->recursiveDeactivation($event, $eventName, $dispatcher);
}
$this->checkDeactivation($module);
} else {
if ($event->isRecursive()) {
$this->recursiveActivation($event, $eventName, $dispatcher);
}
$this->checkActivation($module);
}
} catch (\Exception $ex) {
$event->stopPropagation();
throw $ex;
}
}
}
/**
* Check if module can be activated : supported version of Thelia, module dependencies.
*
* @param \Thelia\Model\Module $module
* @throws Exception if activation fails.
* @return bool true if the module can be activated, otherwise false
*/
private function checkActivation($module)
{
try {
$moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir());
$moduleValidator->validate(false);
} catch (\Exception $ex) {
throw $ex;
}
return true;
}
/**
* Check if module can be deactivated safely because other modules
* could have dependencies to this module
*
* @param \Thelia\Model\Module $module
* @return bool true if the module can be deactivated, otherwise false
*/
private function checkDeactivation($module)
{
$moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir());
$modules = $moduleValidator->getModulesDependOf();
if (count($modules) > 0) {
$moduleList = implode(', ', array_column($modules, 'code'));
$message = (count($modules) == 1)
? Translator::getInstance()->trans(
'%s has dependency to module %s. You have to deactivate this module before.'
)
: Translator::getInstance()->trans(
'%s have dependencies to module %s. You have to deactivate these modules before.'
);
throw new ModuleException(
sprintf($message, $moduleList, $moduleValidator->getModuleDefinition()->getCode())
);
}
return true;
}
/**
* Get dependencies of the current module and activate it if needed
*
* @param ModuleToggleActivationEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function recursiveActivation(ModuleToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId())) {
$moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir());
$dependencies = $moduleValidator->getCurrentModuleDependencies();
foreach ($dependencies as $defMod) {
$submodule = ModuleQuery::create()
->findOneByCode($defMod["code"]);
if ($submodule && $submodule->getActivate() != BaseModule::IS_ACTIVATED) {
$subevent = new ModuleToggleActivationEvent($submodule->getId());
$subevent->setRecursive(true);
$dispatcher->dispatch(TheliaEvents::MODULE_TOGGLE_ACTIVATION, $subevent);
}
}
}
}
/**
* Get modules having current module in dependence and deactivate it if needed
*
* @param ModuleToggleActivationEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function recursiveDeactivation(ModuleToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId())) {
$moduleValidator = new ModuleValidator($module->getAbsoluteBaseDir());
$dependencies = $moduleValidator->getModulesDependOf(true);
foreach ($dependencies as $defMod) {
$submodule = ModuleQuery::create()
->findOneByCode($defMod["code"]);
if ($submodule && $submodule->getActivate() == BaseModule::IS_ACTIVATED) {
$subevent = new ModuleToggleActivationEvent($submodule->getId());
$subevent->setRecursive(true);
$dispatcher->dispatch(TheliaEvents::MODULE_TOGGLE_ACTIVATION, $subevent);
}
}
}
}
public function delete(ModuleDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$con = Propel::getWriteConnection(ModuleTableMap::DATABASE_NAME);
$con->beginTransaction();
if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId(), $con)) {
try {
if (null === $module->getFullNamespace()) {
throw new \LogicException(
Translator::getInstance()->trans(
'Cannot instanciante module "%name%": the namespace is null. Maybe the model is not loaded ?',
array('%name%' => $module->getCode())
));
'Cannot instantiate module "%name%": the namespace is null. Maybe the model is not loaded ?',
['%name%' => $module->getCode()]
)
);
}
// If the module is referenced by an order, display a meaningful error
// instead of 'delete cannot delete' caused by a constraint violation.
// FIXME: we hav to find a way to delete modules used by order.
if (OrderQuery::create()->filterByDeliveryModuleId($module->getId())->count() > 0
||
OrderQuery::create()->filterByPaymentModuleId($module->getId())->count() > 0
) {
throw new \LogicException(
Translator::getInstance()->trans(
'The module "%name%" is currently in use by at least one order, and can\'t be deleted.',
['%name%' => $module->getCode()]
)
);
}
try {
if ($module->getMandatory() == BaseModule::IS_MANDATORY && $event->getAssumeDelete() === false) {
throw new \Exception(
Translator::getInstance()->trans('Can\'t remove a core module')
);
}
// First, try to create an instance
$instance = $module->createInstance();
// Then, if module is activated, check if we can deactivate it
if ($module->getActivate()) {
// check for modules that depend of this one
$this->checkDeactivation($module);
}
$instance->setContainer($this->container);
$path = $module->getAbsoluteBaseDir();
@@ -99,8 +266,19 @@ class Module extends BaseAction implements EventSubscriberInterface
// Log a warning, and delete the database entry.
Tlog::getInstance()->addWarning(
Translator::getInstance()->trans(
'Failed to create instance of module "%name%" when trying to delete module. Module directory has probably been deleted', array('%name%' => $module->getCode())
));
'Failed to create instance of module "%name%" when trying to delete module. Module directory has probably been deleted',
['%name%' => $module->getCode()]
)
);
} catch (FileNotFoundException $fnfe) {
// The module directory has been deleted.
// Log a warning, and delete the database entry.
Tlog::getInstance()->addWarning(
Translator::getInstance()->trans(
'Module "%name%" directory was not found',
['%name%' => $module->getCode()]
)
);
}
$module->delete($con);
@@ -108,8 +286,7 @@ class Module extends BaseAction implements EventSubscriberInterface
$con->commit();
$event->setModule($module);
$this->cacheClear($event->getDispatcher());
$this->cacheClear($dispatcher);
} catch (\Exception $e) {
$con->rollBack();
throw $e;
@@ -119,19 +296,19 @@ class Module extends BaseAction implements EventSubscriberInterface
/**
* @param ModuleEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(ModuleEvent $event)
public function update(ModuleEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $module = ModuleQuery::create()->findPk($event->getId())) {
$module
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setChapo($event->getChapo())
->setDescription($event->getDescription())
->setPostscriptum($event->getPostscriptum())
;
->setPostscriptum($event->getPostscriptum());
$module->save();
@@ -139,10 +316,86 @@ class Module extends BaseAction implements EventSubscriberInterface
}
}
/**
* @param \Thelia\Core\Event\Module\ModuleInstallEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*
* @throws \Exception
* @throws \Symfony\Component\Filesystem\Exception\IOException
* @throws \Exception
*/
public function install(ModuleInstallEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$moduleDefinition = $event->getModuleDefinition();
$oldModule = ModuleQuery::create()->findOneByFullNamespace($moduleDefinition->getNamespace());
$fs = new Filesystem();
$activated = false;
// check existing module
if (null !== $oldModule) {
$activated = $oldModule->getActivate();
if ($activated) {
// deactivate
$toggleEvent = new ModuleToggleActivationEvent($oldModule->getId());
// disable the check of the module because it's already done
$toggleEvent->setNoCheck(true);
$dispatcher->dispatch(TheliaEvents::MODULE_TOGGLE_ACTIVATION, $toggleEvent);
}
// delete
$modulePath = $oldModule->getAbsoluteBaseDir();
$deleteEvent = new ModuleDeleteEvent($oldModule);
try {
$dispatcher->dispatch(TheliaEvents::MODULE_DELETE, $deleteEvent);
} catch (Exception $ex) {
// if module has not been deleted
if ($fs->exists($modulePath)) {
throw $ex;
}
}
}
// move new module
$modulePath = sprintf('%s%s', THELIA_MODULE_DIR, $event->getModuleDefinition()->getCode());
try {
$fs->mirror($event->getModulePath(), $modulePath);
} catch (IOException $ex) {
if (!$fs->exists($modulePath)) {
throw $ex;
}
}
// Update the module
$moduleDescriptorFile = sprintf('%s%s%s%s%s', $modulePath, DS, 'Config', DS, 'module.xml');
$moduleManagement = new ModuleManagement();
$file = new SplFileInfo($moduleDescriptorFile);
$module = $moduleManagement->updateModule($file, $this->container);
// activate if old was activated
if ($activated) {
$toggleEvent = new ModuleToggleActivationEvent($module->getId());
$toggleEvent->setNoCheck(true);
$dispatcher->dispatch(TheliaEvents::MODULE_TOGGLE_ACTIVATION, $toggleEvent);
}
$event->setModule($module);
}
/**
* Call the payment method of the payment module of the given order
*
* @param OrderPaymentEvent $event
*
* @throws \RuntimeException if no payment module can be found.
*/
public function pay(OrderPaymentEvent $event)
@@ -154,18 +407,19 @@ class Module extends BaseAction implements EventSubscriberInterface
throw new \RuntimeException(
Translator::getInstance()->trans(
"Failed to find a payment Module with ID=%mid for order ID=%oid",
array(
[
"%mid" => $order->getPaymentModuleId(),
"%oid" => $order->getId()
))
]
)
);
}
$paymentModuleInstance = $paymentModule->getModuleInstance($this->container);
$paymentModuleInstance = $paymentModule->getPaymentModuleInstance($this->container);
$response = $paymentModuleInstance->pay($order);
if (null !== $response && $response instanceof \Thelia\Core\HttpFoundation\Response) {
if (null !== $response && $response instanceof Response) {
$event->setResponse($response);
}
}
@@ -174,12 +428,14 @@ class Module extends BaseAction implements EventSubscriberInterface
* Changes position, selecting absolute ou relative change.
*
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(ModuleQuery::create(), $event);
$this->genericUpdatePosition(ModuleQuery::create(), $event, $dispatcher);
$this->cacheClear($event->getDispatcher());
$this->cacheClear($dispatcher);
}
protected function cacheClear(EventDispatcherInterface $dispatcher)
@@ -192,33 +448,20 @@ class Module extends BaseAction implements EventSubscriberInterface
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* @inheritdoc
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::MODULE_TOGGLE_ACTIVATION => array('toggleActivation', 128),
TheliaEvents::MODULE_UPDATE_POSITION => array('updatePosition', 128),
TheliaEvents::MODULE_DELETE => array('delete', 128),
TheliaEvents::MODULE_UPDATE => array('update', 128),
TheliaEvents::MODULE_PAY => array('pay', 128),
);
return [
TheliaEvents::MODULE_TOGGLE_ACTIVATION => [
['checkToggleActivation', 255],
['toggleActivation', 128],
],
TheliaEvents::MODULE_UPDATE_POSITION => ['updatePosition', 128],
TheliaEvents::MODULE_DELETE => ['delete', 128],
TheliaEvents::MODULE_UPDATE => ['update', 128],
TheliaEvents::MODULE_INSTALL => ['install', 128],
TheliaEvents::MODULE_PAY => ['pay', 128],
];
}
}

View File

@@ -0,0 +1,252 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Cache\CacheEvent;
use Thelia\Core\Event\Hook\HookToggleActivationEvent;
use Thelia\Core\Event\Hook\HookUpdateEvent;
use Thelia\Core\Event\Hook\ModuleHookCreateEvent;
use Thelia\Core\Event\Hook\ModuleHookDeleteEvent;
use Thelia\Core\Event\Hook\ModuleHookToggleActivationEvent;
use Thelia\Core\Event\Hook\ModuleHookUpdateEvent;
use Thelia\Core\Event\Module\ModuleDeleteEvent;
use Thelia\Core\Event\Module\ModuleToggleActivationEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Translation\Translator;
use Thelia\Model\Base\IgnoredModuleHookQuery;
use Thelia\Model\HookQuery;
use Thelia\Model\IgnoredModuleHook;
use Thelia\Model\ModuleHook as ModuleHookModel;
use Thelia\Model\ModuleHookQuery;
use Thelia\Model\ModuleQuery;
use Thelia\Module\BaseModule;
/**
* Class ModuleHook
* @package Thelia\Action
* @author Julien Chanséaume <jchanseaume@openstudio.fr>
*/
class ModuleHook extends BaseAction implements EventSubscriberInterface
{
/** @var string */
protected $cacheDir;
public function __construct($cacheDir)
{
$this->cacheDir = $cacheDir;
}
public function toggleModuleActivation(ModuleToggleActivationEvent $event)
{
if (null !== $module = ModuleQuery::create()->findPk($event->getModuleId())) {
ModuleHookQuery::create()
->filterByModuleId($module->getId())
->update(array('ModuleActive' => ($module->getActivate() == BaseModule::IS_ACTIVATED)));
}
return $event;
}
public function deleteModule(ModuleDeleteEvent $event)
{
if ($event->getModuleId()) {
ModuleHookQuery::create()
->filterByModuleId($event->getModuleId())
->delete();
}
return $event;
}
protected function isModuleActive($module_id)
{
if (null !== $module = ModuleQuery::create()->findPk($module_id)) {
return $module->getActivate();
}
return false;
}
protected function isHookActive($hook_id)
{
if (null !== $hook = HookQuery::create()->findPk($hook_id)) {
return $hook->getActivate();
}
return false;
}
protected function getLastPositionInHook($hook_id)
{
$result = ModuleHookQuery::create()
->filterByHookId($hook_id)
->withColumn('MAX(ModuleHook.position)', 'maxPos')
->groupBy('ModuleHook.hook_id')
->select(array('maxPos'))
->findOne();
return intval($result) + 1;
}
public function createModuleHook(ModuleHookCreateEvent $event)
{
$moduleHook = new ModuleHookModel();
// todo: test if classname and method exists
$moduleHook
->setModuleId($event->getModuleId())
->setHookId($event->getHookId())
->setActive(false)
->setClassname($event->getClassname())
->setMethod($event->getMethod())
->setModuleActive($this->isModuleActive($event->getModuleId()))
->setHookActive($this->isHookActive($event->getHookId()))
->setPosition($this->getLastPositionInHook($event->getHookId()))
->setTemplates($event->getTemplates())
->save();
// Be sure to delete this module hook from the ignored module hook table
IgnoredModuleHookQuery::create()
->filterByHookId($event->getHookId())
->filterByModuleId($event->getModuleId())
->delete();
$event->setModuleHook($moduleHook);
}
public function updateModuleHook(ModuleHookUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $moduleHook = ModuleHookQuery::create()->findPk($event->getModuleHookId())) {
// todo: test if classname and method exists
$moduleHook
->setHookId($event->getHookId())
->setModuleId($event->getModuleId())
->setClassname($event->getClassname())
->setMethod($event->getMethod())
->setActive($event->getActive())
->setHookActive($this->isHookActive($event->getHookId()))
->setTemplates($event->getTemplates())
->save();
$event->setModuleHook($moduleHook);
$this->cacheClear($dispatcher);
}
}
public function deleteModuleHook(ModuleHookDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $moduleHook = ModuleHookQuery::create()->findPk($event->getModuleHookId())) {
$moduleHook->delete();
$event->setModuleHook($moduleHook);
// Prevent hook recreation by RegisterListenersPass::registerHook()
// We store the method here to be able to retreive it when
// we need to get all hook declared by a module
$imh = new IgnoredModuleHook();
$imh
->setModuleId($moduleHook->getModuleId())
->setHookId($moduleHook->getHookId())
->setMethod($moduleHook->getMethod())
->setClassname($moduleHook->getClassname())
->save();
$this->cacheClear($dispatcher);
}
}
public function toggleModuleHookActivation(ModuleHookToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $moduleHook = $event->getModuleHook()) {
if ($moduleHook->getModuleActive()) {
$moduleHook->setActive(!$moduleHook->getActive());
$moduleHook->save();
} else {
throw new \LogicException(Translator::getInstance()->trans("The module has to be activated."));
}
}
$this->cacheClear($dispatcher);
return $event;
}
/**
* Changes position, selecting absolute ou relative change.
*
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*
* @return UpdatePositionEvent $event
*/
public function updateModuleHookPosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(ModuleHookQuery::create(), $event, $dispatcher);
$this->cacheClear($dispatcher);
return $event;
}
public function updateHook(HookUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->hasHook()) {
$hook = $event->getHook();
ModuleHookQuery::create()
->filterByHookId($hook->getId())
->update(array('HookActive' => $hook->getActivate()));
$this->cacheClear($dispatcher);
}
}
public function toggleHookActivation(HookToggleActivationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->hasHook()) {
$hook = $event->getHook();
ModuleHookQuery::create()
->filterByHookId($hook->getId())
->update(array('HookActive' => $hook->getActivate()));
$this->cacheClear($dispatcher);
}
}
protected function cacheClear(EventDispatcherInterface $dispatcher)
{
$cacheEvent = new CacheEvent($this->cacheDir);
$dispatcher->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::MODULE_HOOK_CREATE => array('createModuleHook', 128),
TheliaEvents::MODULE_HOOK_UPDATE => array('updateModuleHook', 128),
TheliaEvents::MODULE_HOOK_DELETE => array('deleteModuleHook', 128),
TheliaEvents::MODULE_HOOK_UPDATE_POSITION => array('updateModuleHookPosition', 128),
TheliaEvents::MODULE_HOOK_TOGGLE_ACTIVATION => array('toggleModuleHookActivation', 128),
TheliaEvents::MODULE_TOGGLE_ACTIVATION => array('toggleModuleActivation', 64),
TheliaEvents::MODULE_DELETE => array('deleteModule', 64),
TheliaEvents::HOOK_TOGGLE_ACTIVATION => array('toggleHookActivation', 64),
TheliaEvents::HOOK_UPDATE => array('updateHook', 64),
);
}
}

View File

@@ -11,39 +11,63 @@
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Newsletter\NewsletterEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Mailer\MailerFactory;
use Thelia\Model\ConfigQuery;
use Thelia\Model\NewsletterQuery;
use Thelia\Model\Newsletter as NewsletterModel;
/**
* Class Newsletter
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Newsletter extends BaseAction implements EventSubscriberInterface
{
/** @var MailerFactory */
protected $mailer;
/** @var EventDispatcherInterface */
protected $dispatcher;
public function __construct(MailerFactory $mailer, EventDispatcherInterface $dispatcher)
{
$this->mailer = $mailer;
$this->dispatcher = $dispatcher;
}
public function subscribe(NewsletterEvent $event)
{
// test if the email is already registered and unsubscribed
if (null === $newsletter = NewsletterQuery::create()->findOneByEmail($event->getEmail())) {
$newsletter = new NewsletterModel();
}
$newsletter
->setEmail($event->getEmail())
->setFirstname($event->getFirstname())
->setLastname($event->getLastname())
->setLocale($event->getLocale())
->setUnsubscribed(false)
->save();
$event->setNewsletter($newsletter);
if (ConfigQuery::getNotifyNewsletterSubscription()) {
$this->dispatcher->dispatch(TheliaEvents::NEWSLETTER_CONFIRM_SUBSCRIPTION, $event);
}
}
public function unsubscribe(NewsletterEvent $event)
{
if (null !== $nl = NewsletterQuery::create()->findPk($event->getId())) {
$nl->delete();
$nl
->setUnsubscribed(true)
->save();
$event->setNewsletter($nl);
}
@@ -63,31 +87,33 @@ class Newsletter extends BaseAction implements EventSubscriberInterface
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* @since 2.3.0-alpha2
*/
public function confirmSubscription(NewsletterEvent $event)
{
$this->mailer->sendEmailMessage(
'newsletter_subscription_confirmation',
[ ConfigQuery::getStoreEmail() => ConfigQuery::getStoreName() ],
[ $event->getEmail() => $event->getFirstname()." ".$event->getLastname() ],
[
'email' => $event->getEmail(),
'firstname' => $event->getFirstname(),
'lastname' => $event->getLastname()
],
$event->getLocale()
);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::NEWSLETTER_SUBSCRIBE => array('subscribe', 128),
TheliaEvents::NEWSLETTER_UPDATE => array('update', 128),
TheliaEvents::NEWSLETTER_UNSUBSCRIBE => array('unsubscribe', 128)
TheliaEvents::NEWSLETTER_UNSUBSCRIBE => array('unsubscribe', 128),
TheliaEvents::NEWSLETTER_CONFIRM_SUBSCRIPTION => array('confirmSubscription', 128)
);
}
}

View File

@@ -12,32 +12,42 @@
namespace Thelia\Action;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Cart\CartTrait;
use Symfony\Component\HttpFoundation\RequestStack;
use Thelia\Core\Event\Order\OrderAddressEvent;
use Thelia\Core\Event\Order\OrderEvent;
use Thelia\Core\Event\Order\OrderManualEvent;
use Thelia\Core\Event\Order\OrderPaymentEvent;
use Thelia\Core\Event\Payment\ManageStockOnCreationEvent;
use Thelia\Core\Event\Product\VirtualProductOrderHandleEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpFoundation\Request;
use Thelia\Core\Security\SecurityContext;
use Thelia\Core\Template\ParserInterface;
use Thelia\Core\Security\User\UserInterface;
use Thelia\Exception\TheliaProcessException;
use Thelia\Mailer\MailerFactory;
use Thelia\Model\AddressQuery;
use Thelia\Model\Cart as CartModel;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Currency as CurrencyModel;
use Thelia\Model\Customer as CustomerModel;
use Thelia\Model\Lang as LangModel;
use Thelia\Model\Map\OrderTableMap;
use Thelia\Model\MessageQuery;
use Thelia\Model\ModuleQuery;
use Thelia\Model\Order as ModelOrder;
use Thelia\Model\Order as OrderModel;
use Thelia\Model\OrderAddress;
use Thelia\Model\OrderAddressQuery;
use Thelia\Model\OrderProduct;
use Thelia\Model\OrderProductAttributeCombination;
use Thelia\Model\OrderProductTax;
use Thelia\Model\OrderStatusQuery;
use Thelia\Model\ProductI18n;
use Thelia\Model\ProductSaleElements;
use Thelia\Model\ProductSaleElementsQuery;
use Thelia\Model\TaxRuleI18n;
use Thelia\Module\PaymentModuleInterface;
use Thelia\Tools\I18n;
/**
@@ -48,29 +58,18 @@ use Thelia\Tools\I18n;
*/
class Order extends BaseAction implements EventSubscriberInterface
{
use CartTrait;
/** @var RequestStack */
protected $requestStack;
/**
* @var \Thelia\Core\HttpFoundation\Request
*/
protected $request;
/**
* @var MailerFactory
*/
/** @var MailerFactory */
protected $mailer;
/**
* @var ParserInterface
*/
protected $parser;
/**
* @var SecurityContext
*/
/** @var SecurityContext */
protected $securityContext;
public function __construct(Request $request, ParserInterface $parser, MailerFactory $mailer, SecurityContext $securityContext)
public function __construct(RequestStack $requestStack, MailerFactory $mailer, SecurityContext $securityContext)
{
$this->request = $request;
$this->parser = $parser;
$this->requestStack = $requestStack;
$this->mailer = $mailer;
$this->securityContext = $securityContext;
}
@@ -101,6 +100,8 @@ class Order extends BaseAction implements EventSubscriberInterface
// Reset postage cost if the delivery module had been removed
if ($deliveryModuleId <= 0) {
$order->setPostage(0);
$order->setPostageTax(0);
$order->setPostageTaxRuleTitle(null);
}
$event->setOrder($order);
@@ -114,6 +115,8 @@ class Order extends BaseAction implements EventSubscriberInterface
$order = $event->getOrder();
$order->setPostage($event->getPostage());
$order->setPostageTax($event->getPostageTax());
$order->setPostageTaxRuleTitle($event->getPostageTaxRuleTitle());
$event->setOrder($order);
}
@@ -142,20 +145,48 @@ class Order extends BaseAction implements EventSubscriberInterface
$event->setOrder($order);
}
protected function createOrder(EventDispatcherInterface $dispatcher, ModelOrder $sessionOrder, CurrencyModel $currency, LangModel $lang, CartModel $cart, CustomerModel $customer)
{
$con = \Propel\Runtime\Propel::getConnection(
/**
* @param EventDispatcherInterface $dispatcher
* @param ModelOrder $sessionOrder
* @param CurrencyModel $currency
* @param LangModel $lang
* @param CartModel $cart
* @param UserInterface $customer
* @param bool $manageStock decrement stock when order is created if true
* @param bool $useOrderDefinedAddresses if true, the delivery and invoice OrderAddresses will be used instead of creating new OrderAdresses using Order::getChoosenXXXAddress()
* @return ModelOrder
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
protected function createOrder(
EventDispatcherInterface $dispatcher,
ModelOrder $sessionOrder,
CurrencyModel $currency,
LangModel $lang,
CartModel $cart,
UserInterface $customer,
$manageStock,
$useOrderDefinedAddresses = false
) {
$con = Propel::getConnection(
OrderTableMap::DATABASE_NAME
);
$con->beginTransaction();
$placedOrder = $sessionOrder->copy();
// Be sure to create a brand new order, as copy raises the modified flag for all fields
// and will also copy order reference and id.
$placedOrder->setId(null)->setRef(null)->setNew(true);
// Dates should be marked as not updated so that Propel will update them.
$placedOrder->resetModified(OrderTableMap::CREATED_AT);
$placedOrder->resetModified(OrderTableMap::UPDATED_AT);
$placedOrder->resetModified(OrderTableMap::VERSION_CREATED_AT);
$placedOrder->setDispatcher($dispatcher);
$deliveryAddress = AddressQuery::create()->findPk($sessionOrder->getChoosenDeliveryAddress());
$taxCountry = $deliveryAddress->getCountry();
$invoiceAddress = AddressQuery::create()->findPk($sessionOrder->getChoosenInvoiceAddress());
$cartItems = $cart->getCartItems();
/* fulfill order */
@@ -164,6 +195,16 @@ class Order extends BaseAction implements EventSubscriberInterface
$placedOrder->setCurrencyRate($currency->getRate());
$placedOrder->setLangId($lang->getId());
if ($useOrderDefinedAddresses) {
$taxCountry =
OrderAddressQuery::create()
->findPk($placedOrder->getDeliveryOrderAddressId())
->getCountry()
;
} else {
$deliveryAddress = AddressQuery::create()->findPk($sessionOrder->getChoosenDeliveryAddress());
$invoiceAddress = AddressQuery::create()->findPk($sessionOrder->getChoosenInvoiceAddress());
/* hard save the delivery and invoice addresses */
$deliveryOrderAddress = new OrderAddress();
$deliveryOrderAddress
@@ -177,9 +218,10 @@ class Order extends BaseAction implements EventSubscriberInterface
->setZipcode($deliveryAddress->getZipcode())
->setCity($deliveryAddress->getCity())
->setPhone($deliveryAddress->getPhone())
->setCellphone($deliveryAddress->getCellphone())
->setCountryId($deliveryAddress->getCountryId())
->save($con)
;
->setStateId($deliveryAddress->getStateId())
->save($con);
$invoiceOrderAddress = new OrderAddress();
$invoiceOrderAddress
@@ -193,17 +235,23 @@ class Order extends BaseAction implements EventSubscriberInterface
->setZipcode($invoiceAddress->getZipcode())
->setCity($invoiceAddress->getCity())
->setPhone($invoiceAddress->getPhone())
->setCellphone($invoiceAddress->getCellphone())
->setCountryId($invoiceAddress->getCountryId())
->save($con)
;
->setStateId($deliveryAddress->getStateId())
->save($con);
$placedOrder->setDeliveryOrderAddressId($deliveryOrderAddress->getId());
$placedOrder->setInvoiceOrderAddressId($invoiceOrderAddress->getId());
$taxCountry = $deliveryAddress->getCountry();
}
$placedOrder->setStatusId(
OrderStatusQuery::getNotPaidStatus()->getId()
);
$placedOrder->setCartId($cart->getId());
/* memorize discount */
$placedOrder->setDiscount(
$cart->getDiscount()
@@ -217,22 +265,49 @@ class Order extends BaseAction implements EventSubscriberInterface
$product = $cartItem->getProduct();
/* get translation */
/** @var ProductI18n $productI18n */
$productI18n = I18n::forceI18nRetrieving($lang->getLocale(), 'Product', $product->getId());
$pse = $cartItem->getProductSaleElements();
// get the virtual document path
$virtualDocumentEvent = new VirtualProductOrderHandleEvent($placedOrder, $pse->getId());
// essentially used for virtual product. modules that handles virtual product can
// allow the use of stock even for virtual products
$useStock = true;
$virtual = 0;
// if the product is virtual, dispatch an event to collect information
if ($product->getVirtual() === 1) {
$dispatcher->dispatch(TheliaEvents::VIRTUAL_PRODUCT_ORDER_HANDLE, $virtualDocumentEvent);
$useStock = $virtualDocumentEvent->isUseStock();
$virtual = $virtualDocumentEvent->isVirtual() ? 1 : 0;
}
/* check still in stock */
if ($cartItem->getQuantity() > $pse->getQuantity()) {
if ($cartItem->getQuantity() > $pse->getQuantity()
&& true === ConfigQuery::checkAvailableStock()
&& $useStock) {
throw new TheliaProcessException("Not enough stock", TheliaProcessException::CART_ITEM_NOT_ENOUGH_STOCK, $cartItem);
}
/* decrease stock */
if ($useStock && $manageStock) {
/* decrease stock for non virtual product */
$allowNegativeStock = intval(ConfigQuery::read('allow_negative_stock', 0));
$newStock = $pse->getQuantity() - $cartItem->getQuantity();
//Forbid negative stock
if ($newStock < 0 && 0 === $allowNegativeStock) {
$newStock = 0;
}
$pse->setQuantity(
$pse->getQuantity() - $cartItem->getQuantity()
$newStock
);
$pse->save($con);
}
/* get tax */
/** @var TaxRuleI18n $taxRuleI18n */
$taxRuleI18n = I18n::forceI18nRetrieving($lang->getLocale(), 'TaxRule', $product->getTaxRuleId());
$taxDetail = $product->getTaxRule()->getTaxDetail(
@@ -248,10 +323,13 @@ class Order extends BaseAction implements EventSubscriberInterface
->setOrderId($placedOrder->getId())
->setProductRef($product->getRef())
->setProductSaleElementsRef($pse->getRef())
->setProductSaleElementsId($pse->getId())
->setTitle($productI18n->getTitle())
->setChapo($productI18n->getChapo())
->setDescription($productI18n->getDescription())
->setPostscriptum($productI18n->getPostscriptum())
->setVirtual($virtual)
->setVirtualDocument($virtualDocumentEvent->getPath())
->setQuantity($cartItem->getQuantity())
->setPrice($cartItem->getPrice())
->setPromoPrice($cartItem->getPromoPrice())
@@ -261,12 +339,13 @@ class Order extends BaseAction implements EventSubscriberInterface
->setTaxRuleTitle($taxRuleI18n->getTitle())
->setTaxRuleDescription($taxRuleI18n->getDescription())
->setEanCode($pse->getEanCode())
->setCartIemId($cartItem->getId())
->setCartItemId($cartItem->getId())
->setDispatcher($dispatcher)
->save($con)
;
/* fulfill order_product_tax */
/** @var OrderProductTax $tax */
foreach ($taxDetail as $tax) {
$tax->setOrderProductId($orderProduct->getId());
$tax->save($con);
@@ -274,7 +353,10 @@ class Order extends BaseAction implements EventSubscriberInterface
/* fulfill order_attribute_combination and decrease stock */
foreach ($pse->getAttributeCombinations() as $attributeCombination) {
/** @var \Thelia\Model\Attribute $attribute */
$attribute = I18n::forceI18nRetrieving($lang->getLocale(), 'Attribute', $attributeCombination->getAttributeId());
/** @var \Thelia\Model\AttributeAv $attributeAv */
$attributeAv = I18n::forceI18nRetrieving($lang->getLocale(), 'AttributeAv', $attributeCombination->getAttributeAvId());
$orderAttributeCombination = new OrderProductAttributeCombination();
@@ -299,50 +381,72 @@ class Order extends BaseAction implements EventSubscriberInterface
/**
* Create an order outside of the front-office context, e.g. manually from the back-office.
* @param OrderManualEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function createManual(OrderManualEvent $event)
public function createManual(OrderManualEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$paymentModule = ModuleQuery::create()->findPk($event->getOrder()->getPaymentModuleId());
/** @var \Thelia\Module\PaymentModuleInterface $paymentModuleInstance */
$paymentModuleInstance = $paymentModule->createInstance();
$event->setPlacedOrder(
$this->createOrder(
$event->getDispatcher(),
$dispatcher,
$event->getOrder(),
$event->getCurrency(),
$event->getLang(),
$event->getCart(),
$event->getCustomer()
$event->getCustomer(),
$this->isModuleManageStockOnCreation(
$dispatcher,
$paymentModuleInstance
),
$event->getUseOrderDefinedAddresses()
)
);
$event->setOrder(new \Thelia\Model\Order());
$event->setOrder(new OrderModel());
}
/**
* @param OrderEvent $event
*
* @throws \Thelia\Exception\TheliaProcessException
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(OrderEvent $event)
public function create(OrderEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$session = $this->getSession();
$order = $event->getOrder();
$paymentModule = ModuleQuery::create()->findPk($order->getPaymentModuleId());
/** @var \Thelia\Module\PaymentModuleInterface $paymentModuleInstance */
$paymentModuleInstance = $paymentModule->createInstance();
$placedOrder = $this->createOrder(
$event->getDispatcher(),
$dispatcher,
$event->getOrder(),
$session->getCurrency(),
$session->getLang(),
$session->getCart(),
$this->securityContext->getCustomerUser()
$session->getSessionCart($dispatcher),
$this->securityContext->getCustomerUser(),
$this->isModuleManageStockOnCreation(
$dispatcher,
$paymentModuleInstance
)
);
$event->getDispatcher()->dispatch(TheliaEvents::ORDER_BEFORE_PAYMENT, new OrderEvent($placedOrder));
$dispatcher->dispatch(TheliaEvents::ORDER_BEFORE_PAYMENT, new OrderEvent($placedOrder));
/* but memorize placed order */
$event->setOrder(new \Thelia\Model\Order());
$event->setOrder(new OrderModel());
$event->setPlacedOrder($placedOrder);
/* empty cart */
$dispatcher = $event->getDispatcher();
/* call pay method */
$payEvent = new OrderPaymentEvent($placedOrder);
@@ -354,68 +458,177 @@ class Order extends BaseAction implements EventSubscriberInterface
}
/**
* @param \Thelia\Core\Event\Order\OrderEvent $event
* @param OrderEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function sendOrderEmail(OrderEvent $event)
public function orderBeforePayment(OrderEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$contact_email = ConfigQuery::read('store_email');
$dispatcher ->dispatch(TheliaEvents::ORDER_SEND_CONFIRMATION_EMAIL, clone $event);
if ($contact_email) {
$message = MessageQuery::create()
->filterByName('order_confirmation')
->findOne();
if (false === $message) {
throw new \Exception("Failed to load message 'order_confirmation'.");
}
$order = $event->getOrder();
$customer = $order->getCustomer();
$this->parser->assign('order_id', $order->getId());
$this->parser->assign('order_ref', $order->getRef());
$message
->setLocale($order->getLang()->getLocale());
$instance = \Swift_Message::newInstance()
->addTo($customer->getEmail(), $customer->getFirstname()." ".$customer->getLastname())
->addFrom($contact_email, ConfigQuery::read('store_name'))
;
// Build subject and body
$message->buildMessage($this->parser, $instance);
$this->getMailer()->send($instance);
}
$dispatcher->dispatch(TheliaEvents::ORDER_SEND_NOTIFICATION_EMAIL, clone $event);
}
/**
* Clear the cart and the order in the customer session once the order is placed,
* and the payment performed.
*
* return an instance of \Swift_Mailer with good Transporter configured.
*
* @return \Swift_Mailer
* @param OrderEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function getMailer()
public function orderCartClear(/** @noinspection PhpUnusedParameterInspection */ OrderEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
return $this->mailer->getSwiftMailer();
// Empty cart and clear current order
$session = $this->getSession();
$session->clearSessionCart($dispatcher);
$session->setOrder(new OrderModel());
}
/**
* @param OrderEvent $event
*
* @throws \Exception if the message cannot be loaded.
*/
public function sendConfirmationEmail(OrderEvent $event)
{
$this->mailer->sendEmailToCustomer(
'order_confirmation',
$event->getOrder()->getCustomer(),
[
'order_id' => $event->getOrder()->getId(),
'order_ref' => $event->getOrder()->getRef()
]
);
}
/**
* @param OrderEvent $event
*
* @throws \Exception if the message cannot be loaded.
*/
public function sendNotificationEmail(OrderEvent $event)
{
$this->mailer->sendEmailToShopManagers(
'order_notification',
[
'order_id' => $event->getOrder()->getId(),
'order_ref' => $event->getOrder()->getRef()
]
);
}
/**
* @param OrderEvent $event
*/
public function updateStatus(OrderEvent $event)
public function updateStatus(OrderEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$order = $event->getOrder();
$newStatus = $event->getStatus();
$paymentModule = ModuleQuery::create()->findPk($order->getPaymentModuleId());
/** @var PaymentModuleInterface $paymentModuleInstance */
$paymentModuleInstance = $paymentModule->createInstance();
$order->setStatusId($event->getStatus());
$manageStockOnCreation = $this->isModuleManageStockOnCreation(
$dispatcher,
$paymentModuleInstance
);
$this->updateQuantity($order, $newStatus, $manageStockOnCreation);
$order->setStatusId($newStatus);
$order->save();
$event->setOrder($order);
}
/**
* @param ModelOrder $order
* @param $newStatus $newStatus the new status ID
* @throws \Thelia\Exception\TheliaProcessException
*/
public function updateQuantity(ModelOrder $order, $newStatus, $manageStockOnCreation = true)
{
$canceledStatus = OrderStatusQuery::getCancelledStatus()->getId();
$paidStatus = OrderStatusQuery::getPaidStatus()->getId();
if ($newStatus == $canceledStatus || $order->isCancelled()) {
$this->updateQuantityForCanceledOrder($order, $newStatus, $canceledStatus);
} elseif ($paidStatus == $newStatus && $order->isNotPaid() && $order->getVersion() == 1) {
$this->updateQuantityForPaidOrder($order, $manageStockOnCreation);
}
}
/**
* @param ModelOrder $order
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
protected function updateQuantityForPaidOrder(ModelOrder $order, $manageStockOnCreation)
{
$paymentModule = ModuleQuery::create()->findPk($order->getPaymentModuleId());
/** @var \Thelia\Module\PaymentModuleInterface $paymentModuleInstance */
$paymentModuleInstance = $paymentModule->createInstance();
if (false === $manageStockOnCreation) {
$orderProductList = $order->getOrderProducts();
/** @var OrderProduct $orderProduct */
foreach ($orderProductList as $orderProduct) {
$productSaleElementsId = $orderProduct->getProductSaleElementsId();
/** @var ProductSaleElements $productSaleElements */
if (null !== $productSaleElements = ProductSaleElementsQuery::create()->findPk($productSaleElementsId)) {
/* check still in stock */
if ($orderProduct->getQuantity() > $productSaleElements->getQuantity() && true === ConfigQuery::checkAvailableStock()) {
throw new TheliaProcessException($productSaleElements->getRef() . " : Not enough stock");
}
$productSaleElements->setQuantity($productSaleElements->getQuantity() - $orderProduct->getQuantity());
$productSaleElements->save();
}
}
}
}
/**
* Update product quantity if new status is canceled or if old status is canceled.
*
* @param ModelOrder $order
* @param $newStatus
* @param $canceledStatus
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
protected function updateQuantityForCanceledOrder(ModelOrder $order, $newStatus, $canceledStatus)
{
$orderProductList = $order->getOrderProducts();
/** @var OrderProduct $orderProduct */
foreach ($orderProductList as $orderProduct) {
$productSaleElementsId = $orderProduct->getProductSaleElementsId();
/** @var ProductSaleElements $productSaleElements */
if (null !== $productSaleElements = ProductSaleElementsQuery::create()->findPk($productSaleElementsId)) {
if ($newStatus == $canceledStatus) {
$productSaleElements->setQuantity($productSaleElements->getQuantity() + $orderProduct->getQuantity());
} else {
/* check still in stock */
if ($orderProduct->getQuantity() > $productSaleElements->getQuantity() && true === ConfigQuery::checkAvailableStock()) {
throw new TheliaProcessException($productSaleElements->getRef() . " : Not enough stock");
}
$productSaleElements->setQuantity($productSaleElements->getQuantity() - $orderProduct->getQuantity());
}
$productSaleElements->save();
}
}
}
/**
* @param OrderEvent $event
*/
@@ -429,6 +642,19 @@ class Order extends BaseAction implements EventSubscriberInterface
$event->setOrder($order);
}
/**
* @param OrderEvent $event
*/
public function updateTransactionRef(OrderEvent $event)
{
$order = $event->getOrder();
$order->setTransactionRef($event->getTransactionRef());
$order->save();
$event->setOrder($order);
}
/**
* @param OrderAddressEvent $event
*/
@@ -447,7 +673,9 @@ class Order extends BaseAction implements EventSubscriberInterface
->setZipcode($event->getZipcode())
->setCity($event->getCity())
->setCountryId($event->getCountry())
->setStateId($event->getState())
->setPhone($event->getPhone())
->setCellphone($event->getCellphone())
;
$orderAddress->save();
@@ -455,24 +683,30 @@ class Order extends BaseAction implements EventSubscriberInterface
}
/**
* Returns an array of event names this subscriber wants to listen to.
* Check if a payment module manage stock on creation
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* @param EventDispatcher $dispatcher
* @param PaymentModuleInterface $module
* @return bool if the module manage stock on creation, false otherwise
*/
protected function isModuleManageStockOnCreation(EventDispatcherInterface $dispatcher, PaymentModuleInterface $module)
{
$event = new ManageStockOnCreationEvent($module);
$dispatcher->dispatch(
TheliaEvents::getModuleEvent(
TheliaEvents::MODULE_PAYMENT_MANAGE_STOCK,
$module->getCode()
)
);
return (null !== $event->getManageStock())
? $event->getManageStock()
: $module->manageStockOnCreation();
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
@@ -483,9 +717,13 @@ class Order extends BaseAction implements EventSubscriberInterface
TheliaEvents::ORDER_SET_INVOICE_ADDRESS => array("setInvoiceAddress", 128),
TheliaEvents::ORDER_SET_PAYMENT_MODULE => array("setPaymentModule", 128),
TheliaEvents::ORDER_PAY => array("create", 128),
TheliaEvents::ORDER_BEFORE_PAYMENT => array("sendOrderEmail", 128),
TheliaEvents::ORDER_CART_CLEAR => array("orderCartClear", 128),
TheliaEvents::ORDER_BEFORE_PAYMENT => array("orderBeforePayment", 128),
TheliaEvents::ORDER_SEND_CONFIRMATION_EMAIL => array("sendConfirmationEmail", 128),
TheliaEvents::ORDER_SEND_NOTIFICATION_EMAIL => array("sendNotificationEmail", 128),
TheliaEvents::ORDER_UPDATE_STATUS => array("updateStatus", 128),
TheliaEvents::ORDER_UPDATE_DELIVERY_REF => array("updateDeliveryRef", 128),
TheliaEvents::ORDER_UPDATE_TRANSACTION_REF => array("updateTransactionRef", 128),
TheliaEvents::ORDER_UPDATE_ADDRESS => array("updateAddress", 128),
TheliaEvents::ORDER_CREATE_MANUAL => array("createManual", 128),
);
@@ -498,6 +736,6 @@ class Order extends BaseAction implements EventSubscriberInterface
*/
protected function getSession()
{
return $this->request->getSession();
return $this->requestStack->getCurrentRequest()->getSession();
}
}

View File

@@ -0,0 +1,147 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Propel\Runtime\ActiveQuery\Criteria;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\OrderStatus\OrderStatusCreateEvent;
use Thelia\Core\Event\OrderStatus\OrderStatusDeleteEvent;
use Thelia\Core\Event\OrderStatus\OrderStatusEvent;
use Thelia\Core\Event\OrderStatus\OrderStatusUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Translation\Translator;
use Thelia\Model\OrderQuery;
use Thelia\Model\OrderStatus as OrderStatusModel;
use Thelia\Model\OrderStatusQuery;
/**
* Class OrderStatus
* @package Thelia\Action
* @author Gilles Bourgeat <gbourgeat@openstudio.fr>
* @since 2.4
*/
class OrderStatus extends BaseAction implements EventSubscriberInterface
{
/**
* @param OrderStatusCreateEvent $event
*/
public function create(OrderStatusCreateEvent $event)
{
$this->createOrUpdate($event, new OrderStatusModel());
}
/**
* @param OrderStatusUpdateEvent $event
*/
public function update(OrderStatusUpdateEvent $event)
{
$orderStatus = $this->getOrderStatus($event);
$this->createOrUpdate($event, $orderStatus);
}
/**
* @param OrderStatusDeleteEvent $event
* @throws \Exception
*/
public function delete(OrderStatusDeleteEvent $event)
{
$orderStatus = $this->getOrderStatus($event);
if ($orderStatus->getProtectedStatus()) {
throw new \Exception(
Translator::getInstance()->trans('This status is protected.')
. ' ' . Translator::getInstance()->trans('You can not delete it.')
);
}
if (null !== OrderQuery::create()->findOneByStatusId($orderStatus->getId())) {
throw new \Exception(
Translator::getInstance()->trans('Some commands use this status.')
. ' ' . Translator::getInstance()->trans('You can not delete it.')
);
}
$orderStatus->delete();
$event->setOrderStatus($orderStatus);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::ORDER_STATUS_CREATE => ["create", 128],
TheliaEvents::ORDER_STATUS_UPDATE => ["update", 128],
TheliaEvents::ORDER_STATUS_DELETE => ["delete", 128],
TheliaEvents::ORDER_STATUS_UPDATE_POSITION => ["updatePosition", 128]
);
}
/**
* @param OrderStatusEvent $event
* @param OrderStatusModel $orderStatus
*/
protected function createOrUpdate(OrderStatusEvent $event, OrderStatusModel $orderStatus)
{
$orderStatus
->setCode(!$orderStatus->getProtectedStatus() ? $event->getCode() : $orderStatus->getCode())
->setColor($event->getColor())
// i18n
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setDescription($event->getDescription())
->setPostscriptum($event->getPostscriptum())
->setChapo($event->getChapo());
if ($orderStatus->getId() === null) {
$orderStatus->setPosition(
OrderStatusQuery::create()->orderByPosition(Criteria::DESC)->findOne()->getPosition() + 1
);
}
$orderStatus->save();
$event->setOrderStatus($orderStatus);
}
/**
* Changes position, selecting absolute ou relative change.
*
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(OrderStatusQuery::create(), $event, $dispatcher);
}
/**
* @param OrderStatusUpdateEvent $event
* @return OrderStatusModel
*/
protected function getOrderStatus(OrderStatusUpdateEvent $event)
{
if (null === $orderStatus = OrderStatusQuery::create()->findOneById($event->getId())) {
throw new \LogicException(
"Order status not found"
);
}
return $orderStatus;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Payment\IsValidPaymentEvent;
use Thelia\Core\Event\TheliaEvents;
/**
* Class Payment
* @package Thelia\Action
* @author Julien Chanséaume <julien@thelia.net>
*/
class Payment implements EventSubscriberInterface
{
/**
* Check if a module is valid
*
* @param IsValidPaymentEvent $event
*/
public function isValid(IsValidPaymentEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$module = $event->getModule();
// dispatch event to target specific module
$dispatcher->dispatch(
TheliaEvents::getModuleEvent(
TheliaEvents::MODULE_PAYMENT_IS_VALID,
$module->getCode()
),
$event
);
if ($event->isPropagationStopped()) {
return;
}
// call legacy module method
$event->setValidModule($module->isValidPayment());
}
/**
* @inheritdoc
*/
public static function getSubscribedEvents()
{
return [
TheliaEvents::MODULE_PAYMENT_IS_VALID => ['isValid', 128],
];
}
}

View File

@@ -11,6 +11,7 @@
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\PdfEvent;
use Thelia\Core\Event\TheliaEvents;
@@ -18,15 +19,22 @@ use Thelia\Core\Event\TheliaEvents;
/**
* Class Pdf
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Pdf extends BaseAction implements EventSubscriberInterface
{
public function generatePdf(PdfEvent $event)
{
$html2pdf = new \HTML2PDF(
$event->getOrientation(),
$event->getFormat(),
$event->getLang(),
$event->getUnicode(),
$event->getEncoding(),
$event->getMarges()
);
$html2pdf = new \HTML2PDF($event->getOrientation(), $event->getFormat(), $event->getLang(), $event->getUnicode(), $event->getEncoding(), $event->getMarges());
$html2pdf->setDefaultFont($event->getFontName());
$html2pdf->pdf->SetDisplayMode('real');
@@ -35,24 +43,7 @@ class Pdf extends BaseAction implements EventSubscriberInterface
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{

View File

@@ -12,43 +12,77 @@
namespace Thelia\Action;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Exception\PropelException;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Thelia\Core\Event\Feature\FeatureAvCreateEvent;
use Thelia\Core\Event\Feature\FeatureAvDeleteEvent;
use Thelia\Core\Event\FeatureProduct\FeatureProductDeleteEvent;
use Thelia\Core\Event\FeatureProduct\FeatureProductUpdateEvent;
use Thelia\Core\Event\File\FileDeleteEvent;
use Thelia\Core\Event\Product\ProductAddAccessoryEvent;
use Thelia\Core\Event\Product\ProductAddCategoryEvent;
use Thelia\Core\Event\Product\ProductAddContentEvent;
use Thelia\Core\Event\Product\ProductCloneEvent;
use Thelia\Core\Event\Product\ProductCreateEvent;
use Thelia\Core\Event\Product\ProductDeleteAccessoryEvent;
use Thelia\Core\Event\Product\ProductDeleteCategoryEvent;
use Thelia\Core\Event\Product\ProductDeleteContentEvent;
use Thelia\Core\Event\Product\ProductDeleteEvent;
use Thelia\Core\Event\Product\ProductSetTemplateEvent;
use Thelia\Core\Event\Product\ProductToggleVisibilityEvent;
use Thelia\Core\Event\Product\ProductUpdateEvent;
use Thelia\Core\Event\ProductSaleElement\ProductSaleElementDeleteEvent;
use Thelia\Core\Event\Template\TemplateDeleteAttributeEvent;
use Thelia\Core\Event\Template\TemplateDeleteFeatureEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Core\Event\ViewCheckEvent;
use Thelia\Model\Accessory;
use Thelia\Model\AccessoryQuery;
use Thelia\Model\AttributeTemplateQuery;
use Thelia\Model\Currency as CurrencyModel;
use Thelia\Model\FeatureAvI18n;
use Thelia\Model\FeatureAvI18nQuery;
use Thelia\Model\FeatureAvQuery;
use Thelia\Model\FeatureProduct;
use Thelia\Model\FeatureProductQuery;
use Thelia\Model\FeatureTemplateQuery;
use Thelia\Model\Map\AttributeTemplateTableMap;
use Thelia\Model\Map\FeatureTemplateTableMap;
use Thelia\Model\Map\ProductSaleElementsTableMap;
use Thelia\Model\Map\ProductTableMap;
use Thelia\Model\ProductQuery;
use Thelia\Model\Product as ProductModel;
use Thelia\Model\ProductAssociatedContent;
use Thelia\Model\ProductAssociatedContentQuery;
use Thelia\Model\ProductCategory;
use Thelia\Model\TaxRuleQuery;
use Thelia\Model\AccessoryQuery;
use Thelia\Model\Accessory;
use Thelia\Model\FeatureProduct;
use Thelia\Model\FeatureProductQuery;
use Thelia\Model\ProductCategoryQuery;
use Thelia\Model\ProductDocument;
use Thelia\Model\ProductDocumentQuery;
use Thelia\Model\ProductI18n;
use Thelia\Model\ProductI18nQuery;
use Thelia\Model\ProductImage;
use Thelia\Model\ProductImageQuery;
use Thelia\Model\ProductPrice;
use Thelia\Model\ProductPriceQuery;
use Thelia\Model\ProductQuery;
use Thelia\Model\ProductSaleElementsQuery;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Product\ProductUpdateEvent;
use Thelia\Core\Event\Product\ProductCreateEvent;
use Thelia\Core\Event\Product\ProductDeleteEvent;
use Thelia\Core\Event\Product\ProductToggleVisibilityEvent;
use Thelia\Core\Event\Product\ProductAddContentEvent;
use Thelia\Core\Event\Product\ProductDeleteContentEvent;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\UpdateSeoEvent;
use Thelia\Core\Event\FeatureProduct\FeatureProductUpdateEvent;
use Thelia\Core\Event\FeatureProduct\FeatureProductDeleteEvent;
use Thelia\Core\Event\Product\ProductSetTemplateEvent;
use Thelia\Core\Event\Product\ProductDeleteCategoryEvent;
use Thelia\Core\Event\Product\ProductAddCategoryEvent;
use Thelia\Core\Event\Product\ProductAddAccessoryEvent;
use Thelia\Core\Event\Product\ProductDeleteAccessoryEvent;
use Propel\Runtime\Propel;
use Thelia\Model\TaxRuleQuery;
class Product extends BaseAction implements EventSubscriberInterface
{
/** @var EventDispatcherInterface */
protected $eventDispatcher;
public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
/**
* Create a new product entry
*
@@ -56,42 +90,249 @@ class Product extends BaseAction implements EventSubscriberInterface
*/
public function create(ProductCreateEvent $event)
{
$defaultTaxRuleId = null;
if (null !== $defaultTaxRule = TaxRuleQuery::create()->findOneByIsDefault(true)) {
$defaultTaxRuleId = $defaultTaxRule->getId();
}
$product = new ProductModel();
$product
->setDispatcher($event->getDispatcher())
->setDispatcher($this->eventDispatcher)
->setRef($event->getRef())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setVisible($event->getVisible() ? 1 : 0)
// Set the default tax rule to this product
->setTaxRule(TaxRuleQuery::create()->findOneByIsDefault(true))
->setVirtual($event->getVirtual() ? 1 : 0)
->setTemplateId($event->getTemplateId())
->create(
$event->getDefaultCategory(),
$event->getBasePrice(),
$event->getCurrencyId(),
$event->getTaxRuleId(),
$event->getBaseWeight()
);
// Set the default tax rule if not defined
$event->getTaxRuleId() ?: $defaultTaxRuleId,
$event->getBaseWeight(),
$event->getBaseQuantity()
)
;
$event->setProduct($product);
}
/*******************
* CLONING PROCESS *
*******************/
/**
* @param ProductCloneEvent $event
* @throws \Exception
*/
public function cloneProduct(ProductCloneEvent $event)
{
$con = Propel::getWriteConnection(ProductTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
// Get important datas
$lang = $event->getLang();
$originalProduct = $event->getOriginalProduct();
if (null === $originalProductDefaultI18n = ProductI18nQuery::create()
->findPk([$originalProduct->getId(), $lang])) {
// No i18n entry for the current language. Try to find one for creating the product.
// It will be updated later by updateClone()
$originalProductDefaultI18n = ProductI18nQuery::create()
->findOneById($originalProduct->getId())
;
}
$originalProductDefaultPrice = ProductPriceQuery::create()
->findOneByProductSaleElementsId($originalProduct->getDefaultSaleElements()->getId());
// Cloning process
$this->createClone($event, $originalProductDefaultI18n, $originalProductDefaultPrice);
$this->updateClone($event, $originalProductDefaultPrice);
$this->cloneFeatureCombination($event);
$this->cloneAssociatedContent($event);
$this->cloneAccessories($event);
// Dispatch event for file cloning
$this->eventDispatcher->dispatch(TheliaEvents::FILE_CLONE, $event);
// Dispatch event for PSE cloning
$this->eventDispatcher->dispatch(TheliaEvents::PSE_CLONE, $event);
$con->commit();
} catch (\Exception $e) {
$con->rollBack();
throw $e;
}
}
public function createClone(ProductCloneEvent $event, ProductI18n $originalProductDefaultI18n, ProductPrice $originalProductDefaultPrice)
{
// Build event and dispatch creation of the clone product
$createCloneEvent = new ProductCreateEvent();
$createCloneEvent
->setTitle($originalProductDefaultI18n->getTitle())
->setRef($event->getRef())
->setLocale($event->getLang())
->setVisible(0)
->setVirtual($event->getOriginalProduct()->getVirtual())
->setTaxRuleId($event->getOriginalProduct()->getTaxRuleId())
->setDefaultCategory($event->getOriginalProduct()->getDefaultCategoryId())
->setBasePrice($originalProductDefaultPrice->getPrice())
->setCurrencyId($originalProductDefaultPrice->getCurrencyId())
->setBaseWeight($event->getOriginalProduct()->getDefaultSaleElements()->getWeight());
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_CREATE, $createCloneEvent);
$event->setClonedProduct($createCloneEvent->getProduct());
}
public function updateClone(ProductCloneEvent $event, ProductPrice $originalProductDefaultPrice)
{
// Get original product's I18ns
$originalProductI18ns = ProductI18nQuery::create()
->findById($event->getOriginalProduct()->getId());
/** @var ProductI18n $originalProductI18n */
foreach ($originalProductI18ns as $originalProductI18n) {
$clonedProductUpdateEvent = new ProductUpdateEvent($event->getClonedProduct()->getId());
$clonedProductUpdateEvent
->setRef($event->getClonedProduct()->getRef())
->setVisible($event->getClonedProduct()->getVisible())
->setVirtual($event->getClonedProduct()->getVirtual())
->setLocale($originalProductI18n->getLocale())
->setTitle($originalProductI18n->getTitle())
->setChapo($originalProductI18n->getChapo())
->setDescription($originalProductI18n->getDescription())
->setPostscriptum($originalProductI18n->getPostscriptum())
->setBasePrice($originalProductDefaultPrice->getPrice())
->setCurrencyId($originalProductDefaultPrice->getCurrencyId())
->setBaseWeight($event->getOriginalProduct()->getDefaultSaleElements()->getWeight())
->setTaxRuleId($event->getOriginalProduct()->getTaxRuleId())
->setBrandId($event->getOriginalProduct()->getBrandId())
->setDefaultCategory($event->getOriginalProduct()->getDefaultCategoryId());
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_UPDATE, $clonedProductUpdateEvent);
// SEO info
$clonedProductUpdateSeoEvent = new UpdateSeoEvent($event->getClonedProduct()->getId());
$clonedProductUpdateSeoEvent
->setLocale($originalProductI18n->getLocale())
->setMetaTitle($originalProductI18n->getMetaTitle())
->setMetaDescription($originalProductI18n->getMetaDescription())
->setMetaKeywords($originalProductI18n->getMetaKeywords())
->setUrl(null);
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_UPDATE_SEO, $clonedProductUpdateSeoEvent);
}
$event->setClonedProduct($clonedProductUpdateEvent->getProduct());
// Set clone's template
$clonedProductUpdateTemplateEvent = new ProductSetTemplateEvent(
$event->getClonedProduct(),
$event->getOriginalProduct()->getTemplateId(),
$originalProductDefaultPrice->getCurrencyId()
);
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_SET_TEMPLATE, $clonedProductUpdateTemplateEvent);
}
public function cloneFeatureCombination(ProductCloneEvent $event)
{
// Get original product FeatureProduct list
$originalProductFeatureList = FeatureProductQuery::create()
->findByProductId($event->getOriginalProduct()->getId());
// Set clone product FeatureProducts
/** @var FeatureProduct $originalProductFeature */
foreach ($originalProductFeatureList as $originalProductFeature) {
// Get original FeatureAvI18n list
$originalProductFeatureAvI18nList = FeatureAvI18nQuery::create()
->findById($originalProductFeature->getFeatureAvId());
/** @var FeatureAvI18n $originalProductFeatureAvI18n */
foreach ($originalProductFeatureAvI18nList as $originalProductFeatureAvI18n) {
// Create a FeatureProduct for each FeatureAv (not for each FeatureAvI18n)
$clonedProductCreateFeatureEvent = new FeatureProductUpdateEvent(
$event->getClonedProduct()->getId(),
$originalProductFeature->getFeatureId(),
$originalProductFeature->getFeatureAvId()
);
$clonedProductCreateFeatureEvent->setLocale($originalProductFeatureAvI18n->getLocale());
// If it's a free text value, pass the FeatureAvI18n's title as featureValue to the event
if ($originalProductFeature->getFreeTextValue() !== null) {
$clonedProductCreateFeatureEvent->setFeatureValue($originalProductFeatureAvI18n->getTitle());
$clonedProductCreateFeatureEvent->setIsTextValue(true);
}
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_FEATURE_UPDATE_VALUE, $clonedProductCreateFeatureEvent);
}
}
}
public function cloneAssociatedContent(ProductCloneEvent $event)
{
// Get original product associated contents
$originalProductAssocConts = ProductAssociatedContentQuery::create()
->findByProductId($event->getOriginalProduct()->getId());
// Set clone product associated contents
/** @var ProductAssociatedContent $originalProductAssocCont */
foreach ($originalProductAssocConts as $originalProductAssocCont) {
$clonedProductCreatePAC = new ProductAddContentEvent($event->getClonedProduct(), $originalProductAssocCont->getContentId());
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_ADD_CONTENT, $clonedProductCreatePAC);
}
}
public function cloneAccessories(ProductCloneEvent $event)
{
// Get original product accessories
$originalProductAccessoryList = AccessoryQuery::create()
->findByProductId($event->getOriginalProduct()->getId());
// Set clone product accessories
/** @var Accessory $originalProductAccessory */
foreach ($originalProductAccessoryList as $originalProductAccessory) {
$clonedProductAddAccessoryEvent = new ProductAddAccessoryEvent($event->getClonedProduct(), $originalProductAccessory->getAccessory());
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_ADD_ACCESSORY, $clonedProductAddAccessoryEvent);
}
}
/***************
* END CLONING *
***************/
/**
* Change a product
*
* @param \Thelia\Core\Event\Product\ProductUpdateEvent $event
* @throws PropelException
* @throws \Exception
*/
public function update(ProductUpdateEvent $event)
{
if (null !== $product = ProductQuery::create()->findPk($event->getProductId())) {
$con = Propel::getWriteConnection(ProductTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$prevRef = $product->getRef();
$product
->setDispatcher($event->getDispatcher())
->setDispatcher($this->eventDispatcher)
->setRef($event->getRef())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
@@ -99,57 +340,102 @@ class Product extends BaseAction implements EventSubscriberInterface
->setChapo($event->getChapo())
->setPostscriptum($event->getPostscriptum())
->setVisible($event->getVisible() ? 1 : 0)
->setVirtual($event->getVirtual() ? 1 : 0)
->setBrandId($event->getBrandId() <= 0 ? null : $event->getBrandId())
->save()
->save($con)
;
// Update default category (ifd required)
$product->updateDefaultCategory($event->getDefaultCategory());
// Update default PSE (if product has no attributes and the product's ref change)
$defaultPseRefChange = $prevRef !== $product->getRef()
&& 0 === $product->getDefaultSaleElements()->countAttributeCombinations();
if ($defaultPseRefChange) {
$defaultPse = $product->getDefaultSaleElements();
$defaultPse->setRef($product->getRef())->save();
}
// Update default category (if required)
$product->setDefaultCategory($event->getDefaultCategory());
$event->setProduct($product);
$con->commit();
} catch (PropelException $e) {
$con->rollBack();
throw $e;
}
}
}
/**
* Change a product SEO
*
* @param \Thelia\Core\Event\UpdateSeoEvent $event
* @param UpdateSeoEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return mixed
*/
public function updateSeo(UpdateSeoEvent $event)
public function updateSeo(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
return $this->genericUpdateSeo(ProductQuery::create(), $event);
return $this->genericUpdateSeo(ProductQuery::create(), $event, $dispatcher);
}
/**
* Delete a product entry
*
* @param \Thelia\Core\Event\Product\ProductDeleteEvent $event
* @throws \Exception
*/
public function delete(ProductDeleteEvent $event)
{
if (null !== $product = ProductQuery::create()->findPk($event->getProductId())) {
$con = Propel::getWriteConnection(ProductTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$fileList = ['images' => [], 'documentList' => []];
// Get product's files to delete after product deletion
$fileList['images']['list'] = ProductImageQuery::create()
->findByProductId($event->getProductId());
$fileList['images']['type'] = TheliaEvents::IMAGE_DELETE;
$fileList['documentList']['list'] = ProductDocumentQuery::create()
->findByProductId($event->getProductId());
$fileList['documentList']['type'] = TheliaEvents::DOCUMENT_DELETE;
// Delete product
$product
->setDispatcher($event->getDispatcher())
->delete()
->setDispatcher($this->eventDispatcher)
->delete($con)
;
$event->setProduct($product);
// Dispatch delete product's files event
foreach ($fileList as $fileTypeList) {
foreach ($fileTypeList['list'] as $fileToDelete) {
$fileDeleteEvent = new FileDeleteEvent($fileToDelete);
$this->eventDispatcher->dispatch($fileTypeList['type'], $fileDeleteEvent);
}
}
$con->commit();
} catch (\Exception $e) {
$con->rollBack();
throw $e;
}
}
}
/**
* Toggle product visibility. No form used here
*
* @param ActionEvent $event
* @param ProductToggleVisibilityEvent $event
*/
public function toggleVisibility(ProductToggleVisibilityEvent $event)
{
$product = $event->getProduct();
$product
->setDispatcher($event->getDispatcher())
->setDispatcher($this->eventDispatcher)
->setVisible($product->getVisible() ? false : true)
->save()
;
@@ -160,11 +446,19 @@ class Product extends BaseAction implements EventSubscriberInterface
/**
* Changes position, selecting absolute ou relative change.
*
* @param ProductChangePositionEvent $event
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updatePosition(UpdatePositionEvent $event)
public function updatePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$this->genericUpdatePosition(ProductQuery::create(), $event);
$this->genericUpdateDelegatePosition(
ProductCategoryQuery::create()
->filterByProductId($event->getObjectId())
->filterByCategoryId($event->getReferrerId()),
$event,
$dispatcher
);
}
public function addContent(ProductAddContentEvent $event)
@@ -172,11 +466,10 @@ class Product extends BaseAction implements EventSubscriberInterface
if (ProductAssociatedContentQuery::create()
->filterByContentId($event->getContentId())
->filterByProduct($event->getProduct())->count() <= 0) {
$content = new ProductAssociatedContent();
$content
->setDispatcher($event->getDispatcher())
->setDispatcher($this->eventDispatcher)
->setProduct($event->getProduct())
->setContentId($event->getContentId())
->save()
@@ -191,12 +484,13 @@ class Product extends BaseAction implements EventSubscriberInterface
->filterByProduct($event->getProduct())->findOne()
;
if ($content !== null)
if ($content !== null) {
$content
->setDispatcher($event->getDispatcher())
->setDispatcher($this->eventDispatcher)
->delete()
;
}
}
public function addCategory(ProductAddCategoryEvent $event)
{
@@ -204,15 +498,14 @@ class Product extends BaseAction implements EventSubscriberInterface
->filterByProduct($event->getProduct())
->filterByCategoryId($event->getCategoryId())
->count() <= 0) {
$productCategory = new ProductCategory();
$productCategory
$productCategory = (new ProductCategory())
->setProduct($event->getProduct())
->setCategoryId($event->getCategoryId())
->setDefaultCategory(false)
->save()
;
->setDefaultCategory(false);
$productCategory
->setPosition($productCategory->getNextPosition())
->save();
}
}
@@ -223,7 +516,9 @@ class Product extends BaseAction implements EventSubscriberInterface
->filterByCategoryId($event->getCategoryId())
->findOne();
if ($productCategory != null) $productCategory->delete();
if ($productCategory != null) {
$productCategory->delete();
}
}
public function addAccessory(ProductAddAccessoryEvent $event)
@@ -231,11 +526,10 @@ class Product extends BaseAction implements EventSubscriberInterface
if (AccessoryQuery::create()
->filterByAccessory($event->getAccessoryId())
->filterByProductId($event->getProduct()->getId())->count() <= 0) {
$accessory = new Accessory();
$accessory
->setDispatcher($event->getDispatcher())
->setDispatcher($this->eventDispatcher)
->setProductId($event->getProduct()->getId())
->setAccessory($event->getAccessoryId())
->save()
@@ -250,58 +544,105 @@ class Product extends BaseAction implements EventSubscriberInterface
->filterByProductId($event->getProduct()->getId())->findOne()
;
if ($accessory !== null)
if ($accessory !== null) {
$accessory
->setDispatcher($event->getDispatcher())
->setDispatcher($this->eventDispatcher)
->delete()
;
}
}
public function setProductTemplate(ProductSetTemplateEvent $event)
{
$con = Propel::getWriteConnection(ProductTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$product = $event->getProduct();
// Delete all product feature relations
if (null !== $featureProducts = FeatureProductQuery::create()->findByProductId($product->getId())) {
/** @var \Thelia\Model\FeatureProduct $featureProduct */
foreach ($featureProducts as $featureProduct) {
$eventDelete = new FeatureProductDeleteEvent($product->getId(), $featureProduct->getFeatureId());
// Check differences between current coobination and the next one, and clear obsoletes values.
$nextTemplateId = $event->getTemplateId();
$currentTemplateId = $product->getTemplateId();
$event->getDispatcher()->dispatch(TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE, $eventDelete);
}
// 1. Process product features.
$currentFeatures = FeatureTemplateQuery::create()
->filterByTemplateId($currentTemplateId)
->select([ FeatureTemplateTableMap::FEATURE_ID ])
->find($con);
$nextFeatures = FeatureTemplateQuery::create()
->filterByTemplateId($nextTemplateId)
->select([ FeatureTemplateTableMap::FEATURE_ID ])
->find($con);
// Find features values we shoud delete. To do this, we have to
// find all features in $currentFeatures that are not present in $nextFeatures
$featuresToDelete = array_diff($currentFeatures->getData(), $nextFeatures->getData());
// Delete obsolete features values
foreach ($featuresToDelete as $featureId) {
$this->eventDispatcher->dispatch(
TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE,
new FeatureProductDeleteEvent($product->getId(), $featureId)
);
}
// Delete all product attributes sale elements
ProductSaleElementsQuery::create()->filterByProduct($product)->delete($con);
// 2. Process product Attributes
$currentAttributes = AttributeTemplateQuery::create()
->filterByTemplateId($currentTemplateId)
->select([ AttributeTemplateTableMap::ATTRIBUTE_ID ])
->find($con);
$nextAttributes = AttributeTemplateQuery::create()
->filterByTemplateId($nextTemplateId)
->select([ AttributeTemplateTableMap::ATTRIBUTE_ID ])
->find($con);
// Find attributes values we shoud delete. To do this, we have to
// find all attributes in $currentAttributes that are not present in $nextAttributes
$attributesToDelete = array_diff($currentAttributes->getData(), $nextAttributes->getData());
// Find PSE which includes $attributesToDelete for the current product/
$pseToDelete = ProductSaleElementsQuery::create()
->filterByProductId($product->getId())
->useAttributeCombinationQuery()
->filterByAttributeId($attributesToDelete, Criteria::IN)
->endUse()
->select([ ProductSaleElementsTableMap::ID ])
->find();
// Delete obsolete PSEs
foreach ($pseToDelete->getData() as $pseId) {
$this->eventDispatcher->dispatch(
TheliaEvents::PRODUCT_DELETE_PRODUCT_SALE_ELEMENT,
new ProductSaleElementDeleteEvent(
$pseId,
CurrencyModel::getDefaultCurrency()->getId()
)
);
}
// Update the product template
$template_id = $event->getTemplateId();
// Set it to null if it's zero.
if ($template_id <= 0) $template_id = NULL;
if ($template_id <= 0) {
$template_id = null;
}
$product->setTemplateId($template_id)->save($con);
// Create a new default product sale element
$product->createProductSaleElement($con, 0, 0, 0, $event->getCurrencyId(), true);
$product->clearProductSaleElementss();
$event->setProduct($product);
// Store all the stuff !
$con->commit();
} catch (\Exception $ex) {
$con->rollback();
$con->rollBack();
throw $ex;
}
@@ -310,21 +651,27 @@ class Product extends BaseAction implements EventSubscriberInterface
/**
* Changes accessry position, selecting absolute ou relative change.
*
* @param ProductChangePositionEvent $event
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return Object
*/
public function updateAccessoryPosition(UpdatePositionEvent $event)
public function updateAccessoryPosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
return $this->genericUpdatePosition(AccessoryQuery::create(), $event);
return $this->genericUpdatePosition(AccessoryQuery::create(), $event, $dispatcher);
}
/**
* Changes position, selecting absolute ou relative change.
*
* @param ProductChangePositionEvent $event
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @return Object
*/
public function updateContentPosition(UpdatePositionEvent $event)
public function updateContentPosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
return $this->genericUpdatePosition(ProductAssociatedContentQuery::create(), $event);
return $this->genericUpdatePosition(ProductAssociatedContentQuery::create(), $event, $dispatcher);
}
/**
@@ -334,40 +681,98 @@ class Product extends BaseAction implements EventSubscriberInterface
*/
public function updateFeatureProductValue(FeatureProductUpdateEvent $event)
{
// If the feature is not free text, it may have one ore more values.
// If the value exists, we do not change it
// If the value does not exists, we create it.
//
// If the feature is free text, it has only a single value.
// Etiher create or update it.
// Prepare the FeatureAv's ID
$featureAvId = $event->getFeatureValue();
// Search for existing FeatureProduct
$featureProductQuery = FeatureProductQuery::create()
->filterByFeatureId($event->getFeatureId())
->filterByProductId($event->getProductId())
->filterByFeatureId($event->getFeatureId())
;
if ($event->getIsTextValue() !== true) {
$featureProductQuery->filterByFeatureAvId($event->getFeatureValue());
// If it's not a free text value, we can filter by the event's featureValue (which is an ID)
if ($event->getFeatureValue() !== null && $event->getIsTextValue() === false) {
$featureProductQuery->filterByFeatureAvId($featureAvId);
}
$featureProduct = $featureProductQuery->findOne();
if ($featureProduct == null) {
// If the FeatureProduct does not exist, create it
if ($featureProduct === null) {
$featureProduct = new FeatureProduct();
$featureProduct
->setDispatcher($event->getDispatcher())
->setDispatcher($this->eventDispatcher)
->setProductId($event->getProductId())
->setFeatureId($event->getFeatureId())
;
// If it's a free text value, create a FeatureAv to handle i18n
if ($event->getIsTextValue() === true) {
$featureProduct->setFreeTextValue(true);
$createFeatureAvEvent = new FeatureAvCreateEvent();
$createFeatureAvEvent
->setFeatureId($event->getFeatureId())
->setLocale($event->getLocale())
->setTitle($event->getFeatureValue());
$this->eventDispatcher->dispatch(TheliaEvents::FEATURE_AV_CREATE, $createFeatureAvEvent);
$featureAvId = $createFeatureAvEvent->getFeatureAv()->getId();
}
} // Else if the FeatureProduct exists and is a free text value
elseif ($featureProduct !== null && $event->getIsTextValue() === true) {
// Get the FeatureAv
$freeTextFeatureAv = FeatureAvQuery::create()
->filterByFeatureProduct($featureProduct)
->findOneByFeatureId($event->getFeatureId());
// Get the FeatureAvI18n by locale
$freeTextFeatureAvI18n = FeatureAvI18nQuery::create()
->filterById($freeTextFeatureAv->getId())
->findOneByLocale($event->getLocale());
// Nothing found for this lang and the new value is not empty : create FeatureAvI18n
if ($freeTextFeatureAvI18n === null && !empty($featureAvId)) {
$featureAvI18n = new FeatureAvI18n();
$featureAvI18n
->setId($freeTextFeatureAv->getId())
->setLocale($event->getLocale())
->setTitle($event->getFeatureValue())
->save();
$featureAvId = $featureAvI18n->getId();
} // Else if i18n exists but new value is empty : delete FeatureAvI18n
elseif ($freeTextFeatureAvI18n !== null && empty($featureAvId)) {
$freeTextFeatureAvI18n->delete();
// Check if there are still some FeatureAvI18n for this FeatureAv
$freeTextFeatureAvI18ns = FeatureAvI18nQuery::create()
->findById($freeTextFeatureAv->getId());
// If there are no more FeatureAvI18ns for this FeatureAv, remove the corresponding FeatureProduct & FeatureAv
if (count($freeTextFeatureAvI18ns) == 0) {
$deleteFeatureProductEvent = new FeatureProductDeleteEvent($event->getProductId(), $event->getFeatureId());
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE, $deleteFeatureProductEvent);
$deleteFeatureAvEvent = new FeatureAvDeleteEvent($freeTextFeatureAv->getId());
$this->eventDispatcher->dispatch(TheliaEvents::FEATURE_AV_DELETE, $deleteFeatureAvEvent);
return;
}
} // Else if a FeatureAvI18n is found and the new value is not empty : update existing FeatureAvI18n
elseif ($freeTextFeatureAvI18n !== null && !empty($featureAvId)) {
$freeTextFeatureAvI18n->setTitle($featureAvId);
$freeTextFeatureAvI18n->save();
$featureAvId = $freeTextFeatureAvI18n->getId();
}
} // Else the FeatureProduct exists and is not a free text value
else {
$featureAvId = $event->getFeatureValue();
}
if ($event->getIsTextValue() == true) {
$featureProduct->setFreeTextValue($event->getFeatureValue());
} else {
$featureProduct->setFeatureAvId($event->getFeatureValue());
}
$featureProduct->setFeatureAvId($featureAvId);
$featureProduct->save();
@@ -388,6 +793,111 @@ class Product extends BaseAction implements EventSubscriberInterface
;
}
public function deleteImagePSEAssociations(FileDeleteEvent $event)
{
$model = $event->getFileToDelete();
if ($model instanceof ProductImage) {
$model->getProductSaleElementsProductImages()->delete();
}
}
public function deleteDocumentPSEAssociations(FileDeleteEvent $event)
{
$model = $event->getFileToDelete();
if ($model instanceof ProductDocument) {
$model->getProductSaleElementsProductDocuments()->delete();
}
}
/**
* When a feature is removed from a template, the products which are using this feature should be updated.
*
* @param TemplateDeleteFeatureEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function deleteTemplateFeature(TemplateDeleteFeatureEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
// Detete the removed feature in all products which are using this template
$products = ProductQuery::create()
->filterByTemplateId($event->getTemplate()->getId())
->find()
;
foreach ($products as $product) {
$dispatcher->dispatch(
TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE,
new FeatureProductDeleteEvent($product->getId(), $event->getFeatureId())
);
}
}
/**
* When an attribute is removed from a template, the conbinations and PSE of products which are using this template
* should be updated.
*
* @param TemplateDeleteAttributeEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function deleteTemplateAttribute(TemplateDeleteAttributeEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
// Detete the removed attribute in all products which are using this template
$pseToDelete = ProductSaleElementsQuery::create()
->useProductQuery()
->filterByTemplateId($event->getTemplate()->getId())
->endUse()
->useAttributeCombinationQuery()
->filterByAttributeId($event->getAttributeId())
->endUse()
->select([ ProductSaleElementsTableMap::ID ])
->find();
$currencyId = CurrencyModel::getDefaultCurrency()->getId();
foreach ($pseToDelete->getData() as $pseId) {
$dispatcher->dispatch(
TheliaEvents::PRODUCT_DELETE_PRODUCT_SALE_ELEMENT,
new ProductSaleElementDeleteEvent(
$pseId,
$currencyId
)
);
}
}
/**
* Check if is a product view and if product_id is visible
*
* @param ViewCheckEvent $event
* @param string $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function viewCheck(ViewCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event->getView() == 'product') {
$product = ProductQuery::create()
->filterById($event->getViewId())
->filterByVisible(1)
->count();
if ($product == 0) {
$dispatcher->dispatch(TheliaEvents::VIEW_PRODUCT_ID_NOT_VISIBLE, $event);
}
}
}
/**
* @param ViewCheckEvent $event
* @throws NotFoundHttpException
*/
public function viewProductIdNotVisible(ViewCheckEvent $event)
{
throw new NotFoundHttpException();
}
/**
* {@inheritDoc}
*/
@@ -395,6 +905,7 @@ class Product extends BaseAction implements EventSubscriberInterface
{
return array(
TheliaEvents::PRODUCT_CREATE => array("create", 128),
TheliaEvents::PRODUCT_CLONE => array("cloneProduct", 128),
TheliaEvents::PRODUCT_UPDATE => array("update", 128),
TheliaEvents::PRODUCT_DELETE => array("delete", 128),
TheliaEvents::PRODUCT_TOGGLE_VISIBILITY => array("toggleVisibility", 128),
@@ -417,6 +928,16 @@ class Product extends BaseAction implements EventSubscriberInterface
TheliaEvents::PRODUCT_FEATURE_UPDATE_VALUE => array("updateFeatureProductValue", 128),
TheliaEvents::PRODUCT_FEATURE_DELETE_VALUE => array("deleteFeatureProductValue", 128),
TheliaEvents::TEMPLATE_DELETE_ATTRIBUTE => array("deleteTemplateAttribute", 128),
TheliaEvents::TEMPLATE_DELETE_FEATURE => array("deleteTemplateFeature", 128),
// Those two have to be executed before
TheliaEvents::IMAGE_DELETE => array("deleteImagePSEAssociations", 192),
TheliaEvents::DOCUMENT_DELETE => array("deleteDocumentPSEAssociations", 192),
TheliaEvents::VIEW_CHECK => array('viewCheck', 128),
TheliaEvents::VIEW_PRODUCT_ID_NOT_VISIBLE => array('viewProductIdNotVisible', 128),
);
}
}

View File

@@ -12,21 +12,30 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Product\ProductCloneEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\ProductSaleElement\ProductSaleElementCreateEvent;
use Thelia\Core\Template\Loop\ProductSaleElementsDocument;
use Thelia\Core\Template\Loop\ProductSaleElementsImage;
use Thelia\Model\AttributeCombinationQuery;
use Thelia\Model\Map\ProductSaleElementsTableMap;
use Thelia\Model\ProductDocumentQuery;
use Thelia\Model\ProductImageQuery;
use Thelia\Model\ProductSaleElements;
use Thelia\Model\ProductPrice;
use Thelia\Model\AttributeCombination;
use Thelia\Core\Event\ProductSaleElement\ProductSaleElementDeleteEvent;
use Thelia\Model\ProductSaleElementsProductDocument;
use Thelia\Model\ProductSaleElementsProductDocumentQuery;
use Thelia\Model\ProductSaleElementsProductImage;
use Thelia\Model\ProductSaleElementsProductImageQuery;
use Thelia\Model\ProductSaleElementsQuery;
use Thelia\Core\Event\ProductSaleElement\ProductSaleElementUpdateEvent;
use Thelia\Model\ProductPriceQuery;
use Propel\Runtime\Propel;
use Thelia\Model\AttributeAvQuery;
use Thelia\Model\Map\AttributeCombinationTableMap;
use Propel\Runtime\ActiveQuery\Criteria;
use Thelia\Core\Event\Product\ProductCombinationGenerationEvent;
@@ -34,11 +43,19 @@ use Propel\Runtime\Connection\ConnectionInterface;
class ProductSaleElement extends BaseAction implements EventSubscriberInterface
{
/** @var EventDispatcherInterface */
protected $eventDispatcher;
public function __construct(EventDispatcherInterface $eventDispatcher)
{
$this->eventDispatcher = $eventDispatcher;
}
/**
* Create a new product sale element, with or without combination
*
* @param ProductSaleElementCreateEvent $event
* @throws Exception
* @throws \Exception
*/
public function create(ProductSaleElementCreateEvent $event)
{
@@ -56,7 +73,7 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
if ($salesElement == null) {
// Create a new default product sale element
$salesElement = $event->getProduct()->createProductSaleElement($con, 0, 0, 0, $event->getCurrencyId(), true);
$salesElement = $event->getProduct()->createProductSaleElement($con, 0, 0, 0, $event->getCurrencyId(), false);
} else {
// This (new) one is the default
$salesElement->setIsDefault(true)->save($con);
@@ -66,9 +83,7 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
$combinationAttributes = $event->getAttributeAvList();
if (count($combinationAttributes) > 0) {
foreach ($combinationAttributes as $attributeAvId) {
$attributeAv = AttributeAvQuery::create()->findPk($attributeAvId);
if ($attributeAv !== null) {
@@ -88,7 +103,6 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
// Store all the stuff !
$con->commit();
} catch (\Exception $ex) {
$con->rollback();
throw $ex;
@@ -99,6 +113,7 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
* Update an existing product sale element
*
* @param ProductSaleElementUpdateEvent $event
* @throws \Exception
*/
public function update(ProductSaleElementUpdateEvent $event)
{
@@ -109,7 +124,6 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
$con->beginTransaction();
try {
// Update the product's tax rule
$event->getProduct()->setTaxRuleId($event->getTaxRuleId())->save($con);
@@ -120,6 +134,16 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
$salesElement->setProduct($event->getProduct());
}
// If this PSE is the default one, be sure to have *only one* default for this product
if ($event->getIsDefault()) {
ProductSaleElementsQuery::create()
->filterByProduct($event->getProduct())
->filterByIsDefault(true)
->filterById($event->getProductSaleElementId(), Criteria::NOT_EQUAL)
->update(['IsDefault' => false], $con)
;
}
// Update sale element
$salesElement
->setRef($event->getReference())
@@ -140,7 +164,6 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
// If price is not defined, create it.
if ($productPrice == null) {
$productPrice = new ProductPrice();
$productPrice
@@ -171,7 +194,6 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
// Store all the stuff !
$con->commit();
} catch (\Exception $ex) {
$con->rollback();
throw $ex;
@@ -182,11 +204,11 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
* Delete a product sale element
*
* @param ProductSaleElementDeleteEvent $event
* @throws \Exception
*/
public function delete(ProductSaleElementDeleteEvent $event)
{
if (null !== $pse = ProductSaleElementsQuery::create()->findPk($event->getProductSaleElementId())) {
$product = $pse->getProduct();
$con = Propel::getWriteConnection(ProductSaleElementsTableMap::DATABASE_NAME);
@@ -194,28 +216,28 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
$con->beginTransaction();
try {
$pse->delete($con);
if ($product->countSaleElements() <= 0) {
if ($product->countSaleElements($con) <= 0) {
// If we just deleted the last PSE, create a default one
$product->createProductSaleElement($con, 0, 0, 0, $event->getCurrencyId(), true);
} elseif ($pse->getIsDefault()) {
// If we deleted the default PSE, make the last created one the default
$pse = ProductSaleElementsQuery::create()
$newDefaultPse = ProductSaleElementsQuery::create()
->filterByProductId($product->getId())
->filterById($pse->getId(), Criteria::NOT_EQUAL)
->orderByCreatedAt(Criteria::DESC)
->findOne($con)
;
$pse->setIsDefault(true)->save($con);
if (null !== $newDefaultPse) {
$newDefaultPse->setIsDefault(true)->save($con);
}
}
// Store all the stuff !
$con->commit();
} catch (\Exception $ex) {
$con->rollback();
throw $ex;
@@ -227,6 +249,7 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
* Generate combinations. All existing combinations for the product are deleted.
*
* @param ProductCombinationGenerationEvent $event
* @throws \Exception
*/
public function generateCombinations(ProductCombinationGenerationEvent $event)
{
@@ -235,7 +258,6 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
$con->beginTransaction();
try {
// Delete all product's productSaleElement
ProductSaleElementsQuery::create()->filterByProductId($event->product->getId())->delete();
@@ -243,7 +265,6 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
// Create all combinations
foreach ($event->getCombinations() as $combinationAttributesAvIds) {
// Create the PSE
$saleElement = $event->getProduct()->createProductSaleElement(
$con,
@@ -267,7 +288,6 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
// Store all the stuff !
$con->commit();
} catch (\Exception $ex) {
$con->rollback();
throw $ex;
@@ -278,13 +298,12 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
* Create a combination for a given product sale element
*
* @param ConnectionInterface $con the Propel connection
* @param ProductSaleElement $salesElement the product sale element
* @param unknown $combinationAttributes an array oif attributes av IDs
* @param ProductSaleElements $salesElement the product sale element
* @param array $combinationAttributes an array oif attributes av IDs
*/
protected function createCombination(ConnectionInterface $con, ProductSaleElements $salesElement, $combinationAttributes)
{
foreach ($combinationAttributes as $attributeAvId) {
$attributeAv = AttributeAvQuery::create()->findPk($attributeAvId);
if ($attributeAv !== null) {
@@ -299,6 +318,156 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
}
}
/*******************
* CLONING PROCESS *
*******************/
/**
* Clone product's PSEs and associated datas
*
* @param ProductCloneEvent $event
*/
public function clonePSE(ProductCloneEvent $event)
{
$clonedProduct = $event->getClonedProduct();
// Get original product's PSEs
$originalProductPSEs = ProductSaleElementsQuery::create()
->orderByIsDefault(Criteria::DESC)
->findByProductId($event->getOriginalProduct()->getId());
/**
* Handle PSEs
*
* @var int $key
* @var ProductSaleElements $originalProductPSE
*/
foreach ($originalProductPSEs as $key => $originalProductPSE) {
$currencyId = ProductPriceQuery::create()
->filterByProductSaleElementsId($originalProductPSE->getId())
->select('CURRENCY_ID')
->findOne();
// The default PSE, created at the same time as the clone product, is overwritten
$clonedProductPSEId = $this->createClonePSE($event, $originalProductPSE, $currencyId);
$this->updateClonePSE($event, $clonedProductPSEId, $originalProductPSE, $key);
// PSE associated images
$originalProductPSEImages = ProductSaleElementsProductImageQuery::create()
->findByProductSaleElementsId($originalProductPSE->getId());
if (null !== $originalProductPSEImages) {
$this->clonePSEAssociatedFiles($clonedProduct->getId(), $clonedProductPSEId, $originalProductPSEImages, $type = 'image');
}
// PSE associated documents
$originalProductPSEDocuments = ProductSaleElementsProductDocumentQuery::create()
->findByProductSaleElementsId($originalProductPSE->getId());
if (null !== $originalProductPSEDocuments) {
$this->clonePSEAssociatedFiles($clonedProduct->getId(), $clonedProductPSEId, $originalProductPSEDocuments, $type = 'document');
}
}
}
public function createClonePSE(ProductCloneEvent $event, ProductSaleElements $originalProductPSE, $currencyId)
{
$attributeCombinationList = AttributeCombinationQuery::create()
->filterByProductSaleElementsId($originalProductPSE->getId())
->select(['ATTRIBUTE_AV_ID'])
->find();
$clonedProductCreatePSEEvent = new ProductSaleElementCreateEvent($event->getClonedProduct(), $attributeCombinationList, $currencyId);
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_ADD_PRODUCT_SALE_ELEMENT, $clonedProductCreatePSEEvent);
return $clonedProductCreatePSEEvent->getProductSaleElement()->getId();
}
public function updateClonePSE(ProductCloneEvent $event, $clonedProductPSEId, ProductSaleElements $originalProductPSE, $key)
{
$originalProductPSEPrice = ProductPriceQuery::create()
->findOneByProductSaleElementsId($originalProductPSE->getId());
$clonedProductUpdatePSEEvent = new ProductSaleElementUpdateEvent($event->getClonedProduct(), $clonedProductPSEId);
$clonedProductUpdatePSEEvent
->setReference($event->getClonedProduct()->getRef().'-'.($key + 1))
->setIsdefault($originalProductPSE->getIsDefault())
->setFromDefaultCurrency(0)
->setWeight($originalProductPSE->getWeight())
->setQuantity($originalProductPSE->getQuantity())
->setOnsale($originalProductPSE->getPromo())
->setIsnew($originalProductPSE->getNewness())
->setEanCode($originalProductPSE->getEanCode())
->setTaxRuleId($event->getOriginalProduct()->getTaxRuleId())
->setPrice($originalProductPSEPrice->getPrice())
->setSalePrice($originalProductPSEPrice->getPromoPrice())
->setCurrencyId($originalProductPSEPrice->getCurrencyId());
$this->eventDispatcher->dispatch(TheliaEvents::PRODUCT_UPDATE_PRODUCT_SALE_ELEMENT, $clonedProductUpdatePSEEvent);
}
public function clonePSEAssociatedFiles($clonedProductId, $clonedProductPSEId, $originalProductPSEFiles, $type)
{
/** @var ProductSaleElementsDocument|ProductSaleElementsImage $originalProductPSEFile */
foreach ($originalProductPSEFiles as $originalProductPSEFile) {
$originalProductFilePositionQuery = [];
$originalProductPSEFileId = null;
// Get file's original position
switch ($type) {
case 'image':
$originalProductFilePositionQuery = ProductImageQuery::create();
$originalProductPSEFileId = $originalProductPSEFile->getProductImageId();
break;
case 'document':
$originalProductFilePositionQuery = ProductDocumentQuery::create();
$originalProductPSEFileId = $originalProductPSEFile->getProductDocumentId();
break;
}
$originalProductFilePosition = $originalProductFilePositionQuery
->select(['POSITION'])
->findPk($originalProductPSEFileId);
// Get cloned file ID to link to the cloned PSE
switch ($type) {
case 'image':
$clonedProductFileIdToLinkToPSEQuery = ProductImageQuery::create();
break;
case 'document':
$clonedProductFileIdToLinkToPSEQuery = ProductDocumentQuery::create();
break;
}
$clonedProductFileIdToLinkToPSE = $clonedProductFileIdToLinkToPSEQuery
->filterByProductId($clonedProductId)
->filterByPosition($originalProductFilePosition)
->select(['ID'])
->findOne();
// Save association
switch ($type) {
case 'image':
$assoc = new ProductSaleElementsProductImage();
$assoc->setProductImageId($clonedProductFileIdToLinkToPSE);
break;
case 'document':
$assoc = new ProductSaleElementsProductDocument();
$assoc->setProductDocumentId($clonedProductFileIdToLinkToPSE);
break;
}
$assoc
->setProductSaleElementsId($clonedProductPSEId)
->save();
}
}
/***************
* END CLONING *
***************/
/**
* {@inheritDoc}
*/
@@ -309,7 +478,7 @@ class ProductSaleElement extends BaseAction implements EventSubscriberInterface
TheliaEvents::PRODUCT_UPDATE_PRODUCT_SALE_ELEMENT => array("update", 128),
TheliaEvents::PRODUCT_DELETE_PRODUCT_SALE_ELEMENT => array("delete", 128),
TheliaEvents::PRODUCT_COMBINATION_GENERATION => array("generateCombinations", 128),
TheliaEvents::PSE_CLONE => array("clonePSE", 128)
);
}
}

View File

@@ -12,6 +12,7 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Profile\ProfileEvent;
use Thelia\Core\Event\TheliaEvents;
@@ -29,13 +30,15 @@ class Profile extends BaseAction implements EventSubscriberInterface
{
/**
* @param ProfileEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(ProfileEvent $event)
public function create(ProfileEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$profile = new ProfileModel();
$profile
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setCode($event->getCode())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
@@ -51,13 +54,14 @@ class Profile extends BaseAction implements EventSubscriberInterface
/**
* @param ProfileEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(ProfileEvent $event)
public function update(ProfileEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $profile = ProfileQuery::create()->findPk($event->getId())) {
$profile
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setChapo($event->getChapo())
@@ -85,10 +89,9 @@ class Profile extends BaseAction implements EventSubscriberInterface
$profileResource = new ProfileResource();
$profileResource->setProfileId($event->getId())
->setResource(ResourceQuery::create()->findOneByCode($resourceCode))
->setAccess( $manager->getAccessValue() );
->setAccess($manager->getAccessValue());
$profileResource->save();
}
$event->setProfile($profile);
@@ -109,10 +112,9 @@ class Profile extends BaseAction implements EventSubscriberInterface
$profileModule = new ProfileModule();
$profileModule->setProfileId($event->getId())
->setModule(ModuleQuery::create()->findOneByCode($moduleCode))
->setAccess( $manager->getAccessValue() );
->setAccess($manager->getAccessValue());
$profileModule->save();
}
$event->setProfile($profile);
@@ -125,7 +127,6 @@ class Profile extends BaseAction implements EventSubscriberInterface
public function delete(ProfileEvent $event)
{
if (null !== $profile = ProfileQuery::create()->findPk($event->getId())) {
$profile
->delete()
;

View File

@@ -0,0 +1,60 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Thelia\Tools\URL;
use Thelia\Core\Security\Exception\AuthenticationException;
use Thelia\Core\HttpKernel\Exception\RedirectException as ExceptionRedirectException;
/**
* Class RedirectException
* @package Thelia\Action
* @author manuel raynaud <manu@raynaud.io>
*/
class RedirectException extends BaseAction implements EventSubscriberInterface
{
/** @var URL */
protected $urlManager;
public function __construct(URL $urlManager)
{
$this->urlManager = $urlManager;
}
public function checkRedirectException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if ($exception instanceof ExceptionRedirectException) {
$response = RedirectResponse::create($exception->getUrl(), $exception->getStatusCode());
$event->setResponse($response);
} elseif ($exception instanceof AuthenticationException) {
// Redirect to the login template
$response = RedirectResponse::create($this->urlManager->viewUrl($exception->getLoginTemplate()));
$event->setResponse($response);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::EXCEPTION => array("checkRedirectException", 128),
];
}
}

View File

@@ -0,0 +1,505 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\Runtime\Exception\PropelException;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Sale\ProductSaleStatusUpdateEvent;
use Thelia\Core\Event\Sale\SaleActiveStatusCheckEvent;
use Thelia\Core\Event\Sale\SaleClearStatusEvent;
use Thelia\Core\Event\Sale\SaleCreateEvent;
use Thelia\Core\Event\Sale\SaleDeleteEvent;
use Thelia\Core\Event\Sale\SaleToggleActivityEvent;
use Thelia\Core\Event\Sale\SaleUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\Base\ProductPriceQuery;
use Thelia\Model\Country as CountryModel;
use Thelia\Model\Map\SaleTableMap;
use Thelia\Model\ProductSaleElements;
use Thelia\Model\ProductSaleElementsQuery;
use Thelia\Model\Sale as SaleModel;
use Thelia\Model\SaleOffsetCurrency;
use Thelia\Model\SaleOffsetCurrencyQuery;
use Thelia\Model\SaleProduct;
use Thelia\Model\SaleProductQuery;
use Thelia\Model\SaleQuery;
use Thelia\TaxEngine\Calculator;
/**
* Class Sale
*
* @package Thelia\Action
* @author Franck Allimant <franck@cqfdev.fr>
*/
class Sale extends BaseAction implements EventSubscriberInterface
{
/**
* Update PSE for a given product
*
* @param array $pseList an array of priduct sale elements
* @param bool $promoStatus true if the PSEs are on sale, false otherwise
* @param int $offsetType the offset type, see SaleModel::OFFSET_* constants
* @param Calculator $taxCalculator the tax calculator
* @param array $saleOffsetByCurrency an array of price offset for each currency (currency ID => offset_amount)
* @param ConnectionInterface $con
*/
protected function updateProductSaleElementsPrices($pseList, $promoStatus, $offsetType, Calculator $taxCalculator, $saleOffsetByCurrency, ConnectionInterface $con)
{
/** @var ProductSaleElements $pse */
foreach ($pseList as $pse) {
if ($pse->getPromo()!= $promoStatus) {
$pse
->setPromo($promoStatus)
->save($con)
;
}
/** @var SaleOffsetCurrency $offsetByCurrency */
foreach ($saleOffsetByCurrency as $currencyId => $offset) {
$productPrice = ProductPriceQuery::create()
->filterByProductSaleElementsId($pse->getId())
->filterByCurrencyId($currencyId)
->findOne($con);
if (null !== $productPrice) {
// Get the taxed price
$priceWithTax = $taxCalculator->getTaxedPrice($productPrice->getPrice());
// Remove the price offset to get the taxed promo price
switch ($offsetType) {
case SaleModel::OFFSET_TYPE_AMOUNT:
$promoPrice = max(0, $priceWithTax - $offset);
break;
case SaleModel::OFFSET_TYPE_PERCENTAGE:
$promoPrice = $priceWithTax * (1 - $offset / 100);
break;
default:
$promoPrice = $priceWithTax;
}
// and then get the untaxed promo price.
$promoPrice = $taxCalculator->getUntaxedPrice($promoPrice);
$productPrice
->setPromoPrice($promoPrice)
->save($con)
;
}
}
}
}
/**
* Update the promo status of the sale's selected products and combinations
*
* @param ProductSaleStatusUpdateEvent $event
* @throws \RuntimeException
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
public function updateProductsSaleStatus(ProductSaleStatusUpdateEvent $event)
{
$taxCalculator = new Calculator();
$sale = $event->getSale();
// Get all selected product sale elements for this sale
if (null !== $saleProducts = SaleProductQuery::create()->filterBySale($sale)->orderByProductId()) {
$saleOffsetByCurrency = $sale->getPriceOffsets();
$offsetType = $sale->getPriceOffsetType();
$con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
/** @var SaleProduct $saleProduct */
foreach ($saleProducts as $saleProduct) {
// Reset all sale status on product's PSE
ProductSaleElementsQuery::create()
->filterByProductId($saleProduct->getProductId())
->update([ 'Promo' => false], $con)
;
$taxCalculator->load(
$saleProduct->getProduct($con),
CountryModel::getShopLocation()
);
$attributeAvId = $saleProduct->getAttributeAvId();
$pseRequest = ProductSaleElementsQuery::create()
->filterByProductId($saleProduct->getProductId())
;
// If no attribute AV id is defined, consider ALL product combinations
if (! is_null($attributeAvId)) {
// Find PSE attached to combination containing this attribute av :
// SELECT * from product_sale_elements pse
// left join attribute_combination ac on ac.product_sale_elements_id = pse.id
// where pse.product_id=363
// and ac.attribute_av_id = 7
// group by pse.id
$pseRequest
->useAttributeCombinationQuery(null, Criteria::LEFT_JOIN)
->filterByAttributeAvId($attributeAvId)
->endUse()
;
}
$pseList = $pseRequest->find();
if (null !== $pseList) {
$this->updateProductSaleElementsPrices(
$pseList,
$sale->getActive(),
$offsetType,
$taxCalculator,
$saleOffsetByCurrency,
$con
);
}
}
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
}
/**
* Create a new Sale
*
* @param SaleCreateEvent $event
*/
public function create(SaleCreateEvent $event)
{
$sale = new SaleModel();
$sale
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setSaleLabel($event->getSaleLabel())
->save()
;
$event->setSale($sale);
}
/**
* Process update sale
*
* @param SaleUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws PropelException
*/
public function update(SaleUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $sale = SaleQuery::create()->findPk($event->getSaleId())) {
$sale->setDispatcher($dispatcher);
$con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
// Disable all promo flag on sale's currently selected products,
// to reset promo status of the products that have been removed from the selection.
$sale->setActive(false);
$now = new \DateTime();
$startDate = $event->getStartDate();
$endDate = $event->getEndDate();
$update = ($startDate <= $now && $now <= $endDate);
if ($update) {
$dispatcher->dispatch(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
}
$sale
->setActive($event->getActive())
->setStartDate($startDate)
->setEndDate($endDate)
->setPriceOffsetType($event->getPriceOffsetType())
->setDisplayInitialPrice($event->getDisplayInitialPrice())
->setLocale($event->getLocale())
->setSaleLabel($event->getSaleLabel())
->setTitle($event->getTitle())
->setDescription($event->getDescription())
->setChapo($event->getChapo())
->setPostscriptum($event->getPostscriptum())
->save($con)
;
$event->setSale($sale);
// Update price offsets
SaleOffsetCurrencyQuery::create()->filterBySaleId($sale->getId())->delete($con);
foreach ($event->getPriceOffsets() as $currencyId => $priceOffset) {
$saleOffset = new SaleOffsetCurrency();
$saleOffset
->setCurrencyId($currencyId)
->setSaleId($sale->getId())
->setPriceOffsetValue($priceOffset)
->save($con)
;
}
// Update products
SaleProductQuery::create()->filterBySaleId($sale->getId())->delete($con);
$productAttributesArray = $event->getProductAttributes();
foreach ($event->getProducts() as $productId) {
if (isset($productAttributesArray[$productId])) {
foreach ($productAttributesArray[$productId] as $attributeId) {
$saleProduct = new SaleProduct();
$saleProduct
->setSaleId($sale->getId())
->setProductId($productId)
->setAttributeAvId($attributeId)
->save($con)
;
}
} else {
$saleProduct = new SaleProduct();
$saleProduct
->setSaleId($sale->getId())
->setProductId($productId)
->setAttributeAvId(null)
->save($con)
;
}
}
if ($update) {
// Update related products sale status
$dispatcher->dispatch(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
}
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
}
/**
* Toggle Sale activity
*
* @param SaleToggleActivityEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Propel\Runtime\Exception\PropelException
*/
public function toggleActivity(SaleToggleActivityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$sale = $event->getSale();
$con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$sale
->setDispatcher($dispatcher)
->setActive(!$sale->getActive())
->save($con);
// Update related products sale status
$dispatcher->dispatch(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
$event->setSale($sale);
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
/**
* Delete a sale
*
* @param SaleDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Propel\Runtime\Exception\PropelException
*/
public function delete(SaleDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $sale = SaleQuery::create()->findPk($event->getSaleId())) {
$con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
// Update related products sale status, if required
if ($sale->getActive()) {
$sale->setActive(false);
// Update related products sale status
$dispatcher->dispatch(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
}
$sale->setDispatcher($dispatcher)->delete($con);
$event->setSale($sale);
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
}
/**
* Clear all sales
*
* @param SaleClearStatusEvent $event
* @throws \Propel\Runtime\Exception\PropelException
*/
public function clearStatus(/** @noinspection PhpUnusedParameterInspection */ SaleClearStatusEvent $event)
{
$con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
// Set the active status of all Sales to false
SaleQuery::create()
->filterByActive(true)
->update([ 'Active' => false ], $con)
;
// Reset all sale status on PSE
ProductSaleElementsQuery::create()
->filterByPromo(true)
->update([ 'Promo' => false], $con)
;
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
/**
* This method check the activation and deactivation dates of sales, and perform
* the required action depending on the current date.
*
* @param SaleActiveStatusCheckEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Propel\Runtime\Exception\PropelException
*/
public function checkSaleActivation(SaleActiveStatusCheckEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$con = Propel::getWriteConnection(SaleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$now = time();
// Disable expired sales
if (null !== $salesToDisable = SaleQuery::create()
->filterByActive(true)
->filterByEndDate($now, Criteria::LESS_THAN)
->find()) {
/** @var SaleModel $sale */
foreach ($salesToDisable as $sale) {
$sale->setActive(false)->save();
// Update related products sale status
$dispatcher->dispatch(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
}
}
// Enable sales that should be enabled.
if (null !== $salesToEnable = SaleQuery::create()
->filterByActive(false)
->filterByStartDate($now, Criteria::LESS_EQUAL)
->filterByEndDate($now, Criteria::GREATER_EQUAL)
->find()) {
/** @var SaleModel $sale */
foreach ($salesToEnable as $sale) {
$sale->setActive(true)->save();
// Update related products sale status
$dispatcher->dispatch(
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS,
new ProductSaleStatusUpdateEvent($sale)
);
}
}
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
/**
* @inheritdoc
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::SALE_CREATE => array('create', 128),
TheliaEvents::SALE_UPDATE => array('update', 128),
TheliaEvents::SALE_DELETE => array('delete', 128),
TheliaEvents::SALE_TOGGLE_ACTIVITY => array('toggleActivity', 128),
TheliaEvents::SALE_CLEAR_SALE_STATUS => array('clearStatus', 128),
TheliaEvents::UPDATE_PRODUCT_SALE_STATUS => array('updateProductsSaleStatus', 128),
TheliaEvents::CHECK_SALE_ACTIVATION_EVENT => array('checkSaleActivation', 128),
);
}
}

View File

@@ -11,6 +11,7 @@
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\ShippingZone\ShippingZoneAddAreaEvent;
use Thelia\Core\Event\ShippingZone\ShippingZoneRemoveAreaEvent;
@@ -21,11 +22,10 @@ use Thelia\Model\AreaDeliveryModuleQuery;
/**
* Class ShippingZone
* @package Thelia\Action
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class ShippingZone extends BaseAction implements EventSubscriberInterface
{
public function addArea(ShippingZoneAddAreaEvent $event)
{
$areaDelivery = new AreaDeliveryModule();
@@ -51,24 +51,7 @@ class ShippingZone extends BaseAction implements EventSubscriberInterface
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*
* @api
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{

View File

@@ -0,0 +1,103 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\State\StateCreateEvent;
use Thelia\Core\Event\State\StateDeleteEvent;
use Thelia\Core\Event\State\StateToggleVisibilityEvent;
use Thelia\Core\Event\State\StateUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\State as StateModel;
use Thelia\Model\StateQuery;
/**
* Class State
* @package Thelia\Action
* @author Julien Chanséaume <julien@thelia.net>
*/
class State extends BaseAction implements EventSubscriberInterface
{
public function create(StateCreateEvent $event)
{
$state = new StateModel();
$state
->setVisible($event->isVisible())
->setCountryId($event->getCountry())
->setIsocode($event->getIsocode())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->save()
;
$event->setState($state);
}
public function update(StateUpdateEvent $event)
{
if (null !== $state = StateQuery::create()->findPk($event->getStateId())) {
$state
->setVisible($event->isVisible())
->setCountryId($event->getCountry())
->setIsocode($event->getIsocode())
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->save()
;
$event->setState($state);
}
}
public function delete(StateDeleteEvent $event)
{
if (null !== $state = StateQuery::create()->findPk($event->getStateId())) {
$state->delete();
$event->setState($state);
}
}
/**
* Toggle State visibility
*
* @param StateToggleVisibilityEvent $event
*/
public function toggleVisibility(StateToggleVisibilityEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$state = $event->getState();
$state
->setDispatcher($dispatcher)
->setVisible(!$state->getVisible())
->save()
;
$event->setState($state);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::STATE_CREATE => array('create', 128),
TheliaEvents::STATE_UPDATE => array('update', 128),
TheliaEvents::STATE_DELETE => array('delete', 128),
TheliaEvents::STATE_TOGGLE_VISIBILITY => array('toggleVisibility', 128)
);
}
}

View File

@@ -12,6 +12,7 @@
namespace Thelia\Action;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Tax\TaxEvent;
use Thelia\Core\Event\TheliaEvents;
@@ -22,13 +23,15 @@ class Tax extends BaseAction implements EventSubscriberInterface
{
/**
* @param TaxEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(TaxEvent $event)
public function create(TaxEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$tax = new TaxModel();
$tax
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setRequirements($event->getRequirements())
->setType($event->getType())
->setLocale($event->getLocale())
@@ -43,13 +46,14 @@ class Tax extends BaseAction implements EventSubscriberInterface
/**
* @param TaxEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(TaxEvent $event)
public function update(TaxEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $tax = TaxQuery::create()->findPk($event->getId())) {
$tax
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setRequirements($event->getRequirements())
->setType($event->getType())
->setLocale($event->getLocale())
@@ -69,7 +73,6 @@ class Tax extends BaseAction implements EventSubscriberInterface
public function delete(TaxEvent $event)
{
if (null !== $tax = TaxQuery::create()->findPk($event->getId())) {
$tax
->delete()
;

View File

@@ -13,6 +13,7 @@
namespace Thelia\Action;
use Propel\Runtime\ActiveQuery\Criteria;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Tax\TaxRuleEvent;
use Thelia\Core\Event\TheliaEvents;
@@ -26,12 +27,12 @@ class TaxRule extends BaseAction implements EventSubscriberInterface
/**
* @param TaxRuleEvent $event
*/
public function create(TaxRuleEvent $event)
public function create(TaxRuleEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$taxRule = new TaxRuleModel();
$taxRule
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setDescription($event->getDescription())
@@ -39,18 +40,17 @@ class TaxRule extends BaseAction implements EventSubscriberInterface
$taxRule->save();
$event->setTaxRule($taxRule);
$event->setTaxRule($taxRule)->setId($taxRule->getId());
}
/**
* @param TaxRuleEvent $event
*/
public function update(TaxRuleEvent $event)
public function update(TaxRuleEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $taxRule = TaxRuleQuery::create()->findPk($event->getId())) {
$taxRule
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setTitle($event->getTitle())
->setDescription($event->getDescription())
@@ -67,25 +67,34 @@ class TaxRule extends BaseAction implements EventSubscriberInterface
public function updateTaxes(TaxRuleEvent $event)
{
if (null !== $taxRule = TaxRuleQuery::create()->findPk($event->getId())) {
$taxList = $this->getArrayFromJson($event->getTaxList());
$countryList = $this->getArrayFromJson22Compat($event->getCountryList());
$countryDeletedList = $this->getArrayFromJson22Compat($event->getCountryDeletedList());
$taxList = json_decode($event->getTaxList(), true);
/* clean the current tax rule for the countries */
/* clean the current tax rule for the countries/states */
$deletes = array_merge($countryList, $countryDeletedList);
foreach ($deletes as $item) {
TaxRuleCountryQuery::create()
->filterByTaxRule($taxRule)
->filterByCountryId($event->getCountryList(), Criteria::IN)
->filterByCountryId(intval($item[0]), Criteria::EQUAL)
->filterByStateId(intval($item[1]) !== 0 ? $item[1] : null, Criteria::EQUAL)
->delete();
}
/* for each country */
foreach ($event->getCountryList() as $country) {
foreach ($countryList as $item) {
$position = 1;
$countryId = intval($item[0]);
$stateId = intval($item[1]);
/* on applique les nouvelles regles */
foreach ($taxList as $tax) {
if (is_array($tax)) {
foreach ($tax as $samePositionTax) {
$taxModel = new TaxRuleCountry();
$taxModel->setTaxRule($taxRule)
->setCountryId($country)
->setCountryId($countryId)
->setStateId($stateId ?: null)
->setTaxId($samePositionTax)
->setPosition($position);
$taxModel->save();
@@ -93,7 +102,8 @@ class TaxRule extends BaseAction implements EventSubscriberInterface
} else {
$taxModel = new TaxRuleCountry();
$taxModel->setTaxRule($taxRule)
->setCountryId($country)
->setCountryId($countryId)
->setStateId($stateId ?: null)
->setTaxId($tax)
->setPosition($position);
$taxModel->save();
@@ -106,13 +116,55 @@ class TaxRule extends BaseAction implements EventSubscriberInterface
}
}
protected function getArrayFromJson($obj)
{
if (is_null($obj)) {
$obj = [];
} else {
$obj = is_array($obj)
? $obj
: json_decode($obj, true);
}
return $obj;
}
/**
* This method ensures compatibility with the 2.2.x country arrays passed throught the TaxRuleEvent
*
* In 2.2.x, the TaxRuleEvent::getXXXCountryList() methods returned an array of country IDs. [ country ID, country ID ...].
* From 2.3.0-alpha1, these functions are expected to return an array of arrays, each one containing a country ID and
* a state ID. [ [ country ID, state ID], [ country ID, state ID], ...].
*
* This method checks the $obj parameter, and create a 2.3.0-alpha1 compatible return value if $obj is expressed using
* the 2.2.x form.
*
* @param array $obj
*
* @return array
*/
protected function getArrayFromJson22Compat($obj)
{
$obj = $this->getArrayFromJson($obj);
if (isset($obj[0]) && ! is_array($obj[0])) {
$objEx = [];
foreach ($obj as $item) {
$objEx[] = [$item, 0];
}
return $objEx;
}
return $obj;
}
/**
* @param TaxRuleEvent $event
*/
public function delete(TaxRuleEvent $event)
{
if (null !== $taxRule = TaxRuleQuery::create()->findPk($event->getId())) {
$taxRule
->delete()
;
@@ -127,7 +179,6 @@ class TaxRule extends BaseAction implements EventSubscriberInterface
public function setDefault(TaxRuleEvent $event)
{
if (null !== $taxRule = TaxRuleQuery::create()->findPk($event->getId())) {
TaxRuleQuery::create()->update(array(
"IsDefault" => 0
));

View File

@@ -12,26 +12,29 @@
namespace Thelia\Action;
use Propel\Runtime\Propel;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Model\TemplateQuery;
use Thelia\Model\Template as TemplateModel;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Template\TemplateUpdateEvent;
use Thelia\Core\Event\Template\TemplateCreateEvent;
use Thelia\Core\Event\Template\TemplateDeleteEvent;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Model\ProductQuery;
use Thelia\Core\Event\Template\TemplateAddAttributeEvent;
use Thelia\Core\Event\Template\TemplateDeleteAttributeEvent;
use Thelia\Model\AttributeTemplateQuery;
use Thelia\Model\AttributeTemplate;
use Thelia\Core\Event\Template\TemplateDeleteFeatureEvent;
use Thelia\Core\Event\Template\TemplateAddFeatureEvent;
use Thelia\Model\FeatureTemplateQuery;
use Thelia\Core\Event\Template\TemplateCreateEvent;
use Thelia\Core\Event\Template\TemplateDeleteAttributeEvent;
use Thelia\Core\Event\Template\TemplateDeleteEvent;
use Thelia\Core\Event\Template\TemplateDeleteFeatureEvent;
use Thelia\Core\Event\Template\TemplateDuplicateEvent;
use Thelia\Core\Event\Template\TemplateUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Translation\Translator;
use Thelia\Model\AttributeTemplate;
use Thelia\Model\AttributeTemplateQuery;
use Thelia\Model\CategoryQuery;
use Thelia\Model\FeatureTemplate;
use Thelia\Model\FeatureTemplateQuery;
use Thelia\Model\Map\TemplateTableMap;
use Thelia\Model\ProductQuery;
use Thelia\Model\Template as TemplateModel;
use Thelia\Model\TemplateQuery;
class Template extends BaseAction implements EventSubscriberInterface
{
@@ -39,13 +42,15 @@ class Template extends BaseAction implements EventSubscriberInterface
* Create a new template entry
*
* @param \Thelia\Core\Event\Template\TemplateCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function create(TemplateCreateEvent $event)
public function create(TemplateCreateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$template = new TemplateModel();
$template
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setName($event->getTemplateName())
@@ -56,18 +61,65 @@ class Template extends BaseAction implements EventSubscriberInterface
$event->setTemplate($template);
}
/**
* Dupliucate an existing template entry
*
* @param \Thelia\Core\Event\Template\TemplateCreateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function duplicate(TemplateDuplicateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $source = TemplateQuery::create()->findPk($event->getSourceTemplateId())) {
$source->setLocale($event->getLocale());
$createEvent = new TemplateCreateEvent();
$createEvent
->setLocale($event->getLocale())
->setTemplateName(
Translator::getInstance()->trans("Copy of %tpl", ["%tpl" => $source->getName() ])
);
$dispatcher->dispatch(TheliaEvents::TEMPLATE_CREATE, $createEvent);
$clone = $createEvent->getTemplate();
$attrList = AttributeTemplateQuery::create()->findByTemplateId($source->getId());
/** @var $feat AttributeTemplate */
foreach ($attrList as $feat) {
$dispatcher->dispatch(
TheliaEvents::TEMPLATE_ADD_ATTRIBUTE,
new TemplateAddAttributeEvent($clone, $feat->getAttributeId())
);
}
$featList = FeatureTemplateQuery::create()->findByTemplateId($source->getId());
/** @var $feat FeatureTemplate */
foreach ($featList as $feat) {
$dispatcher->dispatch(
TheliaEvents::TEMPLATE_ADD_FEATURE,
new TemplateAddFeatureEvent($clone, $feat->getFeatureId())
);
}
$event->setTemplate($clone);
}
}
/**
* Change a product template
*
* @param \Thelia\Core\Event\Template\TemplateUpdateEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function update(TemplateUpdateEvent $event)
public function update(TemplateUpdateEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== $template = TemplateQuery::create()->findPk($event->getTemplateId())) {
$template
->setDispatcher($event->getDispatcher())
->setDispatcher($dispatcher)
->setLocale($event->getLocale())
->setName($event->getTemplateName())
@@ -81,34 +133,55 @@ class Template extends BaseAction implements EventSubscriberInterface
* Delete a product template entry
*
* @param \Thelia\Core\Event\Template\TemplateDeleteEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
* @throws \Exception
*/
public function delete(TemplateDeleteEvent $event)
public function delete(TemplateDeleteEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
if (null !== ($template = TemplateQuery::create()->findPk($event->getTemplateId()))) {
// Check if template is used by a product
$product_count = ProductQuery::create()->findByTemplateId($template->getId())->count();
$productCount = ProductQuery::create()->findByTemplateId($template->getId())->count();
if ($product_count <= 0) {
if ($productCount <= 0) {
$con = Propel::getWriteConnection(TemplateTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$template
->setDispatcher($event->getDispatcher())
->delete()
;
->setDispatcher($dispatcher)
->delete($con);
// We have to also delete any reference of this template in category tables
// We can't use a FK here, as the DefaultTemplateId column may be NULL
// so let's take care of this.
CategoryQuery::create()
->filterByDefaultTemplateId($event->getTemplateId())
->update([ 'DefaultTemplateId' => null], $con);
$con->commit();
} catch (\Exception $ex) {
$con->rollback();
throw $ex;
}
}
$event->setTemplate($template);
$event->setProductCount($product_count);
$event->setProductCount($productCount);
}
}
public function addAttribute(TemplateAddAttributeEvent $event)
{
if (null === AttributeTemplateQuery::create()->filterByAttributeId($event->getAttributeId())->filterByTemplate($event->getTemplate())->findOne()) {
if (null === AttributeTemplateQuery::create()
->filterByAttributeId($event->getAttributeId())
->filterByTemplate($event->getTemplate())
->findOne()) {
$attributeTemplate = new AttributeTemplate();
$attribute_template = new AttributeTemplate();
$attribute_template
$attributeTemplate
->setAttributeId($event->getAttributeId())
->setTemplate($event->getTemplate())
->save()
@@ -119,40 +192,54 @@ class Template extends BaseAction implements EventSubscriberInterface
/**
* Changes position, selecting absolute ou relative change.
*
* @param CategoryChangePositionEvent $event
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updateAttributePosition(UpdatePositionEvent $event)
public function updateAttributePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
return $this->genericUpdatePosition(AttributeTemplateQuery::create(), $event);
$this->genericUpdatePosition(AttributeTemplateQuery::create(), $event, $dispatcher);
}
/**
* Changes position, selecting absolute ou relative change.
*
* @param CategoryChangePositionEvent $event
* @param UpdatePositionEvent $event
* @param $eventName
* @param EventDispatcherInterface $dispatcher
*/
public function updateFeaturePosition(UpdatePositionEvent $event)
public function updateFeaturePosition(UpdatePositionEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
return $this->genericUpdatePosition(FeatureTemplateQuery::create(), $event);
$this->genericUpdatePosition(FeatureTemplateQuery::create(), $event, $dispatcher);
}
public function deleteAttribute(TemplateDeleteAttributeEvent $event)
public function deleteAttribute(TemplateDeleteAttributeEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$attribute_template = AttributeTemplateQuery::create()
$attributeTemplate = AttributeTemplateQuery::create()
->filterByAttributeId($event->getAttributeId())
->filterByTemplate($event->getTemplate())->findOne()
;
if ($attribute_template !== null) $attribute_template->delete();
if ($attributeTemplate !== null) {
$attributeTemplate
->setDispatcher($dispatcher)
->delete();
} else {
// Prevent event propagation
$event->stopPropagation();
}
}
public function addFeature(TemplateAddFeatureEvent $event)
{
if (null === FeatureTemplateQuery::create()->filterByFeatureId($event->getFeatureId())->filterByTemplate($event->getTemplate())->findOne()) {
if (null === FeatureTemplateQuery::create()
->filterByFeatureId($event->getFeatureId())
->filterByTemplate($event->getTemplate())
->findOne()
) {
$featureTemplate = new FeatureTemplate();
$feature_template = new FeatureTemplate();
$feature_template
$featureTemplate
->setFeatureId($event->getFeatureId())
->setTemplate($event->getTemplate())
->save()
@@ -160,14 +247,21 @@ class Template extends BaseAction implements EventSubscriberInterface
}
}
public function deleteFeature(TemplateDeleteFeatureEvent $event)
public function deleteFeature(TemplateDeleteFeatureEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$feature_template = FeatureTemplateQuery::create()
$featureTemplate = FeatureTemplateQuery::create()
->filterByFeatureId($event->getFeatureId())
->filterByTemplate($event->getTemplate())->findOne()
;
if ($feature_template !== null) $feature_template->delete();
if ($featureTemplate !== null) {
$featureTemplate
->setDispatcher($dispatcher)
->delete();
} else {
// Prevent event propagation
$event->stopPropagation();
}
}
/**
@@ -179,6 +273,7 @@ class Template extends BaseAction implements EventSubscriberInterface
TheliaEvents::TEMPLATE_CREATE => array("create", 128),
TheliaEvents::TEMPLATE_UPDATE => array("update", 128),
TheliaEvents::TEMPLATE_DELETE => array("delete", 128),
TheliaEvents::TEMPLATE_DUPLICATE => array("duplicate", 128),
TheliaEvents::TEMPLATE_ADD_ATTRIBUTE => array("addAttribute", 128),
TheliaEvents::TEMPLATE_DELETE_ATTRIBUTE => array("deleteAttribute", 128),
@@ -188,7 +283,6 @@ class Template extends BaseAction implements EventSubscriberInterface
TheliaEvents::TEMPLATE_CHANGE_ATTRIBUTE_POSITION => array('updateAttributePosition', 128),
TheliaEvents::TEMPLATE_CHANGE_FEATURE_POSITION => array('updateFeaturePosition', 128),
);
}
}

View File

@@ -0,0 +1,372 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Action;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Filesystem\Filesystem;
use Thelia\Core\Event\Cache\CacheEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Translation\TranslationEvent;
use Thelia\Core\Translation\Translator;
use Thelia\Log\Tlog;
/**
* Class Translation
* @package Thelia\Action
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Translation extends BaseAction implements EventSubscriberInterface
{
/** @var ContainerInterface */
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function getTranslatableStrings(TranslationEvent $event)
{
$stringCount = $this->walkDir(
$event->getDirectory(),
$event->getMode(),
$event->getLocale(),
$event->getDomain(),
$strings
);
$event
->setTranslatableStrings($strings)
->setTranslatableStringCount($stringCount)
;
}
/**
* Recursively examine files in a directory tree, and extract translatable strings.
*
* Returns an array of translatable strings, each item having with the following structure:
* 'files' an array of file names in which the string appears,
* 'text' the translatable text
* 'translation' => the text translation, or an empty string if none available.
* 'dollar' => true if the translatable text contains a $
*
* @param string $directory the path to the directory to examine
* @param string $walkMode type of file scanning: WALK_MODE_PHP or WALK_MODE_TEMPLATE
* @param string $currentLocale the current locale
* @param string $domain the translation domain (fontoffice, backoffice, module, etc...)
* @param array $strings the list of strings
* @throws \InvalidArgumentException if $walkMode contains an invalid value
* @return number the total number of translatable texts
*/
protected function walkDir($directory, $walkMode, $currentLocale, $domain, &$strings)
{
$numTexts = 0;
if ($walkMode == TranslationEvent::WALK_MODE_PHP) {
$prefix = '\-\>[\s]*trans[\s]*\([\s]*';
$allowedExts = array('php');
} elseif ($walkMode == TranslationEvent::WALK_MODE_TEMPLATE) {
$prefix = '\{intl(?:.*?)[\s]l=[\s]*';
$allowedExts = array('html', 'tpl', 'xml', 'txt');
} else {
throw new \InvalidArgumentException(
Translator::getInstance()->trans(
'Invalid value for walkMode parameter: %value',
array('%value' => $walkMode)
)
);
}
try {
Tlog::getInstance()->debug("Walking in $directory, in mode $walkMode");
/** @var \DirectoryIterator $fileInfo */
foreach (new \DirectoryIterator($directory) as $fileInfo) {
if ($fileInfo->isDot()) {
continue;
}
if ($fileInfo->isDir()) {
$numTexts += $this->walkDir(
$fileInfo->getPathName(),
$walkMode,
$currentLocale,
$domain,
$strings
);
}
if ($fileInfo->isFile()) {
$ext = $fileInfo->getExtension();
if (in_array($ext, $allowedExts)) {
if ($content = file_get_contents($fileInfo->getPathName())) {
$short_path = $this->normalizePath($fileInfo->getPathName());
Tlog::getInstance()->debug("Examining file $short_path\n");
$matches = array();
if (preg_match_all(
'/'.$prefix.'((?<![\\\\])[\'"])((?:.(?!(?<![\\\\])\1))*.?)*?\1/ms',
$content,
$matches
)) {
Tlog::getInstance()->debug("Strings found: ", $matches[2]);
$idx = 0;
foreach ($matches[2] as $match) {
$hash = md5($match);
if (isset($strings[$hash])) {
if (! in_array($short_path, $strings[$hash]['files'])) {
$strings[$hash]['files'][] = $short_path;
}
} else {
$numTexts++;
// remove \' (or \"), that will prevent the translator to work properly, as
// "abc \def\" ghi" will be passed as abc "def" ghi to the translator.
$quote = $matches[1][$idx];
$match = str_replace("\\$quote", $quote, $match);
// Ignore empty strings
if (strlen($match) == 0) {
continue;
}
$strings[$hash] = array(
'files' => array($short_path),
'text' => $match,
'translation' => Translator::getInstance()->trans(
$match,
[],
$domain,
$currentLocale,
false,
false
),
'custom_fallback' => Translator::getInstance()->trans(
sprintf(
Translator::GLOBAL_FALLBACK_KEY,
$domain,
$match
),
[],
Translator::GLOBAL_FALLBACK_DOMAIN,
$currentLocale,
false,
false
),
'global_fallback' => Translator::getInstance()->trans(
$match,
[],
Translator::GLOBAL_FALLBACK_DOMAIN,
$currentLocale,
false,
false
),
'dollar' => strstr($match, '$') !== false
);
}
$idx++;
}
}
}
}
}
}
} catch (\UnexpectedValueException $ex) {
// Directory does not exists => ignore it.
}
return $numTexts;
}
public function writeTranslationFile(TranslationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$file = $event->getTranslationFilePath();
$fs = new Filesystem();
if (! $fs->exists($file) && true === $event->isCreateFileIfNotExists()) {
$dir = dirname($file);
if (! $fs->exists($file)) {
$fs->mkdir($dir);
$this->cacheClear($dispatcher);
}
}
if ($fp = @fopen($file, 'w')) {
fwrite($fp, '<' . "?php\n\n");
fwrite($fp, "return array(\n");
$texts = $event->getTranslatableStrings();
$translations = $event->getTranslatedStrings();
// Sort keys alphabetically while keeping index
asort($texts);
foreach ($texts as $key => $text) {
// Write only defined (not empty) translations
if (! empty($translations[$key])) {
$text = str_replace("'", "\'", $text);
$translation = str_replace("'", "\'", $translations[$key]);
fwrite($fp, sprintf(" '%s' => '%s',\n", $text, $translation));
}
}
fwrite($fp, ");\n");
@fclose($fp);
} else {
throw new \RuntimeException(
Translator::getInstance()->trans(
'Failed to open translation file %file. Please be sure that this file is writable by your Web server',
array('%file' => $file)
)
);
}
}
public function writeFallbackFile(TranslationEvent $event, $eventName, EventDispatcherInterface $dispatcher)
{
$file = THELIA_LOCAL_DIR . 'I18n' . DS . $event->getLocale() . '.php';
$fs = new Filesystem();
$translations = [];
if (! $fs->exists($file)) {
if (true === $event->isCreateFileIfNotExists()) {
$dir = dirname($file);
$fs->mkdir($dir);
$this->cacheClear($dispatcher);
} else {
throw new \RuntimeException(
Translator::getInstance()->trans(
'Failed to open translation file %file. Please be sure that this file is writable by your Web server',
array('%file' => $file)
)
);
}
} else {
/*$loader = new PhpFileLoader();
$catalogue = $loade r->load($file);
$translations = $catalogue->all();
*/
$translations = require $file;
if (! is_array($translations)) {
$translations = [];
}
}
if ($fp = @fopen($file, 'w')) {
$texts = $event->getTranslatableStrings();
$customs = $event->getCustomFallbackStrings();
$globals = $event->getGlobalFallbackStrings();
// just reset current translations for this domain to remove strings that do not exist anymore
$translations[$event->getDomain()] = [];
foreach ($texts as $key => $text) {
if (!empty($customs[$key])) {
$translations[$event->getDomain()][$text] = $customs[$key];
}
if (!empty($globals[$key])) {
$translations[$text] = $globals[$key];
} else {
unset($translations[$text]);
}
}
fwrite($fp, '<' . "?php\n\n");
fwrite($fp, "return [\n");
// Sort keys alphabetically while keeping index
ksort($translations);
foreach ($translations as $key => $text) {
// Write only defined (not empty) translations
if (!empty($translations[$key])) {
if (is_array($translations[$key])) {
$key = str_replace("'", "\'", $key);
fwrite($fp, sprintf(" '%s' => [\n", $key));
ksort($translations[$key]);
foreach ($translations[$key] as $subKey => $subText) {
$subKey = str_replace("'", "\'", $subKey);
$translation = str_replace("'", "\'", $subText);
fwrite($fp, sprintf(" '%s' => '%s',\n", $subKey, $translation));
}
fwrite($fp, " ],\n");
} else {
$key = str_replace("'", "\'", $key);
$translation = str_replace("'", "\'", $text);
fwrite($fp, sprintf(" '%s' => '%s',\n", $key, $translation));
}
}
}
fwrite($fp, "];\n");
@fclose($fp);
}
}
protected function normalizePath($path)
{
$path = str_replace(
str_replace('\\', '/', THELIA_ROOT),
'',
str_replace('\\', '/', realpath($path))
);
return ltrim($path, '/');
}
protected function cacheClear(EventDispatcherInterface $dispatcher)
{
$cacheEvent = new CacheEvent(
$this->container->getParameter('kernel.cache_dir')
);
$dispatcher->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent);
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
TheliaEvents::TRANSLATION_GET_STRINGS => array('getTranslatableStrings', 128),
TheliaEvents::TRANSLATION_WRITE_FILE => [
['writeTranslationFile', 128],
['writeFallbackFile', 128]
]
);
}
}

View File

@@ -13,11 +13,11 @@
namespace Thelia\Cart;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Thelia\Core\HttpFoundation\Request;
use Thelia\Model\CartQuery;
use Thelia\Model\Cart as CartModel;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Customer;
use Symfony\Component\HttpFoundation\Request;
use Thelia\Core\HttpFoundation\Session\Session;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\Cart\CartEvent;
@@ -27,7 +27,9 @@ use Thelia\Core\Event\Cart\CartEvent;
*
* Trait CartTrait
* @package Thelia\Cart
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*
* @deprecated CartTrait is deprecated, please use Session::getSessionCart method instead
*/
trait CartTrait
{
@@ -37,101 +39,16 @@ trait CartTrait
*
* @param EventDispatcherInterface $dispatcher the event dispatcher
* @param \Symfony\Component\HttpFoundation\Request $request
* @deprecated use Session::getSessionCart method instead
* @return \Thelia\Model\Cart
*/
public function getCart(EventDispatcherInterface $dispatcher, Request $request)
{
$session = $request->getSession();
if (null !== $cart = $session->getCart()) {
return $cart;
}
if ($request->cookies->has("thelia_cart")) {
//le cookie de panier existe, on le récupère
$token = $request->cookies->get("thelia_cart");
$cart = CartQuery::create()->findOneByToken($token);
if ($cart) {
//le panier existe en base
$customer = $session->getCustomerUser();
if ($customer) {
if ($cart->getCustomerId() != $customer->getId()) {
//le customer du panier n'est pas le mm que celui connecté, il faut cloner le panier sans le customer_id
$cart = $this->duplicateCart($dispatcher, $cart, $session, $customer);
}
} else {
if ($cart->getCustomerId() != null) {
//il faut dupliquer le panier sans le customer_id
$cart = $this->duplicateCart($dispatcher, $cart, $session);
}
}
} else {
$cart = $this->createCart($session);
}
} else {
//le cookie de panier n'existe pas, il va falloir le créer et faire un enregistrement en base.
$cart = $this->createCart($session);
}
$session->setCart($cart->getId());
return $cart;
}
/**
* @param \Thelia\Core\HttpFoundation\Session\Session $session
* @return \Thelia\Model\Cart
*/
protected function createCart(Session $session)
{
$cart = new CartModel();
$cart->setToken($this->generateCookie($session));
$cart->setCurrency($session->getCurrency(true));
if (null !== $customer = $session->getCustomerUser()) {
$cart->setCustomer($customer);
}
$cart->save();
$session->setCart($cart->getId());
return $cart;
}
/**
* try to duplicate existing Cart. Customer is here to determine if this cart belong to him.
*
* @param \Thelia\Model\Cart $cart
* @param \Thelia\Core\HttpFoundation\Session\Session $session
* @param \Thelia\Model\Customer $customer
* @return \Thelia\Model\Cart
*/
protected function duplicateCart(EventDispatcherInterface $dispatcher, CartModel $cart, Session $session, Customer $customer = null)
{
$currency = $session->getCurrency();
$newCart = $cart->duplicate($this->generateCookie($session), $customer, $currency, $dispatcher);
$session->setCart($newCart->getId());
$cartEvent = new CartEvent($newCart);
$dispatcher->dispatch(TheliaEvents::CART_DUPLICATE, $cartEvent);
return $cartEvent->getCart();
}
protected function generateCookie(Session $session)
{
$id = null;
if (ConfigQuery::read("cart.session_only", 0) == 0) {
$id = uniqid('', true);
$session->set('cart_use_cookie', $id);
}
return $id;
trigger_error(
'CartTrait is deprecated, please use Session::getSessionCart method instead',
E_USER_DEPRECATED
);
return $request->getSession()->getSessionCart($dispatcher);
}
}

View File

@@ -16,8 +16,12 @@ use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Thelia\Core\Event\Administrator\AdministratorUpdatePasswordEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpFoundation\Request;
use Thelia\Core\HttpFoundation\Session\Session;
use Thelia\Model\AdminQuery;
use Thelia\Tools\Password;
@@ -28,10 +32,21 @@ use Thelia\Tools\Password;
*
* Class AdminUpdatePasswordCommand
* @package Thelia\Command
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class AdminUpdatePasswordCommand extends ContainerAwareCommand
{
protected function init()
{
$container = $this->getContainer();
$request = new Request();
$request->setSession(new Session(new MockArraySessionStorage()));
/** @var RequestStack $requestStack */
$requestStack = $container->get('request_stack');
$requestStack->push($request);
}
/**
* Configures the current command.
@@ -58,6 +73,8 @@ class AdminUpdatePasswordCommand extends ContainerAwareCommand
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->init();
$login = $input->getArgument('login');
if (null === $admin = AdminQuery::create()->filterByLogin($login)->findOne()) {
@@ -69,10 +86,7 @@ class AdminUpdatePasswordCommand extends ContainerAwareCommand
$event = new AdministratorUpdatePasswordEvent($admin);
$event->setPassword($password);
$this->
getContainer()
->get('event_dispatcher')
->dispatch(TheliaEvents::ADMINISTRATOR_UPDATEPASSWORD, $event);
$this->getDispatcher()->dispatch(TheliaEvents::ADMINISTRATOR_UPDATEPASSWORD, $event);
$output->writeln(array(
'',
@@ -80,7 +94,5 @@ class AdminUpdatePasswordCommand extends ContainerAwareCommand
sprintf('<info>new password is : %s</info>', $password),
''
));
}
}

View File

@@ -12,14 +12,12 @@
namespace Thelia\Command;
use Thelia\Model\Module;
/**
* base class for module commands
*
* Class BaseModuleGenerate
* @package Thelia\Command
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
abstract class BaseModuleGenerate extends ContainerAwareCommand
{
@@ -38,14 +36,19 @@ abstract class BaseModuleGenerate extends ContainerAwareCommand
'Controller',
'EventListeners',
'I18n',
Module::ADMIN_INCLUDES_DIRECTORY_NAME,
'templates',
'Hook',
);
protected function verifyExistingModule()
{
if (file_exists($this->moduleDirectory)) {
throw new \RuntimeException(sprintf("%s module already exists", $this->module));
throw new \RuntimeException(
sprintf(
"%s module already exists. Use --force option to force generation.",
$this->module
)
);
}
}
@@ -57,4 +60,13 @@ abstract class BaseModuleGenerate extends ContainerAwareCommand
return ucfirst($name);
}
protected function validModuleName($name)
{
if (!preg_match('#^[A-Z]([A-Za-z\d])+$#', $name)) {
throw new \RuntimeException(
sprintf("%s module name is not a valid name, it must be in CamelCase. (ex: MyModuleName)", $name)
);
}
}
}

View File

@@ -15,18 +15,17 @@ namespace Thelia\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Exception\IOException;
use Thelia\Core\Event\Cache\CacheEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\ConfigQuery;
/**
* clear the cache
*
* Class CacheClear
* @package Thelia\Command
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*
*/
class CacheClear extends ContainerAwareCommand
@@ -46,26 +45,46 @@ class CacheClear extends ContainerAwareCommand
'with-images',
null,
InputOption::VALUE_NONE,
'clear images generated in web/cache directory'
'clear images generated in `image_cache_dir_from_web_root` or web/cache/images directory'
)
->addOption(
'with-documents',
null,
InputOption::VALUE_NONE,
'clear documents generated in `document_cache_dir_from_web_root` or web/cache/documents directory'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$cacheDir = $this->getContainer()->getParameter("kernel.cache_dir");
$this->clearCache($cacheDir, $output);
if (!$input->getOption("without-assets")) {
$this->clearCache(THELIA_WEB_DIR . "assets", $output);
if (!$input->getOption('without-assets')) {
$this->clearCache(THELIA_WEB_DIR . ConfigQuery::read('asset_dir_from_web_root', 'assets'), $output);
}
if ($input->getOption('with-images')) {
$this->clearCache(THELIA_CACHE_DIR, $output);
$this->clearCache(
THELIA_WEB_DIR . ConfigQuery::read(
'image_cache_dir_from_web_root',
'cache' . DS . 'images'
),
$output
);
}
if ($input->getOption('with-documents')) {
$this->clearCache(
THELIA_WEB_DIR . ConfigQuery::read(
'document_cache_dir_from_web_root',
'cache' . DS . 'documents'
),
$output
);
}
}
protected function clearCache($dir, OutputInterface $output)
@@ -74,10 +93,7 @@ class CacheClear extends ContainerAwareCommand
try {
$cacheEvent = new CacheEvent($dir);
$this->
getContainer()
->get('event_dispatcher')
->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent);
$this->getDispatcher()->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent);
} catch (\UnexpectedValueException $e) {
// throws same exception code for does not exist and permission denied ...
if (!file_exists($dir)) {
@@ -92,6 +108,5 @@ class CacheClear extends ContainerAwareCommand
}
$output->writeln(sprintf("<info>%s cache directory cleared successfully</info>", $dir));
}
}

View File

@@ -32,8 +32,6 @@ class ClearImageCache extends ContainerAwareCommand
protected function execute(InputInterface $input, OutputInterface $output)
{
$dispatcher = $this->getContainer()->get('event_dispatcher');
$request = new Request();
try {
@@ -41,9 +39,11 @@ class ClearImageCache extends ContainerAwareCommand
$subdir = $input->getArgument('subdir');
if (! is_null($subdir)) $event->setCacheSubdirectory($subdir);
if (! is_null($subdir)) {
$event->setCacheSubdirectory($subdir);
}
$dispatcher->dispatch(TheliaEvents::IMAGE_CLEAR_CACHE, $event);
$this->getDispatcher()->dispatch(TheliaEvents::IMAGE_CLEAR_CACHE, $event);
$output->writeln(sprintf('%s image cache successfully cleared.', is_null($subdir) ? 'Entire' : ucfirst($subdir)));
} catch (\Exception $ex) {

View File

@@ -0,0 +1,225 @@
<?php
/*******************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Thelia\Model\Config;
use Thelia\Model\ConfigQuery;
/**
* command line for managing configuration variables
*
* php Thelia thelia:config COMMAND [name] [value] [--secured] [--visible]
*
* Where COMMAND is list, get, set or delete.
*
* For command get and delete, you should also set the name attribute.
*
* For command set, you should set the name and value attributes and optionally add
* --secured and/or --visible arguments.
*
* Class ConfigCommand
* @package Thelia\Command
* @author Julien Chanséaume <jchanseaume@openstudio.fr>
*/
class ConfigCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName("thelia:config")
->setDescription("Manage configuration variables")
->addArgument(
'COMMAND',
InputArgument::REQUIRED,
'Command : list, get, set, delete'
)
->addArgument(
'name',
InputArgument::OPTIONAL,
'The variable name'
)
->addArgument(
'value',
InputArgument::OPTIONAL,
'The variable value'
)
->addOption(
'secured',
null,
InputOption::VALUE_NONE,
'When setting a new variable tell variable is secured.'
)
->addOption(
'visible',
null,
InputOption::VALUE_NONE,
'When setting a new variable tell variable is visible.'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$command = $input->getArgument("COMMAND");
switch ($command) {
case "list":
$this->listConfig($input, $output);
break;
case "get":
$this->getConfig($input, $output);
break;
case "set":
$this->setConfig($input, $output);
break;
case "delete":
$this->deleteConfig($input, $output);
break;
default:
$output->writeln(
"<error>Unknown argument 'COMMAND' : list, get, set, delete</error>"
);
}
}
private function listConfig(InputInterface $input, OutputInterface $output)
{
$output->writeln([
"",
"<error>Variables list</error>",
""
]);
$vars = ConfigQuery::create()
->orderByName()
->find()
;
$rows = [];
/** @var Config $var */
foreach ($vars as $var) {
$rows[] = [
$var->getName(),
$var->getValue(),
$var->getSecured() !== 0 ? "yes" : "no",
$var->getHidden() !== 0 ? "yes" : "no"
];
}
$table = new Table($output);
$table
->setHeaders(['Name', 'Value', 'secured', 'hidden'])
->setRows($rows)
;
$table->render();
}
private function getConfig(InputInterface $input, OutputInterface $output)
{
$varName = $input->getArgument("name");
if (null === $varName) {
$output->writeln(
"<error>Need argument 'name' for get command</error>"
);
return;
}
$var = ConfigQuery::create()->findOneByName($varName);
$out = [];
if (null === $var) {
$out[] = sprintf(
"<error>Unknown variable '%s'</error>",
$varName
);
} else {
$out = [
sprintf('%12s: <%3$s>%s</%3$s>', "Name", $var->getName(), "info"),
sprintf('%12s: <%3$s>%s</%3$s>', "Value", $var->getValue(), "info"),
sprintf('%12s: <%3$s>%s</%3$s>', "Secured", $var->getSecured() ? "yes" : "no", "info"),
sprintf('%12s: <%3$s>%s</%3$s>', "Hidden", $var->getHidden() ? "yes" : "no", "info"),
sprintf('%12s: <%3$s>%s</%3$s>', "Title", $var->getTitle(), "info"),
sprintf('%12s: <%3$s>%s</%3$s>', "Description", $var->getDescription(), "info"),
];
}
$output->writeln($out);
}
private function setConfig(InputInterface $input, OutputInterface $output)
{
$varName = $input->getArgument("name");
$varValue = $input->getArgument("value");
if (null === $varName || null === $varValue) {
$output->writeln(
"<error>Need argument 'name' and 'value' for set command</error>"
);
return;
}
ConfigQuery::write(
$varName,
$varValue,
$input->getOption("secured"),
!$input->getOption("visible")
);
$output->writeln("<info>Variable has been set</info>");
}
private function deleteConfig(InputInterface $input, OutputInterface $output)
{
$varName = $input->getArgument("name");
if (null === $varName) {
$output->writeln(
"<error>Need argument 'name' for get command</error>"
);
return;
}
$var = ConfigQuery::create()->findOneByName($varName);
if (null === $var) {
$output->writeln(
sprintf(
"<error>Unknown variable '%s'</error>",
$varName
)
);
} else {
$var->delete();
$output->writeln(
sprintf(
"<info>Variable '%s' has been deleted</info>",
$varName
)
);
}
}
}

View File

@@ -15,11 +15,23 @@ namespace Thelia\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
use Symfony\Component\Routing\RequestContext;
use Thelia\Core\Application;
use Thelia\Core\HttpFoundation\Request;
use Thelia\Core\HttpFoundation\Session\Session;
use Thelia\Core\Translation\Translator;
use Thelia\Model\ConfigQuery;
use Thelia\Model\Lang;
use Thelia\Model\LangQuery;
use Thelia\Tools\URL;
/**
* Command.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Gilles Bourgeat <gbourgeat@openstudio.fr>
*/
class ContainerAwareCommand extends Command implements ContainerAwareInterface
{
@@ -34,7 +46,9 @@ class ContainerAwareCommand extends Command implements ContainerAwareInterface
protected function getContainer()
{
if (null === $this->container) {
$this->container = $this->getApplication()->getKernel()->getContainer();
/** @var Application $application */
$application = $this->getApplication();
$this->container = $application->getKernel()->getContainer();
}
return $this->container;
@@ -42,9 +56,78 @@ class ContainerAwareCommand extends Command implements ContainerAwareInterface
/**
* @see ContainerAwareInterface::setContainer()
* @param ContainerInterface $container
*/
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
/**
* @return \Symfony\Component\EventDispatcher\EventDispatcher
*/
public function getDispatcher()
{
$container = $this->getContainer();
// Initialize Thelia translator, if not already done.
try {
Translator::getInstance();
} catch (\Exception $ex) {
$this->container->get('thelia.translator');
}
return $container->get('event_dispatcher');
}
/**
* For init an Request, if your command has need an Request
* @param Lang|null $lang
* @since 2.3
*/
protected function initRequest(Lang $lang = null)
{
$container = $this->getContainer();
$request = Request::create($this->getBaseUrl($lang));
$request->setSession(new Session(new MockArraySessionStorage()));
$container->set("request_stack", new RequestStack());
$container->get('request_stack')->push($request);
$requestContext = new RequestContext();
$requestContext->fromRequest($request);
$url = $container->get('thelia.url.manager');
$url->setRequestContext($requestContext);
$this->getContainer()->get('router.admin')->setContext($requestContext);
}
/**
* @param Lang|null $lang
* @return string
* @since 2.3
*/
protected function getBaseUrl(Lang $lang = null)
{
$baseUrl = '';
if ((int) ConfigQuery::read('one_domain_foreach_lang') === 1) {
if ($lang === null) {
$lang = LangQuery::create()->findOneByByDefault(true);
}
$baseUrl = $lang->getUrl();
}
$baseUrl = trim($baseUrl);
if (empty($baseUrl)) {
$baseUrl = ConfigQuery::read('url_site');
}
if (empty($baseUrl)) {
$baseUrl = 'http://localhost';
}
return $baseUrl;
}
}

View File

@@ -12,11 +12,13 @@
namespace Thelia\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Thelia\Model\Admin;
use Thelia\Model\AdminQuery;
class CreateAdminUser extends ContainerAwareCommand
{
@@ -50,6 +52,13 @@ class CreateAdminUser extends ContainerAwareCommand
'User last name',
null
)
->addOption(
"email",
null,
InputOption::VALUE_OPTIONAL,
'Admin email address',
null
)
->addOption(
"locale",
null,
@@ -65,13 +74,13 @@ class CreateAdminUser extends ContainerAwareCommand
null
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln('Please enter the admin user information:');
/** @var Admin $admin */
$admin = $this->getAdminInfo($input, $output);
$admin->save();
@@ -83,22 +92,30 @@ class CreateAdminUser extends ContainerAwareCommand
));
}
protected function enterData($dialog, $output, $label, $error_message, $hidden = false)
{
$command = $hidden ? 'askHiddenResponse' : 'askAndValidate';
protected function enterData(
QuestionHelper $helper,
InputInterface $input,
OutputInterface $output,
$label,
$errorMessage,
$hidden = false
) {
$question = new Question($this->decorateInfo($label));
return $dialog->$command(
$output,
$this->decorateInfo($label),
function ($answer) {
$answer = trim($answer);
if (empty($answer)) {
throw new \RuntimeException("This information is mandatory.");
if ($hidden) {
$question->setHidden(true);
$question->setHiddenFallback(false);
}
return $answer;
$question->setValidator(function ($value) use (&$errorMessage) {
if (trim($value) == '') {
throw new \Exception($errorMessage);
}
);
return $value;
});
return $helper->ask($input, $output, $question);
}
/**
@@ -110,21 +127,23 @@ class CreateAdminUser extends ContainerAwareCommand
*/
protected function getAdminInfo(InputInterface $input, OutputInterface $output)
{
$dialog = $this->getHelperSet()->get('dialog');
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$admin = new Admin();
$admin->setLogin($input->getOption("login_name") ?: $this->enterData($dialog, $output, "Admin login name : ", "Please enter a login name."));
$admin->setFirstname($input->getOption("first_name") ?: $this->enterData($dialog, $output, "User first name : ", "Please enter user first name."));
$admin->setLastname($input->getOption("last_name") ?: $this->enterData($dialog, $output, "User last name : ", "Please enter user last name."));
$admin->setLogin($input->getOption("login_name") ?: $this->enterLogin($helper, $input, $output));
$admin->setFirstname($input->getOption("first_name") ?: $this->enterData($helper, $input, $output, "User first name : ", "Please enter user first name."));
$admin->setLastname($input->getOption("last_name") ?: $this->enterData($helper, $input, $output, "User last name : ", "Please enter user last name."));
$admin->setLocale($input->getOption("locale") ?: 'en_US');
$admin->setEmail($input->getOption("email") ?: $this->enterEmail($helper, $input, $output));
do {
$password = $input->getOption("password") ?: $this->enterData($dialog, $output, "Password : ", "Please enter a password.", true);
$password_again = $input->getOption("password") ?: $this->enterData($dialog, $output, "Password (again): ", "Please enter the password again.", true);
$password = $input->getOption("password") ?: $this->enterData($helper, $input, $output, "Password : ", "Please enter a password.", true);
$password_again = $input->getOption("password") ?: $this->enterData($helper, $input, $output, "Password (again): ", "Please enter the password again.", true);
if (! empty($password) && $password == $password_again) {
$admin->setPassword($password);
break;
@@ -143,4 +162,44 @@ class CreateAdminUser extends ContainerAwareCommand
return sprintf("<info>%s</info>", $text);
}
protected function enterLogin(QuestionHelper $helper, InputInterface $input, OutputInterface $output)
{
$question = new Question($this->decorateInfo("Admin login name : "));
$question->setValidator(function ($answer) {
$answer = trim($answer);
if (empty($answer)) {
throw new \RuntimeException("Please enter a login name.");
}
if (AdminQuery::create()->findOneByLogin($answer)) {
throw new \RuntimeException("An administrator with this login already exists.");
}
return $answer;
});
return $helper->ask($input, $output, $question);
}
protected function enterEmail(QuestionHelper $helper, InputInterface $input, OutputInterface $output)
{
$question = new Question($this->decorateInfo("Admin email or empty value : "));
$question->setValidator(function ($answer) {
$answer = trim($answer);
if (!empty($answer) && !filter_var($answer, FILTER_VALIDATE_EMAIL)) {
throw new \RuntimeException("Please enter an email or an empty value.");
}
if (AdminQuery::create()->findOneByEmail($answer)) {
throw new \RuntimeException("An administrator with this email already exists.");
}
return !empty($answer) ? $answer : uniqid('CHANGE_ME_');
});
return $helper->ask($input, $output, $question);
}
}

View File

@@ -0,0 +1,241 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Thelia\Core\Archiver\ArchiverInterface;
use Thelia\Core\Archiver\ArchiverManager;
use Thelia\Core\DependencyInjection\Compiler\RegisterArchiverPass;
use Thelia\Core\DependencyInjection\Compiler\RegisterSerializerPass;
use Thelia\Core\Serializer\SerializerInterface;
use Thelia\Core\Serializer\SerializerManager;
use Thelia\Model\ExportQuery;
use Thelia\Model\LangQuery;
/**
* Class ExportCommand
* @author Jérôme Billiras <jbilliras@openstudio.fr>
*/
class ExportCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('export')
->setDescription('Export data')
->setHelp('The <info>export</info> command run selected export')
->addArgument(
'ref',
InputArgument::OPTIONAL,
'Export reference.'
)
->addArgument(
'serializer',
InputArgument::OPTIONAL,
'Serializer identifier.'
)
->addArgument(
'archiver',
InputArgument::OPTIONAL,
'Archiver identifier.'
)
->addOption(
'locale',
null,
InputOption::VALUE_REQUIRED,
'Locale for export',
'en_US'
)
->addOption(
'list-export',
null,
InputOption::VALUE_NONE,
'List available exports and exit.'
)
->addOption(
'list-serializer',
null,
InputOption::VALUE_NONE,
'List available serializers and exit.'
)
->addOption(
'list-archiver',
null,
InputOption::VALUE_NONE,
'List available archivers and exit.'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('list-export')) {
$this->listExport($output);
return;
}
if ($input->getOption('list-serializer')) {
$this->listSerializer($output);
return;
}
if ($input->getOption('list-archiver')) {
$this->listArchiver($output);
return;
}
$exportRef = $input->getArgument('ref');
$serializer = $input->getArgument('serializer');
if ($exportRef === null || $serializer === null) {
throw new \RuntimeException(
'Not enough arguments.' . PHP_EOL . 'If no options are provided, ref and serializer arguments are required.'
);
}
/** @var \Thelia\Handler\ExportHandler $exportHandler */
$exportHandler = $this->getContainer()->get('thelia.export.handler');
$export = $exportHandler->getExportByRef($exportRef);
if ($export === null) {
throw new \RuntimeException(
$exportRef . ' export doesn\'t exist.'
);
}
$serializerManager = $this->getContainer()->get(RegisterSerializerPass::MANAGER_SERVICE_ID);
$serializer = $serializerManager->get($serializer);
$archiver = null;
if ($input->getArgument('archiver')) {
/** @var \Thelia\Core\Archiver\ArchiverManager $archiverManager */
$archiverManager = $this->getContainer()->get(RegisterArchiverPass::MANAGER_SERVICE_ID);
$archiver = $archiverManager->get($input->getArgument('archiver'));
}
$exportEvent = $exportHandler->export(
$export,
$serializer,
$archiver,
(new LangQuery)->findOneByLocale($input->getOption('locale'))
);
$formattedLine = $this->getHelper('formatter')->formatBlock(
'Export finish',
'fg=black;bg=green',
true
);
$output->writeln($formattedLine);
$output->writeln('<info>Export available at path:</info>');
$output->writeln('<comment>' . $exportEvent->getFilePath() . '</comment>');
}
/**
* Output available exports
*
* @param \Symfony\Component\Console\Output\OutputInterface $output An output interface
*/
protected function listExport(OutputInterface $output)
{
$table = new Table($output);
foreach ((new ExportQuery)->find() as $export) {
$table->addRow([
$export->getRef(),
$export->getTitle(),
$export->getDescription()
]);
}
$table
->setHeaders([
'Reference',
'Title',
'Description'
])
->render()
;
}
/**
* Output available serializers
*
* @param \Symfony\Component\Console\Output\OutputInterface $output An output interface
*/
protected function listSerializer(OutputInterface $output)
{
$table = new Table($output);
/** @var SerializerManager $serializerManager */
$serializerManager = $this->getContainer()->get(RegisterSerializerPass::MANAGER_SERVICE_ID);
/** @var SerializerInterface $serializer */
foreach ($serializerManager->getSerializers() as $serializer) {
$table->addRow([
$serializer->getId(),
$serializer->getName(),
$serializer->getExtension(),
$serializer->getMimeType()
]);
}
$table
->setHeaders([
'Id',
'Name',
'Extension',
'MIME type'
])
->render()
;
}
/**
* Output available archivers
*
* @param \Symfony\Component\Console\Output\OutputInterface $output An output interface
*/
protected function listArchiver(OutputInterface $output)
{
$table = new Table($output);
/** @var ArchiverManager $archiverManager */
$archiverManager = $this->getContainer()->get(RegisterArchiverPass::MANAGER_SERVICE_ID);
/** @var ArchiverInterface $archiver */
foreach ($archiverManager->getArchivers(true) as $archiver) {
$table->addRow([
$archiver->getId(),
$archiver->getName(),
$archiver->getExtension(),
$archiver->getMimeType()
]);
}
$table
->setHeaders([
'Id',
'Name',
'Extension',
'MIME type'
])
->render()
;
}
}

View File

@@ -15,9 +15,7 @@ namespace Thelia\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Thelia\Core\Security\Resource\AdminResources;
use Thelia\Model\Map\ResourceI18nTableMap;
use Thelia\Model\Map\ResourceTableMap;
@@ -40,7 +38,6 @@ class GenerateResources extends ContainerAwareCommand
null
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
@@ -65,7 +62,7 @@ class GenerateResources extends ContainerAwareCommand
}
$compteur++;
$output->writeln(
"($compteur, '$value', NOW(), NOW())" . ($constant === key( array_slice( $constants, -1, 1, true ) ) ? ';' : ',')
"($compteur, '$value', NOW(), NOW())" . ($constant === key(array_slice($constants, -1, 1, true)) ? ';' : ',')
);
}
break;
@@ -81,17 +78,17 @@ class GenerateResources extends ContainerAwareCommand
$compteur++;
$title = ucwords( str_replace('.', ' / ', str_replace('admin.', '', $value) ) );
$title = ucwords(str_replace('.', ' / ', str_replace('admin.', '', $value)));
$output->writeln(
"($compteur, 'en_US', '$title'),"
);
$output->writeln(
"($compteur, 'fr_FR', '$title')" . ($constant === key( array_slice( $constants, -1, 1, true ) ) ? ';' : ',')
"($compteur, 'fr_FR', '$title')" . ($constant === key(array_slice($constants, -1, 1, true)) ? ';' : ',')
);
}
break;
default :
default:
foreach ($constants as $constant => $value) {
if ($constant == AdminResources::SUPERADMINISTRATOR) {
continue;
@@ -101,5 +98,4 @@ class GenerateResources extends ContainerAwareCommand
break;
}
}
}

View File

@@ -0,0 +1,221 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Command;
use Imagine\Exception\RuntimeException;
use Propel\Runtime\Propel;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Thelia\Core\Translation\Translator;
use Thelia\Model\CustomerQuery;
use Thelia\Model\Map\ProductTableMap;
use Thelia\Tools\URL;
use Thelia\Tools\Version\Version;
use TheliaSmarty\Template\SmartyParser;
/**
* Class GenerateSQLCommand
* @package Thelia\Command
* @author Julien Chanséaume <jchanseaume@openstudio.fr>
*/
class GenerateSQLCommand extends ContainerAwareCommand
{
/** @var Translator $translator */
protected $translator = null;
/** @var SmartyParser $parser */
protected $parser = null;
/** @var \PDO */
protected $con;
/** @var array */
protected $locales;
protected function configure()
{
$this
->setName("generate:sql")
->setDescription("Generate SQL files (insert.sql, update*.sql)")
->addOption(
"locales",
null,
InputOption::VALUE_OPTIONAL,
"generate only for only specific locales (separated by a ,) : fr_FR,es_ES or es_ES"
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->init($input);
// Main insert.sql file
$content = file_get_contents(THELIA_SETUP_DIRECTORY . 'insert.sql.tpl');
$version = Version::parse();
$content = $this->parser->renderString($content, $version, false);
if (false === file_put_contents(THELIA_SETUP_DIRECTORY . 'insert.sql', $content)) {
$output->writeln("Can't write file " . THELIA_SETUP_DIRECTORY . 'insert.sql');
} else {
$output->writeln("File " . THELIA_SETUP_DIRECTORY . 'insert.sql generated successfully.');
}
// sql update files
$finder = Finder::create()
->name('*.tpl')
->depth(0)
->in(THELIA_SETUP_DIRECTORY . 'update' . DS . 'tpl');
/** @var \SplFileInfo $file */
foreach ($finder as $file) {
$content = file_get_contents($file->getRealPath());
$content = $this->parser->renderString($content, [], false);
$destination = THELIA_SETUP_DIRECTORY . 'update' . DS . 'sql' . DS . $file->getBasename('.tpl');
if (false === file_put_contents($destination, $content)) {
$output->writeln("Can't write file " . $destination);
} else {
$output->writeln("File " . $destination . ' generated successfully.');
}
}
}
protected function init(InputInterface $input)
{
$this->initRequest();
$container = $this->getContainer();
$this->translator = $container->get('thelia.translator');
$this->parser = $container->get('thelia.parser');
$this->con = Propel::getConnection(ProductTableMap::DATABASE_NAME);
$this->initLocales($input);
$this->initParser();
}
/**
* @param InputInterface $input
* @return array
*/
protected function initLocales(InputInterface $input)
{
$this->locales = [];
$availableLocales = [];
$finder = Finder::create()
->name('*.php')
->depth(0)
->sortByName()
->in(THELIA_SETUP_DIRECTORY . 'I18n');
// limit to only some locale(s)
$localesToKeep = $input->getOption("locales");
if (!empty($localesToKeep)) {
$localesToKeep = explode(',', $localesToKeep);
} else {
$localesToKeep = null;
}
/** @var \SplFileInfo $file */
foreach ($finder as $file) {
$locale = $file->getBasename('.php');
$availableLocales[] = $locale;
if (empty($localesToKeep) || in_array($locale, $localesToKeep)) {
$this->locales[] = $locale;
$this->translator->addResource(
'php',
$file->getRealPath(),
$locale,
'install'
);
}
}
if (empty($this->locales)) {
throw new \RuntimeException(
sprintf(
"You should at least generate sql for one locale. Available locales : %s",
implode(', ', $availableLocales)
)
);
}
}
/**
* Initialize the smarty parser.
*
* The intl function is replaced, and locales are assigned.
*
* @throws \SmartyException
*/
protected function initParser()
{
$this->parser->unregisterPlugin('function', 'intl');
$this->parser->registerPlugin('function', 'intl', [$this, 'translate']);
$this->parser->assign("locales", $this->locales);
}
/**
* Smarty function that replace the classic `intl` function.
*
* The attributes of the function are:
* - `l`: the key
* - `locale`: the locale. eg.: fr_FR
* - `in_string`: set to 1 not add simple quote around the string. (default = 0)
* - `use_default`: set to 1 to use the `l` string as a fallback. (default = 0)
*
* @param $params
* @param $smarty
* @return string
*/
public function translate($params, $smarty)
{
$translation = '';
if (empty($params["l"])) {
throw new RuntimeException('Translation Error. Key is empty.');
} elseif (empty($params["locale"])) {
throw new RuntimeException('Translation Error. Locale is empty.');
} else {
$inString = (0 !== intval($params["in_string"]));
$useDefault = (0 !== intval($params["use_default"]));
$translation = $this->translator->trans(
$params["l"],
[],
'install',
$params["locale"],
$useDefault
);
if (empty($translation)) {
$translation = ($inString) ? '' : "NULL";
} else {
$translation = $this->con->quote($translation);
// remove quote
if ($inString) {
$translation = substr($translation, 1, -1);
}
}
}
return $translation;
}
}

View File

@@ -0,0 +1,157 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Thelia\Core\Event\Cache\CacheEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\IgnoredModuleHookQuery;
use Thelia\Model\Module;
use Thelia\Model\ModuleHookQuery;
use Thelia\Model\ModuleQuery;
/**
* Clean hook
*
* Class HookCleanCommand
* @package Thelia\Command
*
* @author Julien Chanséaume <julien@thelia.net>
*
*/
class HookCleanCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName("hook:clean")
->setDescription("Clean hooks. It will delete all hooks, then recreate it.")
->addOption(
"assume-yes",
'y',
InputOption::VALUE_NONE,
'Assume to answer yes to all questions'
)
->addArgument(
"module",
InputArgument::OPTIONAL,
"The module code to clean up"
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
try {
$module = $this->getModule($input);
if (!$this->askConfirmation($input, $output)) {
return;
}
$this->deleteHooks($module);
$output->writeln("<info>Hooks have been successfully deleted</info>");
$this->clearCache($output);
} catch (\Exception $ex) {
$output->writeln(sprintf("<error>%s</error>", $ex->getMessage()));
}
}
private function getModule(InputInterface $input)
{
$module = null;
$moduleCode = $input->getArgument("module");
if (!empty($moduleCode)) {
if (null === $module = ModuleQuery::create()->findOneByCode($moduleCode)) {
throw new \RuntimeException(sprintf("Module %s does not exist.", $moduleCode));
}
}
return $module;
}
private function askConfirmation(InputInterface $input, OutputInterface $output)
{
$assumeYes = $input->getOption("assume-yes");
$moduleCode = $input->getArgument("module");
if (!$assumeYes) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$questionText = "Would you like to delete all hooks ";
$questionText .= (empty($moduleCode))
? "of all modules"
: "of module " . $moduleCode;
$questionText .= " ? (yes, or no) ";
$question = new ConfirmationQuestion($questionText, false);
if (!$helper->ask($input, $output, $question)) {
$output->writeln("<info>No hooks deleted</info>");
return false;
}
}
return true;
}
/**
* Delete module hooks
*
* @param Module|null $module if specified it will only delete hooks related to this module.
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
protected function deleteHooks($module)
{
$query = ModuleHookQuery::create();
if (null !== $module) {
$query
->filterByModule($module)
->delete();
} else {
$query->deleteAll();
}
$query = IgnoredModuleHookQuery::create();
if (null !== $module) {
$query
->filterByModule($module)
->delete();
} else {
$query->deleteAll();
}
}
/**
* @param OutputInterface $output
* @throws \Exception
*/
protected function clearCache(OutputInterface $output)
{
try {
$cacheDir = $this->getContainer()->getParameter("kernel.cache_dir");
$cacheEvent = new CacheEvent($cacheDir);
$this->getDispatcher()->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent);
} catch (\Exception $ex) {
throw new \Exception(sprintf("Error during clearing of cache : %s", $ex->getMessage()));
}
}
}

View File

@@ -0,0 +1,141 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\HttpFoundation\File\File;
use Thelia\Model\ImportQuery;
use Thelia\Model\LangQuery;
/**
* Class ImportCommand
* @author Jérôme Billiras <jbilliras@openstudio.fr>
*/
class ImportCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('import')
->setDescription('Import data')
->setHelp('The <info>import</info> command run selected import')
->addArgument(
'ref',
InputArgument::OPTIONAL,
'Import reference.'
)
->addArgument(
'filePath',
InputArgument::OPTIONAL,
'File path to import'
)
->addOption(
'locale',
null,
InputOption::VALUE_REQUIRED,
'Locale for export',
'en_US'
)
->addOption(
'list',
null,
InputOption::VALUE_NONE,
'List available imports and exit.'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if ($input->getOption('list')) {
$this->listImport($output);
return;
}
$importRef = $input->getArgument('ref');
$path = $input->getArgument('filePath');
if ($importRef === null || $path === null) {
throw new \RuntimeException(
'Not enough arguments.' . PHP_EOL . 'If no options are provided, ref and filePath arguments are required.'
);
}
/** @var \Thelia\Handler\ImportHandler $importHandler */
$importHandler = $this->getContainer()->get('thelia.import.handler');
$import = $importHandler->getImportByRef($importRef);
if ($import === null) {
throw new \RuntimeException(
$importRef . ' import doesn\'t exist.'
);
}
$importEvent = $importHandler->import(
$import,
new File($input->getArgument('filePath')),
(new LangQuery)->findOneByLocale($input->getOption('locale'))
);
$formattedLine = $this->getHelper('formatter')->formatBlock(
'Successfully import ' . $importEvent->getImport()->getImportedRows() . ' row(s)',
'fg=black;bg=green',
true
);
$output->writeln($formattedLine);
if (count($importEvent->getErrors()) > 0) {
$formattedLine = $this->getHelper('formatter')->formatBlock(
'With error',
'fg=black;bg=yellow',
true
);
$output->writeln($formattedLine);
foreach ($importEvent->getErrors() as $error) {
$output->writeln('<comment>' . $error . '</comment>');
}
}
}
/**
* Output available imports
*
* @param \Symfony\Component\Console\Output\OutputInterface $output An output interface
*/
protected function listImport(OutputInterface $output)
{
$table = new Table($output);
foreach ((new ImportQuery)->find() as $import) {
$table->addRow([
$import->getRef(),
$import->getTitle(),
$import->getDescription()
]);
}
$table
->setHeaders([
'Reference',
'Title',
'Description'
])
->render()
;
}
}

View File

@@ -12,20 +12,23 @@
namespace Thelia\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Filesystem\Filesystem;
use Thelia\Core\Translation\Translator;
use Thelia\Install\CheckPermission;
use Thelia\Install\Database;
use Thelia\Tools\TokenProvider;
/**
* try to install a new instance of Thelia
*
* Class Install
* @package Thelia\Command
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class Install extends ContainerAwareCommand
{
@@ -42,7 +45,8 @@ class Install extends ContainerAwareCommand
"db_host",
null,
InputOption::VALUE_OPTIONAL,
"host for your database"
"host for your database",
"localhost"
)
->addOption(
"db_username",
@@ -62,8 +66,14 @@ class Install extends ContainerAwareCommand
InputOption::VALUE_OPTIONAL,
"database name"
)
->addOption(
"db_port",
null,
InputOption::VALUE_OPTIONAL,
"database port",
"3306"
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
@@ -84,7 +94,8 @@ class Install extends ContainerAwareCommand
"host" => $input->getOption("db_host"),
"dbName" => $input->getOption("db_name"),
"username" => $input->getOption("db_username"),
"password" => $input->getOption("db_password")
"password" => $input->getOption("db_password"),
"port" => $input->getOption("db_port")
);
while (false === $connection = $this->tryConnection($connectionInfo, $output)) {
@@ -101,6 +112,7 @@ class Install extends ContainerAwareCommand
""
));
$database->insertSql($connectionInfo["dbName"]);
$this->manageSecret($database);
$output->writeln(array(
"",
@@ -118,6 +130,13 @@ class Install extends ContainerAwareCommand
));
}
protected function manageSecret(Database $database)
{
$secret = TokenProvider::generateToken();
$sql = "UPDATE `config` SET `value`=? WHERE `name`='form.secret'";
$database->execute($sql, [$secret]);
}
/**
* Test if needed directories have write permission
*
@@ -129,26 +148,32 @@ class Install extends ContainerAwareCommand
"Checking some permissions"
));
$permissions = new CheckPermission(false, $this->getContainer()->get('thelia.translator'));
/** @var Translator $translator */
$translator = $this->getContainer()->get('thelia.translator');
$permissions = new CheckPermission(false, $translator);
$isValid = $permissions->exec();
foreach ($permissions->getValidationMessages() as $item => $data) {
if ($data['status']) {
$output->writeln(array(
sprintf("<info>%s ...</info> %s",
$output->writeln(
array(
sprintf(
"<info>%s ...</info> %s",
$data['text'],
"<info>Ok</info>")
"<info>Ok</info>"
)
)
);
} else {
$output->writeln(array(
sprintf("<error>%s </error>%s",
sprintf(
"<error>%s </error>%s",
$data['text'],
sprintf("<error>%s</error>", $data["hint"])
)
));
}
}
if (false === $isValid) {
@@ -177,14 +202,13 @@ class Install extends ContainerAwareCommand
$configContent = str_replace("%PASSWORD%", $connectionInfo["password"], $configContent);
$configContent = str_replace(
"%DSN%",
sprintf("mysql:host=%s;dbname=%s", $connectionInfo["host"], $connectionInfo["dbName"]),
sprintf("mysql:host=%s;dbname=%s;port=%s", $connectionInfo["host"], $connectionInfo["dbName"], $connectionInfo['port']),
$configContent
);
file_put_contents($configFile, $configContent);
$fs->remove($this->getContainer()->getParameter("kernel.cache_dir"));
}
/**
@@ -196,16 +220,15 @@ class Install extends ContainerAwareCommand
*/
protected function tryConnection($connectionInfo, OutputInterface $output)
{
if (is_null($connectionInfo["dbName"])) {
return false;
}
$dsn = "mysql:host=%s";
$dsn = "mysql:host=%s;port=%s";
try {
$connection = new \PDO(
sprintf($dsn, $connectionInfo["host"]),
sprintf($dsn, $connectionInfo["host"], $connectionInfo["port"]),
$connectionInfo["username"],
$connectionInfo["password"]
);
@@ -230,62 +253,93 @@ class Install extends ContainerAwareCommand
*/
protected function getConnectionInfo(InputInterface $input, OutputInterface $output)
{
$dialog = $this->getHelperSet()->get('dialog');
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$connectionInfo = array();
$connectionInfo["host"] = $dialog->askAndValidate(
$connectionInfo['host'] = $this->enterData(
$helper,
$input,
$output,
$this->decorateInfo("Database host : "),
function ($answer) {
$answer = trim($answer);
if (is_null($answer)) {
throw new \RuntimeException("You must specify a database host");
}
return $answer;
}
"Database host [default: localhost] : ",
"You must specify a database host",
false,
"localhost"
);
$connectionInfo["dbName"] = $dialog->askAndValidate(
$connectionInfo['port'] = $this->enterData(
$helper,
$input,
$output,
$this->decorateInfo("Database name (if database does not exist, Thelia will try to create it) : "),
function ($answer) {
$answer = trim($answer);
if (is_null($answer)) {
throw new \RuntimeException("You must specify a database name");
}
return $answer;
}
"Database port [default: 3306] : ",
"You must specify a database port",
false,
"3306"
);
$connectionInfo["username"] = $dialog->askAndValidate(
$connectionInfo['dbName'] = $this->enterData(
$helper,
$input,
$output,
$this->decorateInfo("Database username : "),
function ($answer) {
$answer = trim($answer);
if (is_null($answer)) {
throw new \RuntimeException("You must specify a database username");
}
return $answer;
}
"Database name (if database does not exist, Thelia will try to create it) : ",
"You must specify a database name"
);
$connectionInfo["password"] = $dialog->askHiddenResponse(
$connectionInfo['username'] = $this->enterData(
$helper,
$input,
$output,
$this->decorateInfo("Database password : ")
"Database username : ",
"You must specify a database username"
);
$connectionInfo['password'] = $this->enterData(
$helper,
$input,
$output,
"Database password : ",
"You must specify a database username",
true,
null,
true
);
return $connectionInfo;
}
protected function enterData(
QuestionHelper $helper,
InputInterface $input,
OutputInterface $output,
$label,
$errorMessage,
$hidden = false,
$defaultValue = null,
$beEmpty = false
) {
$question = new Question($label, $defaultValue);
if ($hidden) {
$question->setHidden(true);
$question->setHiddenFallback(false);
}
$question->setValidator(function ($value) use (&$errorMessage, &$beEmpty) {
if (trim($value) == '') {
if (is_null($value) && !$beEmpty) {
throw new \Exception($errorMessage);
}
}
return $value;
});
return $helper->ask($input, $output, $question);
}
protected function decorateInfo($text)
{
return sprintf("<info>%s</info>", $text);
}
}

View File

@@ -15,8 +15,11 @@ namespace Thelia\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Thelia\Core\Event\Module\ModuleToggleActivationEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\ModuleQuery;
use Thelia\Module\BaseModule;
/**
* activates a module
@@ -33,12 +36,17 @@ class ModuleActivateCommand extends BaseModuleGenerate
$this
->setName("module:activate")
->setDescription("Activates a module")
->addOption(
"with-dependencies",
null,
InputOption::VALUE_NONE,
'activate module recursively'
)
->addArgument(
"module" ,
"module",
InputArgument::REQUIRED,
"module to activate"
)
;
);
}
protected function execute(InputInterface $input, OutputInterface $output)
@@ -51,12 +59,26 @@ class ModuleActivateCommand extends BaseModuleGenerate
throw new \RuntimeException(sprintf("module %s not found", $moduleCode));
}
try {
$moduleInstance = $module->createInstance();
if ($module->getActivate() == BaseModule::IS_ACTIVATED) {
throw new \RuntimeException(sprintf("module %s is already actived", $moduleCode));
}
$moduleInstance->activate();
try {
$event = new ModuleToggleActivationEvent($module->getId());
if ($input->getOption("with-dependencies")) {
$event->setRecursive(true);
}
$this->getDispatcher()->dispatch(TheliaEvents::MODULE_TOGGLE_ACTIVATION, $event);
} catch (\Exception $e) {
throw new \RuntimeException(sprintf("Activation fail with Exception : [%d] %s", $e->getCode(), $e->getMessage()));
throw new \RuntimeException(
sprintf(
"Activation fail with Exception : [%d] %s",
$e->getCode(),
$e->getMessage()
)
);
}
//impossible to change output class in CommandTester...

View File

@@ -12,11 +12,17 @@
namespace Thelia\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Thelia\Action\Module;
use Thelia\Core\Event\Module\ModuleToggleActivationEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Model\ModuleQuery;
use Thelia\Module\BaseModule;
/**
* Deactivates a module
@@ -33,11 +39,23 @@ class ModuleDeactivateCommand extends BaseModuleGenerate
$this
->setName("module:deactivate")
->setDescription("Deactivate a module")
->addOption(
"with-dependencies",
null,
InputOption::VALUE_NONE,
'activate module recursively'
)
->addArgument(
"module" ,
"module",
InputArgument::REQUIRED,
"module to deactivate"
)
->addOption(
"assume-yes",
'y',
InputOption::VALUE_NONE,
'Assume to deactivate a mandatory module'
)
;
}
@@ -51,10 +69,26 @@ class ModuleDeactivateCommand extends BaseModuleGenerate
throw new \RuntimeException(sprintf("module %s not found", $moduleCode));
}
try {
$moduleInstance = $module->createInstance();
if ($module->getActivate() == BaseModule::IS_NOT_ACTIVATED) {
throw new \RuntimeException(sprintf("module %s is already deactivated", $moduleCode));
}
$moduleInstance->deActivate();
try {
$event = new ModuleToggleActivationEvent($module->getId());
$module = ModuleQuery::create()->findPk($module->getId());
if ($module->getMandatory() == BaseModule::IS_MANDATORY) {
if (!$this->askConfirmation($input, $output)) {
return;
}
$event->setAssumeDeactivate(true);
}
if ($input->getOption("with-dependencies")) {
$event->setRecursive(true);
}
$this->getDispatcher()->dispatch(TheliaEvents::MODULE_TOGGLE_ACTIVATION, $event);
} catch (\Exception $e) {
throw new \RuntimeException(sprintf("Deactivation fail with Exception : [%d] %s", $e->getCode(), $e->getMessage()));
}
@@ -68,4 +102,33 @@ class ModuleDeactivateCommand extends BaseModuleGenerate
), "bg=green;fg=black");
}
}
private function askConfirmation(InputInterface $input, OutputInterface $output)
{
$assumeYes = $input->getOption("assume-yes");
$moduleCode = $input->getArgument("module");
if (!$assumeYes) {
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$questionText = "Module ";
$questionText .= (empty($moduleCode))
? ""
: $moduleCode;
$questionText .= " is mandatory.\n";
$questionText .= "Would you like to deactivate the module ";
$questionText .= (empty($moduleCode))
? ""
: $moduleCode;
$questionText .= " ? (yes, or no) ";
$question = new ConfirmationQuestion($questionText, false);
if (!$helper->ask($input, $output, $question)) {
return false;
}
}
return true;
}
}

View File

@@ -14,6 +14,7 @@ namespace Thelia\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
@@ -22,7 +23,7 @@ use Symfony\Component\Filesystem\Filesystem;
*
* Class ModuleGenerateCommand
* @package Thelia\Command
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class ModuleGenerateCommand extends BaseModuleGenerate
{
@@ -32,23 +33,38 @@ class ModuleGenerateCommand extends BaseModuleGenerate
->setName("module:generate")
->setDescription("generate all needed files for creating a new Module")
->addArgument(
"name" ,
"name",
InputArgument::REQUIRED,
"name wanted for your Module"
)
->addOption(
'force',
null,
InputOption::VALUE_NONE,
'If defined, it will update the module with missing directories and files (no overrides).'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->module = $this->formatModuleName($input->getArgument("name"));
$this->moduleDirectory = THELIA_MODULE_DIR . DIRECTORY_SEPARATOR . $this->module;
$this->moduleDirectory = THELIA_MODULE_DIR . $this->module;
$this->validModuleName($this->module);
try {
$this->verifyExistingModule();
} catch (\RuntimeException $ex) {
if (false === $input->getOption('force')) {
throw $ex;
}
}
$this->createDirectories();
$this->createFiles();
if (method_exists($this, "renderBlock")) {
//impossible to change output class in CommandTester...
if (method_exists($output, "renderBlock")) {
// impossible to change output class in CommandTester...
$output->renderBlock(array(
'',
sprintf("module %s create with success", $this->module),
@@ -56,19 +72,38 @@ class ModuleGenerateCommand extends BaseModuleGenerate
''
), "bg=green;fg=black");
}
}
private function createDirectories()
{
$fs = new Filesystem();
if (!$fs->exists($this->moduleDirectory)) {
$fs->mkdir($this->moduleDirectory);
foreach ($this->neededDirectories as $directory) {
$fs->mkdir($this->moduleDirectory . DIRECTORY_SEPARATOR . $directory);
}
foreach ($this->neededDirectories as $directory) {
if (!$fs->exists($this->moduleDirectory . DIRECTORY_SEPARATOR . $directory)) {
$fs->mkdir($this->moduleDirectory . DIRECTORY_SEPARATOR . $directory);
}
}
}
protected function copyConfigFile($filename, $skeletonDir, Filesystem $fs)
{
$filename = $this->moduleDirectory . DIRECTORY_SEPARATOR . "Config" . DIRECTORY_SEPARATOR . $filename;
if (!$fs->exists($filename)) {
$configContent = file_get_contents($skeletonDir . "config.xml");
$configContent = str_replace("%%CLASSNAME%%", $this->module, $configContent);
$configContent = str_replace("%%NAMESPACE%%", $this->module, $configContent);
$configContent = str_replace("%%NAMESPACE_LOWER%%", strtolower($this->module), $configContent);
file_put_contents(
$filename,
$configContent
);
}
}
private function createFiles()
@@ -76,56 +111,116 @@ class ModuleGenerateCommand extends BaseModuleGenerate
$fs = new Filesystem();
try {
$skeletonDir = str_replace("/", DIRECTORY_SEPARATOR, THELIA_ROOT . "/core/lib/Thelia/Command/Skeleton/Module/");
$skeletonDir = str_replace("/", DIRECTORY_SEPARATOR, __DIR__ . "/Skeleton/Module/");
// config.xml file
$fs->copy($skeletonDir . "config.xml", $this->moduleDirectory . DIRECTORY_SEPARATOR . "Config" . DIRECTORY_SEPARATOR . "config.xml");
$this->copyConfigFile("config.xml", $skeletonDir, $fs);
$this->copyConfigFile("config_prod.xml", $skeletonDir, $fs);
$this->copyConfigFile("config_dev.xml", $skeletonDir, $fs);
$this->copyConfigFile("config_test.xml", $skeletonDir, $fs);
// Readme.md file
$filename = $this->moduleDirectory . DIRECTORY_SEPARATOR . "Readme.md";
if (!$fs->exists($filename)) {
$readmeContent = file_get_contents($skeletonDir . "Readme.md");
// generate title for readme
preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $this->module, $readmeTitle);
$composerFinalName = strtolower(implode("-", $readmeTitle[0]));
$readmeContent = str_replace("%%MODULENAME%%", $this->module, $readmeContent);
$readmeContent = str_replace("%%MODULENAMETITLE%%", implode(" ", $readmeTitle[0]), $readmeContent);
$readmeContent = str_replace("%%COMPOSERNAME%%", $composerFinalName, $readmeContent);
file_put_contents($filename, $readmeContent);
}
// composer.json file
$filename = $this->moduleDirectory . DIRECTORY_SEPARATOR . "composer.json";
if (!$fs->exists($filename)) {
$composerContent = file_get_contents($skeletonDir . "composer.json");
// generate composer module name
preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $this->module, $composerName);
$composerContent = str_replace("%%MODULENAME%%", $this->module, $composerContent);
$composerContent = str_replace("%%COMPOSERNAME%%", strtolower(implode("-", $composerName[0])), $composerContent);
file_put_contents($filename, $composerContent);
}
// module.xml file
$filename = $this->moduleDirectory . DIRECTORY_SEPARATOR . "Config". DIRECTORY_SEPARATOR . "module.xml";
if (!$fs->exists($filename)) {
$moduleContent = file_get_contents($skeletonDir . "module.xml");
$moduleContent = str_replace("%%CLASSNAME%%", $this->module, $moduleContent);
$moduleContent = str_replace("%%NAMESPACE%%", $this->module, $moduleContent);
file_put_contents($this->moduleDirectory . DIRECTORY_SEPARATOR . "Config". DIRECTORY_SEPARATOR . "module.xml", $moduleContent);
file_put_contents($filename, $moduleContent);
}
// PHP Class template
$filename = $this->moduleDirectory . DIRECTORY_SEPARATOR . $this->module . ".php";
if (!$fs->exists($filename)) {
$classContent = file_get_contents($skeletonDir . "Class.php.template");
$classContent = str_replace("%%CLASSNAME%%", $this->module, $classContent);
$classContent = str_replace("%%NAMESPACE%%", $this->module, $classContent);
$classContent = str_replace("%%DOMAINNAME%%", strtolower($this->module), $classContent);
file_put_contents($this->moduleDirectory . DIRECTORY_SEPARATOR . $this->module.".php", $classContent);
file_put_contents($filename, $classContent);
}
// schema.xml file
$filename = $this->moduleDirectory . DIRECTORY_SEPARATOR . "Config" . DIRECTORY_SEPARATOR . "schema.xml";
if (!$fs->exists($filename)) {
$schemaContent = file_get_contents($skeletonDir . "schema.xml");
$schemaContent = str_replace("%%NAMESPACE%%", $this->module, $schemaContent);
$schemaContent = str_replace(
'%%XSD_LOCATION%%',
$fs->makePathRelative(
THELIA_VENDOR . 'propel/propel/resources/xsd/',
$this->moduleDirectory
) . 'database.xsd',
$schemaContent
);
file_put_contents($this->moduleDirectory . DIRECTORY_SEPARATOR . "Config". DIRECTORY_SEPARATOR . "schema.xml", $schemaContent);
file_put_contents($filename, $schemaContent);
}
// routing.xml file
$filename = $this->moduleDirectory . DIRECTORY_SEPARATOR . "Config" . DIRECTORY_SEPARATOR . "routing.xml";
if (!$fs->exists($filename)) {
$routingContent = file_get_contents($skeletonDir . "routing.xml");
$routingContent = str_replace("%%NAMESPACE%%", $this->module, $routingContent);
$routingContent = str_replace("%%CLASSNAME_LOWER%%", strtolower($this->module), $routingContent);
file_put_contents($this->moduleDirectory . DIRECTORY_SEPARATOR . "Config". DIRECTORY_SEPARATOR . "routing.xml", $routingContent);
file_put_contents($filename, $routingContent);
}
// I18n sample files
$filename = $this->moduleDirectory . DIRECTORY_SEPARATOR . "I18n" . DIRECTORY_SEPARATOR . "fr_FR.php";
if (!$fs->exists($filename)) {
$fs->copy(
$skeletonDir . DIRECTORY_SEPARATOR . "I18n" . DIRECTORY_SEPARATOR . "fr_FR.php",
$this->moduleDirectory . DIRECTORY_SEPARATOR . "I18n" . DIRECTORY_SEPARATOR . "fr_FR.php"
$filename
);
}
$filename = $this->moduleDirectory . DIRECTORY_SEPARATOR . "I18n" . DIRECTORY_SEPARATOR . "en_US.php";
if (!$fs->exists($filename)) {
$fs->copy(
$skeletonDir . DIRECTORY_SEPARATOR . "I18n" . DIRECTORY_SEPARATOR . "en_US.php",
$this->moduleDirectory . DIRECTORY_SEPARATOR . "I18n" . DIRECTORY_SEPARATOR . "en_US.php"
);
}
} catch (\Exception $ex) {
$fs->remove($this->moduleDirectory);
throw $ex;
}
}
}

View File

@@ -13,6 +13,7 @@
namespace Thelia\Command;
use Propel\Generator\Command\ModelBuildCommand;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -25,7 +26,7 @@ use Symfony\Component\Filesystem\Filesystem;
*
* Class ModuleGenerateModelCommand
* @package Thelia\Command
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class ModuleGenerateModelCommand extends BaseModuleGenerate
{
@@ -46,13 +47,12 @@ class ModuleGenerateModelCommand extends BaseModuleGenerate
"with this option generate sql file at the same time"
)
;
}
public function execute(InputInterface $input, OutputInterface $output)
{
$this->module = $this->formatModuleName($input->getArgument("name"));
$this->moduleDirectory = THELIA_MODULE_DIR . DS . $this->module;
$this->moduleDirectory = THELIA_MODULE_DIR . $this->module;
$fs = new Filesystem();
@@ -66,11 +66,13 @@ class ModuleGenerateModelCommand extends BaseModuleGenerate
$this->generateModel($output);
$output->renderBlock(array(
'',
'Model generated successfuly',
''
), 'bg=green;fg=black');
/** @var FormatterHelper $formatter */
$formatter = $this->getHelper('formatter');
$formattedBlock = $formatter->formatBlock(
'Model generated successfully',
'bg=green;fg=black'
);
$output->writeln($formattedBlock);
if ($input->getOption("generate-sql")) {
$output->writeln(' ');
@@ -80,7 +82,6 @@ class ModuleGenerateModelCommand extends BaseModuleGenerate
protected function generateSql(OutputInterface $output)
{
$command = $this->getApplication()->find("module:generate:sql");
$command->run(
@@ -108,7 +109,7 @@ class ModuleGenerateModelCommand extends BaseModuleGenerate
);
$verifyDirectories = array(
THELIA_MODULE_DIR . DS . "Thelia",
THELIA_MODULE_DIR . "Thelia",
$this->moduleDirectory . DS . "Model" . DS . "Thelia"
);
@@ -117,7 +118,5 @@ class ModuleGenerateModelCommand extends BaseModuleGenerate
$fs->remove($directory);
}
}
}
}

View File

@@ -13,6 +13,7 @@
namespace Thelia\Command;
use Propel\Generator\Command\SqlBuildCommand;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -24,7 +25,7 @@ use Symfony\Component\Filesystem\Filesystem;
*
* Class ModuleGenerateSqlCommand
* @package Thelia\Command
* @author Manuel Raynaud <mraynaud@openstudio.fr>
* @author Manuel Raynaud <manu@raynaud.io>
*/
class ModuleGenerateSqlCommand extends BaseModuleGenerate
{
@@ -44,7 +45,7 @@ class ModuleGenerateSqlCommand extends BaseModuleGenerate
public function execute(InputInterface $input, OutputInterface $output)
{
$this->module = $this->formatModuleName($input->getArgument("name"));
$this->moduleDirectory = THELIA_MODULE_DIR . DS . $this->module;
$this->moduleDirectory = THELIA_MODULE_DIR . $this->module;
$fs = new Filesystem();
@@ -68,12 +69,15 @@ class ModuleGenerateSqlCommand extends BaseModuleGenerate
$output
);
$output->renderBlock(array(
'',
'Sql generated successfuly',
/** @var FormatterHelper $formatter */
$formatter = $this->getHelper('formatter');
$formattedBlock = $formatter->formatBlock(
[
'Sql generated successfully',
'File available in your module config directory',
''
), 'bg=green;fg=black');
],
'bg=green;fg=black'
);
$output->writeln($formattedBlock);
}
}

View File

@@ -0,0 +1,87 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Thelia\Model\Map\ModuleTableMap;
use Thelia\Model\ModuleQuery;
use Thelia\Module\BaseModule;
/**
* Class ModuleListCommand
* @package Thelia\Command
* @author Benjamin Perche <bperche@openstudio.fr>
*/
class ModuleListCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('module:list')
->setDescription('List the modules')
;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$helper = new Table($output);
$helper->addRows($this->getModulesData());
$helper
->setHeaders(["Code", "Active", "Type", "Version"])
->render()
;
}
protected function getModulesData()
{
$moduleData = ModuleQuery::create()
->orderByType()
->addAsColumn("code", ModuleTableMap::CODE)
->addAsColumn("active", "IF(".ModuleTableMap::ACTIVATE.", \"Yes\", \"No\")")
->addAsColumn("type", ModuleTableMap::TYPE)
->addAsColumn("version", ModuleTableMap::VERSION)
->select([
"code",
"active",
"type",
"version",
])
->find()
->toArray()
;
foreach ($moduleData as &$row) {
switch ($row["type"]) {
case BaseModule::CLASSIC_MODULE_TYPE:
$row["type"] = "classic";
break;
case BaseModule::DELIVERY_MODULE_TYPE:
$row["type"] = "delivery";
break;
case BaseModule::PAYMENT_MODULE_TYPE:
$row["type"] = "payment";
break;
}
}
return $moduleData;
}
}

View File

@@ -0,0 +1,219 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\Command;
use Propel\Runtime\ActiveQuery\Criteria;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Model\ModuleQuery;
use Symfony\Component\Console\Helper\QuestionHelper;
/**
* Class ModulePositionCommand
* Set modules position
*
* @package Thelia\Command
* @author Jérôme Billiras <jerome.billiras+github@gmail.com>
*/
class ModulePositionCommand extends ContainerAwareCommand
{
/**
* @var \Thelia\Model\ModuleQuery
*/
protected $moduleQuery;
/**
* @var array Modules list
*/
protected $modulesList = [];
/**
* @var array Modules positions list
*/
protected $positionsList = [];
public function __construct($name = null)
{
parent::__construct($name);
$this->moduleQuery = new ModuleQuery;
}
protected function configure()
{
$this
->setName('module:position')
->setDescription('Set module(s) position')
->addArgument(
'modules',
InputArgument::REQUIRED | InputArgument::IS_ARRAY,
'Module in format moduleName:[+|-]position where position is an integer or up or down.'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$argsList = $input->getArgument('modules');
array_walk($argsList, [$this, 'checkModuleArgument']);
if (!$this->checkPositions($input, $output, $isAbsolute)) {
return;
}
if ($isAbsolute) {
array_multisort($this->positionsList, SORT_ASC, SORT_REGULAR, $this->modulesList);
}
$maxPositionByType = $this->cleanPosition();
foreach ($this->modulesList as $moduleIdx => $moduleName) {
$this->moduleQuery->clear();
$module = $this->moduleQuery->findOneByCode($moduleName);
$position = $this->positionsList[$moduleIdx];
if ($position === 'up') {
$event = new UpdatePositionEvent($module->getId(), UpdatePositionEvent::POSITION_UP);
} elseif ($position === 'down') {
$event = new UpdatePositionEvent($module->getId(), UpdatePositionEvent::POSITION_DOWN);
} else {
if ($position[0] === '+' || $position[0] === '-') {
$position = $module->getPosition() + $position;
}
if ($position < 1) {
$position = 1;
}
$maxPosition = $maxPositionByType[$module->getType()];
if ($position > $maxPosition) {
$position = $maxPosition;
}
$event = new UpdatePositionEvent($module->getId(), UpdatePositionEvent::POSITION_ABSOLUTE, $position);
}
$this->getDispatcher()->dispatch(TheliaEvents::MODULE_UPDATE_POSITION, $event);
}
/** @var FormatterHelper $formatter */
$formatter = $this->getHelper('formatter');
$formattedBlock = $formatter->formatBlock('Module position(s) updated', 'bg=green;fg=black', true);
$output->writeln($formattedBlock);
}
/**
* Check a module argument format
*
* @param string $paramValue
*
* @throws \InvalidArgumentException
* @throws \RuntimeException
*/
protected function checkModuleArgument($paramValue)
{
if (!preg_match('#^([a-z0-9]+):([\+-]?[0-9]+|up|down)$#i', $paramValue, $matches)) {
throw new \InvalidArgumentException(
'Arguments must be in format moduleName:[+|-]position where position is an integer or up or down.'
);
}
$this->moduleQuery->clear();
$module = $this->moduleQuery->findOneByCode($matches[1]);
if ($module === null) {
throw new \RuntimeException(sprintf('%s module does not exists. Try to refresh first.', $matches[1]));
}
$this->modulesList[] = $matches[1];
$this->positionsList[] = $matches[2];
}
/**
* Reorder modules positions (without holes)
*
* @return array Maximum position by type
*/
protected function cleanPosition()
{
$modulesType = [];
$this->moduleQuery->clear();
$modules = $this->moduleQuery->orderByPosition(Criteria::ASC);
/** @var \Thelia\Model\Module $module */
foreach ($modules as $module) {
if (!isset($modulesType[$module->getType()])) {
$modulesType[$module->getType()] = 0;
}
$module
->setPosition(++$modulesType[$module->getType()])
->save()
;
}
return $modulesType;
}
/**
* Check positions consistency
*
* @param InputInterface $input An InputInterface instance
* @param OutputInterface $output An OutputInterface instance
* @param bool $isAbsolute Set to true or false according to position values
*
* @throws \InvalidArgumentException
*
* @return bool Continue or stop command
*/
protected function checkPositions(InputInterface $input, OutputInterface $output, &$isAbsolute = false)
{
$isRelative = false;
foreach (array_count_values($this->positionsList) as $value => $count) {
if (is_int($value) && $value[0] !== '+' && $value[0] !== '-') {
$isAbsolute = true;
if ($count > 1) {
throw new \InvalidArgumentException('Two (or more) absolute positions are identical.');
}
} else {
$isRelative = true;
}
}
if ($isAbsolute && $isRelative) {
/** @var FormatterHelper $formatter */
$formatter = $this->getHelper('formatter');
$formattedBlock = $formatter->formatBlock(
'Mix absolute and relative positions may produce unexpected results !',
'bg=yellow;fg=black',
true
);
$output->writeln($formattedBlock);
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('<question>Do you want to continue ? y/[n]<question>', false);
return $helper->ask($input, $output, $question);
}
return true;
}
}

View File

@@ -14,6 +14,7 @@ namespace Thelia\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Thelia\Exception\InvalidModuleException;
use Thelia\Module\ModuleManagement;
/**
@@ -36,13 +37,20 @@ class ModuleRefreshCommand extends ContainerAwareCommand
{
try {
$moduleManagement = new ModuleManagement;
$moduleManagement->updateModules();
$moduleManagement->updateModules($this->getContainer());
} catch (InvalidModuleException $ime) {
throw new \RuntimeException(
sprintf('One or more modules could not be refreshed : %s', $ime->getErrorsAsString("\n"))
);
} catch (\Exception $e) {
throw new \RuntimeException(sprintf('Refresh modules list fail with Exception : [%d] %s', $e->getCode(), $e->getMessage()));
throw new \RuntimeException(
sprintf('Refresh modules list fail with Exception : [%d] %s', $e->getCode(), $e->getMessage())
);
}
if (method_exists($output, 'renderBlock')) {
$output->renderBlock([
$output->renderBlock(
[
'',
'Modules list successfully refreshed',
''

Some files were not shown because too many files have changed in this diff Show More