first commit

This commit is contained in:
2025-08-02 16:30:27 +02:00
commit 23646bfcee
14851 changed files with 1750626 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
<?php
namespace KupShop\MarketingBundle\Admin\Tabs;
use KupShop\AdminBundle\Admin\WindowTab;
class MarketingSettingsWindowTab extends WindowTab
{
protected $title = 'flapMarketing';
protected $template = 'window/marketingSettingsWindowTab.tpl';
public static function getTypes()
{
return [
'settings' => 1,
];
}
}

View File

@@ -0,0 +1,173 @@
<?php
namespace KupShop\MarketingBundle\Admin\Tabs;
use KupShop\AdminBundle\Admin\WindowTab;
use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\AdminBundle\Util\Filter\OrdersFilterSpecs;
use KupShop\AdminBundle\Util\UsersFilterSpecs;
use KupShop\CatalogBundle\Util\ProductsFilterSpecs;
use KupShop\KupShopBundle\Util\Excel\ExcelGenerator;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use Query\QueryBuilder;
class StatsMailerliteTab extends WindowTab
{
protected $title = 'flapMailerlite';
protected $template = 'window/tabs/statsMailerliteTab.tpl';
private $ordersFilterSpecs;
private $productsFilterSpecs;
private $usersFilterSpecs;
public function __construct(OrdersFilterSpecs $ordersFilterSpecs, ProductsFilterSpecs $productsFilterSpecs, UsersFilterSpecs $usersFilterSpecs)
{
$this->ordersFilterSpecs = $ordersFilterSpecs;
$this->productsFilterSpecs = $productsFilterSpecs;
$this->usersFilterSpecs = $usersFilterSpecs;
}
public static function getTypes()
{
return [
'stats' => 94,
];
}
public function getVars($smarty_tpl_vars)
{
$vars = [];
if (getVal('showUsersStat')) {
$vars['rowNumber'] = $this->getCountUserData();
$vars['usersData'] = $this->getShownData();
}
return $vars;
}
public function handleGetMailerlite()
{
set_time_limit(60 * 5);
$generator = new ExcelGenerator();
$filename = 'Users_'.date('Y-m-d_H-i').'.xlsx';
$generator->generateExcel($this->getHeader(), $this->loadData(), $filename);
addActivityLog(ActivityLog::SEVERITY_NOTICE, ActivityLog::TYPE_SECURITY, "Export uživatelů: {$filename}");
exit;
}
protected function loadData()
{
sqlGetConnection()->getWrappedConnection()->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
foreach ($this->GetUserData()->execute() as $product) {
yield [
$product['id'] ?? null,
$product['name'] ?? null,
$product['surname'] ?? null,
$product['email'],
$product['phone'] ?? null,
(empty($product['date_reg']) || $product['date_reg'] == '0000-00-00 00:00:00') ? 'N' : 'Y',
$product['count'] ?? null,
$product['price'] ?? null,
$product['average_price'] ?? null,
$product['mindate'] ?? null,
$product['maxdate'] ?? null,
($product['get_news'] == 'Y') ? 'Y' : 'N',
];
}
}
protected function getHeader(): array
{
return [
['name' => 'ID', 'type' => DataType::TYPE_NUMERIC],
['name' => 'Jméno', 'type' => DataType::TYPE_STRING],
['name' => 'Přijmení', 'type' => DataType::TYPE_STRING],
['name' => 'Email', 'type' => DataType::TYPE_STRING],
['name' => 'Tel. číslo', 'type' => DataType::TYPE_STRING],
['name' => 'Registrován', 'type' => DataType::TYPE_STRING],
['name' => 'Počet objednávek', 'type' => DataType::TYPE_NUMERIC],
['name' => 'Suma', 'type' => DataType::TYPE_NUMERIC, 'format' => '0.00'],
['name' => 'Průměrná cena', 'type' => DataType::TYPE_NUMERIC, 'format' => '0.00'],
['name' => 'První nákup', 'type' => 'datetime', 'format' => 'd.m.yyyy hh:mm:ss'],
['name' => 'Poslední nákup', 'type' => 'datetime', 'format' => 'd.m.yyyy hh:mm:ss'],
['name' => 'Newsletter', 'type' => DataType::TYPE_STRING],
];
}
public function GetUserData()
{
$actionTypeSet = getVal('actionTypeSet');
$query = sqlQueryBuilder()
->select('u.id, u.name, u.surname, u.phone, u.get_news, u.date_reg, u.email, COUNT(o.invoice_email) as count, SUM(o.total_price * o.currency_rate) as price, AVG(o.total_price * o.currency_rate) as average_price, MAX(o.date_created) as maxdate, MIN(o.date_created) as mindate')
->from('users', 'u')
->leftJoin('u', 'orders', 'o', 'u.email = o.invoice_email')
->andWhere('o.status_storno != 1')
->groupBy('u.email');
switch ($actionTypeSet) {
case 'newsletter':
$query->andWhere('u.get_news = "Y"');
break;
case 'just_buy':
$query->andWhere('u.get_news != "Y" AND u.date_reg IS NULL AND u.passw = ""')
->having('COUNT(o.invoice_email) >= 1');
break;
}
$query = $this->applySpecsToQuery($query);
return $query;
}
private function applySpecsToQuery(QueryBuilder $query)
{
$filter = getVal('filter', null, []);
$specs = $this->productsFilterSpecs->getSpecs($filter);
if ($specs) {
$subQuery = sqlQueryBuilder()->select('oi.id_order')
->from('order_items', 'oi')
->leftJoin('oi', 'products', 'p', 'oi.id_product = p.id')
->andWhere($specs)
->groupBy('oi.id_order');
$query->joinSubQuery('o', $subQuery, 'oi', 'o.id = oi.id_order');
}
$specs = $this->ordersFilterSpecs->getSpecs($filter);
if ($specs) {
$query->andWhere($specs);
}
$specs = $this->usersFilterSpecs->getSpecs($filter);
if ($specs) {
$query->andWhere($specs);
}
return $query;
}
public function getCountUserData()
{
$qb = $this->GetUserData();
return returnSQLResult("SELECT COUNT(*) FROM ({$qb->getSQL()}) a", $qb->getParameters(), $qb->getParameterTypes());
}
public function getShownData()
{
$query = $this->GetUserData()
->setMaxResults(20)
->execute()
->fetchAll();
return $query;
}
}

View File

