Merge pull request #3350 from pamil/metadata-formulas

[Metadata] Complete component & bundle integrated with Sylius Core
This commit is contained in:
Paweł Jędrzejewski 2016-01-26 16:31:16 +01:00
commit 90ccc116dd
135 changed files with 7924 additions and 27 deletions

View file

@ -0,0 +1,43 @@
<?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\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class Version20150817113941 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('CREATE TABLE sylius_metadata (id INT AUTO_INCREMENT NOT NULL, `key` VARCHAR(255) NOT NULL, metadata LONGTEXT NOT NULL COMMENT \'(DC2Type:object)\', UNIQUE INDEX UNIQ_B0AF6FFB4E645A7E (`key`), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
}
/**
* @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 TABLE sylius_metadata');
}
}

View file

@ -144,6 +144,19 @@ default:
filters:
tags: "@localization"
metadata:
contexts:
- Behat\MinkExtension\Context\MinkContext
- Sylius\Bundle\CoreBundle\Behat\HookContext
- Sylius\Bundle\CoreBundle\Behat\CoreContext
- Sylius\Bundle\ChannelBundle\Behat\ChannelContext
- Sylius\Bundle\ResourceBundle\Behat\FixtureContext
- Sylius\Bundle\WebBundle\Behat\WebContext
- Sylius\Bundle\ProductBundle\Behat\ProductContext
- Sylius\Bundle\CoreBundle\Behat\MetadataContext
filters:
tags: "@metadata"
oauth:
contexts:
- Behat\MinkExtension\Context\MinkContext

View file

@ -0,0 +1,75 @@
@metadata @ui
Feature: Metadata customization
In order to customize metadata
As a store owner
I want to have user-friendly metadata customization form
Background:
Given store has default configuration
And I am logged in as administrator
Scenario: Showing customization form
When I am customizing metadata
Then I should see metadata customization form
Scenario: Creating page metadata without Twitter metadata
Given I am customizing metadata with identifier "FOO-BAR"
When I fill in "Title" with "Lorem ipsum"
And I fill in "Keywords" with "sylius, ecommerce"
And I press "Save changes"
Then I should be on the page of metadata container with id "FOO-BAR"
And I should see the following metadata:
| Title | Lorem ipsum |
| Keywords | sylius, ecommerce |
| Description | <empty> |
| Twitter | <empty> |
@javascript
Scenario: Managing dynamic Twitter form
Given I am customizing metadata
When I select "Application" from "Twitter Card"
Then I should see Twitter's application card form
@javascript
Scenario: Removing dynamic Twitter form content when select is empty
Given I am customizing metadata
And I select "Application" from "Twitter Card"
When I deselect "Twitter Card"
Then I should not see Twitter's application card form
@javascript
Scenario: Creating page metadata with Twitter metadata
Given I am customizing metadata with identifier "TWEET-IT"
When I fill in "Title" with "Lorem ipsum"
And I select "Player" from "Twitter Card"
And I fill in "Site" with "@sylius"
And I press "Save changes"
Then I should be on the page of metadata container with id "TWEET-IT"
And I should see the following metadata:
| Title | Lorem ipsum |
| Twitter.Site | @sylius |
@javascript
Scenario: Setting Twitter metadata as empty after filling a few fields removes Twitter metadata
Given I am customizing metadata with identifier "DEFAULT"
When I fill in "Title" with "Lorem ipsum"
And I select "Player" from "Twitter Card"
And I fill in "Site" with "@sylius"
And I deselect "Twitter Card"
And I press "Save changes"
Then I should be on the page of metadata container with id "DEFAULT"
And I should see the following metadata:
| Title | Lorem ipsum |
| Twitter | <empty> |
@javascript
Scenario: Setting Twitter metadata as empty removes it
Given there is the following metadata "YOLO-616":
| Twitter.Card | Summary |
| Twitter.Title | Lorem ipsum |
And I am customizing metadata with identifier "YOLO-616"
When I deselect "Twitter Card"
And I press "Save changes"
Then I should be on the page of metadata container with id "YOLO-616"
And I should see the following metadata:
| Twitter | <empty> |

View file

@ -0,0 +1,14 @@
@metadata
Feature: Metadata management
In order to manage metadata on my store
As a store owner
I want to have easy and intuitive access to managing metadata
Background:
Given store has default configuration
And I am logged in as administrator
Scenario: Accessing default metadata customization page
Given I am on the metadata container index page
When I click "Customize default metadata"
Then I should be customizing default metadata

View file

@ -0,0 +1,21 @@
@metadata
Feature: Metadata management
In order to manage metadata on my store
As a store owner
I want to have easy and intuitive access to managing metadata
Background:
Given store has default configuration
And there are following options:
| code | name | presentation | values |
| TSC | T-Shirt color | Color | Red[R], Blue[B], Green[G] |
And the following products exist:
| code | name | price | options |
| STS | Super T-Shirt | 19.99 | T-Shirt color |
And product "Super T-Shirt" is available in all variations
And I am logged in as administrator
Scenario: Accessing specific product variant metadata customization page
Given I am on the page of product "Super T-Shirt"
When I click "Customize metadata" near "Color: Red"
Then I should be customizing specific product variant metadata

View file

@ -0,0 +1,27 @@
@metadata
Feature: Metadata management
In order to manage metadata on my store
As a store owner
I want to have easy and intuitive access to managing metadata
Background:
Given store has default configuration
And there are products:
| name | price |
| Banana | 4.20 |
And I am logged in as administrator
Scenario: Accessing products metadata customization page
Given I am on the product index page
When I click "Customize products metadata"
Then I should be customizing products metadata
Scenario: Accessing specific product metadata customization page via index page
Given I am on the product index page
When I click "Customize metadata" near "Banana"
Then I should be customizing specific product metadata
Scenario: Accessing specific product metadata customization page via product page
Given I am on the page of product "Banana"
When I click "Customize metadata"
Then I should be customizing specific product metadata

View file

@ -0,0 +1,30 @@
@metadata
Feature: Metadata rendering
In order to manage metadata on my store
As a store owner
I want to have easy and intuitive access to managing metadata
Background:
Given store has default configuration
And there are products:
| name | price |
| Banana | 4.20 |
And product "Banana" has the following page metadata:
| Title | Tasty banana |
| Description | The best you have ever eaten! |
| Keywords | banana, fruit, healthy food |
| Twitter.Card | Summary |
| Twitter.Site | @example |
| Twitter.Image | http://example.com/image.jpg |
And all products are assigned to the default channel
Scenario: Rendering page metadata
When I am on the product page for "Banana"
Then I should see "Tasty banana" as page title
And the page keywords should contain "fruit"
Scenario: Rendering page Twitter metadata
When I am on the product page for "Banana"
Then there should be Twitter summary card metadata on this page
And Twitter site should be "@example"
And Twitter image should be "http://example.com/image.jpg"

View file

@ -0,0 +1,18 @@
@metadata
Feature: Metadata rendering & processing
In order to render metadata dynamically
As a store owner
I want to be able to use expressions and Twig templating engine in metadata properties
Background:
Given store has default configuration
And there are products:
| name | price |
| Banana | 4.20 |
And product "Banana" has the following page metadata:
| Title | {{ subject.name }} - Sylius |
And all products are assigned to the default channel
Scenario: Rendering processed page metadata
When I am on the product page for "Banana"
Then I should see "Banana - Sylius" as page title

View file

@ -10,6 +10,7 @@ suites:
Inventory: { namespace: Sylius\Component\Inventory, psr4_prefix: Sylius\Component\Inventory, spec_path: src/Sylius/Component/Inventory, src_path: src/Sylius/Component/Inventory }
Locale: { namespace: Sylius\Component\Locale, psr4_prefix: Sylius\Component\Locale, spec_path: src/Sylius/Component/Locale, src_path: src/Sylius/Component/Locale }
Mailer: { namespace: Sylius\Component\Mailer, psr4_prefix: Sylius\Component\Mailer, spec_path: src/Sylius/Component/Mailer, src_path: src/Sylius/Component/Mailer }
Metadata: { namespace: Sylius\Component\Metadata, psr4_prefix: Sylius\Component\Metadata, spec_path: src/Sylius/Component/Metadata, src_path: src/Sylius/Component/Metadata }
Order: { namespace: Sylius\Component\Order, psr4_prefix: Sylius\Component\Order, spec_path: src/Sylius/Component/Order, src_path: src/Sylius/Component/Order }
Originator: { namespace: Sylius\Component\Originator, psr4_prefix: Sylius\Component\Originator, spec_path: src/Sylius/Component/Originator, src_path: src/Sylius/Component/Originator }
Payment: { namespace: Sylius\Component\Payment, psr4_prefix: Sylius\Component\Payment, spec_path: src/Sylius/Component/Payment, src_path: src/Sylius/Component/Payment }
@ -45,6 +46,7 @@ suites:
InventoryBundle: { namespace: Sylius\Bundle\InventoryBundle, psr4_prefix: Sylius\Bundle\InventoryBundle, spec_path: src/Sylius/Bundle/InventoryBundle, src_path: src/Sylius/Bundle/InventoryBundle }
LocaleBundle: { namespace: Sylius\Bundle\LocaleBundle, psr4_prefix: Sylius\Bundle\LocaleBundle, spec_path: src/Sylius/Bundle/LocaleBundle, src_path: src/Sylius/Bundle/LocaleBundle }
MailerBundle: { namespace: Sylius\Bundle\MailerBundle, psr4_prefix: Sylius\Bundle\MailerBundle, spec_path: src/Sylius/Bundle/MailerBundle, src_path: src/Sylius/Bundle/MailerBundle }
MetadataBundle: { namespace: Sylius\Bundle\MetadataBundle, psr4_prefix: Sylius\Bundle\MetadataBundle, spec_path: src/Sylius/Bundle/MetadataBundle, src_path: src/Sylius/Bundle/MetadataBundle }
MoneyBundle: { namespace: Sylius\Bundle\MoneyBundle, psr4_prefix: Sylius\Bundle\MoneyBundle, spec_path: src/Sylius/Bundle/MoneyBundle, src_path: src/Sylius/Bundle/MoneyBundle }
OrderBundle: { namespace: Sylius\Bundle\OrderBundle, psr4_prefix: Sylius\Bundle\OrderBundle, spec_path: src/Sylius/Bundle/OrderBundle, src_path: src/Sylius/Bundle/OrderBundle }
PaymentBundle: { namespace: Sylius\Bundle\PaymentBundle, psr4_prefix: Sylius\Bundle\PaymentBundle, spec_path: src/Sylius/Bundle/PaymentBundle, src_path: src/Sylius/Bundle/PaymentBundle }

View file

@ -0,0 +1,393 @@
<?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\CoreBundle\Behat;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Gherkin\Node\TableNode;
use Behat\Mink\Element\ElementInterface;
use Behat\Mink\Element\NodeElement;
use Sylius\Bundle\ResourceBundle\Behat\DefaultContext;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Metadata\Model\Custom\PageMetadata;
use Sylius\Component\Metadata\Model\Custom\PageMetadataInterface;
use Sylius\Component\Metadata\Model\MetadataContainerInterface;
use Sylius\Component\Metadata\Model\Twitter\AppCard;
use Sylius\Component\Metadata\Model\Twitter\CardInterface;
use Sylius\Component\Metadata\Model\Twitter\PlayerCard;
use Sylius\Component\Metadata\Model\Twitter\SummaryCard;
use Sylius\Component\Metadata\Model\Twitter\SummaryLargeImageCard;
use Symfony\Component\PropertyAccess\PropertyAccessor;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class MetadataContext extends DefaultContext
{
/**
* @var PropertyAccessor
*/
private $propertyAccessor;
/**
* {@inheritdoc}
*/
public function __construct($applicationName = null)
{
parent::__construct($applicationName);
$this->propertyAccessor = new PropertyAccessor();
}
/**
* @When I am customizing metadata
* @When I am customizing metadata with identifier :identifier
*/
public function iAmCustomizingMetadata($identifier = 'FooBar')
{
$this->getSession()->visit($this->generateUrl('sylius_backend_metadata_container_customize', ['id' => $identifier]));
}
/**
* @Then I should see metadata customization form
*/
public function iShouldSeeMetadataCustomizationForm()
{
$this->assertThereIsFormWithFields(
$this->getSession()->getPage(),
['Title', 'Description', 'Keywords', 'Twitter Card']
);
}
/**
* @Then I should see the following metadata:
*/
public function iShouldSeeTheFollowingMetadata(TableNode $metadata)
{
/** @var NodeElement $table */
$table = $this->getSession()->getPage()->find('css', '#content > table');
/** @var NodeElement $row */
$row = $table->findAll('css', 'tr')[1];
/** @var NodeElement[] $columns */
$columns = $row->findAll('css', 'td');
$contentIndex = $this->getColumnIndex($table, 'Content');
/** @var NodeElement $list */
$list = $columns[$contentIndex];
foreach ($metadata->getRowsHash() as $key => $value) {
$currentElement = $list;
$parts = explode('.', $key);
foreach ($parts as $part) {
$currentElement = $currentElement->find('xpath', sprintf('/ul/li[starts-with(normalize-space(.), "%s:")]', $part));
}
$exploded = explode(':', $currentElement->getText());
$text = trim(end($exploded));
$expectedValue = $value;
if ('<empty>' === $expectedValue) {
$expectedValue = 'empty';
} elseif (false !== strpos($expectedValue, ',')) {
$expectedValue = str_replace([', ', ','], [' ', ' '], $expectedValue);
}
if ($text !== $expectedValue) {
throw new \Exception(sprintf(
'Expected "%s", got "%s" (item: "%s", original value: "%s")',
$expectedValue,
$text,
$key,
$value
));
}
}
}
/**
* @Then I should be customizing default metadata
*/
public function iShouldBeCustomizingDefaultMetadata()
{
$this->assertItIsMetadataCustomizationPage(
$this->getSession()->getPage(),
'/DefaultPage/'
);
}
/**
* @Then I should be customizing products metadata
*/
public function iShouldBeCustomizingProductsMetadata()
{
$this->assertItIsMetadataCustomizationPage(
$this->getSession()->getPage(),
'/Product(?!\-)/'
);
}
/**
* @Then I should be customizing specific product metadata
*/
public function iShouldBeCustomizingSpecificProductMetadata()
{
$this->assertItIsMetadataCustomizationPage(
$this->getSession()->getPage(),
'/Product-\d+/'
);
}
/**
* @Then I should be customizing specific product variant metadata
*/
public function iShouldBeCustomizingSpecificProductVariantMetadata()
{
$this->assertItIsMetadataCustomizationPage(
$this->getSession()->getPage(),
'/ProductVariant-\d+/'
);
}
/**
* @Then I should see Twitter's application card form
*/
public function iShouldSeeTwitterApplicationCardForm()
{
$this->assertSession()->fieldExists('Iphone application name');
$this->assertSession()->fieldExists('Ipad application url');
}
/**
* @Then I should not see Twitter's application card form
*/
public function iShouldNotSeeTwitterApplicationCardForm()
{
$this->assertSession()->fieldNotExists('Iphone application name');
$this->assertSession()->fieldNotExists('Ipad application url');
}
/**
* @Given there is the following metadata :metadataName:
*/
public function thereIsTheFollowingMetadata($metadataName, TableNode $table)
{
/** @var MetadataContainerInterface $metadata */
$metadata = $this->getFactory('metadata_container')->createNew();
$pageMetadata = new PageMetadata();
$metadata->setId($metadataName);
$metadata->setMetadata($pageMetadata);
foreach ($table->getRowsHash() as $key => $value) {
if ($this->createNewMetadataObjectIfNeeded($pageMetadata, $key, $value)) {
continue;
}
if (false !== strpos($value, ',')) {
$value = array_map('trim', explode(',', $value));
}
$this->propertyAccessor->setValue($pageMetadata, $key, $value);
}
$em = $this->getEntityManager();
$em->persist($metadata);
$em->flush();
}
/**
* @Given product :productName has the following page metadata:
*/
public function productHasTheFollowingPageMetadata($productName, TableNode $table)
{
/** @var ProductInterface $product */
$product = $this->getRepository('product')->findOneBy(['name' => $productName]);
$this->thereIsTheFollowingMetadata($product->getMetadataIdentifier(), $table);
}
/**
* @Then I should see :title as page title
*/
public function iShouldSeeAsPageTitle($title)
{
$this->assertSession()->elementTextContains('css', 'title', $title);
}
/**
* @Then the page keywords should contain :keyword
*/
public function thePageKeywordsShouldContain($keyword)
{
$this->assertSession()->elementExists('css', sprintf('meta[name="keywords"][content*="%s"]', $keyword));
}
/**
* @Then there should be Twitter summary card metadata on this page
*/
public function thereShouldBeTwitterSummaryCardMetadataOnThisPage()
{
$this->assertSession()->elementExists('css', 'meta[name="twitter:card"][content="summary"]');
}
/**
* @Then Twitter site should be :site
*/
public function twitterSiteShouldBe($site)
{
$this->assertSession()->elementExists('css', sprintf('meta[name="twitter:site"][content="%s"]', $site));
}
/**
* @Then Twitter image should be :image
*/
public function twitterImageShouldBe($image)
{
$this->assertSession()->elementExists('css', sprintf('meta[name="twitter:image"][content="%s"]', $image));
}
/**
* @When /I deselect "([^"]+)"/
*/
public function iDeselectSelectField($fieldName)
{
$this->selectOption($fieldName, "");
}
/**
* @param ElementInterface $element
* @param string $regexp
*
* @throws \Exception If assertion failed
*/
private function assertItIsMetadataCustomizationPage(ElementInterface $element, $regexp)
{
if (!$this->isItMetadataCustomizationPage($element, $regexp)) {
throw new \Exception(sprintf("It is not metadata customziation page (regexp: %s)", $regexp));
}
}
/**
* @param ElementInterface $element
* @param string $regexp
*
* @return bool
*/
private function isItMetadataCustomizationPage(ElementInterface $element, $regexp)
{
$header = $element->find('css', '.page-header h1');
if (null === $header) {
return false;
}
if (false === strpos($header->getText(), "Customizing metadata")) {
return false;
}
if (false === (bool) preg_match($regexp, $header->getText())) {
return false;
}
return true;
}
/**
* @param ElementInterface $element
* @param array $fields
*
* @throws \Exception If assertion failed
*/
private function assertThereIsFormWithFields(ElementInterface $element, array $fields)
{
if (null === $this->getFormWithFields($element, $fields)) {
throw new \Exception(sprintf("Could not found table with fields: %s", implode(', ', $fields)));
}
}
/**
* @param ElementInterface $element
* @param string[] $fields
*
* @return ElementInterface|null
*/
private function getFormWithFields(ElementInterface $element, array $fields)
{
/** @var NodeElement[] $forms */
$forms = $element->findAll('css', 'form');
foreach ($forms as $form) {
$found = true;
foreach ($fields as $field) {
if (null === $form->findField($field)) {
$found = false;
}
}
if ($found) {
return $form;
}
}
return null;
}
/**
* @param string $value
*
* @return CardInterface
*/
protected function createTwitterCardFromString($value)
{
switch (strtolower($value)) {
case 'summary':
return new SummaryCard();
case 'summary with large image':
case 'summary large image':
case 'summarylargeimage':
return new SummaryLargeImageCard();
case 'player':
return new PlayerCard();
case 'app':
case 'application':
return new AppCard();
default:
throw new \InvalidArgumentException(sprintf('Unknown card type "%s"!', $value));
}
}
/**
* @param PageMetadataInterface $pageMetadata
* @param string $key
* @param string $value
*
* @return boolean True if created new metadata object
*/
protected function createNewMetadataObjectIfNeeded(PageMetadataInterface $pageMetadata, $key, $value)
{
if ('twitter.card' === strtolower($key)) {
$pageMetadata->setTwitter($this->createTwitterCardFromString($value));
return true;
}
return false;
}
}

