Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Post transclusion #995

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Jobs/EntityMappingJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function __construct(Model $model)
public function handle()
{
//Get the model
$model = $this->class::where('id', $this->modelId)->first();
$model = $this->class::find($this->modelId);

/** @var EntityMappingService $entityMappingService. */
$entityMappingService = app()->make(EntityMappingService::class);
Expand Down
77 changes: 41 additions & 36 deletions app/Services/EntityMappingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class EntityMappingService
*/
public bool $verbose = false;

protected array $existingTargets = [];

/**
* Set errors and verbose to silent
*/
Expand Down Expand Up @@ -126,15 +128,15 @@ protected function images(): self
} elseif ($this->model instanceof Post) {
$images = $this->extractImages($this->post()->entry);
}
$existingTargets = [];
$this->existingTargets = [];
if ($this->model instanceof Entity) {
/** @var ImageMention $map */
foreach ($this->model->imageMentions()->whereNull('post_id')->get() as $map) {
$existingTargets[$map->image_id] = $map;
$this->existingTargets[$map->image_id] = $map;
}
} else {
foreach ($this->model->imageMentions as $map) {
$existingTargets[$map->image_id] = $map;
$this->existingTargets[$map->image_id] = $map;
}
}

Expand All @@ -157,13 +159,13 @@ protected function images(): self
continue;
}
// Don't map the same image multiple times
if (!empty($existingTargets[$target->id])) {
if ($this->model instanceof Post && $existingTargets[$target->id]->post_id == $this->model->id) {
unset($existingTargets[$target->id]);
if (!empty($this->existingTargets[$target->id])) {
if ($this->model instanceof Post && $this->existingTargets[$target->id]->post_id == $this->model->id) {
unset($this->existingTargets[$target->id]);
$this->updatedImages++;
continue;
} elseif ($this->model instanceof Entity && !$existingTargets[$target->id]->post_id) {
unset($existingTargets[$target->id]);
} elseif ($this->model instanceof Entity && !$this->existingTargets[$target->id]->post_id) {
unset($this->existingTargets[$target->id]);
$this->updatedImages++;
continue;
}
Expand All @@ -172,7 +174,7 @@ protected function images(): self
}

// Existing mappings that are no longer needed
foreach ($existingTargets as $targetId => $map) {
foreach ($this->existingTargets as $targetId => $map) {
$map->delete();
$this->deletedImages++;
}
Expand All @@ -182,20 +184,14 @@ protected function images(): self

protected function entry(): self
{
$existingTargets = [];
$this->existingTargets = [];
// @phpstan-ignore-next-line
foreach ($this->model->mentions as $map) {
$existingTargets[$map->target_id] = $map;
$this->existingTargets[$map->target_id] = $map;
}
$createdMappings = 0;
$existingMappings = 0;

if ($this->model instanceof Entity) {
$mentions = $this->extract($this->model->entry);
} else {
// @phpstan-ignore-next-line
$mentions = $this->extract($this->model->{$this->model->entryFieldName()});
}
// @phpstan-ignore-next-line
$mentions = $this->extract($this->model->{$this->model->entryFieldName()});

foreach ($mentions as $data) {
$type = $data['type'];
Expand All @@ -213,45 +209,54 @@ protected function entry(): self
if ($singularType == 'campaign') {
continue;
}
$target = null;

$entityType = $this->getEntityTypeID($singularType);
if (!$entityType) {
if (!$entityType && $singularType !== 'post') {
continue;
}

// Determine the real campaign id from the model.
$campaignId = $this->campaignID();

/** @var ?Entity $target */
$target = Entity::where([
'type_id' => $entityType,
'id' => $id,
'campaign_id' => $campaignId
])->first();
/** @var Entity|Post|null $target */
$target = $this->getTarget($id, $entityType);
if (!$target) {
continue;
}
// Do we already have this mention mapped?
if (!empty($existingTargets[$target->id])) {
if (!empty($this->existingTargets[$target->id])) {
//$this->log("- already have mapping");
unset($existingTargets[$target->id]);
$existingMappings++;
unset($this->existingTargets[$target->id]);
continue;
}

$this->createNewMention($target->id);
$createdMappings++;
}

// Existing mappings that are no longer needed
$deletedMappings = 0;
foreach ($existingTargets as $targetId => $map) {
foreach ($this->existingTargets as $targetId => $map) {
$map->delete();
$deletedMappings++;
}

return $this;
}

protected function getTarget(int $id, ?int $entityType): Entity|null
{
if (!isset($entityType)) {
$post = Post::with('entity')->where([
'id' => $id,
])->first();
if ($post && $post->entity && $post->entity->campaign_id === $this->campaignID()) {
return $post->entity;
}
return null;
}
return Entity::where([
'type_id' => $entityType,
'id' => $id,
'campaign_id' => $this->campaignID()
])->first();
}

protected function campaignID(): int
{
// Todo: should be a method on the object or something, not the service's job to figure out
Expand Down
125 changes: 119 additions & 6 deletions app/Services/MentionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use App\Models\EntityAsset;
use App\Models\EntityType;
use App\Models\MiscModel;
use App\Models\Post;
use App\Models\Quest;
use App\Services\Entity\NewService;
use App\Services\TOC\TocSlugify;
Expand All @@ -33,6 +34,9 @@ class MentionsService
/** @var array|Entity[] List of entities */
protected array $entities = [];

/** @var array|Post[] List of posts */
protected array $posts = [];

/** @var array|Entity[] List of private entities */
protected array $privateEntities = [];

Expand Down Expand Up @@ -61,7 +65,7 @@ class MentionsService
protected bool $createdNewEntities = false;

/** @var string Class used to inject and strip advanced mention name helpers */
public const ADVANCED_MENTION_CLASS = 'advanced-mention-name';
public const string ADVANCED_MENTION_CLASS = 'advanced-mention-name';

/** @var bool When false, parsing field:entry won't render mentions */
protected bool $enableEntryField = true;
Expand Down Expand Up @@ -232,7 +236,6 @@ function ($data) {
$text
);

//dd($text);
return $text;
}

Expand Down Expand Up @@ -279,10 +282,13 @@ protected function replaceEntityMentions(): void
// Icons
$fontAwesomes = ['fa ', 'fas ', 'far ', 'fab ', 'ra ', 'fa-solid ', 'fa-regular ', 'fa-brands '];
if ($matches[1] == 'icon' && Str::startsWith($matches[2], $fontAwesomes)) {
return '<i class="' . e($matches[2]) . '"></i>';
return '<i class="' . e($matches[2]) . '" aria-hidden="true"></i>';
}

$data = $this->extractData($matches);
if (Arr::get($data, 'type') === 'post') {
return $this->mentionPost($data);
}

$entity = $this->entity($data['id']);
$tagClasses = [];
Expand Down Expand Up @@ -373,7 +379,7 @@ protected function replaceEntityMentions(): void
}

// Add tags as a class
foreach ($entity->tags as $tag) {
foreach ($entity->tagsWithEntity() as $tag) {
$tagClasses[] = 'id-' . $tag->id;
$tagClasses[] = $tag->slug;
}
Expand Down Expand Up @@ -504,8 +510,11 @@ protected function parseMentionsForEdit(): self
$this->text = preg_replace_callback('`\[([a-z_]+):(.*?)\]`i', function ($matches) {
$data = $this->extractData($matches);

$hasCustom = Arr::has($data, 'custom');
if ($data['type'] === 'post') {
return $this->parsePostForEdit($matches[0], $data);
}

$hasCustom = Arr::has($data, 'custom');
// If the user always wants advanced mentions, we force the [] syntax upon them
if ($hasCustom || auth()->user()->alwaysAdvancedMentions()) {
// Still need to show the target's name in the advanced mention
Expand Down Expand Up @@ -586,6 +595,29 @@ protected function parseAttributesForEdit(): self
return $this;
}

protected function parsePostForEdit(string $mention, array $data): string
{
$post = $this->post($data['id']);

$hasCustom = Arr::has($data, 'custom');
if ($hasCustom || auth()->user()->alwaysAdvancedMentions()) {
$advancedName = $this->advancedMentionHelper($post->name);
return Str::replaceLast(']', $advancedName . ']', $mention);
}

// No entity found, the user might not be allowed to see it
if (empty($post) || $post->entity->isMissingChild()) {
$name = __('crud.history.unknown');
$dataName = $name;
} else {
$name = $post->name;
$dataName = Str::replace('"', '&quot;', $post->name);
}

return '<a href="#" class="post-mention" data-name="' . $dataName . '" data-mention="' . $mention
. '">' . $name . '</a>';
}

/**
*/
protected function entity(int $id): Entity|null
Expand All @@ -597,6 +629,17 @@ protected function entity(int $id): Entity|null
return Arr::get($this->entities, $id);
}

protected function post(int $id): Post|null
{
if (!Arr::has($this->posts, (string) $id)) {
$this->posts[$id] = Post::with(['entity', 'entity.tags', 'entity.entityType'])
->has('entity')
->find($id);
}

return Arr::get($this->posts, $id);
}

/**
*/
protected function hiddenEntity(int $id): Entity|null
Expand Down Expand Up @@ -885,7 +928,7 @@ protected function unlockEntryRendering(): void
protected function linkAttributes(string $html): array
{
// Don't waste time on the expensive DOMDocument call if there is no mention
if (!Str::contains($html, ['"mention"', '"attribute attribute-mention"'])) {
if (!Str::contains($html, ['"mention"', '"post-mention"', '"attribute attribute-mention"'])) {
return [];
}
$attributes = [];
Expand All @@ -909,4 +952,74 @@ protected function linkAttributes(string $html): array

return $attributes;
}

protected function mentionPost(array $data): string
{
$post = $this->post($data['id']);
$isTranscluding = Arr::get($data, 'text') === 'transclude';
if (!$post) {
if ($this->onlyName || $isTranscluding) {
return __('crud.history.unknown');
}
return Arr::get(
$data,
'text',
'<i class="unknown-mention unknown-entity">' . __('crud.history.unknown') . '</i>'
);
}

$url = route('entities.show', [$this->campaign, $post->entity, '#post-' . $post->id]);
$tooltipUrl = route('entities.tooltip', [$this->campaign, $post->entity]);

$cssClasses = ['entity-mention'];

$tagClasses = [];
foreach ($post->entity->tagsWithEntity() as $tag) {
$tagClasses[] = 'id-' . $tag->id;
$tagClasses[] = $tag->slug;
}

if ($isTranscluding) {
if ($this->enableEntryField) {
$this->lockEntryRendering();
$parsedTargetEntry = $post->parsedEntry();
$this->unlockEntryRendering();
} else {
$parsedTargetEntry = $post->entry;
}
$cssClasses[] = 'mention-field-post block';
$entityName = '<a href="' . $url . '"'
. ' class="entity-mention-name block mb-2"'
. ' data-toggle="tooltip-ajax"'
. ' data-id="' . $post->entity->id . '"'
. ' data-url="' . $tooltipUrl . '"'
. '>'
. $post->name
. '</a>';
return '<span class="' . implode(' ', $cssClasses) . '"'
. ' data-entity-tags="' . implode(' ', $tagClasses) . '"'
. '>'
. $entityName
. '<div class="mention-post-content">'
. $parsedTargetEntry
. '</div>'
. '</span>';
}

if ($this->onlyName) {
return Arr::get($data, 'text', $post->name);
}
return '<a href="' . $url . '"'
. ' class="' . implode(' ', $cssClasses) . '"'
. ' data-entity-tags="' . implode(' ', $tagClasses) . '"'
. ' data-entity-type="' . $post->entity->entityType->code . '"'
. ' data-toggle="tooltip-ajax"'
. ' data-id="' . $post->entity->id . '"'
. ' data-url="' . $tooltipUrl . '"'
// . ' data-mention-url="' . route('entities.tooltip', $entity). '"'
// . ' title="<i class=\'fa fa-spinner fa-spin\'></i>"'
. '>'
. Arr::get($data, 'text', $post->name)
. '</a>';
}
}
2 changes: 1 addition & 1 deletion app/Traits/MentionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ protected function extractData(array $matches): array

$type = Arr::first($subSegments);
$value = Arr::last($subSegments);
if (in_array($type, ['page', 'field'])) {
if (in_array($type, ['page', 'field', 'transclude'])) {
$data[$type] = mb_strtolower($value);
$data['custom'] = true;
} elseif (in_array($type, ['anchor', 'params', 'tooltip'])) {
Expand Down
Loading