package Slovo::Plugin::Prodan;
use feature ':5.26';
use Mojo::Base 'Mojolicious::Plugin', -signatures;
use Mojo::JSON qw(true false);

our $AUTHORITY = 'cpan:BEROV';
our $VERSION   = '0.04';

has app => sub { Slovo->new }, weak => 1;

sub register ($self, $app, $conf) {
  $self->app($app);

  # Prepend class
  unshift @{$app->renderer->classes}, __PACKAGE__;
  unshift @{$app->static->classes},   __PACKAGE__;
  $app->stylesheets('/css/cart.css');
  $app->javascripts('/js/cart.js');
  $app->config->{gdpr_consent_url}
    = $conf->{gdpr_consent_url} || '/ѿносно/условия.bg.html';
  $app->config->{phone_url} = $conf->{phone_url} || '+359899999999';


  # $app->log->debug(join $/, sort keys %INC);
  # $app->debug('Prodan $config', $conf);
  # Set this flag, when we have changes to the tables to be applied.
  $self->_migrate($app, $conf) if $conf->{migrate};

  my $spec = $app->openapi_spec;
  %{$spec->{definitions}} = (%{$spec->{definitions}}, $self->_definitions);
  %{$spec->{paths}}       = (%{$spec->{paths}},       $self->_paths);

  # Add new data_type for celina. Now a corresponding partial template can be
  # used to render the new data_type.
  push @{$spec->{parameters}{data_type}{enum}}, '_gdpr_consent';
  $app->plugin(OpenAPI => {spec => $spec});

  # $app->debug($spec);
  # Generate helpers for instantiating Slovo::Model classes just like
  # Slovo::PLugin::MojoDBx
  for my $t ('poruchki', 'products') {
    my $T     = Mojo::Util::camelize($t);
    my $class = "Slovo::Model::$T";
    $app->load_class($class);
    $app->helper(
      $t => sub ($c) {
        my $m = $class->new(dbx => $c->dbx, c => $c);
        Scalar::Util::weaken $m->{c};
        return $m;
      });
  }

  # configure deliverers
  $self->_configure_deliverers($conf);
  return $self;
}

sub _paths {
  return (
    '/poruchki' => {
      post => {
        description => 'Create a new order',
        'x-mojo-to' => 'poruchki#store',
        parameters  => [{
            required => true,
            in       => 'body',
            name     => 'Poruchka',
            schema   => {'$ref' => '#/definitions/Poruchka'}
          },
        ],
        responses => {
          201 => {
            description => 'Order created successfully!',
            schema      => {'$ref' => '#/definitions/Poruchka'}

          },
          default => {'$ref' => '#/definitions/ErrorResponse'}}}
    },

    '/poruchka/:deliverer/:deliverer_id' => {
      put => {
        description => 'show an order by given :deliverer and :id with that deliverer. ',
        'x-mojo-to' => 'poruchki#show',
        parameters  => [{
            name        => 'deliverer_id',
            in          => 'path',
            description => 'Id of the order in deliverer\'s system',
            type        => 'integer',
            required    => true,

          },
          {
            name        => 'deliverer',
            in          => 'path',
            description => 'A string which denotes the deliverer. Example: "email". ',
            type        => 'string',
            enum        => [qw(email econt)],
            required    => true,
          },
          {
            name        => 'id',
            in          => 'formData',
            description =>
              '"id" property, found in the order structure. Generated by the Econt delivery confirmation form',
            type     => 'string',
            required => true,

          },
        ],
        responses => {
          200 => {
            description =>
              'Show eventually updated order from :deliverer with :deliverer_id',
            schema => {'$ref' => '#/definitions/Poruchka'}

          },
          default => {'$ref' => '#/definitions/ErrorResponse'}}}
    },
    '/shop' => {
      get => {
        description => 'Provides data for the shop',
        'x-mojo-to' => 'poruchki#shop',
        responses   => {default => {'$ref' => '#/definitions/ErrorResponse'}}}
    },
    '/gdpr_consent' => {
      get => {
        description => 'Page URL for GDPR concent',
        'x-mojo-to' => 'poruchki#gdpr_consent',
        responses   => {
          200 => {
            description => 'The URL to the detailed usage conditions, GDPR, cookies.',
            schema      => {'$ref' => '#/definitions/GdprUrl'}
          },
          default => {'$ref' => '#/definitions/ErrorResponse'}}}
    },
  );
}

# Returns description as a perl structure of objects defined for the json API
# to be added to the /definitions of our OpenAPI
sub _definitions {
  return (
    ListOfPoruchki => {
      description => 'An array of Poruchka items.',
      items       => {'$ref' => '#/definitions/Poruchka', type => 'array',}
    },
    Poruchka => {
      properties => {
        deliverer_id => {
          description =>
            'Id of the order as given by Econt, returned with the response to the user-agent(browser)',
          type => 'integer',
        },
        name        => {maxLength => 100, type      => 'string',},
        email       => {maxLength => 100, minLength => 0, type => 'string',},
        phone       => {maxLength => 20,                            type => 'string',},
        deliverer   => {maxLength => 100,                           type => 'string',},
        city_name   => {maxLength => 55,                            type => 'string'},
        address     => {maxLength => 155,                           type => 'string'},
        notes       => {maxLength => 255,                           type => 'string'},
        items       => {'$ref'    => '#/definitions/OrderProducts', type => 'array',},
        way_bill_id => {
          description =>
            'Id at the deliverer site, returned by their system after we created the way-bill at their site.',
          maxLength => 40,
          type      => 'string'
        },
        executed => {
          type        => 'integer',
          minimum     => 0,
          maximum     => 9,
          description => 'Level of execution of the order.0:registered',
        }
      },
    },
    OrderProducts => {
      description => 'An array of OrderProduct items in an order.',
      items       => {
        '$ref' => '#/definitions/OrderProduct',
        type   => 'array',

      }
    },
    OrderProduct => {
      description => 'An item in an order (cart): sku, title, quantity, price',
      properties  => {
        sku      => {maxLength => 40,  type => 'string'},
        title    => {maxLength => 155, type => 'string'},
        quantity => {type      => 'integer'},
        weight   => {type      => 'number'},
        price    => {type      => 'number'},
      }
    },
    GdprUrl => {
      properties => {ihost => {type => 'string'}, url => {type => 'string'}},
      required   => [qw(url ihost)],
    },
  );
}

# Create tables in the database on the very first run if they do not exist.
sub _migrate ($self, $app, $conf) {
  $app->dbx->migrations->migrate;
  $app->dbx->migrations->name('prodan')
    ->from_data(__PACKAGE__, 'resources/data/prodan_migrations.sql')->migrate();
  return $self;
}

# Deliverers are companies which deliver goods to users of our online shop.
# Such delivereres in Bulgaria are Econt, Speedy, Bulgarian Posts and others.
# Currently we integrate only Econt
my sub DELIVERERS {
  return qw(econt);
}

sub _configure_deliverers ($self, $conf) {
  for my $d (DELIVERERS) {
    my $d_sub = '_configure_' . $d;
    $self->$d_sub($conf);
  }
  return;
}

# The keys in the $conf hash reference are named after the examples given at
# http://delivery.econt.com/services/
sub _configure_econt ($self, $conf) {
  my $eco = $conf->{econt};

  # ID на магазина в "Достави с Еконт"
  $eco->{shop_id} //= 'demo';

  # Код за свързване
  $eco->{private_key} //= 'demo';

  # валута на магазина (валута на наложения платеж)
  $eco->{shop_currency} //= 'BGN';

  # URL визуализиращ форма за доставка
  $eco->{shippment_calc_url} //= 'https://delivery-demo.econt.com/customer_info.php';

  # Ендпойнта на услугата за създаване или редактиране на поръчка
  $eco->{crupdate_order_endpoint}
    //= 'https://delivery-demo.econt.com/services/OrdersService.updateOrder.json';

  # Ендпойнта на услугата за създаване или редактиране на товарителница
  $eco->{create_awb_endpoint}
    //= 'https://delivery-demo.econt.com/services/OrdersService.createAWB.json';

  # $self->app->debug($conf);
  $self->app->config->{shop} = $eco;
  return;
}

1;


=encoding utf8

=head1 NAME

Slovo::Plugin::Prodan - Make and manage sales in your Slovo-based site