View file

@ -77,6 +77,7 @@ class SyliusCoreExtension extends AbstractResourceExtension implements PrependEx
'reports.xml',
'state_machine.xml',
'email.xml',
'metadata.xml',
);
$env = $container->getParameter('kernel.environment');

View file

@ -72,6 +72,7 @@ abstract class Kernel extends BaseKernel
new \Sylius\Bundle\UserBundle\SyliusUserBundle(),
new \Sylius\Bundle\UiBundle\SyliusUiBundle(),
new \Sylius\Bundle\AdminBundle\SyliusAdminBundle(),
new \Sylius\Bundle\MetadataBundle\SyliusMetadataBundle(),
new \Sylius\Bundle\CoreBundle\SyliusCoreBundle(),
new \Sylius\Bundle\WebBundle\SyliusWebBundle(),

View file

@ -107,7 +107,7 @@ sylius_archetype:
product:
archetype:
classes:
model: Sylius\Component\Product\Model\Archetype
model: Sylius\Component\Core\Model\Archetype
translation:
classes:
model: Sylius\Component\Product\Model\ArchetypeTranslation
@ -345,6 +345,13 @@ sylius_rbac:
sylius.group.create: Create group
sylius.group.update: Edit group
sylius.group.delete: Delete group
sylius.manage.metadata: Manage metadatas
sylius.metadata.show: Show metadata
sylius.metadata.index: List metadatas
sylius.metadata.create: Create metadata
sylius.metadata.update: Edit metadata
sylius.metadata.delete: Delete metadata
sylius.manage.channel: Manage channels
sylius.channel.show: Show channel
@ -648,6 +655,7 @@ sylius_rbac:
sylius.taxes: Manage taxes
sylius.support: Manage customer support
permissions_hierarchy:
sylius.manage.metadata: [sylius.metadata.show, sylius.metadata.index, sylius.metadata.create, sylius.metadata.update, sylius.metadata.delete]
sylius.manage.channel: [sylius.channel.show, sylius.channel.index, sylius.channel.create, sylius.channel.update, sylius.channel.delete]
sylius.manage.customer: [sylius.customer.show, sylius.customer.index, sylius.customer.create, sylius.customer.update, sylius.customer.delete]
sylius.manage.group: [sylius.group.show, sylius.group.index, sylius.group.create, sylius.group.update, sylius.group.delete]
@ -691,12 +699,11 @@ sylius_rbac:
sylius.manage.contact_request: [sylius.contact_request.show, sylius.contact_request.index, sylius.contact_request.create, sylius.contact_request.update, sylius.contact_request.delete]
sylius.manage.contact_topic: [sylius.contact_topic.show, sylius.contact_topic.index, sylius.contact_topic.create, sylius.contact_topic.update, sylius.contact_topic.delete]
sylius.manage.settings: [sylius.settings.sylius_general, sylius.settings.sylius_security, sylius.settings.sylius_taxation]
sylius.catalog: [sylius.manage.product, sylius.manage.product_attribute, sylius.manage.product_option, sylius.manage.product_archetype, sylius.manage.product_variant, sylius.manage.taxonomy, sylius.manage.taxon]
sylius.catalog: [sylius.manage.product, sylius.manage.product_attribute, sylius.manage.product_option, sylius.manage.product_archetype, sylius.manage.product_variant, sylius.manage.taxonomy, sylius.manage.taxon, sylius.manage.metadata]
sylius.marketing: [sylius.manage.promotion, sylius.manage.promotion_coupon, sylius.manage.email, sylius.manage.metadata]
sylius.sales: [sylius.manage.order, sylius.manage.order_item, sylius.manage.adjustment, sylius.manage.payment, sylius.manage.report]
sylius.marketing: [sylius.manage.promotion, sylius.manage.promotion_coupon, sylius.manage.email]
sylius.shipping: [sylius.manage.shipment, sylius.manage.shipping_method, sylius.manage.shipping_category]
sylius.content: [sylius.manage.static_content, sylius.manage.route, sylius.manage.simple_block, sylius.manage.menu, sylius.manage.slideshow, sylius.manage.string_block, sylius.manage.slideshow_block, sylius.manage.imagine_block]
sylius.content: [sylius.manage.static_content, sylius.manage.route, sylius.manage.simple_block, sylius.manage.menu, sylius.manage.slideshow, sylius.manage.string_block, sylius.manage.slideshow_block, sylius.manage.imagine_block, sylius.manage.metadata]
sylius.security: [sylius.manage.permission, sylius.manage.role, sylius.manage.customer, sylius.manage.group, sylius.accounts]
sylius.accounts: [sylius.manage.customer, sylius.manage.group]
sylius.taxes: [sylius.manage.tax_rate, sylius.manage.tax_category]

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-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">
<mapped-superclass name="Sylius\Component\Core\Model\Archetype" table="sylius_product_archetype" />
</doctrine-mapping>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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">
<parameters>
<parameter key="sylius.metadata.hierarchy_provider.product.class">Sylius\Component\Core\Metadata\HierarchyProvider\ProductHierarchyProvider</parameter>
<parameter key="sylius.metadata.hierarchy_provider.product_variant.class">Sylius\Component\Core\Metadata\HierarchyProvider\ProductVariantHierarchyProvider</parameter>
<parameter key="sylius.metadata.provider.core.class">Sylius\Component\Core\Metadata\Provider\MetadataProvider</parameter>
</parameters>
<services>
<service id="sylius.metadata.provider.core" class="%sylius.metadata.provider.core.class%" decorates="sylius.metadata.provider" public="false">
<argument type="service" id="sylius.metadata.provider.core.inner" />
<argument type="service" id="sylius.metadata.hierarchy_provider" />
</service>
<service id="sylius.metadata.hierarchy_provider.product" class="%sylius.metadata.hierarchy_provider.product.class%" public="false">
<tag name="sylius.metadata_hierarchy_provider" />
</service>
<service id="sylius.metadata.hierarchy_provider.product_variant" class="%sylius.metadata.hierarchy_provider.product_variant.class%" public="false">
<tag name="sylius.metadata_hierarchy_provider" />
</service>
</services>
</container>

View file

@ -0,0 +1,7 @@
vendor/
bin/
composer.phar
composer.lock
phpspec.yml

View file

@ -0,0 +1,15 @@
language: php
sudo: false
php:
- 7.0
- 5.6
- 5.5
before_script:
- phpenv config-rm xdebug.ini || true
- composer install --no-interaction --prefer-source
script:
- vendor/bin/phpspec run -f pretty --verbose

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\MetadataBundle\Controller;
use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class MetadataController extends ResourceController
{
/**
* @param Request $request
*
* @return Response
*/
public function customizeAction(Request $request)
{
try {
return $this->updateAction($request);
} catch (NotFoundHttpException $exception) {
return $this->createAction($request);
}
}
}

View file

@ -0,0 +1,103 @@
<?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\MetadataBundle\DependencyInjection\Compiler;
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class DynamicFormsChoicesMapCompilerPass implements CompilerPassInterface
{
/**
* @var ContainerBuilder
*/
private $container;
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
if (!$container->hasDefinition('sylius.metadata.dynamic_forms_choices_map')) {
return;
}
$definition = $container->getDefinition('sylius.metadata.dynamic_forms_choices_map');
$taggedServices = $container->findTaggedServiceIds('sylius.metadata.dynamic_form_choice');
foreach ($taggedServices as $id => $tags) {
$formDefinition = $container->getDefinition($id);
$dataClass = $this->getFormDataClass($formDefinition);
$formName = $this->getFormName($formDefinition);
$definition->addMethodCall('addForm', [
$tags[0]['group'],
$dataClass,
$formName,
isset($tags[0]['label']) ? $tags[0]['label'] : $formName
]);
}
}
/**
* @param Definition $formDefinition
*
* @return string Form data class
*/
private function getFormDataClass(Definition $formDefinition)
{
$tags = $formDefinition->getTag('sylius.metadata.dynamic_form_choice');
$class = isset($tags[0]['class']) ? $tags[0]['class'] : null;
if (null === $class) {
throw new \InvalidArgumentException(sprintf(
'Definition "%s" tagged by "%s" should define class attribute in tag definition',
$formDefinition->getClass(),
'sylius.metadata.dynamic_form'
));
}
return $class;
}
/**
* @param Definition $formDefinition
*
* @return string Form name
*/
private function getFormName(Definition $formDefinition)
{
$tags = $formDefinition->getTag('form.type');
$formName = isset($tags[0]['alias']) ? $tags[0]['alias'] : null;
if (null === $formName) {
throw new \InvalidArgumentException(sprintf(
'Definition "%s" tagged by "%s" should also be tagged by "%s" with attribute "%s"',
$formDefinition->getClass(),
'sylius.metadata.dynamic_form',
'form.type',
'alias'
));
}
return $formName;
}
}

View file

@ -0,0 +1,43 @@
<?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\MetadataBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class MetadataHierarchyProviderCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('sylius.metadata.hierarchy_provider')) {
return;
}
$definition = $container->getDefinition('sylius.metadata.hierarchy_provider');
$taggedServices = $container->findTaggedServiceIds('sylius.metadata_hierarchy_provider');
$hierarchyProviders = $definition->getArgument(0) ?: [];
foreach ($taggedServices as $id => $tags) {
$hierarchyProviders[] = new Reference($id);
}
$definition->replaceArgument(0, $hierarchyProviders);
}
}

