gamify-1.1.x-dev/src/UserPointsLogService.php
src/UserPointsLogService.php
<?php
namespace Drupal\gamify;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\gamify\Traits\GamifyEntityLogTrait;
use Drupal\userpoints\Service\UserPointsService;
use Drupal\gamify\TypedData\Options\AbstractUserOptions as UserOpts;
/**
* Helper class for RulesConditions and RulesActions by query userpoints log.
*
* With this class we can check log entries of prior userpoint assignments, and
* form RulesConditions based on it.
*
* @ingroup gamify
*/
class UserPointsLogService {
use GamifyEntityLogTrait;
const DEFAULT_POINT_TYPE = 'advancement';
/**
* The current active database's master connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected Connection $database;
/**
* The current active database's master connection.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected EntityTypeManagerInterface $entityTypeManager;
/**
* Drupal\gamify\AbstractUserService definition.
*
* @var \Drupal\gamify\AbstractUserService
*/
protected AbstractUserService $abstractUserService;
/**
* Drupal\userpoints\Service\UserPointsService definition.
*
* @var \Drupal\userpoints\Service\UserPointsService
*/
protected UserPointsService $userpointsService;
/**
* Drupal\Component\Datetime\TimeInterface definition.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected TimeInterface $time;
/**
* Constructs a new EvaluatingService object.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager, Connection $database, AbstractUserService $abstract_user_service, UserPointsService $userpoints_service, TimeInterface $time) {
$this->entityTypeManager = $entity_type_manager;
$this->database = $database;
$this->abstractUserService = $abstract_user_service;
$this->userpointsService = $userpoints_service;
$this->time = $time;
}
/**
* Add to or subtract points from an entity.
*
* @param float|int $quantity
* The number of points (can be negative to subtract).
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to check.
* @param string $log
* Revision log messsage for the operation.
*/
public function addPoints(float|int $quantity, EntityInterface $entity, string $log = ''): void {
$this->userpointsService->addPoints($quantity, self::DEFAULT_POINT_TYPE, $entity, $log);
}
/**
* Executes the Plugin.
*
* @param string $type
* Original value of an element which is being updated.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that should get an alias.
* @param string $addressed_user
* The abstract user who will receive user points.
* @param int $user_points
* Number of points to assign.
*/
public function assign(string $type, EntityInterface $entity, string $addressed_user, int $user_points): void {
$log_msg = "{$this->buildLogHash($type, $entity)} {$entity->label()}";
foreach ($this->abstractUserService->getUsers($addressed_user, $entity, TRUE) as $user) {
$this->addPoints($user_points, $user, $log_msg);
}
}
/**
* Returns number of repeats, user received points for executing same action.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity to check for the provided field.
* @param string $type
* Action type identifier (create, ...) used in the following action.
*
* @return ?int
* Returns number of repeats current number executes the operation. Returns NULL no user active.
*
* @throws \Exception
*/
public function userActionRepeat(FieldableEntityInterface $entity, string $type, string $abstract_user = UserOpts::CURRENT_USER): ?int {
$log_hash = $this->getLogHashSearchStr($type, $entity);
foreach ($this->abstractUserService->getUsers($abstract_user, $entity, TRUE) as $user) {
$query = $this->queryLog($log_hash, $user);
return count($query);
}
return NULL;
}
/**
* Gets time elapsed since the user previously performed same action.
*
* @param string $log_hash
* Action type identifier (create, ...) used in the following action.
* \Drupal\gamify\TypedData\Options\EntityOperationOptions
*
* @return mixed
*/
public function getLastActionExecLog(string $log_hash): mixed {
$users = $this->abstractUserService->getUsers(UserOpts::CURRENT_USER, NULL, TRUE);
if ($user = reset($users)) {
$results = $this->queryLog($log_hash, $user);
$time = 0;
$current = NULL;
foreach ($results as $result) {
if ($result->revision_timestamp >= $time) {
$current = $result;
$time = $result->revision_timestamp;
}
}
return $current;
}
return NULL;
}
/**
* Gets time elapsed since the user previously performed same action.
*
* @param null|object $log_entry
* A raw log entry as it returned from db query.
*
* @return int|null
* Returns time since user previously performed same action.
*/
public function getActionExecLogTime(?object $log_entry = NULL): ?int {
return $log_entry?->revision_timestamp;
}
/**
* Gets time elapsed between given time and time the logged action was performed.
*
* @param null|object $log_entry
* A raw log entry as it returned from db query.
* @param $time int|null
* Timestamp to build the relation. If time is not given, request time will be used.
*
* @return int|null
* Returns time since user previously performed same action.
*/
public function getActionExecTimeDiff(?object $log_entry = NULL, int $time = NULL): ?int {
if ($prev_log_time = $this->getActionExecLogTime($log_entry)) {
$request_time = $time ?? $this->time->getRequestTime();
return $request_time - $prev_log_time;
}
return NULL;
}
/**
* Method reverts all userpoints given on a log id (or fragment of it).
*
* @param string $type
* Type of operation.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The target entity.
* @param string $abstract_user
* The abstract user or user group.
* @param array $revert_operations
* The abstract user or user group.
*/
public function revertUserPoints(string $type, EntityInterface $entity, string $abstract_user, array $revert_operations): void {
try {
$reverts = [];
$users = $this->abstractUserService->getUsers($abstract_user, $entity, TRUE);
$single_user = (count($users) && ($abstract_user !== UserOpts::INVOLVED_USERS)) ? reset($users) : NULL;
foreach ($revert_operations as $operation) {
$log_hash = $this->getLogHashSearchStr($operation, $entity);
// Filter by user. Involved don't need a filter. Just taking all.
$log_entries = $this->queryLog($log_hash, $single_user);
// Search the query log and spread revert points to each user.
foreach ($log_entries as $upr) {
if ($upr->entity_type_id !== 'user') {
continue;
}
if (!isset($reverts[$upr->entity_id])) {
$reverts[$upr->entity_id] = 0;
}
$reverts[$upr->entity_id] += (int) $upr->points ?? 0;
}
}
// Revert user points.
foreach ($users as $user) {
$log_id = $this->buildLogHash($type, $entity);
if ($points = $reverts[$user->id()] ?? NULL) {
$this->userpointsService->addPoints(-$points, self::DEFAULT_POINT_TYPE, $user, "$log_id Reverting points from prior actions.");
}
}
}
catch (\Exception $e) {
$msg = "Try to revert user points for {$entity->getEntityTypeId()}:{$entity->id()}. ";
\Drupal::logger('gamify')->error($msg. $e->getMessage());
}
}
}
