Browse Source

Fix form exception for ACL editor

Meyer Baptiste 10 years ago
parent
commit
11ead1e369

+ 65 - 0
Form/Type/AclMatrixType.php

@@ -0,0 +1,65 @@
+<?php
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ *
+ */
+
+namespace Sonata\AdminBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+
+/**
+ * This type define an ACL matrix
+ *
+ * @author Samuel Roze <samuel@sroze.io>
+ * @author Baptiste Meyer <baptiste@les-tilleuls.coop>
+ */
+class AclMatrixType extends AbstractType
+{
+    /**
+     * {@inheritDoc}
+     */
+    public function buildForm(FormBuilderInterface $builder, array $options)
+    {
+        $aclValueType = $options['acl_value'] instanceof UserInterface ? 'user' : 'role';
+        $aclValueData = $options['acl_value'] instanceof UserInterface ? $options['acl_value']->getUsername() : $options['acl_value'];
+
+        $builder->add($aclValueType, 'hidden', array('data' => $aclValueData));
+
+        foreach ($options['permissions'] as $permission => $attributes) {
+            $builder->add($permission, 'checkbox', $attributes);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function setDefaultOptions(OptionsResolverInterface $resolver)
+    {
+        $resolver->setRequired(array(
+            'permissions',
+            'acl_value',
+        ));
+
+        $resolver->setAllowedTypes(array(
+            'permissions' => 'array',
+            'acl_value' => array('string', '\Symfony\Component\Security\Core\User\UserInterface'),
+        ));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function getName()
+    {
+        return 'sonata_type_acl_matrix';
+    }
+}

+ 4 - 80
Resources/views/CRUD/base_acl.html.twig

@@ -18,89 +18,13 @@ file that was distributed with this source code.
     <li>{% include 'SonataAdminBundle:Button:list_button.html.twig' %}</li>
 {% endblock %}
 
+{% import 'SonataAdminBundle:CRUD:base_acl_macro.html.twig' as acl %}
+
 {% block form %}
     {% block form_acl_roles %}
-        <form class="form-horizontal"
-              action="{{ admin.generateUrl('acl', {'id': admin.id(object), 'uniqid': admin.uniqid, 'subclass': app.request.get('subclass')}) }}" {{ form_enctype(aclRolesForm) }}
-              method="POST"
-                {% if not admin_pool.getOption('html5_validate') %}novalidate="novalidate"{% endif %}
-                >
-            {% if aclRolesForm.vars.errors|length > 0 %}
-                <div class="sonata-ba-form-error">
-                    {{ form_errors(aclRolesForm) }}
-                </div>
-            {% endif %}
-
-            <table class="table">
-                <thead>
-                <tr>
-                    <th>{{ "td_role"|trans({}, 'SonataAdminBundle') }}</th>
-                    {% for permission in permissions %}
-                        <th>{{ permission }}</th>
-                    {% endfor %}
-                </tr>
-                </thead>
-                <tbody>
-                {% for role in roles %}
-                    <tr>
-                        <td>{{ role }}</td>
-                        {% for permission in permissions %}
-                            <td>{{ form_widget(aclRolesForm[role ~ '_' ~ permission]) }}</td>
-                        {% endfor %}
-                    </tr>
-                {% endfor %}
-                </tbody>
-            </table>
-
-            {{ form_rest(aclRolesForm) }}
-
-            {% block formactions_acl_roles %}
-                <div class="well well-small form-actions">
-                    <input class="btn btn-primary" type="submit" name="btn_create_and_edit" value="{{ 'btn_update_acl'|trans({}, 'SonataAdminBundle') }}">
-                </div>
-            {% endblock %}
-        </form>
+        {{ acl.render_form(aclRolesForm, permissions, 'td_role', admin, admin_pool, object) }}
     {% endblock %}
     {% block form_acl_users %}
-        <form class="form-horizontal"
-                  action="{{ admin.generateUrl('acl', {'id': admin.id(object), 'uniqid': admin.uniqid, 'subclass': app.request.get('subclass')}) }}" {{ form_enctype(aclUsersForm) }}
-                  method="POST"
-                  {% if not admin_pool.getOption('html5_validate') %}novalidate="novalidate"{% endif %}
-                  >
-            {% if aclUsersForm.vars.errors|length > 0 %}
-                <div class="sonata-ba-form-error">
-                    {{ form_errors(aclUsersForm) }}
-                </div>
-            {% endif %}
-
-            <table class="table">
-                <thead>
-                    <tr>
-                        <th>{{ "td_username"|trans({}, 'SonataAdminBundle') }}</th>
-                        {% for permission in permissions %}
-                            <th>{{ permission }}</th>
-                        {% endfor %}
-                    </tr>
-                </thead>
-                <tbody>
-                {% for user in users %}
-                    <tr>
-                        <td>{{ user }}</td>
-                        {% for permission in permissions %}
-                            <td>{{ form_widget(aclUsersForm[user.username ~ '_' ~ permission]) }}</td>
-                        {% endfor %}
-                    </tr>
-                {% endfor %}
-                </tbody>
-            </table>
-
-            {{ form_rest(aclUsersForm) }}
-
-            {% block formactions_acl_users %}
-                <div class="well well-small form-actions">
-                    <input class="btn btn-primary" type="submit" name="btn_create_and_edit" value="{{ 'btn_update_acl'|trans({}, 'SonataAdminBundle') }}">
-                </div>
-            {% endblock %}
-        </form>
+        {{ acl.render_form(aclUsersForm, permissions, 'td_username', admin, admin_pool, object) }}
     {% endblock %}
 {% endblock %}

+ 55 - 0
Resources/views/CRUD/base_acl_macro.html.twig

@@ -0,0 +1,55 @@
+{#
+
+This file is part of the Sonata package.
+
+(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+
+For the full copyright and license information, please view the LICENSE
+file that was distributed with this source code.
+
+#}
+
+{% macro render_form(form, permissions, td_type, admin, admin_pool, object) %}
+    <form class="form-horizontal"
+          action="{{ admin.generateUrl('acl', {'id': admin.id(object), 'uniqid': admin.uniqid, 'subclass': app.request.get('subclass')}) }}" {{ form_enctype(form) }}
+          method="POST"
+            {% if not admin_pool.getOption('html5_validate') %}novalidate="novalidate"{% endif %}
+            >
+        {% if form.vars.errors|length > 0 %}
+            <div class="sonata-ba-form-error">
+                {{ form_errors(form) }}
+            </div>
+        {% endif %}
+
+        <table class="table">
+            <thead>
+            <tr>
+                <th>{{ td_type|trans({}, 'SonataAdminBundle') }}</th>
+                {% for permission in permissions %}
+                    <th>{{ permission }}</th>
+                {% endfor %}
+            </tr>
+            </thead>
+            <tbody>
+                {% for child in form.children if child.vars.name != '_token' %}
+                    <tr>
+                        <td>
+                            {% set typeChild = child['role'] is defined ? child['role'] : child['user'] %}
+                            {{ typeChild.vars.value }}
+                            {{ form_widget(typeChild) }}
+                        </td>
+                        {% for permission in permissions %}
+                            <td>{{ form_widget(child[permission]) }}</td>
+                        {% endfor %}
+                    </tr>
+                {% endfor %}
+            </tbody>
+        </table>
+
+        {{ form_row(form._token) }}
+
+        <div class="well well-small form-actions">
+            <input class="btn btn-primary" type="submit" name="btn_create_and_edit" value="{{ 'btn_update_acl'|trans({}, 'SonataAdminBundle') }}">
+        </div>
+    </form>
+{% endmacro %}

+ 50 - 0
Tests/Form/Type/AclMatrixTypeTest.php

@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of the Sonata package.
+ *
+ * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Sonata\AdminBundle\Tests\Form\Type;
+
+use Sonata\AdminBundle\Form\Type\AclMatrixType;
+use Symfony\Component\Form\Test\TypeTestCase;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+
+/**
+ * @author Baptiste Meyer <baptiste@les-tilleuls.coop>
+ */
+class AclMatrixTypeTest extends TypeTestCase
+{
+    public function testGetDefaultOptions()
+    {
+        $type = new AclMatrixType();
+        $user = $this->getMock('Symfony\Component\Security\Core\User\UserInterface');
+
+        $permissions = array(
+            'OWNER' => array(
+                'required' => false,
+                'data' => false,
+                'disabled' => false,
+                'attr' => array(),
+            ),
+        );
+
+        $optionResolver = new OptionsResolver();
+
+        $type->setDefaultOptions($optionResolver);
+
+        $options = $optionResolver->resolve(array(
+            'acl_value' => $user,
+            'permissions' => $permissions,
+        ));
+
+        $this->assertInstanceOf('Symfony\Component\Security\Core\User\UserInterface', $options['acl_value']);
+        $this->assertEquals($user, $options['acl_value']);
+        $this->assertEquals($permissions, $options['permissions']);
+    }
+}

+ 33 - 27
Util/AdminObjectAclManipulator.php

@@ -11,6 +11,7 @@
 
 namespace Sonata\AdminBundle\Util;
 
+use Sonata\AdminBundle\Form\Type\AclMatrixType;
 use Symfony\Component\Form\Form;
 use Symfony\Component\Form\FormBuilderInterface;
 use Symfony\Component\Form\FormFactoryInterface;
@@ -24,6 +25,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
  * A manipulator for updating ACL related to an object.
  *
  * @author Kévin Dunglas <kevin@les-tilleuls.coop>
+ * @author Baptiste Meyer <baptiste@les-tilleuls.coop>
  */
 class AdminObjectAclManipulator
 {
@@ -155,19 +157,36 @@ class AdminObjectAclManipulator
      */
     protected function buildAcl(AdminObjectAclData $data, Form $form, \Traversable $aclValues)
     {
+        $masks = $data->getMasks();
+        $acl = $data->getAcl();
+        $matrices = $form->getData();
+
         foreach ($aclValues as $aclValue) {
-            $securityIdentity = $this->getSecurityIdentity($aclValue);
+            foreach ($matrices as $key => $matrix) {
+                if ($aclValue instanceof UserInterface) {
+                    if (array_key_exists('user', $matrix) && $aclValue->getUsername() === $matrix['user']) {
+                        $matrices[$key]['acl_value'] = $aclValue;
+                    }
+                } elseif (array_key_exists('role', $matrix) && $aclValue === $matrix['role']) {
+                    $matrices[$key]['acl_value'] = $aclValue;
+                }
+            }
+        }
 
+        foreach ($matrices as $matrix) {
+            if (!isset($matrix['acl_value'])) {
+                continue;
+            }
+
+            $securityIdentity = $this->getSecurityIdentity($matrix['acl_value']);
             $maskBuilder = new $this->maskBuilderClass();
+
             foreach ($data->getUserPermissions() as $permission) {
-                if ($form->get($this->getFieldName($aclValue, $permission))->getData()) {
+                if (isset($matrix[$permission]) && $matrix[$permission] === true) {
                     $maskBuilder->add($permission);
                 }
             }
 
-            $masks = $data->getMasks();
-            $acl = $data->getAcl();
-
             // Restore OWNER and MASTER permissions
             if (!$data->isOwner()) {
                 foreach ($data->getOwnerPermissions() as $permission) {
@@ -221,8 +240,9 @@ class AdminObjectAclManipulator
         $masks = $data->getMasks();
         $securityInformation = $data->getSecurityInformation();
 
-        foreach ($aclValues as $aclValue) {
+        foreach ($aclValues as $key => $aclValue) {
             $securityIdentity = $this->getSecurityIdentity($aclValue);
+            $permissions = array();
 
             foreach ($data->getUserPermissions() as $permission) {
                 try {
@@ -240,17 +260,15 @@ class AdminObjectAclManipulator
                     $attr['disabled'] = 'disabled';
                 }
 
-                $formBuilder->add(
-                    $this->getFieldName($aclValue, $permission),
-                    'checkbox',
-                    array(
-                        'required' => false,
-                        'data' => $checked,
-                        'disabled' => array_key_exists('disabled', $attr),
-                        'attr' => $attr
-                    )
+                $permissions[$permission] = array(
+                    'required' => false,
+                    'data' => $checked,
+                    'disabled' => array_key_exists('disabled', $attr),
+                    'attr' => $attr,
                 );
             }
+
+            $formBuilder->add($key, new AclMatrixType(), array('permissions' => $permissions, 'acl_value' => $aclValue));
         }
 
         return $formBuilder->getForm();
@@ -269,16 +287,4 @@ class AdminObjectAclManipulator
             : new RoleSecurityIdentity($aclValue)
         ;
     }
-
-    /**
-     * Gets the form field name
-     *
-     * @param  string|\Symfony\Component\Security\Core\User\UserInterface $aclValue
-     * @param  string                                                     $permission
-     * @return string
-     */
-    protected function getFieldName($aclValue, $permission)
-    {
-        return sprintf('%s_%s', ($aclValue instanceof UserInterface) ? $aclValue->getUsername() : $aclValue, $permission);
-    }
 }