{"id":25,"date":"2026-09-16T10:28:33","date_gmt":"2026-09-16T10:28:33","guid":{"rendered":"https:\/\/docs.latw.ai\/uncategorized\/template-overrides\/"},"modified":"2026-09-16T10:28:33","modified_gmt":"2026-09-16T10:28:33","slug":"template-overrides","status":"publish","type":"post","link":"https:\/\/docs.latw.ai\/pl\/esti-to-wordpress\/template-overrides\/","title":{"rendered":"Template Overrides"},"content":{"rendered":"<h1>Overriding templates<\/h1>\n<p>The plugin ships six templates. Any of them can be replaced from the theme, and the plugin never touches your copy<br \/>\nagain.<\/p>\n<p>Work in a <strong>child theme<\/strong> (or your own theme) \u2014 a copy in a parent theme is lost on the next theme update.<\/p>\n<h2>Where each file goes<\/h2>\n<table>\n<thead>\n<tr>\n<th>Plugin file<\/th>\n<th>Your copy<\/th>\n<th>Rendered by<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>templates\/single-osfec_offer.php<\/code><\/td>\n<td><code>single-osfec_offer.php<\/code> in the theme root<\/td>\n<td>A single offer<\/td>\n<\/tr>\n<tr>\n<td><code>templates\/archive-osfec_offer.php<\/code><\/td>\n<td><code>archive-osfec_offer.php<\/code> in the theme root<\/td>\n<td>The offer archive<\/td>\n<\/tr>\n<tr>\n<td><code>templates\/parts\/card.php<\/code><\/td>\n<td><code>osfec\/parts\/card.php<\/code> in the theme<\/td>\n<td>One card, on the archive and in the shortcode<\/td>\n<\/tr>\n<tr>\n<td><code>templates\/parts\/filters.php<\/code><\/td>\n<td><code>osfec\/parts\/filters.php<\/code><\/td>\n<td>The filter bar<\/td>\n<\/tr>\n<tr>\n<td><code>templates\/parts\/gallery.php<\/code><\/td>\n<td><code>osfec\/parts\/gallery.php<\/code><\/td>\n<td>The photo slider<\/td>\n<\/tr>\n<tr>\n<td><code>templates\/parts\/media.php<\/code><\/td>\n<td><code>osfec\/parts\/media.php<\/code><\/td>\n<td>Video and virtual tour tiles<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Two different mechanisms, worth keeping straight:<\/p>\n<ul>\n<li><strong>Page templates<\/strong> are resolved on <code>template_include<\/code>. WordPress&#8217;s own lookup runs first, so a<br \/>\n<code>single-osfec_offer.php<\/code> in the theme wins; the bundled file is only the fallback.<\/li>\n<li><strong>Parts<\/strong> are resolved by <code>osfec_get_template()<\/code>, which calls <code>locate_template( 'osfec\/&lt;name&gt;' )<\/code> first and falls back<br \/>\nto the plugin&#8217;s copy.<\/li>\n<\/ul>\n<p>Copy the plugin file, edit, done. There is no registration step.<\/p>\n<h2>What a part receives<\/h2>\n<p>Each part gets an <code>$args<\/code> array:<\/p>\n<table>\n<thead>\n<tr>\n<th>Part<\/th>\n<th><code>$args<\/code><\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>parts\/card.php<\/code><\/td>\n<td><code>post_id<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>parts\/gallery.php<\/code><\/td>\n<td><code>post_id<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>parts\/media.php<\/code><\/td>\n<td><code>post_id<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>parts\/filters.php<\/code><\/td>\n<td><code>request<\/code> \u2014 the sanitized GET parameters<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The parts fall back to <code>get_the_ID()<\/code> when <code>post_id<\/code> is missing, so they also work inside a loop.<\/p>\n<h2>A minimal card override<\/h2>\n<p><code>wp-content\/themes\/your-child-theme\/osfec\/parts\/card.php<\/code>:<\/p>\n<pre><code class=\"language-php\">&lt;?php\ndefined( 'ABSPATH' ) || exit;\n\n$post_id = isset( $args['post_id'] ) ? (int) $args['post_id'] : get_the_ID();\n\nif ( ! $post_id ) {\n    return;\n}\n?&gt;\n&lt;li class=\"osfec-card-item\"&gt;\n    &lt;a class=\"osfec-card__link\" href=\"&lt;?php echo esc_url( get_permalink( $post_id ) ); ?&gt;\"&gt;\n        &lt;article class=\"osfec-card\"&gt;\n            &lt;?php if ( osfec_field( 'thumb', $post_id ) ) : ?&gt;\n                &lt;div class=\"osfec-card__media\"&gt;\n                    &lt;img src=\"&lt;?php echo esc_url( osfec_field( 'thumb', $post_id ) ); ?&gt;\"\n                        alt=\"&lt;?php echo esc_attr( get_the_title( $post_id ) ); ?&gt;\"\n                        loading=\"lazy\" decoding=\"async\"&gt;\n                &lt;\/div&gt;\n            &lt;?php endif; ?&gt;\n\n            &lt;div class=\"osfec-card__content\"&gt;\n                &lt;h2 class=\"osfec-card__title\"&gt;&lt;?php echo esc_html( get_the_title( $post_id ) ); ?&gt;&lt;\/h2&gt;\n                &lt;p class=\"osfec-card__price\"&gt;\n                    &lt;?php echo esc_html( osfec_format_price( osfec_field( 'price', $post_id ), osfec_field( 'currency', $post_id ) ) ); ?&gt;\n                &lt;\/p&gt;\n            &lt;\/div&gt;\n        &lt;\/article&gt;\n    &lt;\/a&gt;\n&lt;\/li&gt;<\/code><\/pre>\n<p>Keep the <code>&lt;li&gt;<\/code> as the outermost element: cards are rendered inside a <code>&lt;ul class=\"osfec-list\"&gt;<\/code>.<\/p>\n<h2>Rules to respect<\/h2>\n<p><strong>Escape everything.<\/strong> Field values come from an external API. <code>esc_html()<\/code> for text, <code>esc_url()<\/code> for URLs, <code>esc_attr()<\/code><br \/>\nfor attributes, <code>wp_kses_post()<\/code> for HTML. The description is already sanitized on import and is printed with<br \/>\n<code>the_content()<\/code>.<\/p>\n<p><strong>Drop empty values rather than printing blank rows.<\/strong> Missing fields return <code>''<\/code>, and dictionary-backed attributes<br \/>\nreturn <code>''<\/code> while unresolved. The bundled templates filter their fact and attribute lists before rendering; do the same.<\/p>\n<p><strong>Wrap a full-page template in <code>osfec_header()<\/code> \/ <code>osfec_footer()<\/code>.<\/strong> On block themes <code>get_header()<\/code> produces the<br \/>\ntheme-compatibility stub instead of the theme&#8217;s real header. If you write your own single or archive template, open with<br \/>\n<code>osfec_header()<\/code> and close with <code>osfec_footer()<\/code>.<\/p>\n<p><strong>Keep <code>.osfec<\/code> on the outer element<\/strong> of anything you render, or<br \/>\nthe <a href=\"\/esti-to-wordpress\/styling#custom-properties\">custom properties<\/a> are not in scope and the component loses its colours and<br \/>\nwidths.<\/p>\n<p>*<em>Keep the `data-osfec-<\/em><code>attributes in the gallery** \u2014<\/code>data-osfec-slider<code>,<\/code>data-osfec-track<code>,<\/code>data-osfec-prev<code>, <\/code>data-osfec-next<code>,<\/code>data-osfec-current<code>. 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 dequeue<\/code>osfec-gallery`.<\/p>\n<p><strong>Keep the filter bar&#8217;s field names<\/strong> \u2014 <code>type<\/code>, <code>city<\/code>, <code>transaction<\/code>, <code>price_min<\/code>, <code>price_max<\/code>, <code>area_min<\/code>, <code>area_max<\/code>,<br \/>\n<code>rooms<\/code>, <code>sort<\/code> \u2014 and keep the form on <code>method=\"get\"<\/code> pointing at <code>OSFEC_Query::archive_url()<\/code>. Those names are the<br \/>\nentire filtering contract.<\/p>\n<p><strong>Respect the archive-filters setting<\/strong> if you override <code>parts\/filters.php<\/code>. The bundled part reads<br \/>\n<code>OSFEC_Settings::value( 'archive_filters' )<\/code> and returns early when it is empty, which is how the site owner switches<br \/>\nthe bar off.<\/p>\n<h2>Adding a field the bundled template does not show<\/h2>\n<p>Every imported value is available through <code>osfec_field()<\/code>, so no override is needed to <em>store<\/em> anything \u2014 only to<br \/>\ndisplay it. For example, the province:<\/p>\n<pre><code class=\"language-php\">&lt;?php if ( osfec_field( 'province', $post_id ) ) : ?&gt;\n    &lt;div class=\"osfec-attributes__item\"&gt;\n        &lt;span class=\"osfec-attributes__label\"&gt;&lt;?php esc_html_e( 'Province', 'your-theme' ); ?&gt;&lt;\/span&gt;\n        &lt;span class=\"osfec-attributes__value\"&gt;&lt;?php echo esc_html( osfec_field( 'province', $post_id ) ); ?&gt;&lt;\/span&gt;\n    &lt;\/div&gt;\n&lt;?php endif; ?&gt;<\/code><\/pre>\n<h2>Alternatives to a full override<\/h2>\n<ul>\n<li><strong>Only colours, radii or widths differ?<\/strong> Override the custom properties instead \u2014 <a href=\"\/esti-to-wordpress\/styling\">Styling<\/a>.<\/li>\n<li><strong>Only the list layout differs?<\/strong> The <code>columns<\/code> attribute and <code>.osfec-list--grid<\/code> may be enough.<\/li>\n<li><strong>You want the theme&#8217;s own design, not the plugin&#8217;s?<\/strong> Build your list with <code>WP_Query<\/code> and skip the bundled markup<br \/>\naltogether \u2014 <a href=\"\/esti-to-wordpress\/custom-ui\">Building a custom UI<\/a>.<\/li>\n<\/ul>\n<h2>Maintenance<\/h2>\n<p>Overrides are frozen copies. When the plugin ships a fixed or improved template, your copy keeps the old markup: diff it<br \/>\nagainst <code>templates\/<\/code> after a plugin update. That is the price of an override, and the reason to override the smallest<br \/>\npart that does the job \u2014 <code>parts\/card.php<\/code> rather than the whole archive.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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\u2026<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_latwm_translated_slug":"","footnotes":""},"categories":[2],"tags":[],"class_list":["post-25","post","type-post","status-publish","format-standard","hentry","category-esti-to-wordpress"],"_links":{"self":[{"href":"https:\/\/docs.latw.ai\/pl\/wp-json\/wp\/v2\/posts\/25","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/docs.latw.ai\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/docs.latw.ai\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/docs.latw.ai\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/docs.latw.ai\/pl\/wp-json\/wp\/v2\/comments?post=25"}],"version-history":[{"count":1,"href":"https:\/\/docs.latw.ai\/pl\/wp-json\/wp\/v2\/posts\/25\/revisions"}],"predecessor-version":[{"id":53,"href":"https:\/\/docs.latw.ai\/pl\/wp-json\/wp\/v2\/posts\/25\/revisions\/53"}],"wp:attachment":[{"href":"https:\/\/docs.latw.ai\/pl\/wp-json\/wp\/v2\/media?parent=25"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/docs.latw.ai\/pl\/wp-json\/wp\/v2\/categories?post=25"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/docs.latw.ai\/pl\/wp-json\/wp\/v2\/tags?post=25"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}