[11/06/2024] Les premières modifs + installation de quelques modules indispensables
This commit is contained in:
5
domokits/.gitignore
vendored
5
domokits/.gitignore
vendored
@@ -17,7 +17,6 @@
|
||||
/templates/pdf/default
|
||||
|
||||
# Thelia config
|
||||
/local/config
|
||||
/local/setup
|
||||
/local/media
|
||||
/local/session
|
||||
@@ -25,9 +24,6 @@
|
||||
!local/media/images/store/thelia.svg
|
||||
!local/media/images/store/banner.png
|
||||
|
||||
# Thelia modules
|
||||
/local/modules/*
|
||||
|
||||
### Please add your dependancies here
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
@@ -46,3 +42,4 @@
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
###< symfony/webpack-encore-bundle ###
|
||||
/log/
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"thelia/thelia-library-module": "^1.1.7",
|
||||
"thelia/product-loop-attribute-filter-module": "~2.0.0",
|
||||
"thelia/reset-password-module": "~1.0.1",
|
||||
"thelia/re-captcha-module": "~3.0.1"
|
||||
"thelia/re-captcha-module": "~3.0.1",
|
||||
"stripe/stripe-php": "6.*"
|
||||
},
|
||||
"suggest": {
|
||||
"vlopes/maintenance-module": "Add a way to put your site in maintenance mode",
|
||||
|
||||
7
domokits/local/modules/BetterSeo/.github/workflows/release.yml
vendored
Normal file
7
domokits/local/modules/BetterSeo/.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
name: "Auto Release"
|
||||
on:
|
||||
push:
|
||||
branches: [ master, main ]
|
||||
jobs:
|
||||
release:
|
||||
uses: thelia-modules/ReusableWorkflow/.github/workflows/auto_release.yml@main
|
||||
77
domokits/local/modules/BetterSeo/BetterSeo.php
Normal file
77
domokits/local/modules/BetterSeo/BetterSeo.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/*************************************************************************************/
|
||||
/* This file is part of the Thelia package. */
|
||||
/* */
|
||||
/* Copyright (c) OpenStudio */
|
||||
/* email : dev@thelia.net */
|
||||
/* web : http://www.thelia.net */
|
||||
/* */
|
||||
/* For the full copyright and license information, please view the LICENSE.txt */
|
||||
/* file that was distributed with this source code. */
|
||||
/*************************************************************************************/
|
||||
|
||||
namespace BetterSeo;
|
||||
|
||||
use BetterSeo\Model\BetterSeoQuery;
|
||||
use Propel\Runtime\Connection\ConnectionInterface;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Thelia\Install\Database;
|
||||
use Thelia\Module\BaseModule;
|
||||
|
||||
class BetterSeo extends BaseModule
|
||||
{
|
||||
/** @var string */
|
||||
const DOMAIN_NAME = 'betterseo.bo.default';
|
||||
|
||||
/**
|
||||
* @param ConnectionInterface|null $con
|
||||
* @throws \Propel\Runtime\Exception\PropelException
|
||||
*/
|
||||
public function postActivation(ConnectionInterface $con = null):void
|
||||
{
|
||||
if (!self::getConfigValue('is_initialized',null)){
|
||||
$database = new Database($con);
|
||||
$database->insertSql(null, [__DIR__ . "/Config/thelia.sql"]);
|
||||
self::setConfigValue('is_initialized', 1);
|
||||
}
|
||||
}
|
||||
|
||||
public function update($currentVersion, $newVersion, ConnectionInterface $con = null):void
|
||||
{
|
||||
$sqlToExecute = [];
|
||||
$finder = new Finder();
|
||||
$sort = function (\SplFileInfo $a, \SplFileInfo $b) {
|
||||
$a = strtolower(substr($a->getRelativePathname(), 0, -4));
|
||||
$b = strtolower(substr($b->getRelativePathname(), 0, -4));
|
||||
return version_compare($a, $b);
|
||||
};
|
||||
|
||||
$files = $finder->name('*.sql')
|
||||
->in(__DIR__ . "/Config/Update/")
|
||||
->sort($sort);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (version_compare($file->getFilename(), $currentVersion, ">")) {
|
||||
$sqlToExecute[$file->getFilename()] = $file->getRealPath();
|
||||
}
|
||||
}
|
||||
|
||||
$database = new Database($con);
|
||||
|
||||
foreach ($sqlToExecute as $version => $sql) {
|
||||
$database->insertSql(null, [$sql]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how services are loaded in your modules.
|
||||
*/
|
||||
public static function configureServices(ServicesConfigurator $servicesConfigurator): void
|
||||
{
|
||||
$servicesConfigurator->load(self::getModuleCode().'\\', __DIR__)
|
||||
->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*'])
|
||||
->autowire(true)
|
||||
->autoconfigure(true);
|
||||
}
|
||||
}
|
||||
1
domokits/local/modules/BetterSeo/Config/Update/1.1.0.sql
Normal file
1
domokits/local/modules/BetterSeo/Config/Update/1.1.0.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `better_seo_i18n` ADD `h1` TEXT NOT NULL AFTER `canonical_field`;
|
||||
1
domokits/local/modules/BetterSeo/Config/Update/1.2.0.sql
Normal file
1
domokits/local/modules/BetterSeo/Config/Update/1.2.0.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `better_seo_i18n` ADD `mesh_text_1` TEXT NOT NULL AFTER `h1`, ADD `mesh_url_1` TEXT NOT NULL AFTER `mesh_text_1`, ADD `mesh_text_2` TEXT NOT NULL AFTER `mesh_url_1`, ADD `mesh_url_2` TEXT NOT NULL AFTER `mesh_text_2`, ADD `mesh_text_3` TEXT NOT NULL AFTER `mesh_url_2`, ADD `mesh_url_3` TEXT NOT NULL AFTER `mesh_text_3`, ADD `mesh_text_4` TEXT NOT NULL AFTER `mesh_url_3`, ADD `mesh_url_4` TEXT NOT NULL AFTER `mesh_text_4`, ADD `mesh_text_5` TEXT NOT NULL AFTER `mesh_url_4`, ADD `mesh_url_5` TEXT NOT NULL AFTER `mesh_text_5`;
|
||||
10
domokits/local/modules/BetterSeo/Config/Update/1.2.1.sql
Normal file
10
domokits/local/modules/BetterSeo/Config/Update/1.2.1.sql
Normal file
@@ -0,0 +1,10 @@
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_text_1` `mesh_text_1` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_url_1` `mesh_url_1` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_text_2` `mesh_text_2` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_url_2` `mesh_url_2` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_text_3` `mesh_text_3` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_url_3` `mesh_url_3` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_text_4` `mesh_text_4` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_url_4` `mesh_url_4` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_text_5` `mesh_text_5` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_url_5` `mesh_url_5` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
6
domokits/local/modules/BetterSeo/Config/Update/1.3.0.sql
Normal file
6
domokits/local/modules/BetterSeo/Config/Update/1.3.0.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE `better_seo_i18n`
|
||||
ADD `mesh_1` TEXT NOT NULL AFTER `mesh_url_5`,
|
||||
ADD `mesh_2` TEXT NOT NULL AFTER `mesh_1`,
|
||||
ADD `mesh_3` TEXT NOT NULL AFTER `mesh_2`,
|
||||
ADD `mesh_4` TEXT NOT NULL AFTER `mesh_3`,
|
||||
ADD `mesh_5` TEXT NOT NULL AFTER `mesh_4`;
|
||||
5
domokits/local/modules/BetterSeo/Config/Update/1.3.1.sql
Normal file
5
domokits/local/modules/BetterSeo/Config/Update/1.3.1.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_1` `mesh_1` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_2` `mesh_2` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_3` `mesh_3` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_4` `mesh_4` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
ALTER TABLE `better_seo_i18n` CHANGE `mesh_5` `mesh_5` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL;
|
||||
1
domokits/local/modules/BetterSeo/Config/Update/1.4.0.sql
Normal file
1
domokits/local/modules/BetterSeo/Config/Update/1.4.0.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `better_seo_i18n` ADD `json_data` TEXT NOT NULL AFTER `mesh_5`;
|
||||
20
domokits/local/modules/BetterSeo/Config/config.xml
Normal file
20
domokits/local/modules/BetterSeo/Config/config.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<config xmlns="http://thelia.net/schema/dic/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://thelia.net/schema/dic/config http://thelia.net/schema/dic/config/thelia-1.0.xsd">
|
||||
<forms>
|
||||
<form name="betterseo_form" class="BetterSeo\Form\BetterSeoForm" />
|
||||
</forms>
|
||||
|
||||
<hooks>
|
||||
<hook id="betterseo.addfields.hook" class="BetterSeo\Hook\SeoFormHook">
|
||||
<tag name="hook.event_listener" event="tab-seo.bottom" type="back" method="onTabSeoUpdateForm" />
|
||||
</hook>
|
||||
<hook id="betterseo.meta.hook" class="BetterSeo\Hook\MetaHook" scope="request">
|
||||
<tag name="hook.event_listener" event="main.head-bottom" type="front" method="onMainHeadBottom" />
|
||||
<argument type="service" id="request_stack" />
|
||||
</hook>
|
||||
</hooks>
|
||||
|
||||
</config>
|
||||
32
domokits/local/modules/BetterSeo/Config/module.xml
Normal file
32
domokits/local/modules/BetterSeo/Config/module.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module xmlns="http://thelia.net/schema/dic/module"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://thelia.net/schema/dic/module http://thelia.net/schema/dic/module/module-2_2.xsd">
|
||||
<fullnamespace>BetterSeo\BetterSeo</fullnamespace>
|
||||
<descriptive locale="en_US">
|
||||
<title>Set noindex, nofollow, h1, tag on pages, and manage mesh links</title>
|
||||
</descriptive>
|
||||
<descriptive locale="fr_FR">
|
||||
<title>Ajoute la balise noindex, nofollow, h1, sur les pages, plus gestion des liens maillés</title>
|
||||
</descriptive>
|
||||
<languages>
|
||||
<language>en_US</language>
|
||||
<language>fr_FR</language>
|
||||
</languages>
|
||||
<version>2.1.2</version>
|
||||
<authors>
|
||||
<author>
|
||||
<name>Nicolas Barbey</name>
|
||||
<email>nabrbey@openstudio.fr</email>
|
||||
</author>
|
||||
<author>
|
||||
<name>Gilles Bourgeat</name>
|
||||
<email>gilles.bourgeat@gmail.com</email>
|
||||
</author>
|
||||
</authors>
|
||||
<type>classic</type>
|
||||
<thelia>2.5.0</thelia>
|
||||
<stability>rc</stability>
|
||||
<mandatory>0</mandatory>
|
||||
<hidden>0</hidden>
|
||||
</module>
|
||||
10
domokits/local/modules/BetterSeo/Config/routing.xml
Normal file
10
domokits/local/modules/BetterSeo/Config/routing.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<routes xmlns="http://symfony.com/schema/routing"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
|
||||
|
||||
<route id="betterseo.save" path="/admin/module/betterseo/save">
|
||||
<default key="_controller">BetterSeo\Controller\BetterSeoController::saveAction</default>
|
||||
</route>
|
||||
</routes>
|
||||
56
domokits/local/modules/BetterSeo/Config/schema.xml
Normal file
56
domokits/local/modules/BetterSeo/Config/schema.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<database defaultIdMethod="native" name="TheliaMain"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../../vendor/thelia/propel/resources/xsd/database.xsd" >
|
||||
|
||||
<table name="better_seo" namespace="BetterSeo\Model">
|
||||
<column autoIncrement="true" name="id" primaryKey="true" required="true" type="INTEGER" />
|
||||
<column name="object_id" required="true" type="INTEGER"/>
|
||||
<column name="object_type" required="true" />
|
||||
<column name="noindex" required="true" default="0" type="TINYINT" size="4" />
|
||||
<column name="nofollow" required="true" default="0" type="TINYINT" size="4" />
|
||||
<column name="canonical_field" type="LONGVARCHAR" />
|
||||
<column name="h1" type="LONGVARCHAR" />
|
||||
<column name="mesh_text_1" type="LONGVARCHAR" />
|
||||
<column name="mesh_url_1" type="LONGVARCHAR" />
|
||||
<column name="mesh_text_2" type="LONGVARCHAR" />
|
||||
<column name="mesh_url_2" type="LONGVARCHAR" />
|
||||
<column name="mesh_text_3" type="LONGVARCHAR" />
|
||||
<column name="mesh_url_3" type="LONGVARCHAR" />
|
||||
<column name="mesh_text_4" type="LONGVARCHAR" />
|
||||
<column name="mesh_url_4" type="LONGVARCHAR" />
|
||||
<column name="mesh_text_5" type="LONGVARCHAR" />
|
||||
<column name="mesh_url_5" type="LONGVARCHAR" />
|
||||
<column name="mesh_1" type="LONGVARCHAR" />
|
||||
<column name="mesh_2" type="LONGVARCHAR" />
|
||||
<column name="mesh_3" type="LONGVARCHAR" />
|
||||
<column name="mesh_4" type="LONGVARCHAR" />
|
||||
<column name="mesh_5" type="LONGVARCHAR" />
|
||||
<column name="json_data" type="LONGVARCHAR"/>
|
||||
<behavior name="i18n">
|
||||
<parameter name="i18n_columns"
|
||||
value="noindex,
|
||||
nofollow,
|
||||
canonical_field,
|
||||
h1,
|
||||
mesh_text_1,
|
||||
mesh_url_1,
|
||||
mesh_text_2,
|
||||
mesh_url_2,
|
||||
mesh_text_3,
|
||||
mesh_url_3,
|
||||
mesh_text_4,
|
||||
mesh_url_4,
|
||||
mesh_text_5,
|
||||
mesh_url_5,
|
||||
mesh_1,
|
||||
mesh_2,
|
||||
mesh_3,
|
||||
mesh_4,
|
||||
mesh_5,
|
||||
json_data"
|
||||
/>
|
||||
</behavior>
|
||||
</table>
|
||||
<external-schema filename="local/config/schema.xml" referenceOnly="true" />
|
||||
</database>
|
||||
2
domokits/local/modules/BetterSeo/Config/sqldb.map
Normal file
2
domokits/local/modules/BetterSeo/Config/sqldb.map
Normal file
@@ -0,0 +1,2 @@
|
||||
# Sqlfile -> Database map
|
||||
thelia.sql=thelia
|
||||
58
domokits/local/modules/BetterSeo/Config/thelia.sql
Normal file
58
domokits/local/modules/BetterSeo/Config/thelia.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
# This is a fix for InnoDB in MySQL >= 4.1.x
|
||||
# It "suspends judgement" for fkey relationships until are tables are set.
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- better_seo
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
DROP TABLE IF EXISTS `better_seo`;
|
||||
|
||||
CREATE TABLE `better_seo`
|
||||
(
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`object_id` INTEGER NOT NULL,
|
||||
`object_type` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- better_seo_i18n
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
DROP TABLE IF EXISTS `better_seo_i18n`;
|
||||
|
||||
CREATE TABLE `better_seo_i18n`
|
||||
(
|
||||
`id` INTEGER NOT NULL,
|
||||
`locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL,
|
||||
`noindex` TINYINT(4) DEFAULT 0 NOT NULL,
|
||||
`nofollow` TINYINT(4) DEFAULT 0 NOT NULL,
|
||||
`canonical_field` TEXT,
|
||||
`h1` TEXT,
|
||||
`mesh_text_1` TEXT,
|
||||
`mesh_url_1` TEXT,
|
||||
`mesh_text_2` TEXT,
|
||||
`mesh_url_2` TEXT,
|
||||
`mesh_text_3` TEXT,
|
||||
`mesh_url_3` TEXT,
|
||||
`mesh_text_4` TEXT,
|
||||
`mesh_url_4` TEXT,
|
||||
`mesh_text_5` TEXT,
|
||||
`mesh_url_5` TEXT,
|
||||
`mesh_1` TEXT,
|
||||
`mesh_2` TEXT,
|
||||
`mesh_3` TEXT,
|
||||
`mesh_4` TEXT,
|
||||
`mesh_5` TEXT,
|
||||
`json_data` TEXT,
|
||||
PRIMARY KEY (`id`,`locale`),
|
||||
CONSTRAINT `better_seo_i18n_FK_1`
|
||||
FOREIGN KEY (`id`)
|
||||
REFERENCES `better_seo` (`id`)
|
||||
ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
# This restores the fkey checks, after having unset them earlier
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace BetterSeo\Controller;
|
||||
|
||||
use BetterSeo\Form\BetterSeoForm;
|
||||
use BetterSeo\Model\BetterSeo;
|
||||
use BetterSeo\Model\BetterSeoQuery;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Thelia\Controller\Admin\BaseAdminController;
|
||||
use Thelia\Model\LangQuery;
|
||||
use Thelia\Tools\URL;
|
||||
|
||||
class BetterSeoController extends BaseAdminController
|
||||
{
|
||||
/**
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Propel\Runtime\Exception\PropelException
|
||||
*/
|
||||
public function saveAction(Request $request)
|
||||
{
|
||||
$form = $this->createForm(BetterSeoForm::getName());
|
||||
|
||||
$seoForm = $this->validateForm($form);
|
||||
|
||||
$object_id = $request->get('object_id');
|
||||
$object_type = $request->get('object_type');
|
||||
|
||||
$lang = LangQuery::create()
|
||||
->filterById($request->get('lang_id'))
|
||||
->findOne();
|
||||
|
||||
if (null === $objectSeo = BetterSeoQuery::create()
|
||||
->filterByObjectId($object_id)
|
||||
->filterByObjectType($object_type)
|
||||
->findOne()
|
||||
) {
|
||||
$objectSeo = (new BetterSeo())
|
||||
->setObjectId($object_id)
|
||||
->setObjectType($object_type);
|
||||
}
|
||||
|
||||
$objectSeo
|
||||
->setLocale($lang->getLocale())
|
||||
->setJsonData($seoForm->get('json_data')->getData())
|
||||
->setNoindex(null === $seoForm->get('noindex_checkbox')->getData() ? 0 : 1)
|
||||
->setNofollow(null === $seoForm->get('nofollow_checkbox')->getData() ? 0 : 1)
|
||||
->setH1(null === $seoForm->get('h1')->getData() ? '' : $seoForm->get('h1')->getData());
|
||||
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
call_user_func([$objectSeo, 'setMeshUrl' . $i], $seoForm->get('mesh_url_' . $i)->getData());
|
||||
call_user_func([$objectSeo, 'setMeshText' . $i], $seoForm->get('mesh_text_' . $i)->getData());
|
||||
call_user_func([$objectSeo, 'setMesh' . $i], $seoForm->get('mesh_' . $i)->getData());
|
||||
}
|
||||
|
||||
$objectSeo->save();
|
||||
|
||||
return $this->generateRedirect(
|
||||
URL::getInstance()->absoluteUrl(
|
||||
$request->getSession()->getReturnToUrl(),
|
||||
['current_tab' => 'seo']
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace BetterSeo\EventListeners;
|
||||
|
||||
use AlternateHreflang\Event\AlternateHreflangEvent;
|
||||
use BetterSeo\Model\BetterSeoQuery;
|
||||
use CanonicalUrl\Event\CanonicalUrlEvent;
|
||||
use CanonicalUrl\Event\CanonicalUrlEvents;
|
||||
use Sitemap\Event\SitemapEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Thelia\Core\HttpFoundation\Request;
|
||||
|
||||
class SeoListener implements EventSubscriberInterface
|
||||
{
|
||||
/** @var Request */
|
||||
protected $request;
|
||||
|
||||
public function __construct(RequestStack $requestStack)
|
||||
{
|
||||
$this->request = $requestStack->getCurrentRequest();
|
||||
}
|
||||
|
||||
public function removeHrefLang(AlternateHreflangEvent $event)
|
||||
{
|
||||
$objectType = $this->request->get('_view');
|
||||
$objectId = $this->request->get($objectType.'_id');
|
||||
|
||||
$betterSeoObject = $this->getBetterSeoObject($objectType, $objectId);
|
||||
}
|
||||
|
||||
public function checkSiteMap(SitemapEvent $event)
|
||||
{
|
||||
$objectId = $event->getRewritingUrl()->getViewId();
|
||||
$objectType = $event->getRewritingUrl()->getView();
|
||||
|
||||
$betterSeoObject = $this->getBetterSeoObject($objectType, $objectId);
|
||||
|
||||
if (null !== $betterSeoObject){
|
||||
if ($betterSeoObject->getNoindex() === 1){
|
||||
$event->setHide(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
$events = [];
|
||||
if (class_exists('Sitemap\Event\SitemapEvent')){
|
||||
$events[SitemapEvent::SITEMAP_EVENT] = ['checkSiteMap',128];
|
||||
}
|
||||
if (class_exists('AlternateHreflang\Event\AlternateHreflangEvent')){
|
||||
$events[AlternateHreflangEvent::BASE_EVENT_NAME] = ['removeHrefLang',128];
|
||||
}
|
||||
return $events;
|
||||
}
|
||||
|
||||
protected function getBetterSeoObject($objectType, $objectId)
|
||||
{
|
||||
$lang = $this->request->getSession()->getLang()->getLocale();
|
||||
|
||||
$betterSeoObject = BetterSeoQuery::create()
|
||||
->filterByObjectType($objectType)
|
||||
->filterByObjectId($objectId)
|
||||
->findOne();
|
||||
if (null !== $betterSeoObject){
|
||||
$betterSeoObject->setLocale($lang);
|
||||
}
|
||||
|
||||
return $betterSeoObject;
|
||||
}
|
||||
}
|
||||
136
domokits/local/modules/BetterSeo/Form/BetterSeoForm.php
Normal file
136
domokits/local/modules/BetterSeo/Form/BetterSeoForm.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace BetterSeo\Form;
|
||||
|
||||
use BetterSeo\BetterSeo;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\UrlType;
|
||||
use Thelia\Core\Translation\Translator;
|
||||
use Thelia\Form\BaseForm;
|
||||
use Thelia\Type\JsonType;
|
||||
|
||||
class BetterSeoForm extends BaseForm
|
||||
{
|
||||
protected function buildForm()
|
||||
{
|
||||
|
||||
$form = $this->formBuilder;
|
||||
$form
|
||||
->add(
|
||||
'noindex_checkbox',
|
||||
IntegerType::class,
|
||||
array(
|
||||
'required' => false,
|
||||
'label' => Translator::getInstance()->trans(
|
||||
'noindex',
|
||||
array(),
|
||||
BetterSeo::DOMAIN_NAME
|
||||
),
|
||||
'label_attr' => array(
|
||||
'for' => 'noindex_checkbox'
|
||||
)
|
||||
)
|
||||
)
|
||||
->add(
|
||||
'nofollow_checkbox',
|
||||
IntegerType::class,
|
||||
array(
|
||||
'required' => false,
|
||||
'label' => Translator::getInstance()->trans(
|
||||
'nofollow',
|
||||
array(),
|
||||
BetterSeo::DOMAIN_NAME
|
||||
),
|
||||
'label_attr' => array(
|
||||
'for' => 'nofollow_checkbox'
|
||||
)
|
||||
)
|
||||
)
|
||||
->add(
|
||||
'h1',
|
||||
TextType::class,
|
||||
array(
|
||||
'required' => false,
|
||||
'label' => Translator::getInstance()->trans(
|
||||
'h1',
|
||||
array(),
|
||||
BetterSeo::DOMAIN_NAME
|
||||
),
|
||||
'label_attr' => array(
|
||||
'for' => 'h1'
|
||||
)
|
||||
)
|
||||
)
|
||||
->add(
|
||||
'json_data',
|
||||
TextareaType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => Translator::getInstance()->trans(
|
||||
'JSON structured data',
|
||||
[],
|
||||
BetterSeo::DOMAIN_NAME
|
||||
),
|
||||
'label_attr' => array(
|
||||
'for' => 'json_data'
|
||||
)
|
||||
]
|
||||
|
||||
);
|
||||
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$form->add(
|
||||
'mesh_text_' . $i,
|
||||
TextType::class,
|
||||
array(
|
||||
'required' => false,
|
||||
'label' => Translator::getInstance()->trans(
|
||||
'text',
|
||||
array(),
|
||||
BetterSeo::DOMAIN_NAME
|
||||
),
|
||||
'label_attr' => array(
|
||||
'for' => 'mesh_text_' . $i
|
||||
)
|
||||
)
|
||||
)
|
||||
->add(
|
||||
'mesh_url_' . $i,
|
||||
UrlType::class,
|
||||
array(
|
||||
'required' => false,
|
||||
'label' => Translator::getInstance()->trans(
|
||||
'url',
|
||||
array(),
|
||||
BetterSeo::DOMAIN_NAME
|
||||
),
|
||||
'label_attr' => array(
|
||||
'for' => 'mesh_url_' . $i
|
||||
)
|
||||
)
|
||||
)
|
||||
->add(
|
||||
'mesh_' . $i,
|
||||
TextType::class,
|
||||
array(
|
||||
'required' => false,
|
||||
'label' => Translator::getInstance()->trans(
|
||||
'text',
|
||||
array(),
|
||||
BetterSeo::DOMAIN_NAME
|
||||
),
|
||||
'label_attr' => array(
|
||||
'for' => 'mesh_' . $i
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'betterseo_form';
|
||||
}
|
||||
}
|
||||
38
domokits/local/modules/BetterSeo/Hook/MetaHook.php
Normal file
38
domokits/local/modules/BetterSeo/Hook/MetaHook.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace BetterSeo\Hook;
|
||||
|
||||
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Thelia\Core\Event\Hook\HookRenderEvent;
|
||||
use Thelia\Core\Hook\BaseHook;
|
||||
use Thelia\Core\HttpFoundation\Request;
|
||||
|
||||
class MetaHook extends BaseHook
|
||||
{
|
||||
protected $request;
|
||||
|
||||
public function __construct(RequestStack $requestStack)
|
||||
{
|
||||
$this->request = $requestStack->getCurrentRequest();
|
||||
}
|
||||
|
||||
public function onMainHeadBottom(HookRenderEvent $event)
|
||||
{
|
||||
$view = $this->request->get('_view');
|
||||
if ($view && preg_match('#^[a-zA-Z0-9\-_\.]+$#', $view)) {
|
||||
|
||||
$id = $this->request->get($view . '_id');
|
||||
|
||||
$lang = $this->request->getSession()->getLang();
|
||||
|
||||
$event->add(
|
||||
$this->render('meta_hook.html', [
|
||||
'object_id' => $id,
|
||||
'object_type' => $view,
|
||||
'lang_id' => $lang->getId()
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
domokits/local/modules/BetterSeo/Hook/SeoFormHook.php
Normal file
35
domokits/local/modules/BetterSeo/Hook/SeoFormHook.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace BetterSeo\Hook;
|
||||
|
||||
|
||||
use BetterSeo\Model\BetterSeo;
|
||||
use BetterSeo\Model\BetterSeoQuery;
|
||||
use Thelia\Core\Event\Hook\HookRenderEvent;
|
||||
use Thelia\Core\Hook\BaseHook;
|
||||
use Thelia\Model\BrandQuery;
|
||||
use Thelia\Model\CategoryQuery;
|
||||
use Thelia\Model\ContentQuery;
|
||||
use Thelia\Model\FolderQuery;
|
||||
use Thelia\Model\Lang;
|
||||
use Thelia\Model\LangQuery;
|
||||
use Thelia\Model\ProductQuery;
|
||||
|
||||
class SeoFormHook extends BaseHook
|
||||
{
|
||||
public function onTabSeoUpdateForm(HookRenderEvent $event)
|
||||
{
|
||||
$objectId = $event->getArgument('id');
|
||||
$objectType = $event->getArgument('type');
|
||||
|
||||
$event->add(
|
||||
$this->render(
|
||||
"seo-additional-fields.html",
|
||||
[
|
||||
'object_id' => $objectId,
|
||||
'object_type' => $objectType,
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
'Save' => 'Save',
|
||||
|
||||
'noindex_nofollow.help' => 'Management of the meta robots noindex, nofollow. Allow to not index this page for search engines. Be careful before checking this, check that your page does not generate traffic on Google Analytics. You risk losing SEO.',
|
||||
|
||||
|
||||
'label.noindex' => 'Management of the meta robots noindex, nofollow, h1 tag and mesh links',
|
||||
'h1' => 'H1',
|
||||
);
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
'Save' => 'Enregistrer',
|
||||
|
||||
'noindex_nofollow.help' => 'Gestion de la meta robots noindex, nofollow. Permet de ne pas indexer cette page pour les moteurs de recherche. Attention avant de cocher cela, bien vérifier que votre page ne génère pas de trafic sur Google Analytics. Vous risquez de perdre du référencement.',
|
||||
|
||||
|
||||
'label.noindex' => 'Gestion de la meta robots noindex, nofollow, balise H1 et liens maillés :',
|
||||
'h1' => 'H1',
|
||||
'Link' => 'Lien',
|
||||
'text' => 'texte',
|
||||
'url' => 'url',
|
||||
'Link text' => 'Texte du lien',
|
||||
'Link URL' => 'URL du lien',
|
||||
'Mesh links' => 'Liens maillés',
|
||||
'Mesh' => 'Texte de maillage',
|
||||
'Text' => 'Texte',
|
||||
);
|
||||
5
domokits/local/modules/BetterSeo/I18n/en_US.php
Normal file
5
domokits/local/modules/BetterSeo/I18n/en_US.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
|
||||
);
|
||||
5
domokits/local/modules/BetterSeo/I18n/fr_FR.php
Normal file
5
domokits/local/modules/BetterSeo/I18n/fr_FR.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
|
||||
);
|
||||
21
domokits/local/modules/BetterSeo/LICENCE.md
Normal file
21
domokits/local/modules/BetterSeo/LICENCE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
87
domokits/local/modules/BetterSeo/Loop/BetterSeoLoop.php
Normal file
87
domokits/local/modules/BetterSeo/Loop/BetterSeoLoop.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace BetterSeo\Loop;
|
||||
|
||||
use BetterSeo\Model\BetterSeo;
|
||||
use BetterSeo\Model\BetterSeoQuery;
|
||||
use BetterSeo\Model\Map\BetterSeoI18nTableMap;
|
||||
use Thelia\Core\Template\Element\BaseI18nLoop;
|
||||
use Thelia\Core\Template\Element\LoopResult;
|
||||
use Thelia\Core\Template\Element\LoopResultRow;
|
||||
use Thelia\Core\Template\Element\PropelSearchLoopInterface;
|
||||
use Thelia\Core\Template\Loop\Argument\Argument;
|
||||
use Thelia\Core\Template\Loop\Argument\ArgumentCollection;
|
||||
use Thelia\Model\LangQuery;
|
||||
|
||||
class BetterSeoLoop extends BaseI18nLoop implements PropelSearchLoopInterface
|
||||
{
|
||||
protected function getArgDefinitions()
|
||||
{
|
||||
return new ArgumentCollection(
|
||||
Argument::createAlphaNumStringTypeArgument('object_id'),
|
||||
Argument::createAlphaNumStringTypeArgument('object_type'),
|
||||
Argument::createIntTypeArgument('lang_id')
|
||||
);
|
||||
}
|
||||
|
||||
public function buildModelCriteria()
|
||||
{
|
||||
|
||||
$objectId = $this->getObjectId();
|
||||
$objectType = $this->getObjectType();
|
||||
$langId = $this->getLangId();
|
||||
|
||||
$lang = LangQuery::create()
|
||||
->filterById($langId)
|
||||
->findOne();
|
||||
|
||||
$query = BetterSeoQuery::create()
|
||||
->filterByObjectId($objectId)
|
||||
->filterByObjectType($objectType)
|
||||
->useBetterSeoI18nQuery()
|
||||
->filterByLocale($lang->getLocale())
|
||||
->endUse()
|
||||
->withColumn(BetterSeoI18nTableMap::NOINDEX, 'noindex')
|
||||
->withColumn(BetterSeoI18nTableMap::NOFOLLOW, 'nofollow')
|
||||
->withColumn(BetterSeoI18nTableMap::H1, 'h1')
|
||||
->withColumn(BetterSeoI18nTableMap::JSON_DATA, 'json_data');
|
||||
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$query->withColumn(constant(BetterSeoI18nTableMap::class . '::MESH_TEXT_' . $i), 'mesh_text_' . $i);
|
||||
$query->withColumn(constant(BetterSeoI18nTableMap::class . '::MESH_URL_' . $i), 'mesh_url_' . $i);
|
||||
$query->withColumn(constant(BetterSeoI18nTableMap::class . '::MESH_' . $i), 'mesh_' . $i);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LoopResult $loopResult
|
||||
* @return LoopResult
|
||||
* @throws \Propel\Runtime\Exception\PropelException
|
||||
*/
|
||||
public function parseResults(LoopResult $loopResult)
|
||||
{
|
||||
/** @var BetterSeo $data */
|
||||
foreach ($loopResult->getResultDataCollection() as $data) {
|
||||
$loopResultRow = new LoopResultRow($data);
|
||||
|
||||
$loopResultRow->set('ID', $data->getId());
|
||||
$loopResultRow->set('OBJECT_ID', $data->getObjectId());
|
||||
$loopResultRow->set('OBJECT_TYPE', $data->getObjectType());
|
||||
$loopResultRow->set('NOINDEX', $data->getVirtualColumn('noindex'));
|
||||
$loopResultRow->set('NOFOLLOW', $data->getVirtualColumn('nofollow'));
|
||||
$loopResultRow->set('H1', $data->getVirtualColumn('h1'));
|
||||
$loopResultRow->set('JSON_DATA', $data->getVirtualColumn('json_data'));
|
||||
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$loopResultRow->set('MESH_TEXT_' . $i, $data->getVirtualColumn('mesh_text_' . $i));
|
||||
$loopResultRow->set('MESH_URL_' . $i, $data->getVirtualColumn('mesh_url_' . $i));
|
||||
$loopResultRow->set('MESH_' . $i, $data->getVirtualColumn('mesh_' . $i));
|
||||
}
|
||||
|
||||
$loopResult->addRow($loopResultRow);
|
||||
}
|
||||
return $loopResult;
|
||||
}
|
||||
}
|
||||
10
domokits/local/modules/BetterSeo/Model/BetterSeo.php
Normal file
10
domokits/local/modules/BetterSeo/Model/BetterSeo.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace BetterSeo\Model;
|
||||
|
||||
use BetterSeo\Model\Base\BetterSeo as BaseBetterSeo;
|
||||
|
||||
class BetterSeo extends BaseBetterSeo
|
||||
{
|
||||
|
||||
}
|
||||
10
domokits/local/modules/BetterSeo/Model/BetterSeoI18n.php
Normal file
10
domokits/local/modules/BetterSeo/Model/BetterSeoI18n.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace BetterSeo\Model;
|
||||
|
||||
use BetterSeo\Model\Base\BetterSeoI18n as BaseBetterSeoI18n;
|
||||
|
||||
class BetterSeoI18n extends BaseBetterSeoI18n
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace BetterSeo\Model;
|
||||
|
||||
use BetterSeo\Model\Base\BetterSeoI18nQuery as BaseBetterSeoI18nQuery;
|
||||
|
||||
|
||||
/**
|
||||
* Skeleton subclass for performing query and update operations on the 'better_seo_i18n' table.
|
||||
*
|
||||
*
|
||||
*
|
||||
* You should add additional methods to this class to meet the
|
||||
* application requirements. This class will only be generated as
|
||||
* long as it does not already exist in the output directory.
|
||||
*
|
||||
*/
|
||||
class BetterSeoI18nQuery extends BaseBetterSeoI18nQuery
|
||||
{
|
||||
|
||||
} // BetterSeoI18nQuery
|
||||
21
domokits/local/modules/BetterSeo/Model/BetterSeoQuery.php
Normal file
21
domokits/local/modules/BetterSeo/Model/BetterSeoQuery.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace BetterSeo\Model;
|
||||
|
||||
use BetterSeo\Model\Base\BetterSeoQuery as BaseBetterSeoQuery;
|
||||
|
||||
|
||||
/**
|
||||
* Skeleton subclass for performing query and update operations on the 'better_seo' table.
|
||||
*
|
||||
*
|
||||
*
|
||||
* You should add additional methods to this class to meet the
|
||||
* application requirements. This class will only be generated as
|
||||
* long as it does not already exist in the output directory.
|
||||
*
|
||||
*/
|
||||
class BetterSeoQuery extends BaseBetterSeoQuery
|
||||
{
|
||||
|
||||
} // BetterSeoQuery
|
||||
75
domokits/local/modules/BetterSeo/README.md
Normal file
75
domokits/local/modules/BetterSeo/README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Better Seo
|
||||
|
||||
Add Noindex checkbox and Canonical Url, h1 field and manage mesh links, in the Seo tab in back
|
||||
|
||||
**For this module to work properly you need to install ```Sitemap``` module, ```AlternateHreflang``` module and ```CanonicalUrl``` module.**
|
||||
|
||||
## Installation
|
||||
|
||||
### Manually
|
||||
|
||||
* Copy the module into ```<thelia_root>/local/modules/``` directory and be sure that the name of the module is BetterSeo.
|
||||
* Activate it in your thelia administration panel
|
||||
|
||||
### Composer
|
||||
|
||||
Add it in your main thelia composer.json file
|
||||
|
||||
```
|
||||
composer require thelia/better-seo-module:~1.4.1
|
||||
```
|
||||
|
||||
## Loop
|
||||
|
||||
[better_seo_loop]
|
||||
|
||||
### Input arguments
|
||||
|
||||
|Argument |Description |
|
||||
|--- |--- |
|
||||
|**object_id** | The id of the object to display, exemple: object_id="12" |
|
||||
|**object_type** | The type of the object to display (product, category, brand, folder, content) exemple object_type="brand"|
|
||||
|**lang_id** | The id of the language|
|
||||
|
||||
### Output arguments
|
||||
|
||||
|Variable |Description |
|
||||
|--- |--- |
|
||||
|$ID | the id in seo_noindex table |
|
||||
|$OBJECT_ID | the id of the object |
|
||||
|$OBJECT_TYPE | the type of the object |
|
||||
|$NOINDEX | if the page of the object is index or not (value 0 or 1) |
|
||||
|$NOFOLLOW | if the page of the object is follow or not (value 0 or 1) |
|
||||
|$CANONICAL | Canonical Url |
|
||||
|$H1 | H1 |
|
||||
|$MESH_TEXT_1 | mesh text 1 |
|
||||
|$MESH_URL_1 | mesh url 1 |
|
||||
|$MESH_TEXT_2 | mesh text 2 |
|
||||
|$MESH_URL_2 | mesh url 2 |
|
||||
|$MESH_TEXT_3 | mesh text 3 |
|
||||
|$MESH_URL_3 | mesh url 3 |
|
||||
|$MESH_TEXT_4 | mesh text 4 |
|
||||
|$MESH_URL_4 | mesh url 4 |
|
||||
|$MESH_TEXT_5 | mesh text 5 |
|
||||
|$MESH_URL_5 | mesh url 5 |
|
||||
|$MESH_1 | mesh 1 |
|
||||
|$MESH_2 | mesh 2 |
|
||||
|$MESH_3 | mesh 3 |
|
||||
|$MESH_4 | mesh 4 |
|
||||
|$MESH_5 | mesh 5 |
|
||||
|$JSON_DATA | JSON data for ld json |
|
||||
|
||||
### Exemple
|
||||
|
||||
{loop type="better_seo_loop" name="exemple.loop" object_id="42" object_type="category" lang_id="1"}
|
||||
|
||||
|
||||
To use ld json you need to add this part to the head of your pages (product, category, brand, folder, content)
|
||||
|
||||
{loop name="loop-name" type="better_seo_loop" object_id=$object_id object_type=$object_type lang_id=$langId}
|
||||
<script type="application/ld+json">
|
||||
{$JSON_DATA nofilter}
|
||||
</script>
|
||||
{/loop}
|
||||
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace BetterSeo\Smarty\Plugins;
|
||||
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Thelia\Core\Event\Image\ImageEvent;
|
||||
use Thelia\Core\Event\TheliaEvents;
|
||||
use Thelia\Exception\TaxEngineException;
|
||||
use Thelia\Model\Category;
|
||||
use Thelia\Model\CategoryQuery;
|
||||
use Thelia\Model\ConfigQuery;
|
||||
use Thelia\Model\Content;
|
||||
use Thelia\Model\ContentQuery;
|
||||
use Thelia\Model\Folder;
|
||||
use Thelia\Model\FolderQuery;
|
||||
use Thelia\Model\Lang;
|
||||
use Thelia\Model\LangQuery;
|
||||
use Thelia\Model\Product;
|
||||
use Thelia\Model\ProductImageQuery;
|
||||
use Thelia\Model\ProductPriceQuery;
|
||||
use Thelia\Model\ProductQuery;
|
||||
use Thelia\Model\ProductSaleElementsQuery;
|
||||
use Thelia\TaxEngine\TaxEngine;
|
||||
use TheliaSmarty\Template\AbstractSmartyPlugin;
|
||||
use TheliaSmarty\Template\SmartyPluginDescriptor;
|
||||
|
||||
class BetterSeoMicroDataPlugin extends AbstractSmartyPlugin
|
||||
{
|
||||
protected $request;
|
||||
protected $taxEngine;
|
||||
protected $dispatcher;
|
||||
|
||||
public function __construct(RequestStack $requestStack, TaxEngine $taxEngine, EventDispatcherInterface $dispatcher)
|
||||
{
|
||||
$this->request = $requestStack->getCurrentRequest();
|
||||
$this->taxEngine = $taxEngine;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
public function getPluginDescriptors()
|
||||
{
|
||||
return [
|
||||
new SmartyPluginDescriptor('function', 'BetterSeoMicroData', $this, 'betterSeoMicroData'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $params
|
||||
*
|
||||
* @return array|int|string
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \Propel\Runtime\Exception\PropelException
|
||||
*/
|
||||
public function betterSeoMicroData($params)
|
||||
{
|
||||
$type = $params['type'] ?? $this->request->get('_view');
|
||||
|
||||
$lang = $this->request->getSession()->getLang();
|
||||
|
||||
if (!$lang) {
|
||||
$lang = LangQuery::create()->filterByByDefault(1)->findOne();
|
||||
}
|
||||
$microdata = null;
|
||||
|
||||
switch ($type) {
|
||||
case 'product':
|
||||
$id = $params['id'] ?? $this->request->get('product_id');
|
||||
$product = ProductQuery::create()->filterById($id)->findOne();
|
||||
$relatedProducts = null;
|
||||
|
||||
if (array_key_exists('related_products', $params)){
|
||||
$relatedProducts = \is_array($params['related_products']) ? $params['related_products'] : $this->explode($params['related_products']);
|
||||
}
|
||||
|
||||
$microdata = $this->getProductMicroData($product, $lang, $relatedProducts);
|
||||
break;
|
||||
case 'category':
|
||||
$id = $params['id'] ?? $this->request->get('category_id');
|
||||
if ($id) {
|
||||
$category = CategoryQuery::create()->filterById($id)->findOne();
|
||||
$microdata = $this->getCategoryMicroData($category, $lang);
|
||||
}
|
||||
break;
|
||||
case 'folder':
|
||||
$id = $params['id'] ?? $this->request->get('folder_id');
|
||||
if ($id) {
|
||||
$folder = FolderQuery::create()->filterById($id)->findOne();
|
||||
$microdata = $this->getFolderMicroData($folder, $lang);
|
||||
}
|
||||
break;
|
||||
case 'content':
|
||||
$id = $params['id'] ?? $this->request->get('content_id');
|
||||
if ($id) {
|
||||
$microdata = $this->getContentMicroData($id, $lang);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$scriptsTag = '';
|
||||
|
||||
$scriptsTag .= '<script type="application/ld+json">'.json_encode($this->getStoreMicroData(), JSON_UNESCAPED_UNICODE).'</script>';
|
||||
if (null !== $microdata) {
|
||||
$scriptsTag .= '<script type="application/ld+json">'.json_encode($microdata, JSON_UNESCAPED_UNICODE).'</script>';
|
||||
}
|
||||
|
||||
return $scriptsTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getStoreMicroData()
|
||||
{
|
||||
$microData = [
|
||||
'@context' => 'https://schema.org/',
|
||||
'@type' => 'Organization',
|
||||
'name' => ConfigQuery::read('store_name'),
|
||||
'description' => ConfigQuery::read('store_description'),
|
||||
'url' => ConfigQuery::read('url_site'),
|
||||
'address' => [
|
||||
'@type' => 'PostalAddress',
|
||||
'streetAddress' => ConfigQuery::read('store_address1').' '.ConfigQuery::read('store_address2').' '.ConfigQuery::read('store_address3'),
|
||||
'addressLocality' => ConfigQuery::read('store_city'),
|
||||
'postalCode' => ConfigQuery::read('store_zipcode'),
|
||||
],
|
||||
];
|
||||
|
||||
return $microData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $relatedProducts
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \Propel\Runtime\Exception\PropelException
|
||||
*/
|
||||
protected function getProductMicroData(Product $product, Lang $lang, $relatedProducts = [])
|
||||
{
|
||||
$product->setLocale($lang->getLocale());
|
||||
$image = ProductImageQuery::create()->filterByProductId($product->getId())->orderByPosition()->find()[0];
|
||||
$pse = ProductSaleElementsQuery::create()->filterByProductId($product->getId())->filterByIsDefault(1)->findOne();
|
||||
$psePrice = ProductPriceQuery::create()->filterByProductSaleElementsId($pse->getId())->findOne();
|
||||
$taxCountry = $this->taxEngine->getDeliveryCountry();
|
||||
|
||||
try {
|
||||
$taxedPrice = $product->getTaxedPrice(
|
||||
$taxCountry,
|
||||
$psePrice->getPrice()
|
||||
);
|
||||
if ($pse->getPromo()) {
|
||||
$taxedPrice = $product->getTaxedPromoPrice(
|
||||
$taxCountry,
|
||||
$psePrice->getPromoPrice()
|
||||
);
|
||||
}
|
||||
} catch (TaxEngineException $e) {
|
||||
$taxedPrice = null;
|
||||
}
|
||||
|
||||
$imagePath = null;
|
||||
|
||||
if ($image) {
|
||||
$baseSourceFilePath = ConfigQuery::read('images_library_path');
|
||||
if ($baseSourceFilePath === null) {
|
||||
$baseSourceFilePath = THELIA_LOCAL_DIR.'media'.DS.'images';
|
||||
} else {
|
||||
$baseSourceFilePath = THELIA_ROOT.$baseSourceFilePath;
|
||||
}
|
||||
$event = new ImageEvent();
|
||||
$sourceFilePath = $baseSourceFilePath.'/product/'.$image->getFile();
|
||||
|
||||
$event->setSourceFilepath($sourceFilePath);
|
||||
$event->setCacheSubdirectory('product');
|
||||
|
||||
try {
|
||||
$this->dispatcher->dispatch($event, TheliaEvents::IMAGE_PROCESS);
|
||||
$imagePath = $event->getFileUrl();
|
||||
} catch (\Exception $e) {
|
||||
$imagePath = $image->getFile();
|
||||
}
|
||||
}
|
||||
|
||||
$microData = [
|
||||
'@context' => 'https://schema.org/',
|
||||
'@type' => 'Product',
|
||||
'name' => $product->getTitle(),
|
||||
'image' => $imagePath,
|
||||
'description' => $product->getDescription(),
|
||||
'sku' => $product->getRef(),
|
||||
'offers' => [
|
||||
'url' => $product->getUrl(),
|
||||
'priceCurrency' => $this->request->getSession()->getCurrency()->getCode(),
|
||||
'price' => $taxedPrice,
|
||||
'itemCondition' => 'https://schema.org/NewCondition',
|
||||
'availability' => $pse->getQuantity() > 0 ? 'http://schema.org/InStock' : 'http://schema.org/OutOfStock',
|
||||
],
|
||||
];
|
||||
|
||||
if ($pse->getEanCode()) {
|
||||
$microData['gtin13'] = $pse->getEanCode();
|
||||
}
|
||||
|
||||
if ($brand = $product->getBrand()) {
|
||||
$microData['brand']['@type'] = 'Brand';
|
||||
$microData['brand']['name'] = $brand->getTitle();
|
||||
}
|
||||
|
||||
if ($relatedProducts) {
|
||||
foreach ($relatedProducts as $relatedProductId) {
|
||||
$relatedProduct = ProductQuery::create()->filterById($relatedProductId)->findOne();
|
||||
$microData['isRelatedTo'][] = $this->getProductMicroData($relatedProduct, $lang);
|
||||
}
|
||||
}
|
||||
|
||||
return $microData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getCategoryMicroData(Category $category, Lang $lang)
|
||||
{
|
||||
$category->setLocale($lang->getLocale());
|
||||
|
||||
$products = $category->getProducts();
|
||||
|
||||
$itemListElement = [];
|
||||
|
||||
$i = 1;
|
||||
foreach ($products as $product) {
|
||||
$itemListElement[] = [
|
||||
'@type' => 'ListItem',
|
||||
'position' => $i++,
|
||||
'url' => $product->getUrl(),
|
||||
];
|
||||
}
|
||||
|
||||
$microData = [
|
||||
'@context' => 'https://schema.org/',
|
||||
'@type' => 'ItemList',
|
||||
'url' => $category->getUrl(),
|
||||
'numberOfItems' => \count($products),
|
||||
'itemListElement' => $itemListElement,
|
||||
];
|
||||
|
||||
return $microData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getFolderMicroData(Folder $folder, Lang $lang)
|
||||
{
|
||||
$folder->setLocale($lang->getLocale());
|
||||
|
||||
$microData = [
|
||||
'@context' => 'https://schema.org/',
|
||||
'@type' => 'Guide',
|
||||
'url' => $folder->getUrl(),
|
||||
"name" => $folder->getTitle(),
|
||||
"abstract" => $folder->getChapo(),
|
||||
];
|
||||
|
||||
|
||||
return $microData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getContentMicroData($contentId, Lang $lang)
|
||||
{
|
||||
$content = ContentQuery::create()->filterById($contentId)->findOne();
|
||||
|
||||
if (null === $content) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$content->setLocale($lang->getLocale());
|
||||
|
||||
$microData = [
|
||||
'@context' => 'https://schema.org/',
|
||||
'@type' => 'Article',
|
||||
'url' => $content->getUrl(),
|
||||
"name" => $content->getTitle(),
|
||||
"abstract" => $content->getChapo(),
|
||||
];
|
||||
|
||||
$defaultFoIdlder = $content->getDefaultFolderId();
|
||||
|
||||
if (null !== $defaultFoIdlder) {
|
||||
$default_folder = FolderQuery::create()->findOneById($defaultFoIdlder);
|
||||
if (null !== $default_folder) {
|
||||
$default_folder->setLocale($lang->getLocale());
|
||||
$microData['isPartOf'] = [
|
||||
'name' => $default_folder->getTitle(),
|
||||
'url' => $default_folder->getUrl()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $microData;
|
||||
}
|
||||
}
|
||||
11
domokits/local/modules/BetterSeo/composer.json
Normal file
11
domokits/local/modules/BetterSeo/composer.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "thelia/better-seo-module",
|
||||
"license": "LGPL-3.0+",
|
||||
"type": "thelia-module",
|
||||
"require": {
|
||||
"thelia/installer": "~1.1"
|
||||
},
|
||||
"extra": {
|
||||
"installer-name": "BetterSeo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
{$pageUrl|default:null}
|
||||
{$noindex_val = null}
|
||||
{$nofollow_val = null}
|
||||
{$json_data = null}
|
||||
{$h1 = null}
|
||||
{for $i=1 to 5}
|
||||
{assign var="mesh_text_$i" value=null}
|
||||
{assign var="mesh_url_$i" value=null}
|
||||
{assign var="mesh_$i" value=null}
|
||||
{/for}
|
||||
|
||||
{loop type="better_seo_loop" name="better_seo_data" object_id=$object_id object_type=$object_type lang_id=$edit_language_id}
|
||||
{$noindex_val = $NOINDEX}
|
||||
{$nofollow_val = $NOFOLLOW}
|
||||
{$json_data = $JSON_DATA}
|
||||
{$h1 = $H1}
|
||||
{for $i=1 to 5}
|
||||
{assign var="mesh_text_$i" value={$MESH_TEXT_{$i}}}
|
||||
{assign var="mesh_url_$i" value={$MESH_URL_{$i}}}
|
||||
{assign var="mesh_$i" value={$MESH_{$i}}}
|
||||
{/for}
|
||||
{/loop}
|
||||
{form name = "betterseo_form"}
|
||||
<form method="POST" action="{url path="/admin/module/betterseo/save" object_id=$object_id object_type=$object_type lang_id=$edit_language_id}">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<label>{intl l="label.noindex" d="betterseo.bo.default"}</label>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
{form_hidden_fields form=$form}
|
||||
{if $form_error}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert-danger">{$form_error_message}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="panel-body text-right">
|
||||
<button type="submit" name="save_mode" value="stay" class="form-submit-button btn btn-sm btn-default btn-success">
|
||||
{intl l='Save'} <span class="glyphicon glyphicon-ok"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{form_field field="noindex_checkbox"}
|
||||
<div class="{if $error} has-error{/if}">
|
||||
<input id="{$label_attr.for}" type="checkbox" name="{$name}" value="1" {if $noindex_val == 1} checked {/if}/>
|
||||
<label class="control-label danger" for="{$label_attr.for}">{intl l=$label d="betterseo.bo.default"}</label>
|
||||
{if $error}
|
||||
<div class="text-danger">
|
||||
{$message}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/form_field}
|
||||
|
||||
{form_field field="nofollow_checkbox"}
|
||||
<div class="{if $error} has-error{/if}">
|
||||
<input id="{$label_attr.for}" type="checkbox" name="{$name}" value="1" {if $nofollow_val == 1} checked {/if}/>
|
||||
<label class="control-label danger" for="{$label_attr.for}">{intl l=$label d="betterseo.bo.default"}</label>
|
||||
{if $error}
|
||||
<div class="text-danger">
|
||||
{$message}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/form_field}
|
||||
<div class="help-block">
|
||||
{intl l="noindex_nofollow.help" d="betterseo.bo.default"}
|
||||
</div>
|
||||
|
||||
{form_field field="h1"}
|
||||
<div class="{if $error} has-error{/if}">
|
||||
<label class="control-label danger" for="{$label_attr.for}">{intl l=$label d="betterseo.bo.default"}</label>
|
||||
<input type="text" id="{$label_attr.for}" class="form-control" name="{$name}" value="{$h1}"/>
|
||||
{if $error}
|
||||
<div class="text-danger">
|
||||
{$message}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="help-block">
|
||||
</div>
|
||||
</div>
|
||||
{/form_field}
|
||||
|
||||
{form_field field="json_data"}
|
||||
<div class="form-group">
|
||||
<label for="{$label_attr.for}" class="control-label">{$label}</label>
|
||||
<textarea name={$name} id="{$label_attr.for}" cols="30" rows="10" class="form-control">{$json_data}</textarea>
|
||||
</div>
|
||||
{/form_field}
|
||||
|
||||
<hr/>
|
||||
<table class="table table-striped">
|
||||
<caption>{intl l="Mesh" d="betterseo.bo.default"}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
{for $i=1 to 5}
|
||||
<th>
|
||||
{intl l="Text" d="betterseo.bo.default"} {$i}
|
||||
</th>
|
||||
{/for}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
{for $i=1 to 5}
|
||||
{$textVar = "MESH_$i"}
|
||||
|
||||
<td>
|
||||
{form_field field="mesh_$i"}
|
||||
<div class="{if $error} has-error{/if}">
|
||||
<input type="text" id="{$label_attr.for}" class="form-control" name="{$name}" value="{$mesh_{$i}}"/>
|
||||
{if $error}
|
||||
<div class="text-danger">
|
||||
{$message}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/form_field}
|
||||
</td>
|
||||
{/for}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
<table class="table table-striped">
|
||||
<caption>{intl l="Mesh links" d="betterseo.bo.default"}</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
|
||||
</th>
|
||||
<th>
|
||||
{intl l="Link text" d="betterseo.bo.default"}
|
||||
</th>
|
||||
<th>
|
||||
{intl l="Link URL" d="betterseo.bo.default"}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{for $i=1 to 5}
|
||||
{$urlVar = "MESH_URL_$i"}
|
||||
{$textVar = "MESH_TEXT_$i"}
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
{intl l="Link" d="betterseo.bo.default"} {$i}
|
||||
</td>
|
||||
<td>
|
||||
{form_field field="mesh_text_$i"}
|
||||
<div class="{if $error} has-error{/if}">
|
||||
<label class="control-label danger" for="{$label_attr.for}">{intl l=$label d="betterseo.bo.default"}</label>
|
||||
<input type="text" id="{$label_attr.for}" class="form-control" name="{$name}" value="{$mesh_text_{$i}}"/>
|
||||
{if $error}
|
||||
<div class="text-danger">
|
||||
{$message}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/form_field}
|
||||
</td>
|
||||
<td>
|
||||
{form_field field="mesh_url_$i"}
|
||||
<div class="{if $error} has-error{/if}">
|
||||
<label class="control-label danger" for="{$label_attr.for}">{intl l=$label d="betterseo.bo.default"}</label>
|
||||
<input type="url" id="{$label_attr.for}" class="form-control" name="{$name}" value="{$mesh_url_{$i}}"/>
|
||||
{if $error}
|
||||
<div class="text-danger">
|
||||
{$message}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/form_field}
|
||||
</td>
|
||||
</tr>
|
||||
{/for}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="panel-body text-right">
|
||||
<button type="submit" name="save_mode" value="stay" class="form-submit-button btn btn-sm btn-default btn-success">
|
||||
{intl l='Save'} <span class="glyphicon glyphicon-ok"></span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/form}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
{BetterSeoMicroData}
|
||||
|
||||
{loop type="better_seo_loop" name="better_seo_meta_loop" object_id=$object_id object_type=$object_type lang_id=$lang_id}
|
||||
{if $NOINDEX == 1 and $NOFOLLOW == 1}
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
{elseif $NOINDEX == 1}
|
||||
<meta name="robots" content="noindex, follow">
|
||||
{elseif $NOFOLLOW == 1}
|
||||
<meta name="robots" content="nofollow">
|
||||
{/if}
|
||||
|
||||
{if $JSON_DATA}
|
||||
<script type="application/ld+json">
|
||||
{$JSON_DATA nofilter}
|
||||
</script>
|
||||
{/if}
|
||||
{/loop}
|
||||
20
domokits/local/modules/CanonicalUrl/CHANGELOG.md
Normal file
20
domokits/local/modules/CanonicalUrl/CHANGELOG.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 1.2.0
|
||||
|
||||
- Add canonical override in seo form
|
||||
|
||||
# 1.1.1
|
||||
|
||||
- Fix exception when Thelia was not configured
|
||||
|
||||
# 1.1.0
|
||||
|
||||
- Adds the unit tests in the case of a single domain
|
||||
- Adds the case there is a subfolder
|
||||
|
||||
# 1.0.1
|
||||
|
||||
- Fix ```installer-name``` in the composer.json file
|
||||
|
||||
# 1.0.2
|
||||
|
||||
- Fix hook scope for compatibility with the other modules
|
||||
35
domokits/local/modules/CanonicalUrl/CanonicalUrl.php
Normal file
35
domokits/local/modules/CanonicalUrl/CanonicalUrl.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CanonicalUrl;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
|
||||
use Thelia\Module\BaseModule;
|
||||
|
||||
class CanonicalUrl extends BaseModule
|
||||
{
|
||||
/** @var string */
|
||||
const DOMAIN_NAME = 'canonicalurl';
|
||||
|
||||
const SEO_CANONICAL_META_KEY = 'seo_canonical_meta';
|
||||
|
||||
/**
|
||||
* Defines how services are loaded in your modules.
|
||||
*/
|
||||
public static function configureServices(ServicesConfigurator $servicesConfigurator): void
|
||||
{
|
||||
$servicesConfigurator->load(self::getModuleCode().'\\', __DIR__)
|
||||
->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*'])
|
||||
->autowire(true)
|
||||
->autoconfigure(true);
|
||||
}
|
||||
}
|
||||
16
domokits/local/modules/CanonicalUrl/Config/config.xml
Normal file
16
domokits/local/modules/CanonicalUrl/Config/config.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<config xmlns="http://thelia.net/schema/dic/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://thelia.net/schema/dic/config http://thelia.net/schema/dic/config/thelia-1.0.xsd">
|
||||
|
||||
<hooks>
|
||||
<hook id="canonicalurl.meta.hook" class="CanonicalUrl\Hook\MetaHook" scope="request">
|
||||
<tag name="hook.event_listener" event="main.head-bottom" type="front" method="onMainHeadBottom" />
|
||||
<argument type="service" id="event_dispatcher" />
|
||||
</hook>
|
||||
<hook id="canonicalurl.seo.update.form" class="CanonicalUrl\Hook\SeoUpdateFormHook">
|
||||
<tag name="hook.event_listener" event="tab-seo.update-form" type="backoffice" method="addInputs"/>
|
||||
</hook>
|
||||
</hooks>
|
||||
</config>
|
||||
38
domokits/local/modules/CanonicalUrl/Config/module.xml
Normal file
38
domokits/local/modules/CanonicalUrl/Config/module.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module xmlns="http://thelia.net/schema/dic/module"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://thelia.net/schema/dic/module http://thelia.net/schema/dic/module/module-2_2.xsd">
|
||||
<fullnamespace>CanonicalUrl\CanonicalUrl</fullnamespace>
|
||||
<descriptive locale="en_US">
|
||||
<title>Adds the canonical Url in the metas of your site</title>
|
||||
</descriptive>
|
||||
<descriptive locale="fr_FR">
|
||||
<title>Ajoute l'Url canonique dans les metas de votre site</title>
|
||||
</descriptive>
|
||||
<languages>
|
||||
<language>en_US</language>
|
||||
<language>fr_FR</language>
|
||||
</languages>
|
||||
<version>2.1.6</version>
|
||||
<authors>
|
||||
<author>
|
||||
<name>Gilles Bourgeat</name>
|
||||
<email>gilles.bourgeat@gmail.com</email>
|
||||
<website>https://github.com/gillesbourgeat</website>
|
||||
</author>
|
||||
<author>
|
||||
<name>Franck Allimant</name>
|
||||
<company>CQFDev</company>
|
||||
<email>franck@cqfdev.fr</email>
|
||||
<website>www.cqfdev.fr</website>
|
||||
</author>
|
||||
<author>
|
||||
<name>Vincent Lopes-Vicente</name>
|
||||
<company>OpenStudio</company>
|
||||
<email>vlopes@openstudio.fr</email>
|
||||
</author>
|
||||
</authors>
|
||||
<type>classic</type>
|
||||
<thelia>2.5.0</thelia>
|
||||
<stability>prod</stability>
|
||||
</module>
|
||||
7
domokits/local/modules/CanonicalUrl/Config/routing.xml
Normal file
7
domokits/local/modules/CanonicalUrl/Config/routing.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<routes xmlns="http://symfony.com/schema/routing"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
|
||||
|
||||
</routes>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CanonicalUrl\Event;
|
||||
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Class CanonicalUrlEvent.
|
||||
*
|
||||
* @author Gilles Bourgeat <gilles.bourgeat@gmail.com>
|
||||
*/
|
||||
class CanonicalUrlEvent extends Event
|
||||
{
|
||||
/** @var string|null */
|
||||
protected $url = null;
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $url
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setUrl($url)
|
||||
{
|
||||
if ($url !== null && $url[0] !== '/' && filter_var($url, \FILTER_VALIDATE_URL) === false) {
|
||||
throw new \InvalidArgumentException('The value "'.(string) $url.'" is not a valid Url or Uri.');
|
||||
}
|
||||
|
||||
$this->url = $url;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CanonicalUrl\Event;
|
||||
|
||||
/**
|
||||
* Class CanonicalUrlEvents.
|
||||
*
|
||||
* @author Gilles Bourgeat <gilles.bourgeat@gmail.com>
|
||||
*/
|
||||
class CanonicalUrlEvents
|
||||
{
|
||||
const GENERATE_CANONICAL = 'canonical.url.generate.canonical';
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CanonicalUrl\EventListener;
|
||||
|
||||
use BetterSeo\Model\BetterSeoQuery;
|
||||
use CanonicalUrl\CanonicalUrl;
|
||||
use CanonicalUrl\Event\CanonicalUrlEvent;
|
||||
use CanonicalUrl\Event\CanonicalUrlEvents;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Thelia\Core\HttpFoundation\Request;
|
||||
use Thelia\Core\HttpFoundation\Session\Session;
|
||||
use Thelia\Log\Tlog;
|
||||
use Thelia\Model\ConfigQuery;
|
||||
use Thelia\Model\Lang;
|
||||
use Thelia\Model\LangQuery;
|
||||
use Thelia\Model\MetaDataQuery;
|
||||
|
||||
/**
|
||||
* Class CanonicalUrlListener.
|
||||
*
|
||||
* @author Gilles Bourgeat <gilles.bourgeat@gmail.com>
|
||||
*/
|
||||
class CanonicalUrlListener implements EventSubscriberInterface
|
||||
{
|
||||
/** @var RequestStack */
|
||||
protected $requestStack;
|
||||
|
||||
/** @var Session */
|
||||
protected $session;
|
||||
|
||||
public function __construct(RequestStack $requestStack)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
public function generateUrlCanonical(CanonicalUrlEvent $event): void
|
||||
{
|
||||
/** @var Request $request */
|
||||
if (null === $request = $this->requestStack->getCurrentRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->getUrl() !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $canonicalOverride = $this->getCanonicalOverride()) {
|
||||
try {
|
||||
$event->setUrl($canonicalOverride);
|
||||
|
||||
return;
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
Tlog::getInstance()->addWarning($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$parseUrlByCurrentLocale = $this->getParsedUrlByCurrentLocale();
|
||||
|
||||
if (empty($parseUrlByCurrentLocale['host'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Be sure to use the proper domain name
|
||||
$canonicalUrl = $parseUrlByCurrentLocale['scheme'].'://'.$parseUrlByCurrentLocale['host'];
|
||||
|
||||
// preserving a potential subdirectory, e.g. http://somehost.com/mydir/index.php/...
|
||||
$canonicalUrl .= $request->getBaseUrl();
|
||||
|
||||
// Remove script name from path, e.g. http://somehost.com/index.php/...
|
||||
$canonicalUrl = preg_replace("!/index(_dev)?\.php!", '', $canonicalUrl);
|
||||
|
||||
$path = $request->getPathInfo();
|
||||
|
||||
if (!empty($path) && $path != '/') {
|
||||
$canonicalUrl .= $path;
|
||||
|
||||
$canonicalUrl = rtrim($canonicalUrl, '/');
|
||||
} else if (isset($parseUrlByCurrentLocale['query'])) {
|
||||
$canonicalUrl .= '/?'. (array_key_exists("query", $parseUrlByCurrentLocale)) ? $parseUrlByCurrentLocale['query'] : "";
|
||||
}
|
||||
|
||||
try {
|
||||
$event->setUrl($canonicalUrl);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
Tlog::getInstance()->addWarning($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
CanonicalUrlEvents::GENERATE_CANONICAL => [
|
||||
'generateUrlCanonical', 128,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*
|
||||
* At least one element will be present within the array.
|
||||
* Potential keys within this array are:
|
||||
* scheme - e.g. http
|
||||
* host
|
||||
* port
|
||||
* user
|
||||
* pass
|
||||
* path
|
||||
* query - after the question mark ?
|
||||
* fragment - after the hashmark #
|
||||
*/
|
||||
protected function getParsedUrlByCurrentLocale()
|
||||
{
|
||||
/** @var Request $request */
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
// for one domain by lang
|
||||
if ((int) ConfigQuery::read('one_domain_foreach_lang', 0) === 1) {
|
||||
// We always query the DB here, as the Lang configuration (then the related URL) may change during the
|
||||
// user session lifetime, and improper URLs could be generated. This is quite odd, okay, but may happen.
|
||||
$langUrl = LangQuery::create()->findPk($request->getSession()->getLang()->getId())->getUrl();
|
||||
|
||||
if (!empty($langUrl) && false !== $parse = parse_url($langUrl)) {
|
||||
return $parse;
|
||||
}
|
||||
}
|
||||
|
||||
// Configured site URL
|
||||
$urlSite = ConfigQuery::read('url_site');
|
||||
if (!empty($urlSite) && false !== $parse = parse_url($urlSite)) {
|
||||
return $parse;
|
||||
}
|
||||
|
||||
// return current URL
|
||||
return parse_url($request->getUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getCanonicalOverride()
|
||||
{
|
||||
/** @var Request $request */
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
$lang = $request->getSession()->getLang();
|
||||
|
||||
$routeParameters = $this->getRouteParameters();
|
||||
|
||||
if (null === $routeParameters) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = null;
|
||||
|
||||
$metaCanonical = MetaDataQuery::create()
|
||||
->filterByMetaKey(CanonicalUrl::SEO_CANONICAL_META_KEY)
|
||||
->filterByElementKey($routeParameters['view'])
|
||||
->filterByElementId($routeParameters['id'])
|
||||
->findOne();
|
||||
|
||||
if (null !== $metaCanonical) {
|
||||
$canonicalValues = json_decode($metaCanonical->getValue(), true);
|
||||
|
||||
$url = isset($canonicalValues[$lang->getLocale()]) && ! empty($canonicalValues[$lang->getLocale()]) ? $canonicalValues[$lang->getLocale()] :null;
|
||||
}
|
||||
|
||||
// Try to get old field of BetterSeoModule
|
||||
if (null === $url && class_exists("BetterSeo\BetterSeo")) {
|
||||
try {
|
||||
$betterSeoData = BetterSeoQuery::create()
|
||||
->filterByObjectType($routeParameters['view'])
|
||||
->filterByObjectId($routeParameters['id'])
|
||||
->findOne();
|
||||
|
||||
$url = $betterSeoData->setLocale($lang->getLocale())
|
||||
->getCanonicalField();
|
||||
} catch (\Throwable $exception) {
|
||||
//Catch if field doesn't exist but do nothing
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (false === filter_var($url, \FILTER_VALIDATE_URL)) {
|
||||
return rtrim($this->getSiteBaseUrlForLocale($lang), "/")."/".$url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
protected function getSiteBaseUrlForLocale(Lang $lang = null)
|
||||
{
|
||||
if (null === $lang) {
|
||||
$lang = $this->requestStack->getCurrentRequest()->getSession()->getLang();
|
||||
}
|
||||
if ((int) ConfigQuery::read('one_domain_foreach_lang', 0) === 1) {
|
||||
// We always query the DB here, as the Lang configuration (then the related URL) may change during the
|
||||
// user session lifetime, and improper URLs could be generated. This is quite odd, okay, but may happen.
|
||||
$langUrl = LangQuery::create()->findPk($lang->getId())->getUrl();
|
||||
return $langUrl;
|
||||
}
|
||||
|
||||
// Configured site URL
|
||||
$urlSite = ConfigQuery::read('url_site');
|
||||
return $urlSite;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
protected function getRouteParameters()
|
||||
{
|
||||
/** @var Request $request */
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
$view = $request->get('view');
|
||||
if (null === $view) {
|
||||
$view = $request->get('_view');
|
||||
}
|
||||
if (null === $view) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$id = $request->get($view.'_id');
|
||||
|
||||
if (null === $id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return compact('view', 'id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CanonicalUrl\EventListener;
|
||||
|
||||
use CanonicalUrl\CanonicalUrl;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Thelia\Action\BaseAction;
|
||||
use Thelia\Core\Event\TheliaEvents;
|
||||
use Thelia\Core\Event\TheliaFormEvent;
|
||||
use Thelia\Core\Event\UpdateSeoEvent;
|
||||
use Thelia\Core\HttpFoundation\Request;
|
||||
use Thelia\Model\MetaDataQuery;
|
||||
|
||||
class SeoFormListener extends BaseAction implements EventSubscriberInterface
|
||||
{
|
||||
/** @var RequestStack */
|
||||
protected $requestStack;
|
||||
|
||||
public function __construct(RequestStack $requestStack)
|
||||
{
|
||||
$this->requestStack = $requestStack;
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
TheliaEvents::FORM_AFTER_BUILD.'.thelia_seo' => ['addCanonicalField', 128],
|
||||
TheliaEvents::CATEGORY_UPDATE_SEO => ['saveCategorySeoFields', 128],
|
||||
TheliaEvents::BRAND_UPDATE_SEO => ['saveBrandSeoFields', 128],
|
||||
TheliaEvents::CONTENT_UPDATE_SEO => ['saveContentSeoFields', 128],
|
||||
TheliaEvents::FOLDER_UPDATE_SEO => ['saveFolderSeoFields', 128],
|
||||
TheliaEvents::PRODUCT_UPDATE_SEO => ['saveProductSeoFields', 128],
|
||||
];
|
||||
}
|
||||
|
||||
public function saveCategorySeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
|
||||
{
|
||||
$this->saveSeoFields($event, $eventName, $dispatcher, 'category');
|
||||
}
|
||||
|
||||
public function saveBrandSeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
|
||||
{
|
||||
$this->saveSeoFields($event, $eventName, $dispatcher, 'brand');
|
||||
}
|
||||
|
||||
public function saveContentSeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
|
||||
{
|
||||
$this->saveSeoFields($event, $eventName, $dispatcher, 'content');
|
||||
}
|
||||
|
||||
public function saveFolderSeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
|
||||
{
|
||||
$this->saveSeoFields($event, $eventName, $dispatcher, 'folder');
|
||||
}
|
||||
|
||||
public function saveProductSeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
|
||||
{
|
||||
$this->saveSeoFields($event, $eventName, $dispatcher, 'product');
|
||||
}
|
||||
|
||||
protected function saveSeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher, $elementKey): void
|
||||
{
|
||||
$form = $this->requestStack->getCurrentRequest()->get('thelia_seo');
|
||||
|
||||
if (null === $form || !\array_key_exists('id', $form) || !\array_key_exists('canonical', $form)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$canonicalValues = [];
|
||||
|
||||
$canonicalMetaData = MetaDataQuery::create()
|
||||
->filterByMetaKey(CanonicalUrl::SEO_CANONICAL_META_KEY)
|
||||
->filterByElementKey($elementKey)
|
||||
->filterByElementId($form['id'])
|
||||
->findOneOrCreate();
|
||||
|
||||
if (!$canonicalMetaData->isNew()) {
|
||||
$canonicalValues = json_decode($canonicalMetaData->getValue(), true);
|
||||
}
|
||||
|
||||
$locale = $form['locale'];
|
||||
$canonicalValues[$locale] = $form['canonical'];
|
||||
|
||||
$canonicalMetaData
|
||||
->setIsSerialized(0)
|
||||
->setValue(json_encode($canonicalValues))
|
||||
->save();
|
||||
}
|
||||
|
||||
public function addCanonicalField(TheliaFormEvent $event): void
|
||||
{
|
||||
$event->getForm()->getFormBuilder()
|
||||
->add(
|
||||
'canonical',
|
||||
TextType::class
|
||||
);
|
||||
}
|
||||
}
|
||||
49
domokits/local/modules/CanonicalUrl/Hook/MetaHook.php
Normal file
49
domokits/local/modules/CanonicalUrl/Hook/MetaHook.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CanonicalUrl\Hook;
|
||||
|
||||
use CanonicalUrl\Event\CanonicalUrlEvent;
|
||||
use CanonicalUrl\Event\CanonicalUrlEvents;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Thelia\Core\Event\Hook\HookRenderEvent;
|
||||
use Thelia\Core\Hook\BaseHook;
|
||||
|
||||
/**
|
||||
* Class MetaHook.
|
||||
*
|
||||
* @author Gilles Bourgeat <gilles.bourgeat@gmail.com>
|
||||
*/
|
||||
class MetaHook extends BaseHook
|
||||
{
|
||||
/** @var EventDispatcherInterface */
|
||||
protected $eventDispatcher;
|
||||
|
||||
public function __construct(EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public function onMainHeadBottom(HookRenderEvent $hookRender): void
|
||||
{
|
||||
$event = new CanonicalUrlEvent();
|
||||
|
||||
$this->eventDispatcher->dispatch(
|
||||
$event,
|
||||
CanonicalUrlEvents::GENERATE_CANONICAL,
|
||||
);
|
||||
|
||||
if ($event->getUrl()) {
|
||||
$hookRender->add('<link rel="canonical" href="'.$event->getUrl().'">');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CanonicalUrl\Hook;
|
||||
|
||||
use CanonicalUrl\CanonicalUrl;
|
||||
use Thelia\Core\Event\Hook\HookRenderEvent;
|
||||
use Thelia\Core\Hook\BaseHook;
|
||||
use Thelia\Model\MetaDataQuery;
|
||||
|
||||
class SeoUpdateFormHook extends BaseHook
|
||||
{
|
||||
public function addInputs(HookRenderEvent $event): void
|
||||
{
|
||||
$id = $event->getArgument('id');
|
||||
$type = $event->getArgument('type');
|
||||
|
||||
$canonical = null;
|
||||
$canonicalMetaData = MetaDataQuery::create()
|
||||
->filterByMetaKey(CanonicalUrl::SEO_CANONICAL_META_KEY)
|
||||
->filterByElementKey($type)
|
||||
->filterByElementId($id)
|
||||
->findOneOrCreate();
|
||||
|
||||
$canonicalMetaDataValues = json_decode($canonicalMetaData->getValue(), true);
|
||||
|
||||
$lang = $this->getSession()->getAdminEditionLang();
|
||||
|
||||
if (isset($canonicalMetaDataValues[$lang->getLocale()])) {
|
||||
$canonical = $canonicalMetaDataValues[$lang->getLocale()];
|
||||
}
|
||||
|
||||
$event->add($this->render(
|
||||
'hook-seo-update-form.html',
|
||||
[
|
||||
'form' => $event->getArgument('form'),
|
||||
'canonical' => $canonical,
|
||||
]
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'Surcharge de l\'url canonique' => 'Canonical url override',
|
||||
];
|
||||
165
domokits/local/modules/CanonicalUrl/LICENSE.txt
Normal file
165
domokits/local/modules/CanonicalUrl/LICENSE.txt
Normal file
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
47
domokits/local/modules/CanonicalUrl/Readme.md
Normal file
47
domokits/local/modules/CanonicalUrl/Readme.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Canonical Url
|
||||
|
||||
This module generates a canonical URL for every page of your shop. Once activated, you'll find a `<link rel="canonical" href="..." />` tag in the header of your pages.
|
||||
|
||||
## Examples
|
||||
|
||||
- If the page URL is not rewritten, the canonical URL will contain all the URL parameters. Example for : For URL ```http://demo.thelia.net/?view=product&locale=en_US&product_id=18```
|
||||
```html
|
||||
<link rel="canonical" href="http://demo.thelia.net/?view=product&locale=en_US&product_id=18" />
|
||||
```
|
||||
Obviously, this is far from ideal. Consider activating URL rewriting !
|
||||
|
||||
- When the page URL contains the script name (index.php), it will be removed from the canonical URL. Example, the canonical URL of ```http://demo.thelia.net/index.php?view=product&locale=en_US&product_id=18``` is :
|
||||
```html
|
||||
<link rel="canonical" href="http://demo.thelia.net/?view=product&locale=en_US&product_id=18" />
|
||||
```
|
||||
|
||||
When a rewritten URL contains parameters, these parameters a removed. For ```http://demo.thelia.net/index.php/en_en-your-path.html?page=44```, the canonical URL is :
|
||||
```html
|
||||
<link rel="canonical" href="http://demo.thelia.net/en_en-your-path" />
|
||||
```
|
||||
|
||||
- If the page URL contains a domain which is not the main shop domain, this domain is replaced by the main shop domain. For ```http://demo458.thelia.net/index.php/en_en-your-path.html?page=44``` the canonical URL is :
|
||||
```html
|
||||
<link rel="canonical" href="http://demo.thelia.net/en_en-your-path" />
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Manually
|
||||
|
||||
* Copy the module into ```<thelia_root>/local/modules/``` directory and be sure that the name of the module is CanonicalUrl.
|
||||
* Activate it in your thelia administration panel
|
||||
|
||||
### Composer
|
||||
|
||||
Add it in your main thelia composer.json file
|
||||
|
||||
```
|
||||
composer require thelia/canonical-url-module:~2.1
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
You just have to activate the module and check the meta tags of your shop.
|
||||
|
||||
The canonical will be generated automatically but you can define a canonical url in seo form for each item if you want override the generated url.
|
||||
245
domokits/local/modules/CanonicalUrl/Tests/CanonicalUrlTest.php
Normal file
245
domokits/local/modules/CanonicalUrl/Tests/CanonicalUrlTest.php
Normal file
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CanonicalUrl\Tests;
|
||||
|
||||
use CanonicalUrl\Event\CanonicalUrlEvent;
|
||||
use CanonicalUrl\EventListener\CanonicalUrlListener;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class CanonicalUrlTest.
|
||||
*
|
||||
* @author Gilles Bourgeat <gilles.bourgeat@gmail.com>
|
||||
*/
|
||||
class CanonicalUrlTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
/*$config = $this->getMock('Thelia\Model\ConfigQuery');
|
||||
|
||||
$config->expects($this->any())
|
||||
->method('read')
|
||||
->with('allow_slash_ended_uri')
|
||||
->will($this->returnValue(true));*/
|
||||
}
|
||||
|
||||
public function testRemoveFileIndex(): void
|
||||
{
|
||||
$this->performList('http://myhost.com/test', [
|
||||
'http://myhost.com/index.php/test',
|
||||
'http://myhost.com/index.php/test/',
|
||||
'http://myhost.com/index.php/test?page=22&list=1',
|
||||
'http://myhost.com/index.php/test/?page=22&list=1',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testRemoveFileIndexDev(): void
|
||||
{
|
||||
$this->performList('http://myhost.com/test', [
|
||||
'http://myhost.com/index_dev.php/test',
|
||||
'http://myhost.com/index_dev.php/test/',
|
||||
'http://myhost.com/index_dev.php/test?page=22&list=1',
|
||||
'http://myhost.com/index_dev.php/test/?page=22&list=1',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/index_dev.php',
|
||||
'/index_dev.php'
|
||||
));
|
||||
}
|
||||
|
||||
public function testHTTPWithSubDomain(): void
|
||||
{
|
||||
$this->performList('http://mysubdomain.myhost.com/test', [
|
||||
'http://mysubdomain.myhost.com/index.php/test/?page=22&list=1',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testHTTPS(): void
|
||||
{
|
||||
$this->performList('https://myhost.com/test', [
|
||||
'https://myhost.com/index.php/test/?page=22&list=1',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testHTTPSWithSubDomain(): void
|
||||
{
|
||||
$this->performList('https://mysubdomain.myhost.com/test', [
|
||||
'https://mysubdomain.myhost.com/index.php/test/?page=22&list=1',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testHTTPWithSubdirectory(): void
|
||||
{
|
||||
$this->performList('http://myhost.com/web/test', [
|
||||
'http://myhost.com/web/index.php/test',
|
||||
'http://myhost.com/web/index.php/test/',
|
||||
'http://myhost.com/web/index.php/test?page=22&list=1',
|
||||
'http://myhost.com/web/index.php/test?page=22&list=1/',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/index.php',
|
||||
'/web/index.php'
|
||||
));
|
||||
|
||||
$this->performList('http://myhost.com/web/test', [
|
||||
'http://myhost.com/web/index_dev.php/test',
|
||||
'http://myhost.com/web/index_dev.php/test/',
|
||||
'http://myhost.com/web/index_dev.php/test?page=22&list=1',
|
||||
'http://myhost.com/web/index_dev.php/test?page=22&list=1/',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/index_dev.php',
|
||||
'/web/index_dev.php'
|
||||
));
|
||||
}
|
||||
|
||||
public function testHTTPWithMultipleSubdirectory(): void
|
||||
{
|
||||
$this->performList('http://myhost.com/web/web2/web3/test', [
|
||||
'http://myhost.com/web/web2/web3/index.php/test/?page=22&list=1',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/web2/web3/index.php',
|
||||
'/web/web2/web3/index.php'
|
||||
));
|
||||
|
||||
$this->performList('http://myhost.com/web/web2/web3/test', [
|
||||
'http://myhost.com/web/web2/web3/index_dev.php/test/?page=22&list=1',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/web2/web3/index_dev.php',
|
||||
'/web/web2/web3/index_dev.php'
|
||||
));
|
||||
}
|
||||
|
||||
public function testHTTPSWithSubdirectory(): void
|
||||
{
|
||||
$this->performList('https://myhost.com/web/test', [
|
||||
'https://myhost.com/web/index.php/test/?page=22&list=1',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/index.php',
|
||||
'/web/index.php'
|
||||
));
|
||||
}
|
||||
|
||||
public function testHTTPSWithMultipleSubdirectory(): void
|
||||
{
|
||||
$this->performList('https://myhost.com/web/web2/web3/test', [
|
||||
'https://myhost.com/web/web2/web3/index.php/test/?page=22&list=1',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/web2/web3/index.php',
|
||||
'/web/web2/web3/index.php'
|
||||
));
|
||||
}
|
||||
|
||||
public function testWithNoPath(): void
|
||||
{
|
||||
$this->performList('http://myhost.com/?list=22&page=1', [
|
||||
'http://myhost.com?list=22&page=1',
|
||||
'http://myhost.com/?list=22&page=1',
|
||||
'http://myhost.com/index.php?list=22&page=1',
|
||||
'http://myhost.com/index.php/?list=22&page=1',
|
||||
]);
|
||||
|
||||
$this->performList('http://myhost.com/?list=22&page=1', [
|
||||
'http://myhost.com/index_dev.php?list=22&page=1',
|
||||
'http://myhost.com/index_dev.php/?list=22&page=1',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/index_dev.php',
|
||||
'/index_dev.php'
|
||||
));
|
||||
}
|
||||
|
||||
public function testWithNoPathAndMultipleSubdirectory(): void
|
||||
{
|
||||
$this->performList('http://myhost.com/web/?list=22&page=1', [
|
||||
'http://myhost.com/web/index.php?list=22&page=1',
|
||||
'http://myhost.com/web/?list=22&page=1',
|
||||
'http://myhost.com/web/index.php?list=22&page=1',
|
||||
'http://myhost.com/web/index.php/?list=22&page=1',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/index.php',
|
||||
'/web/index.php'
|
||||
));
|
||||
|
||||
$this->performList('http://myhost.com/web/?list=22&page=1', [
|
||||
'http://myhost.com/web/index_dev.php?list=22&page=1',
|
||||
'http://myhost.com/web/index_dev.php/?list=22&page=1',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/index_dev.php',
|
||||
'/web/index_dev.php'
|
||||
));
|
||||
}
|
||||
|
||||
public function testWithNotRewrittenUrl(): void
|
||||
{
|
||||
$this->performList('http://myhost.com/web/?view=category&lang=fr_FR&category_id=48', [
|
||||
'http://myhost.com/web/index.php?view=category&lang=fr_FR&category_id=48',
|
||||
'http://myhost.com/web/?lang=fr_FR&view=category&category_id=48',
|
||||
'http://myhost.com/web/index.php?&category_id=48&lang=fr_FR&view=category',
|
||||
'http://myhost.com/web/index.php/?category_id=48&view=category&lang=fr_FR',
|
||||
], $this->fakeServer(
|
||||
'/var/www/web/index.php',
|
||||
'/web/index.php'
|
||||
));
|
||||
}
|
||||
|
||||
public function testOverrideCanonicalEvent(): void
|
||||
{
|
||||
$canonicalUrlListener = new CanonicalUrlListener(Request::create('https://myhost.com/test'));
|
||||
|
||||
$event = new CanonicalUrlEvent();
|
||||
|
||||
// override canonical
|
||||
$canonical = 'http://myscanonical.com';
|
||||
$event->setUrl($canonical);
|
||||
|
||||
$canonicalUrlListener->generateUrlCanonical($event);
|
||||
|
||||
$this->assertEquals($canonical, $event->getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $scriptFileName
|
||||
* @param string $scriptName
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function fakeServer(
|
||||
$scriptFileName = '/var/www/web/index.php',
|
||||
$scriptName = '/index.php'
|
||||
) {
|
||||
return [
|
||||
'SCRIPT_FILENAME' => $scriptFileName,
|
||||
'SCRIPT_NAME' => $scriptName,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $canonicalExpected canonical expected
|
||||
* @param array $list array of uri
|
||||
*/
|
||||
protected function performList($canonicalExpected, array $list, array $server = []): void
|
||||
{
|
||||
if (empty($server)) {
|
||||
$server = $this->fakeServer();
|
||||
}
|
||||
|
||||
foreach ($list as $uri) {
|
||||
$canonicalUrlListener = new CanonicalUrlListener(
|
||||
Request::create($uri, 'GET', [], [], [], $server)
|
||||
);
|
||||
|
||||
$event = new CanonicalUrlEvent();
|
||||
|
||||
$canonicalUrlListener->generateUrlCanonical($event);
|
||||
|
||||
$this->assertEquals($canonicalExpected, $event->getUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
domokits/local/modules/CanonicalUrl/composer.json
Normal file
11
domokits/local/modules/CanonicalUrl/composer.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "thelia/canonical-url-module",
|
||||
"license": "LGPL-3.0+",
|
||||
"type": "thelia-module",
|
||||
"require": {
|
||||
"thelia/installer": "~1.1"
|
||||
},
|
||||
"extra": {
|
||||
"installer-name": "CanonicalUrl"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{form_field form=$form field='canonical' }
|
||||
<div class="form-group">
|
||||
<label for="{$name}">{intl l="Surcharge de l'url canonique" d="canonicalurl.bo.default"}</label>
|
||||
<input type="text" id="{$name}" name="{$name}" value="{$canonical}" class="form-control">
|
||||
</div>
|
||||
{/form_field}
|
||||
6
domokits/local/modules/Carousel/CHANGELOG.md
Normal file
6
domokits/local/modules/Carousel/CHANGELOG.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 2.3.0-alpha1
|
||||
|
||||
- Moved the images from the directory 'media' in the module to thelia/local/media/images/carousel.
|
||||
- The current images will be automatically copied in the new directory during the update of the module
|
||||
- Removed AdminIncludes directory
|
||||
- All html,js and css files are now in 'templates'
|
||||
124
domokits/local/modules/Carousel/Carousel.php
Normal file
124
domokits/local/modules/Carousel/Carousel.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Carousel;
|
||||
|
||||
use Propel\Runtime\Connection\ConnectionInterface;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
use Thelia\Install\Database;
|
||||
use Thelia\Model\ConfigQuery;
|
||||
use Thelia\Module\BaseModule;
|
||||
|
||||
/**
|
||||
* Class Carousel.
|
||||
*
|
||||
* @author Franck Allimant <franck@cqfdev.fr>
|
||||
*/
|
||||
class Carousel extends BaseModule
|
||||
{
|
||||
public const DOMAIN_NAME = 'carousel';
|
||||
|
||||
/**
|
||||
* @return bool true to continue module activation, false to prevent it
|
||||
*/
|
||||
public function preActivation(ConnectionInterface $con = null)
|
||||
{
|
||||
if (!self::getConfigValue('is_initialized', false)) {
|
||||
$database = new Database($con);
|
||||
|
||||
$database->insertSql(null, [__DIR__.'/Config/TheliaMain.sql']);
|
||||
|
||||
self::setConfigValue('is_initialized', true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function destroy(ConnectionInterface $con = null, $deleteModuleData = false): void
|
||||
{
|
||||
$database = new Database($con);
|
||||
|
||||
$database->insertSql(null, [__DIR__.'/Config/sql/destroy.sql']);
|
||||
}
|
||||
|
||||
public function getUploadDir()
|
||||
{
|
||||
$uploadDir = ConfigQuery::read('images_library_path');
|
||||
|
||||
if ($uploadDir === null) {
|
||||
$uploadDir = THELIA_LOCAL_DIR.'media'.DS.'images';
|
||||
} else {
|
||||
$uploadDir = THELIA_ROOT.$uploadDir;
|
||||
}
|
||||
|
||||
return $uploadDir.DS.self::DOMAIN_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $currentVersion
|
||||
* @param string $newVersion
|
||||
*
|
||||
* @author Thomas Arnaud <tarnaud@openstudio.fr>
|
||||
*/
|
||||
public function update($currentVersion, $newVersion, ConnectionInterface $con = null): void
|
||||
{
|
||||
$uploadDir = $this->getUploadDir();
|
||||
$fileSystem = new Filesystem();
|
||||
|
||||
if (!$fileSystem->exists($uploadDir) && $fileSystem->exists(__DIR__.DS.'media'.DS.'carousel')) {
|
||||
$finder = new Finder();
|
||||
$finder->files()->in(__DIR__.DS.'media'.DS.'carousel');
|
||||
|
||||
$fileSystem->mkdir($uploadDir);
|
||||
|
||||
/** @var SplFileInfo $file */
|
||||
foreach ($finder as $file) {
|
||||
copy($file, $uploadDir.DS.$file->getRelativePathname());
|
||||
}
|
||||
$fileSystem->remove(__DIR__.DS.'media');
|
||||
}
|
||||
|
||||
$finder = (new Finder())->files()->name('#.*?\.sql#')->sortByName()->in(__DIR__.DS.'Config'.DS.'update');
|
||||
|
||||
if (0 === $finder->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$database = new Database($con);
|
||||
|
||||
// apply update only if table exists
|
||||
if ($database->execute("SHOW TABLES LIKE 'carousel'")->rowCount() === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var SplFileInfo $updateSQLFile */
|
||||
foreach ($finder as $updateSQLFile) {
|
||||
if (version_compare($currentVersion, str_replace('.sql', '', $updateSQLFile->getFilename()), '<')) {
|
||||
$database->insertSql(null, [$updateSQLFile->getPathname()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how services are loaded in your modules.
|
||||
*/
|
||||
public static function configureServices(ServicesConfigurator $servicesConfigurator): void
|
||||
{
|
||||
$servicesConfigurator->load(self::getModuleCode().'\\', __DIR__)
|
||||
->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*'])
|
||||
->autowire(true)
|
||||
->autoconfigure(true);
|
||||
}
|
||||
}
|
||||
51
domokits/local/modules/Carousel/Config/TheliaMain.sql
Normal file
51
domokits/local/modules/Carousel/Config/TheliaMain.sql
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
# This is a fix for InnoDB in MySQL >= 4.1.x
|
||||
# It "suspends judgement" for fkey relationships until are tables are set.
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- carousel
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
DROP TABLE IF EXISTS `carousel`;
|
||||
|
||||
CREATE TABLE `carousel`
|
||||
(
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||||
`file` VARCHAR(255),
|
||||
`position` INTEGER,
|
||||
`disable` INTEGER,
|
||||
`group` VARCHAR(255),
|
||||
`url` VARCHAR(255),
|
||||
`limited` INTEGER,
|
||||
`start_date` DATETIME,
|
||||
`end_date` DATETIME,
|
||||
`created_at` DATETIME,
|
||||
`updated_at` DATETIME,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- carousel_i18n
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
DROP TABLE IF EXISTS `carousel_i18n`;
|
||||
|
||||
CREATE TABLE `carousel_i18n`
|
||||
(
|
||||
`id` INTEGER NOT NULL,
|
||||
`locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL,
|
||||
`alt` VARCHAR(255),
|
||||
`title` VARCHAR(255),
|
||||
`description` LONGTEXT,
|
||||
`chapo` TEXT,
|
||||
`postscriptum` TEXT,
|
||||
PRIMARY KEY (`id`,`locale`),
|
||||
CONSTRAINT `carousel_i18n_fk_2ec1b2`
|
||||
FOREIGN KEY (`id`)
|
||||
REFERENCES `carousel` (`id`)
|
||||
ON DELETE CASCADE
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
# This restores the fkey checks, after having unset them earlier
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
15
domokits/local/modules/Carousel/Config/config.xml
Normal file
15
domokits/local/modules/Carousel/Config/config.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<config xmlns="http://thelia.net/schema/dic/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://thelia.net/schema/dic/config http://thelia.net/schema/dic/config/thelia-1.0.xsd">
|
||||
<hooks>
|
||||
<hook id="carousel.hook">
|
||||
<tag name="hook.event_listener" event="home.body" type="front" templates="render:carousel.html" />
|
||||
<tag name="hook.event_listener" event="module.configuration" type="back" templates="render:module_configuration.html" />
|
||||
<tag name="hook.event_listener" event="module.config-js" type="back" templates="js:assets/js/module-configuration.js" />
|
||||
</hook>
|
||||
<hook id="carousel.hook.back" class="Carousel\Hook\BackHook">
|
||||
<tag name="hook.event_listener" event="main.top-menu-tools" type="back" />
|
||||
</hook>
|
||||
</hooks>
|
||||
</config>
|
||||
24
domokits/local/modules/Carousel/Config/module.xml
Normal file
24
domokits/local/modules/Carousel/Config/module.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module xmlns="http://thelia.net/schema/dic/module"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://thelia.net/schema/dic/module http://thelia.net/schema/dic/module/module-2_1.xsd">
|
||||
<fullnamespace>Carousel\Carousel</fullnamespace>
|
||||
<descriptive locale="en_US">
|
||||
<title>An image carousel</title>
|
||||
</descriptive>
|
||||
<descriptive locale="fr_FR">
|
||||
<title>Un carrousel d'images</title>
|
||||
</descriptive>
|
||||
<languages>
|
||||
<language>en_US</language>
|
||||
<language>fr_FR</language>
|
||||
</languages>
|
||||
<version>2.5.4</version>
|
||||
<author>
|
||||
<name>Manuel Raynaud, Franck Allimant</name>
|
||||
<email>manu@raynaud.io, franck@cqfdev.fr</email>
|
||||
</author>
|
||||
<type>classic</type>
|
||||
<thelia>2.5.4</thelia>
|
||||
<stability>alpha</stability>
|
||||
</module>
|
||||
42
domokits/local/modules/Carousel/Config/routing.xml
Normal file
42
domokits/local/modules/Carousel/Config/routing.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<routes xmlns="http://symfony.com/schema/routing"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
|
||||
|
||||
<!--
|
||||
|
||||
if a /admin/module/carousel/ route is provided, a "Configuration" button will be displayed
|
||||
for the module in the module list. Clicking this button will invoke this route.
|
||||
|
||||
<route id="my_route_id" path="/admin/module/carousel">
|
||||
<default key="_controller">Carousel\Full\Class\Name\Of\YourConfigurationController::methodName</default>
|
||||
</route>
|
||||
|
||||
<route id="my_route_id" path="/admin/module/carousel/route-name">
|
||||
<default key="_controller">Carousel\Full\Class\Name\Of\YourAdminController::methodName</default>
|
||||
</route>
|
||||
|
||||
<route id="my_route_id" path="/my/route/name">
|
||||
<default key="_controller">Carousel\Full\Class\Name\Of\YourOtherController::methodName</default>
|
||||
</route>
|
||||
|
||||
...add as many routes as required.
|
||||
|
||||
<route>
|
||||
...
|
||||
</route>
|
||||
-->
|
||||
<route id="carousel.upload.image" path="/admin/module/carousel/upload" methods="post">
|
||||
<default key="_controller">Carousel\Controller\ConfigurationController::uploadImage</default>
|
||||
</route>
|
||||
|
||||
<route id="carousel.update" path="/admin/module/carousel/update" methods="post">
|
||||
<default key="_controller">Carousel\Controller\ConfigurationController::updateAction</default>
|
||||
</route>
|
||||
|
||||
<route id="carousel.delete" path="/admin/module/carousel/delete" methods="post">
|
||||
<default key="_controller">Carousel\Controller\ConfigurationController::deleteAction</default>
|
||||
</route>
|
||||
|
||||
</routes>
|
||||
29
domokits/local/modules/Carousel/Config/schema.xml
Normal file
29
domokits/local/modules/Carousel/Config/schema.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<database defaultIdMethod="native" name="TheliaMain" namespace="Carousel\Model">
|
||||
<!--
|
||||
See propel documentation on http://propelorm.org for all information about schema file
|
||||
-->
|
||||
|
||||
<table name="carousel">
|
||||
<column autoIncrement="true" name="id" primaryKey="true" required="true" type="INTEGER" />
|
||||
<column name="file" type="VARCHAR" size="255" />
|
||||
<column name="position" type="INTEGER" />
|
||||
<column name="disable" type="INTEGER" />
|
||||
<column name="group" size="255" type="VARCHAR" />
|
||||
<column name="alt" size="255" type="VARCHAR" />
|
||||
<column name="url" size="255" type="VARCHAR" />
|
||||
<column name="title" size="255" type="VARCHAR" />
|
||||
<column name="description" type="CLOB" />
|
||||
<column name="chapo" type="LONGVARCHAR" />
|
||||
<column name="postscriptum" type="LONGVARCHAR" />
|
||||
<column name="limited" type="INTEGER" />
|
||||
<column name="start_date" type="TIMESTAMP" />
|
||||
<column name="end_date" type="TIMESTAMP" />
|
||||
<behavior name="timestampable" />
|
||||
<behavior name="i18n">
|
||||
<parameter name="i18n_columns" value="alt, title, description, chapo, postscriptum" />
|
||||
</behavior>
|
||||
</table>
|
||||
|
||||
<external-schema filename="local/config/schema.xml" referenceOnly="true" />
|
||||
</database>
|
||||
6
domokits/local/modules/Carousel/Config/sql/destroy.sql
Normal file
6
domokits/local/modules/Carousel/Config/sql/destroy.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
DROP TABLE IF EXISTS `carousel`;
|
||||
DROP TABLE IF EXISTS `carousel_i18n`;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
2
domokits/local/modules/Carousel/Config/sqldb.map
Normal file
2
domokits/local/modules/Carousel/Config/sqldb.map
Normal file
@@ -0,0 +1,2 @@
|
||||
# Sqlfile -> Database map
|
||||
TheliaMain.sql=TheliaMain
|
||||
1
domokits/local/modules/Carousel/Config/update/2.4.0.sql
Normal file
1
domokits/local/modules/Carousel/Config/update/2.4.0.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `carousel` ADD (`disable` INTEGER, `group` VARCHAR(255),`limited` INTEGER, `start_date` DATETIME, `end_date` DATETIME);
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Carousel\Controller;
|
||||
|
||||
use Carousel\Form\CarouselImageForm;
|
||||
use Carousel\Form\CarouselUpdateForm;
|
||||
use Carousel\Model\Carousel;
|
||||
use Carousel\Model\CarouselQuery;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Thelia\Controller\Admin\BaseAdminController;
|
||||
use Thelia\Core\Event\File\FileCreateOrUpdateEvent;
|
||||
use Thelia\Core\Event\TheliaEvents;
|
||||
use Thelia\Core\Form\TheliaFormFactory;
|
||||
use Thelia\Core\HttpFoundation\Request;
|
||||
use Thelia\Core\Security\AccessManager;
|
||||
use Thelia\Core\Security\Resource\AdminResources;
|
||||
use Thelia\Form\Exception\FormValidationException;
|
||||
use Thelia\Model\Lang;
|
||||
use Thelia\Model\LangQuery;
|
||||
use Thelia\Tools\URL;
|
||||
|
||||
/**
|
||||
* Class ConfigurationController.
|
||||
*
|
||||
* @author manuel raynaud <mraynaud@openstudio.fr>
|
||||
*/
|
||||
class ConfigurationController extends BaseAdminController
|
||||
{
|
||||
public function uploadImage(
|
||||
Request $request,
|
||||
TheliaFormFactory $formFactory,
|
||||
EventDispatcherInterface $eventDispatcher
|
||||
) {
|
||||
if (null !== $response = $this->checkAuth(AdminResources::MODULE, ['carousel'], AccessManager::CREATE)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$form = $formFactory->createForm(CarouselImageForm::class);
|
||||
$error_message = null;
|
||||
try {
|
||||
$formData = $this->validateForm($form)->getData();
|
||||
|
||||
/** @var UploadedFile $fileBeingUploaded */
|
||||
$fileBeingUploaded = $formData['file'];
|
||||
|
||||
$fileModel = new Carousel();
|
||||
|
||||
$fileCreateOrUpdateEvent = new FileCreateOrUpdateEvent(1);
|
||||
$fileCreateOrUpdateEvent->setModel($fileModel);
|
||||
$fileCreateOrUpdateEvent->setUploadedFile($fileBeingUploaded);
|
||||
|
||||
$eventDispatcher->dispatch(
|
||||
$fileCreateOrUpdateEvent,
|
||||
TheliaEvents::IMAGE_SAVE
|
||||
);
|
||||
|
||||
// Compensate issue #1005
|
||||
$langs = LangQuery::create()->find();
|
||||
|
||||
/** @var Lang $lang */
|
||||
foreach ($langs as $lang) {
|
||||
$fileCreateOrUpdateEvent->getModel()->setLocale($lang->getLocale())->setTitle('')->save();
|
||||
}
|
||||
|
||||
$response = $this->redirectToConfigurationPage();
|
||||
} catch (FormValidationException $e) {
|
||||
$error_message = $this->createStandardFormValidationErrorMessage($e);
|
||||
}
|
||||
|
||||
if (null !== $error_message) {
|
||||
$this->setupFormErrorContext(
|
||||
'carousel upload',
|
||||
$error_message,
|
||||
$form
|
||||
);
|
||||
|
||||
$response = $this->render(
|
||||
'module-configure',
|
||||
[
|
||||
'module_code' => 'Carousel',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Form $form
|
||||
* @param string $fieldName
|
||||
* @param int $id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFormFieldValue($form, $fieldName, $id)
|
||||
{
|
||||
$value = $form->get(sprintf('%s%d', $fieldName, $id))->getData();
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function updateAction(
|
||||
TheliaFormFactory $formFactory
|
||||
) {
|
||||
if (null !== $response = $this->checkAuth(AdminResources::MODULE, ['carousel'], AccessManager::UPDATE)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$form = $formFactory->createForm(CarouselUpdateForm::class);
|
||||
|
||||
$error_message = null;
|
||||
|
||||
try {
|
||||
$updateForm = $this->validateForm($form);
|
||||
|
||||
$carousels = CarouselQuery::create()->findAllByPosition();
|
||||
|
||||
$locale = $this->getCurrentEditionLocale();
|
||||
|
||||
/** @var Carousel $carousel */
|
||||
foreach ($carousels as $carousel) {
|
||||
$id = $carousel->getId();
|
||||
|
||||
$carousel
|
||||
->setPosition($this->getFormFieldValue($updateForm, 'position', $id))
|
||||
->setDisable($this->getFormFieldValue($updateForm, 'disable', $id))
|
||||
->setUrl($this->getFormFieldValue($updateForm, 'url', $id))
|
||||
->setLocale($locale)
|
||||
->setTitle($this->getFormFieldValue($updateForm, 'title', $id))
|
||||
->setAlt($this->getFormFieldValue($updateForm, 'alt', $id))
|
||||
->setChapo($this->getFormFieldValue($updateForm, 'chapo', $id))
|
||||
->setDescription($this->getFormFieldValue($updateForm, 'description', $id))
|
||||
->setPostscriptum($this->getFormFieldValue($updateForm, 'postscriptum', $id))
|
||||
->setGroup($this->getFormFieldValue($updateForm, 'group', $id))
|
||||
->setLimited($this->getFormFieldValue($updateForm, 'limited', $id))
|
||||
->setStartDate($this->getFormFieldValue($updateForm, 'start_date', $id))
|
||||
->setEndDate($this->getFormFieldValue($updateForm, 'end_date', $id))
|
||||
->save();
|
||||
}
|
||||
|
||||
$response = $this->redirectToConfigurationPage();
|
||||
} catch (FormValidationException $e) {
|
||||
$error_message = $this->createStandardFormValidationErrorMessage($e);
|
||||
}
|
||||
|
||||
if (null !== $error_message) {
|
||||
$this->setupFormErrorContext(
|
||||
'carousel upload',
|
||||
$error_message,
|
||||
$form
|
||||
);
|
||||
|
||||
$response = $this->render('module-configure', ['module_code' => 'Carousel']);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function deleteAction(
|
||||
Request $request
|
||||
) {
|
||||
if (null !== $response = $this->checkAuth(AdminResources::MODULE, ['carousel'], AccessManager::DELETE)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$imageId = $request->get('image_id');
|
||||
|
||||
if ($imageId != '') {
|
||||
$carousel = CarouselQuery::create()->findPk($imageId);
|
||||
|
||||
if (null !== $carousel) {
|
||||
$carousel->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->redirectToConfigurationPage();
|
||||
}
|
||||
|
||||
protected function redirectToConfigurationPage()
|
||||
{
|
||||
return new RedirectResponse(URL::getInstance()->absoluteUrl('/admin/module/Carousel'));
|
||||
}
|
||||
}
|
||||
46
domokits/local/modules/Carousel/Form/CarouselImageForm.php
Normal file
46
domokits/local/modules/Carousel/Form/CarouselImageForm.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Carousel\Form;
|
||||
|
||||
use Carousel\Carousel;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Validator\Constraints\Image;
|
||||
use Thelia\Core\Translation\Translator;
|
||||
use Thelia\Form\BaseForm;
|
||||
|
||||
/**
|
||||
* Class CarouselImageForm.
|
||||
*
|
||||
* @author manuel raynaud <mraynaud@openstudio.fr>
|
||||
*/
|
||||
class CarouselImageForm extends BaseForm
|
||||
{
|
||||
protected function buildForm(): void
|
||||
{
|
||||
$translator = Translator::getInstance();
|
||||
$this->formBuilder
|
||||
->add(
|
||||
'file',
|
||||
FileType::class,
|
||||
[
|
||||
'constraints' => [
|
||||
new Image(),
|
||||
],
|
||||
'label' => $translator->trans('Carousel image', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'file',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
222
domokits/local/modules/Carousel/Form/CarouselUpdateForm.php
Normal file
222
domokits/local/modules/Carousel/Form/CarouselUpdateForm.php
Normal file
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Carousel\Form;
|
||||
|
||||
use Carousel\Carousel;
|
||||
use Carousel\Model\CarouselQuery;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\UrlType;
|
||||
use Thelia\Form\BaseForm;
|
||||
|
||||
/**
|
||||
* Class CarouselUpdateForm.
|
||||
*
|
||||
* @author manuel raynaud <mraynaud@openstudio.fr>
|
||||
*/
|
||||
class CarouselUpdateForm extends BaseForm
|
||||
{
|
||||
protected function buildForm(): void
|
||||
{
|
||||
$formBuilder = $this->formBuilder;
|
||||
|
||||
$carousels = CarouselQuery::create()->orderByPosition()->find();
|
||||
|
||||
/** @var \Carousel\Model\Carousel $carousel */
|
||||
foreach ($carousels as $carousel) {
|
||||
$id = $carousel->getId();
|
||||
|
||||
$formBuilder->add(
|
||||
'position'.$id,
|
||||
TextType::class,
|
||||
[
|
||||
'label' => $this->translator->trans('Image position in carousel', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'position'.$id,
|
||||
],
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'placeholder' => $this->translator->trans(
|
||||
'Image position in carousel',
|
||||
[],
|
||||
Carousel::DOMAIN_NAME
|
||||
),
|
||||
],
|
||||
]
|
||||
)->add(
|
||||
'alt'.$id,
|
||||
TextType::class,
|
||||
[
|
||||
'label' => $this->translator->trans('Alternative image text', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'alt'.$id,
|
||||
],
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'placeholder' => $this->translator->trans(
|
||||
'Displayed when image is not visible',
|
||||
[],
|
||||
Carousel::DOMAIN_NAME
|
||||
),
|
||||
],
|
||||
]
|
||||
)->add(
|
||||
'group'.$id,
|
||||
TextType::class,
|
||||
[
|
||||
'label' => $this->translator->trans('Group image', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'group'.$id,
|
||||
],
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'placeholder' => $this->translator->trans(
|
||||
'Group of images',
|
||||
[],
|
||||
Carousel::DOMAIN_NAME
|
||||
),
|
||||
],
|
||||
]
|
||||
)->add(
|
||||
'url'.$id,
|
||||
UrlType::class,
|
||||
[
|
||||
'label' => $this->translator->trans('Image URL', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'url'.$id,
|
||||
],
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'placeholder' => $this->translator->trans(
|
||||
'Please enter a valid URL',
|
||||
[],
|
||||
Carousel::DOMAIN_NAME
|
||||
),
|
||||
],
|
||||
]
|
||||
)->add(
|
||||
'title'.$id,
|
||||
TextType::class,
|
||||
[
|
||||
'constraints' => [],
|
||||
'required' => false,
|
||||
'label' => $this->translator->trans('Title', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'title_field'.$id,
|
||||
],
|
||||
'attr' => [
|
||||
'placeholder' => $this->translator->trans('A descriptive title', [], Carousel::DOMAIN_NAME),
|
||||
],
|
||||
]
|
||||
)->add(
|
||||
'chapo'.$id,
|
||||
TextareaType::class,
|
||||
[
|
||||
'constraints' => [],
|
||||
'required' => false,
|
||||
'label' => $this->translator->trans('Summary', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'summary_field'.$id,
|
||||
'help' => $this->translator->trans(
|
||||
'A short description, used when a summary or an introduction is required',
|
||||
[],
|
||||
Carousel::DOMAIN_NAME
|
||||
),
|
||||
],
|
||||
'attr' => [
|
||||
'rows' => 3,
|
||||
'placeholder' => $this->translator->trans('Short description text', [], Carousel::DOMAIN_NAME),
|
||||
],
|
||||
]
|
||||
)->add(
|
||||
'description'.$id,
|
||||
TextareaType::class,
|
||||
[
|
||||
'constraints' => [],
|
||||
'required' => false,
|
||||
'label' => $this->translator->trans('Detailed description', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'detailed_description_field'.$id,
|
||||
'help' => $this->translator->trans('The detailed description.', [], Carousel::DOMAIN_NAME),
|
||||
],
|
||||
'attr' => [
|
||||
'rows' => 5,
|
||||
],
|
||||
]
|
||||
)->add(
|
||||
'disable'.$id,
|
||||
CheckboxType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $this->translator->trans('Disable image', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'enable'.$id,
|
||||
],
|
||||
]
|
||||
)->add(
|
||||
'limited'.$id,
|
||||
CheckboxType::class,
|
||||
[
|
||||
'required' => false,
|
||||
'label' => $this->translator->trans('Limited', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'limited'.$id,
|
||||
],
|
||||
]
|
||||
)->add(
|
||||
'start_date'.$id,
|
||||
DateTimeType::class,
|
||||
[
|
||||
'label' => $this->translator->trans('Start date', [], Carousel::DOMAIN_NAME),
|
||||
'widget' => 'single_text',
|
||||
'required' => false,
|
||||
]
|
||||
)->add(
|
||||
'end_date'.$id,
|
||||
DateTimeType::class,
|
||||
[
|
||||
'label' => $this->translator->trans('End date', [], Carousel::DOMAIN_NAME),
|
||||
'widget' => 'single_text',
|
||||
'required' => false,
|
||||
]
|
||||
)->add(
|
||||
'postscriptum'.$id,
|
||||
TextareaType::class,
|
||||
[
|
||||
'constraints' => [],
|
||||
'required' => false,
|
||||
'label' => $this->translator->trans('Conclusion', [], Carousel::DOMAIN_NAME),
|
||||
'label_attr' => [
|
||||
'for' => 'conclusion_field'.$id,
|
||||
'help' => $this->translator->trans(
|
||||
'A short text, used when an additional or supplemental information is required.',
|
||||
[],
|
||||
Carousel::DOMAIN_NAME
|
||||
),
|
||||
],
|
||||
'attr' => [
|
||||
'placeholder' => $this->translator->trans('Short additional text', [], Carousel::DOMAIN_NAME),
|
||||
'rows' => 3,
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'carousel_update';
|
||||
}
|
||||
}
|
||||
43
domokits/local/modules/Carousel/Hook/BackHook.php
Normal file
43
domokits/local/modules/Carousel/Hook/BackHook.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Carousel\Hook;
|
||||
|
||||
use Carousel\Carousel;
|
||||
use Thelia\Core\Event\Hook\HookRenderBlockEvent;
|
||||
use Thelia\Core\Hook\BaseHook;
|
||||
use Thelia\Tools\URL;
|
||||
|
||||
/**
|
||||
* Class BackHook.
|
||||
*
|
||||
* @author Emmanuel Nurit <enurit@openstudio.fr>
|
||||
*/
|
||||
class BackHook extends BaseHook
|
||||
{
|
||||
/**
|
||||
* Add a new entry in the admin tools menu.
|
||||
*
|
||||
* should add to event a fragment with fields : id,class,url,title
|
||||
*/
|
||||
public function onMainTopMenuTools(HookRenderBlockEvent $event): void
|
||||
{
|
||||
$event->add(
|
||||
[
|
||||
'id' => 'tools_menu_carousel',
|
||||
'class' => '',
|
||||
'url' => URL::getInstance()->absoluteUrl('/admin/module/Carousel'),
|
||||
'title' => $this->trans('Edit your carousel', [], Carousel::DOMAIN_NAME),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'Add an image to the carousel' => 'Ein Bild zu Karussell hinzufügen',
|
||||
'Add this image to the carousel' => 'Dieses Bild zu Karussell hinzufügen',
|
||||
'Carousel image' => 'Karussell-Bild',
|
||||
'Carousel images' => 'Karussell-Bilder',
|
||||
'Delete a carousel image' => 'Ein Karussell-Bild löschen',
|
||||
'Do you really want to remove this image from the carousel ?' => 'Wollen Sie dieses Bild wirklich aus dem Karussell entfernen?',
|
||||
'Edit your carousel.' => 'Karussell bearbeiten.',
|
||||
'Remove this image' => 'Dieses Bild entfernen',
|
||||
'Your carousel contains no image. Please add one using the form above.' => 'Das Karussell enthält kein Bild. Bitte fügen Sie mit dem Formular oben eines hinzu.',
|
||||
'Position' => 'Position',
|
||||
];
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'Add an image to the carousel' => 'Add an image to the carousel',
|
||||
'Add this image to the carousel' => 'Add this image to the carousel',
|
||||
'Carousel image' => 'Carousel image',
|
||||
'Carousel images' => 'Carousel images',
|
||||
'Delete a carousel image' => 'Delete a carousel image',
|
||||
'Do you really want to remove this image from the carousel ?' => 'Do you really want to remove this image from the carousel ?',
|
||||
'Edit your carousel.' => 'Edit your carousel.',
|
||||
'Remove this image' => 'Remove this image',
|
||||
'Your carousel contains no image. Please add one using the form above.' => 'Your carousel contains no image. Please add one using the form above.',
|
||||
'Position' => 'Position',
|
||||
'YYYY-MM-DD' => 'YYYY-MM-DD',
|
||||
];
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'Add an image to the carousel' => 'Ajouter une image au carrousel',
|
||||
'Add this image to the carousel' => 'Ajouter l\'image au carrousel',
|
||||
'Carousel image' => 'Image du carrousel',
|
||||
'Carousel images' => 'Images du carrousel',
|
||||
'Delete a carousel image' => 'Supprimer une image du carrousel',
|
||||
'Do you really want to remove this image from the carousel ?' => 'Voulez-vous vraiment retirer cette image du carrousel ?',
|
||||
'Edit your carousel.' => 'Modifier votre carrousel',
|
||||
'Remove this image' => 'Supprimer cette image',
|
||||
'Your carousel contains no image. Please add one using the form above.' => 'Votre carrousel ne contient aucune image. Ajoutez votre première image avec le formulaire ci-dessus',
|
||||
'Position' => 'Position',
|
||||
'YYYY-MM-DD' => 'AAAA-MM-JJ',
|
||||
];
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'Add an image to the carousel' => 'Добавить изображение в карусель',
|
||||
'Add this image to the carousel' => 'Добавить это изображение в карусель',
|
||||
'Carousel image' => 'Изображение карусели',
|
||||
'Carousel images' => 'Изображения карусели',
|
||||
'Delete a carousel image' => 'Удалить изображение карусели',
|
||||
'Do you really want to remove this image from the carousel ?' => 'Вы действительно хотите удалить это изображение из карусели ?',
|
||||
'Edit your carousel.' => 'Редактировать вашу карусель.',
|
||||
'Remove this image' => 'Удалить это изображение',
|
||||
'Your carousel contains no image. Please add one using the form above.' => 'Ваша карусель не содержит изображений. Пожалуйста, добавьте одно используя форму ниже.',
|
||||
'Position' => 'Позиция',
|
||||
];
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'Add an image to the carousel' => 'Slayt için bir resim ekle',
|
||||
'Add this image to the carousel' => 'slayt için bu resim ekleme',
|
||||
'Carousel image' => 'slayt görüntü',
|
||||
'Carousel images' => 'slayt görüntüleri',
|
||||
'Delete a carousel image' => 'Bir slayt resmi silme',
|
||||
'Do you really want to remove this image from the carousel ?' => 'Bu görüntüyü slayttan kaldırmak istiyor musunuz?',
|
||||
'Edit your carousel.' => 'slayt düzenleyin.',
|
||||
'Remove this image' => 'Bu resmi kaldırma',
|
||||
'Your carousel contains no image. Please add one using the form above.' => 'Senin slayt hiçbir görüntü içermiyor . Lütfen yukarıdaki formu kullanarak ekleyin.',
|
||||
'Position' => 'Pozisyon',
|
||||
];
|
||||
30
domokits/local/modules/Carousel/I18n/de_DE.php
Normal file
30
domokits/local/modules/Carousel/I18n/de_DE.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'A descriptive title' => 'Beschreibungstitel',
|
||||
'A short description, used when a summary or an introduction is required' => 'Eine kurze beschreibung, benutzt wenn eine Zusammenfassung order eine Einleitung ist nötig',
|
||||
'A short text, used when an additional or supplemental information is required.' => 'Ein kurzer Text, der verwendet wird, wenn eine zusätzliche oder ergänzende Information erforderlich ist.',
|
||||
'Alternative image text' => 'Alternativer Bildtext',
|
||||
'Carousel image' => 'Karussell-Bild',
|
||||
'Conclusion' => 'Abschluss',
|
||||
'Detailed description' => 'Detaillierte Beschreibung',
|
||||
'Displayed when image is not visible' => 'Angezeigt, wenn das Bild nicht sichtbar ist',
|
||||
'Image URL' => 'Bild-URL',
|
||||
'Image position in carousel' => 'Position des Bildes im Karussell',
|
||||
'Please enter a valid URL' => 'Bitte geben Sie eine gültige URL ein',
|
||||
'Short additional text' => 'Kurzer zusätzlicher Text',
|
||||
'Short description text' => 'Kurzes Beschreibungstext',
|
||||
'Summary' => 'Zusammenfassung',
|
||||
'The detailed description.' => 'Die detaillierte Beschreibung.',
|
||||
'Title' => 'Titel',
|
||||
];
|
||||
32
domokits/local/modules/Carousel/I18n/en_US.php
Normal file
32
domokits/local/modules/Carousel/I18n/en_US.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'A descriptive title' => 'A descriptive title',
|
||||
'A short description, used when a summary or an introduction is required' => 'A short description, used when a summary or an introduction is required',
|
||||
'A short text, used when an additional or supplemental information is required.' => 'A short text, used when an additional or supplemental information is required.',
|
||||
'Alternative image text' => 'Alternative image text',
|
||||
'Carousel image' => 'Carousel image',
|
||||
'Conclusion' => 'Conclusion',
|
||||
'Detailed description' => 'Detailed description',
|
||||
'Displayed when image is not visible' => 'Displayed when image is not visible',
|
||||
'Edit your carousel' => 'Edit your carousel',
|
||||
'Image URL' => 'Image URL',
|
||||
'Image position in carousel' => 'Image position in carousel',
|
||||
'Please enter a valid URL' => 'Please enter a valid URL',
|
||||
'Short additional text' => 'Short additional text',
|
||||
'Short description text' => 'Short description text',
|
||||
'Summary' => 'Summary',
|
||||
'The detailed description.' => 'The detailed description.',
|
||||
'Title' => 'Title',
|
||||
'YYYY-MM-DD' => 'AAAA-MM-JJ',
|
||||
];
|
||||
37
domokits/local/modules/Carousel/I18n/fr_FR.php
Normal file
37
domokits/local/modules/Carousel/I18n/fr_FR.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'A descriptive title' => 'Un titre descriptif',
|
||||
'A short description, used when a summary or an introduction is required' => 'Une courte description, utilisée lorsqu\'un résumé ou une introduction est requise',
|
||||
'A short text, used when an additional or supplemental information is required.' => 'Un texte court, utilisé quand une conclusion ou une information complémentaire est nécessaire.',
|
||||
'Alternative image text' => 'Texte alternatif de l\'image',
|
||||
'Carousel image' => 'Image du carrousel',
|
||||
'Conclusion' => 'Conclusion',
|
||||
'Detailed description' => 'Description détaillée',
|
||||
'Disable image' => 'Désactiver l\'image',
|
||||
'Displayed when image is not visible' => 'Affiché lorsque l\'image n\'est pas visible',
|
||||
'Edit your carousel' => 'Modifier votre carousel',
|
||||
'End date' => 'Date de fin',
|
||||
'Group image' => 'Groupe de l\'image',
|
||||
'Group of images' => 'Nom du groupe auquel l\'image appartient',
|
||||
'Image URL' => 'URL de l\'image',
|
||||
'Image position in carousel' => 'Position de l\'image dans le carrousel',
|
||||
'Limited' => 'Afficher l\'image entre les dates ci-dessous',
|
||||
'Please enter a valid URL' => 'Merci d\'indiquer une URL valide',
|
||||
'Short additional text' => 'Un court texte supplémentaire',
|
||||
'Short description text' => 'Un court texte de description',
|
||||
'Start date' => 'Date de début',
|
||||
'Summary' => 'Résumé',
|
||||
'The detailed description.' => 'La description détaillée.',
|
||||
'Title' => 'Titre',
|
||||
];
|
||||
21
domokits/local/modules/Carousel/I18n/it_IT.php
Normal file
21
domokits/local/modules/Carousel/I18n/it_IT.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'A descriptive title' => 'Un titolo descrittivo',
|
||||
'A short description, used when a summary or an introduction is required' => 'Una breve descrizione, utilizzata quando è necessario un sommario o un\'introduzione',
|
||||
'Conclusion' => 'Conclusione',
|
||||
'Detailed description' => 'Descrizione dettagliata',
|
||||
'Summary' => 'Riassunto',
|
||||
'The detailed description.' => 'La descrizione dettagliata.',
|
||||
'Title' => 'Titolo',
|
||||
];
|
||||
31
domokits/local/modules/Carousel/I18n/ru_RU.php
Normal file
31
domokits/local/modules/Carousel/I18n/ru_RU.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'A descriptive title' => 'Описательный заголовок',
|
||||
'A short description, used when a summary or an introduction is required' => 'Краткое описание, используется когда необходимо',
|
||||
'A short text, used when an additional or supplemental information is required.' => 'Краткий текст используемый, когда необходима дополнительной информации.',
|
||||
'Alternative image text' => 'Альтернативный текст изображения',
|
||||
'Carousel image' => 'Изображение карусели',
|
||||
'Conclusion' => 'Заключение',
|
||||
'Detailed description' => 'Детальное описание',
|
||||
'Displayed when image is not visible' => 'Отображается когда изображения не видно',
|
||||
'Edit your carousel' => 'Редактировать вашу карусель',
|
||||
'Image URL' => 'URL изображения',
|
||||
'Image position in carousel' => 'Позиция изображения в карусели',
|
||||
'Please enter a valid URL' => 'Пожалуйста введите корректный URL',
|
||||
'Short additional text' => 'Краткий дополнительный текст',
|
||||
'Short description text' => 'Текст краткого описания',
|
||||
'Summary' => 'Краткое описание',
|
||||
'The detailed description.' => 'Детальное описание',
|
||||
'Title' => 'Заголовок',
|
||||
];
|
||||
30
domokits/local/modules/Carousel/I18n/tr_TR.php
Normal file
30
domokits/local/modules/Carousel/I18n/tr_TR.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'A descriptive title' => 'Açıklayıcı bir başlık',
|
||||
'A short description, used when a summary or an introduction is required' => 'Bir Özeti veya giriş gerekli olduğunda kullanılan kısa bir açıklama',
|
||||
'A short text, used when an additional or supplemental information is required.' => 'Bir ek ya da tamamlayıcı bilgi gerekli olduğunda kullanılan kısa bir metin.',
|
||||
'Alternative image text' => 'Alternatif resim metini',
|
||||
'Carousel image' => 'slayt görüntü',
|
||||
'Conclusion' => 'Sonuç',
|
||||
'Detailed description' => 'Detaylı açıklama',
|
||||
'Displayed when image is not visible' => 'resim görünür olmadığında görüntülenen',
|
||||
'Image URL' => 'Resim Bağlantı [Link]',
|
||||
'Image position in carousel' => 'slayt bulunduğu resim',
|
||||
'Please enter a valid URL' => 'Lütfen geçerli bir URL girin',
|
||||
'Short additional text' => 'Kısa ek metin',
|
||||
'Short description text' => 'Kısa açıklama metni',
|
||||
'Summary' => 'Özet',
|
||||
'The detailed description.' => 'Ayrıntılı açıklama.',
|
||||
'Title' => 'Başlık',
|
||||
];
|
||||
237
domokits/local/modules/Carousel/Loop/Carousel.php
Normal file
237
domokits/local/modules/Carousel/Loop/Carousel.php
Normal file
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Carousel\Loop;
|
||||
|
||||
use Carousel\Model\CarouselQuery;
|
||||
use Propel\Runtime\ActiveQuery\Criteria;
|
||||
use Thelia\Core\Event\Image\ImageEvent;
|
||||
use Thelia\Core\Event\TheliaEvents;
|
||||
use Thelia\Core\Template\Element\LoopResult;
|
||||
use Thelia\Core\Template\Element\LoopResultRow;
|
||||
use Thelia\Core\Template\Loop\Argument\Argument;
|
||||
use Thelia\Core\Template\Loop\Argument\ArgumentCollection;
|
||||
use Thelia\Core\Template\Loop\Image;
|
||||
use Thelia\Log\Tlog;
|
||||
use Thelia\Type\EnumListType;
|
||||
use Thelia\Type\EnumType;
|
||||
use Thelia\Type\TypeCollection;
|
||||
|
||||
/**
|
||||
* Class CarouselLoop.
|
||||
*
|
||||
* @author manuel raynaud <mraynaud@openstudio.fr>
|
||||
*/
|
||||
class Carousel extends Image
|
||||
{
|
||||
protected function getArgDefinitions()
|
||||
{
|
||||
return new ArgumentCollection(
|
||||
Argument::createIntTypeArgument('width'),
|
||||
Argument::createIntTypeArgument('height'),
|
||||
Argument::createIntTypeArgument('rotation', 0),
|
||||
Argument::createAnyTypeArgument('background_color'),
|
||||
Argument::createIntTypeArgument('quality'),
|
||||
new Argument(
|
||||
'resize_mode',
|
||||
new TypeCollection(
|
||||
new EnumType(['crop', 'borders', 'none'])
|
||||
),
|
||||
'none'
|
||||
),
|
||||
new Argument(
|
||||
'order',
|
||||
new TypeCollection(
|
||||
new EnumListType(['alpha', 'alpha-reverse', 'manual', 'manual-reverse', 'random'])
|
||||
),
|
||||
'manual'
|
||||
),
|
||||
Argument::createAnyTypeArgument('effects'),
|
||||
Argument::createBooleanTypeArgument('allow_zoom', false),
|
||||
Argument::createBooleanTypeArgument('filter_disable_slides', true),
|
||||
Argument::createAlphaNumStringTypeArgument('group'),
|
||||
Argument::createAlphaNumStringTypeArgument('format')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Propel\Runtime\Exception\PropelException
|
||||
*
|
||||
* @return LoopResult
|
||||
*/
|
||||
public function parseResults(LoopResult $loopResult)
|
||||
{
|
||||
/** @var \Carousel\Model\Carousel $carousel */
|
||||
foreach ($loopResult->getResultDataCollection() as $carousel) {
|
||||
$imgSourcePath = $carousel->getUploadDir().DS.$carousel->getFile();
|
||||
if (!file_exists($imgSourcePath)) {
|
||||
Tlog::getInstance()->error(sprintf('Carousel source image file %s does not exists.', $imgSourcePath));
|
||||
continue;
|
||||
}
|
||||
|
||||
$startDate = $carousel->getStartDate();
|
||||
$endDate = $carousel->getEndDate();
|
||||
|
||||
if ($carousel->getLimited()) {
|
||||
$now = new \DateTime();
|
||||
if ($carousel->getDisable()) {
|
||||
if ($now > $startDate && $now < $endDate) {
|
||||
$carousel
|
||||
->setDisable(0)
|
||||
->save();
|
||||
}
|
||||
} else {
|
||||
if ($now < $startDate || $now > $endDate) {
|
||||
$carousel
|
||||
->setDisable(1)
|
||||
->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->getFilterDisableSlides() && $carousel->getDisable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$loopResultRow = new LoopResultRow($carousel);
|
||||
|
||||
$event = new ImageEvent();
|
||||
$event->setSourceFilepath($imgSourcePath)
|
||||
->setCacheSubdirectory('carousel');
|
||||
|
||||
switch ($this->getResizeMode()) {
|
||||
case 'crop':
|
||||
$resize_mode = \Thelia\Action\Image::EXACT_RATIO_WITH_CROP;
|
||||
break;
|
||||
case 'borders':
|
||||
$resize_mode = \Thelia\Action\Image::EXACT_RATIO_WITH_BORDERS;
|
||||
break;
|
||||
case 'none':
|
||||
default:
|
||||
$resize_mode = \Thelia\Action\Image::KEEP_IMAGE_RATIO;
|
||||
}
|
||||
|
||||
// Prepare tranformations
|
||||
$width = $this->getWidth();
|
||||
$height = $this->getHeight();
|
||||
$rotation = $this->getRotation();
|
||||
$background_color = $this->getBackgroundColor();
|
||||
$quality = $this->getQuality();
|
||||
$effects = $this->getEffects();
|
||||
$format = $this->getFormat();
|
||||
|
||||
if (null !== $width) {
|
||||
$event->setWidth($width);
|
||||
}
|
||||
if (null !== $height) {
|
||||
$event->setHeight($height);
|
||||
}
|
||||
$event->setResizeMode($resize_mode);
|
||||
if (null !== $rotation) {
|
||||
$event->setRotation($rotation);
|
||||
}
|
||||
if (null !== $background_color) {
|
||||
$event->setBackgroundColor($background_color);
|
||||
}
|
||||
if (null !== $quality) {
|
||||
$event->setQuality($quality);
|
||||
}
|
||||
if (null !== $effects) {
|
||||
$event->setEffects($effects);
|
||||
}
|
||||
if (null !== $format) {
|
||||
$event->setFormat($format);
|
||||
}
|
||||
|
||||
$event->setAllowZoom($this->getAllowZoom());
|
||||
|
||||
// Dispatch image processing event
|
||||
$this->dispatcher->dispatch($event, TheliaEvents::IMAGE_PROCESS);
|
||||
|
||||
if ($startDate) {
|
||||
$startDate = $startDate->format('Y-m-d').'T'.$startDate->format('H:i');
|
||||
}
|
||||
if ($endDate) {
|
||||
$endDate = $endDate->format('Y-m-d').'T'.$endDate->format('H:i');
|
||||
}
|
||||
|
||||
$loopResultRow
|
||||
->set('ID', $carousel->getId())
|
||||
->set('LOCALE', $this->locale)
|
||||
->set('IMAGE_URL', $event->getFileUrl())
|
||||
->set('ORIGINAL_IMAGE_URL', $event->getOriginalFileUrl())
|
||||
->set('IMAGE_PATH', $event->getCacheFilepath())
|
||||
->set('ORIGINAL_IMAGE_PATH', $event->getSourceFilepath())
|
||||
->set('TITLE', $carousel->getVirtualColumn('i18n_TITLE'))
|
||||
->set('CHAPO', $carousel->getVirtualColumn('i18n_CHAPO'))
|
||||
->set('DESCRIPTION', $carousel->getVirtualColumn('i18n_DESCRIPTION'))
|
||||
->set('POSTSCRIPTUM', $carousel->getVirtualColumn('i18n_POSTSCRIPTUM'))
|
||||
->set('ALT', $carousel->getVirtualColumn('i18n_ALT'))
|
||||
->set('URL', $carousel->getUrl())
|
||||
->set('POSITION', $carousel->getPosition())
|
||||
->set('DISABLE', $carousel->getDisable())
|
||||
->set('GROUP', $carousel->getGroup())
|
||||
->set('LIMITED', $carousel->getLimited())
|
||||
->set('START_DATE', $startDate)
|
||||
->set('END_DATE', $endDate)
|
||||
;
|
||||
|
||||
$loopResult->addRow($loopResultRow);
|
||||
}
|
||||
|
||||
return $loopResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* this method returns a Propel ModelCriteria.
|
||||
*
|
||||
* @return \Propel\Runtime\ActiveQuery\ModelCriteria
|
||||
*/
|
||||
public function buildModelCriteria()
|
||||
{
|
||||
$search = CarouselQuery::create();
|
||||
$group = $this->getGroup();
|
||||
|
||||
$this->configureI18nProcessing($search, ['ALT', 'TITLE', 'CHAPO', 'DESCRIPTION', 'POSTSCRIPTUM']);
|
||||
|
||||
$orders = $this->getOrder();
|
||||
|
||||
// Results ordering
|
||||
foreach ($orders as $order) {
|
||||
switch ($order) {
|
||||
case 'alpha':
|
||||
$search->addAscendingOrderByColumn('i18n_TITLE');
|
||||
break;
|
||||
case 'alpha-reverse':
|
||||
$search->addDescendingOrderByColumn('i18n_TITLE');
|
||||
break;
|
||||
case 'manual-reverse':
|
||||
$search->orderByPosition(Criteria::DESC);
|
||||
break;
|
||||
case 'manual':
|
||||
$search->orderByPosition(Criteria::ASC);
|
||||
break;
|
||||
case 'random':
|
||||
$search->clearOrderByColumns();
|
||||
$search->addAscendingOrderByColumn('RAND()');
|
||||
break 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($group) {
|
||||
$search->filterByGroup($group);
|
||||
}
|
||||
|
||||
return $search;
|
||||
}
|
||||
}
|
||||
120
domokits/local/modules/Carousel/Model/Carousel.php
Normal file
120
domokits/local/modules/Carousel/Model/Carousel.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Carousel\Model;
|
||||
|
||||
use Carousel\Model\Base\Carousel as BaseCarousel;
|
||||
use Propel\Runtime\ActiveQuery\ModelCriteria;
|
||||
use Propel\Runtime\Connection\ConnectionInterface;
|
||||
use Symfony\Component\Filesystem\Exception\IOException;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Thelia\Files\FileModelInterface;
|
||||
use Thelia\Files\FileModelParentInterface;
|
||||
use Thelia\Form\BaseForm;
|
||||
|
||||
class Carousel extends BaseCarousel implements FileModelInterface
|
||||
{
|
||||
public function preDelete(ConnectionInterface $con = null)
|
||||
{
|
||||
$carousel = new \Carousel\Carousel();
|
||||
|
||||
$fs = new Filesystem();
|
||||
|
||||
try {
|
||||
$fs->remove($carousel->getUploadDir().DS.$this->getFile());
|
||||
|
||||
return true;
|
||||
} catch (IOException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set file parent id.
|
||||
*
|
||||
* @param int $parentId parent id
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setParentId($parentId)
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file parent id.
|
||||
*
|
||||
* @return int parent id
|
||||
*/
|
||||
public function getParentId()
|
||||
{
|
||||
return $this->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FileModelParentInterface the parent file model
|
||||
*/
|
||||
public function getParentFileModel()
|
||||
{
|
||||
return new static();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the form used to change this object information.
|
||||
*
|
||||
* @return BaseForm the form
|
||||
*/
|
||||
public function getUpdateFormId()
|
||||
{
|
||||
return 'carousel.image';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the path to the upload directory where files are stored, without final slash
|
||||
*/
|
||||
public function getUploadDir()
|
||||
{
|
||||
$carousel = new \Carousel\Carousel();
|
||||
|
||||
return $carousel->getUploadDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the URL to redirect to after update from the back-office
|
||||
*/
|
||||
public function getRedirectionUrl()
|
||||
{
|
||||
return '/admin/module/Carousel';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Query instance for this object.
|
||||
*
|
||||
* @return ModelCriteria
|
||||
*/
|
||||
public function getQueryInstance()
|
||||
{
|
||||
return CarouselQuery::create();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $visible true if the file is visible, false otherwise
|
||||
*
|
||||
* @return FileModelInterface
|
||||
*/
|
||||
public function setVisible($visible)
|
||||
{
|
||||
// Not implemented
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
19
domokits/local/modules/Carousel/Model/CarouselI18n.php
Normal file
19
domokits/local/modules/Carousel/Model/CarouselI18n.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Carousel\Model;
|
||||
|
||||
use Carousel\Model\Base\CarouselI18n as BaseCarouselI18n;
|
||||
|
||||
class CarouselI18n extends BaseCarouselI18n
|
||||
{
|
||||
}
|
||||
26
domokits/local/modules/Carousel/Model/CarouselI18nQuery.php
Normal file
26
domokits/local/modules/Carousel/Model/CarouselI18nQuery.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Carousel\Model;
|
||||
|
||||
use Carousel\Model\Base\CarouselI18nQuery as BaseCarouselI18nQuery;
|
||||
|
||||
/**
|
||||
* Skeleton subclass for performing query and update operations on the 'carousel_i18n' table.
|
||||
*
|
||||
* You should add additional methods to this class to meet the
|
||||
* application requirements. This class will only be generated as
|
||||
* long as it does not already exist in the output directory.
|
||||
*/
|
||||
class CarouselI18nQuery extends BaseCarouselI18nQuery
|
||||
{
|
||||
} // CarouselI18nQuery
|
||||
31
domokits/local/modules/Carousel/Model/CarouselQuery.php
Normal file
31
domokits/local/modules/Carousel/Model/CarouselQuery.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Carousel\Model;
|
||||
|
||||
use Carousel\Model\Base\CarouselQuery as BaseCarouselQuery;
|
||||
|
||||
/**
|
||||
* Skeleton subclass for performing query and update operations on the 'carousel' table.
|
||||
*
|
||||
* You should add additional methods to this class to meet the
|
||||
* application requirements. This class will only be generated as
|
||||
* long as it does not already exist in the output directory.
|
||||
*/
|
||||
class CarouselQuery extends BaseCarouselQuery
|
||||
{
|
||||
public function findAllByPosition()
|
||||
{
|
||||
return $this->orderByPosition()
|
||||
->find();
|
||||
}
|
||||
} // CarouselQuery
|
||||
69
domokits/local/modules/Carousel/Readme.md
Normal file
69
domokits/local/modules/Carousel/Readme.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Carousel
|
||||
|
||||
This module for Thelia add a customizable carousel on your home page. You can upload you own image and overload the default template in your template for using the carousel.
|
||||
|
||||
## Installation
|
||||
|
||||
* Copy the module into ```<thelia_root>/local/modules/``` directory and be sure that the name of the module is Carousel.
|
||||
* Activate it in your thelia administration panel
|
||||
|
||||
## Usage
|
||||
|
||||
In the configuration panel of this module, you can upload as many images as you want.
|
||||
|
||||
## Hook
|
||||
|
||||
The carousel is installed in the "Home page - main area" (home.body) hook.
|
||||
|
||||
## Loop
|
||||
|
||||
Customize images with the `carousel` loop, which has the same arguments as the `image` loop. You can define a width, a height, and many other parameters
|
||||
|
||||
### Input arguments
|
||||
|
||||
|Argument |Description |
|
||||
|--- |--- |
|
||||
|**width** | A width in pixels, for resizing image. If only the width is provided, the image ratio is preserved. Example : width="200" |
|
||||
|**height** | A height in pixels, for resizing image. If only the height is provided, the image ratio is preserved. example : height="200" |
|
||||
|**rotation** |The rotation angle in degrees (positive or negative) applied to the image. The background color of the empty areas is the one specified by 'background_color'. example : rotation="90" |
|
||||
|**background_color** |The color applied to empty image parts during processing. Use $rgb or $rrggbb color format. example : background_color="$cc8000"|
|
||||
|**quality** |The generated image quality, from 0(!) to 100%. The default value is 75% (you can hange this in the Administration panel). example : quality="70"|
|
||||
|**resize_mode** | If 'crop', the image will have the exact specified width and height, and will be cropped if required. If 'borders', the image will have the exact specified width and height, and some borders may be added. The border color is the one specified by 'background_color'. If 'none' or missing, the image ratio is preserved, and depending od this ratio, may not have the exact width and height required. resize_mode="crop"|
|
||||
|**effects** |One or more comma separated effects definitions, that will be applied to the image in the specified order. Please see below a detailed description of available effects. Expected values :<ul><li>gamma:value : change the image Gamma to the specified value. Example: gamma:0.7.</li><li>grayscale or greyscale : switch image to grayscale.</li><li>colorize:color : apply a color mask to the image. The color format is $rgb or $rrggbb. Example: colorize:$ff2244.</li><li>negative : transform the image in its negative equivalent.</li><li>vflip or vertical_flip : flip the image vertically.</li><li>hflip or horizontal_flip : flip the image horizontally.</li></ul>example : effects="greyscale,gamma:0.7,vflip" |
|
||||
|**group** |The name of an image group. Return only images from the specified group|
|
||||
|**filter_disable_slides** |if true (the default), the disabled slides will not be displayed|
|
||||
|
||||
### Ouput arguments
|
||||
|
||||
|Variable |Description |
|
||||
|--- |--- |
|
||||
|$ID |the image ID |
|
||||
|$IMAGE_URL |The absolute URL to the generated image |
|
||||
|$ORIGINAL_IMAGE_URL |The absolute URL to the original image |
|
||||
|$IMAGE_PATH |The absolute path to the generated image file |
|
||||
|$ORIGINAL_IMAGE_PATH |The absolute path to the original image file |
|
||||
|$ALT |alt text |
|
||||
|$TITLE |the slide title |
|
||||
|$CHAPO |the slide summary |
|
||||
|$DESCRIPTION |the slide description |
|
||||
|$POSTSCRIPTUM |the slide conclusion |
|
||||
|$LOCALE |the textual elements locale |
|
||||
|$POSITION |the slide position in the carousel |
|
||||
|$URL |the related URL |
|
||||
|$LIMITED| true if slide is disabled, false otherwise |
|
||||
|$START_DATE| limited slide display start date |
|
||||
|$END_DATE| limited slide display end date |
|
||||
|$DISABLE| true if slide display is limited |
|
||||
|$GROUP| name of the group the slide belong to |
|
||||
|
||||
### Exemple
|
||||
|
||||
```
|
||||
{loop type="carousel" name="carousel.front" width="1200" height="390" resize_mode="borders"}
|
||||
<img src="{$IMAGE_URL}" alt="{$ALT}">
|
||||
{/loop}
|
||||
```
|
||||
|
||||
## How to override ?
|
||||
|
||||
If you want your own carousel in your tempalte, create the directory ```modules/Carousel``` then create the template ```carousel.html``` in this directory. Here you can create your own carousel and the replace the default template provided in the module.
|
||||
11
domokits/local/modules/Carousel/composer.json
Normal file
11
domokits/local/modules/Carousel/composer.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "thelia/carousel-module",
|
||||
"license": "LGPL-3.0+",
|
||||
"type": "thelia-module",
|
||||
"require": {
|
||||
"thelia/installer": "~1.1"
|
||||
},
|
||||
"extra": {
|
||||
"installer-name": "Carousel"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
$(function() {
|
||||
// Set proper image ID in delete from
|
||||
$('a.image-delete').click(function(ev) {
|
||||
$('#image_delete_id').val($(this).data('id'));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,179 @@
|
||||
<div class="general-block-decorator">
|
||||
<div class="row">
|
||||
<div class="col-md-12 title title-without-tabs">
|
||||
{intl l='Edit your carousel.' d='carousel.bo.default'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-container">
|
||||
{form name=Carousel\Form\CarouselImageForm::getName()}
|
||||
<form method="POST" action="{url path="/admin/module/carousel/upload"}" {form_enctype} class="clearfix">
|
||||
|
||||
{form_hidden_fields}
|
||||
|
||||
{form_field field='file'}
|
||||
<div class="form-group {if $error}has-error{/if}">
|
||||
<label for="{$label_attr.for|default:null}" class="control-label">{intl d='carousel.bo.default' l='Add an image to the carousel'}</label>
|
||||
<div class="input-group">
|
||||
<input type="file" id="{$label_attr.for|default:null}" {if $required}required="required"{/if} name="{$name}" value="{$value}" title="{intl l='Carousel image' d='carousel.bo.default'}" placeholder="{intl l='Carousel image' d='carousel.bo.default'}" class="form-control">
|
||||
<span class="input-group-btn">
|
||||
<input type="submit" class="form-submit-button btn btn-sm btn-success" value="{intl d='carousel.bo.default' l='Add this image to the carousel'}" >
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/form_field}
|
||||
</form>
|
||||
{/form}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 title title-without-tabs">
|
||||
{intl l='Carousel images' d='carousel.bo.default'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-container">
|
||||
{ifloop rel="carousel.image"}
|
||||
{form name="Carousel\Form\CarouselUpdateForm"}
|
||||
<form method="post" action="{url path="/admin/module/carousel/update"}" {form_enctype} class="clearfix">
|
||||
|
||||
{include
|
||||
file = "includes/inner-form-toolbar.html"
|
||||
page_url = "{url path='/admin/module/Carousel'}"
|
||||
close_url = "{url path='/admin/modules'}"
|
||||
}
|
||||
|
||||
{form_hidden_fields}
|
||||
|
||||
{loop name="carousel.image" type="carousel" width="550" height="200" resize_mode="borders" backend_context="1" lang="$edit_language_id" filter_disable_slides=false}
|
||||
|
||||
<div class="well well-sm">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p>
|
||||
<a href="{$ORIGINAL_IMAGE_URL}" class="thumbnail" target="_blank">
|
||||
<img src="{$IMAGE_URL}" alt="{$ALT}">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-default btn-sm image-delete" href="#delete_carousel_dialog" data-toggle="modal" data-id="{$ID}">
|
||||
<i class="glyphicon glyphicon-trash"></i> {intl d='carousel.bo.default' l='Remove this image'}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="pull-right row" style="width:170px">
|
||||
<div class="col-xs-5" style="padding-top:5px">
|
||||
<label for="position{$ID}">{intl d='carousel.bo.default' l='Position'}:</label>
|
||||
</div>
|
||||
<div class="col-xs-7">
|
||||
{form_field field="position{$ID}"}
|
||||
<input id="position{$ID}" class="form-control" type="number" min="1" name="{$name}" value={$POSITION}>
|
||||
{/form_field}
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding-top: 10px">
|
||||
<div class="row col-md-12">
|
||||
{form_field field="disable{$ID}"}
|
||||
<input id="disable{$ID}" type="checkbox" name="{$name}" {if $DISABLE}checked{/if}>
|
||||
<label for="disable{$ID}">{$label}</label>
|
||||
{/form_field}
|
||||
</div>
|
||||
<div class="row col-md-12">
|
||||
{form_field field="limited{$ID}"}
|
||||
<input id="limited{$ID}" type="checkbox" name="{$name}" {if $LIMITED}checked{/if}>
|
||||
<label for="limited{$ID}">{$label}</label>
|
||||
{/form_field}
|
||||
</div>
|
||||
|
||||
<div class="row col-md-6">
|
||||
{form_field field="start_date{$ID}"}
|
||||
<label class="row col-md-12" for="{$name}">{$label}</label>
|
||||
<div class="col-md-10" style="padding: 0">
|
||||
<input name="{$name}"
|
||||
placeholder="{intl l='YYYY-MM-DD' d='carousel.bo.default'}"
|
||||
id="start_date{$ID}"
|
||||
type="datetime-local"
|
||||
class="form-control datetime-picker"
|
||||
value="{$START_DATE}"/>
|
||||
</div>
|
||||
{/form_field}
|
||||
</div>
|
||||
|
||||
<div class="row col-md-6">
|
||||
{form_field field="end_date{$ID}"}
|
||||
<label class="row col-md-12" for="{$name}">{$label}</label>
|
||||
<div class="col-md-10" style="padding: 0">
|
||||
<input name="{$name}"
|
||||
placeholder="{intl l='YYYY-MM-DD' d='carousel.bo.default'}"
|
||||
id="end_date{$ID}"
|
||||
type="datetime-local"
|
||||
class="form-control datetime-picker"
|
||||
value="{$END_DATE}"/>
|
||||
</div>
|
||||
{/form_field}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
{* Not yet implemented
|
||||
{render_form_field field="chapo{$ID} value=$CHAPO"}
|
||||
*}
|
||||
{render_form_field field="title{$ID}" value=$TITLE}
|
||||
{render_form_field field="alt{$ID}" value=$ALT}
|
||||
{render_form_field field="url{$ID}" value=$URL}
|
||||
{render_form_field field="description{$ID}" extra_class="wysiwyg" value=$DESCRIPTION}
|
||||
{render_form_field field="group{$ID}" value=$GROUP}
|
||||
{* Not yet implemented
|
||||
{render_form_field field="postscriptum{$ID}" value=$POSTSCRIPTUM}
|
||||
*}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/loop}
|
||||
|
||||
{include
|
||||
file = "includes/inner-form-toolbar.html"
|
||||
page_url = "{url path='/admin/module/Carousel'}"
|
||||
close_url = "{url path='/admin/modules'}"
|
||||
page_bottom = true
|
||||
}
|
||||
</form>
|
||||
{/form}
|
||||
{/ifloop}
|
||||
|
||||
{elseloop rel="carousel.image"}
|
||||
<div class="alert alert-info">
|
||||
{intl d='carousel.bo.default' l="Your carousel contains no image. Please add one using the form above."}
|
||||
</div>
|
||||
{/elseloop}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{capture "delete_dialog"}
|
||||
<input type="hidden" name="image_id" id="image_delete_id" value="" />
|
||||
{/capture}
|
||||
|
||||
{include
|
||||
file = "includes/generic-confirm-dialog.html"
|
||||
|
||||
dialog_id = "delete_carousel_dialog"
|
||||
dialog_title = {intl l="Delete a carousel image" d="carousel.bo.default"}
|
||||
dialog_message = {intl l="Do you really want to remove this image from the carousel ?" d="carousel.bo.default"}
|
||||
|
||||
form_action = {url path='/admin/module/carousel/delete'}
|
||||
form_content = {$smarty.capture.delete_dialog nofilter}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{ifloop rel="carousel.front"}
|
||||
<section class="carousel-container">
|
||||
<div id="carousel" class="carousel slide" data-ride="carousel">
|
||||
<div class="carousel-wrapper">
|
||||
<div class="carousel-inner">
|
||||
{loop type="carousel" name="carousel.front" width="1200" height="390" resize_mode="borders"}
|
||||
<figure class="item {if $LOOP_COUNT == 1}active{/if}">
|
||||
{if $URL}<a href="{$URL|default:'#'}">{/if}
|
||||
<img src="{$IMAGE_URL}" alt="{$ALT}">
|
||||
{if $URL}</a>{/if}
|
||||
|
||||
<div class="carousel-caption">
|
||||
{if $TITLE}<h3>{$TITLE}</h3>{/if}
|
||||
{if $DESCRIPTION}{$DESCRIPTION nofilter}{/if}
|
||||
</div>
|
||||
</figure>
|
||||
{/loop}
|
||||
</div>
|
||||
</div>
|
||||
<a class="left carousel-control" href="#carousel" data-slide="prev"><span class="icon-prev"></span></a>
|
||||
<a class="right carousel-control" href="#carousel" data-slide="next"><span class="icon-next"></span></a>
|
||||
</div>
|
||||
</section><!-- #carousel -->
|
||||
{/ifloop}
|
||||
71
domokits/local/modules/Cheque/Cheque.php
Normal file
71
domokits/local/modules/Cheque/Cheque.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Cheque;
|
||||
|
||||
use Propel\Runtime\Connection\ConnectionInterface;
|
||||
use Thelia\Install\Database;
|
||||
use Thelia\Model\MessageQuery;
|
||||
use Thelia\Model\Order;
|
||||
use Thelia\Module\AbstractPaymentModule;
|
||||
|
||||
class Cheque extends AbstractPaymentModule
|
||||
{
|
||||
public const MESSAGE_DOMAIN = 'Cheque';
|
||||
|
||||
public function pay(Order $order): void
|
||||
{
|
||||
// Nothing special to to.
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is call on Payment loop.
|
||||
*
|
||||
* If you return true, the payment method will de display
|
||||
* If you return false, the payment method will not be display
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidPayment()
|
||||
{
|
||||
return $this->getCurrentOrderTotalAmount() > 0;
|
||||
}
|
||||
|
||||
public function postActivation(ConnectionInterface $con = null): void
|
||||
{
|
||||
$database = new Database($con);
|
||||
|
||||
// Insert email message
|
||||
$database->insertSql(null, [__DIR__.'/Config/setup.sql']);
|
||||
}
|
||||
|
||||
public function destroy(ConnectionInterface $con = null, $deleteModuleData = false): void
|
||||
{
|
||||
// Delete our message
|
||||
if (null !== $message = MessageQuery::create()->findOneByName('order_confirmation_cheque')) {
|
||||
$message->delete($con);
|
||||
}
|
||||
|
||||
parent::destroy($con, $deleteModuleData);
|
||||
}
|
||||
|
||||
/**
|
||||
* if you want, you can manage stock in your module instead of order process.
|
||||
* Return false if you want to manage yourself the stock.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function manageStockOnCreation()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
25
domokits/local/modules/Cheque/Config/config.xml
Normal file
25
domokits/local/modules/Cheque/Config/config.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<config xmlns="http://thelia.net/schema/dic/config"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://thelia.net/schema/dic/config http://thelia.net/schema/dic/config/thelia-1.0.xsd">
|
||||
|
||||
<forms>
|
||||
<form name="cheque.instructions.configure" class="Cheque\Form\ConfigurationForm" />
|
||||
</forms>
|
||||
|
||||
<hooks>
|
||||
<hook id="cheque.hook" class="Cheque\Hook\HookManager" scope="request">
|
||||
<tag name="hook.event_listener" event="module.configuration" type="back" templates="render:module_configuration.html" />
|
||||
<tag name="hook.event_listener" event="order-placed.additional-payment-info" type="front" method="onAdditionalPaymentInfo" />
|
||||
</hook>
|
||||
</hooks>
|
||||
|
||||
<services>
|
||||
<service id="send.cheque.mail" class="Cheque\Listener\SendPaymentConfirmationEmail" scope="request">
|
||||
<argument type="service" id="mailer"/>
|
||||
<tag name="kernel.event_subscriber"/>
|
||||
</service>
|
||||
</services>
|
||||
|
||||
</config>
|
||||
25
domokits/local/modules/Cheque/Config/module.xml
Normal file
25
domokits/local/modules/Cheque/Config/module.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module xmlns="http://thelia.net/schema/dic/module"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://thelia.net/schema/dic/module http://thelia.net/schema/dic/module/module-2_1.xsd">
|
||||
<fullnamespace>Cheque\Cheque</fullnamespace>
|
||||
<descriptive locale="en_US">
|
||||
<title>Cheque</title>
|
||||
</descriptive>
|
||||
<descriptive locale="fr_FR">
|
||||
<title>Cheque</title>
|
||||
</descriptive>
|
||||
<images-folder>images</images-folder>
|
||||
<languages>
|
||||
<language>en_US</language>
|
||||
<language>fr_FR</language>
|
||||
</languages>
|
||||
<version>2.5.4</version>
|
||||
<author>
|
||||
<name>Manuel Raynaud</name>
|
||||
<email>manu@raynaud.io</email>
|
||||
</author>
|
||||
<type>payment</type>
|
||||
<thelia>2.5.4</thelia>
|
||||
<stability>alpha</stability>
|
||||
</module>
|
||||
9
domokits/local/modules/Cheque/Config/routing.xml
Normal file
9
domokits/local/modules/Cheque/Config/routing.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<routes xmlns="http://symfony.com/schema/routing"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
|
||||
|
||||
<route id="cheque.configure" path="/admin/cheque/configure" methods="post">
|
||||
<default key="_controller">Cheque\Controller\ConfigureController::configure</default>
|
||||
</route>
|
||||
</routes>
|
||||
32
domokits/local/modules/Cheque/Config/setup.sql
Normal file
32
domokits/local/modules/Cheque/Config/setup.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Mail template for cheque
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
-- First, delete existing entries
|
||||
SET @var := 0;
|
||||
SELECT @var := `id` FROM `message` WHERE name="order_confirmation_cheque";
|
||||
DELETE FROM `message` WHERE `id`=@var;
|
||||
|
||||
-- Then add new entries
|
||||
SELECT @max := MAX(`id`) FROM `message`;
|
||||
SET @max := @max+1;
|
||||
|
||||
-- insert message
|
||||
INSERT INTO `message` (`id`, `name`, `secured`) VALUES
|
||||
(@max,
|
||||
'order_confirmation_cheque',
|
||||
'0'
|
||||
);
|
||||
-- and mail templates
|
||||
INSERT INTO `message_i18n` (`id`, `locale`, `title`, `subject`, `text_message`, `html_message`) VALUES
|
||||
(@max,
|
||||
'en_US',
|
||||
'Confirmation of payment by cheque',
|
||||
'Payment of order {$order_ref}', 'Dear customer,\r\nThis is a confirmation of the payment by cheque of your order {$order_ref} on our shop.\r\nYour invoice is now available in your customer account at {config key="url_site"}\r\nThank you again for your purchase.\r\nThe {config key="store_name"} team.', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r\n<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en">\r\n<head>\r\n <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>\r\n <title>courriel de confirmation de commande de {config key="url_site"} </title>\r\n <style type="text/css">\r\n body {\r\n font-family: Arial, Helvetica, sans-serif;\r\n font-size: 100%;\r\n text-align: center;\r\n }\r\n #liencompte {\r\n margin: 15px 0;\r\n text-align: center;\r\n font-size: 10pt;\r\n }\r\n #wrapper {\r\n width: 480pt;\r\n margin: 0 auto;\r\n }\r\n #entete {\r\n padding-bottom: 20px;\r\n margin-bottom: 10px;\r\n border-bottom: 1px dotted #000;\r\n }\r\n #logotexte {\r\n float: left;\r\n width: 180pt;\r\n height: 75pt;\r\n border: 1pt solid #000;\r\n font-size: 18pt;\r\n text-align: center;\r\n }\r\n </style>\r\n</head>\r\n<body>\r\n<div id="wrapper">\r\n <div id="entete">\r\n <h1 id="logotexte">{config key="store_name"}</h1>\r\n <h2 id="info">The payment of your order is confirmed</h2>\r\n <h3 id="commande">Reference {$order_ref} </h3>\r\n </div>\r\n <p id="liencompte">\r\n Your invoice is now available in your customer account on\r\n <a href="{config key="url_site"}">{config key="store_name"}</a>.\r\n </p>\r\n <p>Thank you for your order !</p>\r\n <p>The {config key="store_name"} team.</p>\r\n</div>\r\n</body>\r\n</html>'
|
||||
),
|
||||
(@max,
|
||||
'fr_FR',
|
||||
'Confirmation de paiement par chèque',
|
||||
'Paiement de la commande : {$order_ref}',
|
||||
'Cher client,\r\nCe message confirme le paiement par chèque de votre commande numero {$order_ref} sur notre boutique.\r\nVotre facture est maintenant disponible dans votre compte client à l''adresse {config key="url_site"}\r\nMerci encore pour votre achat !\r\nL''équipe {config key="store_name"}', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r\n<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="fr">\r\n<head>\r\n <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>\r\n <title>Confirmation du paiement de votre commande sur {config key="url_site"} </title>\r\n <style type="text/css">\r\n body {\r\n font-family: Arial, Helvetica, sans-serif;\r\n font-size: 100%;\r\n text-align: center;\r\n }\r\n #liencompte {\r\n margin: 15px 0;\r\n text-align: center;\r\n font-size: 10pt;\r\n }\r\n #wrapper {\r\n width: 480pt;\r\n margin: 0 auto;\r\n }\r\n #entete {\r\n padding-bottom: 20px;\r\n margin-bottom: 10px;\r\n border-bottom: 1px dotted #000;\r\n }\r\n #logotexte {\r\n float: left;\r\n width: 180pt;\r\n height: 75pt;\r\n border: 1pt solid #000;\r\n font-size: 18pt;\r\n text-align: center;\r\n }\r\n </style>\r\n</head>\r\n<body>\r\n<div id="wrapper">\r\n <div id="entete">\r\n <h1 id="logotexte">{config key="store_name"}</h1>\r\n <h2 id="info">Confirmation du paiement de votre commande</h2>\r\n <h3 id="commande">N° {$order_ref}</h3>\r\n </div>\r\n <p id="liencompte">\r\n Le suivi de votre commande est disponible dans la rubrique mon compte sur\r\n <a href="{config key="url_site"}">{config key="url_site"}</a>\r\n </p>\r\n <p>Merci pour votre achat !</p>\r\n <p>L''équipe {config key="store_name"}</p>\r\n</div>\r\n</body>\r\n</html>'
|
||||
);
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Cheque\Controller;
|
||||
|
||||
use Cheque\Cheque;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Thelia\Controller\Admin\BaseAdminController;
|
||||
use Thelia\Core\Security\AccessManager;
|
||||
use Thelia\Core\Security\Resource\AdminResources;
|
||||
use Thelia\Form\Exception\FormValidationException;
|
||||
use Thelia\Tools\URL;
|
||||
|
||||
/**
|
||||
* Class SetTransferConfig.
|
||||
*
|
||||
* @author Thelia <info@thelia.net>
|
||||
*/
|
||||
class ConfigureController extends BaseAdminController
|
||||
{
|
||||
public function configure()
|
||||
{
|
||||
if (null !== $response = $this->checkAuth(AdminResources::MODULE, 'Cheque', AccessManager::UPDATE)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Initialize the potential exception
|
||||
$ex = null;
|
||||
|
||||
// Create the Form from the request
|
||||
$configurationForm = $this->createForm('cheque.instructions.configure');
|
||||
|
||||
try {
|
||||
// Check the form against constraints violations
|
||||
$form = $this->validateForm($configurationForm, 'POST');
|
||||
|
||||
// Get the form field values
|
||||
$data = $form->getData();
|
||||
|
||||
Cheque::setConfigValue('instructions', $data['instructions'], $this->getCurrentEditionLocale());
|
||||
Cheque::setConfigValue('payable_to', $data['payable_to']);
|
||||
|
||||
// Log configuration modification
|
||||
$this->adminLogAppend(
|
||||
'cheque.configuration.message',
|
||||
AccessManager::UPDATE,
|
||||
'Cheque instructions configuration updated'
|
||||
);
|
||||
|
||||
// Everything is OK.
|
||||
return new RedirectResponse(URL::getInstance()->absoluteUrl('/admin/module/Cheque'));
|
||||
} catch (FormValidationException $ex) {
|
||||
// Form cannot be validated. Create the error message using
|
||||
// the BaseAdminController helper method.
|
||||
$error_msg = $this->createStandardFormValidationErrorMessage($ex);
|
||||
} catch (\Exception $ex) {
|
||||
// Any other error
|
||||
$error_msg = $ex->getMessage();
|
||||
}
|
||||
|
||||
// At this point, the form has errors, and should be redisplayed. We don not redirect,
|
||||
// just redisplay the same template.
|
||||
// Setup the Form error context, to make error information available in the template.
|
||||
$this->setupFormErrorContext(
|
||||
$this->getTranslator()->trans('Cheque instructions configuration', [], Cheque::MESSAGE_DOMAIN),
|
||||
$error_msg,
|
||||
$configurationForm,
|
||||
$ex
|
||||
);
|
||||
|
||||
// Do not redirect at this point, or the error context will be lost.
|
||||
// Just redisplay the current template.
|
||||
return $this->render('module-configure', ['module_code' => 'Cheque']);
|
||||
}
|
||||
}
|
||||
80
domokits/local/modules/Cheque/Form/ConfigurationForm.php
Normal file
80
domokits/local/modules/Cheque/Form/ConfigurationForm.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Cheque\Form;
|
||||
|
||||
use Cheque\Cheque;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Thelia\Core\Translation\Translator;
|
||||
use Thelia\Form\BaseForm;
|
||||
|
||||
/**
|
||||
* Class ConfigurationForm.
|
||||
*
|
||||
* @author Thelia <info@thelia.net>
|
||||
*/
|
||||
class ConfigurationForm extends BaseForm
|
||||
{
|
||||
protected function trans($str, $params = [])
|
||||
{
|
||||
return Translator::getInstance()->trans($str, $params, Cheque::MESSAGE_DOMAIN);
|
||||
}
|
||||
|
||||
protected function buildForm(): void
|
||||
{
|
||||
$this->formBuilder
|
||||
->add(
|
||||
'payable_to',
|
||||
TextType::class,
|
||||
[
|
||||
'constraints' => [new NotBlank()],
|
||||
'label' => $this->trans('Cheque is payable to: '),
|
||||
'label_attr' => [
|
||||
'for' => 'payable_to',
|
||||
'help' => $this->trans('The name to which the cheque shoud be payable to.'),
|
||||
],
|
||||
'attr' => [
|
||||
'rows' => 10,
|
||||
'placeholder' => $this->trans('Pay cheque to'),
|
||||
],
|
||||
]
|
||||
)
|
||||
->add(
|
||||
'instructions',
|
||||
TextareaType::class,
|
||||
[
|
||||
'constraints' => [],
|
||||
'required' => false,
|
||||
'label' => $this->trans('Cheque instructions'),
|
||||
'label_attr' => [
|
||||
'for' => 'namefield',
|
||||
'help' => $this->trans('Please enter here the payment by cheque instructions'),
|
||||
],
|
||||
'attr' => [
|
||||
'rows' => 10,
|
||||
'placeholder' => $this->trans('Payment instruction'),
|
||||
],
|
||||
]
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string the name of you form. This name must be unique
|
||||
*/
|
||||
public static function getName()
|
||||
{
|
||||
return 'cheque_configuration_instructions';
|
||||
}
|
||||
}
|
||||
33
domokits/local/modules/Cheque/Hook/HookManager.php
Normal file
33
domokits/local/modules/Cheque/Hook/HookManager.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Cheque\Hook;
|
||||
|
||||
use Thelia\Core\Event\Hook\HookRenderEvent;
|
||||
use Thelia\Core\Hook\BaseHook;
|
||||
|
||||
/**
|
||||
* Class HookManager.
|
||||
*
|
||||
* @author Franck Allimant <franck@cqfdev.fr>
|
||||
*/
|
||||
class HookManager extends BaseHook
|
||||
{
|
||||
public function onAdditionalPaymentInfo(HookRenderEvent $event): void
|
||||
{
|
||||
$content = $this->render('order-placed.additional-payment-info.html', [
|
||||
'placed_order_id' => $event->getArgument('placed_order_id'),
|
||||
]);
|
||||
|
||||
$event->add($content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'Cheque instructions configuration' => 'Scheck-Anleitungen-Konfiguration',
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'Cheque instructions configuration' => 'Cheque instructions configuration',
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'Cheque instructions configuration' => 'Instructions de paiement par chèque',
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Thelia package.
|
||||
* http://www.thelia.net
|
||||
*
|
||||
* (c) OpenStudio <info@thelia.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
return [
|
||||
'Cheque instructions configuration' => 'Конфигурация инструкций для чека',
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user