From d6b3101685621387d73901a2b83165e7bde4a7c3 Mon Sep 17 00:00:00 2001 From: natalia Date: Mon, 28 Mar 2022 16:49:26 +0200 Subject: [PATCH] Import products, Update product page --- .../Movie/Console/Command/MovieImport.php | 49 +++ app/code/Cadence/Movie/Helper/Config.php | 8 - app/code/Cadence/Movie/Model/Config.php | 54 ++++ .../Cadence/Movie/Service/ImageImport.php | 53 +++ .../Cadence/Movie/Service/MovieImport.php | 305 ++++++++++++++++++ .../Setup/Patch/Data/UpdateApiConfig.php | 58 ++++ .../Movie/ViewModel/MovieProductData.php | 104 ++++++ app/code/Cadence/Movie/etc/acl.xml | 16 + .../Cadence/Movie/etc/adminhtml/system.xml | 23 ++ app/code/Cadence/Movie/etc/di.xml | 10 + .../frontend/layout/catalog_product_view.xml | 30 ++ .../templates/pdp/html/movie_details.phtml | 23 ++ .../templates/pdp/html/movie_rating.phtml | 17 + .../templates/pdp/html/movie_year.phtml | 3 + .../view/frontend/web/css/source/_module.less | 95 ++++++ 15 files changed, 840 insertions(+), 8 deletions(-) create mode 100644 app/code/Cadence/Movie/Console/Command/MovieImport.php delete mode 100644 app/code/Cadence/Movie/Helper/Config.php create mode 100644 app/code/Cadence/Movie/Model/Config.php create mode 100644 app/code/Cadence/Movie/Service/ImageImport.php create mode 100644 app/code/Cadence/Movie/Service/MovieImport.php create mode 100644 app/code/Cadence/Movie/Setup/Patch/Data/UpdateApiConfig.php create mode 100644 app/code/Cadence/Movie/ViewModel/MovieProductData.php create mode 100644 app/code/Cadence/Movie/etc/acl.xml create mode 100644 app/code/Cadence/Movie/etc/adminhtml/system.xml create mode 100644 app/code/Cadence/Movie/etc/di.xml create mode 100644 app/code/Cadence/Movie/view/frontend/layout/catalog_product_view.xml create mode 100644 app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_details.phtml create mode 100644 app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_rating.phtml create mode 100644 app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_year.phtml create mode 100644 app/code/Cadence/Movie/view/frontend/web/css/source/_module.less diff --git a/app/code/Cadence/Movie/Console/Command/MovieImport.php b/app/code/Cadence/Movie/Console/Command/MovieImport.php new file mode 100644 index 0000000..3398554 --- /dev/null +++ b/app/code/Cadence/Movie/Console/Command/MovieImport.php @@ -0,0 +1,49 @@ +movieImportService = $movieImportService; + $this->state = $state; + } + + protected function configure() + { + $this->setName('cadence:movie:import'); + $this->setDescription('Movie import'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln("Start Products Import."); + $this->state->setAreaCode(Area::AREA_ADMINHTML); + try { + $this->movieImportService->execute(); + $output->writeln("Finish products Import."); + return \Magento\Framework\Console\Cli::RETURN_SUCCESS; + } catch (\Exception $exception) { + $output->writeln("{$exception->getMessage()}"); + return \Magento\Framework\Console\Cli::RETURN_FAILURE; + } + } +} diff --git a/app/code/Cadence/Movie/Helper/Config.php b/app/code/Cadence/Movie/Helper/Config.php deleted file mode 100644 index cac10b4..0000000 --- a/app/code/Cadence/Movie/Helper/Config.php +++ /dev/null @@ -1,8 +0,0 @@ -scopeConfig = $scopeConfig; + } + + public function getTmdbKey(): string + { + return $this->scopeConfig->getValue( + self::XML_PATH_TMDB_API_KEY, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } + + public function getTmdbRequestUri(): string + { + return $this->scopeConfig->getValue( + self::XML_PATH_TMDB_API_REQUEST_URI, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } + + public function getTmdbImageBaseUri(): string + { + return $this->scopeConfig->getValue( + self::XML_PATH_TMDB_API_IMAGE_BASE_URI, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } +} diff --git a/app/code/Cadence/Movie/Service/ImageImport.php b/app/code/Cadence/Movie/Service/ImageImport.php new file mode 100644 index 0000000..f892577 --- /dev/null +++ b/app/code/Cadence/Movie/Service/ImageImport.php @@ -0,0 +1,53 @@ +directoryList = $directoryList; + $this->file = $file; + $this->cadenceConfig = $cadenceConfig; + } + + public function execute(\Magento\Catalog\Model\Product $product, string $filePath) + { + $tmpDir = $this->directoryList->getPath(DirectoryList::MEDIA) . DIRECTORY_SEPARATOR . 'tmp'; + $this->file->checkAndCreateFolder($tmpDir); + $imageUrl = $this->cadenceConfig->getTmdbImageBaseUri() . self::MOVIE_IMAGE_FILE_SIZE . $filePath; + $newFileName = $tmpDir . baseName($imageUrl); + $result = $this->file->read($imageUrl, $newFileName); + if ($result) { + $product->addImageToMediaGallery($newFileName, ['image', 'small_image', 'thumbnail'], false, false); + } + return $result; + } +} diff --git a/app/code/Cadence/Movie/Service/MovieImport.php b/app/code/Cadence/Movie/Service/MovieImport.php new file mode 100644 index 0000000..cc8cefa --- /dev/null +++ b/app/code/Cadence/Movie/Service/MovieImport.php @@ -0,0 +1,305 @@ +clientFactory = $clientFactory; + $this->responseFactory = $responseFactory; + $this->productFactory = $productFactory; + $this->cadenceConfig = $cadenceConfig; + $this->storeManagerInterface = $storeManagerInterface; + $this->productRepositoryInterface = $productRepositoryInterface; + $this->imageImport = $imageImport; + $this->categoryCollectionFactory = $categoryCollectionFactory; + $this->attributeCollectionFactory = $attributeCollectionFactory; + } + + public function execute(): void + { + $response = $this->doRequest( + static::API_REQUEST_ENDPOINT, + [ + 'query' => [ + 'api_key' => $this->cadenceConfig->getTmdbKey(), 'language' => 'en-US', 'page' => 1 + ] + ] + + ); + + $this->createMovieProducts($this->processResponse($response)); + } + + public function doRequest( + string $uriEndpoint, + array $params = [], + string $requestMethod = Request::HTTP_METHOD_GET + ): Response { + $client = $this->clientFactory->create(['config' => [ + 'base_uri' => $this->cadenceConfig->getTmdbRequestUri() + ]]); + + try { + $response = $client->request( + $requestMethod, + $uriEndpoint, + $params + ); + } catch (GuzzleException $exception) { + /** @var Response $response */ + $response = $this->responseFactory->create([ + 'status' => $exception->getCode(), + 'reason' => $exception->getMessage() + ]); + } + + return $response; + } + + public function processResponse(ResponseInterface $response): array + { + $responseBody = $response->getBody(); + $responseContent = $responseBody->getContents(); + return json_decode($responseContent, true); + } + + public function createMovieProducts(array $data) + { + $productsData = $data['results']; + + $websiteId = $this->storeManagerInterface->getStore()->getWebsiteId(); + + foreach ($productsData as $productData) { + $product = $this->productFactory->create(); + + $productData = [ + 'type_id' => Type::TYPE_VIRTUAL, + 'attribute_set_id' => $this->getAttributeSetId(), + 'sku' => (string) $productData['id'], + 'website_ids' => [$websiteId], + 'name' => $productData['title'], + 'description' => $productData['overview'], + 'price' => Config::MOVIE_PRODUCT_PRICE, + 'stock_data' => [ + 'use_config_manage_stock' => 1, + 'qty' => Config::MOVIE_PRODUCT_QTY, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1 + ], + 'category_ids' => [$this->getCategoryId()], + 'visibility' => Visibility::VISIBILITY_BOTH, + 'status' => Status::STATUS_ENABLED, + ]; + + $product->setData($productData); + + $this->addMovieDetails($product); + $this->addMovieCredits($product); + $this->assignImages($product); + + $this->productRepositoryInterface->save($product); + } + } + + public function addMovieDetails(\Magento\Catalog\Model\Product $product) + { + $response = $this->doRequest( + static::API_REQUEST_ENDPOINT_MOVIE_INFO . $product->getSku(), + [ + 'query' => [ + 'api_key' => $this->cadenceConfig->getTmdbKey(), 'language' => 'en-US' + ] + ] + + ); + + $data = $this->processResponse($response); + + if (empty($data)) { + return; + } + + $genres = array_map(function($genre) { return $genre['name']; }, $data['genres']); + $dateData = explode("-", $data['release_date']); + $year = trim($dateData[0]); + $product->setCustomAttribute('genre', implode(', ', $genres)); + $product->setCustomAttribute('year', $year); + $product->setCustomAttribute('vote_average', $data['vote_average']); + + } + + public function addMovieCredits(\Magento\Catalog\Model\Product $product) + { + $response = $this->doRequest( + static::API_REQUEST_ENDPOINT_MOVIE_INFO . $product->getSku() . '/credits', + [ + 'query' => [ + 'api_key' => $this->cadenceConfig->getTmdbKey(), 'language' => 'en-US' + ] + ] + + ); + + $data = $this->processResponse($response); + + $cast = []; + foreach ($data['cast'] as $castItem) { + array_push($cast, $castItem['name']); + } + $crewProducers = []; + $crewDirectors = []; + foreach ($data['crew'] as $crewItem) { + if (strpos('Producer', $crewItem['job']) !== false) { + array_push($crewProducers, $crewItem['name']); + } + if (strpos('Director', $crewItem['job']) !== false) { + array_push($crewDirectors, $crewItem['name']); + } + } + + $product->setCustomAttribute('actors', implode(', ', $cast)); + $product->setCustomAttribute('producer', implode(', ', $crewProducers)); + $product->setCustomAttribute('director', implode(', ', $crewDirectors)); + } + + public function assignImages(\Magento\Catalog\Model\Product $product) + { + $response = $this->doRequest( + static::API_REQUEST_ENDPOINT_MOVIE_INFO . $product->getSku() . '/images', + [ + 'query' => [ + 'api_key' => $this->cadenceConfig->getTmdbKey() + ] + ] + + ); + + $data = $this->processResponse($response); + + if (!empty($data['posters'])) { + $i = 0; + foreach ($data['posters'] as $poster) { + if ($i++ >= self::MOVIE_IMAGE_MAX_ITEMS) { + break; + } + if ($poster['file_path'] !== '') { + $this->imageImport->execute($product, $poster['file_path']); + } + } + } + } + + public function getCategoryId() + { + $collection = $this->categoryCollectionFactory + ->create() + ->addAttributeToFilter('name', Config::MOVIE_CATEGORY_NAME) + ->setPageSize(1); + + if ($collection->getSize()) { + return $collection->getFirstItem()->getId(); + } + + return null; + } + + public function getAttributeSetId() + { + $collection = $this->attributeCollectionFactory + ->create() + ->addFieldToFilter('attribute_set_name', Config::MOVIE_ATTRIBUTE_SET_NAME) + ->setPageSize(1); + + if ($collection->getSize()) { + return $collection->getFirstItem()->getId(); + } + + return null; + } +} diff --git a/app/code/Cadence/Movie/Setup/Patch/Data/UpdateApiConfig.php b/app/code/Cadence/Movie/Setup/Patch/Data/UpdateApiConfig.php new file mode 100644 index 0000000..b205c7d --- /dev/null +++ b/app/code/Cadence/Movie/Setup/Patch/Data/UpdateApiConfig.php @@ -0,0 +1,58 @@ +moduleDataSetup = $moduleDataSetup; + $this->resourceConfig = $resourceConfig; + } + + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + + $this->resourceConfig->saveConfig( + 'tmdb/api/request_uri', + 'https://api.themoviedb.org/3/', + \Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + 0 + ); + + $this->resourceConfig->saveConfig( + 'tmdb/api/image_base_uri', + 'http://image.tmdb.org/t/p/', + \Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + 0 + ); + + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * {@inheritdoc} + */ + public static function getDependencies() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Cadence/Movie/ViewModel/MovieProductData.php b/app/code/Cadence/Movie/ViewModel/MovieProductData.php new file mode 100644 index 0000000..4047c73 --- /dev/null +++ b/app/code/Cadence/Movie/ViewModel/MovieProductData.php @@ -0,0 +1,104 @@ +productRepository = $productRepository; + $this->productHelper = $productHelper; + } + + public function getMovieYear() + { + $currentProduct = $this->getCurrentProduct(); + return $currentProduct->getYear(); + } + + public function getMovieGanre() + { + $currentProduct = $this->getCurrentProduct(); + return $currentProduct->getGenre(); + } + + public function getMovieRating() + { + $currentProduct = $this->getCurrentProduct(); + return $currentProduct->getVoteAverage(); + } + + public function getMovieFullRating() + { + $currentProduct = $this->getCurrentProduct(); + return (int)$currentProduct->getVoteAverage(); + } + + public function getMoviePartRating() + { + $ratingFull = (float)$this->getMovieRating(); + $ratingFullPart = $this->getMovieFullRating(); + return ($ratingFull - $ratingFullPart) * 100; + } + + public function getActors() + { + $currentProduct = $this->getCurrentProduct(); + return $currentProduct->getActors(); + } + + public function getDescription() + { + $currentProduct = $this->getCurrentProduct(); + return $currentProduct->getDescription(); + } + + public function getDirector() + { + $currentProduct = $this->getCurrentProduct(); + return $currentProduct->getDirector(); + } + + public function getProducer() + { + $currentProduct = $this->getCurrentProduct(); + return $currentProduct->getProducer(); + } + + public function getMovieCast() + { + $currentProduct = $this->getCurrentProduct(); + return $currentProduct->getActors(); + } + + public function getCurrentProduct() + { + if (!$this->currentProduct) { + $this->currentProduct = $this->productHelper->getProduct(); + if ($this->currentProduct === null) { + throw new NoSuchEntityException(); + } + } + + return $this->currentProduct; + } +} diff --git a/app/code/Cadence/Movie/etc/acl.xml b/app/code/Cadence/Movie/etc/acl.xml new file mode 100644 index 0000000..ba272b7 --- /dev/null +++ b/app/code/Cadence/Movie/etc/acl.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/code/Cadence/Movie/etc/adminhtml/system.xml b/app/code/Cadence/Movie/etc/adminhtml/system.xml new file mode 100644 index 0000000..6b56634 --- /dev/null +++ b/app/code/Cadence/Movie/etc/adminhtml/system.xml @@ -0,0 +1,23 @@ + + + + +
+ + service + Cadence_Movie::config_tmdb + + + + + + + + + + + + +
+
+
diff --git a/app/code/Cadence/Movie/etc/di.xml b/app/code/Cadence/Movie/etc/di.xml new file mode 100644 index 0000000..33bb15d --- /dev/null +++ b/app/code/Cadence/Movie/etc/di.xml @@ -0,0 +1,10 @@ + + + + + + Cadence\Movie\Console\Command\MovieImport + + + + diff --git a/app/code/Cadence/Movie/view/frontend/layout/catalog_product_view.xml b/app/code/Cadence/Movie/view/frontend/layout/catalog_product_view.xml new file mode 100644 index 0000000..ab9fa7b --- /dev/null +++ b/app/code/Cadence/Movie/view/frontend/layout/catalog_product_view.xml @@ -0,0 +1,30 @@ + + + + + + + Cadence\Movie\ViewModel\MovieProductData + + + + + + + + Cadence\Movie\ViewModel\MovieProductData + + + + + Cadence\Movie\ViewModel\MovieProductData + + + + + + + + + + diff --git a/app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_details.phtml b/app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_details.phtml new file mode 100644 index 0000000..ef7799e --- /dev/null +++ b/app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_details.phtml @@ -0,0 +1,23 @@ + +getViewModel(); ?> +
+

