Initial commit

This commit is contained in:
2021-01-19 18:19:37 +01:00
commit 6524a071df
14506 changed files with 1808535 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
<?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 Thelia\Module;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Class AbastractAdminResourcesCompiler
* @package Thelia\Module
* @since 2.3
* @author Penalver Antony <apenalver@openstudio.fr>
*/
abstract class AbstractAdminResourcesCompiler implements CompilerPassInterface
{
/**
* @return Array of resources
* Exemple :
* [
* "ADDRESS" => "admin.address",
* ...
* ]
*/
abstract public function getResources();
/**
* @return string ModuleCode
*/
abstract public function getModuleCode();
/**
* Allow module to add resources in AdminResources Service
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
*/
public function process(\Symfony\Component\DependencyInjection\ContainerBuilder $container)
{
if (!$container->hasDefinition("thelia.admin.resources")) {
return;
}
/** @var \Symfony\Component\DependencyInjection\Definition $adminResources */
$adminResources = $container->getDefinition("thelia.admin.resources");
$adminResources->addMethodCall("addModuleResources", [$this->getResources(), $this->getModuleCode()]);
}
}

View File

@@ -0,0 +1,57 @@
<?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 Thelia\Module;
use Thelia\Model\Area;
use Thelia\Model\AreaDeliveryModuleQuery;
use Thelia\Model\Country;
use Thelia\Model\State;
abstract class AbstractDeliveryModule extends BaseModule implements DeliveryModuleInterface
{
// This class is the base class for delivery modules
// It may contains common methods in the future.
/**
* @return bool
*/
public function handleVirtualProductDelivery()
{
return false;
}
/**
* Return the first area that matches the given country for the given module
* @param Country $country
* @param BaseModule $module
* @return Area|null
*/
public function getAreaForCountry(Country $country)
{
$area = null;
if (null !== $areaDeliveryModule = AreaDeliveryModuleQuery::create()->findByCountryAndModule(
$country,
$this->getModuleModel()
)) {
$area = $areaDeliveryModule->getArea();
}
return $area;
}
public function getDeliveryMode()
{
return "delivery";
}
}

View File

@@ -0,0 +1,58 @@
<?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 Thelia\Module;
use Thelia\Model\Area;
use Thelia\Model\AreaDeliveryModuleQuery;
use Thelia\Model\Country;
use Thelia\Model\State;
abstract class AbstractDeliveryModuleWithState extends BaseModule implements DeliveryModuleWithStateInterface
{
// This class is the base class for delivery modules
// It may contains common methods in the future.
/**
* @return bool
*/
public function handleVirtualProductDelivery()
{
return false;
}
/**
* Return the first area that matches the given country for the given module
* @param Country $country
* @param State $state
* @return Area|null
*/
public function getAreaForCountry(Country $country, State $state = null)
{
$area = null;
if (null !== $areaDeliveryModule = AreaDeliveryModuleQuery::create()->findByCountryAndModule(
$country,
$this->getModuleModel(),
$state
)) {
$area = $areaDeliveryModule->getArea();
}
return $area;
}
public function getDeliveryMode()
{
return "delivery";
}
}

View File

@@ -0,0 +1,117 @@
<?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 Thelia\Module;
use Symfony\Component\Routing\Router;
use Thelia\Core\HttpFoundation\Response;
use Thelia\Core\Template\ParserInterface;
use Thelia\Core\Template\TemplateHelperInterface;
use Thelia\Model\Country;
use Thelia\Model\Order;
use Thelia\Model\State;
use Thelia\Tools\URL;
abstract class AbstractPaymentModule extends BaseModule implements PaymentModuleInterface
{
/**
* Render the payment gateway template. The module should provide the gateway URL and the form fields names and values.
*
* @param Order $order the order
* @param string $gateway_url the payment gateway URL
* @param array $form_data an associative array of form data, that will be rendered as hiddent fields
*
* @return Response the HTTP response.
*/
public function generateGatewayFormResponse($order, $gateway_url, $form_data)
{
/** @var ParserInterface $parser */
$parser = $this->getContainer()->get("thelia.parser");
$parser->setTemplateDefinition(
$parser->getTemplateHelper()->getActiveFrontTemplate()
);
$renderedTemplate = $parser->render(
"order-payment-gateway.html",
array(
"order_id" => $order->getId(),
"cart_count" => $this->getRequest()->getSession()->getSessionCart($this->getDispatcher())->getCartItems()->count(),
"gateway_url" => $gateway_url,
"payment_form_data" => $form_data
)
);
return Response::create($renderedTemplate);
}
/**
* Return the order payment success page URL
*
* @param int $order_id the order ID
* @return string the order payment success page URL
*/
public function getPaymentSuccessPageUrl($order_id)
{
$frontOfficeRouter = $this->getContainer()->get('router.front');
return URL::getInstance()->absoluteUrl(
$frontOfficeRouter->generate(
"order.placed",
array("order_id" => $order_id),
Router::ABSOLUTE_URL
)
);
}
/**
* Redirect the customer to the failure payment page. if $message is null, a generic message is displayed.
*
* @param int $order_id the order ID
* @param string|null $message an error message.
*
* @return string the order payment failure page URL
*/
public function getPaymentFailurePageUrl($order_id, $message)
{
$frontOfficeRouter = $this->getContainer()->get('router.front');
return URL::getInstance()->absoluteUrl(
$frontOfficeRouter->generate(
"order.failed",
array(
"order_id" => $order_id,
"message" => $message
),
Router::ABSOLUTE_URL
)
);
}
/**
* @inherited
*/
public function manageStockOnCreation()
{
return true;
}
public function getMinimumAmount()
{
return null;
}
public function getMaximumAmount()
{
return null;
}
}

View File

