Browse Source

Ajout de système de traduction / gallery / file manager

develop
Fab 3 years ago
parent
commit
29fe81737c
36 changed files with 1157 additions and 88 deletions
  1. +42
    -0
      Controller/Admin/AbstractCrudController.php
  2. +1
    -1
      Controller/Admin/DashboardController.php
  3. +31
    -0
      DependencyInjection/Configuration.php
  4. +10
    -0
      DependencyInjection/LcAdminExtension.php
  5. +19
    -0
      Entity/Translation/EntityTranslation.php
  6. +63
    -0
      EventSubscriber/Translation/TranslationEasyAdminSubscriber.php
  7. +41
    -0
      Field/CKEditorField.php
  8. +79
    -0
      Field/CollectionField.php
  9. +50
    -0
      Field/FileManagerField.php
  10. +85
    -0
      Field/GalleryManagerField.php
  11. +85
    -0
      Form/Type/FileManagerType.php
  12. +2
    -4
      IModel/Cms/FileInterface.php
  13. +9
    -0
      IModel/Translation/TranslatableInterface.php
  14. +11
    -0
      Manager/EntityManager.php
  15. +3
    -0
      Model/Cms/AbstractDocument.php
  16. +72
    -0
      Model/Cms/File.php
  17. +0
    -52
      Model/Cms/ImageTrait.php
  18. +53
    -0
      Model/Translation/TranslatableTrait.php
  19. +16
    -1
      README.md
  20. +53
    -30
      Repository/BaseRepository.php
  21. +20
    -0
      Repository/Cms/FileRepository.php
  22. +0
    -0
      Resources/config/services.yml
  23. +93
    -0
      Resources/public/js/form-type-collection.js
  24. +29
    -0
      Resources/public/js/form-type-file-manager.js
  25. +13
    -0
      Resources/public/js/jquery-ui/jquery-ui.min.js
  26. +57
    -0
      Resources/public/js/utils.js
  27. +21
    -0
      Resources/views/crud/action/translatable.html.twig
  28. +8
    -0
      Resources/views/crud/edit.html.twig
  29. +11
    -0
      Resources/views/crud/field/collection.html.twig
  30. +9
    -0
      Resources/views/crud/field/gallery.html.twig
  31. +11
    -0
      Resources/views/crud/field/image.html.twig
  32. +18
    -0
      Resources/views/crud/filemanager/file-manager-modal.html.twig
  33. +100
    -0
      Resources/views/crud/form_theme.html.twig
  34. +9
    -0
      Resources/views/crud/new.html.twig
  35. +9
    -0
      Resources/views/dashboard.html.twig
  36. +24
    -0
      composer.json

+ 42
- 0
Controller/Admin/AbstractCrudController.php View File

@@ -3,12 +3,54 @@
namespace Lc\AdminBundle\Controller\Admin;

use App\Entity\Page;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Assets;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController as EaAbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Lc\AdminBundle\IModel\Translation\TranslatableInterface;

