Merge pull request #6657 from Zales0123/edit-taxons-slug

[Taxon] Edit taxon slug
This commit is contained in:
Paweł Jędrzejewski 2016-11-09 09:27:32 +01:00 committed by GitHub
commit 6a6aa6c36b
49 changed files with 571 additions and 177 deletions

View file

@ -0,0 +1,38 @@
<?php
namespace Sylius\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20161107154554 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('DROP INDEX permalink_uidx ON sylius_taxon_translation');
$this->addSql('ALTER TABLE sylius_taxon_translation DROP permalink');
$this->addSql('CREATE UNIQUE INDEX slug_uidx ON sylius_taxon_translation (locale, slug)');
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('DROP INDEX slug_uidx ON sylius_taxon_translation');
$this->addSql('ALTER TABLE sylius_taxon_translation ADD permalink VARCHAR(255) NOT NULL COLLATE utf8_unicode_ci');
$this->addSql('CREATE UNIQUE INDEX permalink_uidx ON sylius_taxon_translation (locale, permalink)');
}
}

View file

@ -13,16 +13,17 @@ Feature: Adding a new taxon
Given I want to create a new taxon
When I specify its code as "t-shirts"
And I name it "T-Shirts" in "English (United States)"
And I set its slug to "t-shirts"
And I add it
Then I should be notified that it has been successfully created
And the "T-Shirts" taxon should appear in the registry
@ui
Scenario: Adding a new taxon with permalink and description
Scenario: Adding a new taxon with slug and description
Given I want to create a new taxon
When I specify its code as "category"
And I name it "Category" in "English (United States)"
And I specify its permalink as "category" in "English (United States)"
And I set its slug to "category"
And I describe it as "Main taxonomy for products." in "English (United States)"
And I add it
Then I should be notified that it has been successfully created

View file

@ -14,6 +14,7 @@ Feature: Adding a new taxon for parent
Given I want to create a new taxon for "Category"
And I specify its code as "guns"
And I name it "Guns" in "English (United States)"
And I set its slug to "guns"
And I add it
Then I should be notified that it has been successfully created
And the "Guns" taxon should appear in the registry

View file

@ -26,22 +26,12 @@ Feature: Editing a taxon
Then I should be notified that it has been successfully edited
And this taxon description should be "Main taxonomy for stickers"
@ui
Scenario: Changing permalink
Given I want to modify the "T-Shirts" taxon
When I rename it to "Stickers" in "English (United States)"
And I change its description to "Main taxonomy for stickers" in "English (United States)"
And I change its permalink to "stickers" in "English (United States)"
And I save my changes
Then I should be notified that it has been successfully edited
And this taxon permalink should be "stickers"
@ui
Scenario: Changing parent taxon
Given I want to modify the "T-Shirts" taxon
When I rename it to "Stickers" in "English (United States)"
And I change its description to "Main taxonomy for stickers" in "English (United States)"
And I change its permalink to "stickers" in "English (United States)"
And I set its slug to "stickers"
And I change its parent taxon to "Accessories"
And I save my changes
Then I should be notified that it has been successfully edited

View file

@ -0,0 +1,80 @@
@managing_taxons
Feature: Editing taxon's slug
In order to In order to manage access path to taxon page
As an Administrator
I want to be able to edit taxon's slug
Background:
Given the store is available in "English (United States)"
And I am logged in as an administrator
@ui @javascript
Scenario: Creating a root taxon with an autogenerated slug
Given I want to create a new taxon
When I specify its code as "MEDIEVAL_WEAPONS"
And I name it "Medieval weapons" in "English (United States)"
And I add it
Then this taxon slug should be "medieval-weapons"
@ui
Scenario: Creating a root taxon with a custom slug
Given I want to create a new taxon
When I specify its code as "MEDIEVAL_WEAPONS"
And I name it "Medieval weapons" in "English (United States)"
And I set its slug to "mw"
And I add it
Then this taxon slug should be "mw"
@ui @javascript
Scenario: Creating a taxon with an autogenerated slug for parent
Given the store has "Medieval weapons" taxonomy
And I want to create a new taxon for "Medieval weapons"
When I specify its code as "SIEGE_ENGINES"
And I name it "Siege engines" in "English (United States)"
And I add it
Then this taxon slug should be "medieval-weapons/siege-engines"
@ui
Scenario: Creating a taxon with a custom slug for parent
Given the store has "Medieval weapons" taxonomy
And I want to create a new taxon for "Medieval weapons"
When I specify its code as "SIEGE_ENGINES"
And I name it "Siege engines" in "English (United States)"
And I set its slug to "medieval-weapons/siege"
And I add it
Then this taxon slug should be "medieval-weapons/siege"
@ui
Scenario: Seeing disabled slug field when editing a taxon
Given the store has "Medieval weapons" taxonomy
When I want to modify the "Medieval weapons" taxon
Then the slug field should not be editable
@ui @javascript
Scenario: Prevent from editing a slug while changing a taxon name
Given the store has "Medieval weapons" taxonomy
When I want to modify the "Medieval weapons" taxon
And I rename it to "Renaissance weapons" in "English (United States)"
And I save my changes
Then the slug of the "Renaissance weapons" taxon should still be "medieval-weapons"
@ui @javascript
Scenario: Automatically changing a taxon's slug while editing a taxon's name
Given the store has "Medieval weapons" taxonomy
When I want to modify the "Medieval weapons" taxon
And I enable slug modification
And I rename it to "Renaissance weapons" in "English (United States)"
And I save my changes
Then the slug of the "Renaissance weapons" taxon should be "renaissance-weapons"
@ui @javascript
Scenario: Manually changing a taxon's slug while editing a taxon's name
Given the store has "Medieval weapons" taxonomy
When I want to modify the "Medieval weapons" taxon
And I enable slug modification
And I rename it to "Renaissance weapons" in "English (United States)"
And I set its slug to "renaissance"
And I save my changes
Then the slug of the "Renaissance weapons" taxon should be "renaissance"