@@ -0,0 +1,54 @@
<?php
$txt_str['marketingSettingsWindowTab'] = [
'flapMarketing' => 'Měřící kódy',
'OrderConversionMeasurement' => 'Měření konverzí objednávek',
'VerificationString' => 'Ověřovací řetězec',
'VerifiedByCustomers' => 'Ověřeno zákazníky',
'VerifiedByCustomersSK' => 'Ověřeno zákazníky - SK',
'ConversionTracking' => 'Měření konverzí',
'SendProductsToHeureka' => 'Zahrnout produkty',
'ConversionTrackingSK' => 'Měření konverzí - SK',
'CountingConversions' => 'Zboží.cz - Počítání konverzí',
'Code' => 'Kód',
'SecretKey' => 'Tajný klíč',
'ZboziSecretKey' => 'Zboží tajný klíč',
'ZboziAPIKey' => 'Zboží API klíč',
'OrderConversionMeasurementInfo' => 'Zaškrtnete-li, bude měření probíhat pouze při vytvoření objednávky!',
'GTMPricesWithoutVAT' => 'Odesílat ceny bez DPH',
'GTMPricesWithoutVATInfo' => 'Zaškrtnete-li, budou se do datových vrstev odesílat veškeré ceny bez DPH!',
'GTMPurchaseWithoutDelivery' => 'Cena objednávky bez dopravy a platby',
'GTMPurchaseWithoutDeliveryInfo' => 'Zaškrtnete-li, bude se v purchase event zasílat celková částka objednávky bez dopravy a platby!',
'GTMPriceCurrency' => 'Měna',
'GTMPriceCurrencyDefault' => 'Výchozí',
'GTMReallyRun' => 'Spouštět tento kontejner?',
'GTMReallyRunInfo' => 'Pokud jsme na beta / review verzi e-shopu, spouští se výchozí wpj kontejner. Touto funkcí lze vynutit spuštění výše vyplněného kontejneru.',
'GTMShaSalt' => 'Salt pro SHA-256 emailu',
'ZboziIdStore' => 'Zboží ID provozovny',
'NewConversionTracking' => 'Sklik ID konverze',
'SklikRetargetingId' => 'Sklik retargeting ID',
'buttonAddValue' => 'Přidat kód',
'note' => 'Poznámka',
'buttonDeleteValue' => 'Smazat kód',
'adsDescr' => 'Kód je 10-ti místné číslo nebo řetězec ve formátu XXX-XXX-XXXX',
'GTMServerUrl' => 'Doména značkovacího serveru (server side kontejner)',
'GTMServerUrlInfo' => 'Doména ve formátu ad4sfc.eshop.com',
'GTMServerDebugHash' => 'Debug Hash',
'GTM_DL_version' => 'Verze datové vrstvy',
'feEvents' => 'Pokročilé FE eventy',
'GTM_Domain' => 'Doména pro výdej GTM',
'GTM_CC_FE_toggle' => 'Zapnout client-side měření na infrastruktuře WPJ',
'GTM_CC_SS_toggle' => 'Zapnout server-side měření na infrastruktuře WPJ',
'backendConversionOnly' => 'Odesílat pouze backend konverze',
'DL_opinions' => [
0 => 'v1.0 pro všechny',
1 => 'v2.0 pouze pro přihlášené administrátory',
2 => 'v2.0 pro všechny',
],
'active' => 'Ano',
'inactive' => 'Ne',
'admin_only' => 'Pouze pro administrátory',
];

View File

@@ -0,0 +1,5 @@
<?php
$txt_str['statsMailerliteTab'] = [
'flapMailerlite' => 'Uživatelé',
];

View File

@@ -0,0 +1,55 @@
<?php
$txt_str['marketingSettingsWindowTab'] = [
'flapMarketing' => 'Analytics',
'OrderConversionMeasurement' => 'Order Conversion Measurement',
'VerificationString' => 'Verification string',
'VerifiedByCustomers' => 'Verified by customers',
'VerifiedByCustomersSK' => 'Verified by customers - SK',
'ConversionTracking' => 'Conversion tracking',
'SendProductsToHeureka' => 'Include products',
'ConversionTrackingSK' => 'Conversion tracking - SK',
'CountingConversions' => 'Zboží.cz - Counting conversions',
'Code' => 'Code',
'SecretKey' => 'Secret Key',
'APIKey' => 'API Key',
'OrderConversionMeasurementInfo' => 'If checked, analytics will measure orders only, not visits!',
'GTMPricesWithoutVAT' => 'Send price without vat',
'GTMPricesWithoutVATInfo' => 'If checked, all prices will be send without vat!',
'GTMPriceCurrency' => 'Currency',
'GTMReallyRun' => 'Run this container?',
'GTMReallyRunInfo' => 'If we are on the beta / review version of the e-shop, the default wpj container is running. This function can be used to force the above-filled container to run.',
'NewConversionTracking' => 'New conversion code',
'sklikStoreId' => 'ID provozovny Zboží.cz',
'buttonAddValue' => 'Add code',
'note' => 'Note',
'buttonDeleteValue' => 'Delete code',
'adsDescr' => 'Code is a 10-digit number or a string in the format XXX-XXX-XXXX',
'GTMServerUrl' => 'Tag server domain (server side container)',
'GTMServerUrlInfo' => 'Domain in format ad4sfc.eshop.com',
'GTMServerDebugHash' => 'Debug Hash',
'GTM_DL_version' => 'DL version',
'feEvents' => 'Advanced FE events',
'GTM_Domain' => 'Domain for GTM',
'GTM_CC_FE_toggle' => 'Enable client-side measurement on WPJ infrastructure',
'GTM_CC_SS_toggle' => 'Enable server-side measurement on WPJ infrastructure',
'backendConversionOnly' => 'Send only backend conversions',
'GTMPurchaseWithoutDelivery' => 'Order price without shipping and payment',
'GTMPriceCurrencyDefault' => 'Default',
'GTMShaSalt' => 'Salt (SHA-256) email',
'ZboziIdStore' => 'Zboží ID of establishment',
'SklikRetargetingId' => 'Sklik retargeting ID',
'ZboziSecretKey' => 'Zboží secret key',
'ZboziAPIKey' => 'Zboží API key',
'DL_opinions' => [
0 => 'v1.0 for everyone',
1 => 'v2.0 only for logged administrators',
2 => 'v2.0 for everyone',
],
'active' => 'Yes',
'inactive' => 'No',
'admin_only' => 'Only for admins',
];

View File

@@ -0,0 +1,5 @@
<?php
$txt_str['statsMailerliteTab'] = [
'flapMailerlite' => 'Users',
];

View File