abstract class AbstractCrudController extends EaAbstractCrudController
{

public function configureActions(Actions $actions): Actions
{
if (in_array(TranslatableInterface::class, class_implements($this->getEntityFqcn()))) {
$actions->update(Crud::PAGE_INDEX, Action::EDIT, function (Action $action) {
return $action->setTemplatePath('@LcAdmin/crud/action/translatable.html.twig');
});
}

return $actions;
}

public function configureCrud(Crud $crud): Crud
{
return $crud
->overrideTemplates([
'crud/edit' => '@LcAdmin/crud/edit.html.twig',
'crud/new' => '@LcAdmin/crud/new.html.twig'
])
// don't forget to add EasyAdmin's form theme at the end of the list
// (otherwise you'll lose all the styles for the rest of form fields)
->setFormThemes(['@LcAdmin/crud/form_theme.html.twig', '@FOSCKEditor/Form/ckeditor_widget.html.twig']);
}


public function configureAssets(Assets $assets): Assets
{
return $assets
// adds the CSS and JS assets associated to the given Webpack Encore entry
// it's equivalent to calling encore_entry_link_tags('...') and encore_entry_script_tags('...')
//->addWebpackEncoreEntry('admin-app')

// the argument of these methods is passed to the asset() Twig function
// CSS assets are added just before the closing </head> element
// and JS assets are added just before the closing </body> element
->addJsFile('bundles/lcadmin/js/utils.js');
}

/*
public function configureFields(string $pageName): iterable
{

+ 1
- 1
Controller/Admin/DashboardController.php View File

@@ -15,7 +15,7 @@ class DashboardController extends AbstractDashboardController

public function index(): Response
{
return parent::index();
return $this->render('@LcAdmin/dashboard.html.twig');
}

public function configureDashboard(): Dashboard

+ 31
- 0
DependencyInjection/Configuration.php View File

@@ -0,0 +1,31 @@
<?php

namespace Lc\AdminBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
* This is the class that validates and merges configuration from your app/config files
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('lc_admin');
$rootNode = $treeBuilder->getRootNode();

$rootNode
->children()
->scalarNode('dashboardDefault')->defaultValue('App\Controller\Admin\DashboardController')->end()
->end();


return $treeBuilder;
}
}

+ 10
- 0
DependencyInjection/LcAdminExtension.php View File

@@ -8,11 +8,21 @@ use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;


class LcAdminExtension extends Extension implements PrependExtensionInterface
{


public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);

foreach ($config as $parameter => $value)
$container->setParameter(sprintf('lc_admin.%s', $parameter), $value);

$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
}

public function prepend(ContainerBuilder $container)

+ 19
- 0
Entity/Translation/EntityTranslation.php View File

@@ -0,0 +1,19 @@
<?php
namespace Lc\AdminBundle\Entity\Translation;


use Doctrine\ORM\Mapping as ORM;
use Gedmo\Translatable\Entity\MappedSuperclass\AbstractTranslation;

/**
* @ORM\Table(name="lc_translations_entity", indexes={
* @ORM\Index(name="entity_translation_idx", columns={"locale", "object_class", "field", "foreign_key"})
* })
* @ORM\Entity(repositoryClass="Lc\AdminBundle\Repository\BaseRepository")
*/

class EntityTranslation extends AbstractTranslation
{
//put your code here
}

+ 63
- 0
EventSubscriber/Translation/TranslationEasyAdminSubscriber.php View File

@@ -0,0 +1,63 @@
<?php

namespace Lc\AdminBundle\EventSubscriber\Translation;

use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterCrudActionEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeCrudActionEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent;
use Gedmo\Translatable\TranslatableListener;
use Lc\AdminBundle\IModel\Translation\TranslatableInterface;
use Lc\AdminBundle\Manager\EntityManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use function Symfony\Component\Translation\t;

class TranslationEasyAdminSubscriber implements EventSubscriberInterface
{
protected $em;

public function __construct(EntityManager $entityManager)
{
$this->em = $entityManager;
}

public static function getSubscribedEvents()
{
return [
BeforeCrudActionEvent::class => ['setTranslatableLocale']
];
}

public function setTranslatableLocale(BeforeCrudActionEvent $event)
{

$entity = $event->getAdminContext()->getEntity()->getInstance();
$_locale = $event->getAdminContext()->getRequest()->get('_locale');

if($entity instanceof TranslatableInterface){

//parcours les champs association pour détecter un champ de association qui est traduisible
foreach ($this->em->getClassMetadata(get_class($entity))->getAssociationMappings() as $associationMapping){
if (in_array(TranslatableInterface::class, class_implements($associationMapping["targetEntity"]))){
if($entity->__get($associationMapping['fieldName']) instanceof PersistentCollection){
foreach ($entity->__get($associationMapping['fieldName']) as $item){
$item->setTranslatableLocale($_locale);
$this->em->refresh($item);
}
}else{
if($entity->__get($associationMapping['fieldName'])) {
$entity->__get($associationMapping['fieldName'])->setTranslatableLocale($_locale);
$this->em->refresh($entity->__get($associationMapping['fieldName']));
}
}

}
}
$entity->setTranslatableLocale($_locale);
$this->em->refresh($entity);
}
}


}

+ 41
- 0
Field/CKEditorField.php View File

@@ -0,0 +1,41 @@
<?php

namespace Lc\AdminBundle\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use FOS\CKEditorBundle\Form\Type\CKEditorType;

/**
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
final class CKEditorField implements FieldInterface
{
use FieldTrait;

public const OPTION_NUM_OF_ROWS = 'numOfRows';

public static function new(string $propertyName, ?string $label = null): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setTemplateName('crud/field/text_editor')
->setFormType(CKEditorType::class)
->addCssClass('field-text_editor')
->addCssFiles('bundles/easyadmin/form-type-text-editor.css')
->addJsFiles('bundles/easyadmin/form-type-text-editor.js')
->setCustomOption(self::OPTION_NUM_OF_ROWS, null);
}

public function setNumOfRows(int $rows): self
{
if ($rows < 1) {
throw new \InvalidArgumentException(sprintf('The argument of the "%s()" method must be 1 or higher (%d given).', __METHOD__, $rows));
}

$this->setCustomOption(self::OPTION_NUM_OF_ROWS, $rows);

return $this;
}
}

+ 79
- 0
Field/CollectionField.php View File

@@ -0,0 +1,79 @@
<?php

namespace Lc\AdminBundle\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use Lc\AdminBundle\Form\Type\FileManagerType;
use Lc\AdminBundle\Form\Type\GalleryManagerType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;


class CollectionField implements FieldInterface
{
use FieldTrait;

public const OPTION_ALLOW_ADD = 'allowAdd';
public const OPTION_ALLOW_DELETE = 'allowDelete';
public const OPTION_ENTRY_IS_COMPLEX = 'entryIsComplex';
public const OPTION_ENTRY_TYPE = 'entryType';
public const OPTION_SHOW_ENTRY_LABEL = 'showEntryLabel';

public static function new(string $propertyName, ?string $label = null): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setTemplatePath('@LcAdmin/crud/field/collection.html.twig')
->setFormType(CollectionType::class)
->addCssClass('field-collection')
->addJsFiles('bundles/lcadmin/js/form-type-collection-array.js')
->setFormTypeOption('allow_add', true)
->setFormTypeOption('allow_delete', true)
->setFormTypeOption('entry_options', array('label' => false))
->setFormTypeOption('attr', array('class' => 'field-collection-group'))

//Fixe le bug easyadmin lors de la gestion d'un champ de type array, laisser a false pour une entité
->setFormTypeOption('row_attr', array('data-reindex-key' => true));

}

public function allowAdd(bool $allow = true): self
{
$this->setCustomOption(self::OPTION_ALLOW_ADD, $allow);

return $this;
}

public function allowDelete(bool $allow = true): self
{
$this->setCustomOption(self::OPTION_ALLOW_DELETE, $allow);

return $this;
}

/**
* Set this option to TRUE if the collection items are complex form types
* composed of several form fields (EasyAdmin applies a special rendering to make them look better).
*/
public function setEntryIsComplex(bool $isComplex): self
{
$this->setCustomOption(self::OPTION_ENTRY_IS_COMPLEX, $isComplex);

return $this;
}

public function setEntryType(string $formTypeFqcn): self
{
$this->setCustomOption(self::OPTION_ENTRY_TYPE, $formTypeFqcn);

return $this;
}

public function showEntryLabel(bool $showLabel = true): self
{
$this->setCustomOption(self::OPTION_SHOW_ENTRY_LABEL, $showLabel);

return $this;
}
}

