comsa/sulu-shopping-cart

4.3.1 2022-04-14 09:12 UTC

README

composer req comsa/sulu-shopping-cart

Add to assets/admin/package.json:

"sulu-shopping-cart-bundle": "file:node_modules/@sulu/vendor/comsa/sulu-shopping-cart/Resources/js"

Run npm install

Add it to assets/admin/app.js:

import 'sulu-shopping-cart-bundle/admin'

And build it using npm run build, this might take a while :)

Setup the frontend js: Add it to package.json:

"sulu-shopping-cart-bundle": "file:../../vendor/comsa/sulu-shopping-cart/Resources/js"

Add it to index.js:

import 'sulu-shopping-cart-bundle/website';

And build it using npm run build or use npm run dev in development

Add routes to both routes_admin.yaml and routes_website.yaml

In: config/packages/doctrine.yaml

doctrine:
  orm:
    mappings:
      SuluShoppingCartBundle:
        is_bundle: true
        type: attribute
        dir: '/Entity'
        prefix: 'Comsa\SuluShoppingCart\Entity'
        alias: SuluShoppingCart

In: config/routes_admin.yaml

sulu_shopping_cart_admin:
  type: rest
  resource: "@SuluShoppingCartBundle/Resources/config/routes/admin.yaml"
  prefix: /admin/api
sulu_shopping_cart_admin_controller:
  resource: "@SuluShoppingCartBundle/Resources/config/routes/adminController.yaml"

In: config/routes_website.yaml

sulu_shopping_cart_website:
  resource: "@SuluShoppingCartBundle/Resources/config/routes/website.yaml"

Default configuration

Adjust to your wishes

parameters:
  #Configure the correct value in the .env file
  comsa_sulu_shopping_mollie_api_key: '%env(COMSA_SC_MOLLIE_API_KEY)%'
Make sure your product template is called "comsa_product".
<property name="product" type="single_product_selection">
    <meta>
        <title lang="en">Product</title>
        <title lang="nl">Product</title>
    </meta>
</property>

Add the following to your product template, this will render the add to cart part:

{{ render(controller('Comsa\\SuluShoppingCart\\Controller\\CartController::addToCart', {'uuid': id})) }}

To add the cart somewhere, use the following, you can overwrite this template from within your own templates:

{% include '@SuluShoppingCart/cart-small.html.twig' %}

Extending Entities

Create an CartItem.php class in src/entity.

<?php

declare(strict_types=1);

namespace App\Entity;

use Comsa\SuluShoppingCart\Entity\CartItem as BaseCartItem;
use Doctrine\ORM\Mapping\Entity;

#[Entity()]
class CartItem extends BaseCartItem
{

}

Create an Order.php in src/entity:

<?php


namespace App\Entity;

use Comsa\SuluShoppingCart\Entity\Order as BaseOrder;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Order extends BaseOrder
{

}

Here you can define custom properties.
For example:

<?php


namespace App\Entity;

use Comsa\SuluShoppingCart\Entity\CartItem as BaseCartItem;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class CartItem extends BaseCartItem
{
      /**
     * @ORM\Column(type="text", length=6000, nullable=true)
     */
    private $accessoryText;

    public function getAccessoryText(): ?string
    {
        return $this->accessoryText;
    }

    public function setAccessoryText(?string $accessoryText)
    {
        $this->accessoryText = $accessoryText;
    }
}

Make sure to apply the Symfony coding standards and the namespaces are correct as shown above.

After extending your entities update your database:

php bin/console doctrine:schema:update -f

Extending Forms

Extend the basic forms with your custom properties by extending the forms.

For example:

<?php

namespace App\Form\Extension;


use Comsa\SuluShoppingCart\Form\AddToCartType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class AddToCartTypeExtension extends AbstractTypeExtension
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       $builder->add("accessoryText", TextType::class);
    }

    public static function getExtendedTypes(): iterable
    {
        return [
          AddToCartType::class
        ];
    }
}

Make sure the namespaces are correct as shown above.

Loading Settings

To configure paymentmethods and shipmentmethods, settings need to be loaded.

