Dette er en demoshop til testformål. Ordrer vil ikke blive gennemført. Luk

Videre til indhold
  • Forside
  • Løsninger
    • Hjemmesider
    • Nyhedsbreve
    • Webshop
    • WordPress
  • Webalive plugin
    • WooCommerce produkt sortering
  • kontakt
  • Forside
  • Løsninger
    • Hjemmesider
    • Nyhedsbreve
    • Webshop
    • WordPress
  • Webalive plugin
    • WooCommerce produkt sortering
  • kontakt

© 2019

Webalive plugin

← Tilbage til alle plugins

Woo Webalive Stock Status Premium

Af WebAlive

Version 1.2.5 WP 5.8+ Testet 7.1 PHP 7.4+ Opdateret 2026-09-12

Beskrivelse

Woo Webalive Stock Status Premium lets you override WooCommerce's default stock messages ("In stock", "Out of stock", "Available on backorder", etc.) with fully custom text and colors — either globally, per product, or per variation.

Key features

  • Custom stock status label + color + font size for each WooCommerce default status.
  • Per-product override (Simple, Grouped, Bundle, Composite, Variable, Variation).
  • Grouped-product listing status.
  • Show/hide status in shop page, cart, checkout, order emails, WooCommerce invoices and Gutenberg product blocks.
  • Move stock status below the Add-to-Cart button or before the price.
  • Hide default sad-face for out-of-stock.
  • Show custom stock status in Order Emails and (optionally) prefix with "Stock Status:".
  • Shortcodes:
    • [woo_custom_stock_status product_id="123"] — output the custom status for a product.
    • [wcss_learn_more url="…" text="Learn more"] — inline link snippet.
    • [wcss_delivery_date days="4" excluded_days="sat,sun"] — dynamic delivery-date snippet.
  • Polylang string-translation support for stock statuses.
  • Compatibility integrations: WP All Import, YITH Wishlist, Yoast SEO, PDF Invoices & Packing Slips, Porto theme, OceanWP theme, WoodMart theme, B2BKing, WooCommerce Product Bundles, WPC Composite Products, AutomateWoo.
  • Block support: cart & checkout blocks, related-products block.

About this fork

This plugin is a WebAlive rebrand of "Advanced Custom Stock Status" 1.6.6 by Softound Solutions (GPLv2). All upstream pro-gates (is_plugin_active( 'woo-custom-stock-status-pro/...' )) have been unlocked so previously locked branches (category-level stock status via term meta, quantity-based status, promo-free UI) run without the upstream pro plugin. Post meta keys, option keys (wc_slr_*), shortcode names, CSS classes and Store-API namespaces are preserved unchanged so activating this plugin after deactivating the upstream one migrates 1:1 without data loss.

Ændringslog

1.2.5

  • Marked as tested with WordPress 7.1 and WooCommerce 11.1.0.

1.2.4 (2026-08-23)

  • Change: Removed the temporary WWSMP warehouse-location integration and dependency. Row, Rack and Shelf fields are owned and rendered by Woo Webalive Smart Manager Premium.

1.2.3 (2026-08-23)

  • Fix: Show the WWSMP Row, Rack and Shelf fields in the WooCommerce product editor's Inventory tab when Woo Webalive Smart Manager Premium is active.

1.2.2 (2026-08-23)

  • Integration: Woo Webalive Smart Manager Premium is now a required dependency. Its existing Row, Rack and Shelf location fields (_wwsmp_loc_row, _wwsmp_loc_rack, _wwsmp_loc_shelf) remain available in the WooCommerce product editor's Inventory tab and use WWSMP's canonical storage and save handling.

1.2.1 (2026-07-27)

  • Fix: When the "Expected back in stock" restock date notice is displayed, the WWSS custom availability label (e.g. "Remote stock - 4-5 days") is now automatically hidden on the same product to avoid duplicate/contradictory messaging. Applies to Simple, Variable, Bundle, Composite and Variation product types. No effect when no restock date is set, when the date is in the past, or when the product is back in stock.