+ 50
- 0
Field/FileManagerField.php View File

@@ -0,0 +1,50 @@
<?php

namespace Lc\AdminBundle\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use Lc\AdminBundle\Form\Type\FileManagerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;

/**
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
final class FileManagerField implements FieldInterface
{
use FieldTrait;

public const OPTION_MAX_LENGTH = 'maxLength';
public const OPTION_RENDER_AS_HTML = 'renderAsHtml';

public static function new(string $propertyName, ?string $label = null): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setTemplatePath('@LcAdmin/crud/field/image.html.twig')
->addJsFiles('bundles/lcadmin/js/form-type-collection.js')
->setFormType(FileManagerType::class)
->addCssClass('field-text')
->setCustomOption(self::OPTION_MAX_LENGTH, null)
->setCustomOption(self::OPTION_RENDER_AS_HTML, false);
}

public function setMaxLength(int $length): self
{
if ($length < 1) {
throw new \InvalidArgumentException(sprintf('The argument of the "%s()" method must be 1 or higher (%d given).', __METHOD__, $length));
}

$this->setCustomOption(self::OPTION_MAX_LENGTH, $length);

return $this;
}

public function renderAsHtml(bool $asHtml = true): self
{
$this->setCustomOption(self::OPTION_RENDER_AS_HTML, $asHtml);

return $this;
}
}

+ 85
- 0
Field/GalleryManagerField.php View File

@@ -0,0 +1,85 @@
<?php

namespace Lc\AdminBundle\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use Lc\AdminBundle\Form\Type\FileManagerType;
use Lc\AdminBundle\Form\Type\GalleryManagerType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;


class GalleryManagerField implements FieldInterface
{
use FieldTrait;

public const OPTION_ALLOW_ADD = 'allowAdd';
public const OPTION_ALLOW_DELETE = 'allowDelete';
public const OPTION_ENTRY_IS_COMPLEX = 'entryIsComplex';
public const OPTION_ENTRY_TYPE = 'entryType';
public const OPTION_SHOW_ENTRY_LABEL = 'showEntryLabel';

public static function new(string $propertyName, ?string $label = null): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setTemplatePath('@LcAdmin/crud/field/collection.html.twig')
->setFormType(CollectionType::class)
->addCssClass('field-collection')
->addJsFiles('bundles/lcadmin/js/form-type-collection.js')
->addJsFiles('bundles/lcadmin/js/form-type-file-manager.js')
->addJsFiles('bundles/lcadmin/js/jquery-ui/jquery-ui.min.js')
->setFormTypeOption('allow_add', true)
->setFormTypeOption('allow_delete', true)
->setFormTypeOption('entry_options', array('label'=> false))
->setFormTypeOption('entry_type', FileManagerType::class)
->setFormTypeOption('attr', array('class'=> 'field-collection-group'))
->setFormTypeOption('row_attr', array('data-sortable'=> true))
->hideOnIndex();

//->setEntryType(FileManagerType::class);

//->setFormTypeOption('show_entry_label', false);

}

public function allowAdd(bool $allow = true): self
{
$this->setCustomOption(self::OPTION_ALLOW_ADD, $allow);

return $this;
}

public function allowDelete(bool $allow = true): self
{
$this->setCustomOption(self::OPTION_ALLOW_DELETE, $allow);

return $this;
}

/**
* Set this option to TRUE if the collection items are complex form types
* composed of several form fields (EasyAdmin applies a special rendering to make them look better).
*/
public function setEntryIsComplex(bool $isComplex): self
{
$this->setCustomOption(self::OPTION_ENTRY_IS_COMPLEX, $isComplex);

return $this;
}

public function setEntryType(string $formTypeFqcn): self
{
$this->setCustomOption(self::OPTION_ENTRY_TYPE, $formTypeFqcn);

return $this;
}

public function showEntryLabel(bool $showLabel = true): self
{
$this->setCustomOption(self::OPTION_SHOW_ENTRY_LABEL, $showLabel);

return $this;
}
}

+ 85
- 0
Form/Type/FileManagerType.php View File

@@ -0,0 +1,85 @@
<?php

namespace Lc\AdminBundle\Form\Type;

