Preview:
<?php

/**
 * @file
 * Contains belmil_product_rating.module.
 */

use Drupal\block\Entity\Block;
use Drupal\commerce_product\Entity\ProductInterface;
use Drupal\commerce_product\Entity\ProductType;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\Routing\RouteMatchInterface;
use \Drupal\user\EntityOwnerInterface;
/**
 * Implements hook_help().
 */
function belmil_product_rating_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    // Main module help for the belmil_product_rating module.
    case 'help.page.belmil_product_rating':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('This module provides custom rating functionalities for products.') . '</p>';
      return $output;

    default:
  }

  return '';
}

/**
 * Implements hook_theme().
 */
function belmil_product_rating_theme() {
  return [
    'belmil_product_rating' => [
      'render element' => 'children',
    ],
    'belmil_product_rating_statistics_widget' => [
      'render element' => 'children',
    ]
  ];
}

/**
 * Implements hook_ENTITY_TYPE_insert().
 */
function belmil_product_rating_comment_insert(EntityInterface $entity) {
  $userRoles = \Drupal::currentUser()->getRoles();
  if ($entity instanceof EntityOwnerInterface) {
      $ownerEmail = $entity->getOwner()->getEmail();
    if (!in_array('administrator', $userRoles) && $ownerEmail != 'trustami@studiopresent.com') {
      $link = '/comment/' . $entity->id() . '/edit';
      $body = t('There is new comment on your site') . '<br> <a target="_blank" href="' . $link . '">' . t('Show') . ' </a>';

      // Send mail.
      /** @var \Drupal\Core\Mail\MailManagerInterface $mailManager */
      $mailManager = \Drupal::service('plugin.manager.mail');

      $module = $key = 'belmil_product_rating';
      // Get email from config and remove whitespaces.
      $to = \Drupal::config('system.site')->get('mail');
      $params['subject'] = t('New comment on website');
      $params['body'] = $body;
      $params['format'] = 'text/html';
      $langcode = \Drupal::currentUser()->getPreferredLangcode();

      $result = $mailManager->mail($module, $key, $to, $langcode, $params, NULL, TRUE);

      // Log result.
      if ($result['result'] != TRUE) {
        $message = t('There was a problem sending your email notification to @email.', ['@email' => $to]);
        \Drupal::logger('mail')->error($message);
      }
      else {
        $message = t('An email notification for new comment has been sent to @email.', ['@email' => $to]);
        \Drupal::logger('mail')->notice($message);
      }
    }
  }
}

/**
 * Implements hook_mail().
 */
function belmil_product_rating_mail($key, &$message, $params) {
  switch ($key) {
    case 'belmil_product_rating':
      $message['subject'] = $params['subject'];
      $message['body'][] = Markup::create($params['body']);
      $message['headers']['Content-Type'] = $params['format'];
      break;
  }
}

/**
 * Implements hook_form_alter().
 */
function belmil_product_rating_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Open details for admin comment approval.
  if ($form_id == "comment_product_comment_form") {
    $form['#validate'][] = 'belmil_product_rating_settings_validate';
    if (isset($form['author'])) {
      $form['author']['#open'] = TRUE;
    }
  }
}

/**
 * Implements hook_form_submit().
 */
function belmil_product_rating_settings_validate(&$form, FormStateInterface $form_state) {
  $uid = \Drupal::currentUser()->id();
  if (empty($form_state->getValue('uid'))) {
    $form['author']['uid']['#value'] = $uid;
    $form['author']['name']['#value'] = 'Anonymous';
  }
}

/**
 * Implements hook_entity_extra_field_info().
 */
function belmil_product_rating_entity_extra_field_info() {
  $stars_field = [];

  foreach (ProductType::loadMultiple() as $bundle) {
    $stars_field['commerce_product'][$bundle->id()]['display']['trustamiproductreview'] = [
      'label' => t('Trustami product review'),
      'description' => t('A pseudo field to display the Trustami product review block'),
      'weight' => 0,
      'visible' => TRUE,
    ];

    // Define stars field for displaying rating by stars and link to comment section.
    $stars_field['commerce_product'][$bundle->id()]['display']['stars_field'] = [
      'label' => t('Product stars'),
      'description' => t('This is pseudo field for showing stars rating'),
      'visible' => FALSE,
    ];

    // Define statistic widget for rating.
    $stars_field['commerce_product'][$bundle->id()]['display']['stars_widget'] = [
      'label' => t('Stars widget'),
      'description' => t('Stars widget with statistic by marks'),
      'visible' => FALSE,
    ];
  }

  return $stars_field;
}

/**
 * Implements hook_ENTITY_TYPE_view().
 *
 * Add stars field & widget to product view.
 */