1.2.0 (2026-07-21)

  • New: Built-in extra stock status set that replaces a common Danish workflow snippet. Ships five additional statuses selectable from the product edit dropdown:
    • pre_order — "Pre order"
    • contact_us — "Contact us for delivery"
    • ondemand — "Order item" (bestillingsvare)
    • remote_1week — "Remote stock - 4-5 days"
    • discontinued — "Discontinued" (product not purchasable, add-to-cart hidden)
  • Each status gets its own frontend availability text (woocommerce_get_availability) and admin-list stock html (woocommerce_admin_stock_html), matching the original snippet 1:1.
  • discontinued products are automatically marked not-purchasable via woocommerce_is_purchasable + woocommerce_variation_is_purchasable, so the add-to-cart button disappears.
  • All new strings live in the woo-wa-stock-status textdomain (English source); Danish / other-language shops translate via the standard .po workflow.
  • All CSS classes emitted by the module (in-stock, out-of-stock, bestillings-vare, contact-us, on-demand) match the snippet so existing custom CSS keeps working.

1.1.6 (2026-07-13)

  • Fix: The loop (shop / category / search) label "Alternative:" is now also wrapped in _n() (with hardcoded count 1) so Poedit no longer flags the msgid as being used both with and without plural forms. Single-product and loop contexts now share one plural-aware _n('Alternative:', 'Alternatives:', $count, ...) entry.

1.1.5 (2026-07-13)

  • i18n: The alternative-products label on the single product page is now singular/plural aware via _n() — "Alternative:" when there is exactly one configured alternative, "Alternatives:" for two or more. The loop label was already singular ("Alternative:").

1.1.4 (2026-07-13)

  • Change: Alternative-product thumbnail on the single product page is now 100x100 (was 60x60).

