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 / repositories / PluginUpdateMetadataRepository.php

PluginUpdateMetadataRepository.php in 404 Solution trunk, at includes/repositories/PluginUpdateMetadataRepository.php

158 lines 6.0 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 * Plugin self-maintenance metadata: wordpress.org version lookup, version-gap
9 * heuristic for error email throttling, and the legacy Redirectioner import.
10 *
11 * Extracted from the DataAccess monolith to keep DataAccess.php under the
12 * file size limit (FileSizeLimitsTest::MAX_LINES). Methods on the DAO remain
13 * as thin delegates so existing call sites and test doubles continue to work.
14 */
15 class ABJ_404_Solution_PluginUpdateMetadataRepository {
16
17 /** @var ABJ_404_Solution_DatabaseCore */
18 private $dbCore;
19
20 /** @var ABJ_404_Solution_Functions */
21 private $f;
22
23 /** @var ABJ_404_Solution_Logging */
24 private $logger;
25
26 /**
27 * @param ABJ_404_Solution_DatabaseCore $dbCore
28 * @param ABJ_404_Solution_Functions|null $functions
29 * @param ABJ_404_Solution_Logging|null $logging
30 */
31 public function __construct(
32 ABJ_404_Solution_DatabaseCore $dbCore,
33 $functions = null,
34 $logging = null
35 ) {
36 $this->dbCore = $dbCore;
37 $this->f = $functions !== null ? $functions : abj_service('functions');
38 $this->logger = $logging !== null ? $logging : abj_service('logging');
39 }
40
41 /** @return array{version: string, last_updated: string|null} */
42 public function getLatestPluginVersion(): array {
43 // Cache version info to avoid repeated slow wordpress.org API calls.
44 $cacheKey = 'abj404_latest_plugin_version_info';
45 if (function_exists('get_transient')) {
46 $cached = get_transient($cacheKey);
47 if (is_array($cached) && isset($cached['version'])) {
48 /** @var array{version: string, last_updated: string|null} $cached */
49 return $cached;
50 }
51 }
52
53 if (!function_exists('plugins_api')) {
54 $pluginInstallPath = ABSPATH . 'wp-admin/includes/plugin-install.php';
55 if (is_readable($pluginInstallPath)) {
56 require_once($pluginInstallPath);
57 }
58 }
59 if (!function_exists('plugins_api')) {
60 $this->logger->infoMessage("I couldn't find the plugins_api function to check for the latest version.");
61 $fallback = array('version' => ABJ404_VERSION, 'last_updated' => null);
62 return $fallback;
63 }
64
65 $pluginSlug = dirname(ABJ404_NAME);
66
67 $args = array(
68 'slug' => $pluginSlug,
69 'fields' => array(
70 'version' => true,
71 'last_updated' => true,
72 )
73 );
74
75 $call_api = plugins_api('plugin_information', $args);
76
77 if (is_wp_error($call_api)) {
78 $api_error = $call_api->get_error_message();
79 $this->logger->infoMessage("There was an API issue checking the latest plugin version ("
80 . $api_error . ")");
81
82 $fallback = array('version' => ABJ404_VERSION, 'last_updated' => null);
83 return $fallback;
84 }
85
86 /** @var object $call_api */
87 $apiVersion = property_exists($call_api, 'version') ? (string)$call_api->version : ABJ404_VERSION;
88 $apiLastUpdated = property_exists($call_api, 'last_updated') ? (string)$call_api->last_updated : null;
89 $result = array('version' => $apiVersion, 'last_updated' => $apiLastUpdated);
90 if (function_exists('set_transient')) {
91 $ttl = defined('DAY_IN_SECONDS') ? DAY_IN_SECONDS : 86400;
92 // allow-cache-empty: $result always carries a version string (fallback to ABJ404_VERSION when plugins_api omits it); is_wp_error early-returns above
93 set_transient($cacheKey, $result, $ttl);
94 }
95 return $result;
96 }
97
98 /**
99 * Decide whether the error file should be emailed based on the gap between
100 * the installed plugin version and the latest published version. Accepts
101 * the version info as an argument so the DAO can route the lookup through
102 * its own (potentially mocked) `getLatestPluginVersion()`.
103 *
104 * @param array{version: string, last_updated: string|null} $pluginInfo
105 * @return bool
106 */
107 public function shouldEmailErrorFileFor(array $pluginInfo): bool {
108 $latestVersion = $pluginInfo['version'];
109 $currentVersion = ABJ404_VERSION;
110 if ($latestVersion == $currentVersion) {
111 return true;
112 }
113
114 if (version_compare(ABJ404_VERSION, $latestVersion) == 1) {
115 $this->logger->infoMessage("Development version: A more recent version is installed than " .
116 "what is available on the WordPress site (" . ABJ404_VERSION . " / " .
117 $latestVersion . ").");
118 return true;
119 }
120
121 $currentArray = explode(".", $currentVersion);
122 $latestArray = explode(".", $latestVersion);
123
124 if (count($currentArray) != 3 || count($latestArray) != 3) {
125 $this->logger->errorMessage("Issue parsing version numbers. " .
126 $currentVersion . ' / ' . $latestVersion);
127
128 } else if ($currentArray[0] == $latestArray[0] && $currentArray[1] == $latestArray[1]) {
129 $difference = absint(absint($latestArray[2]) - absint($currentArray[2]));
130
131 if ($difference <= 1) {
132 return true;
133 }
134 }
135
136 return (ABJ404_VERSION == $pluginInfo['version']);
137 }
138
139 /** @return array<string, mixed> */
140 public function importDataFromPluginRedirectioner(): array {
141 global $wpdb;
142
143 $oldTable = $wpdb->prefix . 'wbz404_redirects';
144 $newTable = $this->dbCore->doTableNameReplacements('{wp_abj404_redirects}');
145
146 $query = ABJ_404_Solution_FileSystemService::readFileContents(__DIR__ . "/../sql/importDataFromPluginRedirectioner.sql");
147 $query = $this->f->str_replace('{OLD_TABLE}', $oldTable, $query);
148 $query = $this->f->str_replace('{NEW_TABLE}', $newTable, $query);
149
150 $result = $this->dbCore->queryAndGetResults($query);
151
152 $this->logger->infoMessage("Importing redirectioner SQL result: " .
153 wp_kses_post((string)json_encode($result)));
154
155 return $result;
156 }
157 }
158