=head1 SYNOPSIS

  # In slovo.conf
  load_plugins => [
    #...
    'Themes::Malka',
    {
      Prodan => {
        migrate          => 1,
        gdpr_consent_url => '/ѿносно/условия.bg.html',
        econt            => {
          shop_id                 => $ENV{SLOVO_PRODAN_SHOP_ID},
          private_key             => $ENV{SLOVO_PRODAN_PRIVATE_KEY},
          shippment_calc_url      => 'https://delivery.econt.com/customer_info.php',
          crupdate_order_endpoint =>
            'https://delivery.econt.com/services/OrdersService.updateOrder.json',
          create_awb_endpoint =>
            'https://delivery.econt.com/services/OrdersService.createAWB.json'
        }}
    },
    #...
  ],

=head1 DESCRIPTION

The word про̀дан (прода̀жба) in Bulgarian means sale. Roots are found in Old
Common Slavic (Old Bulgarian) I<проданьѥ>. Here is an exerpt from Codex
Suprasliensis(331.27) where this word was witnessed: I<сꙑнъ божии. вол҄еѭ
на сьпасьнѫѭ страсть съ вами придетъ. и на B<продании> станетъ.
искѹпѹѭштааго животворьноѭ кръвьѭ. своеѭ миръ.>

L<Slovo::Plugin::Prodan> is a L<Mojolicious::Plugin> that extends a
Slovo-based site and turns it into an online shop. 

=head1 FEATURES

In this edition of L<Slovo::Plugin::Prodan> we implement the following features:

=head2 A Shopping cart

A jQuery and localStorage based shopping cart. Two static files contain
the implementation and they can be inflated. The files are
C</css/cart.css> and C</js/cart.js>. You should inflate these files into
your public forlder C<domove/example.com/public> for the domain on which you
will use it. Even not inflated these will be referred from any page of the
site. The site layout C<layouts/site.html.ep> includes automatically these
two static files if this plugin is loaded.

  # Inflate new static files from Slovo::Plugin::Prodan
  bin/slovo inflate --class Slovo::Plugin::Prodan -p --path domove/xn--b1arjbl.xn--90ae/public

To add a product to your cart and make an order, you need a button, containing
the product data. For example:

    <button class="primary sharer button add-to-cart"
        title="книжно издание" data-sku="9786199169001" 
        data-title="Житие на света Петка Българска от свети патриарх Евтимий"
        data-weight="0.5" data-price="7.00"><img
        src="/css/malka/book-open-page-variant-outline.svg">
        <img src="/img/cart-plus-white.svg"></button>

See "A template..." below.

=head2 Delivery of sold goods

A "Pay on delivery" integration with Bulgarian currier L<Econt (in
Bulgarian)|https://www.econt.com/developers/43-kakvo-e-dostavi-s-ekont.html>.

=head2 Products

Products - a products SQL table to populate your pages with products. You
create a page with several articles (celini) in it. These celini will be the
pages for the products. You prepare a YAML file with products. Each product
C<alias> property must match exactly the celina C<alias> and C<data_type>
on wich this product  will be placed. See C<t/products.yaml>
and C<t/update_products.yaml> for examples. See
L<Slovo::Command::prodan::products> on how to add and update products.

=head2 Products template

A template for displaying products within a C<celina>. You can modify this
template as you wish to display other types of products - not just books as it
is now. See C<partials/_kniga.html.ep> inlined in this file's C<__DATA__>
section. It of course can be inflated using
L<Slovo::Command::Author::inflate>. The template produces the HTML from the
products table, including the button mentioned above already.

  # Add the template form Prodan
  bin/slovo inflate --class Slovo::Plugin::Prodan \
    -t --path domove/xn--b1arjbl.xn--90ae/templates/themes/malka


=head2 GDPR and Cookies consent

A GDPR and cookies consent alert in the footer which upon click leads to the
page (celina) where all conditions on using the site can be described. When the
user clicks on the link to the I<Consent> page a flag in C<localStorage> is put
so the alert is not shown any more. This flag disappears if the user clears any
site data and the alert will appear again if the user vists the  site again.
The Consent celina is created automatically in the localhost domain as an
example. Search for C<gdpr_consent> in the source of this module to see how it
is implemented.

=head2 TODO some day

=over 1

=item Invoices - generate an invoice in PDF via headless LibreOffice instance
on your server.

=item Merchants - a merchants SQL table with Open API to manage and
automatically populate invoices.

=item Other "Pay on Delivery" providers. Feel free to contibute yours. 

=item Other types of Payments and/or Online Payment Providers like online POS
Terminals etc.

=back

=head1 METHODS

The usual method is implemented.

=head2 register

Prepends the class to renderer and static classes. Adds some REST API routes,
configures the deliverer.

=head1 EMBEDDED FILES

    @@ css/cart.css
    @@ js/cart.js
    @@ img/arrow-collapse-all.svg
    @@ img/cart-arrow-right.svg
    @@ img/cart.svg
    @@ img/cart-check.svg
    @@ img/cart-off.svg
    @@ img/cart-minus.svg
    @@ img/cart-plus-white.svg
    @@ img/cart-plus.svg
    @@ img/cart-remove.svg
    @@ img/econt.svg
    @@ partials/_footer_right.html.ep
    @@ partials/_gdpr_consent.html.ep
    @@ partials/_kniga.html.ep
    @@ resources/data/prodan_migrations.sql

=head1 SEE ALSO

L<Slovo::Command::prodan::products>,
L<Slovo>,
L<Mojolicious::Guides::Tutorial/Stash and templates>,
L<Mojolicious/renderer>,
L<Mojolicious::Renderer>,
L<Mojolicious::Guides::Rendering/Bundling assets with plugins>,
L<Slovo::Command::Author::inflate>

=head1 AUTHOR

    Красимир Беров
    CPAN ID: BEROV
    berov на cpan точка org
    http://слово.бг

=head1 CONTRIBUTORS

Ordered by time of first commit.

=over

=item * Your Name

=item * Someone Else

=item * Another Contributor

=back

=head1 COPYRIGHT

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)

The full text of the license can be found in the
LICENSE file included with this module.

This distribution contains icons from L<https://materialdesignicons.com/> and
may contain other free software which belongs to their respective authors.

=cut

__DATA__

@@ css/cart.css
/* cd ~/opt/dev/Slovo && beautify-css -p  domove/xn--b1arjbl.xn--90ae/public/css/cart.css */

@charset "utf-8";

#show_cart {
  background-color: var(--bg-color);
}

#cart_widget {
  padding: 0;
  z-index: 2;
  background-color: var(--bg-color);
  color: black;
  /* 'position' cannot be fixed, because it must be slidable in case there
     * are more products and the bottom of the table is not visible on the
     * screen. */
  position: absolute;
  top: 8rem;
  left: 0;
}

#cart_widget>h3 {
  margin: 0;
}

#cart_widget>button:nth-child(1) {
  float: right;
}

#cart_widget>table {
  position: relative;
}

#cart_widget>table>tfoot th:nth-last-child(1),
#cart_widget>table>tbody td:nth-last-child(1) {
  max-width: 13rem;
  /* white-space: nowrap; */
  text-align: center;
}

#last_order_items tfoot th:last-child,
#last_order_items tfoot td:last-child,
#last_order_items tbody th:last-child,
#last_order_items tbody td:last-child,
#cart_widget>table>thead th:nth-last-child(2),
#cart_widget>table>tfoot th:nth-last-child(3),
#cart_widget>table>tbody td:nth-last-child(2) {
  max-width: 5rem;
  white-space: nowrap;
  text-align: end;
}

#econt_order_items>thead th:nth-last-child(3),
#econt_order_items>tfoot th:nth-last-child(3),
#econt_order_items>tbody td:nth-last-child(3),
#cart_widget>table>thead th:nth-last-child(3),
#cart_widget>table>tfoot th:nth-last-child(3),
#cart_widget>table>tbody td:nth-last-child(3) {
  max-width: 7rem;
  white-space: nowrap;
  text-align: end;
}

#econt_order_items>tbody th:first-child,
#cart_widget>table>tfoot th:nth-last-child(4),
#cart_widget>table>tbody td:nth-last-child(4) {
  max-width: 40rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#cart_widget>table>tfoot th:nth-last-child(4) {
  text-align: right;
}

button.product,
.button.product,
.button.cart,
button.cart {
  font-family: FreeSans, sans-serif;
  color: var(--color-success);
  font-weight: bolder;
  border-radius: 4px;
  font-size: small;
  padding: .2rem .2em;
}

#cart_widget tr:nth-child(even) td {
  background-color: var(--color-lightGrey);
}

#cart_widget tr:nth-child(odd) td {
  background-color: white;
}

img.outline {
  color: var(--color-primary);
  border: 1px solid var(--color-primary);
  border-radius: 4px;
  cursor: pointer;
}

