Changeset 3793441
- Timestamp:
- 11/10/2026 12:03:58 AM (18 hours ago)
- Location:
- aumnexcart
- Files:
-
- 3 deleted
- 14 edited
-
assets/icon.svg (deleted)
-
trunk/assets/css/enquiry.css (modified) (3 diffs)
-
trunk/assets/css/frontend.css (modified) (1 diff)
-
trunk/assets/js/enquiry.js (modified) (5 diffs)
-
trunk/aumnexcart.php (modified) (4 diffs)
-
trunk/changelog.txt (modified) (1 diff)
-
trunk/includes/class-aumviso-bridge.php (modified) (1 diff)
-
trunk/includes/class-docs.php (modified) (3 diffs)
-
trunk/includes/class-enquiry.php (modified) (6 diffs)
-
trunk/includes/class-meta.php (modified) (1 diff)
-
trunk/includes/class-plugin.php (modified) (4 diffs)
-
trunk/includes/class-settings.php (modified) (2 diffs)
-
trunk/includes/class-tools.php (modified) (5 diffs)
-
trunk/languages/aumnexcart-zh_CN.mo (deleted)
-
trunk/languages/aumnexcart-zh_CN.po (deleted)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/templates/single-aumnexcart-product.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
aumnexcart/trunk/assets/css/enquiry.css
r3755124 r3793441 10 10 .aum-enquiry [hidden], .aum-enquiry-link [hidden] { display: none !important; } 11 11 12 /* ── Entry point with badge ──*/12 /* Entry point with badge */ 13 13 .aum-enquiry-link { display: inline-flex; align-items: center; gap: 8px; text-decoration: none; color: inherit; } 14 14 .aum-enquiry-count { … … 21 21 .aum-enquiry-count.is-empty { opacity: .45; } 22 22 23 /* ── Add button (as emitted by [aumframe_product_action]) ──*/23 /* Add button (as emitted by [aumframe_product_action]) */ 24 24 .aum-enquiry-add { display: inline-flex; align-items: center; gap: 8px; text-decoration: none; cursor: pointer; } 25 25 /* Themes with an element-level colour reset would recolour the inner span; inherit keeps the button's own colour */ … … 27 27 .aum-enquiry-add[data-aum-enquiry-in] { opacity: .72; } 28 28 29 /* ── The sheet ──*/29 /* The sheet */ 30 30 .aum-enquiry { width: 100%; } 31 31 .aum-enquiry__empty { padding: 24px 0; } -
aumnexcart/trunk/assets/css/frontend.css
r3755124 r3793441 212 212 } 213 213 214 /* Full-width product body (图文正文)below the two-column layout. */214 /* Full-width product body below the two-column layout. */ 215 215 .aumnexcart-single__content { 216 216 margin: 48px 0 0; -
aumnexcart/trunk/assets/js/enquiry.js
r3755124 r3793441 24 24 const L = cfg.labels || {}; 25 25 26 /* ── State ───────────────────────────────────────────────────*/26 /* State */ 27 27 28 28 function read() { … … 67 67 window.AumNexCartEnquiry = api; 68 68 69 /* ── Parsing add links ───────────────────────────────────────*/69 /* Parsing add links */ 70 70 71 71 /** `12` / `12:4` / `12:4,15` → [{id, qty}] */ … … 84 84 } 85 85 86 /* ── Reflecting state ────────────────────────────────────────*/86 /* Reflecting state */ 87 87 88 88 function swapLabel(el, from, to) { … … 115 115 } 116 116 117 /* ── The sheet ───────────────────────────────────────────────*/117 /* The sheet */ 118 118 119 119 const cache = new Map(); // id → row data from REST … … 231 231 } 232 232 233 /* ── Wiring ──────────────────────────────────────────────────*/233 /* Wiring */ 234 234 235 235 document.addEventListener('click', (e) => { -
aumnexcart/trunk/aumnexcart.php
r3755124 r3793441 4 4 * Plugin URI: https://aumcreate.com/plugins/aumnexcart 5 5 * 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. 46 * Version: 1.4.5 7 7 * Requires at least: 5.8 8 8 * Requires PHP: 7.4 … … 14 14 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 15 15 * 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. 17 17 * 18 18 * There used to be both. The header pointed at aumcreate.com, and WordPress 5.8+ reads it as … … 21 21 * it as an update. 22 22 * 23 * On wordpress.org that same header **inverts**: updates come from .org, and a header naming any23 * On wordpress.org that same header inverts: updates come from .org, and a header naming any 24 24 * other host makes core skip the .org lookup, so published updates would never reach anyone. The 25 25 * slug is ours once the plugin is approved, so the thing the header was protecting against is gone … … 40 40 */ 41 41 /* 42 * 🔴 **版本号只有一处来源:插件头。** 不要写死。42 * The version has a single source: the plugin header. It must not be hard-coded here. 43 43 * 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. 53 49 */ 54 50 define( 'AUMNEXCART_VERSION', (string) ( get_file_data( __FILE__, array( 'Version' => 'Version' ) )['Version'] ?: '0' ) ); -
aumnexcart/trunk/changelog.txt
r3755124 r3793441 4 4 readme.txt carries only the most recent entries: wordpress.org truncates the Changelog 5 5 section 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. 6 14 7 15 = 1.4.4 = -
aumnexcart/trunk/includes/class-aumviso-bridge.php
r3755124 r3793441 14 14 15 15 /* 16 * ⚠️The interface is `AumViso_Product_Source`.16 * The interface is `AumViso_Product_Source`. 17 17 * 18 18 * 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. 25 23 */ 26 24 if ( ! interface_exists( 'AumViso_Product_Source' ) ) { -
aumnexcart/trunk/includes/class-docs.php
r3755124 r3793441 7 7 * no such channel, and before this there was nothing in the admin that said where the manual is. 8 8 * 9 * 🔴 **An address nobody can reach fails silently.** A404 here raises no warning, writes no log9 * An address nobody can reach fails silently: a 404 here raises no warning, writes no log 10 10 * entry, and does not stop the plugin working — someone clicks it, lands on nothing, and we hear 11 11 * about it from a support email if at all. So the table below is the only place an address is 12 12 * 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 here14 * (2026-10-19). Never type one from memory: a `/wp-json` endpoint was invented once in this15 * 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. 16 16 * 17 17 * Modelled on `aumframe/src/Docs.php` — same table shape, same locale mapping, same reasons. … … 35 35 * WordPress admin language → which of those four to send them to. 36 36 * 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. 38 38 * This plugin ships one translation, `zh_CN` (there is no `zh_TW` and no `ja` in `languages/`, 39 39 * 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 pages41 * are translations of the prose carrying the **English** screenshots —which is what matches40 * 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 42 42 * what is on their screen. Sending them to the English page would be worse; sending `zh_TW` 43 43 * to zh-hans would be a guess. … … 56 56 * 57 57 * `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. 59 59 */ 60 60 private static function locale(): string { -
aumnexcart/trunk/includes/class-enquiry.php
r3755124 r3793441 3 3 * The enquiry list — the stateful half of an enquiry catalogue (1.1.0). 4 4 * 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. 9 8 * 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, 12 11 * 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>` links12 * - A product joins the list through one URL: `<enquiry page>?aumnexcart_enquiry_add=<id>`. Plain `<a>` links 14 13 * carrying it work everywhere — inside an Aumframe loop bound to `post.product.action_url`, in a mail, with 15 14 * JavaScript off (the visitor lands on the enquiry page, which adds it) — and the front-end script upgrades 16 15 * 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 set18 * 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 19 18 * (`enquiry_field` setting, default `enquiry_items`). The theme's `aumframe_form` carries it into the mail and the 20 19 * entries screen. That is the whole hand-off: no coupling to the form system beyond a field name. 21 20 * 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 the24 * cache layers most buyers run (LiteSpeed, host-level) do not know about unless told — the cart problem that25 * 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. 26 25 * 27 26 * @package AumNexCart … … 36 35 37 36 /* 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. 42 42 */ 43 43 const SHORTCODE_SHEET = 'aumnexcart_enquiry'; … … 70 70 } 71 71 72 /* ── Settings-derived facts ─────────────────────────────────*/72 /* Settings-derived facts */ 73 73 74 74 /** The enquiry page, when one is set and published. */ … … 135 135 } 136 136 137 /* ── Shortcodes ────────────────────────────────────────────*/137 /* Shortcodes */ 138 138 139 139 public function shortcode_link( $atts ) { … … 182 182 } 183 183 184 /* ── REST: ids → rows ──────────────────────────────────────*/184 /* REST: ids → rows */ 185 185 186 186 public function register_rest() { … … 246 246 } 247 247 248 /* ── Front-end assets ──────────────────────────────────────*/248 /* Front-end assets */ 249 249 250 250 /* 251 * ⚠️No `is_admin()` guard: the Aumframe editor runs `wp_enqueue_scripts` inside its admin request to learn251 * No `is_admin()` guard: the Aumframe editor runs `wp_enqueue_scripts` inside its admin request to learn 252 252 * 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). 254 254 */ 255 255 public function enqueue() { -
aumnexcart/trunk/includes/class-meta.php
r3755124 r3793441 60 60 } 61 61 /* 62 * ⚠️ 只给了 attachment_id、没给 url 的行**也算数**。62 * A row that gives only `attachment_id` and no `url` is valid. 63 63 * 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. 70 69 */ 71 70 if ( empty( $item['url'] ) && ! empty( $item['attachment_id'] ) ) { -
aumnexcart/trunk/includes/class-plugin.php
r3755124 r3793441 51 51 52 52 /* 53 * ⚠️On `init`, not here.53 * On `init`, not here. 54 54 * 55 * Plugins load in directory order, so `aumnexcart` boots **before**`aumviso` and none of AumViso's55 * Plugins load in directory order, so `aumnexcart` boots before `aumviso` and none of AumViso's 56 56 * constants, interfaces or filters exist yet at this point. Wiring the bridge during 57 57 * `plugins_loaded` therefore checked for things that had not been defined and quietly did nothing. … … 61 61 62 62 /** 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. 70 66 * 71 * 🔴 This bridge was dead for as long as the rename was old, and in **three independent ways** — the72 * constant, the interface and the filter were all renamed on AumViso's side, and every one of them73 * fails by doing nothing at all. NexCart products carried no Product schema and were left out of74 * 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. 75 71 * 76 * Public because it is nowa 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`. 77 73 */ 78 74 public function maybe_bridge_aumviso() { … … 91 87 92 88 /* 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. 98 93 */ 99 94 add_filter( … … 107 102 108 103 /* 109 * ⚠️ Also renamed. Same silent nothing as above: NexCart products were never added to AumViso's104 * Also renamed, with the same silent result: NexCart products were never added to AumViso's 110 105 * enabled types, so a NexCart site got no meta description, canonical or schema on its product 111 106 * pages until somebody ticked the box by hand. -
aumnexcart/trunk/includes/class-settings.php
r3755124 r3793441 133 133 * separate "AumNexCart" CPT menu. 134 134 * 135 * 🔴The parent slug is asked for, not hard-coded. AumCreate 2.11.0 moved its135 * The parent slug is asked for, not hard-coded. AumCreate 2.11.0 moved its 136 136 * admin pages under Appearance, because wordpress.org does not allow a theme to 137 137 * create a top-level menu — so `aumcreate-settings` stopped being a top-level 138 138 * menu. `add_submenu_page()` still returns a normal hook suffix in that case and 139 139 * 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 no141 * error of any kind.** Measured on 2026-09-30 — 83 menu items, "NexCart" not one142 * 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. 143 143 * 144 144 * `function_exists()` keeps this working on older versions of the theme, where … … 354 354 * There is no licence gate here, and there is no licence anywhere in this plugin. 355 355 * 356 * It used to lock an unlicensed site onto a "License" tab. That w ent in 2026-08-26 (the plugin is357 * part of a suite , never sold on its own), leaving only a read of the theme's licence code for the358 * self-hosted update check — and that went too when the plugin moved to wordpress.org, where359 * 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. 360 360 */ 361 361 $settings = self::get_all(); -
aumnexcart/trunk/includes/class-tools.php
r3755124 r3793441 89 89 // 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. 90 90 fwrite( $out, "\xEF\xBB\xBF" ); 91 fputcsv( $out, self::CSV_COLUMNS );91 fputcsv( $out, self::CSV_COLUMNS, ',', '"', '' ); 92 92 93 93 $paged = 1; … … 108 108 } 109 109 foreach ( $q->posts as $post ) { 110 fputcsv( $out, self::row_for_post( $post ) );110 fputcsv( $out, self::row_for_post( $post ), ',', '"', '' ); 111 111 } 112 112 $paged++; … … 194 194 } 195 195 196 $header = fgetcsv( $fh );196 $header = fgetcsv( $fh, null, ',', '"', '' ); 197 197 if ( ! $header ) { 198 198 // 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. … … 214 214 215 215 $rows = array(); 216 while ( ( $row = fgetcsv( $fh ) ) !== false ) {216 while ( ( $row = fgetcsv( $fh, null, ',', '"', '' ) ) !== false ) { 217 217 if ( count( array_filter( $row, 'strlen' ) ) === 0 ) { 218 218 continue; … … 320 320 321 321 /* 322 * 🔴 **CSV 的内容不是导入者打出来的,是文件给的。**322 * CSV content is supplied by the file, not typed by the person importing it. 323 323 * 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. 326 331 * 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. 330 336 * 331 * `wp_kses_post()` 保留正文该有的东西(段落、链接、强调、列表、图片), 332 * 去掉 `<script>` / `<style>` / `on*` 事件属性。短代码是纯文本,不受影响。 333 * 标题不该带标记,直接 `sanitize_text_field()`。 334 * 335 * 两个方向都验过(真跑 `wp_kses_post()`,不是推的): 336 * 恶意 —— `<script>` / `onerror=` / `<style>` 全部去掉 337 * 合法 —— 段落、强调、链接、列表、图片、短代码、中日韩文字,一字未动 338 * ⚠️ 唯一一处会变的是裸 `&` → `&`。那是**正确的 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 `&`, 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. 342 343 */ 343 344 $post_id = wp_insert_post( -
aumnexcart/trunk/readme.txt
r3755124 r3793441 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 1.4. 47 Stable tag: 1.4.5 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 49 49 50 50 The 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. 51 64 52 65 = 1.4.4 = -
aumnexcart/trunk/templates/single-aumnexcart-product.php
r3755124 r3793441 84 84 85 85 <?php 86 /* Full-width product body (图文正文). Only rendered when the86 /* Full-width product body. Only rendered when the 87 87 * editor content is non-empty, so summary-only products don't 88 88 * leave an empty block. */
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)