use Lc\AdminBundle\DataTransformer\FileManagerTypeToDataTransformer;
use Lc\AdminBundle\Entity\File\File;
use Lc\AdminBundle\IModel\Cms\FileInterface;
use Lc\AdminBundle\Manager\EntityManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class FileManagerType extends AbstractType implements DataTransformerInterface
{
protected $em;

public function __construct(EntityManager $entityManager){
$this->em = $entityManager;
}


public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('image', HiddenType::class, array(
'block_prefix' => 'file_manager_image',
'label' => false
));
$builder->add('legend', TextType::class, array(
'block_prefix' => 'file_manager_legend',
'attr'=> array(
"placeholder"=> 'Légende'
),
'label' => false
));
$builder->add('position', HiddenType::class, array(
'block_prefix' => 'file_manager_position',
'empty_data'=> 0,
'required' => true,
'attr' => array(
'class' => 'field-position'
),
'label' => false
));
}

/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => $this->em->getEntityName(FileInterface::class),
'compound' => true,
]);
}

/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'file_manager';
}

/**
* {@inheritdoc}
*/
public function transform($data)
{
// Model data should not be transformed
return $data;
}

/**
* {@inheritdoc}
*/
public function reverseTransform($data)
{
return null === $data ? '' : $data;
}
}

IModel/Cms/ImageInterface.php → IModel/Cms/FileInterface.php View File

@@ -2,10 +2,8 @@

namespace Lc\AdminBundle\IModel\Cms;


interface ImageInterface
interface FileInterface
{



}

+ 9
- 0
IModel/Translation/TranslatableInterface.php View File

@@ -0,0 +1,9 @@
<?php

namespace Lc\AdminBundle\IModel\Translation;

interface TranslatableInterface
{

}

+ 11
- 0
Manager/EntityManager.php View File

@@ -74,11 +74,22 @@ class EntityManager
return $this;
}

public function refresh(EntityInterface $entity): self
{
$this->entityManager->refresh($entity);

return $this;
}

protected function persist(EntityInterface $entity)
{
$this->entityManager->persist($entity);
}

public function getClassMetadata($className){
return $this->entityManager->getClassMetadata($className);
}

