Changeset 3768035
- Timestamp:
- 10/27/2026 08:36:17 AM (74 minutes ago)
- Location:
- storelly-product-builder-for-woocommerce
- Files:
-
- 4 added
- 32 edited
- 1 copied
-
tags/1.9.0 (copied) (copied from storelly-product-builder-for-woocommerce/trunk )
-
tags/1.9.0/includes/admin/class-admin-menu.php (modified) (6 diffs)
-
tags/1.9.0/includes/admin/class-settings-tabs.php (added)
-
tags/1.9.0/includes/class-admin-options.php (modified) (12 diffs)
-
tags/1.9.0/includes/class-global-import-admin.php (modified) (1 diff)
-
tags/1.9.0/includes/class-license-manager.php (modified) (2 diffs)
-
tags/1.9.0/includes/email/class-email-admin.php (modified) (1 diff)
-
tags/1.9.0/readme.txt (modified) (3 diffs)
-
tags/1.9.0/static/css/admin-menu.css (modified) (1 diff)
-
tags/1.9.0/static/css/overview.css (modified) (3 diffs)
-
tags/1.9.0/static/css/storelly-admin-ui.css (modified) (1 diff)
-
tags/1.9.0/static/js/overview-stats.js (added)
-
tags/1.9.0/storelly-product-builder-for-woocommerce.php (modified) (3 diffs)
-
tags/1.9.0/views/license.php (modified) (1 diff)
-
tags/1.9.0/views/menu-settings.php (modified) (1 diff)
-
tags/1.9.0/views/options/edit-option.php (modified) (1 diff)
-
tags/1.9.0/views/overview.php (modified) (8 diffs)
-
tags/1.9.0/views/setup-wizard/landing.php (modified) (1 diff)
-
tags/1.9.0/views/setup-wizard/woo-seed.php (modified) (1 diff)
-
trunk/includes/admin/class-admin-menu.php (modified) (6 diffs)
-
trunk/includes/admin/class-settings-tabs.php (added)
-
trunk/includes/class-admin-options.php (modified) (12 diffs)
-
trunk/includes/class-global-import-admin.php (modified) (1 diff)
-
trunk/includes/class-license-manager.php (modified) (2 diffs)
-
trunk/includes/email/class-email-admin.php (modified) (1 diff)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/static/css/admin-menu.css (modified) (1 diff)
-
trunk/static/css/overview.css (modified) (3 diffs)
-
trunk/static/css/storelly-admin-ui.css (modified) (1 diff)
-
trunk/static/js/overview-stats.js (added)
-
trunk/storelly-product-builder-for-woocommerce.php (modified) (3 diffs)
-
trunk/views/license.php (modified) (1 diff)
-
trunk/views/menu-settings.php (modified) (1 diff)
-
trunk/views/options/edit-option.php (modified) (1 diff)
-
trunk/views/overview.php (modified) (8 diffs)
-
trunk/views/setup-wizard/landing.php (modified) (1 diff)
-
trunk/views/setup-wizard/woo-seed.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
storelly-product-builder-for-woocommerce/tags/1.9.0/includes/admin/class-admin-menu.php
r3568219 r3768035 9 9 * 1. Re-orders the existing submenu items into three task bands 10 10 * (BUILD / SELL / CONFIGURE) and injects non-clickable section headings. 11 * 2. Swaps the washed-out PNG menu icon for a crisp inline SVG.11 * 2. Swaps the washed-out PNG menu icon for an SVG mask drawn in currentColor. 12 12 * 13 13 * Safety: the re-order is loss-proof — any submenu item not named in the … … 64 64 protected static function desired_order() { 65 65 $opts = defined( 'SPBWC_PB_OPTIONS_SLUG' ) ? SPBWC_PB_OPTIONS_SLUG : 'storelly-product-builder-for-woocommerce-options'; 66 $emails = defined( 'SPBWC_PB_EMAILS_SLUG' ) ? SPBWC_PB_EMAILS_SLUG : 'storelly-product-builder-for-woocommerce-emails';67 66 68 67 return array( … … 85 84 array( 'heading', __( 'Configure', 'storelly-product-builder-for-woocommerce' ), 'configure' ), 86 85 $opts, 87 SPBWC_PB_LICENSE_SLUG,88 $emails,89 $opts . '/global-import',90 86 ); 91 87 } … … 135 131 } 136 132 133 // Pages folded into Settings tabs stay registered (their URLs, caps and 134 // asset hooks are unchanged) but are hidden from the menu by class. 135 $hidden = class_exists( 'SPBWC_Settings_Tabs' ) ? SPBWC_Settings_Tabs::moved_pages() : array(); 136 137 137 // Loss-proof: append anything we did not explicitly place. 138 138 foreach ( $submenu[ $parent ] as $item ) { 139 139 $slug = isset( $item[2] ) ? $item[2] : ''; 140 140 if ( '' !== $slug && empty( $used[ $slug ] ) ) { 141 if ( in_array( $slug, $hidden, true ) ) { 142 $item[4] = ( isset( $item[4] ) ? $item[4] . ' ' : '' ) . SPBWC_Settings_Tabs::HIDDEN_CLASS; 143 } 141 144 $ordered[] = $item; 142 145 } … … 147 150 148 151 /** 149 * Replace the top-level menu icon (index 6 of its $menu row) with a 150 * crisp inline SVG so it reads cleanly on the dark admin sidebar. 152 * Replace the top-level menu icon (index 6 of its $menu row) with 'none': 153 * WP then prints an empty .wp-menu-image that admin-menu.css fills with 154 * the Storelly mark as a CSS mask in currentColor, so the icon follows 155 * WP's idle / hover / current colours like a dashicon. 151 156 * 152 157 * @param array $menu The global $menu (by reference via global). … … 157 162 return; 158 163 } 159 $svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a7aaad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polygon points='12 2 2 7 12 12 22 7 12 2'/><polyline points='2 17 12 22 22 17'/><polyline points='2 12 12 17 22 12'/></svg>";160 $uri = 'data:image/svg+xml;base64,' . base64_encode( str_replace( '%23', '#', $svg ) );161 164 foreach ( $menu as $key => $row ) { 162 165 if ( isset( $row[2] ) && $row[2] === $parent && isset( $menu[ $key ][6] ) ) { 163 $menu[ $key ][6] = $uri;166 $menu[ $key ][6] = 'none'; 164 167 break; 165 168 } -
storelly-product-builder-for-woocommerce/tags/1.9.0/includes/class-admin-options.php
r3761204 r3768035 40 40 // REST API — Pricing Options grid (GET, read-only, faster than admin-ajax.php). 41 41 add_action( 'rest_api_init', array( $this, 'spbwc_register_rest_routes' ) ); 42 // Menu badge cache (Custom Orders to fulfil): drop it when an order moves. 43 add_action( 'woocommerce_order_status_changed', array( __CLASS__, 'flush_custom_order_count' ) ); 44 add_action( 'woocommerce_new_order', array( __CLASS__, 'flush_custom_order_count' ) ); 42 45 } 43 46 public function spbwc_ajax() { … … 70 73 'spbwc_swap_product_option' => false, // admin only 71 74 'spbwc_unlink_product_option' => false, // admin only 75 // Overview: refresh the Storelly stats after the page has painted (A11) 76 'spbwc_overview_stats' => false, // admin only 72 77 ); 73 78 foreach ($ajax_events as $ajax_event => $nopriv) { … … 294 299 add_submenu_page( 295 300 SPBWC_PB_OVERVIEW_SLUG, 296 esc_html__(' GeneralSettings', 'storelly-product-builder-for-woocommerce'),297 esc_html__(' GeneralSettings', 'storelly-product-builder-for-woocommerce'),301 esc_html__('Settings', 'storelly-product-builder-for-woocommerce'), 302 esc_html__('Settings', 'storelly-product-builder-for-woocommerce'), 298 303 'manage_options', 299 304 SPBWC_PB_OPTIONS_SLUG, … … 324 329 array($this, 'spbwc_products_manager') 325 330 ); 331 // Same bubble as Quotes and B2B: custom orders paid and waiting to be made. 332 $spbwc_orders_menu_title = esc_html__('Custom Orders', 'storelly-product-builder-for-woocommerce'); 333 $spbwc_orders_to_fulfil = self::count_custom_orders_to_fulfil(); 334 if ( $spbwc_orders_to_fulfil > 0 ) { 335 $spbwc_orders_menu_title .= ' <span class="awaiting-mod"><span class="pending-count">' . esc_html( number_format_i18n( $spbwc_orders_to_fulfil ) ) . '</span></span>'; 336 } 326 337 add_submenu_page( 327 338 SPBWC_PB_OVERVIEW_SLUG, 328 339 esc_html__('Custom Orders', 'storelly-product-builder-for-woocommerce'), 329 esc_html__('Custom Orders', 'storelly-product-builder-for-woocommerce'),340 $spbwc_orders_menu_title, 330 341 'manage_options', 331 342 SPBWC_PB_ORDERS_SLUG, … … 382 393 array($this, 'spbwc_global_import') 383 394 ); 395 // System and About sit under the Storelly menu (hidden there; reached 396 // through the Settings tab bar) so WP opens that menu on them. 384 397 add_submenu_page( 385 SPBWC_PB_O PTIONS_SLUG,398 SPBWC_PB_OVERVIEW_SLUG, 386 399 esc_html__('System Infor', 'storelly-product-builder-for-woocommerce'), 387 400 esc_html__('System Infor', 'storelly-product-builder-for-woocommerce'), … … 391 404 ); 392 405 add_submenu_page( 393 SPBWC_PB_O PTIONS_SLUG,406 SPBWC_PB_OVERVIEW_SLUG, 394 407 esc_html__('About', 'storelly-product-builder-for-woocommerce'), 395 408 esc_html__('About', 'storelly-product-builder-for-woocommerce'), … … 511 524 if ( $hook === 'toplevel_page_' . SPBWC_PB_OVERVIEW_SLUG ) { 512 525 wp_enqueue_style('spbwc-overview-css'); 526 // Fills in the Storelly numbers when they were not cached yet (A11). 527 $spbwc_stats = class_exists( 'SPBWC_License_Manager' ) ? SPBWC_License_Manager::get_cached_overview_stats() : null; 528 if ( is_wp_error( $spbwc_stats ) && 'no_cache' === $spbwc_stats->get_error_code() ) { 529 wp_enqueue_script( 'spbwc-overview-stats', SPBWC_PB_JS_URL . 'overview-stats.js', array(), SPBWC_PB_VERSION, true ); 530 wp_localize_script( 'spbwc-overview-stats', 'spbwcOverviewStats', array( 531 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 532 'nonce' => wp_create_nonce( 'spbwc_overview_stats' ), 533 'subtitle' => __( 'Live numbers from your Storelly dashboard. Each card links straight to the related management screen.', 'storelly-product-builder-for-woocommerce' ), 534 ) ); 535 } 513 536 } 514 537 … … 1960 1983 </div> 1961 1984 </header> 1962 <?php // Anchor so WP admin notices land below the hero, not on it. ?> 1963 <hr class="wp-header-end" /> 1985 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'system' ); } ?> 1964 1986 1965 1987 <div class="spbwc-block"> … … 2026 2048 </div> 2027 2049 </header> 2028 <?php // Anchor so WP admin notices land below the hero, not on it. ?> 2029 <hr class="wp-header-end" /> 2050 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'about' ); } ?> 2030 2051 2031 2052 <!-- Brand intro --> … … 2092 2113 <?php 2093 2114 } 2115 /** Transient holding the Custom Orders menu badge count. */ 2116 const CUSTOM_ORDER_COUNT_KEY = 'spbwc_custom_orders_to_fulfil'; 2117 2118 /** 2119 * Orders in "processing" (paid, not yet shipped) that contain at least one 2120 * builder line item. Runs on every admin page for the menu badge, so it is 2121 * cached for 10 minutes and dropped whenever an order changes status. 2122 * 2123 * @return int 2124 */ 2125 public static function count_custom_orders_to_fulfil() { 2126 $cached = get_transient( self::CUSTOM_ORDER_COUNT_KEY ); 2127 if ( false !== $cached ) { 2128 return (int) $cached; 2129 } 2130 $count = 0; 2131 if ( function_exists( 'wc_get_orders' ) ) { 2132 $ids = wc_get_orders( array( 'status' => array( 'processing' ), 'return' => 'ids', 'limit' => 500 ) ); 2133 if ( ! empty( $ids ) ) { 2134 global $wpdb; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Global variable $wpdb. 2135 $ids = array_map( 'absint', $ids ); 2136 $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); 2137 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Cached in a transient above; table names from $wpdb, ids via placeholders. 2138 $count = (int) $wpdb->get_var( 2139 $wpdb->prepare( 2140 "SELECT COUNT(DISTINCT oi.order_id) 2141 FROM {$wpdb->prefix}woocommerce_order_items oi 2142 INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta oim ON oi.order_item_id = oim.order_item_id 2143 WHERE oim.meta_key = '_pcpb_option_price' 2144 AND oi.order_item_type = 'line_item' 2145 AND oi.order_id IN ({$placeholders})", 2146 $ids 2147 ) 2148 ); 2149 // phpcs:enable 2150 } 2151 } 2152 set_transient( self::CUSTOM_ORDER_COUNT_KEY, $count, 10 * MINUTE_IN_SECONDS ); 2153 return $count; 2154 } 2155 2156 /** Drop the cached Custom Orders badge count. */ 2157 public static function flush_custom_order_count() { 2158 delete_transient( self::CUSTOM_ORDER_COUNT_KEY ); 2159 } 2160 2094 2161 public function spbwc_orders_manager() { 2095 2162 if ( ! current_user_can( 'spbwc_manage_product_builder' ) ) { … … 4488 4555 4489 4556 // --- Remote (Storelly) stats overlay --- 4490 $remote_stats = SPBWC_License_Manager::get_overview_stats(); 4557 // Cache only: the API call moved to the spbwc_overview_stats AJAX (A11), so a 4558 // slow or unreachable Storelly never holds up this page. 4559 $remote_stats = SPBWC_License_Manager::get_cached_overview_stats(); 4491 4560 4492 4561 // --- License info --- … … 4504 4573 4505 4574 include_once( SPBWC_PB_PLUGIN_DIR . 'views/overview.php' ); 4575 } 4576 4577 /** 4578 * AJAX: fetch the Storelly overview stats after the Overview page has 4579 * painted (A11). The result is cached by get_overview_stats(). 4580 * wp_ajax_spbwc_overview_stats 4581 */ 4582 public function spbwc_overview_stats() { 4583 if ( ! current_user_can( 'manage_options' ) ) { 4584 wp_send_json_error( null, 403 ); 4585 } 4586 check_ajax_referer( 'spbwc_overview_stats', 'nonce' ); 4587 $stats = SPBWC_License_Manager::get_overview_stats(); 4588 if ( is_wp_error( $stats ) ) { 4589 wp_send_json_error( array( 'code' => $stats->get_error_code() ) ); 4590 } 4591 wp_send_json_success( array( 4592 'products' => number_format_i18n( (int) $stats['total_products'] ), 4593 'orders' => number_format_i18n( (int) $stats['total_orders'] ), 4594 'quotes' => number_format_i18n( (int) $stats['total_quotes'] ), 4595 ) ); 4506 4596 } 4507 4597 -
storelly-product-builder-for-woocommerce/tags/1.9.0/includes/class-global-import-admin.php
r3568219 r3768035 102 102 </div> 103 103 </header> 104 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'setup' ); } ?> 104 105 <div id="spbwc-global-import-app"></div> 105 106 </div> -
storelly-product-builder-for-woocommerce/tags/1.9.0/includes/class-license-manager.php
r3761204 r3768035 225 225 * @return array|WP_Error 226 226 */ 227 /** 228 * Overview stats from the cache only - never calls the API, so the 229 * Overview page can render at once (A11). WP_Error 'no_cache' means the 230 * store is connected but the numbers have to be refreshed (by AJAX). 231 * 232 * @return array|WP_Error 233 */ 234 public static function get_cached_overview_stats() { 235 $cached = get_transient( self::TRANSIENT_OVERVIEW_STATS ); 236 if ( false !== $cached ) { 237 return $cached; 238 } 239 $api_keys = get_option( 'spbwc_connect_api_keys', array() ); 240 if ( empty( $api_keys['business_id'] ) ) { 241 return new WP_Error( 'no_business', __( 'No business ID configured.', 'storelly-product-builder-for-woocommerce' ) ); 242 } 243 if ( get_transient( self::TRANSIENT_OVERVIEW_STATS . '_failed' ) ) { 244 return new WP_Error( 'api_error', __( 'Could not retrieve overview stats.', 'storelly-product-builder-for-woocommerce' ) ); 245 } 246 return new WP_Error( 'no_cache', __( 'Overview stats not cached yet.', 'storelly-product-builder-for-woocommerce' ) ); 247 } 248 227 249 public static function get_overview_stats() { 228 250 $cached = get_transient( self::TRANSIENT_OVERVIEW_STATS ); … … 242 264 243 265 if ( is_wp_error( $resp ) || empty( $resp['success'] ) ) { 266 // Remember the failure for 5 minutes so every Overview visit does not retry. 267 set_transient( self::TRANSIENT_OVERVIEW_STATS . '_failed', 1, 5 * MINUTE_IN_SECONDS ); 244 268 return new WP_Error( 'api_error', __( 'Could not retrieve overview stats.', 'storelly-product-builder-for-woocommerce' ) ); 245 269 } -
storelly-product-builder-for-woocommerce/tags/1.9.0/includes/email/class-email-admin.php
r3568219 r3768035 259 259 </div> 260 260 </header> 261 <?php // WP relocates admin notices to right after the first <h1>; this marker 262 // makes them land below the hero instead of on top of the gradient. ?> 263 <hr class="wp-header-end" /> 261 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'emails' ); } ?> 264 262 265 263 <?php if ( isset( $_GET['spbwc_test_sent'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only flash. ?> -
storelly-product-builder-for-woocommerce/tags/1.9.0/readme.txt
r3761204 r3768035 7 7 Requires PHP: 7.4 8 8 Requires Plugins: woocommerce 9 Stable tag: 1. 8.09 Stable tag: 1.9.0 10 10 License: GPLv2 or later 11 11 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 238 238 == Changelog == 239 239 240 = 1.9.0 = 241 * **New: one Settings area with tabs.** Settings, Emails, Cloud & License, Setup Wizard, System and About now share one tab bar, and the Storelly menu shows a single "Settings" item. Every old link and bookmark keeps working. 242 * **Faster Overview.** The Overview no longer waits for Storelly Cloud before it shows; the cloud numbers fill in afterwards without moving the page, and a failed connection is not retried on every visit. 243 * New: a count badge on Custom Orders for paid orders that are waiting to be made. 244 * New: Setup Wizard card on the Overview; "License & billing" is now "Account & Plan". 245 * The Storelly menu icon now follows the admin colour scheme like other menu icons. 246 * Fixed: notices from other plugins no longer appear inside the page header on Settings, Account & Plan, Setup Wizard and the option editor. 247 240 248 = 1.8.0 = 241 249 * **New: Download PDF always gives you a file.** On the free plan it now builds a preview PDF on your own site, marked "Made with Storelly", and offers the clean print-ready version through Storelly Cloud. Nothing is sent off-site for the preview. … … 367 375 == Upgrade Notice == 368 376 377 = 1.9.0 = 378 Settings, Emails, Account & Plan, Setup Wizard, System and About are now tabs of one Settings area, and the Overview opens without waiting for Storelly Cloud. 379 369 380 = 1.8.0 = 370 381 Free stores now get a watermarked preview when they click Download PDF, the upgrade notice only shows after you use a Cloud feature, and the quote form's Select field is a real dropdown. -
storelly-product-builder-for-woocommerce/tags/1.9.0/static/css/admin-menu.css
r3568219 r3768035 59 59 vertical-align: top; 60 60 } 61 62 /* === Pages folded into Settings tabs (S2) ===================== */ 63 /* Still registered (URLs, caps and asset hooks unchanged), only */ 64 /* hidden from the submenu; reached through the Settings tab bar. */ 65 #adminmenu .wp-submenu li.spbwc-menu-hidden { 66 display: none; 67 } 68 69 /* === Menu icon as a mask (S2) ================================= */ 70 /* The icon is drawn with currentColor, so it follows WP's idle, */ 71 /* hover and current colours like a dashicon (a data-URI <img> is */ 72 /* only repainted by WP when it uses fill="", not stroke). */ 73 #adminmenu .toplevel_page_storelly-product-builder-for-woocommerce-overview div.wp-menu-image::before { 74 content: ""; 75 display: block; 76 width: 20px; 77 height: 20px; 78 margin: 7px auto 0; 79 padding: 0; 80 background-color: currentColor; 81 -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='12 2 2 7 12 12 22 7 12 2'/%3E%3Cpolyline points='2 17 12 22 22 17'/%3E%3Cpolyline points='2 12 12 17 22 12'/%3E%3C/svg%3E") center / 20px 20px no-repeat; 82 mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='12 2 2 7 12 12 22 7 12 2'/%3E%3Cpolyline points='2 17 12 22 22 17'/%3E%3Cpolyline points='2 12 12 17 22 12'/%3E%3C/svg%3E") center / 20px 20px no-repeat; 83 } -
storelly-product-builder-for-woocommerce/tags/1.9.0/static/css/overview.css
r3568219 r3768035 188 188 padding: 2px 8px; 189 189 background: var(--st-brand, #1d4ed8); 190 color: #fff;190 color: var(--nbd-st-text-inverse, #fff); 191 191 font-size: 11px; 192 192 font-weight: 700; … … 202 202 height: 40px; 203 203 border-radius: var(--nbd-radius, 10px); 204 background: #fff;204 background: var(--nbd-st-bg, #fff); 205 205 border: 1px solid var(--nbd-st-border, #e2e8f0); 206 206 color: var(--st-brand, #1d4ed8); … … 373 373 .spbwc-welcome__cloud-row { flex-direction: column; } 374 374 } 375 376 /* A11: numbers refreshed after paint keep the same box (digits of equal width, 377 * one line reserved), so a late update never shifts the cards. */ 378 .spbwc-stat-card__value { 379 font-variant-numeric: tabular-nums; 380 min-height: 1.2em; 381 } 382 383 /* The "Local data" badge is hidden by overview-stats.js once live numbers arrive; 384 * its display:inline-flex would otherwise win over the hidden attribute. */ 385 .spbwc-page-hero__badge[hidden] { 386 display: none; 387 } -
storelly-product-builder-for-woocommerce/tags/1.9.0/static/css/storelly-admin-ui.css
r3761204 r3768035 1878 1878 margin: var(--nbd-space-2) 0 0; 1879 1879 } 1880 1881 /* === Settings tab bar (S2 / U2) =============================== 1882 * One bar under the hero of every Settings page (General, Emails, 1883 * Cloud & License, Setup Wizard, System, About). Pages keep their own 1884 * slugs; the bar makes them read as one tabbed Settings area. 1885 * ============================================================= */ 1886 .spbwc-settings-tabbar { 1887 display: flex; 1888 flex-wrap: wrap; 1889 gap: var(--nbd-space-1); 1890 margin-block: calc(-1 * var(--nbd-space-2)) var(--nbd-space-6); 1891 border-block-end: 1px solid var(--nbd-st-border-light); 1892 } 1893 1894 .spbwc-settings-tabbar__tab { 1895 display: inline-flex; 1896 align-items: center; 1897 gap: var(--nbd-space-2); 1898 padding: var(--nbd-space-3) var(--nbd-space-4); 1899 margin-block-end: -1px; 1900 border-block-end: 2px solid transparent; 1901 color: var(--nbd-st-text-soft); 1902 font-size: var(--text-md); 1903 font-weight: var(--font-semibold, 600); 1904 text-decoration: none; 1905 } 1906 1907 .spbwc-settings-tabbar__tab .dashicons { 1908 font-size: var(--text-xl); 1909 width: var(--text-xl); 1910 height: var(--text-xl); 1911 } 1912 1913 .spbwc-settings-tabbar__tab:hover, 1914 .spbwc-settings-tabbar__tab:focus { 1915 color: var(--st-brand); 1916 } 1917 1918 .spbwc-settings-tabbar__tab:focus-visible { 1919 outline: 2px solid var(--st-brand); 1920 outline-offset: -2px; 1921 box-shadow: none; 1922 } 1923 1924 .spbwc-settings-tabbar__tab.is-active { 1925 color: var(--st-brand); 1926 border-block-end-color: var(--st-brand); 1927 } -
storelly-product-builder-for-woocommerce/tags/1.9.0/storelly-product-builder-for-woocommerce.php
r3761204 r3768035 9 9 Plugin URI: https://storelly.com/product-builder 10 10 Description: Create product builder for Woocommerce products 11 Version: 1. 8.011 Version: 1.9.0 12 12 Requires Plugins: woocommerce 13 13 WC requires at least: 6.0.0 … … 25 25 $spbwc_basedir = $spbwc_upload_dir['basedir']; 26 26 $spbwc_baseurl = $spbwc_upload_dir['baseurl']; 27 define('SPBWC_PB_VERSION', '1. 8.0');27 define('SPBWC_PB_VERSION', '1.9.0'); 28 28 define('SPBWC_PB_NUMBER_VERSION', 138); 29 29 define('SPBWC_PB_PLUGIN_URL', plugin_dir_url(__FILE__)); … … 162 162 require_once(SPBWC_PB_PLUGIN_DIR . 'includes/class-admin-options.php'); 163 163 require_once(SPBWC_PB_PLUGIN_DIR . 'includes/admin/class-admin-menu.php'); 164 require_once(SPBWC_PB_PLUGIN_DIR . 'includes/admin/class-settings-tabs.php'); 164 165 require_once(SPBWC_PB_PLUGIN_DIR . 'includes/class-i18n-notice.php'); 165 166 require_once(SPBWC_PB_PLUGIN_DIR . 'includes/class-onboarding.php'); -
storelly-product-builder-for-woocommerce/tags/1.9.0/views/license.php
r3568219 r3768035 85 85 </div> 86 86 </header> 87 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'license' ); } ?> 87 88 88 89 <?php if ( $just_connected ) : ?> -
storelly-product-builder-for-woocommerce/tags/1.9.0/views/menu-settings.php
r3568219 r3768035 106 106 </div> 107 107 </header> 108 109 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'general' ); } ?> 108 110 109 111 <?php if ($message && $status) : ?> -
storelly-product-builder-for-woocommerce/tags/1.9.0/views/options/edit-option.php
r3568314 r3768035 103 103 </div> 104 104 </header> 105 <?php // WP moves admin notices below this marker instead of into the hero. ?> 106 <hr class="wp-header-end" /> 105 107 106 108 <!-- ──────────────── Flash message ──────────────── --> -
storelly-product-builder-for-woocommerce/tags/1.9.0/views/overview.php
r3568219 r3768035 103 103 <?php esc_html_e( 'Storelly Product Builder', 'storelly-product-builder-for-woocommerce' ); ?> 104 104 <?php if ( ! $is_remote_ok ) : ?> 105 <a class="spbwc-page-hero__badge" href="<?php echo esc_url( $license_url ); ?>"105 <a id="spbwc-local-data-badge" class="spbwc-page-hero__badge" href="<?php echo esc_url( $license_url ); ?>" 106 106 title="<?php esc_attr_e( 'Couldn\'t reach Storelly — showing your local database counts. Connect to enable live metrics.', 'storelly-product-builder-for-woocommerce' ); ?>"> 107 107 <span class="dashicons dashicons-database" aria-hidden="true"></span> … … 734 734 <?php esc_html_e( 'Key metrics', 'storelly-product-builder-for-woocommerce' ); ?> 735 735 </h2> 736 <p class="spbwc-section__subtitle" >736 <p class="spbwc-section__subtitle" id="spbwc-metrics-subtitle"> 737 737 <?php 738 738 if ( $is_remote_ok ) { … … 753 753 <div class="spbwc-stat-card__label"><?php esc_html_e( 'Products', 'storelly-product-builder-for-woocommerce' ); ?></div> 754 754 </div> 755 <div class="spbwc-stat-card__value" ><?php echo esc_html( number_format_i18n( $disp_products ) ); ?></div>755 <div class="spbwc-stat-card__value" data-spbwc-stat="products"><?php echo esc_html( number_format_i18n( $disp_products ) ); ?></div> 756 756 <p class="spbwc-stat-card__hint"> 757 757 <?php esc_html_e( 'WooCommerce products that have the Storelly product builder enabled.', 'storelly-product-builder-for-woocommerce' ); ?> … … 791 791 <div class="spbwc-stat-card__label"><?php esc_html_e( 'Orders', 'storelly-product-builder-for-woocommerce' ); ?></div> 792 792 </div> 793 <div class="spbwc-stat-card__value" ><?php echo esc_html( number_format_i18n( $disp_orders ) ); ?></div>793 <div class="spbwc-stat-card__value" data-spbwc-stat="orders"><?php echo esc_html( number_format_i18n( $disp_orders ) ); ?></div> 794 794 <p class="spbwc-stat-card__hint"> 795 795 <?php esc_html_e( 'All-time WooCommerce orders containing a customized Storelly product.', 'storelly-product-builder-for-woocommerce' ); ?> … … 810 810 <div class="spbwc-stat-card__label"><?php esc_html_e( 'Quote Requests', 'storelly-product-builder-for-woocommerce' ); ?></div> 811 811 </div> 812 <div class="spbwc-stat-card__value" ><?php echo esc_html( number_format_i18n( $disp_quotes ) ); ?></div>812 <div class="spbwc-stat-card__value" data-spbwc-stat="quotes"><?php echo esc_html( number_format_i18n( $disp_quotes ) ); ?></div> 813 813 <p class="spbwc-stat-card__hint"> 814 814 <?php esc_html_e( 'Customer-submitted quote requests awaiting your response or follow-up.', 'storelly-product-builder-for-woocommerce' ); ?> … … 1440 1440 </div> 1441 1441 <h3 class="spbwc-quick-card__title"> 1442 <?php esc_html_e( ' License & billing', 'storelly-product-builder-for-woocommerce' ); ?>1442 <?php esc_html_e( 'Account & Plan', 'storelly-product-builder-for-woocommerce' ); ?> 1443 1443 </h3> 1444 1444 </div> … … 1448 1448 <div class="spbwc-quick-card__footer"> 1449 1449 <span class="spbwc-cta-btn"> 1450 <?php esc_html_e( 'Open license', 'storelly-product-builder-for-woocommerce' ); ?>1450 <?php esc_html_e( 'Open Account & Plan', 'storelly-product-builder-for-woocommerce' ); ?> 1451 1451 <span class="dashicons dashicons-arrow-right-alt2" aria-hidden="true"></span> 1452 1452 </span> … … 1489 1489 <span class="spbwc-cta-btn"> 1490 1490 <?php esc_html_e( 'Manage emails', 'storelly-product-builder-for-woocommerce' ); ?> 1491 <span class="dashicons dashicons-arrow-right-alt2" aria-hidden="true"></span> 1492 </span> 1493 </div> 1494 </a> 1495 1496 <a class="spbwc-quick-card" href="<?php echo esc_url( admin_url( 'admin.php?page=' . SPBWC_PB_OPTIONS_SLUG . '/global-import' ) ); ?>"> 1497 <div class="spbwc-quick-card__head"> 1498 <div class="spbwc-quick-card__icon"> 1499 <span class="dashicons dashicons-admin-tools" aria-hidden="true"></span> 1500 </div> 1501 <h3 class="spbwc-quick-card__title"> 1502 <?php esc_html_e( 'Setup Wizard', 'storelly-product-builder-for-woocommerce' ); ?> 1503 </h3> 1504 </div> 1505 <p class="spbwc-quick-card__desc"> 1506 <?php esc_html_e( 'Import sample products, or turn your existing WooCommerce variations into pricing options in one step.', 'storelly-product-builder-for-woocommerce' ); ?> 1507 </p> 1508 <div class="spbwc-quick-card__footer"> 1509 <span class="spbwc-cta-btn"> 1510 <?php esc_html_e( 'Open Setup Wizard', 'storelly-product-builder-for-woocommerce' ); ?> 1491 1511 <span class="dashicons dashicons-arrow-right-alt2" aria-hidden="true"></span> 1492 1512 </span> -
storelly-product-builder-for-woocommerce/tags/1.9.0/views/setup-wizard/landing.php
r3568219 r3768035 157 157 </div> 158 158 </section> 159 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'setup' ); } ?> 159 160 160 161 <?php if ( $spbwc_demo_cleaned >= 0 ) : ?> -
storelly-product-builder-for-woocommerce/tags/1.9.0/views/setup-wizard/woo-seed.php
r3568219 r3768035 42 42 </div> 43 43 </section> 44 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'setup' ); } ?> 44 45 45 46 <div id="spbwc-woo-seed-app" class="spbwc-block spbwc-ws-app"> -
storelly-product-builder-for-woocommerce/trunk/includes/admin/class-admin-menu.php
r3568219 r3768035 9 9 * 1. Re-orders the existing submenu items into three task bands 10 10 * (BUILD / SELL / CONFIGURE) and injects non-clickable section headings. 11 * 2. Swaps the washed-out PNG menu icon for a crisp inline SVG.11 * 2. Swaps the washed-out PNG menu icon for an SVG mask drawn in currentColor. 12 12 * 13 13 * Safety: the re-order is loss-proof — any submenu item not named in the … … 64 64 protected static function desired_order() { 65 65 $opts = defined( 'SPBWC_PB_OPTIONS_SLUG' ) ? SPBWC_PB_OPTIONS_SLUG : 'storelly-product-builder-for-woocommerce-options'; 66 $emails = defined( 'SPBWC_PB_EMAILS_SLUG' ) ? SPBWC_PB_EMAILS_SLUG : 'storelly-product-builder-for-woocommerce-emails';67 66 68 67 return array( … … 85 84 array( 'heading', __( 'Configure', 'storelly-product-builder-for-woocommerce' ), 'configure' ), 86 85 $opts, 87 SPBWC_PB_LICENSE_SLUG,88 $emails,89 $opts . '/global-import',90 86 ); 91 87 } … … 135 131 } 136 132 133 // Pages folded into Settings tabs stay registered (their URLs, caps and 134 // asset hooks are unchanged) but are hidden from the menu by class. 135 $hidden = class_exists( 'SPBWC_Settings_Tabs' ) ? SPBWC_Settings_Tabs::moved_pages() : array(); 136 137 137 // Loss-proof: append anything we did not explicitly place. 138 138 foreach ( $submenu[ $parent ] as $item ) { 139 139 $slug = isset( $item[2] ) ? $item[2] : ''; 140 140 if ( '' !== $slug && empty( $used[ $slug ] ) ) { 141 if ( in_array( $slug, $hidden, true ) ) { 142 $item[4] = ( isset( $item[4] ) ? $item[4] . ' ' : '' ) . SPBWC_Settings_Tabs::HIDDEN_CLASS; 143 } 141 144 $ordered[] = $item; 142 145 } … … 147 150 148 151 /** 149 * Replace the top-level menu icon (index 6 of its $menu row) with a 150 * crisp inline SVG so it reads cleanly on the dark admin sidebar. 152 * Replace the top-level menu icon (index 6 of its $menu row) with 'none': 153 * WP then prints an empty .wp-menu-image that admin-menu.css fills with 154 * the Storelly mark as a CSS mask in currentColor, so the icon follows 155 * WP's idle / hover / current colours like a dashicon. 151 156 * 152 157 * @param array $menu The global $menu (by reference via global). … … 157 162 return; 158 163 } 159 $svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a7aaad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polygon points='12 2 2 7 12 12 22 7 12 2'/><polyline points='2 17 12 22 22 17'/><polyline points='2 12 12 17 22 12'/></svg>";160 $uri = 'data:image/svg+xml;base64,' . base64_encode( str_replace( '%23', '#', $svg ) );161 164 foreach ( $menu as $key => $row ) { 162 165 if ( isset( $row[2] ) && $row[2] === $parent && isset( $menu[ $key ][6] ) ) { 163 $menu[ $key ][6] = $uri;166 $menu[ $key ][6] = 'none'; 164 167 break; 165 168 } -
storelly-product-builder-for-woocommerce/trunk/includes/class-admin-options.php
r3761204 r3768035 40 40 // REST API — Pricing Options grid (GET, read-only, faster than admin-ajax.php). 41 41 add_action( 'rest_api_init', array( $this, 'spbwc_register_rest_routes' ) ); 42 // Menu badge cache (Custom Orders to fulfil): drop it when an order moves. 43 add_action( 'woocommerce_order_status_changed', array( __CLASS__, 'flush_custom_order_count' ) ); 44 add_action( 'woocommerce_new_order', array( __CLASS__, 'flush_custom_order_count' ) ); 42 45 } 43 46 public function spbwc_ajax() { … … 70 73 'spbwc_swap_product_option' => false, // admin only 71 74 'spbwc_unlink_product_option' => false, // admin only 75 // Overview: refresh the Storelly stats after the page has painted (A11) 76 'spbwc_overview_stats' => false, // admin only 72 77 ); 73 78 foreach ($ajax_events as $ajax_event => $nopriv) { … … 294 299 add_submenu_page( 295 300 SPBWC_PB_OVERVIEW_SLUG, 296 esc_html__(' GeneralSettings', 'storelly-product-builder-for-woocommerce'),297 esc_html__(' GeneralSettings', 'storelly-product-builder-for-woocommerce'),301 esc_html__('Settings', 'storelly-product-builder-for-woocommerce'), 302 esc_html__('Settings', 'storelly-product-builder-for-woocommerce'), 298 303 'manage_options', 299 304 SPBWC_PB_OPTIONS_SLUG, … … 324 329 array($this, 'spbwc_products_manager') 325 330 ); 331 // Same bubble as Quotes and B2B: custom orders paid and waiting to be made. 332 $spbwc_orders_menu_title = esc_html__('Custom Orders', 'storelly-product-builder-for-woocommerce'); 333 $spbwc_orders_to_fulfil = self::count_custom_orders_to_fulfil(); 334 if ( $spbwc_orders_to_fulfil > 0 ) { 335 $spbwc_orders_menu_title .= ' <span class="awaiting-mod"><span class="pending-count">' . esc_html( number_format_i18n( $spbwc_orders_to_fulfil ) ) . '</span></span>'; 336 } 326 337 add_submenu_page( 327 338 SPBWC_PB_OVERVIEW_SLUG, 328 339 esc_html__('Custom Orders', 'storelly-product-builder-for-woocommerce'), 329 esc_html__('Custom Orders', 'storelly-product-builder-for-woocommerce'),340 $spbwc_orders_menu_title, 330 341 'manage_options', 331 342 SPBWC_PB_ORDERS_SLUG, … … 382 393 array($this, 'spbwc_global_import') 383 394 ); 395 // System and About sit under the Storelly menu (hidden there; reached 396 // through the Settings tab bar) so WP opens that menu on them. 384 397 add_submenu_page( 385 SPBWC_PB_O PTIONS_SLUG,398 SPBWC_PB_OVERVIEW_SLUG, 386 399 esc_html__('System Infor', 'storelly-product-builder-for-woocommerce'), 387 400 esc_html__('System Infor', 'storelly-product-builder-for-woocommerce'), … … 391 404 ); 392 405 add_submenu_page( 393 SPBWC_PB_O PTIONS_SLUG,406 SPBWC_PB_OVERVIEW_SLUG, 394 407 esc_html__('About', 'storelly-product-builder-for-woocommerce'), 395 408 esc_html__('About', 'storelly-product-builder-for-woocommerce'), … … 511 524 if ( $hook === 'toplevel_page_' . SPBWC_PB_OVERVIEW_SLUG ) { 512 525 wp_enqueue_style('spbwc-overview-css'); 526 // Fills in the Storelly numbers when they were not cached yet (A11). 527 $spbwc_stats = class_exists( 'SPBWC_License_Manager' ) ? SPBWC_License_Manager::get_cached_overview_stats() : null; 528 if ( is_wp_error( $spbwc_stats ) && 'no_cache' === $spbwc_stats->get_error_code() ) { 529 wp_enqueue_script( 'spbwc-overview-stats', SPBWC_PB_JS_URL . 'overview-stats.js', array(), SPBWC_PB_VERSION, true ); 530 wp_localize_script( 'spbwc-overview-stats', 'spbwcOverviewStats', array( 531 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 532 'nonce' => wp_create_nonce( 'spbwc_overview_stats' ), 533 'subtitle' => __( 'Live numbers from your Storelly dashboard. Each card links straight to the related management screen.', 'storelly-product-builder-for-woocommerce' ), 534 ) ); 535 } 513 536 } 514 537 … … 1960 1983 </div> 1961 1984 </header> 1962 <?php // Anchor so WP admin notices land below the hero, not on it. ?> 1963 <hr class="wp-header-end" /> 1985 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'system' ); } ?> 1964 1986 1965 1987 <div class="spbwc-block"> … … 2026 2048 </div> 2027 2049 </header> 2028 <?php // Anchor so WP admin notices land below the hero, not on it. ?> 2029 <hr class="wp-header-end" /> 2050 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'about' ); } ?> 2030 2051 2031 2052 <!-- Brand intro --> … … 2092 2113 <?php 2093 2114 } 2115 /** Transient holding the Custom Orders menu badge count. */ 2116 const CUSTOM_ORDER_COUNT_KEY = 'spbwc_custom_orders_to_fulfil'; 2117 2118 /** 2119 * Orders in "processing" (paid, not yet shipped) that contain at least one 2120 * builder line item. Runs on every admin page for the menu badge, so it is 2121 * cached for 10 minutes and dropped whenever an order changes status. 2122 * 2123 * @return int 2124 */ 2125 public static function count_custom_orders_to_fulfil() { 2126 $cached = get_transient( self::CUSTOM_ORDER_COUNT_KEY ); 2127 if ( false !== $cached ) { 2128 return (int) $cached; 2129 } 2130 $count = 0; 2131 if ( function_exists( 'wc_get_orders' ) ) { 2132 $ids = wc_get_orders( array( 'status' => array( 'processing' ), 'return' => 'ids', 'limit' => 500 ) ); 2133 if ( ! empty( $ids ) ) { 2134 global $wpdb; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Global variable $wpdb. 2135 $ids = array_map( 'absint', $ids ); 2136 $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) ); 2137 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Cached in a transient above; table names from $wpdb, ids via placeholders. 2138 $count = (int) $wpdb->get_var( 2139 $wpdb->prepare( 2140 "SELECT COUNT(DISTINCT oi.order_id) 2141 FROM {$wpdb->prefix}woocommerce_order_items oi 2142 INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta oim ON oi.order_item_id = oim.order_item_id 2143 WHERE oim.meta_key = '_pcpb_option_price' 2144 AND oi.order_item_type = 'line_item' 2145 AND oi.order_id IN ({$placeholders})", 2146 $ids 2147 ) 2148 ); 2149 // phpcs:enable 2150 } 2151 } 2152 set_transient( self::CUSTOM_ORDER_COUNT_KEY, $count, 10 * MINUTE_IN_SECONDS ); 2153 return $count; 2154 } 2155 2156 /** Drop the cached Custom Orders badge count. */ 2157 public static function flush_custom_order_count() { 2158 delete_transient( self::CUSTOM_ORDER_COUNT_KEY ); 2159 } 2160 2094 2161 public function spbwc_orders_manager() { 2095 2162 if ( ! current_user_can( 'spbwc_manage_product_builder' ) ) { … … 4488 4555 4489 4556 // --- Remote (Storelly) stats overlay --- 4490 $remote_stats = SPBWC_License_Manager::get_overview_stats(); 4557 // Cache only: the API call moved to the spbwc_overview_stats AJAX (A11), so a 4558 // slow or unreachable Storelly never holds up this page. 4559 $remote_stats = SPBWC_License_Manager::get_cached_overview_stats(); 4491 4560 4492 4561 // --- License info --- … … 4504 4573 4505 4574 include_once( SPBWC_PB_PLUGIN_DIR . 'views/overview.php' ); 4575 } 4576 4577 /** 4578 * AJAX: fetch the Storelly overview stats after the Overview page has 4579 * painted (A11). The result is cached by get_overview_stats(). 4580 * wp_ajax_spbwc_overview_stats 4581 */ 4582 public function spbwc_overview_stats() { 4583 if ( ! current_user_can( 'manage_options' ) ) { 4584 wp_send_json_error( null, 403 ); 4585 } 4586 check_ajax_referer( 'spbwc_overview_stats', 'nonce' ); 4587 $stats = SPBWC_License_Manager::get_overview_stats(); 4588 if ( is_wp_error( $stats ) ) { 4589 wp_send_json_error( array( 'code' => $stats->get_error_code() ) ); 4590 } 4591 wp_send_json_success( array( 4592 'products' => number_format_i18n( (int) $stats['total_products'] ), 4593 'orders' => number_format_i18n( (int) $stats['total_orders'] ), 4594 'quotes' => number_format_i18n( (int) $stats['total_quotes'] ), 4595 ) ); 4506 4596 } 4507 4597 -
storelly-product-builder-for-woocommerce/trunk/includes/class-global-import-admin.php
r3568219 r3768035 102 102 </div> 103 103 </header> 104 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'setup' ); } ?> 104 105 <div id="spbwc-global-import-app"></div> 105 106 </div> -
storelly-product-builder-for-woocommerce/trunk/includes/class-license-manager.php
r3761204 r3768035 225 225 * @return array|WP_Error 226 226 */ 227 /** 228 * Overview stats from the cache only - never calls the API, so the 229 * Overview page can render at once (A11). WP_Error 'no_cache' means the 230 * store is connected but the numbers have to be refreshed (by AJAX). 231 * 232 * @return array|WP_Error 233 */ 234 public static function get_cached_overview_stats() { 235 $cached = get_transient( self::TRANSIENT_OVERVIEW_STATS ); 236 if ( false !== $cached ) { 237 return $cached; 238 } 239 $api_keys = get_option( 'spbwc_connect_api_keys', array() ); 240 if ( empty( $api_keys['business_id'] ) ) { 241 return new WP_Error( 'no_business', __( 'No business ID configured.', 'storelly-product-builder-for-woocommerce' ) ); 242 } 243 if ( get_transient( self::TRANSIENT_OVERVIEW_STATS . '_failed' ) ) { 244 return new WP_Error( 'api_error', __( 'Could not retrieve overview stats.', 'storelly-product-builder-for-woocommerce' ) ); 245 } 246 return new WP_Error( 'no_cache', __( 'Overview stats not cached yet.', 'storelly-product-builder-for-woocommerce' ) ); 247 } 248 227 249 public static function get_overview_stats() { 228 250 $cached = get_transient( self::TRANSIENT_OVERVIEW_STATS ); … … 242 264 243 265 if ( is_wp_error( $resp ) || empty( $resp['success'] ) ) { 266 // Remember the failure for 5 minutes so every Overview visit does not retry. 267 set_transient( self::TRANSIENT_OVERVIEW_STATS . '_failed', 1, 5 * MINUTE_IN_SECONDS ); 244 268 return new WP_Error( 'api_error', __( 'Could not retrieve overview stats.', 'storelly-product-builder-for-woocommerce' ) ); 245 269 } -
storelly-product-builder-for-woocommerce/trunk/includes/email/class-email-admin.php
r3568219 r3768035 259 259 </div> 260 260 </header> 261 <?php // WP relocates admin notices to right after the first <h1>; this marker 262 // makes them land below the hero instead of on top of the gradient. ?> 263 <hr class="wp-header-end" /> 261 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'emails' ); } ?> 264 262 265 263 <?php if ( isset( $_GET['spbwc_test_sent'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only flash. ?> -
storelly-product-builder-for-woocommerce/trunk/readme.txt
r3761204 r3768035 7 7 Requires PHP: 7.4 8 8 Requires Plugins: woocommerce 9 Stable tag: 1. 8.09 Stable tag: 1.9.0 10 10 License: GPLv2 or later 11 11 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 238 238 == Changelog == 239 239 240 = 1.9.0 = 241 * **New: one Settings area with tabs.** Settings, Emails, Cloud & License, Setup Wizard, System and About now share one tab bar, and the Storelly menu shows a single "Settings" item. Every old link and bookmark keeps working. 242 * **Faster Overview.** The Overview no longer waits for Storelly Cloud before it shows; the cloud numbers fill in afterwards without moving the page, and a failed connection is not retried on every visit. 243 * New: a count badge on Custom Orders for paid orders that are waiting to be made. 244 * New: Setup Wizard card on the Overview; "License & billing" is now "Account & Plan". 245 * The Storelly menu icon now follows the admin colour scheme like other menu icons. 246 * Fixed: notices from other plugins no longer appear inside the page header on Settings, Account & Plan, Setup Wizard and the option editor. 247 240 248 = 1.8.0 = 241 249 * **New: Download PDF always gives you a file.** On the free plan it now builds a preview PDF on your own site, marked "Made with Storelly", and offers the clean print-ready version through Storelly Cloud. Nothing is sent off-site for the preview. … … 367 375 == Upgrade Notice == 368 376 377 = 1.9.0 = 378 Settings, Emails, Account & Plan, Setup Wizard, System and About are now tabs of one Settings area, and the Overview opens without waiting for Storelly Cloud. 379 369 380 = 1.8.0 = 370 381 Free stores now get a watermarked preview when they click Download PDF, the upgrade notice only shows after you use a Cloud feature, and the quote form's Select field is a real dropdown. -
storelly-product-builder-for-woocommerce/trunk/static/css/admin-menu.css
r3568219 r3768035 59 59 vertical-align: top; 60 60 } 61 62 /* === Pages folded into Settings tabs (S2) ===================== */ 63 /* Still registered (URLs, caps and asset hooks unchanged), only */ 64 /* hidden from the submenu; reached through the Settings tab bar. */ 65 #adminmenu .wp-submenu li.spbwc-menu-hidden { 66 display: none; 67 } 68 69 /* === Menu icon as a mask (S2) ================================= */ 70 /* The icon is drawn with currentColor, so it follows WP's idle, */ 71 /* hover and current colours like a dashicon (a data-URI <img> is */ 72 /* only repainted by WP when it uses fill="", not stroke). */ 73 #adminmenu .toplevel_page_storelly-product-builder-for-woocommerce-overview div.wp-menu-image::before { 74 content: ""; 75 display: block; 76 width: 20px; 77 height: 20px; 78 margin: 7px auto 0; 79 padding: 0; 80 background-color: currentColor; 81 -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='12 2 2 7 12 12 22 7 12 2'/%3E%3Cpolyline points='2 17 12 22 22 17'/%3E%3Cpolyline points='2 12 12 17 22 12'/%3E%3C/svg%3E") center / 20px 20px no-repeat; 82 mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolygon points='12 2 2 7 12 12 22 7 12 2'/%3E%3Cpolyline points='2 17 12 22 22 17'/%3E%3Cpolyline points='2 12 12 17 22 12'/%3E%3C/svg%3E") center / 20px 20px no-repeat; 83 } -
storelly-product-builder-for-woocommerce/trunk/static/css/overview.css
r3568219 r3768035 188 188 padding: 2px 8px; 189 189 background: var(--st-brand, #1d4ed8); 190 color: #fff;190 color: var(--nbd-st-text-inverse, #fff); 191 191 font-size: 11px; 192 192 font-weight: 700; … … 202 202 height: 40px; 203 203 border-radius: var(--nbd-radius, 10px); 204 background: #fff;204 background: var(--nbd-st-bg, #fff); 205 205 border: 1px solid var(--nbd-st-border, #e2e8f0); 206 206 color: var(--st-brand, #1d4ed8); … … 373 373 .spbwc-welcome__cloud-row { flex-direction: column; } 374 374 } 375 376 /* A11: numbers refreshed after paint keep the same box (digits of equal width, 377 * one line reserved), so a late update never shifts the cards. */ 378 .spbwc-stat-card__value { 379 font-variant-numeric: tabular-nums; 380 min-height: 1.2em; 381 } 382 383 /* The "Local data" badge is hidden by overview-stats.js once live numbers arrive; 384 * its display:inline-flex would otherwise win over the hidden attribute. */ 385 .spbwc-page-hero__badge[hidden] { 386 display: none; 387 } -
storelly-product-builder-for-woocommerce/trunk/static/css/storelly-admin-ui.css
r3761204 r3768035 1878 1878 margin: var(--nbd-space-2) 0 0; 1879 1879 } 1880 1881 /* === Settings tab bar (S2 / U2) =============================== 1882 * One bar under the hero of every Settings page (General, Emails, 1883 * Cloud & License, Setup Wizard, System, About). Pages keep their own 1884 * slugs; the bar makes them read as one tabbed Settings area. 1885 * ============================================================= */ 1886 .spbwc-settings-tabbar { 1887 display: flex; 1888 flex-wrap: wrap; 1889 gap: var(--nbd-space-1); 1890 margin-block: calc(-1 * var(--nbd-space-2)) var(--nbd-space-6); 1891 border-block-end: 1px solid var(--nbd-st-border-light); 1892 } 1893 1894 .spbwc-settings-tabbar__tab { 1895 display: inline-flex; 1896 align-items: center; 1897 gap: var(--nbd-space-2); 1898 padding: var(--nbd-space-3) var(--nbd-space-4); 1899 margin-block-end: -1px; 1900 border-block-end: 2px solid transparent; 1901 color: var(--nbd-st-text-soft); 1902 font-size: var(--text-md); 1903 font-weight: var(--font-semibold, 600); 1904 text-decoration: none; 1905 } 1906 1907 .spbwc-settings-tabbar__tab .dashicons { 1908 font-size: var(--text-xl); 1909 width: var(--text-xl); 1910 height: var(--text-xl); 1911 } 1912 1913 .spbwc-settings-tabbar__tab:hover, 1914 .spbwc-settings-tabbar__tab:focus { 1915 color: var(--st-brand); 1916 } 1917 1918 .spbwc-settings-tabbar__tab:focus-visible { 1919 outline: 2px solid var(--st-brand); 1920 outline-offset: -2px; 1921 box-shadow: none; 1922 } 1923 1924 .spbwc-settings-tabbar__tab.is-active { 1925 color: var(--st-brand); 1926 border-block-end-color: var(--st-brand); 1927 } -
storelly-product-builder-for-woocommerce/trunk/storelly-product-builder-for-woocommerce.php
r3761204 r3768035 9 9 Plugin URI: https://storelly.com/product-builder 10 10 Description: Create product builder for Woocommerce products 11 Version: 1. 8.011 Version: 1.9.0 12 12 Requires Plugins: woocommerce 13 13 WC requires at least: 6.0.0 … … 25 25 $spbwc_basedir = $spbwc_upload_dir['basedir']; 26 26 $spbwc_baseurl = $spbwc_upload_dir['baseurl']; 27 define('SPBWC_PB_VERSION', '1. 8.0');27 define('SPBWC_PB_VERSION', '1.9.0'); 28 28 define('SPBWC_PB_NUMBER_VERSION', 138); 29 29 define('SPBWC_PB_PLUGIN_URL', plugin_dir_url(__FILE__)); … … 162 162 require_once(SPBWC_PB_PLUGIN_DIR . 'includes/class-admin-options.php'); 163 163 require_once(SPBWC_PB_PLUGIN_DIR . 'includes/admin/class-admin-menu.php'); 164 require_once(SPBWC_PB_PLUGIN_DIR . 'includes/admin/class-settings-tabs.php'); 164 165 require_once(SPBWC_PB_PLUGIN_DIR . 'includes/class-i18n-notice.php'); 165 166 require_once(SPBWC_PB_PLUGIN_DIR . 'includes/class-onboarding.php'); -
storelly-product-builder-for-woocommerce/trunk/views/license.php
r3568219 r3768035 85 85 </div> 86 86 </header> 87 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'license' ); } ?> 87 88 88 89 <?php if ( $just_connected ) : ?> -
storelly-product-builder-for-woocommerce/trunk/views/menu-settings.php
r3568219 r3768035 106 106 </div> 107 107 </header> 108 109 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'general' ); } ?> 108 110 109 111 <?php if ($message && $status) : ?> -
storelly-product-builder-for-woocommerce/trunk/views/options/edit-option.php
r3568314 r3768035 103 103 </div> 104 104 </header> 105 <?php // WP moves admin notices below this marker instead of into the hero. ?> 106 <hr class="wp-header-end" /> 105 107 106 108 <!-- ──────────────── Flash message ──────────────── --> -
storelly-product-builder-for-woocommerce/trunk/views/overview.php
r3568219 r3768035 103 103 <?php esc_html_e( 'Storelly Product Builder', 'storelly-product-builder-for-woocommerce' ); ?> 104 104 <?php if ( ! $is_remote_ok ) : ?> 105 <a class="spbwc-page-hero__badge" href="<?php echo esc_url( $license_url ); ?>"105 <a id="spbwc-local-data-badge" class="spbwc-page-hero__badge" href="<?php echo esc_url( $license_url ); ?>" 106 106 title="<?php esc_attr_e( 'Couldn\'t reach Storelly — showing your local database counts. Connect to enable live metrics.', 'storelly-product-builder-for-woocommerce' ); ?>"> 107 107 <span class="dashicons dashicons-database" aria-hidden="true"></span> … … 734 734 <?php esc_html_e( 'Key metrics', 'storelly-product-builder-for-woocommerce' ); ?> 735 735 </h2> 736 <p class="spbwc-section__subtitle" >736 <p class="spbwc-section__subtitle" id="spbwc-metrics-subtitle"> 737 737 <?php 738 738 if ( $is_remote_ok ) { … … 753 753 <div class="spbwc-stat-card__label"><?php esc_html_e( 'Products', 'storelly-product-builder-for-woocommerce' ); ?></div> 754 754 </div> 755 <div class="spbwc-stat-card__value" ><?php echo esc_html( number_format_i18n( $disp_products ) ); ?></div>755 <div class="spbwc-stat-card__value" data-spbwc-stat="products"><?php echo esc_html( number_format_i18n( $disp_products ) ); ?></div> 756 756 <p class="spbwc-stat-card__hint"> 757 757 <?php esc_html_e( 'WooCommerce products that have the Storelly product builder enabled.', 'storelly-product-builder-for-woocommerce' ); ?> … … 791 791 <div class="spbwc-stat-card__label"><?php esc_html_e( 'Orders', 'storelly-product-builder-for-woocommerce' ); ?></div> 792 792 </div> 793 <div class="spbwc-stat-card__value" ><?php echo esc_html( number_format_i18n( $disp_orders ) ); ?></div>793 <div class="spbwc-stat-card__value" data-spbwc-stat="orders"><?php echo esc_html( number_format_i18n( $disp_orders ) ); ?></div> 794 794 <p class="spbwc-stat-card__hint"> 795 795 <?php esc_html_e( 'All-time WooCommerce orders containing a customized Storelly product.', 'storelly-product-builder-for-woocommerce' ); ?> … … 810 810 <div class="spbwc-stat-card__label"><?php esc_html_e( 'Quote Requests', 'storelly-product-builder-for-woocommerce' ); ?></div> 811 811 </div> 812 <div class="spbwc-stat-card__value" ><?php echo esc_html( number_format_i18n( $disp_quotes ) ); ?></div>812 <div class="spbwc-stat-card__value" data-spbwc-stat="quotes"><?php echo esc_html( number_format_i18n( $disp_quotes ) ); ?></div> 813 813 <p class="spbwc-stat-card__hint"> 814 814 <?php esc_html_e( 'Customer-submitted quote requests awaiting your response or follow-up.', 'storelly-product-builder-for-woocommerce' ); ?> … … 1440 1440 </div> 1441 1441 <h3 class="spbwc-quick-card__title"> 1442 <?php esc_html_e( ' License & billing', 'storelly-product-builder-for-woocommerce' ); ?>1442 <?php esc_html_e( 'Account & Plan', 'storelly-product-builder-for-woocommerce' ); ?> 1443 1443 </h3> 1444 1444 </div> … … 1448 1448 <div class="spbwc-quick-card__footer"> 1449 1449 <span class="spbwc-cta-btn"> 1450 <?php esc_html_e( 'Open license', 'storelly-product-builder-for-woocommerce' ); ?>1450 <?php esc_html_e( 'Open Account & Plan', 'storelly-product-builder-for-woocommerce' ); ?> 1451 1451 <span class="dashicons dashicons-arrow-right-alt2" aria-hidden="true"></span> 1452 1452 </span> … … 1489 1489 <span class="spbwc-cta-btn"> 1490 1490 <?php esc_html_e( 'Manage emails', 'storelly-product-builder-for-woocommerce' ); ?> 1491 <span class="dashicons dashicons-arrow-right-alt2" aria-hidden="true"></span> 1492 </span> 1493 </div> 1494 </a> 1495 1496 <a class="spbwc-quick-card" href="<?php echo esc_url( admin_url( 'admin.php?page=' . SPBWC_PB_OPTIONS_SLUG . '/global-import' ) ); ?>"> 1497 <div class="spbwc-quick-card__head"> 1498 <div class="spbwc-quick-card__icon"> 1499 <span class="dashicons dashicons-admin-tools" aria-hidden="true"></span> 1500 </div> 1501 <h3 class="spbwc-quick-card__title"> 1502 <?php esc_html_e( 'Setup Wizard', 'storelly-product-builder-for-woocommerce' ); ?> 1503 </h3> 1504 </div> 1505 <p class="spbwc-quick-card__desc"> 1506 <?php esc_html_e( 'Import sample products, or turn your existing WooCommerce variations into pricing options in one step.', 'storelly-product-builder-for-woocommerce' ); ?> 1507 </p> 1508 <div class="spbwc-quick-card__footer"> 1509 <span class="spbwc-cta-btn"> 1510 <?php esc_html_e( 'Open Setup Wizard', 'storelly-product-builder-for-woocommerce' ); ?> 1491 1511 <span class="dashicons dashicons-arrow-right-alt2" aria-hidden="true"></span> 1492 1512 </span> -
storelly-product-builder-for-woocommerce/trunk/views/setup-wizard/landing.php
r3568219 r3768035 157 157 </div> 158 158 </section> 159 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'setup' ); } ?> 159 160 160 161 <?php if ( $spbwc_demo_cleaned >= 0 ) : ?> -
storelly-product-builder-for-woocommerce/trunk/views/setup-wizard/woo-seed.php
r3568219 r3768035 42 42 </div> 43 43 </section> 44 <?php if ( class_exists( 'SPBWC_Settings_Tabs' ) ) { SPBWC_Settings_Tabs::render( 'setup' ); } ?> 44 45 45 46 <div id="spbwc-woo-seed-app" class="spbwc-block spbwc-ws-app">
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)