PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 All 128 releases
extendify / bootstrap.php

bootstrap.php in Extendify 3.2.2, at bootstrap.php

323 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Bootstrap the application
5 */
6
7 defined('ABSPATH') || die('No direct access.');
8
9 use Extendify\AdminPageRouter;
10 use Extendify\Assist\Admin as AssistAdmin;
11 use Extendify\Agent\Admin as AgentAdmin;
12 use Extendify\ComingSoon\Frontend as ComingSoonFrontend;
13 use Extendify\Config;
14 use Extendify\Draft\Admin as DraftAdmin;
15 use Extendify\HelpCenter\Admin as HelpCenterAdmin;
16 use Extendify\Insights;
17 use Extendify\Launch\Admin as LaunchAdmin;
18 use Extendify\AutoLaunch\Admin as AutoLaunchAdmin;
19 use Extendify\Library\Admin as LibraryAdmin;
20 use Extendify\Library\Frontend as LibraryFrontend;
21 use Extendify\Mcp\Server as McpServer;
22 use Extendify\Mcp\OAuth\Metadata as McpMetadata;
23 use Extendify\Mcp\OAuth\Authorize as McpAuthorize;
24 use Extendify\Mcp\OAuth\TokenEndpoint as McpTokenEndpoint;
25 use Extendify\Mcp\OAuth\RegistrationEndpoint as McpRegistrationEndpoint;
26 use Extendify\Mcp\Profile as McpProfile;
27 use Extendify\Mcp\Lifecycle as McpLifecycle;
28 use Extendify\Mcp\Jobs as McpJobs;
29 use Extendify\Agent\Frontend as AgentFrontend;
30 use Extendify\QuickEdit\Frontend as QuickEditFrontend;
31 use Extendify\PageCreator\Admin as PageCreatorAdmin;
32 use Extendify\PartnerData;
33 use Extendify\Notifications\Admin as NotificationsAdmin;
34 use Extendify\Notifications\Frontend as NotificationsFrontend;
35 use Extendify\Toolbar\Admin as ToolbarAdmin;
36 use Extendify\Toolbar\Frontend as ToolbarFrontend;
37 use Extendify\Recommendations\Admin as RecommendationsAdmin;
38 use Extendify\PluginNotifications\Admin as PluginNotificationsAdmin;
39 use Extendify\Shared\Admin as SharedAdmin;
40 use Extendify\Shared\DataProvider\NotificationData;
41 use Extendify\Shared\DataProvider\ResourceData;
42 use Extendify\Shared\Services\BlockStyleVariations;
43 use Extendify\Shared\Services\ForcePluginReinstall;
44 use Extendify\Shared\Services\Import\ImagesImporter;
45 use Extendify\Shared\Services\PluginRedirectDisabler;
46 use Extendify\Shared\Services\PluginsActivation\SimplyBook;
47 use Extendify\Shared\Services\VersionMigrator;
48 use Extendify\SiteVisibility;
49
50 if (!defined('EXTENDIFY_REQUIRED_CAPABILITY')) {
51 define('EXTENDIFY_REQUIRED_CAPABILITY', 'manage_options');
52 }
53
54 if (!defined('EXTENDIFY_PATH')) {
55 define('EXTENDIFY_PATH', \plugin_dir_path(__FILE__));
56 }
57
58 if (!defined('EXTENDIFY_URL')) {
59 define('EXTENDIFY_URL', \plugin_dir_url(__FILE__));
60 }
61
62 if (is_readable(EXTENDIFY_PATH . 'vendor/autoload.php')) {
63 require EXTENDIFY_PATH . 'vendor/autoload.php';
64 }
65
66 // Registered after the autoloader — these callbacks reference classes it loads.
67 add_filter('http_request_args', function ($args, $url) {
68 $extendifyHosts = array_filter(array_map(function ($host) {
69 return wp_parse_url($host, PHP_URL_HOST);
70 }, \Extendify\Constants::serviceUrls()));
71
72 $host = wp_parse_url($url, PHP_URL_HOST);
73 if ($host && (str_contains($host, 'extendify') || in_array($host, $extendifyHosts, true))) {
74 $args['timeout'] = 45;
75 }
76
77 return $args;
78 }, 100, 2);
79
80 add_action('update_option', function ($option) {
81 if (in_array($option, ['WPLANG', 'blogname'], true)) {
82 \delete_transient('extendify_recommendations');
83 \delete_transient('extendify_domains');
84 \delete_transient('extendify_supportArticles');
85 }
86
87 // Delete the partner transient so we can fetch new data when the locale is switched.
88 if (($option === 'WPLANG') && get_transient('extendify_partner_data_cache_check')) {
89 delete_transient('extendify_partner_data_cache_check');
90 PartnerData::getPartnerData();
91 }
92 });
93
94 // Delete the partner transient so we can fetch new data when the locale is switched via WP-CLI.
95 add_action('cli_init', function () {
96 $command = sanitize_text_field(wp_unslash(($_SERVER['argv'][1] ?? '')));
97 if ($command === 'language' && get_transient('extendify_partner_data_cache_check')) {
98 delete_transient('extendify_partner_data_cache_check');
99 PartnerData::getPartnerData();
100 }
101 });
102
103 if (!defined('EXTENDIFY_IS_THEME_EXTENDABLE')) {
104 define('EXTENDIFY_IS_THEME_EXTENDABLE', get_option('stylesheet') === 'extendable');
105 }
106
107 (static function () {
108 // This file should have no dependencies and always load.
109 new LibraryFrontend();
110 // This file hooks into an external task and should always load.
111 new Insights();
112 // This class set up the image import check scheduler.
113 new ImagesImporter();
114
115 // Run various database updates depending on the plugin version.
116 new VersionMigrator();
117
118 // Force-reinstall support for /wp/v2/plugins (opt-in via request header).
119 ForcePluginReinstall::register();
120
121 // Keeps our design variation names registered so global styles can hold them.
122 BlockStyleVariations::register();
123
124 // Their registration callback comes in unauthenticated, so this cannot sit behind the capability gate.
125 SimplyBook::register();
126
127 // This class will fetch and cache partner data to be used
128 // throughout every class below. If opt in.
129 new PartnerData();
130
131 // Assign A/B test variants now that partner data is loaded.
132 Insights::setup((array) PartnerData::setting('activeTests'));
133
134 // Set up scheduled cache (if opt-in and active).
135 if (!PartnerData::setting('deactivated')) {
136 ResourceData::scheduleCache();
137 NotificationData::scheduleCache();
138 }
139
140 // A token request carries no session and would fail the capability check.
141 if (defined('EXTENDIFY_PARTNER_ID') && !PartnerData::setting('deactivated')) {
142 McpServer::register();
143 McpMetadata::register();
144 McpAuthorize::register();
145 McpTokenEndpoint::register();
146 McpRegistrationEndpoint::register();
147 McpProfile::register();
148 McpLifecycle::register();
149 McpJobs::register();
150 }
151
152 if (!current_user_can(EXTENDIFY_REQUIRED_CAPABILITY)) {
153 if (!SiteVisibility::isPublished()) {
154 new ComingSoonFrontend();
155 }
156
157 return;
158 }
159
160 // The config class will collect information about the
161 // partner and plugin, so it's easier to access.
162 new Config();
163 if (!defined('EXTENDIFY_DEVMODE')) {
164 define('EXTENDIFY_DEVMODE', Config::$environment === 'DEVELOPMENT');
165 }
166
167 // This is a global "loader" class that loads in any assets that are shared everywhere.
168 new SharedAdmin();
169 // This class will handle loading library assets.
170 new LibraryAdmin();
171 if (PartnerData::setting('hidePluginNotifications')) {
172 new PluginNotificationsAdmin();
173 }
174
175 // Only load these if the partner ID is set. These are all opt-in features.
176 if (!Config::$partnerId && !constant('EXTENDIFY_DEVMODE')) {
177 return;
178 }
179
180 if (PartnerData::setting('deactivated')) {
181 return;
182 }
183
184 // This class handles the admin pages required for the plugin.
185 new AdminPageRouter();
186 new PluginRedirectDisabler();
187
188 // This class will handle loading page creator assets.
189 if (PartnerData::setting('showAIPageCreation') || constant('EXTENDIFY_DEVMODE')) {
190 new PageCreatorAdmin();
191 }
192
193 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
194 $page = sanitize_text_field(wp_unslash($_GET['page'] ?? ''));
195
196 if ($page === 'extendify-launch') {
197 new LaunchAdmin();
198 return;
199 }
200
201 if ($page === 'extendify-auto-launch') {
202 new AutoLaunchAdmin();
203 return;
204 }
205
206 // The remaining classes handle loading assets for each individual products.
207 // They are essentially asset loading classes.
208 if ($page === 'extendify-assist') {
209 new AssistAdmin();
210 }
211
212 $extendifyShowAgent = constant('EXTENDIFY_IS_THEME_EXTENDABLE')
213 && Config::$launchCompleted
214 && (PartnerData::setting('showAIAgents') || Config::preview('ai-agent'));
215
216 // True when the AI Agent is actually loaded this request — the gate
217 // above, or dev mode (so it can be exercised locally). Quick Edit and
218 // the simple toolbar both build on the Agent, so they share this
219 // decision rather than gating themselves independently.
220 $extendifyLoadAgent = $extendifyShowAgent || constant('EXTENDIFY_DEVMODE');
221
222 if (!$extendifyShowAgent) {
223 new HelpCenterAdmin();
224 }
225
226 if (PartnerData::setting('showProductRecommendations') || constant('EXTENDIFY_DEVMODE')) {
227 new RecommendationsAdmin();
228 }
229
230 new NotificationsAdmin();
231 new NotificationsFrontend();
232
233 if (PartnerData::setting('showDraft') || constant('EXTENDIFY_DEVMODE')) {
234 new DraftAdmin();
235 }
236
237 if ($extendifyLoadAgent) {
238 new AgentAdmin();
239 new AgentFrontend();
240 }
241
242 if ($extendifyLoadAgent) {
243 new QuickEditFrontend();
244 }
245
246 // Simple Extendify toolbar — replaces the WordPress core admin
247 // bar on the front end with a minimal AI Agent / Quick Edit /
248 // Edit / WP Admin bar. The user-profile "Toolbar Style" setting
249 // controls per-user override; the default is Launch-aware
250 // ('simple' post-Launch, 'full' pre-Launch). Gated on the Agent
251 // too: the toolbar's "AI Agent" button drives the Agent's mounted
252 // admin-bar button, so without the Agent it would present a dead
253 // button — never ship the toolbar unless the Agent is there.
254 if ($extendifyLoadAgent && (PartnerData::setting('showSimpleToolbar') || Config::preview('simple-toolbar'))) {
255 new ToolbarAdmin();
256 new ToolbarFrontend();
257 }
258 })();
259
260 (static function () {
261 if (!current_user_can(EXTENDIFY_REQUIRED_CAPABILITY)) {
262 return;
263 }
264
265 // This loads in all the REST API routes used by the plugin.
266 require EXTENDIFY_PATH . 'routes/api.php';
267 })();
268
269 // This file is used to update the plugin and removed before w.org release.
270 if (is_readable(EXTENDIFY_PATH . '/updater.php')) {
271 require EXTENDIFY_PATH . 'updater.php';
272 }
273
274 add_action('init', function () {
275 load_plugin_textdomain('extendify-local', false, dirname(plugin_basename(__FILE__)) . '/languages/php');
276 });
277
278 // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
279 add_filter('load_script_translation_file', function ($file, $handle, $domain) {
280 if ($domain !== 'extendify-local') {
281 return $file;
282 }
283
284 return extendifyResolveFallbackLocaleFile($file);
285 }, 10, 3);
286
287 add_filter('load_textdomain_mofile', function ($mofile, $domain) {
288 if ($domain !== 'extendify-local') {
289 return $mofile;
290 }
291
292 return extendifyResolveFallbackLocaleFile($mofile);
293 }, 10, 2);
294
295 /**
296 * Resolves a fallback translation file path if the requested locale is unsupported.
297 *
298 * This function checks if the current file path includes an unsupported locale
299 * and replaces it with a supported fallback (e.g. es_AR → es_ES),
300 * as long as the fallback file exists on disk.
301 *
302 * @param string $filepath The original .mo or .json translation file path.
303 * @return string The fallback file path if it exists, or the original one.
304 */
305
306 function extendifyResolveFallbackLocaleFile($filepath)
307 {
308 if (str_contains($filepath, '-es_') && !file_exists($filepath)) {
309 $fallbackFile = preg_replace('/-es_[A-Z]{2}/', '-es_ES', $filepath);
310 if ($fallbackFile && file_exists($fallbackFile)) {
311 return $fallbackFile;
312 }
313 }
314
315 return $filepath;
316 }
317
318 // To cover legacy conflicts.
319 // phpcs:ignore
320 class ExtendifySdk
321 {
322 }
323