diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 8d085483..329fb2a1 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -3,15 +3,12 @@ on: [push, pull_request] jobs: # Check there is no syntax errors in the project php-linter: - name: PHP Syntax check 5.6 => 8.1 + name: PHP Syntax check 7.2 => 8.2 runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3.1.0 - - name: PHP syntax checker 5.6 - uses: prestashop/github-action-php-lint/5.6@master - - name: PHP syntax checker 7.2 uses: prestashop/github-action-php-lint/7.2@master @@ -27,6 +24,9 @@ jobs: - name: PHP syntax checker 8.1 uses: prestashop/github-action-php-lint/8.1@master + - name: PHP syntax checker 8.2 + uses: prestashop/github-action-php-lint/8.2@master + # Check the PHP code follow the coding standards php-cs-fixer: name: PHP-CS-Fixer @@ -50,7 +50,7 @@ jobs: run: composer install - name: Run PHP-CS-Fixer - run: ./vendor/bin/php-cs-fixer fix --dry-run --diff --using-cache=no --diff-format udiff + run: ./vendor/bin/php-cs-fixer fix --dry-run --diff --using-cache=no # Run PHPStan against the module and a PrestaShop release phpstan: @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - presta-versions: ['1.7.6', '1.7.7', '1.7.8', 'latest'] + presta-versions: ['1.7.7', '1.7.8', '8.0', 'latest'] steps: - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -86,4 +86,4 @@ jobs: # Docker images prestashop/prestashop may be used, even if the shop remains uninstalled - name: Execute PHPStan on PrestaShop (Tag ${{ matrix.presta-versions }}) - run: ./tests/phpstan.sh ${{ matrix.presta-versions }} + run: ./tests/phpstan.sh ${{ matrix.presta-versions }} \ No newline at end of file diff --git a/ProductComment.php b/ProductComment.php index 9d5cd015..f3fc56a6 100644 --- a/ProductComment.php +++ b/ProductComment.php @@ -190,6 +190,9 @@ public static function getRatings($id_product) return Db::getInstance((bool) _PS_USE_SQL_SLAVE_)->getRow($sql); } + /** + * @deprecated 4.0.0 + */ public static function getAverageGrade($id_product) { $validate = Configuration::get('PRODUCT_COMMENTS_MODERATE'); @@ -235,6 +238,8 @@ public static function getAveragesByProduct($id_product, $id_lang) * Return number of comments and average grade by products * * @return int|false + * + * @deprecated 4.0.0 */ public static function getCommentNumber($id_product) { @@ -279,6 +284,8 @@ public static function getGradedCommentNumber($id_product) * Get comments by Validation * * @return array Comments + * + * @deprecated 6.0.0 */ public static function getByValidate($validate = '0', $deleted = false, $p = null, $limit = null, $skip_validate = false) { @@ -306,6 +313,8 @@ public static function getByValidate($validate = '0', $deleted = false, $p = nul * Get numbers of comments by Validation * * @return int Count of comments + * + * @deprecated 6.0.0 */ public static function getCountByValidate($validate = '0', $skip_validate = false) { @@ -360,6 +369,8 @@ public function validate($validate = '1') * Delete a comment, grade and report data * * @return bool succeed + * + * @deprecated 6.0.0 */ public function delete() { @@ -373,6 +384,8 @@ public function delete() * Delete Grades * * @return bool succeed + * + * @deprecated 6.0.0 */ public static function deleteGrades($id_product_comment) { @@ -389,6 +402,8 @@ public static function deleteGrades($id_product_comment) * Delete Reports * * @return bool succeed + * + * @deprecated 6.0.0 */ public static function deleteReports($id_product_comment) { @@ -405,6 +420,8 @@ public static function deleteReports($id_product_comment) * Delete usefulness * * @return bool succeed + * + * @deprecated 6.0.0 */ public static function deleteUsefulness($id_product_comment) { @@ -473,6 +490,8 @@ public static function isAlreadyUsefulness($id_product_comment, $id_customer) * Get reported comments * * @return array Comments + * + * @deprecated 6.0.0 */ public static function getReportedComments() { diff --git a/ProductCommentCriterion.php b/ProductCommentCriterion.php index e1c0e538..7e412aaa 100644 --- a/ProductCommentCriterion.php +++ b/ProductCommentCriterion.php @@ -130,6 +130,8 @@ public function addCategory($id_category) * Add grade to a criterion * * @return bool succeed + * + * @deprecated 4.0.0 */ public function addGrade($id_product_comment, $grade) { @@ -154,6 +156,8 @@ public function addGrade($id_product_comment, $grade) * Get criterion by Product * * @return array Criterion + * + * @deprecated 4.0.0 */ public static function getByProduct($id_product, $id_lang) { @@ -201,6 +205,8 @@ public static function getByProduct($id_product, $id_lang) * Get Criterions * * @return array Criterions + * + * @deprecated 6.0.0 */ public static function getCriterions($id_lang, $type = false, $active = false) { @@ -224,6 +230,9 @@ public static function getCriterions($id_lang, $type = false, $active = false) return $criterions; } + /** + * @deprecated 6.0.0 + */ public function getProducts() { $res = Db::getInstance()->executeS(' @@ -240,6 +249,9 @@ public function getProducts() return $products; } + /** + * @deprecated 6.0.0 + */ public function getCategories() { $res = Db::getInstance()->executeS(' @@ -256,6 +268,9 @@ public function getCategories() return $criterions; } + /** + * @deprecated 6.0.0 + */ public function deleteCategories() { return Db::getInstance()->execute(' @@ -263,6 +278,9 @@ public function deleteCategories() WHERE `id_product_comment_criterion` = ' . (int) $this->id); } + /** + * @deprecated 6.0.0 + */ public function deleteProducts() { return Db::getInstance()->execute(' @@ -270,6 +288,9 @@ public function deleteProducts() WHERE `id_product_comment_criterion` = ' . (int) $this->id); } + /** + * @deprecated 6.0.0 + */ public static function getTypes() { // Instance of module class for translations diff --git a/config/common.yml b/config/common.yml index b57877fa..67af6a71 100644 --- a/config/common.yml +++ b/config/common.yml @@ -5,12 +5,14 @@ services: product_comment_criterion_repository: class: PrestaShop\Module\ProductComment\Repository\ProductCommentCriterionRepository arguments: + - '@doctrine' - '@doctrine.dbal.default_connection' - '%database_prefix%' product_comment_repository: class: PrestaShop\Module\ProductComment\Repository\ProductCommentRepository arguments: + - '@doctrine' - '@doctrine.dbal.default_connection' - '%database_prefix%' - '@=service("prestashop.adapter.legacy.configuration").get("PRODUCT_COMMENTS_ALLOW_GUESTS")' diff --git a/productcomments.php b/productcomments.php index 99a505b7..651e9609 100644 --- a/productcomments.php +++ b/productcomments.php @@ -28,8 +28,7 @@ exit; } -use PrestaShop\Module\ProductComment\Repository\ProductCommentCriterionRepository; -use PrestaShop\Module\ProductComment\Repository\ProductCommentRepository; +use PrestaShop\Module\ProductComment\Entity\ProductCommentCriterion; use PrestaShop\PrestaShop\Core\Module\WidgetInterface; class ProductComments extends Module implements WidgetInterface @@ -41,11 +40,14 @@ class ProductComments extends Module implements WidgetInterface private $_productCommentsCriterionTypes = []; private $_baseUrl; + private $langId; + private $shopId; + public function __construct() { $this->name = 'productcomments'; $this->tab = 'front_office_features'; - $this->version = '5.0.3'; + $this->version = '6.0.0'; $this->author = 'PrestaShop'; $this->need_instance = 0; $this->bootstrap = true; @@ -55,7 +57,10 @@ public function __construct() $this->displayName = $this->trans('Product Comments', [], 'Modules.Productcomments.Admin'); $this->description = $this->trans('Allow users to post reviews on your products and/or rate them based on specific criteria.', [], 'Modules.Productcomments.Admin'); - $this->ps_versions_compliancy = ['min' => '1.7.6', 'max' => _PS_VERSION_]; + $this->ps_versions_compliancy = ['min' => '1.7.7', 'max' => _PS_VERSION_]; + + $this->langId = $this->context->language->id; + $this->shopId = $this->context->shop->id ? $this->context->shop->id : Configuration::get('PS_SHOP_DEFAULT'); } public function install($keep = true) @@ -163,6 +168,10 @@ public function getCacheId($id_product = null) protected function _postProcess() { + $id_product_comment = (int) Tools::getValue('id_product_comment'); + $commentRepository = $this->get('product_comment_repository'); + $criterionRepository = $this->get('product_comment_criterion_repository'); + if (Tools::isSubmit('submitModerate')) { $errors = []; $productCommentsMinimalTime = Tools::getValue('PRODUCT_COMMENTS_MINIMAL_TIME'); @@ -193,76 +202,70 @@ protected function _postProcess() $this->_html .= $this->displayConfirmation($this->trans('Settings updated', [], 'Modules.Productcomments.Admin')); } } elseif (Tools::isSubmit('productcomments')) { - $id_product_comment = (int) Tools::getValue('id_product_comment'); - $comment = new ProductComment($id_product_comment); - $comment->validate(); - ProductComment::deleteReports($id_product_comment); + $comment = $commentRepository->find($id_product_comment); + $commentRepository->validate('1', $comment); + $commentRepository->deleteReports($id_product_comment); } elseif (Tools::isSubmit('deleteproductcomments')) { - $id_product_comment = (int) Tools::getValue('id_product_comment'); - $comment = new ProductComment($id_product_comment); - $comment->delete(); + $comment = $$commentRepository->find($id_product_comment); + $commentRepository->delete($comment); } elseif (Tools::isSubmit('submitEditCriterion')) { + /* $criterion = new ProductCommentCriterion((int) Tools::getValue('id_product_comment_criterion')); $criterion->id_product_comment_criterion_type = (int) Tools::getValue('id_product_comment_criterion_type'); $criterion->active = Tools::getValue('active'); + */ + $criterion = $criterionRepository->findRelation((int) Tools::getValue('id_product_comment_criterion')); + file_put_contents('khoachemgio', print_r($criterion, true)); + $criterion->setType((int) Tools::getValue('id_product_comment_criterion_type')); + $criterion->setActive(Tools::getValue('active')); $languages = Language::getLanguages(); $name = []; foreach ($languages as $key => $value) { $name[$value['id_lang']] = Tools::getValue('name_' . $value['id_lang']); } - $criterion->name = $name; + $criterion->setNames($name); - if (!$criterion->validateFields(false) || !$criterion->validateFieldsLang(false)) { + if (!$criterion->isValid()) { $this->_html .= $this->displayError($this->trans('The criterion cannot be saved', [], 'Modules.Productcomments.Admin')); } else { - $criterion->save(); - - // Clear before reinserting data - $criterion->deleteCategories(); - $criterion->deleteProducts(); - if ($criterion->id_product_comment_criterion_type == 2) { - if ($categories = Tools::getValue('categoryBox')) { - if (count($categories)) { - foreach ($categories as $id_category) { - $criterion->addCategory((int) $id_category); - } - } - } - } elseif ($criterion->id_product_comment_criterion_type == 3) { - if ($products = Tools::getValue('ids_product')) { - if (count($products)) { - foreach ($products as $product) { - $criterion->addProduct((int) $product); - } - } - } - } - if ($criterion->save()) { + $criterion->setCategories(Tools::getValue('categoryBox')); + $criterion->setProducts(Tools::getValue('ids_product')); + if ($criterionRepository->update($criterion)) { Tools::redirectAdmin(Context::getContext()->link->getAdminLink('AdminModules', true, [], ['configure' => $this->name, 'conf' => 4])); } else { $this->_html .= $this->displayError($this->trans('The criterion cannot be saved', [], 'Modules.Productcomments.Admin')); } } } elseif (Tools::isSubmit('deleteproductcommentscriterion')) { + /* $productCommentCriterion = new ProductCommentCriterion((int) Tools::getValue('id_product_comment_criterion')); if ($productCommentCriterion->id) { if ($productCommentCriterion->delete()) { $this->_html .= $this->displayConfirmation($this->trans('Criterion deleted', [], 'Modules.Productcomments.Admin')); } } + */ + $criterion = $criterionRepository->findRelation((int) Tools::getValue('id_product_comment_criterion')); + if ($criterionRepository->delete($criterion)) { + $this->_html .= $this->displayConfirmation($this->trans('Criterion deleted', [], 'Modules.Productcomments.Admin')); + } } elseif (Tools::isSubmit('statusproductcommentscriterion')) { + /* $criterion = new ProductCommentCriterion((int) Tools::getValue('id_product_comment_criterion')); if ($criterion->id) { $criterion->active = (int) (!$criterion->active); $criterion->save(); } + */ + $criterion = $criterionRepository->findRelation((int) Tools::getValue('id_product_comment_criterion')); + $criterion->setActive(!$criterion->isActive()); Tools::redirectAdmin($this->context->link->getAdminLink('AdminModules', true, [], ['configure' => $this->name, 'tab_module' => $this->tab, 'conf' => 4, 'module_name' => $this->name])); } elseif ($id_product_comment = (int) Tools::getValue('approveComment')) { - $comment = new ProductComment($id_product_comment); - $comment->validate(); + $comment = $commentRepository->find($id_product_comment); + $commentRepository->validate('1', $comment); } elseif ($id_product_comment = (int) Tools::getValue('noabuseComment')) { - ProductComment::deleteReports($id_product_comment); + $commentRepository->deleteReports($id_product_comment); Tools::redirectAdmin($this->context->link->getAdminLink('AdminModules', true, [], ['configure' => $this->name])); } @@ -271,9 +274,6 @@ protected function _postProcess() public function getContent() { - include_once dirname(__FILE__) . '/ProductComment.php'; - include_once dirname(__FILE__) . '/ProductCommentCriterion.php'; - $this->_html = ''; if (Tools::isSubmit('updateproductcommentscriterion')) { $this->_html .= $this->renderCriterionForm((int) Tools::getValue('id_product_comment_criterion')); @@ -286,7 +286,7 @@ public function getContent() } $this->_setBaseUrl(); - $this->_productCommentsCriterionTypes = ProductCommentCriterion::getTypes(); + $this->_productCommentsCriterionTypes = $this->get('product_comment_criterion_repository')->getTypes(); $this->context->controller->addJs($this->_path . 'js/moderate.js'); @@ -423,7 +423,7 @@ public function renderConfigForm() $helper->tpl_vars = [ 'fields_value' => $this->getConfigFieldsValues(), 'languages' => $this->context->controller->getLanguages(), - 'id_language' => $this->context->language->id, + 'id_language' => $this->langId, ]; return $helper->generateForm([$fields_form_1]); @@ -432,9 +432,10 @@ public function renderConfigForm() public function renderModerateLists() { $return = null; + $commentRepository = $this->get('product_comment_repository'); if (Configuration::get('PRODUCT_COMMENTS_MODERATE')) { - $comments = ProductComment::getByValidate(0, false); + $comments = $commentRepository->getByValidate($this->langId, $this->shopId, 0, false); $fields_list = $this->getStandardFieldList(); @@ -459,7 +460,7 @@ public function renderModerateLists() $return .= $helper->generateList($comments, $fields_list); } - $comments = ProductComment::getReportedComments(); + $comments = $commentRepository->getReportedComments($this->langId, $this->shopId); $fields_list = $this->getStandardFieldList(); @@ -526,7 +527,7 @@ public function displayNoabuseLink($token, $id, $name = null) public function renderCriterionList() { - $criterions = ProductCommentCriterion::getCriterions($this->context->language->id, false, false); + $criterions = $this->get('product_comment_criterion_repository')->getCriterions($this->langId, false, false); $fields_list = [ 'id_product_comment_criterion' => [ @@ -586,16 +587,17 @@ public function renderCommentsList() $helper->currentIndex = AdminController::$currentIndex . '&configure=' . $this->name; $helper->no_link = true; - $page = ($page = Tools::getValue('submitFilter' . $helper->list_id)) ? $page : 1; - $pagination = ($pagination = Tools::getValue($helper->list_id . '_pagination')) ? $pagination : 50; + $page = ($page = Tools::getValue('submitFilter' . $helper->list_id)) ? (int) $page : 1; + $pagination = ($pagination = Tools::getValue($helper->list_id . '_pagination')) ? (int) $pagination : 50; $moderate = Configuration::get('PRODUCT_COMMENTS_MODERATE'); + $commentRepository = $this->get('product_comment_repository'); if (empty($moderate)) { - $comments = ProductComment::getByValidate(0, false, (int) $page, (int) $pagination, true); - $count = (int) ProductComment::getCountByValidate(0, true); + $comments = $commentRepository->getByValidate($this->langId, $this->shopId, 0, false, $page, $pagination, true); + $count = $commentRepository->getCountByValidate(0, true); } else { - $comments = ProductComment::getByValidate(1, false, (int) $page, (int) $pagination); - $count = (int) ProductComment::getCountByValidate(1); + $comments = $commentRepository->getByValidate($this->langId, $this->shopId, 1, false, $page, $pagination); + $count = $commentRepository->getCountByValidate(1); } $helper->listTotal = $count; @@ -617,6 +619,7 @@ public function getConfigFieldsValues() public function getCriterionFieldsValues($id = 0) { + /* $criterion = new ProductCommentCriterion($id); return [ @@ -625,6 +628,17 @@ public function getCriterionFieldsValues($id = 0) 'active' => $criterion->active, 'id_product_comment_criterion' => $criterion->id, ]; + */ + + $criterionRepos = $this->get('product_comment_criterion_repository'); + $criterion = $criterionRepos->findRelation($id); + + return [ + 'name' => $criterion->getNames(), + 'id_product_comment_criterion_type' => $criterion->getType(), + 'active' => $criterion->isActive(), + 'id_product_comment_criterion' => $criterion->getId(), + ]; } public function getStandardFieldList() @@ -703,7 +717,7 @@ public function renderAuthorName($value, $row) public function renderCriterionForm($id_criterion = 0) { - $types = ProductCommentCriterion::getTypes(); + $types = $this->get('product_comment_criterion_repository')->getTypes(); $query = []; foreach ($types as $key => $value) { $query[] = [ @@ -712,11 +726,14 @@ public function renderCriterionForm($id_criterion = 0) ]; } - $criterion = new ProductCommentCriterion((int) $id_criterion); - $selected_categories = $criterion->getCategories(); + $criterionRepository = $this->get('product_comment_criterion_repository'); + + $criterion = $criterionRepository->findRelation($id_criterion); + $selected_categories = $criterionRepository->getCategories($id_criterion); + + $product_table_values = Product::getSimpleProducts($this->langId); + $selected_products = $criterionRepository->getProducts($id_criterion); - $product_table_values = Product::getSimpleProducts($this->context->language->id); - $selected_products = $criterion->getProducts(); foreach ($product_table_values as $key => $product) { if (false !== array_search($product['id_product'], $selected_products)) { $product_table_values[$key]['selected'] = 1; @@ -830,7 +847,7 @@ public function renderCriterionForm($id_criterion = 0) $helper->tpl_vars = [ 'fields_value' => $this->getCriterionFieldsValues($id_criterion), 'languages' => $this->context->controller->getLanguages(), - 'id_language' => $this->context->language->id, + 'id_language' => $this->langId, ]; return $helper->generateForm([$fields_form_1]); @@ -846,8 +863,9 @@ public function initCategoriesAssociation($id_root = null, $id_criterion = 0) if ($id_criterion == 0) { $selected_cat = []; } else { - $pdc_object = new ProductCommentCriterion($id_criterion); - $selected_cat = $pdc_object->getCategories(); + $criterionRepository = $this->get('product_comment_criterion_repository'); + $criterion = $criterionRepository->find((int) $id_criterion); + $selected_cat = $criterionRepository->getCategories($criterion); } if (Shop::getContext() == Shop::CONTEXT_SHOP && Tools::isSubmit('id_shop')) { @@ -855,7 +873,7 @@ public function initCategoriesAssociation($id_root = null, $id_criterion = 0) } else { $root_category = new Category($id_root); } - $root_category = ['id_category' => $root_category->id, 'name' => $root_category->name[$this->context->language->id]]; + $root_category = ['id_category' => $root_category->id, 'name' => $root_category->name[$this->langId]]; $helper = new Helper(); @@ -865,8 +883,7 @@ public function initCategoriesAssociation($id_root = null, $id_criterion = 0) public function hookActionDeleteGDPRCustomer($customer) { if (isset($customer['id'])) { - /** @var ProductCommentRepository $productCommentRepository */ - $productCommentRepository = $this->context->controller->getContainer()->get('product_comment_repository'); + $productCommentRepository = $this->get('product_comment_repository'); $productCommentRepository->cleanCustomerData($customer['id']); } @@ -876,9 +893,8 @@ public function hookActionDeleteGDPRCustomer($customer) public function hookActionExportGDPRData($customer) { if (isset($customer['id'])) { - /** @var ProductCommentRepository $productCommentRepository */ - $productCommentRepository = $this->context->controller->getContainer()->get('product_comment_repository'); - $langId = isset($customer['id_lang']) ? $customer['id_lang'] : $this->context->language->id; + $productCommentRepository = $this->get('product_comment_repository'); + $langId = isset($customer['id_lang']) ? $customer['id_lang'] : $this->langId; return json_encode($productCommentRepository->getCustomerData($customer['id'], $langId)); } @@ -934,11 +950,9 @@ public function hookFilterProductContent(array $params) if (empty($params['object']->id)) { return $params; } - /** @var ProductCommentRepository $productCommentRepository */ - $productCommentRepository = $this->context->controller->getContainer()->get('product_comment_repository'); - - $averageRating = $productCommentRepository->getAverageGrade($params['object']->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE')); - $nbComments = $productCommentRepository->getCommentsNumber($params['object']->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE')); + $commentRepository = $this->get('product_comment_repository'); + $averageRating = $commentRepository->getAverageGrade($params['object']->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE')); + $nbComments = $commentRepository->getCommentsNumber($params['object']->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE')); /* @phpstan-ignore-next-line */ $params['object']->productComments = [ @@ -961,12 +975,10 @@ public function hookFilterProductContent(array $params) */ private function renderProductCommentsList($product) { - /** @var ProductCommentRepository $productCommentRepository */ - $productCommentRepository = $this->context->controller->getContainer()->get('product_comment_repository'); - - $averageGrade = $productCommentRepository->getAverageGrade($product->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE')); - $commentsNb = $productCommentRepository->getCommentsNumber($product->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE')); - $isPostAllowed = $productCommentRepository->isPostAllowed($product->id, (int) $this->context->cookie->id_customer, (int) $this->context->cookie->id_guest); + $commentRepository = $this->get('product_comment_repository'); + $averageGrade = $commentRepository->getAverageGrade($product->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE')); + $commentsNb = $commentRepository->getCommentsNumber($product->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE')); + $isPostAllowed = $commentRepository->isPostAllowed($product->id, (int) $this->context->cookie->id_customer, (int) $this->context->cookie->id_guest); /* configure pagination */ $commentsTotalPages = 0; @@ -1011,9 +1023,8 @@ private function renderProductCommentsList($product) */ private function renderProductCommentModal($product) { - /** @var ProductCommentCriterionRepository $criterionRepository */ - $criterionRepository = $this->context->controller->getContainer()->get('product_comment_criterion_repository'); - $criterions = $criterionRepository->getByProduct($product->id, $this->context->language->id); + $criterionRepository = $this->get('product_comment_criterion_repository'); + $criterions = $criterionRepository->getByProduct($product->id, $this->langId); $this->context->smarty->assign([ 'logged' => (bool) $this->context->cookie->id_customer, @@ -1033,10 +1044,10 @@ private function renderProductCommentModal($product) public function getWidgetVariables($hookName = null, array $configuration = []) { - $productCommentRepository = $this->context->controller->getContainer()->get('product_comment_repository'); - $averageGrade = $productCommentRepository->getAverageGrade($configuration['id_product'], Configuration::get('PRODUCT_COMMENTS_MODERATE')); - $commentsNb = $productCommentRepository->getCommentsNumber($configuration['id_product'], Configuration::get('PRODUCT_COMMENTS_MODERATE')); - $isPostAllowed = $productCommentRepository->isPostAllowed($configuration['id_product'], (int) $this->context->cookie->id_customer, (int) $this->context->cookie->id_guest); + $commentRepository = $this->get('product_comment_repository'); + $averageGrade = $commentRepository->getAverageGrade($configuration['id_product'], Configuration::get('PRODUCT_COMMENTS_MODERATE')); + $commentsNb = $commentRepository->getCommentsNumber($configuration['id_product'], Configuration::get('PRODUCT_COMMENTS_MODERATE')); + $isPostAllowed = $commentRepository->isPostAllowed($configuration['id_product'], (int) $this->context->cookie->id_customer, (int) $this->context->cookie->id_guest); return [ 'average_grade' => $averageGrade, diff --git a/src/Entity/ProductCommentCriterion.php b/src/Entity/ProductCommentCriterion.php index b3ea724b..b2d5d2e7 100644 --- a/src/Entity/ProductCommentCriterion.php +++ b/src/Entity/ProductCommentCriterion.php @@ -27,6 +27,8 @@ namespace PrestaShop\Module\ProductComment\Entity; use Doctrine\ORM\Mapping as ORM; +use Language; +use Validate; /** * @ORM\Table() @@ -34,6 +36,7 @@ */ class ProductCommentCriterion { + const NAME_MAX_LENGTH = 64; const ENTIRE_CATALOG_TYPE = 1; const CATEGORIES_TYPE = 2; const PRODUCTS_TYPE = 3; @@ -61,6 +64,93 @@ class ProductCommentCriterion */ private $active = false; + /** + * @var array + * Need to be implemented as ORM\OneToMany in the future + */ + private $names; + + /** + * @var array + * Need to be implemented as ORM\OneToMany in the future + */ + private $categories; + + /** + * @var array + * Need to be implemented as ORM\OneToMany in the future + */ + private $products; + + public function __construct() + { + $langIsoIds = Language::getIsoIds(); + $langString = ''; + foreach ($langIsoIds as $langIsoId) { + $this->names[$langIsoId['id_lang']] = $langIsoId['iso_code']; + } + } + + /** + * @return array + */ + public function getNames() + { + return $this->names; + } + + /** + * @param array $langNames + * + * @return ProductCommentCriterion + */ + public function setNames($langNames) + { + $this->names = $langNames; + + return $this; + } + + /** + * @return array + */ + public function getCategories() + { + return $this->categories; + } + + /** + * @param array $selectedCategories + * + * @return ProductCommentCriterion + */ + public function setCategories($selectedCategories) + { + $this->categories = $selectedCategories; + + return $this; + } + + /** + * @return array + */ + public function getProducts() + { + return $this->products; + } + + /** + * @param array $selectedProducts + * + * @return ProductCommentCriterion + */ + public function setProducts($selectedProducts) + { + $this->products = $selectedProducts; + + return $this; + } + /** * @return int */ @@ -108,4 +198,17 @@ public function setActive($active) return $this; } + + /** + * @return bool + */ + public function isValid() + { + $res = true; + foreach ($this->names as $key => $value) { + $res &= Validate::isGenericName($value); + } + + return $res; + } } diff --git a/src/Repository/ProductCommentCriterionRepository.php b/src/Repository/ProductCommentCriterionRepository.php index 41f5fa5c..de214acd 100644 --- a/src/Repository/ProductCommentCriterionRepository.php +++ b/src/Repository/ProductCommentCriterionRepository.php @@ -26,12 +26,28 @@ namespace PrestaShop\Module\ProductComment\Repository; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Query\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; use PrestaShop\Module\ProductComment\Entity\ProductCommentCriterion; +use PrestaShop\PrestaShop\Adapter\SymfonyContainer; -class ProductCommentCriterionRepository +/** + * @extends ServiceEntityRepository + * + * @method ProductCommentCriterion|null find($id, $lockMode = null, $lockVersion = null) + * @method ProductCommentCriterion|null findOneBy(array $criteria, array $orderBy = null) + * @method ProductCommentCriterion[] findAll() + * @method ProductCommentCriterion[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class ProductCommentCriterionRepository extends ServiceEntityRepository { + /** + * @var ManagerRegistry the EntityManager + */ + private $registry; + /** * @var Connection the Database connection */ @@ -43,15 +59,165 @@ class ProductCommentCriterionRepository private $databasePrefix; /** + * @param ManagerRegistry $registry * @param Connection $connection * @param string $databasePrefix */ - public function __construct(Connection $connection, $databasePrefix) + public function __construct($registry, $connection, $databasePrefix) { + parent::__construct($registry, ProductCommentCriterion::class); $this->connection = $connection; $this->databasePrefix = $databasePrefix; } + public function add(ProductCommentCriterion $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(ProductCommentCriterion $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + private function deleteLangs($criterion): int + { + return $this->connection->executeUpdate(' + DELETE FROM `' . _DB_PREFIX_ . 'product_comment_criterion_lang` + WHERE `id_product_comment_criterion` = ' . $criterion->getId()); + } + + private function deleteCategories($criterion): int + { + return $this->connection->executeUpdate(' + DELETE FROM `' . _DB_PREFIX_ . 'product_comment_criterion_category` + WHERE `id_product_comment_criterion` = ' . $criterion->getId()); + } + + private function deleteProducts($criterion): int + { + return $this->connection->executeUpdate(' + DELETE FROM `' . _DB_PREFIX_ . 'product_comment_criterion_product` + WHERE `id_product_comment_criterion` = ' . $criterion->getId()); + } + + private function deleteGrades($criterion): int + { + return $this->connection->executeUpdate(' + DELETE FROM `' . _DB_PREFIX_ . 'product_comment_criterion_grade` + WHERE `id_product_comment_criterion` = ' . $criterion->getId()); + } + + /* Remove a criterion and Delete its manual relation _lang, _category, _product, _grade */ + public function delete(ProductCommentCriterion $criterion): int + { + $res = 0; + + $criterionType = $criterion->getType(); + + $this->remove($criterion, true); + $this->getEntityManager()->flush(); + + $res += $this->deleteLangs($criterion); + + if ($criterionType == 2) { + $res += $this->deleteCategories($criterion); + } elseif ($criterionType == 3) { + $res += $this->deleteProducts($criterion); + } + + $res += $this->deleteGrades($criterion); + + return $res; + } + + /* Update a criterion and Delete its manual relation _lang, _category, _product, _grade */ + public function update(ProductCommentCriterion $criterion): int + { + $res = 0; + + $criterionType = $criterion->getType(); + + $this->getEntityManager()->persist($criterion); + $this->getEntityManager()->flush(); + + $res += $this->deleteLangs($criterion); + $res += $this->updateLangs($criterion); + + if ($criterionType == 2) { + $res += $this->deleteCategories($criterion); + $res += $this->updateCategories($criterion); + } elseif ($criterionType == 3) { + $res += $this->deleteProducts($criterion); + $res += $this->updateProducts($criterion); + } + + return $res; + } + + private function updateLangs($criterion): int + { + $res = 0; + $criterionId = $criterion->getId(); + foreach ($criterion->getNames() as $key => $value) { + $qb = $this->connection->createQueryBuilder(); + $qb + ->insert(_DB_PREFIX_ . 'product_comment_criterion_lang') + ->values( + [ + 'id_product_comment_criterion' => '?', + 'id_lang' => '?', + 'name' => '?', + ] + ) + ->setParameter(0, $criterionId) + ->setParameter(1, $key) + ->setParameter(2, $value) + ; + $res += $this->connection->executeUpdate($qb->getSQL(), $qb->getParameters(), $qb->getParameterTypes()); + } + + return $res; + } + + private function updateCategories($criterion): int + { + $res = 0; + $criterionId = $criterion->getId(); + foreach ($criterion->getCategories() as $id_category) { + $res += $this->connection->executeUpdate( + 'INSERT INTO `' . + _DB_PREFIX_ . 'product_comment_criterion_category` (`id_product_comment_criterion`, `id_category`) + VALUES(' . $criterionId . ',' . $id_category . ')' + ); + } + + return $res; + } + + private function updateProducts($criterion): int + { + $res = 0; + $criterionId = $criterion->getId(); + foreach ($criterion->getProducts() as $id_product) { + $res += $this->connection->executeUpdate( + 'INSERT INTO `' . + _DB_PREFIX_ . 'product_comment_criterion_product` (`id_product_comment_criterion`, `id_product`) + VALUES(' . $criterionId . ',' . $id_product . ')' + ); + } + + return $res; + } + /** * @param int $idProduct * @param int $idLang @@ -88,4 +254,116 @@ public function getByProduct($idProduct, $idLang) return $qb->execute()->fetchAll(); } + + /** + * Get Criterions + * + * @return array Criterions + */ + public function getCriterions($id_lang, $type = false, $active = false) + { + $sql = ' + SELECT pcc.`id_product_comment_criterion`, pcc.id_product_comment_criterion_type, pccl.`name`, pcc.active + FROM `' . _DB_PREFIX_ . 'product_comment_criterion` pcc + JOIN `' . _DB_PREFIX_ . 'product_comment_criterion_lang` pccl ON (pcc.id_product_comment_criterion = pccl.id_product_comment_criterion) + WHERE pccl.`id_lang` = ' . $id_lang . ($active ? ' AND active = 1' : '') . ($type ? ' AND id_product_comment_criterion_type = ' . (int) $type : '') . ' + ORDER BY pccl.`name` ASC'; + $criterions = $this->connection->executeQuery($sql)->fetchAll(); + + $types = self::getTypes(); + foreach ($criterions as $key => $data) { + $criterions[$key]['type_name'] = $types[$data['id_product_comment_criterion_type']]; + } + + return $criterions; + } + + /** + * @param int $id_criterion + * + * @return array + */ + public function getProducts(int $id_criterion) + { + $sql = ' + SELECT pccp.id_product, pccp.id_product_comment_criterion + FROM `' . _DB_PREFIX_ . 'product_comment_criterion_product` pccp + WHERE pccp.id_product_comment_criterion = ' . $id_criterion; + + $res = $this->connection->executeQuery($sql)->fetchAll(); + + $products = []; + if ($res) { + foreach ($res as $row) { + $products[] = (int) $row['id_product']; + } + } + + return $products; + } + + /** + * @param int $id_criterion + * + * @return array + */ + public function getCategories(int $id_criterion) + { + $sql = ' + SELECT pccc.id_category, pccc.id_product_comment_criterion + FROM `' . _DB_PREFIX_ . 'product_comment_criterion_category` pccc + WHERE pccc.id_product_comment_criterion = ' . $id_criterion; + + $res = $this->connection->executeQuery($sql)->fetchAll(); + + $criterions = []; + if ($res) { + foreach ($res as $row) { + $criterions[] = (int) $row['id_category']; + } + } + + return $criterions; + } + + /** + * @return array + */ + public function getTypes() + { + $sfTranslator = SymfonyContainer::getInstance()->get('translator'); + + return [ + 1 => $sfTranslator->trans('Valid for the entire catalog', [], 'Modules.Productcomments.Admin'), + 2 => $sfTranslator->trans('Restricted to some categories', [], 'Modules.Productcomments.Admin'), + 3 => $sfTranslator->trans('Restricted to some products', [], 'Modules.Productcomments.Admin'), + ]; + } + + /** + * Get Criterion with names in ative languages + * + * @return ProductCommentCriterion + */ + public function findRelation($id_criterion) + { + if ($id_criterion > 0) { + $criterion = $this->find($id_criterion); + $sql = ' + SELECT `id_lang`, `name` + FROM `' . _DB_PREFIX_ . 'product_comment_criterion_lang` pccl + WHERE pccl.id_product_comment_criterion = ' . $id_criterion . ' + ORDER BY pccl.`id_lang` ASC'; + $langNames = $this->connection->executeQuery($sql)->fetchAll(); + $langArray = []; + foreach ($langNames as $langName) { + $langArray[$langName['id_lang']] = $langName['name']; + } + $criterion->setNames($langArray); + } else { + $criterion = new ProductCommentCriterion(); + } + + return $criterion; + } } diff --git a/src/Repository/ProductCommentRepository.php b/src/Repository/ProductCommentRepository.php index a1e0d1a3..a5d334e2 100644 --- a/src/Repository/ProductCommentRepository.php +++ b/src/Repository/ProductCommentRepository.php @@ -26,11 +26,34 @@ namespace PrestaShop\Module\ProductComment\Repository; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Query\QueryBuilder; +use Doctrine\Persistence\ManagerRegistry; +use Hook; +use PrestaShop\Module\ProductComment\Entity\ProductComment; -class ProductCommentRepository +/* +use Doctrine\DBAL\Cache\QueryCacheProfile; +use Doctrine\Common\Cache\Psr6\DoctrineProvider; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; +*/ + +/** + * @extends ServiceEntityRepository + * + * @method ProductComment|null find($id, $lockMode = null, $lockVersion = null) + * @method ProductComment|null findOneBy(array $criteria, array $orderBy = null) + * @method ProductComment[] findAll() + * @method ProductComment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class ProductCommentRepository extends ServiceEntityRepository { + /** + * @var ManagerRegistry the EntityManager + */ + private $registry; + /** * @var Connection the Database connection */ @@ -54,21 +77,53 @@ class ProductCommentRepository const DEFAULT_COMMENTS_PER_PAGE = 5; /** + * @param ManagerRegistry $registry * @param Connection $connection * @param string $databasePrefix * @param bool $guestCommentsAllowed * @param int $commentsMinimalTime */ public function __construct( - Connection $connection, + $registry, + $connection, $databasePrefix, $guestCommentsAllowed, $commentsMinimalTime ) { + parent::__construct($registry, ProductComment::class); $this->connection = $connection; $this->databasePrefix = $databasePrefix; $this->guestCommentsAllowed = (bool) $guestCommentsAllowed; $this->commentsMinimalTime = (int) $commentsMinimalTime; + /* Only works since PS 8.0.0 - Doctrine\Cache 1.11.x + $cachePool = new FilesystemAdapter(); + $cache = DoctrineProvider::wrap($cachePool); + $config = $this->connection->getConfiguration(); + $config->setResultCacheImpl($cache); + */ + } + + public function add(ProductComment $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(ProductComment $entity, bool $flush = false): void + { + $entityId = $entity->getId(); + + $this->getEntityManager()->remove($entity); + if ($flush) { + $this->getEntityManager()->flush(); + } + + $this->deleteGrades($entityId); + $this->deleteReports($entityId); + $this->deleteUsefulness($entityId); } /** @@ -171,6 +226,85 @@ public function getAverageGrade($productId, $validatedOnly) return (float) $qb->execute()->fetchColumn(); } + /** + * @param int $langId + * @param int $shopId + * @param int $validate + * @param bool $deleted + * @param int $p + * @param int $limit + * @param bool $skip_validate + * + * @return array + */ + public function getByValidate($langId, $shopId, $validate = 0, $deleted = false, $p = null, $limit = null, $skip_validate = false) + { + /** @var QueryBuilder $qb */ + $qb = $this->connection->createQueryBuilder(); + $qb + ->select('pc.`id_product_comment`, pc.`id_product`, c.id_customer AS customer_id, + IF(c.id_customer, CONCAT(c.`firstname`, \' \', c.`lastname`), pc.customer_name) customer_name, + pc.`title`, pc.`content`, pc.`grade`, pc.`date_add`, pl.`name`') + ->from($this->databasePrefix . 'product_comment', 'pc') + ->leftJoin('pc', $this->databasePrefix . 'customer', 'c', 'pc.id_customer = c.id_customer') + ->leftJoin('pc', $this->databasePrefix . 'product_lang', 'pl', 'pc.id_product = pl.id_product') + ->andWhere('pc.deleted = :deleted') + ->setParameter('deleted', $deleted) + ->andWhere('pl.id_lang = :id_lang') + ->setParameter('id_lang', $langId) + ->andWhere('pl.id_shop = :id_shop') + ->setParameter('id_shop', $shopId) + ->addOrderBy('pc.date_add', 'DESC') + ; + + if (!$skip_validate) { + $qb + ->andWhere('pc.validate = :validate') + ->setParameter('validate', $validate) + ; + } + if ($p && $limit) { + $limit = (int) $limit; + $offset = ($p - 1) * $limit; + $qb + ->setFirstResult($offset) + ->setMaxResults($limit); + } + + return $this->connection->executeQuery( + $qb->getSQL(), $qb->getParameters(), $qb->getParameterTypes() + //, new QueryCacheProfile(300, "product-comments-getByValidate") + )->fetchAll(); + } + + /** + * @param int $validate + * @param bool $skip_validate + * + * @return int + */ + public function getCountByValidate($validate = 0, $skip_validate = false) + { + /** @var QueryBuilder $qb */ + $qb = $this->connection->createQueryBuilder(); + $qb + ->select('COUNT(*)') + ->from($this->databasePrefix . 'product_comment', 'pc') + ; + + if (!$skip_validate) { + $qb + ->andWhere('pc.validate = :validate') + ->setParameter('validate', $validate) + ; + } + + return (int) $this->connection->executeQuery( + $qb->getSQL(), $qb->getParameters(), $qb->getParameterTypes() + //, new QueryCacheProfile(300, "product-comments-getCountByValidate") + )->fetchColumn(); + } + /** * @param array $productIds * @param bool $validatedOnly @@ -267,8 +401,6 @@ public function getCommentsNumberForProducts(array $productIds, $validatedOnly) $sql .= ' FROM ' . $this->databasePrefix . 'product_comment'; - // return $sql; - $query = $this->connection->prepare($sql); $query->execute(); @@ -416,4 +548,88 @@ private function getLastComment(array $criteria) return empty($comments) ? [] : $comments[0]; } + + /** + * @param string $validate + * @param ProductComment $productComment + * + * @return bool + */ + public function validate($validate = '1', $productComment) + { + $success = $this->connection->executeUpdate(' + UPDATE `' . _DB_PREFIX_ . 'product_comment` SET + `validate` = ' . (int) $validate . ' + WHERE `id_product_comment` = ' . $productComment->getId()); + + Hook::exec('actionObjectProductCommentValidateAfter', ['object' => $productComment]); + + return (bool) $success; + } + + /** + * @param int $id_product_comment + * + * @return bool + */ + public function deleteGrades($id_product_comment) + { + $success = $this->connection->executeUpdate(' + DELETE FROM `' . _DB_PREFIX_ . 'product_comment_grade` + WHERE `id_product_comment` = ' . $id_product_comment); + + return (bool) $success; + } + + /** + * @param int $id_product_comment + * + * @return bool + */ + public function deleteReports($id_product_comment) + { + $success = $this->connection->executeUpdate(' + DELETE FROM `' . $this->databasePrefix . 'product_comment_report` + WHERE `id_product_comment` = ' . $id_product_comment); + + return (bool) $success; + } + + /** + * @param int $id_product_comment + * + * @return bool + */ + public function deleteUsefulness($id_product_comment) + { + $success = $this->connection->executeUpdate(' + DELETE FROM `' . _DB_PREFIX_ . 'product_comment_usefulness` + WHERE `id_product_comment` = ' . $id_product_comment); + + return (bool) $success; + } + + /** + * @param int $langId + * @param int $shopId + * + * @return array + */ + public function getReportedComments($langId, $shopId) + { + $sql = 'SELECT DISTINCT(pc.`id_product_comment`), pc.`id_product`, pc.`content`, pc.`grade`, pc.`date_add`, pc.`title` + , IF(c.id_customer, CONCAT(c.`firstname`, \' \', c.`lastname`), pc.customer_name) customer_name, pl.`name` + FROM `' . $this->databasePrefix . 'product_comment_report` pcr + LEFT JOIN `' . $this->databasePrefix . 'product_comment` pc + ON pcr.id_product_comment = pc.id_product_comment + LEFT JOIN `' . $this->databasePrefix . 'customer` c ON (c.`id_customer` = pc.`id_customer`) + LEFT JOIN `' . $this->databasePrefix . 'product_lang` pl ON ' . + '(pl.`id_product` = pc.`id_product` ' . + ' AND pl.`id_lang` = ' . $langId . + ' AND pl.`id_shop` = ' . $shopId . + ') + ORDER BY pc.`date_add` DESC'; + + return $this->connection->executeQuery($sql)->fetchAll(); + } } diff --git a/tests/phpstan.sh b/tests/phpstan.sh index 9fcc0c62..e20e09a7 100755 --- a/tests/phpstan.sh +++ b/tests/phpstan.sh @@ -23,6 +23,6 @@ echo "Run PHPStan using phpstan-${PS_VERSION}.neon file" docker run --rm --volumes-from temp-ps \ -v $PWD:/var/www/html/modules/productcomments \ -e _PS_ROOT_DIR_=/var/www/html \ - --workdir=/var/www/html/modules/productcomments phpstan/phpstan:0.12 \ + --workdir=/var/www/html/modules/productcomments phpstan/phpstan:latest \ analyse \ --configuration=/var/www/html/modules/productcomments/tests/phpstan/phpstan-$PS_VERSION.neon diff --git a/tests/phpstan/phpstan-1.7.6.neon b/tests/phpstan/phpstan-1.7.6.neon deleted file mode 100644 index 8c1ccec5..00000000 --- a/tests/phpstan/phpstan-1.7.6.neon +++ /dev/null @@ -1,12 +0,0 @@ -includes: - - %currentWorkingDirectory%/tests/phpstan/phpstan.neon - -parameters: - ignoreErrors: - - '#Access to an undefined property Cookie::\$id_customer.#' - - '#Access to an undefined property Cookie::\$id_guest.#' - - '#Access to an undefined property HelperList::\$list_id.#' - - '#Access to an undefined property HelperList::\$shopLinkType.#' - - '#Parameter \#1 \$idCategory of class Category constructor expects null, int given.#' - - '#Parameter \#1 \$value of method ControllerCore::ajaxRender\(\) expects null, string\|false given.#' - - '#Parameter \#2 \$value of static method CacheCore::store\(\) expects string, array\|mysqli_result\|PDOStatement\|resource\|false\|null given.#' diff --git a/tests/phpstan/phpstan-8.0.neon b/tests/phpstan/phpstan-8.0.neon new file mode 100644 index 00000000..23b71e8d --- /dev/null +++ b/tests/phpstan/phpstan-8.0.neon @@ -0,0 +1,5 @@ +includes: + - %currentWorkingDirectory%/tests/phpstan/phpstan.neon + +parameters: + ignoreErrors: diff --git a/tests/phpstan/phpstan.neon b/tests/phpstan/phpstan.neon index 037652f7..c72e7494 100644 --- a/tests/phpstan/phpstan.neon +++ b/tests/phpstan/phpstan.neon @@ -10,3 +10,16 @@ parameters: - ../../src/ - ../../upgrade/ level: 5 + ignoreErrors: + - + message: '#Cannot call method [a-zA-Z0-9\\_]+\(\) on object\|false.#' + path: ../../productcomments.php + - + message: '#Property .+::\$id is never written, only read.#' + path: ../../src/Entity/ + - + message: '#Property .+::\$grade is never read, only written.#' + path: ../../src/Entity/ + - + message: '#Property .+::\$registry is unused.#' + path: ../../src/Repository/ \ No newline at end of file