getDescription(); ?>

+
+
+

+ + getDirector(); ?> +

+
+
+

+ + getProducer(); ?> +

+
+
+

+ + getMovieCast(); ?> +

+
diff --git a/app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_rating.phtml b/app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_rating.phtml new file mode 100644 index 0000000..278e4e1 --- /dev/null +++ b/app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_rating.phtml @@ -0,0 +1,17 @@ + +getViewModel(); ?> +
+

getMovieGanre(); ?>

+
+
+ getMovieRating()): ?> +
+ getMovieFullRating(); $i++): ?> + + + getMoviePartRating()): ?> + + +
+ +
diff --git a/app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_year.phtml b/app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_year.phtml new file mode 100644 index 0000000..363b901 --- /dev/null +++ b/app/code/Cadence/Movie/view/frontend/templates/pdp/html/movie_year.phtml @@ -0,0 +1,3 @@ + +getViewModel(); ?> +(getMovieYear(); ?>) diff --git a/app/code/Cadence/Movie/view/frontend/web/css/source/_module.less b/app/code/Cadence/Movie/view/frontend/web/css/source/_module.less new file mode 100644 index 0000000..d7b36bd --- /dev/null +++ b/app/code/Cadence/Movie/view/frontend/web/css/source/_module.less @@ -0,0 +1,95 @@ +& when (@media-common = true) { + .product-info-main { + .product-add-form .qty { + display: none; + } + + .page-title-wrapper { + display: flex; + align-items: center; + flex-wrap: wrap; + + h1 { + margin-right: 5px; + } + + h1, h1 + span { + font-weight: 700; + } + } + + .full-star:before { + content: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 25.6 24.3' xmlns='http://www.w3.org/2000/svg' fill='%23FBDA13'%3E%3Cpath clip-rule='evenodd' d='m12.8 20-7.4 4.3c-.1 0-.2.1-.3 0-.2 0-.4-.3-.3-.5l1.8-8.4-6.5-5.7c0-.1-.1-.2-.1-.3 0-.2.1-.4.4-.4l8.6-.9 3.5-7.9c0-.1.1-.2.2-.2.2-.1.4 0 .5.2l3.5 7.9 8.5.9c.1 0 .2.1.3.1.1.2.1.4 0 .6l-6.5 5.7 1.8 8.4v.3c-.1.2-.4.3-.5.1z' fill='%23fbda13' fill-rule='evenodd'/%3E%3C/svg%3E") !important; + display: inline-block; + vertical-align: middle; + font-size: 0; + line-height: 1; + width: 16px; + height: 16px; + } + + .half-star:before { + content: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 25.7 24.3' xmlns='http://www.w3.org/2000/svg' fill='%23C6C6C6'%3E%3Cpath d='m12.8 0c-.1 0-.1 0-.2.1.1-.1.2-.1.2-.1zm0 20v-20h.1c.1 0 .2.1.2.2l3.5 7.9 8.7.9c.3 0 .4.2.4.4 0 .1-.1.2-.2.2l-6.4 5.7 1.8 8.4c.1.2-.1.5-.3.5-.1.1-.2 0-.3 0z' fill='%23c6c6c6'/%3E%3Cpath d='m.1 9.7c-.1-.2-.1-.4 0-.6.1 0 .2-.1.3-.1l8.6-.9 3.5-7.9c0-.1.1-.1.1-.2h.2v20l-7.5 4.3c-.1.2-.4.1-.5-.1v-.3l1.6-8.3z' fill='%23fbda13'/%3E%3C/svg%3E") !important; + display: inline-block; + vertical-align: middle; + font-size: 0; + line-height: 1; + width: 16px; + height: 16px; + } + + .movie-director, + .movie-producer, + .movie-cast { + &__title { + font-weight: 700; + } + } + + .movie-rating { + margin-bottom: 10px; + } + } +} + +.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { + .product-info-main { + .page-title-wrapper { + h1.page-title { + &, + & + span { + font-size: 40px; + margin-top: 0; + margin-bottom: 0; + } + } + } + + .product-options-bottom .price-box .price-container .price, + .product-info-price .price-box .price-container .price { + font-size: 20px; + } + + .product-info-price { + border-bottom: 0; + margin-bottom: 0; + } + } +} + +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { + .product-info-main { + .page-title-wrapper { + margin: 10px 0; + + h1.page-title { + &, + & + span { + font-size: 20px; + margin-top: 0; + margin-bottom: 0; + } + } + } + } +}