improve CRUD and switch to symfony3

This commit is contained in:
Julien Fastré 2018-06-05 14:53:30 +02:00
parent a1ee85b0c0
commit eda8f2c033
11 changed files with 167 additions and 78 deletions

View File

@ -11,6 +11,8 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
/** /**
* @Route("/{_locale}/person/{person}/document") * @Route("/{_locale}/person/{person}/document")
@ -30,11 +32,11 @@ class DocumentPersonController extends Controller
throw $this->createNotFoundException('Person not found'); throw $this->createNotFoundException('Person not found');
} }
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); $this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
$reachableScopes = $this->get('chill.main.security.authorization.helper') $reachableScopes = $this->get('chill.main.security.authorization.helper')
->getReachableScopes( ->getReachableScopes(
$this->getUser(), new Role('CHILL_PERSON_DOCUMENT_SEE'), $this->getUser(), new Role(PersonDocumentVoter::SEE),
$person->getCenter()); $person->getCenter());
$documents = $em $documents = $em
@ -61,11 +63,10 @@ class DocumentPersonController extends Controller
throw $this->createNotFoundException('person not found'); throw $this->createNotFoundException('person not found');
} }
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); $this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
$user = $this->get('security.context')->getToken()->getUser();
$document = new PersonDocument(); $document = new PersonDocument();
$document->setUser($user); $document->setUser($this->getUser());
$document->setPerson($person); $document->setPerson($person);
$document->setDate(new \DateTime('Now')); $document->setDate(new \DateTime('Now'));
@ -115,8 +116,7 @@ class DocumentPersonController extends Controller
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
$this->denyAccessUnlessGranted('CHILL_PERSON_DOCUMENT_UPDATE', $document); $this->denyAccessUnlessGranted('CHILL_PERSON_DOCUMENT_UPDATE', $document);
$user = $this->get('security.context')->getToken()->getUser(); $document->setUser($this->getUser());
$document->setUser($user);
$document->setDate(new \DateTime('Now')); $document->setDate(new \DateTime('Now'));
$form = $this->createForm( $form = $this->createForm(

View File

@ -7,6 +7,7 @@ use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader; use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
/** /**
* This is the class that loads and manages your bundle configuration * This is the class that loads and manages your bundle configuration
@ -30,6 +31,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
public function prepend(ContainerBuilder $container) public function prepend(ContainerBuilder $container)
{ {
$this->prependRoute($container); $this->prependRoute($container);
$this->prependAuthorization($container);
} }
protected function prependRoute(ContainerBuilder $container) protected function prependRoute(ContainerBuilder $container)
@ -43,4 +45,16 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
) )
)); ));
} }
protected function prependAuthorization(ContainerBuilder $container)
{
$container->prependExtensionConfig('security', array(
'role_hierarchy' => array(
PersonDocumentVoter::UPDATE => [PersonDocumentVoter::SEE_DETAILS],
PersonDocumentVoter::CREATE => [PersonDocumentVoter::SEE_DETAILS],
PersonDocumentVoter::DELETE => [PersonDocumentVoter::SEE_DETAILS],
PersonDocumentVoter::SEE_DETAILS => [PersonDocumentVoter::SEE],
)
));
}
} }

View File

@ -28,7 +28,7 @@ class Document implements HasScopeInterface
/** /**
* @ORM\Column(type="text") * @ORM\Column(type="text")
*/ */
private $description; private $description = '';
/** /**
* @ORM\ManyToOne(targetEntity="Chill\DocStoreBundle\Entity\DocumentCategory") * @ORM\ManyToOne(targetEntity="Chill\DocStoreBundle\Entity\DocumentCategory")
@ -83,9 +83,9 @@ class Document implements HasScopeInterface
return $this->description; return $this->description;
} }
public function setDescription(string $description): self public function setDescription($description): self
{ {
$this->description = $description; $this->description = (string) $description;
return $this; return $this;
} }

View File

@ -17,12 +17,14 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInt
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Persistence\ObjectManager;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
class PersonDocumentType extends AbstractType class PersonDocumentType extends AbstractType
{ {
use AppendScopeChoiceTypeTrait;
/** /**
* the user running this form * the user running this form
* *
@ -49,18 +51,9 @@ class PersonDocumentType extends AbstractType
protected $translatableStringHelper; protected $translatableStringHelper;
public function __construct( public function __construct(
TokenStorageInterface $tokenStorage,
AuthorizationHelper $authorizationHelper,
ObjectManager $om,
TranslatableStringHelper $translatableStringHelper TranslatableStringHelper $translatableStringHelper
) )
{ {
if (!$tokenStorage->getToken()->getUser() instanceof User) {
throw new \RuntimeException("you should have a valid user");
}
$this->user = $tokenStorage->getToken()->getUser();
$this->authorizationHelper = $authorizationHelper;
$this->om = $om;
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
} }
@ -68,12 +61,16 @@ class PersonDocumentType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder $builder
->add('title') ->add('title', TextType::class)
->add('description') ->add('description', TextareaType::class, [
'required' => false
])
->add('content') ->add('content')
//->add('center') ->add('scope', ScopePickerType::class, [
->add('scope') 'center' => $options['center'],
->add('date', 'date', array('required' => false, 'widget' => 'single_text', 'format' => 'dd-MM-yyyy')) 'role' => $options['role']
])
->add('date', ChillDateType::class)
->add('category', EntityType::class, array( ->add('category', EntityType::class, array(
'class' => 'ChillDocStoreBundle:DocumentCategory', 'class' => 'ChillDocStoreBundle:DocumentCategory',
'query_builder' => function (EntityRepository $er) { 'query_builder' => function (EntityRepository $er) {
@ -87,10 +84,6 @@ class PersonDocumentType extends AbstractType
)) ))
; ;
$this->appendScopeChoices($builder, $options['role'],
$options['center'], $this->user, $this->authorizationHelper,
$this->translatableStringHelper, $this->om);
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
@ -98,7 +91,10 @@ class PersonDocumentType extends AbstractType
$resolver->setDefaults([ $resolver->setDefaults([
'data_class' => Document::class, 'data_class' => Document::class,
]); ]);
$this->appendScopeChoicesOptions($resolver); $resolver->setRequired(['role', 'center'])
->setAllowedTypes('role', [ \Symfony\Component\Security\Core\Role\Role::class ])
->setAllowedTypes('center', [ \Chill\MainBundle\Entity\Center::class ])
;
} }
} }

View File

@ -11,9 +11,6 @@ services:
Chill\DocStoreBundle\Form\PersonDocumentType: Chill\DocStoreBundle\Form\PersonDocumentType:
class: Chill\DocStoreBundle\Form\PersonDocumentType class: Chill\DocStoreBundle\Form\PersonDocumentType
arguments: arguments:
- "@security.token_storage"
- "@chill.main.security.authorization.helper"
- "@doctrine.orm.entity_manager"
- "@chill.main.helper.translatable_string" - "@chill.main.helper.translatable_string"
tags: tags:
- { name: form.type, alias: chill_docstorebundle_form_document } - { name: form.type, alias: chill_docstorebundle_form_document }

View File

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Create schema for chill_doc
*/
final class Version20180605102533 extends AbstractMigration
{
public function up(Schema $schema) : void
{
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('CREATE SCHEMA chill_doc');
$this->addSql('CREATE SEQUENCE chill_doc.person_document_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_doc.document_category (bundle_id VARCHAR(255) NOT NULL, id_inside_bundle INT NOT NULL, document_class VARCHAR(255) NOT NULL, name JSON NOT NULL, PRIMARY KEY(bundle_id, id_inside_bundle))');
$this->addSql('COMMENT ON COLUMN chill_doc.document_category.name IS \'(DC2Type:json_array)\'');
$this->addSql('CREATE TABLE chill_doc.person_document (id INT NOT NULL, category_bundle_id VARCHAR(255) DEFAULT NULL, category_id_inside_bundle INT DEFAULT NULL, scope_id INT DEFAULT NULL, user_id INT DEFAULT NULL, person_id INT DEFAULT NULL, title TEXT NOT NULL, description TEXT NOT NULL, content TEXT NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_41DA53C369A0BE36EF62EFC ON chill_doc.person_document (category_bundle_id, category_id_inside_bundle)');
$this->addSql('CREATE INDEX IDX_41DA53C682B5931 ON chill_doc.person_document (scope_id)');
$this->addSql('CREATE INDEX IDX_41DA53CA76ED395 ON chill_doc.person_document (user_id)');
$this->addSql('CREATE INDEX IDX_41DA53C217BBB47 ON chill_doc.person_document (person_id)');
$this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C369A0BE36EF62EFC FOREIGN KEY (category_bundle_id, category_id_inside_bundle) REFERENCES chill_doc.document_category (bundle_id, id_inside_bundle) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C682B5931 FOREIGN KEY (scope_id) REFERENCES scopes (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53CA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema) : void
{
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('DROP SCHEMA chill_doc CASCADE');
}
}

View File

@ -1,4 +1 @@
{{ form_start(form) }} {{ form_widget(form) }}
{{ form_widget(form) }}
<button class="sc-button bt-create">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}