#econt_order_layer,
#email_order_layer,
#last_order_layer {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  z-index: 3;
  background-color: rgba(250, 250, 250, 0.80);
}

#last_order_items div {
  font-family: sans-serif;
}

/*
*/

#order_help h5,
#order_help h6 {
  margin: 0.35rem 0 0 0;
}

#email_order_form input:invalid {
  border: red solid 2px;
}

#order_help p {
  font-family: Veleka, serif;
}

#order_help {
  z-index: 4;
  position: absolute;
  top: 0;
  left: 30%;
  right: 30%;
}

#last_order_table div.col:first-child {
  font-weight: bolder;
}

#last_order_table div.col:nth-child(2) {
  font-weight: normal;
  font-family: sans-serif;
}

/*econt form iframe*/

iframe#econt_shipment {
  width: 100%;
  height: 600px;
}

#econt_order_layer .card {
  padding: 1rem;
}

#econt_order_items th,
#econt_order_items td,
#cart_widget th,
#cart_widget td,
#last_order_layer th,
#last_order_layer td {
  padding: 0.5rem !important;
  line-height: 85%;
}

@media (max-width: 700px) {
  /* .plus, .minus,.remove images as buttons */

  #cart_widget img.outline {
    width: 32px;
  }

  #econt_order_items th:nth-last-child(1),
  #econt_order_items td:nth-last-child(1),
  #econt_order_items tbody th:nth-last-child(2),
  #econt_order_items tbody td:nth-last-child(2),
  #econt_order_items tbody th:nth-last-child(3),
  #econt_order_items tbody td:nth-last-child(3),
  #last_order_items tfoot th:last-child,
  #last_order_items tfoot td:last-child,
  #cart_widget>table>tfoot th:nth-last-child(1),
  #cart_widget>table>tbody td:nth-last-child(1) {
    max-width: 5rem;
    text-align: end;
  }

  #econt_order_items>tbody th:first-child,
  #last_order_items th:first-child,
  #econt_order_items th:first-child,
  #econt_order_items td:first-child,
  #last_order_items td:nth-last-child(1),
  #cart_widget>table>tfoot th:nth-last-child(4),
  #cart_widget>table>tbody td:nth-last-child(4) {
    max-width: 10rem;
  }

  .remove {
    display: none;
  }

  #order_help {
    top: 0;
    left: 0;
    right: 0;
  }
}

.button.primary.sharer {
  font-size: 80%;
}

/* end @media (max-width: 700px) */

/* books (products) */

section.book figure {
  margin: 1rem;
  clear: both;
}

section.book figure>img {
  max-width: 200px;
  max-height: 288px;
  border: 1px solid black;
}

section.book figure>figcaption {
  position: relative;
  bottom: 0;
}

section.book table#meta {
  margin-bottom: 2rem;
  display: inline-table;
  max-width: 75%;
  min-width: 50%;
}

section.book table#meta th {
  max-width: 15rem;
}

section.book table#meta th,
table#meta td {
  vertical-align: top;
  border-bottom: 1px solid #ddd;
  padding: 0.4rem 0.4rem;
}

.button.primary.sharer {
  font-weight: bolder;
  border-radius: 4px;
  padding: .1rem .5rem;
  display: inline-block;
  vertical-align: text-top;
}

.social {
  white-space: nowrap;
}

section.book h2 {
  margin: 0;
}

/* end books (products) */

@@ js/cart.js
/* An unobtrusive shopping cart based on localStorage
 * Formatted with `js-beautify -j -r -f domove/xn--b1arjbl.xn--90ae/public/js/cart.js`
 */
