diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e1818d46..f85296b6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,16 +1,23 @@ version: 2 updates: -- package-ecosystem: npm +- package-ecosystem: composer directory: "/" schedule: interval: daily time: "04:00" open-pull-requests-limit: 10 target-branch: dev -- package-ecosystem: composer +- package-ecosystem: github-actions directory: "/" schedule: interval: daily time: "04:00" open-pull-requests-limit: 10 target-branch: dev +- package-ecosystem: npm + directory: "/" + schedule: + interval: daily + time: "04:00" + open-pull-requests-limit: 10 + target-branch: dev \ No newline at end of file diff --git a/.github/workflows/add-issue-to-project.yml b/.github/workflows/add-issue-to-project.yml new file mode 100644 index 00000000..cfa4ba3f --- /dev/null +++ b/.github/workflows/add-issue-to-project.yml @@ -0,0 +1,13 @@ +name: Add Issue to Project + +on: + issues: + types: [ opened ] + +jobs: + add-to-project: + uses: PrestaShop/.github/.github/workflows/reusable-add-to-project.yml@master + with: + project-number: 47 + secrets: + TOKEN: ${{ secrets.JARVIS_TOKEN }} diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 32dfb883..172ce9c6 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -7,17 +7,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install composer dependencies run: composer install --no-dev -o - name: Clean-up project - uses: PrestaShopCorp/github-action-clean-before-deploy@v1.0 + uses: PrestaShopCorp/github-action-clean-before-deploy@v2.0 - name: Prepare auto-index tool run: composer global require prestashop/autoindex - name: Generate index.php run: ~/.composer/vendor/bin/autoindex - name: Create & upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ github.event.repository.name }} path: /home/runner/work/${{ github.event.repository.name }} @@ -27,12 +27,12 @@ jobs: if: github.event_name == 'push' && (github.event.ref == 'refs/heads/main' || github.event.ref == 'refs/heads/master') steps: - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: ${{ github.event.repository.name }} path: ${{ github.event.repository.name }} - id: release_info - uses: toolmantim/release-drafter@v5 + uses: toolmantim/release-drafter@v7.7.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Prepare for Release diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 8e08b03f..c4cb6c4a 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -1,13 +1,12 @@ name: PHP tests on: [push, pull_request] jobs: - # Check there is no syntax errors in the project php-linter: - name: PHP Syntax check 7.2 => 8.2 + name: PHP Syntax check 7.2 => 8.5 runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: PHP syntax checker 7.2 uses: prestashop/github-action-php-lint/7.2@master @@ -25,65 +24,127 @@ jobs: uses: prestashop/github-action-php-lint/8.1@master - name: PHP syntax checker 8.2 - uses: prestashop/github-action-php-lint/8.2@master + uses: prestashop/github-action-php-lint/8.2@master + + - name: PHP syntax checker 8.3 + uses: prestashop/github-action-php-lint/8.3@master + + - name: PHP syntax checker 8.4 + uses: prestashop/github-action-php-lint/8.4@master + + - name: PHP syntax checker 8.5 + uses: prestashop/github-action-php-lint/8.5@master # Check the PHP code follow the coding standards php-cs-fixer: name: PHP-CS-Fixer runs-on: ubuntu-latest steps: - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - name: Run PHP-CS-Fixer + uses: PrestaShop/.github/.github/actions/php-ci/php-cs@master with: - php-version: '7.4' - - - name: Checkout - uses: actions/checkout@v4 + php-version: '7.4' - - name: Cache dependencies - uses: actions/cache@v4 + # Run PHPStan against the module (PHP 7.4 – PS 8.2) + phpstan-74-81: + name: PHPStan (PHP 7.4 - 8.1) + runs-on: ubuntu-latest + strategy: + matrix: + presta_version: ['8.2.x'] + php_version: ['7.4', '8.1'] + fail-fast: false + env: + PHPRC: ${{ github.workspace }}/${{ github.event.repository.name }}/.phpstan-php-ini + steps: + - name: Checkout repository + uses: actions/checkout@v7 with: - path: vendor - key: php-${{ hashFiles('composer.lock') }} - - - name: Install dependencies - run: composer install - - - name: Run PHP-CS-Fixer - run: ./vendor/bin/php-cs-fixer fix --dry-run --diff --using-cache=no - - # Run PHPStan against the module and a PrestaShop release - phpstan: - name: PHPStan + path: ${{ github.event.repository.name }} + + - name: Prepare PHP env for PrestaShop 8 (define constants before any bootstrap) + run: | + mkdir -p ${{ github.event.repository.name }}/.phpstan-php-ini + { + echo "auto_prepend_file=$GITHUB_WORKSPACE/${{ github.event.repository.name }}/tests/php/phpstan/prepend-constants.php" + echo "memory_limit=512M" + } > ${{ github.event.repository.name }}/.phpstan-php-ini/php.ini + + - name: Run PHPStan + uses: PrestaShop/.github/.github/actions/php-ci/phpstan@master + with: + php-version: ${{ matrix.php_version }} + presta-version: ${{ matrix.presta_version }} + module-name: ${{ github.event.repository.name }} + phpstan-level: '5' + phpstan-config: tests/php/phpstan/phpstan-${{ matrix.presta_version }}.neon + phpstan-version: '^0.12' + composer-version: '2.2.18' + + # Run PHPStan against the module (PHP 8.1 – 8.4) + phpstan-81-84: + name: PHPStan (PHP 8.1 - 8.4) runs-on: ubuntu-latest strategy: matrix: - presta-versions: ['1.7.8', '8.0', 'latest'] + presta_version: ['9.0.3'] + php_version: ['8.1', '8.4'] + fail-fast: false + env: + PHPRC: ${{ github.workspace }}/${{ github.event.repository.name }}/.phpstan-php-ini steps: - - name: Setup PHP - uses: shivammathur/setup-php@v2 + - name: Checkout repository + uses: actions/checkout@v7 with: - php-version: '7.4' - - - name: Checkout - uses: actions/checkout@v4 - - # Add vendor folder in cache to make next builds faster - - name: Cache vendor folder - uses: actions/cache@v4 + path: ${{ github.event.repository.name }} + + - name: Prepare PHP env for PrestaShop 9.1.x and later (define constants before any bootstrap) + run: | + mkdir -p ${{ github.event.repository.name }}/.phpstan-php-ini + { + echo "auto_prepend_file=$GITHUB_WORKSPACE/${{ github.event.repository.name }}/tests/php/phpstan/prepend-constants.php" + echo "memory_limit=512M" + } > ${{ github.event.repository.name }}/.phpstan-php-ini/php.ini + + - name: Run PHPStan + uses: PrestaShop/.github/.github/actions/php-ci/phpstan@master with: - path: vendor - key: php-${{ hashFiles('composer.lock') }} + php-version: ${{ matrix.php_version }} + presta-version: ${{ matrix.presta_version }} + module-name: ${{ github.event.repository.name }} + phpstan-level: '5' + phpstan-config: tests/php/phpstan/phpstan-${{ matrix.presta_version }}.neon - # Add composer local folder in cache to make next builds faster - - name: Cache composer folder - uses: actions/cache@v4 + # Run PHPStan against the module (PHP 8.1 – 8.5) + phpstan: + name: PHPStan (PHP 8.1 - 8.5) + runs-on: ubuntu-latest + strategy: + matrix: + presta_version: ['9.1.5', '9.2.x', 'develop'] + php_version: ['8.1', '8.5'] + fail-fast: false + env: + PHPRC: ${{ github.workspace }}/${{ github.event.repository.name }}/.phpstan-php-ini + steps: + - name: Checkout repository + uses: actions/checkout@v7 with: - path: ~/.composer/cache - key: php-composer-cache - - - run: composer install - - # 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 }} + path: ${{ github.event.repository.name }} + + - name: Prepare PHP env for PrestaShop 9.1.x and later (define constants before any bootstrap) + run: | + mkdir -p ${{ github.event.repository.name }}/.phpstan-php-ini + { + echo "auto_prepend_file=$GITHUB_WORKSPACE/${{ github.event.repository.name }}/tests/php/phpstan/prepend-constants.php" + echo "memory_limit=512M" + } > ${{ github.event.repository.name }}/.phpstan-php-ini/php.ini + + - name: Run PHPStan + uses: PrestaShop/.github/.github/actions/php-ci/phpstan@master + with: + php-version: ${{ matrix.php_version }} + presta-version: ${{ matrix.presta_version }} + module-name: ${{ github.event.repository.name }} + phpstan-level: '5' + phpstan-config: tests/php/phpstan/phpstan-${{ matrix.presta_version }}.neon \ No newline at end of file diff --git a/ProductComment.php b/ProductComment.php index d9ce2069..d0210b0d 100644 --- a/ProductComment.php +++ b/ProductComment.php @@ -29,9 +29,6 @@ class ProductComment extends ObjectModel { - /** @var int */ - public $id; - /** @var int */ public $id_product; diff --git a/ProductCommentCriterion.php b/ProductCommentCriterion.php index 5caf632c..0eb573d6 100644 --- a/ProductCommentCriterion.php +++ b/ProductCommentCriterion.php @@ -27,7 +27,6 @@ class ProductCommentCriterion extends ObjectModel { const NAME_MAX_LENGTH = 64; - public $id; public $id_product_comment_criterion_type; public $name; public $active = true; diff --git a/README.md b/README.md index 0f18c6d9..43de567b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Allow users to post reviews on your products and/or rate them based on specific ## Compatibility -PrestaShop: `1.7.8` or newer +PrestaShop: `8.2.0` or newer ## Multistore compatibility diff --git a/config.xml b/config.xml index 958f4458..ba407583 100644 --- a/config.xml +++ b/config.xml @@ -2,11 +2,11 @@ productcomments - + 1 0 - + diff --git a/config/admin/services.yml b/config/admin/services.yml index 02ce511d..d161f72a 100644 --- a/config/admin/services.yml +++ b/config/admin/services.yml @@ -13,6 +13,5 @@ services: class: 'PrestaShop\Module\ProductComment\Form\ProductCommentCriterionFormDataHandler' public: true arguments: - - '@product_comment_criterion_repository' - '@prestashop.core.admin.lang.repository' - '@doctrine.orm.default_entity_manager' \ No newline at end of file diff --git a/controllers/front/CommentGrade.php b/controllers/front/CommentGrade.php index e9cb4940..51560ca3 100644 --- a/controllers/front/CommentGrade.php +++ b/controllers/front/CommentGrade.php @@ -30,20 +30,28 @@ class ProductCommentsCommentGradeModuleFrontController extends ModuleFrontContro public function display() { $idProducts = Tools::getValue('id_products'); - /* @var ProductCommentRepository $productCommentRepository */ header('Content-Type: application/json'); if (!is_array($idProducts)) { - return $this->ajaxRender(null); + $this->ajaxRender(null); + + return; } $idProducts = array_unique(array_map('intval', $idProducts)); - $productCommentRepository = $this->context->controller->getContainer()->get('product_comment_repository'); + /** @var ProductCommentRepository|null $commentRepository */ + $commentRepository = $this->context->controller->getContainer()->get('product_comment_repository'); + + if (!$commentRepository instanceof ProductCommentRepository) { + $this->ajaxRender(null); + + return; + } - $productsCommentsNb = $productCommentRepository->getCommentsNumberForProducts($idProducts, Configuration::get('PRODUCT_COMMENTS_MODERATE')); - $averageGrade = $productCommentRepository->getAverageGrades($idProducts, Configuration::get('PRODUCT_COMMENTS_MODERATE')); + $productsCommentsNb = $commentRepository->getCommentsNumberForProducts($idProducts, Configuration::get('PRODUCT_COMMENTS_MODERATE')); + $averageGrade = $commentRepository->getAverageGrades($idProducts, Configuration::get('PRODUCT_COMMENTS_MODERATE')); $resultFormated = []; diff --git a/controllers/front/ListComments.php b/controllers/front/ListComments.php index b5c87bab..8af7f4d0 100644 --- a/controllers/front/ListComments.php +++ b/controllers/front/ListComments.php @@ -29,6 +29,8 @@ class ProductCommentsListCommentsModuleFrontController extends ModuleFrontContro { public function display() { + header('Content-Type: application/json'); + $idProduct = (int) Tools::getValue('id_product'); $page = (int) Tools::getValue('page', 1); $isLastNameAnonymous = Configuration::get('PRODUCT_COMMENTS_ANONYMISATION'); @@ -85,7 +87,6 @@ public function display() $responseArray['comments'][] = $productComment; } - header('Content-Type: application/json'); $this->ajaxRender( json_encode( $responseArray diff --git a/controllers/front/PostComment.php b/controllers/front/PostComment.php index 34d6e4be..01359b55 100644 --- a/controllers/front/PostComment.php +++ b/controllers/front/PostComment.php @@ -53,7 +53,7 @@ public function display() ) ); - return false; + return; } $id_product = (int) Tools::getValue('id_product'); @@ -79,7 +79,7 @@ public function display() ) ); - return false; + return; } /** @var EntityManagerInterface $entityManager */ @@ -110,7 +110,7 @@ public function display() ) ); - return false; + return; } $entityManager->persist($productComment); diff --git a/controllers/front/ReportComment.php b/controllers/front/ReportComment.php index 9be0af98..4634de2e 100644 --- a/controllers/front/ReportComment.php +++ b/controllers/front/ReportComment.php @@ -44,7 +44,7 @@ public function display() ) ); - return false; + return; } $id_product_comment = (int) Tools::getValue('id_product_comment'); @@ -65,7 +65,7 @@ public function display() ) ); - return false; + return; } $productCommentAbuseRepository = $entityManager->getRepository(ProductCommentReport::class); @@ -85,7 +85,7 @@ public function display() ) ); - return false; + return; } $productCommentAbuse = new ProductCommentReport( diff --git a/controllers/front/UpdateCommentUsefulness.php b/controllers/front/UpdateCommentUsefulness.php index 774435b9..6342992a 100644 --- a/controllers/front/UpdateCommentUsefulness.php +++ b/controllers/front/UpdateCommentUsefulness.php @@ -44,7 +44,7 @@ public function display() ) ); - return false; + return; } $customerId = (int) $this->context->cookie->id_customer; @@ -67,7 +67,7 @@ public function display() ) ); - return false; + return; } $id_product_comment = (int) Tools::getValue('id_product_comment'); @@ -89,7 +89,7 @@ public function display() ) ); - return false; + return; } $productCommentUsefulnesRepository = $entityManager->getRepository(ProductCommentUsefulness::class); diff --git a/productcomments.php b/productcomments.php index 851d063c..875f40d7 100644 --- a/productcomments.php +++ b/productcomments.php @@ -29,25 +29,45 @@ } use PrestaShop\Module\ProductComment\Entity\ProductCommentCriterion; +use PrestaShop\Module\ProductComment\Form\ProductCommentCriterionFormDataHandler; +use PrestaShop\Module\ProductComment\Form\ProductCommentCriterionFormDataProvider; +use PrestaShop\Module\ProductComment\Repository\ProductCommentCriterionRepository; +use PrestaShop\Module\ProductComment\Repository\ProductCommentRepository; +use PrestaShop\PrestaShop\Adapter\Presenter\Product\ProductLazyArray; use PrestaShop\PrestaShop\Core\Module\WidgetInterface; class ProductComments extends Module implements WidgetInterface { const INSTALL_SQL_FILE = 'install.sql'; + /** + * @var string + */ private $_html = ''; - - private $_productCommentsCriterionTypes = []; + /** + * @var string + */ private $_baseUrl; - private $langId; private $shopId; + public const HOOKS = [ + 'displayFooterProduct', + 'displayHeader', + 'displayProductListReviews', + 'displayProductAdditionalInfo', + 'filterProductContent', + 'registerGDPRConsent', + 'actionDeleteGDPRCustomer', + 'actionExportGDPRData', + 'actionFrontControllerSetVariables', + ]; + public function __construct() { $this->name = 'productcomments'; $this->tab = 'front_office_features'; - $this->version = '8.0.0'; + $this->version = '9.0.0'; $this->author = 'PrestaShop'; $this->need_instance = 0; $this->bootstrap = true; @@ -60,7 +80,7 @@ public function __construct() $this->langId = $this->context->language->id; $this->shopId = $this->context->shop->id ? $this->context->shop->id : Configuration::get('PS_SHOP_DEFAULT'); - $this->ps_versions_compliancy = ['min' => '1.7.8', 'max' => _PS_VERSION_]; + $this->ps_versions_compliancy = ['min' => '8.2.0', 'max' => _PS_VERSION_]; } public function install($keep = true) @@ -85,66 +105,30 @@ public function install($keep = true) } } - if ( - parent::install() == false || - !$this->registerHook('displayFooterProduct') || //Product page footer - !$this->registerHook('displayHeader') || //Adds css and javascript on front - !$this->registerHook('displayProductListReviews') || //Product list miniature - !$this->registerHook('displayProductAdditionalInfo') || //Display info in checkout column - !$this->registerHook('filterProductContent') || // Add infos to Product page - !$this->registerHook('registerGDPRConsent') || - !$this->registerHook('actionDeleteGDPRCustomer') || - !$this->registerHook('actionExportGDPRData') || - - !Configuration::updateValue('PRODUCT_COMMENTS_MINIMAL_TIME', 30) || - !Configuration::updateValue('PRODUCT_COMMENTS_ALLOW_GUESTS', 0) || - !Configuration::updateValue('PRODUCT_COMMENTS_USEFULNESS', 1) || - !Configuration::updateValue('PRODUCT_COMMENTS_COMMENTS_PER_PAGE', 5) || - !Configuration::updateValue('PRODUCT_COMMENTS_ANONYMISATION', 0) || - !Configuration::updateValue('PRODUCT_COMMENTS_MODERATE', 1) - ) { - return false; - } - - return true; + return parent::install() && $this->registerHook(self::HOOKS) && + Configuration::updateValue('PRODUCT_COMMENTS_MINIMAL_TIME', 30) && + Configuration::updateValue('PRODUCT_COMMENTS_ALLOW_GUESTS', 0) && + Configuration::updateValue('PRODUCT_COMMENTS_USEFULNESS', 1) && + Configuration::updateValue('PRODUCT_COMMENTS_COMMENTS_PER_PAGE', 5) && + Configuration::updateValue('PRODUCT_COMMENTS_ANONYMISATION', 0) && + Configuration::updateValue('PRODUCT_COMMENTS_MODERATE', 1); } public function uninstall($keep = true) { - if ( - !parent::uninstall() || ($keep && !$this->deleteTables()) || - !Configuration::deleteByName('PRODUCT_COMMENTS_MODERATE') || - !Configuration::deleteByName('PRODUCT_COMMENTS_COMMENTS_PER_PAGE') || - !Configuration::deleteByName('PRODUCT_COMMENTS_ANONYMISATION') || - !Configuration::deleteByName('PRODUCT_COMMENTS_ALLOW_GUESTS') || - !Configuration::deleteByName('PRODUCT_COMMENTS_USEFULNESS') || - !Configuration::deleteByName('PRODUCT_COMMENTS_MINIMAL_TIME') || - - !$this->unregisterHook('registerGDPRConsent') || - !$this->unregisterHook('actionDeleteGDPRCustomer') || - !$this->unregisterHook('actionExportGDPRData') || - - !$this->unregisterHook('displayProductAdditionalInfo') || - !$this->unregisterHook('displayHeader') || - !$this->unregisterHook('displayFooterProduct') || - !$this->unregisterHook('displayProductListReviews') - ) { - return false; - } - - return true; + return parent::uninstall() && + ($keep && !$this->deleteTables()) && + Configuration::deleteByName('PRODUCT_COMMENTS_MODERATE') && + Configuration::deleteByName('PRODUCT_COMMENTS_COMMENTS_PER_PAGE') && + Configuration::deleteByName('PRODUCT_COMMENTS_ANONYMISATION') && + Configuration::deleteByName('PRODUCT_COMMENTS_ALLOW_GUESTS') && + Configuration::deleteByName('PRODUCT_COMMENTS_USEFULNESS') && + Configuration::deleteByName('PRODUCT_COMMENTS_MINIMAL_TIME'); } public function reset() { - if (!$this->uninstall(false)) { - return false; - } - if (!$this->install(false)) { - return false; - } - - return true; + return $this->uninstall(false) && $this->install(false); } public function deleteTables() @@ -170,8 +154,11 @@ protected function _postProcess() { $id_product_comment = (int) Tools::getValue('id_product_comment'); $id_product_comment_criterion = (int) Tools::getValue('id_product_comment_criterion'); + /** @var ProductCommentRepository $commentRepository */ $commentRepository = $this->get('product_comment_repository'); + /** @var ProductCommentCriterionRepository $criterionRepository */ $criterionRepository = $this->get('product_comment_criterion_repository'); + /** @var ProductCommentCriterionFormDataHandler $criterionFormHandler */ $criterionFormHandler = $this->get('product_comment_criterion_form_data_handler'); if (Tools::isSubmit('submitModerate')) { @@ -287,7 +274,6 @@ public function getContent() } $this->_setBaseUrl(); - $this->_productCommentsCriterionTypes = $this->get('product_comment_criterion_repository')->getTypes(); $this->context->controller->addJs($this->_path . 'js/moderate.js'); @@ -433,6 +419,7 @@ public function renderConfigForm() public function renderModerateLists() { $return = null; + /** @var ProductCommentRepository $commentRepository */ $commentRepository = $this->get('product_comment_repository'); if (Configuration::get('PRODUCT_COMMENTS_MODERATE')) { @@ -528,7 +515,9 @@ public function displayNoabuseLink($token, $id, $name = null) public function renderCriterionList() { - $criterions = $this->get('product_comment_criterion_repository')->getCriterions($this->langId, false, false); + /** @var ProductCommentCriterionRepository $criterionRepository */ + $criterionRepository = $this->get('product_comment_criterion_repository'); + $criterions = $criterionRepository->getCriterions($this->langId, false, false); $fields_list = [ 'id_product_comment_criterion' => [ @@ -592,6 +581,7 @@ public function renderCommentsList() $pagination = ($pagination = Tools::getValue($helper->list_id . '_pagination')) ? (int) $pagination : 50; $moderate = Configuration::get('PRODUCT_COMMENTS_MODERATE'); + /** @var ProductCommentRepository $commentRepository */ $commentRepository = $this->get('product_comment_repository'); if (empty($moderate)) { $comments = $commentRepository->getByValidate($this->langId, $this->shopId, 0, false, $page, $pagination, true); @@ -620,7 +610,7 @@ public function getConfigFieldsValues() public function getCriterionFieldsValues(int $id = 0) { - $criterionRepos = $this->get('product_comment_criterion_repository'); + /** @var ProductCommentCriterionFormDataProvider $criterionFormProvider */ $criterionFormProvider = $this->get('product_comment_criterion_form_data_provider'); if ($id > 0) { @@ -713,7 +703,10 @@ public function renderAuthorName($value, $row) public function renderCriterionForm($id_criterion = 0) { - $types = $this->get('product_comment_criterion_repository')->getTypes(); + /** @var ProductCommentCriterionRepository $criterionRepository */ + $criterionRepository = $this->get('product_comment_criterion_repository'); + + $types = $criterionRepository->getTypes(); $query = []; foreach ($types as $key => $value) { $query[] = [ @@ -722,8 +715,6 @@ public function renderCriterionForm($id_criterion = 0) ]; } - $criterionRepository = $this->get('product_comment_criterion_repository'); - $criterion = $criterionRepository->find($id_criterion); $selected_categories = $criterionRepository->getCategories($id_criterion); @@ -859,9 +850,9 @@ public function initCategoriesAssociation($id_root = null, $id_criterion = 0) if ($id_criterion == 0) { $selected_cat = []; } else { + /** @var ProductCommentCriterionRepository $criterionRepository */ $criterionRepository = $this->get('product_comment_criterion_repository'); - $criterion = $criterionRepository->find((int) $id_criterion); - $selected_cat = $criterionRepository->getCategories($criterion); + $selected_cat = $criterionRepository->getCategories((int) $id_criterion); } if (Shop::getContext() == Shop::CONTEXT_SHOP && Tools::isSubmit('id_shop')) { @@ -879,8 +870,9 @@ public function initCategoriesAssociation($id_root = null, $id_criterion = 0) public function hookActionDeleteGDPRCustomer($customer) { if (isset($customer['id'])) { - $productCommentRepository = $this->get('product_comment_repository'); - $productCommentRepository->cleanCustomerData($customer['id']); + /** @var ProductCommentRepository $commentRepository */ + $commentRepository = $this->get('product_comment_repository'); + $commentRepository->cleanCustomerData($customer['id']); } return true; @@ -889,10 +881,11 @@ public function hookActionDeleteGDPRCustomer($customer) public function hookActionExportGDPRData($customer) { if (isset($customer['id'])) { - $productCommentRepository = $this->get('product_comment_repository'); + /** @var ProductCommentRepository $commentRepository */ + $commentRepository = $this->get('product_comment_repository'); $langId = isset($customer['id_lang']) ? $customer['id_lang'] : $this->langId; - return json_encode($productCommentRepository->getCustomerData($customer['id'], $langId)); + return json_encode($commentRepository->getCustomerData($customer['id'], $langId)); } } @@ -935,9 +928,9 @@ public function hookDisplayFooterProduct($params) } /** - * Inject data about productcomments in the product object for frontoffice + * Inject data about productcomments in the product object for frontoffice. This is the older way of adding the data before PrestaShop 9.2. * - * @param array $params + * @param array{object: ProductLazyArray} $params * * @return array */ @@ -946,11 +939,15 @@ public function hookFilterProductContent(array $params) if (empty($params['object']->id)) { return $params; } + /** @var ProductCommentRepository $commentRepository */ $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 */ + /* + * @phpstan-ignore-next-line + * Needs #[AllowDynamicProperties] on ProductLazyArray class to avoid error in PHP 8.2+ + */ $params['object']->productComments = [ 'averageRating' => $averageRating, 'nbComments' => $nbComments, @@ -971,6 +968,7 @@ public function hookFilterProductContent(array $params) */ private function renderProductCommentsList($product) { + /** @var ProductCommentRepository $commentRepository */ $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')); @@ -1019,6 +1017,7 @@ private function renderProductCommentsList($product) */ private function renderProductCommentModal($product) { + /** @var ProductCommentCriterionRepository $criterionRepository */ $criterionRepository = $this->get('product_comment_criterion_repository'); $criterions = $criterionRepository->getByProduct($product->id, $this->langId); @@ -1040,6 +1039,7 @@ private function renderProductCommentModal($product) public function getWidgetVariables($hookName = null, array $configuration = []) { + /** @var ProductCommentRepository $commentRepository */ $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')); @@ -1057,6 +1057,9 @@ public function renderWidget($hookName = null, array $configuration = []) $variables = []; $tplHookPath = 'module:productcomments/views/templates/hook/'; + /** @var \ProductControllerCore $controller */ + $controller = $this->context->controller; + if ('displayProductListReviews' === $hookName || isset($configuration['type']) && 'product_list' === $configuration['type']) { $product = $configuration['product']; $idProduct = $product['id_product']; @@ -1068,8 +1071,8 @@ public function renderWidget($hookName = null, array $configuration = []) ]); $filePath = $tplHookPath . 'product-list-reviews.tpl'; - } elseif ($this->context->controller instanceof ProductControllerCore) { - $idProduct = $this->context->controller->getProduct()->id; + } elseif ($controller instanceof ProductControllerCore) { + $idProduct = $controller->getProduct()->id; $variables = $this->getWidgetVariables($hookName, ['id_product' => $idProduct]); switch (Tools::getValue('action')) { @@ -1112,4 +1115,41 @@ public function hookRegisterGDPRConsent() at module installation. */ } + + /** + * This hook adds reviews into structured data on PrestaShop 9.2 and newer. + * On lower versions, it doesn't do anything. + */ + public function hookActionFrontControllerSetVariables($params) + { + // Check if the current page is a product page and if structured data for the product is available + if ($this->context->controller->php_self !== 'product' || !isset($params['templateVars']['structured_data']['product'])) { + return; + } + + // Get product ID, check for pages where the product is not loaded (e.g., 404 pages) + $product = $this->context->controller->getProduct(); + if (empty($product) || !Validate::isLoadedObject($product)) { + return; + } + + // Get the rating and append it to the array + $commentRepository = $this->get('product_comment_repository'); + $averageRating = $commentRepository->getAverageGrade($product->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE')); + $nbComments = $commentRepository->getCommentsNumber($product->id, (bool) Configuration::get('PRODUCT_COMMENTS_MODERATE')); + + // If no rating or no comments, don't add the structured data + if (empty($averageRating) || empty($nbComments)) { + return; + } + + $params['templateVars']['structured_data']['product']['aggregateRating'] = [ + '@type' => 'AggregateRating', + 'ratingValue' => $averageRating, + 'reviewCount' => $nbComments, + // Constant values for best and worst rating, as per schema.org specifications, so the bot knows what the scale is. + 'bestRating' => 5, + 'worstRating' => 1, + ]; + } } diff --git a/src/Entity/ProductCommentGrade.php b/src/Entity/ProductCommentGrade.php index 0aae703d..7b06e7d0 100644 --- a/src/Entity/ProductCommentGrade.php +++ b/src/Entity/ProductCommentGrade.php @@ -71,7 +71,7 @@ public function __construct( } /** - * @return mixed + * @return ProductComment */ public function getComment() { @@ -79,10 +79,15 @@ public function getComment() } /** - * @return mixed + * @return ProductCommentCriterion */ public function getCriterion() { return $this->criterion; } + + public function getGrade(): int + { + return $this->grade; + } } diff --git a/src/Form/ProductCommentCriterionFormDataHandler.php b/src/Form/ProductCommentCriterionFormDataHandler.php index 8e0437d9..2f15e7c3 100644 --- a/src/Form/ProductCommentCriterionFormDataHandler.php +++ b/src/Form/ProductCommentCriterionFormDataHandler.php @@ -24,17 +24,11 @@ use Doctrine\ORM\EntityManagerInterface; use PrestaShop\Module\ProductComment\Entity\ProductCommentCriterion; use PrestaShop\Module\ProductComment\Entity\ProductCommentCriterionLang; -use PrestaShop\Module\ProductComment\Repository\ProductCommentCriterionRepository; use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\DataHandler\FormDataHandlerInterface; use PrestaShopBundle\Entity\Repository\LangRepository; class ProductCommentCriterionFormDataHandler implements FormDataHandlerInterface { - /** - * @var ProductCommentCriterionRepository - */ - private $pccriterionRepository; - /** * @var LangRepository */ @@ -46,16 +40,13 @@ class ProductCommentCriterionFormDataHandler implements FormDataHandlerInterface private $entityManager; /** - * @param ProductCommentCriterionRepository $pccriterionRepository * @param LangRepository $langRepository * @param EntityManagerInterface $entityManager */ public function __construct( - ProductCommentCriterionRepository $pccriterionRepository, LangRepository $langRepository, EntityManagerInterface $entityManager ) { - $this->pccriterionRepository = $pccriterionRepository; $this->langRepository = $langRepository; $this->entityManager = $entityManager; } diff --git a/src/Repository/ProductCommentCriterionRepository.php b/src/Repository/ProductCommentCriterionRepository.php index 6b142af2..46295a5c 100644 --- a/src/Repository/ProductCommentCriterionRepository.php +++ b/src/Repository/ProductCommentCriterionRepository.php @@ -43,11 +43,6 @@ */ class ProductCommentCriterionRepository extends ServiceEntityRepository { - /** - * @var ManagerRegistry the Doctrine Registry - */ - private $registry; - /** * @var Connection the Database connection */ @@ -159,12 +154,13 @@ public function update(ProductCommentCriterion $criterion): int private function updateCategories($criterion): int { $res = 0; - $criterionId = $criterion->getId(); + $criterionId = (int) $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 . ')' + VALUES(?, ?)', + [$criterionId, (int) $id_category] ); } @@ -174,12 +170,13 @@ private function updateCategories($criterion): int private function updateProducts($criterion): int { $res = 0; - $criterionId = $criterion->getId(); + $criterionId = (int) $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 . ')' + VALUES(?, ?)', + [$criterionId, (int) $id_product] ); } diff --git a/src/Repository/ProductCommentRepository.php b/src/Repository/ProductCommentRepository.php index 88db9688..0292723a 100644 --- a/src/Repository/ProductCommentRepository.php +++ b/src/Repository/ProductCommentRepository.php @@ -43,11 +43,6 @@ */ class ProductCommentRepository extends ServiceEntityRepository { - /** - * @var ManagerRegistry the Doctrine Registry - */ - private $registry; - /** * @var Connection the Database connection */ diff --git a/tests/phpstan/index.php b/tests/php/index.php similarity index 100% rename from tests/phpstan/index.php rename to tests/php/index.php diff --git a/tests/php/phpstan/index.php b/tests/php/phpstan/index.php new file mode 100644 index 00000000..45df26c5 --- /dev/null +++ b/tests/php/phpstan/index.php @@ -0,0 +1,34 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + */ +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/tests/php/phpstan/phpstan-8.2.x.neon b/tests/php/phpstan/phpstan-8.2.x.neon new file mode 100644 index 00000000..b048e5fd --- /dev/null +++ b/tests/php/phpstan/phpstan-8.2.x.neon @@ -0,0 +1,18 @@ +includes: + - %currentWorkingDirectory%/tests/php/phpstan/phpstan.neon + +parameters: + ignoreErrors: + - '#Method ProductCommentsCommentGradeModuleFrontController::display\(\) should return bool but empty return statement found.#' + - '#Method ProductCommentsPostCommentModuleFrontController::display\(\) should return bool but empty return statement found.#' + - '#Method ProductCommentsReportCommentModuleFrontController::display\(\) should return bool but empty return statement found.#' + - '#Method ProductCommentsUpdateCommentUsefulnessModuleFrontController::display\(\) should return bool but empty return statement found.#' + - '#Method ProductCommentsCommentGradeModuleFrontController::display\(\) should return bool but return statement is missing.#' + - '#Method ProductCommentsListCommentsModuleFrontController::display\(\) should return bool but return statement is missing.#' + - '#Method ProductCommentsPostCommentModuleFrontController::display\(\) should return bool but return statement is missing.#' + - '#Method ProductCommentsReportCommentModuleFrontController::display\(\) should return bool but return statement is missing.#' + - '#Method ProductCommentsUpdateCommentUsefulnessModuleFrontController::display\(\) should return bool but return statement is missing.#' + - '#Property [a-zA-Z0-9\\_]+::\$id is never written, only read.#' + - '#Call to an undefined method AdminController\|FrontController\|PrestaShopBundle\\Bridge\\AdminController\\LegacyControllerBridgeInterface::getProduct\(\).#' + - '#Cannot call method getAverageGrade\(\) on object\|false.#' + - '#Cannot call method getCommentsNumber\(\) on object\|false.#' \ No newline at end of file diff --git a/tests/php/phpstan/phpstan-9.0.3.neon b/tests/php/phpstan/phpstan-9.0.3.neon new file mode 100644 index 00000000..dcce4f57 --- /dev/null +++ b/tests/php/phpstan/phpstan-9.0.3.neon @@ -0,0 +1,6 @@ +includes: + - %currentWorkingDirectory%/tests/php/phpstan/phpstan.neon + +parameters: + ignoreErrors: + - '#Call to an undefined method AdminController\|FrontController\|PrestaShop\\PrestaShop\\Core\\Context\\LegacyControllerContext::getProduct\(\).#' \ No newline at end of file diff --git a/tests/php/phpstan/phpstan-9.1.5.neon b/tests/php/phpstan/phpstan-9.1.5.neon new file mode 100644 index 00000000..dcce4f57 --- /dev/null +++ b/tests/php/phpstan/phpstan-9.1.5.neon @@ -0,0 +1,6 @@ +includes: + - %currentWorkingDirectory%/tests/php/phpstan/phpstan.neon + +parameters: + ignoreErrors: + - '#Call to an undefined method AdminController\|FrontController\|PrestaShop\\PrestaShop\\Core\\Context\\LegacyControllerContext::getProduct\(\).#' \ No newline at end of file diff --git a/tests/php/phpstan/phpstan-9.2.x.neon b/tests/php/phpstan/phpstan-9.2.x.neon new file mode 100644 index 00000000..dcce4f57 --- /dev/null +++ b/tests/php/phpstan/phpstan-9.2.x.neon @@ -0,0 +1,6 @@ +includes: + - %currentWorkingDirectory%/tests/php/phpstan/phpstan.neon + +parameters: + ignoreErrors: + - '#Call to an undefined method AdminController\|FrontController\|PrestaShop\\PrestaShop\\Core\\Context\\LegacyControllerContext::getProduct\(\).#' \ No newline at end of file diff --git a/tests/php/phpstan/phpstan-develop.neon b/tests/php/phpstan/phpstan-develop.neon new file mode 100644 index 00000000..dcce4f57 --- /dev/null +++ b/tests/php/phpstan/phpstan-develop.neon @@ -0,0 +1,6 @@ +includes: + - %currentWorkingDirectory%/tests/php/phpstan/phpstan.neon + +parameters: + ignoreErrors: + - '#Call to an undefined method AdminController\|FrontController\|PrestaShop\\PrestaShop\\Core\\Context\\LegacyControllerContext::getProduct\(\).#' \ No newline at end of file diff --git a/tests/php/phpstan/phpstan.neon b/tests/php/phpstan/phpstan.neon new file mode 100644 index 00000000..018ab98c --- /dev/null +++ b/tests/php/phpstan/phpstan.neon @@ -0,0 +1,2 @@ + # Base PHPStan configuration for the module + # This file is included by version-specific configs \ No newline at end of file diff --git a/tests/php/phpstan/prepend-constants.php b/tests/php/phpstan/prepend-constants.php new file mode 100644 index 00000000..733689a6 --- /dev/null +++ b/tests/php/phpstan/prepend-constants.php @@ -0,0 +1,11 @@ + but class [a-zA-Z0-9\\_]+ is not generic.#' - path: ../../src/Repository diff --git a/tests/phpstan/phpstan-1.7.8.neon b/tests/phpstan/phpstan-1.7.8.neon deleted file mode 100644 index e6ad951a..00000000 --- a/tests/phpstan/phpstan-1.7.8.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.#' - - - message: '#PHPDoc tag @extends contains generic type [a-zA-Z0-9\\_]+<[a-zA-Z0-9\\_]+> but class [a-zA-Z0-9\\_]+ is not generic.#' - path: ../../src/Repository diff --git a/tests/phpstan/phpstan-8.0.neon b/tests/phpstan/phpstan-8.0.neon deleted file mode 100644 index 23b71e8d..00000000 --- a/tests/phpstan/phpstan-8.0.neon +++ /dev/null @@ -1,5 +0,0 @@ -includes: - - %currentWorkingDirectory%/tests/phpstan/phpstan.neon - -parameters: - ignoreErrors: diff --git a/tests/phpstan/phpstan-latest.neon b/tests/phpstan/phpstan-latest.neon deleted file mode 100644 index c604f7c7..00000000 --- a/tests/phpstan/phpstan-latest.neon +++ /dev/null @@ -1,2 +0,0 @@ -includes: - - %currentWorkingDirectory%/tests/phpstan/phpstan.neon diff --git a/tests/phpstan/phpstan.neon b/tests/phpstan/phpstan.neon deleted file mode 100644 index 0961334b..00000000 --- a/tests/phpstan/phpstan.neon +++ /dev/null @@ -1,16 +0,0 @@ -includes: - - %currentWorkingDirectory%/vendor/prestashop/php-dev-tools/phpstan/ps-module-extension.neon -parameters: - paths: - # From PHPStan 0.12, paths to check are relative to the neon file - - ../../productcomments.php - - ../../ProductComment.php - - ../../ProductCommentCriterion.php - - ../../controllers/ - - ../../src/ - - ../../upgrade/ - level: 5 - ignoreErrors: - - - message: '#Cannot call method [a-zA-Z0-9\\_]+\(\) on object\|false.#' - path: ../../productcomments.php diff --git a/upgrade/install-9.0.0.php b/upgrade/install-9.0.0.php new file mode 100644 index 00000000..dc970fa3 --- /dev/null +++ b/upgrade/install-9.0.0.php @@ -0,0 +1,33 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + */ +if (!defined('_PS_VERSION_')) { + exit; +} + +function upgrade_module_9_0_0($object) +{ + return $object->registerHook('actionFrontControllerSetVariables'); +} diff --git a/views/templates/admin/_configure/helpers/form/form.tpl b/views/templates/admin/_configure/helpers/form/form.tpl index 4b7ebdec..6e1e1375 100644 --- a/views/templates/admin/_configure/helpers/form/form.tpl +++ b/views/templates/admin/_configure/helpers/form/form.tpl @@ -44,27 +44,7 @@ {/foreach} - {elseif $input.type == 'switch' && $smarty.const._PS_VERSION_|@addcslashes:'\'' < '1.6'} - {foreach $input.values as $value} - - - {if isset($input.br) && $input.br}
{/if} - {if isset($value.p) && $value.p}

{$value.p}

{/if} - {/foreach} {else} {$smarty.block.parent} - {/if} - + {/if} {/block}