PluginProbe
404 Solution / 4.1.19
404 Solution v4.1.19
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 / ViewTrait_Shared.php

ViewTrait_Shared.php in 404 Solution 4.1.19, at includes/ViewTrait_Shared.php

593 lines 22.8 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 * ViewTrait_Shared methods.
9 */
10 trait ViewTrait_Shared {
11
12 /** Get the 'checked' attribute for a checkbox based on option value.
13 * @param array<string, mixed> $options The options array
14 * @param string $key The option key to check
15 * @return string Returns ' checked' if option is '1', empty string otherwise
16 */
17 private function getCheckedAttr($options, $key) {
18 return (array_key_exists($key, $options) && $options[$key] == '1') ? " checked" : "";
19 }
20
21 /** @return array<string, string> */
22 private function getFallbackOptionDefaults() {
23 return array(
24 'default_redirect' => '301',
25 'DB_VERSION' => defined('ABJ404_VERSION') ? ABJ404_VERSION : '',
26 'menuLocation' => 'optionsLevel',
27 'admin_theme' => 'default',
28 'capture_deletion' => '0',
29 'admin_notification' => '0',
30 'maximum_log_disk_usage' => '0',
31 'admin_notification_email' => '',
32 'suggest_cats' => '0',
33 'suggest_tags' => '0',
34 'update_suggest_url' => '0',
35 'suggest_max' => '5',
36 'suggest_title' => '',
37 'suggest_before' => '',
38 'suggest_after' => '',
39 'suggest_entrybefore' => '',
40 'suggest_entryafter' => '',
41 'suggest_noresults' => '',
42 'ignore_doprocess' => '',
43 'ignore_dontprocess' => '',
44 'recognized_post_types' => 'page',
45 'recognized_categories' => '',
46 'folders_files_ignore' => '',
47 'suggest_regex_exclusions' => '',
48 'plugin_admin_users' => '',
49 'auto_score' => '0',
50 'template_redirect_priority' => '9',
51 'days_wait_before_major_update' => '0',
52 'excludePages[]' => '',
53 // These are used by other option cards; harmless defaults.
54 'auto_deletion' => '0',
55 'manual_deletion' => '0',
56 );
57 }
58
59 /**
60 * @param array<string, mixed> $options
61 * @return array<string, mixed>
62 */
63 private function normalizeOptionsForView($options) {
64 return array_merge($this->getFallbackOptionDefaults(), $options);
65 }
66
67 /**
68 * Build the behavior tiles HTML for the 404 destination setting.
69 * Used by both simple and advanced mode.
70 *
71 * @param array<string, mixed> $options
72 * @return string
73 */
74 private function getBehaviorTilesHTML($options) {
75 $behavior = isset($options['dest404_behavior']) && is_string($options['dest404_behavior'])
76 ? $options['dest404_behavior'] : 'theme_default';
77
78 $userSelectedDefault404PageRaw = (array_key_exists('dest404page', $options) &&
79 isset($options['dest404page']) ? $options['dest404page'] : null);
80 $userSelectedDefault404Page = is_string($userSelectedDefault404PageRaw) ? $userSelectedDefault404PageRaw : '';
81 $urlDestinationRaw = (array_key_exists('dest404pageURL', $options) &&
82 isset($options['dest404pageURL']) ? $options['dest404pageURL'] : null);
83 $urlDestination = is_string($urlDestinationRaw) ? $urlDestinationRaw : '';
84
85 // Build the custom page dropdown for when 'custom' is selected
86 $pageTitle = $this->logic->getPageTitleFromIDAndType($userSelectedDefault404Page, $urlDestination);
87 $pageMissingWarning = "";
88 if ($behavior === 'custom' && $userSelectedDefault404Page !== '') {
89 $permalink = ABJ_404_Solution_Functions::permalinkInfoToArray($userSelectedDefault404Page, 0);
90 if (!in_array($permalink['status'], array('publish', 'published'))) {
91 $pageMissingWarning = __("(The specified page doesn't exist. Please update this setting.)", '404-solution');
92 }
93 }
94
95 $customDropdown = ABJ_404_Solution_Functions::readFileContents(__DIR__ .
96 "/html/addManualRedirectPageSearchDropdown.html");
97 $customDropdown = $this->f->str_replace('{redirect_to_label}', '', $customDropdown);
98 $customDropdown = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_EMPTY}',
99 __('(Type a page name or an external URL)', '404-solution'), $customDropdown);
100 $customDropdown = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_PAGE}',
101 __('(A page has been selected.)', '404-solution'), $customDropdown);
102 $customDropdown = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_CUSTOM_STRING}',
103 __('(A custom string has been entered.)', '404-solution'), $customDropdown);
104 $customDropdown = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_URL}',
105 __('(An external URL will be used.)', '404-solution'), $customDropdown);
106 $customDropdown = $this->f->str_replace('{REDIRECT_TO_USER_FIELD_WARNING}', $pageMissingWarning, $customDropdown);
107 $customDropdown = $this->f->str_replace('{redirectPageTitle}', esc_attr($pageTitle), $customDropdown);
108 $customDropdown = $this->f->str_replace('{pageIDAndType}', esc_attr($userSelectedDefault404Page), $customDropdown);
109 $customDropdown = $this->f->str_replace('{data-url}',
110 "admin-ajax.php?action=echoRedirectToPages&includeDefault404Page=true&includeSpecial=true&nonce=" . wp_create_nonce('abj404_ajax'), $customDropdown);
111 $customDropdown = $this->f->doNormalReplacements($customDropdown);
112
113 // Build tiles template
114 $html = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/behaviorTiles.html");
115
116 $behaviors = array('suggest', 'homepage', 'custom', 'theme_default');
117 foreach ($behaviors as $b) {
118 $key = str_replace('_', '_', $b); // just to be explicit
119 $isSelected = ($behavior === $b);
120 $html = $this->f->str_replace('{tile_' . $key . '_selected}', $isSelected ? ' selected' : '', $html);
121 $html = $this->f->str_replace('{' . $key . '_aria_checked}', $isSelected ? 'true' : 'false', $html);
122 }
123
124 $html = $this->f->str_replace('{selected_behavior}', esc_attr($behavior), $html);
125 $html = $this->f->str_replace('{pageIDAndType}', esc_attr($userSelectedDefault404Page), $html);
126 $html = $this->f->str_replace('{custom_picker_display}', $behavior === 'custom' ? '' : 'none', $html);
127 $html = $this->f->str_replace('{customPageDropdown}', $customDropdown, $html);
128
129 // Translations
130 $html = $this->f->str_replace('{Recommended}', __('Recommended', '404-solution'), $html);
131 $html = $this->f->str_replace('{Suggest similar pages}', __('Suggest similar pages', '404-solution'), $html);
132 $html = $this->f->str_replace('{Shows visitors a list of pages matching the URL they were looking for}',
133 __('Shows visitors a list of pages matching the URL they were looking for', '404-solution'), $html);
134 $html = $this->f->str_replace('{Redirect to homepage}', __('Redirect to homepage', '404-solution'), $html);
135 $html = $this->f->str_replace('{Sends all 404 visitors to the site front page}',
136 __('Sends all 404 visitors to the site front page', '404-solution'), $html);
137 $html = $this->f->str_replace('{Custom page}', __('Custom page', '404-solution'), $html);
138 $html = $this->f->str_replace('{Choose a specific page to show for all 404 errors}',
139 __('Choose a specific page to show for all 404 errors', '404-solution'), $html);
140 $html = $this->f->str_replace('{Theme default}', __('Theme default', '404-solution'), $html);
141 $html = $this->f->str_replace('{Uses the theme built-in 404 page, no redirect}',
142 __('Uses the theme built-in 404 page, no redirect', '404-solution'), $html);
143 $html = $this->f->str_replace('{Select a page}', __('Select a page', '404-solution'), $html);
144
145 return $html;
146 }
147
148 /**
149 * Safely extract a string value from an options array.
150 *
151 * @param array<string, mixed> $options
152 * @param string $key
153 * @param string $default
154 * @return string
155 */
156 private function optStr($options, $key, $default = '') {
157 if (!array_key_exists($key, $options)) {
158 return $default;
159 }
160 $val = $options[$key];
161 if (is_string($val)) {
162 return $val;
163 }
164 if (is_scalar($val)) {
165 return (string)$val;
166 }
167 return $default;
168 }
169
170 /**
171 * Normalize a scalar value for table signature comparisons.
172 *
173 * @param mixed $value
174 * @return string
175 */
176 private function normalizeSignatureValue($value) {
177 if ($value === null) {
178 return '';
179 }
180 if (is_bool($value)) {
181 return $value ? '1' : '0';
182 }
183 if (is_int($value) || is_float($value)) {
184 return (string)$value;
185 }
186 if (is_array($value)) {
187 $value = implode(',', array_map(array($this, 'normalizeSignatureValue'), $value));
188 }
189 $text = is_scalar($value) ? (string)$value : '';
190 $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
191 $text = preg_replace('/\s+/', ' ', $text);
192 return trim((string)$text);
193 }
194
195 /**
196 * Build a deterministic row signature payload for a specific admin list subpage.
197 *
198 * @param string $sub
199 * @param array<string, mixed> $row
200 * @return array<string,string>
201 */
202 private function getSignatureFieldsForSubpage($sub, $row) {
203 $sub = (string)$sub;
204
205 if ($sub === 'abj404_redirects') {
206 return array(
207 'id' => $this->normalizeSignatureValue($row['id'] ?? ''),
208 'url' => $this->normalizeSignatureValue($row['url'] ?? ''),
209 'status' => $this->normalizeSignatureValue($row['status'] ?? ''),
210 'type' => $this->normalizeSignatureValue($row['type'] ?? ''),
211 'final_dest' => $this->normalizeSignatureValue($row['final_dest'] ?? ''),
212 'dest_for_view' => $this->normalizeSignatureValue($row['dest_for_view'] ?? ''),
213 'code' => $this->normalizeSignatureValue($row['code'] ?? ''),
214 'logshits' => $this->normalizeSignatureValue($row['logshits'] ?? 0),
215 'timestamp' => $this->normalizeSignatureValue($row['timestamp'] ?? 0),
216 'last_used' => $this->normalizeSignatureValue($row['last_used'] ?? 0),
217 );
218 }
219
220 if ($sub === 'abj404_captured') {
221 $hits = array_key_exists('logshits', $row) ? $row['logshits'] : ($row['hit_count'] ?? 0);
222 $timestamp = array_key_exists('timestamp', $row) ? $row['timestamp'] : ($row['created'] ?? 0);
223 return array(
224 'id' => $this->normalizeSignatureValue($row['id'] ?? ''),
225 'url' => $this->normalizeSignatureValue($row['url'] ?? ''),
226 'status' => $this->normalizeSignatureValue($row['status'] ?? ''),
227 'logshits' => $this->normalizeSignatureValue($hits),
228 'timestamp' => $this->normalizeSignatureValue($timestamp),
229 'last_used' => $this->normalizeSignatureValue($row['last_used'] ?? 0),
230 );
231 }
232
233 if ($sub === 'abj404_logs') {
234 return array(
235 'id' => $this->normalizeSignatureValue($row['id'] ?? ''),
236 'url' => $this->normalizeSignatureValue($row['url'] ?? ''),
237 'url_detail' => $this->normalizeSignatureValue($row['url_detail'] ?? ''),
238 'remote_host' => $this->normalizeSignatureValue($row['remote_host'] ?? ''),
239 'referrer' => $this->normalizeSignatureValue($row['referrer'] ?? ''),
240 'action' => $this->normalizeSignatureValue($row['action'] ?? ''),
241 'timestamp' => $this->normalizeSignatureValue($row['timestamp'] ?? 0),
242 'username' => $this->normalizeSignatureValue($row['username'] ?? ''),
243 );
244 }
245
246 $normalized = array();
247 foreach ($row as $k => $v) {
248 if (is_scalar($v) || is_array($v) || $v === null) {
249 $normalized[(string)$k] = $this->normalizeSignatureValue($v);
250 }
251 }
252 ksort($normalized);
253 return $normalized;
254 }
255
256 /**
257 * Compute and remember a deterministic table signature for detect-only refresh checks.
258 *
259 * @param string $sub
260 * @param array<int, array<string, mixed>> $rows
261 * @return void
262 */
263 private function rememberTableDataSignature($sub, $rows) {
264 $sub = (string)$sub;
265 if (!is_array($rows)) {
266 $this->tableDataSignatures[$sub] = sha1($sub . '|0');
267 return;
268 }
269
270 $rowSignatures = array();
271 foreach ($rows as $row) {
272 $fields = $this->getSignatureFieldsForSubpage($sub, $row);
273 $parts = array();
274 foreach ($fields as $k => $v) {
275 $parts[] = $k . '=' . $v;
276 }
277 $rowSignatures[] = implode("\x1f", $parts);
278 }
279 sort($rowSignatures, SORT_STRING);
280 $payload = $sub . '|' . count($rowSignatures) . '|' . implode("\n", $rowSignatures);
281 $this->tableDataSignatures[$sub] = sha1($payload);
282 }
283
284 /**
285 * Get the most recently computed table data signature for a subpage.
286 *
287 * @param string $sub
288 * @return string
289 */
290 public function getCurrentTableDataSignature($sub) {
291 $sub = (string)$sub;
292 return (string)($this->tableDataSignatures[$sub] ?? '');
293 }
294
295 /**
296 * Get plugin options merged with defaults.
297 *
298 * Some tests use partial/mocked PluginLogic instances; this method must not
299 * assume getDefaultOptions() is available or safe to call.
300 *
301 * @return array<string, mixed>
302 */
303 private function getOptionsWithDefaults() {
304 $options = $this->logic->getOptions();
305 if (!is_array($options)) {
306 $options = array();
307 }
308
309 $defaults = array();
310 if (is_object($this->logic) && method_exists($this->logic, 'getDefaultOptions')) {
311 try {
312 $defaults = $this->logic->getDefaultOptions();
313 } catch (Throwable $e) {
314 $defaults = array();
315 }
316 }
317
318 $defaults = is_array($defaults) && !empty($defaults)
319 ? array_merge($this->getFallbackOptionDefaults(), $defaults)
320 : $this->getFallbackOptionDefaults();
321
322 $options = array_merge($defaults, $options);
323
324 return $options;
325 }
326
327 /**
328 * Get the tooltip HTML for Hits/Last Used columns when those values may lag.
329 *
330 * We only show this for views sorted by hits/last_used because those modes may
331 * rely on the aggregated logs-hits table. Other sorts use live per-row lookup,
332 * so showing an aggregation timestamp there is misleading.
333 *
334 * @param array<string, mixed> $tableOptions Current table options.
335 * @return string Tooltip HTML (not escaped - contains data attributes)
336 */
337 private function getHitsColumnTooltip($tableOptions = array()) {
338 $rawOrderby = $tableOptions['orderby'] ?? '';
339 $orderby = strtolower(is_string($rawOrderby) ? $rawOrderby : '');
340 $isAggregatedMode = ($orderby === 'logshits' || $orderby === 'last_used');
341 if (!$isAggregatedMode) {
342 return '';
343 }
344
345 // Ensure the "last checked"/"refresh scheduled" tooltip state is computed for this request.
346 // This runs cheap checks and (when needed) schedules the expensive rebuild for shutdown.
347 if (is_object($this->dao) && method_exists($this->dao, 'maybeUpdateRedirectsForViewHitsTable')) {
348 $this->dao->maybeUpdateRedirectsForViewHitsTable();
349 }
350
351 $timestamp = $this->dao->getLogsHitsTableLastUpdated();
352 $lines = array();
353 if ($timestamp !== null) {
354 $lastUpdated = $this->dao->getLogsHitsTableLastUpdatedHuman();
355 $timeHtml = '<span class="abj404-time-ago" data-timestamp="' . esc_attr((string)$timestamp) . '">' . esc_html($lastUpdated) . '</span>';
356 $lines[] = sprintf(__('Last updated: %s', '404-solution'), $timeHtml);
357 }
358
359 $checkedAt = $this->dao->getLogsHitsTableLastCheckedAt();
360 if ($checkedAt !== null) {
361 $checkedHtml = '<span class="abj404-time-ago" data-timestamp="' . esc_attr((string)$checkedAt) . '">' . esc_html($this->formatTimeAgo($checkedAt)) . '</span>';
362 $lines[] = sprintf(__('Last checked: %s', '404-solution'), $checkedHtml);
363 }
364
365 $decision = $this->dao->getLogsHitsTableLastDecision();
366 // Treat "cooldown" as "scheduled recently" from a user perspective.
367 if ($decision === 'scheduled' || $decision === 'cooldown') {
368 $lines[] = __('Refresh scheduled', '404-solution');
369 } else if ($decision === 'running') {
370 $lines[] = __('Refresh running', '404-solution');
371 } else if ($decision === 'paused') {
372 $lines[] = __('Refresh paused', '404-solution');
373 }
374
375 return implode('<br>', array_filter($lines));
376 }
377
378 /**
379 * Small, dependency-free time-ago formatter for tooltip use.
380 * (We don't want to rely on WP human_time_diff() in unit tests.)
381 *
382 * @param int $timestamp
383 * @return string
384 */
385 private function formatTimeAgo($timestamp) {
386 $diff = time() - absint($timestamp);
387 if ($diff < 60) {
388 return __('Just now', '404-solution');
389 }
390 if ($diff < 3600) {
391 $minutes = (int)floor($diff / 60);
392 return sprintf(_n('%d minute ago', '%d minutes ago', $minutes, '404-solution'), $minutes);
393 }
394 if ($diff < 86400) {
395 $hours = (int)floor($diff / 3600);
396 return sprintf(_n('%d hour ago', '%d hours ago', $hours, '404-solution'), $hours);
397 }
398 $days = (int)floor($diff / 86400);
399 return sprintf(_n('%d day ago', '%d days ago', $days, '404-solution'), $days);
400 }
401
402 /**
403 * Build shared sort state for table headers.
404 *
405 * @param array<string, mixed> $tableOptions
406 * @param string $orderby
407 * @param bool $preferDescOnFirstClick
408 * @return array{isSortable:bool,thClass:string,nextOrder:string,indicator:string}
409 */
410 private function getHeaderSortState($tableOptions, $orderby, $preferDescOnFirstClick = false) {
411 $result = array(
412 'isSortable' => false,
413 'thClass' => '',
414 'nextOrder' => 'ASC',
415 'indicator' => '',
416 );
417
418 $orderby = (string)$orderby;
419 if ($orderby === '') {
420 return $result;
421 }
422
423 $result['isSortable'] = true;
424 $rawCurrentOrderby = $tableOptions['orderby'] ?? '';
425 $currentOrderby = is_string($rawCurrentOrderby) ? $rawCurrentOrderby : '';
426 $rawCurrentOrder = $tableOptions['order'] ?? 'ASC';
427 $currentOrder = strtoupper(is_string($rawCurrentOrder) ? $rawCurrentOrder : 'ASC');
428 if ($currentOrder !== 'DESC') {
429 $currentOrder = 'ASC';
430 }
431
432 if ($currentOrderby === $orderby) {
433 $result['thClass'] = 'sorted ' . strtolower($currentOrder);
434 $result['nextOrder'] = ($currentOrder === 'ASC') ? 'DESC' : 'ASC';
435 $result['indicator'] = ($currentOrder === 'ASC') ? ' ↑' : ' ↓';
436 return $result;
437 }
438
439 $result['thClass'] = 'sortable ' . ($preferDescOnFirstClick ? 'asc' : 'desc');
440 $result['nextOrder'] = $preferDescOnFirstClick ? 'DESC' : 'ASC';
441 return $result;
442 }
443
444 /**
445 * Build action links for table rows (edit, logs, trash, delete, etc.)
446 *
447 * @param array<string, mixed> $row The data row from the database
448 * @param string $sub The subpage parameter value
449 * @param array<string, mixed> $tableOptions Table options including filter, orderby, order
450 * @param bool $isCapturedPage True for captured URLs page, false for redirects page
451 * @return array<string, string> Array of links and titles
452 */
453 protected function buildTableActionLinks($row, $sub, $tableOptions, $isCapturedPage = false) {
454 $result = [];
455
456 // Sanitize $sub for safe use in URLs (prevents XSS via quote injection)
457 $sub = rawurlencode($sub);
458
459 // ID handling differs between pages
460 $rawId = $row['id'] ?? 0;
461 $rawLogsId = $row['logsid'] ?? 0;
462 if ($isCapturedPage) {
463 // Captured page uses raw ID for most links
464 $id = $rawId;
465 $logsId = $rawLogsId;
466 } else {
467 // Redirects page uses absint for all IDs
468 $id = absint(is_scalar($rawId) ? $rawId : 0);
469 $logsId = absint(is_scalar($rawLogsId) ? $rawLogsId : 0);
470 }
471
472 // Build base links
473 $result['editlink'] = "?page=" . ABJ404_PP . "&subpage=abj404_edit&id=" . $id . "&source_page=" . $sub;
474 $result['logslink'] = "?page=" . ABJ404_PP . "&subpage=abj404_logs&id=" . $logsId;
475
476 if ($isCapturedPage) {
477 // Captured page - use the dynamic $sub parameter only once
478 $result['trashlink'] = "?page=" . ABJ404_PP . "&id=" . $id .
479 "&subpage=" . $sub;
480 $result['ajaxTrashLink'] = "admin-ajax.php?action=trashLink" . "&id=" . absint(is_scalar($rawId) ? $rawId : 0) .
481 "&subpage=" . $sub;
482 $result['deletelink'] = "?page=" . ABJ404_PP . "&remove=1&id=" . $id .
483 "&subpage=" . $sub;
484 } else {
485 // Redirects page does not have hardcoded subpage
486 $result['trashlink'] = "?page=" . ABJ404_PP . "&id=" . $id .
487 "&subpage=" . $sub;
488 $result['ajaxTrashLink'] = "admin-ajax.php?action=trashLink" . "&id=" . $id .
489 "&subpage=" . $sub;
490 $result['deletelink'] = "?page=" . ABJ404_PP . "&remove=1&id=" . $id .
491 "&subpage=" . $sub;
492 }
493
494 // Extract type-safe table option values
495 $toOrderby = is_array($tableOptions) && array_key_exists('orderby', $tableOptions) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : '';
496 $toOrder = is_array($tableOptions) && array_key_exists('order', $tableOptions) && is_string($tableOptions['order']) ? $tableOptions['order'] : '';
497 $toFilter = is_array($tableOptions) && array_key_exists('filter', $tableOptions) ? $tableOptions['filter'] : 0;
498
499 // Trash/Restore title and action
500 if ($toFilter == ABJ404_TRASH_FILTER) {
501 $result['trashlink'] .= "&trash=0";
502 $result['ajaxTrashLink'] .= "&trash=0";
503 $result['trashtitle'] = __('Restore', '404-solution');
504 } else {
505 $result['trashlink'] .= "&trash=1";
506 $result['ajaxTrashLink'] .= "&trash=1";
507 $result['trashtitle'] = __('Trash', '404-solution');
508 }
509
510 // Captured page has ignore and later links
511 if ($isCapturedPage) {
512 $result['ignorelink'] = "?page=" . ABJ404_PP . "&id=" . $id .
513 "&subpage=" . $sub;
514 $result['laterlink'] = "?page=" . ABJ404_PP . "&id=" . $id .
515 "&subpage=" . $sub;
516
517 // Ignore title and action
518 $result['ignoretitle'] = "";
519 if ($toFilter == ABJ404_STATUS_IGNORED) {
520 $result['ignorelink'] .= "&ignore=0";
521 $result['ignoretitle'] = __('Remove Ignore Status', '404-solution');
522 } else {
523 $result['ignorelink'] .= "&ignore=1";
524 $result['ignoretitle'] = __('Ignore 404 Error', '404-solution');
525 }
526
527 // Later title and action
528 $result['latertitle'] = '?Organize Later?';
529 if ($toFilter == ABJ404_STATUS_LATER) {
530 $result['laterlink'] .= "&later=0";
531 $result['latertitle'] = __('Remove Later Status', '404-solution');
532 } else {
533 $result['laterlink'] .= "&later=1";
534 $result['latertitle'] = __('Organize Later', '404-solution');
535 }
536 }
537
538 // Add orderby/order parameters if not default
539 if ($toOrderby !== '' && $toOrder !== '') {
540 if (!($toOrderby == "url" && $toOrder == "ASC")) {
541 $result['trashlink'] .= "&orderby=" . sanitize_text_field($toOrderby) . "&order=" . sanitize_text_field($toOrder);
542 $result['deletelink'] .= "&orderby=" . sanitize_text_field($toOrderby) . "&order=" . sanitize_text_field($toOrder);
543
544 if ($isCapturedPage && array_key_exists('ignorelink', $result) && array_key_exists('laterlink', $result)) {
545 $result['ignorelink'] .= "&orderby=" . sanitize_text_field($toOrderby) . "&order=" . sanitize_text_field($toOrder);
546 $result['laterlink'] .= "&orderby=" . sanitize_text_field($toOrderby) . "&order=" . sanitize_text_field($toOrder);
547 }
548 }
549 }
550
551 // Add filter parameter if not zero
552 if ($toFilter != 0) {
553 $result['trashlink'] .= "&filter=" . $toFilter;
554 $result['deletelink'] .= "&filter=" . $toFilter;
555 $result['editlink'] .= "&filter=" . $toFilter;
556
557 if ($isCapturedPage && array_key_exists('ignorelink', $result) && array_key_exists('laterlink', $result)) {
558 $result['ignorelink'] .= "&filter=" . $toFilter;
559 $result['laterlink'] .= "&filter=" . $toFilter;
560 }
561 }
562
563 // Add orderby/order parameters to edit link
564 if ($toOrderby !== '' && $toOrder !== '') {
565 if (!($toOrderby == "url" && $toOrder == "ASC")) {
566 $result['editlink'] .= "&orderby=" . sanitize_text_field($toOrderby) . "&order=" . sanitize_text_field($toOrder);
567 }
568 }
569
570 // Add paged parameter to edit link if present
571 if (is_array($tableOptions) && array_key_exists('paged', $tableOptions) && $tableOptions['paged'] > 1) {
572 $result['editlink'] .= "&paged=" . $tableOptions['paged'];
573 }
574
575 // Apply nonces
576 $result['trashlink'] = wp_nonce_url($result['trashlink'], "abj404_trashRedirect");
577 $result['ajaxTrashLink'] = wp_nonce_url($result['ajaxTrashLink'], "abj404_ajaxTrash");
578
579 if ($toFilter == ABJ404_TRASH_FILTER) {
580 $result['deletelink'] = wp_nonce_url($result['deletelink'], "abj404_removeRedirect");
581 }
582
583 if ($isCapturedPage && array_key_exists('ignorelink', $result) && array_key_exists('laterlink', $result)) {
584 $result['ignorelink'] = wp_nonce_url($result['ignorelink'], "abj404_ignore404");
585 $result['laterlink'] = wp_nonce_url($result['laterlink'], "abj404_organizeLater");
586 }
587
588 return $result;
589 }
590
591
592 }
593