View file

@ -20,6 +20,7 @@ use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Core\Uploader\ImageUploaderInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\Component\Taxonomy\Generator\TaxonSlugGeneratorInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
@ -52,6 +53,11 @@ final class TaxonomyContext implements Context
*/
private $imageUploader;
/**
* @var TaxonSlugGeneratorInterface
*/
private $taxonSlugGenerator;
/**
* @var array
*/
@ -63,6 +69,7 @@ final class TaxonomyContext implements Context
* @param FactoryInterface $taxonImageFactory
* @param ObjectManager $objectManager
* @param ImageUploaderInterface $imageUploader
* @param TaxonSlugGeneratorInterface $taxonSlugGenerator
* @param array $minkParameters
*/
public function __construct(
@ -71,6 +78,7 @@ final class TaxonomyContext implements Context
FactoryInterface $taxonImageFactory,
ObjectManager $objectManager,
ImageUploaderInterface $imageUploader,
TaxonSlugGeneratorInterface $taxonSlugGenerator,
array $minkParameters
) {
$this->taxonRepository = $taxonRepository;
@ -78,6 +86,7 @@ final class TaxonomyContext implements Context
$this->taxonImageFactory = $taxonImageFactory;
$this->objectManager = $objectManager;
$this->imageUploader = $imageUploader;
$this->taxonSlugGenerator = $taxonSlugGenerator;
$this->minkParameters = $minkParameters;
}
@ -133,6 +142,7 @@ final class TaxonomyContext implements Context
$taxon = $this->taxonFactory->createNew();
$taxon->setName($name);
$taxon->setCode(StringInflector::nameToCode($name));
$taxon->setSlug($this->taxonSlugGenerator->generate($name));
return $taxon;
}

View file

@ -17,6 +17,7 @@ use Sylius\Behat\Page\Admin\Taxon\CreatePageInterface;
use Sylius\Behat\Page\Admin\Taxon\UpdatePageInterface;
use Sylius\Behat\Service\Resolver\CurrentPageResolverInterface;
use Sylius\Behat\Service\SharedStorageInterface;
use Sylius\Component\Core\Model\Taxon;
use Sylius\Component\Core\Model\TaxonInterface;
use Webmozart\Assert\Assert;
@ -122,6 +123,40 @@ final class ManagingTaxonsContext implements Context
$this->updatePage->nameIt($name, $language);
}
/**
* @When I set its slug to :slug
*/
public function iSetItsSlugTo($slug)
{
/** @var CreatePageInterface|UpdatePageInterface $currentPage */
$currentPage = $this->currentPageResolver->getCurrentPageWithForm([
$this->createPage,
$this->createForParentPage,
$this->updatePage,
]);
$currentPage->specifySlug($slug);
}
/**
* @Then the slug field should not be editable
*/
public function theSlugFieldShouldNotBeEditable()
{
Assert::true(
$this->updatePage->isSlugReadOnly(),
'Slug should be immutable, but it does not.'
);
}
/**
* @When I enable slug modification
*/
public function iEnableSlugModification()
{
$this->updatePage->enableSlugModification();
}
/**
* @When I change its description to :description in :language
*/
@ -130,22 +165,6 @@ final class ManagingTaxonsContext implements Context
$this->updatePage->describeItAs($description, $language);
}
/**
* @When I specify its permalink as :permalink in :language
*/
public function iSpecifyItsPermalinkAs($permalink, $language)
{
$this->createPage->specifyPermalink($permalink, $language);
}
/**
* @When I change its permalink to :permalink in :language
*/
public function iChangeItsPermalinkToIn($permalink, $language)
{
$this->updatePage->specifyPermalink($permalink, $language);
}
/**
* @When I describe it as :description in :language
*/
@ -263,6 +282,19 @@ final class ManagingTaxonsContext implements Context
);
}
/**
* @Then /^the slug of the ("[^"]+" taxon) should(?:| still) be "([^"]+)"$/
*/
public function productSlugShouldBe(TaxonInterface $taxon, $slug)
{
$this->updatePage->open(['id' => $taxon->getId()]);
Assert::true(
$this->updatePage->hasResourceValues(['slug' => $slug]),
sprintf('Taxon\'s slug should be %s.', $slug)
);
}
/**
* @Then /^this taxon should (belongs to "[^"]+")$/
*/

