Merge branch 'master' into actions

Conflicts:
	core/lib/Thelia/Action/BaseAction.php
	core/lib/Thelia/Action/Category.php
	core/lib/Thelia/Action/Customer.php
This commit is contained in:
Manuel Raynaud
2013-08-09 16:46:56 +02:00
25 changed files with 7899 additions and 102 deletions

View File

@@ -23,38 +23,68 @@
namespace Thelia\Action;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Thelia\Form\CategoryDeletionForm;
use Thelia\Form\BaseForm;
use Thelia\Core\HttpFoundation\Request;
use Thelia\Action\Exception\FormValidationException;
use Thelia\Core\Event\ActionEvent;
use Symfony\Component\Form\Form;
use Symfony\Component\DependencyInjection\ContainerAware;
use Thelia\Core\Template\ParserContext;
use Thelia\Log\Tlog;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Thelia\Core\Security\SecurityContext;
use Thelia\Core\Security\Exception\AuthorizationException;
abstract class BaseAction
class BaseAction
{
/**
* @var The container
*/
protected $container;
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
/**
* Validate a BaseForm
*
* @param BaseForm $aBaseForm the form
* @param string $expectedMethod the expected method, POST or GET, or null for any of them
* @throws FormValidationException is the form contains error, or the method is not the right one
* @return Symfony\Component\Form\Form Form the symfony form object
*/
protected function validateForm(BaseForm $aBaseForm, $expectedMethod = null)
{
$form = $aBaseForm->getForm();
if ($aBaseForm->getRequest()->isMethod($expectedMethod)) {
if ($expectedMethod == null || $aBaseForm->getRequest()->isMethod($expectedMethod)) {
$form->bind($aBaseForm->getRequest());
if ($form->isValid()) {
return $form;
} else {
throw new FormValidationException("Missing or invalid data");
}
} else {
else {
throw new FormValidationException("Missing or invalid data");
}
}
else {
throw new FormValidationException(sprintf("Wrong form method, %s expected.", $expectedMethod));
}
}
/**
* Propagate a form error in the action event
*
* @param BaseForm $aBaseForm
* @param string $error_message
* @param ActionEvent $event
* @param BaseForm $aBaseForm the form
* @param string $error_message an error message that may be displayed to the customer
* @param ActionEvent $event the action event
*/
protected function propagateFormError(BaseForm $aBaseForm, $error_message, ActionEvent $event)
{
protected function propagateFormError(BaseForm $aBaseForm, $error_message, ActionEvent $event) {
// The form has an error
$aBaseForm->setError(true);
$aBaseForm->setErrorMessage($error_message);
@@ -66,6 +96,62 @@ abstract class BaseAction
$event->stopPropagation();
}
/**
* Check current user authorisations.
*
* @param mixed $roles a single role or an array of roles.
* @param mixed $permissions a single permission or an array of permissions.
*
* @throws AuthenticationException if permissions are not granted to the current user.
*/
protected function checkAuth($roles, $permissions, $context = false) {
if (! $this->getSecurityContext($context)->isGranted(
is_array($roles) ? $roles : array($roles),
is_array($permissions) ? $permissions : array($permissions)) ) {
Tlog::getInstance()->addAlert("Authorization roles:", $roles, " permissions:", $permissions, " refused.");
throw new AuthorizationException("Sorry, you're not allowed to perform this action");
}
}
/**
* Return the event dispatcher,
*
* @return ParserContext
*/
protected function getDispatcher()
{
return $this->container->get('event_dispatcher');
}
/**
* Return the parser context,
*
* @return ParserContext
*/
protected function getParserContext()
{
return $this->container->get('thelia.parser.context');
}
/**
* Return the security context, by default in admin mode.
*
* @param string the context, either SecurityContext::CONTEXT_BACK_OFFICE or SecurityContext::CONTEXT_FRONT_OFFICE
*
* @return Thelia\Core\Security\SecurityContext
*/
protected function getSecurityContext($context = false)
{
$securityContext = $this->container->get('thelia.securityContext');
$securityContext->setContext($context === false ? SecurityContext::CONTEXT_BACK_OFFICE : $context);
return $securityContext;
}
protected function redirect($url, $status = 302)
{
$response = new RedirectResponse($url, $status);
@@ -73,5 +159,4 @@ abstract class BaseAction
$response->send();
exit;
}
}
}

View File

@@ -47,18 +47,6 @@ use Thelia\Action\Exception\FormValidationException;
class Cart extends BaseAction implements EventSubscriberInterface
{
use \Thelia\Cart\CartTrait;
/**
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $dispatcher;
/**
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
*/
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
*
@@ -113,7 +101,7 @@ class Cart extends BaseAction implements EventSubscriberInterface
protected function updateQuantity(CartItem $cartItem, $quantity)
{
$cartItem->setDisptacher($this->dispatcher);
$cartItem->setDisptacher($this->getDispatcher());
$cartItem->addQuantity($quantity)
->save();
}
@@ -121,7 +109,7 @@ class Cart extends BaseAction implements EventSubscriberInterface
protected function addItem(\Thelia\Model\Cart $cart, $productId, $productSaleElementsId, $quantity, ProductPrice $productPrice)
{
$cartItem = new CartItem();
$cartItem->setDisptacher($this->dispatcher);
$cartItem->setDisptacher($this->getDispatcher());
$cartItem
->setCart($cart)
->setProductId($productId)

View File

@@ -34,13 +34,21 @@ use Thelia\Model\CategoryQuery;
use Thelia\Model\AdminLog;
use Thelia\Form\CategoryDeletionForm;
use Thelia\Action\Exception\FormValidationException;
use Propel\Runtime\ActiveQuery\Criteria;
use Propel\Runtime\Propel;
use Thelia\Model\Map\CategoryTableMap;
use Propel\Runtime\Exception\PropelException;
class Category extends BaseAction implements EventSubscriberInterface
{
public function create(ActionEvent $event)
{
$request = $event->getRequest();
$this->checkAuth("ADMIN", "admin.category.create");
$request = $event->getRequest();
try {
$categoryCreationForm = new CategoryCreationForm($request);
@@ -83,7 +91,9 @@ class Category extends BaseAction implements EventSubscriberInterface
public function modify(ActionEvent $event)
{
/*
$this->checkAuth("ADMIN", "admin.category.delete");
$request = $event->getRequest();
$customerModification = new CustomerModification($request);
@@ -152,7 +162,10 @@ class Category extends BaseAction implements EventSubscriberInterface
*/
public function delete(ActionEvent $event)
{
$request = $event->getRequest();
$this->checkAuth("ADMIN", "admin.category.delete");
$request = $event->getRequest();
try {
$categoryDeletionForm = new CategoryDeletionForm($request);
@@ -200,9 +213,12 @@ class Category extends BaseAction implements EventSubscriberInterface
*/
public function toggleVisibility(ActionEvent $event)
{
$request = $event->getRequest();
$category = CategoryQuery::create()->findPk($request->get('id', 0));
$this->checkAuth("ADMIN", "admin.category.edit");
$request = $event->getRequest();
$category = CategoryQuery::create()->findPk($request->get('category_id', 0));
if ($category !== null) {
@@ -216,6 +232,144 @@ class Category extends BaseAction implements EventSubscriberInterface
}
}
/**
* Move category up
*
* @param ActionEvent $event
*/
public function changePositionUp(ActionEvent $event) {
return $this->exchangePosition($event, 'up');
}
/**
* Move category down
*
* @param ActionEvent $event
*/
public function changePositionDown(ActionEvent $event) {
return $this->exchangePosition($event, 'down');
}
/**
* Move up or down a category
*
* @param ActionEvent $event
* @param string $direction up to move up, down to move down
*/
protected function exchangePosition(ActionEvent $event, $direction) {
$this->checkAuth("ADMIN", "admin.category.edit");
$request = $event->getRequest();
$category = CategoryQuery::create()->findPk($request->get('category_id', 0));
if ($category !== null) {
// The current position of the category
$my_position = $category->getPosition();
// Find category to exchange position with
$search = CategoryQuery::create()
->filterByParent($category->getParent());
// Up or down ?
if ($direction == 'up') {
// Find the category immediately before me
$search->filterByPosition(array('max' => $my_position-1))->orderByPosition(Criteria::DESC);
}
else if ($direction == 'down') {
// Find the category immediately after me
$search->filterByPosition(array('min' => $my_position+1))->orderByPosition(Criteria::ASC);
}
else
return;
$result = $search->findOne();
// If we found the proper category, exchange their positions
if ($result) {
$cnx = Propel::getWriteConnection(CategoryTableMap::DATABASE_NAME);
$cnx->beginTransaction();
try {
$category->setPosition($result->getPosition())->save();
$result->setPosition($my_position)->save();
$cnx->commit();
}
catch(Exception $e) {
$cnx->rollback();
}
}
}
}
/**
* Changes category position
*
* @param ActionEvent $event
*/
public function changePosition(ActionEvent $event) {
$this->checkAuth("ADMIN", "admin.category.edit");
$request = $event->getRequest();
$category = CategoryQuery::create()->findPk($request->get('category_id', 0));
if ($category !== null) {
// The required position
$new_position = $request->get('position', null);
// The current position
$current_position = $category->getPosition();
if ($new_position != null && $new_position > 0 && $new_position != $current_position) {
// Find categories to offset
$search = CategoryQuery::create()->filterByParent($category->getParent());
if ($new_position > $current_position) {
// The new position is after the current position -> we will offset + 1 all categories located between us and the new position
$search->filterByPosition(array('min' => 1+$current_position, 'max' => $new_position));
$delta = -1;
}
else {
// The new position is brefore the current position -> we will offset - 1 all categories located between us and the new position
$search->filterByPosition(array('min' => $new_position, 'max' => $current_position - 1));
$delta = 1;
}
$results = $search->find();
$cnx = Propel::getWriteConnection(CategoryTableMap::DATABASE_NAME);
$cnx->beginTransaction();
try {
foreach($results as $result) {
$result->setPosition($result->getPosition() + $delta)->save($cnx);
}
$category->setPosition($new_position)->save($cnx);
$cnx->commit();
}
catch(Exception $e) {
$cnx->rollback();
}
}
}
}
/**
* Returns an array of event names this subscriber listens to.
*
@@ -243,7 +397,10 @@ class Category extends BaseAction implements EventSubscriberInterface
"action.modifyCategory" => array("modify", 128),
"action.deleteCategory" => array("delete", 128),
"action.toggleCategoryVisibility" => array("toggleVisibility", 128),
"action.toggleCategoryVisibility" => array("toggleVisibility", 128),
"action.changeCategoryPositionUp" => array("changePositionUp", 128),
"action.changeCategoryPositionDown" => array("changePositionDown", 128),
"action.changeCategoryPosition" => array("changePosition", 128),
);
}
}

View File

@@ -43,15 +43,6 @@ use Thelia\Action\Exception\FormValidationException;
class Customer extends BaseAction implements EventSubscriberInterface
{
/**
* @var \Thelia\Core\Security\SecurityContext
*/
protected $securityContext;
public function __construct(SecurityContext $securityContext)
{
$this->securityContext = $securityContext;
}
public function create(ActionEvent $event)
{
@@ -85,6 +76,7 @@ class Customer extends BaseAction implements EventSubscriberInterface
// Connect the newly created user,and redirect to the success URL
$this->processSuccessfullLogin($event, $customer, $customerCreationForm, true);
} catch (PropelException $e) {
Tlog::getInstance()->error(sprintf('error during creating customer on action/createCustomer with message "%s"', $e->getMessage()));
$message = "Failed to create your account, please try again.";
@@ -130,6 +122,7 @@ class Customer extends BaseAction implements EventSubscriberInterface
$this->processSuccessfullLogin($event, $customer, $customerModification);
} catch (PropelException $e) {
Tlog::getInstance()->error(sprintf('error during modifying customer on action/modifyCustomer with message "%s"', $e->getMessage()));
$message = "Failed to change your account, please try again.";
@@ -151,7 +144,8 @@ class Customer extends BaseAction implements EventSubscriberInterface
{
$event->getDispatcher()->dispatch(TheliaEvents::CUSTOMER_LOGOUT, $event);
$this->getSecurityContext()->clear();
$this->getFrontSecurityContext()->clear();
}
/**
@@ -240,6 +234,7 @@ class Customer extends BaseAction implements EventSubscriberInterface
*/
protected function processSuccessfullLogin(ActionEvent $event, CustomerModel $user, BaseForm $form, $sendLoginEvent = false)
{
$successUrl = $form->getSuccessUrl();
if ($this->securityContext->getContext() === SecurityContext::CONTEXT_FRONT_OFFICE) {
$this->processSuccessfullFrontEndLogin($event, $user, $form, $sendLoginEvent);
@@ -253,8 +248,9 @@ class Customer extends BaseAction implements EventSubscriberInterface
protected function processSuccessfullFrontEndLogin(ActionEvent $event, CustomerModel $user, BaseForm $form, $sendLoginEvent = false)
{
// Success -> store user in security context
$this->getSecurityContext()->setUser($user);
// Success -> store user in security context
$this->getFrontSecurityContext()->setUser($user);
if ($sendLoginEvent) $event->getDispatcher()->dispatch(TheliaEvents::CUSTOMER_LOGIN, $event);
@@ -265,9 +261,8 @@ class Customer extends BaseAction implements EventSubscriberInterface
*
* @return SecurityContext the security context
*/
protected function getSecurityContext()
{
//$this->securityContext->setContext(SecurityContext::CONTEXT_FRONT_OFFICE);
return $this->securityContext;
protected function getFrontSecurityContext() {
return $this->getSecurityContext(SecurityContext::CONTEXT_FRONT_OFFICE);
}
}

View File

@@ -70,9 +70,29 @@ class BaseAdminController extends ContainerAware
// Nothing special
}
return $this->pageNotFound();
}
/**
* Return a 404 error
*
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function pageNotFound() {
return new Response($this->renderRaw(self::TEMPLATE_404), 404);
}
/**
* Return a general error page
*
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function errorPage($message) {
return $this->render('general_error', array(
"error_message" => $message)
);
}
/**
* Check current admin user authorisations. An ADMIN role is assumed.
*

View File

@@ -25,13 +25,11 @@ namespace Thelia\Admin\Controller;
use Thelia\Model\CategoryQuery;
use Thelia\Core\Security\Exception\AuthenticationException;
use Thelia\Core\Security\Exception\AuthorizationException;
class CategoryController extends BaseAdminController {
protected function createNewCategory($args) {
$this->checkAuth("ADMIN", "admin.category.create");
$this->dispatchEvent("createCategory");
// At this point, the form has error, and should be redisplayed.
@@ -40,24 +38,16 @@ class CategoryController extends BaseAdminController {
protected function editCategory($args) {
$this->checkAuth("AMIN", "admin.category.edit");
$this->checkAuth("ADMIN", "admin.category.edit");
return $this->render('edit_category', $args);
}
protected function deleteCategory($category_id) {
$this->checkAuth("AMIN", "admin.category.delete");
$category = CategoryQuery::create()->findPk($category_id);
protected function deleteCategory($args) {
$this->dispatchEvent("deleteCategory");
// Something was wrong, category was not deleted. Display parent category list
return $this->render(
'categories',
array('current_category_id' => $category->getParent())
);
return $this->render('categories', $args);
}
protected function browseCategory($args) {
@@ -68,32 +58,32 @@ class CategoryController extends BaseAdminController {
}
protected function visibilityToggle($args) {
$this->checkAuth("AMIN", "admin.category.edit");
$this->dispatchEvent("toggleCategoryVisibility");
return $this->nullResponse();
}
protected function changePosition($args) {
$this->checkAuth("AMIN", "admin.category.edit");
$this->dispatchEvent("changeCategoryPosition");
return $this->render('categories', $args);
}
protected function positionDown($args) {
$this->dispatchEvent("changeCategoryPositionDown");
return $this->render('categories', $args);
}
protected function positionUp($args) {
$this->dispatchEvent("changeCategoryPositionUp");
return $this->render('categories', $args);
}
public function indexAction()
{
// Show top level categories and products
$args = array(
'action' => 'browse',
'current_category_id' => 0
);
return $this->browseCategory($args);
return $this->processAction();
}
public function processAction()
@@ -121,19 +111,29 @@ class CategoryController extends BaseAdminController {
return $this->editCategory($args);
case 'delete' : // Delete an existing category
return $this->deleteCategory($id);
return $this->deleteCategory($args);
case 'visibilityToggle' : // Toggle visibility
return $this->visibilityToggle($id);
case 'changePosition' : // Change position
return $this->changePosition($args);
}
case 'positionUp' : // Move up category
return $this->positionUp($args);
case 'positionDown' : // Move down category
return $this->positionDown($args);
}
}
catch(AuthorizationException $ex) {
return $this->errorPage($ex->getMessage());
}
catch(AuthenticationException $ex) {
return $this->render('general_error', array(
"error_message" => $ex->getMessage())
);
return $this->errorPage($ex->getMessage());
}
// We did not recognized the action -> return a 404 page
return $this->pageNotFound();
}
}

View File

@@ -118,7 +118,7 @@ trait CartTrait {
$session->setCart($newCart->getId());
$cartEvent = new CartEvent($newCart);
$this->dispatcher->dispatch(TheliaEvents::CART_DUPLICATE, $cartEvent);
$this->getDispatcher()->dispatch(TheliaEvents::CART_DUPLICATE, $cartEvent);
return $cartEvent->cart;
}

View File

@@ -13,19 +13,18 @@
<services>
<service id="thelia.action.cart" class="Thelia\Action\Cart">
<argument type="service" id="service_container"/>
<tag name="kernel.event_subscriber"/>
<argument type="service" id="event_dispatcher"/>
</service>
<service id="thelia.action.customer" class="Thelia\Action\Customer" scope="request">
<service id="thelia.action.customer" class="Thelia\Action\Customer">
<argument type="service" id="service_container"/>
<tag name="kernel.event_subscriber"/>
<argument type="service" id="thelia.securityContext"/>
</service>
<service id="thelia.action.category" class="Thelia\Action\Category">
<tag name="kernel.event_subscriber"/>
<argument type="service" id="service_container"/>
<tag name="kernel.event_subscriber"/>
</service>
</services>

View File

@@ -133,7 +133,7 @@ class SecurityContext {
}
// Get permissions from profile
// $userPermissions = $user->getPermissions();
// $userPermissions = $user->getPermissions(); FIXME
// TODO: Finalize permissions system !;

View File

@@ -49,7 +49,13 @@ class UrlGenerator extends AbstractSmartyPlugin
// the path to process
$path = $this->getParam($params, 'path');
return URL::absoluteUrl($path, $this->getArgsFromParam($params, array('path')));
$target = $this->getParam($params, 'target', null);
$url = URL::absoluteUrl($path, $this->getArgsFromParam($params, array('path', 'target')));
if ($target != null) $url .= '#'.$target;
return $url;
}
/**
@@ -84,7 +90,7 @@ class UrlGenerator extends AbstractSmartyPlugin
// the related action (optionale)
$action = $this->getParam($params, 'action');
$args = $this->getArgsFromParam($params, array('view', 'action'));
$args = $this->getArgsFromParam($params, array('view', 'action', 'target'));
if (! empty($action)) $args['action'] = $action;

View File

@@ -30,7 +30,7 @@ class CategoryDeletionForm extends BaseForm {
protected function buildForm()
{
$this->formBuilder
->add("id", "integer", array(
->add("category_id", "integer", array(
"constraints" => array(
new NotBlank()
)

View File

@@ -33,7 +33,8 @@ class CustomerCreation extends BaseForm
protected function buildForm()
{
$this->formBuilder
->add("firstname", "text", array(
->add("auto_login", "boolean")
->add("firstname", "text", array(
"constraints" => array(
new Constraints\NotBlank()
),

View File

@@ -53,6 +53,7 @@ class CustomerModification extends BaseForm {
{
$this->formBuilder
->add('update_logged_in_user', 'boolean') // In a front office context, update the in-memory logged-in user data
->add("firstname", "text", array(
"constraints" => array(
new Constraints\NotBlank()

View File

@@ -43,6 +43,16 @@ class CartTest extends \PHPUnit_Framework_TestCase
protected $uniqid;
public function getContainer()
{
$container = new \Symfony\Component\DependencyInjection\ContainerBuilder();
$dispatcher = $this->getMock("Symfony\Component\EventDispatcher\EventDispatcherInterface");
$container->set("event_dispatcher", $dispatcher);
return $container;
}
public function setUp()
{
@@ -53,12 +63,12 @@ class CartTest extends \PHPUnit_Framework_TestCase
$this->uniqid = uniqid('', true);
$dispatcher = $this->getMock("Symfony\Component\EventDispatcher\EventDispatcherInterface");
$container = $this->getContainer();
$this->actionCart = $this->getMock(
"\Thelia\Action\Cart",
array("generateCookie", "redirect"),
array($dispatcher)
array($container)
);