jQuery(function ($) {
    'use strict';
    // cart will go finally to order.items
    let cart = localStorage.cart ? JSON.parse(localStorage.cart) : {};
    let order = localStorage.order ? JSON.parse(localStorage.order) : {};
    const deliverers = {
        econt: 'Еконт',
        email: 'Е-Поща'
    };
    const currency = {
        BGN: 'лв.',
        EUR: 'евро'
    };

    const cart_widget_template = `
<div id="cart_widget" class="card text-center">
<button class="button primary outline icon cart" title="Показване/Скриване на поръчката"
    id="show_cart"><span class="order_total"></span><img src="/img/cart.svg" width="32" height="32" /></button>
<h3 style="display:none">Поръчка</h3>
<table style="display:none">
<thead><tr><th>Изделие</th><th>Ед. цена</th><th>Бр.</th><th><!-- action --></th></tr></thead>
<tbody><!-- Here will be the order items --></tbody>
<tfoot>
<tr><th>Тегло (кг.)</th><th class="order_weight"></th><th></th><th></th></tr>
<tr><th>Общо (лв.)</th><th class="order_total"></th>
<th>
    <button class="button primary outline icon cart pull-left"
        title="Отказ от поръчката" id="cancel_order"><img 
            src="/img/cart-off.svg" width="32" /></button>
</th>
<th>
    <button class="button primary primary icon cart"
    title="Купувам (Доставка с Еконт)" id="econt_order"><img
        src="/img/cart-check.svg" width="32" /></button><!--
   <button class="button primary icon cart"
        title="Купувам" id="email_order"><img
            src="/img/cart-check.svg" width="32" /></button> -->
</th>
</tr>
<tr title="* При поръчка над 35 лв. до офис на Еконт или Еконтомат, доставката е за наша сметка.">
<th colspan="4">* При поръчка над 35 лв. до офис на Еконт или Еконтомат, доставката е за наша сметка.</th>
</tfoot>
</table>
</div>
`;

    const email_order_template = `
<div id="email_order_layer" style="display:none">
<form id="email_order_form" method="POST" action="/api/poruchki" class="container">
<fieldset class="card">
<legend data-description="Повечето от полетата, които попълвате тук, съдържат лични данни. Нужно е да ги предоставите, за да поръчате."
    >Поръчка</legend>
<button class="button outline icon cart pull-right" title="Скриване на формуляра"
    id="hide_email_order"><span class="order_total"></span><img src="/img/arrow-collapse-all.svg" width="32" /></button>
<button class="button outline icon cart pull-right" title="Пояснения"
    id="help_email_order"><img src="/css/malka/help-circle-outline.svg" width="32" /></button>

<label for="recipient_names">Получател</label>
<input type="text" name="recipient_names" placeholder="Иванка Петканова" required="required" maxlength="100"
title="Собствено и родово име или име на фирма, получател."/>
<label for="email">E-поща</label>
<input type="email" name="email" placeholder="ivanka@primer.com" required="required" maxlength="100"
title="Адрес, на който ще получите уведомление, когато предадем пратката на доставчика. При закупуване на невеществени изделия (електронни книги, софтуер…) на този адрес ще получите връзка за изтегляне на файла."/>
<label for="phone">Телефон</label>
<input type="tel" name="phone" placeholder="0891234567" required="required" maxlength="20"
title="Телефонен номер, на който ще бъдете известени от доставчика, когато пратката ви пристигне."/>
<label for="deliverer">Предпочитан доставчик</label>
<select name="deliverer" title="Изберете кой от доставчиците, с които работим, предпочитате. При закупуване на невеществени изделия, ще изпратим връзка за изтегляне и банкова сметка, на която да преведете сумата по поръчката.">
    <option value="email">Е-поща (за електронни издания и софтуер)</option>
    <option value="econt">Еконт</option>
</select>
<label for="address">Адрес за получаване</label>
<input type="text" name="address" placeholder="п.к. 3210 с. Горно Нанадолнище, ул. „Цветуша“ №123" maxlength="155"
title="Вашият точен адрес за получаване на пратката или адрес на офис на избрания доставчик. При закупуване на невеществени изделия и услуги това поле не се попълва." />
<label for="notes">Допълнителни бележки</label>
<textarea name="notes" rows="2" maxlength="255"
title="Ако желаете да добавите някакви подробности и уточнения, въведете ги в това поле."></textarea>
<input type="hidden" name="items" value="{}"/>
<footer class="is-right" style="margin-top:1rem">
    <button type="reset" class="secondary outline button icon-only"
        class="reset_order_form" title="изчистване"><img src="/css/malka/card-bulleted-off-outline.svg" width="32" /></button>
    &nbsp;&nbsp;<button class="primary button icon-only" type="submit"
        title="Поръчвам"><img src="/img/cart-check.svg" width="32" /></button>
</footer>
</fieldset>
</form>
</div>
`;
    const econt_order_template = `
<div id="econt_order_layer" style="display:none">
<div  class="container card">
<form id="econt_order_form" method="POST" action="/api/poruchki">
        <button class="button outline icon cart pull-right" title="Скриване на формуляра"
            id="hide_econt_order"><img 
                src="/img/arrow-collapse-all.svg" width="32" /></button>
    <h4><img src="/img/econt.svg" width="24" />Доставка с Еконт</h4>

    <!-- В това поле ще се запази уникален индентификатор на адреса за доставка.
    Попълва се от JavaScript функцията която 'слуша' съобщенията от формата за
    доставка -->
    <input type="hidden" name="customerInfo[id]">

    <input type="hidden" name="cod" value="Да" readonly="" />
        <table id="econt_order_items">
            <cation class="text-center">Изделия</caption>
            <tbody></tbody>
            <tfoot></tfoot>
        </table>
</form>
    <!-- ФОРМА ЗА ДОСТАВКА -->
    <fieldset>
        <legend>Данни за доставка</legend>
        <p>Полетата, които попълвате тук,
        съдържат лични данни. Предоставяте ги на Еконт Експрес ООД единствено за извършване на доставката.</p>
    <iframe id="econt_shipment" src=""></iframe>
    </fieldset>
</div>
</div>
`;
    const gdpr_consent_template = `
    <div id="gdpr_consent" class="col" style="font-size:medium;font-family:sans-serif">
    Ние не ви следим. Доставчикът ни ползва бисквитки, когато поръчвате.
    Продължавайки се съгласявате с <a class="gdpr_consent_url text-success" href="">„Условията за ползване"</a>
    на <a id="gdpr_consent_ihost" href="/" class="text-success"></a>.
    </div>
`;
    const last_order_template = `
<div id="last_order_layer" style="display:none">
    <div class="container card">
        <button class="button outline icon cart pull-right hide_last_order"
        title="Скриване"><span class="order_total"></span><img src="/img/arrow-collapse-all.svg" width="32" /></button>
    <p>Вашата поръчка е приета. Ще бъдете уведомени своевременно от превозвача, когато вашата пратка пристигне.</p>
        <section id="last_order_table">
            <div class="row"><div class="col">Поръчка №:</div><div class="col" id="deliverer_id"></div></div>
            <div class="row"><div class="col">Получател:</div><div class="col" id="name"></div></div>
            <div class="row"><div class="col">E-поща:</div><div class="col" id="email"></div></div>
            <div class="row"><div class="col">Телефон:</div><div class="col" id="phone"></div></div>
            <div class="row"><div class="col">Доставчик:</div><div class="col" id="deliverer"></div></div>
            <div class="row"><div class="col">Адрес за получаване:</div>
                <div class="col"><span id="city_name"></span><span id="address"></span></div>
            </div>
            <div class="row"><div class="col">Товарителница:</div><div class="col" id="way_bill_id"></div></div>
            <!-- <div class="row"><div class="col">Допълнителни бележки:</div><div class="col" id="notes"></div></div> -->
        <table id="last_order_items">
            <cation class="text-center">Изделия</caption>
            <tbody></tbody>
            <tfoot></tfoot>
        </table>
  <footer class="is-center">
    <button class="button primary hide_last_order">Добре</button>
  </footer>
    </div>
</div>
    `;

    show_gdpr_consent();
    show_cart();
    /* In a regular page we present a product(book, software package,
     * whatever). On the page there is one or more buttons(one per product)
     * (.add-to-cart) in which data-attributes are stored all the properties
     * of the product. Clicking on the button adds the product to the card.
     * */
    $('.add-to-cart').click(add_to_cart);

    function add_to_cart() {
        let product = $(this).data();
        let product_id = '_' + product.sku;
        if (product_id in cart) {
            ++cart[product_id].quantity;
        } else {
            cart[product_id] = {
                sku: product.sku,
                title: product.title,
                quantity: 1,
                weight: product.weight,
                price: product.price
            };
        }
        // display the cart
        show_cart();
        // expand it
        $('#show_cart').trigger('click');
    }

    function cancel_order() {
        console.log('#cancel_order');
        localStorage.removeItem('cart');
        cart = {};
        $('#cart_widget').remove();
    }

    function show_cart() {
        show_last_order_button();
        // there is nothing to show? return.
        if (!Object.keys(cart).length) return;

        // Store the changed cart!!!
        localStorage.setItem('cart', JSON.stringify(cart));
        let cart_widget = $('#cart_widget');
        //if not yet in dom, create it
        if (!cart_widget.length) {
            $('body').append(cart_widget_template);
            //populate the table>tbody with the contents of the cart
            $(Object.keys(cart)).each(populate_order_table);
            // make the cart button to toggle the visibility of the products table
            $('#show_cart').click(toggle_order_table_visibility);
            // append the email_order_form
            // if (!$('#email_order_layer').length)
            //     $('body').append(email_order_template);
            // append the econt_order_template
            if (!$('#econt_order_layer').length)
                $('body').append(econt_order_template);
        }
        //else update it
        else {
            repopulate_order_table();
        }
        // calculate the sums of the order from the items and rules in the cart
        let sum = 0;
        let weight = 0;
        Object.keys(cart).forEach((key) => {
            weight += cart[key].weight * cart[key].quantity;
            sum += cart[key].price * cart[key].quantity;
        });
        /*
        const delivery_price = 4.00; // in he shop currency. Todo: make this a configuration value of the shop.
        const free_delivery_sum = 35;
        // VAT and delivery_price are included in the price if total sum < free_delivery_sum
        if (sum > free_delivery_sum)
            $('th.delivery_price').text(0.00.toFixed(2));
        else
            $('th.delivery_price').text(delivery_price.toFixed(2));
        */
        $('.order_total').html(sum.toFixed(2));
        $('.order_weight').html(weight.toFixed(3));

        $('#cancel_order').click(cancel_order);
        $('#econt_order').click(show_econt_order);
        // Display an order form and handle data collection from the user 
        //$('#email_order').click(show_email_order);
    } // end function show_cart()

    function populate_order_table() {
        let this_id = this;
        let ow_jq = '#cart_widget>table>tbody';
        $(ow_jq).append(`
                  <tr id="${this_id}">
                    <td title="${cart[this_id].title}">${cart[this].title}</td>
                    <td>${cart[this_id].price}</td>
                    <td>${cart[this_id].quantity} бр.</td>
                    <td>
                      <img class="outline minus" title="Премахване на един брой" src="/img/cart-minus.svg" width="32" />
                      <img class="outline plus" title="Добавяне на един брой" src="/img/cart-plus.svg" width="32" />
                      <img class="outline remove" title="Без това изделие" src="/img/cart-remove.svg" width="32" />
                    </td>
                  </tr>`);
        //Add functionality to plus,minus and remove
        $(`${ow_jq} tr#${this_id} .minus`).click(function () {
            if (cart[this_id].quantity == 1) {
                remove_item();
                return;
            }
            --cart[this_id].quantity;
            localStorage.removeItem('cart');
            show_cart();
        });
        $(`${ow_jq} tr#${this_id} .plus`).click(function () {
            ++cart[this_id].quantity;
            localStorage.removeItem('cart');
            show_cart();
        });
        $(`${ow_jq} tr#${this_id} .remove`).click(remove_item);

        function remove_item() {
            if (Object.keys(cart).length == 1) {
                $('#cancel_order').trigger('click');
                return;
            }
            delete cart[this_id];
            localStorage.removeItem('cart');
            show_cart();
        }

    } // end function populate_order_table() 

    function repopulate_order_table() {
        $('#cart_widget>table>tbody').empty();
        $(Object.keys(cart)).each(populate_order_table);
    }

    function toggle_order_table_visibility() {
        let order_button_icon = $('#cart_widget #show_cart>img');
        if (order_button_icon.attr('src').match(/cart/))
            order_button_icon.attr('src', '/img/arrow-collapse-all.svg');
        else
            order_button_icon.attr('src', '/img/cart.svg');
        $('#cart_widget>h3,#cart_widget>table').toggle();
    }

    function show_econt_order() {
        $('#econt_order_layer').show();
        let hide = $('#hide_econt_order');
        hide.off('click');
        hide.click(function (e) {
            $('#econt_order_layer').hide();
            e.preventDefault();
        });
        get_set_shop_data();

    } // end function show_econt_order()

    function get_set_shop_data() {
        let shop_data = localStorage.shop_data ? JSON.parse(localStorage.shop_data) : {
            retrieved: 1
        };
        let now = parseInt(Date.now() / 1000); // get seconds since 1970
        let eco_shipment = $('#econt_shipment');
        let items = [];
        Object.keys(cart).forEach((key) => items.push(cart[key]));
        inline_order_items('#econt_order_items', items);
        order.items = items;
        // Get fresh shop_data not older than 2 hours and do not get shop data
        // on each display of order checkout, because shop_data may change on the server.
        if ((now - shop_data.retrieved) < 3600 * 2) {
            if (!eco_shipment.prop('src').match(new RegExp(shop_data.shop_id))) {
                eco_shipment.prop('src', prepare_shipment_url(shop_data));
            }
        } else {
            $.get('/api/shop').done(function (data) {
                data.retrieved = now;
                localStorage.setItem('shop_data', JSON.stringify(data));
                eco_shipment.prop('src', prepare_shipment_url(data));
            }).fail(function (jqXHR, textStatus, errorThrown) {
                console.log(jqXHR, textStatus, errorThrown);
                alert(
                    'Състояние:' + textStatus +
                    'ГРЕШКА: \n' + errorThrown);
            });
        }
        handle_econt_order_form();

    }

    // https://www.econt.com/developers/44-implementirane-na-dostavi-s-ekont-v-elektronniya-vi-magazin.html
    function prepare_shipment_url(shop) {
        const country_code = 1033; // Bulgaria
        let shipment = {
            // Get user data from localStorage from previous time if it exists
            customer_company: order.name,
            customer_name: order.face,
            customer_phone: order.phone,
            customer_email: order.email,
            customer_country: (order.id_country ? order.id_country : country_code),
            customer_post_code: order.post_code,
            customer_office_code: order.office_code,
            customer_address: order.address,
            id_shop: shop.shop_id,
            order_currency: shop.shop_currency,
            order_total: parseFloat($('#cart_widget table>tfoot .order_total').text()),
            order_weight: parseFloat($('#cart_widget table>tfoot .order_weight').text()),
            customer_address: order.address,
            ignore_history: 0,
            confirm_txt: 'Потвърждавам'
        };
        let params = $.param(shipment, true);
        return `${shop.shippment_calc_url}?${params}`;
    }

    //2.3. JavaScript функця, която получава резултата от формата за доставка:
    function handle_econt_order_form() {
        window.addEventListener('message', handle_econt_order_confirm, false);
    }
    /* Added as EventListener to econt iframe in which is placed the "Deliver
       with Econt" confirmation form */
    function handle_econt_order_confirm(message) {
        // Елемент от кода, където е указано дали стоката ще се заплаща с НП или не
        let codInput = document.getElementsByName('cod')[0];
        //Елемент от формата в който ще се постави уникалното ID на доставката
        let customerInfoIdInput = document.getElementsByName('customerInfo[id]')[0];
        //Формата в която се съдържат данните по поръчката в магазина и същата трябва да се подаде
        let econt_order_form = document.getElementById('econt_order_form');
        // добавяне на функция, която 'слуша' данни връщани от формите за доставка
        // Данни връщани от формата за доставка:
        // id: уникално ID на адреса. Това поле трябва да бъде поставено в скритото customerInfo[id]
        // id_country: ID на държавата
        // zip: зип код на населеното място
        // post_code: пощенски код на населеното място
        // city_name: населено място
        // office_code: код на офиса на Еконт ако бъде избран такъв
        // address: адрес
        // name: име / фирма
        // face: лице
        // phone: телефон
        // email: имейл
        // shipping_price: цена на пратката без НП
        // shipping_price_cod: цена на пратката с НП
        // shipping_price_currency: валута на калкулираната цена
        // shipment_error: поясняващ текст ако е възникнала грешка

        let data = message['data'];
        if (data.office_code) {
            delete data.num;
            data.address = data.office_name;
            delete data.street;
            delete data.other;
            delete data.quarter;
            // todo: get the office address somehow (via some API call) to show it to the user
            // data.address = $(`option[value=${data.office_code}]`, document.getElementById('econt_shipment').contentWindow.document).text();
        }

        order = {
            ...data,
            items: order.items,
            deliverer: 'econt',
            sum: parseFloat($('#cart_widget table>tfoot .order_total').text()),
            weight: parseFloat($('#cart_widget table>tfoot .order_weight').text())
        };

        //localStorage.setItem('order', JSON.stringify(order));

        // възможно е да възникнат грешки при неправилно конфигурирани настройки на електронния магазин, които пречат за калкулацията
        if (data['shipment_error'] && data['shipment_error'] !== '') {
            alert('Възникна грешка при изчисляване на стройноста на пратката.\n' +
                data['shipment_error'] +
                '\nМолим пишете ни на otzivi@studio-berov.eu,' +
                ' като приложите грешката към писмото и ние ще направим всичко възможно да я отстраним.');
        }
        // формата за изчисляване връща цена с НП и такава без
        // спрямо избора на клиента в "Заплащане чрез НП" показваме правилната цена
        let shippmentPrice;

        if (codInput) shippmentPrice = parseFloat(data['shipping_price_cod']);
        else shippmentPrice = parseFloat(data['shipping_price']);
        $('#econt_order_items tfoot>tr:last-child>td').replaceWith(`${shippmentPrice.toFixed(2)} ${currency[data['shipping_price_currency']]}`);
        let confirmMessage =
            `Куриерската ви услуга е на стройност ${shippmentPrice} ${currency[data['shipping_price_currency']]}
Наложеният платеж е на стойност ${order.sum} ${currency[data['shipping_price_currency']]}
Общо: ${shippmentPrice + order.sum} ${currency[data['shipping_price_currency']]}
Потвърждавате ли покупката?`;
        if (confirm(confirmMessage)) {
            //customerInfoIdInput.value = data['id'];
            // confirmForm.submit();
            // Prevent this event to fire twice or more times after first confirmation
            window.removeEventListener("message", handle_econt_order_confirm, false);
            place_econt_order(econt_order_form)
        }
    }

    /* Inline order items into econt_order_template or last_order_template */
    function inline_order_items(table_id, items) {
        let tbody = $(`${table_id} tbody`);
        let tfoot = $(`${table_id} tfoot`);
        tbody.empty();
        tfoot.empty();
        let sum = 0;
        let weight = 0;
        for (const it of items) {
            let item_sum = it.price * it.quantity;
            sum += item_sum;
            weight += it.weight * it.quantity;
            tbody.append(`
                  <tr>
                    <th title="${it.title}">${it.title}</th><td>${it.price}</td><td>${it.quantity} бр.</td><td>${item_sum.toFixed(2)}</td>
                  </tr>
            `);
        }
        tfoot.append(`<tr><th colspan="3">Общо</th><td>${sum.toFixed(2)} лв.</td></tr>`);
        tfoot.append(`<tr><th colspan="3">Тегло</th><td>${weight.toFixed(3)} кг.</td></tr>`);
        tfoot.append(`<tr><th colspan="3">Доставка</th><td></td></tr>`);
    }
    /*
     * 3. Генериране/редактиране на поръчка към "Достави с Еконт" http://delivery.econt.com/services/
     * Услугата може да бъде извикана в следните случаи:
     * - При завършване на поръчката от клиента;
     * - При редактиране на поръчката от търговеца в рамките на административния си панел; 
     */
    // Приключване на поръчката.
    function place_econt_order(form) {
        let req = $.ajax({
            url: $(form).prop('action'),
            method: 'POST',
            data: JSON.stringify({
                Poruchka: order
            }),
            dataType: 'json'
        });
        // Prevent further firing of econt iframe eventListener
        $('#econt_order_layer').remove();

        req.done(function (data) {
            // store the order for showing later too
            localStorage.setItem('order', JSON.stringify(data));
            order = data; // last order
            delete_cart();
            show_last_order(data);
        });

        req.fail(function (jqXHR, textStatus, errorThrown) {
            let json = JSON.parse(jqXHR.responseText);
            alert(`
Състояние: ${jqXHR.status} ${errorThrown}
ГРЕШКА: ${json.errors[0].path}
${json.errors[0].message}
Нещо се обърка на сървъра.
`);
        });
    } // end place_econt_order(form)

    /**
     * Displays the just made or last order to the user 
     */
    function show_last_order(o_d) {
        let now = parseInt(Date.now() / 1000); // get seconds since 1970
        // Get fresh o_d not older than 2 hours, because order_data may change
        // on the server and do not get order data on each display.
        if ((now - o_d.tstamp) > 3600 * 2) {
            let req = $.ajax({
                url: `/api/poruchka/${o_d.deliverer}/${o_d.deliverer_id}`,
                method: 'PUT',
                data: {
                    id: o_d.id
                },
                dataType: 'json'
            });
            req.done(function (data) {
                localStorage.setItem('order', JSON.stringify(data));
                o_d = order = data;
            });
            req.fail(function (jqXHR, textStatus, errorThrown) {
                console.log(jqXHR, textStatus, errorThrown);
                alert(
                    'Състояние:' + textStatus +
                    'ГРЕШКА: \n' + errorThrown);
            });
        }
        let lol = '#last_order_layer';
        $(lol).remove();
        $('body').append(last_order_template);
        let delivery = ['deliverer_id', 'name', 'email', 'phone', 'deliverer', 'office_name', 'address'];
        let lot = '#last_order_table';
        for (const k of delivery) {
            if (o_d[k])
                k == 'deliverer' ? $(`${lot} #${k}`).text(deliverers[o_d[k]]) : $(`${lot} #${k}`).text(o_d[k]);
        }

        if (o_d.way_bill_id)
            $(`${lot} #way_bill_id`)
            .html(`<a target="_blank" href="https://www.econt.com/services/track-shipment/${o_d.way_bill_id}">${o_d.way_bill_id}</a>`);

        inline_order_items('#last_order_items', o_d.items);
        $('#last_order_items tfoot tr:last-child td:last-child')
            .text(`${o_d.shipping_price_cod} ${currency[o_d.shipping_price_currency]}`);

        /* if (o_d.email)
            $(`${lol} p:first-child`).append(`На електронната ви поща ще изпратим номера на
                    товарителницата, с който можете да проследите пратката.`); */
        $(lol).show();
        $('.hide_last_order').click(function (e) {
            $(lol).hide();
            e.preventDefault();
        });
    } // end function show_last_order

    //Disabled for now. Will be shown when the order implementation is ready.
    function show_last_order_button() {
        //return;
        if (!order.deliverer) return;
        if ($('#last_order_button').length) return;

        $('nav.nav-center').append(
            `&nbsp;<button class="button primary outline icon sharer" title="Вашата последна поръчка"
            id="last_order_button"><img src="/img/cart-arrow-right.svg" width="24"></button>`);
        $('#last_order_button').click(function () {
            show_last_order(order)
        });
    }

    /**
     * Delete the just made by the user order.
     */
    function delete_cart() {
        console.log('#delete_made_order');
        localStorage.removeItem('cart');
        cart = {};
        $('#cart_widget').remove();
    }

    /**
     * Show in the footer a line stating that we do not use cookies except for...
     * */
    function show_gdpr_consent() {
        const gdpr_consent = JSON.parse(localStorage.getItem('gdpr_consent'));
        if (gdpr_consent !== null && gdpr_consent.visited === true) return;
        if (gdpr_consent === null)
            $.get('/api/gdpr_consent').done(function (data) {
                set_gdpr_consent(data);
            }).fail(function (jqXHR, textStatus, errorThrown) {
                console.log(jqXHR, textStatus, errorThrown);
            });
        else
            set_gdpr_consent(gdpr_consent);
    } // end show_gdpr_consent()

    function set_gdpr_consent(gdpr_consent) {
        // Do not show the message and store the consent if we are on the
        // gdpr_consent.url page.
        let footer = $('body>footer.is-fixed')
        if (decodeURI(window.location.pathname) === gdpr_consent.url) {
            gdpr_consent.visited = true;
            localStorage.setItem('gdpr_consent', JSON.stringify(gdpr_consent));
            // A button, that gets the user to where he/she was.
            // Note! It is important to not use the window.history object so
            // the page loads fresh with no gdpr_consent message.
            footer.html(`<a href="${document.referrer}"
                title="Назад, откъдето дойдох."
                class="col outline button text-success">Добре!</a>`);
            return;
        }
        footer.html(gdpr_consent_template);
        $('#gdpr_consent_ihost').text(gdpr_consent.ihost);
        $('.gdpr_consent_url').attr('href', gdpr_consent.url);
        localStorage.setItem('gdpr_consent', JSON.stringify(gdpr_consent));
    } // end set_gdpr_consent(gdpr_consent) 


    /* All code below relates to email order and is not used currently. */
    /*************************************************/
    function show_email_order() {
        $('#email_order_layer').show();
        let hide = $('#hide_email_order');
        hide.off('click');
        hide.click(function (e) {
            $('#email_order_layer').hide();
            e.preventDefault();
        });

        let help = $('#help_email_order');
        help.off('click');
        //Display help text for each field in the form.
        let fo = '#email_order_form';
        help.click(function (e) {
            e.preventDefault();
            let legend = $(`${fo} legend`);
            let titles = `
<button class="button outline icon cart pull-right" title="Скриване на поясненията"
    id="hide_order_help"><img src="/img/arrow-collapse-all.svg" width="32" /></button>
                <h4>${legend.text()}</h4><p>${legend.data('description')}</p>`;
            // take the help from label titles
            $(`${fo} label`).each(function () {
                let self = $(this);
                let field_title = $(`${fo} [name=${self.prop('for')}]`).prop('title');
                titles += `<h5>${self.html()}</h5><p>${field_title}</p>`;
            });
            // display the help
            $('#email_order_layer').append(`<section id="order_help" class="card">${titles}</section>`);
            // remove the help from the DOM
            $('#hide_order_help').click(() => $('#order_help').remove());
        });
        let fields = $(`${fo} :input`);
        // Populate each field (excluding order items) with previous data if there is such.
        fields.each(function () {
            if (order[$(this).prop('name')])
                $(this).val(order[$(this).prop('name')]);
        });
        // Add events to each field to save the state (value) of the field so
        // the next time the user will have the info ready and prefilled. 
        fields.change(function () {
            order[$(this).prop('name')] = $(this).val();
            localStorage.setItem('order', JSON.stringify(order));
        });
        $(fo).off('submit');
        $(fo).submit(submit_order);
    } // end function show_email_order()


    function submit_order(ev) {
        let fo = '#email_order_form';
        let fields = $(`${fo} :input`);
        // make sure all fields data goes to order
        fields.each(function () {
            if ($(this).prop('name') !== '')
                order[$(this).prop('name')] = $(this).val();
        });
        order.items = [];
        // Put the cart items as order items
        Object.keys(cart).forEach((curr) => order.items.push(cart[curr]));
        $.post($(fo).prop('action'), JSON.stringify(order), function (data, status) {
            if (status === 'success') {
                // store the order for showing later too
                localStorage.setItem('last_order', JSON.stringify(data));
                delete_cart();
                $('#email_order_layer').hide();
                show_last_order(data);
            } else {
                let response = JSON.stringify(data);
                alert(`
Нещо се обърка на сървъра.
Опитайте да изпратите поръчката си на poruchki@studio-berov.eu.
Следва отговорът от сървъра. Молим изпратете, снимка на екрана си, за да ни
улесните в отстраняването на грешката.
${response}
`);
            }
        }, 'json');
        ev.preventDefault();
    } // end function submit_order
});