View file

@ -0,0 +1,43 @@
<?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\MetadataBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class MetadataRendererCompilerPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('sylius.metadata.renderer')) {
return;
}
$definition = $container->getDefinition('sylius.metadata.renderer');
$taggedServices = $container->findTaggedServiceIds('sylius.metadata_renderer');
$renderers = [];
foreach ($taggedServices as $id => $tags) {
$renderers[] = new Reference($id);
}
$definition->setArguments([$renderers]);
}
}

View file

@ -0,0 +1,102 @@
<?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\MetadataBundle\DependencyInjection;
use Sylius\Bundle\MetadataBundle\Controller\MetadataController;
use Sylius\Bundle\MetadataBundle\Form\Type\MetadataContainerType;
use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
use Sylius\Bundle\ResourceBundle\SyliusResourceBundle;
use Sylius\Bundle\MetadataBundle\Model\MetadataContainer;
use Sylius\Component\Metadata\Factory\MetadataContainerFactory;
use Sylius\Component\Metadata\Model\MetadataContainerInterface;
use Sylius\Component\Resource\Factory\Factory;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This class contains the configuration information for the bundle.
*
* This information is solely responsible for how the different configuration
* sections are normalized, and merged.
*
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class Configuration implements ConfigurationInterface
{
/**
* {@inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('sylius_metadata');
$rootNode
->children()
->scalarNode('driver')->defaultValue(SyliusResourceBundle::DRIVER_DOCTRINE_ORM)->end()
->end()
;
$this->addResourcesSection($rootNode);
return $treeBuilder;
}
/**
* @param ArrayNodeDefinition $node
*/
private function addResourcesSection(ArrayNodeDefinition $node)
{
$resourcesBuilder = $node
->fixXmlConfig('resource')
->children()
->arrayNode('resources')
->addDefaultsIfNotSet()
->children()
;
$resourcesBuilder
->arrayNode('metadata_container')
->addDefaultsIfNotSet()
->children()
->variableNode('options')->end()
->arrayNode('classes')
->addDefaultsIfNotSet()
->children()
->scalarNode('model')->defaultValue(MetadataContainer::class)->cannotBeEmpty()->end()
->scalarNode('interface')->defaultValue(MetadataContainerInterface::class)->cannotBeEmpty()->end()
->scalarNode('controller')->defaultValue(MetadataController::class)->cannotBeEmpty()->end()
->scalarNode('repository')->cannotBeEmpty()->end()
->scalarNode('factory')->defaultValue(MetadataContainerFactory::class)->end()
->arrayNode('form')
->addDefaultsIfNotSet()
->children()
->scalarNode('default')->defaultValue(MetadataContainerType::class)->cannotBeEmpty()->end()
->end()
->end()
->end()
->end()
->arrayNode('validation_groups')
->addDefaultsIfNotSet()
->children()
->arrayNode('default')
->prototype('scalar')->end()
->defaultValue(array('sylius'))
->end()
->end()
->end()
->end()
->end()
;
}
}

View file

@ -0,0 +1,90 @@
<?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\MetadataBundle\DependencyInjection;
use Sylius\Bundle\ResourceBundle\DependencyInjection\Extension\AbstractResourceExtension;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Parameter;
use Symfony\Component\DependencyInjection\Reference;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class SyliusMetadataExtension extends AbstractResourceExtension implements PrependExtensionInterface
{
/**
* {@inheritdoc}
*/
public function load(array $config, ContainerBuilder $container)
{
$config = $this->processConfiguration(new Configuration(), $config);
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$this->registerResources('sylius', $config['driver'], $config['resources'], $container);
$loader->load('services.xml');
$loader->load('forms.xml');
$container
->getDefinition('sylius.form.type.page_metadata')
->addArgument(new Reference('sylius.metadata.dynamic_form_choice_builder'))
;
$this->addDynamicChoiceTagToForm($container, 'twitter', 'twitter_summary_card');
$this->addDynamicChoiceTagToForm($container, 'twitter', 'twitter_summary_large_image_card');
$this->addDynamicChoiceTagToForm($container, 'twitter', 'twitter_player_card');
$this->addDynamicChoiceTagToForm($container, 'twitter', 'twitter_app_card');
}
/**
* {@inheritdoc}
*/
public function prepend(ContainerBuilder $container)
{
if (!$container->hasExtension('twig')) {
return;
}
$container->prependExtensionConfig('twig', [
'form_themes' => [
'SyliusMetadataBundle:Form:dynamic_form_theme.html.twig',
],
]);
}
/**
* @param ContainerBuilder $container
* @param string $group
* @param string $formName
*/
private function addDynamicChoiceTagToForm(ContainerBuilder $container, $group, $formName)
{
$serviceName = 'sylius.form.type.' . $formName;
if (!$container->hasDefinition($serviceName)) {
throw new \InvalidArgumentException(sprintf(
'Service "%s" was not found!',
$serviceName
));
}
$formDefinition = $container->getDefinition($serviceName);
$formDefinition->addTag('sylius.metadata.dynamic_form_choice', [
'group' => $group,
'label' => 'sylius.metadata.type.' . $formName,
'class' => $formDefinition->getArgument(0),
]);
}
}

View file

@ -0,0 +1,115 @@
<?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\MetadataBundle\DynamicForm;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
final class DynamicFormBuilder implements DynamicFormBuilderInterface
{
/**
* @var DynamicFormsChoicesMapInterface
*/
private $dynamicFormsChildrenMap;
/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* @param DynamicFormsChoicesMapInterface $dynamicFormsChildrenMap
* @param PropertyAccessorInterface $propertyAccessor
*/
public function __construct(DynamicFormsChoicesMapInterface $dynamicFormsChildrenMap, PropertyAccessorInterface $propertyAccessor)
{
$this->dynamicFormsChildrenMap = $dynamicFormsChildrenMap;
$this->propertyAccessor = $propertyAccessor;
}
/**
* {@inheritdoc}
*/
public function buildDynamicForm(FormBuilderInterface $builder, $name, $type, array $options = [])
{
$builder->add($name, $type, $options);
$this->addModelDataListener($builder, $name, $builder->get($name)->getOption('group'));
$this->addSubmittedDataListener($builder, $name);
}
/**
* @param FormBuilderInterface $dynamicFormBuilder
* @param FormInterface $dynamicForm
* @param string $formName
*/
private function addEmbeddedFormField(FormBuilderInterface $dynamicFormBuilder, FormInterface $dynamicForm, $formName)
{
$embeddedForm = $dynamicFormBuilder->getFormFactory()->createNamed(
$dynamicFormBuilder->getName(),
$formName,
null,
['auto_initialize' => false]
);
$dynamicForm->add($embeddedForm);
}
/**
* @param FormBuilderInterface $builder
* @param string $name
* @param string $group
*/
private function addModelDataListener(FormBuilderInterface $builder, $name, $group)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder, $name, $group) {
try {
$data = $this->propertyAccessor->getValue($event->getData(), $name);
} catch (\RuntimeException $exception) {
$data = null;
}
if (null === $data || !is_object($data)) {
return;
}
$formName = $this->dynamicFormsChildrenMap->getFormNameByGroupAndDataClass($group, get_class($data));
$options = $event->getForm()->get($name)->get('_form')->getConfig()->getOptions();
$event->getForm()->get($name)->add('_form', 'choice', array_merge($options, ['data' => $formName]));
$this->addEmbeddedFormField($builder->get($name), $event->getForm()->get($name), $formName);
});
}
/**
* @param FormBuilderInterface $builder
* @param string $name
*/
private function addSubmittedDataListener(FormBuilderInterface $builder, $name)
{
$builder->get($name)->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($builder, $name) {
$formName = $event->getData()['_form'];
if (empty($formName)) {
$event->setData([$name => null]);
}
$this->addEmbeddedFormField($builder->get($name), $event->getForm(), $formName ?: 'text');
});
}
}

View file

@ -0,0 +1,31 @@
<?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\MetadataBundle\DynamicForm;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface DynamicFormBuilderInterface
{
/**
* Adds dynamic form field to given builder's form, sets up events
* and transformers to handle it properly.
*
* @param FormBuilderInterface $builder
* @param string $name
* @param string $type
* @param array $options
*/
public function buildDynamicForm(FormBuilderInterface $builder, $name, $type, array $options = []);
}

View file

@ -0,0 +1,85 @@
<?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\MetadataBundle\DynamicForm;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
final class DynamicFormsChoicesMap implements DynamicFormsChoicesMapInterface
{
/**
* Maps data class to form name.
*
* @var string[]
*/
private $forms;
/**
* Maps form name to label.
*
* @var string[]
*/
private $labels;
/**
* {@inheritdoc}
*/
public function addForm($group, $dataClass, $formName, $label)
{
$this->forms[$group][$dataClass] = $formName;
$this->labels[$formName] = $label;
}
/**
* {@inheritdoc}
*/
public function getFormsNamesByGroup($group)
{
return isset($this->forms[$group]) ? $this->forms[$group] : [];
}
/**
* {@inheritdoc}
*/
public function getFormNameByGroupAndDataClass($group, $dataClass)
{
foreach ($this->getFormsNamesByGroup($group) as $currentDataClass => $formName) {
if ($currentDataClass === $dataClass) {
return $formName;
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function getDataClassByGroupAndFormName($group, $formName)
{
foreach ($this->getFormsNamesByGroup($group) as $dataClass => $currentFormName) {
if ($currentFormName === $formName) {
return $dataClass;
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function getLabelByFormName($formName)
{
return isset($this->labels[$formName]) ? $this->labels[$formName] : $formName;
}
}

View file

@ -0,0 +1,57 @@
<?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\MetadataBundle\DynamicForm;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface DynamicFormsChoicesMapInterface
{
/**
* @param string $group
* @param string $dataClass
* @param string $formName
* @param string $label
*/
public function addForm($group, $dataClass, $formName, $label);
/**
* @param string $group
*
* @return string[]
*/
public function getFormsNamesByGroup($group);
/**
* @param string $group
* @param string $dataClass
*
* @return string|null
*
*/
public function getFormNameByGroupAndDataClass($group, $dataClass);
/**
* @param string $group
* @param string $formName
*
* @return string|null
*/
public function getDataClassByGroupAndFormName($group, $formName);
/**
* @param string $formName
*
* @return string
*/
public function getLabelByFormName($formName);
}

View file

@ -0,0 +1,132 @@
<?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\MetadataBundle\DynamicForm\Type;
use Sylius\Bundle\MetadataBundle\DynamicForm\DynamicFormsChoicesMapInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class DynamicFormChoiceType extends AbstractType
{
/**
* @var DynamicFormsChoicesMapInterface
*/
protected $dynamicFormsChildrenMap;
/**
* @param DynamicFormsChoicesMapInterface $dynamicFormsChildrenMap
*/
public function __construct(DynamicFormsChoicesMapInterface $dynamicFormsChildrenMap)
{
$this->dynamicFormsChildrenMap = $dynamicFormsChildrenMap;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$availableFormsNames = $this->dynamicFormsChildrenMap->getFormsNamesByGroup($options['group']);
$this->addAvailableFormsField($builder, $availableFormsNames, $options);
$this->addAvailableFormsPrototypes($builder, $availableFormsNames);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$this->exportAvailableFormsPrototypesToView($view, $form);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
parent::configureOptions($resolver);
$resolver
->setDefaults([
'inherit_data' => true,
'select' => [],
])
;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'sylius_dynamic_form';
}
/**
* @param FormBuilderInterface $builder
* @param string[] $availableFormsNames
*/
private function addAvailableFormsField(FormBuilderInterface $builder, $availableFormsNames, array $options)
{
$choices = [];
foreach ($availableFormsNames as $availableFormName) {
$choices[$availableFormName] = $this->dynamicFormsChildrenMap->getLabelByFormName($availableFormName);
}
$options = array_merge(
['choices' => $choices, 'mapped' => false],
$options['select']
);
$builder->add('_form', 'choice', $options);
}
/**
* @param FormBuilderInterface $builder
* @param string[] $availableFormsNames
*/
private function addAvailableFormsPrototypes(FormBuilderInterface $builder, $availableFormsNames)
{
$prototypes = [];
foreach ($availableFormsNames as $availableFormName) {
$prototypes[$availableFormName] = $builder->create($builder->getName(), $availableFormName)->getForm();
}
$builder->setAttribute('prototypes', $prototypes);
}
/**
* @param FormView $view
* @param FormInterface $form
*/
private function exportAvailableFormsPrototypesToView(FormView $view, FormInterface $form)
{
/** @var FormInterface[] $prototypes */
$prototypes = $form->getConfig()->getAttribute('prototypes');
$view->vars['prototypes'] = [];
foreach ($prototypes as $type => $prototype) {
$view->vars['prototypes'][$type] = $prototype->createView($view);
}
}
}

View file

@ -0,0 +1,100 @@
<?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\MetadataBundle\Form\Type\Custom;
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Sylius\Bundle\MetadataBundle\DynamicForm\DynamicFormBuilderInterface;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class PageMetadataType extends AbstractResourceType
{
/**
* @var DynamicFormBuilderInterface
*/
private $dynamicFormBuilder;
/**
* {@inheritdoc}
*
* @var DynamicFormBuilderInterface $dynamicFormBuilder
*/
public function __construct($dataClass, array $validationGroups = [], DynamicFormBuilderInterface $dynamicFormBuilder)
{
parent::__construct($dataClass, $validationGroups);
$this->dynamicFormBuilder = $dynamicFormBuilder;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text', ['label' => 'sylius.metadata.page.title'])
->add('description', 'textarea', ['label' => 'sylius.metadata.page.description'])
->add('keywords', 'text', ['label' => 'sylius.metadata.page.keywords'])
;
$this->dynamicFormBuilder->buildDynamicForm(
$builder,
'twitter',
'sylius_twitter_card',
[
'select' => [
'label' => 'sylius.metadata.page.twitter',
'required' => false,
'placeholder' => 'sylius.metadata.type.none',
],
]
);
$this->addKeywordsTransformer($builder);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'sylius_page_metadata';
}
/**
* @param FormBuilderInterface $builder
*/
private function addKeywordsTransformer(FormBuilderInterface $builder)
{
$builder->get('keywords')->addModelTransformer(new CallbackTransformer(
function ($originalKeywords) {
if (!is_array($originalKeywords)) {
return '';
}
return join(', ', $originalKeywords);
},
function ($submittedKeywords) {
$keywords = explode(',', $submittedKeywords);
array_walk($keywords, function ($keyword) {
return trim($keyword);
});
return $keywords;
}
));
}
}

View file

@ -0,0 +1,40 @@
<?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\MetadataBundle\Form\Type;
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class MetadataContainerType extends AbstractResourceType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('id', 'text', ['read_only' => true])
->add('metadata', 'sylius_page_metadata')
;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'sylius_metadata_container';
}
}

View file

@ -0,0 +1,50 @@
<?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\MetadataBundle\Form\Type\Twitter;
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class AppCardType extends AbstractResourceType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', 'textarea', ['label' => 'sylius.metadata.twitter.description'])
->add('site', 'text', ['label' => 'sylius.metadata.twitter.site'])
->add('siteId', 'text', ['label' => 'sylius.metadata.twitter.site_id'])
->add('appNameIphone', 'text', ['label' => 'sylius.metadata.twitter.app_name_iphone'])
->add('appIdIphone', 'text', ['label' => 'sylius.metadata.twitter.app_id_iphone'])
->add('appUrlIphone', 'text', ['label' => 'sylius.metadata.twitter.app_url_iphone'])
->add('appNameIpad', 'text', ['label' => 'sylius.metadata.twitter.app_name_ipad'])
->add('appIdIpad', 'text', ['label' => 'sylius.metadata.twitter.app_id_ipad'])
->add('appUrlIpad', 'text', ['label' => 'sylius.metadata.twitter.app_url_ipad'])
->add('appNameGooglePlay', 'text', ['label' => 'sylius.metadata.twitter.app_name_googleplay'])
->add('appIdGooglePlay', 'text', ['label' => 'sylius.metadata.twitter.app_id_googleplay'])
->add('appUrlGooglePlay', 'text', ['label' => 'sylius.metadata.twitter.app_url_googleplay'])
;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'sylius_twitter_app_card';
}
}

View file

@ -0,0 +1,48 @@
<?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\MetadataBundle\Form\Type\Twitter;
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class PlayerCardType extends AbstractResourceType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text', ['label' => 'sylius.metadata.twitter.title'])
->add('description', 'textarea', ['label' => 'sylius.metadata.twitter.description'])
->add('image', 'text', ['label' => 'sylius.metadata.twitter.image'])
->add('player', 'textarea', ['label' => 'sylius.metadata.twitter.player'])
->add('site', 'text', ['label' => 'sylius.metadata.twitter.site'])
->add('siteId', 'text', ['label' => 'sylius.metadata.twitter.site_id'])
->add('playerWidth', 'number', ['label' => 'sylius.metadata.twitter.player_width'])
->add('playerHeight', 'number', ['label' => 'sylius.metadata.twitter.player_height'])
->add('playerStream', 'text', ['label' => 'sylius.metadata.twitter.player_stream'])
->add('playerStreamContentType', 'text', ['label' => 'sylius.metadata.twitter.player_stream_content_type'])
;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'sylius_twitter_player_card';
}
}