@@ -0,0 +1,305 @@
<style>
.custom-plugin-universal {
background-color: white;
padding: 10px;
border: 1px solid #ddd;
}
.custom-plugin-universal .st-logo {
margin: 10px auto 20px auto;
width: 114px;
}
.custom-plugin-universal .pers-info {
font-size: 14px;
margin-bottom: 10px;
}
.custom-plugin-universal .pers-info img {
position: relative;
float: left;
margin-right: 10px;
bottom: -2px;
width: 17px;
}
.custom-plugin-universal .pers-info p {
float: left;
margin-top: 0;
}
.custom-plugin-universal .pers-info a {
color: #1EA0C9;
font-weight: bold;
}
.custom-plugin-universal .spent {
padding: 10px;
width: 100%;
background-color: #EEF0F3;
}
.custom-plugin-universal .spent img {
float: left;
margin-right: 15px;
position: relative;
bottom: -7px;
}
.custom-plugin-universal .spent .utraceno {
font-size: 14px;
}
.custom-plugin-universal .spent .value {
font-size: 16px;
font-weight: bold;
padding-left: 50px;
}
.custom-plugin-universal .divider {
border: 1px solid #E8E8E8;
margin: 20px -10px 20px -10px;
}
.custom-plugin-universal h2 {
font-size: 16px;
font-weight: bold;
color: #333;
margin: 0px 0 5px 0;
}
.custom-plugin-universal .order {
border-bottom: 1px solid #E8E8E8;
padding: 10px 0 10px 0;
}
.custom-plugin-universal .order .order-number {
font-size: 14px;
float: left;
}
.custom-plugin-universal .order .order-number a {
color: #1EA0C9;
font-weight: bold;
}
.custom-plugin-universal .order .order-item-name {
float: left;
font-size: 11px;
}
.custom-plugin-universal .order .order-item-price {
text-align: right;
font-size: 11px;
}
.custom-plugin-universal .order .order-value {
font-size: 14px;
float: right;
font-weight: bold;
}
.custom-plugin-universal .order .order-calendar {
float: left;
font-size: 12px;
color: #ABABAB;
margin-top: 6px;
}
.custom-plugin-universal .order .order-calendar img {
position: relative;
left: 0;
top: 2px;
margin-right: 0;
}
.custom-plugin-universal .order .order-car {
float: left;
font-size: 12px;
color: #ABABAB;
margin-top: 6px;
}
.custom-plugin-universal .order .order-car img {
position: relative;
left: 0;
top: 2px;
margin-right: 0;
}
.custom-plugin-universal .show-rest {
text-align: center;
margin-top: 10px;
}
.custom-plugin-universal .show-rest a {
color: #1EA0C9;
font-size: 14px;
}
.cleaner {
clear: both;
}
.unavailable-data {
text-align: center;
font-size: 14px;
}
@media only screen and (min-width: 1000px) and (max-width: 1600px) {
#mail-ticket-plugins .custom-plugin-universal .order .order-car {
float: left;
width: 150px;
}
}
@media only screen and (min-width: 1000px) and (max-width: 1450px) {
#mail-ticket-plugins .custom-plugin-universal .order .order-value {
float: left;
width: 150px;
margin: 6px 0 3px 0;
}
}
</style>
{if $orders || $user || $sales}
<div class="custom-plugin-universal">
<div class="st-logo" data-original-title="" title="">
<img src="{$cfg.Addr.full}admin/static/images/logo.svg"
style="background-color: #0d4db6;border-radius: 5%;padding: 7px;">
</div>
<div class="available-data" style="">
<div class="pers-info" data-original-title="" title="">
{if $user.id} <a href="{$cfg.Addr.full|rtrim:'/'}{getAdminUrl('users', ['ID' => $user.id])}">
<img src="https://app.supportbox.cz/assets/build/image/plugin-shoptet/person.png">{/if} <strong
class="data-name">{$orders[0].invoice_name|default:$user->name} {$orders[0].invoice_surname|default:$user->surname}</strong></a>
</div>
<div class="pers-info" data-original-title="" title="">
<img src="https://app.supportbox.cz/assets/build/image/plugin-shoptet/phone.png">
<span class="data-phone">{$orders[0].invoice_phone|default:$user->name}</span>
</div>
<div class="pers-info" data-original-title="" title="">
<img src="https://app.supportbox.cz/assets/build/image/plugin-shoptet/place.png">
<span class="data-address"><p>{$orders[0].invoice_street|default:$user->street}<br>{$orders[0].invoice_city|default:$user->city}
<br>{$orders[0].invoice_zip|default:$user->zip}</p></span>
</div>
<div class="cleaner" data-original-title="" title=""></div>
<div class="pers-info" data-original-title="" title="">
<strong>E-mail:</strong> {$orders[0].invoice_email|default:$user->email}
</div>
<div class="pers-info" data-original-title="" title="">
<strong>Odběr k novinkám:</strong> {if $user->get_news == 'Y'}Ano{else}Ne{/if}
</div>
<div class="cleaner" data-original-title="" title=""></div>
<div class="spent" data-original-title="" title="">
<img src="https://app.supportbox.cz/assets/build/image/plugin-shoptet/money.png">
<div class="utraceno" data-original-title="" title="">Celkem utraceno</div>
<div class="value" data-original-title="" title="">
{$userTotals = $total_orders_price}
{if $total_sales_price}
{$userTotals = $userTotals + $total_sales_price}
{/if}
{$userTotals|round|default:'-'} CZK<br>
</div>
<div class="cleaner" data-original-title="" title=""></div>
</div>
<div class="divider" data-original-title="" title=""></div>
<h2>Objednávky</h2>
<div class="order-list">
{foreach $orders as $order}
<div class="order" data-original-title="" title="">
<div class="order-number" data-original-title="" title=""><i class="fa fa-circle red" data-original-title="" title=""
style="color: gray"></i> <a
href="{$cfg.Addr.full|rtrim:'/'}{getAdminUrl('orders', ['ID' => $order.id])}" data-original-title="" title=""
class="data-id" target="_blank">{$order.order_no}</a></div>
<div class="order-value" data-original-title=""
title="">{$order['total_price']|round} {$order['currency']|default:'Kč'}</div>
<div class="cleaner" data-original-title="" title=""></div>
{foreach $order.items as $item}
<div>
<div class="order-item-name">{$item.descr|substr:0:30}</div>
<div class="order-item-price">{{$item.total_price * ( 1 + ($item.tax / 100))}|round} {$order.currency}</div>
</div>
{/foreach}
<div class="cleaner" data-original-title="" title=""></div>
<div class="order-calendar" data-original-title="" title="">
<img src="https://app.supportbox.cz/assets/build/image/plugin-shoptet/calendar.png">
<span class="order-date">{$order['date_created']|format_date:'d.m.Y'} - {$order.order_status}</span>
</div>
<div class="order-car" data-original-title="" title="">
<img src="https://app.supportbox.cz/assets/build/image/plugin-shoptet/car.png">
<span class="delivery-date">{$order['date_handle']|format_date:'d.m.Y'}</span>
</div>
<div class="cleaner" data-original-title="" title=""></div>
</div>
{/foreach}
</div>
{ifmodule SALES}
<div class="divider" data-original-title="" title=""></div>
<h2>Nákupy na prodejně</h2>
<div class="order-list">
{foreach $sales as $sale}
<div class="order" data-original-title="" title="">
<div class="order-number" data-original-title="" title="">
<i class="fa fa-circle red" data-original-title="" title="" style="color: gray"></i>
<a href="{$cfg.Addr.full|rtrim:'/'}{getAdminUrl('Sales', ['ID' => $sale.id])}" data-original-title="" title=""
class="data-id" target="_blank">{$sale.code}</a>
</div>
<div class="order-value" data-original-title=""
title="">{$sale['total_price']|round} {$order['currency']|default:'Kč'}
</div>
<div class="cleaner" data-original-title="" title=""></div>
{foreach $sale.items as $item}
<div>
<div class="order-item-name">{$item.name|substr:0:30}</div>
<div class="order-item-price">{{$item.total_price * ( 1 + ($item.tax / 100))}|round} {$order.currency}</div>
</div>
{/foreach}
<div class="cleaner" data-original-title="" title=""></div>
<div class="order-calendar" data-original-title="" title="">
<img src="https://app.supportbox.cz/assets/build/image/plugin-shoptet/calendar.png">
<span class="order-date">
{$sale['date_created']|format_date:'d.m.Y'} - <a href="{$cfg.Addr.full|rtrim:'/'}{getAdminUrl('sellers', ['ID' => $sale.id_seller])}" target="_blank">{$sale.seller_name}</a>
</span>
</div>
<div class="cleaner" data-original-title="" title=""></div>
</div>
{/foreach}
</div>
{/ifmodule}
{else}
<div class="custom-plugin-universal">
<div class="unavailable-data">
Zákazník nebyl<br>v e-shopu nalezen
</div>
</div>
{/if}

View File

