Plugin Directory

Changeset 3793441


Ignore:
Timestamp:
11/10/2026 12:03:58 AM (18 hours ago)
Author:
aumcreate
Message:

1.4.5

  • Changed: the catalogue export and import call fputcsv() and fgetcsv() with an explicit $escape argument of . The default changes in PHP 8.4, and an empty string is the behaviour described by RFC 4180. Files written by earlier versions that contain a backslash immediately before a quotation mark will read that backslash as part of the field rather than as an escape. Files from Excel, Numbers and Google Sheets are unaffected.
  • Removed: the bundled Simplified Chinese translation. Translations are served as language packs built from translate.wordpress.org, not shipped inside the plugin. No language pack exists yet, so after updating, a site running in Simplified Chinese will show the English strings until one is approved.

Comment-only edits in 8 further files (no runtime change; verified by
comparing php -w output before and after).

assets: remove icon.svg. wordpress.org prefers the SVG when one is
present, so the PNG icons were never referenced.

Location:
aumnexcart
Files:
3 deleted
14 edited

Legend:

Unmodified
Added
Removed
  • aumnexcart/trunk/assets/css/enquiry.css

    r3755124 r3793441  
    1010.aum-enquiry [hidden], .aum-enquiry-link [hidden] { display: none !important; }
    1111
    12 /* ── Entry point with badge ── */
     12/* Entry point with badge */
    1313.aum-enquiry-link { display: inline-flex; align-items: center; gap: 8px; text-decoration: none; color: inherit; }
    1414.aum-enquiry-count {
    … …  
    2121.aum-enquiry-count.is-empty { opacity: .45; }
    2222
    23 /* ── Add button (as emitted by [aumframe_product_action]) ── */
     23/* Add button (as emitted by [aumframe_product_action]) */
    2424.aum-enquiry-add { display: inline-flex; align-items: center; gap: 8px; text-decoration: none; cursor: pointer; }
    2525/* Themes with an element-level colour reset would recolour the inner span; inherit keeps the button's own colour */
    … …  
    2727.aum-enquiry-add[data-aum-enquiry-in] { opacity: .72; }
    2828
    29 /* ── The sheet ── */
     29/* The sheet */
    3030.aum-enquiry { width: 100%; }
    3131.aum-enquiry__empty { padding: 24px 0; }
  • aumnexcart/trunk/assets/css/frontend.css

    r3755124 r3793441  
    212212}
    213213
    214 /* Full-width product body (图文正文) below the two-column layout. */
     214/* Full-width product body below the two-column layout. */
    215215.aumnexcart-single__content {
    216216        margin: 48px 0 0;
  • aumnexcart/trunk/assets/js/enquiry.js

    r3755124 r3793441  
    2424  const L = cfg.labels || {};
    2525
    26   /* ── State ─────────────────────────────────────────────────── */
     26  /* State */
    2727
    2828  function read() {
    … …  
    6767  window.AumNexCartEnquiry = api;
    6868
    69   /* ── Parsing add links ─────────────────────────────────────── */
     69  /* Parsing add links */
    7070
    7171  /** `12` / `12:4` / `12:4,15` → [{id, qty}] */
    … …  
    8484  }
    8585
    86   /* ── Reflecting state ──────────────────────────────────────── */
     86  /* Reflecting state */
    8787
    8888  function swapLabel(el, from, to) {
    … …  
    115115  }
    116116
    117   /* ── The sheet ─────────────────────────────────────────────── */
     117  /* The sheet */
    118118
    119119  const cache = new Map();   // id → row data from REST
    … …  
    231231  }
    232232
    233   /* ── Wiring ────────────────────────────────────────────────── */
     233  /* Wiring */
    234234
    235235  document.addEventListener('click', (e) => {
  • aumnexcart/trunk/aumnexcart.php

    r3755124 r3793441  
    44 * Plugin URI:        https://aumcreate.com/plugins/aumnexcart
    55 * Description:       A product catalogue with real specification fields, where visitors collect items and send one structured enquiry instead of checking out.
    6  * Version:           1.4.4
     6 * Version:           1.4.5
    77 * Requires at least: 5.8
    88 * Requires PHP:      7.4
    … …  
    1414 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
    1515 *
    16  * ⚠️ **There is deliberately no `Update URI:` header and no self-hosted updater.**
     16 *  There is deliberately no `Update URI:` header and no self-hosted updater.
    1717 *
    1818 * There used to be both. The header pointed at aumcreate.com, and WordPress 5.8+ reads it as
    … …  
    2121 * it as an update.
    2222 *
    23  * On wordpress.org that same header **inverts**: updates come from .org, and a header naming any
     23 * On wordpress.org that same header inverts: updates come from .org, and a header naming any
    2424 * other host makes core skip the .org lookup, so published updates would never reach anyone. The
    2525 * slug is ours once the plugin is approved, so the thing the header was protecting against is gone
    … …  
    4040 */
    4141/*
    42  * 🔴 **版本号只有一处来源:插件头。** 不要写死。
     42 * The version has a single source: the plugin header. It must not be hard-coded here.
    4343 *
    44  * 2026-10-06 查出来:头是 1.3.1,而这个常量冻在 1.2.0 —— 差了几个发布,
    45  * 而且**它不是一个显示用的数**:
    46  *   · 它是 14 处 `wp_enqueue_style/script` 的版本参数。冻住 = 买家更新后浏览器和 CDN 继续发旧的 CSS/JS,
    47  *     而 PHP 已经换了新的:新逻辑配旧样式,两边日志都干净。
    48  *   · 没有任何闸在看这两处是否一致(.org 的 Plugin Check 不管,我们自己的也不管)
    49  *
    50  * 这是这条线上最一致的失败形状:**两处必须保持一致,而没有任何机制在管。**
    51  * 正确做法不是再加一道闸去盯着两个数,是让它只有一个数。
    52  * 写法抄自 `aumframe/aumframe.php` —— 那边一直是对的。
     44 * This constant is the version argument for fourteen `wp_enqueue_style`/`wp_enqueue_script`
     45 * calls, so a stale value makes browsers and CDNs keep serving the previous CSS and JS
     46 * after an update while the PHP has already changed, with nothing logged on either side.
     47 * No check compares a literal here against the header, so the two are read from one value
     48 * instead.
    5349 */
    5450define( 'AUMNEXCART_VERSION', (string) ( get_file_data( __FILE__, array( 'Version' => 'Version' ) )['Version'] ?: '0' ) );
  • aumnexcart/trunk/changelog.txt

    r3755124 r3793441  
    44readme.txt carries only the most recent entries: wordpress.org truncates the Changelog
    55section at 5000 bytes, silently. Everything is kept here.
     6
     7= 1.4.5 =
     8* Changed: the catalogue export and import call `fputcsv()` and `fgetcsv()` with an explicit
     9  `$escape` argument of `''`. The default changes in PHP 8.4, and an empty string is the
     10  behaviour described by RFC 4180 — the one spreadsheet software produces and expects. Files
     11  written by earlier versions that contain a backslash immediately before a quotation mark will
     12  read that backslash as part of the field rather than as an escape. Files from Excel, Numbers
     13  and Google Sheets are unaffected.
    614
    715= 1.4.4 =
  • aumnexcart/trunk/includes/class-aumviso-bridge.php

    r3755124 r3793441  
    1414
    1515/*
    16  * ⚠️ The interface is `AumViso_Product_Source`.
     16 * The interface is `AumViso_Product_Source`.
    1717 *
    1818 * It was `AumSEO_Product_Source` when this bridge was written, and AumViso later renamed it. Nothing here
    19  * errored — the guard below simply stopped matching, so this file **returned at the top on every request
    20  * and the bridge has been dead ever since**. NexCart products silently carried no Product schema on any
    21  * site running both plugins, and the only visible symptom was structured data that was never there.
    22  *
    23  * Found 2026-09-03 while checking which source actually claimed `aumnexcart_product`: the answer was the
    24  * theme's catalogue bridge, which had quietly been covering for this one.
     19 * errored: the guard below simply stopped matching, so this file returned at the top on every request and
     20 * the bridge did nothing. NexCart products carried no Product schema on any site running both plugins, and
     21 * the only symptom was structured data that was never there. The theme's catalogue bridge had been claiming
     22 * `aumnexcart_product` in its place, which is why nothing looked broken.
    2523 */
    2624if ( ! interface_exists( 'AumViso_Product_Source' ) ) {
  • aumnexcart/trunk/includes/class-docs.php

    r3755124 r3793441  
    77 * no such channel, and before this there was nothing in the admin that said where the manual is.
    88 *
    9  * 🔴 **An address nobody can reach fails silently.** A 404 here raises no warning, writes no log
     9 * An address nobody can reach fails silently: a 404 here raises no warning, writes no log
    1010 * entry, and does not stop the plugin working — someone clicks it, lands on nothing, and we hear
    1111 * about it from a support email if at all. So the table below is the only place an address is
    1212 * written, an empty string means *not published in that language* and renders no link at all,
    13  * and every one of these four was fetched and returned 200 before being written here
    14  * (2026-10-19). Never type one from memory: a `/wp-json` endpoint was invented once in this
    15  * codebase and nothing anywhere reported it.
     13 * and every one of these four returned 200 when fetched before being written here. An address
     14 * must not be written from memory: an endpoint invented this way once went unreported by every
     15 * layer of the stack.
    1616 *
    1717 * Modelled on `aumframe/src/Docs.php` — same table shape, same locale mapping, same reasons.
    … …  
    3535         * WordPress admin language → which of those four to send them to.
    3636         *
    37          * 🔴 **The language of the prose and the language of the screenshots are two different things.**
     37         *  The language of the prose and the language of the screenshots are two different things.
    3838         * This plugin ships one translation, `zh_CN` (there is no `zh_TW` and no `ja` in `languages/`,
    3939         * and WordPress does not fall `zh_TW` back to `zh_CN`). So a Traditional Chinese or Japanese
    40          * administrator is reading an **English admin screen**, and the manual's zh-hant and ja pages
    41          * are translations of the prose carrying the **English** screenshots — which is what matches
     40         * administrator is reading an English admin screen, and the manual's zh-hant and ja pages
     41         * are translations of the prose carrying the English screenshots, which is what matches
    4242         * what is on their screen. Sending them to the English page would be worse; sending `zh_TW`
    4343         * to zh-hans would be a guess.
    … …  
    5656         *
    5757         * `get_user_locale()` rather than `get_locale()`: the Plugins screen is read in whatever
    58          * language **that administrator** set for themselves, which can differ from the site's.
     58         * language that administrator set for themselves, which can differ from the site's.
    5959         */
    6060        private static function locale(): string {
  • aumnexcart/trunk/includes/class-enquiry.php

    r3755124 r3793441  
    33 * The enquiry list — the stateful half of an enquiry catalogue (1.1.0).
    44 *
    5  * Until now a NexCart product could only end in a **contact link**: mailto:, wa.me, a custom URL. That answers
    6  * "how do I reach you" but not the thing a buyer actually does on a B2B catalogue — collect a dozen items
    7  * across a dozen pages, with quantities, and send them **as one enquiry**. The plugin is called NexCart and
    8  * had no cart. (Template #3 wrote this down as the wall to hit next, 2026-08-22; template #12 hit it.)
     5 * Before this, a NexCart product could only end in a contact link - mailto:, wa.me, a custom URL - which
     6 * answers how to reach the seller but not what a buyer does on a B2B catalogue: collect a dozen items across
     7 * a dozen pages, with quantities, and send them as one enquiry.
    98 *
    10  * ── The shape ─────────────────────────────────────────────────
    11  *  · The list lives **in the visitor's browser** (`localStorage`), never on the server. No session, no cookie,
     9 *  The shape
     10 *  - The list lives in the visitor's browser (`localStorage`), never on the server. No session, no cookie,
    1211 *    nothing for a page cache to serve stale, nothing to purge — the same reason a wishlist is client-side.
    13  *  · A product joins the list through **one URL**: `<enquiry page>?aumnexcart_enquiry_add=<id>`. Plain `<a>` links
     12 *  - A product joins the list through one URL: `<enquiry page>?aumnexcart_enquiry_add=<id>`. Plain `<a>` links
    1413 *    carrying it work everywhere — inside an Aumframe loop bound to `post.product.action_url`, in a mail, with
    1514 *    JavaScript off (the visitor lands on the enquiry page, which adds it) — and the front-end script upgrades
    1615 *    every such link in place: click → added, no navigation, badge updates.
    17  *  · The enquiry page renders the list from a shortcode, resolves ids to titles through REST, lets the buyer set
    18  *    quantities, and **writes a plain-text summary into a hidden field of whatever form sits on the page**
     16 *  - The enquiry page renders the list from a shortcode, resolves ids to titles through REST, lets the buyer set
     17 *    quantities, and writes a plain-text summary into a hidden field of whatever form sits on the page
    1918 *    (`enquiry_field` setting, default `enquiry_items`). The theme's `aumframe_form` carries it into the mail and the
    2019 *    entries screen. That is the whole hand-off: no coupling to the form system beyond a field name.
    2120 *
    22  * ── Why the server never sees the list ────────────────────────
    23  *  A cookie-backed list (what WooCommerce does for the cart) would make every page vary by visitor, which the
    24  *  cache layers most buyers run (LiteSpeed, host-level) do not know about unless told — the cart problem that
    25  *  cost a day on 2026-08-30. Keeping it in the browser costs one REST call on the enquiry page and nothing else.
     21 *  Why the server never sees the list
     22 * A cookie-backed list, as WooCommerce uses for its cart, would make every page vary by visitor, which the
     23 * cache layers most buyers run (LiteSpeed, host-level) do not account for unless configured to. Keeping the
     24 * list in the browser costs one REST call on the enquiry page and nothing else.
    2625 *
    2726 * @package AumNexCart
    … …  
    3635
    3736        /*
    38          * 短代码名**只在这里定义一次**。2026-10-11 之前它是两处字面量
    39          * (`add_shortcode()` 一处、设置页那句说明里一处),而说明那一句是
    40          * 可翻译字符串 —— 改名会把译文打成 fuzzy,而 fuzzy 不进 `.mo`。
    41          * 会被改名的东西不该待在 msgid 里,也不该有第二份定义。
     37         * The shortcode name is defined here once. It was previously a literal in two places,
     38         * `add_shortcode()` and a sentence on the settings screen, and that sentence is a
     39         * translatable string: renaming the shortcode marked its translations fuzzy, and fuzzy
     40         * entries are excluded from the compiled `.mo`. A name that can change does not belong
     41         * inside a msgid, and must not have a second definition.
    4242         */
    4343        const SHORTCODE_SHEET = 'aumnexcart_enquiry';
    … …  
    7070        }
    7171
    72         /* ── Settings-derived facts ───────────────────────────────── */
     72        /* Settings-derived facts */
    7373
    7474        /** The enquiry page, when one is set and published. */
    … …  
    135135        }
    136136
    137         /* ── Shortcodes ──────────────────────────────────────────── */
     137        /* Shortcodes */
    138138
    139139        public function shortcode_link( $atts ) {
    … …  
    182182        }
    183183
    184         /* ── REST: ids → rows ────────────────────────────────────── */
     184        /* REST: ids → rows */
    185185
    186186        public function register_rest() {
    … …  
    246246        }
    247247
    248         /* ── Front-end assets ────────────────────────────────────── */
     248        /* Front-end assets */
    249249
    250250        /*
    251          * ⚠️ No `is_admin()` guard: the Aumframe editor runs `wp_enqueue_scripts` inside its admin request to learn
     251         * No `is_admin()` guard: the Aumframe editor runs `wp_enqueue_scripts` inside its admin request to learn
    252252         * what the front end loads, and the stylesheet must reach the canvas too (the badge's size decides the
    253          * header's width; the theme's cart link learned this on 2026-09-13).
     253         * header's width).
    254254         */
    255255        public function enqueue() {
  • aumnexcart/trunk/includes/class-meta.php

    r3755124 r3793441  
    6060                        }
    6161                        /*
    62                          * ⚠️ 只给了 attachment_id、没给 url 的行**也算数**。
     62                         * A row that gives only `attachment_id` and no `url` is valid.
    6363                         *
    64                          * 原来这里是 `empty($item['url']) -> continue`,于是一行"只知道附件 ID"
    65                          * 的媒体被静默丢掉。两个地方会踩到:
    66                          *   · 模板包(kit)里只能写附件引用,**写不出 URL** —— 包在导入之前
    67                          *     根本不知道买家站的域名是什么;
    68                          *   · 站点换域名之后,存下来的绝对 URL 全部失效,而 ID 不会。
    69                          * 存 ID 比存 URL 更对,所以这里补一条:url 为空就从 ID 现算。
     64                         * Discarding those rows drops media silently in two cases: a kit can only express
     65                         * attachment references, because the package does not know the buyer's domain
     66                         * before it is imported; and after a site changes domain, stored absolute URLs stop
     67                         * resolving while attachment IDs still do. The ID is the better record, so a missing
     68                         * URL is derived from it here.
    7069                         */
    7170                        if ( empty( $item['url'] ) && ! empty( $item['attachment_id'] ) ) {
  • aumnexcart/trunk/includes/class-plugin.php

    r3755124 r3793441  
    5151
    5252                /*
    53                  * ⚠️ On `init`, not here.
     53                 * On `init`, not here.
    5454                 *
    55                  * Plugins load in directory order, so `aumnexcart` boots **before** `aumviso` and none of AumViso's
     55                 * Plugins load in directory order, so `aumnexcart` boots before `aumviso` and none of AumViso's
    5656                 * constants, interfaces or filters exist yet at this point. Wiring the bridge during
    5757                 * `plugins_loaded` therefore checked for things that had not been defined and quietly did nothing.
    … …  
    6161
    6262        /**
    63          * When the AumViso GEO plugin is active, register AumNexCart as a product
    64          * source and opt its CPT into AumViso's SEO/GEO coverage, so catalog
    65          * products receive meta, Open Graph and Product JSON-LD. No-op otherwise,
    66          * keeping NexCart fully standalone.
    67          */
    68         /**
    69          * Register with AumViso, when AumViso is there.
     63         * Registers AumNexCart with AumViso when AumViso is active, as a product source and as a
     64         * covered post type, so catalog products receive meta, Open Graph and Product JSON-LD.
     65         * A no-op otherwise, which keeps NexCart standalone.
    7066         *
    71          * 🔴 This bridge was dead for as long as the rename was old, and in **three independent ways** — the
    72          * constant, the interface and the filter were all renamed on AumViso's side, and every one of them
    73          * fails by doing nothing at all. NexCart products carried no Product schema and were left out of
    74          * AumViso's enabled types on every site running both, with no error anywhere to say so.
     67         * The constant, the interface and the filter this bridge depends on were all renamed on
     68         * AumViso's side, and each of the three fails by doing nothing: products carried no
     69         * Product schema and were absent from AumViso's enabled types on every site running
     70         * both plugins, with nothing reported anywhere.
    7571         *
    76          * Public because it is now a hook callback; see `boot_subsystems()` for why it runs on `init`.
     72         * Public because it is a hook callback; see `boot_subsystems()` for why it runs on `init`.
    7773         */
    7874        public function maybe_bridge_aumviso() {
    … …  
    9187
    9288                /*
    93                  * ⚠️ `aumviso_product_sources` — the filter was `aumseo_product_sources` when this was written.
    94                  *
    95                  * Both this name and the interface it implements were renamed on AumViso's side, and neither
    96                  * produced an error: the interface check made the bridge file return early, and even had it not,
    97                  * this hook fires for a filter nothing runs. Two independent reasons for the same silent nothing.
     89                 * The filter is `aumviso_product_sources`; it was `aumseo_product_sources` under the old
     90                 * name. Both this name and the interface it implements were renamed on AumViso's side, and
     91                 * neither change produced an error: the interface check made the bridge file return early,
     92                 * and even without that, this hook would fire for a filter nothing runs.
    9893                 */
    9994                add_filter(
    … …  
    107102
    108103                /*
    109                  * ⚠️ Also renamed. Same silent nothing as above: NexCart products were never added to AumViso's
     104                 * Also renamed, with the same silent result: NexCart products were never added to AumViso's
    110105                 * enabled types, so a NexCart site got no meta description, canonical or schema on its product
    111106                 * pages until somebody ticked the box by hand.
  • aumnexcart/trunk/includes/class-settings.php

    r3755124 r3793441  
    133133                         * separate "AumNexCart" CPT menu.
    134134                         *
    135                          * 🔴 The parent slug is asked for, not hard-coded. AumCreate 2.11.0 moved its
     135                         * The parent slug is asked for, not hard-coded. AumCreate 2.11.0 moved its
    136136                         * admin pages under Appearance, because wordpress.org does not allow a theme to
    137137                         * create a top-level menu — so `aumcreate-settings` stopped being a top-level
    138138                         * menu. `add_submenu_page()` still returns a normal hook suffix in that case and
    139139                         * the entry still lands in `$submenu['aumcreate-settings']`, but that array has
    140                          * no menu to be rendered under: **this page disappears from the admin with no
    141                          * error of any kind.** Measured on 2026-09-30 — 83 menu items, "NexCart" not one
    142                          * of them, while the plugin reported itself as working.
     140                         * no menu to be rendered under, so the page disappears from the admin with no error
     141                         * of any kind: the rendered menu held 83 items with no "NexCart" entry among them,
     142                         * while the plugin reported itself as working.
    143143                         *
    144144                         * `function_exists()` keeps this working on older versions of the theme, where
    … …  
    354354                 * There is no licence gate here, and there is no licence anywhere in this plugin.
    355355                 *
    356                  * It used to lock an unlicensed site onto a "License" tab. That went in 2026-08-26 (the plugin is
    357                  * part of a suite, never sold on its own), leaving only a read of the theme's licence code for the
    358                  * self-hosted update check — and that went too when the plugin moved to wordpress.org, where
    359                  * updates come from .org and there is nothing to authorise.
     356                 * It used to lock an unlicensed site onto a "License" tab. That was removed because the plugin is
     357                 * part of a suite and is never sold on its own, leaving only a read of the theme's licence code
     358                 * for the self-hosted update check, which was removed in turn when the plugin moved to
     359                 * wordpress.org, where updates come from .org and there is nothing to authorise.
    360360                 */
    361361                $settings   = self::get_all();
  • aumnexcart/trunk/includes/class-tools.php

    r3755124 r3793441  
    8989                // phpcs:ignore WordPress.WP.AlternativeFunctions -- this is the CSV download stream, not the filesystem: the handle is php://output, opened after the Content-Disposition header above, so every write goes straight to the browser. WP_Filesystem abstracts disk access (and FTP/SSH credentials) and has no path for an output stream.
    9090                fwrite( $out, "\xEF\xBB\xBF" );
    91                 fputcsv( $out, self::CSV_COLUMNS );
     91                fputcsv( $out, self::CSV_COLUMNS, ',', '"', '' );
    9292
    9393                $paged = 1;
    … …  
    108108                        }
    109109                        foreach ( $q->posts as $post ) {
    110                                 fputcsv( $out, self::row_for_post( $post ) );
     110                                fputcsv( $out, self::row_for_post( $post ), ',', '"', '' );
    111111                        }
    112112                        $paged++;
    … …  
    194194                }
    195195
    196                 $header = fgetcsv( $fh );
     196                $header = fgetcsv( $fh, null, ',', '"', '' );
    197197                if ( ! $header ) {
    198198                        // phpcs:ignore WordPress.WP.AlternativeFunctions -- reading PHP's own upload temp file ($_FILES['tmp_name']) row by row. WP_Filesystem can only hand back the whole file at once (get_contents_array), which would put an arbitrarily large catalogue import in memory, and fgetcsv needs a handle. The path never comes from user input.
    … …  
    214214
    215215                $rows = array();
    216                 while ( ( $row = fgetcsv( $fh ) ) !== false ) {
     216                while ( ( $row = fgetcsv( $fh, null, ',', '"', '' ) ) !== false ) {
    217217                        if ( count( array_filter( $row, 'strlen' ) ) === 0 ) {
    218218                                continue;
    … …  
    320320
    321321                /*
    322                  * 🔴 **CSV 的内容不是导入者打出来的,是文件给的。**
     322                 * CSV content is supplied by the file, not typed by the person importing it.
    323323                 *
    324                  * `wp_insert_post()` 只对**没有** `unfiltered_html` 的用户跑 kses,而单站管理员是有的。
    325                  * 于是一份 CSV 里的 `<script>` 会原样进 `post_content`,再由 `the_content` 渲染出来。
     324                 * `wp_insert_post()` runs kses only for users without `unfiltered_html`, and a
     325                 * single-site administrator has that capability, so a `<script>` element in a CSV
     326                 * would reach `post_content` unchanged and be rendered by `the_content`. The usual
     327                 * argument that an administrator could paste a script into the editor anyway does not
     328                 * apply here: the administrator did not write this file, and nobody reads every row of
     329                 * an imported kit before clicking Import. Trusting the administrator is not the same
     330                 * as trusting the file.
    326331                 *
    327                  * 「管理员本来就能在编辑器里贴脚本」这个说法在这里不成立:**他没写这个文件**。
    328                  * 买家导入的是我们或第三方做的 kit,他点「导入」时没读过里面每一行。
    329                  * 这条路径把「我信任这个管理员」偷换成了「我信任这个文件」。
     332                 * `wp_kses_post()` keeps what body content needs - paragraphs, links, emphasis, lists,
     333                 * images - and removes `<script>`, `<style>` and `on*` attributes. Shortcodes are
     334                 * plain text and pass through. Titles carry no markup, so `sanitize_text_field()` is
     335                 * enough for them.
    330336                 *
    331                  * `wp_kses_post()` 保留正文该有的东西(段落、链接、强调、列表、图片),
    332                  * 去掉 `<script>` / `<style>` / `on*` 事件属性。短代码是纯文本,不受影响。
    333                  * 标题不该带标记,直接 `sanitize_text_field()`。
    334                  *
    335                  * 两个方向都验过(真跑 `wp_kses_post()`,不是推的):
    336                  *   恶意 —— `<script>` / `onerror=` / `<style>` 全部去掉
    337                  *   合法 —— 段落、强调、链接、列表、图片、短代码、中日韩文字,一字未动
    338                  * ⚠️ 唯一一处会变的是裸 `&` → `&amp;`。那是**正确的 HTML 编码**,渲染出来还是 `&` ——
    339                  *    逐字节比对会把它报成「改坏了」,别据此回退。
    340                  *
    341                  * 2026-10-11 .org 审核第二轮提的(T2)。
     337                 * Both directions were checked against the real `wp_kses_post()`: malicious input
     338                 * (`<script>`, `onerror=`, `<style>`) is removed, and legitimate input - paragraphs,
     339                 * emphasis, links, lists, images, shortcodes, CJK text - is returned unchanged. The
     340                 * one transformation is a bare `&` becoming `&amp;`, which is correct HTML encoding
     341                 * and still renders as `&`; a byte comparison reports it as damage and must not be
     342                 * taken as grounds to revert.
    342343                 */
    343344                $post_id = wp_insert_post(
  • aumnexcart/trunk/readme.txt

    r3755124 r3793441  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.4.4
     7Stable tag: 1.4.5
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    4949
    5050The full history, including older releases, is in `changelog.txt` inside the plugin folder.
     51
     52= 1.4.5 =
     53* Changed: the catalogue export and import call `fputcsv()` and `fgetcsv()` with an explicit
     54  `$escape` argument of `''`. The default changes in PHP 8.4, and an empty string is the
     55  behaviour described by RFC 4180 — the one spreadsheet software produces and expects. Files
     56  written by earlier versions that contain a backslash immediately before a quotation mark will
     57  read that backslash as part of the field rather than as an escape. Files from Excel, Numbers
     58  and Google Sheets are unaffected.
     59* Removed: the bundled Simplified Chinese translation, `languages/aumnexcart-zh_CN.po` and
     60  `aumnexcart-zh_CN.mo`. Translations for plugins hosted on WordPress.org are served as
     61  language packs built from translate.wordpress.org, not shipped inside the plugin. No
     62  language pack exists for this plugin yet, so after updating, a site running in Simplified
     63  Chinese will show the English strings until one is approved and published.
    5164
    5265= 1.4.4 =
  • aumnexcart/trunk/templates/single-aumnexcart-product.php

    r3755124 r3793441  
    8484
    8585                <?php
    86                 /* Full-width product body (图文正文). Only rendered when the
     86                /* Full-width product body. Only rendered when the
    8787                 * editor content is non-empty, so summary-only products don't
    8888                 * leave an empty block. */
Note: See TracChangeset for help on using the changeset viewer.