Template Overrides
On this page
Overriding templates
The plugin ships six templates. Any of them can be replaced from the theme, and the plugin never touches your copy
again.
Work in a child theme (or your own theme) — a copy in a parent theme is lost on the next theme update.
#Where each file goes
| Plugin file | Your copy | Rendered by |
|---|---|---|
templates/single-osfec_offer.php |
single-osfec_offer.php in the theme root |
A single offer |
templates/archive-osfec_offer.php |
archive-osfec_offer.php in the theme root |
The offer archive |
templates/parts/card.php |
osfec/parts/card.php in the theme |
One card, on the archive and in the shortcode |
templates/parts/filters.php |
osfec/parts/filters.php |
The filter bar |
templates/parts/gallery.php |
osfec/parts/gallery.php |
The photo slider |
templates/parts/media.php |
osfec/parts/media.php |
Video and virtual tour tiles |
Two different mechanisms, worth keeping straight:
- Page templates are resolved on
template_include. WordPress’s own lookup runs first, so a
single-osfec_offer.phpin the theme wins; the bundled file is only the fallback. - Parts are resolved by
osfec_get_template(), which callslocate_template( 'osfec/<name>' )first and falls back
to the plugin’s copy.
Copy the plugin file, edit, done. There is no registration step.
#What a part receives
Each part gets an $args array:
| Part | $args |
|---|---|
parts/card.php |
post_id |
parts/gallery.php |
post_id |
parts/media.php |
post_id |
parts/filters.php |
request — the sanitized GET parameters |
The parts fall back to get_the_ID() when post_id is missing, so they also work inside a loop.
#A minimal card override
wp-content/themes/your-child-theme/osfec/parts/card.php:
<?php
defined( 'ABSPATH' ) || exit;
$post_id = isset( $args['post_id'] ) ? (int) $args['post_id'] : get_the_ID();
if ( ! $post_id ) {
return;
}
?>
<li class="osfec-card-item">
<a class="osfec-card__link" href="<?php echo esc_url( get_permalink( $post_id ) ); ?>">
<article class="osfec-card">
<?php if ( osfec_field( 'thumb', $post_id ) ) : ?>
<div class="osfec-card__media">
<img src="<?php echo esc_url( osfec_field( 'thumb', $post_id ) ); ?>"
alt="<?php echo esc_attr( get_the_title( $post_id ) ); ?>"
loading="lazy" decoding="async">
</div>
<?php endif; ?>
<div class="osfec-card__content">
<h2 class="osfec-card__title"><?php echo esc_html( get_the_title( $post_id ) ); ?></h2>
<p class="osfec-card__price">
<?php echo esc_html( osfec_format_price( osfec_field( 'price', $post_id ), osfec_field( 'currency', $post_id ) ) ); ?>
</p>
</div>
</article>
</a>
</li>
Keep the <li> as the outermost element: cards are rendered inside a <ul class="osfec-list">.
#Rules to respect
Escape everything. Field values come from an external API. esc_html() for text, esc_url() for URLs, esc_attr()
for attributes, wp_kses_post() for HTML. The description is already sanitized on import and is printed with
the_content().
Drop empty values rather than printing blank rows. Missing fields return '', and dictionary-backed attributes
return '' while unresolved. The bundled templates filter their fact and attribute lists before rendering; do the same.
Wrap a full-page template in osfec_header() / osfec_footer(). On block themes get_header() produces the
theme-compatibility stub instead of the theme’s real header. If you write your own single or archive template, open with
osfec_header() and close with osfec_footer().
Keep .osfec on the outer element of anything you render, or
the custom properties are not in scope and the component loses its colours and
widths.
*Keep the `data-osfec-attributes in the gallery** —data-osfec-slider,data-osfec-track,data-osfec-prev, data-osfec-next,data-osfec-current. The script binds to those, not to the classes. Change the markup around them freely; remove them and the slider stops responding to arrows, keyboard and drag. If you replace the slider entirely, also dequeueosfec-gallery`.
Keep the filter bar’s field names — type, city, transaction, price_min, price_max, area_min, area_max,
rooms, sort — and keep the form on method="get" pointing at OSFEC_Query::archive_url(). Those names are the
entire filtering contract.
Respect the archive-filters setting if you override parts/filters.php. The bundled part reads
OSFEC_Settings::value( 'archive_filters' ) and returns early when it is empty, which is how the site owner switches
the bar off.
#Adding a field the bundled template does not show
Every imported value is available through osfec_field(), so no override is needed to store anything — only to
display it. For example, the province:
<?php if ( osfec_field( 'province', $post_id ) ) : ?>
<div class="osfec-attributes__item">
<span class="osfec-attributes__label"><?php esc_html_e( 'Province', 'your-theme' ); ?></span>
<span class="osfec-attributes__value"><?php echo esc_html( osfec_field( 'province', $post_id ) ); ?></span>
</div>
<?php endif; ?>
#Alternatives to a full override
- Only colours, radii or widths differ? Override the custom properties instead — Styling.
- Only the list layout differs? The
columnsattribute and.osfec-list--gridmay be enough. - You want the theme’s own design, not the plugin’s? Build your list with
WP_Queryand skip the bundled markup
altogether — Building a custom UI.
#Maintenance
Overrides are frozen copies. When the plugin ships a fixed or improved template, your copy keeps the old markup: diff it
against templates/ after a plugin update. That is the price of an override, and the reason to override the smallest
part that does the job — parts/card.php rather than the whole archive.