HUSKY aka WOOF
Дає покупцям звузити великий каталог за категорією, атрибутом, ціною, наявністю й текстом, зі сторінками фільтрів, які може індексувати Google.
Я веду продукт від першого технічного завдання до черги підтримки через багато років. 12 плагінів на wordpress.org, SaaS, що перетворює магазин на WooCommerce на Telegram-бот, розширення для Chrome і застосунки для Android.
const realmag777 = { role: ["Senior WordPress & WooCommerce Developer", "Plugins & Products Architect"], inIT: 2006, ownProductsSince: 2012, // first plugin on wordpress.org activeInstalls: 167_000, // sum of wordpress.org counters stack: ["PHP", "WordPress", "WooCommerce", "MySQL", "JavaScript", "Node.js", "HTML5", "CSS3", "Docker", "MCP"],}; export default realmag777;
Проєкти
На сторінці кожного проєкту перелічено задачі, розв'язані всередині продукту, згруповані за напрямами. Кількість встановлень і рейтинги беруться з wordpress.org, де можна прочитати, що пишуть користувачі: відгуки про HUSKY, відгуки про FOX, відгуки про BEAR.
Дає покупцям звузити великий каталог за категорією, атрибутом, ціною, наявністю й текстом, зі сторінками фільтрів, які може індексувати Google.
Показує ціни у валюті відвідувача й може приймати оплату в ній, тож іноземні клієнти знають, скільки платять.
Дає персоналу редагувати тисячі товарів, як у таблиці, з можливістю скасувати кожну зміну, і дозволяє ШІ-асистентам робити це безпечно.
Показує каталог як таблиці товарів із сортуванням і фільтрами, де клієнти додають позиції в кошик прямо з кожного рядка.
Дає персоналу сайту редагувати, фільтрувати, експортувати й відкочувати тисячі записів, сторінок і користувацького вмісту на одному екрані, схожому на таблицю.
Відвідувачі шукають оголошення, записи або товари одразу за кількома властивостями, наприклад за ціною, роком і категорією.
Показує ціни, записані у вмісті сайту, у валюті кожного відвідувача, без потреби в магазині на WooCommerce.
Показує записи або товари як таблиці з сортуванням і пошуком, які відвідувачі можуть фільтрувати й переглядати на будь-якому пристрої.
Прикріпіть набір зображень до будь-якого запису й показуйте їх як галереї або слайдери в будь-якому місці сайту.
Покрокові випадні списки, як-от марка, модель і покоління, ведуть відвідувачів великим каталогом до потрібної сторінки або товару.
Магазин на WooCommerce, що працював як Telegram-бот: клієнти переглядали каталог, наповнювали кошик і платили, не виходячи з чату.
Розширення для Chrome, яке вводить сказане вами в будь-яке текстове поле на будь-якому сайті, з опційним ШІ-редагуванням.
Офлайн-секундомір для Android із часом кіл, який веде відлік правильно, навіть коли застосунок у фоні.
Читалка, що показує текст по одному слову в обраному вами темпі та пам'ятає, де ви зупинилися.
Компанії як бульбашки на квадрантній діаграмі, що переміщуються, коли змінюються ваги моделі.
Збір подій, агрегація та звіти з діаграмами в адмінці WordPress.
Покупець підписується на запит фільтра й отримує лист, коли з'являються нові відповідні товари.
Оформлений PDF з усіма автомобілями поточної вибірки каталогу авто.
Що розв'язано · 14 продуктів
Оберіть продукт. Кожна вкладка — один напрям його роботи, і вона починається з того, що це дає власникові, простими словами. Нижче — інженерна частина для ваших технічних фахівців: проблема та як її розв'язано, за вихідним кодом. Відкрийте рядок, щоб побачити подробиці.
що розв'язано · оберіть проєкт
Що це даєВідфільтровані сторінки отримують чисті стабільні адреси, які пошукові системи можуть індексувати. Власник вирішує, які сторінки фільтра потрапляють у Google і з яким заголовком.
Перехопити розбір запиту, відрізати сегмент фільтра, дати WordPress зіставити решту й відновити стан фільтра із сегмента за фіксованим кодуванням і правилами індексації залежно від глибини.
Посилання фільтра — це адреса архіву плюс slug фільтра (за замовчуванням swoof) і по одному сегменту на кожен фільтр: /product-category/shoes/swoof/color-red-and-blue/size-xl/. Плагін не реєструє жодних rewrite-правил. url_process() запускається на do_parse_request на фронтенді й одразу завершується, якщо get_search_request() не знаходить /<slug>/ у шляху. Вона бере сегмент фільтра до /page/, ? або #, видаляє його й зіставляє залишок (/product-category/shoes/ або /shop/page/2) з $wp_rewrite->wp_rewrite_rules() за допомогою коду, скопійованого з WP::parse_request(): докладні правила сторінок, публічні й приватні змінні запиту, доступні для перегляду типи записів, таксономії, недоступні для запитів, wp_resolve_numeric_slug_conflicts() і фільтр request. Потім вона викликає query_posts(), handle_404() і register_globals() (до WordPress 6.0 — дію parse_request) та повертає false, тож ядро не розбирає URL удруге.
parse_url_query() ділить сегмент за '/' і зіставляє кожну частину з картою назв від get_all_items(): slug-и атрибутів без pa_, slug-и таксономій, перекладені WPML, price та фіксовані слова instock, onsale, featured, backorder_not_in. -and- і -or- стають комами, -to- — роздільником діапазону, + — пробілом; price-10-to-50 стає min_price і max_price. add_request() зливає результат через фільтр woof_get_request_data, тож get_request_data() повертає той самий масив, що й для рядка запиту ?swoof=1&pa_color=red. Далі в головному запиті parse_query() задає post_type product, позначає запит як архів типу запису й додає мета-запити, а tax_query() на woocommerce_product_query_tax_query замінює записи WooCommerce для тих самих таксономій на tax query плагіна.
Оскільки залишок маршрутизується наявними правилами, база магазину, база категорії товарів і пагінація архіву працюють без нових правил: /page/N/ відрізається до того, як знайдено сегмент фільтра, і залишається в маршрутизованому шляху. parse_query() вимикає is_tax на сторінках фільтра, тож поточну категорію відновлюють з останнього сегмента очищеного URL (get_term_from_cleared_url()). Коли сторінка магазину водночас є головною, виправлення redirect_canonical, описане нижче, не дає WooCommerce перенаправити URL фільтра на /.
Для URL, що відповідає SEO-правилу, meta_head() виводить canonical: URL фільтра без рядка запиту, якщо стан індексується, при цьому canonical від Yoast і SEOPress вимикаються; інакше — URL, обрізаний на slug-у фільтра, тобто звичайний архів. З Yoast SEO sitemap_index() на wpseo_sitemap_index додає адреси, які адміністратор перелічує в текстовому полі налаштувань по одній у рядку; преміум-збірка екранує їх через esc_url_raw() і esc_xml().
woof_get_submit_link() у url_parser.js сортує активні фільтри за ключем і записує кожен як <name>-<values>, де ',' — це -and-, роздільник діапазону — -to-, а пробіли — +. Ціна стає price-<min>-to-<max>; «в наявності», «зі знижкою», «рекомендовані» й backorder стають фіксованими словами. parse_url_query() виконує зворотне відображення.
Завдяки сортуванню вибір спершу color, а потім size дає той самий шлях, що й у зворотному порядку. Значення в межах одного фільтра записуються в порядку їх вибору.
Назви в URL відкидають префікс pa_, тому атрибут і мета-ключ можуть дати однакову назву, і один запис карти сховав би другий. resolve_seo_key_conflict() віддає коротку назву зареєстрованій таксономії незалежно від порядку фільтрів у налаштуваннях, тож наявні URL лишаються чинними, а для іншого фільтра виводить назву з його справжнього ключа, додаючи за потреби числовий суфікс. slug-и таксономій, перекладені WPML, розв'язуються до цього кроку.
get_request_data() викликається з кожного запиту перерахунку, а з SEO-адресами кожен виклик іде через woof_get_request_data до parse_url_query(). З Dynamic Recount і Hide Empty (преміум) одна сторінка може робити понад тисячу таких викликів, тому результат розбору кешується для кожного URL.
Кеш вмикається лише після wp_loaded, тож виклик, зроблений до появи таксономій і налаштувань, не може заморозити неповну карту назв до кінця запиту.
WOOF_SEO рахує активні фільтри. Понад index_deep (за замовчуванням 2) сторінка отримує налаштування 'Disable page indexing' (за замовчуванням yes, тобто noindex,nofollow через wp_robots), а правила не перевіряються. На цьому рівні або нижче збіг із правилом робить сторінку індексованою. Правила — це шаблони URL із заповнювачами {any}, скомпільовані в регулярні вирази, кожне з заголовком, мета-описом, H1 і SEO-текстом, що можуть містити назви вибраних термінів; зберігаються окремо для кожної локалі. Теги canonical виводяться лише для URL, що відповідають правилу.
У преміум-збірці preg_match виконується під тимчасовим обробником помилок, тож правило, чий URL не є коректним виразом, пропускається й потрапляє в сповіщення в адмінці, а порожнє правило пропускається. Безкоштовна версія використовує лише перші два правила.
Коли сторінка магазину водночас є статичною головною, WooCommerce 11.0.0 робить її queried object в архівах товарів. Тоді redirect_canonical() вважає /swoof/... неканонічною копією головної й перенаправляє на /, втрачаючи всі фільтри. fix_front_page_shop_canonical() повертає false лише тоді, коли головна — це сторінка магазину, шлях містить slug фільтра, а ціль редіректу — головна адреса, тож нормалізація слешів і slug-ів працює в усіх інших випадках.
Що це даєКожна опція фільтра показує, скільки товарів вона поверне, тож покупці не потрапляють у глухий кут. Лічильники кешуються, тому магазин лишається швидким на великих каталогах.
Лічильники беруться зі спрощеного запиту лише за ID, який відтворює семантику фільтра. Результати кешуються в межах запиту, у виділеній таблиці й у transients, а форму можна відкласти, доки відвідувач її не попросить.
dynamic_count() перебудовує tax_query для кожного терміна. Уже вибрані терміни лічильника не отримують. Для radio і select (одиничний вибір) термін замінює вибране значення своєї таксономії; для checkbox і multi-select (множинний вибір) він додається до нього. Терміни поточної сторінки категорії та таксономій з атрибута taxonomies шорткоду завжди додаються через AND, тож лічильники на сторінці категорії лишаються в межах цієї категорії.
Логіка для кожної таксономії (OR, AND, NOT IN) з налаштувань, діапазони ціни й рейтингу, мета-умови розширень, мова WPML і видимість у каталозі застосовуються так само, як у запиті товарів. Розширення оголошують свою поведінку перерахунку через html_type_dynamic_recount_behavior, тож типи color, image і label повторно використовують цей код.
Приховування опцій із нульовим результатом побудоване на цих лічильниках: термін підраховується й пропускається, якщо лічильник дорівнює 0, тож це не може бути дешевше за показ лічильників. Цей перемикач доступний лише в преміумі; безкоштовна версія примусово вимикає його у відображеннях.
WP_QueryWOOFCounterIn розширює WP_Query і перевизначає set_found_posts(), setup_postdata(), the_post() і have_posts(). Запит лічильника виконується з fields=ids і nopaging, тож MySQL повертає список ID, WordPress не будує об'єктів записів і не виконує другий запит FOUND_ROWS. Лічильник — це довжина списку.
Фільтри posts_clauses за популярністю й рейтингом від WooCommerce прибираються перед запитом лічильника, бо порядок для лічильника не має значення, а ці фільтри додають у SQL об'єднання.
З увімкненим 'cache count data' WP_QueryWoofCounter хешує JSON фінальних аргументів запиту, які вже містять частини мови й видимості, і шукає хеш у {prefix}woof_query_cache — двоколонковій таблиці з хешем як первинним ключем. Запис іде через INSERT ... ON DUPLICATE KEY UPDATE, тож два перегляди сторінки, що одночасно пишуть той самий ключ, не призводять до помилки. Сайт зі стійким кешем об'єктів може перенести лічильники в wp_cache_* через фільтр woof_use_wp_cache.
Дешевого способу дізнатися, на які кешовані лічильники впливає новий або змінений товар, немає, тому запланована подія очищає всю таблицю через налаштований період. Якщо користувач MySQL не має привілею CREATE, сторінка налаштувань виводить SQL для ручного виконання й залишає опцію вимкненою.
get_filtered_price() будує один SELECT MIN/MAX за _price, об'єднаний із поточним tax- і meta-запитом. Той самий діапазон запитувався кілька разів на сторінці (min і max для кожного фільтра ціни), тож результат мемоїзується в статичному масиві за ключем md5 від SQL. З увімкненим 'Optimize price filter' результати також потрапляють в один transient на добу, а будь-який woocommerce_update_product його видаляє. SQL проходить через woof_get_filtered_price_query, тож сайт може додати підказки індексів.
WOOF_HELPER::get_terms() завантажує всі терміни таксономії через get_categories() і збирає дерево «батько — нащадок» у PHP. Результат мемоїзується в межах запиту й, з увімкненим 'Cache terms', зберігається в transient, ключ якого містить таксономію, hide_empty, прапорці глибини та поточну мову WPML або Polylang, тож кожна мова має власне кешоване дерево.
Очищення проходить по wp_options за _transient_woof_terms_cache_% і видаляє кожен transient, оскільки WordPress не має видалення transients за префіксом.
З start_filtering_btn=1 сервер виводить лише кнопку, тож під час завантаження сторінки запити термінів не виконуються. Після кліку front.js надсилає woof_draw_products із шорткодом товарів, заданим як 'woof_nothing': сервер відображає форму фільтра без товарів, зона перемальовування замінюється, а віджети ініціалізуються заново. Кнопка пропускається, якщо пошук уже активний, тож відфільтрована сторінка й далі показує свій вибір.
Приховування форми через CSS чи правила display нічого не заощаджує, бо лічильники обчислюються на сервері до відправлення HTML. У темах про продуктивність на форумі підтримки часто доводилося наголошувати на цьому.
Що це даєФільтрація продовжує працювати з власною темою магазину, сторінками категорій, варіативними товарами, податками й валютами, тож покупці ніколи не бачать позицій у розмірах, яких немає в наявності.
Підключатися пізно до parse_query і запиту товарів WooCommerce, відновлювати стан архіву, якого очікують шаблони, і явно обробляти кожну відмінність моделі даних за версією або за таблицею.
Коли slug пошуку є в запиті, parse_query() задає post_type=product і is_post_type_archive та скидає is_tax, is_tag, is_home, is_single і is_search. Тоді WooCommerce і тема відображають цикл магазину для будь-якого відфільтрованого URL. Розширення додають свої мета-умови через assemble_query_params(), а оператори для кожної таксономії надходять з фільтра woof_main_query_tax_relations.
Ті самі хуки не повинні зачіпати запити REST і Store API. Обробники завершуються одразу на URL /wp-json/, а parse_query відключається на rest_api_init, тож блокові теми та headless-клієнти отримують власні результати WooCommerce.
URL фільтра більше не є архівом терміна, тож get_queried_object() перестає повертати категорію. Коли архів відображається, set_really_current_term() зберігає термін через WOOF_STORAGE, який плагін запускає в режимі transient: один transient на відвідувача, за ключем із хеша IP відвідувача, на добу. JS також додає really_curr_tax=<term_id>-<taxonomy> до посилання фільтра, а get_really_current_term() використовує його, якщо нічого не збережено. Потім термін додається через AND до запиту товарів, запитів перерахунку й текстового пошуку. AJAX-запити читають збережений термін, але ніколи не перезаписують його.
WooCommerce 3.0 переніс прапорці видимості, рекомендованості й відсутності в наявності з postmeta в таксономію product_visibility. product_visibility_not_in() перетворює потрібні прапорці на умови term_taxonomy_id NOT IN. generate_visibility_keys() додає outofstock, коли магазин ховає товари, яких немає в наявності; коли опція 'listen catalog visibility' увімкнена, вона також завжди додає exclude-from-search, а поза пошуком — exclude-from-catalog. Для старіших магазинів натомість додається мета-умова _visibility.
Ті самі правила застосовуються до головного запиту, запиту шорткоду, запитів перерахунку, текстового пошуку й даних turbo-режиму. Якщо одне з них відрізняється, лічильники й результати розходяться.
Варіативний товар вважається таким, що є в наявності, якщо є хоча б одна варіація, тож простий запит «в наявності + розмір M» повертає сорочку, чия варіація M розпродана. Коли фільтр наявності поєднано з фільтрами атрибутів, posts_where() збирає вибрані терміни pa_*, зокрема термін, що рахується під час перерахунку, і виконує два згруповані запити до wc_product_attributes_lookup: батьки з підходящою варіацією, якої немає в наявності, і батьки з підходящою варіацією в наявності. Різниця додається як ID NOT IN.
Старіший режим, який можна вибрати в налаштуваннях фільтра наявності, натомість запитує записи product_variation за мета-атрибутами та _stock_status замість таблиці пошуку. Він повільніший і за замовчуванням виключає кожного батька, що має розпродану підходящу варіацію; батьки, які також мають підходящу варіацію в наявності, зберігаються лише тоді, коли фільтр woof_exclude_existing_variations повертає true.
_price зберігається в базовій валюті магазину і, у багатьох магазинах, без податку. Якщо відвідувач використовує валюту WPML (WCML), assemble_price_params() конвертує межі назад у базову валюту. Якщо ціни показуються з податком, а зберігаються без нього, податок за стандартною ставкою віднімається від обох меж. Для верхнього краю повзунка get_max_price() знаходить товар з максимальною ціною й запитує в WooCommerce його ціну з податком.
З FOX (WOOCS) значення повзунка, що показуються, конвертуються через woocs_exchange_value, а запит і далі використовує числа в базовій валюті.
woof_draw_products() перебудовує $_GET із посилання, яке відкрив би відвідувач, відображає [woof_products] і [woof] з цим станом і повертає у JSON товари, форму та додаткові фрагменти (кількість результатів, сортування, пагінацію). front.js замінює кожен фрагмент, повертає фрагменти, яких не було у відповіді, повторно ініціалізує віджети й викликає history.pushState, щоб URL можна було поширити. onpopstate перезавантажує сторінку, якщо рядок запиту змінився. Прапорець блокування відкидає кліки, поки виконується запит.
Цикл магазину обгорнуто через хуки woocommerce_before_shop_loop і woocommerce_after_shop_loop. Теми, які не викликають обидва хуки, ламають режим AJAX; це пояснює цілу групу тем підтримки з різними симптомами й однією причиною.
Що це даєНа великих каталогах фільтрація виконується в браузері покупця, а не на сервері. Результати з'являються швидко, а сервер менше навантажений у години пік.
Попередньо обчислити плоский JSON-індекс товарів і варіацій, завантажити його в браузер і виконувати фільтр як SQL над ним за допомогою AlaSQL. Сервер лише відображає відповідні ID. Входить в обидві версії.
get_data_by_id() записує ID, назву, SKU, автора, батька, статус наявності, slug-и термінів кожної таксономії, за якою можна фільтрувати (для варіацій — атрибути варіації), налаштовані мета-поля та список пар звичайної/акційної ціни. Варіативний товар зберігає по одній парі на кожного нащадка, тож браузер може обчислити діапазон його цін за тим самим правилом, що й WooCommerce: акційна ціна враховується лише тоді, коли вона нижча за звичайну.
Ціни зберігаються з податком, коли магазин показує ціни з податком, а FOX перед їх читанням перемикається на валюту за замовчуванням, тож індекс складено в одній валюті.
create_data_search_files() приймає зсув, обробляє 10 ID товарів і варіацій із застосованими правилами видимості, зливає їх у JSON-файл і повертає наступний зсув; скрипт адмінки працює в циклі, доки не отримає -1. WPML перемикається на мову за замовчуванням, і кожен товар записується у файл кожної мови через його ID перекладу. check_id_file() додає ID блогу на мультисайті, щоб сайти не ділили один файл.
Ліміт за замовчуванням — 15,000 записів, його можна змінити фільтром woof_turbo_mode_products_limit.
PN_WP_CRON_WOOF_TURBO_MODE зберігає стан події (повний список ID, позицію, наступний запуск) в опції та виконується на init. Кожен виклик process() обробляє один зріз із 10 ID, а 5-секундне обмеження, збережене в woof_cron_limit_do, не дає сплеску переглядів сторінок запускати зрізи один за одним. Зрізи записуються в data_<id>_temp_<lang>.json; коли список вичерпано, do_recreate_file() переміщує тимчасовий файл поверх робочого, а відвідувачі й далі бачать старий індекс, доки новий не буде завершено.
Ціна такого рішення — застарілість: товари, додані після останньої перебудови, невидимі для фільтра, а перебудова залежить від трафіку сайту так само, як WP-Cron.
turbo_mode.js перетворює поточні значення на рядок WHERE: належність до таксономії через indexOf на масивах термінів, порівняння мета-полів, LIKE для SKU і назви, BETWEEN для повзунків і дат. Власні функції AlaSQL обчислюють мінімальну й максимальну ціну товару зі списку його варіацій та застосовують видимість у каталозі. Для варіативних товарів фільтр наявності спершу вибирає батьків варіацій, яких немає в наявності й які відповідають іншим фільтрам. Перерахунок виконується над тією самою таблицею в пам'яті.
Отримані ID надсилаються на woof_draw_products як turbo_mode_ids, а сервер відображає запит post__in замість об'єднань фільтра.
JS відтворює логіку фільтра з woof_settings, тож йому потрібна на сторінці більша частина цієї опції. get_public_settings() видаляє блок статистики, який містить облікові дані зовнішнього MySQL, і кожен cron_secret_key, а решту пропускає через фільтр для подальшого вилучення. У преміум-збірці точка перебудови вимагає nonce і manage_woocommerce.
Що це даєВласник бачить, за чим покупці насправді фільтрують і що шукають, не сповільнюючи магазин і не переповнюючи його базу даних журналом, що безупинно росте.
Записувати один малий сирий рядок на запит у проміжну таблицю, а довготривалу статистику тримати на окремому сервері MySQL, якого вимагає розширення.
Розширення статистики підключається до woof_get_request_data, який виконується багато разів на сторінку; статичний прапорець обмежує логування першим викликом, а AJAX-виклики, окрім відображення товарів, ігноруються. Зберігаються лише ключі фільтра, вибрані в налаштуваннях, запит обрізається до request_max_deep ключів (за замовчуванням 5) із збереженням діапазону ціни, і хешується разом з IP та датою.
Рядок вставляється лише якщо цей хеш новий і в проміжній таблиці для цього IP менше ніж user_max_requests рядків (за замовчуванням 10).
Розширенню потрібен власний сервер MySQL, заданий в опціях. init_pdo() підключається до нього через PDO, бо $wpdb не може тримати друге з'єднання з іншими обліковими даними. place_statdata_into_db() створює таблицю на місяць з назвою <year>_<month> і вставляє рядки підготовленими запитами.
Звіти запитують місячні таблиці за діапазоном дат, тож база даних магазину не містить довготривалої статистики.
Що це даєПокупці знаходять товари за SKU, атрибутом чи словами в описах варіацій. Зареєстровані клієнти можуть зберегти пошук і отримувати сповіщення, коли з'являються нові відповідні товари.
Будувати умову WHERE з підготовлених фрагментів для кожного слова, додаючи об'єднання лише за потреби, зіставляти варіації з батьками окремим запитом, обмежити те, що може запитувати публічна точка, і повторно виконувати збережені пошуки із записаного сервером SQL.
create_where_query() ділить фразу на слова. Для кожного слова вона з'єднує через OR вибрані поля запису й перелічені мета-ключі (SKU — це просто _sku), плюс збіг назви терміна з усією фразою, а групи слів з'єднуються через AND. Початковий '-' перетворює слово на NOT LIKE, з'єднаний через AND. Фрагменти проходять через $wpdb->prepare().
Об'єднання з термінами й postmeta додаються в posts_join лише коли відповідні опції ввімкнено, а posts_groupby згортає дубльовані рядки, які створюють об'єднання.
Варіації — окремі записи, і запит батьків не бачить _variation_description. Коли опцію ввімкнено, один запит знаходить записи product_variation, чий опис містить усі слова, другий зіставляє їх із post_parent, а головна умова стає (умови слів OR ID IN (батьки)). Список батьків може кешуватися в кеші об'єктів для кожного списку слів.
ajax_search() відкрита для гостей і розбиває запит на сторінки в SQL з max_posts як розміром сторінки, тож завантажується лише одна сторінка збігів. У преміум-збірці max_posts із запиту обмежується діапазоном від 1 до більшого з налаштування адміна й 100, яке можна змінити фільтром. Підказки враховують поточний стан фільтра, сторінку категорії та обмеження «лише зі знижкою».
Текстовий фільтр будує власний пошуковий запит, тож окремий пошуковий плагін не може визначити набір результатів у тому самому запиті; у темах підтримки серед таких конфліктів згадуються Ivory Search, Relevanssi і Advanced Woo Search. get_all_ids() спершу застосовує фільтр woof_text_search_ids. Коли зворотний виклик повертає масив, наприклад ID із SearchWP, цей масив стає набором збігів, а вбудований SQL пропускається. Підключення конкретного рушія потребує власного коду.
Відображення Products Messenger зберігає $wp_query->request — SQL, згенерований WordPress для відфільтрованих результатів, — у сховищі плагіна для кожного відвідувача під ID користувача. Під час підписки, яка вимагає авторизованого користувача, сервер зчитує його назад із цього сховища, а не із запиту, видаляє LIMIT, перевіряє, що це один SELECT без ключових слів DML чи DDL, виконує його один раз, щоб зафіксувати початковий набір ID, і зберігає підписку в мета користувача.
Запланована задача повторно виконує кожен збережений запит і надсилає листом лише ті ID, яких немає в початковому наборі, після чого розширює цей набір. Підписка закінчується за кількістю повідомлень або за датою. Мета користувача оновлюється лише тоді, коли wp_mail() завершується успішно, тож невдала відправка повторюється під час наступного запуску.
Що це даєАнонімні відвідувачі не можуть використати фільтр, щоб виконати код чи дістатися до прихованих даних, а збережені налаштування не губляться й не перезаписуються під час внесення змін.
Ставитися до кожного значення із запиту як до даних: дозволяти лише відомі шорткоди й шаблони, рекурсивно санітизувати налаштування за шляхом ключів, вимагати права для всього, що зберігає код, і зливати налаштування замість перезапису.
[woof_products custom_tpl=...] цілком легітимно передається назад в AJAX-викликах, тож значенню не можна ні довіряти, ні відкинути його. sanitize_custom_tpl() залишає лише літери, цифри, '_', '-' і '/', що прибирає крапки та послідовності обходу каталогів. Назва, відображена зі справжнього вмісту сторінки, додається в опцію-білий список (зберігаються останні 20); під час AJAX приймаються лише назви, що вже є в цьому списку.
Шляхи include, побудовані з назв типів відображення, проходять через safe_view_slug(), яка відхиляє все, що не є простим slug-ом.
woof_draw_products() отримує зі сторінки текст шорткодів товарів і форми, щоб вивід AJAX збігався з початковим викликом. check_shortcode() будує get_shortcode_regex() лише для тегів плагіна ([woof_products] або [woof], плюс псевдоніми шорткоду товарів плагіна) і зберігає перший збіг; усе інше в рядку відкидається перед do_shortcode().
sanitize_array() обходить дерево налаштувань, зберігає кожне значення під санітизованим ключем і класифікує кожне поле за іменем або за шляхом у дереві: текст SEO-правил — як HTML через wp_kses_post, описи — як textarea, ключі, що використовуються в шляхах include, — як суворі slug-и, решта — як звичайний текст. Правила за шляхом потрібні, бо ключі глибоко в дереві, як-от text чи description, надто загальні, щоб перелічувати їх глобально.
Налаштування 'JavaScript after AJAX' можуть змінювати лише користувачі з unfiltered_html.
Відображення AJAX відновлює $_GET із посилання сторінки, надісланого браузером, а відповідь вставляє woof_redraw_elements на сторінку як HTML, тож підроблене посилання не повинно мати змоги передати цей ключ. У преміум-збірці ключ видаляється з розібраного посилання. Легітимні фрагменти (SEO-заголовок і H1, кількість результатів, сортування) додаються серверними фільтрами, і кожен фрагмент проходить через wp_kses перед поверненням.
Сторінка налаштувань показує лише терміни поточної мови WPML або Polylang, тож при збереженні надсилаються кольори лише для цих термінів. woof_save_options() зливає збережені записи color, color_img і images_term_* під надісланими значеннями, тож інші мови зберігають свої.
Перекладений термін без власного кольору використовує свій оригінал мови за замовчуванням, знайдений через get_original_language_term(). Вона читає slug безпосередньо з wp_terms, бо з коригуванням ID у WPML get_term() може знову повернути перекладений термін.
Що це даєПроблеми на живому магазині знаходять за їхньою справжньою причиною, навіть коли це тема, сервер, кеш чи інший плагін.
Відтворити на чистій установці або на копії сайту, виключати по одному шару за раз і виправляти там, де причина: у плагіні для всіх або цільовою зміною на цьому сайті.
Навіть безглуздий параметр на кшталт ?dwdadd=1adw ламав сторінку, що довело: несправність у обробці рядка запиту, а не у фільтрі. Конфігурація Nginx вставляла зайвий ? після php і відкидала кожен параметр, тож фільтр ніколи не отримував своїх аргументів.
Відповідь надходила, але JSON.parse падав через хибний екранований символ, а це означало, що CDN Mapping у LiteSpeed переписував URL усередині неї. Вимкнення CDN Mapping відновило фільтрацію.
Сайт завантажував jQuery slim, у якому немає модуля AJAX, тож запит на відфільтровані результати ніколи не надсилався, хоча URL змінювався на клієнті; вимкнення всіх інших плагінів нічого не змінило. Збірка slim прийшла зі зміненим ядром WordPress від автоінсталятора хостингу; відновлення повного jQuery і перевстановлення ядра усунули проблему.
woof_slider_meta_query_type, щоб приведення типу в SQL можна було задати як DECIMAL для кожного мета-ключаАвтор повідомлення перебудував поле як звичайне довільне поле, і помилка лишилась, тож ACF не був причиною; підтримка відтворила її. Мета-запит порівнював значення без приведення до DECIMAL, і плагін отримав хук woof_slider_meta_query_type, який приймає $type і $key.
pageshow, який скидає блокування надсилання й спливне вікно при відновленні з bfcache, випущено в оновленняхСторінки, відновлені з кешу «назад/вперед», зберігали встановленим woof_submit_link_locked і відкритим спливне вікно завантаження. Слухач перевіряє event.persisted або тип навігації 2 і скидає обидва; зміну внесено в плагін.
Внутрішнє кільце: основний стек · зовнішнє кільце: інструменти й платформи
Про мене · стек
Я проєктую архітектуру та модель даних, потім пишу код, випускаю його й підтримую. Жоден рівень я не передаю тому, кого не можу перевірити.
Індивідуальна розробка
Індивідуальна розробка для клієнтів, зазвичай тих, хто почав з одного з моїх плагінів. Імена та сайти не розголошуються.
Великий B2B-довідник постачальників на WordPress.
Той самий портал на WordPress.
Почалося як індивідуальна розробка для одного власника магазину, згодом випущено як розширення Products Messenger для HUSKY і MDTF.
Каталог автомобілів на MDTF, індивідуальне доповнення для клієнта в Німеччині.
Вбудовано, а не прикручено
Власники отримують цифри в тій самій адмінці, де налаштовують плагін.
Хронологія
Лише мої власні продукти. Дати релізів — це дати, коли кожен плагін було додано на wordpress.org.
Перш ніж писати
Опишіть завдання у формі нижче або листом на realmag777 [at] gmail.com: що потрібно створити або змінити і до якого терміну. Якщо у вас є staging-сайт, вкажіть його: саме там робота виконується й перевіряється. Я відповідаю протягом два робочі дні з уточнювальними запитаннями або з оцінкою часу й вартості. Робота починається, щойно ми погодимо обсяг.
Чітко описане завдання отримує фіксовану оцінку на погоджений обсяг. Робота без чітких меж, як-от пошук причини проблеми на робочому сайті чи постійна підтримка, погоджується за годинною ставкою.
Центральноєвропейський час (UTC+1, влітку UTC+2). Я пишу й спілкуюся такими мовами: Англійська, українська та іспанська. Основна робота проходить листами й повідомленнями, тож різниця в часі рідко має значення.
Так. Імена клієнтів і їхні сайти тут ніколи не показуються, а NDA я підписую, коли проєкт цього потребує.
Ні. Я створюю функції поверх моїх плагінів або будь-яких інших, працюю з кодом, написаним іншими розробниками, і проєктую нові проєкти з нуля. З чужим кодом я спершу читаю його й розповідаю вам, що знайшов, перш ніж щось змінювати.
Сам. Архітектура, код, випуск і підтримка — на мені, тож ви спілкуєтеся з тим, хто створює продукт, і ніщо не губиться між людьми.
Так, зокрема під брендом агенції. Напишіть, хто ви і що вам потрібно.
За домовленістю. Розкажіть про проєкт і очікуваний обсяг, і ми узгодимо це до початку роботи.
У євро.
Staging-сайт, копія робочого, з доступом до адмінки та файлів, а також копія бази даних, коли завдання стосується даних. Якщо staging-сайту немає, я його розгортаю. Зміни потрапляють на робочий сайт лише після того, як запрацюють на staging.
Вам, щойно роботу оплачено.
Помилки в зданій роботі виправляються. Подальші зміни та підтримку можна погодити окремо. Мої власні плагіни підтримуються понад десять років, тож довгострокове супроводження — звичайна справа, а не виняток.
Контакти
Напишіть, що потрібно створити або змінити і до якого терміну. Я відповідаю протягом два робочі дні.
realmag777 [at] gmail.com linkedin.com/in/realmag777