View file

@ -0,0 +1,44 @@
<?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\MetadataBundle\Form\Type\Twitter;
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class SummaryCardType extends AbstractResourceType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text', ['label' => 'sylius.metadata.twitter.title'])
->add('description', 'textarea', ['label' => 'sylius.metadata.twitter.description'])
->add('image', 'text', ['label' => 'sylius.metadata.twitter.image'])
->add('site', 'text', ['label' => 'sylius.metadata.twitter.site'])
->add('siteId', 'text', ['label' => 'sylius.metadata.twitter.site_id'])
->add('creatorId', 'text', ['label' => 'sylius.metadata.twitter.creator_id'])
;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'sylius_twitter_summary_card';
}
}

View file

@ -0,0 +1,45 @@
<?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\MetadataBundle\Form\Type\Twitter;
use Sylius\Bundle\ResourceBundle\Form\Type\AbstractResourceType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class SummaryLargeImageCardType extends AbstractResourceType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', 'text', ['label' => 'sylius.metadata.twitter.title'])
->add('description', 'textarea', ['label' => 'sylius.metadata.twitter.description'])
->add('image', 'text', ['label' => 'sylius.metadata.twitter.image'])
->add('site', 'text', ['label' => 'sylius.metadata.twitter.site'])
->add('siteId', 'text', ['label' => 'sylius.metadata.twitter.site_id'])
->add('creator', 'text', ['label' => 'sylius.metadata.twitter.creator'])
->add('creatorId', 'text', ['label' => 'sylius.metadata.twitter.creator_id'])
;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'sylius_twitter_summary_large_image_card';
}
}

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\Bundle\MetadataBundle\Form\Type\Twitter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class TwitterCardType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'group' => null,
])
->setNormalizer(
'group',
function () {
return 'twitter';
}
)
;
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return 'sylius_dynamic_form';
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'sylius_twitter_card';
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Sylius\Bundle\MetadataBundle\Model;
use Sylius\Component\Metadata\Model\MetadataInterface;
use Sylius\Component\Metadata\Model\MetadataContainer as BaseMetadataContainer;
use Sylius\Component\Metadata\Model\MetadataContainerInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class MetadataContainer extends BaseMetadataContainer implements MetadataContainerInterface
{
/**
* @var MetadataInterface
*/
protected $metadataAsObject;
/**
* {@inheritdoc}
*/
public function getMetadata()
{
if (null !== $this->metadataAsObject) {
return $this->metadataAsObject;
}
return unserialize($this->metadata) ?: null;
}
/**
* {@inheritdoc}
*/
public function setMetadata(MetadataInterface $metadata)
{
$this->metadataAsObject = $metadata;
}
public function serializeMetadata()
{
$this->metadata = serialize($this->metadataAsObject);
}
}

View file

