<?php
namespace B2bSellersCore;
use B2bCopperBrassSurcharge\B2bCopperBrassSurcharge;
use B2bCostCenter\B2bCostCenter;
use B2bDiscountRate\B2bDiscountRate;
use B2bEmployeeBudgets\B2bEmployeeBudgets;
use B2bEProcurementCxml\B2bEProcurementCxmlPT;
use B2bEProcurementOci\B2bEProcurementOci;
use B2bEventManager\B2bEventManager;
use B2bMobileSalesPortalApp\B2bMobileSalesPortalApp;
use B2bOffer\B2bOffer;
use B2bPartialAssortments\B2bPartialAssortments;
use B2bProductSubscription\B2bProductSubscription;
use B2bSalesRepresentativeFastOrder\B2bSalesRepresentativeFastOrder;
use B2bSellersCore\Components\Checkout\Payment\PaymentConditionPayment;
use B2bSellersCore\Components\Framework\DependencyInjection\B2bSellersCoreExtension;
use B2bSellersCore\Setup\B2bBonusProgram\B2bBonusProgramCustomFieldInstaller;
use B2bSellersCore\Setup\B2bProductLists\B2bProductListsPlatformMenuInstaller;
use B2bSellersCore\Setup\B2bProductLists\ProductListsConfigSetup;
use B2bSellersCore\Setup\B2bProductRequest\B2bProductRequestCustomFieldInstaller;
use B2bSellersCore\Setup\B2bProductRequest\MailTemplate\B2bProductRequestMailTemplateInstaller;
use B2bSellersCore\Setup\B2bUrlAuthentication\B2bUrlAuthenticationCustomFieldInstaller;
use B2bSellersCore\Setup\CoreConfigSetup;
use B2bSellersCore\Setup\CustomFieldSetup;
use B2bSellersCore\Setup\EventActionSetup;
use B2bSellersCore\Setup\FlowBuilderSetup;
use B2bSellersCore\Setup\InstallationCheck;
use B2bSellersCore\Setup\InstallationConstraints\PluginInstallationViolationException;
use B2bSellersCore\Setup\MailTemplateSetup;
use B2bSellersCore\Setup\MailTemplateUpdater;
use B2bSellersCore\Setup\PlatformMenuAddonFeatureNameMigration;
use B2bSellersCore\Setup\PlatformMenuTranslationsMigration;
use B2bSellersCore\Setup\RuleSetup;
use B2bSpareParts\B2bSpareParts;
use Composer\Autoload\ClassLoader;
use Composer\Autoload\ClassMapGenerator;
use Doctrine\DBAL\Connection;
use Shopware\Core\DevOps\Environment\EnvironmentHelper;
use Shopware\Core\Framework\Bundle;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\Parameter\AdditionalBundleParameters;
use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use Shopware\Core\Framework\Plugin\Context\UninstallContext;
use Shopware\Core\Framework\Plugin\Context\UpdateContext;
use Shopware\Core\Framework\Plugin\Util\PluginIdProvider;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\Kernel;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\DelegatingLoader;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
class B2bSellersCore extends Plugin
{
public const AVAILABLE_ADDONS = [
B2bOffer::class,
B2bEmployeeBudgets::class,
B2bCostCenter::class,
B2bDiscountRate::class,
B2bProductSubscription::class,
B2bCopperBrassSurcharge::class,
B2bSpareParts::class,
B2bEProcurementOci::class,
B2bEProcurementCxmlPT::class,
B2bSalesRepresentativeFastOrder::class,
B2bPartialAssortments::class,
B2bMobileSalesPortalApp::class,
B2bEventManager::class
];
public const AVAILABLE_FEATURES = [
'B2bBonusProgram', 'B2bProductRequest', 'B2bUrlAuthentication', 'B2bPdpVariantList', 'B2bProductLists'
];
public const LICENCE_FILE_PATH = '/var/b2b-licence.json';
/**
* @var Plugin[]
*/
private array $addonBundles = [];
public function uninstall(UninstallContext $context): void
{
parent::uninstall($context);
try {
$this->deleteCustomFieldSets();
} catch (\Exception $e) {
}
if ($context->keepUserData()) {
return;
}
$this->uninstallEventActions($context);
$this->uninstallFlowBuilder($context);
$this->uninstallMailTemplates();
$this->uninstallRules($context);
$this->uninstallPlatformMenus();
$connection = $this->container->get(Connection::class);
$connection->executeStatement('SET FOREIGN_KEY_CHECKS = 0');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_sales_representative_customer`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_employee`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_employee_customer`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_collection_account`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_customer_price`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_payment_condition`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_cart_extension`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_employee_role`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_employee_role_translation`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_employee_permission`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_employee_permission_translation`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_employee_permission_group`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_employee_permission_group_translation`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_passwordless_login`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_property_set`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_property_set_translation`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_customer_activity`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_customer_activity_type`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_customer_activity_type_translation`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_platform_menu_item`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_platform_menu_item_translation`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_express_checkout_setting`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_employee_invitation`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_order_extension`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_product_list`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_product_list_item`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_product_list_type`');
$connection->executeStatement('DROP TABLE IF EXISTS `b2b_product_list_type_translation`');
$connection->executeStatement('SET FOREIGN_KEY_CHECKS = 1');
$this->uninstallAddonBundles($context);
}
public function install(InstallContext $installContext): void
{
$errors = (new InstallationCheck($this->container))->checkConstraints();
if (count($errors)) {
throw new PluginInstallationViolationException('Unable to install B2Bsellers Suite:', $errors);
}
$this->createMailTemplates();
if (version_compare($installContext->getCurrentShopwareVersion(), '6.4.6.0', '>=')) {
$this->createFlowBuilder($installContext);
} else {
$this->createEventActions($installContext);
}
$this->createCustomFieldsets();
$this->addPaymentMethod($installContext->getContext());
}
public function postInstall(InstallContext $installContext): void
{
$this->installRules($installContext);
$this->updatePluginConfig();
$this->createPlatformMenus();
$this->installDefaultConfigValues($installContext);
$this->installAddonBundles($installContext);
(new PlatformMenuAddonFeatureNameMigration())->install($this->container->get(Connection::class));
(new PlatformMenuTranslationsMigration())->install($this->container->get(Connection::class));
}
public function update(UpdateContext $updateContext): void
{
$this->installRules($updateContext);
$this->createMailTemplates();
if (version_compare($updateContext->getCurrentShopwareVersion(), '6.4.6.0', '>=')) {
$this->createFlowBuilder($updateContext);
} else {
$this->createEventActions($updateContext);
}
$this->createCustomFieldsets();
$this->addPaymentMethod($updateContext->getContext());
}
public function postUpdate(UpdateContext $updateContext): void
{
$this->updateDefaultConfigValues($updateContext);
$this->updateAddonBundles($updateContext);
if (version_compare($updateContext->getCurrentPluginVersion(), '0.9.6', '<')) {
(new MailTemplateSetup($this->container))->update();
(new PlatformMenuTranslationsMigration())->install($this->container->get(Connection::class));
}
if (version_compare($updateContext->getCurrentPluginVersion(), '1.0.2', '<')) {
(new PlatformMenuAddonFeatureNameMigration())->install($this->container->get(Connection::class));
}
(new MailTemplateUpdater($updateContext, $this->container))->update();
}
private function createCustomFieldSets()
{
(new CustomFieldSetup($this->container))->install();
(new B2bBonusProgramCustomFieldInstaller($this->container))->install();
(new B2bProductRequestCustomFieldInstaller($this->container))->install();
(new B2bUrlAuthenticationCustomFieldInstaller($this->container))->install();
}
private function createMailTemplates()
{
(new MailTemplateSetup($this->container))->install();
(new B2bProductRequestMailTemplateInstaller($this->container))->install();
}
private function createPlatformMenus()
{
(new B2bProductListsPlatformMenuInstaller())->install($this->container->get(Connection::class));
}
private function uninstallPlatformMenus()
{
(new B2bProductListsPlatformMenuInstaller())->uninstall($this->container->get(Connection::class));
}
private function uninstallMailTemplates()
{
(new MailTemplateSetup($this->container))->uninstall();
(new B2bProductRequestMailTemplateInstaller($this->container))->uninstall();
}
private function deleteCustomFieldSets()
{
(new CustomFieldSetup($this->container))->uninstall();
(new B2bBonusProgramCustomFieldInstaller($this->container))->uninstall();
(new B2bProductRequestCustomFieldInstaller($this->container))->uninstall();
(new B2bUrlAuthenticationCustomFieldInstaller($this->container))->uninstall();
}
private function createEventActions(InstallContext $installContext)
{
(new EventActionSetup($this->container, $installContext->getContext()))->install();
}
private function createFlowBuilder(InstallContext $installContext)
{
(new FlowBuilderSetup($this->container, $installContext->getContext()))->install();
}
private function uninstallEventActions(UninstallContext $uninstallContext)
{
(new EventActionSetup($this->container, $uninstallContext->getContext()))->uninstall();
}
private function uninstallFlowBuilder(InstallContext $installContext)
{
(new FlowBuilderSetup($this->container, $installContext->getContext()))->uninstall();
}
private function installRules(InstallContext $context)
{
(new RuleSetup($this->container, $context->getContext()))->install();
}
private function uninstallRules(UninstallContext $context)
{
(new RuleSetup($this->container, $context->getContext()))->uninstall();
}
private function installDefaultConfigValues(InstallContext $context)
{
(new ProductListsConfigSetup($this->container, $context->getContext()))->install();
(new CoreConfigSetup($this->container, $context->getContext()))->install();
}
private function updateDefaultConfigValues(UpdateContext $context)
{
(new ProductListsConfigSetup($this->container, $context->getContext()))->update();
(new CoreConfigSetup($this->container, $context->getContext()))->update();
}
private function addPaymentMethod(Context $context)
{
/** @var EntityRepositoryInterface $paymentRepository */
$paymentRepository = $this->container->get('payment_method.repository');
/** @var PluginIdProvider $pluginIdProvider */
$pluginIdProvider = $this->container->get(PluginIdProvider::class);
$pluginId = $pluginIdProvider->getPluginIdByBaseClass(self::class, $context);
$paymentMethod = $this->getPaymentMethodId($context);
$paymentMethodId = Uuid::randomHex();
if (!empty($paymentMethod)) {
$paymentMethodId = $paymentMethod->getId();
}
$paymentMethodData = [
'id' => $paymentMethodId,
'handlerIdentifier' => PaymentConditionPayment::class,
'position' => 0,
'afterOrderEnabled' => false,
'active' => true,
'pluginId' => $pluginId,
'translations' => [
'de-DE' => [
'name' => 'Zahlungskondition',
'description' => 'Kunden Zahlungskonditionen Zahlungsart.',
],
'en-GB' => [
'name' => 'Payment condition',
'description' => 'Customer payment condition payment method.',
],
],
];
$paymentRepository->upsert([$paymentMethodData], $context);
}
private function getPaymentMethodId(Context $context)
{
/** @var EntityRepositoryInterface $paymentRepository */
$paymentRepository = $this->container->get('payment_method.repository');
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('handlerIdentifier', PaymentConditionPayment::class));
return $paymentRepository->search($criteria, $context)->first();
}
public function getContainerExtension()
{
if (null === $this->extension) {
$this->extension = new B2bSellersCoreExtension();
}
return $this->extension;
}
public function build(ContainerBuilder $container): void
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/Resources/config/packages/'));
$loader->load('shopware.yaml');
$loader->load('b2b_sellers_core.yaml');
parent::build($container);
$this->registerFeatureContainerFiles($container);
}
public function configureRoutes(RoutingConfigurator $routes, string $environment): void
{
parent::configureRoutes($routes, $environment);
$this->registerFeatureRoutes($routes);
}
public function getAdditionalFeaturePath(): ?string
{
if (!$this->getPath()) {
return null;
}
return $this->getPath() . '/Components/AdditionalFeatures';
}
private function registerFeatureContainerFiles(ContainerBuilder $container): void
{
$fileLocator = new FileLocator($this->getPath());
$loaderResolver = new LoaderResolver([new XmlFileLoader($container, $fileLocator),]);
$delegatingLoader = new DelegatingLoader($loaderResolver);
$licenceInformation = $this->loadLicenceInformation($this->getProjectDir());
$features = $licenceInformation['features'] ?? [];
$fileSystem = new Filesystem();
foreach ($features as $feature) {
$filePath = $this->getAdditionalFeaturePath() . '/' . $feature . '/DependencyInjection/services.xml';
if ($fileSystem->exists($filePath)) {
$delegatingLoader->load($filePath);
}
}
}
private function registerFeatureRoutes(RoutingConfigurator $routes): void
{
if (!$this->isActive()) {
return;
}
$licenceInformation = $this->loadLicenceInformation($this->getProjectDir());
$features = $licenceInformation['features'] ?? [];
$fileSystem = new Filesystem();
foreach ($features as $feature) {
$filePath = $this->getAdditionalFeaturePath() . '/' . $feature . '/DependencyInjection/routes.xml';
if ($fileSystem->exists($filePath)) {
$routes->import($filePath);
}
}
}
private function updatePluginConfig()
{
/** @var Connection $connection */
$connection = $this->container->get(Connection::class);
$connection->update('system_config',
['configuration_value' => json_encode(['_value' => ['customer']])],
['configuration_key' => 'B2bSellersCore.config.orderMailRecipients']
);
}
/**
* @return Bundle[]
*/
public function getAdditionalBundles(AdditionalBundleParameters $parameters): array
{
$this->synchronizeAutoloader(
$parameters->getClassLoader(),
$parameters->getKernelParameters()['kernel.project_dir']
);
$licence = $this->loadLicenceInformation($parameters->getKernelParameters()['kernel.project_dir']);
if (empty($licence) || empty($licence['addons'])) {
return [];
}
$activeBundles = $licence['addons'];
return array_filter($this->getAddonBundles(), function (Plugin $bundle) use ($activeBundles) {
return in_array((new \ReflectionClass($bundle))->getShortName(), $activeBundles);
});
}
/**
* @return Bundle[]
*/
public function getAllAdditionalBundles(AdditionalBundleParameters $parameters): array
{
$this->synchronizeAutoloader(
$parameters->getClassLoader(),
$parameters->getKernelParameters()['kernel.project_dir']
);
return $this->getAddonBundles();
}
/**
* @return Plugin[]
*/
private function getAddonBundles(): array
{
if (!empty($this->addonBundles)) {
return $this->addonBundles;
}
$projectDir = $this->getProjectDir();
$this->addonBundles = [];
foreach (self::AVAILABLE_ADDONS as $bundle) {
$addonBundle = new $bundle(true, $this->getBasePath(), $projectDir);
$addonBundle->setContainer($this->container);
$this->addonBundles[] = $addonBundle;
}
return $this->addonBundles;
}
private function installAddonBundles(InstallContext $context): void
{
$this->addonBundles = [];
foreach ($this->getAddonBundles() as $b2bBundle) {
$b2bBundle->install($context);
}
}
private function updateAddonBundles(UpdateContext $context): void
{
$this->addonBundles = [];
foreach ($this->getAddonBundles() as $b2bBundle) {
$b2bBundle->update($context);
}
}
private function uninstallAddonBundles(UninstallContext $context): void
{
$this->addonBundles = [];
foreach ($this->getAddonBundles() as $b2bBundle) {
$b2bBundle->uninstall($context);
}
}
private function loadLicenceInformation($projectDir): array
{
$filePath = $projectDir . '/var/b2b-licence.json';
if (!file_exists($filePath)) {
$this->createLicenceFile($projectDir);
return $this->loadLicenceInformation($projectDir);
}
return json_decode(file_get_contents($filePath), true);
}
private function getProjectDir(): string
{
if (mb_strpos($this->getBasePath(), '/vendor') !== false) {
return substr($this->getBasePath(), 0, mb_strpos($this->getBasePath(), '/vendor')) . '/';
}
return substr($this->getBasePath(), 0, mb_strpos($this->getBasePath(), '/custom/plugins')) . '/';
}
private function createLicenceFile(string $projectDir): void
{
$filePath = $projectDir . self::LICENCE_FILE_PATH;
if (file_exists($filePath)) {
return;
}
file_put_contents($filePath, json_encode([
'licenceId' => '', 'licenceDomain' => '', 'restrictions' => ['salesRepresentativeLimit' => 5], 'addons' => [], 'features' => []
]));
}
private function synchronizeAutoloader(ClassLoader $classLoader, string $projectDir)
{
/** @var Kernel $kernelClass */
$kernelClass = EnvironmentHelper::getVariable('KERNEL_CLASS', Kernel::class);
$connection = $kernelClass::getConnection();
$composerJson = json_decode(file_get_contents(__DIR__ . '/../composer.json'), true);
$autoload = $composerJson['autoload'];
$autoloadPsr4 = $autoload['psr-4'];
$synchronized = true;
$missingAutoload = [];
foreach ($autoloadPsr4 as $namespace => $dir) {
if (!array_key_exists($namespace, $classLoader->getPrefixesPsr4())) {
$missingAutoload[$namespace] = $dir;
$synchronized = false;
}
}
if ($synchronized) {
return;
}
$this->temporarilyRegisterClasses($classLoader, $missingAutoload, $this->getBasePath(), $projectDir);
$connection->update(
'plugin',
['autoload' => json_encode($autoload)],
['name' => $this->getName()]
);
}
private function temporarilyRegisterClasses(ClassLoader $classLoader, array $psr4, string $pluginPath, string $projectDir)
{
foreach ($psr4 as $namespace => $paths) {
if (\is_string($paths)) {
$paths = [$paths];
}
$mappedPaths = $this->mapPsrPaths($paths, $projectDir, $pluginPath);
$classLoader->addPsr4($namespace, $mappedPaths);
if ($classLoader->isClassMapAuthoritative()) {
foreach ($mappedPaths as $mappedPath) {
$classLoader->addClassMap(ClassMapGenerator::createMap($mappedPath));
}
}
}
}
private function mapPsrPaths(array $psr, string $projectDir, string $pluginRootPath): array
{
$mappedPaths = [];
$absolutePluginRootPath = $this->getAbsolutePluginRootPath($projectDir, $pluginRootPath);
foreach ($psr as $path) {
$mappedPaths[] = $absolutePluginRootPath . '/' . $path;
}
return $mappedPaths;
}
private function getAbsolutePluginRootPath(string $projectDir, string $pluginRootPath): string
{
if (mb_strpos($pluginRootPath, '/') !== 0) {
$pluginRootPath = $projectDir . '/' . $pluginRootPath;
}
return $pluginRootPath;
}
}