PluginProbe
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder / 3.6.0
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder v3.6.0
3.6.0 3.5.0 trunk 1.0.10 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.6.1 1.0.7 1.0.8 1.0.9 1.1 1.1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5 1.5.1 All 110 releases
buttonizer-multifunctional-button / app / Migration / ModuleLoader.php

ModuleLoader.php in Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder 3.6.0, at app/Migration/ModuleLoader.php

261 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /*
3 * SOFTWARE LICENSE INFORMATION
4 *
5 * Copyright (c) 2017 Buttonizer, all rights reserved.
6 *
7 * This file is part of Buttonizer
8 *
9 * For detailed information regarding to the licensing of
10 * this software, please review the license.txt or visit:
11 * https://buttonizer.pro/license/
12 */
13
14 namespace Buttonizer\Migration;
15
16 use Buttonizer\Core\PluginConfig;
17
18 # No script kiddies
19 defined('ABSPATH') or die('No script kiddies please!');
20
21 /**
22 * Serves an absorbed plugin's own code from inside Buttonizer.
23 *
24 * Sites still running the acquired plugin's pre-Buttonizer system keep their
25 * buttons, their settings and their admin screens exactly as they were: the
26 * code is copied under modules/ untouched, and Buttonizer loads it in place of
27 * the plugin that used to.
28 *
29 * Runs at plugin boot, not on admin_init: the module registers hooks on
30 * plugins_loaded, wp_footer and admin_menu, so it has to be in place before
31 * WordPress gets there.
32 */
33 class ModuleLoader
34 {
35 /**
36 * Marks a visit to Buttonizer's own page as the point of the click.
37 *
38 * Without it there is no way to tell "the user asked for Buttonizer" from
39 * "this link was built before the module took over", and the two need
40 * opposite answers.
41 */
42 const OWN_DASHBOARD_PARAM = 'bz_signup';
43
44 /**
45 * @var bool Whether a module is being served this request.
46 */
47 private static $serving = false;
48
49 /**
50 * @var SourcePlugin|null The module being served this request, if any.
51 */
52 private static $servingSource = null;
53
54 /**
55 * @var bool Whether Buttonizer keeps itself out of the admin sidebar.
56 */
57 private static $hostMenuHidden = false;
58
59 /**
60 * Load every module this site still needs.
61 */
62 public static function boot(): void
63 {
64 foreach (SourcePlugins::all() as $source) {
65 if (!self::shouldLoad($source)) {
66 continue;
67 }
68
69 // Decided before the module registers anything, because its
70 // adapter needs the answer while wiring itself up.
71 self::decideOwnMenu();
72
73 require_once $source->modulePath();
74
75 self::$serving = true;
76 self::$servingSource = $source;
77
78 // Everything the copied code cannot know about Buttonizer lives in
79 // the adapter, so the copy itself stays untouched.
80 $adapter = $source->moduleAdapter();
81
82 if ($adapter) {
83 $adapter::attach();
84 }
85 }
86
87 // Only when the module took Buttonizer's own place in the sidebar:
88 // that is exactly when a link built before this request — a redirect
89 // after signup, a bookmark, "Open Buttonizer" — still points at
90 // Buttonizer's own page. That page is never unregistered, so it would
91 // render its own connect screen right over the module the user is
92 // supposed to still be looking at.
93 if (self::$hostMenuHidden) {
94 add_action('admin_init', [self::class, 'redirectOwnDashboard']);
95 }
96 }
97
98 /**
99 * Send a direct hit on Buttonizer's own page to the module instead.
100 *
101 * Runs on admin_init, ahead of admin_menu: the menu is irrelevant here,
102 * this is about which page callback actually renders.
103 */
104 public static function redirectOwnDashboard(): void
105 {
106 if (!self::$servingSource || !isset($_GET['page']) || $_GET['page'] !== PluginConfig::pageSlug()) {
107 return;
108 }
109
110 // The user asked to be here. "Try now" is the one way to reach the
111 // signup screen while the old system is still being served, so sending
112 // it back to the module would make signing up impossible.
113 if (isset($_GET[self::OWN_DASHBOARD_PARAM])) {
114 return;
115 }
116
117 $moduleSlug = self::$servingSource->modulePageSlug();
118
119 if (!$moduleSlug) {
120 return;
121 }
122
123 wp_safe_redirect(admin_url('admin.php?page=' . $moduleSlug));
124 exit;
125 }
126
127 /**
128 * Is Buttonizer serving an absorbed plugin's own system right now?
129 */
130 public static function isServingModule(): bool
131 {
132 return self::$serving;
133 }
134
135 /**
136 * Leave the absorbed plugin's menu as the only one in the sidebar.
137 *
138 * The point of the module is that nothing changed for this user, and
139 * before the migration their sidebar had exactly one entry. Buttonizer's
140 * own page stays registered and reachable — by direct URL and from the
141 * "Open Buttonizer" button in the migration notice — it just does not add
142 * a second entry next to the one they already know.
143 *
144 * Only when Buttonizer has nothing of its own to show: an install with
145 * buttons in the cloud keeps its menu, hiding it would take away something
146 * the user actively uses.
147 */
148 private static function decideOwnMenu(): void
149 {
150 if (!is_admin() || ConnectionAdopter::isTargetConnected()) {
151 return;
152 }
153
154 self::$hostMenuHidden = true;
155 }
156
157 /**
158 * Is Buttonizer keeping itself out of the sidebar?
159 *
160 * The module asks before registering its screens: hanging them off a menu
161 * that is about to be removed would take them off the sidebar too, so in
162 * that case it registers itself top-level, exactly as the plugin did.
163 */
164 public static function hostMenuHidden(): bool
165 {
166 return self::$hostMenuHidden;
167 }
168
169 /**
170 * Should this source plugin's module be served by Buttonizer?
171 */
172 private static function shouldLoad(SourcePlugin $source): bool
173 {
174 if (!$source->hasModule()) {
175 return false;
176 }
177
178 // Only a system Buttonizer took over is served from here, and it keeps
179 // being served regardless of the source plugin's own flags: going by
180 // those means anything that flips one takes the user's buttons off the
181 // site. Old options on their own prove nothing either way — whether
182 // they are absorbed is MigrationManager's call, and this only follows it.
183 if (!self::wasEmbedded($source)) {
184 return false;
185 }
186
187 // The user has moved on to the cloud
188 if (self::hasMovedToCloud($source)) {
189 return false;
190 }
191
192 // The source plugin already loaded this exact code — it is the same
193 // classes and functions in the global namespace, so a second copy is a
194 // fatal redeclaration.
195 //
196 // Deliberately about the code and not about the plugin: a source plugin
197 // reactivated after the migration steps aside for the module, and going
198 // by its active flag instead would hand that user Buttonizer's signup
199 // screen in place of the screens they have been using all along.
200 if ($source->isModuleLoaded()) {
201 return false;
202 }
203
204 return true;
205 }
206
207 /**
208 * Has this site graduated from the old system to the cloud?
209 *
210 * The module was taken over while Buttonizer had no site of its own, and
211 * Buttonizer got connected: the only way to get there is the user deciding
212 * to move. Serving both would put two sets of buttons on their site — the
213 * old ones from these options and the new ones from the cloud.
214 *
215 * The answer is remembered rather than re-read from the connection, so a
216 * disconnect does not quietly put the old system back. Someone whose
217 * session expired should not find a different plugin rendering on their
218 * site; coming back is a decision, and LegacyFallback is where it is made.
219 *
220 * The options are never touched, so the buttons return exactly as they
221 * were left.
222 */
223 private static function hasMovedToCloud(SourcePlugin $source): bool
224 {
225 $state = MigrationState::get($source->id());
226
227 if (($state['result'] ?? '') !== MigrationManager::RESULT_LEGACY_MODULE) {
228 return false;
229 }
230
231 // Already on the cloud before the migration: this site always had both
232 if (!empty($state['target_connected'])) {
233 return false;
234 }
235
236 if (!empty($state['moved_to_cloud'])) {
237 return true;
238 }
239
240 if (!ConnectionAdopter::isTargetConnected()) {
241 return false;
242 }
243
244 // First request after the move. Written once, and only ever unwritten
245 // by the user asking for the old system back.
246 MigrationState::set($source->id(), ['moved_to_cloud' => true]);
247
248 return true;
249 }
250
251 /**
252 * Did Buttonizer already take this source plugin's old system over?
253 */
254 private static function wasEmbedded(SourcePlugin $source): bool
255 {
256 $state = MigrationState::get($source->id());
257
258 return ($state['result'] ?? '') === MigrationManager::RESULT_LEGACY_MODULE;
259 }
260 }
261