View file

@ -139,7 +139,7 @@ final class ProductContext implements Context
*/
public function iCheckListOfProductsForTaxon(TaxonInterface $taxon)
{
$this->taxonShowPage->open(['permalink' => $taxon->getPermalink()]);
$this->taxonShowPage->open(['slug' => $taxon->getSlug()]);
}
/**
@ -295,7 +295,7 @@ final class ProductContext implements Context
{
$sorting = ['createdAt' => 'oldest' === $sortDirection ? 'asc' : 'desc'];
$this->taxonShowPage->open(['permalink' => $taxon->getPermalink(), 'sorting' => $sorting]);
$this->taxonShowPage->open(['slug' => $taxon->getSlug(), 'sorting' => $sorting]);
}
/**

View file

@ -106,14 +106,16 @@ class CreatePage extends BaseCreatePage implements CreatePageInterface
public function nameIt($name, $languageCode)
{
$this->getDocument()->fillField(sprintf('sylius_taxon_translations_%s_name', $languageCode), $name);
$this->waitForSlugGenerationIfNecessary();
}
/**
* {@inheritdoc}
*/
public function specifyPermalink($permalink, $languageCode)
public function specifySlug($slug)
{
$this->getDocument()->fillField(sprintf('sylius_taxon_translations_%s_permalink', $languageCode), $permalink);
$this->getDocument()->fillField('Slug', $slug);
}
/**
@ -210,7 +212,7 @@ JS;
'images' => '#sylius_taxon_images',
'name' => '#sylius_taxon_translations_en_US_name',
'parent' => '#sylius_taxon_parent',
'permalink' => '#sylius_taxon_translations_en_US_permalink',
'slug' => '#sylius_taxon_translations_en_US_slug',
'tree' => '.ui.list',
]);
}
@ -275,4 +277,13 @@ JS;
return $driver;
}
private function waitForSlugGenerationIfNecessary()
{
if ($this->getDriver() instanceof Selenium2Driver) {
$this->getDocument()->waitFor(1000, function () {
return '' !== $this->getElement('slug')->getValue();
});
}
}
}

View file

@ -71,10 +71,9 @@ interface CreatePageInterface extends BaseCreatePageInterface
public function specifyCode($code);
/**
* @param string $permalink
* @param string $languageCode
* @param string $slug
*/
public function specifyPermalink($permalink, $languageCode);
public function specifySlug($slug);
/**
* @param string $path

View file

@ -11,6 +11,7 @@
namespace Sylius\Behat\Page\Admin\Taxon;
use Behat\Mink\Driver\Selenium2Driver;
use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\ElementNotFoundException;
use Sylius\Behat\Behaviour\ChecksCodeImmutability;
@ -47,14 +48,16 @@ class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
public function nameIt($name, $languageCode)
{
$this->getDocument()->fillField(sprintf('sylius_taxon_translations_%s_name', $languageCode), $name);
$this->waitForSlugGenerationIfNecessary();
}
/**
* {@inheritdoc}
*/
public function specifyPermalink($permalink, $languageCode)
public function specifySlug($slug)
{
$this->getDocument()->fillField(sprintf('sylius_taxon_translations_%s_permalink', $languageCode), $permalink);
$this->getDocument()->fillField('Slug', $slug);
}
/**
@ -93,6 +96,14 @@ class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
return false === stripos($pageText, '404 Not Found');
}
/**
* {@inheritdoc}
*/
public function isSlugReadOnly()
{
return 'readonly' === $this->getElement('slug')->getAttribute('readonly');
}
/**
* {@inheritdoc}
*/
@ -108,6 +119,11 @@ class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
$imageElement->clickLink('Delete');
}
public function enableSlugModification()
{
$this->getElement('toggle_taxon_slug_modification_button')->press();
}
/**
* {@inheritdoc}
*/
@ -194,7 +210,8 @@ class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
'images' => '#sylius_taxon_images',
'name' => '#sylius_taxon_translations_en_US_name',
'parent' => '#sylius_taxon_parent',
'permalink' => '#sylius_taxon_translations_en_US_permalink',
'slug' => '#sylius_taxon_translations_en_US_slug',
'toggle_taxon_slug_modification_button' => '#toggle-taxon-slug-modification',
]);
}
@ -246,4 +263,21 @@ class UpdatePage extends BaseUpdatePage implements UpdatePageInterface
return $inputCode->getParent()->getParent()->getParent();
}
private function waitForSlugGenerationIfNecessary()
{
if (!$this->getDriver() instanceof Selenium2Driver) {
return;
}
$slugElement = $this->getElement('slug');
if ($slugElement->hasAttribute('readonly')) {
return;
}
$value = $slugElement->getValue();
$this->getDocument()->waitFor(1000, function () use ($slugElement, $value) {
return $value !== $slugElement->getValue();
});
}
}