@@ -0,0 +1,809 @@
<?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 Thelia\Module;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\Runtime\Propel;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\Request;
use Thelia\Core\Event\Hook\HookCreateAllEvent;
use Thelia\Core\Event\Hook\HookUpdateEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpFoundation\Session\Session;
use Thelia\Core\Template\TemplateDefinition;
use Thelia\Core\Thelia;
use Thelia\Core\Translation\Translator;
use Thelia\Exception\ModuleException;
use Thelia\Log\Tlog;
use Thelia\Model\Cart;
use Thelia\Model\Country;
use Thelia\Model\HookQuery;
use Thelia\Model\Lang;
use Thelia\Model\LangQuery;
use Thelia\Model\Map\ModuleImageTableMap;
use Thelia\Model\Map\ModuleTableMap;
use Thelia\Model\Module;
use Thelia\Model\ModuleConfigQuery;
use Thelia\Model\ModuleI18n;
use Thelia\Model\ModuleI18nQuery;
use Thelia\Model\ModuleImage;
use Thelia\Model\ModuleQuery;
use Thelia\Model\Order;
use Thelia\TaxEngine\TaxEngine;
use Thelia\Tools\Image;
class BaseModule implements BaseModuleInterface
{
use ContainerAwareTrait;
const CLASSIC_MODULE_TYPE = 1;
const DELIVERY_MODULE_TYPE = 2;
const PAYMENT_MODULE_TYPE = 3;
const MODULE_CATEGORIES = 'classic,delivery,payment,marketplace,price,accounting,seo,administration,statistic';
const IS_ACTIVATED = 1;
const IS_NOT_ACTIVATED = 0;
const IS_MANDATORY = 1;
const IS_NOT_MANDATORY = 0;
const IS_HIDDEN = 1;
const IS_NOT_HIDDEN = 0;
protected $reflected;
protected $dispatcher = null;
protected $request = null;
// Do no use this attribute directly, use getModuleModel() instead.
private $moduleModel = null;
/**
* @param Module $moduleModel
* @throws \Propel\Runtime\Exception\PropelException
* @throws \Throwable
*/
public function activate($moduleModel = null)
{
if (null === $moduleModel) {
$moduleModel = $this->getModuleModel();
}
if ($moduleModel->getActivate() === self::IS_NOT_ACTIVATED) {
$moduleModel->setActivate(self::IS_ACTIVATED);
$moduleModel->save();
// Refresh propel cache to be sure that module's model is created
// when the module's initialization methods will be called.
/** @var Thelia $theliaKernel */
$theliaKernel = $this->container->get('kernel');
$theliaKernel->initializePropelService(true, $cacheRefresh);
$con = Propel::getWriteConnection(ModuleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$this->initializeCoreI18n();
if ($this->preActivation($con)) {
$this->postActivation($con);
$con->commit();
}
} catch (\Exception $e) {
$con->rollBack();
$moduleModel->setActivate(self::IS_NOT_ACTIVATED);
$moduleModel->save();
throw $e;
}
$this->registerHooks();
}
}
public function deActivate($moduleModel = null)
{
if (null === $moduleModel) {
$moduleModel = $this->getModuleModel();
}
if ($moduleModel->getActivate() == self::IS_ACTIVATED) {
$con = Propel::getWriteConnection(ModuleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
if ($this->preDeactivation($con)) {
$moduleModel->setActivate(self::IS_NOT_ACTIVATED);
$moduleModel->save($con);
$this->postDeactivation($con);
$con->commit();
}
} catch (\Exception $e) {
$con->rollBack();
throw $e;
}
}
}
public function hasContainer()
{
return null !== $this->container;
}
public function getContainer()
{
if ($this->hasContainer() === false) {
throw new \RuntimeException("Sorry, container is not available in this context");
}
return $this->container;
}
public function hasRequest()
{
return null !== $this->request;
}
public function setRequest(Request $request)
{
$this->request = $request;
}
/**
* @return \Thelia\Core\HttpFoundation\Request the request.
*
* @throws \RuntimeException
*/
public function getRequest()
{
if ($this->hasRequest() === false) {
// Try to get request from container.
$this->setRequest($this->getContainer()->get('request_stack')->getCurrentRequest());
}
if ($this->hasRequest() === false) {
throw new \RuntimeException("Sorry, the request is not available in this context");
}
return $this->request;
}
public function hasDispatcher()
{
return null !== $this->dispatcher;
}
public function setDispatcher(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
/**
* @return EventDispatcherInterface
* @throws \RuntimeException
*/
public function getDispatcher()
{
if ($this->hasDispatcher() === false) {
// Try to get dispatcher from container.
$this->setDispatcher($this->getContainer()->get('event_dispatcher'));
}
if ($this->hasDispatcher() === false) {
throw new \RuntimeException("Sorry, the dispatcher is not available in this context");
}
return $this->dispatcher;
}
/**
* @inheritdoc
*/
public function setTitle(Module $module, $titles)
{
if (\is_array($titles)) {
foreach ($titles as $locale => $title) {
$moduleI18n = ModuleI18nQuery::create()
->filterById($module->getId())->filterByLocale($locale)
->findOne();
if (null === $moduleI18n) {
$moduleI18n = new ModuleI18n();
$moduleI18n
->setId($module->getId())
->setLocale($locale)
->setTitle($title)
;
$moduleI18n->save();
} else {
$moduleI18n->setTitle($title);
$moduleI18n->save();
}
}
}
}
/**
* @inheritdoc
*/
public static function getConfigValue($variableName, $defaultValue = null, $valueLocale = null)
{
return ModuleConfigQuery::create()
->getConfigValue(self::getModuleId(), $variableName, $defaultValue, $valueLocale);
}
/**
* @inheritdoc
*/
public static function setConfigValue($variableName, $variableValue, $valueLocale = null, $createIfNotExists = true)
{
ModuleConfigQuery::create()
->setConfigValue(self::getModuleId(), $variableName, $variableValue, $valueLocale, $createIfNotExists);
}
/**
* @inheritdoc
*/
public function deployImageFolder(Module $module, $folderPath, ConnectionInterface $con = null)
{
try {
$directoryBrowser = new \DirectoryIterator($folderPath);
} catch (\UnexpectedValueException $e) {
throw $e;
}
if (null === $con) {
$con = Propel::getConnection(
ModuleImageTableMap::DATABASE_NAME
);
}
/* browse the directory */
$imagePosition = 1;
/** @var \DirectoryIterator $directoryContent */
foreach ($directoryBrowser as $directoryContent) {
/* is it a file ? */
if ($directoryContent->isFile()) {
$fileName = $directoryContent->getFilename();
$filePath = $directoryContent->getPathName();
/* is it a picture ? */
if (Image::isImage($filePath)) {
$con->beginTransaction();
$image = new ModuleImage();
$image->setModuleId($module->getId());
$image->setPosition($imagePosition);
$image->save($con);
$imageDirectory = sprintf("%s/media/images/module", THELIA_LOCAL_DIR);
$imageFileName = sprintf("%s-%d-%s", $module->getCode(), $image->getId(), $fileName);
$increment = 0;
while (file_exists($imageDirectory . '/' . $imageFileName)) {
$imageFileName = sprintf(
"%s-%d-%d-%s",
$module->getCode(),
$image->getId(),
$increment,
$fileName
);
$increment++;
}
$imagePath = sprintf('%s/%s', $imageDirectory, $imageFileName);
if (! is_dir($imageDirectory)) {
if (! @mkdir($imageDirectory, 0777, true)) {
$con->rollBack();
throw new ModuleException(
sprintf("Cannot create directory : %s", $imageDirectory),
ModuleException::CODE_NOT_FOUND
);
}
}
if (! @copy($filePath, $imagePath)) {
$con->rollBack();
throw new ModuleException(
sprintf("Cannot copy file : %s to : %s", $filePath, $imagePath),
ModuleException::CODE_NOT_FOUND
);
}
$image->setFile($imageFileName);
$image->save($con);
$con->commit();
$imagePosition++;
}
}
}
}
/**
* @inheritdoc
*/
public function getModuleModel()
{
if (null === $this->moduleModel) {
$this->moduleModel = ModuleQuery::create()->findOneByCode($this->getCode());
if (null === $this->moduleModel) {
throw new ModuleException(
sprintf("Module Code `%s` not found", $this->getCode()),
ModuleException::CODE_NOT_FOUND
);
}
}
return $this->moduleModel;
}
/**
* Module A may use static method from module B, thus we have to cache
* a couple (module code => module id).
*
* @return int The module id, in a static way, with a cache
*/
private static $moduleIds = [];
/**
* @inheritdoc
*/
public static function getModuleId()
{
$code = self::getModuleCode();
if (! isset(self::$moduleIds[$code])) {
if (null === $module = ModuleQuery::create()->findOneByCode($code)) {
throw new ModuleException(
sprintf("Module Code `%s` not found", $code),
ModuleException::CODE_NOT_FOUND
);
}
self::$moduleIds[$code] = $module->getId();
}
return self::$moduleIds[$code];
}
/**
* @inheritdoc
*/
public static function getModuleCode()
{
$fullClassName = explode('\\', \get_called_class());
return end($fullClassName);
}
/*
* The module code
*/
public function getCode()
{
return self::getModuleCode();
}
/**
* Check if this module is the payment module for a given order
*
* @param Order $order an order
* @return bool true if this module is the payment module for the given order.
*/
public function isPaymentModuleFor(Order $order)
{
$model = $this->getModuleModel();
return $order->getPaymentModuleId() == $model->getId();
}
/**
* Check if this module is the delivery module for a given order
*
* @param Order $order an order
* @return bool true if this module is the delivery module for the given order.
*/
public function isDeliveryModuleFor(Order $order)
{
$model = $this->getModuleModel();
return $order->getDeliveryModuleId() == $model->getId();
}
/**
* A convenient method to get the current order total, with or without tax, discount or postage.
* This method operates on the order currently in the user's session, and should not be used to
* get the total amount of an order already stored in the database. For such orders, use
* Order::getTotalAmount() method.
*
* @param bool $with_tax if true, to total price will include tax amount
* @param bool $with_discount if true, the total price will include discount, if any
* @param bool $with_postage if true, the total price will include the delivery costs, if any.
*
* @return float|int the current order amount.
*/
public function getCurrentOrderTotalAmount($with_tax = true, $with_discount = true, $with_postage = true)
{
/** @var Session $session */
$session = $this->getRequest()->getSession();
/** @var Cart $cart */
$cart = $session->getSessionCart($this->getDispatcher());
/** @var Order $order */
$order = $session->getOrder();
/** @var TaxEngine $taxEngine */
$taxEngine = $this->getContainer()->get("thelia.taxengine");
/** @var Country $country */
$country = $taxEngine->getDeliveryCountry();
$state = $taxEngine->getDeliveryState();
$amount = $with_tax ? $cart->getTaxedAmount($country, $with_discount, $state) : $cart->getTotalAmount($with_discount, $country, $state);
if ($with_postage) {
if ($with_tax) {
$amount += $order->getPostage();
} else {
$amount += $order->getPostage() - $order->getPostageTax();
}
}
return $amount;
}
/**
* @inheritdoc
*/
public static function getCompilers()
{
return array();
}
/**
* @inheritdoc
*/
public function install(ConnectionInterface $con = null)
{
// Override this method to do something useful.
}
/**
* @inheritdoc
*/
public function update($currentVersion, $newVersion, ConnectionInterface $con = null)
{
// Override this method to do something useful.
}
/**
* @inheritdoc
*/
public function preActivation(ConnectionInterface $con = null)
{
// Override this method to do something useful.
return true;
}
/**
* @inheritdoc
*/
public function postActivation(ConnectionInterface $con = null)
{
// Override this method to do something useful.
}
/**
* @inheritdoc
*/
public function preDeactivation(ConnectionInterface $con = null)
{
// Override this method to do something useful.
return true;
}
/**
* @inheritdoc
*/
public function postDeactivation(ConnectionInterface $con = null)
{
// Override this method to do something useful.
}
/**
* @inheritdoc
*/
public function destroy(ConnectionInterface $con = null, $deleteModuleData = false)
{
// Override this method to do something useful.
}
/**
* @inheritdoc
*/
public function getHooks()
{
return array();
}
/**
* @inheritdoc
*/
public function registerHooks()
{
$moduleHooks = $this->getHooks();
if (\is_array($moduleHooks) && !empty($moduleHooks)) {
$allowedTypes = (array) TemplateDefinition::getStandardTemplatesSubdirsIterator();
$defaultLang = Lang::getDefaultLanguage();
$defaultLocale = $defaultLang->getLocale();
/**
* @var EventDispatcherInterface $dispatcher
*/
$dispatcher = $this->container->get("event_dispatcher");
foreach ($moduleHooks as $hook) {
$isValid = \is_array($hook) &&
isset($hook["type"]) &&
array_key_exists($hook["type"], $allowedTypes) &&
isset($hook["code"]) &&
\is_string($hook["code"]) &&
!empty($hook["code"])
;
if (!$isValid) {
Tlog::getInstance()->notice("The module ".$this->getCode()." tried to register an invalid hook");
continue;
}
/**
* Create or update hook db entry.
*
* @var \Thelia\Model\Hook $hookModel
*/
list($hookModel, $updateData) = $this->createOrUpdateHook($hook, $dispatcher, $defaultLocale);
/**
* Update translations
*/
$event = new HookUpdateEvent($hookModel->getId());
foreach ($updateData as $locale => $data) {
$event
->setCode($hookModel->getCode())
->setNative($hookModel->getNative())
->setByModule($hookModel->getByModule())
->setActive($hookModel->getActivate())
->setBlock($hookModel->getBlock())
->setNative($hookModel->getNative())
->setType($hookModel->getType())
->setLocale($locale)
->setChapo($data["chapo"])
->setTitle($data["title"])
->setDescription($data["description"])
;
$dispatcher->dispatch(TheliaEvents::HOOK_UPDATE, $event);
}
}
}
}
protected function createOrUpdateHook(array $hook, EventDispatcherInterface $dispatcher, $defaultLocale)
{
$hookModel = HookQuery::create()->filterByCode($hook["code"])->findOne();
if ($hookModel === null) {
$event = new HookCreateAllEvent();
} else {
$event = new HookUpdateEvent($hookModel->getId());
}
/**
* Get used I18n variables
*/
$locale = $defaultLocale;
list($titles, $descriptions, $chapos) = $this->getHookI18nInfo($hook, $defaultLocale);
/**
* If the default locale exists
* extract it to save it in create action
*
* otherwise take the first
*/
if (isset($titles[$defaultLocale])) {
$title = $titles[$defaultLocale];
unset($titles[$defaultLocale]);
} else {
reset($titles);
$locale = key($titles);
$title = array_shift($titles);
}
$description = $this->arrayKeyPop($locale, $descriptions);
$chapo = $this->arrayKeyPop($locale, $chapos);
/**
* Set data
*/
$event
->setBlock(isset($hook["block"]) && (bool) $hook["block"])
->setLocale($locale)
->setTitle($title)
->setDescription($description)
->setChapo($chapo)
->setType($hook["type"])
->setCode($hook["code"])
->setNative(false)
->setByModule(isset($hook["module"]) && (bool) $hook["module"])
->setActive(isset($hook["active"]) && (bool) $hook["active"])
;
/**
* Dispatch the event
*/
$dispatcher->dispatch(
(
$hookModel === null ?
TheliaEvents::HOOK_CREATE_ALL :
TheliaEvents::HOOK_UPDATE
),
$event
);
return [
$event->getHook(),
$this->formatHookDataForI18n($titles, $descriptions, $chapos)
];
}
protected function formatHookDataForI18n(array $titles, array $descriptions, array $chapos)
{
$locales = array_merge(
array_keys($titles),
array_keys($descriptions),
array_keys($chapos)
);
$locales = array_unique($locales);
$data = array();
foreach ($locales as $locale) {
$data[$locale] = [
'title' => !isset($titles[$locale]) ? null : $titles[$locale],
'description' => !isset($descriptions[$locale]) ? null: $descriptions[$locale],
'chapo' => !isset($chapos[$locale]) ? null : $chapos[$locale]
];
}
return $data;
}
protected function getHookI18nInfo(array $hook, $defaultLocale)
{
$titles = array();
$descriptions = array();
$chapos = array();
/**
* Get the defined titles
*/
if (isset($hook["title"])) {
$titles = $this->extractI18nValues($hook["title"], $defaultLocale);
}
/**
* Then the defined descriptions
*/
if (isset($hook["description"])) {
$descriptions = $this->extractI18nValues($hook["description"], $defaultLocale);
}
/**
* Then the short descriptions
*/
if (isset($hook["chapo"])) {
$chapos = $this->extractI18nValues($hook["chapo"], $defaultLocale);
}
return [$titles, $descriptions, $chapos];
}
protected function extractI18nValues($data, $defaultLocale)
{
$returnData = array();
if (\is_array($data)) {
foreach ($data as $key => $value) {
if (!\is_string($key)) {
continue;
}
$returnData[$key] = $value;
}
} elseif (is_scalar($data)) {
$returnData[$defaultLocale] = $data;
}
return $returnData;
}
protected function arrayKeyPop($key, array &$array)
{
$value = null;
if (array_key_exists($key, $array)) {
$value = $array[$key];
unset($array[$key]);
}
return $value;
}
/**
* @since 2.4
* @return string
*/
protected function getPropelSchemaDir()
{
return THELIA_MODULE_DIR . $this->getCode() . DS . 'Config' . DS . 'schema.xml';
}
/**
* @since 2.4
* @return bool
*/
protected function hasPropelSchema()
{
return (new Filesystem())->exists($this->getPropelSchemaDir());
}
/**
* Add core translations of the module to use in `preActivation` and `postActivation`
* when the module is not yest activated and translations are not available
*/
private function initializeCoreI18n()
{
if ($this->hasContainer()) {
/** @var Translator $translator */
$translator = $this->container->get('thelia.translator');
if (null !== $translator) {
$i18nPath = sprintf('%s%s/I18n/', THELIA_MODULE_DIR, $this->getCode());
$languages = LangQuery::create()->find();
foreach ($languages as $language) {
$locale = $language->getLocale();
$i18nFile = sprintf('%s%s.php', $i18nPath, $locale);
if (is_file($i18nFile) && is_readable($i18nFile)) {
$translator->addResource('php', $i18nFile, $locale, strtolower(self::getModuleCode()));
}
}
}
}
}
}

View File

@@ -0,0 +1,250 @@
<?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 Thelia\Module;
use Propel\Runtime\Connection\ConnectionInterface;
use Thelia\Model\Module;
interface BaseModuleInterface
{
/**
* This method is called when the plugin is installed for the first time
*
* @param ConnectionInterface $con
*/
public function install(ConnectionInterface $con = null);
/**
* This method is called when a newer version of the plugin is installed
*
* @param string $currentVersion the current (installed) module version, as defined in the module.xml file
* @param string $newVersion the new module version, as defined in the module.xml file
* @param ConnectionInterface $con
*/
public function update($currentVersion, $newVersion, ConnectionInterface $con = null);
/**
* This method is called just before the deletion of the module, giving the module an opportunity
* to delete its data.
*
* @param ConnectionInterface $con
* @param bool $deleteModuleData if true, the module should remove all its data from the system.
*/
public function destroy(ConnectionInterface $con = null, $deleteModuleData = false);
/**
* This method is called when the module is activated
*
* @param Module $moduleModel the module
*/
public function activate($moduleModel = null);
/**
* This method is called when the module is deactivated
*
* @param Module $moduleModel the module
*/
public function deActivate($moduleModel = null);
/**
* This method is called before the module activation, and may prevent it by returning false.
*
* @param ConnectionInterface $con
*
* @return bool true to continue module activation, false to prevent it.
*/
public function preActivation(ConnectionInterface $con = null);
/**
* This method is called just after the module was successfully activated.
*
* @param ConnectionInterface $con
*/
public function postActivation(ConnectionInterface $con = null);
/**
* This method is called before the module de-activation, and may prevent it by returning false.
*
* @param ConnectionInterface $con
* @return bool true to continue module de-activation, false to prevent it.
*/
public function preDeactivation(ConnectionInterface $con = null);
/**
* This method is called just after the module was successfully deactivated.
*
* @param ConnectionInterface $con
*/
public function postDeactivation(ConnectionInterface $con = null);
/**
* Sets a module titles for various languages
*
* @param Module $module the module.
* @param array $titles an associative array of locale => title_string
*/
public function setTitle(Module $module, $titles);
/**
* Get a module's configuration variable
*
* @param string $variableName the variable name
* @param string $defaultValue the default value, if variable is not defined
* @param null $valueLocale the required locale, or null to get default one
* @return string the variable value
*/
public static function getConfigValue($variableName, $defaultValue = null, $valueLocale = null);
/**
* Set module configuration variable, creating it if required
*
* @param string $variableName the variable name
* @param string $variableValue the variable value
* @param null $valueLocale the locale, or null if not required
* @param bool $createIfNotExists if true, the variable will be created if not already defined
* @throws \LogicException if variable does not exists and $createIfNotExists is false
* @return $this;
*/
public static function setConfigValue(
$variableName,
$variableValue,
$valueLocale = null,
$createIfNotExists = true
);
/**
* Ensure the proper deployment of the module's images.
*
* TODO : this method does not take care of internationalization. This is a bug.
*
* @param Module $module the module
* @param string $folderPath the image folder path
* @param ConnectionInterface $con
*
* @throws \Thelia\Exception\ModuleException
* @throws \Exception
* @throws \UnexpectedValueException
*/
public function deployImageFolder(Module $module, $folderPath, ConnectionInterface $con = null);
/**
* @return Module
* @throws \Thelia\Exception\ModuleException
*/
public function getModuleModel();
/**
* @return string The module id
*/
public static function getModuleId();
/**
* @return string The module code, in a static way
*/
public static function getModuleCode();
/**
* @return string The module code
*/
public function getCode();
/**
*
* This method adds new compilers to Thelia container
*
* You must return an array. This array can contain :
* - arrays
* - one or many instance(s) of \Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface
*
* in the first case, your array must contains 2 indexes.
* The first is the compiler instance and the second the compilerPass type.
* Example :
* return array(
* array(
* new \MyModule\DependencyInjection\Compiler\MySuperCompilerPass(),
* \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION
* )
* );
*
* In the seconde case, just an instance of CompilerPassInterface.
* Example :
* return array (
* new \MyModule\DependencyInjection\Compiler\MySuperCompilerPass()
* );
*
* But you can combine both behaviors
* Example :
*
* return array(
* new \MyModule\DependencyInjection\Compiler\MySuperCompilerPass(),
* array(
* new \MyModule\DependencyInjection\Compiler\MyOtherSuperCompilerPass(),
* Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION
* )
* );
*
*/
public static function getCompilers();
/**
* @return array
*
* This method must be used when your module defines hooks.
* Override this and return an array of your hooks names to register them
*
* This returned value must be like the example, only type and code are mandatory
*
* Example:
*
* return array(
*
* // Only register the title in the default language
* array(
* "type" => TemplateDefinition::BACK_OFFICE,
* "code" => "my_super_hook_name",
* "title" => "My hook",
* "description" => "My hook is really, really great",
* ),
*
* // Manage i18n
* array(
* "type" => TemplateDefinition::FRONT_OFFICE,
* "code" => "my_hook_name",
* "title" => array(
* "fr_FR" => "Mon Hook",
* "en_US" => "My hook",
* ),
* "description" => array(
* "fr_FR" => "Mon hook est vraiment super",
* "en_US" => "My hook is really, really great",
* ),
* "chapo" => array(
* "fr_FR" => "Mon hook est vraiment super",
* "en_US" => "My hook is really, really great",
* ),
* "block" => true,
* "active" => true
* )
* );
*/
public function getHooks();
/**
* Create or update module hooks returned by the `getHooks` function
*/
public function registerHooks();
}

View File

@@ -0,0 +1,274 @@
<?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 Thelia\Module;
use Symfony\Component\Routing\Router;
use Thelia\Controller\Front\BaseFrontController;
use Thelia\Core\Event\Order\OrderEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpKernel\Exception\RedirectException;
use Thelia\Log\Tlog;
use Thelia\Model\OrderQuery;
use Thelia\Model\OrderStatusQuery;
/**
* This class implement the minimum
*
* @package Thelia\Module
* @author Thelia <info@thelia.net>
*/
abstract class BasePaymentModuleController extends BaseFrontController
{
protected $log = null;
/**
* Return a module identifier used to calculate the name of the log file,
* and in the log messages.
*
* @return string the module code
*/
abstract protected function getModuleCode();
/**
* Returns the module-specific logger, initializing it if required.
*
* @return Tlog a Tlog instance
*/
protected function getLog()
{
if ($this->log == null) {
$this->log = Tlog::getNewInstance();
$logFilePath = $this->getLogFilePath();
$this->log->setPrefix("#LEVEL: #DATE #HOUR: ");
$this->log->setDestinations("\\Thelia\\Log\\Destination\\TlogDestinationFile");
$this->log->setConfig("\\Thelia\\Log\\Destination\\TlogDestinationFile", 0, $logFilePath);
$this->log->setLevel(Tlog::INFO);
}
return $this->log;
}
/**
* @return string The path to the module's log file.
*/
protected function getLogFilePath()
{
return sprintf(THELIA_ROOT . "log" . DS . "%s.log", strtolower($this->getModuleCode()));
}
/**
* Process the confirmation of an order. This method should be called
* once the module has performed the required checks to confirm a valid payment.
*
* @param int $orderId the order ID
* @throws \Exception
*/
public function confirmPayment($orderId)
{
try {
$orderId = \intval($orderId);
if (null !== $order = $this->getOrder($orderId)) {
$this->getLog()->addInfo(
$this->getTranslator()->trans(
"Processing confirmation of order ref. %ref, ID %id",
array('%ref' => $order->getRef(), '%id' => $order->getId())
)
);
$event = new OrderEvent($order);
$event->setStatus(OrderStatusQuery::getPaidStatus()->getId());
$this->dispatch(TheliaEvents::ORDER_UPDATE_STATUS, $event);
$this->getLog()->addInfo(
$this->getTranslator()->trans(
"Order ref. %ref, ID %id has been successfully paid.",
array('%ref' => $order->getRef(), '%id' => $order->getId())
)
);
}
} catch (\Exception $ex) {
$this->getLog()->addError(
$this->getTranslator()->trans(
"Error occured while processing order ref. %ref, ID %id: %err",
array(
'%err' => $ex->getMessage(),
'%ref' => !isset($order) ? "?" : $order->getRef(),
'%id' => !isset($order) ? "?" : $order->getId()
)
)
);
throw $ex;
}
}
/**
* Save the transaction/payment ref in the order
*
* @param int $orderId the order ID
* @param int $transactionRef the transaction reference
*
* @throws \Exception
*/
public function saveTransactionRef($orderId, $transactionRef)
{
try {
$orderId = \intval($orderId);
if (null !== $order = $this->getOrder($orderId)) {
$event = new OrderEvent($order);
$event->setTransactionRef($transactionRef);
$this->dispatch(TheliaEvents::ORDER_UPDATE_TRANSACTION_REF, $event);
$this->getLog()->addInfo(
$this->getTranslator()->trans(
"Payment transaction %transaction_ref for order ref. %ref, ID %id has been successfully saved.",
[
'%transaction_ref' => $transactionRef,
'%ref' => $order->getRef(),
'%id' => $order->getId()
]
)
);
}
} catch (\Exception $ex) {
$this->getLog()->addError(
$this->getTranslator()->trans(
"Error occurred while saving payment transaction %transaction_ref for order ID %id.",
[
'%transaction_ref' => $transactionRef,
'%id' => $orderId
]
)
);
throw $ex;
}
}
/**
* Process the cancellation of a payment on the payment gateway. The order will go back to the
* "not paid" status.
*
* @param int $orderId the order ID
*/
public function cancelPayment($orderId)
{
try {
$orderId = \intval($orderId);
if (null !== $order = $this->getOrder($orderId)) {
$this->getLog()->addInfo(
$this->getTranslator()->trans(
"Processing cancelation of payment for order ref. %ref",
array('%ref' => $order->getRef())
)
);
$event = new OrderEvent($order);
$event->setStatus(OrderStatusQuery::getNotPaidStatus()->getId());
$this->dispatch(TheliaEvents::ORDER_UPDATE_STATUS, $event);
$this->getLog()->addInfo(
$this->getTranslator()->trans(
"Order ref. %ref is now unpaid.",
array('%ref' => $order->getRef())
)
);
}
} catch (\Exception $ex) {
$this->getLog()->addError(
$this->getTranslator()->trans(
"Error occurred while cancelling order ref. %ref, ID %id: %err",
array(
'%err' => $ex->getMessage(),
'%ref' => !isset($order) ? "?" : $order->getRef(),
'%id' => !isset($order) ? "?" : $order->getId()
)
)
);
throw $ex;
}
}
/**
* Get an order and issue a log message if not found.
*
* @param $orderId
* @return null|\Thelia\Model\Order
*/
protected function getOrder($orderId)
{
if (null == $order = OrderQuery::create()->findPk($orderId)) {
$this->getLog()->addError(
$this->getTranslator()->trans("Unknown order ID: %id", array('%id' => $orderId))
);
}
return $order;
}
/**
* Redirect the customer to the successful payment page.
*
* @param int $orderId the order ID
*/
public function redirectToSuccessPage($orderId)
{
$this->getLog()->addInfo("Redirecting customer to payment success page");
throw new RedirectException(
$this->retrieveUrlFromRouteId(
'order.placed',
[],
[
'order_id' => $orderId
],
Router::ABSOLUTE_PATH
)
);
}
/**
* Redirect the customer to the failure payment page. if $message is null, a generic message is displayed.
*
* @param int $orderId the order ID
* @param string|null $message an error message.
*/
public function redirectToFailurePage($orderId, $message)
{
$this->getLog()->addInfo("Redirecting customer to payment failure page");
throw new RedirectException(
$this->retrieveUrlFromRouteId(
'order.failed',
[],
[
'order_id' => $orderId,
'message' => $message
],
Router::ABSOLUTE_PATH
)
);
}
}

View File

@@ -0,0 +1,51 @@
<?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 Thelia\Module;
use Thelia\Model\Country;
use Thelia\Model\OrderPostage;
use Thelia\Module\Exception\DeliveryException;
interface DeliveryModuleInterface extends BaseModuleInterface
{
/**
* This method is called by the Delivery loop, to check if the current module has to be displayed to the customer.
* Override it to implements your delivery rules/
*
* If you return true, the delivery method will de displayed to the customer
* If you return false, the delivery method will not be displayed
*
* @param Country $country the country to deliver to.
*
* @return boolean
*/
public function isValidDelivery(Country $country);
/**
* Calculate and return delivery price in the shop's default currency
*
* @param Country $country the country to deliver to.
*
* @return OrderPostage|float the delivery price
* @throws DeliveryException if the postage price cannot be calculated.
*/
public function getPostage(Country $country);
/**
*
* This method return true if your delivery manages virtual product delivery.
*
* @return bool
*/
public function handleVirtualProductDelivery();
}

View File

@@ -0,0 +1,54 @@
<?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 Thelia\Module;
use Thelia\Model\Country;
use Thelia\Model\OrderPostage;
use Thelia\Model\State;
use Thelia\Module\Exception\DeliveryException;
interface DeliveryModuleWithStateInterface extends BaseModuleInterface
{
/**
* This method is called by the Delivery loop, to check if the current module has to be displayed to the customer.
* Override it to implements your delivery rules/
*
* If you return true, the delivery method will de displayed to the customer
* If you return false, the delivery method will not be displayed
*
* @param Country $country the country to deliver to.
* @param State $state
*
* @return boolean
*/
public function isValidDelivery(Country $country, State $state = null);
/**
* Calculate and return delivery price in the shop's default currency
*
* @param Country $country the country to deliver to.
* @param State $state
*
* @return OrderPostage|float the delivery price
* @throws DeliveryException if the postage price cannot be calculated.
*/
public function getPostage(Country $country, State $state = null);
/**
*
* This method return true if your delivery manages virtual product delivery.
*
* @return bool
*/
public function handleVirtualProductDelivery();
}

View File

@@ -0,0 +1,17 @@
<?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 Thelia\Module\Exception;
class DeliveryException extends \RuntimeException
{
}

View File

@@ -0,0 +1,22 @@
<?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 Thelia\Module\Exception;
/**
* Class InvalidXmlDocumentException
* @package Thelia\Module\Exception
* @author Manuel Raynaud <manu@raynaud.io>
*/
class InvalidXmlDocumentException extends \RuntimeException
{
}

View File

@@ -0,0 +1,132 @@
<?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 Thelia\Module;
use ErrorException;
use Symfony\Component\Finder\Finder;
use Thelia\Module\Exception\InvalidXmlDocumentException;
/**
* Class ModuleDescriptorValidator
* @package Thelia\Module
* @author Manuel Raynaud <manu@raynaud.io>
*/
class ModuleDescriptorValidator
{
protected static $versions = [
'1' => 'module.xsd',
'2' => 'module-2_1.xsd',
'3' => 'module-2_2.xsd'
];
/** @var Finder */
protected $xsdFinder;
protected $moduleVersion;
public function __construct()
{
$this->xsdFinder = new Finder();
$this->xsdFinder
->name('*.xsd')
->in(__DIR__ . '/schema/module/');
}
/**
* @return mixed
*/
public function getModuleVersion()
{
return $this->moduleVersion;
}
public function validate($xml_file, $version = null)
{
$dom = new \DOMDocument();
$errors = [];
if ($dom->load($xml_file)) {
/** @var \SplFileInfo $xsdFile */
foreach ($this->xsdFinder as $xsdFile) {
$xsdVersion = array_search($xsdFile->getBasename(), self::$versions);
if (false === $xsdVersion || (null !== $version && $version != $xsdVersion)) {
continue;
}
$errors = $this->schemaValidate($dom, $xsdFile);
if (\count($errors) === 0) {
$this->moduleVersion = $xsdVersion;
return true;
}
}
}
throw new InvalidXmlDocumentException(
sprintf(
"%s file is not a valid file : %s",
$xml_file,
implode(", ", $errors)
)
);
}
/**
* Validate the schema of a XML file with a given xsd file
*
* @param \DOMDocument $dom The XML document
* @param \SplFileInfo $xsdFile The XSD file
* @return array an array of errors if validation fails, otherwise an empty array
*/
protected function schemaValidate(\DOMDocument $dom, \SplFileInfo $xsdFile)
{
$errorMessages = [];
try {
libxml_use_internal_errors(true);
if (!$dom->schemaValidate($xsdFile->getRealPath())) {
$errors = libxml_get_errors();
foreach ($errors as $error) {
$errorMessages[] = sprintf(
'XML error "%s" [%d] (Code %d) in %s on line %d column %d' . "\n",
$error->message,
$error->level,
$error->code,
$error->file,
$error->line,
$error->column
);
}
libxml_clear_errors();
}
libxml_use_internal_errors(false);
} catch (ErrorException $ex) {
libxml_use_internal_errors(false);
}
return $errorMessages;
}
public function getDescriptor($xml_file)
{
$this->validate($xml_file);
return @simplexml_load_file($xml_file);
}
}

View File

@@ -0,0 +1,225 @@
<?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 Thelia\Module;
use Propel\Runtime\Connection\ConnectionInterface;
use Propel\Runtime\Propel;
use SplFileInfo;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Finder\Finder;
use Thelia\Core\Event\Cache\CacheEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Exception\InvalidModuleException;
use Thelia\Log\Tlog;
use Thelia\Model\Map\ModuleTableMap;
use Thelia\Model\Module;
use Thelia\Model\ModuleQuery;
/**
* Class ModuleManagement
* @package Thelia\Module
* @author Manuel Raynaud <manu@raynaud.io>
*/
class ModuleManagement
{
protected $baseModuleDir;
protected $reflected;
/** @var ModuleDescriptorValidator $descriptorValidator */
protected $descriptorValidator;
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->baseModuleDir = THELIA_MODULE_DIR;
}
public function updateModules(ContainerInterface $container)
{
$finder = new Finder();
$finder
->name('module.xml')
->in($this->baseModuleDir . '*' . DS . 'Config')
;
$errors = [];
$modulesUpdated = [];
foreach ($finder as $file) {
try {
$this->updateModule($file, $container);
} catch (\Exception $ex) {
// Guess module code
$moduleCode = basename(dirname(dirname($file)));
$errors[$moduleCode] = $ex;
}
}
if (\count($errors) > 0) {
throw new InvalidModuleException($errors);
}
if (\count($modulesUpdated)) {
$this->cacheClear();
}
}
/**
* Update module information, and invoke install() for new modules (e.g. modules
* just discovered), or update() modules for which version number ha changed.
*
* @param SplFileInfo $file the module.xml file descriptor
* @param ContainerInterface $container the container
*
* @return Module
*
* @throws \Exception
* @throws \Propel\Runtime\Exception\PropelException
*/
public function updateModule($file, ContainerInterface $container)
{
$descriptorValidator = $this->getDescriptorValidator();
$content = $descriptorValidator->getDescriptor($file->getRealPath());
$reflected = new \ReflectionClass((string)$content->fullnamespace);
$code = basename(dirname($reflected->getFileName()));
$version = (string)$content->version;
$mandatory = \intval($content->mandatory);
$hidden = \intval($content->hidden);
$module = ModuleQuery::create()->filterByCode($code)->findOne();
if (null === $module) {
$module = new Module();
$module->setActivate(0);
$action = 'install';
} elseif ($version !== $module->getVersion()) {
$currentVersion = $module->getVersion();
$action = 'update';
} else {
$action = 'none';
}
$con = Propel::getWriteConnection(ModuleTableMap::DATABASE_NAME);
$con->beginTransaction();
try {
$module
->setCode($code)
->setVersion($version)
->setFullNamespace((string)$content->fullnamespace)
->setType($this->getModuleType($reflected))
->setCategory((string)$content->type)
->setMandatory($mandatory)
->setHidden($hidden)
->save($con)
;
// Update the module images, title and description when the module is installed, but not after
// as these data may have been modified byt the administrator
if ('install' === $action) {
$this->saveDescription($module, $content, $con);
if (isset($content->{"images-folder"}) && !$module->isModuleImageDeployed($con)) {
/** @var \Thelia\Module\BaseModule $moduleInstance */
$moduleInstance = $reflected->newInstance();
$imagesFolder = THELIA_MODULE_DIR . $code . DS . (string)$content->{"images-folder"};
$moduleInstance->deployImageFolder($module, $imagesFolder, $con);
}
}
// Tell the module to install() or update()
$instance = $module->createInstance();
$instance->setContainer($container);
if ($action == 'install') {
$instance->install($con);
} elseif ($action == 'update') {
$instance->update($currentVersion, $version, $con);
}
if ($action !== 'none') {
$instance->registerHooks();
}
$con->commit();
} catch (\Exception $ex) {
Tlog::getInstance()->addError("Failed to update module " . $module->getCode(), $ex);
$con->rollBack();
throw $ex;
}
return $module;
}
/**
* @return \Thelia\Module\ModuleDescriptorValidator
*/
public function getDescriptorValidator()
{
if (null === $this->descriptorValidator) {
$this->descriptorValidator = new ModuleDescriptorValidator();
}
return $this->descriptorValidator;
}
protected function cacheClear()
{
$cacheEvent = new CacheEvent(
$this->container->getParameter('kernel.cache_dir')
);
$this->container->get('event_dispatcher')->dispatch(TheliaEvents::CACHE_CLEAR, $cacheEvent);
}
private function getModuleType(\ReflectionClass $reflected)
{
if (
$reflected->implementsInterface('Thelia\Module\DeliveryModuleInterface')
||
$reflected->implementsInterface('Thelia\Module\DeliveryModuleWithStateInterface')
) {
return BaseModule::DELIVERY_MODULE_TYPE;
} elseif ($reflected->implementsInterface('Thelia\Module\PaymentModuleInterface')) {
return BaseModule::PAYMENT_MODULE_TYPE;
} else {
return BaseModule::CLASSIC_MODULE_TYPE;
}
}
private function saveDescription(Module $module, \SimpleXMLElement $content, ConnectionInterface $con)
{
foreach ($content->descriptive as $description) {
$locale = (string)$description->attributes()->locale;
$module
->setLocale($locale)
->setTitle($description->title)
->setDescription(isset($description->description) ? $description->description : null)
->setPostscriptum(isset($description->postscriptum) ? $description->postscriptum : null)
->setChapo(isset($description->subtitle) ? $description->subtitle : null)
->save($con)
;
}
}
}

