BHT Reviews
Front-end review showcase for WordPress, driven by the ACF `review` CPT.
by Blue Heart Travel · github.com/josipmestrovic/ecom-wp-reviews
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/josipmestrovic/ecom-wp-reviews/archive/refs/heads/main.zipModule version: 1.1.1 — repo: josipmestrovic/ecom-wp-reviews

Front-end review showcase for Blue Heart Travel, driven by the ACF
review CPT. Self-contained Divi child-theme module — drop the folder in,
require the bootstrap from functions.php, and you have:
[ecom-reviews-slider]— Swiper carousel of all published reviews[ecom-reviews-grid]— paginated grid (15 per page) with AJAX Load more- A
Reviews → Settingspanel to pick the site-wide sort order - A polished
reviewCPT admin list (avatar, rating stars, date, excerpt, rating filter, smart search across reviewer name + content, sortable headers) - Auto-synced post titles + slugs from the ACF reviewer-name field
Install
Option A — clone (recommended, lets you git pull updates)
From the theme root:
cd wp-content/themes/<your-theme>
git clone https://github.com/josipmestrovic/ecom-wp-reviews.git reviews
The folder MUST end up named
reviews/— the bootstrap path below depends on it.
Option B — manual copy
Download the repo as a ZIP, extract, and drop the reviews/ folder
into wp-content/themes/<your-theme>/.
Activation (both options)
Add this single line to your child theme's functions.php:
require_once get_stylesheet_directory() . '/reviews/reviews.php';
No DB schema and no migrations — the module piggybacks on the existing
ACF review CPT and its postmeta.
Dependency: ACF (free or Pro)
This module reads its data from the ACF review CPT and a single ACF
field group. Both are bundled in this repo as a ready-to-import export:
- File:
docs/acf-review-export.json - Import via WP Admin → ACF → Tools → Import — this registers the
reviewCPT and theReviews fieldsfield group in one go.
After import you can edit / extend either the CPT or the field group in the ACF UI; the export in this repo is the canonical baseline.
File structure
reviews/
├── README.md ← you are here
├── screenshot.png ← README preview image
├── reviews.php ← thin bootstrap, requires every includes/*.php
├── reviews.css ← shared front-end styles (CSS variables)
├── reviews.js ← Swiper init, Read-more toggle, AJAX load-more
├── docs/
│ └── acf-review-export.json ← ACF Tools → Import this to register the `review` CPT + fields
├── vendor/
│ └── swiper/ ← vendored Swiper bundle (CSS + JS), version pinned
└── includes/
├── cpt.php ← CPT visibility override + title sync + auto-draft fallback
├── assets.php ← register CSS/JS + wp_localize_script(EcomReviews)
├── query.php ← ecom_reviews_get_query_args() + avatar-first SQL ordering
├── card.php ← ecom_reviews_render_card() + initials helper
├── shortcode-slider.php ← [ecom-reviews-slider]
├── shortcode-grid.php ← [ecom-reviews-grid]
├── handler.php ← AJAX: ecom_reviews_load_more
├── admin-list-table.php ← review CPT columns / sortable / search / rating filter
└── admin.php ← Settings submenu + ecom_reviews_settings option
ACF fields consumed
The module reads the following fields off each review post.
Full definitions live in docs/acf-review-export.json.
| Field | Type | Req. | Notes |
|---|---|---|---|
first_and_last_name_of_a_reviewer |
text (max 100) | yes | Used as post title + slug; drives initials avatar fallback. Placeholder: John Doe. |
user_avatar |
image (array, return array) |
no | Falls back to a coloured initials circle if empty. |
date_of_a_review |
date_picker (display + return F j, Y, week starts Mon) |
yes | Sortable. |
review_rating |
button_group 1..5 (string), default 5, horizontal |
yes | Sortable + filterable. |
review_content |
textarea | yes | Searchable. Placeholder: "Type here what user said about the experience with Blue Heart Travel...". |
review CPT (registered by the bundled ACF export)
| Setting | Value |
|---|---|
| Post type slug | review |
| Public / queryable | yes / yes |
show_in_menu / show_in_admin_bar / show_in_rest |
yes |
| Menu position | 3 |
| Menu icon | dashicons-star-filled |
supports (per ACF export) |
custom-fields only \u2014 the module re-adds title support at runtime via add_post_type_support() in includes/cpt.php so the auto-synced reviewer name has somewhere to live. |
has_archive |
no |
| Hierarchical | no |
| Capabilities | inherit post |
Sort modes (Reviews → Settings)
Stored as wp_options.ecom_reviews_settings → { sort: <slug> }.
Applies to both shortcodes.
| Slug | Behaviour |
|---|---|
image_rating_date (default) |
Reviews with an avatar first, then highest rating, then newest. |
rating_date |
Highest rating, then newest. |
date_desc |
Newest first. |
random |
Random — stable per page load when the ecom_reviews_seed cookie is set. |
The "image first" priority is implemented via a posts_clauses filter
(see includes/query.php) because WP_Query has no first-class way to
order by "a meta key exists AND is non-empty".
Public surface
| Surface | Where | Notes |
|---|---|---|
[ecom-reviews-slider] |
includes/shortcode-slider.php |
Swiper carousel of all reviews. |
[ecom-reviews-grid] |
includes/shortcode-grid.php |
15-per-page grid with Load more. |
AJAX action ecom_reviews_load_more |
includes/handler.php |
Nonce: ecom_reviews_load_more. Logged-in + nopriv. |
Option ecom_reviews_settings |
includes/admin.php |
{ sort: <slug> }. Sanitised against the choice whitelist. |
JS global EcomReviews |
includes/assets.php (localised) |
{ ajax_url, nonce, per_page, i18n }. |
Constant ECOM_REVIEWS_PER_PAGE |
reviews.php |
15. |
Constant ECOM_REVIEWS_SWIPER_VERSION |
reviews.php |
Pinned Swiper CDN version. |
Constant BHT_REVIEWS_DIR / BHT_REVIEWS_VERSION |
reviews.php |
Module paths / version (parity with sibling modules). |
Public PHP functions
| Function | File | Purpose |
|---|---|---|
ecom_reviews_get_sort() |
includes/admin.php |
Returns the active sort slug (validated). |
ecom_reviews_sort_choices() |
includes/admin.php |
Sort-mode label map. |
ecom_reviews_get_query_args( $sort, $paged, $per_page ) |
includes/query.php |
Builds WP_Query args. |
ecom_reviews_render_card( $post_id ) |
includes/card.php |
Returns one card's HTML. |
ecom_reviews_initials( $name ) |
includes/card.php |
Reviewer-name initials helper. |
ecom_reviews_enqueue() |
includes/assets.php |
Enqueues Swiper + module CSS/JS on demand. |
Admin UI
review list table
- Columns: Avatar (image or coloured initials) · Reviewer · Rating (★★★★★) · Review date · Excerpt (120 chars) · Published.
- Sortable headers: Reviewer (title), Rating (
review_ratingnumeric), Review date (date_of_a_review). - Search: matches
post_title,first_and_last_name_of_a_reviewer, andreview_content(joined via aposts_clausesfilter —ecom_reviews_admin_search_clauses). - Rating filter dropdown: "All ratings" + 5..1 stars; combines (AND) with search.
- Auto title sync: when the reviewer-name field changes, the post title and
slug are updated on
acf/save_post(recursion-guarded). Athe_titlefilter shows the reviewer name as a fallback for any unsynced legacy rows.
Reviews → Settings
manage_options only. Single radio group choosing one of the four sort modes
above. Saved via the standard Settings API.
How to extend
| Want to… | Edit |
|---|---|
| Add a new sort mode | includes/query.php (add a case in ecom_reviews_get_query_args()) and includes/admin.php (add a label to ecom_reviews_sort_choices()). |
| Change the card markup | includes/card.php — ecom_reviews_render_card(). |
| Bump Swiper version | Update ECOM_REVIEWS_SWIPER_VERSION in reviews.php, then re-download the two vendor/swiper/swiper-bundle.min.* files from https://cdn.jsdelivr.net/npm/swiper@<version>/. |
| Tweak per-page count | ECOM_REVIEWS_PER_PAGE in reviews.php. |
| Change admin columns | includes/admin-list-table.php. |
| Add another shortcode | Drop a new includes/shortcode-*.php file and require_once it from reviews.php. |
Working with this component (git workflow)
The folder on disk is a clone of the GitHub repo — there's no
subtree, submodule, or Composer layer. Every command below is plain
vanilla git, run from inside reviews/.
Pull updates from upstream
cd wp-content/themes/<your-theme>/reviews
git pull
Hard-refresh the browser to defeat asset caching.
Make a change and push it
git status
git add .
git commit -m "Fix: <short description>"
git push
"I edited the same file on two sites"
Whichever clone pushes first wins. The second one will see
rejected — non-fast-forward on git push. Resolve with:
git pull # may produce a merge conflict — resolve in editor
git add .
git commit -m "Merge upstream"
git push
Tip:
git pullbefore starting any non-trivial edit saves you the merge dance.
Release a new version
- Bump the version in two places:
BHT_REVIEWS_VERSIONconstant + plugin header inreviews.php.- "Module version" line at the top of this README + a Changelog entry.
- Commit, tag, push:
git add .
git commit -m "Release 1.2.0: <one-line summary>"
git tag v1.2.0
git push
git push --tags
Other sites opt into the new version with their next git pull. To
pin a site to a specific version (no future auto-updates):
git checkout v1.2.0 # detached HEAD on that exact tag
Semver guideline
| Change | Bump |
|---|---|
| Bug fix, no behavior change | patch (1.1.1 → 1.1.2) |
| New feature, backward compatible | minor (1.1.1 → 1.2.0) |
| Breaking change (removed shortcode arg, renamed public function, renamed CSS class consumers depend on, ACF field rename) | major (1.1.1 → 2.0.0) |
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
git push rejected (non-fast-forward) |
Someone else pushed first | git pull, resolve any conflict, git push |
| Shortcodes stop rendering after pull | The require_once line in functions.php was removed, or PHP opcache stale |
Re-check functions.php; clear opcache / WP cache |
| Old CSS / JS after pull | Browser cache | Hard refresh (Ctrl+Shift+R) |
fatal: not a git repository |
You're outside the reviews/ folder |
cd into it first |
Nested .git warning when wrapping the parent theme in git |
Component was cloned before parent was git-init'd | Add reviews/ to the parent's .gitignore |
Changelog
1.1.1 — 2026-05-06
- Grid: sibling cards no longer stretch when one card is expanded via
Read more. The shared
.ecom-reviewrule keepsheight: 100%for the slider (each card fills its flexswiper-slide), but the grid now overrides it withheight: auto; align-self: start;on.ecom-reviews-grid__items .ecom-review, matching the slider's independent-height behaviour.
1.1.0 — 2026-05-06
Consolidated visual / typography / interaction refresh of the reviews module.
Card surface
- Removed box-shadow, background changed to
#FCFCFD, border-radius increased to 18px (--ecom-review-card-bg,--ecom-review-card-radius,--ecom-review-card-shadow). - Card flex
gapreset to0so explicit per-element spacing is authoritative. - Independent card heights: grid uses
align-items: startand slider slides usealign-self: flex-start, so expanding Read more on one card no longer stretches sibling cards in the same row.
Typography
- Reviewer name: 20px / weight 500, colour
#384049. - Review date: 18px / weight 400, colour
#384049. - Review text: 18px / 1.6 line-height / weight 400 / colour
#384049. - Read more toggle: 18px / weight 500, colour
#909090. - Mobile (
<= 600px): every text element drops by 2px (avatar initials 20→18, name 20→18, date 18→16, review text 18→16, Read more toggle 18→16, Load more button 18→16).
Avatar
- Image and initials variants now render at 50×50. Initials are 20px
on desktop and 18px on mobile.
card.phpimage markup updated towidth="50" height="50".
Spacing
- Stars row has 12px top/bottom spacing.
- Read more toggle: 30px top spacing; bottom margin set to 4px so that, combined with the card's 22px bottom padding, the visible space below the toggle is 26px.
Slider arrows
- Replaced chevron SVGs with the long-arrow vector (25×18,
currentColorfill). The next button reuses the same SVG rotated 180° via.ecom-reviews-slider__nav--next svg. - Restyled buttons: 18.5px border-radius, 1px solid
#CED3DCborder, no box-shadow, arrow colour#384049. - Pinned vertically on desktop:
pinSliderArrows()measures the first non-expanded card on init/resize and writes--ecom-reviews-arrow-top, so expanding Read more no longer pushes the arrows downward. - Hidden on tablet / mobile (
<= 1023px).
Slider scrollbar (tablet / mobile)
- New Swiper scrollbar indicator (
.ecom-reviews-slider__scrollbar): 3px track in#E5E7EB, draggable thumb in#384049. Hidden on desktop. - Centered at 80% width with 12px top spacing; sits directly below the tallest visible slide and grows with it when a card is expanded.
- Slider collapses any expanded cards on
slideChange, so the swiper wrapper returns to its natural height and the scrollbar / arrows snap back to their resting position when the user navigates.
Load more button
- Typography updated to 18px / weight 500 with 5% letter-spacing
(16px on mobile
<= 600px). - Renders the same long-arrow SVG as the slider arrows, rotated 90°
to point downward, alongside the label
(
.ecom-reviews-grid__load-more-label+.ecom-reviews-grid__load-more-icon). - AJAX handler swaps only the label text during loading so the icon is preserved across the request lifecycle.
Docs / version
- Added this Changelog section to record module version history.
- Bumped
BHT_REVIEWS_VERSIONand plugin header to1.1.0.
1.0.0 — Initial release
[ecom-reviews-slider]Swiper carousel of all published reviews.[ecom-reviews-grid]paginated 15-per-page grid with AJAX Load more (ecom_reviews_load_moreaction, nonce-protected, logged-in + nopriv).Reviews → Settingspanel with four sort modes:image_rating_date(default),rating_date,date_desc,random(stable per-session viaecom_reviews_seedcookie).- Avatar-first ordering implemented via
posts_clausesfilter inincludes/query.php. reviewCPT admin list customisations: avatar/initials column, rating stars, review-date column, excerpt, sortable headers, rating-filter dropdown, smart search across reviewer name + content.- Auto title + slug sync from ACF
first_and_last_name_of_a_revieweronacf/save_post(recursion-guarded), withthe_titlefallback for legacy unsynced rows. - Vendored Swiper 11.1.4 under
vendor/swiper/, version pinned viaECOM_REVIEWS_SWIPER_VERSION. - Lazy asset loading: CSS/JS only enqueued when a shortcode renders.
- Public surface:
ecom_reviews_settingsoption,EcomReviewsJS global,ECOM_REVIEWS_PER_PAGE,BHT_REVIEWS_DIR,BHT_REVIEWS_VERSION.