View file

@ -43,10 +43,9 @@ interface UpdatePageInterface extends BaseUpdatePageInterface
public function nameIt($name, $languageCode);
/**
* @param string $permalink
* @param string $languageCode
* @param string $slug
*/
public function specifyPermalink($permalink, $languageCode);
public function specifySlug($slug);
/**
* @return bool
@ -66,6 +65,11 @@ interface UpdatePageInterface extends BaseUpdatePageInterface
*/
public function isImageWithCodeDisplayed($code);
/**
* @return bool
*/
public function isSlugReadOnly();
/**
* @param string $code
*/
@ -73,6 +77,8 @@ interface UpdatePageInterface extends BaseUpdatePageInterface
public function removeFirstImage();
public function enableSlugModification();
/**
* @return int
*/

View file

@ -218,6 +218,7 @@
<argument type="service" id="__symfony__.sylius.factory.taxon_image" />
<argument type="service" id="__symfony__.doctrine.orm.entity_manager" />
<argument type="service" id="__symfony__.sylius.image_uploader" />
<argument type="service" id="__symfony__.sylius.generator.taxon_slug" />
<argument>%__behat__.mink.parameters%</argument>
<tag name="fob.context_service" />
</service>

View file

@ -1,3 +1,7 @@
sylius_admin_api_taxon:
resource: "@SyliusAdminBundle/Resources/config/routing/api/taxon.yml"
prefix: /taxons
sylius_admin_api_product:
resource: "@SyliusAdminBundle/Resources/config/routing/api/product.yml"
prefix: /products

View file

@ -0,0 +1,5 @@
sylius_admin_api_generate_taxon_slug:
path: /generate-slug/
methods: [GET]
defaults:
_controller: sylius.controller.taxon_slug:generateAction

View file

@ -0,0 +1,62 @@
$(document).ready(function() {
var timeout;
$('[name*="sylius_taxon[translations]"][name*="[name]"]').on('input', function() {
clearTimeout(timeout);
$element = $(this);
timeout = setTimeout(function() {
updateSlug($element);
}, 1000);
});
$('#toggle-taxon-slug-modification').on('click', function(e) {
e.preventDefault();
toggleSlugModification($(this), $(this).siblings('input'));
});
});
function updateSlug($element) {
$slugInput = $element.parents('.content').find('[name*="[slug]"]');
$loadableParent = $slugInput.parents('.field.loadable');
if ('readonly' == $slugInput.attr('readonly')) {
return;
}
$loadableParent.addClass('loading');
if ('' != $slugInput.attr('data-parent') && undefined != $slugInput.attr('data-parent')) {
$data = { name: $element.val(), parentId: $slugInput.attr('data-parent') };
} else if ($('#sylius_taxon_parent').length > 0 && $('#sylius_taxon_parent').is(':visible') && '' != $('#sylius_taxon_parent').val()) {
$data = { name: $element.val(), parentId: $('#sylius_taxon_parent').val() };
} else {
$data = { name: $element.val() };
}
$.ajax({
type: "GET",
url: $slugInput.attr('data-url'),
data: $data,
dataType: "json",
accept: "application/json",
success: function(data) {
$slugInput.val(data.slug);
if ($slugInput.parents('.field').hasClass('error')) {
$slugInput.parents('.field').removeClass('error');
$slugInput.parents('.field').find('.sylius-validation-error').remove();
}
$loadableParent.removeClass('loading');
}
});
}
function toggleSlugModification($button, $slugInput) {
if ($slugInput.attr('readonly')) {
$slugInput.removeAttr('readonly');
$button.html('<i class="unlock icon"></i>');
} else {
$slugInput.attr('readonly', 'readonly');
$button.html('<i class="lock icon"></i>');
}
}

View file

@ -6,7 +6,7 @@
<div class="ui action input">
{{ form_widget(slugField, {'attr': {'readonly': 'readonly', 'data-url': path('sylius_admin_api_generate_product_slug')}}) }}
<span id="toggle-slug-modification" class="ui icon button">
<i class="lock icon"></i>
<i class="unlock icon"></i>
</span>
</div>
{% endif %}

View file

@ -17,7 +17,7 @@
</div>
<div class="content{% if 0 == loop.index0 %} active{% endif %}">
{{ form_row(translationForm.name) }}
{{ form_row(translationForm.permalink) }}
{% include '@SyliusAdmin/Taxon/_slugField.html.twig' with { 'slugField': translationForm.slug } %}
{{ form_row(translationForm.description) }}
</div>
{% endfor %}

View file

