From e88600a39a5db8afb7313364b233393f714bd0ad Mon Sep 17 00:00:00 2001 From: Quentin Somazzi Date: Fri, 2 Dec 2016 10:58:43 +0100 Subject: [PATCH 1/3] Handle fragment service validation --- Admin/ArticleAdmin.php | 5 +++++ FragmentService/AbstractFragmentService.php | 9 +++++++-- FragmentService/FragmentServiceInterface.php | 9 +++++++-- FragmentService/TextFragmentService.php | 12 +++++------ FragmentService/TitleFragmentService.php | 21 +++++++++----------- 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/Admin/ArticleAdmin.php b/Admin/ArticleAdmin.php index 5600d94a..c40b7bca 100644 --- a/Admin/ArticleAdmin.php +++ b/Admin/ArticleAdmin.php @@ -108,6 +108,11 @@ public function validate(ErrorElement $errorElement, $object) AbstractArticle::getStatuses() : AbstractArticle::getContributorStatus())) ->end() ; + + $fragmentAdmin = $this->getChild('sonata.article.admin.fragment'); + foreach ($object->getFragments() as $fragment) { + $fragmentAdmin->validate($errorElement, $fragment); + } } /** diff --git a/FragmentService/AbstractFragmentService.php b/FragmentService/AbstractFragmentService.php index 4ade10b7..a9cc443f 100644 --- a/FragmentService/AbstractFragmentService.php +++ b/FragmentService/AbstractFragmentService.php @@ -13,8 +13,8 @@ use Sonata\AdminBundle\Form\FormMapper; use Sonata\ArticleBundle\Model\FragmentInterface; +use Sonata\CoreBundle\Validator\ErrorElement; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @author Hugo Briand @@ -109,8 +109,13 @@ public function configureOptions(OptionsResolver $resolver) /** * {@inheritdoc} */ - public function validate(FragmentInterface $fragment, ExecutionContextInterface $context) + public function validate(ErrorElement $errorElement, $object) { + if (empty($object->getBackofficeTitle())) { + $errorElement + ->addViolation(sprintf('Fragment %s - `Backoffice Title` must not be empty', $this->getName())) + ; + } } /** diff --git a/FragmentService/FragmentServiceInterface.php b/FragmentService/FragmentServiceInterface.php index 386b0a82..0b259524 100644 --- a/FragmentService/FragmentServiceInterface.php +++ b/FragmentService/FragmentServiceInterface.php @@ -13,8 +13,8 @@ use Sonata\AdminBundle\Form\FormMapper; use Sonata\ArticleBundle\Model\FragmentInterface; +use Sonata\CoreBundle\Validator\ErrorElement; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @author Hugo Briand @@ -42,8 +42,13 @@ public function buildCreateForm(FormMapper $form, FragmentInterface $fragment); * * @param FragmentInterface $fragment * @param ExecutionContextInterface $context + /** + * Validates the fragment (you'll need to add your violations through $errorElement). + * + * @param ErrorElement $errorElement + * @param object $object */ - public function validate(FragmentInterface $fragment, ExecutionContextInterface $context); + public function validate(ErrorElement $errorElement, $object); /** * Returns the Fragment service readable name. diff --git a/FragmentService/TextFragmentService.php b/FragmentService/TextFragmentService.php index b49ef714..a81815f6 100644 --- a/FragmentService/TextFragmentService.php +++ b/FragmentService/TextFragmentService.php @@ -13,8 +13,8 @@ use Sonata\AdminBundle\Form\FormMapper; use Sonata\ArticleBundle\Model\FragmentInterface; +use Sonata\CoreBundle\Validator\ErrorElement; use Symfony\Component\Validator\Constraints\NotBlank; -use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @author Hugo Briand @@ -42,13 +42,11 @@ public function buildEditForm(FormMapper $form, FragmentInterface $fragment) /** * {@inheritdoc} */ - public function validate(FragmentInterface $fragment, ExecutionContextInterface $context) + public function validate(ErrorElement $errorElement, $object) { - if (empty($fragment->getSettings()['text'])) { - $context - ->buildViolation('`Text` must not be empty') - ->atPath('settings.text') - ->addViolation() + if (empty($object->getSetting('text'))) { + $errorElement + ->addViolation('Fragment Text - `Text` must not be empty') ; } } diff --git a/FragmentService/TitleFragmentService.php b/FragmentService/TitleFragmentService.php index ce73352b..dee796fc 100644 --- a/FragmentService/TitleFragmentService.php +++ b/FragmentService/TitleFragmentService.php @@ -13,9 +13,9 @@ use Sonata\AdminBundle\Form\FormMapper; use Sonata\ArticleBundle\Model\FragmentInterface; +use Sonata\CoreBundle\Validator\ErrorElement; use Symfony\Component\Validator\Constraints\Length; use Symfony\Component\Validator\Constraints\NotBlank; -use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * @author Hugo Briand @@ -44,20 +44,17 @@ public function buildEditForm(FormMapper $form, FragmentInterface $fragment) /** * {@inheritdoc} */ - public function validate(FragmentInterface $fragment, ExecutionContextInterface $context) + public function validate(ErrorElement $errorElement, $object) { - if (empty($fragment->getSettings()['text'])) { - $context - ->buildViolation('`Title` must not be empty') - ->atPath('settings.text') - ->addViolation() + if (empty($object->getSetting('text'))) { + $errorElement + ->addViolation('Fragment Title - `Text` must not be empty') ; } - if (strlen($fragment->getSetting('text')) > 255) { - $context - ->buildViolation('`Title` must not be longer than 255 characters.') - ->atPath('settings.text') - ->addViolation() + + if (strlen($object->getSetting('text')) > 255) { + $errorElement + ->addViolation('Fragment Text - `Text` must not be longer than 255 characters.') ; } } From 5dc93e3d719d134406f7c74d3586604701e97cea Mon Sep 17 00:00:00 2001 From: Quentin Somazzi Date: Fri, 2 Dec 2016 11:00:39 +0100 Subject: [PATCH 2/3] Add fragment backoffice title --- FragmentService/AbstractFragmentService.php | 10 ++++++ FragmentService/FragmentServiceInterface.php | 9 ++++-- FragmentService/TextFragmentService.php | 2 +- FragmentService/TitleFragmentService.php | 2 +- Model/AbstractFragment.php | 31 +++++++++++++++++-- .../config/doctrine/AbstractFragment.orm.xml | 1 + .../translations/SonataArticleBundle.en.xliff | 4 +++ .../translations/SonataArticleBundle.fr.xliff | 4 +++ Resources/views/FragmentAdmin/form.html.twig | 4 +-- 9 files changed, 57 insertions(+), 10 deletions(-) diff --git a/FragmentService/AbstractFragmentService.php b/FragmentService/AbstractFragmentService.php index a9cc443f..55b1bae8 100644 --- a/FragmentService/AbstractFragmentService.php +++ b/FragmentService/AbstractFragmentService.php @@ -54,6 +54,16 @@ final public function buildCreateForm(FormMapper $form, FragmentInterface $fragm * {@inheritdoc} */ public function buildEditForm(FormMapper $form, FragmentInterface $fragment) + { + // Add BO title + $form->add('backofficeTitle'); + $this->buildForm($form, $fragment); + } + + /** + * {@inheritdoc} + */ + public function buildForm(FormMapper $form, FragmentInterface $fragment) { } diff --git a/FragmentService/FragmentServiceInterface.php b/FragmentService/FragmentServiceInterface.php index 0b259524..5a6f9a48 100644 --- a/FragmentService/FragmentServiceInterface.php +++ b/FragmentService/FragmentServiceInterface.php @@ -38,10 +38,13 @@ public function buildEditForm(FormMapper $form, FragmentInterface $fragment); public function buildCreateForm(FormMapper $form, FragmentInterface $fragment); /** - * Validates the fragment (you'll need to add your violations through context). + * Builds the common part of creation|edition form for the fragment. * - * @param FragmentInterface $fragment - * @param ExecutionContextInterface $context + * @param FormMapper $form + * @param FragmentInterface $fragment + */ + public function buildForm(FormMapper $form, FragmentInterface $fragment); + /** * Validates the fragment (you'll need to add your violations through $errorElement). * diff --git a/FragmentService/TextFragmentService.php b/FragmentService/TextFragmentService.php index a81815f6..a5aff3c5 100644 --- a/FragmentService/TextFragmentService.php +++ b/FragmentService/TextFragmentService.php @@ -24,7 +24,7 @@ class TextFragmentService extends AbstractFragmentService /** * {@inheritdoc} */ - public function buildEditForm(FormMapper $form, FragmentInterface $fragment) + public function buildForm(FormMapper $form, FragmentInterface $fragment) { $form->add('settings', 'sonata_type_immutable_array', array( 'keys' => array( diff --git a/FragmentService/TitleFragmentService.php b/FragmentService/TitleFragmentService.php index dee796fc..059ab1e2 100644 --- a/FragmentService/TitleFragmentService.php +++ b/FragmentService/TitleFragmentService.php @@ -25,7 +25,7 @@ class TitleFragmentService extends AbstractFragmentService /** * {@inheritdoc} */ - public function buildEditForm(FormMapper $form, FragmentInterface $fragment) + public function buildForm(FormMapper $form, FragmentInterface $fragment) { $form->add('settings', 'sonata_type_immutable_array', array( 'keys' => array( diff --git a/Model/AbstractFragment.php b/Model/AbstractFragment.php index ac85754f..324026e8 100644 --- a/Model/AbstractFragment.php +++ b/Model/AbstractFragment.php @@ -49,18 +49,23 @@ abstract class AbstractFragment implements FragmentInterface, ArticleFragmentInt protected $position; /** - * @var int + * @var string + */ + protected $backofficeTitle; + + /** + * @var ArticleInterface */ protected $article; /** - * Returns type of fragment. + * Returns Backoffice title of fragment. * * @return string */ public function __toString() { - return (string) $this->getType(); + return $this->getBackofficeTitle(); } /** @@ -218,4 +223,24 @@ public function getUpdatedAt() { return $this->updatedAt; } + + /** + * @return string + */ + public function getBackofficeTitle() + { + return $this->backofficeTitle; + } + + /** + * @param string $backofficeTitle + * + * @return $this + */ + public function setBackofficeTitle($backofficeTitle) + { + $this->backofficeTitle = $backofficeTitle; + + return $this; + } } diff --git a/Resources/config/doctrine/AbstractFragment.orm.xml b/Resources/config/doctrine/AbstractFragment.orm.xml index 2838e87b..8e940502 100644 --- a/Resources/config/doctrine/AbstractFragment.orm.xml +++ b/Resources/config/doctrine/AbstractFragment.orm.xml @@ -1,6 +1,7 @@ + diff --git a/Resources/translations/SonataArticleBundle.en.xliff b/Resources/translations/SonataArticleBundle.en.xliff index 11b45c11..f49f2e4c 100644 --- a/Resources/translations/SonataArticleBundle.en.xliff +++ b/Resources/translations/SonataArticleBundle.en.xliff @@ -70,6 +70,10 @@ form.label_publication_ends_at Publication end date + + form.label_backoffice_title + Backoffice title + diff --git a/Resources/translations/SonataArticleBundle.fr.xliff b/Resources/translations/SonataArticleBundle.fr.xliff index 03f7a981..ad6c5ac6 100644 --- a/Resources/translations/SonataArticleBundle.fr.xliff +++ b/Resources/translations/SonataArticleBundle.fr.xliff @@ -70,6 +70,10 @@ form.label_publication_ends_at Date de fin de publication + + form.label_backoffice_title + Titre backoffice + diff --git a/Resources/views/FragmentAdmin/form.html.twig b/Resources/views/FragmentAdmin/form.html.twig index 54f6d689..0fb5f0fe 100644 --- a/Resources/views/FragmentAdmin/form.html.twig +++ b/Resources/views/FragmentAdmin/form.html.twig @@ -1,6 +1,6 @@ {% block form %} {% set fragmentName = admin.fragmentServices[form.vars.data.type].name %} -
+
@@ -9,7 +9,7 @@
- {% if form.vars.errors|length > 0 %} + {% if form.vars.errors is defined and form.vars.errors|length > 0 %}
{{ form_errors(form) }}
From 2f94ba6f9d7e31efee1eceb98e70566e7e9307fc Mon Sep 17 00:00:00 2001 From: Quentin Somazzi Date: Fri, 2 Dec 2016 11:01:04 +0100 Subject: [PATCH 3/3] Add doc and fix fragment remove issue --- Resources/doc/index.rst | 1 + Resources/doc/reference/todo.rst | 11 +++ .../public/js/editOneAssociationFragment.js | 4 +- Resources/public/js/fragmentList.jquery.js | 74 +++++++++++++++---- .../translations/SonataArticleBundle.en.xliff | 4 + .../translations/SonataArticleBundle.fr.xliff | 4 + .../edit_collection_fragment.html.twig | 5 +- Resources/views/FragmentAdmin/form.html.twig | 7 +- 8 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 Resources/doc/reference/todo.rst diff --git a/Resources/doc/index.rst b/Resources/doc/index.rst index 77d1b815..c3f10326 100644 --- a/Resources/doc/index.rst +++ b/Resources/doc/index.rst @@ -14,3 +14,4 @@ Reference Guide reference/getting_started reference/configuration reference/custom_fragment + reference/todo diff --git a/Resources/doc/reference/todo.rst b/Resources/doc/reference/todo.rst new file mode 100644 index 00000000..99b97c17 --- /dev/null +++ b/Resources/doc/reference/todo.rst @@ -0,0 +1,11 @@ +Todo +==== + +This bundle is still a work in progress, we need to work more on some parts. +We will try to keep this "Todo list" up to date to inform you of our expectations. + +- Find a better way to handle fragment validation +- Add articleTranslation feature (each articles are translatable and related to a Site) +- Publishing Workflow +- Handle formats (web, json, ...) +- Duplicate article diff --git a/Resources/public/js/editOneAssociationFragment.js b/Resources/public/js/editOneAssociationFragment.js index 97c9f60c..70f13207 100644 --- a/Resources/public/js/editOneAssociationFragment.js +++ b/Resources/public/js/editOneAssociationFragment.js @@ -5,6 +5,7 @@ jQuery(document).ready(function() { var id = $selector.data('id'); var fragmentAddUrl = $selector.data('add-fragment-url'); + var translations = $selector.data('translations'); /** * Get URL value to POST a new fragment @@ -34,6 +35,7 @@ jQuery(document).ready(function() { formSource: '#field_widget_'+id, addButtonSource: '#field_actions_'+id, getAddUrl: getFragmentAddUrl, - addedCallback: addFragmentCallback + addedCallback: addFragmentCallback, + translations: translations }); }); diff --git a/Resources/public/js/fragmentList.jquery.js b/Resources/public/js/fragmentList.jquery.js index 56a3afa1..65c09a2b 100644 --- a/Resources/public/js/fragmentList.jquery.js +++ b/Resources/public/js/fragmentList.jquery.js @@ -1,4 +1,4 @@ -(function ($, window, document, undefined) { +(function($) { // Create the defaults once var pluginName = 'fragmentList', defaults = { @@ -14,6 +14,7 @@ pageScrollOffset: 50, pageScrollDuration: 1000, getAddUrl: null, + translations: {}, addedCallback: function(html) {} }; @@ -23,6 +24,7 @@ this.options = $.extend({}, defaults, options) ; this._defaults = defaults; this._name = pluginName; + this._elementsIndex = 0; this.init(); } @@ -37,6 +39,20 @@ this.setDraggableElements(); }, + /** + * Translates given label. + * + * @param {String} label + * @return {String} + */ + translate: function (label) { + if (this.options.translations[label]) { + return this.options.translations[label]; + } + + return label; + }, + /** * Cache DOM elements to limit DOM parsing */ @@ -90,10 +106,29 @@ // Before adding, be sure the current fragment is valid if(!this.checkValidity()) return; + if (this._elementsIndex === 0) { + var maxIndex = 0; + + this.$list.children().each(function(i, child) { + var fragId = $(child).data('frag-id'); + fragId = fragId.split('_'); + var fragmentId = fragId.length > 0 ? fragId[fragId.length - 1] : null; + + if (!isNaN(fragmentId) && fragmentId > maxIndex) { + maxIndex = parseInt(fragmentId); + } + }); + + this._elementsIndex = maxIndex; + } + + this._elementsIndex++; + Admin.log('[fragments|add] current index is ' + this._elementsIndex); + // Submit the new fragment var self = this; jQuery(this.$form).ajaxSubmit({ - url: this.options.getAddUrl(this.$list.children().length), + url: this.options.getAddUrl(this._elementsIndex), type: 'POST', dataType: 'html', data: { _xml_http_request: true }, @@ -134,6 +169,10 @@ e.preventDefault(); e.stopPropagation(); + if (!confirm(this.translate('remove_confirm'))) { + return; + } + var $target = $(e.currentTarget), $frag = $target.closest('[data-fragment]'), id = $frag.data('fragId'), @@ -141,14 +180,13 @@ $cb = $form.find(this.options.formRemoveName); // Remove a persisted fragment (just check delete checkbox) - if($form.data('formTmp')) { + if (!$frag.hasClass('is-tmp')) { // Check the delete checkbox $cb.prop('checked', true).attr('checked', true); - console.log($form.find(this.options.formRemoveName)); // Hide form block $form.hide(); - // Remove a new fragment (not already saved) + // Remove a new fragment (not already saved) } else { $form.remove(); this.setFormListElement(); @@ -160,7 +198,7 @@ // Removed the last element ? if(!this.$list.children().length) { this.$activeFragment = null; - // Removed the current visible item ? + // Removed the current visible item ? } else if(id === this.$activeFragment.data('fragId')) { this.setActive(this.$list.children(':first-child')); } @@ -325,13 +363,17 @@ } }; - // A really lightweight plugin wrapper around the constructor, - // preventing against multiple instantiations - $.fn[pluginName] = function ( options ) { - return this.each(function () { - if (!$.data(this, 'plugin_' + pluginName)) { - $.data(this, 'plugin_' + pluginName, new Plugin(this, options)); - } - }); - }; -})(jQuery, window, document); + $(document).ready(function() { + + // A really lightweight plugin wrapper around the constructor, + // preventing against multiple instantiations + $.fn[pluginName] = function ( options ) { + return this.each(function () { + if (!$.data(this, 'plugin_' + pluginName)) { + $.data(this, 'plugin_' + pluginName, new Plugin(this, options)); + } + }); + }; + + }); +})(jQuery); diff --git a/Resources/translations/SonataArticleBundle.en.xliff b/Resources/translations/SonataArticleBundle.en.xliff index f49f2e4c..bb44c681 100644 --- a/Resources/translations/SonataArticleBundle.en.xliff +++ b/Resources/translations/SonataArticleBundle.en.xliff @@ -74,6 +74,10 @@ form.label_backoffice_title Backoffice title + + remove_confirm + The fragment will be deleted after update. Are you sure ? + diff --git a/Resources/translations/SonataArticleBundle.fr.xliff b/Resources/translations/SonataArticleBundle.fr.xliff index ad6c5ac6..42d8dd21 100644 --- a/Resources/translations/SonataArticleBundle.fr.xliff +++ b/Resources/translations/SonataArticleBundle.fr.xliff @@ -74,6 +74,10 @@ form.label_backoffice_title Titre backoffice + + remove_confirm + Ce fragment sera supprimé après mise à jour. Êtes-vous certain ? + diff --git a/Resources/views/FragmentAdmin/edit_collection_fragment.html.twig b/Resources/views/FragmentAdmin/edit_collection_fragment.html.twig index 2652ec78..5a4198ac 100644 --- a/Resources/views/FragmentAdmin/edit_collection_fragment.html.twig +++ b/Resources/views/FragmentAdmin/edit_collection_fragment.html.twig @@ -14,7 +14,10 @@ 'subclass': sonata_admin.admin.getActiveSubclassCode(), 'objectId': sonata_admin.admin.root.subject ? sonata_admin.admin.root.id(sonata_admin.admin.root.subject) : false, 'uniqid': sonata_admin.admin.root.uniqid - } + sonata_admin.field_description.getOption('link_parameters', {})) }}"> + } + sonata_admin.field_description.getOption('link_parameters', {})) }}" + data-translations="{{ { + remove_confirm: 'remove_confirm'|trans({}, 'SonataArticleBundle') + }|json_encode }}"> {% for fragServId, fragServ in sonata_admin.field_description.associationadmin.fragmentServices %} {% endfor %} diff --git a/Resources/views/FragmentAdmin/form.html.twig b/Resources/views/FragmentAdmin/form.html.twig index 0fb5f0fe..59d907db 100644 --- a/Resources/views/FragmentAdmin/form.html.twig +++ b/Resources/views/FragmentAdmin/form.html.twig @@ -1,6 +1,9 @@ {% block form %} {% set fragmentName = admin.fragmentServices[form.vars.data.type].name %} -
+
@@ -9,7 +12,7 @@
- {% if form.vars.errors is defined and form.vars.errors|length > 0 %} + {% if form.vars.errors|default([])|length > 0 %}
{{ form_errors(form) }}