@@ img/arrow-collapse-all.svg
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M19.5,3.09L20.91,4.5L16.41,9H20V11H13V4H15V7.59L19.5,3.09M20.91,19.5L19.5,20.91L15,16.41V20H13V13H20V15H16.41L20.91,19.5M4.5,3.09L9,7.59V4H11V11H4V9H7.59L3.09,4.5L4.5,3.09M3.09,19.5L7.59,15H4V13H11V20H9V16.41L4.5,20.91L3.09,19.5Z" /></svg>
@@ img/cart-arrow-right.svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"  width="24" height="24" viewBox="0 0 24 24">
   <path fill="#fff" d="M9,20A2,2 0 0,1 7,22A2,2 0 0,1 5,20A2,2 0 0,1 7,18A2,2 0 0,1 9,20M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18M7.2,14.63C7.19,14.67 7.19,14.71 7.2,14.75A0.25,0.25 0 0,0 7.45,15H19V17H7A2,2 0 0,1 5,15C5,14.65 5.07,14.31 5.24,14L6.6,11.59L3,4H1V2H4.27L5.21,4H20A1,1 0 0,1 21,5C21,5.17 20.95,5.34 20.88,5.5L17.3,12C16.94,12.62 16.27,13 15.55,13H8.1L7.2,14.63M9,9.5H13V11.5L16,8.5L13,5.5V7.5H9V9.5Z" />