Copy and paste the following inside AppFixtures.php in App\DataFixtures

<?php

namespace App\DataFixtures;

use Comsa\SuluShoppingCart\DataFixtures\AppSeed;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Persistence\ObjectManager;

class AppFixtures extends Fixture implements DependentFixtureInterface
{
    public function load(ObjectManager $manager)
    {

    }

    public function getDependencies(): array
    {
        return [
            AppSeed::class
        ];
    }
}

Run the following command to load in the settings:

php bin/console doctrine:fixtures:load --append

Make sure they are configured correctly to your needs.

Translations

Extended properties are automatically rendered in the order summary.

Translations can be made inside the translations/sulu folder in the root of the app. Name of the file should be admin.{locale}.yaml.

For Example:

properties:
  accessoryText: "Tekst op kaart"

Make sure to enter all custom properties under the properties namespace as shown above.

Templates (optional)

Product overview XML:

<?xml version="1.0" ?>
<type name="product_overview" xmlns="http://schemas.sulu.io/template/template"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd">
    <meta>
        <title lang="en">Product Overview</title>
        <title lang="nl">Product Overzicht</title>
    </meta>
    <properties>
        <property name="products" type="smart_content">
            <meta>
                <title lang="en">Products</title>
                <title lang="nl">Producten</title>
            </meta>

            <params>
                <param name="provider" value="pages"/>
                <param name="max_per_page" value="16"/>
                <param name="page_parameter" value="page"/>
                <param name="properties" type="collection">
                    <param name="title" value="title"/>
                    <param name="text" value="text"/>
                    <param name="product" value="product"/>
                    <param name="images" value="images"/>
                    <param name="rating" value="rating"/>
                    <param name="description" value="description"/>
                    <param name="imageFormat" value="imageFormat"/>
                    <param name="location" value="location"/>
                    <param name="sold" value="sold"/>
                    <param name="licence" value="licence"/>
                    <param name="price" value="price"/>
                    <param name="btntext" value="btntext"/>
                    <param name="status" value="status"/>
                </param>
            </params>
        </property>
    </properties>
</type>

Product overview TWIG

<section class="product-overview">
  <div class="row gx-5">
    {% for page in block.products %}
      {% set product = page.product %}
      {% if product.thumbnail %}
        {% set thumbnail = sulu_resolve_media(product.thumbnail, app.request.locale) %}
      {% else %}
        {% set thumbnail = false %}
      {% endif %}
      <article class="col-12 col-sm-6 col-xl-3">
        <div class="card mx-auto my-2">
          <div class="image-wrapper">
            {% if thumbnail %}
              <img src="{{ thumbnail.formats['x380_default'] }}" class="card-img-top" alt="{{ product.title }}" title="{{ product.title }}">
            {% else %}
              <p>{{ 'comsa_sulu_shopping_cart.no_thumbnail'|trans }}</p>
            {% endif %}
          </div>
          <div class="card-body">
            <h5 class="card-title">{{ product.title }}</h5>
            <h6 class="card-title">€{{ product.price|number_format(2) }}</h6>
            <a href="{{ sulu_content_path(page.url) }}" class="btn btn-primary">Bekijk product</a>
          </div>
        </div>
      </article>
    {% endfor %}
    {% if pagination is defined and pagination == true %}
      <nav class="mt-3" aria-label="{{ 'label.paginering'|trans|capitalize }} ">
        {% set page = viewLink.page %}
        <ul class="pagination {% if page -1 < 1 %} justify-content-end {% endif %}">
          {% if page-1 >= 1 %}
            <li class="page-item">
              <a class="page-link" href="{{ sulu_content_path(content.url) }}?page={{ page-1 }}" aria-label="Previous">
                <span aria-hidden="true">&laquo;</span>
                <span>{{ 'label.previous'|trans|capitalize }}</span>
              </a>
            </li>
          {% endif %}

          {% if viewLink.hasNextPage %}
            <li class="page-item">
              <a class="page-link" href="{{ sulu_content_path(content.url) }}?page={{ page+1 }}" aria-label="Next">
                <span>{{ 'label.next'|trans|capitalize }}</span>
                <span aria-hidden="true">&raquo;</span>
              </a>
            </li>
          {% endif %}
        </ul>
      </nav>
    {% endif %}

  </div>
