Refactored Form system to get simple error handling and a clearer

interface
This commit is contained in:
franck
2013-07-16 16:34:47 +02:00
parent d250e0c660
commit e8bf47b929
26 changed files with 626 additions and 220 deletions

View File

@@ -25,6 +25,7 @@ namespace Thelia\Core\Event;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\HttpFoundation\Request;
use Thelia\Form\BaseForm;
/**
*
* Class thrown on Thelia.action event
@@ -47,7 +48,7 @@ abstract class ActionEvent extends Event
*/
protected $action;
protected $form;
protected $form = null;
/**
*
@@ -78,17 +79,30 @@ abstract class ActionEvent extends Event
return $this->request;
}
public function setFormError($form)
/**
* Store a form taht contains error, to pass it to the current session.
*
* @param BaseForm $form
*/
public function setFormError(BaseForm $form)
{
$this->form = $form;
$this->stopPropagation();
}
public function getForm()
/**
* @return BaseForm the errored form, or null
*/
public function getFormError()
{
return $this->form;
}
/**
* Check if theis event contains a form with errors
*
* @return boolean
*/
public function hasFormError()
{
return $this->form !== null;

View File

@@ -50,9 +50,7 @@ class ControllerListener implements EventSubscriberInterface
$dispatcher->dispatch("action.".$action, $actionEvent);
if ($actionEvent->hasFormError()) {
$request->getSession()->set("form_error", true);
$request->getSession()->set("form_name", $actionEvent->getForm()->createView()
->vars["attr"]["thelia_name"]);
$request->getSession()->setErrorFormName($actionEvent->getFormError()->getName());
}
}

View File