</svg>
@@ img/cart.svg
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M17,18C15.89,18 15,18.89 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20C19,18.89 18.1,18 17,18M1,2V4H3L6.6,11.59L5.24,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42A0.25,0.25 0 0,1 7.17,14.75C7.17,14.7 7.18,14.66 7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.58 17.3,11.97L20.88,5.5C20.95,5.34 21,5.17 21,5A1,1 0 0,0 20,4H5.21L4.27,2M7,18C5.89,18 5,18.89 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20C9,18.89 8.1,18 7,18Z" /></svg>
@@ img/cart-check.svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"  width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M9 20C9 21.11 8.11 22 7 22S5 21.11 5 20 5.9 18 7 18 9 18.9 9 20M17 18C15.9 18 15 18.9 15 20S15.9 22 17 22 19 21.11 19 20 18.11 18 17 18M7.17 14.75L7.2 14.63L8.1 13H15.55C16.3 13 16.96 12.59 17.3 11.97L21.16 4.96L19.42 4H19.41L18.31 6L15.55 11H8.53L8.4 10.73L6.16 6L5.21 4L4.27 2H1V4H3L6.6 11.59L5.25 14.04C5.09 14.32 5 14.65 5 15C5 16.11 5.9 17 7 17H19V15H7.42C7.29 15 7.17 14.89 7.17 14.75M18 2.76L16.59 1.34L11.75 6.18L9.16 3.59L7.75 5L11.75 9L18 2.76Z" />
</svg>
@@ img/cart-off.svg
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M22.73,22.73L1.27,1.27L0,2.54L4.39,6.93L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H14.46L15.84,18.38C15.34,18.74 15,19.33 15,20A2,2 0 0,0 17,22C17.67,22 18.26,21.67 18.62,21.16L21.46,24L22.73,22.73M7.42,15A0.25,0.25 0 0,1 7.17,14.75L7.2,14.63L8.1,13H10.46L12.46,15H7.42M15.55,13C16.3,13 16.96,12.59 17.3,11.97L20.88,5.5C20.96,5.34 21,5.17 21,5A1,1 0 0,0 20,4H6.54L15.55,13M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18Z" /></svg>
@@ img/cart-minus.svg
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M16,6V4H8V6M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18M7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2H1V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75Z" /></svg>
@@ img/cart-plus-white.svg
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M11,9H13V6H16V4H13V1H11V4H8V6H11M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18M7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2H1V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75Z" /></svg>
@@ img/cart-plus.svg
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M11,9H13V6H16V4H13V1H11V4H8V6H11M7,18A2,2 0 0,0 5,20A2,2 0 0,0 7,22A2,2 0 0,0 9,20A2,2 0 0,0 7,18M17,18A2,2 0 0,0 15,20A2,2 0 0,0 17,22A2,2 0 0,0 19,20A2,2 0 0,0 17,18M7.17,14.75L7.2,14.63L8.1,13H15.55C16.3,13 16.96,12.59 17.3,11.97L21.16,4.96L19.42,4H19.41L18.31,6L15.55,11H8.53L8.4,10.73L6.16,6L5.21,4L4.27,2H1V4H3L6.6,11.59L5.25,14.04C5.09,14.32 5,14.65 5,15A2,2 0 0,0 7,17H19V15H7.42C7.29,15 7.17,14.89 7.17,14.75Z" /></svg>
@@ img/cart-remove.svg
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M14.12,8.53L12,6.41L9.88,8.54L8.46,7.12L10.59,5L8.47,2.88L9.88,1.47L12,3.59L14.12,1.46L15.54,2.88L13.41,5L15.53,7.12L14.12,8.53M7,18A2,2 0 0,1 9,20A2,2 0 0,1 7,22A2,2 0 0,1 5,20A2,2 0 0,1 7,18M17,18A2,2 0 0,1 19,20A2,2 0 0,1 17,22A2,2 0 0,1 15,20A2,2 0 0,1 17,18M7.17,14.75A0.25,0.25 0 0,0 7.42,15H19V17H7A2,2 0 0,1 5,15C5,14.65 5.09,14.32 5.25,14.04L6.6,11.59L3,4H1V2H4.27L5.21,4L6.16,6L8.4,10.73L8.53,11H15.55L18.31,6L19.41,4H19.42L21.16,4.96L17.3,11.97C16.96,12.59 16.3,13 15.55,13H8.1L7.2,14.63L7.17,14.75Z" /></svg>
@@ img/econt.svg
<svg version="1.1" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"><path style="opacity:1;fill:#234182;fill-opacity:1;stroke-width:1.24709" d="M 6.1289062 -0.001953125 C 4.6048195 -0.017998455 3.2956311 1.2947208 3.125 3.0820312 L 1.5019531 20.080078 C 1.4422923 20.705007 1.5321941 21.30588 1.734375 21.841797 C 2.0990696 23.087443 3.2446653 23.992188 4.6113281 23.992188 L 17.267578 23.992188 C 18.929578 23.992188 20.267578 22.654187 20.267578 20.992188 C 20.267578 19.330188 18.929578 17.992188 17.267578 17.992188 L 7.7382812 17.992188 L 8.8828125 6 L 19.546875 6 C 21.208875 6 22.546875 4.662 22.546875 3 C 22.546875 1.338 21.208875 0 19.546875 0 L 6.484375 0 C 6.4201955 0 6.3580767 0.0058336146 6.2949219 0.009765625 C 6.2393916 0.0056858294 6.1839178 -0.001373973 6.1289062 -0.001953125 z M 15.328125 8.0390625 A 4 4 0 0 0 11.328125 12.039062 A 4 4 0 0 0 15.328125 16.039062 A 4 4 0 0 0 19.328125 12.039062 A 4 4 0 0 0 15.328125 8.0390625 z " /></svg>

