PluginProbe
Site Reviews / trunk
Site Reviews vtrunk
8.3.1 8.3.0 8.2.2 8.2.1 8.2.0 8.1.0 8.0.13 8.0.12 8.0.11 trunk 1.2.2 2.17.1 3.5.4 4.7.0 5.25.1 6.11.8 7.0.10 7.0.11 7.0.12 7.0.13 7.0.14 7.0.15 7.0.16 7.0.17 7.0.18 All 54 releases
site-reviews / plugin / Controllers / UpdateController.php

UpdateController.php in Site Reviews trunk, at plugin/Controllers/UpdateController.php

242 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace GeminiLabs\SiteReviews\Controllers;
4
5 use GeminiLabs\SiteReviews\Addons\UpdateNotice;
6 use GeminiLabs\SiteReviews\Addons\Updater;
7 use GeminiLabs\SiteReviews\Helpers\Cast;
8
9 class UpdateController extends AbstractController
10 {
11 /**
12 * Say why an addon did not update when its update carried no package.
13 *
14 * @param mixed $email
15 *
16 * @return mixed
17 *
18 * @filter auto_plugin_theme_update_email
19 */
20 public function filterAutoUpdateEmail($email, string $type, array $successfulUpdates, array $failedUpdates)
21 {
22 if (!is_array($email) || !in_array($type, ['fail', 'mixed'], true)) {
23 return $email;
24 }
25 $lines = array_filter(array_map(
26 fn ($failed) => $this->licenseFailureLine($failed),
27 $failedUpdates['plugin'] ?? []
28 ));
29 if (empty($lines)) {
30 return $email;
31 }
32 $heading = _x('The following Site Reviews addons did not update because their license does not allow it:', 'admin-text', 'site-reviews');
33 $body = rtrim((string) ($email['body'] ?? ''));
34 $email['body'] = $body."\n\n".$heading."\n".implode("\n", $lines)."\n";
35 return $email;
36 }
37
38 /**
39 * Get the update information for the plugin modal.
40 *
41 * @param false|object|array $data
42 * @param object $args
43 *
44 * @return false|object|array
45 *
46 * @filter plugins_api
47 */
48 public function filterPluginsApi($data, string $action, $args)
49 {
50 if ('plugin_information' !== $action || empty($args->slug)) {
51 return $data;
52 }
53 if (!$this->isAddon($args->slug)) {
54 return $data;
55 }
56 $updater = new Updater($args->slug, [
57 'force' => $this->hasTimeoutExpired($args->slug),
58 ]);
59 $details = $updater->versionDetails();
60 if (empty($details['version'])) {
61 return $data;
62 }
63 return (object) $details;
64 }
65
66 /**
67 * Get the update information for supported addons.
68 *
69 * @param array|false $pluginUpdate
70 *
71 * @return array|false
72 *
73 * @filter update_plugins_niftyplugins.com
74 */
75 public function filterUpdatePlugins($pluginUpdate, array $pluginData)
76 {
77 $addonId = $pluginData['TextDomain'] ?? '';
78 $url = $pluginData['UpdateURI'] ?? '';
79 $updater = new Updater($addonId, [
80 'force' => $this->hasTimeoutExpired($addonId),
81 'url' => $url,
82 ]);
83 $update = $updater->versionUpdate();
84 if (!empty($update['version'])) {
85 return $update;
86 }
87 return $pluginUpdate;
88 }
89
90 /**
91 * Get the update information for unsupported addons.
92 *
93 * @param mixed $updates
94 *
95 * @return mixed
96 *
97 * @filter site_transient_update_plugins
98 */
99 public function filterUpdatePluginsTransient($updates)
100 {
101 if (empty($updates)) {
102 return $updates;
103 }
104 $addons = glsr()->retrieveAs('array', 'compat', []);
105 foreach ($addons as $addonId => $file) {
106 $plugin = plugin_basename($file);
107 $pluginData = get_file_data($file, ['version' => 'Version'], 'plugin');
108 $currentVersion = $pluginData['version'];
109 $updater = new Updater($addonId, [
110 'force' => $this->hasTimeoutExpired($addonId),
111 'url' => Updater::DEFAULT_API_URL,
112 ]);
113 $update = (object) $updater->versionUpdate();
114 if (empty($update->version)) {
115 continue;
116 }
117 $update->id = Updater::DEFAULT_API_URL;
118 $update->plugin = $plugin;
119 $update->new_version = $update->version;
120 unset($updates->no_update[$plugin], $updates->response[$plugin]);
121 if (version_compare($update->version, $currentVersion, '>')) {
122 $updates->response[$plugin] = $update;
123 } else {
124 $updates->no_update[$plugin] = $update;
125 }
126 $updates->checked[$plugin] = $currentVersion;
127 }
128 return $updates;
129 }
130
131 /**
132 * Delete API transient here.
133 *
134 * @action delete_site_transient_update_plugins
135 */
136 // public function onDeleteUpdatePluginsTransient(): void
137 // {
138 // }
139
140 /**
141 * @action upgrader_process_complete
142 */
143 // public function onUpgraderProcessComplete(): void
144 // {
145 // }
146
147 /**
148 * @param array $pluginData
149 * @param object $response
150 *
151 * @action after_in_plugin_update_message-plugin_row_{$addonId}/{$addonId}.php
152 */
153 public function renderPluginUpdateMessage($pluginData, $response): void
154 {
155 if (!empty($response->package)) {
156 return;
157 }
158 $notice = $this->updateNotice($response, (string) ($pluginData['PluginURI'] ?? ''));
159 echo ' '.$notice->html();
160 }
161
162 protected function hasTimeoutExpired(string $addonId): bool
163 {
164 $optionKey = glsr()->prefix.'last_checked_'.$addonId;
165 $lastChecked = Cast::toInt(get_site_option($optionKey, 0));
166 if (doing_filter('upgrader_process_complete')) {
167 $timeout = 0;
168 } elseif (doing_filter('load-update-core.php')) {
169 $timeout = filter_input(\INPUT_GET, 'force-check', \FILTER_VALIDATE_INT) ? 0 : \MINUTE_IN_SECONDS;
170 } elseif (doing_filter('load-plugins.php') || doing_filter('load-update.php')) {
171 $timeout = \HOUR_IN_SECONDS;
172 } elseif (wp_doing_cron()) {
173 $timeout = 2 * \HOUR_IN_SECONDS;
174 } else {
175 $timeout = 12 * \HOUR_IN_SECONDS;
176 }
177 if ($timeout <= (time() - $lastChecked)) {
178 update_site_option($optionKey, time());
179 return true;
180 }
181 return false;
182 }
183
184 protected function isAddon(string $slug): bool
185 {
186 static $cache = [];
187 if (isset($cache[$slug])) {
188 return $cache[$slug];
189 }
190 $cache[$slug] = false;
191 if (!preg_match('/^'.glsr()->id.'-[a-z-]+$/D', $slug)) {
192 return $cache[$slug];
193 }
194 $file = \WP_PLUGIN_DIR."/{$slug}/{$slug}.php";
195 if (is_readable($file)) {
196 $data = get_file_data($file, ['update_uri' => 'Update URI']);
197 $cache[$slug] = trailingslashit(Updater::DEFAULT_API_URL) === trailingslashit($data['update_uri']);
198 }
199 return $cache[$slug];
200 }
201
202 /**
203 * An update object the plugin built names the update server as its id:
204 * WordPress copies the Update URI header there, and the transient filter sets it.
205 */
206 protected function isAddonUpdate(object $update): bool
207 {
208 $id = (string) ($update->id ?? '');
209 return '' !== $id && trailingslashit($id) === trailingslashit(Updater::DEFAULT_API_URL);
210 }
211
212 /**
213 * The email line for a failed addon update that had no package; '' for any other failure.
214 *
215 * @param mixed $failed
216 */
217 protected function licenseFailureLine($failed): string
218 {
219 $item = $failed->item ?? null;
220 if (!is_object($item) || !empty($item->package) || !$this->isAddonUpdate($item)) {
221 return '';
222 }
223 $notice = $this->updateNotice($item);
224 $name = html_entity_decode((string) ($failed->name ?? $item->plugin));
225 return sprintf('- %s: %s %s', $name, $notice->text(), $notice->url());
226 }
227
228 /**
229 * The notice for an update object that has no package.
230 *
231 * @param mixed $update
232 */
233 protected function updateNotice($update, string $pluginUrl = ''): UpdateNotice
234 {
235 return new UpdateNotice(
236 (string) ($update->license_status ?? ''),
237 (string) ($update->license_renewal_url ?? ''),
238 $pluginUrl
239 );
240 }
241 }
242