PluginProbe
Metricool – Social media and site statistics / 2.1.1
Metricool – Social media and site statistics v2.1.1
2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
← All changes | app/Services/OtherPluginService.php +283 -0 2.0.1 → 2.1.1 View file →
@@ -1,0 +1,283 @@
1 +<?php
2 +
3 +declare(strict_types=1);
4 +
5 +namespace Metricool\Services;
6 +
7 +if (!defined('ABSPATH')) {
8 + exit;
9 +}
10 +
11 +use Metricool\Support\Helpers\Storage;
12 +
13 +class OtherPluginService
14 +{
15 + /**
16 + * Should be a Storage object based on one entry in the plugins config
17 + * {@see config/plugins.php}
18 + */
19 + protected Storage $pluginConfig;
20 +
21 + /**
22 + * @param array $pluginConfig Optional so {@see setPluginConfig} can be skipped
23 + */
24 + public function __construct(array $pluginConfig = [])
25 + {
26 + $this->pluginConfig = new Storage($pluginConfig);
27 + }
28 +
29 + /**
30 + * Use this method as the default way to set the plugin config. For an
31 + * example see {@see \Metricool\Http\Endpoints\OtherPluginEndpoints}
32 + */
33 + public function setPluginConfig(array $pluginConfig): void
34 + {
35 + $this->pluginConfig = new Storage($pluginConfig);
36 + }
37 +
38 + /**
39 + * Method returns the url fitting for the context. If a plugin is
40 + * upgradable, the upgrade_url is returned, otherwise the url entry.
41 + */
42 + public function getPluginUrl(): string
43 + {
44 + if ($this->pluginCanBeUpgraded()) {
45 + return $this->pluginConfig->getUrl('upgrade_url');
46 + }
47 +
48 + return $this->pluginConfig->getUrl('url');
49 + }
50 +
51 + /**
52 + * Method returns if the plugin is active.
53 + */
54 + public function getPluginActive(): bool
55 + {
56 + return $this->pluginIsActive() || $this->premiumPluginIsInstalled();
57 + }
58 +
59 + /**
60 + * Method returns the action fitting for the context of the plugin.
61 + */
62 + public function getAvailablePluginAction(): string
63 + {
64 + if ($this->premiumPluginIsInstalled()) {
65 + return 'installed';
66 + }
67 +
68 + if ($this->pluginIsDownloadable()) {
69 + return 'download';
70 + }
71 +
72 + if ($this->pluginCanBeActivated()) {
73 + return 'activate';
74 + }
75 +
76 + if ($this->pluginCanBeUpgraded()) {
77 + return 'upgrade-to-premium';
78 + }
79 +
80 + return 'installed';
81 + }
82 +
83 + /**
84 + * Execute action for another plugin
85 + * @throws \Exception If the plugin info could not be retrieved
86 + */
87 + public function executeAction(string $action): void
88 + {
89 + if (current_user_can('install_plugins') === false) {
90 + return;
91 + }
92 +
93 + ob_start();
94 +
95 + switch ($action) {
96 + case 'download':
97 + $this->downloadCurrentPlugin();
98 + break;
99 + case 'activate':
100 + $this->activateCurrentPlugin();
101 + break;
102 + default:
103 + break;
104 + }
105 +
106 + ob_get_clean();
107 + }
108 +
109 + /**
110 + * Download the other plugin currently stored in the plugin config
111 + * property.
112 + * @throws \Exception If the plugin info could not be retrieved
113 + */
114 + protected function downloadCurrentPlugin(): bool
115 + {
116 + if (!current_user_can('install_plugins')) {
117 + return false;
118 + }
119 +
120 + $transientName = 'rsp_plugin_download_active';
121 + if (get_transient($transientName) === $this->pluginConfig->getString('slug')) {
122 + return true;
123 + }
124 +
125 + set_transient($transientName, $this->pluginConfig->getString('slug'), MINUTE_IN_SECONDS);
126 +
127 + try {
128 + $pluginInfo = $this->getCurrentPluginInfo();
129 + } catch (\Exception $e) {
130 + return false;
131 + }
132 +
133 + $downloadLink = esc_url_raw($pluginInfo->versions['trunk']);
134 +
135 + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
136 + require_once ABSPATH . 'wp-admin/includes/file.php';
137 + include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
138 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
139 +
140 + $skin = new \WP_Ajax_Upgrader_Skin();
141 + $upgrader = new \Plugin_Upgrader($skin);
142 + $result = $upgrader->install($downloadLink);
143 +
144 + if (is_wp_error($result)) {
145 + return false;
146 + }
147 +
148 + delete_transient($transientName);
149 + return true;
150 + }
151 +
152 + /**
153 + * Activate the other plugin currently stored in the plugin config
154 + * property.
155 + */
156 + protected function activateCurrentPlugin(): bool
157 + {
158 + if (!current_user_can('install_plugins')) {
159 + return false;
160 + }
161 +
162 + $slug = $this->pluginConfig->getString('activation_slug');
163 +
164 + //when activated from the network admin, we assume the user wants network activated
165 + $networkwide = is_multisite() && is_network_admin();
166 + if (!defined('DOING_CRON')) {
167 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- DOING_CRON is a WordPress core constant.
168 + define('DOING_CRON', true);
169 + }
170 +
171 + if (!function_exists('activate_plugin')) {
172 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
173 + }
174 +
175 + $result = activate_plugin($slug, '', $networkwide);
176 + if (is_wp_error($result)) {
177 + return false;
178 + }
179 +
180 + $this->cancelShepherdTour();
181 + return true;
182 + }
183 +
184 + /**
185 + * Helper method to check if the current plugin is a premium plugin and if
186 + * it is active.
187 + */
188 + protected function premiumPluginIsInstalled(): bool
189 + {
190 + return $this->pluginConfig->has('constant_premium')
191 + && defined($this->pluginConfig->getString('constant_premium'));
192 + }
193 +
194 + /**
195 + * Helper method to check if the current plugin is downloadable.
196 + */
197 + protected function pluginIsDownloadable(): bool
198 + {
199 + return $this->pluginFileExists() === false;
200 + }
201 +
202 + /**
203 + * Helper method to check if the current plugin can be activated.
204 + */
205 + protected function pluginCanBeActivated(): bool
206 + {
207 + return $this->pluginFileExists() && ($this->pluginIsActive() === false);
208 + }
209 +
210 + /**
211 + * Helper method to check if the current plugin can be upgraded. This means
212 + * the premium version is downloaded, but not yet activated.
213 + */
214 + protected function pluginCanBeUpgraded(): bool
215 + {
216 + return $this->pluginConfig->has('constant_premium')
217 + && !defined($this->pluginConfig->getString('constant_premium'));
218 + }
219 +
220 + /**
221 + * Helper method to check if the current plugin file exists.
222 + */
223 + protected function pluginFileExists(): bool
224 + {
225 + return file_exists(trailingslashit(WP_PLUGIN_DIR) . $this->pluginConfig->getString('activation_slug'));
226 + }
227 +
228 + /**
229 + * Helper method to check if the current plugin is active.
230 + */
231 + public function pluginIsActive(): bool
232 + {
233 + if (!function_exists('is_plugin_active')) {
234 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
235 + }
236 +
237 + return is_plugin_active($this->pluginConfig->getString('activation_slug'));
238 + }
239 +
240 + /**
241 + * Method returns the plugin info for the current plugin. Because we pass
242 + * the action 'plugin_information' to the plugins_api function, an object is
243 + * returned if the plugin is found, otherwise a WP_Error.
244 + * @throws \Exception If the plugin info could not be retrieved
245 + */
246 + protected function getCurrentPluginInfo(): object
247 + {
248 + $transientName = 'rsp_' . $this->pluginConfig->getString('slug') . '_plugin_info';
249 + $pluginInfo = get_transient($transientName);
250 +
251 + if (!empty($pluginInfo)) {
252 + return $pluginInfo;
253 + }
254 +
255 + if (function_exists('plugins_api') === false) {
256 + require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
257 + }
258 +
259 + $pluginInfo = plugins_api('plugin_information', [
260 + 'slug' => $this->pluginConfig->getString('slug'),
261 + ]);
262 +
263 + if (is_wp_error($pluginInfo)) {
264 + throw new \Exception('Unable to get plugin info');
265 + }
266 +
267 + set_transient($transientName, $pluginInfo, WEEK_IN_SECONDS);
268 + return $pluginInfo;
269 + }
270 +
271 + /**
272 + * Cancel shepherd tour
273 + * @todo - This should be moved to a separate service as its not specific to
274 + * this class. Following SRP principle.
275 + */
276 + public function cancelShepherdTour(): void
277 + {
278 + $prefix = $this->pluginConfig->getString('options_prefix');
279 + update_site_option($prefix . '_tour_started', false);
280 + update_site_option($prefix . '_tour_shown_once', true);
281 + delete_transient($prefix . '_redirect_to_settings');
282 + }
283 +}