@@ img/phone-classic-white.svg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M12,3C7.46,3 3.34,4.78 0.29,7.67C0.11,7.85 0,8.1 0,8.38C0,8.66 0.11,8.91 0.29,9.09L2.77,11.57C2.95,11.75 3.2,11.86 3.5,11.86C3.75,11.86 4,11.75 4.18,11.58C4.97,10.84 5.87,10.22 6.84,9.73C7.17,9.57 7.4,9.23 7.4,8.83V5.73C8.85,5.25 10.39,5 12,5C13.59,5 15.14,5.25 16.59,5.72V8.82C16.59,9.21 16.82,9.56 17.15,9.72C18.13,10.21 19,10.84 19.82,11.57C20,11.75 20.25,11.85 20.5,11.85C20.8,11.85 21.05,11.74 21.23,11.56L23.71,9.08C23.89,8.9 24,8.65 24,8.37C24,8.09 23.88,7.85 23.7,7.67C20.65,4.78 16.53,3 12,3M9,7V10C9,10 3,15 3,18V22H21V18C21,15 15,10 15,10V7H13V9H11V7H9M12,12A4,4 0 0,1 16,16A4,4 0 0,1 12,20A4,4 0 0,1 8,16A4,4 0 0,1 12,12M12,13.5A2.5,2.5 0 0,0 9.5,16A2.5,2.5 0 0,0 12,18.5A2.5,2.5 0 0,0 14.5,16A2.5,2.5 0 0,0 12,13.5Z" /></svg>

@@ partials/_footer_right.html.ep
<div class="pull-right social text-right">
<%
my $sharer_url = $canonical_path;
%>
    <a class="button outline primary sharer" href="tel:<%== app->config->{phone_url} %>"
    title="<%== app->config->{phone_url} %>"><img style="float: left;"
    src="/img/phone-classic-white.svg" height="24">&nbsp;<%== app->config->{phone_url} %></a><a

    class="button outline primary sharer" target="_blank"
    href="https://www.facebook.com/share.php?u=<%= $sharer_url %>" rel="noopener"
    aria-label="Споделяне във Facebook"
    title="Споделяне във Facebook"><img src="/css/malka/facebook.svg"></a><a

    class="button outline primary sharer" target="_blank"
    href="https://www.linkedin.com/shareArticle?mini=true&url=<%= $sharer_url %>&title=<%= title %>"
    aria-label="Споделяне в LinkedIn"
    title="Споделяне в LinkedIn"><img src="/css/malka/linkedin.svg"></a><a

    class="button outline primary sharer" target="_blank"
    href="https://twitter.com/intent/tweet?url=<%= $sharer_url %>&via=@kberov&title=<%= title %>"
    aria-label="Споделяне в Twitter"
    title="Споделяне в Twitter"><img src="/css/malka/twitter.svg"></a><a

    class="button outline primary sharer" target="_blank"
    href="mailto:?subject=<%= title %>&body=<%= $sharer_url %>"
    aria-label="Напишете писмо на приятел"
    title="Напишете писмо"><img src="/css/malka/email-fast-outline.svg"></a><a

    class="button outline primary sharer" target="_blank"
    href="tg://msg_url?url=<%= $sharer_url %>&text=<%= title %>"
    aria-label="Споделяне в Telegram"
    title="Споделяне в Telegram"><img src="/css/malka/icons8-telegram-app.svg"></a>
</div>

