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.0 WP 5.8+ Testet 6.7 PHP 7.4+ Opdateret 2026-07-21

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.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=20260721"></script> <script id="sourcebuster-js-js" src="https://webalive.dk/wp-content/plugins/woocommerce/assets/js/sourcebuster/sourcebuster.js?ver=10.9.4"></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=10.9.4"></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.0.2","twemoji":"https://webalive.dk/wp-includes/js/twemoji.js?ver=7.0.2"}} </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 settings = /** @type {WPEmojiSettings} */ ( JSON.parse( document.getElementById( 'wp-emoji-settings' ).textContent ) ); // 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>