function belmil_product_rating_commerce_product_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
  if ($display->getComponent('trustamiproductreview')) {
    $block = Block::load('trustamiproductreview');
    if ($block) {
      $build['trustamiproductreview'] = $block->getPlugin()->build();
    }
  }

  if ($display->getComponent('stars_field') ||
    $display->getComponent('stars_widget')
  ) {
    /** @var \Drupal\belmil_product_rating\BelmilProductRatingManager $product_rating_manager */
    $product_rating_manager = \Drupal::service('belmil_product_rating.belmil_product_rating_manager');

    $productRating = 0;
    $numOfComments = 0;
    $productsWithSameTagWithComments = [];
    $ratings = [];

    // Get product with same tag which contain comments for default product type.
    if ($entity->bundle() === "default") {
      $tagId = (int) $entity->get('field_tag')->target_id;
      $productsWithSameTagWithComments = $product_rating_manager
        ->getProductsWithSameTagWithComments($tagId);
    }
    // Get comments of single product for universal product type.
    elseif ($entity->bundle() === "universal") {
      $productsWithSameTagWithComments = [$entity->id()];
    }

    // Call get rating function.
    $product_rating_manager->getRating($productsWithSameTagWithComments, $productRating, $numOfComments, $ratings);
    // Prepare stars data - num of stars is fixed to 5.
    $starsData = $product_rating_manager->prepareStarsData(5, $productRating);
  }

  // Render product stars.
  if ($display->getComponent('stars_field')) {
    $build['stars_field'] = [
      '#markup' => \Drupal::theme()->render('belmil_product_rating', [
        'rating' => $productRating,
        'stars' => 5,
        'vote_type' => "fivestar-widget-static-vote",
        'num_of_comments' => $numOfComments,
        'widget' => ['name' => 'fivestar-basic'],
        'stars_data' => $starsData,
        'product_id' => $entity->id(),
      ]),
    ];
  }

  // Render stars statistics widget.
  if ($display->getComponent('stars_widget')) {
    // Render only if there is any comment.
    if (!empty($ratings)) {
      $totalRating = ($productRating / 20 );
      $statistics = $product_rating_manager->getStatisticsRatingData($ratings);
      // Get percentage of each mark.
      foreach ($statistics as $key => $rating) {
        $statistics[$key] = $rating / count($ratings) * 100;
      }

      $build['stars_widget'] = [
        '#markup' => \Drupal::theme()->render('belmil_product_rating_statistics_widget', [
          'ratings' => $statistics,
          'total_rating' => $totalRating,
          'rating' => $productRating,
          'stars' => 5,
          'vote_type' => "fivestar-widget-static-vote",
          'num_of_comments' => $numOfComments,
          'widget' => ['name' => 'fivestar-basic'],
          'stars_data' => $starsData,
          'product_id' => $entity->id(),
        ]),
      ];
    }
  }
}

/**
 * Implements hook_page_attachments().
 */
function belmil_product_rating_page_attachments(array &$page) {
  // Get request and entity.
  $request = \Drupal::request();
  $path = $request->attributes->get('_route_object')->getPath();
  /** @var \Drupal\commerce_product\Entity\ProductInterface $entity */
  $entity = $request->attributes->get('_entity');

  // Check if we are on product view page.
  if (!empty($entity) && $path == "/product/{commerce_product}") {
    // Attach google schema style script.
    /** @var \Drupal\belmil_product_rating\BelmilProductRatingManager $product_rating_manager */
    $product_rating_manager = \Drupal::service('belmil_product_rating.belmil_product_rating_manager');

    $product_rating_manager->attachGoogleSchemaStyleScript($entity, $page);

    // Attach Trustami Comment Widget.
    $referrerMeta = [
      '#tag' => 'meta',
      '#attributes' => [
        'name' => 'referrer',
        'content' => 'strict-origin-when-cross-origin',
      ],
    ];
    $page['#attached']['html_head'][] = [$referrerMeta, 'referrerMeta'];
    // attachTrustamiCommentsWidgetScript($entity, $page);
  }
}

/**
 * Trustami product widget script.
 *
 * @param \Drupal\commerce_product\Entity\ProductInterface $entity
 *   The product entity.
 * @param array $page
 *   The page array.
 */
function attachTrustamiCommentsWidgetScript(ProductInterface $entity, array &$page) {
  $variations = $entity->getVariations();
  $variation = reset($variations);

  if ($variation) {
    $trustamiProductWidgetScript = [
      '#tag' => 'script',
      '#attributes' => [
        'type' => 'text/javascript',
        'id' => 'trustami-product-widget',
        'src' => 'https://cdn.trustami.com/widgetapi/productWidget/trustami-product-widget.js',
        'data-uid' => '601e2fffcc96c5152b8b46de',
        'data-gtin' => $variation->getSku(),
        'data-asin' => $variation->get('field_asin')->value,
        'data-modes' => "[4]",
      ],
      '#value' => '',
    ];

    // Add script to head.
    $page['#attached']['html_head'][] = [
      $trustamiProductWidgetScript,
      'trustamiProductWidgetScript'
    ];
  }
}
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter