diff --git a/Bundle/BlogBundle/Builder/BlogReferenceBuilder.php b/Bundle/BlogBundle/Builder/BlogReferenceBuilder.php index 291ee7c4e..ac1501ee1 100644 --- a/Bundle/BlogBundle/Builder/BlogReferenceBuilder.php +++ b/Bundle/BlogBundle/Builder/BlogReferenceBuilder.php @@ -22,6 +22,7 @@ public function buildReference(View $view, EntityManager $em) $viewReference->setName($view->getName()); $viewReference->setViewId($view->getId()); $viewReference->setSlug($view->getSlug()); + $viewReference->setPermalink($view->getPermalink()); $viewReference->setViewNamespace($em->getClassMetadata(get_class($view))->name); if ($parent = $view->getParent()) { $viewReference->setParent(ViewReferenceHelper::generateViewReferenceId($parent)); diff --git a/Bundle/BusinessPageBundle/Builder/BusinessPageReferenceBuilder.php b/Bundle/BusinessPageBundle/Builder/BusinessPageReferenceBuilder.php index 2a82e38d8..01a7b09d3 100644 --- a/Bundle/BusinessPageBundle/Builder/BusinessPageReferenceBuilder.php +++ b/Bundle/BusinessPageBundle/Builder/BusinessPageReferenceBuilder.php @@ -31,6 +31,7 @@ public function buildReference(View $businessPage, EntityManager $em) $businessPageReference->setViewId($businessPage->getId()); $businessPageReference->setTemplateId($businessPage->getTemplate()->getId()); $businessPageReference->setSlug($businessPage->getSlug()); + $businessPageReference->setPermalink($businessPage->getPermalink()); $businessPageReference->setEntityId($businessPage->getBusinessEntity()->getId()); $businessPageReference->setEntityNamespace($em->getClassMetadata(get_class($businessPage->getBusinessEntity()))->name); $businessPageReference->setViewNamespace($em->getClassMetadata(get_class($businessPage))->name); diff --git a/Bundle/CoreBundle/Entity/View.php b/Bundle/CoreBundle/Entity/View.php index 0d75005a3..3d4331c3f 100644 --- a/Bundle/CoreBundle/Entity/View.php +++ b/Bundle/CoreBundle/Entity/View.php @@ -10,6 +10,7 @@ use Knp\DoctrineBehaviors\Model\Translatable\Translatable; use Symfony\Component\PropertyAccess\PropertyAccess; use Victoire\Bundle\BusinessPageBundle\Entity\VirtualBusinessPage; +use Victoire\Bundle\CoreBundle\Validator\Constraints as Assert; use Victoire\Bundle\ViewReferenceBundle\ViewReference\ViewReference; use Victoire\Bundle\WidgetMapBundle\Entity\WidgetMap; @@ -25,6 +26,7 @@ * ) * @ORM\Table("vic_view") * @ORM\HasLifecycleCallbacks + * @Assert\UniquePermalink() */ abstract class View { @@ -766,6 +768,17 @@ public function setSlug($slug, $locale = null) $this->mergeNewTranslations(); } + public function getPermalink() + { + return PropertyAccess::createPropertyAccessor()->getValue($this->translate(null, false), 'getPermalink'); + } + + public function setPermalink($permalink, $locale = null) + { + $this->translate($locale, false)->setPermalink($permalink); + $this->mergeNewTranslations(); + } + /** * @return array */ diff --git a/Bundle/CoreBundle/Entity/WebViewInterface.php b/Bundle/CoreBundle/Entity/WebViewInterface.php index 94e4a4cfd..78ef900de 100644 --- a/Bundle/CoreBundle/Entity/WebViewInterface.php +++ b/Bundle/CoreBundle/Entity/WebViewInterface.php @@ -54,4 +54,8 @@ public function isPublished(); public function isHomepage(); public function setHomepage($homepage); + + public function getPermalink(); + + public function setPermalink($permalink); } diff --git a/Bundle/CoreBundle/Form/ViewType.php b/Bundle/CoreBundle/Form/ViewType.php index 383fa441e..adae8163b 100644 --- a/Bundle/CoreBundle/Form/ViewType.php +++ b/Bundle/CoreBundle/Form/ViewType.php @@ -119,6 +119,12 @@ public function buildForm(FormBuilderInterface $builder, array $options) 'label' => 'form.page.type.slug.label', 'field_type' => UrlvalidatedType::class, ]; + $translationOptions['fields']['permalink'] = [ + 'label' => 'form.page.type.permalink.label', + 'field_type' => UrlvalidatedType::class, + 'parentUrl' => '', + 'allow_empty' => true, + ]; } $form->add('translations', TranslationsType::class, $translationOptions); } diff --git a/Bundle/CoreBundle/Helper/UrlBuilder.php b/Bundle/CoreBundle/Helper/UrlBuilder.php index 5768776f8..c2d043153 100644 --- a/Bundle/CoreBundle/Helper/UrlBuilder.php +++ b/Bundle/CoreBundle/Helper/UrlBuilder.php @@ -23,9 +23,13 @@ public function buildUrl(WebViewInterface $view) if (!(method_exists($view, 'isHomepage') && $view->isHomepage())) { $slug = [$view->getSlug()]; } - - //get the slug of the parents - $url = $this->getParentSlugs($view, $slug); + // if permalink is set + if ($view->getPermalink() != null && $view->getPermalink() != '') { + $url = [$view->getPermalink()]; + } else { + //get the slug of the parents + $url = $this->getParentSlugs($view, $slug); + } //reorder the list of slugs $url = array_reverse($url); diff --git a/Bundle/CoreBundle/Resources/config/services.yml b/Bundle/CoreBundle/Resources/config/services.yml index bc6e623d2..48ddf255d 100644 --- a/Bundle/CoreBundle/Resources/config/services.yml +++ b/Bundle/CoreBundle/Resources/config/services.yml @@ -14,7 +14,17 @@ services: tags: - { name: doctrine.event_subscriber, connection: default } +# ================== Validator ================== # + Victoire\Bundle\CoreBundle\Validator\UniquePermalinkValidator: + arguments: + - "@victoire_view_reference.repository" + - "@request_stack" + - "@victoire_core.url_builder" + tags: + - { name: 'validator.constraint_validator', alias: 'victoire.validator.view.permalink_unique' } + # ================== MENU ================== # + victoire_core.admin_menu_builder: class: Victoire\Bundle\CoreBundle\Menu\MenuBuilder arguments: [ '@knp_menu.factory', '@security.authorization_checker' ] diff --git a/Bundle/CoreBundle/Resources/translations/validators.en.xliff b/Bundle/CoreBundle/Resources/translations/validators.en.xliff new file mode 100644 index 000000000..0d235eebd --- /dev/null +++ b/Bundle/CoreBundle/Resources/translations/validators.en.xliff @@ -0,0 +1,11 @@ + + + + + + victoire.url.alreadyInUse + The url is already in use. + + + + diff --git a/Bundle/CoreBundle/Resources/translations/validators.es.xliff b/Bundle/CoreBundle/Resources/translations/validators.es.xliff new file mode 100644 index 000000000..0fc234e08 --- /dev/null +++ b/Bundle/CoreBundle/Resources/translations/validators.es.xliff @@ -0,0 +1,11 @@ + + + + + + victoire.url.alreadyInUse + La url ya está en uso. + + + + \ No newline at end of file diff --git a/Bundle/CoreBundle/Resources/translations/validators.fr.xliff b/Bundle/CoreBundle/Resources/translations/validators.fr.xliff new file mode 100644 index 000000000..dbfab4a41 --- /dev/null +++ b/Bundle/CoreBundle/Resources/translations/validators.fr.xliff @@ -0,0 +1,11 @@ + + + + + + victoire.url.alreadyInUse + L'url est déjà utilisée. + + + + \ No newline at end of file diff --git a/Bundle/CoreBundle/Validator/Constraints/UniquePermalink.php b/Bundle/CoreBundle/Validator/Constraints/UniquePermalink.php new file mode 100644 index 000000000..a43116dd6 --- /dev/null +++ b/Bundle/CoreBundle/Validator/Constraints/UniquePermalink.php @@ -0,0 +1,22 @@ +viewReferenceRepository = $viewReferenceRepository; + $this->requestStack = $requestStack; + $this->urlBuilder = $urlBuilder; + } + + /** + * @param View $view + * @param Constraint $constraint + */ + public function validate($view, Constraint $constraint) + { + if ($view instanceof WebViewInterface && ($url = $this->urlBuilder->buildUrl($view)) != '') { + $viewReference = $this->viewReferenceRepository->getReferenceByUrl( + $url, + $this->requestStack->getCurrentRequest()->getLocale() + ); + + if ($viewReference && $viewReference->getViewId() != $view->getId()) { + $this->context->buildViolation('victoire.url.alreadyInUse') + ->atPath('permalink') + ->addViolation(); + } + } + } +} diff --git a/Bundle/FormBundle/Form/Type/UrlvalidatedType.php b/Bundle/FormBundle/Form/Type/UrlvalidatedType.php index 9415d07c2..d83685692 100644 --- a/Bundle/FormBundle/Form/Type/UrlvalidatedType.php +++ b/Bundle/FormBundle/Form/Type/UrlvalidatedType.php @@ -41,6 +41,9 @@ public function buildView(FormView $view, FormInterface $form, array $options) } $parentUrl = $page->getParent() ? $page->getParent()->getUrl() : ''; + if (isset($options['parentUrl'])) { + $parentUrl = $options['parentUrl']; + } $url = $this->router->generate('victoire_core_page_show', ['url' => $parentUrl, '_locale' => $locale], Router::ABSOLUTE_URL); $view->vars['base_url'] = $url; @@ -49,6 +52,7 @@ public function buildView(FormView $view, FormInterface $form, array $options) public function configureOptions(OptionsResolver $resolver) { + $resolver->setDefined('parentUrl'); parent::configureOptions($resolver); } diff --git a/Bundle/FormBundle/Resources/public/js/slug.js b/Bundle/FormBundle/Resources/public/js/slug.js index b3ceca434..9beb8fdfa 100644 --- a/Bundle/FormBundle/Resources/public/js/slug.js +++ b/Bundle/FormBundle/Resources/public/js/slug.js @@ -22,7 +22,7 @@ function displaySlugIcons(inputSlug, slug, allowEmpty){ } }else{ if (allowEmpty && slug != 'undefined') { - correctSlugIcon.removeClass('v-hidden'); + correctSlugIcon.addClass('v-hidden'); notCorrectSlugIcon.addClass('v-hidden'); } else { correctSlugIcon.addClass('v-hidden'); diff --git a/Bundle/FormBundle/Resources/views/Form/fields.html.twig b/Bundle/FormBundle/Resources/views/Form/fields.html.twig index d8de783d6..c5a67d0c8 100644 --- a/Bundle/FormBundle/Resources/views/Form/fields.html.twig +++ b/Bundle/FormBundle/Resources/views/Form/fields.html.twig @@ -723,15 +723,17 @@ }} {% endfor %} {% else %} - - {% for error in errors %} - {% set _errorMessage = - error.messagePluralization is null - ? error.messageTemplate|trans(error.messageParameters, 'validators') - : error.messageTemplate|transchoice(error.messagePluralization, error.messageParameters, 'validators') - %}{{ _errorMessage|raw }}
- {% endfor %} -
+ {% embed "VictoireUIBundle:Bricks:alert.html.twig" with { theme: "danger" } %} + {% block body %} + {% for error in errors %} + {% set _errorMessage = + error.messagePluralization is null + ? error.messageTemplate|trans(error.messageParameters, 'validators') + : error.messageTemplate|transchoice(error.messagePluralization, error.messageParameters, 'validators') + %}{{ _errorMessage|raw }}
+ {% endfor %} + {% endblock body %} + {% endembed %} {% endif %} {% endif %} {% endif %} diff --git a/Bundle/I18nBundle/Entity/ViewTranslation.php b/Bundle/I18nBundle/Entity/ViewTranslation.php index 02848cecf..966236a96 100644 --- a/Bundle/I18nBundle/Entity/ViewTranslation.php +++ b/Bundle/I18nBundle/Entity/ViewTranslation.php @@ -32,6 +32,15 @@ class ViewTranslation */ protected $name; + /** + * @var string + * + * @Assert\NotBlank() + * @ORM\Column(name="permalink", type="string", length=255, nullable=true) + * @Serializer\Groups({"search"}) + */ + protected $permalink; + /** * @var string * @@ -72,6 +81,30 @@ public function setName($name) return $this; } + /** + * Get permalink. + * + * @return string + */ + public function getPermalink() + { + return $this->permalink; + } + + /** + * Set permalink. + * + * @param string $permalink + * + * @return View + */ + public function setPermalink($permalink) + { + $this->permalink = $permalink; + + return $this; + } + /** * Set slug. * diff --git a/Bundle/PageBundle/Builder/PageReferenceBuilder.php b/Bundle/PageBundle/Builder/PageReferenceBuilder.php index ae9af01f5..4d69165cf 100644 --- a/Bundle/PageBundle/Builder/PageReferenceBuilder.php +++ b/Bundle/PageBundle/Builder/PageReferenceBuilder.php @@ -29,6 +29,7 @@ public function buildReference(View $view, EntityManager $em) $viewReference->setName($view->getName()); $viewReference->setViewId($view->getId()); $viewReference->setSlug($view->isHomepage() ? '' : $view->getSlug()); + $viewReference->setPermalink($view->getPermalink()); $viewReference->setViewNamespace(ClassUtils::getClass($view)); if ($parent = $view->getParent()) { $parent->translate($view->getCurrentLocale()); diff --git a/Bundle/PageBundle/Resources/translations/victoire.en.xliff b/Bundle/PageBundle/Resources/translations/victoire.en.xliff index 96b423440..2a273ac16 100644 --- a/Bundle/PageBundle/Resources/translations/victoire.en.xliff +++ b/Bundle/PageBundle/Resources/translations/victoire.en.xliff @@ -38,9 +38,13 @@ modal.form.template.settings.title %templateName%]]> - + form.page.type.slug.label - URL + Slug + + + form.page.type.permalink.label + Permalink form.page.type.status.label diff --git a/Bundle/PageBundle/Resources/translations/victoire.es.xliff b/Bundle/PageBundle/Resources/translations/victoire.es.xliff index 07ac8395d..b1731c7aa 100644 --- a/Bundle/PageBundle/Resources/translations/victoire.es.xliff +++ b/Bundle/PageBundle/Resources/translations/victoire.es.xliff @@ -40,7 +40,11 @@ form.page.type.slug.label - URL + Slug + + + form.page.type.permalink.label + Permalink form.page.type.status.label diff --git a/Bundle/PageBundle/Resources/translations/victoire.fr.xliff b/Bundle/PageBundle/Resources/translations/victoire.fr.xliff index eefb7ee0f..0fc55118b 100644 --- a/Bundle/PageBundle/Resources/translations/victoire.fr.xliff +++ b/Bundle/PageBundle/Resources/translations/victoire.fr.xliff @@ -48,7 +48,11 @@ form.page.type.slug.label - URL + Slug + + + form.page.type.permalink.label + Permalink form.page.type.status.label diff --git a/Bundle/PageBundle/Resources/views/Page/settings.html.twig b/Bundle/PageBundle/Resources/views/Page/settings.html.twig index 10b4de3ea..15514ab35 100644 --- a/Bundle/PageBundle/Resources/views/Page/settings.html.twig +++ b/Bundle/PageBundle/Resources/views/Page/settings.html.twig @@ -13,6 +13,9 @@
+ + {{ form_errors(form) }} +
{{ form_row(form.status) }} diff --git a/Bundle/ViewReferenceBundle/Connector/Redis/ViewReferenceRedisManager.php b/Bundle/ViewReferenceBundle/Connector/Redis/ViewReferenceRedisManager.php index e80058421..dcf1ec2ab 100644 --- a/Bundle/ViewReferenceBundle/Connector/Redis/ViewReferenceRedisManager.php +++ b/Bundle/ViewReferenceBundle/Connector/Redis/ViewReferenceRedisManager.php @@ -126,19 +126,23 @@ public function buildUrl($id) $reference = $this->repository->findById($id); $locale = $this->repository->findValueForId('locale', $id); $url = ''; - // while the reference has a slug - while (isset($reference['slug']) && $reference['slug'] != '') { - // Build url - if ($url != '') { - $url = $reference['slug'].'/'.$url; - } else { - $url = $reference['slug']; - } - // Set reference with the parent - if ($parentId = $reference['parent']) { - $reference = $this->repository->findById($parentId); - } else { - $reference = []; + if (null !== $reference['permalink'] && '' !== $reference['permalink']) { + $url = $reference['permalink']; + } else { + // while the reference has a slug (parent loop) + while (isset($reference['slug']) && $reference['slug'] != '') { + // Build url + if ($url != '') { + $url = $reference['slug'].'/'.$url; + } else { + $url = $reference['slug']; + } + // Set reference with the parent + if ($parentId = $reference['parent']) { + $reference = $this->repository->findById($parentId); + } else { + $reference = []; + } } } // set the new url diff --git a/Bundle/ViewReferenceBundle/Connector/ViewReferenceRepository.php b/Bundle/ViewReferenceBundle/Connector/ViewReferenceRepository.php index 2a8bec4fd..5e30f4c5c 100644 --- a/Bundle/ViewReferenceBundle/Connector/ViewReferenceRepository.php +++ b/Bundle/ViewReferenceBundle/Connector/ViewReferenceRepository.php @@ -53,7 +53,7 @@ public function findReferenceByView(View $view) * @param $url * @param $locale * - * @return mixed|null + * @return ViewReference|null */ public function getReferenceByUrl($url, $locale) { diff --git a/Bundle/ViewReferenceBundle/ViewReference/ViewReference.php b/Bundle/ViewReferenceBundle/ViewReference/ViewReference.php index 13ed6ce48..2afb080dc 100644 --- a/Bundle/ViewReferenceBundle/ViewReference/ViewReference.php +++ b/Bundle/ViewReferenceBundle/ViewReference/ViewReference.php @@ -8,6 +8,7 @@ class ViewReference protected $locale; protected $name; protected $slug; + protected $permalink; /** * @var string built by ViewReferenceCacheRepo */ @@ -121,6 +122,22 @@ public function setSlug($slug) $this->slug = $slug; } + /** + * @return mixed + */ + public function getPermalink() + { + return $this->permalink; + } + + /** + * @param mixed $permalink + */ + public function setPermalink($permalink) + { + $this->permalink = $permalink; + } + /** * @return string */ diff --git a/Tests/Features/Page/create.feature b/Tests/Features/Page/create.feature index 9d4c33a06..809016235 100644 --- a/Tests/Features/Page/create.feature +++ b/Tests/Features/Page/create.feature @@ -31,3 +31,29 @@ Feature: Create a page And I wait 5 seconds Then the url should match "/en/anoth" And I should see "Successfully modified page" + + @alice(Template) + Scenario: I can define a permalink for a page which is not already in use + Given the following Page: + | currentLocale | name | slug | parent | template | + | en | anakin skywalker | anakin-skywalker | home | base | + | en | luke skywalker | luke-skywalker | anakin-skywalker | base | + | en | contact page | contact | home | base | + And I am on "/en/anakin-skywalker/luke-skywalker" + And I open the settings menu + And I should see "UPDATE" + Then I fill in "page_settings_translations_en_permalink" with "anakin-skywalker" + And I submit the widget + And I wait 5 seconds + Then the url should match "/en/anakin-skywalker/luke-skywalker" + And I should see "The url is already in use" + When I fill in "page_settings_translations_en_permalink" with "contact" + And I submit the widget + And I wait 5 seconds + Then the url should match "/en/anakin-skywalker/luke-skywalker" + And I should see "The url is already in use" + Then I fill in "page_settings_translations_en_permalink" with "amazing-luke-skywalker" + And I submit the widget + And I wait 5 seconds + Then the url should match "/en/amazing-luke-skywalker" + And I should see "Successfully modified page"