PluginProbe
Extendify / 3.1.1
Extendify v3.1.1
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 0.6.0 All 127 releases
extendify / app / Toolbar / Frontend.php

Frontend.php in Extendify 3.1.1, at app/Toolbar/Frontend.php

254 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Front-end loader for the simple Extendify toolbar.
5 *
6 * Renders a minimal toolbar at the top of the page (AI Agent /
7 * Quick Edit / Edit page / WP Admin) to replace the WordPress core
8 * admin bar for editors who prefer it. The core admin bar stays in
9 * the DOM so the Agent's mounted button still exists for the toolbar's
10 * "AI Agent" link to drive — we just hide it via CSS.
11 *
12 * Render decision: front end + logged in + Extendify's required
13 * capability (default manage_options) + the user's core "Show Toolbar
14 * when viewing site" preference is on + the user's Toolbar Style
15 * setting resolves to "simple". The default style is Launch-aware:
16 * 'simple' when the site has finished Launch, 'full' before it.
17 */
18
19 namespace Extendify\Toolbar;
20
21 defined('ABSPATH') || die('No direct access.');
22
23 use Extendify\Config;
24 use Extendify\PartnerData;
25
26 class Frontend
27 {
28 // phpcs:ignore PSR12.Properties.ConstantVisibility.NotFound -- 7.0 floor: no const visibility
29 const STYLE_META = 'extendify_toolbar_style';
30
31 public function __construct()
32 {
33 \add_action('wp_enqueue_scripts', [$this, 'loadScriptsAndStyles']);
34 \add_action('wp_body_open', [$this, 'render'], 1);
35 \add_action('wp_head', [$this, 'hideCoreAdminBar'], 1);
36 }
37
38 /**
39 * Resolved style for the current user — 'simple' or 'full'.
40 *
41 * Honors the per-user setting; falls back to a Launch-aware
42 * default when the user hasn't picked one.
43 *
44 * @param int|null $userId
45 * @return string
46 */
47 public static function style($userId = null)
48 {
49 $userId = $userId ? (int) $userId : \get_current_user_id();
50 if (!$userId) {
51 return self::defaultStyle();
52 }
53 $val = \get_user_meta($userId, self::STYLE_META, true);
54 if ($val === 'simple' || $val === 'full') {
55 return $val;
56 }
57 return self::defaultStyle();
58 }
59
60 /**
61 * The toolbar style the user lands on when they haven't picked
62 * one explicitly. Simple after Launch, full before.
63 *
64 * @return string
65 */
66 public static function defaultStyle()
67 {
68 return Config::$launchCompleted ? 'simple' : 'full';
69 }
70
71 /**
72 * True iff the user has the core "Show Toolbar when viewing
73 * site" pref on. We respect it — turning the toolbar off in the
74 * core profile setting hides ours too.
75 *
76 * @param int|null $userId
77 * @return bool
78 */
79 protected static function corePrefOn($userId = null)
80 {
81 $userId = $userId ? (int) $userId : \get_current_user_id();
82 if (!$userId) {
83 return false;
84 }
85 return 'true' === \get_user_option('show_admin_bar_front', $userId);
86 }
87
88 /**
89 * Whether to render the simple toolbar on this request.
90 *
91 * @return bool
92 */
93 public static function shouldRender()
94 {
95 if (\is_admin() || !\is_user_logged_in() || !\current_user_can(Config::$requiredCapability)) {
96 return false;
97 }
98 // The Customizer preview iframe is a front-end render — the toolbar
99 // belongs only on the live, top-level page.
100 if (\is_customize_preview()) {
101 return false;
102 }
103 if (!self::corePrefOn()) {
104 return false;
105 }
106 return self::style() === 'simple';
107 }
108
109 /**
110 * @return void
111 */
112 public function loadScriptsAndStyles()
113 {
114 if (!self::shouldRender()) {
115 return;
116 }
117
118 $version = constant('EXTENDIFY_DEVMODE') ? uniqid() : Config::$version;
119 $manifest = Config::$assetManifest;
120 $assetKey = 'extendify-toolbar.php';
121 $jsKey = 'extendify-toolbar.js';
122 $cssKey = 'extendify-toolbar.css';
123
124 if (empty($manifest[$assetKey]) || empty($manifest[$jsKey])) {
125 return;
126 }
127
128 $scriptAssetPath = EXTENDIFY_PATH . 'public/build/' . $manifest[$assetKey];
129 $fallback = ['dependencies' => [], 'version' => $version];
130 $scriptAsset = file_exists($scriptAssetPath) ? require $scriptAssetPath : $fallback;
131
132 \wp_enqueue_script(
133 Config::$slug . '-toolbar-scripts',
134 EXTENDIFY_BASE_URL . 'public/build/' . $manifest[$jsKey],
135 $scriptAsset['dependencies'],
136 $scriptAsset['version'],
137 true
138 );
139
140 \wp_set_script_translations(
141 Config::$slug . '-toolbar-scripts',
142 'extendify-local',
143 EXTENDIFY_PATH . 'languages/js'
144 );
145
146 if (!empty($manifest[$cssKey])) {
147 \wp_enqueue_style(
148 Config::$slug . '-toolbar-styles',
149 EXTENDIFY_BASE_URL . 'public/build/' . $manifest[$cssKey],
150 [],
151 $version,
152 'all'
153 );
154 }
155 }
156
157 /**
158 * Render the toolbar at the top of <body>.
159 *
160 * @return void
161 */
162 public function render()
163 {
164 if (!self::shouldRender()) {
165 return;
166 }
167
168 $editLink = '';
169 $queried = \get_queried_object();
170 if ($queried instanceof \WP_Post) {
171 $maybe = \get_edit_post_link($queried->ID);
172 if ($maybe) {
173 $editLink = $maybe;
174 }
175 }
176
177 // phpcs:disable Generic.Files.LineLength.TooLong -- inline SVG + HTML template markup
178 ?>
179 <div id="extendify-toolbar" role="navigation" aria-label="<?php \esc_attr_e('Site toolbar', 'extendify-local'); ?>">
180 <div class="ext-tb-section ext-tb-left">
181 <button type="button" class="ext-tb-btn ext-tb-ai-agent" id="ext-tb-ai-agent" aria-label="<?php \esc_attr_e('Open the AI Agent', 'extendify-local'); ?>">
182 <svg class="ext-tb-magic" width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
183 <path d="M17.0909 9.81818L18 7.81818L20 6.90909L18 6L17.0909 4L16.1818 6L14.1818 6.90909L16.1818 7.81818L17.0909 9.81818Z" fill="currentColor"/>
184 <path d="M17.0909 14.1818L16.1818 16.1818L14.1818 17.0909L16.1818 18L17.0909 20L18 18L20 17.0909L18 16.1818L17.0909 14.1818Z" fill="currentColor"/>
185 <path d="M11.6364 10.1818L9.81818 6.18182L8 10.1818L4 12L8 13.8182L9.81818 17.8182L11.6364 13.8182L15.6364 12L11.6364 10.1818ZM10.5382 12.72L9.81818 14.3055L9.09818 12.72L7.51273 12L9.09818 11.28L9.81818 9.69455L10.5382 11.28L12.1236 12L10.5382 12.72Z" fill="currentColor"/>
186 </svg>
187 <span class="ext-tb-ai-label"><?php \esc_html_e('AI Agent', 'extendify-local'); ?></span>
188 </button>
189 <?php if (PartnerData::setting('showQuickEdit') || Config::preview('quick-edit')) : ?>
190 <button type="button" class="ext-tb-btn ext-tb-quick-edit" id="ext-tb-quick-edit" role="switch" aria-checked="false">
191 <span class="ext-tb-toggle" aria-hidden="true"><span class="ext-tb-toggle-thumb"></span></span>
192 <span><?php \esc_html_e('Quick Edit', 'extendify-local'); ?></span>
193 </button>
194 <?php endif; ?>
195 </div>
196 <div class="ext-tb-section ext-tb-right">
197 <?php if ($editLink) : ?>
198 <a class="ext-tb-btn ext-tb-edit" href="<?php echo \esc_url($editLink); ?>" target="_blank" rel="noopener noreferrer">
199 <svg class="ext-tb-pencil" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
200 <path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 0 0 0-1.41l-2.34-2.34a1 1 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" fill="currentColor"/>
201 </svg>
202 <span><?php /* translators: button label linking to the WordPress block editor. */ \esc_html_e('Block Editor', 'extendify-local'); ?></span>
203 <svg class="ext-tb-external" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
204 <path d="M7 1.5h3.5V5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
205 <path d="M10.5 1.5L5.5 6.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
206 <path d="M9.5 7v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1H5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
207 </svg>
208 <span class="screen-reader-text"><?php \esc_html_e('(opens in a new tab)', 'extendify-local'); ?></span>
209 </a>
210 <?php endif; ?>
211 <a class="ext-tb-btn ext-tb-admin-link" href="<?php echo \esc_url(\admin_url()); ?>" target="_blank" rel="noopener noreferrer">
212 <span><?php \esc_html_e('WP Admin', 'extendify-local'); ?></span>
213 <svg class="ext-tb-external" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
214 <path d="M7 1.5h3.5V5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
215 <path d="M10.5 1.5L5.5 6.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
216 <path d="M9.5 7v3a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1H5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
217 </svg>
218 <span class="screen-reader-text"><?php \esc_html_e('(opens in a new tab)', 'extendify-local'); ?></span>
219 </a>
220 </div>
221 </div>
222 <?php
223 // phpcs:enable Generic.Files.LineLength.TooLong
224 }
225
226 /**
227 * Hide the core admin bar visually and keep the top-margin reservation
228 * so theme layouts that account for it still flow correctly. We can't
229 * unconditionally drop the bar from the DOM — the Agent script mounts
230 * its own button into `#wp-admin-bar-extendify-agent-btn`, and the
231 * toolbar's "AI Agent" link drives that mounted button at click time.
232 *
233 * @return void
234 */
235 public function hideCoreAdminBar()
236 {
237 if (!self::shouldRender()) {
238 return;
239 }
240
241 ?>
242 <style id="extendify-toolbar-reset">
243 #wpadminbar { display: none !important; }
244 html { margin-top: 32px !important; }
245 * html body { margin-top: 32px !important; }
246 @media screen and (max-width: 782px) {
247 html { margin-top: 46px !important; }
248 * html body { margin-top: 46px !important; }
249 }
250 </style>
251 <?php
252 }
253 }
254