PluginProbe
Metricool – Social media and site statistics / trunk
Metricool – Social media and site statistics vtrunk
2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
metricool / app / Services / OtherPluginService.php

OtherPluginService.php in Metricool – Social media and site statistics trunk, at app/Services/OtherPluginService.php

284 lines 8.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
284