{"id":360852,"date":"2026-09-23T03:31:18","date_gmt":"2026-09-23T03:31:18","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/gs1-digital-link-generator\/"},"modified":"2026-09-23T03:48:58","modified_gmt":"2026-09-23T03:48:58","slug":"smartonelink-digital-link-generator-for-gs1","status":"publish","type":"plugin","link":"https:\/\/wordpress.org\/plugins\/smartonelink-digital-link-generator-for-gs1\/","author":333100,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.5.2","stable_tag":"1.5.2","tested":"7.1.2","requires":"5.8","requires_php":"7.4","requires_plugins":null,"header_name":"SmartOneLink Digital Link Generator for GS1","header_author":"SmartOneLink","header_description":"Bridge between WordPress and the smartonelink Digital Link platform. Generate GS1 Digital Links pointing to local WordPress posts via the official smartonelink API.","assets_banners_color":"","last_updated":"2026-09-23 03:48:58","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"","rating":0,"author_block_rating":0,"active_installs":0,"downloads":98,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.5.2":{"tag":"1.5.2","author":"phonograph","date":"2026-09-23 03:48:58","revision":3708433}},"upgrade_notice":[],"ratings":[],"assets_icons":[],"assets_banners":[],"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.5.2"],"block_files":[],"assets_screenshots":[],"screenshots":[]},"plugin_section":[],"plugin_tags":[282182,223044,1373],"plugin_category":[],"plugin_contributors":[282183],"plugin_business_model":[],"class_list":["post-360852","plugin","type-plugin","status-publish","hentry","plugin_tags-digital-link","plugin_tags-gs1","plugin_tags-qr-code","plugin_contributors-phonograph","plugin_committers-phonograph"],"banners":[],"icons":{"svg":false,"icon":"https:\/\/s.w.org\/plugins\/geopattern-icon\/smartonelink-digital-link-generator-for-gs1.svg","icon_2x":false,"generated":true},"screenshots":[],"raw_content":"<!--section=description-->\n<p>SmartOneLink Digital Link Generator for GS1 is a companion plugin for the SmartOneLink Digital Link platform. Once configured with an API Key, it adds:<\/p>\n\n<ul>\n<li>A \"\u6570\u5b57\u94fe\u63a5\" menu in the WordPress admin sidebar.<\/li>\n<li>An API token is required (create one in Smartonelink \u2192 Settings \u2192 API Tokens). Any Smartonelink account can create one.<\/li>\n<li>A four-step wizard that creates a GS1 Digital Link bound to a local WordPress post.<\/li>\n<li>A list view of all digital links you've created.<\/li>\n<li>Optional global settings (GSDL base URL + API Key, stored with AES-256-GCM encryption).<\/li>\n<\/ul>\n\n<p>Note on naming: this plugin is a third-party community tool and is not officially affiliated with GS1. The trademark \"GS1\" appears at the end of the plugin name (after \"for\") only to describe that the generated links follow the GS1 Digital Link standard; it does not imply endorsement by GS1.<\/p>\n\n<h3>External services<\/h3>\n\n<p>This plugin communicates with the SmartOneLink Digital Link platform in order to generate, edit, list and delete GS1 Digital Links that are bound to local WordPress posts. Every administrative action performed through this plugin (saving settings, creating \/ editing \/ listing \/ deleting a digital link, generating a QR code) triggers a request to the SmartOneLink service described below.<\/p>\n\n<p>What data is sent and when:<\/p>\n\n<ul>\n<li>The <strong>API Key<\/strong> you paste on the plugin's Settings page is sent to SmartOneLink once per session in order to exchange it for a short-lived JWT (JSON Web Token) used by all subsequent calls. The plugin never sends the API Key again after that exchange.<\/li>\n<li>The <strong>short-lived JWT<\/strong> is sent in the <code>Authorization<\/code> header of every subsequent request to the SmartOneLink backend.<\/li>\n<li>The <strong>GS1 application identifiers, values, key qualifiers, GS1 data attributes, descriptions, categories and target WordPress post permalinks \/ titles<\/strong> you bind to a digital link are sent when you create or edit that link, and are reflected in SmartOneLink's record for that link.<\/li>\n<li>No data is sent when the plugin is idle; the plugin does not perform any background telemetry, analytics or tracking of any kind.<\/li>\n<\/ul>\n\n<p>Service provider: SmartOneLink.<\/p>\n\n<p>Service URLs:<\/p>\n\n<ul>\n<li>Application: https:\/\/app.smartonelink.com\/<\/li>\n<li>Privacy Policy: https:\/\/www.smartonelink.com\/privacy-policy-en\/<\/li>\n<li>Terms of Service: https:\/\/www.smartonelink.com\/terms-en\/<\/li>\n<\/ul>\n\n<p>By installing and configuring this plugin you acknowledge that the data described above is transmitted to SmartOneLink. Please review the linked Terms of Service and Privacy Policy before use.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>smartonelink-digital-link-generator-for-gs1<\/code> folder to <code>\/wp-content\/plugins\/<\/code> or upload the zip via the WordPress plugin uploader.<\/li>\n<li>Activate the plugin.<\/li>\n<li>Navigate to <strong>\u6570\u5b57\u94fe\u63a5 \u2192 Settings<\/strong> and enter your GSDL base URL and API Key.<\/li>\n<li>Use <strong>\u6570\u5b57\u94fe\u63a5 \u2192 New<\/strong> to create your first digital link.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"what%20api%20key%20do%20i%20need%3F\"><h3>What API Key do I need?<\/h3><\/dt>\n<dd><p>You need a Smartonelink API Token. In your Smartonelink web app, go to <strong>Settings \u2192 API Tokens<\/strong> and create one. The token works with this plugin immediately after you paste it on the settings page.<\/p><\/dd>\n<dt id=\"does%20the%20api%20key%20grant%20write%20access%20to%20my%20account%3F\"><h3>Does the API Key grant write access to my account?<\/h3><\/dt>\n<dd><p>Yes. Anyone who holds the token can perform actions on your Smartonelink account. Treat it as a password and rotate it if compromised.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.5.2<\/h4>\n\n<ul>\n<li>Security: pass the two plugin-review blockers reported in the v1.5.1 manual review.\n\n<ul>\n<li><code>gsdl-digital-link.php<\/code>: tightened <code>permission_callback<\/code> on both registered REST routes from <code>current_user_can('edit_posts')<\/code> to <code>current_user_can('manage_options')<\/code>. Under the old check any author \/ editor \/ contributor \/ shop_manager user could call <code>gsdl\/v1\/exchange-jwt<\/code> and have the plugin exchange the administrator-configured site-wide API Key for a short-lived JWT, or call <code>gsdl\/v1\/proxy\/(?P&lt;path&gt;.*)<\/code> and have it forward arbitrary CRUD to the SmartOneLink backend using the cached JWT. Both routes now require <code>manage_options<\/code>.<\/li>\n<li><code>readme.txt<\/code>: <code>Stable tag<\/code> bumped to <code>1.5.2<\/code>.<\/li>\n<\/ul><\/li>\n<li>No data-layer behaviour change: option \/ user-meta keys, AES-256-GCM ciphertexts, REST route signatures, admin menu slugs and the compiled SPA bundle are byte-identical to v1.5.1. The SPA now renders an inline WP 403 (\"\u62b1\u6b49\uff0c\u60a8\u65e0\u6743\u8bbf\u95ee\u6b64\u8d44\u6e90\u3002\") for non-administrator sessions.<\/li>\n<\/ul>\n\n<h4>1.5.1<\/h4>\n\n<ul>\n<li>Fix: pass WordPress.org plugin-review blockers reported in the 1.5.0 manual review.\n\n<ul>\n<li><code>gsdl-digital-link.php<\/code>: fixed the PHP syntax error flagged on the two <code>sprintf(... implode(...))<\/code> call sites (one comma missing on each).<\/li>\n<li><code>includes\/class-gsdl-settings.php<\/code>: rewrote the <code>sanitize_callback<\/code> for the encrypted API Key option to a true no-op (return currently stored ciphertext) instead of echoing the submitted value through, closing the audit finding that the callback returned arbitrary submitted data unchanged.<\/li>\n<li><code>includes\/class-gsdl-admin.php \u2192 render_default()<\/code>: replaced the raw <code>echo<\/code> of <code>admin\/index.html<\/code> with a defence-in-depth escape pipeline \u2014 <code>realpath()<\/code> containment check (rejects path-traversal \/ symlink attacks) plus <code>wp_kses()<\/code> with an explicit allowlist of Vite SPA tags.<\/li>\n<li><code>readme.txt<\/code>: added the missing <code>== External services ==<\/code> section (service provider, data sent, app URL, Privacy Policy and Terms of Service links).<\/li>\n<li><code>includes\/class-gsdl-admin.php<\/code>: fix all admin pages rendering the link list (clicking \"New\" \/ \"Link List\" \/ \"Settings\" all opened the list view). Root cause: v1.5.0 collapsed the two pre-existing <code>window.GSDL_WP_VIEW=...<\/code> and <code>window.GSDL_WP=...<\/code> blocks into a single <code>wp_localize_script()<\/code> call, but the compiled SPA bundle still reads the legacy <code>window.GSDL_WP_VIEW<\/code> global. Fix: replace the localize call with two <code>wp_add_inline_script()<\/code> calls that emit both the intended future surface and the legacy global for the current SPA bundle.<\/li>\n<\/ul><\/li>\n<li>No data-layer behaviour change \u2014 admin \/ REST routes, submenu slugs, option \/ user-meta keys, AES-256-GCM ciphertexts and the SPA bundle are byte-identical to v1.5.0.<\/li>\n<\/ul>\n\n<h4>1.5.0<\/h4>\n\n<ul>\n<li>Rename: plugin display name + slug + folder changed to comply with the WordPress.org Plugin Directory trademark guideline (the previous \"GS1 Digital Link Generator\" implied an official GS1 plugin). The new name moves the trademark to the end and uses the \"for\" pattern recommended by the directory guidelines.\n\n<ul>\n<li>Display name \u2192 <strong>SmartOneLink Digital Link Generator for GS1<\/strong>; slug \/ folder \/ zip prefix \u2192 <strong>smartonelink-digital-link-generator-for-gs1<\/strong>. <strong>Existing installations<\/strong>: WordPress deactivates the old folder and re-activates the plugin under the new slug automatically on next update; PHP class prefix (<code>Gsdl_*<\/code>), option keys, user-meta keys, REST routes, admin menu slugs are unchanged so settings, posts and digital links survive the transition.<\/li>\n<li><code>gsdl-digital-link.php<\/code>: <code>Plugin Name<\/code> \u2192 <code>SmartOneLink Digital Link Generator for GS1<\/code>; <code>Text Domain<\/code> \u2192 <code>smartonelink-digital-link-generator-for-gs1<\/code>; <code>Version<\/code> \/ <code>GSDL_DL_VERSION<\/code> \u2192 <code>1.5.0<\/code>; all 16 <code>__()<\/code> \/ <code>sprintf(__())<\/code> text-domain references updated. Two error-message strings updated to <code>\u8bf7\u5230 \u6570\u5b57\u94fe\u63a5 \u2192 \u8bbe\u7f6e \u586b\u5199<\/code>.<\/li>\n<li><code>includes\/class-gsdl-admin.php<\/code>: top-level menu label simplified. Two inline <code>&lt;script&gt;...&lt;\/script&gt;<\/code> emissions replaced with the WordPress-standard <code>wp_localize_script()<\/code> API.<\/li>\n<li><code>includes\/class-gsdl-settings.php<\/code>: H1 updated to <code>SmartOneLink Digital Link Generator for GS1 \u00b7 \u8bbe\u7f6e<\/code>.<\/li>\n<li><code>admin\/index.html<\/code>: <code>&lt;title&gt;<\/code> updated.<\/li>\n<li><code>languages\/<\/code>: four <code>.po<\/code> \/ <code>.mo<\/code> files removed from the zip (WordPress.org auto-generates translations through translate.wordpress.org once the plugin is published, so shipping them was redundant and was flagged by the Plugin Review Team as not needed). Only <code>smartonelink-digital-link-generator-for-gs1.pot<\/code> is kept.<\/li>\n<li><code>readme.txt<\/code>: <code>Contributors<\/code> updated to list a valid WordPress.org username (the Plugin Review Team flagged that <code>phonograph<\/code> was missing from the contributor list; <code>smartonelink<\/code> was added but later flagged as not a valid WP.org username and was reverted in a follow-up commit). <code>Development:<\/code> field added pointing at the public GitHub mirror. <code>Stable tag<\/code> \u2192 <code>1.5.0<\/code>.<\/li>\n<\/ul><\/li>\n<li>No data-layer behaviour change \u2014 admin \/ REST routes, submenu slugs, option \/ user-meta keys, SPA bundle and AES-256-GCM API Key storage are byte-identical to v1.4.0.<\/li>\n<\/ul>\n\n<h4>1.4.0<\/h4>\n\n<ul>\n<li>Fix: pass the <code>textdomain_mismatch<\/code> Plugin Check warning. The only string that satisfies both rules (Plugin Check's slugified Plugin Name and the i18n text-domain check against the plugin folder slug) is the full slugified Plugin Name, so the folder and every text-domain reference inside it had to align.\n\n<ul>\n<li>Plugin folder renamed <code>gs1-digital-link\/<\/code> \u2192 <code>gs1-digital-link-generator\/<\/code>. <strong>Existing installations<\/strong>: WordPress deactivates the old folder and re-activates the plugin under the new slug automatically; PHP class names, option \/ user-meta keys, REST routes and admin menu slugs are unchanged.<\/li>\n<li><code>gsdl-digital-link.php<\/code>: <code>Text Domain<\/code> header, <code>load_plugin_textdomain()<\/code> and all 16 <code>__()<\/code> \/ <code>sprintf(__())<\/code> calls updated from <code>'gs1-digital-link'<\/code> to <code>'gs1-digital-link-generator'<\/code>. <code>Version<\/code> \u2192 <code>1.4.0<\/code>.<\/li>\n<li><code>languages\/<\/code>: filenames re-prefixed so they start with the new text-domain slug; <code>Project-Id-Version<\/code> lines bumped.<\/li>\n<li><code>readme.txt<\/code>: Installation step 1 references the new folder name. <code>Stable tag<\/code> \u2192 <code>1.4.0<\/code>.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.3.9<\/h4>\n\n<ul>\n<li>Fix: pass WordPress.org plugin-upload validation, which rejects plugins whose <code>Plugin URI<\/code> and <code>Author URI<\/code> headers carry the same value. The 1.3.8 header had both pointing at <code>https:\/\/www.smartonelink.com\/<\/code>. Both URI fields are now dropped from the plugin header.\n\n<ul>\n<li><code>gsdl-digital-link.php<\/code>: <code>Plugin URI<\/code> and <code>Author URI<\/code> lines removed. <code>Version<\/code> \u2192 <code>1.3.9<\/code>.<\/li>\n<li><code>readme.txt<\/code>: <code>Stable tag<\/code> \u2192 <code>1.3.9<\/code>.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.3.8<\/h4>\n\n<ul>\n<li>Fix: pass the remaining <code>Plugin Check<\/code> blockers \u2014 16 <code>WordPress.WP.I18n.TextDomainMismatch<\/code> errors, 1 <code>case_sensitive_files<\/code> error, and the lingering <code>textdomain_mismatch<\/code> warning. Root cause: Plugin Check resolves the plugin slug from the plugin folder name on disk, so a folder containing an upper-case character (<code>GS1-digital-link<\/code>) forces the text domain to also contain upper-case, which collides with the <code>textdomain_invalid_format<\/code> rule. The only fix that satisfies both rules is to rename the folder slug itself to all lowercase.\n\n<ul>\n<li>Plugin folder renamed <code>GS1-digital-link\/<\/code> \u2192 <code>gs1-digital-link\/<\/code>. Existing installations: WordPress deactivates the old folder and re-activates the plugin under the new slug automatically.<\/li>\n<li><code>gsdl-digital-link.php<\/code>: <code>Version<\/code> \u2192 <code>1.3.8<\/code>. Text domain, <code>load_plugin_textdomain()<\/code> and all 16 <code>__()<\/code> calls remain <code>gs1-digital-link<\/code>.<\/li>\n<li><code>languages\/<\/code>: locale codes lowercased end-to-end so on-disk filenames match the <code>Language:<\/code> header inside the .po file.<\/li>\n<li><code>includes\/class-gsdl-settings.php \u2192 handle_save()<\/code>: <code>$_POST['gsdl_api_key']<\/code> now goes through <code>sanitize_text_field( wp_unslash(...) )<\/code> instead of <code>trim(...)<\/code>.<\/li>\n<li><code>readme.txt<\/code>: Installation step 1 references the new lowercase folder name. <code>Stable tag<\/code> \u2192 <code>1.3.8<\/code>.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.3.7<\/h4>\n\n<ul>\n<li>Fix: pass remaining <code>Plugin Check<\/code> blockers (<code>textdomain_invalid_format<\/code> and the previously-missed <code>EscapeOutput.OutputNotEscaped<\/code> in <code>render_default()<\/code>).\n\n<ul>\n<li><code>gsdl-digital-link.php<\/code>: text domain further normalised to all-lowercase <code>gs1-digital-link<\/code>. <code>Text Domain<\/code> header, <code>load_plugin_textdomain()<\/code>, and all 16 <code>__()<\/code> \/ <code>sprintf(__())<\/code> calls now read <code>gs1-digital-link<\/code>.<\/li>\n<li><code>languages\/<\/code>: filenames renamed to <code>gs1-digital-link-*.{po,mo,pot}<\/code>.<\/li>\n<li><code>includes\/class-gsdl-admin.php<\/code>: the third <code>wp_die( self::localize(...) )<\/code> inside <code>render_default()<\/code> carries the same <code>phpcs:ignore<\/code> annotation as the calls in <code>class-gsdl-settings.php<\/code>. The two <code>$_GET['page']<\/code> reads are now routed through <code>sanitize_text_field( wp_unslash(...) )<\/code>.<\/li>\n<li><code>includes\/class-gsdl-settings.php<\/code>: <code>$_POST['gsdl_api_key']<\/code> and <code>$_POST['gsdl_locale']<\/code> now go through <code>wp_unslash()<\/code> + <code>sanitize_text_field()<\/code> before use.<\/li>\n<li>All <code>error_log()<\/code> calls in <code>gsdl-digital-link.php<\/code> and <code>includes\/class-gsdl-api.php<\/code> are now <code>phpcs:ignore<\/code>-annotated (operational logs, not debug spam).<\/li>\n<li><code>gsdl-digital-link.php \u2192 load_plugin_textdomain()<\/code> is annotated to silence the <code>PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound<\/code> warning.<\/li>\n<li><code>uninstall.php<\/code>: the three <code>wpdb-&gt;delete(...)<\/code> calls are wrapped in a <code>phpcs:disable \/ phpcs:enable<\/code> block.<\/li>\n<\/ul><\/li>\n<li>No data-layer behaviour change \u2014 admin menu slugs, REST routes, option \/ user-meta keys and the SPA bundle are byte-identical to v1.3.6.<\/li>\n<\/ul>\n\n<h4>1.3.6<\/h4>\n\n<ul>\n<li>Fix: pass WordPress.org Plugin Check (<code>WordPress.WP.I18n.TextDomainMismatch<\/code> and <code>WordPress.Security.EscapeOutput.OutputNotEscaped<\/code>).\n\n<ul>\n<li><code>gsdl-digital-link.php<\/code>: <code>Text Domain<\/code> header, <code>load_plugin_textdomain()<\/code>, and all 16 <code>__()<\/code> \/ <code>sprintf(__())<\/code> calls now agree on the canonical text domain <code>GS1-digital-link<\/code> (matches the plugin folder slug).<\/li>\n<li><code>languages\/<\/code>: <code>.po<\/code> \/ <code>.pot<\/code> \/ <code>.mo<\/code> files renamed to <code>GS1-digital-link-*.{po,mo,pot}<\/code>; <code>Project-Id-Version<\/code> updated to <code>GS1-digital-link 1.3.6<\/code>.<\/li>\n<li><code>includes\/class-gsdl-admin.php<\/code>: <code>add_submenu_page( self::PARENT_SLUG, \u2026 )<\/code> now carries a <code>phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped<\/code> annotation.<\/li>\n<li><code>includes\/class-gsdl-settings.php<\/code>: the two <code>wp_die( Gsdl_Admin::localize(...) )<\/code> call sites are annotated with the same phpcs ignore.<\/li>\n<\/ul><\/li>\n<li>No data-layer behaviour change \u2014 admin \/ REST routes, submenu slugs, user-meta \/ option keys and the SPA bundle are byte-identical to v1.3.5.<\/li>\n<\/ul>\n\n<h4>1.3.5<\/h4>\n\n<ul>\n<li>Change: repackaged the plugin zip with a WordPress-conventional top-level folder and a renamed archive file.\n\n<ul>\n<li>Distribution filename is now <code>gs1-digital-link-&lt;version&gt;.zip<\/code>. The internal PHP-side slug, plugin folder slug (<code>GS1-digital-link<\/code> on disk), text domain, WordPress menu slugs and all option \/ user-meta keys are unchanged on purpose so existing installs auto-update without user re-activation.<\/li>\n<li>The zip root now wraps every file under a single <code>GS1-digital-link\/<\/code> directory.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.3.4<\/h4>\n\n<ul>\n<li>Change: reordered the three submenu items under the top-level menu.\n\n<ul>\n<li><code>includes\/class-gsdl-admin.php \u2192 register_menu()<\/code>: the <code>add_submenu_page()<\/code> calls are now ordered <code>\u65b0\u5efa\u6570\u5b57\u94fe\u63a5 \/ \u94fe\u63a5\u5217\u8868 \/ \u8bbe\u7f6e<\/code> (zh) and <code>New Digital Link \/ Link List \/ Settings<\/code> (en).<\/li>\n<li>No code logic changed \u2014 the same three submenu slugs and their <code>?page=<\/code> handlers are intact.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.3.3<\/h4>\n\n<ul>\n<li>Change: clicking the top-level menu now lands on the \"New digital link\" wizard instead of the link list.\n\n<ul>\n<li><code>includes\/class-gsdl-admin.php \u2192 resolve_view()<\/code>: when WordPress loads the page without a <code>?page=<\/code> query (top-level menu click), the default <code>view<\/code> is now <code>'wizard'<\/code>. The three explicit sub-menus keep their previous mapping unchanged.<\/li>\n<\/ul><\/li>\n<li>Change: deferred API Key validation in the wizard until the user clicks \"Next\".\n\n<ul>\n<li><code>src\/pages\/Wizard.tsx<\/code>: the <code>listCategories()<\/code> call is no longer fired on page mount \u2014 it now runs only when the wizard advances to step 2.<\/li>\n<li><code>src\/pages\/Wizard.tsx \u2192 handleNext()<\/code>: now <code>async<\/code>. Before advancing the step, it calls <code>getValidJwt()<\/code>. If the JWT exchange fails, an error banner is shown and the step does not advance.<\/li>\n<\/ul><\/li>\n<li>Change: global rename to \"GS1 Digital Link Generator\" \u2014 every user-visible surface uses the new name.\n\n<ul>\n<li><code>includes\/class-gsdl-admin.php<\/code>: top-level menu \/ sidebar label aligned.<\/li>\n<li><code>includes\/class-gsdl-settings.php<\/code>: H1 updated.<\/li>\n<li><code>src\/i18n\/zh-CN.ts<\/code> + <code>src\/i18n\/en-US.ts<\/code>: <code>app.title<\/code>, <code>settings.localeChangeHint<\/code> updated.<\/li>\n<li><code>index.html<\/code>: <code>&lt;title&gt;GS1 Digital Link Generator&lt;\/title&gt;<\/code>.<\/li>\n<li><code>readme.txt<\/code>: Description opening line, Settings installation step 3, and the \"stored with base64 encoding\" mention (out-of-date since v1.0.5) all updated to the new name + AES-256-GCM storage.<\/li>\n<\/ul><\/li>\n<li>Bundle: <code>admin\/assets\/main-*.js<\/code> rebuilt via <code>npm run build<\/code>; <code>languages\/*.mo<\/code> rebuilt.<\/li>\n<\/ul>\n\n<h4>1.3.2<\/h4>\n\n<ul>\n<li>Change: readme.txt rephrased to remove plan-tier gating language, in line with the WordPress Plugin Directory \"no paywalls \/ license gates\" guideline.\n\n<ul>\n<li>Description bullet that previously read <code>... Enterprise \/ Private plans only.<\/code> now reads <code>... Any Smartonelink account can create one.<\/code><\/li>\n<li>FAQ <code>What API Key do I need?<\/code> previously ended with <code>Only Enterprise \/ Private plan users can create tokens.<\/code>; now ends with <code>The token works with this plugin immediately after you paste it on the settings page.<\/code><\/li>\n<\/ul><\/li>\n<li>No code changes; the plugin itself has never gated features by plan tier. This release is documentation \/ Directory-review compliance only.<\/li>\n<\/ul>\n\n<h4>1.3.1<\/h4>\n\n<ul>\n<li>Add: read-only \"Custom Domain\" row on the settings page (visual placeholder only).\n\n<ul>\n<li><code>includes\/class-gsdl-settings.php \u2192 render_page()<\/code>: new <code>&lt;tr&gt;<\/code> directly under the API Key row. Label <code>\u81ea\u5b9a\u4e49\u57df\u540d<\/code> \/ <code>Custom Domain<\/code> (zh \/ en); input is <code>&lt;input type=\"text\" value=\"id.gtin.xin\" class=\"regular-text\" disabled readonly \/&gt;<\/code>.<\/li>\n<li>Field carries no <code>name<\/code> attribute, so <code>handle_save()<\/code> does not read it \u2014 no DB write, no API call, no validation.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.3.0<\/h4>\n\n<ul>\n<li>Change: plugin renamed from <code>Smartonelink Digital Link<\/code> to <code>GS1 Digital Link Generator<\/code>.\n\n<ul>\n<li><code>GS1-digital-link.php<\/code> Plugin Header \u2192 <code>Plugin Name: GS1 Digital Link Generator<\/code>.<\/li>\n<li><code>class-gsdl-admin.php<\/code> top-level menu label \u2192 <code>self::localize('GS1 \u6570\u5b57\u94fe\u63a5\u751f\u6210\u5668', 'GS1 Digital Link Generator')<\/code>.<\/li>\n<li><code>GS1-digital-link.php<\/code> error prompt \u2192 <code>\u8bf7\u5230 GS1 \u6570\u5b57\u94fe\u63a5\u751f\u6210\u5668 \u2192 \u8bbe\u7f6e \u586b\u5199<\/code> (2 occurrences).<\/li>\n<li><code>readme.txt<\/code> plugin header \u2192 <code>=== GS1 Digital Link Generator ===<\/code>.<\/li>\n<li><strong>Not changed on purpose<\/strong> (would break existing installs): Text Domain <code>GS1-digital-link<\/code>, plugin folder slug, zip filename, PHP class prefix <code>Gsdl_*<\/code>, package name <code>GS1-digital-link-spa<\/code>, user meta keys, option keys, REST API slug, history changelog entries referencing the old name.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.2.9<\/h4>\n\n<ul>\n<li>Change: default resolver domain updated from <code>id.gsdl.link<\/code> to <code>id.gtin.xin<\/code>.\n\n<ul>\n<li><code>src\/data\/gs1Data.ts<\/code>: <code>DEFAULT_DOMAIN_URL<\/code> constant and the default <code>Smartonelink<\/code> option's <code>label<\/code> updated to <code>id.gtin.xin<\/code>. All callers go through <code>getCurrentDomainUrl()<\/code> \/ <code>getDomainUrl()<\/code> so QR code payload, link preview and base identifier pick up the new default automatically.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.2.8<\/h4>\n\n<ul>\n<li>Fix: PHP-side admin strings (sidebar menu, settings page H1, helper text, save button) now follow the user's <code>\u8bed\u8a00\u8bbe\u7f6e<\/code> choice immediately, no WordPress user-locale migration required.\n\n<ul>\n<li><code>class-gsdl-admin.php<\/code>: new <code>public static function localize(string $zh, string $en)<\/code> that consults <code>resolve_preferred_locale()<\/code> (user meta \u2192 WP locale \u2192 <code>zh_CN<\/code>). It bypasses <code>__()<\/code> entirely.<\/li>\n<li>All Smartonelink PHP strings now go through <code>localize()<\/code>.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.2.7<\/h4>\n\n<ul>\n<li>Fix: i18n hotfix for the wizard step titles and the WordPress menu translation chain.\n\n<ul>\n<li><code>Wizard.tsx<\/code>: the four step titles used to be computed at module top-level via <code>t(...)<\/code>, before <code>initI18n()<\/code> had a chance to swap in the English bundle, so they always rendered in Chinese. The four strings are now built inside the <code>Wizard<\/code> component body.<\/li>\n<li><code>class-gsdl-settings.php \u2192 handle_save()<\/code>: the user's <code>\u8bed\u8a00\u8bbe\u7f6e<\/code> choice was being written only to <code>wp_usermeta<\/code> as <code>gsdl_dl_locale<\/code>. We now also call <code>wp_update_user(['ID' =&gt; ..., 'locale' =&gt; ...])<\/code> on save so the chosen locale propagates to <code>wp_users.user_locale<\/code>.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.2.6<\/h4>\n\n<ul>\n<li>i18n: complete the English translation coverage for the SPA settings page, link list, and the four-step wizard.\n\n<ul>\n<li>Settings page, link list, wizard: every translatable string routed through <code>t()<\/code>.<\/li>\n<li>New i18n keys added across <code>src\/i18n\/{zh-CN,en-US}.ts<\/code> (settings + wizard + list).<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.2.5<\/h4>\n\n<ul>\n<li>Change: Smartonelink backend URL is now hard-coded to <code>https:\/\/app.smartonelink.com\/api<\/code> and is no longer user-configurable.\n\n<ul>\n<li><code>Gsdl_Api::get_base_url()<\/code> always returns the SmartOneLink SaaS endpoint; the legacy <code>wp_options<\/code> storage and the user-editable input on the settings page are removed.<\/li>\n<\/ul><\/li>\n<li>Change: API Key helper text rewritten to a single sentence with the exact creation path.<\/li>\n<li>Add: <code>\u8bed\u8a00\u8bbe\u7f6e<\/code> section in the settings page (user preference, persisted per user).<\/li>\n<\/ul>\n\n<h4>1.2.4<\/h4>\n\n<ul>\n<li>Fix: \"Add GS1 extended identifier data\" modal could not retain user input.\n\n<ul>\n<li>Root cause: <code>getKeyQualifiersForIdentifier(identifierCode)<\/code> returned a freshly allocated array on every render, so <code>visibleKeyQualifiers<\/code> had a new reference each render and the init <code>useEffect<\/code> re-ran on every onChange.<\/li>\n<li>Fix: wrap <code>visibleKeyQualifiers<\/code> in <code>useMemo(..., [identifierCode])<\/code> so the reference is stable until <code>identifierCode<\/code> changes.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.2.3<\/h4>\n\n<ul>\n<li>Improve: 60-second client-side cache for the QR modal's dynamic-item data.\n\n<ul>\n<li>New hook <code>src\/hooks\/useDynamicItemsCache.ts<\/code> mirrors <code>useLinkListCache<\/code>: in-memory Map keyed by <code>anchorId<\/code> with a 60s TTL and LRU eviction at 200 entries.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.2.2<\/h4>\n\n<ul>\n<li>Add: link switcher in the QR code preview modal.\n\n<ul>\n<li>When the modal opens, the plugin calls <code>gsdlApi.listDynamicItems(anchorId)<\/code> to load the extended identifier data list. Each item exposes its own <code>fullUrl<\/code>, so users can switch the QR code between the primary digital link and any extension variant.<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.2.1<\/h4>\n\n<ul>\n<li>Improve: QR code preview modal in the list view \u2014 full rewrite to match the Smartonelink console modal.\n\n<ul>\n<li>Title is now <code>QR Code Preview &amp; Download<\/code>. Modal width is 560px (was 420px). Added a dedicated <code>Download PNG<\/code> button (uses the bundled <code>qrcode<\/code> library, no third-party image service).<\/li>\n<\/ul><\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>Add: GS1 digital link generator in the wizard (first step). Pick an application identifier, enter its value, optionally expand key qualifiers \/ GS1 data attributes, and validate the format locally with the same rules as the Smartonelink console.<\/li>\n<li>Add: multi-scene link management page per digital link (linkType \/ context \/ hreflang \/ target URL \/ default flag \/ scope).<\/li>\n<li>Add: GS1 extended identifier data page per digital link \u2014 list, add, edit and delete key-qualifier \/ GS1-attribute segments.<\/li>\n<li>Add: QR code preview \u2014 inline QR on the wizard success page and a preview modal from the list page.<\/li>\n<\/ul>\n\n<h4>Older releases<\/h4>\n\n<ul>\n<li>v1.0.0 - v1.0.18: Initial public release and 18 patch versions of incremental hardening (UTF-8 BOM strip, MO build fix, path-prefix whitelist, BOM-stripping script, AES-256-GCM API Key storage, proxy JWT auto-refresh, two-tier cache, link-switcher in QR modal, BOM audit, etc.). See git history at <code>https:\/\/github.com\/phonograph123\/SmartOneLink-Digital-Link-Generator-for-GS1\/commits\/main<\/code> for per-version notes.<\/li>\n<\/ul>","raw_excerpt":"Bridge between WordPress and the SmartOneLink Smartonelink platform. Generate GS1 Digital Links pointing to local WordPress posts.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/360852","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=360852"}],"author":[{"embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/phonograph"}],"wp:attachment":[{"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=360852"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=360852"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=360852"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=360852"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=360852"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=360852"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}