diff --git a/Api/CategoryRepositoryInterface.php b/Api/CategoryRepositoryInterface.php new file mode 100644 index 0000000..48d8b29 --- /dev/null +++ b/Api/CategoryRepositoryInterface.php @@ -0,0 +1,57 @@ +urlBuilder = $context->getUrlBuilder(); + } + + /** + * @return array + */ + public function getButtonData() + { + return [ + 'label' => __('Back'), + 'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()), + 'class' => 'back', + 'sort_order' => 10 + ]; + } + + /** + * Get URL for back (reset) button + * + * @return string + */ + public function getBackUrl() + { + return $this->urlBuilder->getUrl('*/*/'); + } +} diff --git a/Block/Adminhtml/Category/Edit/DeleteButton.php b/Block/Adminhtml/Category/Edit/DeleteButton.php new file mode 100644 index 0000000..db9c50c --- /dev/null +++ b/Block/Adminhtml/Category/Edit/DeleteButton.php @@ -0,0 +1,72 @@ +urlBuilder = $context->getUrlBuilder(); + $this->registry = $registry; + } + + /** + * @return array + */ + public function getButtonData() + { + $data = [ + 'label' => __('Delete'), + 'class' => 'delete', + 'id' => 'category-edit-delete-button', + 'data_attribute' => [ + 'url' => $this->getDeleteUrl() + ], + 'on_click' => 'deleteConfirm(\'' . __("Are you sure you want to do this?") . '\', \'' . $this->getDeleteUrl() . '\')', + 'sort_order' => 20, + ]; + return $data; + } + + /** + * @return string + */ + public function getDeleteUrl() + { + return $this->urlBuilder->getUrl('*/*/delete', ['entity_id' => $this->registry->registry('entity_id')]); + } +} diff --git a/Block/Adminhtml/Category/Edit/SaveAndContinueButton.php b/Block/Adminhtml/Category/Edit/SaveAndContinueButton.php new file mode 100644 index 0000000..aa2c0b6 --- /dev/null +++ b/Block/Adminhtml/Category/Edit/SaveAndContinueButton.php @@ -0,0 +1,35 @@ + __('Save and Continue Edit'), + 'class' => 'save', + 'data_attribute' => [ + 'mage-init' => [ + 'button' => ['event' => 'saveAndContinueEdit'], + ], + ], + 'sort_order' => 80, + ]; + return $data; + } +} diff --git a/Block/Adminhtml/Category/Edit/SaveButton.php b/Block/Adminhtml/Category/Edit/SaveButton.php new file mode 100644 index 0000000..b361e0d --- /dev/null +++ b/Block/Adminhtml/Category/Edit/SaveButton.php @@ -0,0 +1,34 @@ + __('Save'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ], + 'sort_order' => 90, + ]; + return $data; + } +} diff --git a/Block/Adminhtml/Size/Edit/BackButton.php b/Block/Adminhtml/Size/Edit/BackButton.php new file mode 100644 index 0000000..89d0ffd --- /dev/null +++ b/Block/Adminhtml/Size/Edit/BackButton.php @@ -0,0 +1,57 @@ +urlBuilder = $context->getUrlBuilder(); + } + + /** + * @return array + */ + public function getButtonData() + { + return [ + 'label' => __('Back'), + 'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()), + 'class' => 'back', + 'sort_order' => 10 + ]; + } + + /** + * Get URL for back (reset) button + * + * @return string + */ + public function getBackUrl() + { + return $this->urlBuilder->getUrl('*/*/'); + } +} diff --git a/Block/Adminhtml/Size/Edit/DeleteButton.php b/Block/Adminhtml/Size/Edit/DeleteButton.php new file mode 100644 index 0000000..5fcbdd8 --- /dev/null +++ b/Block/Adminhtml/Size/Edit/DeleteButton.php @@ -0,0 +1,72 @@ +urlBuilder = $context->getUrlBuilder(); + $this->registry = $registry; + } + + /** + * @return array + */ + public function getButtonData() + { + $data = [ + 'label' => __('Delete'), + 'class' => 'delete', + 'id' => 'size-edit-delete-button', + 'data_attribute' => [ + 'url' => $this->getDeleteUrl() + ], + 'on_click' => 'deleteConfirm(\'' . __("Are you sure you want to do this?") . '\', \'' . $this->getDeleteUrl() . '\')', + 'sort_order' => 20, + ]; + return $data; + } + + /** + * @return string + */ + public function getDeleteUrl() + { + return $this->urlBuilder->getUrl('*/*/delete', ['entity_id' => $this->registry->registry('entity_id')]); + } +} diff --git a/Block/Adminhtml/Size/Edit/SaveAndContinueButton.php b/Block/Adminhtml/Size/Edit/SaveAndContinueButton.php new file mode 100644 index 0000000..e24ef0d --- /dev/null +++ b/Block/Adminhtml/Size/Edit/SaveAndContinueButton.php @@ -0,0 +1,35 @@ + __('Save and Continue Edit'), + 'class' => 'save', + 'data_attribute' => [ + 'mage-init' => [ + 'button' => ['event' => 'saveAndContinueEdit'], + ], + ], + 'sort_order' => 80, + ]; + return $data; + } +} diff --git a/Block/Adminhtml/Size/Edit/SaveButton.php b/Block/Adminhtml/Size/Edit/SaveButton.php new file mode 100644 index 0000000..8475e2d --- /dev/null +++ b/Block/Adminhtml/Size/Edit/SaveButton.php @@ -0,0 +1,34 @@ + __('Save'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ], + 'sort_order' => 90, + ]; + return $data; + } +} diff --git a/Block/Adminhtml/Usage/Edit/BackButton.php b/Block/Adminhtml/Usage/Edit/BackButton.php new file mode 100644 index 0000000..e7bc612 --- /dev/null +++ b/Block/Adminhtml/Usage/Edit/BackButton.php @@ -0,0 +1,57 @@ +urlBuilder = $context->getUrlBuilder(); + } + + /** + * @return array + */ + public function getButtonData() + { + return [ + 'label' => __('Back'), + 'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()), + 'class' => 'back', + 'sort_order' => 10 + ]; + } + + /** + * Get URL for back (reset) button + * + * @return string + */ + public function getBackUrl() + { + return $this->urlBuilder->getUrl('*/*/'); + } +} diff --git a/Block/Adminhtml/Usage/Edit/DeleteButton.php b/Block/Adminhtml/Usage/Edit/DeleteButton.php new file mode 100644 index 0000000..a343bbb --- /dev/null +++ b/Block/Adminhtml/Usage/Edit/DeleteButton.php @@ -0,0 +1,73 @@ +urlBuilder = $context->getUrlBuilder(); + $this->registry = $registry; + } + + /** + * @return array + */ + public function getButtonData() + { + $data = [ + 'label' => __('Delete'), + 'class' => 'delete', + 'id' => 'usage-edit-delete-button', + 'data_attribute' => [ + 'url' => $this->getDeleteUrl() + ], + 'on_click' => + 'deleteConfirm(\'' . __("Are you sure you want to do this?") . '\', \'' . $this->getDeleteUrl() . '\')', + 'sort_order' => 20, + ]; + return $data; + } + + /** + * @return string + */ + public function getDeleteUrl() + { + return $this->urlBuilder->getUrl('*/*/delete', ['entity_id' => $this->registry->registry('entity_id')]); + } +} diff --git a/Block/Adminhtml/Usage/Edit/SaveAndContinueButton.php b/Block/Adminhtml/Usage/Edit/SaveAndContinueButton.php new file mode 100644 index 0000000..63a4d1b --- /dev/null +++ b/Block/Adminhtml/Usage/Edit/SaveAndContinueButton.php @@ -0,0 +1,35 @@ + __('Save and Continue Edit'), + 'class' => 'save', + 'data_attribute' => [ + 'mage-init' => [ + 'button' => ['event' => 'saveAndContinueEdit'], + ], + ], + 'sort_order' => 80, + ]; + return $data; + } +} diff --git a/Block/Adminhtml/Usage/Edit/SaveButton.php b/Block/Adminhtml/Usage/Edit/SaveButton.php new file mode 100644 index 0000000..42bb2bb --- /dev/null +++ b/Block/Adminhtml/Usage/Edit/SaveButton.php @@ -0,0 +1,34 @@ + __('Save'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ], + 'sort_order' => 90, + ]; + return $data; + } +} diff --git a/Block/Catalog/Product/Usage.php b/Block/Catalog/Product/Usage.php new file mode 100644 index 0000000..8a8b527 --- /dev/null +++ b/Block/Catalog/Product/Usage.php @@ -0,0 +1,374 @@ +pricingHelper = $pricingHelper; + $this->encoder = $encoder; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->usageRepository = $usageRepository; + $this->categoryRepository = $categoryRepository; + $this->optionModel = $option; + parent::__construct($context, $data); + } + + /** + * + * @return boolean + */ + public function getLinksPurchasedSeparately() + { + return $this->getProduct()->getLinksPurchasedSeparately(); + } + + /** + * @return boolean + */ + public function getLinkSelectionRequired() + { + return $this->getProduct()->getTypeInstance()->getLinkSelectionRequired($this->getProduct()); + } + + /** + * @return boolean + */ + public function hasLinks() + { + return $this->getProduct()->getTypeInstance()->hasLinks($this->getProduct()); + } + + /** + * @return array + */ + public function getLinks() + { + return $this->getProduct()->getTypeInstance()->getLinks($this->getProduct()); + } + + /** + * @return array + */ + public function getUsages($category = null) + { + if (empty($this->usages)) { + $searchCriteria = $this->searchCriteriaBuilder->create(); + $items = $this->usageRepository->getList($searchCriteria)->getItems(); + + $this->usages = []; + foreach($items as $item) { + $item->afterLoad(); + $this->usages[$item->getCategoryId()][] = $item; + } + } + if ($category) { + if( isset($this->usages[$category->getId()])) { + return $this->usages[$category->getId()]; + } else { + return []; + } + } + if ($this->usages) { + return call_user_func_array('array_merge', $this->usages); + } else { + return $this->usages; + } + } + + + public function getCategories() + { + $searchCriteria = $this->searchCriteriaBuilder->create(); + $list = $this->categoryRepository->getList($searchCriteria)->getItems(); + return $list; + } + + public function getUsagesSelectHtml($usages, $category) + { + $store = $this->getProduct()->getStore(); + + $extraParams = ''; + $select = $this->getLayout()->createBlock( + \Magento\Framework\View\Element\Html\Select::class + )->setData( + [ + 'id' => 'usage_'.$category->getId().'_usages', + 'class' => 'required product-custom-option admin__control-select usage-select-box' + ] + ); + + $select->setName('usage_id['.$category->getId().']')->addOption('', __('-- Please Select --')); + + foreach ($usages as $usage) { + + $select->addOption( + $usage->getId(), + $usage->getName(), + [ + 'price' => $this->pricingHelper->currencyByStore($usage->getPrice(), $store, false), + 'data-terms' => $usage->getTerms(), + ] + ); + + } + $extraParams .= ' data-selector="' . $select->getName() . '"'; + $select->setExtraParams($extraParams); + + return $select->getHtml(); + } + + public function getCategoriesSelectHtml() + { + $extraParams = ''; + $select = $this->getLayout()->createBlock( + \Magento\Framework\View\Element\Html\Select::class + )->setData( + [ + 'id' => 'usage_category', + 'class' => 'required product-custom-option admin__control-select' + ] + ); + + $select->setName('usage_category')->addOption('', __('-- Please Select --')); + + foreach ($this->getCategories() as $category) { + + $select->addOption( + $category->getId(), + $category->getName() + ); + } + $extraParams .= ' data-selector="' . $select->getName() . '"'; + $select->setExtraParams($extraParams); + + return $select->getHtml(); + } + + /** + * Returns price converted to current currency rate + * + * @param float $price + * @return float + */ + public function getCurrencyPrice($price) + { + $store = $this->getProduct()->getStore(); + return $this->pricingHelper->currencyByStore($price, $store, false); + } + + /** + * @return string + */ + public function getJsonConfig() + { + $finalPrice = $this->getProduct()->getPriceInfo() + ->getPrice(FinalPrice::PRICE_CODE); + + $linksConfig = []; + foreach ($this->getUsages() as $usage) { + + $amount = $finalPrice->getCustomAmount($usage->getPrice()); + $linksConfig[$usage->getId()] = [ + 'finalPrice' => $amount->getValue(), + 'basePrice' => $amount->getBaseAmount() + ]; + } + + return $this->encoder->encode(['links' => $linksConfig]); + } + + /** + * @param Link $link + * @return string + */ + public function getLinkSampleUrl($link) + { + $store = $this->getProduct()->getStore(); + return $store->getUrl('downloadable/download/linkSample', ['link_id' => $link->getId()]); + } + + /** + * Return title of links section + * + * @return string + */ + public function getLinksTitle() + { + if ($this->getProduct()->getLinksTitle()) { + return $this->getProduct()->getLinksTitle(); + } + return $this->_scopeConfig->getValue( + \Magento\Downloadable\Model\Link::XML_PATH_LINKS_TITLE, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } + + /** + * Return true if target of link new window + * + * @return bool + * @SuppressWarnings(PHPMD.BooleanGetMethodName) + */ + public function getIsOpenInNewWindow() + { + return $this->_scopeConfig->isSetFlag( + \Magento\Downloadable\Model\Link::XML_PATH_TARGET_NEW_WINDOW, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } + + /** + * Returns whether link checked by default or not + * + * @param Link $link + * @return bool + * @SuppressWarnings(PHPMD.BooleanGetMethodName) + */ + public function getIsLinkChecked($link) + { + $configValue = $this->getProduct()->getPreconfiguredValues()->getLinks(); + if (!$configValue || !is_array($configValue)) { + return false; + } + + return $configValue && in_array($link->getId(), $configValue); + } + + /** + * Returns value for link's input checkbox - either 'checked' or '' + * + * @param Link $link + * @return string + */ + public function getLinkCheckedValue($link) + { + return $this->getIsLinkChecked($link) ? 'checked' : ''; + } + + /** + * @param Link $link + * @return \Magento\Framework\Pricing\Amount\AmountInterface + */ + protected function getLinkAmount($link) + { + return $this->getPriceType()->getLinkAmount($link); + } + + /** + * @param Link $link + * @return string + */ + public function getLinkPrice(Link $link) + { + return $this->getLayout()->getBlock('product.price.render.default')->renderAmount( + $this->getLinkAmount($link), + $this->getPriceType(), + $this->getProduct() + ); + } + + /** + * Get LinkPrice Type + * + * @return \Magento\Framework\Pricing\Price\PriceInterface + */ + protected function getPriceType() + { + return $this->getProduct()->getPriceInfo()->getPrice(LinkPrice::PRICE_CODE); + } + + + /** + * Get option html block + * + * @param \DevStone\UsageCalculator\Model\Usage\Option $option + * @return string + */ + public function getOptionHtml(\DevStone\UsageCalculator\Model\Usage\Option $option) + { + $type = $this->getGroupOfOption($option->getType()); + $renderer = $this->getChildBlock($type); + + $renderer->setProduct($this->getProduct())->setOption($option); + + return $this->getChildHtml($type, false); + } + + /** + * @param string $type + * @return string + */ + public function getGroupOfOption($type) + { + $group = $this->optionModel->getGroupByType($type); + + return $group == '' ? 'default' : $group; + } +} diff --git a/Block/Usage/View/Options.php b/Block/Usage/View/Options.php new file mode 100644 index 0000000..c750e4f --- /dev/null +++ b/Block/Usage/View/Options.php @@ -0,0 +1,311 @@ +pricingHelper = $pricingHelper; + $this->_catalogData = $catalogData; + $this->_jsonEncoder = $jsonEncoder; + $this->_registry = $registry; + $this->_option = $option; + $this->arrayUtils = $arrayUtils; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->usageRepository = $usageRepository; + parent::__construct($context, $data); + } + + /** + * Retrieve product object + * + * @return Product + * @throws \LogicExceptions + */ + public function getProduct() + { + if (!$this->_product) { + if ($this->_registry->registry('current_product')) { + $this->_product = $this->_registry->registry('current_product'); + } else { + throw new \LogicException('Product is not defined'); + } + } + return $this->_product; + } + + /** + * Set product object + * + * @param Product $product + * @return \Magento\Catalog\Block\Product\View\Options + */ + public function setProduct(Product $product = null) + { + $this->_product = $product; + return $this; + } + + public function getUsage() + { + $searchCriteria = $this->searchCriteriaBuilder->create(); + $list = $this->usageRepository->getList($searchCriteria)->getItems(); + return current($list); + } + + public function getCategories() + { + $searchCriteria = $this->searchCriteriaBuilder->create(); + $list = $this->usageRepository->getList($searchCriteria)->getItems(); + return current($list); + } + + public function getCategoriesSelectHtml() + { + $this->getCategories(); + + $extraParams = ''; + $select = $this->getLayout()->createBlock( + \Magento\Framework\View\Element\Html\Select::class + )->setData( + [ + 'id' => 'usage_category', + 'class' => 'required product-custom-option admin__control-select' + ] + ); + + $select->setName('usage_category')->addOption('', __('-- Please Select --')); + + foreach ($this->getCategories() as $category) { + + $select->addOption( + $category->getId(), + $category->getName() + ); + } + if (!$this->getSkipJsReloadPrice()) { + $extraParams .= ' onchange="opConfig.reloadPrice()"'; + } + $extraParams .= ' data-selector="' . $select->getName() . '"'; + $select->setExtraParams($extraParams); + + if ($configValue) { + $select->setValue($configValue); + } + + return $select->getHtml(); + } + + /** + * @param string $type + * @return string + */ + public function getGroupOfOption($type) + { + $group = $this->_option->getGroupByType($type); + + return $group == '' ? 'default' : $group; + } + + /** + * Get product options + * + * @return array + */ + public function getOptions() + { + return $this->getUsage()->getOptions(); + } + + /** + * @return bool + */ + public function hasOptions() + { + if ($this->getOptions()) { + return true; + } + return false; + } + + /** + * Get price configuration + * + * @param \Magento\Catalog\Model\Product\Option\Value|\Magento\Catalog\Model\Product\Option $option + * @return array + */ + protected function _getPriceConfiguration($option) + { + $optionPrice = $this->pricingHelper->currency($option->getPrice(true), false, false); + $data = [ + 'prices' => [ + 'oldPrice' => [ + 'amount' => $this->pricingHelper->currency($option->getRegularPrice(), false, false), + 'adjustments' => [], + ], + 'basePrice' => [ + 'amount' => $this->_catalogData->getTaxPrice( + $option->getProduct(), + $optionPrice, + false, + null, + null, + null, + null, + null, + false + ), + ], + 'finalPrice' => [ + 'amount' => $this->_catalogData->getTaxPrice( + $option->getProduct(), + $optionPrice, + true, + null, + null, + null, + null, + null, + false + ), + ], + ], + 'type' => $option->getPriceType(), + 'name' => $option->getTitle() + ]; + return $data; + } + + /** + * Get json representation of + * + * @return string + */ + public function getJsonConfig() + { + $config = []; + foreach ($this->getOptions() as $option) { + /* @var $option \Magento\Catalog\Model\Product\Option */ + if ($option->hasValues()) { + $tmpPriceValues = []; + foreach ($option->getValues() as $valueId => $value) { + $tmpPriceValues[$valueId] = $this->_getPriceConfiguration($value); + } + $priceValue = $tmpPriceValues; + } else { + $priceValue = $this->_getPriceConfiguration($option); + } + $config[$option->getId()] = $priceValue; + } + + $configObj = new \Magento\Framework\DataObject( + [ + 'config' => $config, + ] + ); + + //pass the return array encapsulated in an object for the other modules to be able to alter it eg: weee + $this->_eventManager->dispatch('catalog_product_option_price_configuration_after', ['configObj' => $configObj]); + + $config=$configObj->getConfig(); + + return $this->_jsonEncoder->encode($config); + } + + /** + * Get option html block + * + * @param \Magento\Catalog\Model\Product\Option $option + * @return string + */ + public function getOptionHtml(\Magento\Catalog\Model\Product\Option $option) + { + $type = $this->getGroupOfOption($option->getType()); + $renderer = $this->getChildBlock($type); + + $renderer->setProduct($this->getProduct())->setOption($option); + + return $this->getChildHtml($type, false); + } + + /** + * Decorate a plain array of arrays or objects + * + * @param array $array + * @param string $prefix + * @param bool $forceSetAll + * @return array + */ + public function decorateArray($array, $prefix = 'decorated_', $forceSetAll = false) + { + return $this->arrayUtils->decorateArray($array, $prefix, $forceSetAll); + } +} diff --git a/Block/Usage/View/Options/AbstractOptions.php b/Block/Usage/View/Options/AbstractOptions.php new file mode 100644 index 0000000..f1964dd --- /dev/null +++ b/Block/Usage/View/Options/AbstractOptions.php @@ -0,0 +1,178 @@ +pricingHelper = $pricingHelper; + $this->_catalogHelper = $catalogData; + parent::__construct($context, $data); + } + + /** + * Set Product object + * + * @param \Magento\Catalog\Model\Product $product + * @return \Magento\Catalog\Block\Product\View\Options\AbstractOptions + */ + public function setProduct(\Magento\Catalog\Model\Product $product = null) + { + $this->_product = $product; + return $this; + } + + /** + * Retrieve Product object + * + * @return \Magento\Catalog\Model\Product + */ + public function getProduct() + { + return $this->_product; + } + + /** + * Set option + * + * @param \DevStone\UsageCalculator\Model\Usage\Option $option + * @return \Magento\Catalog\Block\Product\View\Options\AbstractOptions + */ + public function setOption(\DevStone\UsageCalculator\Model\Usage\Option $option) + { + $this->_option = $option; + return $this; + } + + /** + * Get option + * + * @return \DevStone\UsageCalculator\Model\Usage\Option + */ + public function getOption() + { + return $this->_option; + } + + /** + * @return string + */ + public function getFormatedPrice() + { + if ($option = $this->getOption()) { + return $this->_formatPrice( + [ + 'is_percent' => $option->getPriceType() == 'percent', + 'pricing_value' => $option->getPrice($option->getPriceType() == 'percent'), + ] + ); + } + return ''; + } + + /** + * Return formated price + * + * @param array $value + * @param bool $flag + * @return string + */ + protected function _formatPrice($value, $flag = true) + { + if ($value['pricing_value'] == 0) { + return ''; + } + + $sign = '+'; + if ($value['pricing_value'] < 0) { + $sign = '-'; + $value['pricing_value'] = 0 - $value['pricing_value']; + } + + $priceStr = $sign; + + $customOptionPrice = $this->getProduct()->getPriceInfo()->getPrice('custom_option_price'); + $context = [CustomOptionPriceInterface::CONFIGURATION_OPTION_FLAG => true]; + $optionAmount = $customOptionPrice->getCustomAmount($value['pricing_value'], null, $context); + $priceStr .= $this->getLayout()->getBlock('product.price.render.default')->renderAmount( + $optionAmount, + $customOptionPrice, + $this->getProduct() + ); + + if ($flag) { + $priceStr = '' . $priceStr . ''; + } + + return $priceStr; + } + + /** + * Get price with including/excluding tax + * + * @param float $price + * @param bool $includingTax + * @return float + */ + public function getPrice($price, $includingTax = null) + { + if ($includingTax !== null) { + $price = $this->_catalogHelper->getTaxPrice($this->getProduct(), $price, true); + } else { + $price = $this->_catalogHelper->getTaxPrice($this->getProduct(), $price); + } + return $price; + } + + /** + * Returns price converted to current currency rate + * + * @param float $price + * @return float + */ + public function getCurrencyPrice($price) + { + $store = $this->getProduct()->getStore(); + return $this->pricingHelper->currencyByStore($price, $store, false); + } +} diff --git a/Block/Usage/View/Options/Type/Date.php b/Block/Usage/View/Options/Type/Date.php new file mode 100644 index 0000000..d0a9d68 --- /dev/null +++ b/Block/Usage/View/Options/Type/Date.php @@ -0,0 +1,233 @@ +_catalogProductOptionTypeDate = $catalogProductOptionTypeDate; + parent::__construct($context, $pricingHelper, $catalogData, $data); + } + + /** + * Use JS calendar settings + * + * @return boolean + */ + public function useCalendar() + { + return $this->_catalogProductOptionTypeDate->useCalendar(); + } + + /** + * Date input + * + * @return string Formatted Html + */ + public function getDateHtml() + { + if ($this->useCalendar()) { + return $this->getCalendarDateHtml(); + } else { + return $this->getDropDownsDateHtml(); + } + } + + /** + * JS Calendar html + * + * @return string Formatted Html + */ + public function getCalendarDateHtml() + { + $option = $this->getOption(); + $value = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId() . '/date'); + + $yearStart = $this->_catalogProductOptionTypeDate->getYearStart(); + $yearEnd = $this->_catalogProductOptionTypeDate->getYearEnd(); + + $dateFormat = $this->_localeDate->getDateFormat(\IntlDateFormatter::SHORT); + /** Escape RTL characters which are present in some locales and corrupt formatting */ + $escapedDateFormat = preg_replace('/[^MmDdYy\/\.\-]/', '', $dateFormat); + $calendar = $this->getLayout()->createBlock( + \Magento\Framework\View\Element\Html\Date::class + )->setId( + 'options_' . $this->getOption()->getId() . '_date' + )->setName( + 'options[' . $this->getOption()->getId() . '][date]' + )->setClass( + 'product-custom-option datetime-picker input-text' + )->setImage( + $this->getViewFileUrl('Magento_Theme::calendar.png') + )->setDateFormat( + $escapedDateFormat + )->setValue( + $value + )->setYearsRange( + $yearStart . ':' . $yearEnd + ); + + return $calendar->getHtml(); + } + + /** + * Date (dd/mm/yyyy) html drop-downs + * + * @return string Formatted Html + */ + public function getDropDownsDateHtml() + { + $fieldsSeparator = ' '; + $fieldsOrder = $this->_catalogProductOptionTypeDate->getConfigData('date_fields_order'); + $fieldsOrder = str_replace(',', $fieldsSeparator, $fieldsOrder); + + $monthsHtml = $this->_getSelectFromToHtml('month', 1, 12); + $daysHtml = $this->_getSelectFromToHtml('day', 1, 31); + + $yearStart = $this->_catalogProductOptionTypeDate->getYearStart(); + $yearEnd = $this->_catalogProductOptionTypeDate->getYearEnd(); + $yearsHtml = $this->_getSelectFromToHtml('year', $yearStart, $yearEnd); + + $translations = ['d' => $daysHtml, 'm' => $monthsHtml, 'y' => $yearsHtml]; + return strtr($fieldsOrder, $translations); + } + + /** + * Time (hh:mm am/pm) html drop-downs + * + * @return string Formatted Html + */ + public function getTimeHtml() + { + if ($this->_catalogProductOptionTypeDate->is24hTimeFormat()) { + $hourStart = 0; + $hourEnd = 23; + $dayPartHtml = ''; + } else { + $hourStart = 1; + $hourEnd = 12; + $dayPartHtml = $this->_getHtmlSelect( + 'day_part' + )->setOptions( + ['am' => __('AM'), 'pm' => __('PM')] + )->getHtml(); + } + $hoursHtml = $this->_getSelectFromToHtml('hour', $hourStart, $hourEnd); + $minutesHtml = $this->_getSelectFromToHtml('minute', 0, 59); + + return $hoursHtml . ' : ' . $minutesHtml . ' ' . $dayPartHtml; + } + + /** + * Return drop-down html with range of values + * + * @param string $name Id/name of html select element + * @param int $from Start position + * @param int $to End position + * @param int|null $value Value selected + * @return string Formatted Html + */ + protected function _getSelectFromToHtml($name, $from, $to, $value = null) + { + $options = [['value' => '', 'label' => '-']]; + for ($i = $from; $i <= $to; $i++) { + $options[] = ['value' => $i, 'label' => $this->_getValueWithLeadingZeros($i)]; + } + return $this->_getHtmlSelect($name, $value)->setOptions($options)->getHtml(); + } + + /** + * HTML select element + * + * @param string $name Id/name of html select element + * @param int|null $value + * @return mixed + */ + protected function _getHtmlSelect($name, $value = null) + { + $option = $this->getOption(); + + $this->setSkipJsReloadPrice(1); + + // $require = $this->getOption()->getIsRequire() ? ' required-entry' : ''; + $require = ''; + $select = $this->getLayout()->createBlock( + \Magento\Framework\View\Element\Html\Select::class + )->setId( + 'options_' . $this->getOption()->getId() . '_' . $name + )->setClass( + 'product-custom-option admin__control-select datetime-picker' . $require + )->setExtraParams()->setName( + 'options[' . $option->getId() . '][' . $name . ']' + ); + + $extraParams = 'style="width:auto"'; + if (!$this->getSkipJsReloadPrice()) { + $extraParams .= ' onchange="opConfig.reloadPrice()"'; + } + $extraParams .= ' data-role="calendar-dropdown" data-calendar-role="' . $name . '"'; + $extraParams .= ' data-selector="' . $select->getName() . '"'; + if ($this->getOption()->getIsRequire()) { + $extraParams .= ' data-validate=\'{"datetime-validation": true}\''; + } + + $select->setExtraParams($extraParams); + if ($value === null) { + $value = $this->getProduct()->getPreconfiguredValues()->getData( + 'options/' . $option->getId() . '/' . $name + ); + } + if ($value !== null) { + $select->setValue($value); + } + + return $select; + } + + /** + * Add Leading Zeros to number less than 10 + * + * @param int $value + * @return string|int + */ + protected function _getValueWithLeadingZeros($value) + { + if (!$this->_fillLeadingZeros) { + return $value; + } + return $value < 10 ? '0' . $value : $value; + } +} diff --git a/Block/Usage/View/Options/Type/DefaultType.php b/Block/Usage/View/Options/Type/DefaultType.php new file mode 100644 index 0000000..1720b76 --- /dev/null +++ b/Block/Usage/View/Options/Type/DefaultType.php @@ -0,0 +1,16 @@ +getProduct()->getPreconfiguredValues()->getData('options/' . $this->getOption()->getId()); + if (empty($info)) { + $info = new \Magento\Framework\DataObject(); + } elseif (is_array($info)) { + $info = new \Magento\Framework\DataObject($info); + } + return $info; + } +} diff --git a/Block/Usage/View/Options/Type/Select.php b/Block/Usage/View/Options/Type/Select.php new file mode 100644 index 0000000..34fb13c --- /dev/null +++ b/Block/Usage/View/Options/Type/Select.php @@ -0,0 +1,164 @@ +getOption(); + $configValue = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $_option->getId()); + $store = $this->getProduct()->getStore(); + + $this->setSkipJsReloadPrice(1); + // Remove inline prototype onclick and onchange events + + if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN || + $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE + ) { + $require = $_option->getIsRequire() ? ' required' : ''; + $extraParams = ''; + $select = $this->getLayout()->createBlock( + \Magento\Framework\View\Element\Html\Select::class + )->setData( + [ + 'id' => 'select_' . $_option->getId(), + 'class' => $require . ' product-custom-option admin__control-select', + 'title' => $_option->getTitle(), + ] + ); + if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN) { + $select->setName('options[' . $_option->getid() . ']')->addOption('', __('-- Please Select --')); + } else { + $select->setName('options[' . $_option->getid() . '][]'); + $select->setClass('multiselect admin__control-multiselect' . $require . ' product-custom-option'); + } + foreach ($_option->getValues() as $_value) { + $select->addOption( + $_value->getOptionTypeId(), + $_value->getTitle(), + ['price' => $this->pricingHelper->currencyByStore($_value->getPrice(), $store, false)] + ); + } + if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE) { + $extraParams = ' multiple="multiple"'; + } + if (!$this->getSkipJsReloadPrice()) { + $extraParams .= ' onchange="opConfig.reloadPrice()"'; + } + $extraParams .= ' data-selector="' . $select->getName() . '"'; + $select->setExtraParams($extraParams); + + if ($configValue) { + $select->setValue($configValue); + } + + return $select->getHtml(); + } + + if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO || + $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX + ) { + $selectHtml = '
'; + $require = $_option->getIsRequire() ? ' required' : ''; + $arraySign = ''; + switch ($_option->getType()) { + case \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO: + $type = 'radio'; + $class = 'radio admin__control-radio'; + if (!$_option->getIsRequire()) { + $selectHtml .= '
' . + 'getSkipJsReloadPrice() ? '' : ' onclick="opConfig.reloadPrice()"') . + ' value="" checked="checked" />
'; + } + break; + case \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX: + $type = 'checkbox'; + $class = 'checkbox admin__control-checkbox'; + $arraySign = '[]'; + break; + } + $count = 1; + foreach ($_option->getValues() as $_value) { + $count++; + + $htmlValue = $_value->getOptionTypeId(); + if ($arraySign) { + $checked = is_array($configValue) && in_array($htmlValue, $configValue) ? 'checked' : ''; + } else { + $checked = $configValue == $htmlValue ? 'checked' : ''; + } + + $dataSelector = 'options[' . $_option->getId() . ']'; + if ($arraySign) { + $dataSelector .= '[' . $htmlValue . ']'; + } + + $selectHtml .= '
' . + 'getSkipJsReloadPrice() ? '' : ' onclick="opConfig.reloadPrice()"') . + ' name="options[' . + $_option->getId() . + ']' . + $arraySign . + '" id="options_' . + $_option->getId() . + '_' . + $count . + '" value="' . + $htmlValue . + '" ' . + $checked . + ' data-selector="' . $dataSelector . '"' . + ' price="' . + $this->pricingHelper->currencyByStore($_value->getPrice(true), $store, false) . + '" />' . + ''; + $selectHtml .= '
'; + } + $selectHtml .= '
'; + + return $selectHtml; + } + } +} diff --git a/Block/Usage/View/Options/Type/Text.php b/Block/Usage/View/Options/Type/Text.php new file mode 100644 index 0000000..a358934 --- /dev/null +++ b/Block/Usage/View/Options/Type/Text.php @@ -0,0 +1,21 @@ +getProduct()->getPreconfiguredValues()->getData('options/' . $this->getOption()->getId()); + } +} diff --git a/Controller/Adminhtml/Category/Delete.php b/Controller/Adminhtml/Category/Delete.php new file mode 100644 index 0000000..827f310 --- /dev/null +++ b/Controller/Adminhtml/Category/Delete.php @@ -0,0 +1,63 @@ +objectFactory = $objectFactory; + parent::__construct($context); + } + + /** + * {@inheritdoc} + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::category'); + } + + /** + * Delete action + * + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + $resultRedirect = $this->resultRedirectFactory->create(); + $id = $this->getRequest()->getParam('entity_id', null); + + try { + $objectInstance = $this->objectFactory->create()->load($id); + if ($objectInstance->getId()) { + $objectInstance->delete(); + $this->messageManager->addSuccessMessage(__('You deleted the record.')); + } else { + $this->messageManager->addErrorMessage(__('Record does not exist.')); + } + } catch (\Exception $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); + } + + return $resultRedirect->setPath('*/*'); + } +} diff --git a/Controller/Adminhtml/Category/Edit.php b/Controller/Adminhtml/Category/Edit.php new file mode 100644 index 0000000..9c6d3dd --- /dev/null +++ b/Controller/Adminhtml/Category/Edit.php @@ -0,0 +1,100 @@ +resultPageFactory = $resultPageFactory; + $this->_coreRegistry = $registry; + $this->objectFactory = $objectFactory; + parent::__construct($context); + } + + /** + * {@inheritdoc} + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::category'); + } + + /** + * Edit + * + * @return \Magento\Backend\Model\View\Result\Page|\Magento\Backend\Model\View\Result\Redirect + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function execute() + { + // 1. Get ID + $id = $this->getRequest()->getParam('entity_id'); + $objectInstance = $this->objectFactory->create(); + + // 2. Initial checking + if ($id) { + $objectInstance->load($id); + if (!$objectInstance->getId()) { + $this->messageManager->addErrorMessage(__('This record no longer exists.')); + /** \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + + return $resultRedirect->setPath('*/*/'); + } + } + + // 3. Set entered data if was error when we do save + $data = $this->_session->getFormData(true); + if (!empty($data)) { + $objectInstance->addData($data); + } + + // 4. Register model to use later in blocks + $this->_coreRegistry->register('entity_id', $id); + + // 5. Build edit form + /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ + $resultPage = $this->resultPageFactory->create(); + $resultPage->setActiveMenu('DevStone_UsageCalculator::category'); + $resultPage->getConfig()->getTitle()->prepend(__('Category Edit')); + + return $resultPage; + } +} diff --git a/Controller/Adminhtml/Category/Index.php b/Controller/Adminhtml/Category/Index.php new file mode 100644 index 0000000..d88abc2 --- /dev/null +++ b/Controller/Adminhtml/Category/Index.php @@ -0,0 +1,57 @@ +resultPageFactory = $resultPageFactory; + } + + /** + * Check the permission to run it + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::category'); + } + + /** + * Index action + * + * @return \Magento\Backend\Model\View\Result\Page + */ + public function execute() + { + /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ + $resultPage = $this->resultPageFactory->create(); + $resultPage->setActiveMenu('DevStone_UsageCalculator::category'); + $resultPage->getConfig()->getTitle()->prepend(__('Category')); + + return $resultPage; + } +} diff --git a/Controller/Adminhtml/Category/InlineEdit.php b/Controller/Adminhtml/Category/InlineEdit.php new file mode 100644 index 0000000..89966c3 --- /dev/null +++ b/Controller/Adminhtml/Category/InlineEdit.php @@ -0,0 +1,78 @@ +jsonFactory = $jsonFactory; + $this->objectCollection = $objectCollection; + } + + /** + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->jsonFactory->create(); + $error = false; + $messages = []; + + $postItems = $this->getRequest()->getParam('items', []); + if (!($this->getRequest()->getParam('isAjax') && count($postItems))) { + return $resultJson->setData([ + 'messages' => [__('Please correct the data sent.')], + 'error' => true, + ]); + } + + try { + $this->objectCollection + ->addFieldToFilter('entity_id', array('in' => array_keys($postItems))) + ->walk('saveCollection', array($postItems)); + } catch (\Exception $e) { + $messages[] = __('There was an error saving the data: ') . $e->getMessage(); + $error = true; + } + + return $resultJson->setData([ + 'messages' => $messages, + 'error' => $error + ]); + } +} diff --git a/Controller/Adminhtml/Category/MassDelete.php b/Controller/Adminhtml/Category/MassDelete.php new file mode 100644 index 0000000..bd76e89 --- /dev/null +++ b/Controller/Adminhtml/Category/MassDelete.php @@ -0,0 +1,59 @@ +filter = $filter; + $this->objectCollection = $objectCollection; + parent::__construct($context); + } + + /** + * Execute action + * + * @return \Magento\Backend\Model\View\Result\Redirect + * @throws \Magento\Framework\Exception\LocalizedException|\Exception + */ + public function execute() + { + $collection = $this->filter->getCollection($this->objectCollection); + $collectionSize = $collection->getSize(); + $collection->walk('delete'); + + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been deleted.', $collectionSize)); + + /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + return $resultRedirect->setPath('*/*/'); + } +} diff --git a/Controller/Adminhtml/Category/Save.php b/Controller/Adminhtml/Category/Save.php new file mode 100644 index 0000000..6f6ae28 --- /dev/null +++ b/Controller/Adminhtml/Category/Save.php @@ -0,0 +1,85 @@ +objectFactory = $objectFactory; + parent::__construct($context); + } + + /** + * {@inheritdoc} + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::category'); + } + + /** + * Save action + * + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + $data = $this->getRequest()->getParams(); + /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + if ($data) { + $params = []; + $objectInstance = $this->objectFactory->create(); + $idField = $objectInstance->getIdFieldName(); + if (empty($data[$idField])) { + $data[$idField] = null; + } else { + $objectInstance->load($data[$idField]); + $params[$idField] = $data[$idField]; + } + $objectInstance->addData($data); + + $this->_eventManager->dispatch( + 'devstone_usagecalculator_category_prepare_save', + ['object' => $this->objectFactory, 'request' => $this->getRequest()] + ); + + try { + $objectInstance->save(); + $this->messageManager->addSuccessMessage(__('You saved this record.')); + $this->_getSession()->setFormData(false); + if ($this->getRequest()->getParam('back')) { + $params = [$idField => $objectInstance->getId(), '_current' => true]; + return $resultRedirect->setPath('*/*/edit', $params); + } + return $resultRedirect->setPath('*/*/'); + } catch (\Exception $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the record.')); + } + + $this->_getSession()->setFormData($this->getRequest()->getPostValue()); + return $resultRedirect->setPath('*/*/edit', $params); + } + return $resultRedirect->setPath('*/*/'); + } +} diff --git a/Controller/Adminhtml/Category/Validate.php b/Controller/Adminhtml/Category/Validate.php new file mode 100644 index 0000000..6d16b06 --- /dev/null +++ b/Controller/Adminhtml/Category/Validate.php @@ -0,0 +1,84 @@ +jsonFactory = $jsonFactory; + $this->response = new \Magento\Framework\DataObject(); + } + + /** + * Check if required fields is not empty + * + * @param array $data + */ + public function validateRequireEntries(array $data) + { + $requiredFields = [ + 'identifier' => __('Category Identifier'), + ]; + foreach ($data as $field => $value) { + if (in_array($field, array_keys($requiredFields)) && $value == '') { + $this->_addErrorMessage( + __('To apply changes you should fill in required "%1" field', $requiredFields[$field]) + ); + } + } + } + + /** + * Add error message validation + * + * @param $message + */ + protected function _addErrorMessage($message) + { + $this->response->setError(true); + if (!is_array($this->response->getMessages())) { + $this->response->setMessages([]); + } + $messages = $this->response->getMessages(); + $messages[] = $message; + $this->response->setMessages($messages); + } + + /** + * AJAX customer validation action + * + * @return \Magento\Framework\Controller\Result\Json + */ + public function execute() + { + $this->response->setError(0); + + $this->validateRequireEntries($this->getRequest()->getParams()); + + $resultJson = $this->jsonFactory->create()->setData($this->response); + return $resultJson; + } +} diff --git a/Controller/Adminhtml/Size/Delete.php b/Controller/Adminhtml/Size/Delete.php new file mode 100644 index 0000000..b6aa9f1 --- /dev/null +++ b/Controller/Adminhtml/Size/Delete.php @@ -0,0 +1,63 @@ +objectFactory = $objectFactory; + parent::__construct($context); + } + + /** + * {@inheritdoc} + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::size'); + } + + /** + * Delete action + * + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + $resultRedirect = $this->resultRedirectFactory->create(); + $id = $this->getRequest()->getParam('entity_id', null); + + try { + $objectInstance = $this->objectFactory->create()->load($id); + if ($objectInstance->getId()) { + $objectInstance->delete(); + $this->messageManager->addSuccessMessage(__('You deleted the record.')); + } else { + $this->messageManager->addErrorMessage(__('Record does not exist.')); + } + } catch (\Exception $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); + } + + return $resultRedirect->setPath('*/*'); + } +} diff --git a/Controller/Adminhtml/Size/Edit.php b/Controller/Adminhtml/Size/Edit.php new file mode 100644 index 0000000..6ff8562 --- /dev/null +++ b/Controller/Adminhtml/Size/Edit.php @@ -0,0 +1,101 @@ +resultPageFactory = $resultPageFactory; + $this->_coreRegistry = $registry; + $this->objectFactory = $objectFactory; + parent::__construct($context); + } + + /** + * {@inheritdoc} + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::size'); + } + + /** + * Edit + * + * @return \Magento\Backend\Model\View\Result\Page|\Magento\Backend\Model\View\Result\Redirect + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function execute() + { + // 1. Get ID + $id = $this->getRequest()->getParam('entity_id'); + $objectInstance = $this->objectFactory->create(); + + // 2. Initial checking + if ($id) { + $objectInstance->load($id); + if (!$objectInstance->getId()) { + $this->messageManager->addErrorMessage(__('This record no longer exists.')); + /** \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + + return $resultRedirect->setPath('*/*/'); + } + } + + // 3. Set entered data if was error when we do save + $data = $this->_session->getFormData(true); + if (!empty($data)) { + $objectInstance->addData($data); + } + + // 4. Register model to use later in blocks + $this->_coreRegistry->register('entity_id', $id); + + + // 5. Build edit form + /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ + $resultPage = $this->resultPageFactory->create(); + $resultPage->setActiveMenu('DevStone_UsageCalculator::size'); + $resultPage->getConfig()->getTitle()->prepend(__('Size Edit')); + + return $resultPage; + } +} diff --git a/Controller/Adminhtml/Size/Index.php b/Controller/Adminhtml/Size/Index.php new file mode 100644 index 0000000..8f0dfcf --- /dev/null +++ b/Controller/Adminhtml/Size/Index.php @@ -0,0 +1,57 @@ +resultPageFactory = $resultPageFactory; + } + + /** + * Check the permission to run it + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::size'); + } + + /** + * Index action + * + * @return \Magento\Backend\Model\View\Result\Page + */ + public function execute() + { + /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ + $resultPage = $this->resultPageFactory->create(); + $resultPage->setActiveMenu('DevStone_UsageCalculator::size'); + $resultPage->getConfig()->getTitle()->prepend(__('Size')); + + return $resultPage; + } +} diff --git a/Controller/Adminhtml/Size/InlineEdit.php b/Controller/Adminhtml/Size/InlineEdit.php new file mode 100644 index 0000000..5a65c3b --- /dev/null +++ b/Controller/Adminhtml/Size/InlineEdit.php @@ -0,0 +1,78 @@ +jsonFactory = $jsonFactory; + $this->objectCollection = $objectCollection; + } + + /** + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->jsonFactory->create(); + $error = false; + $messages = []; + + $postItems = $this->getRequest()->getParam('items', []); + if (!($this->getRequest()->getParam('isAjax') && count($postItems))) { + return $resultJson->setData([ + 'messages' => [__('Please correct the data sent.')], + 'error' => true, + ]); + } + + try { + $this->objectCollection + ->addFieldToFilter('entity_id', array('in' => array_keys($postItems))) + ->walk('saveCollection', array($postItems)); + } catch (\Exception $e) { + $messages[] = __('There was an error saving the data: ') . $e->getMessage(); + $error = true; + } + + return $resultJson->setData([ + 'messages' => $messages, + 'error' => $error + ]); + } +} diff --git a/Controller/Adminhtml/Size/MassDelete.php b/Controller/Adminhtml/Size/MassDelete.php new file mode 100644 index 0000000..c1ac1b5 --- /dev/null +++ b/Controller/Adminhtml/Size/MassDelete.php @@ -0,0 +1,59 @@ +filter = $filter; + $this->objectCollection = $objectCollection; + parent::__construct($context); + } + + /** + * Execute action + * + * @return \Magento\Backend\Model\View\Result\Redirect + * @throws \Magento\Framework\Exception\LocalizedException|\Exception + */ + public function execute() + { + $collection = $this->filter->getCollection($this->objectCollection); + $collectionSize = $collection->getSize(); + $collection->walk('delete'); + + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been deleted.', $collectionSize)); + + /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + return $resultRedirect->setPath('*/*/'); + } +} diff --git a/Controller/Adminhtml/Size/Save.php b/Controller/Adminhtml/Size/Save.php new file mode 100644 index 0000000..15f190e --- /dev/null +++ b/Controller/Adminhtml/Size/Save.php @@ -0,0 +1,85 @@ +objectFactory = $objectFactory; + parent::__construct($context); + } + + /** + * {@inheritdoc} + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::size'); + } + + /** + * Save action + * + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + $data = $this->getRequest()->getParams(); + /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + if ($data) { + $params = []; + $objectInstance = $this->objectFactory->create(); + $idField = $objectInstance->getIdFieldName(); + if (empty($data[$idField])) { + $data[$idField] = null; + } else { + $objectInstance->load($data[$idField]); + $params[$idField] = $data[$idField]; + } + $objectInstance->addData($data); + + $this->_eventManager->dispatch( + 'devstone_usagecalculator_size_prepare_save', + ['object' => $this->objectFactory, 'request' => $this->getRequest()] + ); + + try { + $objectInstance->save(); + $this->messageManager->addSuccessMessage(__('You saved this record.')); + $this->_getSession()->setFormData(false); + if ($this->getRequest()->getParam('back')) { + $params = [$idField => $objectInstance->getId(), '_current' => true]; + return $resultRedirect->setPath('*/*/edit', $params); + } + return $resultRedirect->setPath('*/*/'); + } catch (\Exception $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the record.')); + } + + $this->_getSession()->setFormData($this->getRequest()->getPostValue()); + return $resultRedirect->setPath('*/*/edit', $params); + } + return $resultRedirect->setPath('*/*/'); + } +} diff --git a/Controller/Adminhtml/Size/Validate.php b/Controller/Adminhtml/Size/Validate.php new file mode 100644 index 0000000..3e2ab8a --- /dev/null +++ b/Controller/Adminhtml/Size/Validate.php @@ -0,0 +1,84 @@ +jsonFactory = $jsonFactory; + $this->response = new \Magento\Framework\DataObject(); + } + + /** + * Check if required fields is not empty + * + * @param array $data + */ + public function validateRequireEntries(array $data) + { + $requiredFields = [ + 'identifier' => __('Size Identifier'), + ]; + foreach ($data as $field => $value) { + if (in_array($field, array_keys($requiredFields)) && $value == '') { + $this->_addErrorMessage( + __('To apply changes you should fill in required "%1" field', $requiredFields[$field]) + ); + } + } + } + + /** + * Add error message validation + * + * @param $message + */ + protected function _addErrorMessage($message) + { + $this->response->setError(true); + if (!is_array($this->response->getMessages())) { + $this->response->setMessages([]); + } + $messages = $this->response->getMessages(); + $messages[] = $message; + $this->response->setMessages($messages); + } + + /** + * AJAX customer validation action + * + * @return \Magento\Framework\Controller\Result\Json + */ + public function execute() + { + $this->response->setError(0); + + $this->validateRequireEntries($this->getRequest()->getParams()); + + $resultJson = $this->jsonFactory->create()->setData($this->response); + return $resultJson; + } +} diff --git a/Controller/Adminhtml/Usage/Delete.php b/Controller/Adminhtml/Usage/Delete.php new file mode 100644 index 0000000..f9d0ea6 --- /dev/null +++ b/Controller/Adminhtml/Usage/Delete.php @@ -0,0 +1,63 @@ +objectFactory = $objectFactory; + parent::__construct($context); + } + + /** + * {@inheritdoc} + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::usage'); + } + + /** + * Delete action + * + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + $resultRedirect = $this->resultRedirectFactory->create(); + $id = $this->getRequest()->getParam('entity_id', null); + + try { + $objectInstance = $this->objectFactory->create()->load($id); + if ($objectInstance->getId()) { + $objectInstance->delete(); + $this->messageManager->addSuccessMessage(__('You deleted the record.')); + } else { + $this->messageManager->addErrorMessage(__('Record does not exist.')); + } + } catch (\Exception $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); + } + + return $resultRedirect->setPath('*/*'); + } +} diff --git a/Controller/Adminhtml/Usage/Edit.php b/Controller/Adminhtml/Usage/Edit.php new file mode 100644 index 0000000..efc36db --- /dev/null +++ b/Controller/Adminhtml/Usage/Edit.php @@ -0,0 +1,100 @@ +resultPageFactory = $resultPageFactory; + $this->_coreRegistry = $registry; + $this->objectFactory = $objectFactory; + parent::__construct($context); + } + + /** + * {@inheritdoc} + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::usage'); + } + + /** + * Edit + * + * @return \Magento\Backend\Model\View\Result\Page|\Magento\Backend\Model\View\Result\Redirect + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function execute() + { + // 1. Get ID + $id = $this->getRequest()->getParam('entity_id'); + $objectInstance = $this->objectFactory->create(); + + // 2. Initial checking + if ($id) { + $objectInstance->load($id); + if (!$objectInstance->getId()) { + $this->messageManager->addErrorMessage(__('This record no longer exists.')); + /** \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + + return $resultRedirect->setPath('*/*/'); + } + } + + // 3. Set entered data if was error when we do save + $data = $this->_session->getFormData(true); + if (!empty($data)) { + $objectInstance->addData($data); + } + + // 4. Register model to use later in blocks + $this->_coreRegistry->register('entity_id', $id); + + // 5. Build edit form + /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ + $resultPage = $this->resultPageFactory->create(); + $resultPage->setActiveMenu('DevStone_UsageCalculator::usage'); + $resultPage->getConfig()->getTitle()->prepend(__('Usage Edit')); + + return $resultPage; + } +} diff --git a/Controller/Adminhtml/Usage/Index.php b/Controller/Adminhtml/Usage/Index.php new file mode 100644 index 0000000..c786b05 --- /dev/null +++ b/Controller/Adminhtml/Usage/Index.php @@ -0,0 +1,57 @@ +resultPageFactory = $resultPageFactory; + } + + /** + * Check the permission to run it + * + * @return boolean + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::usage'); + } + + /** + * Index action + * + * @return \Magento\Backend\Model\View\Result\Page + */ + public function execute() + { + /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ + $resultPage = $this->resultPageFactory->create(); + $resultPage->setActiveMenu('DevStone_UsageCalculator::usage'); + $resultPage->getConfig()->getTitle()->prepend(__('Usage')); + + return $resultPage; + } +} diff --git a/Controller/Adminhtml/Usage/Initialization/Helper.php b/Controller/Adminhtml/Usage/Initialization/Helper.php new file mode 100644 index 0000000..1e938af --- /dev/null +++ b/Controller/Adminhtml/Usage/Initialization/Helper.php @@ -0,0 +1,217 @@ +request = $request; + $this->customOptionFactory = $customOptionFactory; + } + + /** + * Initialize usage from data + * + * @param \DevStone\UsageCalculator\Model\Usage $usage + * @param array $usageData + * @return \DevStone\UsageCalculator\Model\Usage + */ + public function initializeFromData( + \DevStone\UsageCalculator\Model\Usage $usage, + array $usageData + ) { + + +// $usageData = $this->normalize($usageData); + + if (isset($usageData['options'])) { + $usageOptions = $usageData['options']; + unset($usageData['options']); + } else { + $usageOptions = []; + } + + + $usage = $this->fillProductOptions($usage, $usageOptions); + + $usage->setCanSaveCustomOptions( + !empty($usageData['affect_usage_custom_options']) && !$usage->getOptionsReadonly() + ); + + return $usage; + } + + /** + * Initialize usage before saving + * + * @param \DevStone\UsageCalculator\Model\Usage $usage + * @return \DevStone\UsageCalculator\Model\Usage + */ + public function initialize( \DevStone\UsageCalculator\Model\Usage $usage) + { + $usageData = $this->request->getPost('usage', []); + return $this->initializeFromData($usage, $usageData); + } + + /** + * Internal normalization + * + * @param array $productData + * @return array + * @todo Remove this method + * @since 101.0.0 + */ + protected function normalize(array $productData) + { + foreach ($productData as $key => $value) { + if (is_scalar($value)) { + if ($value === 'true') { + $productData[$key] = '1'; + } elseif ($value === 'false') { + $productData[$key] = '0'; + } + } elseif (is_array($value)) { + $productData[$key] = $this->normalize($value); + } + } + + return $productData; + } + + /** + * Merge product and default options for product + * + * @param array $productOptions product options + * @param array $overwriteOptions default value options + * @return array + */ + public function mergeProductOptions($productOptions, $overwriteOptions) + { + if (!is_array($productOptions)) { + return []; + } + + if (!is_array($overwriteOptions)) { + return $productOptions; + } + + foreach ($productOptions as $optionIndex => $option) { + $optionId = $option['option_id']; + $option = $this->overwriteValue($optionId, $option, $overwriteOptions); + + if (isset($option['values']) && isset($overwriteOptions[$optionId]['values'])) { + foreach ($option['values'] as $valueIndex => $value) { + if (isset($value['option_type_id'])) { + $valueId = $value['option_type_id']; + $value = $this->overwriteValue($valueId, $value, $overwriteOptions[$optionId]['values']); + $option['values'][$valueIndex] = $value; + } + } + } + + $productOptions[$optionIndex] = $option; + } + + return $productOptions; + } + + /** + * Overwrite values of fields to default, if there are option id and field name in array overwriteOptions + * + * @param int $optionId + * @param array $option + * @param array $overwriteOptions + * @return array + */ + private function overwriteValue($optionId, $option, $overwriteOptions) + { + if (isset($overwriteOptions[$optionId])) { + foreach ($overwriteOptions[$optionId] as $fieldName => $overwrite) { + if ($overwrite && isset($option[$fieldName]) && isset($option['default_' . $fieldName])) { + $option[$fieldName] = $option['default_' . $fieldName]; + if ('title' == $fieldName) { + $option['is_delete_store_title'] = 1; + } + if ('help' == $fieldName) { + $option['is_delete_store_help'] = 1; + } + } + } + } + + return $option; + } + + + /** + * Fills $product with options from $productOptions array + * + * @param \DevStone\UsageCalculator\Model\Usage $usage + * @param array $usageOptions + * @return \DevStone\UsageCalculator\Model\Usage + */ + private function fillProductOptions( + \DevStone\UsageCalculator\Model\Usage $usage, + array $usageOptions + ) { + if ($usage->getOptionsReadonly()) { + return $product; + } + + if (empty($usageOptions)) { + return $usage->setOptions([]); + } + + // mark custom options that should to fall back to default value + $options = $this->mergeProductOptions( + $usageOptions, + $this->request->getPost('options_use_default') + ); + $customOptions = []; + foreach ($options as $customOptionData) { + if (!empty($customOptionData['is_delete'])) { + continue; + } + + if (empty($customOptionData['option_id'])) { + $customOptionData['option_id'] = null; + } + + if (isset($customOptionData['values'])) { + $customOptionData['values'] = array_filter($customOptionData['values'], function ($valueData) { + return empty($valueData['is_delete']); + }); + } + + $customOption = $this->customOptionFactory->create(['data' => $customOptionData]); + $customOption->setUsageId($usage->getId()); + $customOptions[] = $customOption; + } + + return $usage->setOptions($customOptions); + } +} diff --git a/Controller/Adminhtml/Usage/InlineEdit.php b/Controller/Adminhtml/Usage/InlineEdit.php new file mode 100644 index 0000000..0debeeb --- /dev/null +++ b/Controller/Adminhtml/Usage/InlineEdit.php @@ -0,0 +1,79 @@ +jsonFactory = $jsonFactory; + $this->objectCollection = $objectCollection; + } + + /** + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + /** @var \Magento\Framework\Controller\Result\Json $resultJson */ + $resultJson = $this->jsonFactory->create(); + $error = false; + $messages = []; + + $postItems = $this->getRequest()->getParam('items', []); + if (!($this->getRequest()->getParam('isAjax') && count($postItems))) { + return $resultJson->setData([ + 'messages' => [__('Please correct the data sent.')], + 'error' => true, + ]); + } + + try { + $this->objectCollection + ->setStoreId($this->getRequest()->getParam('store', 0)) + ->addFieldToFilter('entity_id', ['in' => array_keys($postItems)]) + ->walk('saveCollection', [$postItems]); + } catch (\Exception $e) { + $messages[] = __('There was an error saving the data: ') . $e->getMessage(); + $error = true; + } + + return $resultJson->setData([ + 'messages' => $messages, + 'error' => $error + ]); + } +} diff --git a/Controller/Adminhtml/Usage/MassDelete.php b/Controller/Adminhtml/Usage/MassDelete.php new file mode 100644 index 0000000..486ac4f --- /dev/null +++ b/Controller/Adminhtml/Usage/MassDelete.php @@ -0,0 +1,59 @@ +filter = $filter; + $this->objectCollection = $objectCollection; + parent::__construct($context); + } + + /** + * Execute action + * + * @return \Magento\Backend\Model\View\Result\Redirect + * @throws \Magento\Framework\Exception\LocalizedException|\Exception + */ + public function execute() + { + $collection = $this->filter->getCollection($this->objectCollection); + $collectionSize = $collection->getSize(); + $collection->walk('delete'); + + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been deleted.', $collectionSize)); + + /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + return $resultRedirect->setPath('*/*/'); + } +} diff --git a/Controller/Adminhtml/Usage/Save.php b/Controller/Adminhtml/Usage/Save.php new file mode 100644 index 0000000..e8aa06c --- /dev/null +++ b/Controller/Adminhtml/Usage/Save.php @@ -0,0 +1,98 @@ +objectFactory = $objectFactory; + $this->helper = $helper; + parent::__construct($context); + } + + /** + * {@inheritdoc} + */ + protected function _isAllowed() + { + return $this->_authorization->isAllowed('DevStone_UsageCalculator::usage'); + } + + /** + * Save action + * + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + $storeId = (int)$this->getRequest()->getParam('store_id'); + $data = $this->getRequest()->getParams(); + /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + if ($data) { + $params = []; + $objectInstance = $this->objectFactory->create(); + $objectInstance->setStoreId($storeId); + $params['store'] = $storeId; + if (empty($data['entity_id'])) { + $data['entity_id'] = null; + } else { + $objectInstance->load($data['entity_id']); + $params['entity_id'] = $data['entity_id']; + } + $objectInstance->addData($data); + + $this->_eventManager->dispatch( + 'devstone_usagecalculator_usage_prepare_save', + ['object' => $this->objectFactory, 'request' => $this->getRequest()] + ); + + $objectInstance = $this->helper->initialize($objectInstance); + + try { + $objectInstance->save(); + $this->messageManager->addSuccessMessage(__('You saved this record.')); + $this->_getSession()->setFormData(false); + if ($this->getRequest()->getParam('back')) { + $params['entity_id'] = $objectInstance->getId(); + $params['_current'] = true; + return $resultRedirect->setPath('*/*/edit', $params); + } + return $resultRedirect->setPath('*/*/'); + } catch (\Exception $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the record.')); + } + + $this->_getSession()->setFormData($this->getRequest()->getPostValue()); + return $resultRedirect->setPath('*/*/edit', $params); + } + return $resultRedirect->setPath('*/*/'); + } + +} diff --git a/Controller/Adminhtml/Usage/Validate.php b/Controller/Adminhtml/Usage/Validate.php new file mode 100644 index 0000000..093550b --- /dev/null +++ b/Controller/Adminhtml/Usage/Validate.php @@ -0,0 +1,84 @@ +jsonFactory = $jsonFactory; + $this->response = new \Magento\Framework\DataObject(); + } + + /** + * Check if required fields is not empty + * + * @param array $data + */ + public function validateRequireEntries(array $data) + { + $requiredFields = [ + 'identifier' => __('Usage Identifier'), + ]; + foreach ($data as $field => $value) { + if (in_array($field, array_keys($requiredFields)) && $value == '') { + $this->_addErrorMessage( + __('To apply changes you should fill in required "%1" field', $requiredFields[$field]) + ); + } + } + } + + /** + * Add error message validation + * + * @param $message + */ + protected function _addErrorMessage($message) + { + $this->response->setError(true); + if (!is_array($this->response->getMessages())) { + $this->response->setMessages([]); + } + $messages = $this->response->getMessages(); + $messages[] = $message; + $this->response->setMessages($messages); + } + + /** + * AJAX customer validation action + * + * @return \Magento\Framework\Controller\Result\Json + */ + public function execute() + { + $this->response->setError(0); + + $this->validateRequireEntries($this->getRequest()->getParams()); + + $resultJson = $this->jsonFactory->create()->setData($this->response); + return $resultJson; + } +} diff --git a/Model/Category.php b/Model/Category.php new file mode 100644 index 0000000..fcf1334 --- /dev/null +++ b/Model/Category.php @@ -0,0 +1,79 @@ +_init(\DevStone\UsageCalculator\Model\ResourceModel\Category::class); + } + + /** + * Get identities + * + * @return array + */ + public function getIdentities() + { + return [self::CACHE_TAG . '_' . $this->getId()]; + } + + /** + * Save from collection data + * + * @param array $data + * @return $this|bool + */ + public function saveCollection(array $data) + { + if (isset($data[$this->getId()])) { + $this->addData($data[$this->getId()]); + $this->getResource()->save($this); + } + return $this; + } + + public function setName($name) { + $this->setData(self::NAME, $name); + return $this; + } + + public function getName() + { + return $this->getData(self::NAME); + } +} diff --git a/Model/CategoryRepository.php b/Model/CategoryRepository.php new file mode 100644 index 0000000..9ca3f31 --- /dev/null +++ b/Model/CategoryRepository.php @@ -0,0 +1,197 @@ +resource = $resource; + $this->categoryFactory = $categoryFactory; + $this->categoryCollectionFactory = $categoryCollectionFactory; + $this->searchResultsFactory = $searchResultsFactory; + $this->dataObjectHelper = $dataObjectHelper; + $this->dataObjectProcessor = $dataObjectProcessor; + $this->storeManager = $storeManager; + } + + /** + * {@inheritdoc} + */ + public function save( + CategoryInterface $category + ) { + /* if (empty($category->getStoreId())) { + $storeId = $this->storeManager->getStore()->getId(); + $category->setStoreId($storeId); + } */ + try { + $category->getResource()->save($category); + } catch (\Exception $exception) { + throw new CouldNotSaveException(__( + 'Could not save the category: %1', + $exception->getMessage() + )); + } + return $category; + } + + /** + * {@inheritdoc} + */ + public function getById($categoryId) + { + $category = $this->categoryFactory->create(); + $category->getResource()->load($category, $categoryId); + if (!$category->getId()) { + throw new NoSuchEntityException(__('Category with id "%1" does not exist.', $categoryId)); + } + return $category; + } + + /** + * {@inheritdoc} + */ + public function getList( + \Magento\Framework\Api\SearchCriteriaInterface $criteria + ) { + $collection = $this->categoryCollectionFactory->create(); + + foreach ($criteria->getFilterGroups() as $filterGroup) { + foreach ($filterGroup->getFilters() as $filter) { + if ($filter->getField() === 'store_id') { + $collection->addStoreFilter($filter->getValue(), false); + continue; + } + $condition = $filter->getConditionType() ?: 'eq'; + $collection->addFieldToFilter($filter->getField(), [$condition => $filter->getValue()]); + } + } + + $sortOrders = $criteria->getSortOrders(); + if ($sortOrders) { + /** @var SortOrder $sortOrder */ + foreach ($sortOrders as $sortOrder) { + $collection->addOrder( + $sortOrder->getField(), + ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC' + ); + } + } + $collection->setCurPage($criteria->getCurrentPage()); + $collection->setPageSize($criteria->getPageSize()); + + $searchResults = $this->searchResultsFactory->create(); + $searchResults->setSearchCriteria($criteria); + $searchResults->setTotalCount($collection->getSize()); + $searchResults->setItems($collection->getItems()); + return $searchResults; + +// +// $searchResults = $this->searchResultsFactory->create(); +// $searchResults->setSearchCriteria($searchCriteria); +// /** @var \Magento\Customer\Model\ResourceModel\Customer\Collection $collection */ +// $collection = $this->customerFactory->create()->getCollection(); +// $this->extensionAttributesJoinProcessor->process( +// $collection, +// \Magento\Customer\Api\Data\CustomerInterface::class +// ); +// // This is needed to make sure all the attributes are properly loaded +// foreach ($this->customerMetadata->getAllAttributesMetadata() as $metadata) { +// $collection->addAttributeToSelect($metadata->getAttributeCode()); +// } +// // Needed to enable filtering on name as a whole +// $collection->addNameToSelect(); +// // Needed to enable filtering based on billing address attributes +// $collection->joinAttribute('billing_postcode', 'customer_address/postcode', 'default_billing', null, 'left') +// ->joinAttribute('billing_city', 'customer_address/city', 'default_billing', null, 'left') +// ->joinAttribute('billing_telephone', 'customer_address/telephone', 'default_billing', null, 'left') +// ->joinAttribute('billing_region', 'customer_address/region', 'default_billing', null, 'left') +// ->joinAttribute('billing_country_id', 'customer_address/country_id', 'default_billing', null, 'left') +// ->joinAttribute('company', 'customer_address/company', 'default_billing', null, 'left'); +// +// $this->collectionProcessor->process($searchCriteria, $collection); +// +// $searchResults->setTotalCount($collection->getSize()); +// +// $customers = []; +// /** @var \Magento\Customer\Model\Customer $customerModel */ +// foreach ($collection as $customerModel) { +// $customers[] = $customerModel->getDataModel(); +// } +// $searchResults->setItems($customers); +// return $searchResults; + + } + + /** + * {@inheritdoc} + */ + public function delete( + CategoryInterface $category + ) { + try { + $category->getResource()->delete($category); + } catch (\Exception $exception) { + throw new CouldNotDeleteException(__( + 'Could not delete the Category: %1', + $exception->getMessage() + )); + } + return true; + } + + /** + * {@inheritdoc} + */ + public function deleteById($categoryId) + { + return $this->delete($this->getById($categoryId)); + } +} diff --git a/Model/Metadata/UsageCachedMetadata.php b/Model/Metadata/UsageCachedMetadata.php new file mode 100644 index 0000000..2525e37 --- /dev/null +++ b/Model/Metadata/UsageCachedMetadata.php @@ -0,0 +1,31 @@ +_eavConfig = $eavConfig; + parent::__construct($context, $storeManager, $eavEntityType, $connectionName); + } + + /** + * Perform actions after object save + * + * @param AbstractModel $object + * @return $this + */ + protected function _afterSave(AbstractModel $object) + { + $this->_clearUselessAttributeValues($object); + return parent::_afterSave($object); + } + + /** + * Clear useless attribute values + * + * @param AbstractModel $object + * @return $this + */ + protected function _clearUselessAttributeValues(AbstractModel $object) + { + $origData = $object->getOrigData(); + + if ($object->isScopeGlobal() && isset( + $origData['is_global'] + ) && ScopedAttributeInterface::SCOPE_GLOBAL != $origData['is_global'] + ) { + $attributeStoreIds = array_keys($this->_storeManager->getStores()); + if (!empty($attributeStoreIds)) { + $delCondition = [ + 'attribute_id = ?' => $object->getId(), + 'store_id IN(?)' => $attributeStoreIds, + ]; + $this->getConnection()->delete($object->getBackendTable(), $delCondition); + } + } + + return $this; + } + + /** + * Delete entity + * + * @param AbstractModel $object + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function deleteEntity(AbstractModel $object) + { + if (!$object->getEntityAttributeId()) { + return $this; + } + + $select = $this->getConnection()->select()->from( + $this->getTable('eav_entity_attribute') + )->where( + 'entity_attribute_id = ?', + (int)$object->getEntityAttributeId() + ); + $result = $this->getConnection()->fetchRow($select); + + if ($result) { + $attribute = $this->_eavConfig->getAttribute( + CategorySetup::ENTITY_TYPE_CODE, + $result['attribute_id'] + ); + + $backendTable = $attribute->getBackend()->getTable(); + if ($backendTable) { + $select = $this->getConnection()->select()->from( + $attribute->getEntity()->getEntityTable(), + 'entity_id' + )->where( + 'attribute_set_id = ?', + $result['attribute_set_id'] + ); + + $clearCondition = [ + 'attribute_id =?' => $attribute->getId(), + 'entity_id IN (?)' => $select, + ]; + $this->getConnection()->delete($backendTable, $clearCondition); + } + } + + $condition = ['entity_attribute_id = ?' => $object->getEntityAttributeId()]; + $this->getConnection()->delete($this->getTable('eav_entity_attribute'), $condition); + + return $this; + } +} diff --git a/Model/ResourceModel/Attribute/Collection.php b/Model/ResourceModel/Attribute/Collection.php new file mode 100644 index 0000000..a8f4ffa --- /dev/null +++ b/Model/ResourceModel/Attribute/Collection.php @@ -0,0 +1,104 @@ +_eavEntityFactory = $eavEntityFactory; + parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $eavConfig, $connection, $resource); + } + + /** + * Main select object initialization. + * + * @return $this + */ + protected function _initSelect() + { + $this->getSelect()->from( + ['main_table' => $this->getResource()->getMainTable()] + )->where( + 'main_table.entity_type_id=?', + $this->_eavEntityFactory->create()->setType(UsageSetup::ENTITY_TYPE_CODE)->getTypeId() + )->join( + ['additional_table' => $this->getTable(UsageSetup::ENTITY_TYPE_CODE . '_eav_attribute')], + 'additional_table.attribute_id = main_table.attribute_id' + ); + return $this; + } + + /** + * @return $this + */ + public function getFilterAttributesOnly() + { + $this->getSelect()->where('additional_table.is_filterable', 1); + return $this; + } + + /** + * @param int $status + * @return $this + */ + public function addVisibilityFilter($status = 1) + { + $this->getSelect()->where('additional_table.is_visible', $status); + return $this; + } + + /** + * Specify attribute entity type filter + * + * @param int $typeId + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setEntityTypeFilter($typeId) + { + return $this; + } +} diff --git a/Model/ResourceModel/Category.php b/Model/ResourceModel/Category.php new file mode 100644 index 0000000..cb4c258 --- /dev/null +++ b/Model/ResourceModel/Category.php @@ -0,0 +1,24 @@ +_init('devstone_usage_category', 'entity_id'); + } +} diff --git a/Model/ResourceModel/Category/Collection.php b/Model/ResourceModel/Category/Collection.php new file mode 100644 index 0000000..01cdca2 --- /dev/null +++ b/Model/ResourceModel/Category/Collection.php @@ -0,0 +1,31 @@ +_init( + \DevStone\UsageCalculator\Model\Category::class, + \DevStone\UsageCalculator\Model\ResourceModel\Category::class + ); + } +} diff --git a/Model/ResourceModel/Category/Grid/Collection.php b/Model/ResourceModel/Category/Grid/Collection.php new file mode 100644 index 0000000..05b9027 --- /dev/null +++ b/Model/ResourceModel/Category/Grid/Collection.php @@ -0,0 +1,160 @@ +_eventPrefix = $eventPrefix; + $this->_eventObject = $eventObject; + $this->_init($model, $resourceModel); + $this->setMainTable($mainTable); + } + + /** + * @return AggregationInterface + */ + public function getAggregations() + { + return $this->aggregations; + } + + /** + * @param AggregationInterface $aggregations + * @return $this + */ + public function setAggregations($aggregations) + { + $this->aggregations = $aggregations; + } + + + /** + * Retrieve all ids for collection + * Backward compatibility with EAV collection + * + * @param int $limit + * @param int $offset + * @return array + */ + public function getAllIds($limit = null, $offset = null) + { + return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams); + } + + /** + * Get search criteria. + * + * @return SearchCriteriaInterface|null + */ + public function getSearchCriteria() + { + return null; + } + + /** + * Set search criteria. + * + * @param SearchCriteriaInterface $searchCriteria + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setSearchCriteria(SearchCriteriaInterface $searchCriteria = null) + { + return $this; + } + + /** + * Get total count. + * + * @return int + */ + public function getTotalCount() + { + return $this->getSize(); + } + + /** + * Set total count. + * + * @param int $totalCount + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setTotalCount($totalCount) + { + return $this; + } + + /** + * Set items list. + * + * @param \Magento\Framework\Api\ExtensibleDataInterface[] $items + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setItems(array $items = null) + { + return $this; + } +} diff --git a/Model/ResourceModel/Eav/Attribute.php b/Model/ResourceModel/Eav/Attribute.php new file mode 100644 index 0000000..6be7960 --- /dev/null +++ b/Model/ResourceModel/Eav/Attribute.php @@ -0,0 +1,180 @@ +_init('DevStone\UsageCalculator\Model\ResourceModel\Attribute'); + } + + /** + * Processing object before save data + * + * @return \Magento\Framework\Model\AbstractModel + * @throws LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function beforeSave() //@codingStandardsIgnoreLine + { + $this->setData('modulePrefix', self::MODULE_NAME); + if (isset($this->_origData[self::KEY_IS_GLOBAL])) { + if (!isset($this->_data[self::KEY_IS_GLOBAL])) { + $this->_data[self::KEY_IS_GLOBAL] = self::SCOPE_GLOBAL; + } + } + return parent::beforeSave(); + } + + /** + * Processing object after save data + * + * @return \Magento\Framework\Model\AbstractModel + */ + public function afterSave() //@codingStandardsIgnoreLine + { + /** + * Fix saving attribute in admin + */ + $this->_eavConfig->clear(); + return parent::afterSave(); + } + + /** + * Return is attribute global + * + * @return integer + */ + public function getIsGlobal() + { + if ($this->getBackendType() === self::KEY_IS_STATIC) { + return true; + } + return $this->_getData(self::KEY_IS_GLOBAL); + } + + /** + * Retrieve attribute is global scope flag + * + * @return bool + */ + public function isScopeGlobal() + { + return $this->getIsGlobal() == self::SCOPE_GLOBAL; + } + + /** + * Retrieve attribute is website scope website + * + * @return bool + */ + public function isScopeWebsite() + { + return $this->getIsGlobal() == self::SCOPE_WEBSITE; + } + + /** + * Retrieve attribute is store scope flag + * + * @return bool + */ + public function isScopeStore() + { + return !$this->isScopeGlobal() && !$this->isScopeWebsite(); + } + + /** + * Retrieve store id + * + * @return int + */ + public function getStoreId() + { + $dataObject = $this->getDataObject(); + if ($dataObject) { + return $dataObject->getStoreId(); + } + return $this->getData('store_id'); + } + + /** + * Retrieve source model + * + * @return \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource + */ + public function getSourceModel() + { + $model = $this->getData('source_model'); + if (empty($model)) { + if ($this->getBackendType() == 'int' && $this->getFrontendInput() == 'select') { + return $this->_getDefaultSourceModel(); + } + } + return $model; + } + + /** + * Get default attribute source model + * + * @return string + */ + public function _getDefaultSourceModel() + { + return 'Magento\Eav\Model\Entity\Attribute\Source\Table'; + } + + /** + * {@inheritdoc} + */ + public function afterDelete() //@codingStandardsIgnoreLine + { + $this->_eavConfig->clear(); + return parent::afterDelete(); + } +} diff --git a/Model/ResourceModel/Size.php b/Model/ResourceModel/Size.php new file mode 100644 index 0000000..d603e31 --- /dev/null +++ b/Model/ResourceModel/Size.php @@ -0,0 +1,24 @@ +_init('devstone_downloadable_image_size', 'entity_id'); + } +} diff --git a/Model/ResourceModel/Size/Collection.php b/Model/ResourceModel/Size/Collection.php new file mode 100644 index 0000000..8526272 --- /dev/null +++ b/Model/ResourceModel/Size/Collection.php @@ -0,0 +1,28 @@ +_init(\DevStone\UsageCalculator\Model\Size::class, \DevStone\UsageCalculator\Model\ResourceModel\Size::class); + } +} diff --git a/Model/ResourceModel/Size/Grid/Collection.php b/Model/ResourceModel/Size/Grid/Collection.php new file mode 100644 index 0000000..2066140 --- /dev/null +++ b/Model/ResourceModel/Size/Grid/Collection.php @@ -0,0 +1,160 @@ +_eventPrefix = $eventPrefix; + $this->_eventObject = $eventObject; + $this->_init($model, $resourceModel); + $this->setMainTable($mainTable); + } + + /** + * @return AggregationInterface + */ + public function getAggregations() + { + return $this->aggregations; + } + + /** + * @param AggregationInterface $aggregations + * @return $this + */ + public function setAggregations($aggregations) + { + $this->aggregations = $aggregations; + } + + + /** + * Retrieve all ids for collection + * Backward compatibility with EAV collection + * + * @param int $limit + * @param int $offset + * @return array + */ + public function getAllIds($limit = null, $offset = null) + { + return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams); + } + + /** + * Get search criteria. + * + * @return SearchCriteriaInterface|null + */ + public function getSearchCriteria() + { + return null; + } + + /** + * Set search criteria. + * + * @param SearchCriteriaInterface $searchCriteria + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setSearchCriteria(SearchCriteriaInterface $searchCriteria = null) + { + return $this; + } + + /** + * Get total count. + * + * @return int + */ + public function getTotalCount() + { + return $this->getSize(); + } + + /** + * Set total count. + * + * @param int $totalCount + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setTotalCount($totalCount) + { + return $this; + } + + /** + * Set items list. + * + * @param \Magento\Framework\Api\ExtensibleDataInterface[] $items + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setItems(array $items = null) + { + return $this; + } +} diff --git a/Model/ResourceModel/Usage.php b/Model/ResourceModel/Usage.php new file mode 100644 index 0000000..33845b6 --- /dev/null +++ b/Model/ResourceModel/Usage.php @@ -0,0 +1,141 @@ +setType(UsageSetup::ENTITY_TYPE_CODE); + $this->setConnection(UsageSetup::ENTITY_TYPE_CODE . '_read', UsageSetup::ENTITY_TYPE_CODE . '_write'); + $this->_storeManager = $storeManager; + } + + /** + * Retrieve employee entity default attributes + * + * @return string[] + */ + protected function _getDefaultAttributes() + { + return [ + 'created_at', + 'updated_at', + 'size_id', + 'category_id', + ]; + } + + /** + * Set store Id + * + * @param integer $storeId + * @return $this + */ + public function setStoreId($storeId) + { + $this->_storeId = $storeId; + return $this; + } + + /** + * Return store id + * + * @return integer + */ + public function getStoreId() + { + if ($this->_storeId === null) { + return $this->_storeManager->getStore()->getId(); + } + return $this->_storeId; + } + + /** + * Set Attribute values to be saved + * + * @param \Magento\Framework\Model\AbstractModel $object + * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute + * @param mixed $value + * @return $this + */ + protected function _saveAttribute($object, $attribute, $value) + { + $table = $attribute->getBackend()->getTable(); + if (!isset($this->_attributeValuesToSave[$table])) { + $this->_attributeValuesToSave[$table] = []; + } + + $entityIdField = $attribute->getBackend()->getEntityIdField(); + $storeId = $object->getStoreId()?:Store::DEFAULT_STORE_ID; + $data = [ + $entityIdField => $object->getId(), + 'attribute_id' => $attribute->getId(), + 'value' => $this->_prepareValueForSave($value, $attribute), + 'store_id' => $storeId, + ]; + + if (!$this->getEntityTable() || $this->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE) { + $data['entity_type_id'] = $object->getEntityTypeId(); + } + + if ($attribute->isScopeStore()) { + /** + * Update attribute value for store + */ + $this->_attributeValuesToSave[$table][] = $data; + } elseif ($attribute->isScopeWebsite() && $storeId != Store::DEFAULT_STORE_ID) { + /** + * Update attribute value for website + */ + $storeIds = $this->_storeManager->getStore($storeId)->getWebsite()->getStoreIds(true); + foreach ($storeIds as $storeId) { + $data['store_id'] = (int)$storeId; + $this->_attributeValuesToSave[$table][] = $data; + } + } else { + /** + * Update global attribute value + */ + $data['store_id'] = Store::DEFAULT_STORE_ID; + $this->_attributeValuesToSave[$table][] = $data; + } + + return $this; + } +} diff --git a/Model/ResourceModel/Usage/Collection.php b/Model/ResourceModel/Usage/Collection.php new file mode 100644 index 0000000..b578e34 --- /dev/null +++ b/Model/ResourceModel/Usage/Collection.php @@ -0,0 +1,302 @@ +_storeManager = $storeManager; + parent::__construct( + $entityFactory, + $logger, + $fetchStrategy, + $eventManager, + $eavConfig, + $resource, + $eavEntityFactory, + $resourceHelper, + $universalFactory, + $connection + ); + } + + /** + * Define resource model + * + * @return void + */ + protected function _construct() + { + $this->_init(\DevStone\UsageCalculator\Model\Usage::class, \DevStone\UsageCalculator\Model\ResourceModel\Usage::class); + } + + /** + * Set store scope + * + * @param int|string|\Magento\Store\Model\Store $store + * @return $this + */ + public function setStore($store) + { + $this->setStoreId($this->_storeManager->getStore($store)->getId()); + return $this; + } + + /** + * Set store scope + * + * @param int|string|\Magento\Store\Api\Data\StoreInterface $storeId + * @return $this + */ + public function setStoreId($storeId) + { + if ($storeId instanceof \Magento\Store\Api\Data\StoreInterface) { + $storeId = $storeId->getId(); + } + $this->_storeId = (int)$storeId; + return $this; + } + + /** + * Return current store id + * + * @return int + */ + public function getStoreId() + { + if ($this->_storeId === null) { + $this->setStoreId($this->_storeManager->getStore()->getId()); + } + return $this->_storeId; + } + + /** + * Retrieve default store id + * + * @return int + */ + public function getDefaultStoreId() + { + return \Magento\Store\Model\Store::DEFAULT_STORE_ID; + } + + /** + * Retrieve attributes load select + * + * @param string $table + * @param array|int $attributeIds + * @return \Magento\Eav\Model\Entity\Collection\AbstractCollection + */ + protected function _getLoadAttributesSelect($table, $attributeIds = []) + { + if (empty($attributeIds)) { + $attributeIds = $this->_selectAttributes; + } + $storeId = $this->getStoreId(); + $connection = $this->getConnection(); + + $entityTable = $this->getEntity()->getEntityTable(); + $indexList = $connection->getIndexList($entityTable); + $entityIdField = $indexList[$connection->getPrimaryKeyName($entityTable)]['COLUMNS_LIST'][0]; + + if ($storeId) { + $joinCondition = [ + 't_s.attribute_id = t_d.attribute_id', + "t_s.{$entityIdField} = t_d.{$entityIdField}", + $connection->quoteInto('t_s.store_id = ?', $storeId), + ]; + + $select = $connection->select()->from( + ['t_d' => $table], + ['attribute_id'] + )->join( + ['e' => $entityTable], + "e.{$entityIdField} = t_d.{$entityIdField}", + ['e.entity_id'] + )->where( + "e.entity_id IN (?)", + array_keys($this->_itemsById) + )->where( + 't_d.attribute_id IN (?)', + $attributeIds + )->joinLeft( + ['t_s' => $table], + implode(' AND ', $joinCondition), + [] + )->where( + 't_d.store_id = ?', + $connection->getIfNullSql('t_s.store_id', \Magento\Store\Model\Store::DEFAULT_STORE_ID) + ); + } else { + $select = $connection->select()->from( + ['t_d' => $table], + ['attribute_id'] + )->join( + ['e' => $entityTable], + "e.{$entityIdField} = t_d.{$entityIdField}", + ['e.entity_id'] + )->where( + "e.entity_id IN (?)", + array_keys($this->_itemsById) + )->where( + 'attribute_id IN (?)', + $attributeIds + )->where( + 'store_id = ?', + $this->getDefaultStoreId() + ); + } + return $select; + } + + /** + * @param \Magento\Framework\DB\Select $select + * @param string $table + * @param string $type + * @return \Magento\Framework\DB\Select + */ + protected function _addLoadAttributesSelectValues($select, $table, $type) + { + $storeId = $this->getStoreId(); + if ($storeId) { + $connection = $this->getConnection(); + $valueExpr = $connection->getCheckSql('t_s.value_id IS NULL', 't_d.value', 't_s.value'); + + $select->columns( + ['default_value' => 't_d.value', 'store_value' => 't_s.value', 'value' => $valueExpr] + ); + } else { + $select = parent::_addLoadAttributesSelectValues($select, $table, $type); + } + return $select; + } + + /** + * Adding join statement to collection select instance + * + * @param string $method + * @param object $attribute + * @param string $tableAlias + * @param array $condition + * @param string $fieldCode + * @param string $fieldAlias + * @return \Magento\Eav\Model\Entity\Collection\AbstractCollection + */ + protected function _joinAttributeToSelect($method, $attribute, $tableAlias, $condition, $fieldCode, $fieldAlias) + { + if (isset($this->_joinAttributes[$fieldCode]['store_id'])) { + $storeId = $this->_joinAttributes[$fieldCode]['store_id']; + } else { + $storeId = $this->getStoreId(); + } + + $connection = $this->getConnection(); + + if ($storeId != $this->getDefaultStoreId() && !$attribute->isScopeGlobal()) { + /** + * Add joining default value for not default store + * if value for store is null - we use default value + */ + $defCondition = '(' . implode(') AND (', $condition) . ')'; + $defAlias = $tableAlias . '_default'; + $defAlias = $this->getConnection()->getTableName($defAlias); + $defFieldAlias = str_replace($tableAlias, $defAlias, $fieldAlias); + $tableAlias = $this->getConnection()->getTableName($tableAlias); + + $defCondition = str_replace($tableAlias, $defAlias, $defCondition); + $defCondition .= $connection->quoteInto( + " AND " . $connection->quoteColumnAs("{$defAlias}.store_id", null) . " = ?", + $this->getDefaultStoreId() + ); + + $this->getSelect()->{$method}( + [$defAlias => $attribute->getBackend()->getTable()], + $defCondition, + [] + ); + + $method = 'joinLeft'; + $fieldAlias = $this->getConnection()->getCheckSql( + "{$tableAlias}.value_id > 0", + $fieldAlias, + $defFieldAlias + ); + $this->_joinAttributes[$fieldCode]['condition_alias'] = $fieldAlias; + $this->_joinAttributes[$fieldCode]['attribute'] = $attribute; + } else { + $storeId = $this->getDefaultStoreId(); + } + $condition[] = $connection->quoteInto( + $connection->quoteColumnAs("{$tableAlias}.store_id", null) . ' = ?', + $storeId + ); + return parent::_joinAttributeToSelect($method, $attribute, $tableAlias, $condition, $fieldCode, $fieldAlias); + } +} diff --git a/Model/ResourceModel/Usage/Grid/Collection.php b/Model/ResourceModel/Usage/Grid/Collection.php new file mode 100644 index 0000000..e563c5e --- /dev/null +++ b/Model/ResourceModel/Usage/Grid/Collection.php @@ -0,0 +1,178 @@ +_eventPrefix = $eventPrefix; + $this->_eventObject = $eventObject; + $this->_init($model, $resourceModel); + } + + /** + * @return AggregationInterface + */ + public function getAggregations() + { + return $this->aggregations; + } + + /** + * @param AggregationInterface $aggregations + * @return $this + */ + public function setAggregations($aggregations) + { + $this->aggregations = $aggregations; + } + + /** + * Retrieve all ids for collection + * Backward compatibility with EAV collection + * + * @param int $limit + * @param int $offset + * @return array + */ + public function getAllIds($limit = null, $offset = null) + { + return $this->getConnection()->fetchCol($this->_getAllIdsSelect($limit, $offset), $this->_bindParams); + } + + /** + * Get search criteria. + * + * @return SearchCriteriaInterface|null + */ + public function getSearchCriteria() + { + return null; + } + + /** + * Set search criteria. + * + * @param SearchCriteriaInterface $searchCriteria + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setSearchCriteria(SearchCriteriaInterface $searchCriteria = null) + { + return $this; + } + + /** + * Get total count. + * + * @return int + */ + public function getTotalCount() + { + return $this->getSize(); + } + + /** + * Set total count. + * + * @param int $totalCount + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setTotalCount($totalCount) + { + return $this; + } + + /** + * Set items list. + * + * @param \Magento\Framework\Api\ExtensibleDataInterface[] $items + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function setItems(array $items = null) + { + return $this; + } +} diff --git a/Model/ResourceModel/Usage/Option.php b/Model/ResourceModel/Usage/Option.php new file mode 100644 index 0000000..d53ec24 --- /dev/null +++ b/Model/ResourceModel/Usage/Option.php @@ -0,0 +1,569 @@ +_currencyFactory = $currencyFactory; + $this->_storeManager = $storeManager; + $this->_config = $config; + parent::__construct($context, $connectionName); + } + + /** + * Define main table and initialize connection + * + * @return void + */ + protected function _construct() + { + $this->_init(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option', 'option_id'); + } + + /** + * Save options store data + * + * @param \Magento\Framework\Model\AbstractModel $object + * @return \Magento\Framework\Model\ResourceModel\Db\AbstractDb + */ + protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) + { + $this->_saveValuePrices($object); + $this->_saveValueTitles($object); + $this->_saveValueHelps($object); + + return parent::_afterSave($object); + } + + /** + * Save value prices + * + * @param \Magento\Framework\Model\AbstractModel $object + * @return $this + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + protected function _saveValuePrices(\Magento\Framework\Model\AbstractModel $object) + { + $priceTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_price'); + $connection = $this->getConnection(); + + /* + * Better to check param 'price' and 'price_type' for saving. + * If there is not price skip saving price + */ + + if (in_array($object->getType(), $this->getPriceTypes())) { + //save for store_id = 0 + if (!$object->getData('scope', 'price')) { + $statement = $connection->select()->from( + $priceTable, + 'option_id' + )->where( + 'option_id = ?', + $object->getId() + )->where( + 'store_id = ?', + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + $optionId = $connection->fetchOne($statement); + + if ($optionId) { + $data = $this->_prepareDataForTable( + new \Magento\Framework\DataObject( + ['price' => $object->getPrice(), 'price_type' => $object->getPriceType()] + ), + $priceTable + ); + + $connection->update( + $priceTable, + $data, + [ + 'option_id = ?' => $object->getId(), + 'store_id = ?' => \Magento\Store\Model\Store::DEFAULT_STORE_ID + ] + ); + } else { + $data = $this->_prepareDataForTable( + new \Magento\Framework\DataObject( + [ + 'option_id' => $object->getId(), + 'store_id' => \Magento\Store\Model\Store::DEFAULT_STORE_ID, + 'price' => $object->getPrice(), + 'price_type' => $object->getPriceType(), + ] + ), + $priceTable + ); + $connection->insert($priceTable, $data); + } + } + + $scope = (int)$this->_config->getValue( + \Magento\Store\Model\Store::XML_PATH_PRICE_SCOPE, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + + if ($object->getStoreId() != '0' && $scope == \Magento\Store\Model\Store::PRICE_SCOPE_WEBSITE) { + $baseCurrency = $this->_config->getValue( + \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE, + 'default' + ); + + $storeIds = $this->_storeManager->getStore($object->getStoreId())->getWebsite()->getStoreIds(); + if (is_array($storeIds)) { + foreach ($storeIds as $storeId) { + if ($object->getPriceType() == 'fixed') { + $storeCurrency = $this->_storeManager->getStore($storeId)->getBaseCurrencyCode(); + $rate = $this->_currencyFactory->create()->load($baseCurrency)->getRate($storeCurrency); + if (!$rate) { + $rate = 1; + } + $newPrice = $object->getPrice() * $rate; + } else { + $newPrice = $object->getPrice(); + } + + $statement = $connection->select()->from( + $priceTable + )->where( + 'option_id = ?', + $object->getId() + )->where( + 'store_id = ?', + $storeId + ); + + if ($connection->fetchOne($statement)) { + $data = $this->_prepareDataForTable( + new \Magento\Framework\DataObject( + ['price' => $newPrice, 'price_type' => $object->getPriceType()] + ), + $priceTable + ); + + $connection->update( + $priceTable, + $data, + ['option_id = ?' => $object->getId(), 'store_id = ?' => $storeId] + ); + } else { + $data = $this->_prepareDataForTable( + new \Magento\Framework\DataObject( + [ + 'option_id' => $object->getId(), + 'store_id' => $storeId, + 'price' => $newPrice, + 'price_type' => $object->getPriceType(), + ] + ), + $priceTable + ); + $connection->insert($priceTable, $data); + } + } + } + } elseif ($scope == \Magento\Store\Model\Store::PRICE_SCOPE_WEBSITE && $object->getData('scope', 'price') + ) { + $connection->delete( + $priceTable, + ['option_id = ?' => $object->getId(), 'store_id = ?' => $object->getStoreId()] + ); + } + } + + return $this; + } + + /** + * Save titles + * + * @param \Magento\Framework\Model\AbstractModel $object + * @return void + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + protected function _saveValueTitles(\Magento\Framework\Model\AbstractModel $object) + { + $connection = $this->getConnection(); + $titleTableName = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_title'); + foreach ([\Magento\Store\Model\Store::DEFAULT_STORE_ID, $object->getStoreId()] as $storeId) { + $existInCurrentStore = $this->getColFromOptionTable($titleTableName, (int)$object->getId(), (int)$storeId); + $existInDefaultStore = (int)$storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID ? + $existInCurrentStore : + $this->getColFromOptionTable( + $titleTableName, + (int)$object->getId(), + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + + if ($object->getTitle()) { + $isDeleteStoreTitle = (bool)$object->getData('is_delete_store_title'); + if ($existInCurrentStore) { + if ($isDeleteStoreTitle && (int)$storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID) { + $connection->delete($titleTableName, ['option_title_id = ?' => $existInCurrentStore]); + } elseif ($object->getStoreId() == $storeId) { + $data = $this->_prepareDataForTable( + new \Magento\Framework\DataObject(['title' => $object->getTitle()]), + $titleTableName + ); + $connection->update( + $titleTableName, + $data, + [ + 'option_id = ?' => $object->getId(), + 'store_id = ?' => $storeId, + ] + ); + } + } else { + // we should insert record into not default store only of if it does not exist in default store + if (($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInDefaultStore) || + ( + $storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && + !$existInCurrentStore && + !$isDeleteStoreTitle + ) + ) { + $data = $this->_prepareDataForTable( + new \Magento\Framework\DataObject( + [ + 'option_id' => $object->getId(), + 'store_id' => $storeId, + 'title' => $object->getTitle(), + ] + ), + $titleTableName + ); + $connection->insert($titleTableName, $data); + } + } + } else { + if ($object->getId() && $object->getStoreId() > \Magento\Store\Model\Store::DEFAULT_STORE_ID + && $storeId + ) { + $connection->delete( + $titleTableName, + [ + 'option_id = ?' => $object->getId(), + 'store_id = ?' => $object->getStoreId(), + ] + ); + } + } + } + } + + /** + * Save titles + * + * @param \Magento\Framework\Model\AbstractModel $object + * @return void + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + protected function _saveValueHelps(\Magento\Framework\Model\AbstractModel $object) + { + $connection = $this->getConnection(); + $titleTableName = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_help'); + foreach ([\Magento\Store\Model\Store::DEFAULT_STORE_ID, $object->getStoreId()] as $storeId) { + $existInCurrentStore = $this->getColFromOptionTable($titleTableName, (int)$object->getId(), (int)$storeId); + $existInDefaultStore = (int)$storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID ? + $existInCurrentStore : + $this->getColFromOptionTable( + $titleTableName, + (int)$object->getId(), + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + + if ($object->getTitle()) { + $isDeleteStoreTitle = (bool)$object->getData('is_delete_store_help'); + if ($existInCurrentStore) { + if ($isDeleteStoreTitle && (int)$storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID) { + $connection->delete($titleTableName, ['option_help_id = ?' => $existInCurrentStore]); + } elseif ($object->getStoreId() == $storeId) { + $data = $this->_prepareDataForTable( + new \Magento\Framework\DataObject(['help' => $object->getHelp()]), + $titleTableName + ); + $connection->update( + $titleTableName, + $data, + [ + 'option_id = ?' => $object->getId(), + 'store_id = ?' => $storeId, + ] + ); + } + } else { + // we should insert record into not default store only of if it does not exist in default store + if (($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID && !$existInDefaultStore) || + ( + $storeId != \Magento\Store\Model\Store::DEFAULT_STORE_ID && + !$existInCurrentStore && + !$isDeleteStoreTitle + ) + ) { + $data = $this->_prepareDataForTable( + new \Magento\Framework\DataObject( + [ + 'option_id' => $object->getId(), + 'store_id' => $storeId, + 'help' => $object->getHelp(), + ] + ), + $titleTableName + ); + $connection->insert($titleTableName, $data); + } + } + } else { + if ($object->getId() && $object->getStoreId() > \Magento\Store\Model\Store::DEFAULT_STORE_ID + && $storeId + ) { + $connection->delete( + $titleTableName, + [ + 'option_id = ?' => $object->getId(), + 'store_id = ?' => $object->getStoreId(), + ] + ); + } + } + } + } + + /** + * Get first col from from first row for option table + * + * @param string $tableName + * @param int $optionId + * @param int $storeId + * @return string + */ + protected function getColFromOptionTable($tableName, $optionId, $storeId) + { + $connection = $this->getConnection(); + $statement = $connection->select()->from( + $tableName + )->where( + 'option_id = ?', + $optionId + )->where( + 'store_id = ?', + $storeId + ); + + return $connection->fetchOne($statement); + } + + /** + * Delete prices + * + * @param int $optionId + * @return $this + */ + public function deletePrices($optionId) + { + $this->getConnection()->delete( + $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_price'), + ['option_id = ?' => $optionId] + ); + + return $this; + } + + /** + * Delete titles + * + * @param int $optionId + * @return $this + */ + public function deleteTitles($optionId) + { + $this->getConnection()->delete( + $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_title'), + ['option_id = ?' => $optionId] + ); + + return $this; + } + + /** + * Delete helps + * + * @param int $optionId + * @return $this + */ + public function deleteHelps($optionId) + { + $this->getConnection()->delete( + $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_help'), + ['option_id = ?' => $optionId] + ); + + return $this; + } + + /** + * Duplicate custom options for product + * + * @param \Magento\Catalog\Model\Product\Option $object + * @param int $oldProductId + * @param int $newProductId + * @return \Magento\Catalog\Model\Product\Option + */ + public function duplicate(\Magento\Catalog\Model\Product\Option $object, $oldProductId, $newProductId) + { + $connection = $this->getConnection(); + + $optionsCond = []; + $optionsData = []; + + // read and prepare original product options + $select = $connection->select()->from( + $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option') + )->where( + 'product_id = ?', + $oldProductId + ); + + $query = $connection->query($select); + + while ($row = $query->fetch()) { + $optionsData[$row['option_id']] = $row; + $optionsData[$row['option_id']]['product_id'] = $newProductId; + unset($optionsData[$row['option_id']]['option_id']); + } + + // insert options to duplicated product + foreach ($optionsData as $oId => $data) { + $connection->insert($this->getMainTable(), $data); + $optionsCond[$oId] = $connection->lastInsertId($this->getMainTable()); + } + + // copy options prefs + foreach ($optionsCond as $oldOptionId => $newOptionId) { + // title + $table = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_title'); + + $select = $this->getConnection()->select()->from( + $table, + [new \Zend_Db_Expr($newOptionId), 'store_id', 'title'] + )->where( + 'option_id = ?', + $oldOptionId + ); + + $insertSelect = $connection->insertFromSelect( + $select, + $table, + ['option_id', 'store_id', 'title'], + \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + ); + $connection->query($insertSelect); + + // help + $table = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_help'); + + $select = $this->getConnection()->select()->from( + $table, + [new \Zend_Db_Expr($newOptionId), 'store_id', 'help'] + )->where( + 'option_id = ?', + $oldOptionId + ); + + $insertSelect = $connection->insertFromSelect( + $select, + $table, + ['option_id', 'store_id', 'help'], + \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + ); + $connection->query($insertSelect); + + // price + $table = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_price'); + + $select = $connection->select()->from( + $table, + [new \Zend_Db_Expr($newOptionId), 'store_id', 'price', 'price_type'] + )->where( + 'option_id = ?', + $oldOptionId + ); + + $insertSelect = $connection->insertFromSelect( + $select, + $table, + ['option_id', 'store_id', 'price', 'price_type'], + \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + ); + $connection->query($insertSelect); + + $object->getValueInstance()->duplicate($oldOptionId, $newOptionId); + } + + return $object; + } + + /** + * All Option Types that support price and price_type + * + * @return string[] + */ + public function getPriceTypes() + { + return [ + \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_FIELD, + \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_AREA, + \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_FILE, + \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE, + \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME, + \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_TIME, + ]; + } + +} diff --git a/Model/ResourceModel/Usage/Option/Collection.php b/Model/ResourceModel/Usage/Option/Collection.php new file mode 100644 index 0000000..7385ea3 --- /dev/null +++ b/Model/ResourceModel/Usage/Option/Collection.php @@ -0,0 +1,372 @@ +_optionValueCollectionFactory = $optionValueCollectionFactory; + $this->_storeManager = $storeManager; + $this->metadataPool = $metadataPool ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\EntityManager\MetadataPool::class); + parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource); + } + + /** + * Resource initialization + * + * @return void + */ + protected function _construct() + { + $this->_init( + \DevStone\UsageCalculator\Model\Usage\Option::class, + \DevStone\UsageCalculator\Model\ResourceModel\Usage\Option::class + ); + } + + /** + * Adds title, price & price_type attributes to result + * + * @param int $storeId + * @return $this + */ + public function getOptions($storeId) + { + $this->addPriceToResult($storeId)->addTitleToResult($storeId)->addHelpToResult($storeId); + + return $this; + } + + /** + * Add title to result + * + * @param int $storeId + * @return $this + */ + public function addTitleToResult($storeId) + { + $productOptionTitleTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_title'); + $connection = $this->getConnection(); + $titleExpr = $connection->getCheckSql( + 'store_option_title.title IS NULL', + 'default_option_title.title', + 'store_option_title.title' + ); + + $this->getSelect()->join( + ['default_option_title' => $productOptionTitleTable], + 'default_option_title.option_id = main_table.option_id', + ['default_title' => 'title'] + )->joinLeft( + ['store_option_title' => $productOptionTitleTable], + 'store_option_title.option_id = main_table.option_id AND ' . $connection->quoteInto( + 'store_option_title.store_id = ?', + $storeId + ), + ['store_title' => 'title', 'title' => $titleExpr] + )->where( + 'default_option_title.store_id = ?', + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + + return $this; + } + + /** + * Add help to result + * + * @param int $storeId + * @return $this + */ + public function addHelpToResult($storeId) + { + $productOptionTitleTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_help'); + $connection = $this->getConnection(); + $helpExpr = $connection->getCheckSql( + 'store_option_help.help IS NULL', + 'default_option_help.help', + 'store_option_help.help' + ); + + $this->getSelect()->join( + ['default_option_help' => $productOptionTitleTable], + 'default_option_help.option_id = main_table.option_id', + ['default_help' => 'help'] + )->joinLeft( + ['store_option_help' => $productOptionTitleTable], + 'store_option_help.option_id = main_table.option_id AND ' . $connection->quoteInto( + 'store_option_help.store_id = ?', + $storeId + ), + ['store_help' => 'help', 'help' => $helpExpr] + )->where( + 'default_option_help.store_id = ?', + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + + return $this; + } + + /** + * Add price to result + * + * @param int $storeId + * @return $this + */ + public function addPriceToResult($storeId) + { + $productOptionPriceTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_price'); + $connection = $this->getConnection(); + $priceExpr = $connection->getCheckSql( + 'store_option_price.price IS NULL', + 'default_option_price.price', + 'store_option_price.price' + ); + $priceTypeExpr = $connection->getCheckSql( + 'store_option_price.price_type IS NULL', + 'default_option_price.price_type', + 'store_option_price.price_type' + ); + + $this->getSelect()->joinLeft( + ['default_option_price' => $productOptionPriceTable], + 'default_option_price.option_id = main_table.option_id AND ' . $connection->quoteInto( + 'default_option_price.store_id = ?', + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ), + ['default_price' => 'price', 'default_price_type' => 'price_type'] + )->joinLeft( + ['store_option_price' => $productOptionPriceTable], + 'store_option_price.option_id = main_table.option_id AND ' . $connection->quoteInto( + 'store_option_price.store_id = ?', + $storeId + ), + [ + 'store_price' => 'price', + 'store_price_type' => 'price_type', + 'price' => $priceExpr, + 'price_type' => $priceTypeExpr + ] + ); + + return $this; + } + + /** + * Add value to result + * + * @param int $storeId + * @return $this + */ + public function addValuesToResult($storeId = null) + { + if ($storeId === null) { + $storeId = $this->_storeManager->getStore()->getId(); + } + $optionIds = []; + foreach ($this as $option) { + $optionIds[] = $option->getId(); + } + if (!empty($optionIds)) { + /** @var \DevStone\UsageCalculator\Model\ResourceModel\Usage\Option\Value\Collection $values */ + $values = $this->_optionValueCollectionFactory->create(); + $values->addTitleToResult( + $storeId + )->addPriceToResult( + $storeId + )->addOptionToFilter( + $optionIds + )->setOrder( + 'sort_order', + self::SORT_ORDER_ASC + )->setOrder( + 'title', + self::SORT_ORDER_ASC + ); + + foreach ($values as $value) { + $optionId = $value->getOptionId(); + if ($this->getItemById($optionId)) { + $this->getItemById($optionId)->addValue($value); + $value->setOption($this->getItemById($optionId)); + } + } + } + + return $this; + } + + /** + * Add usage_id filter to select + * + * @param array|\DevStone\UsageCalculator\Model\Usage|int $usage + * @return $this + */ + public function addProductToFilter($usage) + { + if (empty($usage)) { + $this->addFieldToFilter('usage_id', ''); + } elseif (is_array($usage)) { + $this->addFieldToFilter('usage_id', ['in' => $usage]); + } elseif ($usage instanceof \DevStone\UsageCalculator\Model\Usage) { + $this->addFieldToFilter('usage_id', $usage->getId()); + } else { + $this->addFieldToFilter('usage_id', $usage); + } + + return $this; + } + + /** + * @return void + * @throws \Exception + * @since 101.0.0 + */ + protected function _initSelect() + { + parent::_initSelect(); + $this->getSelect()->join( + ['cpe' => $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_entity')], + 'cpe.entity_id = main_table.usage_id', + [] + ); + } + + /** + * @param int $usageId + * @param int $storeId + * @param bool $requiredOnly + * @return \DevStone\UsageCalculator\Api\Data\UsageCustomOptionInterface[] + */ + public function getUsageOptions($usageId, $storeId, $requiredOnly = false) + { + $collection = $this->addFieldToFilter( + 'cpe.entity_id', + $usageId + )->addTitleToResult( + $storeId + )->addHelpToResult( + $storeId + )->addPriceToResult( + $storeId + )->setOrder( + 'sort_order', + 'asc' + )->setOrder( + 'title', + 'asc' + ); + if ($requiredOnly) { + $collection->addRequiredFilter(); + } + $collection->addValuesToResult($storeId); + $this->getJoinProcessor()->process($collection); + return $collection->getItems(); + } + + /** + * Add is_required filter to select + * + * @param bool $required + * @return $this + */ + public function addRequiredFilter($required = true) + { + $this->addFieldToFilter('main_table.is_require', (int)$required); + return $this; + } + + /** + * Add filtering by option ids + * + * @param string|array $optionIds + * @return $this + */ + public function addIdsToFilter($optionIds) + { + $this->addFieldToFilter('main_table.option_id', $optionIds); + return $this; + } + + /** + * Call of protected method reset + * + * @return $this + */ + public function reset() + { + return $this->_reset(); + } + + /** + * @return JoinProcessorInterface + */ + private function getJoinProcessor() + { + if (null === $this->joinProcessor) { + $this->joinProcessor = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface::class); + } + return $this->joinProcessor; + } +} diff --git a/Model/ResourceModel/Usage/Option/Value.php b/Model/ResourceModel/Usage/Option/Value.php new file mode 100644 index 0000000..5cef80d --- /dev/null +++ b/Model/ResourceModel/Usage/Option/Value.php @@ -0,0 +1,450 @@ + + */ +class Value extends AbstractDb +{ + /** + * Store manager + * + * @var StoreManagerInterface + */ + protected $_storeManager; + + /** + * Currency factory + * + * @var CurrencyFactory + */ + protected $_currencyFactory; + + /** + * Core config model + * + * @var ScopeConfigInterface + */ + protected $_config; + + /** + * @var FormatInterface + */ + private $localeFormat; + + /** + * Class constructor + * + * @param Context $context + * @param CurrencyFactory $currencyFactory + * @param StoreManagerInterface $storeManager + * @param ScopeConfigInterface $config + * @param string $connectionName + */ + public function __construct( + Context $context, + CurrencyFactory $currencyFactory, + StoreManagerInterface $storeManager, + ScopeConfigInterface $config, + $connectionName = null + ) { + $this->_currencyFactory = $currencyFactory; + $this->_storeManager = $storeManager; + $this->_config = $config; + parent::__construct($context, $connectionName); + } + + /** + * Define main table and initialize connection + * + * @return void + */ + protected function _construct() + { + $this->_init(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_value', 'option_type_id'); + } + + /** + * Proceed operations after object is saved + * Save options store data + * + * @param AbstractModel $object + * @return AbstractDb + */ + protected function _afterSave(AbstractModel $object) + { + $this->_saveValuePrices($object); + $this->_saveValueTitles($object); + + return parent::_afterSave($object); + } + + /** + * Save option value price data + * + * @param AbstractModel $object + * @return void + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + protected function _saveValuePrices(AbstractModel $object) + { + $objectPrice = $object->getPrice(); + $priceTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_price'); + $formattedPrice = $this->getLocaleFormatter()->getNumber($objectPrice); + + $price = (double)sprintf('%F', $formattedPrice); + $priceType = $object->getPriceType(); + + if (isset($objectPrice) && $priceType) { + //save for store_id = 0 + $select = $this->getConnection()->select()->from( + $priceTable, + 'option_type_id' + )->where( + 'option_type_id = ?', + (int)$object->getId() + )->where( + 'store_id = ?', + Store::DEFAULT_STORE_ID + ); + $optionTypeId = $this->getConnection()->fetchOne($select); + + if ($optionTypeId) { + if ($object->getStoreId() == '0') { + $bind = ['price' => $price, 'price_type' => $priceType]; + $where = [ + 'option_type_id = ?' => $optionTypeId, + 'store_id = ?' => Store::DEFAULT_STORE_ID, + ]; + + $this->getConnection()->update($priceTable, $bind, $where); + } + } else { + $bind = [ + 'option_type_id' => (int)$object->getId(), + 'store_id' => Store::DEFAULT_STORE_ID, + 'price' => $price, + 'price_type' => $priceType, + ]; + $this->getConnection()->insert($priceTable, $bind); + } + } + + $scope = (int)$this->_config->getValue( + Store::XML_PATH_PRICE_SCOPE, + ScopeInterface::SCOPE_STORE + ); + + if ($scope == Store::PRICE_SCOPE_WEBSITE + && $priceType + && isset($objectPrice) + && $object->getStoreId() != Store::DEFAULT_STORE_ID + ) { + $baseCurrency = $this->_config->getValue( + Currency::XML_PATH_CURRENCY_BASE, + 'default' + ); + + $storeIds = $this->_storeManager->getStore($object->getStoreId())->getWebsite()->getStoreIds(); + if (is_array($storeIds)) { + foreach ($storeIds as $storeId) { + if ($priceType == 'fixed') { + $storeCurrency = $this->_storeManager->getStore($storeId)->getBaseCurrencyCode(); + /** @var $currencyModel Currency */ + $currencyModel = $this->_currencyFactory->create(); + $currencyModel->load($baseCurrency); + $rate = $currencyModel->getRate($storeCurrency); + if (!$rate) { + $rate = 1; + } + $newPrice = $price * $rate; + } else { + $newPrice = $price; + } + + $select = $this->getConnection()->select()->from( + $priceTable, + 'option_type_id' + )->where( + 'option_type_id = ?', + (int)$object->getId() + )->where( + 'store_id = ?', + (int)$storeId + ); + $optionTypeId = $this->getConnection()->fetchOne($select); + + if ($optionTypeId) { + $bind = ['price' => $newPrice, 'price_type' => $priceType]; + $where = ['option_type_id = ?' => (int)$optionTypeId, 'store_id = ?' => (int)$storeId]; + + $this->getConnection()->update($priceTable, $bind, $where); + } else { + $bind = [ + 'option_type_id' => (int)$object->getId(), + 'store_id' => (int)$storeId, + 'price' => $newPrice, + 'price_type' => $priceType, + ]; + + $this->getConnection()->insert($priceTable, $bind); + } + } + } + } else { + if ($scope == Store::PRICE_SCOPE_WEBSITE + && !isset($objectPrice) + && !$priceType + ) { + $storeIds = $this->_storeManager->getStore($object->getStoreId())->getWebsite()->getStoreIds(); + foreach ($storeIds as $storeId) { + $where = [ + 'option_type_id = ?' => (int)$object->getId(), + 'store_id = ?' => $storeId, + ]; + $this->getConnection()->delete($priceTable, $where); + } + } + } + } + + /** + * Save option value title data + * + * @param AbstractModel $object + * @return void + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + protected function _saveValueTitles(AbstractModel $object) + { + foreach ([Store::DEFAULT_STORE_ID, $object->getStoreId()] as $storeId) { + $titleTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_title'); + $select = $this->getConnection()->select()->from( + $titleTable, + ['option_type_id'] + )->where( + 'option_type_id = ?', + (int)$object->getId() + )->where( + 'store_id = ?', + (int)$storeId + ); + $optionTypeId = $this->getConnection()->fetchOne($select); + $existInCurrentStore = $this->getOptionIdFromOptionTable($titleTable, (int)$object->getId(), (int)$storeId); + + if ($storeId != Store::DEFAULT_STORE_ID && $object->getData('is_delete_store_title')) { + $object->unsetData('title'); + } + + if ($object->getTitle()) { + if ($existInCurrentStore) { + if ($storeId == $object->getStoreId()) { + $where = [ + 'option_type_id = ?' => (int)$optionTypeId, + 'store_id = ?' => $storeId, + ]; + $bind = ['title' => $object->getTitle()]; + $this->getConnection()->update($titleTable, $bind, $where); + } + } else { + $existInDefaultStore = $this->getOptionIdFromOptionTable( + $titleTable, + (int)$object->getId(), + Store::DEFAULT_STORE_ID + ); + // we should insert record into not default store only of if it does not exist in default store + if (($storeId == Store::DEFAULT_STORE_ID && !$existInDefaultStore) + || ($storeId != Store::DEFAULT_STORE_ID && !$existInCurrentStore) + ) { + $bind = [ + 'option_type_id' => (int)$object->getId(), + 'store_id' => $storeId, + 'title' => $object->getTitle(), + ]; + $this->getConnection()->insert($titleTable, $bind); + } + } + } else { + if ($storeId + && $optionTypeId + && $object->getStoreId() > Store::DEFAULT_STORE_ID + ) { + $where = [ + 'option_type_id = ?' => (int)$optionTypeId, + 'store_id = ?' => $storeId, + ]; + $this->getConnection()->delete($titleTable, $where); + } + } + } + } + + /** + * Get first col from from first row for option table + * + * @param string $tableName + * @param int $optionId + * @param int $storeId + * @return string + */ + protected function getOptionIdFromOptionTable($tableName, $optionId, $storeId) + { + $connection = $this->getConnection(); + $select = $connection->select()->from( + $tableName, + ['option_type_id'] + )->where( + 'option_type_id = ?', + $optionId + )->where( + 'store_id = ?', + (int)$storeId + ); + return $connection->fetchOne($select); + } + + /** + * Delete values by option id + * + * @param int $optionId + * @return $this + */ + public function deleteValue($optionId) + { + $statement = $this->getConnection()->select()->from( + $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_value') + )->where( + 'option_id = ?', + $optionId + ); + + $rowSet = $this->getConnection()->fetchAll($statement); + + foreach ($rowSet as $optionType) { + $this->deleteValues($optionType['option_type_id']); + } + + $this->getConnection()->delete($this->getMainTable(), ['option_id = ?' => $optionId]); + + return $this; + } + + /** + * Delete values by option type + * + * @param int $optionTypeId + * @return void + */ + public function deleteValues($optionTypeId) + { + $condition = ['option_type_id = ?' => $optionTypeId]; + + $this->getConnection()->delete($this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_price'), $condition); + + $this->getConnection()->delete($this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_title'), $condition); + } + + /** + * Duplicate product options value + * + * @param OptionValue $object + * @param int $oldOptionId + * @param int $newOptionId + * @return OptionValue + */ + public function duplicate(OptionValue $object, $oldOptionId, $newOptionId) + { + $connection = $this->getConnection(); + $select = $connection->select()->from($this->getMainTable())->where('option_id = ?', $oldOptionId); + $valueData = $connection->fetchAll($select); + + $valueCond = []; + + foreach ($valueData as $data) { + $optionTypeId = $data[$this->getIdFieldName()]; + unset($data[$this->getIdFieldName()]); + $data['option_id'] = $newOptionId; + + $connection->insert($this->getMainTable(), $data); + $valueCond[$optionTypeId] = $connection->lastInsertId($this->getMainTable()); + } + + unset($valueData); + + foreach ($valueCond as $oldTypeId => $newTypeId) { + // price + $priceTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_price'); + $columns = [new \Zend_Db_Expr($newTypeId), 'store_id', 'price', 'price_type']; + + $select = $connection->select()->from( + $priceTable, + [] + )->where( + 'option_type_id = ?', + $oldTypeId + )->columns( + $columns + ); + $insertSelect = $connection->insertFromSelect( + $select, + $priceTable, + ['option_type_id', 'store_id', 'price', 'price_type'] + ); + $connection->query($insertSelect); + + // title + $titleTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_title'); + $columns = [new \Zend_Db_Expr($newTypeId), 'store_id', 'title']; + + $select = $this->getConnection()->select()->from( + $titleTable, + [] + )->where( + 'option_type_id = ?', + $oldTypeId + )->columns( + $columns + ); + $insertSelect = $connection->insertFromSelect( + $select, + $titleTable, + ['option_type_id', 'store_id', 'title'] + ); + $connection->query($insertSelect); + } + + return $object; + } + + /** + * Get FormatInterface to convert price from string to number format + * + * @return FormatInterface + * @deprecated 101.0.8 + */ + private function getLocaleFormatter() + { + if ($this->localeFormat === null) { + $this->localeFormat = ObjectManager::getInstance() + ->get(FormatInterface::class); + } + return $this->localeFormat; + } +} diff --git a/Model/ResourceModel/Usage/Option/Value/Collection.php b/Model/ResourceModel/Usage/Option/Value/Collection.php new file mode 100644 index 0000000..4d07819 --- /dev/null +++ b/Model/ResourceModel/Usage/Option/Value/Collection.php @@ -0,0 +1,219 @@ +_init( + \DevStone\UsageCalculator\Model\Usage\Option\Value::class, + \DevStone\UsageCalculator\Model\ResourceModel\Usage\Option\Value::class + ); + } + + /** + * Add price, title to result + * + * @param int $storeId + * @return $this + */ + public function getValues($storeId) + { + $this->addPriceToResult($storeId)->addTitleToResult($storeId); + + return $this; + } + + /** + * Add titles to result + * + * @param int $storeId + * @return $this + */ + public function addTitlesToResult($storeId) + { + $connection = $this->getConnection(); + $optionTypePriceTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_price'); + $optionTitleTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_title'); + $priceExpr = $connection->getCheckSql( + 'store_value_price.price IS NULL', + 'default_value_price.price', + 'store_value_price.price' + ); + $priceTypeExpr = $connection->getCheckSql( + 'store_value_price.price_type IS NULL', + 'default_value_price.price_type', + 'store_value_price.price_type' + ); + $titleExpr = $connection->getCheckSql( + 'store_value_title.title IS NULL', + 'default_value_title.title', + 'store_value_title.title' + ); + $joinExprDefaultPrice = 'default_value_price.option_type_id = main_table.option_type_id AND ' . + $connection->quoteInto('default_value_price.store_id = ?', \Magento\Store\Model\Store::DEFAULT_STORE_ID); + + $joinExprStorePrice = 'store_value_price.option_type_id = main_table.option_type_id AND ' . + $connection->quoteInto('store_value_price.store_id = ?', $storeId); + + $joinExprTitle = 'store_value_title.option_type_id = main_table.option_type_id AND ' . $connection->quoteInto( + 'store_value_title.store_id = ?', + $storeId + ); + + $this->getSelect()->joinLeft( + ['default_value_price' => $optionTypePriceTable], + $joinExprDefaultPrice, + ['default_price' => 'price', 'default_price_type' => 'price_type'] + )->joinLeft( + ['store_value_price' => $optionTypePriceTable], + $joinExprStorePrice, + [ + 'store_price' => 'price', + 'store_price_type' => 'price_type', + 'price' => $priceExpr, + 'price_type' => $priceTypeExpr + ] + )->join( + ['default_value_title' => $optionTitleTable], + 'default_value_title.option_type_id = main_table.option_type_id', + ['default_title' => 'title'] + )->joinLeft( + ['store_value_title' => $optionTitleTable], + $joinExprTitle, + ['store_title' => 'title', 'title' => $titleExpr] + )->where( + 'default_value_title.store_id = ?', + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + + return $this; + } + + /** + * Add title result + * + * @param int $storeId + * @return $this + */ + public function addTitleToResult($storeId) + { + $optionTitleTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_title'); + $titleExpr = $this->getConnection()->getCheckSql( + 'store_value_title.title IS NULL', + 'default_value_title.title', + 'store_value_title.title' + ); + + $joinExpr = 'store_value_title.option_type_id = main_table.option_type_id AND ' . + $this->getConnection()->quoteInto('store_value_title.store_id = ?', $storeId); + $this->getSelect()->join( + ['default_value_title' => $optionTitleTable], + 'default_value_title.option_type_id = main_table.option_type_id', + ['default_title' => 'title'] + )->joinLeft( + ['store_value_title' => $optionTitleTable], + $joinExpr, + ['store_title' => 'title', 'title' => $titleExpr] + )->where( + 'default_value_title.store_id = ?', + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + + return $this; + } + + /** + * Add price to result + * + * @param int $storeId + * @return $this + */ + public function addPriceToResult($storeId) + { + $optionTypeTable = $this->getTable(\DevStone\UsageCalculator\Setup\UsageSetup::ENTITY_TYPE_CODE.'_option_type_price'); + $priceExpr = $this->getConnection()->getCheckSql( + 'store_value_price.price IS NULL', + 'default_value_price.price', + 'store_value_price.price' + ); + $priceTypeExpr = $this->getConnection()->getCheckSql( + 'store_value_price.price_type IS NULL', + 'default_value_price.price_type', + 'store_value_price.price_type' + ); + + $joinExprDefault = 'default_value_price.option_type_id = main_table.option_type_id AND ' . + $this->getConnection()->quoteInto( + 'default_value_price.store_id = ?', + \Magento\Store\Model\Store::DEFAULT_STORE_ID + ); + $joinExprStore = 'store_value_price.option_type_id = main_table.option_type_id AND ' . + $this->getConnection()->quoteInto('store_value_price.store_id = ?', $storeId); + $this->getSelect()->joinLeft( + ['default_value_price' => $optionTypeTable], + $joinExprDefault, + ['default_price' => 'price', 'default_price_type' => 'price_type'] + )->joinLeft( + ['store_value_price' => $optionTypeTable], + $joinExprStore, + [ + 'store_price' => 'price', + 'store_price_type' => 'price_type', + 'price' => $priceExpr, + 'price_type' => $priceTypeExpr + ] + ); + + return $this; + } + + /** + * Add option filter + * + * @param array $optionIds + * @param int $storeId + * @return $this + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getValuesByOption($optionIds, $storeId = null) + { + if (!is_array($optionIds)) { + $optionIds = [$optionIds]; + } + + return $this->addFieldToFilter('main_table.option_type_id', ['in' => $optionIds]); + } + + /** + * Add option to filter + * + * @param array|\DevStone\UsageCalculator\Model\Usage\Option|int $option + * @return $this + */ + public function addOptionToFilter($option) + { + if (empty($option)) { + $this->addFieldToFilter('option_id', ''); + } elseif (is_array($option)) { + $this->addFieldToFilter('option_id', ['in' => $option]); + } elseif ($option instanceof \DevStone\UsageCalculator\Model\Usage\Option) { + $this->addFieldToFilter('option_id', $option->getId()); + } else { + $this->addFieldToFilter('option_id', $option); + } + + return $this; + } +} diff --git a/Model/Size.php b/Model/Size.php new file mode 100644 index 0000000..ead0826 --- /dev/null +++ b/Model/Size.php @@ -0,0 +1,71 @@ +_init('DevStone\UsageCalculator\Model\ResourceModel\Size'); + } + + /** + * Get identities + * + * @return array + */ + public function getIdentities() + { + return [self::CACHE_TAG . '_' . $this->getId()]; + } + + /** + * Save from collection data + * + * @param array $data + * @return $this|bool + */ + public function saveCollection(array $data) + { + if (isset($data[$this->getId()])) { + $this->addData($data[$this->getId()]); + $this->getResource()->save($this); + } + return $this; + } +} diff --git a/Model/SizeRepository.php b/Model/SizeRepository.php new file mode 100644 index 0000000..a367c96 --- /dev/null +++ b/Model/SizeRepository.php @@ -0,0 +1,165 @@ +resource = $resource; + $this->sizeFactory = $sizeFactory; + $this->sizeCollectionFactory = $sizeCollectionFactory; + $this->searchResultsFactory = $searchResultsFactory; + $this->dataObjectHelper = $dataObjectHelper; + $this->dataSizeFactory = $dataSizeFactory; + $this->dataObjectProcessor = $dataObjectProcessor; + $this->storeManager = $storeManager; + } + + /** + * {@inheritdoc} + */ + public function save( + \DevStone\UsageCalculator\Api\Data\SizeInterface $size + ) { + /* if (empty($size->getStoreId())) { + $storeId = $this->storeManager->getStore()->getId(); + $size->setStoreId($storeId); + } */ + try { + $size->getResource()->save($size); + } catch (\Exception $exception) { + throw new CouldNotSaveException(__( + 'Could not save the size: %1', + $exception->getMessage() + )); + } + return $size; + } + + /** + * {@inheritdoc} + */ + public function getById($sizeId) + { + $size = $this->sizeFactory->create(); + $size->getResource()->load($size, $sizeId); + if (!$size->getId()) { + throw new NoSuchEntityException(__('Size with id "%1" does not exist.', $sizeId)); + } + return $size; + } + + /** + * {@inheritdoc} + */ + public function getList( + \Magento\Framework\Api\SearchCriteriaInterface $criteria + ) { + $collection = $this->sizeCollectionFactory->create(); + foreach ($criteria->getFilterGroups() as $filterGroup) { + foreach ($filterGroup->getFilters() as $filter) { + if ($filter->getField() === 'store_id') { + $collection->addStoreFilter($filter->getValue(), false); + continue; + } + $condition = $filter->getConditionType() ?: 'eq'; + $collection->addFieldToFilter($filter->getField(), [$condition => $filter->getValue()]); + } + } + + $sortOrders = $criteria->getSortOrders(); + if ($sortOrders) { + /** @var SortOrder $sortOrder */ + foreach ($sortOrders as $sortOrder) { + $collection->addOrder( + $sortOrder->getField(), + ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC' + ); + } + } + $collection->setCurPage($criteria->getCurrentPage()); + $collection->setPageSize($criteria->getPageSize()); + + $searchResults = $this->searchResultsFactory->create(); + $searchResults->setSearchCriteria($criteria); + $searchResults->setTotalCount($collection->getSize()); + $searchResults->setItems($collection->getItems()); + return $searchResults; + } + + /** + * {@inheritdoc} + */ + public function delete( + \DevStone\UsageCalculator\Api\Data\SizeInterface $size + ) { + try { + $size->getResource()->delete($size); + } catch (\Exception $exception) { + throw new CouldNotDeleteException(__( + 'Could not delete the Size: %1', + $exception->getMessage() + )); + } + return true; + } + + /** + * {@inheritdoc} + */ + public function deleteById($sizeId) + { + return $this->delete($this->getById($sizeId)); + } +} diff --git a/Model/Usage.php b/Model/Usage.php new file mode 100644 index 0000000..c12c983 --- /dev/null +++ b/Model/Usage.php @@ -0,0 +1,108 @@ +_init('DevStone\UsageCalculator\Model\ResourceModel\Usage'); + } + + /** + * Get identities + * + * @return array + */ + public function getIdentities() + { + return [self::CACHE_TAG . '_' . $this->getId()]; + } + + /** + * Save from collection data + * + * @param array $data + * @return $this|bool + */ + public function saveCollection(array $data) + { + if (isset($data[$this->getId()])) { + $this->addData($data[$this->getId()]); + $this->getResource()->save($this); + } + return $this; + } + + /** + * Get all options of usage + * + * @return \DevStone\UsageCalculator\Api\Data\UsageCustomOptionInterface[]|null + */ + public function getOptions() + { + return $this->getData('options'); + } + + /** + * @param \DevStone\UsageCalculator\Api\Data\UsageCustomOptionInterface[] $options + * @return $this + */ + public function setOptions(array $options = null) + { + $this->setData('options', $options); + return $this; + } + + public function afterSave() + { + parent::afterSave(); + + $saveHandler = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\DevStone\UsageCalculator\Model\Usage\Option\SaveHandler::class); + + $saveHandler->execute($this); + } + + public function afterLoad() { + parent::afterLoad(); + + $readHandler = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\DevStone\UsageCalculator\Model\Usage\Option\ReadHandler::class); + + $readHandler->execute($this); + } +} diff --git a/Model/Usage/CatagoriesOptionsProvider.php b/Model/Usage/CatagoriesOptionsProvider.php new file mode 100644 index 0000000..939e163 --- /dev/null +++ b/Model/Usage/CatagoriesOptionsProvider.php @@ -0,0 +1,51 @@ +categoryRepository = $categoryRepository; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->objectConverter = $objectConverter; + } + + /** + * @return array + */ + public function toOptionArray() + { + $catagories = $this->categoryRepository->getList( + $this->searchCriteriaBuilder->create() + )->getItems(); + + return $this->objectConverter->toOptionArray($catagories, 'entity_id', 'name'); + } +} diff --git a/Model/Usage/Option.php b/Model/Usage/Option.php new file mode 100644 index 0000000..f00a7f3 --- /dev/null +++ b/Model/Usage/Option.php @@ -0,0 +1,797 @@ +productOptionValue = $productOptionValue; + $this->optionTypeFactory = $optionFactory; + $this->validatorPool = $validatorPool; + $this->string = $string; + parent::__construct( + $context, + $registry, + $extensionFactory, + $customAttributeFactory, + $resource, + $resourceCollection, + $data + ); + } + + /** + * Get resource instance + * + * @return \Magento\Framework\Model\ResourceModel\Db\AbstractDb + * @deprecated 101.1.0 because resource models should be used directly + */ + protected function _getResource() + { + return $this->_resource ?: parent::_getResource(); + } + + /** + * @return void + */ + protected function _construct() + { + $this->_init(\DevStone\UsageCalculator\Model\ResourceModel\Usage\Option::class); + parent::_construct(); + } + + /** + * Add value of option to values array + * + * @param Option\Value $value + * @return $this + */ + public function addValue(Option\Value $value) + { + $this->values[$value->getId()] = $value; + return $this; + } + + /** + * Get value by given id + * + * @param int $valueId + * @return Option\Value|null + */ + public function getValueById($valueId) + { + if (isset($this->values[$valueId])) { + return $this->values[$valueId]; + } + + return null; + } + + /** + * Whether or not the option type contains sub-values + * + * @param string $type + * @return bool + * @since 101.1.0 + */ + public function hasValues($type = null) + { + return $this->getGroupByType($type) == self::OPTION_GROUP_SELECT; + } + + /** + * @return ProductCustomOptionValuesInterface[]|null + */ + public function getValues() + { + return $this->values; + } + + /** + * Retrieve value instance + * + * @return Option\Value + */ + public function getValueInstance() + { + return $this->productOptionValue; + } + + /** + * Add option for save it + * + * @param array $option + * @return $this + */ + public function addOption($option) + { + $this->options[] = $option; + return $this; + } + + /** + * Get all options + * + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * Set options for array + * + * @param array $options + * @return $this + */ + public function setOptions($options) + { + $this->options = $options; + return $this; + } + + /** + * Set options to empty array + * + * @return $this + */ + public function unsetOptions() + { + $this->options = []; + return $this; + } + + /** + * Retrieve usage instance + * + * @return Usage + */ + public function getUsage() + { + return $this->usage; + } + + /** + * Set usage instance + * + * @param Usage $product + * @return $this + */ + public function setUsage(Usage $usage = null) + { + $this->usage = $usage; + return $this; + } + + /** + * Get group name of option by given option type + * + * @param string $type + * @return string + */ + public function getGroupByType($type = null) + { + if ($type === null) { + $type = $this->getType(); + } + $optionGroupsToTypes = [ + self::OPTION_TYPE_FIELD => self::OPTION_GROUP_TEXT, + self::OPTION_TYPE_AREA => self::OPTION_GROUP_TEXT, + self::OPTION_TYPE_FILE => self::OPTION_GROUP_FILE, + self::OPTION_TYPE_DROP_DOWN => self::OPTION_GROUP_SELECT, + self::OPTION_TYPE_RADIO => self::OPTION_GROUP_SELECT, + self::OPTION_TYPE_CHECKBOX => self::OPTION_GROUP_SELECT, + self::OPTION_TYPE_MULTIPLE => self::OPTION_GROUP_SELECT, + self::OPTION_TYPE_DATE => self::OPTION_GROUP_DATE, + self::OPTION_TYPE_DATE_TIME => self::OPTION_GROUP_DATE, + self::OPTION_TYPE_TIME => self::OPTION_GROUP_DATE, + ]; + + return isset($optionGroupsToTypes[$type]) ? $optionGroupsToTypes[$type] : ''; + } + + /** + * Group model factory + * + * @param string $type Option type + * @return \Magento\Catalog\Model\Product\Option\Type\DefaultType + * @throws LocalizedException + */ + public function groupFactory($type) + { + $group = $this->getGroupByType($type); + if (!empty($group)) { + return $this->optionTypeFactory->create( + 'DevStone\UsageCalculator\Model\Usage\Option\Type\\' . $this->string->upperCaseWords($group) + ); + } + throw new LocalizedException(__('The option type to get group instance is incorrect.')); + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @since 101.0.0 + */ + public function beforeSave() + { + parent::beforeSave(); + if ($this->getData('previous_type') != '') { + $previousType = $this->getData('previous_type'); + + /** + * if previous option has different group from one is came now + * need to remove all data of previous group + */ + if ($this->getGroupByType($previousType) != $this->getGroupByType($this->getData('type'))) { + switch ($this->getGroupByType($previousType)) { + case self::OPTION_GROUP_SELECT: + $this->unsetData('values'); + if ($this->getId()) { + $this->getValueInstance()->deleteValue($this->getId()); + } + break; + case self::OPTION_GROUP_TEXT: + break; + } + if ($this->getGroupByType($this->getData('type')) == self::OPTION_GROUP_SELECT) { + $this->unsetData('price'); + $this->unsetData('price_type'); + if ($this->getId()) { + $this->deletePrices($this->getId()); + } + } + } + } + return $this; + } + + /** + * @return \Magento\Framework\Model\AbstractModel + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function afterSave() + { + $this->getValueInstance()->unsetValues(); + $values = $this->getValues() ?: $this->getData('values'); + if (is_array($values)) { + foreach ($values as $value) { + if ($value instanceof \Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface) { + $data = $value->getData(); + } else { + $data = $value; + } + $this->getValueInstance()->addValue($data); + } + + $this->getValueInstance()->setOption($this)->saveValues(); + } elseif ($this->getGroupByType($this->getType()) == self::OPTION_GROUP_SELECT) { + throw new LocalizedException(__('Select type options required values rows.')); + } + + return parent::afterSave(); + } + + /** + * Return price. If $flag is true and price is percent + * return converted percent to price + * + * @param bool $flag + * @return float + */ + public function getPrice($flag = false) + { + if ($flag && $this->getPriceType() == self::$typePercent) { + $basePrice = $this->getUsage()->getPrice(); + $price = $basePrice * ($this->_getData(self::KEY_PRICE) / 100); + return $price; + } + return $this->_getData(self::KEY_PRICE); + } + + /** + * Delete prices of option + * + * @param int $optionId + * @return $this + */ + public function deletePrices($optionId) + { + $this->getResource()->deletePrices($optionId); + return $this; + } + + /** + * Delete titles of option + * + * @param int $optionId + * @return $this + */ + public function deleteTitles($optionId) + { + $this->getResource()->deleteTitles($optionId); + return $this; + } + + /** + * Get Usage Option Collection + * + * @param Usage $product + * @return \DevStone\UsageCalculator\Model\ResourceModel\Usage\Option\Collection + */ + public function getUsageOptions(Usage $product) + { + return $this->getOptionRepository()->getUsageOptions($product, $this->getAddRequiredFilter()); + } + + /** + * Get collection of values for current option + * + * @return Collection + */ + public function getValuesCollection() + { + $collection = $this->getValueInstance()->getValuesCollection($this); + + return $collection; + } + + /** + * Get collection of values by given option ids + * + * @param array $optionIds + * @param int $storeId + * @return Collection + */ + public function getOptionValuesByOptionId($optionIds, $storeId) + { + $collection = $this->productOptionValue->getValuesByOption($optionIds, $this->getId(), $storeId); + + return $collection; + } + + /** + * Duplicate options for product + * + * @param int $oldProductId + * @param int $newProductId + * @return $this + */ + public function duplicate($oldProductId, $newProductId) + { + $this->getResource()->duplicate($this, $oldProductId, $newProductId); + + return $this; + } + + /** + * Retrieve option searchable data + * + * @param int $productId + * @param int $storeId + * @return array + */ + public function getSearchableData($productId, $storeId) + { + return $this->_getResource()->getSearchableData($productId, $storeId); + } + + /** + * Clearing object's data + * + * @return $this + */ + protected function _clearData() + { + $this->_data = []; + $this->values = null; + return $this; + } + + /** + * Clearing cyclic references + * + * @return $this + */ + protected function _clearReferences() + { + if (!empty($this->values)) { + foreach ($this->values as $value) { + $value->unsetOption(); + } + } + return $this; + } + + /** + * {@inheritdoc} + */ + protected function _getValidationRulesBeforeSave() + { + return $this->validatorPool->get($this->getType()); + } + + /** + * Get product SKU + * + * @return string + */ + public function getProductSku() + { + $productSku = $this->_getData(self::KEY_PRODUCT_SKU); + + return $productSku; + } + + /** + * Get option id + * + * @return int|null + * @codeCoverageIgnoreStart + */ + public function getOptionId() + { + return $this->_getData(self::KEY_OPTION_ID); + } + + /** + * Get option title + * + * @return string + */ + public function getTitle() + { + return $this->_getData(self::KEY_TITLE); + } + + /** + * Get help text + * + * @return string + */ + public function getHelp() + { + return $this->_getData(self::KEY_HELP); + } + + /** + * Get option type + * + * @return string + */ + public function getType() + { + return $this->_getData(self::KEY_TYPE); + } + + /** + * Get sort order + * + * @return int + */ + public function getSortOrder() + { + return $this->_getData(self::KEY_SORT_ORDER); + } + + /** + * Get is require + * + * @return bool + * @SuppressWarnings(PHPMD.BooleanGetMethodName) + */ + public function getIsRequire() + { + return $this->_getData(self::KEY_IS_REQUIRE); + } + + /** + * Get price type + * + * @return string|null + */ + public function getPriceType() + { + return $this->_getData(self::KEY_PRICE_TYPE); + } + + /** + * Set product SKU + * + * @param string $productSku + * @return $this + */ + public function setProductSku($productSku) + { + return $this->setData(self::KEY_PRODUCT_SKU, $productSku); + } + + /** + * Set option id + * + * @param int $optionId + * @return $this + */ + public function setOptionId($optionId) + { + return $this->setData(self::KEY_OPTION_ID, $optionId); + } + + /** + * Set option title + * + * @param string $title + * @return $this + */ + public function setTitle($title) + { + return $this->setData(self::KEY_TITLE, $title); + } + + /** + * Set help text + * + * @param string $help + * @return $this + */ + public function setHelp($help) + { + return $this->setData(self::KEY_HELP, $help); + } + + /** + * Set option type + * + * @param string $type + * @return $this + */ + public function setType($type) + { + return $this->setData(self::KEY_TYPE, $type); + } + + /** + * Set sort order + * + * @param int $sortOrder + * @return $this + */ + public function setSortOrder($sortOrder) + { + return $this->setData(self::KEY_SORT_ORDER, $sortOrder); + } + + /** + * Set is require + * + * @param bool $isRequired + * @return $this + */ + public function setIsRequire($isRequired) + { + return $this->setData(self::KEY_IS_REQUIRE, $isRequired); + } + + /** + * Set price + * + * @param float $price + * @return $this + */ + public function setPrice($price) + { + return $this->setData(self::KEY_PRICE, $price); + } + + /** + * Set price type + * + * @param string $priceType + * @return $this + */ + public function setPriceType($priceType) + { + return $this->setData(self::KEY_PRICE_TYPE, $priceType); + } + + /** + * @param \DevStone\UsageCalculator\Api\Data\UsageCustomOptionValuesInterface[] $values + * @return $this + */ + public function setValues(array $values = null) + { + $this->values = $values; + return $this; + } + + /** + * {@inheritdoc} + * + * @return \DevStone\UsageCalculator\Api\Data\UsageCustomOptionExtensionInterface|null + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * Return regular price. + * + * @return float|int + * @since 101.0.0 + */ + public function getRegularPrice() + { + if ($this->getPriceType() == self::$typePercent) { + $basePrice = $this->getUsage()->getPrice(); + $price = $basePrice * ($this->_getData(self::KEY_PRICE) / 100); + return $price; + } + return $this->_getData(self::KEY_PRICE); + } + + /** + * @param Usage $usage + * @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection + */ + public function getUsageOptionCollection(Usage $usage) + { + $collection = clone $this->getCollection(); + $collection->addFieldToFilter( + 'usage_id', + 'entity_id' + )->addTitleToResult( + $usage->getStoreId() + )->addPriceToResult( + $usage->getStoreId() + )->addHelpToResult( + $usage->getStoreId() + )->setOrder( + 'sort_order', + 'asc' + )->setOrder( + 'title', + 'asc' + ); + + if ($this->getAddRequiredFilter()) { + $collection->addRequiredFilter($this->getAddRequiredFilterValue()); + } + + $collection->addValuesToResult($usage->getStoreId()); + return $collection; + } + + /** + * {@inheritdoc} + * + * @param \DevStone\UsageCalculator\Api\Data\UsageCustomOptionExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \DevStone\UsageCalculator\Api\Data\UsageCustomOptionExtensionInterface $extensionAttributes + ) { + return $this->_setExtensionAttributes($extensionAttributes); + } + + /** + * @return Option\Repository + */ + private function getOptionRepository() + { + if (null === $this->optionRepository) { + $this->optionRepository = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\DevStone\UsageCalculator\Model\Usage\Option\Repository::class); + } + return $this->optionRepository; + } + + //@codeCoverageIgnoreEnd +} diff --git a/Model/Usage/Option/Converter.php b/Model/Usage/Option/Converter.php new file mode 100644 index 0000000..ff1060b --- /dev/null +++ b/Model/Usage/Option/Converter.php @@ -0,0 +1,30 @@ +getData(); + $values = $option->getValues(); + $valuesData = []; + if (!empty($values)) { + foreach ($values as $key => $value) { + $valuesData[$key] = $value->getData(); + } + } + $optionData['values'] = $valuesData; + return $optionData; + } +} diff --git a/Model/Usage/Option/ReadHandler.php b/Model/Usage/Option/ReadHandler.php new file mode 100644 index 0000000..524fd56 --- /dev/null +++ b/Model/Usage/Option/ReadHandler.php @@ -0,0 +1,48 @@ +optionRepository = $optionRepository; + } + + /** + * @param object $entity + * @param array $arguments + * @return \DevStone\UsageCalculator\Api\Data\UsageInterface|object + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function execute($entity, $arguments = []) + { + $options = []; + /** @var $entity \DevStone\UsageCalculator\Api\Data\UsageInterface */ + foreach ($this->optionRepository->getUsageOptions($entity) as $option) { + $option->setUsage($entity); + $options[] = $option; + } + $entity->setOptions($options); + return $entity; + } +} diff --git a/Model/Usage/Option/Repository.php b/Model/Usage/Option/Repository.php new file mode 100644 index 0000000..77f832f --- /dev/null +++ b/Model/Usage/Option/Repository.php @@ -0,0 +1,248 @@ +usageRepository = $usageRepository; + $this->optionResource = $optionResource; + $this->converter = $converter; + $this->collectionFactory = $collectionFactory ?: ObjectManager::getInstance() + ->get(\DevStone\UsageCalculator\Model\ResourceModel\Usage\Option\CollectionFactory::class); + $this->optionFactory = $optionFactory ?: ObjectManager::getInstance() + ->get(\DevStone\UsageCalculator\Model\Usage\OptionFactory::class); + $this->metadataPool = $metadataPool ?: ObjectManager::getInstance() + ->get(\Magento\Framework\EntityManager\MetadataPool::class); + } + + /** + * {@inheritdoc} + */ + public function getList($id) + { + $usage = $this->usageRepository->getById($id); + return $usage->getOptions() ?: []; + } + + /** + * {@inheritdoc} + */ + public function getUsageOptions(UsageInterface $usage, $requiredOnly = false) + { + return $this->collectionFactory->create()->getUsageOptions( + $usage->getEntityId(), + $usage->getStoreId(), + $requiredOnly + ); + } + + /** + * {@inheritdoc} + */ + public function get($sku, $optionId) + { + $product = $this->usageRepository->getById($sku); + $option = $product->getOptionById($optionId); + if ($option === null) { + throw NoSuchEntityException::singleField('optionId', $optionId); + } + return $option; + } + + /** + * {@inheritdoc} + */ + public function delete(UsageCustomOptionInterface $entity) + { + $this->optionResource->delete($entity); + return true; + } + + /** + * {@inheritdoc} + */ + public function duplicate( + UsageInterface $product, + UsageInterface $duplicate + ) { + $hydrator = $this->getHydratorPool()->getHydrator(UsageInterface::class); + $metadata = $this->metadataPool->getMetadata(UsageInterface::class); + return $this->optionResource->duplicate( + $this->optionFactory->create([]), + $hydrator->extract($product)[$metadata->getLinkField()], + $hydrator->extract($duplicate)[$metadata->getLinkField()] + ); + } + + /** + * {@inheritdoc} + */ + public function save(UsageCustomOptionInterface $option) + { + $usageId = $option->getUsageId(); + if (!$usageId) { + throw new CouldNotSaveException(__('usage_id should be specified')); + } + /** @var \DevStone\UsageCalculator\Model\Usage $usage */ + $usage = $this->usageRepository->getById($usageId); + $option->setData('usage_id', $usageId); + $option->setData('store_id', $usage->getStoreId()); + + if ($option->getOptionId()) { + $options = $usage->getOptions(); + if (!$options) { + $options = $this->getUsageOptions($usage); + } + + $persistedOption = array_filter($options, function ($iOption) use ($option) { + return $option->getOptionId() == $iOption->getOptionId(); + }); + $persistedOption = reset($persistedOption); + + if (!$persistedOption) { + throw new NoSuchEntityException(); + } + $originalValues = $persistedOption->getValues(); + $newValues = $option->getData('values'); + if ($newValues) { + if (isset($originalValues)) { + $newValues = $this->markRemovedValues($newValues, $originalValues); + } + $option->setData('values', $newValues); + } + } + $option->save(); + return $option; + } + + /** + * {@inheritdoc} + */ + public function deleteByIdentifier($sku, $optionId) + { + $product = $this->usageRepository->getById($sku); + $options = $product->getOptions(); + $option = $product->getOptionById($optionId); + if ($option === null) { + throw NoSuchEntityException::singleField('optionId', $optionId); + } + unset($options[$optionId]); + try { + $this->delete($option); + if (empty($options)) { + $this->usageRepository->save($product); + } + } catch (\Exception $e) { + throw new CouldNotSaveException(__('Could not remove custom option')); + } + return true; + } + + /** + * Mark original values for removal if they are absent among new values + * + * @param $newValues array + * @param $originalValues \Magento\Catalog\Model\Product\Option\Value[] + * @return array + */ + protected function markRemovedValues($newValues, $originalValues) + { + $existingValuesIds = []; + + foreach ($newValues as $newValue) { + if (array_key_exists('option_type_id', $newValue)) { + $existingValuesIds[] = $newValue['option_type_id']; + } + } + /** @var $originalValue \Magento\Catalog\Model\Product\Option\Value */ + foreach ($originalValues as $originalValue) { + if (!in_array($originalValue->getData('option_type_id'), $existingValuesIds)) { + $originalValue->setData('is_delete', 1); + $newValues[] = $originalValue->getData(); + } + } + + return $newValues; + } + + /** + * @return \Magento\Framework\EntityManager\HydratorPool + * @deprecated 101.0.0 + */ + private function getHydratorPool() + { + if (null === $this->hydratorPool) { + $this->hydratorPool = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\EntityManager\HydratorPool::class); + } + return $this->hydratorPool; + } +} diff --git a/Model/Usage/Option/SaveHandler.php b/Model/Usage/Option/SaveHandler.php new file mode 100644 index 0000000..c6a6002 --- /dev/null +++ b/Model/Usage/Option/SaveHandler.php @@ -0,0 +1,61 @@ +optionRepository = $optionRepository; + } + + /** + * @param object $entity + * @param array $arguments + * @return \DevStone\UsageCalculator\Api\Data\UsageInterface|object + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function execute($entity, $arguments = []) + { + $options = $entity->getOptions(); + + $optionIds = []; + + if ($options) { + $optionIds = array_map(function ($option) { + /** @var \DevStone\UsageCalculatorModel\Usage\Option $option */ + return $option->getOptionId(); + }, $options); + } + + /** @var \DevStone\UsageCalculator\Api\Data\UsageInterface $entity */ + foreach ($this->optionRepository->getUsageOptions($entity) as $option) { + if (!in_array($option->getOptionId(), $optionIds)) { + $this->optionRepository->delete($option); + } + } + if ($options) { + foreach ($options as $option) { + $option->setUsageId($entity->getId()); + $this->optionRepository->save($option); + } + } + + return $entity; + } +} diff --git a/Model/Usage/Option/Type.php b/Model/Usage/Option/Type.php new file mode 100644 index 0000000..e9843c7 --- /dev/null +++ b/Model/Usage/Option/Type.php @@ -0,0 +1,103 @@ +getData(self::KEY_LABEL); + } + + /** + * Get option type code + * + * @return string + */ + public function getCode() + { + return $this->getData(self::KEY_CODE); + } + + /** + * Get option type group + * + * @return string + */ + public function getGroup() + { + return $this->getData(self::KEY_GROUP); + } + + /** + * Set option type label + * + * @param string $label + * @return $this + */ + public function setLabel($label) + { + return $this->setData(self::KEY_LABEL, $label); + } + + /** + * Set option type code + * + * @param string $code + * @return $this + */ + public function setCode($code) + { + return $this->setData(self::KEY_CODE, $code); + } + + /** + * Set option type group + * + * @param string $group + * @return $this + */ + public function setGroup($group) + { + return $this->setData(self::KEY_GROUP, $group); + } + + /** + * {@inheritdoc} + * + * @return \DevStone\UsageCalculator\Api\Data\UsageCustomOptionTypeExtensionInterface|null + */ + public function getExtensionAttributes() + { + return $this->_getExtensionAttributes(); + } + + /** + * {@inheritdoc} + * + * @param \DevStone\UsageCalculator\Api\Data\UsageCustomOptionTypeExtensionInterface $extensionAttributes + * @return $this + */ + public function setExtensionAttributes( + \DevStone\UsageCalculator\Api\Data\UsageCustomOptionTypeExtensionInterface $extensionAttributes + ) { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/Model/Usage/Option/Type/Date.php b/Model/Usage/Option/Type/Date.php new file mode 100644 index 0000000..acd85b6 --- /dev/null +++ b/Model/Usage/Option/Type/Date.php @@ -0,0 +1,397 @@ + + */ +class Date extends \Magento\Catalog\Model\Product\Option\Type\DefaultType +{ + /** + * @var string + */ + protected $_formattedOptionValue = null; + + /** + * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + */ + protected $_localeDate; + + /** + * Serializer interface instance. + * + * @var \Magento\Framework\Serialize\Serializer\Json + */ + private $serializer; + + /** + * @param \Magento\Checkout\Model\Session $checkoutSession + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + * @param array $data + * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer + */ + public function __construct( + \Magento\Checkout\Model\Session $checkoutSession, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, + array $data = [], + \Magento\Framework\Serialize\Serializer\Json $serializer = null + ) { + $this->_localeDate = $localeDate; + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\Json::class); + parent::__construct($checkoutSession, $scopeConfig, $data); + } + + /** + * Validate user input for option + * + * @param array $values All product option values, i.e. array (option_id => mixed, option_id => mixed...) + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + public function validateUserValue($values) + { + parent::validateUserValue($values); + + $option = $this->getOption(); + $value = $this->getUserValue(); + + $dateValid = true; + if ($this->_dateExists()) { + if ($this->useCalendar()) { + $dateValid = isset($value['date']) && preg_match('/^\d{1,4}.+\d{1,4}.+\d{1,4}$/', $value['date']); + } else { + $dateValid = isset( + $value['day'] + ) && isset( + $value['month'] + ) && isset( + $value['year'] + ) && $value['day'] > 0 && $value['month'] > 0 && $value['year'] > 0; + } + } + + $timeValid = true; + if ($this->_timeExists()) { + $timeValid = isset( + $value['hour'] + ) && isset( + $value['minute'] + ) && is_numeric( + $value['hour'] + ) && is_numeric( + $value['minute'] + ); + } + + $isValid = $dateValid && $timeValid; + + if ($isValid) { + $this->setUserValue( + [ + 'date' => isset($value['date']) ? $value['date'] : '', + 'year' => isset($value['year']) ? intval($value['year']) : 0, + 'month' => isset($value['month']) ? intval($value['month']) : 0, + 'day' => isset($value['day']) ? intval($value['day']) : 0, + 'hour' => isset($value['hour']) ? intval($value['hour']) : 0, + 'minute' => isset($value['minute']) ? intval($value['minute']) : 0, + 'day_part' => isset($value['day_part']) ? $value['day_part'] : '', + 'date_internal' => isset($value['date_internal']) ? $value['date_internal'] : '', + ] + ); + } elseif (!$isValid && $option->getIsRequire() && !$this->getSkipCheckRequiredOption()) { + $this->setIsValid(false); + if (!$dateValid) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Please specify date required option(s).') + ); + } elseif (!$timeValid) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Please specify time required option(s).') + ); + } else { + throw new \Magento\Framework\Exception\LocalizedException( + __('Please specify product\'s required option(s).') + ); + } + } else { + $this->setUserValue(null); + } + + return $this; + } + + /** + * Prepare option value for cart + * + * @return string|null Prepared option value + * @throws \Magento\Framework\Exception\LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function prepareForCart() + { + if ($this->getIsValid() && $this->getUserValue() !== null) { + $option = $this->getOption(); + $value = $this->getUserValue(); + + if (isset($value['date_internal']) && $value['date_internal'] != '') { + $this->_setInternalInRequest($value['date_internal']); + return $value['date_internal']; + } + + $timestamp = 0; + + if ($this->_dateExists()) { + if ($this->useCalendar()) { + $timestamp += $this->_localeDate->date($value['date'], null, true, false)->getTimestamp(); + } else { + $timestamp += mktime(0, 0, 0, $value['month'], $value['day'], $value['year']); + } + } else { + $timestamp += mktime(0, 0, 0, date('m'), date('d'), date('Y')); + } + + if ($this->_timeExists()) { + // 24hr hour conversion + if (!$this->is24hTimeFormat()) { + $pmDayPart = 'pm' == strtolower($value['day_part']); + if (12 == $value['hour']) { + $value['hour'] = $pmDayPart ? 12 : 0; + } elseif ($pmDayPart) { + $value['hour'] += 12; + } + } + + $timestamp += 60 * 60 * $value['hour'] + 60 * $value['minute']; + } + + $date = (new \DateTime())->setTimestamp($timestamp); + $result = $date->format('Y-m-d H:i:s'); + + // Save date in internal format to avoid locale date bugs + $this->_setInternalInRequest($result); + + return $result; + } else { + return null; + } + } + + /** + * Return formatted option value for quote option + * + * @param string $optionValue Prepared for cart option value + * @return string + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function getFormattedOptionValue($optionValue) + { + if ($this->_formattedOptionValue === null) { + if ($this->getOption()->getType() == ProductCustomOptionInterface::OPTION_TYPE_DATE) { + $result = $this->_localeDate->formatDateTime( + new \DateTime($optionValue), + \IntlDateFormatter::MEDIUM, + \IntlDateFormatter::NONE, + null, + 'UTC' + ); + } elseif ($this->getOption()->getType() == ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME) { + $result = $this->_localeDate->formatDateTime( + new \DateTime($optionValue), + \IntlDateFormatter::SHORT, + \IntlDateFormatter::SHORT, + null, + 'UTC' + ); + } elseif ($this->getOption()->getType() == ProductCustomOptionInterface::OPTION_TYPE_TIME) { + $result = $this->_localeDate->formatDateTime( + new \DateTime($optionValue), + \IntlDateFormatter::NONE, + \IntlDateFormatter::SHORT, + null, + 'UTC' + ); + } else { + $result = $optionValue; + } + $this->_formattedOptionValue = $result; + } + return $this->_formattedOptionValue; + } + + /** + * Return printable option value + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getPrintableOptionValue($optionValue) + { + return $this->getFormattedOptionValue($optionValue); + } + + /** + * Return formatted option value ready to edit, ready to parse + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getEditableOptionValue($optionValue) + { + return $this->getFormattedOptionValue($optionValue); + } + + /** + * Parse user input value and return cart prepared value + * + * @param string $optionValue + * @param array $productOptionValues Values for product option + * @return string|null + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function parseOptionValue($optionValue, $productOptionValues) + { + try { + $date = new \DateTime($optionValue); + } catch (\Exception $e) { + return null; + } + return $date->format('Y-m-d H:i:s'); + } + + /** + * Prepare option value for info buy request + * + * @param string $optionValue + * @return array + */ + public function prepareOptionValueForRequest($optionValue) + { + $confItem = $this->getConfigurationItem(); + $infoBuyRequest = $confItem->getOptionByCode('info_buyRequest'); + try { + $value = $this->serializer->unserialize($infoBuyRequest->getValue()); + if (is_array($value) && isset($value['options']) && isset($value['options'][$this->getOption()->getId()]) + ) { + return $value['options'][$this->getOption()->getId()]; + } else { + return ['date_internal' => $optionValue]; + } + } catch (\Exception $e) { + return ['date_internal' => $optionValue]; + } + } + + /** + * Use Calendar on frontend or not + * + * @return boolean + */ + public function useCalendar() + { + return (bool)$this->getConfigData('use_calendar'); + } + + /** + * Time Format + * + * @return boolean + */ + public function is24hTimeFormat() + { + return (bool)($this->getConfigData('time_format') == '24h'); + } + + /** + * Year range start + * + * @return string|false + */ + public function getYearStart() + { + $_range = explode(',', $this->getConfigData('year_range')); + if (isset($_range[0]) && !empty($_range[0])) { + return $_range[0]; + } else { + return date('Y'); + } + } + + /** + * Year range end + * + * @return string|false + */ + public function getYearEnd() + { + $_range = explode(',', $this->getConfigData('year_range')); + if (isset($_range[1]) && !empty($_range[1])) { + return $_range[1]; + } else { + return date('Y'); + } + } + + /** + * Save internal value of option in infoBuy_request + * + * @param string $internalValue Datetime value in internal format + * @return void + */ + protected function _setInternalInRequest($internalValue) + { + $requestOptions = $this->getRequest()->getOptions(); + if (!isset($requestOptions[$this->getOption()->getId()])) { + $requestOptions[$this->getOption()->getId()] = []; + } + if (!is_array($requestOptions[$this->getOption()->getId()])) { + $requestOptions[$this->getOption()->getId()] = []; + } + $requestOptions[$this->getOption()->getId()]['date_internal'] = $internalValue; + $this->getRequest()->setOptions($requestOptions); + } + + /** + * Does option have date? + * + * @return boolean + */ + protected function _dateExists() + { + return in_array( + $this->getOption()->getType(), + [ + ProductCustomOptionInterface::OPTION_TYPE_DATE, + ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME + ] + ); + } + + /** + * Does option have time? + * + * @return boolean + */ + protected function _timeExists() + { + return in_array( + $this->getOption()->getType(), + [ + ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME, + ProductCustomOptionInterface::OPTION_TYPE_TIME + ] + ); + } +} diff --git a/Model/Usage/Option/Type/DefaultType.php b/Model/Usage/Option/Type/DefaultType.php new file mode 100644 index 0000000..cef5cf6 --- /dev/null +++ b/Model/Usage/Option/Type/DefaultType.php @@ -0,0 +1,410 @@ + + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @since 100.0.2 + */ +class DefaultType extends \Magento\Framework\DataObject +{ + /** + * Option Instance + * + * @var \Magento\Catalog\Model\Product\Option + */ + protected $_option; + + /** + * Product Instance + * + * @var \Magento\Catalog\Model\Product + */ + protected $_product; + + /** + * TODO: Fill in description + * + * @var array + */ + protected $_productOptions = []; + + /** + * Core store config + * + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + protected $_scopeConfig; + + /** + * Checkout session + * + * @var \Magento\Checkout\Model\Session + */ + protected $_checkoutSession; + + /** + * Construct + * + * @param \Magento\Checkout\Model\Session $checkoutSession + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param array $data + */ + public function __construct( + \Magento\Checkout\Model\Session $checkoutSession, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + array $data = [] + ) { + $this->_checkoutSession = $checkoutSession; + parent::__construct($data); + $this->_scopeConfig = $scopeConfig; + } + + /** + * Option Instance setter + * + * @param \Magento\Catalog\Model\Product\Option $option + * @return $this + */ + public function setOption($option) + { + $this->_option = $option; + return $this; + } + + /** + * Option Instance getter + * + * @throws \Magento\Framework\Exception\LocalizedException + * @return \Magento\Catalog\Model\Product\Option + */ + public function getOption() + { + if ($this->_option instanceof \Magento\Catalog\Model\Product\Option) { + return $this->_option; + } + throw new LocalizedException(__('The option instance type in options group is incorrect.')); + } + + /** + * Product Instance setter + * + * @param \Magento\Catalog\Model\Product $product + * @return $this + */ + public function setProduct($product) + { + $this->_product = $product; + return $this; + } + + /** + * Product Instance getter + * + * @throws \Magento\Framework\Exception\LocalizedException + * @return \Magento\Catalog\Model\Product + */ + public function getProduct() + { + if ($this->_product instanceof \Magento\Catalog\Model\Product) { + return $this->_product; + } + throw new LocalizedException(__('The product instance type in options group is incorrect.')); + } + + /** + * Getter for Configuration Item Option + * + * @return \Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface + * @throws LocalizedException + */ + public function getConfigurationItemOption() + { + if ($this->_getData( + 'configuration_item_option' + ) instanceof \Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface + ) { + return $this->_getData('configuration_item_option'); + } + + // Back compatibility with quote specific keys to set configuration item options + if ($this->_getData('quote_item_option') instanceof \Magento\Quote\Model\Quote\Item\Option) { + return $this->_getData('quote_item_option'); + } + + throw new LocalizedException(__('The configuration item option instance in options group is incorrect.')); + } + + /** + * Getter for Configuration Item + * + * @return \Magento\Catalog\Model\Product\Configuration\Item\ItemInterface + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getConfigurationItem() + { + if ($this->_getData( + 'configuration_item' + ) instanceof \Magento\Catalog\Model\Product\Configuration\Item\ItemInterface + ) { + return $this->_getData('configuration_item'); + } + + // Back compatibility with quote specific keys to set configuration item + if ($this->_getData('quote_item') instanceof \Magento\Quote\Model\Quote\Item) { + return $this->_getData('quote_item'); + } + + throw new LocalizedException(__('The configuration item instance in options group is incorrect.')); + } + + /** + * Getter for Buy Request + * + * @return \Magento\Framework\DataObject + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getRequest() + { + if ($this->_getData('request') instanceof \Magento\Framework\DataObject) { + return $this->_getData('request'); + } + throw new LocalizedException(__('The BuyRequest instance in options group is incorrect.')); + } + + /** + * Store Config value + * + * @param string $key Config value key + * @return string + */ + public function getConfigData($key) + { + return $this->_scopeConfig->getValue('catalog/custom_options/' . $key, \Magento\Store\Model\ScopeInterface::SCOPE_STORE); + } + + /** + * Validate user input for option + * + * @param array $values All product option values, i.e. array (option_id => mixed, option_id => mixed...) + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function validateUserValue($values) + { + $this->_checkoutSession->setUseNotice(false); + + $this->setIsValid(false); + + $option = $this->getOption(); + if (!isset($values[$option->getId()]) && $option->getIsRequire() && !$this->getSkipCheckRequiredOption()) { + throw new LocalizedException(__('Please specify product\'s required option(s).')); + } elseif (isset($values[$option->getId()])) { + $this->setUserValue($values[$option->getId()]); + $this->setIsValid(true); + } + return $this; + } + + /** + * Check skip required option validation + * + * @return bool + * @SuppressWarnings(PHPMD.BooleanGetMethodName) + */ + public function getSkipCheckRequiredOption() + { + return $this->getProduct()->getSkipCheckRequiredOption() || + $this->getProcessMode() == \Magento\Catalog\Model\Product\Type\AbstractType::PROCESS_MODE_LITE; + } + + /** + * Prepare option value for cart + * + * @return string|null Prepared option value + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function prepareForCart() + { + if ($this->getIsValid()) { + return $this->getUserValue(); + } + throw new LocalizedException( + __('We can\'t add the product to the cart because of an option validation issue.') + ); + } + + /** + * Flag to indicate that custom option has own customized output (blocks, native html etc.) + * + * @return boolean + */ + public function isCustomizedView() + { + return false; + } + + /** + * Return formatted option value for quote option + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getFormattedOptionValue($optionValue) + { + return $optionValue; + } + + /** + * Return option html + * + * @param array $optionInfo + * @return string + */ + public function getCustomizedView($optionInfo) + { + return isset($optionInfo['value']) ? $optionInfo['value'] : $optionInfo; + } + + /** + * Return printable option value + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getPrintableOptionValue($optionValue) + { + return $optionValue; + } + + /** + * Return formatted option value ready to edit, ready to parse + * (ex: Admin re-order, see \Magento\Sales\Model\AdminOrder\Create) + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getEditableOptionValue($optionValue) + { + return $optionValue; + } + + /** + * Parse user input value and return cart prepared value, i.e. "one, two" => "1,2" + * + * @param string $optionValue + * @param array $productOptionValues Values for product option + * @return string|null + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function parseOptionValue($optionValue, $productOptionValues) + { + return $optionValue; + } + + /** + * Prepare option value for info buy request + * + * @param string $optionValue + * @return string|null + */ + public function prepareOptionValueForRequest($optionValue) + { + return $optionValue; + } + + /** + * Return Price for selected option + * + * @param string $optionValue Prepared for cart option value + * @param float $basePrice For percent price type + * @return float + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getOptionPrice($optionValue, $basePrice) + { + $option = $this->getOption(); + + return $this->_getChargableOptionPrice($option->getPrice(), $option->getPriceType() == 'percent', $basePrice); + } + + /** + * Return SKU for selected option + * + * @param string $optionValue Prepared for cart option value + * @param string $skuDelimiter Delimiter for Sku parts + * @return string + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getOptionSku($optionValue, $skuDelimiter) + { + return $this->getOption()->getSku(); + } + + /** + * Return value => key all product options (using for parsing) + * + * @return array Array of Product custom options, reversing option values and option ids + */ + public function getProductOptions() + { + if (!isset($this->_productOptions[$this->getProduct()->getId()])) { + $options = $this->getProduct()->getOptions(); + if ($options != null) { + foreach ($options as $_option) { + /* @var $option \Magento\Catalog\Model\Product\Option */ + $this->_productOptions[$this->getProduct()->getId()][$_option->getTitle()] = [ + 'option_id' => $_option->getId(), + ]; + if ($_option->getGroupByType() == ProductCustomOptionInterface::OPTION_GROUP_SELECT) { + $optionValues = []; + foreach ($_option->getValues() as $_value) { + /* @var $value \Magento\Catalog\Model\Product\Option\Value */ + $optionValues[$_value->getTitle()] = $_value->getId(); + } + $this->_productOptions[$this + ->getProduct() + ->getId()][$_option + ->getTitle()]['values'] = $optionValues; + } else { + $this->_productOptions[$this->getProduct()->getId()][$_option->getTitle()]['values'] = []; + } + } + } + } + if (isset($this->_productOptions[$this->getProduct()->getId()])) { + return $this->_productOptions[$this->getProduct()->getId()]; + } + return []; + } + + /** + * Return final chargable price for option + * + * @param float $price Price of option + * @param boolean $isPercent Price type - percent or fixed + * @param float $basePrice For percent price type + * @return float + */ + protected function _getChargableOptionPrice($price, $isPercent, $basePrice) + { + if ($isPercent) { + return $basePrice * $price / 100; + } else { + return $price; + } + } +} diff --git a/Model/Usage/Option/Type/Factory.php b/Model/Usage/Option/Type/Factory.php new file mode 100644 index 0000000..d37554b --- /dev/null +++ b/Model/Usage/Option/Type/Factory.php @@ -0,0 +1,50 @@ +_objectManager = $objectManager; + } + + /** + * Create product option + * + * @param string $className + * @param array $data + * @return \Magento\Catalog\Model\Product\Option\Type\DefaultType + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function create($className, array $data = []) + { + $option = $this->_objectManager->create($className, $data); + + if (!$option instanceof \Magento\Catalog\Model\Product\Option\Type\DefaultType) { + throw new \Magento\Framework\Exception\LocalizedException( + __('%1 doesn\'t extends \Magento\Catalog\Model\Product\Option\Type\DefaultType', $className) + ); + } + return $option; + } +} diff --git a/Model/Usage/Option/Type/File.php b/Model/Usage/Option/Type/File.php new file mode 100644 index 0000000..52ad9b4 --- /dev/null +++ b/Model/Usage/Option/Type/File.php @@ -0,0 +1,532 @@ + + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class File extends \Magento\Catalog\Model\Product\Option\Type\DefaultType +{ + /** + * Url for custom option download controller + * @var string + */ + protected $_customOptionDownloadUrl = 'sales/download/downloadCustomOption'; + + /** + * @var string|null + */ + protected $_formattedOptionValue = null; + + /** + * @var \Magento\Framework\Filesystem\Directory\ReadInterface + * @deprecated 101.1.0 + * @see $mediaDirectory + */ + protected $_rootDirectory; + + /** + * @var \Magento\Framework\Filesystem\Directory\WriteInterface + */ + private $mediaDirectory; + + /** + * Core file storage database + * + * @var \Magento\MediaStorage\Helper\File\Storage\Database + */ + protected $_coreFileStorageDatabase = null; + + /** + * @var \Magento\Framework\Escaper + */ + protected $_escaper; + + /** + * Url + * + * @var \Magento\Catalog\Model\Product\Option\UrlBuilder + */ + protected $_urlBuilder; + + /** + * Item option factory + * + * @var \Magento\Quote\Model\Quote\Item\OptionFactory + */ + protected $_itemOptionFactory; + + /** + * @var File\ValidatorInfo + */ + protected $validatorInfo; + + /** + * @var File\ValidatorFile + */ + protected $validatorFile; + + /** + * @var Json + */ + private $serializer; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @param \Magento\Checkout\Model\Session $checkoutSession + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Quote\Model\Quote\Item\OptionFactory $itemOptionFactory + * @param \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase + * @param File\ValidatorInfo $validatorInfo + * @param File\ValidatorFile $validatorFile + * @param \Magento\Catalog\Model\Product\Option\UrlBuilder $urlBuilder + * @param \Magento\Framework\Escaper $escaper + * @param array $data + * @param Filesystem $filesystem + * @param Json|null $serializer + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + \Magento\Checkout\Model\Session $checkoutSession, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\Quote\Model\Quote\Item\OptionFactory $itemOptionFactory, + \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase, + \Magento\Catalog\Model\Product\Option\Type\File\ValidatorInfo $validatorInfo, + \Magento\Catalog\Model\Product\Option\Type\File\ValidatorFile $validatorFile, + \Magento\Catalog\Model\Product\Option\UrlBuilder $urlBuilder, + \Magento\Framework\Escaper $escaper, + array $data = [], + Filesystem $filesystem = null, + Json $serializer = null + ) { + $this->_itemOptionFactory = $itemOptionFactory; + $this->_urlBuilder = $urlBuilder; + $this->_escaper = $escaper; + $this->_coreFileStorageDatabase = $coreFileStorageDatabase; + $this->filesystem = $filesystem ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Filesystem::class); + /** The _rootDirectory is deprecated. The field is initialized for backward compatibility */ + $this->_rootDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); + $this->mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $this->validatorInfo = $validatorInfo; + $this->validatorFile = $validatorFile; + $this->serializer = $serializer ? $serializer : ObjectManager::getInstance()->get(Json::class); + parent::__construct($checkoutSession, $scopeConfig, $data); + } + + /** + * Flag to indicate that custom option has own customized output (blocks, native html etc.) + * + * @return boolean + */ + public function isCustomizedView() + { + return true; + } + + /** + * Return option html + * + * @param array $optionInfo + * @return string|void + */ + public function getCustomizedView($optionInfo) + { + try { + if (isset($optionInfo['option_value'])) { + return $this->_getOptionHtml($optionInfo['option_value']); + } elseif (isset($optionInfo['value'])) { + return $optionInfo['value']; + } + } catch (\Exception $e) { + return $optionInfo['value']; + } + } + + /** + * Returns additional params for processing options + * + * @return \Magento\Framework\DataObject + */ + protected function _getProcessingParams() + { + $buyRequest = $this->getRequest(); + $params = $buyRequest->getData('_processing_params'); + /* + * Notice check for params to be \Magento\Framework\DataObject - by using object we protect from + * params being forged and contain data from user frontend input + */ + if ($params instanceof \Magento\Framework\DataObject) { + return $params; + } + return new \Magento\Framework\DataObject(); + } + + /** + * Returns file info array if we need to get file from already existing file. + * Or returns null, if we need to get file from uploaded array. + * + * @return null|array + */ + protected function _getCurrentConfigFileInfo() + { + $option = $this->getOption(); + $optionId = $option->getId(); + $processingParams = $this->_getProcessingParams(); + $buyRequest = $this->getRequest(); + + // Check maybe restore file from config requested + $optionActionKey = 'options_' . $optionId . '_file_action'; + if ($buyRequest->getData($optionActionKey) == 'save_old') { + $fileInfo = []; + $currentConfig = $processingParams->getCurrentConfig(); + if ($currentConfig) { + $fileInfo = $currentConfig->getData('options/' . $optionId); + } + return $fileInfo; + } + return null; + } + + /** + * Validate user input for option + * + * @param array $values All product option values, i.e. array (option_id => mixed, option_id => mixed...) + * @return $this + * @throws LocalizedException + * @throws \Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function validateUserValue($values) + { + $this->_checkoutSession->setUseNotice(false); + + $this->setIsValid(true); + $option = $this->getOption(); + + /* + * Check whether we receive uploaded file or restore file by: reorder/edit configuration or + * previous configuration with no newly uploaded file + */ + $fileInfo = null; + if (isset($values[$option->getId()]) && is_array($values[$option->getId()])) { + // Legacy style, file info comes in array with option id index + $fileInfo = $values[$option->getId()]; + } else { + /* + * New recommended style - file info comes in request processing parameters and we + * sure that this file info originates from Magento, not from manually formed POST request + */ + $fileInfo = $this->_getCurrentConfigFileInfo(); + } + if ($fileInfo !== null) { + try { + $value = $this->validatorInfo->setUseQuotePath($this->getUseQuotePath()) + ->validate($fileInfo, $option) ? $fileInfo : null; + $this->setUserValue($value); + return $this; + } catch (LocalizedException $exception) { + $this->setIsValid(false); + throw $exception; + } + } + + // Process new uploaded file + try { + $value = $this->validatorFile->setProduct($this->getProduct()) + ->validate($this->_getProcessingParams(), $option); + $this->setUserValue($value); + } catch (ProductException $e) { + switch ($this->getProcessMode()) { + case \Magento\Catalog\Model\Product\Type\AbstractType::PROCESS_MODE_FULL: + throw new LocalizedException(__('Please specify product\'s required option(s).')); + break; + default: + $this->setUserValue(null); + break; + } + } catch (\Magento\Framework\Validator\Exception $e) { + $this->setUserValue(null); + } catch (LocalizedException $e) { + $this->setIsValid(false); + throw new LocalizedException(__($e->getMessage())); + } catch (\Exception $e) { + if ($this->getSkipCheckRequiredOption()) { + $this->setUserValue(null); + } else { + throw new LocalizedException(__($e->getMessage())); + } + } + return $this; + } + + /** + * Prepare option value for cart + * + * @return string|null Prepared option value + */ + public function prepareForCart() + { + $option = $this->getOption(); + $optionId = $option->getId(); + $buyRequest = $this->getRequest(); + + // Prepare value and fill buyRequest with option + $requestOptions = $buyRequest->getOptions(); + if ($this->getIsValid() && $this->getUserValue() !== null) { + $value = $this->getUserValue(); + + // Save option in request, because we have no $_FILES['options'] + $requestOptions[$this->getOption()->getId()] = $value; + $result = $this->serializer->serialize($value); + } else { + /* + * Clear option info from request, so it won't be stored in our db upon + * unsuccessful validation. Otherwise some bad file data can happen in buyRequest + * and be used later in reorders and reconfigurations. + */ + if (is_array($requestOptions)) { + unset($requestOptions[$this->getOption()->getId()]); + } + $result = null; + } + $buyRequest->setOptions($requestOptions); + + // Clear action key from buy request - we won't need it anymore + $optionActionKey = 'options_' . $optionId . '_file_action'; + $buyRequest->unsetData($optionActionKey); + + return $result; + } + + /** + * Return formatted option value for quote option + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getFormattedOptionValue($optionValue) + { + if ($this->_formattedOptionValue === null) { + $value = $this->serializer->unserialize($optionValue); + if ($value === null) { + return $optionValue; + } + $customOptionUrlParams = $this->getCustomOptionUrlParams() + ? $this->getCustomOptionUrlParams() + : [ + 'id' => $this->getConfigurationItemOption()->getId(), + 'key' => $value['secret_key'] + ]; + + $value['url'] = ['route' => $this->_customOptionDownloadUrl, 'params' => $customOptionUrlParams]; + + $this->_formattedOptionValue = $this->_getOptionHtml($value); + $this->getConfigurationItemOption()->setValue($this->serializer->serialize($value)); + } + return $this->_formattedOptionValue; + } + + /** + * Format File option html + * + * @param string|array $optionValue Serialized string of option data or its data array + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + */ + protected function _getOptionHtml($optionValue) + { + $value = $this->_unserializeValue($optionValue); + try { + $sizes = $this->prepareSize($value); + + $urlRoute = !empty($value['url']['route']) ? $value['url']['route'] : ''; + $urlParams = !empty($value['url']['params']) ? $value['url']['params'] : ''; + $title = !empty($value['title']) ? $value['title'] : ''; + + return sprintf( + '%s %s', + $this->_getOptionDownloadUrl($urlRoute, $urlParams), + $this->_escaper->escapeHtml($title), + $sizes + ); + } catch (\Exception $e) { + throw new LocalizedException(__('The file options format is not valid.')); + } + } + + /** + * Create a value from a storable representation + * + * @param string|array $value + * @return array + */ + protected function _unserializeValue($value) + { + if (is_array($value)) { + return $value; + } elseif (is_string($value) && !empty($value)) { + return $this->serializer->unserialize($value); + } else { + return []; + } + } + + /** + * Return printable option value + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getPrintableOptionValue($optionValue) + { + return strip_tags($this->getFormattedOptionValue($optionValue)); + } + + /** + * Return formatted option value ready to edit, ready to parse + * + * @param string $optionValue Prepared for cart option value + * @return string + * + * @deprecated 101.1.0 + */ + public function getEditableOptionValue($optionValue) + { + $unserializedValue = $this->serializer->unserialize($optionValue); + if ($unserializedValue !== null) { + return sprintf( + '%s [%d]', + $this->_escaper->escapeHtml($unserializedValue['title']), + $this->getConfigurationItemOption()->getId() + ); + } + return $optionValue; + } + + /** + * Parse user input value and return cart prepared value + * + * @param string $optionValue + * @param array $productOptionValues Values for product option + * @return string|null + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * + * @deprecated 101.1.0 + */ + public function parseOptionValue($optionValue, $productOptionValues) + { + // search quote item option Id in option value + if (preg_match('/\[([0-9]+)\]/', $optionValue, $matches)) { + $confItemOptionId = $matches[1]; + $option = $this->_itemOptionFactory->create()->load($confItemOptionId); + if ($this->serializer->unserialize($option->getValue()) !== null) { + return $option->getValue(); + } + } + return null; + } + + /** + * Prepare option value for info buy request + * + * @param string $optionValue + * @return string|null + */ + public function prepareOptionValueForRequest($optionValue) + { + $unserializedValue = $this->serializer->unserialize($optionValue); + if ($unserializedValue !== null) { + return $unserializedValue; + } + return null; + } + + /** + * Quote item to order item copy process + * + * @return $this + */ + public function copyQuoteToOrder() + { + $quoteOption = $this->getConfigurationItemOption(); + try { + $value = $this->serializer->unserialize($quoteOption->getValue()); + if (!isset($value['quote_path'])) { + throw new \Exception(); + } + $quotePath = $value['quote_path']; + $orderPath = $value['order_path']; + + if (!$this->mediaDirectory->isFile($quotePath) || !$this->mediaDirectory->isReadable($quotePath)) { + throw new \Exception(); + } + + if ($this->_coreFileStorageDatabase->checkDbUsage()) { + $this->_coreFileStorageDatabase->copyFile( + $this->mediaDirectory->getAbsolutePath($quotePath), + $this->mediaDirectory->getAbsolutePath($orderPath) + ); + } else { + $this->mediaDirectory->copyFile($quotePath, $orderPath); + } + } catch (\Exception $e) { + return $this; + } + return $this; + } + + /** + * Set url to custom option download controller + * + * @param string $url + * @return $this + */ + public function setCustomOptionDownloadUrl($url) + { + $this->_customOptionDownloadUrl = $url; + return $this; + } + + /** + * Return URL for option file download + * + * @param string|null $route + * @param array|null $params + * @return string + */ + protected function _getOptionDownloadUrl($route, $params) + { + return $this->_urlBuilder->getUrl($route, $params); + } + + /** + * @param array $value + * @return string + */ + protected function prepareSize($value) + { + $sizes = ''; + if (!empty($value['width']) && !empty($value['height']) && $value['width'] > 0 && $value['height'] > 0) { + $sizes = $value['width'] . ' x ' . $value['height'] . ' ' . __('px.'); + } + return $sizes; + } +} diff --git a/Model/Usage/Option/Type/File/ValidateFactory.php b/Model/Usage/Option/Type/File/ValidateFactory.php new file mode 100644 index 0000000..6bac550 --- /dev/null +++ b/Model/Usage/Option/Type/File/ValidateFactory.php @@ -0,0 +1,18 @@ +scopeConfig = $scopeConfig; + $this->rootDirectory = $filesystem->getDirectoryRead(DirectoryList::MEDIA); + $this->fileSize = $fileSize; + } + + /** + * Store Config value + * + * @param string $key Config value key + * @return string + */ + protected function getConfigData($key) + { + return $this->scopeConfig->getValue( + 'catalog/custom_options/' . $key, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } + + /** + * Get Error messages for validator Errors + * + * @param string[] $errors Array of validation failure message codes @see \Zend_Validate::getErrors() + * @param array $fileInfo File info + * @param \Magento\Catalog\Model\Product\Option $option + * @return string[] Array of error messages + * @see \Magento\Catalog\Model\Product\Option\Type\File::_getValidatorErrors + */ + protected function getValidatorErrors($errors, $fileInfo, $option) + { + $result = []; + foreach ($errors as $errorCode) { + switch ($errorCode) { + case \Zend_Validate_File_ExcludeExtension::FALSE_EXTENSION: + $result[] = __( + "The file '%1' for '%2' has an invalid extension.", + $fileInfo['title'], + $option->getTitle() + ); + break; + case \Zend_Validate_File_Extension::FALSE_EXTENSION: + $result[] = __( + "The file '%1' for '%2' has an invalid extension.", + $fileInfo['title'], + $option->getTitle() + ); + break; + case \Zend_Validate_File_ImageSize::WIDTH_TOO_BIG: + case \Zend_Validate_File_ImageSize::HEIGHT_TOO_BIG: + $result[] = __( + "The maximum allowed image size for '%1' is %2x%3 px.", + $option->getTitle(), + $option->getImageSizeX(), + $option->getImageSizeY() + ); + break; + case \Zend_Validate_File_FilesSize::TOO_BIG: + $result[] = __( + "The file '%1' you uploaded is larger than the %2 megabytes allowed by our server.", + $fileInfo['title'], + $this->fileSize->getMaxFileSizeInMb() + ); + break; + case \Zend_Validate_File_ImageSize::NOT_DETECTED: + $result[] = __( + "The file '%1' is empty. Please choose another one", + $fileInfo['title'] + ); + break; + default: + $result[] = __( + "The file '%1' is invalid. Please choose another one", + $fileInfo['title'] + ); + } + } + return $result; + } + + /** + * Parse file extensions string with various separators + * + * @param string $extensions String to parse + * @return array|null + * @see \Magento\Catalog\Model\Product\Option\Type\File::_parseExtensionsString + */ + protected function parseExtensionsString($extensions) + { + if (preg_match_all('/(?[a-z0-9]+)/si', strtolower($extensions), $matches)) { + return $matches['extension'] ?: null; + } + return null; + } + + /** + * @param \Zend_File_Transfer_Adapter_Http|\Zend_Validate $object + * @param \Magento\Catalog\Model\Product\Option $option + * @param array $fileFullPath + * @return \Zend_File_Transfer_Adapter_Http|\Zend_Validate $object + * @throws \Magento\Framework\Exception\InputException + */ + protected function buildImageValidator($object, $option, $fileFullPath = null) + { + $dimensions = []; + + if ($option->getImageSizeX() > 0) { + $dimensions['maxwidth'] = $option->getImageSizeX(); + } + if ($option->getImageSizeY() > 0) { + $dimensions['maxheight'] = $option->getImageSizeY(); + } + if (count($dimensions) > 0) { + if ($fileFullPath !== null && !$this->isImage($fileFullPath)) { + throw new \Magento\Framework\Exception\InputException( + __('File \'%1\' is not an image.', $option->getTitle()) + ); + } + $object->addValidator(new \Zend_Validate_File_ImageSize($dimensions)); + } + + // File extension + $allowed = $this->parseExtensionsString($option->getFileExtension()); + if ($allowed !== null) { + $object->addValidator(new \Zend_Validate_File_Extension($allowed)); + } else { + $forbidden = $this->parseExtensionsString($this->getConfigData('forbidden_extensions')); + if ($forbidden !== null) { + $object->addValidator(new \Zend_Validate_File_ExcludeExtension($forbidden)); + } + } + + $object->addValidator( + new \Zend_Validate_File_FilesSize(['max' => $this->fileSize->getMaxFileSize()]) + ); + return $object; + } + + /** + * Simple check if file is image + * + * @param array|string $fileInfo - either file data from \Zend_File_Transfer or file path + * @return boolean + * @see \Magento\Catalog\Model\Product\Option\Type\File::_isImage + */ + protected function isImage($fileInfo) + { + // Maybe array with file info came in + if (is_array($fileInfo)) { + return strstr($fileInfo['type'], 'image/'); + } + + // File path came in - check the physical file + if (!$this->rootDirectory->isReadable($this->rootDirectory->getRelativePath($fileInfo))) { + return false; + } + $imageInfo = getimagesize($fileInfo); + if (!$imageInfo) { + return false; + } + return true; + } +} diff --git a/Model/Usage/Option/Type/File/ValidatorFile.php b/Model/Usage/Option/Type/File/ValidatorFile.php new file mode 100644 index 0000000..0ef3a67 --- /dev/null +++ b/Model/Usage/Option/Type/File/ValidatorFile.php @@ -0,0 +1,244 @@ +mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $this->filesystem = $filesystem; + $this->httpFactory = $httpFactory; + $this->isImageValidator = $isImageValidator; + parent::__construct($scopeConfig, $filesystem, $fileSize); + } + + /** + * @param Product $product + * @return $this + */ + public function setProduct(Product $product) + { + $this->product = $product; + return $this; + } + + /** + * @param \Magento\Framework\DataObject $processingParams + * @param \Magento\Catalog\Model\Product\Option $option + * @return array + * @throws LocalizedException + * @throws ProductException + * @throws \Exception + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Validator\Exception + * @throws \Zend_File_Transfer_Exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function validate($processingParams, $option) + { + $upload = $this->httpFactory->create(); + $file = $processingParams->getFilesPrefix() . 'options_' . $option->getId() . '_file'; + try { + $runValidation = $option->getIsRequire() || $upload->isUploaded($file); + if (!$runValidation) { + throw new \Magento\Framework\Validator\Exception( + __('Validation failed. Required options were not filled or the file was not uploaded.') + ); + } + + $fileInfo = $upload->getFileInfo($file)[$file]; + $fileInfo['title'] = $fileInfo['name']; + } catch (\Magento\Framework\Validator\Exception $e) { + throw $e; + } catch (\Exception $e) { + // when file exceeds the upload_max_filesize, $_FILES is empty + if ($this->validateContentLength()) { + $value = $this->fileSize->getMaxFileSizeInMb(); + throw new LocalizedException( + __('The file you uploaded is larger than %1 Megabytes allowed by server', $value) + ); + } else { + throw new ProductException(__('Option required.')); + } + } + + /** + * Option Validations + */ + $upload = $this->buildImageValidator($upload, $option); + + /** + * Upload process + */ + $this->initFilesystem(); + $userValue = []; + + if ($upload->isUploaded($file) && $upload->isValid($file)) { + $extension = pathinfo(strtolower($fileInfo['name']), PATHINFO_EXTENSION); + + $fileName = \Magento\MediaStorage\Model\File\Uploader::getCorrectFileName($fileInfo['name']); + $dispersion = \Magento\MediaStorage\Model\File\Uploader::getDispretionPath($fileName); + + $filePath = $dispersion; + + $tmpDirectory = $this->filesystem->getDirectoryRead(DirectoryList::SYS_TMP); + $fileHash = md5($tmpDirectory->readFile($tmpDirectory->getRelativePath($fileInfo['tmp_name']))); + $filePath .= '/' . $fileHash . '.' . $extension; + $fileFullPath = $this->mediaDirectory->getAbsolutePath($this->quotePath . $filePath); + + $upload->addFilter(new \Zend_Filter_File_Rename(['target' => $fileFullPath, 'overwrite' => true])); + + if ($this->product !== null) { + $this->product->getTypeInstance()->addFileQueue( + [ + 'operation' => 'receive_uploaded_file', + 'src_name' => $file, + 'dst_name' => $fileFullPath, + 'uploader' => $upload, + 'option' => $this, + ] + ); + } + + $_width = 0; + $_height = 0; + + if ($tmpDirectory->isReadable($tmpDirectory->getRelativePath($fileInfo['tmp_name']))) { + if (filesize($fileInfo['tmp_name'])) { + if ($this->isImageValidator->isValid($fileInfo['tmp_name'])) { + $imageSize = getimagesize($fileInfo['tmp_name']); + } + } else { + throw new LocalizedException(__('The file is empty. Please choose another one')); + } + + if (!empty($imageSize)) { + $_width = $imageSize[0]; + $_height = $imageSize[1]; + } + } + + $userValue = [ + 'type' => $fileInfo['type'], + 'title' => $fileInfo['name'], + 'quote_path' => $this->quotePath . $filePath, + 'order_path' => $this->orderPath . $filePath, + 'fullpath' => $fileFullPath, + 'size' => $fileInfo['size'], + 'width' => $_width, + 'height' => $_height, + 'secret_key' => substr($fileHash, 0, 20), + ]; + } elseif ($upload->getErrors()) { + $errors = $this->getValidatorErrors($upload->getErrors(), $fileInfo, $option); + + if (count($errors) > 0) { + throw new LocalizedException(__(implode("\n", $errors))); + } + } else { + throw new LocalizedException(__('Please specify product\'s required option(s).')); + } + return $userValue; + } + + /** + * Directory structure initializing + * + * @return void + * @see \Magento\Catalog\Model\Product\Option\Type\File::_initFilesystem + */ + protected function initFilesystem() + { + $this->mediaDirectory->create($this->path); + $this->mediaDirectory->create($this->quotePath); + $this->mediaDirectory->create($this->orderPath); + + // Directory listing and hotlink secure + $path = $this->path . '/.htaccess'; + if (!$this->mediaDirectory->isFile($path)) { + $this->mediaDirectory->writeFile($path, "Order deny,allow\nDeny from all"); + } + } + + /** + * @return bool + * @todo need correctly name + */ + protected function validateContentLength() + { + return isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > $this->fileSize->getMaxFileSize(); + } +} diff --git a/Model/Usage/Option/Type/File/ValidatorInfo.php b/Model/Usage/Option/Type/File/ValidatorInfo.php new file mode 100644 index 0000000..7e94064 --- /dev/null +++ b/Model/Usage/Option/Type/File/ValidatorInfo.php @@ -0,0 +1,151 @@ +coreFileStorageDatabase = $coreFileStorageDatabase; + $this->validateFactory = $validateFactory; + parent::__construct($scopeConfig, $filesystem, $fileSize); + } + + /** + * @param mixed $useQuotePath + * @return $this + */ + public function setUseQuotePath($useQuotePath) + { + $this->useQuotePath = $useQuotePath; + return $this; + } + + /** + * @param array $optionValue + * @param \Magento\Catalog\Model\Product\Option $option + * @return bool + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function validate($optionValue, $option) + { + if (!is_array($optionValue)) { + return false; + } + + $this->fileFullPath = null; + $this->fileRelativePath = null; + $this->initFilePath($optionValue); + + if ($this->fileFullPath === null) { + return false; + } + + $validatorChain = $this->validateFactory->create(); + try { + $validatorChain = $this->buildImageValidator($validatorChain, $option, $this->fileFullPath); + } catch (\Magento\Framework\Exception\InputException $notImage) { + return false; + } + + $result = false; + if ($validatorChain->isValid($this->fileFullPath)) { + $result = $this->rootDirectory->isReadable($this->fileRelativePath) + && isset($optionValue['secret_key']) + && $this->buildSecretKey($this->fileRelativePath) == $optionValue['secret_key']; + } elseif ($validatorChain->getErrors()) { + $errors = $this->getValidatorErrors($validatorChain->getErrors(), $optionValue, $option); + + if (count($errors) > 0) { + throw new \Magento\Framework\Exception\LocalizedException(__(implode("\n", $errors))); + } + } else { + throw new \Magento\Framework\Exception\LocalizedException( + __('Please specify product\'s required option(s).') + ); + } + return $result; + } + + /** + * @param string $fileRelativePath + * @return string + */ + protected function buildSecretKey($fileRelativePath) + { + return substr(md5($this->rootDirectory->readFile($fileRelativePath)), 0, 20); + } + + /** + * @param array $optionValue + * @return void + */ + protected function initFilePath($optionValue) + { + /** + * @see \Magento\Catalog\Model\Product\Option\Type\File\ValidatorFile::validate + * There setUserValue() sets correct fileFullPath only for + * quote_path. So we must form both full paths manually and + * check them. + */ + $checkPaths = []; + if (isset($optionValue['quote_path'])) { + $checkPaths[] = $optionValue['quote_path']; + } + if (isset($optionValue['order_path']) && !$this->useQuotePath) { + $checkPaths[] = $optionValue['order_path']; + } + + foreach ($checkPaths as $path) { + if (!$this->rootDirectory->isFile($path)) { + if (!$this->coreFileStorageDatabase->saveFileToFilesystem($path)) { + continue; + } + } + $this->fileFullPath = $this->rootDirectory->getAbsolutePath($path); + $this->fileRelativePath = $path; + break; + } + } +} diff --git a/Model/Usage/Option/Type/Select.php b/Model/Usage/Option/Type/Select.php new file mode 100644 index 0000000..85d4631 --- /dev/null +++ b/Model/Usage/Option/Type/Select.php @@ -0,0 +1,310 @@ +string = $string; + $this->_escaper = $escaper; + parent::__construct($checkoutSession, $scopeConfig, $data); + } + + /** + * Validate user input for option + * + * @param array $values All product option values, i.e. array (option_id => mixed, option_id => mixed...) + * @return $this + * @throws LocalizedException + */ + public function validateUserValue($values) + { + parent::validateUserValue($values); + + $option = $this->getOption(); + $value = $this->getUserValue(); + + if (empty($value) && $option->getIsRequire() && !$this->getSkipCheckRequiredOption()) { + $this->setIsValid(false); + throw new LocalizedException(__('Please specify product\'s required option(s).')); + } + if (!$this->_isSingleSelection()) { + $valuesCollection = $option->getOptionValuesByOptionId($value, $this->getProduct()->getStoreId())->load(); + if ($valuesCollection->count() != count($value)) { + $this->setIsValid(false); + throw new LocalizedException(__('Please specify product\'s required option(s).')); + } + } + return $this; + } + + /** + * Prepare option value for cart + * + * @return string|null Prepared option value + */ + public function prepareForCart() + { + if ($this->getIsValid() && $this->getUserValue()) { + return is_array($this->getUserValue()) ? implode(',', $this->getUserValue()) : $this->getUserValue(); + } else { + return null; + } + } + + /** + * Return formatted option value for quote option + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getFormattedOptionValue($optionValue) + { + if ($this->_formattedOptionValue === null) { + $this->_formattedOptionValue = $this->_escaper->escapeHtml($this->getEditableOptionValue($optionValue)); + } + return $this->_formattedOptionValue; + } + + /** + * Return printable option value + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getPrintableOptionValue($optionValue) + { + return $this->getFormattedOptionValue($optionValue); + } + + /** + * Return currently unavailable product configuration message + * + * @return \Magento\Framework\Phrase + */ + protected function _getWrongConfigurationMessage() + { + return __('Some of the selected item options are not currently available.'); + } + + /** + * Return formatted option value ready to edit, ready to parse + * + * @param string $optionValue Prepared for cart option value + * @return string + */ + public function getEditableOptionValue($optionValue) + { + $option = $this->getOption(); + $result = ''; + if (!$this->_isSingleSelection()) { + foreach (explode(',', $optionValue) as $_value) { + $_result = $option->getValueById($_value); + if ($_result) { + $result .= $_result->getTitle() . ', '; + } else { + if ($this->getListener()) { + $this->getListener()->setHasError(true)->setMessage($this->_getWrongConfigurationMessage()); + $result = ''; + break; + } + } + } + $result = $this->string->substr($result, 0, -2); + } elseif ($this->_isSingleSelection()) { + $_result = $option->getValueById($optionValue); + if ($_result) { + $result = $_result->getTitle(); + } else { + if ($this->getListener()) { + $this->getListener()->setHasError(true)->setMessage($this->_getWrongConfigurationMessage()); + } + $result = ''; + } + } else { + $result = $optionValue; + } + return $result; + } + + /** + * Parse user input value and return cart prepared value, i.e. "one, two" => "1,2" + * + * @param string $optionValue + * @param array $productOptionValues Values for product option + * @return string|null + */ + public function parseOptionValue($optionValue, $productOptionValues) + { + $values = []; + if (!$this->_isSingleSelection()) { + foreach (explode(',', $optionValue) as $value) { + $value = trim($value); + if (array_key_exists($value, $productOptionValues)) { + $values[] = $productOptionValues[$value]; + } + } + } elseif ($this->_isSingleSelection() && array_key_exists($optionValue, $productOptionValues)) { + $values[] = $productOptionValues[$optionValue]; + } + if (count($values)) { + return implode(',', $values); + } else { + return null; + } + } + + /** + * Prepare option value for info buy request + * + * @param string $optionValue + * @return string + */ + public function prepareOptionValueForRequest($optionValue) + { + if (!$this->_isSingleSelection()) { + return explode(',', $optionValue); + } + return $optionValue; + } + + /** + * Return Price for selected option + * + * @param string $optionValue Prepared for cart option value + * @param float $basePrice + * @return float + */ + public function getOptionPrice($optionValue, $basePrice) + { + $option = $this->getOption(); + $result = 0; + + if (!$this->_isSingleSelection()) { + foreach (explode(',', $optionValue) as $value) { + $_result = $option->getValueById($value); + if ($_result) { + $result += $this->_getChargableOptionPrice( + $_result->getPrice(), + $_result->getPriceType() == 'percent', + $basePrice + ); + } else { + if ($this->getListener()) { + $this->getListener()->setHasError(true)->setMessage($this->_getWrongConfigurationMessage()); + break; + } + } + } + } elseif ($this->_isSingleSelection()) { + $_result = $option->getValueById($optionValue); + if ($_result) { + $result = $this->_getChargableOptionPrice( + $_result->getPrice(), + $_result->getPriceType() == 'percent', + $basePrice + ); + } else { + if ($this->getListener()) { + $this->getListener()->setHasError(true)->setMessage($this->_getWrongConfigurationMessage()); + } + } + } + + return $result; + } + + /** + * Return SKU for selected option + * + * @param string $optionValue Prepared for cart option value + * @param string $skuDelimiter Delimiter for Sku parts + * @return string + */ + public function getOptionSku($optionValue, $skuDelimiter) + { + $option = $this->getOption(); + + if (!$this->_isSingleSelection()) { + $skus = []; + foreach (explode(',', $optionValue) as $value) { + $optionSku = $option->getValueById($value); + if ($optionSku) { + $skus[] = $optionSku->getSku(); + } else { + if ($this->getListener()) { + $this->getListener()->setHasError(true)->setMessage($this->_getWrongConfigurationMessage()); + break; + } + } + } + $result = implode($skuDelimiter, $skus); + } elseif ($this->_isSingleSelection()) { + $result = $option->getValueById($optionValue); + if ($result) { + return $result->getSku(); + } else { + if ($this->getListener()) { + $this->getListener()->setHasError(true)->setMessage($this->_getWrongConfigurationMessage()); + } + return ''; + } + } else { + $result = parent::getOptionSku($optionValue, $skuDelimiter); + } + + return $result; + } + + /** + * Check if option has single or multiple values selection + * + * @return boolean + */ + protected function _isSingleSelection() + { + $single = [ + \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN, + \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO, + ]; + return in_array($this->getOption()->getType(), $single); + } +} diff --git a/Model/Usage/Option/Type/Text.php b/Model/Usage/Option/Type/Text.php new file mode 100644 index 0000000..a4b0ce0 --- /dev/null +++ b/Model/Usage/Option/Type/Text.php @@ -0,0 +1,94 @@ +_escaper = $escaper; + $this->string = $string; + parent::__construct($checkoutSession, $scopeConfig, $data); + } + + /** + * Validate user input for option + * + * @param array $values All product option values, i.e. array (option_id => mixed, option_id => mixed...) + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function validateUserValue($values) + { + parent::validateUserValue($values); + + $option = $this->getOption(); + $value = trim($this->getUserValue()); + + // Check requires option to have some value + if (strlen($value) == 0 && $option->getIsRequire() && !$this->getSkipCheckRequiredOption()) { + $this->setIsValid(false); + throw new LocalizedException(__('Please specify product\'s required option(s).')); + } + + $this->setUserValue($value); + return $this; + } + + /** + * Prepare option value for cart + * + * @return string|null Prepared option value + */ + public function prepareForCart() + { + if ($this->getIsValid() && strlen($this->getUserValue()) > 0) { + return $this->getUserValue(); + } else { + return null; + } + } + + /** + * Return formatted option value for quote option + * + * @param string $value Prepared for cart option value + * @return string + */ + public function getFormattedOptionValue($value) + { + return $this->_escaper->escapeHtml($value); + } +} diff --git a/Model/Usage/Option/UrlBuilder.php b/Model/Usage/Option/UrlBuilder.php new file mode 100644 index 0000000..6fcbdb7 --- /dev/null +++ b/Model/Usage/Option/UrlBuilder.php @@ -0,0 +1,32 @@ +_frontendUrlBuilder = $frontendUrlBuilder; + } + + /** + * @param string|null $route + * @param array|null $params + * @return string + */ + public function getUrl($route, $params) + { + return $this->_frontendUrlBuilder->getUrl($route, $params); + } +} diff --git a/Model/Usage/Option/Validator/DefaultValidator.php b/Model/Usage/Option/Validator/DefaultValidator.php new file mode 100644 index 0000000..33d0b5a --- /dev/null +++ b/Model/Usage/Option/Validator/DefaultValidator.php @@ -0,0 +1,167 @@ +getAll() as $option) { + foreach ($option['types'] as $type) { + $this->productOptionTypes[] = $type['name']; + } + } + + foreach ($priceConfig->toOptionArray() as $item) { + $this->priceTypes[] = $item['value']; + } + } + + /** + * Returns true if and only if $value meets the validation requirements + * + * If $value fails validation, then this method returns false, and + * getMessages() will return an array of messages that explain why the + * validation failed. + * + * @param \Magento\Catalog\Model\Product\Option $value + * @return boolean + * @throws Zend_Validate_Exception If validation of $value is impossible + */ + public function isValid($value) + { + $messages = []; + + if (!$this->validateOptionRequiredFields($value)) { + $messages['option required fields'] = 'Missed values for option required fields'; + } + + if (!$this->validateOptionType($value)) { + $messages['option type'] = 'Invalid option type'; + } + + if (!$this->validateOptionValue($value)) { + $messages['option values'] = 'Invalid option value'; + } + + $this->_addMessages($messages); + + return empty($messages); + } + + /** + * Validate option required fields + * + * @param Option $option + * @return bool + */ + protected function validateOptionRequiredFields(Option $option) + { + $storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID; + $product = $option->getProduct(); + if ($product) { + $storeId = $product->getStoreId(); + } + $title = $option->getTitle(); + return $this->isValidOptionTitle($title, $storeId) && !$this->isEmpty($option->getType()); + } + + /** + * Validate option title + * + * @param string $title + * @param int $storeId + * @return bool + */ + protected function isValidOptionTitle($title, $storeId) + { + // we should be able to set null title for not default store (used for deletion from store view) + if ($storeId > \Magento\Store\Model\Store::DEFAULT_STORE_ID && $title === null) { + return true; + } + if ($this->isEmpty($title)) { + return false; + } + + return true; + } + + /** + * Validate option type fields + * + * @param Option $option + * @return bool + */ + protected function validateOptionType(Option $option) + { + return $this->isInRange($option->getType(), $this->productOptionTypes); + } + + /** + * Validate option type fields + * + * @param Option $option + * @return bool + */ + protected function validateOptionValue(Option $option) + { + return $this->isInRange($option->getPriceType(), $this->priceTypes) && !$this->isNegative($option->getPrice()); + } + + /** + * Check whether value is empty + * + * @param mixed $value + * @return bool + */ + protected function isEmpty($value) + { + return empty($value); + } + + /** + * Check whether value is in range + * + * @param string $value + * @param array $range + * @return bool + */ + protected function isInRange($value, array $range) + { + return in_array($value, $range); + } + + /** + * Check whether value is not negative + * + * @param string $value + * @return bool + */ + protected function isNegative($value) + { + return intval($value) < 0; + } +} diff --git a/Model/Usage/Option/Validator/File.php b/Model/Usage/Option/Validator/File.php new file mode 100644 index 0000000..5440d1d --- /dev/null +++ b/Model/Usage/Option/Validator/File.php @@ -0,0 +1,20 @@ +isNegative($option->getImageSizeX()) && !$this->isNegative($option->getImageSizeY()); + } +} diff --git a/Model/Usage/Option/Validator/Pool.php b/Model/Usage/Option/Validator/Pool.php new file mode 100644 index 0000000..27cd876 --- /dev/null +++ b/Model/Usage/Option/Validator/Pool.php @@ -0,0 +1,30 @@ +validators = $validators; + } + + /** + * Get validator + * + * @param string $type + * @return \Zend_Validate_Interface + */ + public function get($type) + { + return isset($this->validators[$type]) ? $this->validators[$type] : $this->validators['default']; + } +} diff --git a/Model/Usage/Option/Validator/Select.php b/Model/Usage/Option/Validator/Select.php new file mode 100644 index 0000000..c2a90a0 --- /dev/null +++ b/Model/Usage/Option/Validator/Select.php @@ -0,0 +1,89 @@ +getValues() ?: $option->getData('values'); + + if (!is_array($values) || $this->isEmpty($values)) { + return false; + } + + //forbid removal of last value for option + if ($this->checkAllValuesRemoved($values)) { + return false; + } + + $storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID; + if ($option->getProduct()) { + $storeId = $option->getProduct()->getStoreId(); + } + foreach ($values as $value) { + if (isset($value['is_delete']) && (bool)$value['is_delete']) { + continue; + } + $type = isset($value['price_type']) ? $value['price_type'] : null; + $price = isset($value['price']) ? $value['price'] : null; + $title = isset($value['title']) ? $value['title'] : null; + if (!$this->isValidOptionPrice($type, $price, $storeId) + || !$this->isValidOptionTitle($title, $storeId) + ) { + return false; + } + } + return true; + } + + /** + * Validate option price + * + * @param string $priceType + * @param int $price + * @param int $storeId + * @return bool + */ + protected function isValidOptionPrice($priceType, $price, $storeId) + { + // we should be able to remove website values for default store fallback + if ($storeId > \Magento\Store\Model\Store::DEFAULT_STORE_ID && $priceType === null && $price === null) { + return true; + } + if (!$priceType && !$price) { + return true; + } + if (!$this->isInRange($priceType, $this->priceTypes) || $this->isNegative($price)) { + return false; + } + + return true; + } +} diff --git a/Model/Usage/Option/Validator/Text.php b/Model/Usage/Option/Validator/Text.php new file mode 100644 index 0000000..d86971d --- /dev/null +++ b/Model/Usage/Option/Validator/Text.php @@ -0,0 +1,19 @@ +_valueCollectionFactory = $valueCollectionFactory; + parent::__construct( + $context, + $registry, + $resource, + $resourceCollection, + $data + ); + } + + /** + * @return void + */ + protected function _construct() + { + $this->_init(\DevStone\UsageCalculator\Model\ResourceModel\Usage\Option\Value::class); + } + + /** + * @codeCoverageIgnoreStart + * @param mixed $value + * @return $this + */ + public function addValue($value) + { + $this->_values[] = $value; + return $this; + } + + /** + * @return array + */ + public function getValues() + { + return $this->_values; + } + + /** + * @param array $values + * @return $this + */ + public function setValues($values) + { + $this->_values = $values; + return $this; + } + + /** + * @return $this + */ + public function unsetValues() + { + $this->_values = []; + return $this; + } + + /** + * @param Option $option + * @return $this + */ + public function setOption(Option $option) + { + $this->_option = $option; + return $this; + } + + /** + * @return $this + */ + public function unsetOption() + { + $this->_option = null; + return $this; + } + + /** + * Enter description here... + * + * @return Option + */ + public function getOption() + { + return $this->_option; + } + + /** + * @param Usage $usage + * @return $this + */ + public function setProduct(Usage $usage) + { + $this->_usage = $usage; + return $this; + } + + //@codeCoverageIgnoreEnd + + /** + * @return Usage + */ + public function getUsage() + { + if (is_null($this->_usage)) { + $this->_usage = $this->getOption()->getUsage(); + } + return $this->_usage; + } + + /** + * @return $this + */ + public function saveValues() + { + foreach ($this->getValues() as $value) { + $this->setData( + $value + )->setData( + 'option_id', + $this->getOption()->getId() + )->setData( + 'store_id', + $this->getOption()->getStoreId() + ); + + if ($this->getData('is_delete') == '1') { + if ($this->getId()) { + $this->deleteValues($this->getId()); + $this->delete(); + } + } else { + $this->save(); + } + } + //eof foreach() + return $this; + } + + /** + * Return price. If $flag is true and price is percent + * return converted percent to price + * + * @param bool $flag + * @return float|int + */ + public function getPrice($flag = false) + { + if ($flag && $this->getPriceType() == self::TYPE_PERCENT) { + $basePrice = $this->getOption()->getUsage()->getPrice(); + $price = $basePrice * ($this->_getData(self::KEY_PRICE) / 100); + return $price; + } + return $this->_getData(self::KEY_PRICE); + } + + /** + * Return regular price. + * + * @return float|int + */ + public function getRegularPrice() + { + if ($this->getPriceType() == self::TYPE_PERCENT) { + $basePrice = $this->getOption()->getUsage()->getPrice(); + $price = $basePrice * ($this->_getData(self::KEY_PRICE) / 100); + return $price; + } + return $this->_getData(self::KEY_PRICE); + } + + /** + * Enter description here... + * + * @param Option $option + * @return \DevStone\UsageCalculator\Model\ResourceModel\Usage\Option\Value\Collection + */ + public function getValuesCollection(Option $option) + { + $collection = $this->_valueCollectionFactory->create()->addFieldToFilter( + 'option_id', + $option->getId() + )->getValues( + $option->getStoreId() + ); + + return $collection; + } + + /** + * @param array $optionIds + * @param int $option_id + * @param int $store_id + * @return \DevStone\UsageCalculator\Model\ResourceModel\Usage\Option\Value\Collection + */ + public function getValuesByOption($optionIds, $option_id, $store_id) + { + $collection = $this->_valueCollectionFactory->create()->addFieldToFilter( + 'option_id', + $option_id + )->getValuesByOption( + $optionIds, + $store_id + ); + + return $collection; + } + + /** + * @param int $option_id + * @return $this + */ + public function deleteValue($option_id) + { + $this->getResource()->deleteValue($option_id); + return $this; + } + + /** + * @param int $option_type_id + * @return $this + */ + public function deleteValues($option_type_id) + { + $this->getResource()->deleteValues($option_type_id); + return $this; + } + + /** + * Duplicate usage options value + * + * @param int $oldOptionId + * @param int $newOptionId + * @return $this + */ + public function duplicate($oldOptionId, $newOptionId) + { + $this->getResource()->duplicate($this, $oldOptionId, $newOptionId); + return $this; + } + + /** + * Get option title + * + * @return string + * @codeCoverageIgnoreStart + */ + public function getTitle() + { + return $this->_getData(self::KEY_TITLE); + } + + /** + * Get sort order + * + * @return int + */ + public function getSortOrder() + { + return $this->_getData(self::KEY_SORT_ORDER); + } + + /** + * Get price type + * + * @return string + */ + public function getPriceType() + { + return $this->_getData(self::KEY_PRICE_TYPE); + } + + + + /** + * Get Sku + * + * @return string|null + */ + public function getOptionTypeId() + { + return $this->_getData(self::KEY_OPTION_TYPE_ID); + } + + /** + * Set option title + * + * @param string $title + * @return $this + */ + public function setTitle($title) + { + return $this->setData(self::KEY_TITLE, $title); + } + + /** + * Set sort order + * + * @param int $sortOrder + * @return $this + */ + public function setSortOrder($sortOrder) + { + return $this->setData(self::KEY_SORT_ORDER, $sortOrder); + } + + /** + * Set price + * + * @param float $price + * @return $this + */ + public function setPrice($price) + { + return $this->setData(self::KEY_PRICE, $price); + } + + /** + * Set price type + * + * @param string $priceType + * @return $this + */ + public function setPriceType($priceType) + { + return $this->setData(self::KEY_PRICE_TYPE, $priceType); + } + + + + /** + * Set Option type id + * + * @param int $optionTypeId + * @return int|null + */ + public function setOptionTypeId($optionTypeId) + { + return $this->setData(self::KEY_OPTION_TYPE_ID, $optionTypeId); + } + + //@codeCoverageIgnoreEnd +} diff --git a/Model/Usage/SizesOptionsProvider.php b/Model/Usage/SizesOptionsProvider.php new file mode 100644 index 0000000..05b0551 --- /dev/null +++ b/Model/Usage/SizesOptionsProvider.php @@ -0,0 +1,57 @@ +sizeRepository = $sizeRepository; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->objectConverter = $objectConverter; + } + + /** + * @return array + */ + public function toOptionArray($placeholder = false) + { + $sizes = $this->sizeRepository->getList( + $this->searchCriteriaBuilder->create() + )->getItems(); + + $optionArray = $this->objectConverter->toOptionArray($sizes, 'entity_id', 'code'); + + if ($placeholder) { + array_unshift($optionArray, ['value' => '', 'label' => $placeholder]); + } + + return $optionArray; + } +} diff --git a/Model/UsageRepository.php b/Model/UsageRepository.php new file mode 100644 index 0000000..a5e334e --- /dev/null +++ b/Model/UsageRepository.php @@ -0,0 +1,184 @@ +resource = $resource; + $this->usageFactory = $usageFactory; + $this->usageCollectionFactory = $usageCollectionFactory; + $this->searchResultsFactory = $searchResultsFactory; + $this->dataObjectHelper = $dataObjectHelper; + $this->dataObjectProcessor = $dataObjectProcessor; + $this->storeManager = $storeManager; + $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor; + } + + /** + * {@inheritdoc} + */ + public function save( + \DevStone\UsageCalculator\Api\Data\UsageInterface $usage + ) { + /* if (empty($usage->getStoreId())) { + $storeId = $this->storeManager->getStore()->getId(); + $usage->setStoreId($storeId); + } */ + try { + $usage->getResource()->save($usage); + } catch (\Exception $exception) { + throw new CouldNotSaveException(__( + 'Could not save the usage: %1', + $exception->getMessage() + )); + } + return $usage; + } + + /** + * {@inheritdoc} + */ + public function getById($usageId) + { +// $usage = $this->usageFactory->create(); +// $usage->setData('store_id', 5); +// +// $usage->getResource()->load($usage, $usageId); + + $collection = $this->usageCollectionFactory->create(); + + $collection->addAttributeToSelect('*'); + +// $collection->addStoreFilter($this->storeManager->getStore()->getId(), false); + + $collection->addFieldToFilter('entity_id', $usageId); + + $items = $collection->getItems(); + $usage = current($items); + $usage->afterLoad(); + if (!$usage->getId()) { + throw new NoSuchEntityException(__('Usage with id "%1" does not exist.', $usageId)); + } + return $usage; + } + + /** + * {@inheritdoc} + */ + public function getList( + \Magento\Framework\Api\SearchCriteriaInterface $criteria + ) { + $collection = $this->usageCollectionFactory->create(); + + $collection->addAttributeToSelect('*'); + + foreach ($criteria->getFilterGroups() as $filterGroup) { + foreach ($filterGroup->getFilters() as $filter) { + if ($filter->getField() === 'store_id') { + $collection->addStoreFilter($filter->getValue(), false); + continue; + } + $condition = $filter->getConditionType() ?: 'eq'; + $collection->addFieldToFilter($filter->getField(), [$condition => $filter->getValue()]); + } + } + + $sortOrders = $criteria->getSortOrders(); + if ($sortOrders) { + /** @var SortOrder $sortOrder */ + foreach ($sortOrders as $sortOrder) { + $collection->addOrder( + $sortOrder->getField(), + ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC' + ); + } + } + $collection->setCurPage($criteria->getCurrentPage()); + $collection->setPageSize($criteria->getPageSize()); + + $searchResults = $this->searchResultsFactory->create(); + $searchResults->setSearchCriteria($criteria); + $searchResults->setTotalCount($collection->getSize()); + $searchResults->setItems($collection->getItems()); + return $searchResults; + } + + /** + * {@inheritdoc} + */ + public function delete( + \DevStone\UsageCalculator\Api\Data\UsageInterface $usage + ) { + try { + $usage->getResource()->delete($usage); + } catch (\Exception $exception) { + throw new CouldNotDeleteException(__( + 'Could not delete the Usage: %1', + $exception->getMessage() + )); + } + return true; + } + + /** + * {@inheritdoc} + */ + public function deleteById($usageId) + { + return $this->delete($this->getById($usageId)); + } +} diff --git a/Plugin/Block/Product/ListProduct.php b/Plugin/Block/Product/ListProduct.php new file mode 100644 index 0000000..6016f40 --- /dev/null +++ b/Plugin/Block/Product/ListProduct.php @@ -0,0 +1,28 @@ +getTypeId()) { + return $proceed($product); + } + + $priceText = 'Calculate Price'; + + return $priceText; + } +} diff --git a/Setup/EavTablesSetup.php b/Setup/EavTablesSetup.php new file mode 100644 index 0000000..dc6f843 --- /dev/null +++ b/Setup/EavTablesSetup.php @@ -0,0 +1,203 @@ +setup = $setup; + } + + public function createEavTables($entityCode) + { + $this->createEAVMainTable($entityCode); + $this->createEntityTable($entityCode, 'datetime', Table::TYPE_DATETIME); + $this->createEntityTable($entityCode, 'decimal', Table::TYPE_DECIMAL, '12,4'); + $this->createEntityTable($entityCode, 'int', Table::TYPE_INTEGER); + $this->createEntityTable($entityCode, 'text', Table::TYPE_TEXT, '64k'); + $this->createEntityTable($entityCode, 'varchar', Table::TYPE_TEXT, 255); + } + + protected function createEAVMainTable($entityCode) + { + $tableName = $entityCode . '_eav_attribute'; + + $table = $this->setup->getConnection()->newTable( + $this->setup->getTable($tableName) + )->addColumn( + 'attribute_id', + Table::TYPE_SMALLINT, + null, + ['identity' => false, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Attribute Id' + )->addColumn( + 'is_global', + Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '1'], + 'Is Global' + )->addColumn( + 'is_filterable', + Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Is Filterable' + )->addColumn( + 'is_visible', + Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '1'], + 'Is Visible' + )->addColumn( + 'validate_rules', + Table::TYPE_TEXT, + '64k', + [], + 'Validate Rules' + )->addColumn( + 'is_system', + Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Is System' + )->addColumn( + 'sort_order', + Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Sort Order' + )->addColumn( + 'data_model', + Table::TYPE_TEXT, + 255, + [], + 'Data Model' + )->addForeignKey( + $this->setup->getFkName($tableName, 'attribute_id', 'eav_attribute', 'attribute_id'), + 'attribute_id', + $this->setup->getTable('eav_attribute'), + 'attribute_id', + Table::ACTION_CASCADE + )->setComment( + $entityCode . 'Eav Attribute' + ); + $this->setup->getConnection()->createTable($table); + } + + protected function createEntityTable($entityCode, $type, $valueType, $valueLength = null) + { + $tableName = $entityCode . '_entity_' . $type; + + $table = $this->setup->getConnection() + ->newTable($this->setup->getTable($tableName)) + ->addColumn( + 'value_id', + Table::TYPE_INTEGER, + null, + ['identity' => true, 'nullable' => false, 'primary' => true], + 'Value ID' + ) + ->addColumn( + 'attribute_id', + Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Attribute ID' + ) + ->addColumn( + 'store_id', + Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Store ID' + ) + ->addColumn( + 'entity_id', + Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Entity ID' + ) + ->addColumn( + 'value', + $valueType, + $valueLength, + [], + 'Value' + ) + ->addIndex( + $this->setup->getIdxName( + $tableName, + ['entity_id', 'attribute_id', 'store_id'], + AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['entity_id', 'attribute_id', 'store_id'], + ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] + ) + ->addIndex( + $this->setup->getIdxName($tableName, ['entity_id']), + ['entity_id'] + ) + ->addIndex( + $this->setup->getIdxName($tableName, ['attribute_id']), + ['attribute_id'] + ) + ->addIndex( + $this->setup->getIdxName($tableName, ['store_id']), + ['store_id'] + ) + ->addForeignKey( + $this->setup->getFkName( + $tableName, + 'attribute_id', + 'eav_attribute', + 'attribute_id' + ), + 'attribute_id', + $this->setup->getTable('eav_attribute'), + 'attribute_id', + Table::ACTION_CASCADE + ) + ->addForeignKey( + $this->setup->getFkName( + $tableName, + 'entity_id', + $entityCode, + 'entity_id' + ), + 'entity_id', + $this->setup->getTable($entityCode . '_entity'), + 'entity_id', + Table::ACTION_CASCADE + ) + ->addForeignKey( + $this->setup->getFkName($tableName, 'store_id', 'store', 'store_id'), + 'store_id', + $this->setup->getTable('store'), + 'store_id', + Table::ACTION_CASCADE + ) + ->setComment($entityCode . ' ' . $type . 'Attribute Backend Table'); + $this->setup->getConnection()->createTable($table); + } +} diff --git a/Setup/InstallData.php b/Setup/InstallData.php new file mode 100644 index 0000000..515cdac --- /dev/null +++ b/Setup/InstallData.php @@ -0,0 +1,58 @@ +usageSetupFactory = $usageSetupFactory; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) //@codingStandardsIgnoreLine + { + /** @var UsageSetup $usageSetup */ + $usageSetup = $this->usageSetupFactory->create(['setup' => $setup]); + + $setup->startSetup(); + + $usageSetup->installEntities(); + $entities = $usageSetup->getDefaultEntities(); + foreach ($entities as $entityName => $entity) { + $usageSetup->addEntityType($entityName, $entity); + } + + $setup->endSetup(); + } +} diff --git a/Setup/InstallSchema.php b/Setup/InstallSchema.php new file mode 100644 index 0000000..9274d59 --- /dev/null +++ b/Setup/InstallSchema.php @@ -0,0 +1,716 @@ +eavTablesSetupFactory = $eavTablesSetupFactory; + } + + /** + * {@inheritdoc} + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) //@codingStandardsIgnoreLine + { + $setup->startSetup(); + + /** @var \DevStone\UsageCalculator\Setup\EavTablesSetup $eavTablesSetup */ + $eavTablesSetup = $this->eavTablesSetupFactory->create(['setup' => $setup]); + + $categoryTableName = 'devstone_usage_category'; + + $categoryTable = $setup->getConnection() + ->newTable($setup->getTable($categoryTableName)) + ->addColumn( + 'entity_id', + Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Entity ID' + )->addColumn( + 'name', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 100, + ['nullable' => false, 'default' => ''], + 'Category Name' + )->addColumn( + 'terms', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 10000, + ['nullable' => false, 'default' => ''], + 'Category Terms' + )->addColumn( + 'created_at', + Table::TYPE_TIMESTAMP, + null, + ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], + 'Creation Time' + )->addColumn( + 'updated_at', + Table::TYPE_TIMESTAMP, + null, + ['nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE], + 'Update Time' + )->setComment('Usage Category Entity Table'); + + $setup->getConnection()->createTable($categoryTable); + + $sizeTableName = 'devstone_downloadable_image_size'; + + $sizeTable = $setup->getConnection() + ->newTable($setup->getTable($sizeTableName)) + ->addColumn( + 'entity_id', + Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Entity ID' + )->addColumn( + 'is_active', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '1'], + 'Is Active' + )->addColumn( + 'max_width', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'maximum width in pixels (0 unlimited)' + )->addColumn( + 'max_height', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'maximum width in pixels (0 unlimited)' + )->addColumn( + 'code', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 100, + ['nullable' => false, 'default' => ''], + 'code to identify size' + )->addColumn( + 'created_at', + Table::TYPE_TIMESTAMP, + null, + ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], + 'Creation Time' + )->addColumn( + 'updated_at', + Table::TYPE_TIMESTAMP, + null, + ['nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE], + 'Update Time' + )->setComment('Downloadable image sizes'); + + $setup->getConnection()->createTable($sizeTable); + + $tableName = UsageSetup::ENTITY_TYPE_CODE . '_entity'; + + $table = $setup->getConnection() + ->newTable($setup->getTable($tableName)) + ->addColumn( + 'entity_id', + Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Entity ID' + )->setComment('Usage Entity Table'); + $table->addColumn( + 'category_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Usage Category Id' + )->addIndex( + $setup->getIdxName($tableName, ['category_id']), + ['category_id'] + )->addForeignKey( + $setup->getFkName($tableName, 'category_id', $categoryTableName, 'entity_id'), + 'category_id', + $setup->getTable($categoryTableName), + 'entity_id' + )->addColumn( + 'is_active', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '1'], + 'Is Active' + )->addColumn( + 'price', + \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, + '12,4', + ['nullable' => false, 'default' => '0.0000'], + 'Price' + ); + + $table->addColumn( + 'size_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Usage Size Id' + )->addIndex( + $setup->getIdxName($tableName, ['size_id']), + ['size_id'] + )->addForeignKey( + $setup->getFkName($tableName, 'size_id', $sizeTableName, 'entity_id'), + 'size_id', + $setup->getTable($sizeTableName), + 'entity_id' + ); + + $table->addColumn( + 'created_at', + Table::TYPE_TIMESTAMP, + null, + ['nullable' => false, 'default' => Table::TIMESTAMP_INIT], + 'Creation Time' + )->addColumn( + 'updated_at', + Table::TYPE_TIMESTAMP, + null, + ['nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE], + 'Update Time' + ); + + $setup->getConnection()->createTable($table); + + $eavTablesSetup->createEavTables(UsageSetup::ENTITY_TYPE_CODE); + + + /** + * Create table 'devstone_usage_option' + */ + $table = $setup->getConnection() + ->newTable( + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option') + ) + ->addColumn( + 'option_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Option ID' + ) + ->addColumn( + 'usage_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Usage ID' + ) + ->addColumn( + 'type', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 50, + ['nullable' => true, 'default' => null], + 'Type' + ) + ->addColumn( + 'is_require', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['nullable' => false, 'default' => '1'], + 'Is Required' + ) + ->addColumn( + 'sort_order', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Sort Order' + ) + ->addIndex( + $setup->getIdxName(UsageSetup::ENTITY_TYPE_CODE.'_option', ['usage_id']), + ['usage_id'] + ) + ->addForeignKey( + $setup->getFkName(UsageSetup::ENTITY_TYPE_CODE.'_option', 'usage_id', UsageSetup::ENTITY_TYPE_CODE.'_entity', 'entity_id'), + 'usage_id', + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_entity'), + 'entity_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->setComment( + 'Usage Option Table' + ); + $setup->getConnection() + ->createTable($table); + + /** + * Create table 'devstone_usage_option_price' + */ + $table = $setup->getConnection() + ->newTable( + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option_price') + ) + ->addColumn( + 'option_price_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Option Price ID' + ) + ->addColumn( + 'option_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Option ID' + ) + ->addColumn( + 'store_id', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Store ID' + ) + ->addColumn( + 'price', + \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, + '12,4', + ['nullable' => false, 'default' => '0.0000'], + 'Price' + ) + ->addColumn( + 'price_type', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 7, + ['nullable' => false, 'default' => 'fixed'], + 'Price Type' + ) + ->addIndex( + $setup->getIdxName( + UsageSetup::ENTITY_TYPE_CODE.'_option_price', + ['option_id', 'store_id'], + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['option_id', 'store_id'], + ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE] + ) + ->addIndex( + $setup->getIdxName(UsageSetup::ENTITY_TYPE_CODE.'_option_price', ['store_id']), + ['store_id'] + ) + ->addForeignKey( + $setup->getFkName( + UsageSetup::ENTITY_TYPE_CODE.'_option_price', + 'option_id', + UsageSetup::ENTITY_TYPE_CODE.'_option', + 'option_id' + ), + 'option_id', + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option'), + 'option_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->addForeignKey( + $setup->getFkName(UsageSetup::ENTITY_TYPE_CODE.'_option_price', 'store_id', 'store', 'store_id'), + 'store_id', + $setup->getTable('store'), + 'store_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->setComment( + 'Usage Option Price Table' + ); + $setup->getConnection() + ->createTable($table); + + /** + * Create table 'devstone_usage_option_title' + */ + $table = $setup->getConnection() + ->newTable( + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option_title') + ) + ->addColumn( + 'option_title_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Option Title ID' + ) + ->addColumn( + 'option_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Option ID' + ) + ->addColumn( + 'store_id', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Store ID' + ) + ->addColumn( + 'title', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 255, + ['nullable' => true, 'default' => null], + 'Title' + ) + ->addIndex( + $setup->getIdxName( + UsageSetup::ENTITY_TYPE_CODE.'_option_title', + ['option_id', 'store_id'], + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['option_id', 'store_id'], + ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE] + ) + ->addIndex( + $setup->getIdxName(UsageSetup::ENTITY_TYPE_CODE.'_option_title', ['store_id']), + ['store_id'] + ) + ->addForeignKey( + $setup->getFkName( + UsageSetup::ENTITY_TYPE_CODE.'_option_title', + 'option_id', + UsageSetup::ENTITY_TYPE_CODE.'_option', + 'option_id' + ), + 'option_id', + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option'), + 'option_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->addForeignKey( + $setup->getFkName(UsageSetup::ENTITY_TYPE_CODE.'_option_title', 'store_id', 'store', 'store_id'), + 'store_id', + $setup->getTable('store'), + 'store_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->setComment( + 'Usage Option Title Table' + ); + $setup->getConnection() + ->createTable($table); + + /** + * Create table 'devstone_usage_option_help' + */ + $table = $setup->getConnection() + ->newTable( + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option_help') + ) + ->addColumn( + 'option_help_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Option Title ID' + ) + ->addColumn( + 'option_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Option ID' + ) + ->addColumn( + 'store_id', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Store ID' + ) + ->addColumn( + 'help', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 1024, + ['nullable' => true, 'default' => null], + 'Help Text' + ) + ->addIndex( + $setup->getIdxName( + UsageSetup::ENTITY_TYPE_CODE.'_option_help', + ['option_id', 'store_id'], + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['option_id', 'store_id'], + ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE] + ) + ->addIndex( + $setup->getIdxName(UsageSetup::ENTITY_TYPE_CODE.'_option_title', ['store_id']), + ['store_id'] + ) + ->addForeignKey( + $setup->getFkName( + UsageSetup::ENTITY_TYPE_CODE.'_option_help', + 'option_id', + UsageSetup::ENTITY_TYPE_CODE.'_option', + 'option_id' + ), + 'option_id', + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option'), + 'option_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->addForeignKey( + $setup->getFkName(UsageSetup::ENTITY_TYPE_CODE.'_option_help', 'store_id', 'store', 'store_id'), + 'store_id', + $setup->getTable('store'), + 'store_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->setComment( + 'Usage Option Help Text Table' + ); + $setup->getConnection() + ->createTable($table); + + + /** + * Create table 'devstone_usage_option_type_value' + */ + $table = $setup->getConnection() + ->newTable( + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option_type_value') + ) + ->addColumn( + 'option_type_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Option Type ID' + ) + ->addColumn( + 'option_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Option ID' + ) + ->addColumn( + 'size_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => true, 'default' => 'NULL'], + 'Usage Option Size Id' + )->addIndex( + $setup->getIdxName(UsageSetup::ENTITY_TYPE_CODE.'_option_type_value', ['size_id']), + ['size_id'] + )->addForeignKey( + $setup->getFkName(UsageSetup::ENTITY_TYPE_CODE.'_option_type_value', 'size_id', $sizeTableName, 'entity_id'), + 'size_id', + $setup->getTable($sizeTableName), + 'entity_id' + ) + ->addColumn( + 'sort_order', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Sort Order' + ) + ->addIndex( + $setup->getIdxName(UsageSetup::ENTITY_TYPE_CODE.'_option_type_value', ['option_id']), + ['option_id'] + ) + ->addForeignKey( + $setup->getFkName( + UsageSetup::ENTITY_TYPE_CODE.'_option_type_value', + 'option_id', + UsageSetup::ENTITY_TYPE_CODE.'_option', + 'option_id' + ), + 'option_id', + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option'), + 'option_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->setComment( + 'Usage Option Type Value Table' + ); + $setup->getConnection() + ->createTable($table); + + /** + * Create table 'devstone_usage_option_type_price' + */ + $table = $setup->getConnection() + ->newTable( + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option_type_price') + ) + ->addColumn( + 'option_type_price_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Option Type Price ID' + ) + ->addColumn( + 'option_type_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Option Type ID' + ) + ->addColumn( + 'store_id', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Store ID' + ) + ->addColumn( + 'price', + \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, + '12,4', + ['nullable' => false, 'default' => '0.0000'], + 'Price' + ) + ->addColumn( + 'price_type', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 7, + ['nullable' => false, 'default' => 'fixed'], + 'Price Type' + ) + ->addIndex( + $setup->getIdxName( + UsageSetup::ENTITY_TYPE_CODE.'_option_type_price', + ['option_type_id', 'store_id'], + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['option_type_id', 'store_id'], + ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE] + ) + ->addIndex( + $setup->getIdxName(UsageSetup::ENTITY_TYPE_CODE.'_option_type_price', ['store_id']), + ['store_id'] + ) + ->addForeignKey( + $setup->getFkName( + UsageSetup::ENTITY_TYPE_CODE.'_option_type_price', + 'option_type_id', + UsageSetup::ENTITY_TYPE_CODE.'_option_type_value', + 'option_type_id' + ), + 'option_type_id', + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option_type_value'), + 'option_type_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->addForeignKey( + $setup->getFkName(UsageSetup::ENTITY_TYPE_CODE.'_option_type_price', 'store_id', 'store', 'store_id'), + 'store_id', + $setup->getTable('store'), + 'store_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->setComment( + 'Usage Option Type Price Table' + ); + $setup->getConnection() + ->createTable($table); + + /** + * Create table 'devstone_usage_option_type_title' + */ + $table = $setup->getConnection() + ->newTable( + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option_type_title') + ) + ->addColumn( + 'option_type_title_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Option Type Title ID' + ) + ->addColumn( + 'option_type_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Option Type ID' + ) + ->addColumn( + 'store_id', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + null, + ['unsigned' => true, 'nullable' => false, 'default' => '0'], + 'Store ID' + ) + ->addColumn( + 'title', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 255, + ['nullable' => true, 'default' => null], + 'Title' + ) + ->addIndex( + $setup->getIdxName( + UsageSetup::ENTITY_TYPE_CODE.'_option_type_title', + ['option_type_id', 'store_id'], + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE + ), + ['option_type_id', 'store_id'], + ['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_UNIQUE] + ) + ->addIndex( + $setup->getIdxName(UsageSetup::ENTITY_TYPE_CODE.'_option_type_title', ['store_id']), + ['store_id'] + ) + ->addForeignKey( + $setup->getFkName( + UsageSetup::ENTITY_TYPE_CODE.'_option_type_title', + 'option_type_id', + UsageSetup::ENTITY_TYPE_CODE.'_option_type_value', + 'option_type_id' + ), + 'option_type_id', + $setup->getTable(UsageSetup::ENTITY_TYPE_CODE.'_option_type_value'), + 'option_type_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->addForeignKey( + $setup->getFkName(UsageSetup::ENTITY_TYPE_CODE.'_option_type_title', 'store_id', 'store', 'store_id'), + 'store_id', + $setup->getTable('store'), + 'store_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + ) + ->setComment( + 'Usage Option Type Title Table' + ); + $setup->getConnection() + ->createTable($table); + + $setup->endSetup(); + } +} diff --git a/Setup/Uninstall.php b/Setup/Uninstall.php new file mode 100644 index 0000000..8a8b384 --- /dev/null +++ b/Setup/Uninstall.php @@ -0,0 +1,55 @@ +startSetup(); + + foreach ($this->tablesToUninstall as $table) { + if ($setup->tableExists($table)) { + $setup->getConnection()->dropTable($setup->getTable($table)); + } + } + + $setup->endSetup(); + } +} diff --git a/Setup/UsageSetup.php b/Setup/UsageSetup.php new file mode 100644 index 0000000..8a9ee6e --- /dev/null +++ b/Setup/UsageSetup.php @@ -0,0 +1,95 @@ + [ + 'type' => 'varchar', + 'label' => 'Name', + 'input' => 'text', + 'sort_order' => 1, + 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, + 'group' => 'General Information', + ], + 'is_active' => [ + 'type' => 'int', + 'label' => 'Is Active', + 'input' => 'select', + 'source' => \Magento\Eav\Model\Entity\Attribute\Source\Boolean::class, + 'sort_order' => 10, + 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, + 'group' => 'General Information', + ], + 'terms' => [ + 'type' => 'text', + 'label' => 'Description', + 'input' => 'textarea', + 'required' => false, + 'sort_order' => 4, + 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, + 'wysiwyg_enabled' => true, + 'is_html_allowed_on_front' => true, + 'group' => 'General Information', + ], + 'price' => [ + 'type' => 'decimal', + 'label' => 'Price', + 'input' => 'price', + 'backend' => \Magento\Catalog\Model\Product\Attribute\Backend\Price::class, + 'sort_order' => 1, + 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, + 'group' => 'General', + ], + ]; + } + + /** + * Retrieve default entities: usage + * + * @return array + */ + public function getDefaultEntities() + { + $categoryAttributes = $this->getAttributes(); + unset($categoryAttributes['price']); + + $entities = [ + self::ENTITY_TYPE_CODE => [ + 'entity_model' => \DevStone\UsageCalculator\Model\ResourceModel\Usage::class, + 'attribute_model' => \DevStone\UsageCalculator\Model\ResourceModel\Eav\Attribute::class, + 'table' => self::ENTITY_TYPE_CODE . '_entity', + 'increment_model' => null, + 'additional_attribute_table' => self::ENTITY_TYPE_CODE . '_eav_attribute', + 'entity_attribute_collection' => \DevStone\UsageCalculator\Model\ResourceModel\Attribute\Collection::class, + 'attributes' => $this->getAttributes(), + ], + ]; + return $entities; + } +} diff --git a/Ui/Component/Form/Category/DataProvider.php b/Ui/Component/Form/Category/DataProvider.php new file mode 100644 index 0000000..e813ebf --- /dev/null +++ b/Ui/Component/Form/Category/DataProvider.php @@ -0,0 +1,71 @@ +collection = $collection; + $this->filterPool = $filterPool; + } + + /** + * Get data + * + * @return array + */ + public function getData() + { + if (!$this->loadedData) { + $items = $this->collection->getItems(); + foreach ($items as $item) { + $this->loadedData[$item->getId()] = $item->getData(); + } + } + return $this->loadedData; + } +} diff --git a/Ui/Component/Form/Size/DataProvider.php b/Ui/Component/Form/Size/DataProvider.php new file mode 100644 index 0000000..3029577 --- /dev/null +++ b/Ui/Component/Form/Size/DataProvider.php @@ -0,0 +1,87 @@ +collection = $collection; + $this->filterPool = $filterPool; + } + + /** + * Get data + * + * @return array + */ + public function getData() + { + if (!$this->loadedData) { + $items = $this->collection->getItems(); + foreach ($items as $item) { + + $this->loadedData[$item->getId()] = $item->getData(); + break; + } + } + return $this->loadedData; + + + $data = $this->dataPersistor->get('devstone_pricecalculator_usage'); + + if (!empty($data)) { + $model = $this->collection->getNewEmptyItem(); + $model->setData($data); + $this->loadedData[$model->getId()] = $model->getData(); + $this->dataPersistor->clear('devstone_pricecalculator_usage'); + } + + return $this->loadedData; + } + + +} diff --git a/Ui/Component/Form/Usage/DataProvider.php b/Ui/Component/Form/Usage/DataProvider.php new file mode 100644 index 0000000..6c6d9cc --- /dev/null +++ b/Ui/Component/Form/Usage/DataProvider.php @@ -0,0 +1,106 @@ +collection = $collection; + $this->filterPool = $filterPool; + + $this->request = $request; + $this->usageOptions = $usageOptions; + } + + /** + * Get data + * + * @return array + */ + public function getData() + { + if (!$this->loadedData) { + $this->loadedData = []; + $storeId = (int)$this->request->getParam('store'); + $this->collection + ->setStoreId($storeId) + ->addAttributeToSelect('*'); + $items = $this->collection->getItems(); + foreach ($items as $item) { + $item->setStoreId($storeId); + $this->loadedData[$item->getId()] = $item->getData(); + break; + } + + $this->loadedData = $this->usageOptions->modifydata($this->loadedData); + } + return $this->loadedData; + } + + public function getMeta() + { + $meta = parent::getMeta(); + + return $this->usageOptions->modifyMeta($meta); + } + +} diff --git a/Ui/Component/Form/Usage/UsageOptions.php b/Ui/Component/Form/Usage/UsageOptions.php new file mode 100644 index 0000000..78be963 --- /dev/null +++ b/Ui/Component/Form/Usage/UsageOptions.php @@ -0,0 +1,966 @@ +locator = $locator; + $this->storeManager = $storeManager; + $this->productOptionsPrice = $productOptionsPrice; + $this->urlBuilder = $urlBuilder; + $this->arrayManager = $arrayManager; + $this->optionRepository = $optionRepo; + $this->sizesOptionsProvider = $sizesOptionsProvider; + } + + /** + * {@inheritdoc} + */ + public function modifyData(array $data) + { + foreach($data as $id => &$usage) { + $options = []; + $productOptions = $this->optionRepository->getList($id); + /** @var \DevStone\UsageCalculator\Model\Usage\Option $option */ + foreach ($productOptions as $index => $option) { + $optionData = $option->getData(); + $optionData[static::FIELD_IS_USE_DEFAULT] = !$option->getData(static::FIELD_STORE_TITLE_NAME); + $options[$index] = $this->formatPriceByPath(static::FIELD_PRICE_NAME, $optionData); + $values = $option->getValues() ?: []; + + foreach ($values as $value) { + $value->setData(static::FIELD_IS_USE_DEFAULT, !$value->getData(static::FIELD_STORE_TITLE_NAME)); + } + /** @var \Magento\Catalog\Model\Product\Option $value */ + foreach ($values as $value) { + $options[$index][static::GRID_TYPE_SELECT_NAME][] = $this->formatPriceByPath( + static::FIELD_PRICE_NAME, + $value->getData() + ); + } + } + + $usage[static::DATA_SOURCE_DEFAULT] = [ + static::FIELD_ENABLE => 1, + static::GRID_OPTIONS_NAME => $options, + ]; + } + + return $data; + } + + /** + * Format float number to have two digits after delimiter + * + * @param string $path + * @param array $data + * @return array + */ + protected function formatPriceByPath($path, array $data) + { + $value = $this->arrayManager->get($path, $data); + + if (is_numeric($value)) { + $data = $this->arrayManager->replace($path, $data, $this->formatPrice($value)); + } + + return $data; + } + + /** + * {@inheritdoc} + * @since 101.0.0 + */ + public function modifyMeta(array $meta) + { + $this->meta = $meta; + + $this->createCustomOptionsPanel(); + + return $this->meta; + } + + /** + * Create "Customizable Options" panel + * + * @return $this + * @since 101.0.0 + */ + protected function createCustomOptionsPanel() + { + $this->meta = array_replace_recursive( + $this->meta, + [ + static::GROUP_CUSTOM_OPTIONS_NAME => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('Usage Options'), + 'componentType' => Fieldset::NAME, + 'dataScope' => static::GROUP_CUSTOM_OPTIONS_SCOPE, + 'collapsible' => false, + 'sortOrder' => $this->getNextGroupSortOrder( + $this->meta, + static::GROUP_CUSTOM_OPTIONS_PREVIOUS_NAME, + static::GROUP_CUSTOM_OPTIONS_DEFAULT_SORT_ORDER + ), + ], + ], + ], + 'children' => [ + static::CONTAINER_HEADER_NAME => $this->getHeaderContainerConfig(10), + static::FIELD_ENABLE => $this->getEnableFieldConfig(20), + static::GRID_OPTIONS_NAME => $this->getOptionsGridConfig(30) + ] + ] + ] + ); + + + return $this; + } + + /** + * Get config for header container + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getHeaderContainerConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => null, + 'formElement' => Container::NAME, + 'componentType' => Container::NAME, + 'template' => 'ui/form/components/complex', + 'sortOrder' => $sortOrder, + 'content' => __('Usage options provide more details about how an image will be used.'), + ], + ], + ], + 'children' => [ + static::BUTTON_ADD => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'title' => __('Add Option'), + 'formElement' => Container::NAME, + 'componentType' => Container::NAME, + 'component' => 'Magento_Ui/js/form/components/button', + 'sortOrder' => 20, + 'actions' => [ + [ + 'targetName' => 'ns = ${ $.ns }, index = ' . static::GRID_OPTIONS_NAME, + 'actionName' => 'processingAddChild', + ] + ] + ] + ], + ], + ], + ], + ]; + } + + /** + * Get config for the whole grid + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getOptionsGridConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'addButtonLabel' => __('Add Option'), + 'componentType' => DynamicRows::NAME, + 'component' => 'Magento_Catalog/js/components/dynamic-rows-import-custom-options', + 'template' => 'ui/dynamic-rows/templates/collapsible', + 'additionalClasses' => 'admin__field-wide', + 'deleteProperty' => static::FIELD_IS_DELETE, + 'deleteValue' => '1', + 'addButton' => false, + 'renderDefaultRecord' => false, + 'columnsHeader' => false, + 'collapsibleHeader' => true, + 'sortOrder' => $sortOrder, + 'dataProvider' => static::CUSTOM_OPTIONS_LISTING, + 'imports' => ['insertData' => '${ $.provider }:${ $.dataProvider }'], + ], + ], + ], + 'children' => [ + 'record' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'headerLabel' => __('New Option'), + 'componentType' => Container::NAME, + 'component' => 'Magento_Ui/js/dynamic-rows/record', + 'positionProvider' => static::CONTAINER_OPTION . '.' . static::FIELD_SORT_ORDER_NAME, + 'isTemplate' => true, + 'is_collection' => true, + ], + ], + ], + 'children' => [ + static::CONTAINER_OPTION => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'componentType' => Fieldset::NAME, + 'collapsible' => true, + 'label' => null, + 'sortOrder' => 10, + 'opened' => true, + ], + ], + ], + 'children' => [ + static::FIELD_SORT_ORDER_NAME => $this->getPositionFieldConfig(40), + static::CONTAINER_COMMON_NAME => $this->getCommonContainerConfig(10), + static::CONTAINER_TYPE_STATIC_NAME => $this->getStaticTypeContainerConfig(20), + static::GRID_TYPE_SELECT_NAME => $this->getSelectTypeGridConfig(30) + ] + ], + ] + ] + ] + ]; + } + + /** + * Get config for hidden field responsible for enabling custom options processing + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getEnableFieldConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'formElement' => Field::NAME, + 'componentType' => Input::NAME, + 'dataScope' => static::FIELD_ENABLE, + 'dataType' => Number::NAME, + 'visible' => false, + 'sortOrder' => $sortOrder, + ], + ], + ], + ]; + } + + /** + * Get config for container with common fields for any type + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getCommonContainerConfig($sortOrder) + { + $commonContainer = [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'componentType' => Container::NAME, + 'formElement' => Container::NAME, + 'component' => 'Magento_Ui/js/form/components/group', + 'breakLine' => false, + 'showLabel' => false, + 'additionalClasses' => 'admin__field-group-columns admin__control-group-equal', + 'sortOrder' => $sortOrder, + ], + ], + ], + 'children' => [ + static::FIELD_OPTION_ID => $this->getOptionIdFieldConfig(10), + static::FIELD_TITLE_NAME => $this->getTitleFieldConfig( + 20, + [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('Option Title'), + 'component' => 'Magento_Catalog/component/static-type-input', + 'valueUpdate' => 'input', + 'imports' => [ + 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' + ] + ], + ], + ], + ] + ), + static::FIELD_HELP_NAME => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('Help Text'), + 'component' => 'Magento_Catalog/component/static-type-input', + 'componentType' => Field::NAME, + 'formElement' => Input::NAME, + 'dataScope' => static::FIELD_HELP_NAME, + 'dataType' => Text::NAME, + 'sortOrder' => 21, + 'validation' => [ + 'required-entry' => true + ], + 'imports' => [ + 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' + ] + ], + ], + ], + ], + static::FIELD_TYPE_NAME => $this->getTypeFieldConfig(30), + static::FIELD_IS_REQUIRE_NAME => $this->getIsRequireFieldConfig(40) + ] + ]; + + return $commonContainer; + } + + /** + * Get config for container with fields for all types except "select" + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getStaticTypeContainerConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'componentType' => Container::NAME, + 'formElement' => Container::NAME, + 'component' => 'Magento_Ui/js/form/components/group', + 'breakLine' => false, + 'showLabel' => false, + 'additionalClasses' => 'admin__field-group-columns admin__control-group-equal', + 'sortOrder' => $sortOrder, + 'fieldTemplate' => 'Magento_Catalog/form/field', + 'visible' => false, + ], + ], + ], + 'children' => [ + static::FIELD_PRICE_NAME => $this->getPriceFieldConfig(10), + static::FIELD_PRICE_TYPE_NAME => $this->getPriceTypeFieldConfig(20), + ] + ]; + } + + /** + * Get config for grid for "select" types + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getSelectTypeGridConfig($sortOrder) + { + $options = [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'imports' => [ + 'optionId' => '${ $.provider }:${ $.parentScope }.option_id', + 'optionTypeId' => '${ $.provider }:${ $.parentScope }.option_type_id', + 'isUseDefault' => '${ $.provider }:${ $.parentScope }.is_use_default' + ], + 'service' => [ + 'template' => 'Magento_Catalog/form/element/helper/custom-option-type-service', + ], + ], + ], + ], + ]; + + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'addButtonLabel' => __('Add Value'), + 'componentType' => DynamicRows::NAME, + 'component' => 'Magento_Ui/js/dynamic-rows/dynamic-rows', + 'additionalClasses' => 'admin__field-wide', + 'deleteProperty' => static::FIELD_IS_DELETE, + 'deleteValue' => '1', + 'renderDefaultRecord' => false, + 'sortOrder' => $sortOrder, + ], + ], + ], + 'children' => [ + 'record' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'componentType' => Container::NAME, + 'component' => 'Magento_Ui/js/dynamic-rows/record', + 'positionProvider' => static::FIELD_SORT_ORDER_NAME, + 'isTemplate' => true, + 'is_collection' => true, + ], + ], + ], + 'children' => [ + static::FIELD_TITLE_NAME => $this->getTitleFieldConfig( + 10, + [] + ), + static::FIELD_PRICE_NAME => $this->getPriceFieldConfigForSelectType(20), + static::FIELD_PRICE_TYPE_NAME => $this->getPriceTypeFieldConfig(30, ['fit' => true]), + static::FIELD_IMAGE_SIZE => $this->getImageSizeFieldConfig(40), + static::FIELD_SORT_ORDER_NAME => $this->getPositionFieldConfig(50), + static::FIELD_IS_DELETE => $this->getIsDeleteFieldConfig(60) + ] + ] + ] + ]; + } + + /** + * Get config for hidden id field + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getOptionIdFieldConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'formElement' => Input::NAME, + 'componentType' => Field::NAME, + 'dataScope' => static::FIELD_OPTION_ID, + 'sortOrder' => $sortOrder, + 'visible' => false, + ], + ], + ], + ]; + } + + /** + * Get config for "Title" fields + * + * @param int $sortOrder + * @param array $options + * @return array + * @since 101.0.0 + */ + protected function getTitleFieldConfig($sortOrder, array $options = []) + { + return array_replace_recursive( + [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('Title'), + 'componentType' => Field::NAME, + 'formElement' => Input::NAME, + 'dataScope' => static::FIELD_TITLE_NAME, + 'dataType' => Text::NAME, + 'sortOrder' => $sortOrder, + 'validation' => [ + 'required-entry' => true + ], + ], + ], + ], + ], + $options + ); + } + + /** + * Get config for "Option Type" field + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getTypeFieldConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('Option Type'), + 'componentType' => Field::NAME, + 'formElement' => Select::NAME, + 'component' => 'Magento_Catalog/js/custom-options-type', + 'elementTmpl' => 'ui/grid/filters/elements/ui-select', + 'selectType' => 'optgroup', + 'dataScope' => static::FIELD_TYPE_NAME, + 'dataType' => Text::NAME, + 'sortOrder' => $sortOrder, + 'options' => $this->getProductOptionTypes(), + 'disableLabel' => true, + 'multiple' => false, + 'selectedPlaceholders' => [ + 'defaultPlaceholder' => __('-- Please select --'), + ], + 'validation' => [ + 'required-entry' => true + ], + 'groupsConfig' => [ + 'text' => [ + 'values' => ['field', 'area'], + 'indexes' => [ + static::CONTAINER_TYPE_STATIC_NAME, + static::FIELD_PRICE_NAME, + static::FIELD_PRICE_TYPE_NAME + ] + ], + 'select' => [ + 'values' => ['drop_down', 'radio', 'checkbox', 'multiple'], + 'indexes' => [ + static::GRID_TYPE_SELECT_NAME + ] + ], + ], + ], + ], + ], + ]; + } + + /** + * Get config for "Required" field + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getIsRequireFieldConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('Required'), + 'componentType' => Field::NAME, + 'formElement' => Checkbox::NAME, + 'dataScope' => static::FIELD_IS_REQUIRE_NAME, + 'dataType' => Text::NAME, + 'sortOrder' => $sortOrder, + 'value' => '1', + 'valueMap' => [ + 'true' => '1', + 'false' => '0' + ], + ], + ], + ], + ]; + } + + /** + * Get config for hidden field used for sorting + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getPositionFieldConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'componentType' => Field::NAME, + 'formElement' => Input::NAME, + 'dataScope' => static::FIELD_SORT_ORDER_NAME, + 'dataType' => Number::NAME, + 'visible' => false, + 'sortOrder' => $sortOrder, + ], + ], + ], + ]; + } + + /** + * Get config for hidden field used for removing rows + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getIsDeleteFieldConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'componentType' => ActionDelete::NAME, + 'fit' => true, + 'sortOrder' => $sortOrder + ], + ], + ], + ]; + } + + /** + * Get config for "Price" field + * + * @param int $sortOrder + * @return array + * @since 101.0.0 + */ + protected function getPriceFieldConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('Price'), + 'componentType' => Field::NAME, + 'component' => 'Magento_Catalog/js/components/custom-options-component', + 'formElement' => Input::NAME, + 'dataScope' => static::FIELD_PRICE_NAME, + 'dataType' => Number::NAME, + 'addbefore' => $this->getCurrencySymbol(), + 'addbeforePool' => $this->productOptionsPrice->prefixesToOptionArray(), + 'sortOrder' => $sortOrder, + 'validation' => [ + 'validate-zero-or-greater' => true + ], + ], + ], + ], + ]; + } + + /** + * Get config for "Price" field for select type. + * + * @param int $sortOrder + * @return array + */ + private function getPriceFieldConfigForSelectType($sortOrder) + { + $priceFieldConfig = $this->getPriceFieldConfig($sortOrder); + $priceFieldConfig['arguments']['data']['config']['template'] = 'Magento_Catalog/form/field'; + + return $priceFieldConfig; + } + + /** + * Get config for "Price Type" field + * + * @param int $sortOrder + * @param array $config + * @return array + * @since 101.0.0 + */ + protected function getPriceTypeFieldConfig($sortOrder, array $config = []) + { + return array_replace_recursive( + [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('Price Type'), + 'component' => 'Magento_Catalog/js/components/custom-options-price-type', + 'componentType' => Field::NAME, + 'formElement' => Select::NAME, + 'dataScope' => static::FIELD_PRICE_TYPE_NAME, + 'dataType' => Text::NAME, + 'sortOrder' => $sortOrder, + 'options' => $this->productOptionsPrice->toOptionArray(), + 'imports' => [ + 'priceIndex' => self::FIELD_PRICE_NAME, + ], + ], + ], + ], + ], + $config + ); + } + + protected function getImageSizeFieldConfig($sortOrder) + { + return [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'label' => __('Image Size'), + 'componentType' => Field::NAME, + 'formElement' => Select::NAME, + 'dataScope' => static::FIELD_IMAGE_SIZE, + 'dataType' => Text::NAME, + 'sortOrder' => $sortOrder, + 'options' => $this->sizesOptionsProvider->toOptionArray('---'), + ], + ], + ], + ]; + } + + /** + * Get options for drop-down control with product option types + * + * @return array + * @since 101.0.0 + */ + protected function getProductOptionTypes() + { + + return array( + array( + 'value' => 0, + 'label' => 'Text', + 'optgroup' => + array( + array( + 'label' => 'Field', + 'value' => 'field', + ), + array( + 'label' => 'Area', + 'value' => 'area', + ), + ), + ), + array( + 'value' => 2, + 'label' => 'Select', + 'optgroup' => + array( + array( + 'label' => 'Drop-down', + 'value' => 'drop_down', + ), + array( + 'label' => 'Radio Buttons', + 'value' => 'radio', + ), + array( + 'label' => 'Checkbox', + 'value' => 'checkbox', + ), + array( + 'label' => 'Multiple Select', + 'value' => 'multiple', + ), + ), + ), + + ); + } + + /** + * Get currency symbol + * + * @return string + * @since 101.0.0 + */ + protected function getCurrencySymbol() + { + return $this->storeManager->getStore()->getBaseCurrency()->getCurrencySymbol(); + } + + /** + * The getter function to get the locale currency for real application code + * + * @return \Magento\Framework\Locale\CurrencyInterface + * + * @deprecated 101.0.0 + */ + private function getLocaleCurrency() + { + if ($this->localeCurrency === null) { + $this->localeCurrency = \Magento\Framework\App\ObjectManager::getInstance()->get(CurrencyInterface::class); + } + return $this->localeCurrency; + } + + /** + * Format price according to the locale of the currency + * + * @param mixed $value + * @return string + * @since 101.0.0 + */ + protected function formatPrice($value) + { + if (!is_numeric($value)) { + return null; + } + + $store = $this->storeManager->getStore(); + $currency = $this->getLocaleCurrency()->getCurrency($store->getBaseCurrencyCode()); + $value = $currency->toCurrency($value, ['display' => \Magento\Framework\Currency::NO_SYMBOL]); + + return $value; + } +} diff --git a/Ui/Component/Listing/Column/CategoryActions.php b/Ui/Component/Listing/Column/CategoryActions.php new file mode 100644 index 0000000..938248b --- /dev/null +++ b/Ui/Component/Listing/Column/CategoryActions.php @@ -0,0 +1,75 @@ +urlBuilder = $urlBuilder; + parent::__construct($context, $uiComponentFactory, $components, $data); + } + + /** + * Prepare Data Source + * + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource) + { + if (isset($dataSource['data']['items'])) { + $storeId = $this->context->getFilterParam('store_id'); + + foreach ($dataSource['data']['items'] as &$item) { + if (isset($item['entity_id'])) { + $item[$this->getData('name')]['edit'] = [ + 'href' => $this->urlBuilder->getUrl( + self::URL_PATH_EDIT, + ['entity_id' => $item['entity_id'], 'store' => $storeId] + ), + 'label' => __('Edit'), + 'hidden' => false, + ]; + } + } + } + + return $dataSource; + } +} diff --git a/Ui/Component/Listing/Column/SizeActions.php b/Ui/Component/Listing/Column/SizeActions.php new file mode 100644 index 0000000..12fb1fc --- /dev/null +++ b/Ui/Component/Listing/Column/SizeActions.php @@ -0,0 +1,75 @@ +urlBuilder = $urlBuilder; + parent::__construct($context, $uiComponentFactory, $components, $data); + } + + /** + * Prepare Data Source + * + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource) + { + if (isset($dataSource['data']['items'])) { + $storeId = $this->context->getFilterParam('store_id'); + + foreach ($dataSource['data']['items'] as &$item) { + if (isset($item['entity_id'])) { + $item[$this->getData('name')]['edit'] = [ + 'href' => $this->urlBuilder->getUrl( + self::URL_PATH_EDIT, + ['entity_id' => $item['entity_id'], 'store' => $storeId] + ), + 'label' => __('Edit'), + 'hidden' => false, + ]; + } + } + } + + return $dataSource; + } +} diff --git a/Ui/Component/Listing/Column/UsageActions.php b/Ui/Component/Listing/Column/UsageActions.php new file mode 100644 index 0000000..5f179d6 --- /dev/null +++ b/Ui/Component/Listing/Column/UsageActions.php @@ -0,0 +1,75 @@ +urlBuilder = $urlBuilder; + parent::__construct($context, $uiComponentFactory, $components, $data); + } + + /** + * Prepare Data Source + * + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource) + { + if (isset($dataSource['data']['items'])) { + $storeId = $this->context->getFilterParam('store_id'); + + foreach ($dataSource['data']['items'] as &$item) { + if (isset($item['entity_id'])) { + $item[$this->getData('name')]['edit'] = [ + 'href' => $this->urlBuilder->getUrl( + self::URL_PATH_EDIT, + ['entity_id' => $item['entity_id'], 'store' => $storeId] + ), + 'label' => __('Edit'), + 'hidden' => false, + ]; + } + } + } + + return $dataSource; + } +} diff --git a/Ui/Component/Listing/DataProvider.php b/Ui/Component/Listing/DataProvider.php new file mode 100644 index 0000000..eb0d0c2 --- /dev/null +++ b/Ui/Component/Listing/DataProvider.php @@ -0,0 +1,44 @@ +setStoreId($this->request->getParam('store', 0)) + ->addAttributeToSelect(['price', 'name', 'terms']); + + return parent::searchResultToOutput($searchResult); + } + + /** + * @return void + */ + protected function prepareUpdateUrl() + { + $storeId = $this->request->getParam('store', 0); + if ($storeId) { + $this->data['config']['update_url'] = sprintf( + '%s%s/%s', + $this->data['config']['update_url'], + 'store', + $storeId + ); + } + return parent::prepareUpdateUrl(); + } +} diff --git a/Ui/Component/Listing/DataProvider/Document.php b/Ui/Component/Listing/DataProvider/Document.php new file mode 100644 index 0000000..fcafb96 --- /dev/null +++ b/Ui/Component/Listing/DataProvider/Document.php @@ -0,0 +1,19 @@ +_idFieldName; + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e9e5c8a --- /dev/null +++ b/composer.json @@ -0,0 +1,19 @@ +{ + "name": "devstone/magento2-module-usagecalculator", + "description": "Usage calculator for purchasing images or artwork at various resolutions.", + "type": "magento2-module", + "license": [ + "GPL-3.0" + ], + "require": { + "devstone/module-imageproducts": "*" + }, + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "DevStone\\UsageCalculator\\": "" + } + } +} diff --git a/etc/acl.xml b/etc/acl.xml new file mode 100644 index 0000000..209d6e4 --- /dev/null +++ b/etc/acl.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/etc/adminhtml/menu.xml b/etc/adminhtml/menu.xml new file mode 100644 index 0000000..f2bcc00 --- /dev/null +++ b/etc/adminhtml/menu.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/etc/adminhtml/routes.xml b/etc/adminhtml/routes.xml new file mode 100644 index 0000000..71d1cc9 --- /dev/null +++ b/etc/adminhtml/routes.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/etc/di.xml b/etc/di.xml new file mode 100644 index 0000000..8ad6bb9 --- /dev/null +++ b/etc/di.xml @@ -0,0 +1,181 @@ + + + + + + + + devstone_usage + + + + + + + DevStone\UsageCalculator\Model\ResourceModel\Usage\Collection + RefGridFilterPool + + + + + + Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter + Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter + + + + + + + DevStone\UsageCalculator\Model\ResourceModel\Usage\Grid\Collection + + + + + + devstone_usagecalculator_usage_grid_collection + devstone_usagecalculator_usage_grid_collection + DevStone\UsageCalculator\Model\ResourceModel\Usage + + + + + DevStone\UsageCalculator\Model\ResourceModel\Size\Collection + DevStoneSizeGridFilterPool + + + + + + Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter + Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter + + + + + + + DevStone\UsageCalculator\Model\ResourceModel\Category\Collection + DevStoneCategoryGridFilterPool + + + + + + Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter + Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter + + + + + + + + DevStone\UsageCalculator\Model\ResourceModel\Size\Grid\Collection + DevStone\UsageCalculator\Model\ResourceModel\Category\Grid\Collection + + + + + + devstone_downloadable_image_size + devstone_usagecalculator_size_grid_collection + devstone_usagecalculator_size_grid_collection + DevStone\UsageCalculator\Model\ResourceModel\Size + + + + + + devstone_usage_category + devstone_usagecalculator_category_grid_collection + devstone_usagecalculator_category_grid_collection + DevStone\UsageCalculator\Model\ResourceModel\Category + + + + + + + + + + + + + + + + + + + DevStone\UsageCalculator\Model\Usage\Option\Validator\DefaultValidator + DevStone\UsageCalculator\Model\Usage\Option\Validator\Select + DevStone\UsageCalculator\Model\Usage\Option\Validator\Select + DevStone\UsageCalculator\Model\Usage\Option\Validator\Select + DevStone\UsageCalculator\Model\Usage\Option\Validator\Select + DevStone\UsageCalculator\Model\Usage\Option\Validator\Text + DevStone\UsageCalculator\Model\Usage\Option\Validator\Text + DevStone\UsageCalculator\Model\Usage\Option\Validator\File + + + + + + DevStone\UsageCalculator\Model\Usage\Option\Type\File\ValidatorInfo\Proxy + DevStone\UsageCalculator\Model\Usage\Option\Type\File\ValidatorFile\Proxy + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/etc/extension_attributesb.xml b/etc/extension_attributesb.xml new file mode 100644 index 0000000..c21a4ae --- /dev/null +++ b/etc/extension_attributesb.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/etc/module.xml b/etc/module.xml new file mode 100644 index 0000000..083f03d --- /dev/null +++ b/etc/module.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/registration.php b/registration.php new file mode 100644 index 0000000..e432869 --- /dev/null +++ b/registration.php @@ -0,0 +1,6 @@ + + + + + + + + + \ No newline at end of file diff --git a/view/adminhtml/layout/devstone_usagecalculator_category_index.xml b/view/adminhtml/layout/devstone_usagecalculator_category_index.xml new file mode 100644 index 0000000..c95e843 --- /dev/null +++ b/view/adminhtml/layout/devstone_usagecalculator_category_index.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/view/adminhtml/layout/devstone_usagecalculator_size_edit.xml b/view/adminhtml/layout/devstone_usagecalculator_size_edit.xml new file mode 100644 index 0000000..58a39f5 --- /dev/null +++ b/view/adminhtml/layout/devstone_usagecalculator_size_edit.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file diff --git a/view/adminhtml/layout/devstone_usagecalculator_size_index.xml b/view/adminhtml/layout/devstone_usagecalculator_size_index.xml new file mode 100644 index 0000000..cea4ad9 --- /dev/null +++ b/view/adminhtml/layout/devstone_usagecalculator_size_index.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/view/adminhtml/layout/devstone_usagecalculator_usage_edit.xml b/view/adminhtml/layout/devstone_usagecalculator_usage_edit.xml new file mode 100644 index 0000000..fffa9db --- /dev/null +++ b/view/adminhtml/layout/devstone_usagecalculator_usage_edit.xml @@ -0,0 +1,23 @@ + + + + + + + + 1 + + + + + + + + \ No newline at end of file diff --git a/view/adminhtml/layout/devstone_usagecalculator_usage_index.xml b/view/adminhtml/layout/devstone_usagecalculator_usage_index.xml new file mode 100644 index 0000000..05919b4 --- /dev/null +++ b/view/adminhtml/layout/devstone_usagecalculator_usage_index.xml @@ -0,0 +1,23 @@ + + + + + + + + 1 + + + + + + + + + diff --git a/view/adminhtml/ui_component/devstone_usagecalculator_category_form.xml b/view/adminhtml/ui_component/devstone_usagecalculator_category_form.xml new file mode 100644 index 0000000..e869f30 --- /dev/null +++ b/view/adminhtml/ui_component/devstone_usagecalculator_category_form.xml @@ -0,0 +1,93 @@ + + +
+ + + devstone_usagecalculator_category_form.devstone_usagecalculator_category_form_data_source + devstone_usagecalculator_category_form.devstone_usagecalculator_category_form_data_source + + + data + + templates/form/collapsible + Category Form + + DevStone\UsageCalculator\Block\Adminhtml\Category\Edit\BackButton + DevStone\UsageCalculator\Block\Adminhtml\Category\Edit\DeleteButton + DevStone\UsageCalculator\Block\Adminhtml\Category\Edit\SaveButton + DevStone\UsageCalculator\Block\Adminhtml\Category\Edit\SaveAndContinueButton + + + + + DevStone\UsageCalculator\Ui\Component\Form\Category\DataProvider + devstone_usagecalculator_category_form_data_source + entity_id + entity_id + + + + + + + + + + Magento_Ui/js/form/provider + + + +
+ + + Main Information + + + + + + false + text + input + main_fieldset + + + + + + + true + text + Name + input + main_fieldset + + true + + 1 + + + + + + + true + text + Terms + textarea + main_fieldset + + true + + 10 + + + +
+
\ No newline at end of file diff --git a/view/adminhtml/ui_component/devstone_usagecalculator_category_listing.xml b/view/adminhtml/ui_component/devstone_usagecalculator_category_listing.xml new file mode 100644 index 0000000..050c7d1 --- /dev/null +++ b/view/adminhtml/ui_component/devstone_usagecalculator_category_listing.xml @@ -0,0 +1,241 @@ + + ++ + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing_data_source + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing_data_source + + devstone_usagecalculator_category_columns + + + add + Add New Category + primary + */*/edit + + + + + + DevStoneCategoryGridDataProvider + devstone_usagecalculator_category_listing_data_source + entity_id + id + + + + + entity_id + + + + + + + Magento_Ui/js/grid/provider + + + + + + + ui/grid/toolbar + + + + + + + devstone_usagecalculator_category_listing + + + + + + + + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.devstone_usagecalculator_category_columns + + Magento_Ui/js/grid/controls/columns + dataGridActions + + + + + + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing_data_source + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.listing_top.listing_filters_chips + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.listing_top.bookmarks + current.search + + + + + + + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.devstone_usagecalculator_category_columns + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.listing_top.bookmarks + current.filters + + + + + Magento_Ui/js/form/element/ui-select + ui/grid/filters/elements/ui-select + + + + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.listing_top.listing_filters + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.devstone_usagecalculator_category_columns.${ $.index }:visible + + + + + + + + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.devstone_usagecalculator_category_columns.ids + entity_id + + + + + + delete + Delete + + + Delete items + Are you sure you wan't to delete selected items? + + + + + + + + edit + Edit + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.devstone_usagecalculator_category_columns_editor + editSelected + + + + + + + + + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.listing_top.bookmarks + current.paging + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.devstone_usagecalculator_category_columns.ids + + + + + + + + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.listing_top.bookmarks + current + + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.devstone_usagecalculator_category_columns.ids + true + entity_id + + + false + + + + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.devstone_usagecalculator_category_columns_editor + startEdit + + ${ $.$data.rowIndex } + true + + + + devstone_usagecalculator_category_listing.devstone_usagecalculator_category_listing.listing_top.bookmarks + columns.${ $.index } + current.${ $.storageConfig.root } + + + + + + + + false + 55 + entity_id + + + + + + + textRange + asc + ID + + + + + + + + text + + true + + + text + Name + + + + + + + + text + + true + + + text + Terms + + + + + + + entity_id + + + + + diff --git a/view/adminhtml/ui_component/devstone_usagecalculator_size_form.xml b/view/adminhtml/ui_component/devstone_usagecalculator_size_form.xml new file mode 100644 index 0000000..1cf677d --- /dev/null +++ b/view/adminhtml/ui_component/devstone_usagecalculator_size_form.xml @@ -0,0 +1,138 @@ + + +
+ + + devstone_usagecalculator_size_form.devstone_usagecalculator_size_form_data_source + devstone_usagecalculator_size_form.devstone_usagecalculator_size_form_data_source + + + data + + templates/form/collapsible + Size Form + + DevStone\UsageCalculator\Block\Adminhtml\Size\Edit\BackButton + DevStone\UsageCalculator\Block\Adminhtml\Size\Edit\DeleteButton + DevStone\UsageCalculator\Block\Adminhtml\Size\Edit\SaveButton + DevStone\UsageCalculator\Block\Adminhtml\Size\Edit\SaveAndContinueButton + + + + + DevStone\UsageCalculator\Ui\Component\Form\Size\DataProvider + devstone_usagecalculator_size_form_data_source + entity_id + entity_id + + + + + + + + + + Magento_Ui/js/form/provider + + + +
+ + + Main Information + + + + + + false + text + input + main_fieldset + + + + + + + true + text + Code + input + main_fieldset + + true + + 10 + + + + + + + true + integer + Max Width + input + main_fieldset + + true + + 10 + + + + + + + true + integer + Max Height + input + main_fieldset + + true + + 10 + + + + + + + main_fieldset + + + + number + + true + is_active + + + + + + +
+
\ No newline at end of file diff --git a/view/adminhtml/ui_component/devstone_usagecalculator_size_listing.xml b/view/adminhtml/ui_component/devstone_usagecalculator_size_listing.xml new file mode 100644 index 0000000..1d25602 --- /dev/null +++ b/view/adminhtml/ui_component/devstone_usagecalculator_size_listing.xml @@ -0,0 +1,255 @@ + + ++ + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing_data_source + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing_data_source + + devstone_usagecalculator_size_columns + + + add + Add New Size + primary + */*/edit + + + + + + DevStoneSizeGridDataProvider + devstone_usagecalculator_size_listing_data_source + entity_id + id + + + + + entity_id + + + + + + + Magento_Ui/js/grid/provider + + + + + + + ui/grid/toolbar + + + + + + + devstone_usagecalculator_size_listing + + + + + + + + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.devstone_usagecalculator_size_columns + + Magento_Ui/js/grid/controls/columns + dataGridActions + + + + + + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing_data_source + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.listing_top.listing_filters_chips + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.listing_top.bookmarks + current.search + + + + + + + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.devstone_usagecalculator_size_columns + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.listing_top.bookmarks + current.filters + + + + + Magento_Ui/js/form/element/ui-select + ui/grid/filters/elements/ui-select + + + + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.listing_top.listing_filters + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.devstone_usagecalculator_size_columns.${ $.index }:visible + + + + + + + + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.devstone_usagecalculator_size_columns.ids + entity_id + + + + + + delete + Delete + + + Delete items + Are you sure you wan't to delete selected items? + + + + + + + + edit + Edit + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.devstone_usagecalculator_size_columns_editor + editSelected + + + + + + + + + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.listing_top.bookmarks + current.paging + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.devstone_usagecalculator_size_columns.ids + + + + + + + + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.listing_top.bookmarks + current + + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.devstone_usagecalculator_size_columns.ids + true + entity_id + + + false + + + + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.devstone_usagecalculator_size_columns_editor + startEdit + + ${ $.$data.rowIndex } + true + + + + devstone_usagecalculator_size_listing.devstone_usagecalculator_size_listing.listing_top.bookmarks + columns.${ $.index } + current.${ $.storageConfig.root } + + + + + + + + false + 55 + entity_id + + + + + + + textRange + asc + ID + + + + + + + + text + + true + + + text + Code + + + + + + + + text + + true + + + text + Max Width + + + + + + + + text + + true + + + text + Max Height + + + + + + + entity_id + + + + + diff --git a/view/adminhtml/ui_component/devstone_usagecalculator_usage_form.xml b/view/adminhtml/ui_component/devstone_usagecalculator_usage_form.xml new file mode 100644 index 0000000..4817937 --- /dev/null +++ b/view/adminhtml/ui_component/devstone_usagecalculator_usage_form.xml @@ -0,0 +1,192 @@ + + +
+ + + devstone_usagecalculator_usage_form.devstone_usagecalculator_usage_form_data_source + devstone_usagecalculator_usage_form.devstone_usagecalculator_usage_form_data_source + + + data + + templates/form/collapsible + Usage Form + + DevStone\UsageCalculator\Block\Adminhtml\Usage\Edit\BackButton + DevStone\UsageCalculator\Block\Adminhtml\Usage\Edit\DeleteButton + DevStone\UsageCalculator\Block\Adminhtml\Usage\Edit\SaveButton + DevStone\UsageCalculator\Block\Adminhtml\Usage\Edit\SaveAndContinueButton + + + + + DevStone\UsageCalculator\Ui\Component\Form\Usage\DataProvider + devstone_usagecalculator_usage_form_data_source + entity_id + entity_id + + + + + + + + + + Magento_Ui/js/form/provider + + + +
+ + + Main Information + + + + + + true + number + hidden + + 10 + main_fieldset + + + + + + + false + text + input + main_fieldset + + + + + + + true + text + Name + input + main_fieldset + + true + + 1 + + + + + + + true + text + Terms + textarea + main_fieldset + + true + + 10 + + + + + + + + true + price + Price + input + main_fieldset + + true + + 10 + + + + + + + main_fieldset + + + + number + + true + is_active + + + + + + + + + main_fieldset + + + + number + + + ${ $.provider }:data.usage.size_id + + + + + + + + + + main_fieldset + + + + number + + + ${ $.provider }:data.usage.category_id + + + + + + +
+
\ No newline at end of file diff --git a/view/adminhtml/ui_component/devstone_usagecalculator_usage_listing.xml b/view/adminhtml/ui_component/devstone_usagecalculator_usage_listing.xml new file mode 100644 index 0000000..c95c413 --- /dev/null +++ b/view/adminhtml/ui_component/devstone_usagecalculator_usage_listing.xml @@ -0,0 +1,256 @@ + + ++ + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing_data_source + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing_data_source + + devstone_usagecalculator_usage_columns + + + add + Add New Usage + primary + */*/edit + + + + + + DevStone\UsageCalculator\Ui\Component\Listing\DataProvider + devstone_usagecalculator_usage_listing_data_source + entity_id + id + + + + + + + + + + + + Magento_Ui/js/grid/provider + + + + + + + ui/grid/toolbar + + + + + + + devstone_usagecalculator_usage_listing + + + + + + + + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_columns + + Magento_Ui/js/grid/controls/columns + dataGridActions + + + + + + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing_data_source + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.listing_top.listing_filters_chips + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.listing_top.bookmarks + current.search + + + + + + + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_columns + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.listing_top.bookmarks + current.filters + + + + + Magento_Ui/js/form/element/ui-select + ui/grid/filters/elements/ui-select + + + + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.listing_top.listing_filters + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_columns.${ $.index }:visible + + + + + + + + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_columns.ids + entity_id + + + + + + delete + Delete + + + Delete items + Are you sure you wan't to delete selected items? + + + + + + + + edit + Edit + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_columns_editor + editSelected + + + + + + + + + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.listing_top.bookmarks + current.paging + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_columns.ids + + + + + + + + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.listing_top.bookmarks + current + + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_columns.ids + false + entity_id + + + false + + + + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_columns_editor + startEdit + + ${ $.$data.rowIndex } + true + + + + devstone_usagecalculator_usage_listing.devstone_usagecalculator_usage_listing.listing_top.bookmarks + columns.${ $.index } + current.${ $.storageConfig.root } + + + + + + + + false + 55 + entity_id + + + + + + + textRange + asc + ID + + + + + + + + text + + true + + + text + Name + + + + + + + + price + + true + + + text + Price + + + + + + + + text + + true + + + text + Terms + + + + + + + + entity_id + + + + + diff --git a/view/frontend/layout/catalog_product_view_type_image.xml b/view/frontend/layout/catalog_product_view_type_image.xml new file mode 100644 index 0000000..87c5449 --- /dev/null +++ b/view/frontend/layout/catalog_product_view_type_image.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/view/frontend/requirejs-config.js b/view/frontend/requirejs-config.js new file mode 100644 index 0000000..7f04850 --- /dev/null +++ b/view/frontend/requirejs-config.js @@ -0,0 +1,8 @@ + +var config = { + map: { + '*': { + usage: 'DevStone_UsageCalculator/usage' + } + } +}; diff --git a/view/frontend/templates/catalog/product/type.phtml b/view/frontend/templates/catalog/product/type.phtml new file mode 100644 index 0000000..d4afdf5 --- /dev/null +++ b/view/frontend/templates/catalog/product/type.phtml @@ -0,0 +1,26 @@ + +getProduct() ?> + +getIsSalable()): ?> +
+ +
+ +
+ +
+ +getChildHtml() ?> diff --git a/view/frontend/templates/catalog/product/type/date.phtml b/view/frontend/templates/catalog/product/type/date.phtml new file mode 100644 index 0000000..3420512 --- /dev/null +++ b/view/frontend/templates/catalog/product/type/date.phtml @@ -0,0 +1,58 @@ + +getOption() ?> +getId() ?> +getIsRequire()) ? ' required' : ''; ?> +
+
+ + escapeHtml($_option->getTitle()) ?> + getFormatedPrice() ?> + +
+ getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME + || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE): ?> + + getDateHtml() ?> + + + + getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DATE_TIME + || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_TIME): ?> + getTimeHtml() ?> + + + getIsRequire()): ?> + + + + + +
+
+ +
diff --git a/view/frontend/templates/catalog/product/type/default.phtml b/view/frontend/templates/catalog/product/type/default.phtml new file mode 100644 index 0000000..2006bf6 --- /dev/null +++ b/view/frontend/templates/catalog/product/type/default.phtml @@ -0,0 +1,13 @@ + +getOption() ?> +
+ +
diff --git a/view/frontend/templates/catalog/product/type/file.phtml b/view/frontend/templates/catalog/product/type/file.phtml new file mode 100644 index 0000000..3ceba2e --- /dev/null +++ b/view/frontend/templates/catalog/product/type/file.phtml @@ -0,0 +1,67 @@ + +getOption(); ?> +getFileInfo(); ?> +hasData(); ?> +getId() . '_file'; ?> + + + +getIsRequire()) ? ' required' : ''; ?> + +
+ + +
+ escapeHtml($_fileInfo->getTitle()) ?> + + + + getIsRequire()): ?> + + + +
+ +
> + /> + + getFileExtension()): ?> +

+ : getFileExtension() ?> +

+ + getImageSizeX() > 0): ?> +

+ : getImageSizeX() ?> +

+ + getImageSizeY() > 0): ?> +

+ : getImageSizeY() ?> +

+ +
+
diff --git a/view/frontend/templates/catalog/product/type/select.phtml b/view/frontend/templates/catalog/product/type/select.phtml new file mode 100644 index 0000000..389f0ef --- /dev/null +++ b/view/frontend/templates/catalog/product/type/select.phtml @@ -0,0 +1,31 @@ + + + +getOption(); +$class = ($_option->getIsRequire()) ? ' required' : ''; +?> +
+ +
+ escapeHtml($_option->getHelp()); ?> +
+
+ getValuesHtml() ?> + getIsRequire()): ?> + getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO || $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX): ?> + + + +
+
diff --git a/view/frontend/templates/catalog/product/type/text.phtml b/view/frontend/templates/catalog/product/type/text.phtml new file mode 100644 index 0000000..6ab0370 --- /dev/null +++ b/view/frontend/templates/catalog/product/type/text.phtml @@ -0,0 +1,66 @@ + +getOption(); +$class = ($_option->getIsRequire()) ? ' required' : ''; +?> + +
+ +
+ escapeHtml($_option->getHelp()); ?> +
+
+ getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_FIELD): ?> + getIsRequire()) { + $_textValidate['required'] = true; + } + if ($_option->getMaxCharacters()) { + $_textValidate['maxlength'] = $_option->getMaxCharacters(); + } + $_textValidate['validate-no-utf8mb4-characters'] = true; + ?> + + data-validate="escapeHtml(json_encode($_textValidate)) ?>" + + data-title="escapeHtmlAttr($_option->getTitle()) ?>" + name="options[getId() ?>]" + data-selector="options[getId() ?>]" + value="escapeHtml($block->getDefaultValue()) ?>"/> + getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_AREA): ?> + getIsRequire()) { + $_textAreaValidate['required'] = true; + } + if ($_option->getMaxCharacters()) { + $_textAreaValidate['maxlength'] = $_option->getMaxCharacters(); + } + $_textAreaValidate['validate-no-utf8mb4-characters'] = true; + ?> + + +
+
diff --git a/view/frontend/templates/catalog/product/usages.phtml b/view/frontend/templates/catalog/product/usages.phtml new file mode 100644 index 0000000..ca95adb --- /dev/null +++ b/view/frontend/templates/catalog/product/usages.phtml @@ -0,0 +1,65 @@ + +getProduct()->isSaleable() ):?> +
+ +
+ + +

+ +
+
+ +
+ +
+
+ getCategoriesSelectHtml() ?> +
+
+ getCategories() as $category): ?> + getUsages($category) ?> + + + + + + + +
+ +
+
+ diff --git a/view/frontend/templates/checkout/success.phtml b/view/frontend/templates/checkout/success.phtml new file mode 100644 index 0000000..a32d854 --- /dev/null +++ b/view/frontend/templates/checkout/success.phtml @@ -0,0 +1,12 @@ + +getOrderHasDownloadable()): ?> + My Downloadable Products', $block->getDownloadableProductsUrl()) ?> + diff --git a/view/frontend/templates/customer/products/list.phtml b/view/frontend/templates/customer/products/list.phtml new file mode 100644 index 0000000..08cadb2 --- /dev/null +++ b/view/frontend/templates/customer/products/list.phtml @@ -0,0 +1,67 @@ + + +getItems(); ?> + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + getPurchased()->getOrderIncrementId() ?> + + formatDate($_item->getPurchased()->getCreatedAt()) ?> + escapeHtml($_item->getPurchased()->getProductName()) ?> + getStatus() == \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_AVAILABLE): ?> + getIsOpenInNewWindow() ? 'onclick="this.target=\'_blank\'"' : '' ?>>escapeHtml($_item->getLinkTitle()) ?> + + getStatus())) ?>getRemainingDownloads($_item) ?>
+
+ getChildHtml('pager')): ?> +
+ getChildHtml('pager') ?> +
+ + +
+ + +
+
+ + + +
+
diff --git a/view/frontend/templates/email/order/items/creditmemo/downloadable.phtml b/view/frontend/templates/email/order/items/creditmemo/downloadable.phtml new file mode 100644 index 0000000..0f5a065 --- /dev/null +++ b/view/frontend/templates/email/order/items/creditmemo/downloadable.phtml @@ -0,0 +1,41 @@ + + +getItem() ?> +getItem()->getOrder(); ?> + + +

escapeHtml($_item->getName()) ?>

+

: escapeHtml($block->getSku($_item)) ?>

+ getItemOptions()): ?> +
+ getItemOptions() as $option): ?> +
+
+ +
+ + getLinks()->getPurchasedItems()): ?> +
+
getLinksTitle() ?>
+ +
+ escapeHtml($link->getLinkTitle()) ?> +
+ +
+ + escapeHtml($_item->getDescription()) ?> + + getQty() * 1 ?> + + getItemPrice($_item) ?> + + diff --git a/view/frontend/templates/email/order/items/invoice/downloadable.phtml b/view/frontend/templates/email/order/items/invoice/downloadable.phtml new file mode 100644 index 0000000..5cd9edd --- /dev/null +++ b/view/frontend/templates/email/order/items/invoice/downloadable.phtml @@ -0,0 +1,42 @@ + + +getItem() ?> +getItem()->getOrder() ?> + + +

escapeHtml($_item->getName()) ?>

+

: escapeHtml($block->getSku($_item)) ?>

+ getItemOptions()): ?> +
+ getItemOptions() as $option): ?> +
+
+ +
+ + getLinks()->getPurchasedItems()): ?> +
+
getLinksTitle() ?>
+ +
+ escapeHtml($link->getLinkTitle()) ?>  + () +
+ +
+ + escapeHtml($_item->getDescription()) ?> + + getQty() * 1 ?> + + getItemPrice($_item) ?> + + diff --git a/view/frontend/templates/email/order/items/order/downloadable.phtml b/view/frontend/templates/email/order/items/order/downloadable.phtml new file mode 100644 index 0000000..338a7a4 --- /dev/null +++ b/view/frontend/templates/email/order/items/order/downloadable.phtml @@ -0,0 +1,55 @@ + + +getItem() ?> +getItem()->getOrder() ?> + + +

escapeHtml($_item->getName()) ?>

+

: escapeHtml($block->getSku($_item)) ?>

+ getItemOptions()): ?> +
+ getItemOptions() as $option): ?> +
+
+ +
+ + getLinks()->getPurchasedItems()): ?> +
+
getLinksTitle() ?>
+ +
+ escapeHtml($link->getLinkTitle()) ?>  + () +
+ +
+ + escapeHtml($_item->getDescription()) ?> + getGiftMessageId() && $_giftMessage = $this->helper('Magento\GiftMessage\Helper\Message')->getGiftMessage($_item->getGiftMessageId())): ?> + + + + +
+

+ escapeHtml($_giftMessage->getSender()) ?> +
escapeHtml($_giftMessage->getRecipient()) ?> +
+
escapeHtml($_giftMessage->getMessage()) ?> +
+ + + getQtyOrdered() * 1 ?> + + getItemPrice($_item) ?> + + diff --git a/view/frontend/templates/js/components.phtml b/view/frontend/templates/js/components.phtml new file mode 100644 index 0000000..bad5acc --- /dev/null +++ b/view/frontend/templates/js/components.phtml @@ -0,0 +1,10 @@ + +getChildHtml() ?> diff --git a/view/frontend/templates/sales/order/creditmemo/items/renderer/downloadable.phtml b/view/frontend/templates/sales/order/creditmemo/items/renderer/downloadable.phtml new file mode 100644 index 0000000..2783749 --- /dev/null +++ b/view/frontend/templates/sales/order/creditmemo/items/renderer/downloadable.phtml @@ -0,0 +1,68 @@ + +getItem() ?> +getItem()->getOrderItem()->getOrder() ?> + + + escapeHtml($_item->getName()) ?> + getItemOptions()): ?> + + + + getLinks()): ?> + + + + + getProductAdditionalInformationBlock(); ?> + + setItem($_item->getOrderItem())->toHtml() ?> + + escapeHtml($_item->getDescription()) ?> + + prepareSku($block->getSku()) ?> + + getItemPriceHtml() ?> + + getQty()*1 ?> + + getItemRowTotalHtml() ?> + + formatPrice(-$_item->getDiscountAmount()) ?> + + getItemRowTotalAfterDiscountHtml() ?> + + diff --git a/view/frontend/templates/sales/order/invoice/items/renderer/downloadable.phtml b/view/frontend/templates/sales/order/invoice/items/renderer/downloadable.phtml new file mode 100644 index 0000000..0cf1bf8 --- /dev/null +++ b/view/frontend/templates/sales/order/invoice/items/renderer/downloadable.phtml @@ -0,0 +1,65 @@ + +getItem() ?> +getItem()->getOrderItem()->getOrder() ?> + + + escapeHtml($_item->getName()) ?> + getItemOptions()): ?> + + + + getLinks()): ?> + + + + getProductAdditionalInformationBlock(); ?> + + setItem($_item->getOrderItem())->toHtml() ?> + + escapeHtml($_item->getDescription()) ?> + + prepareSku($block->getSku()) ?> + + getItemPriceHtml() ?> + + + getQty()*1 ?> + + + getItemRowTotalHtml() ?> + + diff --git a/view/frontend/templates/sales/order/items/renderer/downloadable.phtml b/view/frontend/templates/sales/order/items/renderer/downloadable.phtml new file mode 100644 index 0000000..40d965e --- /dev/null +++ b/view/frontend/templates/sales/order/items/renderer/downloadable.phtml @@ -0,0 +1,91 @@ + +getItem() ?> + + + escapeHtml($_item->getName()) ?> + getItemOptions()): ?> + + + + getLinks()): ?> + + + + getProductAdditionalInformationBlock(); ?> + + setItem($_item)->toHtml() ?> + + escapeHtml($_item->getDescription()) ?> + + prepareSku($block->getSku()) ?> + + getItemPriceHtml() ?> + + + + + + getItemRowTotalHtml() ?> + + diff --git a/view/frontend/web/css/styles-product-page.less b/view/frontend/web/css/styles-product-page.less new file mode 100644 index 0000000..7ca005a --- /dev/null +++ b/view/frontend/web/css/styles-product-page.less @@ -0,0 +1,47 @@ +.fieldset .legend.usages-title { + margin: 0 0 11px; + font-weight: bold; + font-size: 1.5em; +} +#usages-container { + position: relative; + #usage-button-close { + position: absolute; + right: 0; + z-index: 10; + } + .field { + margin: 10px 0; + position: relative; + &:after { + content: ' '; + border-radius: 4px; + height: 100%; + position: absolute; + left: -10px; + width: 100%; + padding: 6px 10px; + top: -3px; + z-index: -1; + transition: background 0.5s ease; + } + &.active:after { + background: #f5f5f5; + } + .control { + margin-top: 1px; + } + .label { + font-weight: bold; + display: block; + a { + float: right; + font-weight: normal; + } + } + } +} + +.product-info-main .product-info-price .price-box { + margin-top: 10px; +} \ No newline at end of file diff --git a/view/frontend/web/usage.js b/view/frontend/web/usage.js new file mode 100644 index 0000000..922478c --- /dev/null +++ b/view/frontend/web/usage.js @@ -0,0 +1,172 @@ +/** + * @api + */ +define([ + 'jquery', + 'priceBox', + 'jquery/ui' +], function ($, priceBox) { + 'use strict'; + var self; + + $.widget('mage.usage', { + options: { + priceHolderSelector: '#product-options-wrapper .price-box' + }, + hidden: true, + + /** @inheritdoc */ + _create: function () { + self = this; + + this.element.find(this.options.categorySelectElement).on('change', function () { + self.element.find(".category-container").hide().find('select, input, textarea').prop('disabled', true);; + self.element.find(".usage-container").hide(); + self.element.find("#category_container_"+$(this).val()).show() + .find('.usage-select-box').val('').prop('disabled', false); + }); + + this.element.find('select, input, textarea').on('change', this._reloadPrice); + + this.element.find('.help-text').hide(); + + this.element.find('label').on('click', function (e) { + e.preventDefault(); + $(this).closest('.field').find('.help-text').toggle() + var $a = $(this).find('a'); + var current = $a.html(); + $a.html($a.data('less')); + $a.data('less', current); + }); + + this.element.find(".usage-select-box").on('change', function () { + self.element.find(".usage-container").hide().find('select, input, textarea').prop('disabled', true); + self.element.find("#usage_container_"+$(this).val()).show().find('select, input, textarea').prop('disabled', false); + }); + + $('.usages-container-inner').hide().find('select, input, textarea').prop('disabled', true); + $('#usage-button, #usage-button-close').on('click', function (e) { + e.preventDefault(); + $('#maincontent .product.info.detailed').toggle(); + $('#maincontent .product-info-main .product-info-main > div:not(.product-add-form)').toggle(); + $('#product-options-wrapper > div > :not(.product-info-price):not(.usages-container)').toggle(); + $('.usages-container-inner').toggle(); + $('#usage-button').toggle(); + if ( self.hidden ) { + self.element.find(self.options.categorySelectElement).prop('disabled', false).val('').trigger('change'); + } else { + $('.usages-container-inner').find('select, input, textarea').prop('disabled', true); + } + + self.hidden = ! self.hidden; + }); + + $(self.options.priceHolderSelector).priceBox('setDefault', { + 'basePrice': { + 'amount': 0.0, + 'adjustments': [] + }, + 'finalPrice': { + 'amount': 0.0, + 'adjustments': [] + }, + 'oldPrice': { + 'amount': 0.0, + 'adjustments': [] + } + }); + + $(self.options.priceHolderSelector + ', .product-options-bottom').hide(); + }, + + /** + * Reload product price with selected link price included + * @private + */ + _reloadPrice: function () { + var finalPrice = 0, + basePrice = 0, + $usage, + $selected, + terms, + categoryId, + haveActive = false; + + self.element.find().show(); + + categoryId = self.element.find(self.options.categorySelectElement).val(); + + $usage = self.element.find("#category_container_"+categoryId+' .usage-select-box option:selected'); + + finalPrice = basePrice = parseFloat($usage.attr('price')); + terms = $usage.data('terms'); + + if (basePrice) { + self.element.find("#category_container_"+categoryId).removeClass('active'); + } else if (categoryId) { + self.element.find('.field.active').removeClass('active'); + self.element.find("#category_container_"+categoryId).addClass('active'); + } else { + self.element.find('.usages-container-inner > .control > .field').addClass('active'); + } + + self.element.find("#usage_container_"+$usage.attr('value')+' select[name*=options]').each(function (index, select) { + $selected = $(select).find('option:selected'); + if(!$selected.attr('price') && !haveActive) { + $(select).closest('.field').addClass('active'); + haveActive = true; + } else { + $(select).closest('.field.active').removeClass('active'); + } + + finalPrice *= parseFloat($selected.attr('price')) / 100; + terms = terms.replace( + '('+select.title+')', + ''+$selected.text()+'' + ); + }); + + self.element.find("#usage_container_"+$usage.attr('value')+' input').each(function (index, input) { + if (!$(input).val() && !haveActive) { + $(input).closest('.field').addClass('active'); + haveActive = true; + } else { + $(input).closest('.field.active').removeClass('active'); + } + terms = terms.replace( + '('+$(input).data('title')+')', + ''+$(input).val()+'' + ); + }); + + if (isNaN(finalPrice)) { + finalPrice = 0; + } + + if ( 95 !== ((finalPrice * 100) % 100)) { + finalPrice = Math.round(finalPrice); + } + + $(self.options.priceHolderSelector).trigger('updatePrice', { + 'prices': { + 'finalPrice': { + 'amount': finalPrice + }, + 'basePrice': { + 'amount': basePrice + } + } + }); + + if (finalPrice <= 0) { + $(self.options.priceHolderSelector + ', .product-options-bottom').hide(); + $('#usages-advice-container').html(''); + } else { + $('#usages-advice-container').html(terms); + $(self.options.priceHolderSelector + ', .product-options-bottom').show(); + } + } + }); + + return $.mage.usage; +});