public function getEntityName($className)
{
if (substr($className, -9) === 'Interface') {

+ 3
- 0
Model/Cms/AbstractDocument.php View File

@@ -10,6 +10,7 @@ use Lc\AdminBundle\IModel\Cms\SortableInterface;
use Lc\AdminBundle\IModel\Cms\TimestampableInterface;
use Lc\AdminBundle\IModel\Cms\StatusInterface;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Lc\AdminBundle\IModel\EntityInterface;

/**
@@ -27,11 +28,13 @@ abstract class AbstractDocument implements BlameableInterface, SeoInterface, Slu
use DevAliasTrait;

/**
* @Gedmo\Translatable
* @ORM\Column(type="string", length=255)
*/
protected $title;

/**
* @Gedmo\Translatable
* @ORM\Column(type="text", nullable=true)
*/
protected $description;

+ 72
- 0
Model/Cms/File.php View File

@@ -0,0 +1,72 @@
<?php
namespace Lc\AdminBundle\Model\Cms;


use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Lc\AdminBundle\IModel\Cms\BlameableInterface;
use Lc\AdminBundle\IModel\Cms\DevAliasInterface;
use Lc\AdminBundle\IModel\Cms\SortableInterface;
use Lc\AdminBundle\IModel\Cms\TimestampableInterface;
use Lc\AdminBundle\IModel\EntityInterface;
use Lc\AdminBundle\IModel\Translation\TranslatableInterface;
use Lc\AdminBundle\Model\Cms\BlameableTrait;
use Lc\AdminBundle\Model\Cms\DevAliasTrait;
use Lc\AdminBundle\Model\Cms\SortableTrait;
use Lc\AdminBundle\Model\Cms\TimestampableTrait;
use Lc\AdminBundle\Model\Translation\TranslatableTrait;
use Lc\AdminBundle\Entity\Translation\EntityTranslation;

/**
* @Gedmo\TranslationEntity (class=EntityTranslation::class)
*/

abstract class File implements SortableInterface, BlameableInterface, TimestampableInterface, TranslatableInterface, DevAliasInterface, EntityInterface
{
use DevAliasTrait;
use BlameableTrait;
use TimestampableTrait;
use TranslatableTrait;
use SortableTrait;

/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $image;

/**
* @Gedmo\Translatable
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $legend;


public function __toString(){
return 'dfe';
}

public function getImage(): ?string
{
return $this->image;
}

public function setImage(?string $image): self
{
$this->image = $image;

return $this;
}

public function getLegend(): ?string
{
return $this->legend;
}

public function setLegend(?string $legend): self
{
$this->legend = $legend;

return $this;
}
}

+ 0
- 52
Model/Cms/ImageTrait.php View File

@@ -1,52 +0,0 @@
<?php

namespace Lc\AdminBundle\Model\Cms;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;

trait ImageTrait
{
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $image;

/* /**
* @Vich\UploadableField(mapping="images", fileNameProperty="image")
* @var File
*/
//protected $imageFile;*/
/*
public function setImageFile(File $image = null)
{
$this->imageFile = $image;

// VERY IMPORTANT:
// It is required that at least one field changes if you are using Doctrine,
// otherwise the event listeners won't be called and the file is lost
if ($image) {
// if 'updatedAt' is not defined in your entity, use another property
$this->updatedAt = new \DateTime('now');
}
}

public function getImageFile()
{
return $this->imageFile;
}*/

public function getImage(): ?string
{
return $this->image;
}

public function setImage(?string $image): self
{
$this->image = $image;

return $this;
}


}

+ 53
- 0
Model/Translation/TranslatableTrait.php View File

@@ -0,0 +1,53 @@
<?php

namespace Lc\AdminBundle\Model\Translation;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Lc\AdminBundle\Entity\Translation\EntityTranslation;


/**
* @Gedmo\TranslationEntity(class=EntityTranslation::class)
*/
trait TranslatableTrait
{
/**
* Post locale
* Used locale to override Translation listener's locale
* @Gedmo\Locale
*/
protected $locale;

/**
* @ORM\Column(type="array", nullable=true)
*/
protected $localesEnabled = [];

public function __get($name)
{
if(isset($this->{$name})) {
return $this->{$name};
}else{
return null;
}

}

public function setTranslatableLocale($locale)
{
$this->locale = $locale;
}

public function getLocalesEnabled(): ?array
{
return $this->localesEnabled;
}

public function setLocalesEnabled(?array $localesEnabled): self
{
$this->localesEnabled = $localesEnabled;

return $this;
}
}

+ 16
- 1
README.md View File

@@ -18,7 +18,22 @@ Si tu démarres un nouveau projet il te suffit de cloner le projet : https://git
git clone https://gitea.laclic.fr/Laclic/AdminBundle.git .
```


- Initialisation :
- Création des entités de base, créer une entité User.php et File.php, ajouter dans la configuration de doctrine les lignes suivantes
```
doctrine:
orm:
resolve_target_entities:
Lc\AdminBundle\IModel\User\UserInterface: App\Entity\User
Lc\AdminBundle\IModel\Cms\FileInterface: App\Entity\File
#...
```
- Instalation ckeditor
`php bin/console ckeditor:install`
`php bin/console assets:install public`
- Enjoy !

### Pour les projet Aquarium, Laclic, Le plat pentu (et peut être d'autres) suit cette préocédure :

+ 53
- 30
Repository/BaseRepository.php View File

@@ -3,68 +3,91 @@
namespace Lc\AdminBundle\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Gedmo\Translatable\TranslatableListener;
use Lc\AdminBundle\IModel\Cms\StatusInterface;

abstract class BaseRepository extends EntityRepository implements ServiceEntityRepositoryInterface, BaseRepositoryInterface
{
protected $merchantUtils;
protected $utils;

protected $defaultLocale;

public function setDefaultLocale($locale)
{
$this->defaultLocale = $locale;
}

public function __construct(EntityManagerInterface $entityManager)
{
parent::__construct($entityManager, $entityManager->getClassMetadata($this->getInterfaceClass()));
}

public function findOneByOldUrl($url)
{
$qb = $this->createQueryBuilder('entity')
->where('entity.oldUrls LIKE :oldUrl')
->andWhere('entity.status = 1')
->setParameter(':oldUrl', '%'.$url.'%');

return $qb->getQuery()->getOneOrNullResult();
}

public function findByTerm($field, $term, $limit=10){
$qb = $this->findByMerchantQuery();
public function getOneOrNullResult(QueryBuilder $qb, $locale = null, $hydrationMode = null)
{
return $this->getTranslatedQuery($qb, $locale)->getOneOrNullResult($hydrationMode);
}

if($this->utils->hasFilterAssociation($field, '_')){
$aliasRelation = $this->utils->getFilterAssociationAlias($field, '_');

$qb->innerJoin('e.'.$aliasRelation, $aliasRelation);
$qb->select($this->utils->getFilterPropertyInit($field));
$qb->groupBy($this->utils->getFilterPropertyInit($field));
$qb->andWhere(
$qb->expr()->like($this->utils->getFilterPropertyInit($field), ':term'));
}else {
$qb->select('e.' . $field);
$qb->groupBy('e.' . $field);
$qb->andWhere(
$qb->expr()->like('e.' . $field, ':term'));
}
$qb->setParameter('term', '%'.$term.'%');
$qb->setMaxResults($limit);
public function getResult(QueryBuilder $qb, $locale = null, $hydrationMode = AbstractQuery::HYDRATE_OBJECT)
{
return $this->getTranslatedQuery($qb, $locale)->getResult($hydrationMode);
}

return $qb->getQuery()->getResult();

public function getArrayResult(QueryBuilder $qb, $locale = null)
{
return $this->getTranslatedQuery($qb, $locale)->getArrayResult();
}


public function getSingleResult(QueryBuilder $qb, $locale = null, $hydrationMode = null)
{
return $this->getTranslatedQuery($qb, $locale)->getSingleResult($hydrationMode);
}


public function findOneByOldUrl($url)
public function getScalarResult(QueryBuilder $qb, $locale = null)
{
$qb = $this->createQueryBuilder('entity')
->where('entity.oldUrls LIKE :oldUrl')
->andWhere('entity.status = 1')
->setParameter(':oldUrl', '%'.$url.'%');
return $this->getTranslatedQuery($qb, $locale)->getScalarResult();
}

return $qb->getQuery()->getOneOrNullResult();

public function getSingleScalarResult(QueryBuilder $qb, $locale = null)
{
return $this->getTranslatedQuery($qb, $locale)->getSingleScalarResult();
}

public function findByMerchantQuery() :QueryBuilder
protected function getTranslatedQuery(QueryBuilder $qb, $locale = null)
{
return $this->createQueryBuilder('e')
->where('e.merchant = :currentMerchant')
->setParameter('currentMerchant', $this->merchantUtils->getMerchantCurrent()->getId()) ;
$locale = null === $locale ? $this->defaultLocale : $locale;

$query = $qb->getQuery();

$query->setHint(
Query::HINT_CUSTOM_OUTPUT_WALKER,
'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
);

$query->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale);

return $query;
}


public function findAll()
{
return $this->findBy(array());

+ 20
- 0
Repository/Cms/FileRepository.php View File

@@ -0,0 +1,20 @@
<?php

namespace Lc\AdminBundle\Repository\Cms;

use Lc\AdminBundle\IModel\Cms\FileInterface;
use Lc\AdminBundle\Repository\BaseRepository;
/**
* @method FileInterface|null find($id, $lockMode = null, $lockVersion = null)
* @method FileInterface|null findOneBy(array $criteria, array $orderBy = null)
* @method FileInterface[] findAll()
* @method FileInterface[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class FileRepository extends BaseRepository
{
public function getInterfaceClass()
{
return FileInterface::class;
}

}

+ 0
- 0
Resources/config/services.yml View File


+ 93
- 0
Resources/public/js/form-type-collection.js View File

@@ -0,0 +1,93 @@
jQuery(document).ready(function () {
initCollectionWidget();
});


function initCollectionWidget() {
$('.field-collection[data-prototype]').each(function (i, collectionWidget) {
setCollectionWidgetSortable($(collectionWidget));
reindexKeyCollectionWidget($(collectionWidget));

setCollectionWidgetAdd($(collectionWidget));
setCollectionWidgetDelete($(collectionWidget));
});
}

function setCollectionWidgetAdd($collectionWidget) {

if ($collectionWidget.data('allow-add')) {
$collectionWidget.find('.field-collection-add').on('click', function (e) {

// grab the prototype template
var newWidget = $collectionWidget.attr('data-prototype');
// replace the "__name__" used in the id and name of the prototype
// with a number that's unique to your emails
// end name attribute looks like name="contact[emails][2]"
newWidget = newWidget.replace(/__name__/g, getNumItems($collectionWidget));

// create a new list element and add it to the list
$collectionWidget.find('.form-widget-compound .field-collection-group').append(newWidget);
$collectionWidget.find('.field-collection-item:last').find('.field-position').val(getNumItems($collectionWidget));

reindexKeyCollectionWidget($collectionWidget);
setCollectionWidgetDelete($collectionWidget);
initFileManager();
$collectionWidget.data('num-items', $collectionWidget.data('num-items') + 1);
$collectionWidget.find('.collection-empty').hide();
});
}
}

function setCollectionWidgetDelete($collectionWidget) {
if ($collectionWidget.data('allow-delete')) {
$collectionWidget.find('.field-collection-delete').off('click');
$collectionWidget.find('.field-collection-delete').on('click', function () {
$(this).parents('.form-group:first').remove();
reindexKeyCollectionWidget($collectionWidget);
if(getNumItems($collectionWidget)==0)$collectionWidget.find('.collection-empty').show();
});
}
}

function getNumItems($collectionWidget) {
if ($collectionWidget.data('reindex-key')) {
return $collectionWidget.find('.field-collection-item').length;
} else {
return $collectionWidget.data('num-items');
}
}

function reindexKeyCollectionWidget($collectionWidget) {
if ($collectionWidget.data('reindex-key')) {
$collectionWidget.find('.field-collection-item').each(function (i, item) {
$(item).find('.input[type="text"]').each(function (y, field) {
$field = $(field);
//Chanegment ID
posId = indexOfLastDigit($field.prop('id'));
idPrefix = $field.prop('id').substr(0, posId);
idSuffix = $field.prop('id').substr(posId + 1);
$field.prop('id', idPrefix + i + idSuffix);

//Chanegment Name
posName = indexOfLastDigit($field.prop('name'));
namePrefix = $field.prop('name').substr(0, posName);
nameSuffix = $field.prop('name').substr(posName + 1);
$field.prop('name', namePrefix + i + nameSuffix);
});
});
}
}

function setCollectionWidgetSortable($collectionWidget) {
if ($collectionWidget.data('sortable')) {
$collectionWidget.find('.field-collection-group').sortable({
"handle" : '.lc-btn-sortable',
cancel: ''
});
$collectionWidget.find('.field-collection-group').on("sortupdate", function (event, ui) {
$collectionWidget.find('.field-collection-group>div').each(function (index, item) {
$(item).find('.field-position').val(index);
});
});
}
}

+ 29
- 0
Resources/public/js/form-type-file-manager.js View File

@@ -0,0 +1,29 @@
jQuery(document).ready(function () {
initFileManager();
});


function initFileManager() {
$('.lc-filemanager-delete').off('click');
$('.lc-filemanager-delete').on('click', function (e) {
let $field = $(this);
$('#' + $field.data('id')).val("");
$('#' + $field.data('id') + '_preview').prop('src',"");
});
$('.lc-filemanager-open').off('click');
$('.lc-filemanager-open').on('click', function (e) {
let $field = $(this);
$('#lc-filemanager-frame').off('load');
$("#lc-filemanager-frame").on('load', function () {
$('#lc-filemanager-frame').contents().on('click', '.select', function () {
var path = $(this).attr('data-path')
$('#' + $field.data('id')).val(path);
$('#' + $field.data('id') + '_preview').prop('src',path);
$('#lc-filemanager-modal').modal('hide')
});
});
$("#lc-filemanager-frame").prop('src', $field.data('target'));
$('#lc-filemanager-modal').modal('show');
});

}

+ 13
- 0
Resources/public/js/jquery-ui/jquery-ui.min.js
File diff suppressed because it is too large
View File


+ 57
- 0
Resources/public/js/utils.js View File

@@ -0,0 +1,57 @@

function arrayRemove(arr, value) { return arr.filter(function(ele){ return ele != value; });}

const scratchDiv = document.createElement('div');
function toPlainText(html) {
scratchDiv.innerHTML = html;
return scratchDiv.textContent;
}

function getDateFormatted(date, separator) {
if(date) {
var date = new Date(date);
var _d = date.getDate(),
d = _d > 9 ? _d : '0' + _d,
_m = date.getMonth() + 1,
m = _m > 9 ? _m : '0' + _m,
formatted = d + separator + m + separator + date.getFullYear();
return formatted;
}else{
return '';
}
}

function log(msg) {
try {
console.log(msg);
} catch (e) {

}
}

var getUrlParameter = function getUrlParameter(sParam) {
var sPageURL = window.location.search.substring(1),
sURLVariables = sPageURL.split('&'),
sParameterName,
i;

for (i = 0; i < sURLVariables.length; i++) {
sParameterName = sURLVariables[i].split('=');

if (sParameterName[0] === sParam) {
return sParameterName[1] === undefined ? true : decodeURIComponent(sParameterName[1]);
}
}
};

function indexOfFirstDigit(input) {
let i = 0;
for (; input[i] < '0' || input[i] > '9'; i++) ;
return i == input.length ? -1 : i;
}

function indexOfLastDigit(input) {
let i = input.length - 1;
for (; input[i] < '0' || input[i] > '9'; i--) ;
return i == input.length ? -1 : i;
}

+ 21
- 0
Resources/views/crud/action/translatable.html.twig View File

@@ -0,0 +1,21 @@
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var action \EasyCorp\Bundle\EasyAdminBundle\Dto\ActionDto #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}

{% if 'a' == action.htmlElement %}
{% for _locale in app_locales %}
<a class="{{ isIncludedInDropdown|default(false) ? 'dropdown-item' }} {{ action.cssClass }}"
href="{{ action.linkUrl }}&_locale={{ _locale }}"
{% for name, value in action.htmlAttributes %}{{ name }}="{{ value|e('html_attr') }}" {% endfor %}>
{%- if action.icon %}<i class="action-icon {{ action.icon }}"></i> {% endif -%}
{%- if action.label is not empty -%}{{ action.label }} {{ _locale }}{%- endif -%}
</a>
{% endfor %}
{% elseif 'button' == action.htmlElement %}
<button class="{{ action.cssClass }}" {% for name, value in action.htmlAttributes %}{{ name }}="{{ value|e('html_attr') }}" {% endfor %}>
<span class="btn-label">
{%- if action.icon %}<i class="action-icon {{ action.icon }}"></i> {% endif -%}
{%- if action.label is not empty -%}{{ action.label }}{%- endif -%}
</span>
</button>
{% endif %}

+ 8
- 0
Resources/views/crud/edit.html.twig View File

@@ -0,0 +1,8 @@
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
{% extends '@EasyAdmin/crud/edit.html.twig' %}

{% block body_javascript %}
{{ parent() }}
{% include '@LcAdmin/crud/filemanager/file-manager-modal.html.twig' %}
{% endblock %}

+ 11
- 0
Resources/views/crud/field/collection.html.twig View File

@@ -0,0 +1,11 @@
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
{# this is a bit ugly, but Twig doesn't have a 'is numeric' test #}
{% if field.formattedValue is iterable %}
{% for item in field.formattedValue %}
<span class="badge badge-secondary">{{ item }}</span>
{% endfor %}
{% else %}
{{ field.formattedValue }}
{% endif %}

+ 9
- 0
Resources/views/crud/field/gallery.html.twig View File

@@ -0,0 +1,9 @@
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
{# this is a bit ugly, but Twig doesn't have a 'is numeric' test #}
{% if field.formattedValue matches '/^\\d+$/' %}
<span class="badge badge-secondary">{{ field.formattedValue }}</span>
{% else %}
{{ field.formattedValue }}
{% endif %}

+ 11
- 0
Resources/views/crud/field/image.html.twig View File

@@ -0,0 +1,11 @@
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var field \EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
{% set html_id = 'ea-lightbox-' ~ field.uniqueId %}
<a href="#" class="ea-lightbox-thumbnail" title="{{ field.value.legend }}" data-featherlight="#{{ html_id }}" data-featherlight-close-on-click="anywhere">
<img src="{{ asset(field.value.image) }}" class="img-fluid">
</a>

<div id="{{ html_id }}" class="ea-lightbox">
<img src="{{ asset(field.value.image) }}">
</div>

+ 18
- 0
Resources/views/crud/filemanager/file-manager-modal.html.twig View File

@@ -0,0 +1,18 @@
<div class="modal fade" id="lc-filemanager-modal" tabindex="-1" role="dialog" aria-labelledby="File manager modal">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Gestionaire de fichier</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<iframe id="lc-filemanager-frame" src="" width="100%" height="500" frameborder="0"></iframe>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

+ 100
- 0
Resources/views/crud/form_theme.html.twig View File

@@ -0,0 +1,100 @@
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{% use '@EasyAdmin/crud/form_theme.html.twig' %}


{% block gallery_manager_row %}
{{ block('collection_row') }}
{% endblock gallery_manager_row %}

{% block gallery_manager_widget %}
{{ block('collection_widget') }}
{% endblock gallery_manager_widget %}


{% block collection_row %}
{% if prototype is defined and not prototype.rendered %}
{% set row_attr = row_attr|merge({ 'data-prototype': form_row(prototype) }) %}
{% endif %}

{% set row_attr = row_attr|merge({
'data-entry-is-complex': form.vars.ea_crud_form.ea_field and form.vars.ea_crud_form.ea_field.customOptions.get('entryIsComplex') ? 'true' : 'false',
'data-allow-add': allow_add ? 'true' : 'false',
'data-allow-delete': allow_delete ? 'true' : 'false',
'data-num-items': form.children|length,
'data-form-type-name-placeholder': prototype is defined ? prototype.vars.name : '',
}) %}

{{ block('form_row') }}
{% endblock collection_row %}

{% block collection_widget %}
{{ block('form_widget') }}
{% if allow_add|default(false) %}
<button type="button" class="btn btn-link field-collection-add">
<i class="fa fa-plus pr-1"></i>
{{ 'action.add_new_item'|trans({}, 'EasyAdminBundle') }}
</button>
{% endif %}
{% endblock collection_widget %}

{% block collection_entry_widget %}
{% set is_complex = form_parent(form).vars.ea_crud_form.ea_field.customOptions.get('entryIsComplex') ?? false %}

<div class="field-collection-item {{ is_complex ? 'field-collection-item-complex' }}">
{{ form_widget(form) }}

{% if form_parent(form).vars.allow_delete|default(false) %}
<button type="button" class="btn btn-link field-collection-delete"
title="{{ 'action.remove_item'|trans({}, 'EasyAdminBundle') }}">
<i class="fas fa-times"></i>
</button>
{% endif %}
</div>
{% endblock collection_entry_widget %}

{% block file_manager_image_row %}
{{ form_widget(form) }}
{% endblock file_manager_image_row %}

{% block file_manager_legend_row %}
{{ form_widget(form) }}
{% endblock file_manager_legend_row %}

{% block file_manager_position_row %}
{{ form_widget(form) }}
{% endblock file_manager_position_row %}


{% block file_manager_widget %}

<div class="lc-filemanager col-xs-12">
<div class="col-md-6 col-xs-12 nopadding" style="padding: 0px;">
<img style="width: 200px; height: 150px; object-fit: contain; background: #ddd; " src="{{ form.image.vars.value }}" class="lc-filemenager-preview" id="{{ form.image.vars.id }}_preview" alt="">
</div>
<div class="input-group">
{{ form_widget(form) }}

<div class="input-group-append">
<button type="button" class="btn btn-sm lc-filemanager-open" data-id="{{ form.image.vars.id }}"
data-target="{{ path('file_manager', {module:1, conf:'default'})|raw }}">
<i class="fa fa-folder-open-o"></i>
</button>
{% if value %}
<button type="button" class="btn btn-sm lc-filemanager-delete" data-id="{{ form.image.vars.id }}">
<i class="fa fa-trash-o"></i>
</button>
{% endif %}

{% if form.parent.vars['row_attr']['data-sortable'] is defined %}
<button type="button" class="btn btn-sm lc-btn-sortable" >
<i class="fa fa-arrows"></i>
</button>
{% endif %}
</div>
</div>

</div>


{% endblock file_manager_widget %}


+ 9
- 0
Resources/views/crud/new.html.twig View File

@@ -0,0 +1,9 @@
{# @var ea \EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext #}
{# @var entity \EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto #}
{% extends '@EasyAdmin/crud/new.html.twig' %}

{% block body_javascript %}
{{ parent() }}
{% include '@LcAdmin/crud/filemanager/file-manager-modal.html.twig' %}
{% endblock %}


+ 9
- 0
Resources/views/dashboard.html.twig View File

@@ -0,0 +1,9 @@
{% extends '@EasyAdmin/page/content.html.twig' %}

{% block page_title 'LcAdminBundle' %}

{% block page_content %}

Bien sur votre tableau de bord
{% endblock %}


+ 24
- 0
composer.json View File

@@ -0,0 +1,24 @@
{
"name": "lc/admin-bundle",
"type": "symfony-bundle",
"description": "Administration for Symfony applications based on easyadmin",
"keywords": ["backend", "admin", "generator"],
"homepage": "https://gitea.laclic.fr/Laclic/AdminBundle",
"license": "MIT",
"authors": [
{
"name": "La clic !",
"homepage": "laclic.fr/"
}
],
"require": {
"php": ">=7.2.5",
"easycorp/easyadmin-bundle": "^3.0",
"artgris/filemanager-bundle": "^2.2",
"friendsofsymfony/ckeditor-bundle": "^2.2",
"stof/doctrine-extensions-bundle": "^1.5"
},
"config": {
"sort-packages": true
}
}

Loading…
Cancel
Save