@@ -0,0 +1,149 @@
<div id="flapMailerlite" class="tab-pane fade in boxFlex">
{insert_orders_filter filterName='flapStatsOrderMailerlite' saveFilter=true}
{include "block.productsFilter.tpl" saveFilter=true filterName='flapStatsProductMailerlite'}
{insert_users_filter filterName='flapStatsUsersMailerLite' saveFilter=true}
{$barName = 'Mailerlite'}
<div class="row bottom-space" data-filter-name="interval{$barName}">
<div class="col-xs-12">
<div class="panel panel-primary">
<div class="panel-body">
<div class="form-group">
<div class="col-md-1 control-label"><label>Platnost</label></div>
<div class="col-md-1 radio" style="width: auto; padding-right: 0px;">
<input type="radio" class="check" name="actionTypeSet" value="registered" id="registered" checked/>
<label for="registered"></label>
</div>
<div class="col-md-1">
<span class="help-block">Všichni uživatelé
<a class="help-tip" style="position:relative;" data-toggle="tooltip" title=""
data-original-title="Exportuje množinu všech uživatelů."><i
class="bi bi-question-circle"></i></a>
</span>
</div>
<div class="col-md-1 radio" style="width: auto; padding-right: 0px;">
<input type="radio" class="check" name="actionTypeSet" value="newsletter" id="newsletter"/>
<label for="newsletter"></label>
</div>
<div class="col-md-1">
<span class="help-block">Mají newsletter
<a class="help-tip" style="position:relative;" data-toggle="tooltip" title=""
data-original-title="Exportuje množinu uživatelů, kteří mají přihlášený newsletter."><i
class="bi bi-question-circle"></i></a>
</span>
</div>
<div class="col-md-1 radio" style="width: auto; padding-right: 0px;">
<input type="radio" class="check" name="actionTypeSet" value="just_buy" id="just_buy"/>
<label for="just_buy"></label>
</div>
<div class="col-md-1">
<span class="help-block">Pouze nakoupili
<a class="help-tip" style="position:relative;" data-toggle="tooltip" title=""
data-original-title="Exportuje množinu uživatelů, kteří nemají přihlášený newsletter, nemají registraci a mají alespon jednu objednávku."><i
class="bi bi-question-circle"></i></a>
</span>
</div>
<div class="col-md-2 pull-left">
<input type="button" class="btn btn-primary btn-block btn-sm"
value="{'show'|translate}" data-btn-show-{$barName} data-btn-submit>
</div>
</div>
<script type="text/javascript">
wpj.formUtils.initFormSave($('[data-filter-name="interval{$barName}"]'), 'interval{$barName}');
</script>
</div>
{if $graphs}
<script type="text/javascript">
$(document).on('click', '[data-btn-show-{$barName}]', function () {
showGraphsArea($(this));
{foreach $graphs as $graph}
get{$graph}Graph($('[name=interval{$barName}]:checked').val());
{/foreach}
});
</script>
{/if}
</div>
</div>
</div>
<div class="row bottom-space" id="products" data-display-area="mailerlite">
<div class="col-md-12">
<div id="userdata_content">
<div class="panel panel-default panel-sm">
<div class="panel-heading">
<div class="pull-right">
{'countValue'|translate}&nbsp{$tab.data.rowNumber}
</div>
<a class="help-tip" style="position:relative; float:right;margin-top: -2px;margin-right: 10px" data-toggle="tooltip"
title="{'userDataStats'|translate}">
<i class="bi bi-question-circle"></i>
</a>
<h3 class="panel-title">{'userDataStats'|translate}</h3>
</div>
<table class="table">
<tr>
<th>{'userName'|translate}</th>
<th>{'userSurname'|translate}</th>
<th>{'userEmail'|translate}</th>
<th>{'userPhone'|translate}</th>
<th>{'isRegistered'|translate}</th>
<th>{'countOrders'|translate}</th>
<th>{'sumMoney'|translate}</th>
<th>{'avgMoney'|translate}</th>
<th>{'firstShop'|translate}</th>
<th>{'lastShop'|translate}</th>
<th>{'getNews'|translate}</th>
</tr>
{foreach $tab.data.usersData as $item}
<tr style="background-color: #f9f9f9; border-top: 2px solid #ddd !important; ">
<td>{$item.name}</td>
<td>{$item.surname}</td>
<td><a href="javascript:nw('user', '{$item.id}');">{$item.email}</a></td>
<td>{$item.phone}</td>
<td>{if empty($item.date_reg) || $item.date_reg == '0000-00-00 00:00:00'}N{else}Y{/if}</td>
<td>{$item.count}</td>
<td>{$item.price|format_price:"ceil=no;decimal=dynamic"}</td>
<td>{$item.average_price|format_price:"ceil=no;decimal=dynamic"}</td>
<td>{$item.mindate}</td>
<td>{$item.maxdate}</td>
<td>{if $item.get_news == 'Y'}Y{else}N{/if}</td>
</tr>
{/foreach}
</table>
</div>
<div class="row">
<div class="col-md-2 pull-right">
<input type="button" class="btn btn-primary btn-block btn-sm"
value="{'export'|translate}" data-btn-export-Mailerlite data-btn-submit>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="application/javascript">
$(document).on('click', '[data-btn-show-mailerlite]', function() {
reloadDivMailerlite();
showGraphsArea($(this));
});
$(document).on('click', '[data-btn-export-mailerlite]', function() {
var paramsString = concatParams({
type: 'stats',
acn: 'getMailerlite'
}, '#flapMailerlite');
window.location.href = 'launch.php?s=board.php&ajax=1&' + paramsString;
});
function reloadDivMailerlite() {
reloadDiv('#userdata_content', {
type: 'stats',
showUsersStat: 1
}, '#flapMailerlite');
}
</script>

View File

