PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / view / CapturedSourceEvidenceRenderer.php

CapturedSourceEvidenceRenderer.php in 404 Solution trunk, at includes/view/CapturedSourceEvidenceRenderer.php

168 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Renders the "Linked from" expandable internal-source-evidence panel for a
9 * captured 404 URL row: looks up which published pages/posts link to a
10 * visitor-visible 404 URL, and builds the trigger button + expandable panel
11 * HTML for each row. Extracted from View_CapturedURLsTable because internal
12 * source-evidence lookup and rendering is a distinct responsibility from the
13 * rest of the row's presentation formatting.
14 */
15 class ABJ_404_Solution_CapturedSourceEvidenceRenderer {
16
17 /** @var ABJ_404_Solution_Functions */
18 private $f;
19
20 public function __construct(ABJ_404_Solution_Functions $functions) {
21 $this->f = $functions;
22 }
23
24 private function tpl(string $name): string {
25 $raw = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . '/html/' . $name, false);
26 return rtrim((string)$raw, "\n");
27 }
28
29 /** @param array<string,string> $vars */
30 private function fillTpl(string $name, array $vars): string {
31 return (string)$this->f->str_replace(array_keys($vars), array_values($vars), $this->tpl($name));
32 }
33
34 /**
35 * @param array<int, array<string, mixed>> $rows
36 * @return array<string, array<string, mixed>>
37 */
38 public function evidenceByVisibleUrl(array $rows): array {
39 $urls = array();
40 foreach ($rows as $row) {
41 if (isset($row['url']) && is_string($row['url']) && $row['url'] !== '') {
42 $urls[] = $row['url'];
43 }
44 }
45 if (empty($urls)) {
46 return array();
47 }
48
49 $repo = $this->internalSourceEvidenceRepository();
50 if (!is_object($repo) || !method_exists($repo, 'getEvidenceForCapturedUrls')) {
51 return array();
52 }
53 $evidence = $repo->getEvidenceForCapturedUrls($urls, 5);
54 if (!is_array($evidence)) {
55 return array();
56 }
57
58 $typedEvidence = array();
59 foreach ($evidence as $url => $rowEvidence) {
60 if (is_string($url) && is_array($rowEvidence)) {
61 $typedEvidence[$url] = $rowEvidence;
62 }
63 }
64 return $typedEvidence;
65 }
66
67 /** @return mixed */
68 private function internalSourceEvidenceRepository() {
69 return abj_service('internal_source_evidence_repository');
70 }
71
72 /**
73 * @param array<string, mixed> $evidence
74 * @return array{trigger:string,panel:string}
75 */
76 public function htmlFor(string $rowId, array $evidence): array {
77 $sourceCount = isset($evidence['source_count']) && is_numeric($evidence['source_count'])
78 ? (int)$evidence['source_count'] : 0;
79 $sources = isset($evidence['sources']) && is_array($evidence['sources']) ? $evidence['sources'] : array();
80 if ($sourceCount <= 0 || empty($sources)) {
81 return array('trigger' => '', 'panel' => '');
82 }
83
84 $panelId = 'abj404-source-panel-' . sanitize_key($rowId);
85 $trigger = $this->fillTpl('capturedSourceTrigger.html', array(
86 '{panel_id}' => esc_attr($panelId),
87 '{row_id}' => esc_attr($rowId),
88 '{trigger_label}' => esc_html(sprintf(__('Sources (%d)', '404-solution'), $sourceCount)),
89 ));
90
91 $sourceRows = '';
92 foreach ($sources as $source) {
93 if (!is_array($source)) {
94 continue;
95 }
96 $sourceRows .= $this->sourceRowHtml($source);
97 }
98 if ($sourceRows === '') {
99 return array('trigger' => '', 'panel' => '');
100 }
101
102 $displayedCount = isset($evidence['displayed_source_count']) && is_numeric($evidence['displayed_source_count'])
103 ? (int)$evidence['displayed_source_count'] : count($sources);
104 $truncation = '';
105 if ($sourceCount > $displayedCount) {
106 $truncation = $this->fillTpl('capturedSourceTruncation.html', array(
107 '{truncation_text}' => esc_html(sprintf(__('Showing %1$d of %2$d sources.', '404-solution'), $displayedCount, $sourceCount)),
108 ));
109 }
110
111 $panel = $this->fillTpl('capturedSourcesPanel.html', array(
112 '{panel_id}' => esc_attr($panelId),
113 '{heading}' => esc_html__('Linked from', '404-solution'),
114 '{source_rows}' => $sourceRows,
115 '{truncation}' => $truncation,
116 ));
117
118 return array('trigger' => $trigger, 'panel' => $panel);
119 }
120
121 /** @param array<array-key, mixed> $source */
122 private function sourceRowHtml(array $source): string {
123 $title = isset($source['post_title']) && is_scalar($source['post_title']) ? trim((string)$source['post_title']) : '';
124 $referrerUrl = isset($source['referrer_url']) && is_scalar($source['referrer_url']) ? (string)$source['referrer_url'] : '';
125 $label = $title !== '' ? $title : $referrerUrl;
126 $postId = isset($source['post_id']) && is_numeric($source['post_id']) ? (int)$source['post_id'] : 0;
127
128 return $this->fillTpl('capturedSourcesRow.html', array(
129 '{source_label}' => esc_html($label),
130 '{hit_count}' => esc_html((string)(isset($source['hit_count']) && is_numeric($source['hit_count'])
131 ? (int)$source['hit_count'] : 0)),
132 '{edit_link}' => $this->editLinkHtml($postId),
133 ));
134 }
135
136 /**
137 * Builds the "Edit" admin link for a resolved source post, gated on the
138 * current user's edit capability for that specific post.
139 *
140 * Authorization (current_user_can()) and edit-link construction
141 * (get_edit_post_link()) live here -- the caller that renders
142 * source-evidence rows -- rather than in
143 * ABJ_404_Solution_InternalSourceEvidenceRepository, which is a
144 * data-access repository and must return only raw post_id/post_title
145 * data (CLAUDE.md "Strict layer separation"; c308).
146 */
147 private function editLinkHtml(int $postId): string {
148 if ($postId <= 0) {
149 return '';
150 }
151 if (!function_exists('current_user_can') || !current_user_can('edit_post', $postId)) {
152 return '';
153 }
154 if (!function_exists('get_edit_post_link')) {
155 return '';
156 }
157 $editUrl = (string)get_edit_post_link($postId);
158 if ($editUrl === '') {
159 return '';
160 }
161
162 return $this->fillTpl('capturedSourceEditLink.html', array(
163 '{edit_url}' => esc_url($editUrl),
164 '{edit_label}' => esc_html__('Edit', '404-solution'),
165 ));
166 }
167 }
168