gamify-1.1.x-dev/src/Traits/GamifyEntityLogTrait.php
src/Traits/GamifyEntityLogTrait.php
<?php
namespace Drupal\gamify\Traits;
use Drupal\Core\Entity\EntityInterface;
use Drupal\user\UserInterface;
/**
* Trait for using Gamify entity log.
*
* @ingroup gamify
*/
trait GamifyEntityLogTrait {
/**
* Query user points revisions for given user and log_id.
*
* @param string $log_id
* The queried log_id.
* @param \Drupal\user\UserInterface|null $user
* The user receiving user points on log_id (Must not be the current user).
* @param bool $normalize
* Normalize entries to be used with log inquiries.
*
* @return array
* Return assoc array of user points revisions keyed by version id.
*/
protected function queryLog(string $log_id, UserInterface $user = NULL, bool $normalize = FALSE): array {
$query = $this->database
->select('userpoints_revision', 't')
->fields('t')
->condition('revision_log', "%{$this->database->escapeLike($log_id)}%", 'LIKE')
->orderBy('vid');
if ($user) {
$query->condition('entity_type_id', 'user')
->condition('entity_id', $user->id());
}
$results = $query
->execute()
->fetchAllAssoc('vid');
if ($normalize) {
$results = $this->normalizeUpEntry($results);
}
return $results;
}
/**
* Transforms query result in a normalized array, equal to watchdog results.
*
* @param array $results
* Results as StdClass with db-log keys.
*
* @return array
* Normalized array of arrays with default keys.
*/
private function normalizeUpEntry(array $results): array {
foreach ($results as $key => $result) {
$results[$key] = [
'src' => 'userpoints_log',
'log_id' => $result->vid,
'uid' => ($result->entity_type_id == 'user') ? $result->entity_id : 0,
'log_hash' => $result->revision_log,
'timestamp' => (int) $result->revision_timestamp,
'quantity' => (int) $result->quantity,
'points' => (int) $result->points,
];
}
return $results;
}
/**
* Build operation hash as content for the log entry.
*
* @param string $type
* Operation name (create, update or delete).
* @param \Drupal\Core\Entity\EntityInterface $entity
* The root entity on that the operation is done.
* @param bool $deep_context
* If TRUE method returns complete chain of dependent entity context,
* if FALSE returns only entry for this entity (e.g. for db searching).
*
* @return string
* Returns the action id with entity suffix.
*/
public function buildLogHash(string $type, EntityInterface $entity, bool $deep_context = TRUE): string {
$operation = [$type];
$context = $this->getLogHashContext($entity, $deep_context);
$frags = array_merge($operation, $context);
$str = implode('][', $frags);
return "[$str]";
}
/**
* Receive context identifier from invoke hook.
*
* For entities in a nested entity structure you may want to include more
* context variables. E.g. if it is a vote or paragraph entity, you want
* to know the parent entity [paragraph:12][node:4].
* This is to be more flexible in building rules conditions and actions.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity context is requested for.
* @param bool $deep_context
* If TRUE method returns complete chain of dependent entity context,
* if FALSE it returns just context for this entity.
*
* @return array
* The optional context of an entity e.g. parent entity.
*
* @see hook_gamify_log_hash_ENTITY_TYPE_context()
*/
protected function getLogHashContext(EntityInterface $entity, bool $deep_context): array {
$entity_type = $entity->getEntityTypeId();
$params = [
'entity' => $entity,
'deep_context' => $deep_context,
];
return \Drupal::moduleHandler()
->invokeAll("gamify_log_hash_{$entity_type}_context", $params);
/* @todo It is an open problem, to call the hook with invokeAll, because
* there should be only one hook for each entity type. If there are
* multiple hooks the system becomes confused. But we don't know what
* entity comes in and where the hook for this type lives.
*/
}
/**
* Extract operation from event name.
*
* @param string $event_name
* The full event name, e.g. 'rules_entity_delete:node'.
*
* @return string|null
* The operation type <insert|update|delete>
*/
public function getOperationFromEventName(string $event_name): ?string {
if (str_starts_with($event_name, 'gamify.')) {
return substr($event_name, 7);
}
return NULL;
}
/**
* Returns valid search string for operation and entity slug.
*
* Context properties may change on updates, so we have to reduce search
* strings to Operation an entity slug.
*
* Examples:
* - $log_hash = [update][node:42 bundle='rule'][term:4 bundle='section']
* returns '[update][node:42 '
* - $log_hash = [update][node:42][term:4 bundle='section']
* returns '[update][node:42]'
*
* @param string $type
* Operation name (create, update or delete).
* @param \Drupal\Core\Entity\EntityInterface $entity
* The root entity on that the operation is done.
*
* @return string
* The pure entity operation.
*
* @throws \Exception
*/
public function getLogHashSearchStr(string $type, EntityInterface $entity): string {
$log_hash = $this->buildLogHash($type, $entity, FALSE);
// Return context string to array.
$frags = explode('][', substr($log_hash, 1, -1));
$return_frags = [];
if (count($frags) >= 2) {
// Remove context properties.
$return_frags[] = $frags[0];
$entity_frags = explode(' ', $frags[1]);
// Trailing char required to not find 'node:42' when searching 'node:4'.
$trailing_char = ($entity_frags >= 2) ? ' ' : ']';
$return_frags[] = $entity_frags[0] . $trailing_char;
}
if (count($return_frags) < 2) {
$entity_type = $entity->getEntityTypeId();
throw new \Exception("Failed to build log hash '$type' for entity type '$entity_type'.");
}
return "[" . implode('][', $return_frags);
}
}