@ -0,0 +1,14 @@
<div class="{% if slugField.vars.required %}required {% endif %}field{% if slugField.vars.errors|length > 0 %} error{% endif %} ui loadable form">
{{ form_label(slugField) }}
{% if taxon.slug == null %}
{{ form_widget(slugField, {'attr': {'data-url': path('sylius_admin_api_generate_taxon_slug'), 'data-parent': app.request.attributes.get('id')}}) }}
{% else %}
<div class="ui action input">
{{ form_widget(slugField, {'attr': {'readonly': 'readonly', 'data-url': path('sylius_admin_api_generate_taxon_slug')}}) }}
<span id="toggle-taxon-slug-modification" class="ui icon button">
<i class="lock icon"></i>
</span>
</div>
{% endif %}
{{ form_errors(slugField) }}
</div>

View file

@ -17,6 +17,7 @@ use Sylius\Component\Core\Model\TaxonInterface;
use Sylius\Component\Locale\Model\LocaleInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\Component\Taxonomy\Generator\TaxonSlugGeneratorInterface;
use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
@ -46,6 +47,11 @@ final class TaxonExampleFactory implements ExampleFactoryInterface
*/
private $faker;
/**
* @var TaxonSlugGeneratorInterface
*/
private $taxonSlugGenerator;
/**
* @var OptionsResolver
*/
@ -56,16 +62,19 @@ final class TaxonExampleFactory implements ExampleFactoryInterface
* @param TaxonRepositoryInterface $taxonRepository
* @param ObjectManager $taxonManager
* @param RepositoryInterface $localeRepository
* @param TaxonSlugGeneratorInterface $taxonSlugGenerator
*/
public function __construct(
FactoryInterface $taxonFactory,
TaxonRepositoryInterface $taxonRepository,
ObjectManager $taxonManager,
RepositoryInterface $localeRepository
RepositoryInterface $localeRepository,
TaxonSlugGeneratorInterface $taxonSlugGenerator
) {
$this->taxonFactory = $taxonFactory;
$this->taxonRepository = $taxonRepository;
$this->localeRepository = $localeRepository;
$this->taxonSlugGenerator = $taxonSlugGenerator;
$this->faker = \Faker\Factory::create();
$this->optionsResolver =
@ -106,6 +115,7 @@ final class TaxonExampleFactory implements ExampleFactoryInterface
$taxon->setName($options['name']);
$taxon->setDescription($options['description']);
$taxon->setSlug($this->taxonSlugGenerator->generate($options['name']));
}
foreach ($options['children'] as $childOptions) {

View file

@ -74,6 +74,7 @@
<argument type="service" id="sylius.repository.taxon" />
<argument type="service" id="sylius.manager.taxon" />
<argument type="service" id="sylius.repository.locale" />
<argument type="service" id="sylius.generator.taxon_slug" />
</service>
<service id="sylius.fixture.example_factory.product" class="Sylius\Bundle\CoreBundle\Fixture\Factory\ProductExampleFactory">

View file

@ -2,14 +2,14 @@
# (c) Paweł Jędrzejewski
sylius_shop_taxon_show:
path: /taxons/{permalink}
path: /taxons/{slug}
methods: [GET]
defaults:
_controller: sylius.controller.taxon:showAction
_sylius:
template: SyliusShopBundle:Taxon:show.html.twig
repository:
method: findOneByPermalink
arguments: [$permalink]
method: findOneBySlug
arguments: [$slug]
requirements:
permalink: .+
slug: .+

View file

@ -7,7 +7,7 @@
{{ sylius_grid_render_filter(products, filter) }}
{% endfor %}
<button type="submit" class="ui primary icon labeled button"><i class="search icon"></i> {{ 'sylius.ui.search'|trans }}</button>
<a href="{{ path('sylius_shop_taxon_show', { 'permalink': app.request.query.get('permalink') }) }}" class="ui negative icon labeled button">
<a href="{{ path('sylius_shop_taxon_show', { 'slug': app.request.query.get('slug') }) }}" class="ui negative icon labeled button">
<i class="cancel icon"></i> {{ 'sylius.ui.clear'|trans }}
</a>
</div>

View file

@ -1,7 +1,7 @@
{% set definition = resources.definition %}
{% set oldest_first_path = path('sylius_shop_taxon_show', {'sorting': {'createdAt': 'asc'}, 'permalink': resources.parameters.get('permalink')}) %}
{% set newest_first_path = path('sylius_shop_taxon_show', {'sorting': {'createdAt': 'desc'}, 'permalink': resources.parameters.get('permalink')}) %}
{% set oldest_first_path = path('sylius_shop_taxon_show', {'sorting': {'createdAt': 'asc'}, 'slug': resources.parameters.get('slug')}) %}
{% set newest_first_path = path('sylius_shop_taxon_show', {'sorting': {'createdAt': 'desc'}, 'slug': resources.parameters.get('slug')}) %}
{% if app.request.query.get('sorting') is empty or app.request.basePath == newest_first_path %}
{% set current_sorting_label = 'sylius.ui.newest_first'|trans|lower %}

View file

@ -7,11 +7,11 @@
{% set parents = taxon.parents|reverse %}
{% for parent in parents %}
<a href="{{ path('sylius_shop_taxon_show', {'permalink': parent.permalink}) }}" class="section">{{ parent.name }}</a>
<a href="{{ path('sylius_shop_taxon_show', {'slug': parent.slug}) }}" class="section">{{ parent.name }}</a>
<div class="divider"> / </div>
{% endfor %}
<a href="{{ path('sylius_shop_taxon_show', {'permalink': taxon.permalink}) }}" class="section">{{ taxon.name }}</a>
<a href="{{ path('sylius_shop_taxon_show', {'slug': taxon.slug}) }}" class="section">{{ taxon.name }}</a>
<div class="divider"> / </div>
{% endif %}
<div class="section">{{ product.name }}</div>

View file

@ -6,11 +6,11 @@
{% set parents = taxon.parents|reverse %}
{% for parent in parents %}
<a href="{{ path('sylius_shop_taxon_show', {'permalink': parent.permalink}) }}" class="section">{{ parent.name }}</a>
<a href="{{ path('sylius_shop_taxon_show', {'slug': parent.slug}) }}" class="section">{{ parent.name }}</a>
<div class="divider"> / </div>
{% endfor %}
<a href="{{ path('sylius_shop_taxon_show', {'permalink': taxon.permalink}) }}" class="section">{{ taxon.name }}</a>
<a href="{{ path('sylius_shop_taxon_show', {'slug': taxon.slug}) }}" class="section">{{ taxon.name }}</a>
<div class="divider"> / </div>
{% endif %}
<div class="active section">{{ product.name }}</div>

View file

@ -4,7 +4,7 @@
<a href="{{ path('sylius_shop_homepage') }}" class="section">{{ 'sylius.ui.home'|trans }}</a>
<div class="divider"> / </div>
{% for parent in parents %}
<a href="{{ path('sylius_shop_taxon_show', {'permalink': parent.permalink}) }}" class="section">{{ parent.name }}</a>
<a href="{{ path('sylius_shop_taxon_show', {'slug': parent.slug}) }}" class="section">{{ parent.name }}</a>
<div class="divider"> / </div>
{% endfor %}
<div class="active section">{{ taxon.name }}</div>

View file

@ -1,11 +1,11 @@
<div class="ui fluid vertical menu">
<div class="header item">{{ taxon.name }}</div>
{% if taxon.parent is not empty %}
<a href="{{ path('sylius_shop_taxon_show', {'permalink': taxon.parent.permalink}) }}" class="item">
<a href="{{ path('sylius_shop_taxon_show', {'slug': taxon.parent.slug}) }}" class="item">
<i class="up arrow icon"></i> {{ 'sylius.ui.go_level_up'|trans }}
</a>
{% endif %}
{% for child in taxon.children %}
<a href="{{ path('sylius_shop_taxon_show', {'permalink': child.permalink}) }}" class="item">{{ child.name }}</a>
<a href="{{ path('sylius_shop_taxon_show', {'slug': child.slug}) }}" class="item">{{ child.name }}</a>
{% endfor %}
</div>

View file

@ -12,7 +12,7 @@
</div>
</div>
{% else %}
<a href="{{ path('sylius_shop_taxon_show', {'permalink': taxon.permalink}) }}" class="item">{{ taxon.name }}</a>
<a href="{{ path('sylius_shop_taxon_show', {'slug': taxon.slug}) }}" class="item">{{ taxon.name }}</a>
{% endif %}
{% endmacro %}

View file

@ -13,7 +13,7 @@
{
'code': taxon.code,
'template': '@SyliusShop/Product/Grid/_default.html.twig',
'permalink': app.request.attributes.get('permalink'),
'slug': app.request.attributes.get('slug'),
'sorting': app.request.query.get('sorting'),
'criteria': app.request.query.get('criteria')
}))