@@ -0,0 +1,50 @@
<?php
namespace KupShop\MarketingBundle\Controller;
use KupShop\KupShopBundle\Context\UserContext;
use Query\Operator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class CrispController extends AbstractController
{
/**
* @Route(path="/getCrispData/")
*
* @return JsonResponse
*/
public function getCrispDataAction(Request $request, UserContext $userContext)
{
/** @var \User $user */
$user = $userContext->getActive();
if (!empty($user->id)) {
$data = sqlQueryBuilder()
->select("MAX(DATE(o.date_created)) 'Posledni_objednavka',
MAX(o.order_no) 'Kod_posledni_objednavky',
CONCAT(CEIL(SUM(o.total_price)),' ' , o.currency) 'Celkem',
COUNT(o.id) 'Pocet_objednavek',
SUM(o.status_storno) 'Pocet_stornovanych_objednavek'"
)
->from('orders', 'o')
->where(Operator::equals(['o.id_user' => $user->id]))
->execute()
->fetch();
$data['B2B'] = $userContext->isDealer() ? 'ANO' : 'NE';
$data = array_map(function ($value, $key) {
return [$key, $value];
}, array_values($data), array_keys($data));
} else {
$data = [];
}
$response = new JsonResponse($data);
return $response;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace KupShop\MarketingBundle\Controller;
use KupShop\MarketingBundle\SupportBox\InfoProvider;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
class SupportBoxController extends AbstractController
{
/**
* @Route(path="/_supportbox/")
*/
public function accountAction(Request $request, InfoProvider $infoProvider)
{
$this->authorization('id', $request);
if (!$request->get('email') && !$request->get('phone')) {
return new JsonResponse(['html' => '', 'error' => 'Query parameters `email` or `phone` not provided']);
}
return new JsonResponse(
$infoProvider->getAdditionalData((string) $request->get('email'), (string) $request->get('phone'))
);
}
/**
* @Route(path="/_supportbox/phone/{number}/")
*/
public function phoneAction(Request $request, InfoProvider $infoProvider, $number)
{
$this->authorization('id_phone', $request);
return new JsonResponse($infoProvider->getName($number));
}
public function authorization($serviceType, $request)
{
$dbcfg = \Settings::getDefault();
$key = $dbcfg->analytics['supportbox'][$serviceType] ?? false;
if (!$key || ($key != $request->get('key') && 'Basic '.$key != $request->headers->get('Authorization'))
) {
throw new NotFoundHttpException('Route not found.');
}
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace KupShop\MarketingBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
class WebClaimVerifiyng extends AbstractController
{
/**
* @Route(path="/google{hash}.html", name="google_claim_verify")
*
* @return Response
*/
public function getGoogleWebVerify(Request $request, $hash)
{
$dbcfg = \Settings::getDefault();
foreach ($dbcfg->analytics['google_site_verifications_file'] ?? [] as $value) {
if ($value['value'] == $hash) {
return new Response("google-site-verification: google{$hash}.html");
}
}
throw new NotFoundHttpException('Not found');
}
/**
* @Route(path="/{hash}.html", name="facebook_claim_verify", requirements={"hash"="^[a-zA-Z0-9]{30}$"})
*
* @return Response
*/
public function getFacebookWebVerify(Request $request, $hash)
{
$dbcfg = \Settings::getDefault();
foreach ($dbcfg->analytics['facebook_site_verifications_file'] ?? [] as $value) {
if ($value['value'] == $hash) {
return new Response($hash);
}
}
throw new NotFoundHttpException('Not found');
}
/**
* @Route(path="/seznam-wmt-{hash}.txt", name="seznam_claim_verify", requirements={"slug"="^[a-zA-Z0-9]{32}$"})
*
* @return Response
*/
public function getSeznamWebVerify(Request $request, $hash)
{
$dbcfg = \Settings::getDefault();
foreach ($dbcfg->analytics['seznam_site_verifications_file'] ?? [] as $value) {
if ($value['value'] == $hash) {
return new Response($hash);
}
}
throw new NotFoundHttpException('Not found');
}
}

View File

@@ -0,0 +1,324 @@
<?php
namespace KupShop\MarketingBundle\EventListener;
use Heureka\ShopCertification;
use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\KupShopBundle\Config;
use KupShop\KupShopBundle\Context\CountryContext;
use KupShop\KupShopBundle\Context\DomainContext;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Compat\SymfonyBridge;
use KupShop\KupShopBundle\Util\Price\Price;
use KupShop\KupShopBundle\Util\Price\PriceCalculator;
use KupShop\KupShopBundle\Util\Price\PriceUtil;
use KupShop\OrderingBundle\Event\OrderEvent;
use KupShop\OrderingBundle\Util\Order\OrderInfo;
use Query\Operator;
use Soukicz\Zbozicz\CartItem;
use Soukicz\Zbozicz\Client;
use Soukicz\Zbozicz\Order;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class OrderConversionListener implements EventSubscriberInterface
{
/**
* @var PriceUtil
*/
private $priceUtil;
/**
* @var RequestStack
*/
private $requestStack;
private $countryContext;
/** @var DomainContext */
private $domainContext;
/**
* @var LanguageContext
*/
private $languageContext;
private $cookieConsents;
/**
* @required
*/
public function setDomainContext(DomainContext $domainContext): void
{
$this->domainContext = $domainContext;
}
public static function getSubscribedEvents()
{
if (isDevelopment() && !isFunctionalTests()) {
return [];
}
return [
OrderEvent::ORDER_FINISHED => [
['sendZboziConversion', 200],
['sendHeurekaConversion', 200],
['sendCJTracking', 201],
],
];
}
public function __construct(PriceUtil $priceUtil, RequestStack $requestStack, CountryContext $countryContext)
{
$this->priceUtil = $priceUtil;
$this->requestStack = $requestStack;
$this->countryContext = $countryContext;
if ($request = $this->requestStack->getMainRequest()) {
$cookie_bar = $request->cookies->get('cookie-bar');
$this->cookieConsents = array_flip(explode(',', $cookie_bar ?? ''));
}
}
public function sendZboziConversion(OrderEvent $event)
{
$dbcfg = \Settings::getDefault();
if (empty($dbcfg->analytics['zbozi_cz_feedback']['secret_key']) || empty($dbcfg->analytics['zbozi_cz_feedback']['ID'])) {
return;
}
$order = $event->getOrder();
if (empty($order->invoice_email)) {
return;
}
$request = $this->requestStack->getCurrentRequest();
if (!empty($request) && $request->get('heurekaDisagree')) {
return;
}
try {
$zbozi = $this->getZboziClient();
$delivery_type = $order->getDeliveryType();
$order->fetchItems();
$target = $this->priceUtil->getCurrencyById('CZK');
$source = $this->priceUtil->getCurrencyById($order->currency);
// nastavení informací o objednávce
$zboziOrder = new Order($order->order_no);
$zboziOrder
->setDeliveryType($delivery_type->getDelivery()->getZboziDeliveryID())
->setDeliveryPrice(PriceCalculator::convert($delivery_type->getPrice(), $target)->getPriceWithVat()->asFloat())
->setEmail($order->invoice_email)
->setOtherCosts(0)
->setPaymentType($delivery_type['payment']);
foreach ($order->items as $item) {
if ($item['id_product']) {
$id = $item['id_product'].($item['id_variation'] ? '_'.$item['id_variation'] : '');
if (findModule('marketing', 'product_identifier') == 'code') {
$id = $item['code'];
}
$zboziItem = new CartItem();
$zboziItem
->setId($id)
->setUnitPrice(PriceCalculator::convert(new Price($item['piece_price']['value_with_vat'], $source, 0), $target)->getPriceWithVat()->asFloat())
->setQuantity($item['pieces'])
->setName($item['descr']);
$zboziOrder->addCartItem($zboziItem);
}
}
// odeslání
$zbozi->sendOrder($zboziOrder);
} catch (\Exception $e) {
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, 'Selhalo odeslani konverze zbozi.cz', ['error' => $e->getMessage()]);
}
}
/**
* @return Client
*/
public function getZboziClient()
{
$dbcfg = \Settings::getDefault();
return new Client($dbcfg->analytics['zbozi_cz_feedback']['ID'], $dbcfg->analytics['zbozi_cz_feedback']['secret_key'], isDevelopment() ? true : false);
}
public function sendHeurekaConversion(OrderEvent $event)
{
$dbcfg = \Settings::getDefault();
$order = $event->getOrder();
$request = $this->requestStack->getCurrentRequest();
if (!empty($request)) {
$heurekaDisagree = $request->get('heurekaDisagree') ?? '0';
$order->setData('heurekaDisagree', $heurekaDisagree);
}
if (!empty($request) && $request->get('heurekaDisagree')) {
return;
}
if (!empty($dbcfg->analytics['heureka_overeno']['ID']) && !empty($order->invoice_email)) {
try {
$options = $this->getHeurekaOptions();
$overeno = new ShopCertification($options['heureka_id'], $options['options']);
$overeno->setEmail($order->invoice_email);
$overeno->setOrderId($order->order_no);
if (($dbcfg->analytics['heureka_overeno']['send_products'] ?? 'Y') == 'Y') {
$order->fetchItems();
foreach ($order->items as $row) {
$item = $this->getHeurekaProductItem($row);
if (empty($item)) {
continue;
}
try {
$overeno->addProductItemId($item);
} catch (ShopCertification\DuplicateProductItemIdException) {
// ta knihovna nepovoluje vlozit vickrat stejny IDcka
// takze to vyignoruju, at se to proste preskoci a jedu dal
}
}
}
// logError(__FILE__, __LINE__, "Heureka Overeno odesilam:".print_r($overeno, true));
$overeno->logOrder();
} catch (\Exception $e) {
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, 'Selhalo odeslani konverze heureka.cz', [
'orderNumber' => $order->order_no,
'error' => $e->getMessage(),
]);
}
}
}
public function getHeurekaOptions()
{
$dbcfg = \Settings::getDefault();
$country = ShopCertification::HEUREKA_SK;
if ($this->countryContext->getActiveId() == 'SK' && ($dbcfg->analytics['heureka_overeno']['ID_SK'] ?? false)) {
$heureka_id = $dbcfg->analytics['heureka_overeno']['ID_SK'];
} elseif ($this->languageContext->getActiveId() == 'sk' && ($dbcfg->analytics['heureka_overeno']['ID'] ?? false)) {
$heureka_id = $dbcfg->analytics['heureka_overeno']['ID'];
} else {
$heureka_id = $dbcfg->analytics['heureka_overeno']['ID'];
$country = ShopCertification::HEUREKA_CZ;
}
return ['heureka_id' => $heureka_id, 'options' => ['service' => $country]];
}
protected function getHeurekaProductItem($row)
{
if (findModule('marketing', 'product_identifier') == 'code') {
return $row['code'];
}
return $row['id_product'].(!empty($row['id_variation']) ? '_'.$row['id_variation'] : '');
}
public function sendCJTracking(OrderEvent $event)
{
$cfg = Config::get();
$dbcfg = \Settings::getDefault();
$config = $dbcfg->analytics['cjtracking'] ?? [];
if (empty($config['CID'])) {
$config = $cfg['Modules']['CJTracking'] ?? [];
if ($domainConfig = ($cfg['Modules']['CJTracking']['domain_config'][$this->domainContext->getActiveId()] ?? false)) {
$config = array_merge($config, $domainConfig);
}
}
if (empty($CID = $config['CID'] ?? null) || empty($TYPE = $config['TYPE'] ?? null)) {
return;
}
$request = SymfonyBridge::getCurrentRequest();
if (($cjevent = $request->cookies->get('cjevent')) || ($cjevent = $request->getSession()->get('cjevent'))) {
$order = $event->getOrder();
$order->setData('cjevent', $cjevent);
$params = [
'CID' => $CID, 'TYPE' => $TYPE, 'CJEVENT' => $cjevent, 'METHOD' => 'S2S',
'CURRENCY' => $order->currency, 'OID' => $order->order_no,
];
$i = 1;
$amountOfDiscount = \DecimalConstants::zero();
foreach ($order->fetchItems() as $item) {
if ($item['id_product']) {
$id = $item['id_product'].($item['id_variation'] ? '_'.$item['id_variation'] : '');
if (findModule('marketing', 'product_identifier') == 'code') {
$id = $item['code'];
}
if (($item['note']['totalDiscount'] ?? false) && empty($item['note']['discounts'])) {
$amountOfDiscount = $amountOfDiscount->add(toDecimal($item['note']['totalDiscount']));
}
$params['ITEM'.$i] = $id;
$params['AMT'.$i] = $item['piece_price']['value_without_vat']->asFloat();
$params['QTY'.$i] = $item['pieces'];
$i++;
} elseif (!empty($item['note'])) {
$coupon = ($item['note']['coupon'] ?? $item['note']['generated_coupon']['code'] ?? null);
$price = $item['piece_price']['value_without_vat']->asFloat();
if ($coupon && ($price < 0)) {
$params['COUPON'] = $coupon;
$amountOfDiscount = $amountOfDiscount->add(toDecimal(-$price));
}
}
}
if ($coupons = OrderInfo::getUsedCoupons($order)) {
$params['COUPON'] = join(',', $coupons);
}
if (!empty($params['DISCOUNT'])) {
$params['DISCOUNT'] = $amountOfDiscount->add(toDecimal($params['DISCOUNT']))->asFloat();
} elseif ($amountOfDiscount > \DecimalConstants::zero()) {
$params['DISCOUNT'] = $amountOfDiscount->asFloat();
}
$returned = sqlQueryBuilder()->select('COUNT(*)')->from('orders')
->where(Operator::equals(['invoice_email' => $order->getUserEmail()]))
->andWhere('date_created >= DATE_SUB(current_date, INTERVAL 1 YEAR)')
->execute()->fetchColumn();
$params['cust_status'] = ($returned > 1 ? 'return' : 'new');
$url = 'https://www.kdukvh.com/u?'.http_build_query($params);
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'GET',
]);
$response = curl_exec($curl);
if ($error = curl_error($curl)) {
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, 'CJ Tracking: curl error', ['error' => $error]);
}
if (trim($response) != 'received') {
addActivityLog(ActivityLog::SEVERITY_ERROR, ActivityLog::TYPE_COMMUNICATION, 'CJ Tracking: not received', ['response' => $response]);
}
curl_close($curl);
}
}
/**
* @required
*/
public function setLanguageContext(LanguageContext $languageContext): void
{
$this->languageContext = $languageContext;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace KupShop\MarketingBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class ResponseListener implements EventSubscriberInterface
{
public const CJSessionKey = 'cjevent';
public static function getSubscribedEvents()
{
return [
KernelEvents::RESPONSE => [
['onKernelResponse', 100],
],
];
}
public function onKernelResponse(ResponseEvent $event): void
{
$request = $event->getRequest();
try {
$session = $request->getSession();
} catch (SessionNotFoundException $e) {
return;
}
$cookie_bar = $request->cookies->get('cookie-bar');
$consents = array_flip(explode(',', $cookie_bar ?? ''));
$isAnalyticsStorage = isset($consents['analytics_storage']);
if ($requestData = $request->get(self::CJSessionKey)) {
$session->set(self::CJSessionKey, $requestData);
}
if ($dmAfill = $request->get('transaction_id')) {
if (isset($consents['analytics_storage'])) {
$response = $event->getResponse();
$response->headers->setCookie(new Cookie('AP_tracker_TID', $dmAfill, time() + 120 * 24 * 60 * 60));
} else {
$session->set('AP_tracker_TID', $dmAfill);
}
}
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace KupShop\MarketingBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class MarketingBundle extends Bundle
{
}

View File

@@ -0,0 +1,3 @@
content:
resource: "@MarketingBundle/Controller/"
type: annotation

View File

@@ -0,0 +1,19 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: true
KupShop\MarketingBundle\SupportBox\InfoProvider:
class: KupShop\MarketingBundle\SupportBox\InfoProvider
autowire: true
KupShop\MarketingBundle\TrustedShop\TrustedShopProvider:
class: KupShop\MarketingBundle\TrustedShop\TrustedShopProvider
autowire: true
KupShop\MarketingBundle\Admin\Tabs\:
resource: ../../Admin/Tabs/*.php
KupShop\MarketingBundle\:
resource: ../../{Controller,EventListener}

View File

@@ -0,0 +1,42 @@
<?php
/*
* Smarty plugin
* -------------------------------------------------------------
* Type: function
* Name: eval
* Purpose: evaluate a template variable as a template
* -------------------------------------------------------------
*/
function smarty_function_get_cj_info($params, &$smarty)
{
$type = '';
$view = $params['view'];
if (!$view) {
return '';
}
if ($view instanceof \KupShop\ContentBundle\View\HomeView) {
$type = 'homepage';
} elseif ($view instanceof \KupShop\CatalogBundle\View\CategoryView) {
$type = 'category';
} elseif ($view instanceof \KupShop\ContentBundle\View\ProductView) {
$type = 'productDetail';
} elseif ($view instanceof \KupShop\ContentBundle\View\CartView) {
$type = 'cart';
} elseif ($view instanceof \KupShop\CatalogBundle\View\SearchView) {
$type = 'searchResults';
} elseif ($view instanceof \KupShop\UserBundle\View\UserView && $params['view']->newUser()) {
$type = 'accountSignup';
} elseif ($view instanceof \KupShop\UserBundle\View\LoginView) {
$type = 'accountCenter';
} elseif ($view instanceof \KupShop\ContentBundle\View\OrderView && $view->getTemplate() == 'ordering.success.tpl') {
$type = 'conversionConfirmation';
}
if (!empty($params['assign'])) {
$smarty->assign($params['assign'], $type);
} else {
return $type;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* Smarty plugin
* -------------------------------------------------------------
* Type: function
* Name: eval
* Purpose: evaluate a template variable as a template
* -------------------------------------------------------------
*/
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\MarketingBundle\TrustedShop\TrustedShopProvider;
function smarty_function_send_trusted_shop($params, &$smarty)
{
if (empty($params['order']) || empty($params['apikey'])) {
return;
}
$order = $params['order'];
if ($order->invoice_country == 'RO') {
$url = 'https://www.compari.ro/';
} elseif ($order->invoice_country == 'HU') {
$url = 'https://www.arukereso.hu/';
} else {
$url = 'https://api.etrusted.com';
}
$Client = ServiceContainer::getService(TrustedShopProvider::class);
$Client->setWebApiKey($params['apikey']);
$Client->setServiceUrlSend($url);
$Client->SetEmail($order->invoice_email);
foreach ($order->items as $item) {
if ($item['id_product']) {
$identifier = empty($item['id_variation']) ? $item['id_product'] : $item['id_product'].'_'.$item['id_variation'];
$Client->AddProduct($item['descr'], $identifier);
}
}
try {
echo $Client->Prepare();
} catch (Exception $e) {
$raven = getRaven();
$raven->captureException($e);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace KupShop\OrderDiscountBundle\Resources\upgrade;
use KupShop\I18nBundle\Translations\SettingsTranslation;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
class MarketingUpgrade extends \UpgradeNew
{
public function check_GoogleVerificationTransformed()
{
return $this->checkDataMigration(10);
}
/** Migrate google verification codes */
public function upgrade_GoogleVerificationTransformed()
{
$dbcfg = \Settings::getDefault();
$value = $dbcfg->loadValue('analytics');
if (!$value) {
$value = [];
}
$transformedCodes = [];
if (isset($value['google_site_verification'])) {
$explodedCode = explode(';', $value['google_site_verification']['id']);
foreach ($explodedCode as $code) {
if ($code != '') {
$transformedCodes[] = ['value' => $code, 'note' => ''];
}
}
// TODO: Odmazat nepotrebny settingy - google_site_verification;
}
foreach ($value['google_site_verifications'] ?? [] as $item) {
if ($item['value'] != '') {
$transformedCodes[] = $item;
}
}
$value['google_site_verifications'] = $transformedCodes;
$dbcfg->saveValue('analytics', $value);
\Settings::clearCache();
$this->commitDataMigration(10);
$this->upgradeOK();
}
public function check_GoogleVerificationTransformed2()
{
return $this->checkDataMigration(11);
}
/** Migrate google verification codes */
public function upgrade_GoogleVerificationTransformed2()
{
if (findModule(\Modules::CURRENCIES)) {
$languageContext = ServiceContainer::getService(LanguageContext::class);
$settingsTranslations = ServiceContainer::getService(SettingsTranslation::class);
foreach ($languageContext->getSupported() as $language) {
if ($language->getId() != $languageContext->getDefaultId()) {
$data = $settingsTranslations->setLanguageId($language->getId())->get();
if (!isset($data['analytics'])) {
continue;
}
$transformedCodes = [];
$value = $data['analytics'];
if (isset($value['google_site_verification'])) {
$explodedCode = explode(';', $value['google_site_verification']['id']);
foreach ($explodedCode as $code) {
if ($code != '') {
$transformedCodes[] = ['value' => $code, 'note' => ''];
}
}
// TODO: Odmazat nepotrebny settingy - google_site_verification;
}
// Do not save empty array
if (!$transformedCodes) {
continue;
}
$value['google_site_verifications'] = $transformedCodes;
$settingsTranslations->save(['analytics' => $value]);
}
}
}
\Settings::clearCache();
$this->commitDataMigration(11);
$this->upgradeOK();
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace KupShop\OrderDiscountBundle\Resources\upgrade;
use KupShop\CatalogBundle\Util\ReviewsUtil;
class ReviewsUpgrade extends \UpgradeNew
{
protected function isAllowed()
{
return findModule(\Modules::REVIEWS);
}
public function check_heurekaIDNameType()
{
return !$this->checkColumnExists('reviews', 'heureka_id');
}
/** Change name of column heureka_id in reviews to external_id and type from int(11) to varchar(32)*/
public function upgrade_heurekaIDNameType()
{
sqlQuery('ALTER TABLE reviews CHANGE heureka_id external_id VARCHAR(32) NULL');
$this->upgradeOK();
}
public function check_externalId()
{
return $this->checkColumnExists('reviews', 'external_id');
}
/** Add external_id column to table reviews */
public function upgrade_externalId()
{
sqlQuery('ALTER TABLE reviews ADD COLUMN external_id VARCHAR(50) NULL UNIQUE;');
$this->upgradeOK();
}
public function check_externalIdType()
{
return $this->checkColumnType('reviews', 'external_id', 'VARCHAR(50)');
}
/** Increase reviews.external_id length */
public function upgrade_externalIdType()
{
sqlQuery('ALTER TABLE reviews CHANGE COLUMN external_id external_id VARCHAR(50) NULL');
$this->upgradeOK();
}
public function check_DataColumn()
{
return $this->checkColumnExists('reviews', 'data');
}
/** Add data column into reviews */
public function upgrade_DataColumn()
{
sqlQuery('ALTER TABLE reviews ADD COLUMN data MEDIUMTEXT DEFAULT NULL');
$this->upgradeOK();
}
public function check_reportSeverity()
{
return $this->checkColumnExists('reviews', 'source');
}
/** Add source to reviews */
public function upgrade_reportSeverity()
{
sqlQuery("alter table reviews add source enum('heureka.cz', 'heureka.sk', 'arukereso.hu', 'compari.ro', 'zbozi.cz') NULL;");
sqlQuery('create index report_reviews_security_index ON reviews (source);');
$this->updateReviewSource();
$this->upgradeOK();
}
public function check_ReviewsSource()
{
return $this->checkEnumOptions('reviews', 'source', array_keys(ReviewsUtil::$sources));
}
/** reviews: Add 'source' column enum */
public function upgrade_ReviewsSource()
{
$this->updateEnumOptions(
'reviews',
'source',
array_keys(ReviewsUtil::$sources));
$this->upgradeOK();
}
public function check_ReviewsSourceUpToDate()
{
return $this->checkDataMigration(21);
}
/** Reviews: Update source column */
public function upgrade_ReviewsSourceUpToDate()
{
$this->updateReviewSource();
$this->commitDataMigration(21);
$this->upgradeOK();
}
protected function updateReviewSource(): void
{
if (findModule(\Modules::TRANSLATIONS)) {
sqlQuery("UPDATE reviews SET source='heureka.cz' WHERE external_id REGEXP '^[0-9]+$' AND id_language = 'cs' AND source IS NULL");
sqlQuery("UPDATE reviews SET source='heureka.sk' WHERE external_id REGEXP '^[0-9]+$' AND id_language = 'sk' AND source IS NULL");
sqlQuery("UPDATE reviews SET source='zbozi.cz' WHERE external_id NOT REGEXP '^[0-9]+$' AND id_language = 'cs' AND source IS NULL");
sqlQuery("UPDATE reviews SET source='arukereso.hu' WHERE external_id LIKE ('hu_%') AND id_language = 'hu' AND source IS NULL");
sqlQuery("UPDATE reviews SET source='compari.ro' WHERE external_id LIKE ('ro_%') AND id_language = 'ro' AND source IS NULL");
} else {
sqlQuery("UPDATE reviews SET source='heureka.cz' WHERE external_id REGEXP '^[0-9]+$' AND source IS NULL");
sqlQuery("UPDATE reviews SET source='zbozi.cz' WHERE external_id NOT REGEXP '^[0-9]+$' AND source IS NULL");
}
}
public function check_ReviewResponse()
{
return $this->checkColumnExists('reviews', 'response');
}
/** Add review response */
public function upgrade_ReviewResponse()
{
sqlQuery('ALTER TABLE reviews ADD COLUMN response MEDIUMTEXT NULL;');
sqlQuery('ALTER TABLE reviews ADD COLUMN response_time DATETIME NULL;');
$this->upgradeOK();
}
public function check_ReviewSaleIdColumn(): bool
{
return findModule(\Modules::SALES) && $this->checkColumnExists('reviews', 'id_sale');
}
/** Add reviews.id_sale column */
public function upgrade_ReviewSaleIdColumn(): void
{
sqlQuery('ALTER TABLE reviews ADD COLUMN id_sale INT(11) UNSIGNED NULL AFTER id_order');
sqlQuery('ALTER TABLE reviews ADD CONSTRAINT FK_reviews_id_sale FOREIGN KEY (id_sale) REFERENCES sales(id) ON UPDATE CASCADE ON DELETE CASCADE');
$this->upgradeOK();
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace KupShop\MarketingBundle\SupportBox;
use Query\Operator;
class InfoProvider
{
public function getAdditionalData(?string $email, ?string $phone): array
{
$data = [];
if ($email) {
$data['user'] = (new \User())->createFromLogin($email);
} elseif ($phone) {
$phone = str_replace(' ', '', $phone);
$data['user'] = \User::createFromSpec(Operator::like(['phone' => '%'.$phone.'%']));
}
$data = array_merge($data, $this->provideOrdersData($email, $phone));
$data = array_merge($data, $this->provideSalesData($email, $phone));
$smarty = createSmarty(true, true);
$smarty->assign($data);
$result['html'] = $smarty->fetch('supportBox/userInfo.tpl');
return $result;
}
public function getName($phone)
{
$phone = str_replace(' ', '', $phone);
$name = sqlQueryBuilder()
->select('CONCAT_WS(" ", name, surname)')
->from('users')
->where(Operator::like(['phone' => '%'.$phone.'%']))
->orderBy('id', 'DESC')
->setMaxResults(1)
->execute()->fetchColumn();
if (!$name) {
$name = sqlQueryBuilder()
->select('CONCAT_WS(" ", invoice_name, invoice_surname)')
->from('orders')
->where(Operator::like(['invoice_phone' => '%'.$phone.'%']))
->orderBy('id', 'DESC')
->setMaxResults(1)
->execute()->fetchColumn();
}
if (!$name) {
return [];
}
return [
'name' => $name,
];
}
protected function provideOrdersData(?string $email, ?string $phone): array
{
$result = [];
$orX = [];
if ($email) {
$orX[] = Operator::like(['invoice_email' => $email]);
}
if ($phone) {
$orX[] = Operator::like(['invoice_phone' => $phone]);
}
$result['total_orders_price'] = sqlQueryBuilder()->select('SUM(total_price*currency_rate)')
->from('orders')
->where(Operator::orX($orX))
->andWhere('status_storno!=1')
->execute()->fetchOne();
$ordersQb = sqlQueryBuilder()
->select('*')
->from('orders', 'o')
->orderBy('o.id', 'DESC')
->setMaxResults(2)
->where(Operator::orX($orX));
foreach ($ordersQb->execute() as $order) {
$order['order_status'] = getOrderStatus($order['status'])['name'];
$order['items'] = sqlQueryBuilder()
->select('*')
->from('order_items')
->where(Operator::equals(['id_order' => $order['id']]))
->execute()->fetchAllAssociative();
$result['orders'][] = $order;
}
return $result;
}
protected function provideSalesData(?string $email, ?string $phone): array
{
if (!findModule(\Modules::SALES)) {
return [];
}
$result = [];
$orX = [];
if ($email) {
$orX[] = Operator::like(['u.email' => $email]);
}
if ($phone) {
$orX[] = Operator::like(['u.phone' => $phone]);
}
$baseQb = sqlQueryBuilder()
->select('s.*, sell.title as seller_name')
->from('sales', 's')
->leftJoin('s', 'users', 'u', 'u.id = s.id_user')
->leftJoin('s', 'sellers', 'sell', 'sell.id = s.id_seller');
$result['total_sales_price'] = (clone $baseQb)
->select('SUM(total_price)')
->where(Operator::orX($orX))
->execute()->fetchOne();
$salesQb = (clone $baseQb)
->where(Operator::orX($orX));
foreach ($salesQb->execute() as $sale) {
$sale['items'] = sqlQueryBuilder()
->select('*')
->from('sales_items')
->where(Operator::equals(['id_sale' => $sale['id']]))
->execute()->fetchAllAssociative();
$result['sales'][] = $sale;
}
return $result;
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace KupShop\MarketingBundle\TrustedShop;
/** Trusted Shop engine class, which provides purchase data sending.
* Requires PHP version 5.3.0 or higher.
* Requires PHP curl package (php5-curl, libcurl, curl).
*
* @version 2.0
*
* @author Árukereső.hu 2016 */
class TrustedShopProvider
{
public const VERSION = '2.0/PHP';
protected $service_url_send = 'https://www.compari.ro/';
public const SERVICE_URL_AKU = 'https://assets.arukereso.com/aku.min.js';
public const SERVICE_TOKEN_REQUEST = 't2/TokenRequest.php';
public const SERVICE_TOKEN_PROCESS = 't2/TrustedShop.php';
public const ERROR_EMPTY_EMAIL = 'Customer e-mail address is empty.';
public const ERROR_EMPTY_WEBAPIKEY = 'Partner WebApiKey is empty.';
public const ERROR_EXAMPLE_EMAIL = 'Customer e-mail address has been not changed yet.';
public const ERROR_EXAMPLE_PRODUCT = 'Product name has been not changed yet.';
public const ERROR_TOKEN_REQUEST_TIMED_OUT = 'Token request timed out.';
public const ERROR_TOKEN_REQUEST_FAILED = 'Token request failed.';
public const ERROR_TOKEN_BAD_REQUEST = 'Bad request: ';
protected $WebApiKey;
protected $Email;
protected $Products = [];
/** Sets the customer's e-mail address.
* @param string $Email - Current customer's e-mail address. */
public function SetEmail($Email)
{
$this->Email = $Email;
}
/** Adds a product to send. Callable multiple times.
* @param string $ProductName - A product name from the customer's cart
* @param string $ProductId - A product id, it must be same as in the feed. */
public function AddProduct($ProductName, $ProductId = null)
{
$Content = [];
$Content['Name'] = $ProductName;
if (!empty($ProductId)) {
$Content['Id'] = $ProductId;
}
$this->Products[] = $Content;
}
/** Prepares the Trusted code, which provides data sending from the customer's browser to us.
* @return string - Prepared Trusted code (HTML). */
public function Prepare()
{
if (empty($this->WebApiKey)) {
throw new \Exception(self::ERROR_EMPTY_WEBAPIKEY);
}
if (empty($this->Email)) {
throw new \Exception(self::ERROR_EMPTY_EMAIL);
}
if ($this->Email == 'somebody@example.com') {
throw new \Exception(self::ERROR_EXAMPLE_EMAIL);
}
$Examples = ['Name of first purchased product', 'Name of second purchased product'];
foreach ($Examples as $Example) {
foreach ($this->Products as $Product) {
if ($Product['Name'] == $Example) {
throw new \Exception(self::ERROR_EXAMPLE_PRODUCT);
}
}
}
$Params = [];
$Params['Version'] = self::VERSION;
$Params['WebApiKey'] = $this->WebApiKey;
$Params['Email'] = $this->Email;
$Params['Products'] = json_encode($this->Products);
$Random = md5($this->WebApiKey.microtime());
$Query = $this->GetQuery($Params);
// Sending:
$Output = '<script type="text/javascript">window.aku_request_done = function(w, c) {';
$Output .= 'var I = new Image(); I.src="'.$this->service_url_send.self::SERVICE_TOKEN_PROCESS.$Query.'" + c;';
$Output .= '};</script>';
// Include:
$Output .= '<script type="text/javascript"> (function() {';
$Output .= 'var a=document.createElement("script"); a.type="text/javascript"; a.src="'.self::SERVICE_URL_AKU.'"; a.async=true;';
$Output .= '(document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]).appendChild(a);';
$Output .= '})();</script>';
// Fallback:
$Output .= '<noscript>';
$Output .= '<img src="'.$this->service_url_send.self::SERVICE_TOKEN_PROCESS.$Query.$Random.'" />';
$Output .= '</noscript>';
return $Output;
}
/** Performs a request on our servers to get a token and assembles query params with it.
* @param array $Params - Parameters to send with token request
*
* @return string - Query string to assemble sending code snipet on client's side with it. */
protected function GetQuery($Params)
{
// Prepare curl request:
$Curl = curl_init();
curl_setopt($Curl, CURLOPT_URL, $this->service_url_send.self::SERVICE_TOKEN_REQUEST);
curl_setopt($Curl, CURLOPT_POST, 1);
curl_setopt($Curl, CURLOPT_POSTFIELDS, http_build_query($Params));
curl_setopt($Curl, CURLOPT_CONNECTTIMEOUT_MS, 500);
curl_setopt($Curl, CURLOPT_TIMEOUT_MS, 500);
curl_setopt($Curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($Curl, CURLOPT_HEADER, true);
// Execute the request:
$Response = curl_exec($Curl);
if (curl_errno($Curl) === 0 && $Response !== false) {
$Info = curl_getinfo($Curl);
$StatusCode = $Info['http_code'];
$JsonBody = substr($Response, $Info['header_size']);
$JsonArray = json_decode($JsonBody, true);
$JsonError = json_last_error();
curl_close($Curl);
if (empty($JsonError)) {
if ($StatusCode == 200) {
$Query = [];
$Query[] = 'Token='.$JsonArray['Token'];
$Query[] = 'WebApiKey='.$this->WebApiKey;
$Query[] = 'C=';
return '?'.join('&', $Query);
} elseif ($StatusCode == 400) {
throw new \Exception(self::ERROR_TOKEN_BAD_REQUEST.$JsonArray['ErrorCode'].' - '.$JsonArray['ErrorMessage']);
} else {
throw new \Exception(self::ERROR_TOKEN_REQUEST_FAILED);
}
} else {
throw new \Exception('Json error: '.$JsonError);
}
} else {
return '';
}
return null;
}
public function setWebApiKey(string $WebApiKey)
{
$this->WebApiKey = $WebApiKey;
}
public function setServiceUrlSend(string $service_url_send)
{
$this->service_url_send = $service_url_send;
}
}