1<?php 2/** 3 * Copyright since 2007 PrestaShop SA and Contributors 4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA 5 * 6 * NOTICE OF LICENSE 7 * 8 * This source file is subject to the Open Software License (OSL 3.0) 9 * that is bundled with this package in the file LICENSE.md. 10 * It is also available through the world-wide-web at this URL: 11 * https://opensource.org/licenses/OSL-3.0 12 * If you did not receive a copy of the license and are unable to 13 * obtain it through the world-wide-web, please send an email 14 * to license@prestashop.com so we can send you a copy immediately. 15 * 16 * DISCLAIMER 17 * 18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 19 * versions in the future. If you wish to customize PrestaShop for your 20 * needs please refer to https://devdocs.prestashop.com/ for more information. 21 * 22 * @author PrestaShop SA and Contributors <contact@prestashop.com> 23 * @copyright Since 2007 PrestaShop SA and Contributors 24 * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) 25 */ 26 27declare(strict_types=1); 28 29namespace PrestaShop\PrestaShop\Adapter\Product\QueryHandler; 30 31use Customization; 32use DateTime; 33use PrestaShop\PrestaShop\Adapter\Product\Repository\ProductRepository; 34use PrestaShop\PrestaShop\Adapter\Product\Stock\Repository\StockAvailableRepository; 35use PrestaShop\PrestaShop\Adapter\Product\VirtualProduct\Repository\VirtualProductFileRepository; 36use PrestaShop\PrestaShop\Adapter\Tax\TaxComputer; 37use PrestaShop\PrestaShop\Core\Domain\Country\ValueObject\CountryId; 38use PrestaShop\PrestaShop\Core\Domain\Product\ProductCustomizabilitySettings; 39use PrestaShop\PrestaShop\Core\Domain\Product\Query\GetProductForEditing; 40use PrestaShop\PrestaShop\Core\Domain\Product\QueryHandler\GetProductForEditingHandlerInterface; 41use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\LocalizedTags; 42use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductBasicInformation; 43use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductCategoriesInformation; 44use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductCustomizationOptions; 45use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductDetails; 46use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductForEditing; 47use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductOptions; 48use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductPricesInformation; 49use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductSeoOptions; 50use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductShippingInformation; 51use PrestaShop\PrestaShop\Core\Domain\Product\QueryResult\ProductStockInformation; 52use PrestaShop\PrestaShop\Core\Domain\Product\ValueObject\ProductId; 53use PrestaShop\PrestaShop\Core\Domain\Product\VirtualProductFile\Exception\VirtualProductFileNotFoundException; 54use PrestaShop\PrestaShop\Core\Domain\Product\VirtualProductFile\QueryResult\VirtualProductFileForEditing; 55use PrestaShop\PrestaShop\Core\Domain\TaxRulesGroup\ValueObject\TaxRulesGroupId; 56use PrestaShop\PrestaShop\Core\Util\DateTime\DateTime as DateTimeUtil; 57use PrestaShop\PrestaShop\Core\Util\Number\NumberExtractor; 58use PrestaShop\PrestaShop\Core\Util\Number\NumberExtractorException; 59use Product; 60use Tag; 61 62/** 63 * Handles the query GetEditableProduct using legacy ObjectModel 64 */ 65final class GetProductForEditingHandler implements GetProductForEditingHandlerInterface 66{ 67 /** 68 * @var NumberExtractor 69 */ 70 private $numberExtractor; 71 72 /** 73 * @var ProductRepository 74 */ 75 private $productRepository; 76 77 /** 78 * @var StockAvailableRepository 79 */ 80 private $stockAvailableRepository; 81 82 /** 83 * @var VirtualProductFileRepository 84 */ 85 private $virtualProductFileRepository; 86 87 /** 88 * @var TaxComputer 89 */ 90 private $taxComputer; 91 92 /** 93 * @var int 94 */ 95 private $countryId; 96 97 /** 98 * @param NumberExtractor $numberExtractor 99 * @param ProductRepository $productRepository 100 * @param StockAvailableRepository $stockAvailableRepository 101 * @param VirtualProductFileRepository $virtualProductFileRepository 102 * @param TaxComputer $taxComputer 103 * @param int $countryId 104 */ 105 public function __construct( 106 NumberExtractor $numberExtractor, 107 ProductRepository $productRepository, 108 StockAvailableRepository $stockAvailableRepository, 109 VirtualProductFileRepository $virtualProductFileRepository, 110 TaxComputer $taxComputer, 111 int $countryId 112 ) { 113 $this->numberExtractor = $numberExtractor; 114 $this->stockAvailableRepository = $stockAvailableRepository; 115 $this->virtualProductFileRepository = $virtualProductFileRepository; 116 $this->taxComputer = $taxComputer; 117 $this->countryId = $countryId; 118 $this->productRepository = $productRepository; 119 } 120 121 /** 122 * {@inheritdoc} 123 */ 124 public function handle(GetProductForEditing $query): ProductForEditing 125 { 126 $product = $this->productRepository->get($query->getProductId()); 127 128 return new ProductForEditing( 129 (int) $product->id, 130 $product->getProductType(), 131 $this->getCustomizationOptions($product), 132 $this->getBasicInformation($product), 133 $this->getCategoriesInformation($product), 134 $this->getPricesInformation($product), 135 $this->getOptions($product), 136 $this->getDetails($product), 137 $this->getShippingInformation($product), 138 $this->getSeoOptions($product), 139 $product->getAssociatedAttachmentIds(), 140 $this->getProductStockInformation($product), 141 $this->getVirtualProductFile($product) 142 ); 143 } 144 145 /** 146 * @param Product $product 147 * 148 * @return ProductBasicInformation 149 */ 150 private function getBasicInformation(Product $product): ProductBasicInformation 151 { 152 return new ProductBasicInformation( 153 $product->name, 154 $product->description, 155 $product->description_short, 156 $this->getLocalizedTagsList((int) $product->id) 157 ); 158 } 159 160 /** 161 * @param Product $product 162 * 163 * @return ProductCategoriesInformation 164 */ 165 private function getCategoriesInformation(Product $product): ProductCategoriesInformation 166 { 167 $categoryIds = array_map('intval', $product->getCategories()); 168 $defaultCategoryId = (int) $product->id_category_default; 169 170 return new ProductCategoriesInformation($categoryIds, $defaultCategoryId); 171 } 172 173 /** 174 * @param Product $product 175 * 176 * @return ProductPricesInformation 177 */ 178 private function getPricesInformation(Product $product): ProductPricesInformation 179 { 180 $priceTaxExcluded = $this->numberExtractor->extract($product, 'price'); 181 $priceTaxIncluded = $this->taxComputer->computePriceWithTaxes( 182 $priceTaxExcluded, 183 new TaxRulesGroupId((int) $product->id_tax_rules_group), 184 new CountryId($this->countryId) 185 ); 186 187 return new ProductPricesInformation( 188 $priceTaxExcluded, 189 $priceTaxIncluded, 190 $this->numberExtractor->extract($product, 'ecotax'), 191 (int) $product->id_tax_rules_group, 192 (bool) $product->on_sale, 193 $this->numberExtractor->extract($product, 'wholesale_price'), 194 $this->numberExtractor->extract($product, 'unit_price'), 195 (string) $product->unity, 196 $this->numberExtractor->extract($product, 'unit_price_ratio') 197 ); 198 } 199 200 /** 201 * @param Product $product 202 * 203 * @return ProductOptions 204 */ 205 private function getOptions(Product $product): ProductOptions 206 { 207 return new ProductOptions( 208 (bool) $product->active, 209 $product->visibility, 210 (bool) $product->available_for_order, 211 (bool) $product->online_only, 212 (bool) $product->show_price, 213 $product->condition, 214 (bool) $product->show_condition, 215 (int) $product->id_manufacturer 216 ); 217 } 218 219 /** 220 * @param Product $product 221 * 222 * @return ProductDetails 223 */ 224 private function getDetails(Product $product): ProductDetails 225 { 226 return new ProductDetails( 227 $product->isbn, 228 $product->upc, 229 $product->ean13, 230 $product->mpn, 231 $product->reference 232 ); 233 } 234 235 /** 236 * @param Product $product 237 * 238 * @return ProductShippingInformation 239 * 240 * @throws NumberExtractorException 241 */ 242 private function getShippingInformation(Product $product): ProductShippingInformation 243 { 244 $carrierReferences = array_map(function ($carrier): int { 245 return (int) $carrier['id_reference']; 246 }, $product->getCarriers()); 247 248 return new ProductShippingInformation( 249 $this->numberExtractor->extract($product, 'width'), 250 $this->numberExtractor->extract($product, 'height'), 251 $this->numberExtractor->extract($product, 'depth'), 252 $this->numberExtractor->extract($product, 'weight'), 253 $this->numberExtractor->extract($product, 'additional_shipping_cost'), 254 $carrierReferences, 255 (int) $product->additional_delivery_times, 256 $product->delivery_in_stock, 257 $product->delivery_out_stock 258 ); 259 } 260 261 /** 262 * @param int $productId 263 * 264 * @return LocalizedTags[] 265 */ 266 private function getLocalizedTagsList(int $productId): array 267 { 268 $tags = Tag::getProductTags($productId); 269 270 if (!$tags) { 271 return []; 272 } 273 274 $localizedTagsList = []; 275 276 foreach ($tags as $langId => $localizedTags) { 277 $localizedTagsList[] = new LocalizedTags((int) $langId, $localizedTags); 278 } 279 280 return $localizedTagsList; 281 } 282 283 /** 284 * @param Product $product 285 * 286 * @return ProductCustomizationOptions 287 */ 288 private function getCustomizationOptions(Product $product): ProductCustomizationOptions 289 { 290 if (!Customization::isFeatureActive()) { 291 return ProductCustomizationOptions::createNotCustomizable(); 292 } 293 294 $textFieldsCount = (int) $product->text_fields; 295 $fileFieldsCount = (int) $product->uploadable_files; 296 297 switch ((int) $product->customizable) { 298 case ProductCustomizabilitySettings::ALLOWS_CUSTOMIZATION: 299 $options = ProductCustomizationOptions::createAllowsCustomization($textFieldsCount, $fileFieldsCount); 300 break; 301 case ProductCustomizabilitySettings::REQUIRES_CUSTOMIZATION: 302 $options = ProductCustomizationOptions::createRequiresCustomization($textFieldsCount, $fileFieldsCount); 303 break; 304 default: 305 $options = ProductCustomizationOptions::createNotCustomizable(); 306 } 307 308 return $options; 309 } 310 311 /** 312 * @param Product $product 313 * 314 * @return ProductSeoOptions 315 */ 316 private function getSeoOptions(Product $product): ProductSeoOptions 317 { 318 return new ProductSeoOptions( 319 $product->meta_title, 320 $product->meta_description, 321 $product->link_rewrite, 322 $product->redirect_type, 323 (int) $product->id_type_redirected 324 ); 325 } 326 327 /** 328 * Returns the product stock infos, it's important that the Product is fetched with stock data 329 * 330 * @param Product $product 331 * 332 * @return ProductStockInformation 333 */ 334 private function getProductStockInformation(Product $product): ProductStockInformation 335 { 336 //@todo: In theory StockAvailable is created for each product when Product::add is called, 337 // but we should explore some multishop edgecases 338 // (like shop ids might be missing and foreach loop won't start resulting in a missing StockAvailable for product) 339 $stockAvailable = $this->stockAvailableRepository->getForProduct(new ProductId($product->id)); 340 341 return new ProductStockInformation( 342 (int) $product->pack_stock_type, 343 (int) $stockAvailable->out_of_stock, 344 (int) $stockAvailable->quantity, 345 (int) $product->minimal_quantity, 346 (int) $product->low_stock_threshold, 347 (bool) $product->low_stock_alert, 348 $product->available_now, 349 $product->available_later, 350 $stockAvailable->location, 351 DateTimeUtil::NULL_DATE === $product->available_date ? null : new DateTime($product->available_date) 352 ); 353 } 354 355 /** 356 * Get virtual product file 357 * Legacy object ProductDownload is referred as VirtualProductFile in Core 358 * 359 * @param Product $product 360 * 361 * @return VirtualProductFileForEditing|null 362 */ 363 private function getVirtualProductFile(Product $product): ?VirtualProductFileForEditing 364 { 365 try { 366 $virtualProductFile = $this->virtualProductFileRepository->findByProductId(new ProductId($product->id)); 367 } catch (VirtualProductFileNotFoundException $e) { 368 return null; 369 } 370 371 return new VirtualProductFileForEditing( 372 (int) $virtualProductFile->id, 373 $virtualProductFile->filename, 374 $virtualProductFile->display_filename, 375 (int) $virtualProductFile->nb_days_accessible, 376 (int) $virtualProductFile->nb_downloadable, 377 $virtualProductFile->date_expiration === DateTimeUtil::NULL_DATETIME ? null : new DateTime($virtualProductFile->date_expiration) 378 ); 379 } 380} 381