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(); ?>
+
+
= $viewModel->getDescription(); ?>
+
+
+
+ = __('Directed By:') ?>
+ = $viewModel->getDirector(); ?>
+
+
+
+
+ = __('Produced By:') ?>
+ = $viewModel->getProducer(); ?>
+
+
+
+
+ = __('Cast:') ?>
+ = $viewModel->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(); ?>
+
+
= $viewModel->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(); ?>
+(= $viewModel->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;
+ }
+ }
+ }
+ }
+}