@ -0,0 +1,63 @@
SyliusMetadataBundle [![Build status...](https://secure.travis-ci.org/Sylius/SyliusMetadataBundle.png?branch=master)](http://travis-ci.org/Sylius/SyliusMetadataBundle)
==================
[**Symfony2**](http://symfony.com) integration of Sylius Metadata processing component.
Sylius
------
**Sylius** - Modern ecommerce for Symfony2. Visit [Sylius.org](http://sylius.org).
[phpspec](http://phpspec.net) examples
--------------------------------------
```bash
$ composer install
$ bin/phpspec run -f pretty
```
Documentation
-------------
Documentation is available on [**docs.sylius.org**](http://sylius.org/en/latest/bundles/SyliusMetadataBundle/index.html).
Contributing
------------
All informations about contributing to Sylius can be found on [this page](http://docs.sylius.org/en/latest/contributing/index.html).
Sylius twitter account
----------------------
If you want to keep up with updates, [follow the official Sylius account on twitter](http://twitter.com/Sylius).
Bug tracking
------------
This bundle uses [GitHub issues](https://github.com/Sylius/SyliusMetadataBundle/issues).
If you have found bug, please create an issue.
Versioning
----------
Releases will be numbered with the format `major.minor.patch`.
And constructed with the following guidelines.
* Breaking backwards compatibility bumps the major.
* New additions without breaking backwards compatibility bumps the minor.
* Bug fixes and misc changes bump the patch.
For more information on SemVer, please visit [semver.org website](http://semver.org/)...
This versioning method is same for all **Sylius** bundles and applications.
MIT License
-----------
License can be found [here](https://github.com/Sylius/SyliusMetadataBundle/blob/master/Resources/meta/LICENSE).
Authors
-------
The bundle was originally created by [Kamil Kokot](http://kamil.kokot.me).
See the list of [contributors](https://github.com/Sylius/SyliusMetadataBundle/contributors).

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
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.
-->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping">
<mapped-superclass name="Sylius\Bundle\MetadataBundle\Model\MetadataContainer" table="sylius_metadata">
<id name="id" column="id" type="string" />
<field name="metadata" column="metadata" type="text" />
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="serializeMetadata" />
<lifecycle-callback type="preUpdate" method="serializeMetadata" />
</lifecycle-callbacks>
</mapped-superclass>
</doctrine-mapping>

View file

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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">
<parameters>
<parameter key="sylius.metadata.dynamic_form_choice_builder.class">Sylius\Bundle\MetadataBundle\DynamicForm\DynamicFormBuilder</parameter>
<parameter key="sylius.metadata.dynamic_forms_choices_map.class">Sylius\Bundle\MetadataBundle\DynamicForm\DynamicFormsChoicesMap</parameter>
<parameter key="sylius.metadata.form.type.dynamic_form_choice.class">Sylius\Bundle\MetadataBundle\DynamicForm\Type\DynamicFormChoiceType</parameter>
<parameter key="sylius.form.type.page_metadata.class">Sylius\Bundle\MetadataBundle\Form\Type\Custom\PageMetadataType</parameter>
<parameter key="sylius.form.type.twitter_card.class">Sylius\Bundle\MetadataBundle\Form\Type\Twitter\TwitterCardType</parameter>
<parameter key="sylius.form.type.twitter_summary_card.class">Sylius\Bundle\MetadataBundle\Form\Type\Twitter\SummaryCardType</parameter>
<parameter key="sylius.form.type.twitter_summary_large_image_card.class">Sylius\Bundle\MetadataBundle\Form\Type\Twitter\SummaryLargeImageCardType</parameter>
<parameter key="sylius.form.type.twitter_player_card.class">Sylius\Bundle\MetadataBundle\Form\Type\Twitter\PlayerCardType</parameter>
<parameter key="sylius.form.type.twitter_app_card.class">Sylius\Bundle\MetadataBundle\Form\Type\Twitter\AppCardType</parameter>
<parameter key="sylius.model.page_metadata.class">Sylius\Component\Metadata\Model\Custom\PageMetadata</parameter>
<parameter key="sylius.model.twitter_summary_card.class">Sylius\Component\Metadata\Model\Twitter\SummaryCard</parameter>
<parameter key="sylius.model.twitter_summary_large_image_card.class">Sylius\Component\Metadata\Model\Twitter\SummaryLargeImageCard</parameter>
<parameter key="sylius.model.twitter_player_card.class">Sylius\Component\Metadata\Model\Twitter\PlayerCard</parameter>
<parameter key="sylius.model.twitter_app_card.class">Sylius\Component\Metadata\Model\Twitter\AppCard</parameter>
<parameter key="sylius.validation_groups.page_metadata" type="collection"><parameter>Default</parameter></parameter>
<parameter key="sylius.validation_groups.twitter_summary_card" type="collection"><parameter>Default</parameter></parameter>
<parameter key="sylius.validation_groups.twitter_summary_large_image_card" type="collection"><parameter>Default</parameter></parameter>
<parameter key="sylius.validation_groups.twitter_player_card" type="collection"><parameter>Default</parameter></parameter>
<parameter key="sylius.validation_groups.twitter_app_card" type="collection"><parameter>Default</parameter></parameter>
</parameters>
<services>
<service id="sylius.metadata.dynamic_form_choice_builder" class="%sylius.metadata.dynamic_form_choice_builder.class%" public="false">
<argument type="service" id="sylius.metadata.dynamic_forms_choices_map" />
<argument type="service" id="property_accessor" />
</service>
<service id="sylius.metadata.dynamic_forms_choices_map" class="%sylius.metadata.dynamic_forms_choices_map.class%" public="false">
<argument type="collection" />
</service>
<service id="sylius.metadata.form.type.dynamic_form_choice" class="%sylius.metadata.form.type.dynamic_form_choice.class%">
<argument type="service" id="sylius.metadata.dynamic_forms_choices_map" />
<tag name="form.type" alias="sylius_dynamic_form" />
</service>
<service id="sylius.form.type.page_metadata" class="%sylius.form.type.page_metadata.class%">
<argument>%sylius.model.page_metadata.class%</argument>
<argument>%sylius.validation_groups.page_metadata%</argument>
<tag name="form.type" alias="sylius_page_metadata" />
</service>
<service id="sylius.form.type.twitter_card" class="%sylius.form.type.twitter_card.class%">
<tag name="form.type" alias="sylius_twitter_card" />
</service>
<service id="sylius.form.type.twitter_summary_card" class="%sylius.form.type.twitter_summary_card.class%">
<argument>%sylius.model.twitter_summary_card.class%</argument>
<argument>%sylius.validation_groups.twitter_summary_card%</argument>
<tag name="form.type" alias="sylius_twitter_summary_card" />
</service>
<service id="sylius.form.type.twitter_summary_large_image_card" class="%sylius.form.type.twitter_summary_large_image_card.class%">
<argument>%sylius.model.twitter_summary_large_image_card.class%</argument>
<argument>%sylius.validation_groups.twitter_summary_large_image_card%</argument>
<tag name="form.type" alias="sylius_twitter_summary_large_image_card" />
</service>
<service id="sylius.form.type.twitter_player_card" class="%sylius.form.type.twitter_player_card.class%">
<argument>%sylius.model.twitter_player_card.class%</argument>
<argument>%sylius.validation_groups.twitter_player_card%</argument>
<tag name="form.type" alias="sylius_twitter_player_card" />
</service>
<service id="sylius.form.type.twitter_app_card" class="%sylius.form.type.twitter_app_card.class%">
<argument>%sylius.model.twitter_app_card.class%</argument>
<argument>%sylius.validation_groups.twitter_app_card%</argument>
<tag name="form.type" alias="sylius_twitter_app_card" />
</service>
</services>
</container>

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<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">
<parameters>
<parameter key="sylius.metadata.compiler.class">Sylius\Component\Metadata\Compiler\MetadataCompiler</parameter>
<parameter key="sylius.metadata.processor.class">Sylius\Component\Metadata\Processor\TwigMetadataProcessor</parameter>
<parameter key="sylius.metadata.accessor.class">Sylius\Component\Metadata\Accessor\MetadataAccessor</parameter>
<parameter key="sylius.metadata.twig_extension.class">Sylius\Component\Metadata\Twig\MetadataExtension</parameter>
<parameter key="sylius.metadata.renderer.options_resolver.class">Symfony\Component\OptionsResolver\OptionsResolver</parameter>
<parameter key="sylius.metadata.renderer.class">Sylius\Component\Metadata\Renderer\CompositeMetadataRenderer</parameter>
<parameter key="sylius.metadata.renderer.custom_page.class">Sylius\Component\Metadata\Renderer\Custom\PageMetadataRenderer</parameter>
<parameter key="sylius.metadata.renderer.twitter.class">Sylius\Component\Metadata\Renderer\Twitter\GenericTwitterMetadataRenderer</parameter>
<parameter key="sylius.metadata.provider.class">Sylius\Component\Metadata\Provider\MetadataProvider</parameter>
<parameter key="sylius.metadata.provider.processed.class">Sylius\Component\Metadata\Provider\ProcessedMetadataProvider</parameter>
<parameter key="sylius.metadata.hierarchy_provider.class">Sylius\Component\Metadata\HierarchyProvider\CompositeMetadataHierarchyProvider</parameter>
</parameters>
<services>
<service id="sylius.metadata.compiler" class="%sylius.metadata.compiler.class%" public="false" />
<service id="sylius.metadata.processor" class="%sylius.metadata.processor.class%" public="false">
<argument type="service" id="twig" />
</service>
<service id="sylius.metadata.accessor" class="%sylius.metadata.accessor.class%">
<argument type="service" id="sylius.metadata.provider" />
<argument type="service" id="property_accessor" />
</service>
<service id="sylius.metadata.twig_extension" class="%sylius.metadata.twig_extension.class%" public="false">
<argument type="service" id="sylius.metadata.accessor" />
<argument type="service" id="sylius.metadata.renderer" />
<tag name="twig.extension" />
</service>
<service id="sylius.metadata.renderer.options_resolver" class="%sylius.metadata.renderer.options_resolver.class%" scope="prototype" public="false" />
<service id="sylius.metadata.renderer" class="%sylius.metadata.renderer.class%">
<argument type="collection" /> <!-- Metadata renderers array -->
</service>
<service id="sylius.metadata.renderer.custom_page" class="%sylius.metadata.renderer.custom_page.class%" public="false">
<argument type="service" id="sylius.metadata.renderer.twitter" />
<argument type="service" id="sylius.metadata.renderer.options_resolver" strict="false" />
<argument type="service" id="property_accessor" />
<tag name="sylius.metadata_renderer" />
</service>
<service id="sylius.metadata.renderer.twitter" class="%sylius.metadata.renderer.twitter.class%" public="false">
<argument type="service" id="sylius.metadata.renderer.options_resolver" strict="false" />
<tag name="sylius.metadata_renderer" />
</service>
<service id="sylius.metadata.provider" class="%sylius.metadata.provider.class%" public="false">
<argument type="service" id="sylius.repository.metadata_container" />
<argument type="service" id="sylius.metadata.compiler" />
<argument type="service" id="sylius.metadata.hierarchy_provider" />
</service>
<service id="sylius.metadata.provider.processed" class="%sylius.metadata.provider.processed.class%" decorates="sylius.metadata.provider" public="false">
<argument type="service" id="sylius.metadata.provider.processed.inner" />
<argument type="service" id="sylius.metadata.processor" />
</service>
<service id="sylius.metadata.hierarchy_provider" class="%sylius.metadata.hierarchy_provider.class%">
<argument type="collection" /> <!-- Metadata hierarchy provider array -->
</service>
</services>
</container>

View file

@ -0,0 +1,19 @@
Copyright (c) 2011-2016 Paweł Jędrzejewski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,64 @@
(function ($) {
'use strict';
var methods = {
init: function(options) {
var settings = $.extend({
'prototypePrefix': false,
'prototypeElementPrefix': '<hr />',
'containerSelector': false
}, options);
return this.each(function() {
show($(this), false);
$(this).change(function() {
show($(this), true);
});
function show(element, replace) {
var id = element.attr('id');
var selectedValue = element.val();
var prototypePrefix = id;
if (false != settings.prototypePrefix) {
prototypePrefix = settings.prototypePrefix;
}
var prototypeElement = $('#' + prototypePrefix + '_' + selectedValue);
var container;
if (settings.containerSelector) {
container = $(settings.containerSelector);
} else {
container = $(element.data('container'));
}
if (!container.length) {
return;
}
if (!prototypeElement.length) {
container.empty();
return;
}
if (replace || !container.html().trim()) {
container.html(settings.prototypeElementPrefix + prototypeElement.data('prototype'));
}
}
});
}
};
$.fn.handlePrototypes = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.handlePrototypes' );
}
};
$('select[data-dynamic-form]').handlePrototypes();
})(jQuery);

View file

@ -0,0 +1,35 @@
sylius:
metadata:
type:
none: " - NONE - "
twitter_summary_card: Summary
twitter_summary_large_image_card: Summary with large image
twitter_player_card: Player
twitter_app_card: Application
page:
title: Title
description: Description
keywords: Keywords
twitter: Twitter Card
twitter:
title: Title
description: Description
site: Site
site_id: Site ID
creator: Creator
creator_id: Creator ID
image: Image
player: Player
player_width: Player width
player_height: Player height
player_stream: Player stream
player_stream_content_type: Player stream content type
app_name_iphone: Iphone application name
app_id_iphone: Iphone application ID
app_url_iphone: Iphone application url
app_name_ipad: Ipad application name
app_id_ipad: Ipad application ID
app_url_ipad: Ipad application url
app_name_googleplay: Google Play application name
app_id_googleplay: Google Play application ID
app_url_googleplay: Google Play application url

View file

@ -0,0 +1,15 @@
{% block sylius_dynamic_form_row %}
{{ form_row(form._form, { 'attr': { 'data-dynamic-form': true, 'data-container': "#dynamic-form-" ~ name } }) }}
{% for prototypeName, prototype in form.vars.prototypes %}
<div id="{{ form._form.vars.id }}_{{ prototypeName }}" data-prototype="{{ form_widget(prototype)|e }}">
</div>
{% endfor %}
<div id="dynamic-form-{{ name }}">
{# Render nested form (if possible) #}
{% if attribute(form.children, name) is defined %}
{{ form_widget(attribute(form.children, name)) }}
{% endif %}
</div>
{% endblock %}

View file

@ -0,0 +1,66 @@
<?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\MetadataBundle;
use Sylius\Bundle\ResourceBundle\AbstractResourceBundle;
use Sylius\Bundle\ResourceBundle\SyliusResourceBundle;
use Sylius\Bundle\MetadataBundle\DependencyInjection\Compiler\DynamicFormsChoicesMapCompilerPass;
use Sylius\Bundle\MetadataBundle\DependencyInjection\Compiler\MetadataHierarchyProviderCompilerPass;
use Sylius\Bundle\MetadataBundle\DependencyInjection\Compiler\MetadataRendererCompilerPass;
use Sylius\Component\Metadata\Model\MetadataContainerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class SyliusMetadataBundle extends AbstractResourceBundle
{
/**
* {@inheritdoc}
*/
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new MetadataRendererCompilerPass());
$container->addCompilerPass(new MetadataHierarchyProviderCompilerPass());
$container->addCompilerPass(new DynamicFormsChoicesMapCompilerPass());
}
/**
* {@inheritdoc}
*/
public static function getSupportedDrivers()
{
return [
SyliusResourceBundle::DRIVER_DOCTRINE_ORM,
];
}
/**
* {@inheritdoc}
*/
protected function getModelInterfaces()
{
return [
MetadataContainerInterface::class => 'sylius.model.metadata_container.class',
];
}
/**
* {@inheritdoc}
*/
protected function getModelNamespace()
{
return 'Sylius\Bundle\MetadataBundle\Model';
}
}

View file

@ -0,0 +1,41 @@
{
"name": "sylius/metadata-bundle",
"type": "symfony-bundle",
"description": "Metadata management for Symfony2 projects.",
"keywords": ["metadata"],
"homepage": "http://sylius.org",
"license": "MIT",
"authors": [
{
"name": "Kamil Kokot",
"homepage": "http://kamil.kokot.me"
},
{
"name": "Sylius project",
"homepage": "http://sylius.org"
},
{
"name": "Community contributions",
"homepage": "http://github.com/Sylius/Sylius/contributors"
}
],
"require": {
"php": "^5.5.9|^7.0",
"sylius/metadata": "0.17.*@dev"
},
"require-dev": {
"phpspec/phpspec": "^2.4"
},
"autoload": {
"psr-4": { "Sylius\\Bundle\\MetadataBundle\\": "" }
},
"autoload-dev": {
"psr-4": { "Sylius\\Bundle\\MetadataBundle\\spec\\": "spec/" }
},
"extra": {
"branch-alias": {
"dev-master": "0.17-dev"
}
}
}

View file

@ -0,0 +1,5 @@
suites:
main:
namespace: Sylius\Bundle\MetadataBundle
psr4_prefix: Sylius\Bundle\MetadataBundle
src_path: .

View file

@ -46,10 +46,11 @@ abstract class DefaultContext extends RawMinkContext implements Context, KernelA
* @var array
*/
protected $actions = array(
'viewing' => 'show',
'creation' => 'create',
'editing' => 'update',
'building' => 'build',
'viewing' => 'show',
'creation' => 'create',
'editing' => 'update',
'building' => 'build',
'customization' => 'customize',
);
/**

View file

@ -399,6 +399,13 @@ class BackendMenuBuilder extends MenuBuilder
))->setLabel($this->translate(sprintf('sylius.backend.menu.%s.channels', $section)));
}
if ($this->rbacAuthorizationChecker->isGranted('sylius.metadata_container.index')) {
$child->addChild('metadata', array(
'route' => 'sylius_backend_metadata_container_index',
'labelAttributes' => array('icon' => 'glyphicon glyphicon-file'),
))->setLabel($this->translate(sprintf('sylius.backend.menu.%s.metadata', $section)));
}
if ($this->rbacAuthorizationChecker->isGranted('sylius.locale.index')) {
$child->addChild('locales', array(
'route' => 'sylius_backend_locale_index',

View file

@ -174,3 +174,7 @@ sylius_backend_content:
sylius_backend_report:
resource: @SyliusWebBundle/Resources/config/routing/backend/report.yml
prefix: /reports
sylius_backend_metadata_container:
resource: @SyliusWebBundle/Resources/config/routing/backend/metadata_container.yml
prefix: /metadatas

View file

@ -0,0 +1,43 @@
# This file is part of the Sylius package.
# (c) Paweł Jędrzejewski
sylius_backend_metadata_container_index:
path: /
methods: [GET]
defaults:
_controller: sylius.controller.metadata_container:indexAction
_sylius:
template: SyliusWebBundle:Backend/Metadata:index.html.twig
sortable: true
paginate: 50
sorting:
id: asc
sylius_backend_metadata_container_customize:
path: /customize/{id}
methods: [GET, POST, PUT]
defaults:
_controller: sylius.controller.metadata_container:customizeAction
_sylius:
factory:
method: createIdentifiedBy
arguments: [$id]
template: SyliusWebBundle:Backend/Metadata:customize.html.twig
redirect: sylius_backend_metadata_container_show
sylius_backend_metadata_container_delete:
path: /{id}
methods: [DELETE]
defaults:
_controller: sylius.controller.metadata_container:deleteAction
_sylius:
template: SyliusWebBundle:Backend/Misc:delete.html.twig
redirect: sylius_backend_metadata_container_index
sylius_backend_metadata_container_show:
path: /{id}
methods: [GET]
defaults:
_controller: sylius.controller.metadata_container:showAction
_sylius:
template: SyliusWebBundle:Backend/Metadata:show.html.twig

View file

@ -207,6 +207,8 @@ sylius:
index: Menus
menu_node:
index: Menu nodes
metadata:
index: Metadata
new: New
option:
index: Options
@ -556,6 +558,21 @@ sylius:
frontend_description: Sylius is modern ecommerce solution for PHP, based on the Symfony2 framework
frontend_keywords: Sylius, ecommerce, symfony2, shop, cart, webshop
frontend_title: Sylius, modern ecommerce for Symfony2
metadata:
customize_header: Customizing metadata
show_header: Show metadata
id: ID
customize: Customize metadata
manage: Manage metadata
content: Content
index_metadata: Metadata
default_page:
group_customize: Customize default metadata
product:
single_customize: Customize metadata
group_customize: Customize products metadata
product_variant:
single_customize: Customize metadata
move_down: Move down
move_up: Move up
na: N/A

View file

@ -0,0 +1,20 @@
{% form_theme form 'SyliusWebBundle:Backend:forms.html.twig' %}
{{ form_errors(form) }}
<fieldset id="metadata-form">
<div class="row">
<div class="col-md-6">
{{ form_row(form.id) }}
<hr/>
{{ form_row(form.metadata.title) }}
{{ form_row(form.metadata.description) }}
{{ form_row(form.metadata.keywords) }}
</div>
<div class="col-md-6">
{{ form_row(form.metadata.twitter) }}
</div>
</div>
</fieldset>
<hr>
{{ form_widget(form._token) }}

View file

@ -0,0 +1,30 @@
{% extends 'SyliusWebBundle:Backend:layout.html.twig' %}
{% from 'SyliusResourceBundle:Macros:actions.html.twig' import update %}
{% block javascripts %}
{{ parent() }}
<script type="text/javascript" src="{{ asset('bundles/syliusmetadata/js/dynamic-form.js') }}"></script>
{% endblock %}
{% block topbar %}
<ol class="breadcrumb">
<li>{{ 'sylius.breadcrumb.configuration'|trans }}</li>
<li><a href="{{ path('sylius_backend_metadata_container_index') }}">{{ 'sylius.breadcrumb.metadata.index'|trans }}</a></li>
<li><a href="{{ path('sylius_backend_metadata_container_show', {'id': metadata_container.id}) }}">{{ metadata_container.id }}</a></li>
<li>{{ 'sylius.breadcrumb.edit'|trans }}</li>
</ol>
{% endblock %}
{% block content %}
<div class="page-header">
<h1><i class="glyphicon glyphicon-pencil"></i> {{ 'sylius.metadata.customize_header'|trans|raw }} {{ metadata_container.id }}</h1>
</div>
{{ form_errors(form) }}
<form action="{{ path('sylius_backend_metadata_container_customize', {'id': metadata_container.id }) }}" method="post" class="form-horizontal" novalidate>
{% include 'SyliusWebBundle:Backend/Metadata:_form.html.twig' %}
{{ update() }}
</form>
{% endblock %}

View file

@ -0,0 +1,28 @@
{% extends 'SyliusWebBundle:Backend:layout.html.twig' %}
{% import 'SyliusResourceBundle:Macros:buttons.html.twig' as buttons %}
{% from 'SyliusWebBundle:Backend/Macros:misc.html.twig' import pagination %}
{% from 'SyliusWebBundle:Backend/Metadata:macros.html.twig' import list %}
{% block topbar %}
<ol class="breadcrumb">
<li>{{ 'sylius.breadcrumb.configuration'|trans }}</li>
<li>{{ 'sylius.breadcrumb.metadata.index'|trans }}</li>
</ol>
{% endblock %}
{% block content %}
<div class="page-header">
<div class="actions-menu">
<a href="{{ path('sylius_backend_metadata_container_customize', { 'id': 'DefaultPage' }) }}" class="btn btn-default">
<i class="glyphicon glyphicon-file"></i><span>{{ 'sylius.metadata.default_page.group_customize'|trans }}</span>
</a>
</div>
<h1><i class="glyphicon glyphicon-flag"></i> {{ 'sylius.metadata.index_header'|trans|raw }}</h1>
</div>
{{ pagination(metadata_containers) }}
{{ list(metadata_containers) }}
{{ pagination(metadata_containers) }}
{% endblock %}

View file

@ -0,0 +1,32 @@
{% macro list(metadatas) %}
{% import 'SyliusResourceBundle:Macros:buttons.html.twig' as buttons %}
{% import 'SyliusWebBundle:Backend/Macros:alerts.html.twig' as alerts %}
{% if metadatas|length > 0 %}
<table class="table table-condensed">
<thead>
<tr>
<th>{{ sylius_resource_sort('id', 'sylius.metadata.id'|trans) }}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for metadata in metadatas %}
<tr>
<td><a href="{{ path('sylius_backend_metadata_container_show', { 'id': metadata_container.id }) }}">{{ metadata_container.id }}</a></td>
<td>
<div class="pull-right">
{{ buttons.edit(path('sylius_backend_metadata_container_customize', { 'id': metadata_container.id })) }}
{{ buttons.delete(path('sylius_backend_metadata_container_delete', { 'id': metadata_container.id })) }}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
{{ alerts.info('sylius.metadata.no_results'|trans) }}
{% endif %}
{% endmacro %}

View file

@ -0,0 +1,53 @@
{% extends 'SyliusWebBundle:Backend:layout.html.twig' %}
{% import 'SyliusResourceBundle:Macros:buttons.html.twig' as buttons %}
{% macro dataAsList(data) %}
{% if data is iterable and data|length > 0 %}
<ul>
{% for key, element in data %}
<li>
{% if key is not sameas(loop.index0) %}<b>{{ key|humanize }}:</b> {% endif %}{{ _self.dataAsList(element) }}
</li>
{% endfor %}
</ul>
{% elseif data is not empty %}
{{ data }}
{% else %}
<small><i>empty</i></small>
{% endif %}
{% endmacro %}
{% block topbar %}
<ol class="breadcrumb">
<li>{{ 'sylius.breadcrumb.configuration'|trans }}</li>
<li><a href="{{ path('sylius_backend_metadata_container_index') }}">{{ 'sylius.breadcrumb.metadata.index'|trans }}</a></li>
<li>{{ metadata_container.id }}</li>
</ol>
{% endblock %}
{% block content %}
<div class="page-header">
<div class="actions-menu">
{{ buttons.manage(path('sylius_backend_metadata_container_index'), 'sylius.metadata.manage'|trans) }}
{{ buttons.edit(path('sylius_backend_metadata_container_customize', { 'id': metadata_container.id })) }}
{{ buttons.delete(path('sylius_backend_metadata_container_delete', { 'id': metadata_container.id }), null, false, false) }}
</div>
<h1><i class="glyphicon glyphicon-flag"></i> {{ 'sylius.metadata.show_header'|trans|raw }}</h1>
</div>
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th>{{ 'sylius.metadata.id'|trans }}</th>
<th>{{ 'sylius.metadata.content'|trans }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ metadata_container.id }}</td>
<td>{{ _self.dataAsList(metadata_container.metadata.toArray()) }}</td>
</tr>
</tbody>
</table>
{% endblock %}

View file

@ -15,6 +15,9 @@
<div class="page-header">
<div class="actions-menu">
{{ buttons.create(path('sylius_backend_product_create'), 'sylius.product.create'|trans) }}
<a href="{{ path('sylius_backend_metadata_container_customize', { 'id': constant('Sylius\\Component\\Core\\Model\\ProductInterface::METADATA_CLASS_IDENTIFIER') }) }}" class="btn btn-default">
<i class="glyphicon glyphicon-file"></i><span>{{ 'sylius.metadata.product.group_customize'|trans }}</span>
</a>
{{ buttons.manage(path('sylius_backend_product_option_index'), 'sylius.product_option.manage'|trans) }}
{{ buttons.manage(path('sylius_backend_product_attribute_index'), 'sylius.product_attribute.manage'|trans) }}
{% if app.request.query.get('deleted') %}

View file

@ -93,6 +93,9 @@
{{ product.updatedAt|date('Y/m/d') }}
</td>
<td class="center-text">
<a href="{{ path('sylius_backend_metadata_container_customize', { 'id': product.metadataIdentifier }) }}" class="btn btn-default">
<i class="glyphicon glyphicon-file"></i><span>{{ 'sylius.metadata.product.single_customize'|trans }}</span>
</a>
{% if product.deleted %}
{{ buttons.patch(path('sylius_backend_product_delete_restore', {'id': product.id}), 'sylius.restore'|trans, null, 'btn btn-primary') }}
{% else %}

View file

@ -23,12 +23,15 @@
{{ buttons.enable(path('sylius_backend_product_enable', {'id': product.id})) }}
{% endif %}
{% if not product.deleted %}
{{ buttons.edit(path('sylius_backend_product_update', {'id': product.id})) }}
{{ buttons.delete(path('sylius_backend_product_delete', {'id': product.id}), null, false, true) }}
<a href="{{ path(product) }}" class="btn btn-info">
<i class="glyphicon glyphicon-shopping-cart"></i>
{{ 'sylius.product.show_in_store'|trans }}
</a>
<a href="{{ path('sylius_backend_metadata_container_customize', { 'id': product.metadataIdentifier }) }}" class="btn btn-default">
<i class="glyphicon glyphicon-file"></i><span>{{ 'sylius.metadata.product.single_customize'|trans }}</span>
</a>
{{ buttons.edit(path('sylius_backend_product_update', {'id': product.id})) }}
{{ buttons.delete(path('sylius_backend_product_delete', {'id': product.id}), null, false, true) }}
<a href="{{ path(product) }}" class="btn btn-info">
<i class="glyphicon glyphicon-shopping-cart"></i>
{{ 'sylius.product.show_in_store'|trans }}
</a>
{% else %}
{{ buttons.patch(path('sylius_backend_product_delete_restore', {'id': product.id}), 'sylius.restore'|trans, null, 'btn btn-primary') }}
{% endif %}

View file

@ -50,8 +50,11 @@
<td>
<div class="pull-right">
{% if not variant.deleted %}
{{ buttons.edit(path('sylius_backend_product_variant_update', {'productId': product.id, 'id': variant.id})) }}
{{ buttons.delete(path('sylius_backend_product_variant_delete', {'productId': product.id, 'id': variant.id})) }}
<a href="{{ path('sylius_backend_metadata_container_customize', { 'id': variant.metadataIdentifier }) }}" class="btn btn-default">
<i class="glyphicon glyphicon-file"></i><span>{{ 'sylius.metadata.product_variant.single_customize'|trans }}</span>
</a>
{{ buttons.edit(path('sylius_backend_product_variant_update', {'productId': product.id, 'id': variant.id})) }}
{{ buttons.delete(path('sylius_backend_product_variant_delete', {'productId': product.id, 'id': variant.id})) }}
{% endif %}
</div>
</td>

View file

@ -3,6 +3,14 @@
{% set form = sylius_cart_form({'product': product}) %}
{% from 'SyliusWebBundle:Frontend/Product:macros.html.twig' import sylius_display_price %}
{% block head %}
{{ sylius_metadata_render(product, null, { 'defaults': {
'title': settings.title|default('sylius.meta.frontend_title'|trans),
'description': settings.meta_description|default('sylius.meta.frontend_description'|trans),
'keywords': settings.meta_description|default('sylius.meta.frontend_keywords'|trans)
} })|raw }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-3">

View file

@ -2,14 +2,16 @@
{% set settings = sylius_settings_all('sylius_general') %}
<html>
<head>
<title>
{% block title %}
{{ settings.title|default('sylius.meta.frontend_title'|trans) }}
{% endblock %}
</title>
<meta charset="UTF-8">
<meta name="description" content="{{ settings.meta_description|default('sylius.meta.frontend_description'|trans) }}">
<meta name="keywords" content="{{ settings.meta_keywords|default('sylius.meta.frontend_keywords'|trans) }}">
{% block head %}
<title>
{% block title %}
{{ settings.title|default('sylius.meta.frontend_title'|trans) }}
{% endblock %}
</title>
<meta charset="UTF-8">
<meta name="description" content="{{ settings.meta_description|default('sylius.meta.frontend_description'|trans) }}">
<meta name="keywords" content="{{ settings.meta_keywords|default('sylius.meta.frontend_keywords'|trans) }}">
{% endblock %}
<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700|Open+Sans:300italic,400,300,700&subset=latin,latin-ext' rel='stylesheet' type='text/css'>

View file

@ -0,0 +1,54 @@
<?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\Core\Metadata\HierarchyProvider;
use Sylius\Component\Core\Model\ArchetypeInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Metadata\HierarchyProvider\MetadataHierarchyProviderInterface;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class ProductHierarchyProvider implements MetadataHierarchyProviderInterface
{
/**
* {@inheritdoc}
*
* @param ProductInterface|MetadataSubjectInterface $product
*/
public function getHierarchyByMetadataSubject(MetadataSubjectInterface $product)
{
$hierarchy = [
$product->getMetadataIdentifier(),
];
/** @var ArchetypeInterface $productArchetype */
$productArchetype = $product->getArchetype();
if (null !== $productArchetype) {
$hierarchy[] = $productArchetype->getMetadataIdentifier();
}
$hierarchy[] = $product->getMetadataClassIdentifier();
$hierarchy[] = 'DefaultPage';
return $hierarchy;
}
/**
* {@inheritdoc}
*/
public function supports(MetadataSubjectInterface $metadataSubject)
{
return $metadataSubject instanceof ProductInterface;
}
}

View file

@ -0,0 +1,61 @@
<?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\Core\Metadata\HierarchyProvider;
use Sylius\Component\Core\Model\ArchetypeInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Metadata\HierarchyProvider\MetadataHierarchyProviderInterface;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class ProductVariantHierarchyProvider implements MetadataHierarchyProviderInterface
{
/**
* {@inheritdoc}
*/
public function getHierarchyByMetadataSubject(MetadataSubjectInterface $metadataSubject)
{
/** @var ProductVariantInterface $productVariant */
$productVariant = $metadataSubject;
/** @var ProductInterface $product */
$product = $productVariant->getProduct();
$hierarchy = [
$productVariant->getMetadataIdentifier(),
$product->getMetadataIdentifier(),
];
/** @var ArchetypeInterface $productArchetype */
$productArchetype = $product->getArchetype();
if (null !== $productArchetype) {
$hierarchy[] = $productArchetype->getMetadataIdentifier();
}
$hierarchy[] = $product->getMetadataClassIdentifier();
$hierarchy[] = 'DefaultPage';
return $hierarchy;
}
/**
* {@inheritdoc}
*/
public function supports(MetadataSubjectInterface $metadataSubject)
{
return $metadataSubject instanceof ProductVariantInterface;
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Sylius\Component\Core\Metadata\Provider;
use Sylius\Component\Metadata\HierarchyProvider\MetadataHierarchyProviderInterface;
use Sylius\Component\Metadata\Model\Custom\PageMetadata;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
use Sylius\Component\Metadata\Provider\MetadataProviderInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class MetadataProvider implements MetadataProviderInterface
{
/**
* @var MetadataProviderInterface
*/
private $metadataProvider;
/**
* @var MetadataHierarchyProviderInterface
*/
private $metadataHierarchyProvider;
/**
* @param MetadataProviderInterface $metadataProvider
* @param MetadataHierarchyProviderInterface $metadataHierarchyProvider
*/
public function __construct(MetadataProviderInterface $metadataProvider, MetadataHierarchyProviderInterface $metadataHierarchyProvider)
{
$this->metadataProvider = $metadataProvider;
$this->metadataHierarchyProvider = $metadataHierarchyProvider;
}
/**
* {@inheritdoc}
*/
public function findMetadataBySubject(MetadataSubjectInterface $metadataSubject)
{
$metadata = $this->metadataProvider->findMetadataBySubject($metadataSubject);
if (null !== $metadata) {
return $metadata;
}
$hierarchy = $this->metadataHierarchyProvider->getHierarchyByMetadataSubject($metadataSubject);
if ('DefaultPage' === end($hierarchy)) {
return new PageMetadata();
}
return null;
}
}

View file

@ -0,0 +1,45 @@
<?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\Core\Model;
use Sylius\Component\Product\Model\Archetype as BaseArchetype;
use Sylius\Component\Product\Model\ArchetypeTranslation;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class Archetype extends BaseArchetype implements ArchetypeInterface
{
/**
* {@inheritdoc}
*/
public function getMetadataClassIdentifier()
{
return 'Archetype';
}
/**
* {@inheritdoc}
*/
public function getMetadataIdentifier()
{
return $this->getMetadataClassIdentifier() . '-' . $this->getId();
}
/**
* {@inheritdoc}
*/
public static function getTranslationClass()
{
return ArchetypeTranslation::class;
}
}

View file

@ -0,0 +1,22 @@
<?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\Core\Model;
use Sylius\Component\Product\Model\ArchetypeInterface as BaseArchetypeInterface;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface ArchetypeInterface extends BaseArchetypeInterface, MetadataSubjectInterface
{
}

View file

@ -87,6 +87,22 @@ class Product extends BaseProduct implements ProductInterface
$this->variantSelectionMethod = self::VARIANT_SELECTION_CHOICE;
}
/**
* {@inheritdoc}
*/
public function getMetadataClassIdentifier()
{
return self::METADATA_CLASS_IDENTIFIER;
}
/**
* {@inheritdoc}
*/
public function getMetadataIdentifier()
{
return $this->getMetadataClassIdentifier() . '-' . $this->getId();
}
/**
* {@inheritdoc}
*/

View file

@ -15,6 +15,7 @@ use Doctrine\Common\Collections\Collection;
use Sylius\Component\Addressing\Model\ZoneInterface;
use Sylius\Component\Channel\Model\ChannelsAwareInterface;
use Sylius\Component\Product\Model\ProductInterface as BaseProductInterface;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
use Sylius\Component\Shipping\Model\ShippingCategoryInterface;
use Sylius\Component\Taxation\Model\TaxableInterface;
use Sylius\Component\Taxation\Model\TaxCategoryInterface;
@ -28,7 +29,8 @@ interface ProductInterface extends
BaseProductInterface,
TaxableInterface,
TaxonsAwareInterface,
ChannelsAwareInterface
ChannelsAwareInterface,
MetadataSubjectInterface
{
/*
* Variant selection methods.
@ -41,6 +43,8 @@ interface ProductInterface extends
const VARIANT_SELECTION_CHOICE = 'choice';
const VARIANT_SELECTION_MATCH = 'match';
const METADATA_CLASS_IDENTIFIER = 'Product';
/**
* Get product SKU.
*

View file

@ -147,6 +147,22 @@ class ProductVariant extends BaseVariant implements ProductVariantInterface
return $string;
}
/**
* {@inheritdoc}
*/
public function getMetadataClassIdentifier()
{
return self::METADATA_CLASS_IDENTIFIER;
}
/**
* {@inheritdoc}
*/
public function getMetadataIdentifier()
{
return $this->getMetadataClassIdentifier() . '-' . $this->getId();
}
/**
* {@inheritdoc}
*/

View file

@ -15,6 +15,7 @@ use Doctrine\Common\Collections\Collection;
use Sylius\Component\Inventory\Model\StockableInterface;
use Sylius\Component\Pricing\Model\PriceableInterface;
use Sylius\Component\Product\Model\VariantInterface as BaseVariantInterface;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
use Sylius\Component\Shipping\Model\ShippableInterface;
/**
@ -26,8 +27,11 @@ interface ProductVariantInterface extends
BaseVariantInterface,
ShippableInterface,
StockableInterface,
PriceableInterface
PriceableInterface,
MetadataSubjectInterface
{
const METADATA_CLASS_IDENTIFIER = 'ProductVariant';
/**
* Get images.
*

View file

@ -0,0 +1,79 @@
<?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\Core\Metadata\HierarchyProvider;
use PhpSpec\ObjectBehavior;
use Sylius\Component\Core\Metadata\HierarchyProvider\ProductHierarchyProvider;
use Sylius\Component\Core\Model\ArchetypeInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Metadata\HierarchyProvider\MetadataHierarchyProviderInterface;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
/**
* @mixin ProductHierarchyProvider
*
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class ProductHierarchyProviderSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Core\Metadata\HierarchyProvider\ProductHierarchyProvider');
}
function it_implements_Sylius_Metadata_Hierarchy_Provider_interface()
{
$this->shouldImplement(MetadataHierarchyProviderInterface::class);
}
function it_supports_Sylius_Core_Product(
ProductInterface $coreProduct,
MetadataSubjectInterface $metadataSubject
) {
$this->supports($coreProduct)->shouldReturn(true);
$this->supports($metadataSubject)->shouldReturn(false);
}
function it_generates_correct_hierarchy_when_product_has_archetype(
ProductInterface $product,
ArchetypeInterface $archetype
) {
$product->getMetadataIdentifier()->shouldBeCalled()->willReturn('Product-42');
$product->getMetadataClassIdentifier()->shouldBeCalled()->willReturn('Product');
$archetype->getMetadataIdentifier()->shouldBeCalled()->willReturn('Archetype-42');
$product->getArchetype()->shouldBeCalled()->willReturn($archetype);
$this->getHierarchyByMetadataSubject($product)->shouldReturn([
'Product-42',
'Archetype-42',
'Product',
'DefaultPage',
]);
}
function it_generates_correct_hierarchy_when_product_does_not_have_archetype(
ProductInterface $product
) {
$product->getMetadataIdentifier()->shouldBeCalled()->willReturn('Product-42');
$product->getMetadataClassIdentifier()->shouldBeCalled()->willReturn('Product');
$product->getArchetype()->shouldBeCalled()->willReturn(null);
$this->getHierarchyByMetadataSubject($product)->shouldReturn([
'Product-42',
'Product',
'DefaultPage',
]);
}
}

View file

@ -0,0 +1,91 @@
<?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\Core\Metadata\HierarchyProvider;
use PhpSpec\ObjectBehavior;
use Sylius\Component\Core\Model\ArchetypeInterface;
use Sylius\Component\Core\Model\ProductInterface;
use Sylius\Component\Core\Model\ProductVariantInterface;
use Sylius\Component\Metadata\HierarchyProvider\MetadataHierarchyProviderInterface;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
/**
* @mixin \Sylius\Component\Core\Metadata\HierarchyProvider\ProductVariantHierarchyProvider
*
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class ProductVariantHierarchyProviderSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Core\Metadata\HierarchyProvider\ProductVariantHierarchyProvider');
}
function it_implements_Sylius_Metadata_Hierarchy_Provider_interface()
{
$this->shouldImplement(MetadataHierarchyProviderInterface::class);
}
function it_supports_Sylius_Core_ProductVariant(
ProductVariantInterface $coreProductVariant,
MetadataSubjectInterface $metadataSubject
) {
$this->supports($coreProductVariant)->shouldReturn(true);
$this->supports($metadataSubject)->shouldReturn(false);
}
function it_generates_correct_hierarchy_when_product_variant_has_archetype(
ProductVariantInterface $productVariant,
ProductInterface $product,
ArchetypeInterface $archetype
) {
$productVariant->getMetadataIdentifier()->shouldBeCalled()->willReturn('ProductVariant-42');
$product->getMetadataIdentifier()->shouldBeCalled()->willReturn('Product-42');
$product->getMetadataClassIdentifier()->shouldBeCalled()->willReturn('Product');
$archetype->getMetadataIdentifier()->shouldBeCalled()->willReturn('Archetype-42');
$productVariant->getProduct()->shouldBeCalled()->willReturn($product);
$product->getArchetype()->shouldBeCalled()->willReturn($archetype);
$this->getHierarchyByMetadataSubject($productVariant)->shouldReturn([
'ProductVariant-42',
'Product-42',
'Archetype-42',
'Product',
'DefaultPage',
]);
}
function it_generates_correct_hierarchy_when_product_variant_does_not_have_archetype(
ProductVariantInterface $productVariant,
ProductInterface $product
) {
$productVariant->getMetadataIdentifier()->shouldBeCalled()->willReturn('ProductVariant-42');
$product->getMetadataIdentifier()->shouldBeCalled()->willReturn('Product-42');
$product->getMetadataClassIdentifier()->shouldBeCalled()->willReturn('Product');
$productVariant->getProduct()->shouldBeCalled()->willReturn($product);
$product->getArchetype()->shouldBeCalled()->willReturn(null);
$this->getHierarchyByMetadataSubject($productVariant)->shouldReturn([
'ProductVariant-42',
'Product-42',
'Product',
'DefaultPage',
]);
}
}

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 spec\Sylius\Component\Core\Model;
use PhpSpec\ObjectBehavior;
/**
* @mixin \Sylius\Component\Core\Model\Archetype
*
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class ArchetypeSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Sylius\Component\Core\Model\Archetype');
}
function it_implements_Sylius_Core_Archetype_interface()
{
$this->shouldImplement('Sylius\Component\Core\Model\ArchetypeInterface');
}
function it_has_metadata_class_identifier()
{
$this->getMetadataClassIdentifier()->shouldReturn('Archetype');
}
}

View file

@ -53,6 +53,11 @@ class ProductSpec extends ObjectBehavior
$this->shouldHaveType(SyliusProduct::class);
}
function it_has_metadata_class_identifier()
{
$this->getMetadataClassIdentifier()->shouldReturn('Product');
}
function it_initializes_taxon_collection_by_default()
{
$this->getTaxons()->shouldHaveType(Collection::class);

View file

@ -36,6 +36,11 @@ class ProductVariantSpec extends ObjectBehavior
$this->shouldHaveType('Sylius\Component\Product\Model\Variant');
}
function it_has_metadata_class_identifier()
{
$this->getMetadataClassIdentifier()->shouldReturn('ProductVariant');
}
function it_should_not_have_price_by_default()
{
$this->getPrice()->shouldReturn(null);

View file

@ -0,0 +1,7 @@
vendor/
bin/
composer.phar
composer.lock
phpspec.yml

View file

@ -0,0 +1,15 @@
language: php
sudo: false
php:
- 7.0
- 5.6
- 5.5
before_script:
- phpenc config-rm xdebug.ini || true
- composer install --no-interaction --prefer-source
script:
- vendor/bin/phpspec run -f pretty --verbose

View file

@ -0,0 +1,56 @@
<?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\Metadata\Accessor;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
use Sylius\Component\Metadata\Provider\MetadataProviderInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
final class MetadataAccessor implements MetadataAccessorInterface
{
/**
* @var MetadataProviderInterface
*/
private $metadataProvider;
/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* @param MetadataProviderInterface $metadataProvider
* @param PropertyAccessorInterface $propertyAccessor
*/
public function __construct(MetadataProviderInterface $metadataProvider, PropertyAccessorInterface $propertyAccessor)
{
$this->metadataProvider = $metadataProvider;
$this->propertyAccessor = $propertyAccessor;
}
/**
* {@inheritdoc}
*/
public function getProperty(MetadataSubjectInterface $metadataSubject, $propertyPath = null)
{
$metadata = $this->metadataProvider->findMetadataBySubject($metadataSubject);
if (null === $propertyPath) {
return $metadata;
}
return $this->propertyAccessor->getValue($metadata, $propertyPath);
}
}

View file

@ -0,0 +1,28 @@
<?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\Metadata\Accessor;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface MetadataAccessorInterface
{
/**
* @param MetadataSubjectInterface $metadataSubject
* @param string|null $propertyPath
*
* @return mixed
*/
public function getProperty(MetadataSubjectInterface $metadataSubject, $propertyPath = null);
}

View file

@ -0,0 +1,35 @@
<?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\Metadata\Compiler;
use Sylius\Component\Metadata\Model\MetadataInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
final class MetadataCompiler implements MetadataCompilerInterface
{
/**
* {@inheritdoc}
*/
public function compile(MetadataInterface $metadata, array $parents = [])
{
$compiledMetadata = clone $metadata;
/** @var MetadataInterface[] $parents */
foreach ($parents as $parent) {
$compiledMetadata->merge($parent);
}
return $compiledMetadata;
}
}

View file

@ -0,0 +1,28 @@
<?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\Metadata\Compiler;
use Sylius\Component\Metadata\Model\MetadataInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface MetadataCompilerInterface
{
/**
* @param MetadataInterface $metadata
* @param MetadataInterface[] $parents
*
* @return MetadataInterface
*/
public function compile(MetadataInterface $metadata, array $parents = []);
}

View file

@ -0,0 +1,46 @@
<?php
namespace Sylius\Component\Metadata\Factory;
use Sylius\Component\Metadata\Model\MetadataContainerInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
final class MetadataContainerFactory implements MetadataContainerFactoryInterface, FactoryInterface
{
/**
* @var string
*/
private $metadataContainerClass;
/**
* @param string $metadataContainerClass
*/
public function __construct($metadataContainerClass)
{
$this->metadataContainerClass = $metadataContainerClass;
}
/**
* {@inheritdoc}
*
* @return MetadataContainerInterface
*/
public function createNew()
{
return new $this->metadataContainerClass();
}
/**
* {@inheritdoc}
*/
public function createIdentifiedBy($id)
{
$metadataContainer = $this->createNew();
$metadataContainer->setId($id);
return $metadataContainer;
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace Sylius\Component\Metadata\Factory;
use Sylius\Component\Metadata\Model\MetadataContainerInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface MetadataContainerFactoryInterface
{
/**
* @param string $id
*
* @return MetadataContainerInterface
*/
public function createIdentifiedBy($id);
}

View file

@ -0,0 +1,77 @@
<?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\Metadata\HierarchyProvider;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
final class CompositeMetadataHierarchyProvider implements MetadataHierarchyProviderInterface
{
/**
* @var MetadataHierarchyProviderInterface[]
*/
private $providers;
/**
* @param MetadataHierarchyProviderInterface[] $providers
*/
public function __construct(array $providers = [])
{
$this->assertProvidersHaveCorrectType($providers);
$this->providers = $providers;
}
/**
* {@inheritdoc}
*/
public function getHierarchyByMetadataSubject(MetadataSubjectInterface $metadataSubject)
{
foreach ($this->providers as $provider) {
if ($provider->supports($metadataSubject)) {
return $provider->getHierarchyByMetadataSubject($metadataSubject);
}
}
return [
$metadataSubject->getMetadataIdentifier(),
$metadataSubject->getMetadataClassIdentifier(),
];
}
/**
* {@inheritdoc}
*/
public function supports(MetadataSubjectInterface $metadataSubject)
{
return true;
}
/**
* @param MetadataHierarchyProviderInterface[] $providers
*/
private function assertProvidersHaveCorrectType(array $providers)
{
foreach ($providers as $provider) {
if ($provider instanceof MetadataHierarchyProviderInterface) {
continue;
}
throw new \InvalidArgumentException(sprintf(
'Metadata hierarchy provider should have type "%s"',
MetadataHierarchyProviderInterface::class
));
}
}
}

View file

@ -0,0 +1,36 @@
<?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\Metadata\HierarchyProvider;
use Sylius\Component\Metadata\Model\MetadataSubjectInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface MetadataHierarchyProviderInterface
{
/**
* Returns identifiers in order from the most specific to the least specific.
*
* @param MetadataSubjectInterface $metadataSubject
*
* @return string[]
*/
public function getHierarchyByMetadataSubject(MetadataSubjectInterface $metadataSubject);
/**
* @param MetadataSubjectInterface $metadataSubject
*
* @return boolean
*/
public function supports(MetadataSubjectInterface $metadataSubject);
}

View file

@ -0,0 +1,19 @@
Copyright (c) 2011-2016 Paweł Jędrzejewski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,78 @@
<?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\Metadata\Model;
/**
* Base metadata class with reusable merging ability.
*
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
abstract class AbstractMetadata implements MetadataInterface
{
/**
* {@inheritdoc}
*/
public function merge(MetadataInterface $metadata)
{
if (!$metadata instanceof $this || !$this instanceof $metadata) {
throw new \InvalidArgumentException(
sprintf(
'You can only merge instances of the same classes. Tried to merge "%s" with "%s".',
get_class($this),
get_class($metadata)
)
);
}
$inheritedVariables = get_object_vars($metadata);
foreach ($inheritedVariables as $inheritedKey => $inheritedValue) {
if ($this->{$inheritedKey} instanceof MetadataInterface) {
$this->{$inheritedKey}->merge($inheritedValue);
continue;
}
if (null !== $this->{$inheritedKey}) {
continue;
}
$this->{$inheritedKey} = $inheritedValue;
}
}
/**
* {@inheritdoc}
*/
public function toArray()
{
return array_map(
function ($value) {
if ($value instanceof MetadataInterface) {
$value = $value->toArray();
}
return $value;
},
get_object_vars($this)
);
}
/**
* {@inheritdoc}
*/
public function forAll(callable $callable)
{
foreach (get_object_vars($this) as $key => $value) {
$this->$key = $callable($value);
}
}
}

View file

@ -0,0 +1,126 @@
<?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\Metadata\Model\Custom;
use Sylius\Component\Metadata\Model\AbstractMetadata;
use Sylius\Component\Metadata\Model\Twitter\CardInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class PageMetadata extends AbstractMetadata implements PageMetadataInterface
{
/**
* @var string
*/
protected $title;
/**
* @var string
*/
protected $description;
/**
* @var string[]
*/
protected $keywords = [];
/**
* @var string
*/
protected $charset = 'UTF-8';
/**
* @var CardInterface
*/
protected $twitter;
/**
* {@inheritdoc}
*/
public function getTitle()
{
return $this->title;
}
/**
* {@inheritdoc}
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* {@inheritdoc}
*/
public function getDescription()
{
return $this->description;
}
/**
* {@inheritdoc}
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public function getKeywords()
{
return $this->keywords;
}
/**
* {@inheritdoc}
*/
public function setKeywords(array $keywords)
{
$this->keywords = $keywords;
}
/**
* {@inheritdoc}
*/
public function getCharset()
{
return $this->charset;
}
/**
* {@inheritdoc}
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* {@inheritdoc}
*/
public function getTwitter()
{
return $this->twitter;
}
/**
* {@inheritdoc}
*/
public function setTwitter(CardInterface $twitter = null)
{
$this->twitter = $twitter;
}
}

View file

@ -0,0 +1,71 @@
<?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\Metadata\Model\Custom;
use Sylius\Component\Metadata\Model\MetadataInterface;
use Sylius\Component\Metadata\Model\Twitter\CardInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface PageMetadataInterface extends MetadataInterface
{
/**
* @return string
*/
public function getTitle();
/**
* @param string $title
*/
public function setTitle($title);
/**
* @return string
*/
public function getDescription();
/**
* @param string $description
*/
public function setDescription($description);
/**
* @return string[]
*/
public function getKeywords();
/**
* @param string[] $keywords
*/
public function setKeywords(array $keywords);
/**
* @return string
*/
public function getCharset();
/**
* @param string $charset
*/
public function setCharset($charset);
/**
* @return CardInterface|null
*/
public function getTwitter();
/**
* @param CardInterface|null $card
*/
public function setTwitter(CardInterface $card = null);
}

View file

@ -0,0 +1,60 @@
<?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\Metadata\Model;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class MetadataContainer implements MetadataContainerInterface
{
/**
* @var string
*/
protected $id;
/**
* @var MetadataInterface
*/
protected $metadata;
/**
* {@inheritdoc}
*/
public function getId()
{
return $this->id;
}
/**
* {@inheritdoc}
*/
public function setId($id)
{
$this->id = $id;
}
/**
* {@inheritdoc}
*/
public function getMetadata()
{
return $this->metadata;
}
/**
* {@inheritdoc}
*/
public function setMetadata(MetadataInterface $metadata)
{
$this->metadata = $metadata;
}
}

View file

@ -0,0 +1,40 @@
<?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\Metadata\Model;
use Sylius\Component\Resource\Model\ResourceInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface MetadataContainerInterface extends ResourceInterface
{
/**
* @return string
*/
public function getId();
/**
* @param string $id
*/
public function setId($id);
/**
* @return MetadataInterface
*/
public function getMetadata();
/**
* @param MetadataInterface $metadata
*/
public function setMetadata(MetadataInterface $metadata);
}

View file

@ -0,0 +1,33 @@
<?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\Metadata\Model;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface MetadataInterface
{
/**
* @param MetadataInterface $metadata
*/
public function merge(MetadataInterface $metadata);
/**
* @return array
*/
public function toArray();
/**
* @param callable $callable
*/
public function forAll(callable $callable);
}

View file

@ -0,0 +1,32 @@
<?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\Metadata\Model;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface MetadataSubjectInterface
{
/**
* Metadata class identifier is usually a class name (not FQCN), eg. "MetadataSubject".
*
* @return string
*/
public function getMetadataClassIdentifier();
/**
* Metadata identifier is usually a class name (not FQCN) and ID joined by dash, eg. "MetadataSubject-42".
*
* @return string
*/
public function getMetadataIdentifier();
}

View file

@ -0,0 +1,75 @@
<?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\Metadata\Model\Twitter;
use Sylius\Component\Metadata\Model\AbstractMetadata;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
abstract class AbstractCard extends AbstractMetadata implements CardInterface
{
/**
* @var string
*/
protected $type;
/**
* @var string
*/
protected $site;
/**
* @var string
*/
protected $siteId;
/**
* {@inheritdoc}
*/
public function getType()
{
return $this->type;
}
/**
* {@inheritdoc}
*/
public function getSite()
{
return $this->site;
}
/**
* {@inheritdoc}
*/
public function setSite($site)
{
$this->site = $site;
}
/**
* {@inheritdoc}
*/
public function getSiteId()
{
return $this->siteId;
}
/**
* {@inheritdoc}
*/
public function setSiteId($siteId)
{
$this->siteId = $siteId;
}
}

View file

@ -0,0 +1,235 @@
<?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\Metadata\Model\Twitter;
/**
* {@inheritdoc}
*
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class AppCard extends AbstractCard implements AppCardInterface
{
/**
* {@inheritdoc}
*/
protected $type = 'app';
/**
* @var string
*/
protected $description;
/**
* @var string
*/
protected $appNameIphone;
/**
* @var string
*/
protected $appIdIphone;
/**
* @var string
*/
protected $appUrlIphone;
/**
* @var string
*/
protected $appNameIpad;
/**
* @var string
*/
protected $appIdIpad;
/**
* @var string
*/
protected $appUrlIpad;
/**
* @var string
*/
protected $appNameGooglePlay;
/**
* @var string
*/
protected $appIdGooglePlay;
/**
* @var string
*/
protected $appUrlGooglePlay;
/**
* {@inheritdoc}
*/
public function getDescription()
{
return $this->description;
}
/**
* {@inheritdoc}
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public function getAppNameIphone()
{
return $this->appNameIphone;
}
/**
* {@inheritdoc}
*/
public function setAppNameIphone($appNameIphone)
{
$this->appNameIphone = $appNameIphone;
}
/**
* {@inheritdoc}
*/
public function getAppIdIphone()
{
return $this->appIdIphone;
}
/**
* {@inheritdoc}
*/
public function setAppIdIphone($appIdIphone)
{
$this->appIdIphone = $appIdIphone;
}
/**
* {@inheritdoc}
*/
public function getAppUrlIphone()
{
return $this->appUrlIphone;
}
/**
* {@inheritdoc}
*/
public function setAppUrlIphone($appUrlIphone)
{
$this->appUrlIphone = $appUrlIphone;
}
/**
* {@inheritdoc}
*/
public function getAppNameIpad()
{
return $this->appNameIpad;
}
/**
* {@inheritdoc}
*/
public function setAppNameIpad($appNameIpad)
{
$this->appNameIpad = $appNameIpad;
}
/**
* {@inheritdoc}
*/
public function getAppIdIpad()
{
return $this->appIdIpad;
}
/**
* {@inheritdoc}
*/
public function setAppIdIpad($appIdIpad)
{
$this->appIdIpad = $appIdIpad;
}
/**
* {@inheritdoc}
*/
public function getAppUrlIpad()
{
return $this->appUrlIpad;
}
/**
* {@inheritdoc}
*/
public function setAppUrlIpad($appUrlIpad)
{
$this->appUrlIpad = $appUrlIpad;
}
/**
* {@inheritdoc}
*/
public function getAppNameGooglePlay()
{
return $this->appNameGooglePlay;
}
/**
* {@inheritdoc}
*/
public function setAppNameGooglePlay($appNameGooglePlay)
{
$this->appNameGooglePlay = $appNameGooglePlay;
}
/**
* {@inheritdoc}
*/
public function getAppIdGooglePlay()
{
return $this->appIdGooglePlay;
}
/**
* {@inheritdoc}
*/
public function setAppIdGooglePlay($appIdGooglePlay)
{
$this->appIdGooglePlay = $appIdGooglePlay;
}
/**
* {@inheritdoc}
*/
public function getAppUrlGooglePlay()
{
return $this->appUrlGooglePlay;
}
/**
* {@inheritdoc}
*/
public function setAppUrlGooglePlay($appUrlGooglePlay)
{
$this->appUrlGooglePlay = $appUrlGooglePlay;
}
}

View file

@ -0,0 +1,160 @@
<?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\Metadata\Model\Twitter;
/**
* @see https://dev.twitter.com/cards/types/app
*
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface AppCardInterface extends CardInterface
{
/**
* The twitter:description property.
*
* @return string
*/
public function getDescription();
/**
* The twitter:description property.
*
* @param string $description
*/
public function setDescription($description);
/**
* The twitter:app:name:iphone property.
*
* @return string
*/
public function getAppNameIphone();
/**
* The twitter:app:name:iphone property.
*
* @param string $appNameIphone
*/
public function setAppNameIphone($appNameIphone);
/**
* The twitter:app:id:iphone property.
*
* @return string
*/
public function getAppIdIphone();
/**
* The twitter:app:id:iphone property.
*
* @param string $appIdIphone
*/
public function setAppIdIphone($appIdIphone);
/**
* The twitter:app:url:iphone property.
*
* @return string
*/
public function getAppUrlIphone();
/**
* The twitter:app:url:iphone property.
*
* @param string $appUrlIphone
*/
public function setAppUrlIphone($appUrlIphone);
/**
* The twitter:app:name:ipad property.
*
* @return string
*/
public function getAppNameIpad();
/**
* The twitter:app:name:ipad property.
*
* @param string $appNameIpad
*/
public function setAppNameIpad($appNameIpad);
/**
* The twitter:app:id:ipad property.
*
* @return string
*/
public function getAppIdIpad();
/**
* The twitter:app:id:ipad property.
*
* @param string $appIdIpad
*/
public function setAppIdIpad($appIdIpad);
/**
* The twitter:app:url:ipad property.
*
* @return string
*/
public function getAppUrlIpad();
/**
* The twitter:app:url:ipad property.
*
* @param string $appUrlIpad
*/
public function setAppUrlIpad($appUrlIpad);
/**
* The twitter:app:name:googleplay property.
*
* @return string
*/
public function getAppNameGooglePlay();
/**
* The twitter:app:name:googleplay property.
*
* @param string $appNameGooglePlay
*/
public function setAppNameGooglePlay($appNameGooglePlay);
/**
* The twitter:app:id:googleplay property.
*
* @return string
*/
public function getAppIdGooglePlay();
/**
* The twitter:app:id:googleplay property.
*
* @param string $appIdGooglePlay
*/
public function setAppIdGooglePlay($appIdGooglePlay);
/**
* The twitter:app:url:googleplay property.
*
* @return string
*/
public function getAppUrlGooglePlay();
/**
* The twitter:app:url:googleplay property.
*
* @param string $appUrlGooglePlay
*/
public function setAppUrlGooglePlay($appUrlGooglePlay);
}

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\Metadata\Model\Twitter;
use Sylius\Component\Metadata\Model\MetadataInterface;
/**
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
interface CardInterface extends MetadataInterface
{
/**
* The twitter:card property.
*
* @return string
*/
public function getType();
/**
* The twitter:site property.
*
* @return string
*/
public function getSite();
/**
* The twitter:site property.
*
* @param string $site
*/
public function setSite($site);
/**
* The twitter:site:id property.
*
* @return string
*/
public function getSiteId();
/**
* The twitter:site:id property.
*
* @param string $siteId
*/
public function setSiteId($siteId);
}

View file

@ -0,0 +1,193 @@
<?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\Metadata\Model\Twitter;
/**
* {@inheritdoc}
*
* @author Kamil Kokot <kamil.kokot@lakion.com>
*/
class PlayerCard extends AbstractCard implements PlayerCardInterface
{
/**
* {@inheritdoc}
*/
protected $type = 'player';
/**
* @var string
*/
protected $title;
/**
* @var string
*/
protected $description;
/**
* @var string
*/
protected $image;
/**
* @var string
*/
protected $player;
/**
* @var integer
*/
protected $playerWidth;
/**
* @var integer
*/
protected $playerHeight;
/**
* @var string
*/
protected $playerStream;
/**
* @var string
*/
protected $playerStreamContentType;
/**
* {@inheritdoc}
*/
public function getTitle()
{
return $this->title;
}
/**
* {@inheritdoc}
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* {@inheritdoc}
*/
public function getDescription()
{
return $this->description;
}
/**
* {@inheritdoc}
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* {@inheritdoc}
*/
public function getImage()
{
return $this->image;
}
/**
* {@inheritdoc}
*/
public function setImage($image)
{
$this->image = $image;
}
/**
* {@inheritdoc}
*/
public function getPlayer()
{
return $this->player;
}
/**
* @inheritDoc
*/
public function setPlayer($player)
{
$this->player = $player;
}
/**
* {@inheritdoc}
*/
public function getPlayerWidth()
{
return $this->playerWidth;
}
/**
* {@inheritdoc}
*/
public function setPlayerWidth($playerWidth)
{
$this->playerWidth = $playerWidth;
}
/**
* {@inheritdoc}
*/
public function getPlayerHeight()
{
return $this->playerHeight;
}
/**
* {@inheritdoc}
*/
public function setPlayerHeight($playerHeight)
{
$this->playerHeight = $playerHeight;
}
/**
* {@inheritdoc}
*/
public function getPlayerStream()
{
return $this->playerStream;
}
/**
* {@inheritdoc}
*/
public function setPlayerStream($playerStream)
{
$this->playerStream = $playerStream;
}
/**
* {@inheritdoc}
*/
public function getPlayerStreamContentType()
{
return $this->playerStreamContentType;
}
/**
* {@inheritdoc}
*/
public function setPlayerStreamContentType($playerStreamContentType)
{
$this->playerStreamContentType = $playerStreamContentType;
}
}

Some files were not shown because too many files have changed in this diff Show more