JEMBOT MAWOT Bypass Shell
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
namespace PrestaShopBundle\Controller\Admin;
use Category;
use Exception;
use LogicException;
use PrestaShop\PrestaShop\Adapter\Product\AdminProductWrapper;
use PrestaShop\PrestaShop\Adapter\Product\FilterCategoriesRequestPurifier;
use PrestaShop\PrestaShop\Adapter\Product\ListParametersUpdater;
use PrestaShop\PrestaShop\Adapter\Tax\TaxRuleDataProvider;
use PrestaShop\PrestaShop\Adapter\Tools;
use PrestaShop\PrestaShop\Adapter\Warehouse\WarehouseDataProvider;
use PrestaShop\PrestaShop\Core\Domain\Product\Command\UpdateProductCommand;
use PrestaShop\PrestaShop\Core\Domain\Product\Exception\CannotUpdateProductException;
use PrestaShop\PrestaShop\Core\Domain\Product\Exception\ProductConstraintException;
use PrestaShop\PrestaShop\Core\Domain\Product\Exception\ProductException;
use PrestaShop\PrestaShop\Core\Domain\Product\Exception\ProductNotFoundException;
use PrestaShop\PrestaShop\Core\Domain\Product\Query\GetProductForEditing;
use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductForEditing;
use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings;
use PrestaShop\PrestaShop\Core\Hook\HookDispatcher;
use PrestaShop\PrestaShop\Core\Product\ProductCsvExporter;
use PrestaShopBundle\Component\CsvResponse;
use PrestaShopBundle\Entity\AdminFilter;
use PrestaShopBundle\Entity\Attribute;
use PrestaShopBundle\Entity\Repository\AttributeRepository;
use PrestaShopBundle\Entity\Repository\FeatureFlagRepository;
use PrestaShopBundle\Exception\UpdateProductException;
use PrestaShopBundle\Form\Admin\Product\ProductCategories;
use PrestaShopBundle\Form\Admin\Product\ProductCombination;
use PrestaShopBundle\Form\Admin\Product\ProductCombinationBulk;
use PrestaShopBundle\Form\Admin\Product\ProductInformation;
use PrestaShopBundle\Form\Admin\Product\ProductOptions;
use PrestaShopBundle\Form\Admin\Product\ProductPrice;
use PrestaShopBundle\Form\Admin\Product\ProductQuantity;
use PrestaShopBundle\Form\Admin\Product\ProductSeo;
use PrestaShopBundle\Form\Admin\Product\ProductShipping;
use PrestaShopBundle\Model\Product\AdminModelAdapter;
use PrestaShopBundle\Security\Annotation\AdminSecurity;
use PrestaShopBundle\Security\Voter\PageVoter;
use PrestaShopBundle\Service\DataProvider\Admin\ProductInterface as ProductInterfaceProvider;
use PrestaShopBundle\Service\DataProvider\StockInterface;
use PrestaShopBundle\Service\DataUpdater\Admin\ProductInterface as ProductInterfaceUpdater;
use PrestaShopBundle\Service\Hook\HookFinder;
use Product;
use Psr\Log\LoggerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Tools as LegacyTools;
/**
* @deprecated since 8.1 and will be removed in next major.
*
* Admin controller for the Product pages using the Symfony architecture:
* - categories
* - product list
* - product details
* - product attributes
* - ...
*
* This controller is the first one to be refactored to the new Symfony Architecture.
* The retro-compatibility is dropped for the corresponding Admin pages.
* A set of hooks are integrated and an Adapter is made to wrap the new EventDispatcher
* component to the existing hook system. So existing hooks are always triggered, but from the new
* code (and so needs to be adapted on the module side ton comply on the new parameters formats,
* the new UI style, etc...).
*/
class ProductController extends FrameworkBundleAdminController
{
/**
* Used to validate connected user authorizations.
*/
public const PRODUCT_OBJECT = 'ADMINPRODUCTS_';
/**
* Get the Catalog page with KPI banner, product list, bulk actions, filters, search, etc...
*
* URL example: /product/catalog/40/20/id_product/asc
*
* @AdminSecurity("is_granted('create', request.get('_legacy_controller')) || is_granted('update', request.get('_legacy_controller')) || is_granted('read', request.get('_legacy_controller'))")
* @Template("@PrestaShop/Admin/Product/CatalogPage/catalog.html.twig")
*
* @param Request $request
* @param int $limit The size of the listing
* @param int $offset The offset of the listing
* @param string $orderBy To order product list
* @param string $sortOrder To order product list
*
* @return array|Template|RedirectResponse|Response
*
* @throws \Symfony\Component\Translation\Exception\InvalidArgumentException
* @throws \Symfony\Component\Routing\Exception\RouteNotFoundException
* @throws LogicException
* @throws \Symfony\Component\Routing\Exception\MissingMandatoryParametersException
* @throws \Symfony\Component\Routing\Exception\InvalidParameterException
* @throws \Symfony\Component\Form\Exception\LogicException
* @throws \Symfony\Component\Form\Exception\AlreadySubmittedException
*/
public function catalogAction(
Request $request,
$limit = 10,
$offset = 0,
$orderBy = 'id_product',
$sortOrder = 'desc'
) {
if ($this->shouldRedirectToV2()) {
return $this->redirectToRoute('admin_products_index');
}
$language = $this->getContext()->language;
$request->getSession()->set('_locale', $language->locale);
$request = $this->get(FilterCategoriesRequestPurifier::class)->purify($request);
/** @var ProductInterfaceProvider $productProvider */
$productProvider = $this->get('prestashop.core.admin.data_provider.product_interface');
// Set values from persistence and replace in the request
$persistedFilterParameters = $productProvider->getPersistedFilterParameters();
/** @var ListParametersUpdater $listParametersUpdater */
$listParametersUpdater = $this->get(ListParametersUpdater::class);
$listParameters = $listParametersUpdater->buildListParameters(
$request->query->all(),
$persistedFilterParameters,
compact('offset', 'limit', 'orderBy', 'sortOrder')
);
$offset = $listParameters['offset'];
$limit = $listParameters['limit'];
$orderBy = $listParameters['orderBy'];
$sortOrder = $listParameters['sortOrder'];
//The product provider performs the same merge internally, so we do the same so that the displayed filters are
//consistent with the request ones
$combinedFilterParameters = array_replace($persistedFilterParameters, $request->request->all());
$toolbarButtons = $this->getToolbarButtons();
// Fetch product list (and cache it into view subcall to listAction)
$products = $productProvider->getCatalogProductList(
$offset,
$limit,
$orderBy,
$sortOrder,
$request->request->all()
);
$lastSql = $productProvider->getLastCompiledSql();
$hasCategoryFilter = $productProvider->isCategoryFiltered();
$hasColumnFilter = $productProvider->isColumnFiltered();
$totalFilteredProductCount = (count($products) > 0) ? $products[0]['total'] : 0;
// Alternative layout for empty list
if ((!$hasCategoryFilter && !$hasColumnFilter && $totalFilteredProductCount === 0)
|| ($totalProductCount = $productProvider->countAllProducts()) === 0
) {
// no filter, total filtered == 0, and then total count == 0 too.
$legacyUrlGenerator = $this->get('prestashop.core.admin.url_generator_legacy');
return $this->render(
'@PrestaShop/Admin/Product/CatalogPage/catalog_empty.html.twig',
[
'layoutHeaderToolbarBtn' => $toolbarButtons,
'import_url' => $legacyUrlGenerator->generate('AdminImport'),
]
);
}
// Pagination
$paginationParameters = $request->attributes->all();
$paginationParameters['_route'] = 'admin_product_catalog';
$categoriesForm = $this->createForm(ProductCategories::class);
if (!empty($combinedFilterParameters['filter_category'])) {
$categoriesForm->setData(
[
'categories' => [
'tree' => [0 => $combinedFilterParameters['filter_category']],
],
]
);
}
$cleanFilterParameters = $listParametersUpdater->cleanFiltersForPositionOrdering(
$combinedFilterParameters,
$orderBy,
$hasCategoryFilter
);
$permissionError = null;
if ($this->get('session')->getFlashBag()->has('permission_error')) {
$permissionError = $this->get('session')->getFlashBag()->get('permission_error')[0];
}
$categoriesFormView = $categoriesForm->createView();
$selectedCategory = !empty($combinedFilterParameters['filter_category'])
? new Category((int) $combinedFilterParameters['filter_category'])
: null;
//Drag and drop is ONLY activated when EXPLICITLY requested by the user
//Meaning a category is selected and the user clicks on REORDER button
$activateDragAndDrop = 'position_ordering' === $orderBy && $hasCategoryFilter;
// Template vars injection
return array_merge(
$cleanFilterParameters,
[
'limit' => $limit,
'offset' => $offset,
'orderBy' => $orderBy,
'sortOrder' => $sortOrder,
'has_filter' => $hasCategoryFilter || $hasColumnFilter,
'has_category_filter' => $hasCategoryFilter,
'selected_category' => $selectedCategory,
'has_column_filter' => $hasColumnFilter,
'products' => $products,
'last_sql' => $lastSql,
'product_count_filtered' => $totalFilteredProductCount,
'product_count' => $totalProductCount,
'activate_drag_and_drop' => $activateDragAndDrop,
'pagination_parameters' => $paginationParameters,
'layoutHeaderToolbarBtn' => $toolbarButtons,
'categories' => $categoriesFormView,
'pagination_limit_choices' => $productProvider->getPaginationLimitChoices(),
'import_link' => $this->generateUrl('admin_import', ['import_type' => 'products']),
'sql_manager_add_link' => $this->generateUrl('admin_sql_requests_create'),
'enableSidebar' => true,
'help_link' => $this->generateSidebarLink('AdminProducts'),
'is_shop_context' => $this->get('prestashop.adapter.shop.context')->isShopContext(),
'permission_error' => $permissionError,
'layoutTitle' => $this->trans('Products', 'Admin.Global'),
]
);
}
/**
* Get only the list of products to display on the main Admin Product page.
* The full page that shows products list will subcall this action (from catalogAction).
* URL example: /product/list/html/40/20/id_product/asc.
*
* @Template("@PrestaShop/Admin/Product/CatalogPage/Lists/list.html.twig")
*
* @param Request $request
* @param int $limit The size of the listing
* @param int $offset The offset of the listing
* @param string $orderBy To order product list
* @param string $sortOrder To order product list
* @param string $view full|quicknav To change default template used to render the content
*
* @return array|Template|Response
*/
public function listAction(
Request $request,
$limit = 10,
$offset = 0,
$orderBy = 'id_product',
$sortOrder = 'asc',
$view = 'full'
) {
if (!$this->isGranted(PageVoter::READ, self::PRODUCT_OBJECT)) {
return $this->redirect('admin_dashboard');
}
/** @var ProductInterfaceProvider $productProvider */
$productProvider = $this->get('prestashop.core.admin.data_provider.product_interface');
$adminProductWrapper = $this->get(AdminProductWrapper::class);
$totalCount = 0;
$this->get('prestashop.service.product')->cleanupOldTempProducts();
$products = $request->attributes->get('products', null); // get from action subcall data, if any
$lastSql = $request->attributes->get('last_sql', null); // get from action subcall data, if any
if ($products === null) {
// get old values from persistence (before the current update)
$persistedFilterParameters = $productProvider->getPersistedFilterParameters();
/** @var ListParametersUpdater $listParametersUpdater */
$listParametersUpdater = $this->get(ListParametersUpdater::class);
$listParameters = $listParametersUpdater->buildListParameters(
$request->query->all(),
$persistedFilterParameters,
compact('offset', 'limit', 'orderBy', 'sortOrder')
);
$offset = $listParameters['offset'];
$limit = $listParameters['limit'];
$orderBy = $listParameters['orderBy'];
$sortOrder = $listParameters['sortOrder'];
/**
* 2 hooks are triggered here:
* - actionAdminProductsListingFieldsModifier
* - actionAdminProductsListingResultsModifier.
*/
$products = $productProvider->getCatalogProductList($offset, $limit, $orderBy, $sortOrder);
$lastSql = $productProvider->getLastCompiledSql();
}
$hasCategoryFilter = $productProvider->isCategoryFiltered();
// Adds controller info (URLs, etc...) to product list
foreach ($products as &$product) {
$totalCount = isset($product['total']) ? $product['total'] : $totalCount;
$product['url'] = $this->generateUrl(
'admin_product_form',
['id' => $product['id_product']]
);
$product['unit_action_url'] = $this->generateUrl(
'admin_product_unit_action',
[
'action' => 'duplicate',
'id' => $product['id_product'],
]
);
$product['preview_url'] = $adminProductWrapper->getPreviewUrlFromId($product['id_product']);
$product['url_v2'] = $this->generateUrl('admin_products_edit', ['productId' => $product['id_product']]);
}
//Drag and drop is ONLY activated when EXPLICITLY requested by the user
//Meaning a category is selected and the user clicks on REORDER button
$activateDragAndDrop = 'position_ordering' === $orderBy && $hasCategoryFilter;
// Template vars injection
$vars = [
'activate_drag_and_drop' => $activateDragAndDrop,
'products' => $products,
'product_count' => $totalCount,
'last_sql_query' => $lastSql,
'has_category_filter' => $productProvider->isCategoryFiltered(),
'is_shop_context' => $this->get('prestashop.adapter.shop.context')->isShopContext(),
];
if ($view !== 'full') {
return $this->render(
'@Product/CatalogPage/Lists/list_' . $view . '.html.twig',
array_merge(
$vars,
[
'limit' => $limit,
'offset' => $offset,
'total' => $totalCount,
]
)
);
}
return $vars;
}
/**
* Gets the header toolbar buttons.
*
* @return array
*/
private function getToolbarButtons()
{
$toolbarButtons = [];
$toolbarButtons['add'] = [
'href' => $this->generateUrl('admin_product_new'),
'desc' => $this->trans('New product', 'Admin.Actions'),
'icon' => 'add_circle_outline',
'help' => $this->trans('Create a new product: CTRL+P', 'Admin.Catalog.Help'),
];
return $toolbarButtons;
}
/**
* Create a new basic product
* Then return to form action.
*
* @return RedirectResponse
*
* @throws LogicException
* @throws \PrestaShopException
*/
public function newAction()
{
if (!$this->isGranted(PageVoter::CREATE, self::PRODUCT_OBJECT)) {
$errorMessage = $this->trans('You do not have permission to add this.', 'Admin.Notifications.Error');
$this->get('session')->getFlashBag()->add('permission_error', $errorMessage);
return $this->redirectToRoute('admin_product_catalog');
}
$productProvider = $this->get('prestashop.core.admin.data_provider.product_interface');
$languages = $this->get('prestashop.adapter.legacy.context')->getLanguages();
/** @var ProductInterfaceProvider $productProvider */
$productAdapter = $this->get('prestashop.adapter.data_provider.product');
$productShopCategory = $this->getContext()->shop->id_category;
/** @var Product $product */
$product = $productAdapter->getProductInstance();
$product->id_category_default = $productShopCategory;
/** @var TaxRuleDataProvider $taxRuleDataProvider */
$taxRuleDataProvider = $this->get('prestashop.adapter.data_provider.tax');
$product->id_tax_rules_group = $taxRuleDataProvider->getIdTaxRulesGroupMostUsed();
$product->active = $productProvider->isNewProductDefaultActivated();
$product->state = Product::STATE_TEMP;
//set name and link_rewrite in each lang
foreach ($languages as $lang) {
$product->name[$lang['id_lang']] = '';
$product->link_rewrite[$lang['id_lang']] = '';
}
$product->save();
$product->addToCategories([$productShopCategory]);
return $this->redirectToRoute('admin_product_form', ['id' => $product->id]);
}
/**
* Product form.
*
* @Template("@PrestaShop/Admin/Product/ProductPage/product.html.twig")
*
* @param int $id The product ID
* @param Request $request
*
* @return array|Response Template vars
*
* @throws Exception
*/
public function formAction($id, Request $request)
{
if ($this->shouldRedirectToV2()) {
return $this->redirectToRoute('admin_products_edit', ['productId' => $id]);
}
gc_disable();
foreach ([PageVoter::READ, PageVoter::UPDATE, PageVoter::CREATE] as $permission) {
if (!$this->isGranted($permission, self::PRODUCT_OBJECT)) {
return $this->redirect('admin_dashboard');
}
}
$productAdapter = $this->get('prestashop.adapter.data_provider.product');
try {
$product = $productAdapter->getProduct($id);
} catch (LogicException $e) {
$product = null;
}
if (!$product || empty($product->id)) {
$this->addFlash(
'warning',
$this->trans('The product you are trying to access doesn\'t exist.', 'Admin.Catalog.Notification')
);
return $this->redirectToRoute('admin_product_catalog');
}
$shopContext = $this->get('prestashop.adapter.shop.context');
$legacyContextService = $this->get('prestashop.adapter.legacy.context');
$isMultiShopContext = count($shopContext->getContextListShopID()) > 1;
$modelMapper = $this->get('prestashop.adapter.admin.model.product');
$adminProductWrapper = $this->get(AdminProductWrapper::class);
$form = $this->createProductForm($product, $modelMapper);
$formBulkCombinations = $this->createForm(
ProductCombinationBulk::class,
null,
[
'iso_code' => $this
->get('prestashop.adapter.legacy.context')
->getContext()->currency->iso_code,
]
);
// Legacy code. To fix when Object model will change. But report Hooks.
$postData = $request->request->all();
$combinationsList = [];
if (!empty($postData)) {
foreach ($postData as $postKey => $postValue) {
if (preg_match('/^combination_.*/', $postKey)) {
$combinationsList[$postKey] = $postValue;
$postData['form'][$postKey] = $postValue; // need to validate the form
}
}
// Duplicate Request to be a valid form (like it was real) with postData modified ..
$request = $request->duplicate(
$request->query->all(),
$postData,
$request->attributes->all(),
$request->cookies->all(),
$request->files->all(),
$request->server->all()
);
}
/* @var Form $form */
$form->handleRequest($request);
$formData = $form->getData();
$formData['step3']['combinations'] = $combinationsList;
try {
if ($form->isSubmitted()) {
if ($this->isDemoModeEnabled() && $request->isXmlHttpRequest()) {
$errorMessage = $this->getDemoModeErrorMessage();
return $this->returnErrorJsonResponse(
['error' => [$errorMessage]],
Response::HTTP_SERVICE_UNAVAILABLE
);
}
if ($form->isValid()) {
//define POST values for keeping legacy adminController skills
$_POST = $modelMapper->getModelData($formData, $isMultiShopContext) + $_POST;
$_POST['form'] = $formData;
$_POST['state'] = Product::STATE_SAVED;
$adminProductController = $adminProductWrapper->getInstance();
$adminProductController->setIdObject($formData['id_product']);
$adminProductController->setAction('save');
// Hooks: this will trigger legacy AdminProductController, postProcess():
// actionAdminSaveBefore; actionAdminProductsControllerSaveBefore
// actionProductAdd or actionProductUpdate (from processSave() -> processAdd() or processUpdate())
// actionAdminSaveAfter; actionAdminProductsControllerSaveAfter
$productSaveResult = $adminProductController->postCoreProcess();
if (false == $productSaveResult) {
return $this->returnErrorJsonResponse(
['error' => $adminProductController->errors],
Response::HTTP_BAD_REQUEST
);
}
$product = $productSaveResult;
/* @var Product $product */
$adminProductController->processSuppliers($product->id);
$adminProductController->processFeatures($product->id);
$adminProductController->processSpecificPricePriorities();
foreach ($_POST['combinations'] as $combinationValues) {
$adminProductWrapper->processProductAttribute($product, $combinationValues);
// For now, each attribute set the same value.
$adminProductWrapper->processDependsOnStock(
$product,
($_POST['depends_on_stock'] == '1'),
$combinationValues['id_product_attribute']
);
}
$adminProductWrapper->processDependsOnStock($product, ($_POST['depends_on_stock'] == '1'));
// If there is no combination, then quantity and location are managed for the whole product (as combination ID 0)
// In all cases, legacy hooks are triggered: actionProductUpdate and actionUpdateQuantity
if (count($_POST['combinations']) === 0 && isset($_POST['qty_0'])) {
$adminProductWrapper->processQuantityUpdate($product, $_POST['qty_0']);
$adminProductWrapper->processLocation($product, (string) $_POST['location']);
}
// else quantities are managed from $adminProductWrapper->processProductAttribute() above.
$adminProductWrapper->processProductOutOfStock($product, $_POST['out_of_stock']);
$customizationFieldsIds = $adminProductWrapper
->processProductCustomization($product, $_POST['custom_fields']);
$adminProductWrapper->processAttachments($product, $_POST['attachments']);
$adminProductController->processWarehouses();
$response = new JsonResponse();
$response->setData([
'product' => $product,
'customization_fields_ids' => $customizationFieldsIds,
]);
if ($request->isXmlHttpRequest()) {
return $response;
}
} elseif ($request->isXmlHttpRequest()) {
return $this->returnErrorJsonResponse(
$this->getFormErrorsForJS($form),
Response::HTTP_BAD_REQUEST
);
}
}
} catch (Exception $e) {
// this controller can be called as an AJAX JSON route or an HTML page
// so we need to return the right type of response if an exception is thrown
if ($request->isXmlHttpRequest()) {
return $this->returnErrorJsonResponse(
[],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
throw $e;
}
/** @var StockInterface $stockManager */
$stockManager = $this->get('prestashop.core.data_provider.stock_interface');
/** @var WarehouseDataProvider $warehouseProvider */
$warehouseProvider = $this->get('prestashop.adapter.data_provider.warehouse');
//If context shop is define to a group shop, disable the form
if ($shopContext->isGroupShopContext()) {
return $this->render('@Product/ProductPage/disabled_form_alert.html.twig', ['showContentHeader' => false]);
}
// languages for switch dropdown
$languages = $legacyContextService->getLanguages();
// generate url preview
if ($product->active) {
$preview_url = $adminProductWrapper->getPreviewUrl($product);
$preview_url_deactive = $adminProductWrapper->getPreviewUrlDeactivate($preview_url);
} else {
$preview_url_deactive = $adminProductWrapper->getPreviewUrl($product, false);
$preview_url = $adminProductWrapper->getPreviewUrlDeactivate($preview_url_deactive);
}
$doctrine = $this->getDoctrine()->getManager();
$language = empty($languages[0]) ? ['id_lang' => 1, 'id_shop' => 1] : $languages[0];
/** @var AttributeRepository $attributeRepository */
$attributeRepository = $doctrine->getRepository(Attribute::class);
$attributeGroups = $attributeRepository->findByLangAndShop((int) $language['id_lang'], (int) $language['id_shop']);
$drawerModules = (new HookFinder())->setHookName('displayProductPageDrawer')
->setParams(['product' => $product])
->addExpectedInstanceClasses('PrestaShop\PrestaShop\Core\Product\ProductAdminDrawer')
->present();
return [
'form' => $form->createView(),
'formCombinations' => $formBulkCombinations->createView(),
'categories' => $this->get('prestashop.adapter.data_provider.category')->getCategoriesWithBreadCrumb(),
'id_product' => $id,
'ids_product_attribute' => (isset($formData['step3']['id_product_attributes']) ? implode(',', $formData['step3']['id_product_attributes']) : ''),
'has_combinations' => (isset($formData['step3']['id_product_attributes']) && count($formData['step3']['id_product_attributes']) > 0),
'combinations_count' => isset($formData['step3']['id_product_attributes']) ? count($formData['step3']['id_product_attributes']) : 0,
'asm_globally_activated' => $stockManager->isAsmGloballyActivated(),
'warehouses' => ($stockManager->isAsmGloballyActivated()) ? $warehouseProvider->getWarehouses() : [],
'is_multishop_context' => $isMultiShopContext,
'is_combination_active' => $this->getConfiguration()->getBoolean('PS_COMBINATION_FEATURE_ACTIVE'),
'showContentHeader' => false,
'seo_link' => $adminProductWrapper->getPreviewUrl($product, false),
'preview_link' => $preview_url,
'preview_link_deactivate' => $preview_url_deactive,
'stats_link' => $this->getAdminLink('AdminStats', ['module' => 'statsproduct', 'id_product' => $id]),
'help_link' => $this->generateSidebarLink('AdminProducts'),
'languages' => $languages,
'default_language_iso' => $languages[0]['iso_code'],
'attribute_groups' => $attributeGroups,
'max_upload_size' => LegacyTools::formatBytes(UploadedFile::getMaxFilesize()),
'is_shop_context' => $this->get('prestashop.adapter.shop.context')->isShopContext(),
'editable' => $this->isGranted(PageVoter::UPDATE, self::PRODUCT_OBJECT),
'drawerModules' => $drawerModules,
'layoutTitle' => $this->trans('Product', 'Admin.Global'),
'isCreationMode' => (int) $product->state === Product::STATE_TEMP,
];
}
/**
* Builds the product form.
*
* @param Product $product
* @param AdminModelAdapter $modelMapper
*
* @return FormInterface
*
* @throws \Symfony\Component\Process\Exception\LogicException
*/
private function createProductForm(Product $product, AdminModelAdapter $modelMapper)
{
$formBuilder = $this->createFormBuilder(
$modelMapper->getFormData($product),
['allow_extra_fields' => true]
)
->add('id_product', HiddenType::class)
->add('step1', ProductInformation::class)
->add('step2', ProductPrice::class, ['id_product' => $product->id])
->add('step3', ProductQuantity::class)
->add('step4', ProductShipping::class)
->add('step5', ProductSeo::class, [
'mapping_type' => $product->getRedirectType(),
])
->add('step6', ProductOptions::class);
// Prepare combination form (fake but just to validate the form)
$combinations = $product->getAttributesResume(
$this->getContext()->language->id
);
if (is_array($combinations)) {
$maxInputVars = (int) ini_get('max_input_vars');
$combinationsCount = count($combinations) * 25;
$combinationsInputs = ceil($combinationsCount / 1000) * 1000;
if ($combinationsInputs > $maxInputVars) {
$this->addFlash(
'error',
$this->trans(
'The value of the PHP.ini setting "max_input_vars" must be increased to %value% in order to be able to submit the product form.',
'Admin.Notifications.Error',
['%value%' => $combinationsInputs]
)
);
}
foreach ($combinations as $combination) {
$formBuilder->add(
'combination_' . $combination['id_product_attribute'],
ProductCombination::class,
['allow_extra_fields' => true]
);
}
}
return $formBuilder->getForm();
}
/**
* Do bulk action on a list of Products. Used with the 'selection action' dropdown menu on the Catalog page.
*
* @param Request $request
* @param string $action The action to apply on the selected products
*
* @throws Exception if action not properly set or unknown
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function bulkAction(Request $request, $action)
{
if (!$this->actionIsAllowed($action, self::PRODUCT_OBJECT, '_all')) {
$this->addFlash('permission_error', $this->getForbiddenActionMessage($action));
return $this->redirectToRoute('admin_product_catalog');
}
$productIdList = $request->request->get('bulk_action_selected_products');
/** @var ProductInterfaceUpdater $productUpdater */
$productUpdater = $this->get('prestashop.core.admin.data_updater.product_interface');
/** @var LoggerInterface $logger */
$logger = $this->get('logger');
$hookEventParameters = ['product_list_id' => $productIdList];
/** @var HookDispatcher $hookDispatcher */
$hookDispatcher = $this->get('prestashop.core.hook.dispatcher');
try {
$hasMessages = $this->get('session')->getFlashBag()->has('success');
if ($this->isDemoModeEnabled()) {
throw new UpdateProductException($this->getDemoModeErrorMessage());
}
switch ($action) {
case 'activate_all':
$hookDispatcher->dispatchWithParameters(
'actionAdminActivateBefore',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerActivateBefore',
$hookEventParameters
);
// Hooks: managed in ProductUpdater
$productUpdater->activateProductIdList($productIdList);
if (empty($hasMessages)) {
$this->addFlash(
'success',
$this->trans('Product(s) successfully activated.', 'Admin.Catalog.Notification')
);
}
$logger->info('Products activated: (' . implode(',', $productIdList) . ').', $this->getLogDataContext());
$hookDispatcher->dispatchWithParameters(
'actionAdminActivateAfter',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerActivateAfter',
$hookEventParameters
);
break;
case 'deactivate_all':
$hookDispatcher->dispatchWithParameters(
'actionAdminDeactivateBefore',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDeactivateBefore',
$hookEventParameters
);
// Hooks: managed in ProductUpdater
$productUpdater->activateProductIdList($productIdList, false);
if (empty($hasMessages)) {
$this->addFlash(
'success',
$this->trans('Product(s) successfully deactivated.', 'Admin.Catalog.Notification')
);
}
$logger->info('Products deactivated: (' . implode(',', $productIdList) . ').', $this->getLogDataContext());
$hookDispatcher->dispatchWithParameters(
'actionAdminDeactivateAfter',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDeactivateAfter',
$hookEventParameters
);
break;
case 'delete_all':
$hookDispatcher->dispatchWithParameters(
'actionAdminDeleteBefore',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDeleteBefore',
$hookEventParameters
);
// Hooks: managed in ProductUpdater
$productUpdater->deleteProductIdList($productIdList);
if (empty($hasMessages)) {
$this->addFlash(
'success',
$this->trans('Product(s) successfully deleted.', 'Admin.Catalog.Notification')
);
}
$logger->info('Products deleted: (' . implode(',', $productIdList) . ').', $this->getLogDataContext());
$hookDispatcher->dispatchWithParameters(
'actionAdminDeleteAfter',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDeleteAfter',
$hookEventParameters
);
break;
case 'duplicate_all':
$hookDispatcher->dispatchWithParameters(
'actionAdminDuplicateBefore',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDuplicateBefore',
$hookEventParameters
);
// Hooks: managed in ProductUpdater
$productUpdater->duplicateProductIdList($productIdList);
if (empty($hasMessages)) {
$this->addFlash(
'success',
$this->trans('Product(s) successfully duplicated.', 'Admin.Catalog.Notification')
);
}
$logger->info('Products duplicated: (' . implode(',', $productIdList) . ').', $this->getLogDataContext());
$hookDispatcher->dispatchWithParameters(
'actionAdminDuplicateAfter',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDuplicateAfter',
$hookEventParameters
);
break;
default:
/*
* should never happens since the route parameters are
* restricted to a set of action values in YML file.
*/
$logger->error('Bulk action from ProductController received a bad parameter.', $this->getLogDataContext());
throw new Exception('Bad action received from call to ProductController::bulkAction: "' . $action . '"', 2001);
}
} catch (UpdateProductException $due) {
//TODO : need to translate this with an domain name
$message = $due->getMessage();
$this->addFlash('failure', $message);
$logger->warning($message, $this->getLogDataContext());
}
return new Response(json_encode(['result' => 'ok']));
}
/**
* Do mass edit action on the current page of products.
* Used with the 'grouped action' dropdown menu on the Catalog page.
*
* @param Request $request
* @param string $action The action to apply on the selected products
*
* @throws Exception if action not properly set or unknown
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function massEditAction(Request $request, $action)
{
if (!$this->isGranted(PageVoter::UPDATE, self::PRODUCT_OBJECT)) {
$errorMessage = $this->trans(
'You do not have permission to edit this.',
'Admin.Notifications.Error'
);
$this->get('session')->getFlashBag()->add('permission_error', $errorMessage);
return $this->redirectToRoute('admin_product_catalog');
}
/** @var ProductInterfaceProvider $productProvider */
$productProvider = $this->get('prestashop.core.admin.data_provider.product_interface');
/** @var ProductInterfaceUpdater $productUpdater */
$productUpdater = $this->get('prestashop.core.admin.data_updater.product_interface');
/** @var LoggerInterface $logger */
$logger = $this->get('logger');
/* @var HookDispatcher $hookDispatcher */
$hookDispatcher = $this->get('prestashop.core.hook.dispatcher');
/* Initialize router params variable. */
$routerParams = [];
try {
switch ($action) {
case 'sort':
/* Change position_ordering to position */
$routerParams['orderBy'] = 'position';
$productIdList = $request->request->get('mass_edit_action_sorted_products');
$productPositionList = $request->request->get('mass_edit_action_sorted_positions');
$hookEventParameters = [
'product_list_id' => $productIdList,
'product_list_position' => $productPositionList,
];
$hookDispatcher->dispatchWithParameters(
'actionAdminSortBefore',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerSortBefore',
$hookEventParameters
);
// Hooks: managed in ProductUpdater
$persistedFilterParams = $productProvider->getPersistedFilterParameters();
$productList = array_combine($productIdList, $productPositionList);
$productUpdater->sortProductIdList(
$productList,
['filter_category' => $persistedFilterParams['filter_category']]
);
$this->addFlash(
'success',
$this->trans('Products successfully sorted.', 'Admin.Catalog.Notification')
);
$logger->info(
'Products sorted: (' . implode(',', $productIdList) .
') with positions (' . implode(',', $productPositionList) . ').', $this->getLogDataContext()
);
$hookEventParameters = [
'product_list_id' => $productIdList,
'product_list_position' => $productPositionList,
];
$hookDispatcher->dispatchWithParameters(
'actionAdminSortAfter',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerSortAfter',
$hookEventParameters
);
break;
default:
/*
* should never happens since the route parameters are
* restricted to a set of action values in YML file.
*/
$logger->error('Mass edit action from ProductController received a bad parameter.', $this->getLogDataContext());
throw new Exception('Bad action received from call to ProductController::massEditAction: "' . $action . '"', 2001);
}
} catch (UpdateProductException $due) {
//TODO : need to translate with domain name
$message = $due->getMessage();
$this->addFlash('failure', $message);
$logger->warning($message, $this->getLogDataContext());
}
$urlGenerator = $this->get('prestashop.core.admin.url_generator');
return $this->redirect($urlGenerator->generate('admin_product_catalog', $routerParams));
}
/**
* Do action on one product at a time. Can be used at many places in the controller's page.
*
* @param string $action The action to apply on the selected product
* @param int $id the product ID to apply the action on
*
* @throws Exception if action not properly set or unknown
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function unitAction($action, $id)
{
if (!$this->actionIsAllowed($action, self::PRODUCT_OBJECT)) {
$this->addFlash('permission_error', $this->getForbiddenActionMessage($action));
return $this->redirectToRoute('admin_product_catalog');
}
/** @var ProductInterfaceUpdater $productUpdater */
$productUpdater = $this->get('prestashop.core.admin.data_updater.product_interface');
/** @var LoggerInterface $logger */
$logger = $this->get('logger');
$hookEventParameters = ['product_id' => $id];
/** @var HookDispatcher $hookDispatcher */
$hookDispatcher = $this->get('prestashop.core.hook.dispatcher');
try {
if ($this->isDemoModeEnabled()) {
throw new UpdateProductException($this->getDemoModeErrorMessage());
}
switch ($action) {
case 'delete':
$hookDispatcher->dispatchWithParameters(
'actionAdminDeleteBefore',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDeleteBefore',
$hookEventParameters
);
// Hooks: managed in ProductUpdater
$productUpdater->deleteProduct($id);
$this->addFlash(
'success',
$this->trans('Product successfully deleted.', 'Admin.Catalog.Notification')
);
$logger->info('Product deleted: (' . $id . ').', $this->getLogDataContext($id));
$hookDispatcher->dispatchWithParameters(
'actionAdminDeleteAfter',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDeleteAfter',
$hookEventParameters
);
break;
case 'duplicate':
$hookDispatcher->dispatchWithParameters(
'actionAdminDuplicateBefore',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDuplicateBefore',
$hookEventParameters
);
// Hooks: managed in ProductUpdater
$duplicateProductId = $productUpdater->duplicateProduct($id);
$this->addFlash(
'success',
$this->trans('Product successfully duplicated.', 'Admin.Catalog.Notification')
);
$logger->info('Product duplicated: (from ' . $id . ' to ' . $duplicateProductId . ').', $this->getLogDataContext($id));
$hookDispatcher->dispatchWithParameters(
'actionAdminDuplicateAfter',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDuplicateAfter',
$hookEventParameters
);
// stops here and redirect to the new product's page.
return $this->redirectToRoute('admin_product_form', ['id' => $duplicateProductId]);
case 'activate':
$hookDispatcher->dispatchWithParameters(
'actionAdminActivateBefore',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerActivateBefore',
$hookEventParameters
);
// Hooks: managed in ProductUpdater
$productUpdater->activateProductIdList([$id]);
$this->addFlash(
'success',
$this->trans('Product successfully activated.', 'Admin.Catalog.Notification')
);
$logger->info('Product activated: ' . $id, $this->getLogDataContext($id));
$hookDispatcher->dispatchWithParameters(
'actionAdminActivateAfter',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerActivateAfter',
$hookEventParameters
);
break;
case 'deactivate':
$hookDispatcher->dispatchWithParameters(
'actionAdminDeactivateBefore',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDeactivateBefore',
$hookEventParameters
);
// Hooks: managed in ProductUpdater
$productUpdater->activateProductIdList([$id], false);
$this->addFlash(
'success',
$this->trans('Product successfully deactivated.', 'Admin.Catalog.Notification')
);
$logger->info('Product deactivated: ' . $id, $this->getLogDataContext($id));
$hookDispatcher->dispatchWithParameters(
'actionAdminDeactivateAfter',
$hookEventParameters
);
$hookDispatcher->dispatchWithParameters(
'actionAdminProductsControllerDeactivateAfter',
$hookEventParameters
);
break;
default:
/*
* should never happens since the route parameters are
* restricted to a set of action values in YML file.
*/
$logger->error('Unit action from ProductController received a bad parameter.', $this->getLogDataContext($id));
throw new Exception('Bad action received from call to ProductController::unitAction: "' . $action . '"', 2002);
}
} catch (UpdateProductException $due) {
//TODO : need to translate with a domain name
$message = $due->getMessage();
$this->addFlash('failure', $message);
$logger->warning($message, $this->getLogDataContext($id));
}
return $this->redirect($this->get('prestashop.core.admin.url_generator')->generate('admin_product_catalog'));
}
/**
* Toggle product status
*
* @AdminSecurity(
* "is_granted('update', request.get('_legacy_controller'))",
* message="You do not have permission to update this."
* )
*
* @param int $productId
*
* @return JsonResponse
*/
public function toggleStatusAction(Request $request, $productId)
{
if ($this->isDemoModeEnabled()) {
return $this->json([
'status' => false,
'message' => $this->getDemoModeErrorMessage(),
]);
}
$shopConstraint = $request->attributes->get('shopConstraint');
/** @var ProductForEditing $productForEditing */
$productForEditing = $this->getQueryBus()->handle(new GetProductForEditing(
$productId,
$shopConstraint,
$this->getContextLangId()
));
try {
$command = new UpdateProductCommand($productId, $request->attributes->get('shopConstraint'));
$command->setActive(!$productForEditing->isActive());
$this->getCommandBus()->handle($command);
$response = [
'status' => true,
'message' => $this->trans('The status has been successfully updated.', 'Admin.Notifications.Success'),
];
} catch (ProductException $e) {
$response = [
'status' => false,
'message' => $this->getErrorMessageForException($e, $this->getErrorMessages()),
];
}
return $this->json($response);
}
/**
* @AdminSecurity("is_granted('create', request.get('_legacy_controller')) || is_granted('update', request.get('_legacy_controller')) || is_granted('read', request.get('_legacy_controller'))")
*
* @return CsvResponse
*
* @throws \Symfony\Component\Translation\Exception\InvalidArgumentException
*/
public function exportAction()
{
return $this->get(ProductCsvExporter::class)->export();
}
/**
* Set the Catalog filters values and redirect to the catalogAction.
*
* URL example: /product/catalog_filters/42/last/32
*
* @AdminSecurity("is_granted('create', request.get('_legacy_controller')) || is_granted('update', request.get('_legacy_controller')) || is_granted('read', request.get('_legacy_controller'))")
*
* @param int|string $quantity the quantity to set on the catalog filters persistence
* @param string $active the activation state to set on the catalog filters persistence
*
* @return RedirectResponse
*/
public function catalogFiltersAction($quantity = 'none', $active = 'none')
{
$quantity = urldecode($quantity);
/** @var ProductInterfaceProvider $productProvider */
$productProvider = $this->get('prestashop.core.admin.data_provider.product_interface');
// we merge empty filter set with given values, to reset the other filters!
$productProvider->persistFilterParameters(
array_merge(
AdminFilter::getProductCatalogEmptyFilter(),
[
'filter_column_sav_quantity' => ($quantity == 'none') ? '' : $quantity,
'filter_column_active' => ($active == 'none') ? '' : $active,
]
)
);
return $this->redirectToRoute('admin_product_catalog');
}
/**
* @deprecated since 1.7.5.0, to be removed in 1.8 rely on CommonController::renderFieldAction
*
* @throws \OutOfBoundsException
* @throws LogicException
* @throws \PrestaShopException
*/
public function renderFieldAction($productId, $step, $fieldName)
{
@trigger_error(
'This function is deprecated, use CommonController::renderFieldAction instead.',
E_USER_DEPRECATED
);
$productAdapter = $this->get('prestashop.adapter.data_provider.product');
$product = $productAdapter->getProduct($productId);
$modelMapper = new AdminModelAdapter(
$product,
$this->get('prestashop.adapter.legacy.context'),
$this->get(AdminProductWrapper::class),
$this->get(Tools::class),
$productAdapter,
$this->get('prestashop.adapter.data_provider.supplier'),
$this->get('prestashop.adapter.data_provider.warehouse'),
$this->get('prestashop.adapter.data_provider.feature'),
$this->get('prestashop.adapter.data_provider.pack'),
$this->get('prestashop.adapter.shop.context'),
$this->get('prestashop.adapter.data_provider.tax'),
$this->get('prestashop.adapter.legacy.configuration'),
$this->get('router')
);
$form = $this->createFormBuilder($modelMapper->getFormData($product));
switch ($step) {
case 'step1':
$form->add('step1', 'PrestaShopBundle\Form\Admin\Product\ProductInformation');
break;
case 'step2':
$form->add('step2', 'PrestaShopBundle\Form\Admin\Product\ProductPrice');
break;
case 'step3':
$form->add('step3', 'PrestaShopBundle\Form\Admin\Product\ProductQuantity');
break;
case 'step4':
$form->add('step4', 'PrestaShopBundle\Form\Admin\Product\ProductShipping');
break;
case 'step5':
$form->add('step5', 'PrestaShopBundle\Form\Admin\Product\ProductSeo');
break;
case 'step6':
$form->add('step6', 'PrestaShopBundle\Form\Admin\Product\ProductOptions');
break;
case 'default':
}
return $this->render('@PrestaShop/Admin/Common/_partials/_form_field.html.twig', [
'form' => $form->getForm()->get($step)->get($fieldName)->createView(),
'formId' => $step . '_' . $fieldName . '_rendered',
]);
}
/**
* @return array
*/
private function getErrorMessages(): array
{
return [
ProductNotFoundException::class => $this->trans('The object cannot be loaded (or found).', 'Admin.Notifications.Error'),
CannotUpdateProductException::class => $this->trans('An error occurred while updating the status for an object.', 'Admin.Notifications.Error'),
ProductConstraintException::class => [
ProductConstraintException::INVALID_ONLINE_DATA => $this->trans(
'To put this product online, please enter a name.',
'Admin.Catalog.Notification'
),
],
];
}
/**
* @return array
*/
private function getLogDataContext($id_product = null, $error_code = null, $allow_duplicate = null): array
{
return [
'object_type' => 'Product',
'object_id' => $id_product,
'error_code' => $error_code,
'allow_duplicate' => $allow_duplicate,
];
}
/**
* @return bool
*/
private function shouldRedirectToV2(): bool
{
return $this->get(FeatureFlagRepository::class)->isEnabled(FeatureFlagSettings::FEATURE_FLAG_PRODUCT_PAGE_V2);
}
}
xxxxx1.0, XXX xxxx