View File

@ -23,11 +23,26 @@
{% block personcontent %} {% block personcontent %}
<h1>{{ 'Edit Document' | trans }}</h1> <h1>{{ 'Edit Document' | trans }}</h1>
{{ form_start(form) }}
{{ include('ChillDocStoreBundle:PersonDocument:_form.html.twig', {'button_label': 'Update'}) }} {{ include('ChillDocStoreBundle:PersonDocument:_form.html.twig', {'button_label': 'Update'}) }}
<a href="{{ path('person_document_index', {'person': person.id}) }} " class="sc-button"> <ul class="record_actions">
{{ 'Back to list' | trans }} <li class="cancel">
</a> <a href="{{ path('person_document_index', {'person': person.id}) }}" class="sc-button bt-cancel">
{{ 'Back to the list' | trans }}
</a>
</li>
<li class="edit">
<button class="sc-button bt-edit">{{ 'Edit'|trans }}</button>
</li>
{% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %}
<li class="delete">
{{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }}
</li>
{% endif %}
</ul>
{{ form_end(form) }}
{{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }}
{% endblock %} {% endblock %}

View File

@ -28,10 +28,8 @@
<thead> <thead>
<tr> <tr>
<th>{{ 'Title' | trans }}</th> <th>{{ 'Title' | trans }}</th>
<th>{{ 'Description' | trans }}</th> <th>{{ 'Category'|trans }}</th>
<th>{{ 'Content' | trans }}</th> <th>{{ 'Circle' | trans }}</th>
<th>{{ 'Last modification by' | trans }}</th>
<th>{{ 'Last update' | trans }}</th>
<th>{{ 'Actions' | trans }}</th> <th>{{ 'Actions' | trans }}</th>
</tr> </tr>
</thead> </thead>
@ -39,28 +37,42 @@
{% for document in documents %} {% for document in documents %}
<tr> <tr>
<td>{{ document.title }}</td> <td>{{ document.title }}</td>
<td>{{ document.description }}</td> <td>{{ document.category.name|localize_translatable_string }}</td>
<td>{{ document.content }}</td> <td>{{ document.scope.name|localize_translatable_string }}</td>
<td>{{ document.user }}</td>
<td>{{ document.date ? document.date|date('Y-m-d H:i:s') : '' }}</td>
<td> <td>
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="sc-button"> <ul class="record_actions">
show {% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
</a> <li>
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="sc-button bt-edit"> <a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="sc-button">
edit {{ 'See'|trans }}
</a> </a>
</li>
{% endif %}
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="sc-button bt-edit">
{{ 'Edit'|trans }}
</a>
</li>
{% endif %}
</ul>
</td> </td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="9">no records found</td> <td colspan="9"><span class="chill-no-data-statement">{{ 'Any document found'|trans }}</span></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<a href="{{ path('person_document_new', {'person': person.id}) }}" class="sc-button bt-create"> {% if is_granted('CHILL_PERSON_DOCUMENT_CREATE', person) %}
{{ 'Create new' | trans }} <ul class="record_actions">
</a> <li class="create">
<a href="{{ path('person_document_new', {'person': person.id}) }}" class="sc-button bt-create">
{{ 'Create new document' | trans }}
</a>
</li>
</ul>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -23,9 +23,18 @@
{% block personcontent %} {% block personcontent %}
<h1>{{ 'Create new Document' | trans }}</h1> <h1>{{ 'Create new Document' | trans }}</h1>
{{ form_start(form) }}
{{ include('ChillDocStoreBundle:PersonDocument:_form.html.twig') }} {{ include('ChillDocStoreBundle:PersonDocument:_form.html.twig') }}
<a href="{{ path('person_document_index', {'person': person.id}) }}" class="sc-button bt-create"> <ul class="record_actions">
{{ 'back to list' | trans }} <li class="cancel">
</a> <a href="{{ path('person_document_index', {'person': person.id}) }}" class="sc-button bt-cancel">
{{ 'Back to the list' | trans }}
</a>
</li>
<li class="create">
<button class="sc-button bt-create">{{ 'Create'|trans }}</button>
</li>
</ul>
{{ form_end(form) }}
{% endblock %} {% endblock %}

View File

@ -21,12 +21,14 @@ namespace Chill\DocStoreBundle\Security\Authorization;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter; use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\ProvideRoleInterface; use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\PersonBundle\Entity\Person;
/** /**
* *
*/ */
class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleInterface class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{ {
const CREATE = 'CHILL_PERSON_DOCUMENT_CREATE'; const CREATE = 'CHILL_PERSON_DOCUMENT_CREATE';
const SEE = 'CHILL_PERSON_DOCUMENT_SEE'; const SEE = 'CHILL_PERSON_DOCUMENT_SEE';
@ -44,16 +46,29 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleInter
{ {
$this->helper = $helper; $this->helper = $helper;
} }
protected function getSupportedAttributes() public function getRoles()
{ {
return array(self::CREATE, self::SEE, self::UPDATE, self::DELETE, return [
self::SEE_DETAILS); self::CREATE,
self::SEE,
self::SEE_DETAILS,
self::UPDATE,
self::DELETE
];
} }
protected function getSupportedClasses() protected function supports($attribute, $subject)
{ {
return array('Chill\DocStoreBundle\Entity\PersonDocument'); if (\in_array($attribute, $this->getRoles()) && $subject instanceof PersonDocument) {
return true;
}
if ($subject instanceof Person && $attribute === self::CREATE) {
return true;
}
return false;
} }
protected function isGranted($attribute, $report, $user = null) protected function isGranted($attribute, $report, $user = null)
@ -65,11 +80,6 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleInter
return $this->helper->userHasAccess($user, $report, $attribute); return $this->helper->userHasAccess($user, $report, $attribute);
} }
public function getRoles()
{
return $this->getSupportedAttributes();
}
public function getRolesWithoutScope() public function getRolesWithoutScope()
{ {
return array(); return array();