WOOT
Плагін WooCommerce, що показує каталог магазину як таблиці з сортуванням і фільтрами та кнопкою додавання в кошик у кожному рядку.
- Перший реліз
- Активні встановлення
- 1 000+
- Рейтинг
- Читати відгуки
Про мене
WOOT відображає каталог WooCommerce як таблицю: один рядок на товар, колонка для будь-якого поля товару, атрибута, таксономії чи meta-ключа та елемент додавання в кошик у кожному рядку. Таблиці налаштовуються в панелі адміністратора або описуються прямо в шорткоді [woot]. Той самий рушій також малює кошик, списки варіацій, upsell, cross-sell, схожі товари та таблиці окремого товару.
Складність у тому, що одна колонка мусить виконувати три задачі: відображати комірку, сортувати серверний запит і працювати як фільтр. Товари бувають кількох типів (simple, variable, grouped, external, variation), які по-різному поводяться в комірці та в кошику. Усі дані таблиць ідуть через одну публічну AJAX-точку, яка має лишатися придатною для великих каталогів і не повинна розкривати чернетки, непублічні типи записів чи файли на сервері.
Рушій таблиць у браузері (data-table-23.js) — це чистий JavaScript із класами ES6 і fetch. Він кешує сторінки за хешем запиту, скасовує застарілі запити, може перемкнути таблицю в режим у браузері й робити запити до неї на SQL над масивом JSON та змінює компонування для малих екранів. jQuery використовується лише для запуску власних подій кошика WooCommerce.
WOOT · таблиці товарів
що розв'язано
Серверні запити таблиць
shipped · 2020Що це даєПокупці гортають, сортують і фільтрують таблиці товарів за ціною, SKU чи залишком своєю мовою, а пресети-списки, як-от бестселери, показують точну кількість позицій.
- Проблема
- Кожен перехід сторінки, клік сортування та зміна фільтра надходять як один AJAX-запит. Він має стати єдиним WP_Query, що враховує видимість у каталозі, пресети таблиці, фільтри, мову відвідувача та ключі сортування, яких WP_Query не підтримує.
- Рішення
get_table_data() читає білий список ключів запиту, застосовує пресети й фільтри як перетворення аргументів запиту, правит SQL лише там, де WP_Query недостатньо, і повертає рядки із загальною кількістю для пагінації.
- Стек
Одна публічна точка обслуговує кожну таблицю й кожного відвідувачаБілий список ключів запиту, санітизація кожного за типом, побудова аргументів WP_Query, повернення рядків і кількості
get_table_data() зареєстровано для авторизованих і анонімних користувачів. Вона проходить $_REQUEST через switch і зберігає лише відомі ключі: post_type, per_page, orderby, order, current_page, table_id, wp_columns_actions, filter_data, filter_provider, fields, lang, predefinition і shortcode_args_set. Числа проходять через intval, ключі — через sanitize_key, JSON-дані — через json_decode і рекурсивний санітайзер.
Два з цих ключів — імена хуків. wp_columns_actions передається в apply_filters(), щоб завантажити профіль колонок, а filter_provider вибирає фільтр woot_filter_provider_{name}, що додає умови фільтра. Обидва санітизуються як текст, але не звіряються зі списком зареєстрованих імен.
Ту саму функцію викликають із PHP з масивом для таблиць, що рендеряться при завантаженні сторінки, тож серверний шлях і шлях AJAX мають одну реалізацію. Кожен рядок створюється викликом render-замикання кожної запитаної колонки, а ID товару зберігається як pid для дій комірок.
Сортування за ціною, SKU чи залишком означає сортування за postmetaКолонки оголошують meta_key і orderby; запит зіставляє ключі сортування з meta_value(_num)
Запис колонки, як-от price, несе 'orderby' => 'meta_value_num' і 'meta_key' => '_price'. get_table_data() шукає запитаний ключ сортування в профілі колонок і переписує за ним аргументи запиту.
Випадний список сортування використовує власні ключі (price-desc, sku, stock-desc, sales тощо), які перекладаються у фільтрі woot_wp_query_args. Сортування за залишком і загальними продажами виключає згруповані та зовнішні товари умовою product_type NOT IN, бо в цих типів немає власного залишку чи продажів. Для сортувань зі списку плагін також викликає WC()->query->get_catalog_ordering_args(), що підключає власні умови впорядкування WooCommerce за популярністю, рейтингом і ціною, тож ці сортування збігаються зі сторінкою магазину.
WP_Query не вміє впорядковувати за post_statusПідключити фільтр posts_orderby для цього одного запиту й зняти його одразу після виконання запиту
Для orderby=post_status замикання додається до posts_orderby перед WP_Query і знімається після нього, тож інші запити того самого запиту сторінки зберігають власне впорядкування.
Пресети «топ-N» (бестселери, новинки) мають показувати рівно N товарівОбмежити posts_per_page значенням N і встановити found_posts у N, щоб пейджер рахував лише N позицій
Пресет бестселерів впорядковує за total_sales, а пресет новинок — за post_date, обидва з posts_per_page, рівним N. WP_Query повідомляє found_posts для всіх відповідних товарів, тож після запиту found_posts встановлюється в ліміт пресета, і пейджер показує лише топ N.
Пресетам потрібне включення та виключення за кількома таксономіямиРозібрати компактний рядок правила у вкладений tax_query; для виключення інвертувати relation
Правило пресета зберігається як рядок на кшталт product_cat:25,26|pa_color:19|rel:AND. get_by_taxonomy() перетворює його на одну вкладену групу tax_query і пропускає таксономії, яких уже не існує.
Для варіанта виключення кожна умова використовує NOT IN, а relation міняється (AND стає OR і навпаки) — це закон де Моргана: виключити товари, що збігаються з A і B, означає залишити ті, що поза A або поза B. Пресети SKU зіставляють SKU варіації з ID її батьківського товару, бо таблиця перелічує батьківські товари.
Запити admin-ajax втрачають мову відвідувачаНадсилати локаль з кожним запитом і перемикати WPML на неї перед запитом і рендером
admin-ajax.php виконується поза контекстом мови фронтенду. draw_table_data() записує get_locale() у JSON таблиці, браузер надсилає її назад як lang, а get_table_data() пропускає її через фільтр woot_current_lang.
Клас словника підключається до цього фільтра й викликає $sitepress->switch_lang(), тож WP_Query повертає товари потрібною мовою. Заголовки колонок і підписи перекладає WOOT_Vocabulary::get() з власної таблиці плагіна, що завантажується один раз на запит і індексується за локаллю.
Модель колонок
shipped · 2020Що це даєВласник вибирає, які деталі товару показувати колонками таблиці, зокрема власні поля магазину, і може копіювати чи переносити налаштування таблиць.
- Проблема
- Колонка — це більше, ніж комірка. Те саме поле має відображатися для кожного типу товару, керувати сортуванням і з'являтися як віджет фільтра, а власники магазинів додають власні таксономії та meta-ключі.
- Рішення
Кожна таблиця працює на профілі: масиві дескрипторів полів, чиї замикання відображають комірку, змінюють аргументи WP_Query для фільтрації та описують віджет фільтра. Таксономії та meta-ключі додаються до профілю під час виконання.
- Стек
Одне визначення поля має керувати коміркою, сортуванням і фільтромПрофіль дескрипторів із замиканнями render, filter-args і filter-UI для кожного поля
WOOT_WooCommerce::action() повертає профіль. Кожен ключ — це поле із заголовком, замиканням action, що відображає комірку для ID товару, даними сортування (order, orderby, meta_key) і, для полів з фільтром, замиканнями get_filter_query_args та get_filter_draw_data. Індекс 0 тримає налаштування рівня таблиці: тип запису, дозволені статуси, провайдер фільтра.
Профілі реєструються за іменем і розв'язуються через apply_filters на дії таблиці. Кошик, варіації, вигляд окремого товару і навіть власні екрани адмінки плагіна (список таблиць, meta-ключі, словник) працюють на тому самому рушії зі своїми профілями.
Кожна таксономія й атрибут товару мають бути колонкоюКолонки з get_object_taxonomies('product') мінус службові, показані як дерева термінів
product_type, product_visibility, product_shipping_class і translation_priority від WPML видаляються зі списку. Комірка показує власні терміни товару через build_taxonomy_tree(), обмежене призначеними ID термінів, як посилання або звичайний текст залежно від опції колонки.
Сторона фільтра додає умову tax_query, чий оператор (IN, NOT IN, AND) береться з налаштувань таблиці й перевіряється за цим білим списком.
Власникам магазинів потрібні колонки для довільних meta-ключівТипізований реєстр meta (text, number, calendar) дає режим сортування та фільтр для кожного ключа
Meta-ключі зберігаються на таблицю в woot_tables_meta із типом. assemble_meta_for_system() перетворює кожен на поле профілю: text сортується за meta_value і фільтрується через LIKE; number сортується за meta_value_num і фільтрується числовим BETWEEN на повзунку діапазону; calendar фільтрується як DATETIME або як числова мітка unix, що обирається для кожного поля, бо плагіни на кшталт ACF зберігають дати в різних форматах.
Поля зливаються з профілем через фільтр ext_{action}, а кожен ключ також з'являється в списку сортування із записами за зростанням і спаданням.
Рядки помножити на колонки повторюють ті самі пошуки товарів і опційСтатичні кеші на рівні запиту для товарів, опцій таблиць, рядків колонок і рядків meta
Кожне замикання колонки потребує об'єкта WC_Product, тому get_product() тримає статичну мапу результатів wc_get_product(). Опції таблиць декодуються з JSON один раз на таблицю, рядки meta кешуються за md5 рядка SQL, а побудовник фільтра розв'язує профіль один раз, а не для кожного поля.
Конфігурації таблиць потребують власного сховища, клонування та перенесенняЧотири власні таблиці через dbDelta; клон копіює колонки й meta; імпорт наповнює з JSON
install.php створює woot_tables, woot_tables_columns (індексована за table_id та is_active, впорядкована за pos_num), woot_tables_meta і woot_vocabulary. Порядок колонок, змінений перетягуванням, приходить як список ID і записується назад як pos_num.
clone() копіює рядок таблиці, потім її колонки та meta-ключі під новим ID таблиці. Експорт вивантажує всі чотири таблиці й опції як JSON; імпорт очищає кожну таблицю й вставляє рядки після перевірки capability та nonce.
Фільтрація
shipped · 2020Що це даєПокупці звужують таблицю за діапазонами, датами, категоріями й текстом, а таблиці слідують за наявним плагіном фільтра магазину на тій самій сторінці.
- Проблема
- Фільтри приходять із браузера як пласка мапа ключ/значення й мають стати аргументами WP_Query: діапазони meta, умови таксономій, текстовий пошук і діапазони дат. Магазини, які вже використовують плагін фільтра, очікують, що таблиця слідуватиме за ним.
- Рішення
Провайдер фільтра передає кожен ключ власному замиканню запиту колонки. Зовнішні плагіни фільтрів підключаються як альтернативні провайдери, що повертають список ID товарів.
- Стек
Фільтр за діапазоном дат потребує двох полів введення для однієї колонкиРозділити поле calendar на key і key---to; провайдер зрізає суфікс і перемикає compare
Коли будується форма фільтра, поле calendar дублюється під ключем ---to з підписами From і To. На зворотному шляху provider() бачить невідомий ключ, що закінчується на ---to, зрізає суфікс і викликає те саме замикання колонки з прапорцем, тож замикання порівнює через <= замість >=.
Повзункам діапазону потрібні межі ще до встановлення будь-якого фільтраОбчислити MIN і MAX meta-ключа в SQL, коли адміністратор не задав межі
Якщо в поля діапазону немає налаштованого min чи max (-1), get_field_drawing_data() виконує SELECT MIN або MAX(cast(meta_value as UNSIGNED)) по postmeta для meta-ключа поля через $wpdb->prepare. Ім'я функції береться із внутрішнього білого списку, а не із запиту. Повзунок кількості коментарів бере межі з таблиці posts.
Текстовий пошук має охоплювати вибрані поля без SQL-ін'єкціїУмова posts_where з esc_like і prepare; список полів обмежений трьома колонками запису
Поля для пошуку приходять із шорткоду, але фільтруються до post_title, post_content і post_excerpt. Кожне стає LOWER(field) LIKE %s зі значенням, екранованим esc_like і прив'язаним через prepare.
У браузері пошук спрацьовує на Enter лише тоді, коли поле досягає свого minlength, а порожній Enter або Escape прибирає термін, що не дає коротким уривкам слів потрапляти на сервер.
Таблиці мають слідувати за фільтром HUSKY (WOOF) на тій самій сторінціВідтворити запит WOOF, запустити його передбачення ID і передати ID у post__in
З filter_provider=woof шорткод захоплює дані запиту WOOF у JSON таблиці. При кожному AJAX-виклику провайдер відновлює з нього $_GET, запитує всі результати, викликає woof_products_ids_prediction() і читає woof_wp_query_ids, приймаючи масив або рядок через кому й залишаючи лише додатні цілі. Відсутність ID дає post__in = [0].
Стан фільтра MDTF має дійти до AJAX-запитуДекодувати стан MDTF, запитати лише аргументи запиту, зрізати його власні прапорці, попередньо отримати ID товарів
Параметр page_mdf декодується, а MDTF запускається з mdf_get_query_args_only, щоб повертати аргументи WP_Query замість HTML. Умова meta mdf_hide_post та умова product_visibility видаляються (WOOT додає власні), впорядкування відкидається, а запит виконується з fields=ids. Список ID подорожує як дані фільтра й стає post__in.
Додавання в кошик із таблиці
shipped · 2020Що це даєПокупці купують будь-який тип товару прямо з таблиці, додають багато рядків за раз, а кошик лишається в ногу з рештою магазину.
- Проблема
- Кожному типу товару потрібен інший елемент купівлі, кількості обмежені залишком, а кошик таблиці має лишатися синхронізованим із міні-кошиком теми та сторінкою кошика WooCommerce.
- Рішення
Сервер видає компактний дескриптор для кожного типу товару, браузер будує елементи за ним і надсилає POST на власну AJAX-точку WooCommerce. Вміст кошика малює той самий рушій таблиць.
- Стек
Simple, variable, grouped та external потребують різних елементів керуванняСервер видає типізований дескриптор data-about; JS будує поле введення, прапорець або посилання на спливне вікно
add_to_cart.php перевіряє is_purchasable() та is_in_stock() і, коли залишок ведеться, вимагає ненульову кількість. Для простих товарів дескриптор несе ID, залишок і SKU; клієнт будує числове поле з max, рівним залишку, лише для читання для товарів, що продаються поштучно, плюс прапорець для масового додавання.
Змінні та згруповані товари отримують кнопку, що відкриває спливну таблицю їхніх дочірніх. Зовнішні товари посилаються на зовнішній URL. Невідомі типи проходять через фільтр woot_woocommerce_add_to_cart, щоб інші плагіни могли надати розмітку.
Додавання в кошик має поводитися як власна кнопка WooCommercePOST на wc-ajax=add_to_cart, потім запуск wc_fragment_refresh для міні-кошиків теми
URL точки береться з wc_add_to_cart_params, коли WooCommerce його виводить, тож власні AJAX-точки продовжують працювати. Кількість перевіряється щодо max перед надсиланням. Після успіху плагін запускає wc_fragment_refresh, оновлює власну інформацію про кошик і, залежно від опції таблиці, перенаправляє до кошика чи оформлення.
Покупці хочуть додати багато рядків однією дієюПозначені рядки та кількості йдуть одним запитом; варіації отримують ID батька й атрибути
Прапорець у заголовку колонки кошика вибирає всі рядки. add_to_cart_some() проходить по надісланих позиціях. Для варіації вона відновлює мапу attribute_* з get_attributes('edit') і викликає WC()->cart->add_to_cart() з ID батька, кількістю, ID варіації та цією мапою — у формі, якої очікує кошик WooCommerce.
Змінним товарам потрібна таблиця їхніх варіаційПровайдер фільтра перемикає post_type на product_variation і обмежує post__in дочірніми
WOOT_WooCommerceVariations::filter_provider() встановлює post_type у product_variation, а post__in — у дочірні батька, перевіривши, що батько видимий відвідувачеві. Таблиці upsell, cross-sell, згрупованих і схожих товарів ділять базовий клас WOOT_WooCommerceUniversal і відрізняються лише джерелом ID.
У рядках варіацій до заголовка дописуються назви термінів атрибутів, а колонки таксономій читають власний slug атрибута варіації замість термінів, призначених запису.
Сам кошик має бути таблицею з сортуваннямЗамикання профілю кошика приймають ID товару й кількість; рушій малює його в режимі JSON
Профіль woot_cart поєднує налаштовані колонки товарів з кількістю, проміжною сумою (WC_Cart::get_product_subtotal) та видаленням. Кожна зміна кошика відповідає свіжою таблицею кошика, кількістю позицій і проміжною сумою. Видалення й оновлення знаходять рядок кошика за product_id або variation_id.
Спливний кошик перебирає скін таблиці, що його відкрила: блок стилів батька копіюється, а його ID таблиці замінюється на ID спливної таблиці. Опція таблиці також може приховувати товари, що вже в кошику, через post__not_in.
Рушій таблиць у браузері
shipped · 2020Що це даєТаблиці швидко реагують на гортання й сортування, кілька таблиць можуть ділити сторінку, а широкі таблиці лишаються зручними на телефонах.
- Проблема
- Гортання, сортування й пошук мають відчуватися миттєвими, кілька таблиць з різними скінами можуть ділити сторінку, а широкі таблиці мають працювати на телефонах.
- Рішення
data-table-23.js — клас без залежностей на таблицю з кешем за хешем запиту, скасуванням запитів, необов'язковим SQL-режимом у браузері та перемикачем компактного компонування.
- Стек
Вже побачена сторінка чи порядок сортування не повинні знову йти на серверКешувати відповіді за хешем усього об'єкта запиту; очищати кеш за подіями
__hash_object() серіалізує request_data у JSON і згортає його в 32-бітний хеш, що править за ключ кешу. У режимах JSON та load-more кеш вимкнено, бо дані вже локальні. Розширення на кшталт порівняння та обраного надсилають table23-reset-cache, коли їхні дані змінюються.
Малі каталоги мають сортуватися й шукатися без звернень до сервераРежим to_json завантажує всі рядки один раз, потім сортує, розбиває на сторінки й шукає через AlaSQL
У режимі to_json сервер повертає кожен рядок (posts_per_page -1). Після першої відповіді таблиця сама переходить у режим json, а draw_data() будує SELECT ... WHERE ... ORDER BY ... LIMIT ... OFFSET і виконує його через AlaSQL над масивом. Односторінкова таблиця, знайдена в кеші, шукається й сортується так само.
Повільна попередня відповідь не повинна перезаписати новішу сторінкуПерервати попередній fetch через AbortController перед надсиланням наступного
Кожна таблиця тримає свій поточний AbortController. Новий запит сторінки, сортування чи фільтра перериває той, що очікує, тож повільна попередня відповідь більше не може перезаписати новішу сторінку.
Широкі таблиці мають переходити на компактне компонування на телефонахПеремальовувати лише коли ширина перетинає точку зламу; обробник зміни розміру заблоковано на 999 мс
Кожна таблиця має власну compact_view_width. Слухач зміни розміру порівнює новий стан із попереднім і перемальовує лише при зміні; прапорець блокування не дає йому спрацювати знову протягом 999 мс. Колонки, позначені hide-on-mobile, пропускаються при побудові заголовків, плаваючий заголовок вимикається в компактному режимі, а правила media-hide для колонок генеруються як CSS для HTML ID таблиці.
Кілька таблиць з різними скінами на одній сторінціCSS скіна використовує плейсхолдер tid, який під час рендеру замінюється на HTML ID кожної таблиці
get_theme_css() спершу читає скін з теки woot-skins теми, потім з теки skins плагіна, замінює плейсхолдер tid на HTML ID таблиці та прибирає коментарі й зайві пробіли. Кожне правило обмежене однією таблицею, тож скіни не просочуються між таблицями на тій самій сторінці.
Комірки можуть містити власні таблиціКонфігурація вкладеної таблиці їде як JSON у прихованому div і створюється після кожного малювання
draw_table_data() кодує конфігурацію з JSON_HEX_QUOT та JSON_HEX_TAG, щоб вона могла сидіти в HTML, не виривалася з елемента. Після малювання рядків do_after_draw() знаходить таблиці в комірках і створює для кожної екземпляр WOOT_GeneratedTables без горизонтальної смуги прокручування.
Безпека публічних точок
у продуктіЩо це даєПублічні таблиці показують лише ті товари, які мають бути видимі, а відвідувачі не можуть використати запити таблиць, щоб дістатися прихованого вмісту чи виконати код.
- Проблема
- Дані таблиць, спливні вікна й дії кошика віддаються анонімним відвідувачам через admin-ajax, а запити несуть налаштування таблиць, аргументи шорткодів, імена скінів та імена хуків.
- Рішення
Видимість забезпечується під час запиту й для кожного запису, імена, що потрапляють у шляхи, CSS чи SQL, зводяться до безпечних наборів символів в одному місці, а кожен запис в адмінці перевіряє capability та nonce.
- Стек
Публічні запити таблиць несуть тип запису й статус, вибрані клієнтомПримусовий статус publish для відвідувачів; непублічні типи лише для таблиць, побудованих на них
Безпосередньо перед виконанням WP_Query post_status скидається в publish для будь-кого без read_private_posts, що б не просили запит, шорткод чи налаштування таблиці. Статус з аргументів шорткоду враховується лише для таких користувачів.
Типи записів, які не можна переглядати (купони, замовлення, шаблони), відхиляються для користувачів без edit_posts, якщо збережену таблицю не було створено для цього типу запису адміністратором.
Спливні вікна й шорткоди беруть ID товару від клієнтаwoot_can_read_post() перевіряє статус, переглядуваність і, для варіацій, батька
Одна функція вирішує, чи може відвідувач бачити дані запису: опубліковані записи публічних типів відкриті, непублічні типи потребують прав редагування, інші статуси потребують read_post, а варіація видима лише тоді, коли видимий її батько. Таблиці окремого товару, варіацій, upsell та інші спливні таблиці, шорткод галереї й розширення викликають її перед рендером.
Імена скінів приходять із кількох джерел і потрапляють у шлях до файлуbasename() плюс фільтр [A-Za-z0-9_-] в одній вузькій точці; ID таблиці фільтрується перед CSS
Скін може прийти з ?woot_skin=, шорткодів, розширень чи налаштувань таблиці, і всі вони проходять через get_theme_css(). Там ім'я зводиться через basename() і фільтр символів, що видаляє крапки та слеші, тож воно не може вказати за межі тек скінів. HTML ID таблиці, який записується у виведений CSS, фільтрується так само, щоб фігурні чи кутові дужки не могли вирватися з блоку style.
Точка спливних вікон викликає хуки та шорткоди за іменемВикликати можна лише хуки woot_, call_id зрізається, рендеряться лише шорткоди плагіна
get_smth() публічна. Її загальна гілка викликає apply_filters для імені із запиту лише тоді, коли ім'я починається з woot_, а супутній call_id зводиться до літер, цифр, підкреслення й дефіса, перш ніж його побачить будь-який подальший запит. Гілка shortcodes_set видаляє кожен шорткод, якого немає в білому списку плагіна, перед do_shortcode(). Гілки експорту й імпорту вимагають manage_woocommerce.
Записи в адмінці та SQL, побудований з імен колонокCapability та nonce на кожен запис; SQL-ідентифікатори зводяться до [A-Za-z0-9_]
Обробники таблиць, колонок, meta і словника перед записом перевіряють manage_woocommerce та woot_settings_nonce. Розширення вкладень додає перевірку входу та перевірки edit_post на рівні об'єкта.
Імена колонок, поля ORDER BY і ключі WHERE не можна прив'язати через prepare(), тому get_rows() зводить їх до безпечного набору символів, обмежує порядок значеннями ASC чи DESC, а логіку — AND чи OR, і прив'язує лише значення.
Цифри
wordpress.org- Активні встановлення 1 000+ лічильник wordpress.org.
- Відгуки 18 Середня оцінка: 4.8 з 5.
- Років підтримки 6 З 4 вересня 2020 р., останнє оновлення 12 жовтня 2026 р..