View file

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Bundle\TaxonomyBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
/**
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
*/
class TaxonSlugController extends Controller
{
/**
* @param Request $request
*
* @return JsonResponse
*/
public function generateAction(Request $request)
{
$name = $request->query->get('name');
$parentId = $request->query->get('parentId');
return new JsonResponse([
'slug' => $this->get('sylius.generator.taxon_slug')->generate($name, $parentId),
]);
}
}

View file

@ -93,13 +93,13 @@ class TaxonRepository extends EntityRepository implements TaxonRepositoryInterfa
/**
* {@inheritdoc}
*/
public function findOneByPermalink($permalink)
public function findOneBySlug($slug)
{
return $this->createQueryBuilder('o')
->addSelect('translation')
->leftJoin('o.translations', 'translation')
->where('translation.permalink = :permalink')
->setParameter('permalink', $permalink)
->where('translation.slug = :slug')
->setParameter('slug', $slug)
->orderBy('o.position')
->getQuery()
->getOneOrNullResult()

View file

@ -28,9 +28,8 @@ class TaxonTranslationType extends AbstractResourceType
->add('name', 'text', [
'label' => 'sylius.form.taxon.name',
])
->add('permalink', 'text', [
'required' => false,
'label' => 'sylius.form.taxon.permalink',
->add('slug', 'text', [
'label' => 'sylius.form.taxon.slug',
])
->add('description', 'textarea', [
'required' => false,

View file

@ -26,9 +26,6 @@
<field name="slug" column="slug" type="string" unique="true">
<gedmo:slug fields="name" unique="true" />
</field>
<field name="permalink" column="permalink" type="string" unique="true">
<gedmo:slug fields="name" unique="true" />
</field>
<field name="description" column="description" type="string" />
<field name="left" column="tree_left" type="int">
<gedmo:tree-left />

View file

@ -12,7 +12,6 @@
-->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@ -22,24 +21,10 @@
<generator strategy="AUTO" />
</id>
<field name="name" column="name" type="string" />
<field name="slug" column="slug" type="string" unique="false">
<gedmo:slug fields="name" unique="false" />
</field>
<field name="permalink" column="permalink" type="string" unique="false">
<gedmo:slug fields="name" unique="false" >
<gedmo:handler class="Sylius\Bundle\ResourceBundle\GedmoHandler\TranslationSlugHandler">
<gedmo:handler-option name="relationField" value="translatable"/>
<gedmo:handler-option name="relationParentRelationField" value="parent"/>
<gedmo:handler-option name="parentFieldMethod" value="getPermalink"/>
<gedmo:handler-option name="locale" value="locale"/>
<gedmo:handler-option name="translate" value="translate"/>
<gedmo:handler-option name="separator" value="/"/>
</gedmo:handler>
</gedmo:slug>
</field>
<field name="slug" column="slug" type="string" unique="false" />
<field name="description" column="description" type="text" nullable="true" />
<unique-constraints>
<unique-constraint columns="locale,permalink" name="permalink_uidx" />
<unique-constraint columns="locale,slug" name="slug_uidx" />
</unique-constraints>
</mapped-superclass>

View file

@ -13,6 +13,12 @@
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="sylius.controller.taxon_slug" class="Sylius\Bundle\TaxonomyBundle\Controller\TaxonSlugController">
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>
<service id="sylius.form.type.taxon_choice" class="Sylius\Bundle\TaxonomyBundle\Form\Type\TaxonChoiceType">
<argument type="service" id="sylius.repository.taxon" />
<tag name="form.type" alias="sylius_taxon_choice" />
@ -27,5 +33,9 @@
decorates="sylius.factory.taxon" public="false">
<argument type="service" id="sylius.factory.taxon.decorated.inner" />
</service>
<service id="sylius.generator.taxon_slug" class="Sylius\Component\Taxonomy\Generator\TaxonSlugGenerator">
<argument type="service" id="sylius.repository.taxon" />
</service>
</services>
</container>

View file

@ -26,10 +26,10 @@
</property>
<constraint name="Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity">
<option name="fields">
<value>locale</value>
<value>permalink</value>
<value>locale</value>
<value>slug</value>
</option>
<option name="message">sylius.permalink.unique</option>
<option name="message">sylius.slug.unique</option>
<option name="groups">sylius</option>
</constraint>
</class>

View file

@ -7,6 +7,6 @@ sylius:
description: Description
name: Name
parent: Parent
permalink: Permalink
slug: Slug
taxonomy:
name: Name

View file

@ -52,7 +52,7 @@ final class TaxonTranslationTypeSpec extends ObjectBehavior
;
$builder
->add('permalink', 'text', Argument::any())
->add('slug', 'text', Argument::any())
->shouldBeCalled()
->willReturn($builder)
;

View file

@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Taxonomy\Generator;
use Behat\Transliterator\Transliterator;
use Sylius\Component\Taxonomy\Model\TaxonInterface;
use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface;
use Webmozart\Assert\Assert;
/**
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
*/
final class TaxonSlugGenerator implements TaxonSlugGeneratorInterface
{
const SLUG_SEPARATOR = '/';
/**
* @var TaxonRepositoryInterface
*/
private $taxonRepository;
/**
* @param TaxonRepositoryInterface $taxonRepository
*/
public function __construct(TaxonRepositoryInterface $taxonRepository)
{
$this->taxonRepository = $taxonRepository;
}
/**
* {@inheritdoc}
*/
public function generate($name, $parentId = null)
{
$taxonSlug = Transliterator::transliterate($name);
if (null === $parentId) {
return $taxonSlug;
}
/** @var TaxonInterface $parent */
$parent = $this->taxonRepository->find($parentId);
Assert::notNull($parent, sprintf('There is no parent taxon with id %d.', $parentId));
return $parent->getSlug().self::SLUG_SEPARATOR.$taxonSlug;
}
}

View file

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sylius\Component\Taxonomy\Generator;
/**
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
*/
interface TaxonSlugGeneratorInterface
{
/**
* @param string $name
* @param int|null $parentId
*
* @return string
*/
public function generate($name, $parentId = null);
}

View file

@ -236,34 +236,6 @@ class Taxon implements TaxonInterface
$this->translate()->setSlug($slug);
}
/**
* {@inheritdoc}
*/
public function getPermalink()
{
$permalink = $this->translate()->getPermalink();
if (null !== $permalink) {
return $permalink;
}
if (null === $this->parent) {
return $this->getSlug();
}
$this->setPermalink($permalink = $this->parent->getPermalink().'/'.$this->getSlug());
return $permalink;
}
/**
* {@inheritdoc}
*/
public function setPermalink($permalink)
{
$this->translate()->setPermalink($permalink);
}
/**
* {@inheritdoc}
*/

View file

@ -33,11 +33,6 @@ class TaxonTranslation extends AbstractTranslation implements TaxonTranslationIn
*/
protected $slug;
/**
* @var string
*/
protected $permalink;
/**
* @var string
*/
@ -91,22 +86,6 @@ class TaxonTranslation extends AbstractTranslation implements TaxonTranslationIn
$this->slug = $slug;
}
/**
* {@inheritdoc}
*/
public function getPermalink()
{
return $this->permalink;
}
/**
* {@inheritdoc}
*/
public function setPermalink($permalink)
{
$this->permalink = $permalink;
}
/**
* {@inheritdoc}
*/

View file

@ -29,16 +29,6 @@ interface TaxonTranslationInterface extends SlugAwareInterface, ResourceInterfac
*/
public function setName($name);
/**
* @return string
*/
public function getPermalink();
/**
* @param string $permalink
*/
public function setPermalink($permalink);
/**
* @return string
*/

View file

@ -62,11 +62,11 @@ interface TaxonRepositoryInterface extends RepositoryInterface
public function findNodesTreeSorted();
/**
* @param string $permalink
* @param string $slug
*
* @return TaxonInterface|null
*/
public function findOneByPermalink($permalink);
public function findOneBySlug($slug);
/**
* @param string $name

View file

@ -22,7 +22,9 @@
"require": {
"php": "^5.6|^7.0",
"sylius/resource": "^1.0"
"behat/transliterator": "^1.1",
"sylius/resource": "^1.0",
"webmozart/assert": "^1.0"
},
"require-dev": {
"phpspec/phpspec": "^3.1"

View file

@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace spec\Sylius\Component\Taxonomy\Generator;
use PhpSpec\ObjectBehavior;
use Sylius\Component\Taxonomy\Generator\TaxonSlugGenerator;
use Sylius\Component\Taxonomy\Generator\TaxonSlugGeneratorInterface;
use Sylius\Component\Taxonomy\Model\TaxonInterface;
use Sylius\Component\Taxonomy\Repository\TaxonRepositoryInterface;
/**
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
*/
final class TaxonSlugGeneratorSpec extends ObjectBehavior
{
function let(TaxonRepositoryInterface $taxonRepository)
{
$this->beConstructedWith($taxonRepository);
}
function it_is_initializable()
{
$this->shouldHaveType(TaxonSlugGenerator::class);
}
function it_implements_taxon_slug_generator_interface()
{
$this->shouldImplement(TaxonSlugGeneratorInterface::class);
}
function it_generates_slug_based_on_new_taxon_name_and_parent_taxon_slug(
TaxonInterface $parent,
TaxonRepositoryInterface $taxonRepository
) {
$taxonRepository->find(1)->willReturn($parent);
$parent->getSlug()->willReturn('board-games');
$this->generate('Battle games', 1)->shouldReturn('board-games/battle-games');;
}
function it_generates_slug_based_on_new_taxon_name_if_this_taxon_has_no_parent()
{
$this->generate('Board games')->shouldReturn('board-games');;
}
function it_throws_exception_if_parent_taxon_with_given_id_does_not_exist(
TaxonRepositoryInterface $taxonRepository
) {
$taxonRepository->find(1)->willReturn(null);
$this
->shouldThrow(new \InvalidArgumentException('There is no parent taxon with id 1.'))
->during('generate', ['Battle games', 1])
;
}
}

View file

@ -147,17 +147,6 @@ final class TaxonSpec extends ObjectBehavior
$this->getSlug()->shouldReturn('t-shirts');
}
function it_has_no_permalink_by_default()
{
$this->getPermalink()->shouldReturn(null);
}
function its_permalink_is_mutable()
{
$this->setPermalink('woman-clothing');
$this->getPermalink()->shouldReturn('woman-clothing');
}
function it_initializes_child_taxon_collection_by_default()
{
$this->getChildren()->shouldHaveType(Collection::class);

View file

@ -73,15 +73,4 @@ final class TaxonTranslationSpec extends ObjectBehavior
$this->setSlug('t-shirts');
$this->getSlug()->shouldReturn('t-shirts');
}
function it_has_no_permalink_by_default()
{
$this->getPermalink()->shouldReturn(null);
}
function its_permalink_is_mutable()
{
$this->setPermalink('woman-clothing');
$this->getPermalink()->shouldReturn('woman-clothing');
}
}