</section>

Product Page HTML

{% extends "base.html.twig" %}

{% block content %}
  {% set product = content.product %}
  <div class="product-details-wrapper">
    <div class="container">
      <div class="row py-5">
        {% if product.thumbnail %}
          <div class="col-md-6">
            {% set thumbnail = sulu_resolve_media(product.thumbnail, app.request.locale) %}
            <div class="product-thumbnail-wrapper">
              <img class="product-thumbnail" src="{{ thumbnail.formats['default'] }}" alt="{{ product.title }}"
                   title="{{ product.title }}">
            </div>
          </div>
        {% else %}
          <div class="col-md-6">
            <div class="product-thumbnail-wrapper img-thumbnail d-flex align-items-center justify-content-center">
              <p>Geen foto beschikbaar</p>
            </div>
          </div>
        {% endif %}

        <div class="col-md-6 mt-3 d-flex">
          <div class="mx-auto product-info">
            <h1>{{ product.title }}</h1>
            {% if product.code %}
              <p class="text-muted " style="font-size: 0.8rem;"> {{ 'comsa_sulu_shopping_cart.code'|trans }}
                : {{ product.code }}</p>
            {% endif %}

            {% if product.description %}
              {{ product.description|raw }}
            {% else %}
              {{ 'comsa_sulu_shopping_cart.no_description'|trans }}
            {% endif %}

            <div class="products">
              <p class="price">
                <span>€{{ product.price|number_format(2) }}</span>
              </p>
            </div>

            {% if product.followStock %}
              {% if product.stock > 0 %}
                {{ render(controller('Comsa\\SuluShoppingCart\\Controller\\CartController::addToCart', {'uuid': id})) }}
              {% else %}
                <div class="alert alert-danger" role="alert">
                  {{ 'comsa_sulu_shopping_cart.not_available'|trans }}
                </div>
              {% endif %}
            {% else %}
              {{ render(controller('Comsa\\SuluShoppingCart\\Controller\\CartController::addToCart', {'uuid': id})) }}
            {% endif %}
          </div>
        </div>
        {% include 'blocks/all.html.twig' with { 'blocks': content.extendedBlocks } %}
      </div>
    </div>
  </div>
{% endblock %}

Importing Products

Products can be imported with the following command:

comsa:shopping:import-products <inputfile> <parent-page-id> <webspace-key> <locale>

The arguments represent the following:

<tr>
    <th>Argument</th>
    <th>Explanation</th>
    <th>Requirements</th>
</tr>
<tr>
    <td>Inputfile</td>
    <td>The file that is being used to import the products.</td>
    <td>Check chapter "Inputfile Requirements"</td>
</tr>
<tr>
    <td>Parent Page Id</td>
    <td>Uuid of the page where the import will run. All categories will have this page as parent.</td>
    <td>/</td>
</tr>
<tr>
    <td>Webspace key</td>
    <td>Key of the webspace where the pages must bee made</td>
    <td>/</td>
</tr>
<tr>
    <td>Locale</td>
    <td>For which locale must the pages be made?</td>
    <td>/</td>
</tr>

InputFile Requirements

  • File must be an Xlsx file.
  • Inputfile argument must have the full path to file (eg. public/uploads/files/products.xlsx).
  • Products must start on the second row.
  • File must have the following structure:
  • A B C D E F G H
    1 Product Code Category Title Description Price Unit Follow Stock Stock
    2 PRODUCTS START

InputFile Field Requirements

Field Required Requirements
Product Code False Max length: 255
Category True /
Title True Max length: 255
Description False Max length: 65534
Price True Must be in euro (eg. €22,70)
Unit True Must be:
  • kg
  • each
  • pot
  • person
  • jar
  • liter
Follow Stock True Must be "yes" or "no"
Stock False Only integers