@@ partials/_gdpr_consent.html.ep
<%
# This template is practically the same as _writing.html.ep, but demosntrates a
# good example how plugins can create their own 'data_type's and use templates
# to display them on the site. Now if a celina has _gdpr_consent as data_type,
# it will be rendered using this template. With this simple technique we open
# the opportunity for content provided by plugins to get seamlesly pluged
# anywhere in the site, by just chosing the corresponding data_type and
# providing template for rendering this data_type.
%>
<!-- _gdpr_consent -->
<section class="<%= $celina->{data_type} %>">
    %= t 'h' . $level => $celina->{title}
%$celina->{body} .= include 'partials/_created_tstamp';
%==format_body($celina)
</section>
<!-- end _gdpr_consent -->

@@ partials/_kniga.html.ep
<%
# An example of using an existing data_type to extend its functionality. Note
# that this template will replace the default one from Themes::Malka. So it is
# maybe a better idea to create a new data_type and use a new corresponding
# template. See the next template as an example of this simple technique to
# plug anywhere in the site.
my $variants = $c->products->all({
  columns => '*',
  where   => {alias => $celina->{alias}, p_type => $celina->{data_type}}
})->each(sub {
  $_->{properties} = Mojo::JSON::from_json($_->{properties});
});
return unless @$variants;
# $c->debug(' $variants' => $variants);
%>
<!-- _book -->
<!-- <%= $domain->{templates} %> -->
<section class="<%= $celina->{data_type} %>">
%# Get the product variants for this page. These are rows from table products
%# with the same alias like the page alias and with p_type same like celina
%# data_type
    %= t 'h' . $level => $celina->{title}
<figure class="pull-left">
    <img title="<%= $variants->[0]{title} %>" src="<%= $variants->[0]{properties}{images}[0] %>">
    <figcaption class="text-center">
    За покупка<br>
    % for my $b(@$variants) {
    % my $props = $b->{properties};
        <a class="primary sharer button add-to-cart" href="#show_cart" title="<%= $props->{variant} %>"
            data-sku="<%= $b->{sku} %>" data-title="<%= $b->{title} %>"
            data-weight="<%= $props->{weight} %>" data-price="<%= $props->{price} %>"
                ><img src="<%= $props->{button_icon} %>"> <img src="/img/cart-plus-white.svg"></a>
    % }
    </figcaption>
</figure>
<table id="meta">
<tbody>
<tr><th>Заглавие:</th><td><%= $variants->[0]{title} %></td></tr>
<tr><th>Автор:</th><td><%= $variants->[0]{properties}{author} %></td></tr>
<tr><th>Поредица:</th><td><%= $variants->[0]{properties}{series} %></td></tr>
<tr><th>Размери:</th><td><%= $variants->[0]{properties}{dimensions} %></td></tr>
% for my $b(@$variants) {
<tr><th></th><td></td></tr>
<tr><th>ISBN:</th><td><%= $b->{sku} %></td></tr>
<tr><th>Тегло:</th><td><%= sprintf('%.3f', $b->{properties}{weight}) %> кг.</td></tr>
<tr><th>Цена:</th><td><%= $b->{properties}{price} . ' лв. за ' . $b->{properties}{variant} %></td></tr>
% }
<tr><th>Откъси:</th><td><a class="primary button sharer" title="Изтегляне на откъси" href="<%= 
    $variants->[0]{properties}{exerpts_url}
%>"><img src="/css/malka/file-pdf-box.svg"><img src="/css/malka/download.svg"></a></td></tr>
<tr><th>Е-поща:</th><td><a class="primary button sharer" title="Поръчка по е-поща"
href="mailto:poruchki@studio-berov.eu?subject=Поръчка: <%=$variants->[0]{title}%>">
<img src="/css/malka/email-fast-outline.svg">
<img src="/css/malka/book-open-page-variant-outline.svg">
Поръчка по е-поща
</a></td></tr>
</tbody>
</table>
%$celina->{body} .= include 'partials/_created_tstamp';
%== format_body($celina)
</section>

@@ resources/data/prodan_migrations.sql

-- 202112310000 up

-- A list of products and services being sold
CREATE TABLE IF NOT EXISTS products (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  sku VARCHAR(40) UNIQUE NOT NULL,
  -- Lowercased and trimmed of \W characters unique identifier.
  -- Must be the same as the alias of the celina in which it will be embedded.
  alias VARCHAR(100) NOT NULL,
  -- Must be the same as the title of the celina in which it will be embedded.
  title VARCHAR(155) UNIQUE NOT NULL,
  -- The product type: книга(book), картина(painting), ръкоделие(handmade)
  -- software... etc. If the plugin provides a template for the same celina
  -- data_type. The template will rpelace the default Slovo template for this data
  -- type and will retrieve from this table the product with the same alias as the
  -- celina alias. The content of the celina will go after the products content.
  p_type VARCHAR(32) DEFAULT 'book',
  -- the properties which are put in the data-* attributes
  -- of an "Add to cart" button such as data-sku, data-price,
  -- data-vat, data-vat_included, data-title, data-description, etc.
  properties JSON NOT NULL DEFAULT '{}'

);
CREATE UNIQUE INDEX IF NOT EXISTS sku_alias ON products(sku, alias);

-- A list of orders for bying product by customers
CREATE TABLE IF NOT EXISTS orders (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  -- name - own and family name
  name VARCHAR(100) NOT NULL,
  email VARCHAR(100),
  phone VARCHAR(20) NOT NULL,
  deliverer VARCHAR(100) NOT NULL,
  -- id of the order, given by the deliverer
  deliverer_id VARCHAR(40) NOT NULL,
  city_name VARCHAR(55) NOT NULL,
  -- Order with product items as it comes from the deliverer form dispayed to the enduser.
  -- Each item has the properties of a product.
  poruchka JSON NOT NULL,
  -- Way bill structure generated at deliverer site
  way_bill JSON DEFAULT '{}',
  -- When this content was inserted
  created_at INTEGER NOT NULL DEFAULT 0,
  -- Last time the record was touched
  tstamp INTEGER DEFAULT 0,
  -- Id at the deliverer site, returned by their system after we created the
  -- way-bill at their site.
  way_bill_id VARCHAR(40) DEFAULT '',
  executed INT(1) DEFAULT 0

);
CREATE UNIQUE INDEX IF NOT EXISTS deliverer_id ON orders(deliverer, deliverer_id);

-- A list of invoices for services and products, produced by different users
-- of this system.
CREATE TABLE IF NOT EXISTS invoices (
  -- Internal ID for this invoice. For the visible id each user will have its
  -- own incrementing counter, implemented outside the database
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  -- The visible and printable invoice ID - unique per user
  user_invoice_id INTEGER NOT NULL,
  -- User who created the invoice (owner).
  user_id INTEGER NOT NULL,
  -- Which order is this invoice for? NOTE: Items for the invoice are taken from the order.
  order_id INTEGER NOT NULL UNIQUE REFERENCES orders(id),
  -- Who modified this record the last time?
  changed_by INTEGER REFERENCES users(id)
);

CREATE TABLE IF NOT EXISTS users_invoices_last_id (
  --  ID of the user which created this invoice
  user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
  -- Internal invoice ID of the last invoice, generated by this user
  invoice_id INTEGER REFERENCES invoices(id) ON DELETE CASCADE,
  PRIMARY KEY(user_id, invoice_id)
);

-- 202112310000 down

DROP TABLE IF EXISTS invoices;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS users_invoices_last_id;

-- 202202170000 up

INSERT OR IGNORE INTO celini
(alias, pid, page_id, user_id, group_id,  data_type, data_format, created_at,
tstamp, title, description, keywords, body, box, language, published)
VALUES(
    'условия',
    (SELECT id from celini WHERE page_id=(
            SELECT id FROM stranici WHERE alias='ѿносно' AND dom_id=0) AND data_type='title'),
    (SELECT id FROM stranici WHERE alias='ѿносно'),
    (SELECT user_id FROM stranici WHERE alias='ѿносно'),
    (SELECT group_id FROM stranici WHERE alias='ѿносно'),
    '_gdpr_consent',
    'html',
    1645051275,
    1645051275,
    'Условия за ползване на Слово.бг',
    'Щом ползвате Слово.бг, вие се съгласявате със следните условия.',
    'условия,позване,права,ОРДЗ,GDPR',
    '<p>Щом ползвате Слово.бг, вие се съгласявате със следните условия.</p>',
    'main', 'bg', 2
);
-- 202202170000 down
DELETE from celini WHERE alias='условия' 
AND pid=(SELECT id from celini WHERE page_id=(
    SELECT id FROM stranici WHERE alias='ѿносно' AND dom_id=0) AND data_type='title');

