| 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 |
|