View File

@@ -0,0 +1,53 @@
<?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 Thelia\Module;
use Thelia\Model\Order;
interface PaymentModuleInterface extends BaseModuleInterface
{
/**
*
* Method used by payment gateway.
*
* If this method return a \Thelia\Core\HttpFoundation\Response instance, this response is send to the
* browser.
*
* In many cases, it's necessary to send a form to the payment gateway. On your response you can return this form already
* completed, ready to be sent
*
* @param \Thelia\Model\Order $order processed order
* @return null|\Thelia\Core\HttpFoundation\Response
*/
public function pay(Order $order);
/**
*
* 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 boolean
*/
public function isValidPayment();
/**
* if you want, you can manage stock in your module instead of order process.
* Return false to decrease the stock when order status switch to pay
*
* @return bool
*/
public function manageStockOnCreation();
}

View File

@@ -0,0 +1,249 @@
<?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 Thelia\Module\Validator;
/**
* Class ModuleDefinition
* @package Thelia\Module\Validator
* @author Julien Chanséaume <jchanseaume@openstudio.fr>
*/
class ModuleDefinition
{
/** @var string */
protected $code;
/** @var string */
protected $namespace;
/** @var string */
protected $type;
/** @var string */
protected $logo;
/** @var array */
protected $languages = [];
/** @var array */
protected $descriptives = [];
/** @var string */
protected $theliaVersion;
/** @var string */
protected $version;
/** @var array */
protected $dependencies = [];
/** @var string */
protected $documentation;
/** @var string */
protected $stability;
/** @var array */
protected $authors = [];
/**
* @return array
*/
public function getAuthors()
{
return $this->authors;
}
/**
* @param array $authors
*/
public function setAuthors($authors)
{
$this->authors = $authors;
}
/**
* @return string
*/
public function getCode()
{
return $this->code;
}
/**
* @param string $code
*/
public function setCode($code)
{
$this->code = $code;
}
/**
* @return array
*/
public function getDependencies()
{
return $this->dependencies;
}
/**
* @param array $dependencies
*/
public function setDependencies($dependencies)
{
$this->dependencies = $dependencies;
}
/**
* @return array
*/
public function getDescriptives()
{
return $this->descriptives;
}
/**
* @param array $descriptives
*/
public function setDescriptives($descriptives)
{
$this->descriptives = $descriptives;
}
/**
* @return string
*/
public function getDocumentation()
{
return $this->documentation;
}
/**
* @param string $documentation
*/
public function setDocumentation($documentation)
{
$this->documentation = $documentation;
}
/**
* @return array
*/
public function getLanguages()
{
return $this->languages;
}
/**
* @param array $languages
*/
public function setLanguages($languages)
{
$this->languages = $languages;
}
/**
* @return string
*/
public function getLogo()
{
return $this->logo;
}
/**
* @param string $logo
*/
public function setLogo($logo)
{
$this->logo = $logo;
}
/**
* @return string
*/
public function getTheliaVersion()
{
return $this->theliaVersion;
}
/**
* @param string $theliaVersion
*/
public function setTheliaVersion($theliaVersion)
{
$this->theliaVersion = $theliaVersion;
}
/**
* @return string
*/
public function getNamespace()
{
return $this->namespace;
}
/**
* @param string $namespace
*/
public function setNamespace($namespace)
{
$this->namespace = $namespace;
}
/**
* @return string
*/
public function getStability()
{
return $this->stability;
}
/**
* @param string $stability
*/
public function setStability($stability)
{
$this->stability = $stability;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @param string $type
*/
public function setType($type)
{
$this->type = $type;
}
/**
* @return string
*/
public function getVersion()
{
return $this->version;
}
/**
* @param string $version
*/
public function setVersion($version)
{
$this->version = $version;
}
}

View File

@@ -0,0 +1,532 @@
<?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 Thelia\Module\Validator;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Filesystem\Filesystem;
use Thelia\Core\Thelia;
use Thelia\Core\Translation\Translator;
use Thelia\Exception\FileNotFoundException;
use Thelia\Exception\ModuleException;
use Thelia\Model\Module;
use Thelia\Model\ModuleQuery;
use Thelia\Module\BaseModule;
use Thelia\Module\Exception\InvalidXmlDocumentException;
use Thelia\Module\ModuleDescriptorValidator;
use Thelia\Tools\Version\Version;
/**
* Class ModuleValidartor
*
* @package Thelia\Module\Validator
* @author Julien Chanséaume <jchanseaume@openstudio.fr>
*/
class ModuleValidator
{
protected $modulePath;
/** @var \SimpleXMLElement */
protected $moduleDescriptor;
/** @var ModuleDefinition */
protected $moduleDefinition;
protected $moduleVersion;
/** @var Translator */
protected $translator;
/** @var array array of errors */
protected $errors = [];
protected $moduleDirName;
/**
* @param string $modulePath the path of the module directory
* @param \Thelia\Core\Translation\Translator $translator FOR UNIT TEST PURPOSE ONLY
*/
public function __construct($modulePath = null, $translator = null)
{
$this->translator = $translator;
$this->modulePath = $modulePath;
$this->moduleDirName = basename($this->modulePath);
$this->checkDirectoryStructure();
$this->loadModuleDescriptor();
$this->loadModuleDefinition();
}
/**
* @param mixed $modulePath
*/
public function setModulePath($modulePath)
{
$this->modulePath = $modulePath;
}
/**
* @return mixed
*/
public function getModulePath()
{
return $this->modulePath;
}
/**
* @return ModuleDescriptorValidator|null
*/
public function getModuleDescriptor()
{
return $this->moduleDescriptor;
}
/**
* @return ModuleDefinition|null
*/
public function getModuleDefinition()
{
return $this->moduleDefinition;
}
/**
* @return mixed
*/
public function getModuleVersion()
{
return $this->moduleVersion;
}
/**
* @return array
*/
public function getErrors()
{
return $this->errors;
}
protected function trans($id, array $parameters = [])
{
if (null === $this->translator) {
try {
$this->translator = Translator::getInstance();
} catch (\RuntimeException $e) {
return strtr($id, $parameters);
}
}
return $this->translator->trans($id, $parameters);
}
/**
* Validate a module, checks :
*
* - version of Thelia
* - modules dependencies
*
* @param bool $checkCurrentVersion if true it will also check if the module is
* already installed (not activated - present in module list)
*/
public function validate($checkCurrentVersion = true)
{
if (null === $this->moduleDescriptor) {
throw new \Exception(
$this->trans(
"The %name module definition has not been initialized.",
[ '%name' => $this->moduleDirName ]
)
);
}
$this->checkVersion();
if (true === $checkCurrentVersion) {
$this->checkModuleVersion();
}
$this->checkModuleDependencies();
$this->checkModulePropelSchema();
}
protected function checkDirectoryStructure()
{
if (false === file_exists($this->modulePath)) {
throw new FileNotFoundException(
$this->trans(
"Module %name directory doesn't exists.",
[ '%name' => $this->moduleDirName]
)
);
}
$path = sprintf("%s/Config/module.xml", $this->modulePath);
if (false === file_exists($path)) {
throw new FileNotFoundException(
$this->trans(
"Module %name should have a module.xml in the Config directory.",
[ '%name' => $this->moduleDirName]
)
);
}
$path = sprintf("%s/Config/config.xml", $this->modulePath);
if (false === file_exists($path)) {
throw new FileNotFoundException(
$this->trans(
"Module %name should have a config.xml in the Config directory.",
[ '%name' => $this->moduleDirName]
)
);
}
}
protected function loadModuleDescriptor()
{
$path = sprintf("%s/Config/module.xml", $this->modulePath);
$descriptorValidator = new ModuleDescriptorValidator();
try {
// validation with xsd
$this->moduleDescriptor = $descriptorValidator->getDescriptor($path);
$this->moduleVersion = $descriptorValidator->getModuleVersion();
} catch (InvalidXmlDocumentException $ex) {
throw $ex;
}
}
public function loadModuleDefinition()
{
if (null === $this->moduleDescriptor) {
throw new \Exception(
$this->trans(
"The %name module descriptor has not been initialized.",
[ '%name' => $this->moduleDirName ]
)
);
}
$moduleDefinition = new ModuleDefinition();
// Try to guess the proper module name, using the descriptor information.
$fullnamespace = trim((string)$this->moduleDescriptor->fullnamespace);
$namespaceComponents = explode("\\", $fullnamespace);
if (! isset($namespaceComponents[0]) || empty($namespaceComponents[0])) {
throw new ModuleException(
$this->trans(
"Unable to get module code from the fullnamespace element of the module descriptor: '%val'",
[
'%name' => $this->moduleDirName,
'%val' => $fullnamespace
]
)
);
}
// Assume the module code is the first component of the declared namespace
$moduleDefinition->setCode($namespaceComponents[0]);
$moduleDefinition->setNamespace($fullnamespace);
$moduleDefinition->setVersion((string)$this->moduleDescriptor->version);
$this->getModuleLanguages($moduleDefinition);
$this->getModuleDescriptives($moduleDefinition);
$this->getModuleDependencies($moduleDefinition);
$this->getModuleAuthors($moduleDefinition);
$moduleDefinition->setLogo((string)$this->moduleDescriptor->logo);
$moduleDefinition->setTheliaVersion((string)$this->moduleDescriptor->thelia);
$moduleDefinition->setType((string)$this->moduleDescriptor->type);
$moduleDefinition->setStability((string)$this->moduleDescriptor->stability);
// documentation
$moduleDefinition->setDocumentation((string)$this->moduleDescriptor->documentation);
$this->moduleDefinition = $moduleDefinition;
}
public function checkModulePropelSchema()
{
$schemaFile = $this->getModulePath() . DS . "Config" . DS . "schema.xml";
$fs = new Filesystem();
if ($fs->exists($schemaFile) === false) {
return;
}
if (preg_match('/<behavior.*name="versionable".*\/>/s', preg_replace('/<!--(.|\s)*?-->/', '', file_get_contents($schemaFile)))) {
throw new ModuleException(
"On Thelia version >= 2.4.0 the behavior \"versionnable\" is not available for modules, please remove this behavior from your module schema."
);
}
}
protected function checkVersion()
{
if ($this->moduleDefinition->getTheliaVersion()) {
if (!Version::test(Thelia::THELIA_VERSION, $this->moduleDefinition->getTheliaVersion(), false, ">=")) {
throw new ModuleException(
$this->trans(
"The module %name requires Thelia %version or newer",
[
'%name' => $this->moduleDirName,
'%version' => $this->moduleDefinition->getTheliaVersion()
]
)
);
}
}
}
protected function checkModuleVersion()
{
$module = ModuleQuery::create()
->findOneByFullNamespace($this->moduleDefinition->getNamespace());
if (null !== $module) {
if (version_compare($module->getVersion(), $this->moduleDefinition->getVersion(), '>=')) {
throw new ModuleException(
$this->trans(
"The module %name is already installed in the same or greater version.",
[ '%name' => $this->moduleDirName]
)
);
}
}
}
protected function checkModuleDependencies()
{
$errors = [];
foreach ($this->moduleDefinition->getDependencies() as $dependency) {
$module = ModuleQuery::create()
->findOneByCode($dependency[0]);
$pass = false;
if (null !== $module) {
if ($module->getActivate() === BaseModule::IS_ACTIVATED) {
if ("" == $dependency[1] || Version::test($module->getVersion(), $dependency[1], false, ">=")) {
$pass = true;
}
}
}
if (false === $pass) {
if ('' !== $dependency[1]) {
$errors[] = $this->trans(
'%module (version: %version)',
[
'%module' => $dependency[0],
'%version' => $dependency[1]
]
);
} else {
$errors[] = sprintf('%s', $dependency[0]);
}
}
}
if (\count($errors) > 0) {
$errorsMessage = $this->trans(
'To activate module %name, the following modules should be activated first: %modules',
['%name' => $this->moduleDirName, '%modules' => implode(', ', $errors)]
);
throw new ModuleException($errorsMessage);
}
}
/**
* Get an array of modules that depend of the current module
*
* @param bool|null $active if true only search in activated module, false only deactivated and null on all modules
* @return array array of array with `code` which is the module code that depends of this current module and
* `version` which is the required version of current module
*/
public function getModulesDependOf($active = true)
{
$code = $this->getModuleDefinition()->getCode();
$query = ModuleQuery::create();
$dependantModules = [];
if (true === $active) {
$query->findByActivate(1);
} elseif (false === $active) {
$query->findByActivate(0);
}
$modules = $query->find();
/** @var Module $module */
foreach ($modules as $module) {
try {
$validator = new ModuleValidator($module->getAbsoluteBaseDir());
$definition = $validator->getModuleDefinition();
$dependencies = $definition->getDependencies();
if (\count($dependencies) > 0) {
foreach ($dependencies as $dependency) {
if ($dependency[0] == $code) {
$dependantModules[] = [
'code' => $definition->getCode(),
'version' => $dependency[1]
];
break;
}
}
}
} catch (\Exception $ex) {
;
}
}
return $dependantModules;
}
/**
* Get the dependencies of this module.
* @param bool $recursive Whether to also get the dependencies of dependencies, their dependencies, and so on...
* @return array Array of dependencies as ["code" => ..., "version" => ...]. No check for duplicates is made.
*/
public function getCurrentModuleDependencies($recursive = false)
{
if (empty($this->moduleDescriptor->required)) {
return [];
}
$dependencies = [];
foreach ($this->moduleDescriptor->required->module as $dependency) {
$dependencyArray = [
"code" => (string)$dependency,
"version" => (string)$dependency['version'],
];
if (!\in_array($dependencyArray, $dependencies)) {
$dependencies[] = $dependencyArray;
}
if ($recursive) {
$recursiveModuleValidator = new ModuleValidator(THELIA_MODULE_DIR . '/' . (string)$dependency);
array_merge(
$dependencies,
$recursiveModuleValidator->getCurrentModuleDependencies(true)
);
}
}
return $dependencies;
}
/**
* @param ModuleDefinition $moduleDefinition
*/
protected function getModuleLanguages(ModuleDefinition $moduleDefinition)
{
$languages = [];
if ($this->getModuleVersion() != "1") {
foreach ($this->moduleDescriptor->languages->language as $language) {
$languages[] = (string)$language;
}
}
$moduleDefinition->setLanguages($languages);
}
/**
* @param ModuleDefinition $moduleDefinition
*/
protected function getModuleDescriptives(ModuleDefinition $moduleDefinition)
{
$descriptives = [];
foreach ($this->moduleDescriptor->descriptive as $descriptive) {
$descriptives[(string)$descriptive['locale']] = [
'title' => (string)$descriptive->title,
'subtitle' => (string)$descriptive->subtitle,
'description' => (string)$descriptive->description,
'postscriptum' => (string)$descriptive->postscriptum,
];
}
$moduleDefinition->setDescriptives($descriptives);
}
/**
* @param ModuleDefinition $moduleDefinition
*/
protected function getModuleDependencies(ModuleDefinition $moduleDefinition)
{
$dependencies = [];
if (is_countable($this->moduleDescriptor->required) && 0 !== \count($this->moduleDescriptor->required)) {
foreach ($this->moduleDescriptor->required->module as $dependency) {
$dependencies[] = [
(string)$dependency,
(string)$dependency['version'],
];
}
}
$moduleDefinition->setDependencies($dependencies);
}
/**
* @param ModuleDefinition $moduleDefinition
*/
protected function getModuleAuthors(ModuleDefinition $moduleDefinition)
{
$authors = [];
if (is_countable($this->moduleDescriptor->author) && 0 !== \count($this->moduleDescriptor->author)) {
foreach ($this->moduleDescriptor->author as $author) {
$authors[] = [
(string)$author->name,
(string)$author->company,
(string)$author->email,
(string)$author->website
];
}
} else {
$authors = $this->getModuleAuthors22($moduleDefinition);
}
$moduleDefinition->setAuthors($authors);
}
protected function getModuleAuthors22(ModuleDefinition $moduleDefinition)
{
$authors = [];
if (!is_countable($this->moduleDescriptor->authors->author)
|| 0 === \count($this->moduleDescriptor->authors->author)
) {
return $authors;
}
foreach ($this->moduleDescriptor->authors->author as $author) {
$authors[] = [
(string)$author->name,
(string)$author->company,
(string)$author->email,
(string)$author->website
];
}
return $authors;
}
}

View File

@@ -0,0 +1,159 @@
<?xml version="1.0" encoding='UTF-8'?>
<xs:schema
xmlns="http://thelia.net/schema/dic/module"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://thelia.net/schema/dic/module"
attributeFormDefault="unqualified"
elementFormDefault="qualified"
>
<xs:element name="module">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" minOccurs="1" name="fullnamespace">
<xs:annotation>
<xs:documentation>The full namespace for the main class module (for example MyModule\MyModule)</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="descriptive" minOccurs="1" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Complete description, each description must be identify by ISO CODE 639</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="title" minOccurs="1" maxOccurs="1"/>
<xs:element type="xs:string" name="subtitle" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="description" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="postscriptum" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute type="xs:string" name="locale"/>
</xs:complexType>
</xs:element>
<xs:element type="xs:string" minOccurs="0" maxOccurs="1" name="logo">
<xs:annotation>
<xs:documentation>The path to the logo of the module (for example myModule.png). The recommended size is 128x128.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element type="xs:string" minOccurs="0" maxOccurs="1" name="images-folder">
<xs:annotation>
<xs:documentation>The folder that contains the images to deploy.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="languages">
<xs:annotation>
<xs:documentation>Module languages supported : fr_FR, en_US, ...</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="language" minOccurs="1" maxOccurs="unbounded">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[a-z]{2}_[A-Z]{2}"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element type="xs:string" name="version">
<xs:annotation>
<xs:documentation>Module version</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="author">
<xs:annotation>
<xs:documentation>Module author</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="name" minOccurs="1" maxOccurs="1"/>
<xs:element type="xs:string" name="company" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="email" minOccurs="1" maxOccurs="1"/>
<xs:element type="xs:anyURI" name="website" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="type">
<xs:annotation>
<xs:documentation>module type : classic, delivery, payment, ...</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="classic"/>
<xs:enumeration value="delivery"/>
<xs:enumeration value="payment"/>
<xs:enumeration value="marketplace"/>
<xs:enumeration value="price"/>
<xs:enumeration value="accounting"/>
<xs:enumeration value="seo"/>
<xs:enumeration value="administration"/>
<xs:enumeration value="statistic"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="tags" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Module tags</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="tag" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="required" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Plugins that should be presents. Plugins are identified by their codes and an optional minimal version</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="module" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="version" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="thelia" minOccurs="0" type="xs:string">
<xs:annotation>
<xs:documentation>minimum required version of Thelia in 'dot' format (for example 1.2.3.4)</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="stability">
<xs:annotation>
<xs:documentation>current module stability: alpha, beta, rc, prod</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="alpha"/>
<xs:enumeration value="beta"/>
<xs:enumeration value="rc"/>
<xs:enumeration value="prod"/>
<xs:enumeration value="other"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element type="xs:string" name="documentation" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>The name of the directory containing te documentation.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element type="xs:anyURI" name="urlmiseajour" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>URL to test if a new version of the module exists. Will be called with two get parameters : module name, current version</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element type="xs:anyURI" name="updateurl" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>URL to test if a new version of the module exists. Will be called with two get parameters : module name, current version</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -0,0 +1,187 @@
<?xml version="1.0" encoding='UTF-8'?>
<xs:schema
xmlns="http://thelia.net/schema/dic/module"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://thelia.net/schema/dic/module"
attributeFormDefault="unqualified"
elementFormDefault="qualified"
>
<xs:element name="module">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" minOccurs="1" name="fullnamespace">
<xs:annotation>
<xs:documentation>The full namespace for the main class module (for example MyModule\MyModule)</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="descriptive" minOccurs="1" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>Complete description, each description must be identify by ISO CODE 639</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="title" minOccurs="1" maxOccurs="1"/>
<xs:element type="xs:string" name="subtitle" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="description" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="postscriptum" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute type="xs:string" name="locale"/>
</xs:complexType>
</xs:element>
<xs:element type="xs:string" minOccurs="0" maxOccurs="1" name="logo">
<xs:annotation>
<xs:documentation>The path to the logo of the module (for example myModule.png). The recommended size is 128x128.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element type="xs:string" minOccurs="0" maxOccurs="1" name="images-folder">
<xs:annotation>
<xs:documentation>The folder that contains the images to deploy.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="languages">
<xs:annotation>
<xs:documentation>Module languages supported : fr_FR, en_US, ...</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="language" minOccurs="1" maxOccurs="unbounded">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[a-z]{2}_[A-Z]{2}"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element type="xs:string" name="version">
<xs:annotation>
<xs:documentation>Module version</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="authors" maxOccurs="1" minOccurs="0">
<xs:annotation>
<xs:documentation>Module authors</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="author" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="name" minOccurs="1" maxOccurs="1"/>
<xs:element type="xs:string" name="company" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="email" minOccurs="1" maxOccurs="1"/>
<xs:element type="xs:anyURI" name="website" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="type">
<xs:annotation>
<xs:documentation>module type : classic, delivery, payment, ...</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="classic"/>
<xs:enumeration value="delivery"/>
<xs:enumeration value="payment"/>
<xs:enumeration value="marketplace"/>
<xs:enumeration value="price"/>
<xs:enumeration value="accounting"/>
<xs:enumeration value="seo"/>
<xs:enumeration value="administration"/>
<xs:enumeration value="statistic"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="tags" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Module tags</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="tag" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="required" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Plugins that should be presents. Plugins are identified by their codes and an optional minimal version</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="module" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="version" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="thelia" minOccurs="0" type="xs:string">
<xs:annotation>
<xs:documentation>minimum required version of Thelia in 'dot' format (for example 1.2.3.4)</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="stability">
<xs:annotation>
<xs:documentation>current module stability: alpha, beta, rc, prod</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="alpha"/>
<xs:enumeration value="beta"/>
<xs:enumeration value="rc"/>
<xs:enumeration value="prod"/>
<xs:enumeration value="other"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element type="xs:string" name="documentation" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>The name of the directory containing te documentation.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element type="xs:anyURI" name="urlmiseajour" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>URL to test if a new version of the module exists. Will be called with two get parameters : module name, current version</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element type="xs:anyURI" name="updateurl" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>URL to test if a new version of the module exists. Will be called with two get parameters : module name, current version</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="mandatory" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Flag for protected module</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="0"/>
<xs:enumeration value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="hidden" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Flag for visible module</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="0"/>
<xs:enumeration value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding='UTF-8'?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="module">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" minOccurs="1" name="fullnamespace">
<xs:annotation>
<xs:documentation>The full namespace for the main class module (for example MyModule\MyModule)</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="descriptive" maxOccurs="unbounded">
<xs:annotation>
<xs:documentation>complete description, each description must be identify by ISO CODE 639</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="title" minOccurs="1" maxOccurs="1"/>
<xs:element type="xs:string" name="subtitle" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="description" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="postscriptum" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute type="xs:string" name="locale"/>
</xs:complexType>
</xs:element>
<xs:element type="xs:string" name="version">
<xs:annotation>
<xs:documentation>Module version</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="author">
<xs:annotation>
<xs:documentation>Module author</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="name" minOccurs="1" maxOccurs="1"/>
<xs:element type="xs:string" name="company" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="email" minOccurs="1" maxOccurs="1"/>
<xs:element type="xs:anyURI" name="website" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="type">
<xs:annotation>
<xs:documentation>module type : classic, delivery, payment</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="classic"/>
<xs:enumeration value="delivery"/>
<xs:enumeration value="payment"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="required" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Les plugins qui doivent déjà être présents</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="module" maxOccurs="unbounded" minOccurs="0">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="version" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="thelia">
<xs:annotation>
<xs:documentation>minimum required version of Thelia in 'dot' format (for example 1.2.3.4)</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[0-9.]+"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="stability">
<xs:annotation>
<xs:documentation>current module stability: alpha, beta, rc, prod</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="alpha"/>
<xs:enumeration value="beta"/>
<xs:enumeration value="rc"/>
<xs:enumeration value="prod"/>
<xs:enumeration value="other"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element type="xs:string" name="documentation" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>Le nom du fichier contenant la documentation. Ce fichier doit se trouver dans le répertoire du plugin.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element type="xs:anyURI" name="urlmiseajour" minOccurs="0" maxOccurs="1">
<xs:annotation>
<xs:documentation>L'URL a interroger pour vérifier la présence d'une nouvelle version, appellé avec le nom du plugin et sa version</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>