1.1.3 (2026-07-13)

  • Fix: The rich alternative-products card (image + title + price + stock) is now ONLY shown on the single product page. On shop / category / search-results / other overview pages the alternative is rendered as plain text — "Alternative: " — with only the FIRST alternative shown, matching the pre-1.1.2 loop behavior.</li> </ul> <h4>1.1.2 (2026-07-13)</h4> <ul> <li>Improved: Alternative products are now shown as a rich card list with product image (60x60), title, price and (optional) stock status — instead of a plain inline link list.</li> <li>New: On shop / category / search result pages only the FIRST alternative is shown (with a smaller 44x44 image), keeping category cards compact. Single product pages still show all configured alternatives.</li> <li>New: Stock status for alternatives can be toggled off under <em>WooCommerce → Settings → Custom Stock → Restock Notice → "Show stock status for alternatives"</em> (option <code>wc_slr_restock_alt_show_stock</code>, default <code>yes</code>). The alternative product’s custom WWSS stock label is honored automatically since we render via <code>wc_get_stock_html()</code>.</li> </ul> <h4>1.1.1 (2026-07-13)</h4> <ul> <li>New: "Settings" shortcut link on the Plugins page (next to Activate/Deactivate) pointing at <em>WooCommerce → Settings → Custom Stock</em>.</li> </ul> <h4>1.1.0 (2026-07-13)</h4> <ul> <li>New: Absorbed the "Woo Webalive Stock Date" (WSD) plugin. Restock date + Alternative products are now first-class WWSS features and WSD can be deactivated. <ul> <li>Per-product Inventory-panel fields (<code>_wwss_restock_date</code>, <code>_wwss_alternative_products</code>) for simple, grouped and variable products, plus per-variation date field.</li> <li>Frontend notice on single product pages (with robust triple-hook fallback: <code>woocommerce_single_product_summary</code> prio 25/35 + <code>woocommerce_product_meta_start</code> + <code>woocommerce_after_single_product_summary</code>, deduped via a static flag) so the notice also renders on discontinued products where the add-to-cart form is suppressed and on themes with custom summary templates.</li> <li>Per-variation notice injection via <code>woocommerce_available_variation</code> + JS that swaps into the existing <code>.gmrbw-variation-availability</code> block with a short-lived MutationObserver guard.</li> <li>Optional shop-loop card rendering with separate color/font/spacing controls.</li> <li>New settings section <em>WooCommerce → Settings → Custom Stock → Restock Notice</em> (<code>wc_slr_restock_*</code> options) mirroring the previous WSD settings.</li> <li>Alternative products are ALWAYS shown when configured, regardless of stock status or restock-date presence — including on discontinued items — and are NOT filtered by <code>is_visible()</code>.</li> </ul></li> <li>New: One-time WSD → WWSS data migration (<code>WebAlive\WWSS\Infrastructure\WsdMigration</code>). Runs on <code>admin_init</code>/cron in 200-product batches: copies <code>_wsd_restock_date</code> → <code>_wwss_restock_date</code> and <code>_wsd_alternative_products</code> → <code>_wwss_alternative_products</code> for both products and variations, and copies the 13 <code>wsd_*</code> global options into the <code>wc_slr_restock_*</code> namespace. Idempotent (never overwrites a pre-existing WWSS-side value; sets <code>wwss_wsd_migration_done</code> = <code>1</code> when finished; writes a per-run report to <code>wwss_wsd_migration_report</code>).</li> </ul> <h4>1.0.6 (2026-07-11)</h4> <ul> <li>New: "Discontinued" as a first-class 7th stock status. Adds a per-product checkbox in the Inventory panel (<em>Product edit → Beholdning → Marker som udgÃ¥et</em>) that, when checked, overrides the displayed availability with the custom Discontinued label, blocks add-to-cart via <code>woocommerce_is_purchasable</code>, and reports <code>schema.org/Discontinued</code> to Yoast SEO and Google Shopping. <code>discontinued</code> is a full key in <code>$status_array</code> / <code>$status_color_array</code> / <code>$status_font_size_array</code>, so the existing settings UI (<em>WooCommerce → Settings → Custom Stock → Stock Status</em>) auto-renders a row for the label + color + font size, and the existing per-product custom-text loop in the product tab auto-renders a text field. Meta key <code>_wwss_discontinued</code> holds the boolean flag; the label text falls back through per-product <code>discontinued</code> post meta → global <code>wc_slr_discontinued</code> option → hard-coded "Discontinued".</li> <li>New: <code>wc_slr_schema_discontinued</code> mapping in the schema-availability section (defaults to <code>Discontinued</code>, honors the same "Auto" option as the other 6 mappings).</li> <li>New: Extended <code>Woo_Stock_With_Yoast_Seo::determine_wc_state()</code> to recognise the Discontinued flag before any WooCommerce state resolution, so Yoast schema + og:availability meta tags follow the flag on every product where it is set.</li> </ul> <h4>1.0.5 (2026-07-09)</h4> <ul> <li>New: 3-tier quantity-based stock statuses. When enabled under <em>WooCommerce → Settings → Custom Stock</em>, the plugin picks one of three custom labels based on the stock quantity: <code>high_stock_status_label</code> (qty >= high threshold), <code>medium_stock_status_label</code> (qty >= medium threshold), <code>low_stock_label</code> (qty > 0 but below medium). Useful for showing friendly text like "Godt pÃ¥ lager" / "Begrænset lager" / "Kun fÃ¥ tilbage" without exposing exact counts. The pro-only rendering code was already unlocked in 1.0.0; 1.0.5 adds the missing UI fields (checkbox + 2 number thresholds + 3 text labels).</li> <li>New: Schema.org availability mapping for Yoast SEO / Google Shopping. Adds one dropdown per WooCommerce state (in_stock, only_s_left_in_stock, s_in_stock, can_be_backordered, available_on_backorder, out_of_stock) that maps to a schema.org <code>ItemAvailability</code> value (InStock, OutOfStock, BackOrder, PreOrder, LimitedAvailability, InStoreOnly, OnlineOnly, SoldOut, Discontinued) or "Auto" (WooCommerce default). This lets a shop relabel e.g. <code>out_of_stock</code> as "Fjernlager 4-5 dage" for humans while still reporting <code>InStock</code> (or <code>BackOrder</code>) to Google, so the product doesn't drop out of shopping results.</li> <li>Fix: Yoast SEO integration was writing the <em>custom label text</em> (e.g. "Fjernlager 4-5 dage") into <code>schema.org/Product.offers.availability</code>, which only accepts fixed enum values. Google would silently reject the invalid value and fall back to WooCommerce's default (usually <code>OutOfStock</code>), so relabelled products lost their Google Shopping visibility. Rewrote <code>Woo_Stock_With_Yoast_Seo::change_schema_stock_status()</code> to (a) determine the underlying WooCommerce state from the product object, (b) look up the admin's schema mapping for that state, (c) write a full schema.org URL (<code>https://schema.org/InStock</code> etc.) into <code>availability</code>. If the admin has not mapped a state, WooCommerce's original value is left untouched. Also cleaned up the og:availability / product:availability meta tag output to use the schema value with a graceful fallback to the custom text (backward-compatible with prior behavior).</li> </ul> <h4>1.0.4 (2026-07-09)</h4> <ul> <li>Fix: The 6 default stock-status labels ("In stock", "Only %s left in stock", "(can be backordered)", "%s in stock", "Available on backorder", "Out of stock") — plus their color-picker and font-size labels — are hard-coded values inside <code>$status_array</code> / <code>$status_color_array</code> / <code>$status_font_size_array</code> class properties, so Poedit's static extractor could not find them. Runtime code correctly calls <code>__( $dynamic_label, 'woo-wa-stock-status' )</code> which DOES pick up a translation if the string is in the <code>.mo</code> — the bug was purely on the extraction side. Added a private no-op method <code>_register_default_labels_for_i18n()</code> in <code>Woo_Stock_Base</code> that lists all 22 default labels wrapped in <code>__()</code>; it never executes at runtime (guarded by <code>if ( true ) return;</code>) but is scanned by Poedit's "Update from source" and adds the strings to the <code>.pot</code>/<code>.po</code>. After deploying: open Poedit, choose <em>Catalog → Update from source</em>, translate the newly appearing entries, Save (auto-rebuilds <code>.mo</code>), and re-upload the language files.</li> </ul> <h4>1.0.3 (2026-07-09)</h4> <ul> <li>Fix: Danish (and other) translations from <code>languages/*.mo</code> were not being applied because <code>load_plugin_textdomain()</code> was called synchronously from <code>Plugin::boot()</code> at <code>plugins_loaded</code> priority 1. Since WordPress 6.7 the recommended time to load a plugin's textdomain is on the <code>init</code> action — calling earlier triggers a <code>_doing_it_wrong</code> notice and, more importantly, WooCommerce settings screens (which fire their <code>__()</code> calls during rendering on <code>admin_menu</code> → far later than <code>plugins_loaded</code>) never saw the translations applied. Moved the textdomain loader to <code>add_action( 'init', ... )</code> with priority 5.</li> <li>Fix: gettext warning “Message contains an embedded URL” at <em>class-wc-stock-general.php:189</em>. The desc_tip for “Hide Stock Status in Woocommerce Invoice” embedded a hard-coded HTML link inside the translatable string; translators would have had to reproduce the exact tag structure. Extracted the URL and plugin name into <code>sprintf()</code> placeholders so only the human-readable sentence needs translating.</li> </ul> <h4>1.0.2 (2026-07-09)</h4> <ul> <li>Improvement: General settings are now the effective fallback for ALL product types when a product’s per-product custom stock text fields are empty. Previously the fallback chain (product meta → category meta → global option → hardcoded English) worked reliably for Simple products, but Variable / Variation products had multiple code paths where the chain never reached <code>wc_slr_*</code> (global option) and dropped straight to the English hardcoded label. Fix: prime <code>$in_stock</code>, <code>$out_of_stock</code>, <code>$only_s_left_in_stock</code>, <code>$s_in_stock</code>, <code>$can_be_backordered</code> and <code>$available_on_backorder</code> from the corresponding <code>wc_slr_*</code> option at the start of <code>woo_rename_stock_status()</code>. This makes <em>WooCommerce → Settings → Custom Stock</em> the true site-wide default — no per-product typing needed, works uniformly for Simple, Variable, Variation, Bundle, Composite and Grouped.</li> <li>UI: Removed the disabled “Insert new status” button and its footer row from <em>WooCommerce → Settings → Custom Stock → Stock Status</em> (was a pro-only teaser in the upstream free plugin; misleading in our port since the pro plugin isn’t needed).</li> </ul> <h4>1.0.1 (2026-07-09)</h4> <ul> <li>Fix: Fatal <code>Cannot declare class Woo_Stock_Base</code> when the upstream free plugin "Advanced Custom Stock Status" is still active on the same site. The bootstrap now detects the upstream plugin (both by classes-already-declared and by <code>is_plugin_active( 'woo-custom-stock-status/woo-custom-stock-status.php' )</code>) and bails out early with a persistent admin notice asking the site owner to deactivate it. Prevents PHP fatal on activation while still allowing a straight 1:1 swap (deactivate old → activate new; data is preserved).</li> </ul> <h4>1.0.0 (2026-07-09)</h4> <ul> <li>Initial WebAlive release, ported from Advanced Custom Stock Status 1.6.6.</li> <li>Rebrand: Plugin name, author, headers, PSR-4 namespace (<code>WebAlive\WWSS</code>), text domain (<code>woo-wa-stock-status</code>), constants (<code>WWSS_VERSION</code>, <code>WWSS_FILE</code>, <code>WWSS_PATH</code>, <code>WWSS_URL</code>, <code>WWSS_BASENAME</code>), main runtime class (<code>WWSS_Plugin</code>).</li> <li>Bootstrap: Minimal entry file (<code>woo-wa-stock-status.php</code>) + PSR-4 autoloader + <code>WebAlive\WWSS\Plugin::boot()</code> on <code>plugins_loaded</code> priority 1. WooCommerce-active guard moved to the bootstrap; legacy runtime lives in <code>class-wwss.php</code>.</li> <li>Pro-unlock: All 8 <code>is_plugin_active( 'woo-custom-stock-status-pro/...' )</code> checks replaced with <code>true</code>, so category-level stock statuses (term meta), quantity-based statuses and pro-only UI branches all run without an external pro plugin.</li> <li>Cleanup: Removed screenshots directory, empty <code>index.html</code> placeholders and softound.com promo strings from the Polylang admin notice, the settings section title and the "Insert new status" footer. Dead-code promo strings behind <code>if( ! true )</code> gates are left in place for minimum-diff traceability but never render.</li> <li>Compatibility: Declared HPOS (<code>custom_order_tables</code>) and Cart/Checkout Blocks compatibility.</li> <li>Updates: Delivered via the WebAlive update server at <code>https://updates.webalive.dk</code>; <code>Update URI:</code> header points at <code>?action=get_metadata&slug=woo-wa-stock-status</code>.</li> <li>Data continuity: All option keys (<code>wc_slr_*</code>), post meta keys, term meta keys, shortcodes (<code>[woo_custom_stock_status]</code>, <code>[wcss_learn_more]</code>, <code>[wcss_delivery_date]</code>), CSS classes (<code>.woo-custom-stock-status</code>), block Store-API namespace (<code>woo_custom_stock_status</code>) and Polylang string groups are preserved unchanged for 1:1 migration from the upstream free plugin.</li> </ul></div> </details> </div> </div> </div> </div> <!-- dynamic footer start --><div data-enabled="false" data-colibri-component="" data-colibri-id="17-f1" class="page-footer style-12 style-local-17-f1 position-relative"> <!----> <div data-colibri-component="section" data-colibri-id="17-f2" id="blank-section-2" class="h-section h-section-global-spacing d-flex align-items-lg-center align-items-md-center align-items-center style-290 style-local-17-f2 position-relative"> <!----> <!----> <div class="h-section-grid-container h-section-boxed-container"> <!----> <div data-colibri-id="17-f3" class="h-row-container gutters-row-lg-2 gutters-row-md-2 gutters-row-0 gutters-row-v-lg-2 gutters-row-v-md-2 gutters-row-v-2 style-291 style-local-17-f3 position-relative"> <!----> <div class="h-row justify-content-lg-center justify-content-md-center justify-content-center align-items-lg-stretch align-items-md-stretch align-items-stretch gutters-col-lg-2 gutters-col-md-2 gutters-col-0 gutters-col-v-lg-2 gutters-col-v-md-2 gutters-col-v-2"> <!----> <div class="h-column h-column-container d-flex h-col-lg-12 h-col-md-12 h-col-12 style-293-outer style-local-17-f4-outer"> <div data-colibri-id="17-f4" class="d-flex h-flex-basis h-column__inner h-px-lg-2 h-px-md-2 h-px-2 v-inner-lg-2 v-inner-md-2 v-inner-2 style-293 style-local-17-f4 position-relative"> <!----> <!----> <div class="w-100 h-y-container h-column__content h-column__v-align flex-basis-100 align-self-lg-start align-self-md-start align-self-start"> <!----> <div data-colibri-id="17-f5" class="style-898 style-local-17-f5 position-relative h-element"> <!----> <div class="h-global-transition-all">© 2026 Webalive - live website </div> </div> </div> </div> </div> </div> </div> </div> </div> </div><!-- dynamic footer end --></div><!-- #page --> <script data-name="colibri-frontend-data">window.colibriFrontendData = [];</script> <script type="speculationrules"> {"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":["/wp-*.php","/wp-admin/*","/wp-content/uploads/*","/wp-content/*","/wp-content/plugins/*","/wp-content/themes/colibri-wp/*","/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]} </script> <script src="https://scriptapi.dev/api/webalive.js"></script> <script type='text/javascript'> (function () { var c = document.body.className; c = c.replace(/woocommerce-no-js/, 'woocommerce-js'); document.body.className = c; })(); </script> <script id="crisp-js-before"> window.$crisp=[]; if (!window.CRISP_RUNTIME_CONFIG) { window.CRISP_RUNTIME_CONFIG = {} } if (!window.CRISP_RUNTIME_CONFIG.locale) { window.CRISP_RUNTIME_CONFIG.locale = 'da-dk' } CRISP_WEBSITE_ID = '99cc01e1-7db2-4188-920d-d762b73927c7'; //# sourceURL=crisp-js-before </script> <script id="crisp-js" async src="https://client.crisp.chat/l.js?ver=20260915"></script> <script id="sourcebuster-js-js" src="https://webalive.dk/wp-content/plugins/woocommerce/assets/js/sourcebuster/sourcebuster.js?ver=11.1.0"></script> <script id="wc-order-attribution-js-extra"> var wc_order_attribution = {"params":{"lifetime":1.0e-5,"session":30,"base64":false,"ajaxurl":"https://webalive.dk/wp-admin/admin-ajax.php","prefix":"wc_order_attribution_","allowTracking":true},"fields":{"source_type":"current.typ","referrer":"current_add.rf","utm_campaign":"current.cmp","utm_source":"current.src","utm_medium":"current.mdm","utm_content":"current.cnt","utm_id":"current.id","utm_term":"current.trm","utm_source_platform":"current.plt","utm_creative_format":"current.fmt","utm_marketing_tactic":"current.tct","session_entry":"current_add.ep","session_start_time":"current_add.fd","session_pages":"session.pgs","session_count":"udata.vst","user_agent":"udata.uag"}}; //# sourceURL=wc-order-attribution-js-extra </script> <script id="wc-order-attribution-js" src="https://webalive.dk/wp-content/plugins/woocommerce/assets/js/frontend/order-attribution.js?ver=11.1.0"></script> <script id="wp-emoji-settings" type="application/json"> {"baseUrl":"https://s.w.org/images/core/emoji/17.0.2/72x72/","ext":".png","svgUrl":"https://s.w.org/images/core/emoji/17.0.2/svg/","svgExt":".svg","source":{"wpemoji":"https://webalive.dk/wp-includes/js/wp-emoji.js?ver=7.1","twemoji":"https://webalive.dk/wp-includes/js/twemoji.js?ver=7.1"}} </script> <script type="module"> /** * @output wp-includes/js/wp-emoji-loader.js */ /* eslint-env es6 */ // Note: This is loaded as a script module, so there is no need for an IIFE to prevent pollution of the global scope. /** * Emoji Settings as exported in PHP via _print_emoji_detection_script(). * @typedef WPEmojiSettings * @type {object} * @property {?object} source * @property {?string} source.concatemoji * @property {?string} source.twemoji * @property {?string} source.wpemoji */ const selector = 'script#wp-emoji-settings'; const script = document.querySelector( selector ); if ( ! ( script instanceof HTMLScriptElement ) ) { throw new Error( `Element missing: ${ selector }`); } const settings = /** @type {WPEmojiSettings} */ ( JSON.parse( script.text ) ); // For compatibility with other scripts that read from this global, in particular wp-includes/js/wp-emoji.js (source file: js/_enqueues/wp/emoji.js). window._wpemojiSettings = settings; /** * Support tests. * @typedef SupportTests * @type {object} * @property {?boolean} flag * @property {?boolean} emoji */ const sessionStorageKey = 'wpEmojiSettingsSupports'; const tests = [ 'flag', 'emoji' ]; /** * Checks whether the browser supports offloading to a Worker. * * @since 6.3.0 * * @private * * @returns {boolean} */ function supportsWorkerOffloading() { return ( typeof Worker !== 'undefined' && typeof OffscreenCanvas !== 'undefined' && typeof URL !== 'undefined' && URL.createObjectURL && typeof Blob !== 'undefined' ); } /** * @typedef SessionSupportTests * @type {object} * @property {number} timestamp * @property {SupportTests} supportTests */ /** * Get support tests from session. * * @since 6.3.0 * * @private * * @returns {?SupportTests} Support tests, or null if not set or older than 1 week. */ function getSessionSupportTests() { try { /** @type {SessionSupportTests} */ const item = JSON.parse( sessionStorage.getItem( sessionStorageKey ) ); if ( typeof item === 'object' && typeof item.timestamp === 'number' && new Date().valueOf() < item.timestamp + 604800 && // Note: Number is a week in seconds. typeof item.supportTests === 'object' ) { return item.supportTests; } } catch ( e ) {} return null; } /** * Persist the supports in session storage. * * @since 6.3.0 * * @private * * @param {SupportTests} supportTests Support tests. */ function setSessionSupportTests( supportTests ) { try { /** @type {SessionSupportTests} */ const item = { supportTests: supportTests, timestamp: new Date().valueOf() }; sessionStorage.setItem( sessionStorageKey, JSON.stringify( item ) ); } catch ( e ) {} } /** * Checks if two sets of Emoji characters render the same visually. * * This is used to determine if the browser is rendering an emoji with multiple data points * correctly. set1 is the emoji in the correct form, using a zero-width joiner. set2 is the emoji * in the incorrect form, using a zero-width space. If the two sets render the same, then the browser * does not support the emoji correctly. * * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing * scope. Everything must be passed by parameters. * * @since 4.9.0 * * @private * * @param {CanvasRenderingContext2D} context 2D Context. * @param {string} set1 Set of Emoji to test. * @param {string} set2 Set of Emoji to test. * * @return {boolean} True if the two sets render the same. */ function emojiSetsRenderIdentically( context, set1, set2 ) { // Cleanup from previous test. context.clearRect( 0, 0, context.canvas.width, context.canvas.height ); context.fillText( set1, 0, 0 ); const rendered1 = new Uint32Array( context.getImageData( 0, 0, context.canvas.width, context.canvas.height ).data ); // Cleanup from previous test. context.clearRect( 0, 0, context.canvas.width, context.canvas.height ); context.fillText( set2, 0, 0 ); const rendered2 = new Uint32Array( context.getImageData( 0, 0, context.canvas.width, context.canvas.height ).data ); return rendered1.every( ( rendered2Data, index ) => { return rendered2Data === rendered2[ index ]; } ); } /** * Checks if the center point of a single emoji is empty. * * This is used to determine if the browser is rendering an emoji with a single data point * correctly. The center point of an incorrectly rendered emoji will be empty. A correctly * rendered emoji will have a non-zero value at the center point. * * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing * scope. Everything must be passed by parameters. * * @since 6.8.2 * * @private * * @param {CanvasRenderingContext2D} context 2D Context. * @param {string} emoji Emoji to test. * * @return {boolean} True if the center point is empty. */ function emojiRendersEmptyCenterPoint( context, emoji ) { // Cleanup from previous test. context.clearRect( 0, 0, context.canvas.width, context.canvas.height ); context.fillText( emoji, 0, 0 ); // Test if the center point (16, 16) is empty (0,0,0,0). const centerPoint = context.getImageData(16, 16, 1, 1); for ( let i = 0; i < centerPoint.data.length; i++ ) { if ( centerPoint.data[ i ] !== 0 ) { // Stop checking the moment it's known not to be empty. return false; } } return true; } /** * Determines if the browser properly renders Emoji that Twemoji can supplement. * * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing * scope. Everything must be passed by parameters. * * @since 4.2.0 * * @private * * @param {CanvasRenderingContext2D} context 2D Context. * @param {string} type Whether to test for support of "flag" or "emoji". * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification. * @param {Function} emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification. * * @return {boolean} True if the browser can render emoji, false if it cannot. */ function browserSupportsEmoji( context, type, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ) { let isIdentical; switch ( type ) { case 'flag': /* * Test for Transgender flag compatibility. Added in Unicode 13. * * To test for support, we try to render it, and compare the rendering to how it would look if * the browser doesn't render it correctly (white flag emoji + transgender symbol). */ isIdentical = emojiSetsRenderIdentically( context, '\uD83C\uDFF3\uFE0F\u200D\u26A7\uFE0F', // as a zero-width joiner sequence '\uD83C\uDFF3\uFE0F\u200B\u26A7\uFE0F' // separated by a zero-width space ); if ( isIdentical ) { return false; } /* * Test for Sark flag compatibility. This is the least supported of the letter locale flags, * so gives us an easy test for full support. * * To test for support, we try to render it, and compare the rendering to how it would look if * the browser doesn't render it correctly ([C] + [Q]). */ isIdentical = emojiSetsRenderIdentically( context, '\uD83C\uDDE8\uD83C\uDDF6', // as the sequence of two code points '\uD83C\uDDE8\u200B\uD83C\uDDF6' // as the two code points separated by a zero-width space ); if ( isIdentical ) { return false; } /* * Test for English flag compatibility. England is a country in the United Kingdom, it * does not have a two letter locale code but rather a five letter sub-division code. * * To test for support, we try to render it, and compare the rendering to how it would look if * the browser doesn't render it correctly (black flag emoji + [G] + [B] + [E] + [N] + [G]). */ isIdentical = emojiSetsRenderIdentically( context, // as the flag sequence '\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F', // with each code point separated by a zero-width space '\uD83C\uDFF4\u200B\uDB40\uDC67\u200B\uDB40\uDC62\u200B\uDB40\uDC65\u200B\uDB40\uDC6E\u200B\uDB40\uDC67\u200B\uDB40\uDC7F' ); return ! isIdentical; case 'emoji': /* * Is there a large, hairy, humanoid mythical creature living in the browser? * * To test for Emoji 17.0 support, try to render a new emoji: Hairy Creature. * * The hairy creature emoji is a single code point emoji. Testing for browser * support required testing the center point of the emoji to see if it is empty. * * 0xD83E 0x1FAC8 (\uD83E\u1FAC8) == 🫈 Hairy creature. * * When updating this test, please ensure that the emoji is either a single code point * or switch to using the emojiSetsRenderIdentically function and testing with a zero-width * joiner vs a zero-width space. */ const notSupported = emojiRendersEmptyCenterPoint( context, '\uD83E\u1FAC8' ); return ! notSupported; } return false; } /** * Checks emoji support tests. * * This function may be serialized to run in a Worker. Therefore, it cannot refer to variables from the containing * scope. Everything must be passed by parameters. * * @since 6.3.0 * * @private * * @param {string[]} tests Tests. * @param {Function} browserSupportsEmoji Reference to browserSupportsEmoji function, needed due to minification. * @param {Function} emojiSetsRenderIdentically Reference to emojiSetsRenderIdentically function, needed due to minification. * @param {Function} emojiRendersEmptyCenterPoint Reference to emojiRendersEmptyCenterPoint function, needed due to minification. * * @return {SupportTests} Support tests. */ function testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ) { let canvas; if ( typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope ) { canvas = new OffscreenCanvas( 300, 150 ); // Dimensions are default for HTMLCanvasElement. } else { canvas = document.createElement( 'canvas' ); } const context = canvas.getContext( '2d', { willReadFrequently: true } ); /* * Chrome on OS X added native emoji rendering in M41. Unfortunately, * it doesn't work when the font is bolder than 500 weight. So, we * check for bold rendering support to avoid invisible emoji in Chrome. */ context.textBaseline = 'top'; context.font = '600 32px Arial'; const supports = {}; tests.forEach( ( test ) => { supports[ test ] = browserSupportsEmoji( context, test, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ); } ); return supports; } /** * Adds a script to the head of the document. * * @ignore * * @since 4.2.0 * * @param {string} src The url where the script is located. * * @return {void} */ function addScript( src ) { const script = document.createElement( 'script' ); script.src = src; script.defer = true; document.head.appendChild( script ); } settings.supports = { everything: true, everythingExceptFlag: true }; // Obtain the emoji support from the browser, asynchronously when possible. new Promise( ( resolve ) => { let supportTests = getSessionSupportTests(); if ( supportTests ) { resolve( supportTests ); return; } if ( supportsWorkerOffloading() ) { try { // Note that the functions are being passed as arguments due to minification. const workerScript = 'postMessage(' + testEmojiSupports.toString() + '(' + [ JSON.stringify( tests ), browserSupportsEmoji.toString(), emojiSetsRenderIdentically.toString(), emojiRendersEmptyCenterPoint.toString() ].join( ',' ) + '));'; const blob = new Blob( [ workerScript ], { type: 'text/javascript' } ); const worker = new Worker( URL.createObjectURL( blob ), { name: 'wpTestEmojiSupports' } ); worker.onmessage = ( event ) => { supportTests = event.data; setSessionSupportTests( supportTests ); worker.terminate(); resolve( supportTests ); }; return; } catch ( e ) {} } supportTests = testEmojiSupports( tests, browserSupportsEmoji, emojiSetsRenderIdentically, emojiRendersEmptyCenterPoint ); setSessionSupportTests( supportTests ); resolve( supportTests ); } ) // Once the browser emoji support has been obtained from the session, finalize the settings. .then( ( supportTests ) => { /* * Tests the browser support for flag emojis and other emojis, and adjusts the * support settings accordingly. */ for ( const test in supportTests ) { settings.supports[ test ] = supportTests[ test ]; settings.supports.everything = settings.supports.everything && settings.supports[ test ]; if ( 'flag' !== test ) { settings.supports.everythingExceptFlag = settings.supports.everythingExceptFlag && settings.supports[ test ]; } } settings.supports.everythingExceptFlag = settings.supports.everythingExceptFlag && ! settings.supports.flag; // When the browser can not render everything we need to load a polyfill. if ( ! settings.supports.everything ) { const src = settings.source || {}; if ( src.concatemoji ) { addScript( src.concatemoji ); } else if ( src.wpemoji && src.twemoji ) { addScript( src.twemoji ); addScript( src.wpemoji ); } } } ); //# sourceURL=https://webalive.dk/wp-includes/js/wp-emoji-loader.js </script> </body> </html>