@@ -25,6 +25,7 @@ namespace Thelia\Core\HttpFoundation\Session;
use Symfony\Component\HttpFoundation\Session\Session as BaseSession;
use Thelia\Core\Security\User\UserInterface;
use Thelia\Form\BaseForm;
class Session extends BaseSession {
@@ -39,28 +40,52 @@ class Session extends BaseSession {
return substr($this->getLocale(), 0, 2);
}
public function setCustomerUser(UserInterface $user) {
public function setCustomerUser(UserInterface $user)
{
$this->set('customer_user', $user);
}
public function getCustomerUser() {
public function getCustomerUser()
{
return $this->get('customer_user');
}
public function clearCustomerUser() {
public function clearCustomerUser()
{
return $this->remove('customer_user');
}
public function setAdminUser(UserInterface $user) {
public function setAdminUser(UserInterface $user)
{
$this->set('admin_user', $user);
}
public function getAdminUser() {
public function getAdminUser()
{
return $this->get('admin_user');
}
public function clearAdminUser() {
public function clearAdminUser()
{
return $this->remove('admin_user');
}
/**
* @param string $formName the form name
*/
public function setErrorFormName($formName)
{
$this->set('error_form', $formName);
}
public function getErrorFormName()
{
return $this->get('error_form', null);
}
public function clearErrorFormName()
{
return $this->remove('error_form');
}
}

View File

@@ -29,17 +29,15 @@ use Symfony\Component\Form\Form;
use Thelia\Core\Security\UserProvider\AdminUserProvider;
use Thelia\Core\Security\Authentication\UsernamePasswordFormAuthenticator;
use Thelia\Form\AdminLogin;
class AdminUsernamePasswordFormAuthenticator extends UsernamePasswordFormAuthenticator {
public function __construct(Request $request, Form $loginForm) {
public function __construct(Request $request, AdminLogin $loginForm) {
parent::__construct(
$request,
$loginForm,
new AdminUserProvider(),
array(
'username_field_name', 'email'
)
new AdminUserProvider()
);
}
}

View File

@@ -30,11 +30,14 @@ use Thelia\Form\CustomerLogin;
class CustomerUsernamePasswordFormAuthenticator extends UsernamePasswordFormAuthenticator {
public function __construct(Request $request, AdminLogin $loginForm) {
public function __construct(Request $request, CustomerLogin $loginForm) {
parent::__construct(
$request,
$loginForm,
new AdminUserProvider(),
new CustomerUserProvider(),
array(
'username_field_name', 'email'
)
);
}
}

View File

@@ -29,6 +29,8 @@ use Thelia\Core\Security\UserProvider\UserProviderInterface;
use Symfony\Component\Form\Form;
use Thelia\Core\Security\Exception\WrongPasswordException;
use Thelia\Core\Security\Exception\UsernameNotFoundException;
use Symfony\Component\Validator\Exception\ValidatorException;
use Thelia\Form\BaseForm;
class UsernamePasswordFormAuthenticator implements AuthenticatorInterface {
@@ -37,10 +39,12 @@ class UsernamePasswordFormAuthenticator implements AuthenticatorInterface {
protected $userProvider;
protected $options;
protected $baseLoginForm;
public function __construct(Request $request, Form $loginForm, UserProviderInterface $userProvider, array $options = array()) {
public function __construct(Request $request, BaseForm $loginForm, UserProviderInterface $userProvider, array $options = array()) {
$this->request = $request;
$this->loginForm = $loginForm;
$this->baseLoginForm = $loginForm;
$this->loginForm = $this->baseLoginForm->getForm();
$this->userProvider = $userProvider;
$defaults = array(
@@ -54,38 +58,38 @@ class UsernamePasswordFormAuthenticator implements AuthenticatorInterface {
$this->loginForm->bind($this->request);
}
public function getLoginForm() {
return $this->loginForm;
}
/**
* @return string the username value
*/
public function getUsername() {
return $this->loginForm->get($this->options['username_field_name'])->getData();
}
/**
* @see \Thelia\Core\Security\Authentication\AuthenticatorInterface::getAuthentifiedUser()
*/
public function getAuthentifiedUser() {
if ($this->request->isMethod($this->options['required_method'])) {
if ($this->loginForm->isValid()) {
if (! $this->loginForm->isValid()) throw new ValidatorException("Form is not valid.");
// Retreive user
$username = $this->getUsername();
$password = $this->loginForm->get($this->options['password_field_name'])->getData();
// Retreive user
$username = $this->getUsername();
$password = $this->loginForm->get($this->options['password_field_name'])->getData();
$user = $this->userProvider->getUser($username);
$user = $this->userProvider->getUser($username);
if ($user === null) throw new UsernameNotFoundException(sprintf("Username '%s' was not found.", $username));
if ($user === null) throw new UsernameNotFoundException(sprintf("Username '%s' was not found.", $username));
// Check user password
$authOk = $user->checkPassword($password) === true;
// Check user password
$authOk = $user->checkPassword($password) === true;
if ($authOk !== true) throw new WrongPasswordException(sprintf("Wrong password for user '%s'.", $username));
if ($authOk !== true) throw new WrongPasswordException(sprintf("Wrong password for user '%s'.", $username));
return $user;
}
}
else {
throw new \RuntimeException("Invalid method.");
return $user;
}
throw new \RuntimeException("Invalid method.");
}
}

View File

@@ -109,10 +109,51 @@ class SecurityContext {
{
if ($this->isAuthenticated() === true) {
echo "TODO: check roles and permissions !";
$user = $this->getUser();
// TODO : check roles and permissions
return true;
// Check if user's roles matches required roles
$userRoles = $user->getRoles();
$roleFound = false;
foreach($userRoles as $role) {
if (in_array($role, $roles)) {
$roleFound = true;
break;
}
}
if ($roleFound) {
if (empty($permissions)) {
return true;
}
// Get permissions from profile
// $userPermissions = $user->getPermissions();
echo "TODO: Finalize permissions system !";
$userPermissions = array('*'); // FIXME !
$permissionsFound = true;
// User have all permissions ?
if (in_array('*', $userPermissions))
return true;
// Check that user's permissions matches required permissions
foreach($permissions as $permission) {
if (! in_array($permission, $userPermissions)) {
$permissionsFound = false;
break;
}
}
return $permissionsFound;
}
}
return false;

View File

@@ -47,7 +47,8 @@ class Auth extends BaseLoop
{
return new ArgumentCollection(
Argument::createAnyTypeArgument('roles', null, true),
Argument::createAnyTypeArgument('permissions')
Argument::createAnyTypeArgument('permissions'),
Argument::createAnyTypeArgument('context', 'front', false)
);
}
@@ -72,16 +73,20 @@ class Auth extends BaseLoop
*/
public function exec(&$pagination)
{
$context = $this->getContext();
$roles = $this->_explode($this->getRoles());
$permissions = $this->_explode($this->getPermissions());
$loopResult = new LoopResult();
try {
$this->securityContext->isGranted($roles, $permissions == null ? array() : $permissions);
$this->securityContext->setContext($context);
// Create an empty row: loop is no longer empty :)
$loopResult->addRow(new LoopResultRow());
if (true === $this->securityContext->isGranted($roles, $permissions == null ? array() : $permissions)) {
// Create an empty row: loop is no longer empty :)
$loopResult->addRow(new LoopResultRow());
}
}
catch (\Exception $ex) {
// Not granted, loop is empty

View File

@@ -25,6 +25,7 @@ namespace Thelia\Core\Template\Smarty\Assets;
use Thelia\Core\Template\Assets\AsseticHelper;
use Thelia\Model\ConfigQuery;
use Thelia\Tools\URL;
class SmartyAssetsManager
{
@@ -72,7 +73,7 @@ class SmartyAssetsManager
$url = $this->assetic_manager->asseticize(
$asset_dir.'/'.$asset_file,
$this->web_root."/".$this->path_relative_to_web_root,
ConfigQuery::read('base_url', '/') . $this->path_relative_to_web_root,
URL::absoluteUrl($this->path_relative_to_web_root),
$assetType,
$filters,
$debug

View File

@@ -58,7 +58,6 @@ class Form implements SmartyPluginInterface
{
protected $request;
protected $form;
protected $formDefinition = array();
public function __construct(Request $request)
@@ -87,67 +86,74 @@ class Form implements SmartyPluginInterface
throw new \InvalidArgumentException("Missing 'name' parameter in form arguments");
}
$instance = $this->getInstance($params['name']);
$form = $instance->getForm();
$instance = $this->createInstance($params['name']);
if (
true === $this->request->getSession()->get("form_error", false) &&
$this->request->getSession()->get("form_name") == $instance->getName())
{
$form->bind($this->request);
$this->request->getSession()->set("form_error", false);
// Check if session contains our form
$errorForm = $this->request->getSession()->getErrorFormName();
if ($errorForm == $instance->getName()) {
// Bind form with current request to get error messages and field values.
$instance->getForm()->bind($this->request);
// Remove the form from the session
$this->request->getSession()->clearErrorFormName();
}
$template->assign("form", $form->createView());
} else {
$instance->createView();
$template->assign("form", $instance);
}
else {
return $content;
}
}
public function formRender($params, $content, \Smarty_Internal_Template $template, &$repeat)
public function renderFormField($params, $content, \Smarty_Internal_Template $template, &$repeat)
{
if ($repeat) {
$form = $params["form"];
$formFieldView = $this->getFormFieldView($params);
if (! $form instanceof \Symfony\Component\Form\FormView) {
throw new \InvalidArgumentException("form parameter in form_field block must be an instance of
Symfony\Component\Form\FormView");
$template->assign("options", $formFieldView->vars);
$template->assign("name", $formFieldView->vars["full_name"]);
$template->assign("value", $formFieldView->vars["value"]);
$template->assign("label", $formFieldView->vars["label"]);
$errors = $formFieldView->vars["errors"];
$template->assign("error", empty($errors) ? false : true);
if (! empty($errors)) {
$this->assignFieldErrorVars($template, $errors);
}
$template->assign("options", $form->vars);
$template->assign("name", $form->vars["full_name"]);
$template->assign("value", $form->vars["value"]);
$template->assign("label", $form->vars["label"]);
$template->assign("error", empty($form->vars["errors"]) ? false : true);
$attr = array();
foreach ($form->vars["attr"] as $key => $value) {
foreach ($formFieldView->vars["attr"] as $key => $value) {
$attr[] = sprintf('%s="%s"', $key, $value);
}
$template->assign("attr", implode(" ", $attr));
$form->setRendered();
} else {
$formFieldView->setRendered();
}
else {
return $content;
}
}
public function formRenderHidden($params, \Smarty_Internal_Template $template)
public function renderHiddenFormField($params, \Smarty_Internal_Template $template)
{
$form = $params["form"];
$field = '<input type="hidden" name="%s" value="%s">';
if (! $form instanceof \Symfony\Component\Form\FormView) {
throw new \InvalidArgumentException("form parameter in form_field_hidden function must be an instance of
Symfony\Component\Form\FormView");
}
$instance = $this->getInstanceFromParams($params);
$formView = $instance->getView();
$return = "";
foreach ($form->getIterator() as $row) {
foreach ($formView->getIterator() as $row) {
if ($this->isHidden($row) && $row->isRendered() === false) {
$return .= sprintf($field, $row->vars["full_name"], $row->vars["value"]);
}
@@ -156,53 +162,79 @@ class Form implements SmartyPluginInterface
return $return;
}
protected function isHidden(FormView $formView)
{
return array_search("hidden", $formView->vars["block_prefixes"]);
}
public function formEnctype($params, \Smarty_Internal_Template $template)
{
$form = $params["form"];
$instance = $this->getInstanceFromParams($params);
if (! $form instanceof \Symfony\Component\Form\FormView) {
throw new \InvalidArgumentException("form parameter in form_enctype function must be an instance of
Symfony\Component\Form\FormView");
}
$formView = $instance->getForm();
if ($form->vars["multipart"]) {
if ($formView->vars["multipart"]) {
return sprintf('%s="%s"',"enctype", "multipart/form-data");
}
}
public function formError($params, $content, \Smarty_Internal_Template $template, &$repeat)
{
$formFieldView = $this->getFormFieldView($params);
$form = $params["form"];
if (! $form instanceof \Symfony\Component\Form\FormView) {
throw new \InvalidArgumentException("form parameter in form_error block must be an instance of
Symfony\Component\Form\FormView");
}
$errors = $formFieldView->vars["errors"];
if (empty($form->vars["errors"])) {
if (empty($errors)) {
return "";
}
if ($repeat) {
$error = $form->vars["errors"];
$template->assign("message", $error[0]->getMessage());
$template->assign("parameters", $error[0]->getMessageParameters());
$template->assign("pluralization", $error[0]->getMessagePluralization());
} else {
$this->assignFieldErrorVars($template, $errors);
}
else {
return $content;
}
}
public function getInstance($name)
protected function assignFieldErrorVars(\Smarty_Internal_Template $template, array $errors)
{
$template->assign("message", $errors[0]->getMessage());
$template->assign("parameters", $errors[0]->getMessageParameters());
$template->assign("pluralization", $errors[0]->getMessagePluralization());
}
protected function isHidden(FormView $formView)
{
return array_search("hidden", $formView->vars["block_prefixes"]);
}
protected function getFormFieldView($params) {
$instance = $this->getInstanceFromParams($params);
if (! isset($params['field']))
throw new \InvalidArgumentException("'field' parameter is missing");
$fieldName = $params['field'];
if (empty($instance->getView()[$fieldName]))
throw new \InvalidArgumentException(sprintf("Field name '%s' not found in form %s", $fieldName, $instance->getName()));
return $instance->getView()[$fieldName];
}
protected function getInstanceFromParams($params) {
if (empty($params['form'])) {
throw new \InvalidArgumentException("Missing 'form' parameter in form arguments");
}
$instance = $params["form"];
if (! $instance instanceof \Thelia\Form\BaseForm) {
throw new \InvalidArgumentException(sprintf("form parameter in form_field block must be an instance of
\Thelia\Form\BaseForm, instance of %s found", get_class($instance)));
}
return $instance;
}
protected function createInstance($name)
{
if (!isset($this->formDefinition[$name])) {
throw new ElementNotFoundException(sprintf("%s form does not exists", $name));
@@ -210,7 +242,6 @@ class Form implements SmartyPluginInterface
$class = new \ReflectionClass($this->formDefinition[$name]);
return $class->newInstance(
$this->request,
"form"
@@ -224,8 +255,8 @@ class Form implements SmartyPluginInterface
{
return array(
new SmartyPluginDescriptor("block", "form", $this, "generateForm"),
new SmartyPluginDescriptor("block", "form_field", $this, "formRender"),
new SmartyPluginDescriptor("function", "form_field_hidden", $this, "formRenderHidden"),
new SmartyPluginDescriptor("block", "form_field", $this, "renderFormField"),
new SmartyPluginDescriptor("function", "form_hidden_fields", $this, "renderHiddenFormField"),
new SmartyPluginDescriptor("function", "form_enctype", $this, "formEnctype"),
new SmartyPluginDescriptor("block", "form_error", $this, "formError")
);

View File

@@ -0,0 +1,59 @@
<?php
use Thelia\Tools\URL;
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
namespace Thelia\Core\Template\Smarty\Plugins;
use Thelia\Core\Template\Smarty\SmartyPluginDescriptor;
use Thelia\Core\Template\Smarty\SmartyPluginInterface;
use Thelia\Tools\URL;
class UrlGenerator implements SmartyPluginInterface
{
/**
* Process url generator function
*
* @param array $params
* @param unknown $smarty
* @return string no text is returned.
*/
public function generateUrlFunction($params, &$smarty)
{
// the path to process
$path =trim($params['path']);
return URL::absoluteUrl($path);
}
/**
* Define the various smarty plugins hendled by this class
*
* @return an array of smarty plugin descriptors
*/
public function getPluginDescriptors()
{
return array(
new SmartyPluginDescriptor('function', 'url', $this, 'generateUrlFunction')
);
}
}