entity_mesh-1.1.1/src/Repository.php
src/Repository.php
<?php
namespace Drupal\entity_mesh;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\StreamWrapper\AssetsStream;
use Drupal\file\FileInterface;
use Drupal\media\MediaInterface;
use Drupal\redirect\Entity\Redirect;
use Drupal\redirect\Exception\RedirectLoopException;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Service to perform database operations.
*/
class Repository implements RepositoryInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* The logger service.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* Url language prefixes.
*
* @var array
*/
protected array $prefixes;
/**
* Config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Cached mesh account.
*
* @var \Drupal\entity_mesh\DummyAccount|null
*/
protected $meshAccount;
/**
* Constructs a new Repository object.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
* @param \Psr\Log\LoggerInterface $logger
* The logger service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* Config factory.
*/
public function __construct(
Connection $database,
LoggerInterface $logger,
RequestStack $request_stack,
EntityTypeManagerInterface $entity_type_manager,
EntityFieldManagerInterface $entity_field_manager,
ConfigFactoryInterface $config_factory,
) {
$this->database = $database;
$this->logger = $logger;
$this->requestStack = $request_stack;
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->configFactory = $config_factory;
$this->prefixes = $config_factory->get('language.negotiation')->get('url.prefixes');
}
/**
* {@inheritdoc}
*/
public function getDatabaseService() {
return $this->database;
}
/**
* {@inheritdoc}
*/
public function getLogger() {
return $this->logger;
}
/**
* {@inheritdoc}
*/
public function insertSource(SourceInterface $source): bool {
$rows = [];
// We ensure that the Source object has the Hash ID.
if ($source->getHashId() === NULL) {
$source->setHashId();
}
// We ensure that the Source object has the title.
$this->setTitleSourceTarget($source);
$targets = $source->getTargets();
if (empty($targets)) {
return TRUE;
}
$source_properties = $source->toArray();
foreach ($targets as $target) {
$row = [];
// We ensure that the Target object has the Hash ID.
if ($target->getHashId() === NULL) {
$target->setHashId();
}
// We ensure that the Target object has the title.
$this->setTitleSourceTarget($target);
$row = array_merge($source_properties, $target->toArray());
$rows[] = $row;
}
$transaction = $this->database->startTransaction();
try {
$query = $this->database->insert(self::ENTITY_MESH_TABLE)
->fields(array_keys(reset($rows)));
foreach ($rows as $row) {
$query->values($row);
}
$query->execute();
}
catch (\Exception $e) {
$transaction->rollback();
$this->logger->error($e->getMessage());
return FALSE;
}
return TRUE;
}
/**
* Build label for objects.
*
* @param object $object
* Source or Target object.
*/
protected function setTitleSourceTarget($object) {
$title_segment_1 = '';
if ($object instanceof SourceInterface) {
if (!empty($object->getTitle())) {
return;
}
$entity_type = $object->getSourceEntityType();
$id = $object->getSourceEntityId();
$langcode = $object->getSourceEntityLangcode();
$title_segment_1 = $this->getLabel($entity_type, $id, $langcode) ?? '';
}
elseif ($object instanceof TargetInterface) {
if (!empty($object->getTitle())) {
return;
}
if ($object->getLinkType() === 'external') {
$entity_type = 'external';
$id = NULL;
}
else {
$entity_type = $object->getEntityType();
$id = $object->getEntityId();
$langcode = $object->getEntityLangcode();
$title_segment_1 = $this->getLabel($entity_type, $id, $langcode);
}
if (empty($title_segment_1)) {
$title_segment_1 = $object->getHref() ?? '';
}
}
else {
return;
}
$label = $title_segment_1;
$label .= ' (' . $entity_type;
$label .= empty($id) ? ')' : ' - ' . $id . ')';
if ($label === ' ()') {
return;
}
$label = mb_substr($label, 0, 255);
$object->setTitle($label);
}
/**
* Get label from an entity.
*
* @param string|null $entity_type
* The entity type.
* @param string|null $entity_id
* The entity id.
* @param string|null $langcode
* The langcode.
*
* @return string|null
* The string of the link or entity id.
*/
protected function getLabel(?string $entity_type, ?string $entity_id, ?string $langcode): ?string {
if (empty($entity_id) || empty($entity_type)) {
return NULL;
}
try {
$storage = $this->entityTypeManager->getStorage($entity_type);
}
catch (PluginNotFoundException $e) {
return NULL;
}
/** @var \Drupal\Core\Entity\EntityInterface $entity */
$entity = $storage->load($entity_id);
if (!$entity instanceof EntityInterface) {
return NULL;
}
return !empty($langcode) && $entity instanceof TranslatableInterface && $entity->isTranslatable() && $entity->hasTranslation($langcode) ? $entity->getTranslation($langcode)->label() : $entity->label();
}
/**
* {@inheritdoc}
*/
public function deleteSource(SourceInterface $source): bool {
try {
$this->database
->delete(self::ENTITY_MESH_TABLE)
->condition('type', $source->getType())
->condition('source_entity_type', $source->getSourceEntityType())
->condition('source_entity_id', $source->getSourceEntityId())
->execute();
}
catch (\Exception $e) {
$this->logger->error($e->getMessage());
return FALSE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function deleteSourceByType(string $type, array $conditions): bool {
try {
$query = $this->database->delete(self::ENTITY_MESH_TABLE);
$query->condition('type', $type);
foreach ($conditions as $field => $value) {
$query->condition($field, $value);
}
$query->execute();
}
catch (\Exception $e) {
$this->logger->error($e->getMessage());
return FALSE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function deleteAllSourceTarget(): bool {
try {
$this->database
->delete(self::ENTITY_MESH_TABLE)
->execute();
}
catch (\Exception $e) {
$this->logger->error($e->getMessage());
return FALSE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function saveSource(SourceInterface $source): bool {
if (!$this->insertSource($source)) {
return FALSE;
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function instanceEmptySource(): SourceInterface {
return Source::create();
}
/**
* {@inheritdoc}
*/
public function instanceEmptyTarget(): TargetInterface {
$self_domain_internal = $this->configFactory->get('entity_mesh.settings')->get('self_domain_internal');
return Target::create($this->requestStack, $self_domain_internal);
}
/**
* {@inheritdoc}
*/
public function getPathFromFileUrl(string $path): ?string {
$public_base_path = AssetsStream::basePath() . '/';
$path = trim($path, '/');
if (strpos($path, $public_base_path) !== 0) {
return NULL;
}
return str_replace($public_base_path, 'public://', $path);
}
/**
* {@inheritdoc}
*/
public function getFileFromUrl($path): ?FileInterface {
$file = $this->entityTypeManager->getStorage('file')->loadByProperties(['uri' => $path]);
if (empty($file)) {
return NULL;
}
return reset($file);
}
/**
* {@inheritdoc}
*/
public function getMediaFileByEntityFile(FileInterface $file): ?MediaInterface {
$media_types_with_tile_fields = $this->getMediaFieldTypeWithFileFields();
if (empty($media_types_with_tile_fields)) {
return NULL;
}
$media_query = $this->entityTypeManager->getStorage('media')->getQuery();
$media_query->accessCheck(FALSE);
$or_condition = $media_query->orConditionGroup();
foreach ($media_types_with_tile_fields as $media_type_file_field) {
$or_condition->condition($media_type_file_field['field_name'] . '.target_id', $file->id());
}
$media_query->condition($or_condition);
$results = $media_query->execute();
if (empty($results)) {
return NULL;
}
return $this->entityTypeManager->getStorage('media')->load(reset($results));
}
/**
* Get media types with file fields.
*
* @return array
* Array with the media type and the field name.
*/
protected function getMediaFieldTypeWithFileFields() {
$medias_type_reference_to_file = [];
$media_types = $this->entityTypeManager->getStorage('media_type')->loadMultiple();
foreach ($media_types as $media_type) {
$fields = $this->entityFieldManager->getFieldDefinitions('media', (string) $media_type->id());
foreach ($fields as $field) {
if (in_array($field->getType(), ['file', 'image'])) {
$medias_type_reference_to_file[] = [
'field_name' => $field->getName(),
'field_type' => $media_type->id(),
];
}
}
}
return $medias_type_reference_to_file;
}
/**
* {@inheritdoc}
*/
public function ifRedirectionForPath($path, $langcode, $count = 0) {
$service_redirect = 'redirect.repository';
// @phpstan-ignore-next-line
$container = \Drupal::getContainer();
if (!$container->has($service_redirect)) {
return NULL;
}
$path = trim($path, '/');
$redirect_repository = $container->get($service_redirect);
try {
if (!empty($langcode)) {
$redirect_object = $redirect_repository->findMatchingRedirect($path, [], $langcode);
}
else {
$redirect_object = $redirect_repository->findMatchingRedirect($path, []);
}
}
catch (RedirectLoopException $e) {
$this->logger->error($e->getMessage());
return NULL;
}
if (!($redirect_object instanceof Redirect)) {
return NULL;
}
$redirect = $redirect_object->getRedirect();
$uri = $redirect['uri'] ?? NULL;
if ($uri === NULL) {
return NULL;
}
// Check if te redirection has a redirection and avoid infinite loop.
if ($count > 5) {
return $uri;
}
$filtered_uri = $this->handlePrefixFromRedirection($uri, $langcode);
$new_redirection = $this->ifRedirectionForPath($filtered_uri, $langcode, $count++);
return $new_redirection ?? $uri;
}
/**
* Handle prefix from redirection.
*
* @param string $uri
* The uri.
* @param string $langcode
* The langcode.
*
* @return string
* The uri.
*/
public function handlePrefixFromRedirection($uri, $langcode = '') {
$prefixes = ['internal:', 'entity:', $langcode];
foreach ($prefixes as $prefix) {
$uri = (string) preg_replace('/^' . $prefix . '/', '', $uri);
$uri = trim($uri, '/');
}
return $uri;
}
/**
* {@inheritdoc}
*/
public function getPathWithoutLangPrefix($path) {
$prefix = $this->getLangPrefixFromPath($path);
if (empty($prefix)) {
return $path;
}
// We ensure 2 structures: /langcode/path or /langcode.
$processed_path = ltrim($path, '/');
if (str_starts_with($processed_path, $prefix . '/')) {
$path = substr($processed_path, strlen($prefix));
}
elseif ($processed_path === $prefix) {
$path = '/';
}
return $path;
}
/**
* {@inheritdoc}
*/
public function getLangcodeFromPath($path): ?string {
$result = NULL;
if ($langcode_and_prefix = $this->getLangcodeAndPrefixFromPath($path)) {
$result = $langcode_and_prefix['langcode'] ?? NULL;
}
return $result;
}
/**
* {@inheritdoc}
*/
public function getLangPrefixFromPath($path): ?string {
$result = NULL;
if ($langcode_and_prefix = $this->getLangcodeAndPrefixFromPath($path)) {
$result = $langcode_and_prefix['prefix'] ?? NULL;
}
return $result;
}
/**
* Get the langcode and prefix from the path.
*
* @param string $path
* The path.
*
* @return array|null
* The langcode and prefix or NULL.
*/
protected function getLangcodeAndPrefixFromPath($path): ?array {
// If the prefixes ares empty, it is not a multilingual site, so
// there are not langcode to return.
if (empty($this->prefixes)) {
return NULL;
}
$path = '/' . ltrim($path, '/');
foreach ($this->prefixes as $langcode => $prefix) {
if ($prefix && str_starts_with($path, '/' . $prefix . '/')) {
return [
'langcode' => $langcode,
'prefix' => $prefix,
];
}
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function clearMeshAccountCache() {
$this->meshAccount = NULL;
}
/**
* {@inheritdoc}
*/
public function getMeshAccount(): DummyAccount {
if (!$this->meshAccount) {
$this->meshAccount = DummyAccount::create($this->entityTypeManager);
// Get the configured analyzer account from settings.
$config = $this->configFactory->get('entity_mesh.settings');
$analyzer_account = $config->get('analyzer_account') ?: [
'type' => 'anonymous',
'roles' => NULL,
'user_id' => NULL,
];
switch ($analyzer_account['type']) {
case 'anonymous':
$this->meshAccount->setAsAnonymous();
break;
case 'authenticated':
// Start with authenticated role.
$roles = ['authenticated'];
// Add any additional configured roles.
if (!empty($analyzer_account['roles'])) {
$roles = array_merge($roles, $analyzer_account['roles']);
}
$this->meshAccount->setRoles($roles);
break;
case 'user':
if (!empty($analyzer_account['user_id'])) {
try {
$user = $this->entityTypeManager->getStorage('user')->load($analyzer_account['user_id']);
if ($user) {
$this->meshAccount->setRoles($user->getRoles());
$this->meshAccount->setId((int) $user->id());
$this->meshAccount->setPreferredLangcode($user->getPreferredLangcode());
}
else {
$this->meshAccount->setAsAnonymous();
}
}
catch (\Exception $e) {
// Error loading user, fallback to anonymous.
$this->meshAccount->setAsAnonymous();
}
}
else {
// No user ID configured, fallback to anonymous.
$this->meshAccount->setAsAnonymous();
}
break;
default:
// Unknown type, fallback to anonymous.
$this->meshAccount->setAsAnonymous();
break;
}
}
return $this->meshAccount;
}
/**
* {@inheritdoc}
*/
public function checkViewAccessEntity(EntityInterface $entity): bool {
$mesh_account = $this->getMeshAccount();
return $entity->access('view', $mesh_account);
}
}
