Data Model
On this page
Data model reference
The plugin creates no database tables and no taxonomies. Everything lives in wp_posts, wp_postmeta and a handful
of options.
#Post type
| Name | osfec_offer |
| Public | yes, has_archive = the archive slug |
| Rewrite | /{archive_slug}/{post_name}/, with_front = false |
show_in_rest |
true — so the CPT works in the Query Loop block and in page-builder loops |
supports |
title only |
| Capabilities | create_posts is do_not_allow; map_meta_cap is on |
| Menu icon | dashicons-building |
post_content holds the imported description (descriptionWebsite, falling back to description), run through
wp_kses_post(). post_title comes from portalWwwTitle / portalTitle, falling back to “type, city”.
The slug is built once, at creation, from the title plus the public offer number, and is never rewritten on update —
a price change must not move the URL.
#Meta
Every key carries the _osfec_ prefix. Empty values are not stored: a field missing from the payload has no meta
row, so get_post_meta() returns ''. Zero is kept — the ground floor is a real value.
None of these are registered with register_post_meta(), and none are exposed through the REST API.
#Identity
| Key | Type | Notes |
|---|---|---|
_osfec_id |
int | Esti offer ID. The upsert key |
_osfec_number |
string | Public offer number (numberExport, falling back to number) |
_osfec_hash |
string | MD5 of the mapper version plus the payload. Drives change detection |
_osfec_synced_at |
int | Unix timestamp of the last write |
#Classification
| Key | Type | Notes |
|---|---|---|
_osfec_type |
slug | Type name without the bracketed variant, slugified: mieszkanie |
_osfec_type_label |
string | Raw Esti type name: Działka (Rolna) |
_osfec_subtype |
slug | Slugified bracketed variant: rolna |
_osfec_subtype_label |
string | The variant itself: Rolna |
_osfec_transaction |
slug | sale, rent, purchase, lease |
_osfec_market |
slug | primary, secondary |
_osfec_tags |
CSV | Marketing labels from Esti |
#Price and size
| Key | Type | Notes |
|---|---|---|
_osfec_price |
float | Empty when the price is hidden in Esti or is not positive |
_osfec_currency |
string | PLN, EUR, USD |
_osfec_price_m |
float | Price per m² (pricePermeter) |
_osfec_price_prev |
float | Previous price, stored only when it was higher than the current one |
_osfec_area |
float | Total area |
_osfec_area_usable |
float | Usable area |
_osfec_rooms |
int | |
_osfec_floor |
int | Floor the unit is on |
_osfec_floors |
int | Floors in the building |
_osfec_year |
int | Year built |
#Location
| Key | Type | Notes |
|---|---|---|
_osfec_city |
string | locationExportCityName, falling back to locationCityName |
_osfec_commune |
string | |
_osfec_county |
string | |
_osfec_province |
string | |
_osfec_street |
string | |
_osfec_postal |
string | |
_osfec_lat, _osfec_lng |
string | Coordinates as strings |
Location values are stored raw. Esti mixes upper case, lower case and trailing spaces; formatting is the template’s
job.
#Media
| Key | Type | Notes |
|---|---|---|
_osfec_photos |
JSON | Array of full-size URLs on the Esti CDN (_max variant) |
_osfec_thumb |
string | The first photo as its _min variant |
_osfec_video |
string | External link |
_osfec_tour |
string | External link to a virtual tour |
Read photos with osfec_photos() rather than decoding the JSON yourself.
#Dictionary-backed attributes
These hold numeric IDs, not labels. Resolve them with osfec_attribute_label(); an ID the dictionary cannot name
yields an empty string, and the caller is expected to drop the row rather than print a number at a buyer.
| Key | Esti payload key |
|---|---|
_osfec_ground_type |
groundType |
_osfec_ground_fencing |
groundFencing |
_osfec_ground_shape |
groundShape |
_osfec_ground_ownership |
groundOwnership |
_osfec_building_type |
buildingType |
_osfec_building_heating |
buildingHeating |
#Agent
| Key | Type | Notes |
|---|---|---|
_osfec_agent_id |
int | Esti agent ID (contactId) |
_osfec_agent_name |
string | |
_osfec_agent_email |
string | |
_osfec_agent_phone |
string |
The agent is meta, not a taxonomy, so filtering by agent runs through meta_query. The photo, role and office are
not per-offer meta: they come from the agent directory option, matched on _osfec_agent_id. osfec_agent() merges both
sources.
#Options
| Option | Autoloaded | Contents |
|---|---|---|
osfec_settings |
yes | Every setting, one array. Read it through OSFEC_Settings::get() / ::value() |
osfec_agents |
no | Agent directory keyed by Esti agent ID: name, photo, position, office, email, phone |
osfec_dictionary |
no | Flattened Esti dictionary, id => label |
osfec_runs |
no | The last 20 sync runs, newest first, up to 200 events each |
osfec_last_sync |
no | Unix timestamp of the last successful cleanup |
osfec_license_key |
no | The activated license key |
osfec_license_status |
no | Last verdict from the license server: active, invalid or version_mismatch |
osfec_license_expires |
no | Date the subscription is paid to, empty for a lifetime license |
osfec_license_type |
no | License type as the server names it |
osfec_license_allowed_version |
no | Newest plugin version the license covers |
osfec_license_activated_at |
no | UTC datetime of the first successful activation |
#Transients
| Transient | TTL | Purpose |
|---|---|---|
osfec_sync_ids |
1 h | Esti ID → post ID map for the current run |
osfec_sync_seen |
1 h | Esti IDs seen so far in the current run; cleanup depends on it |
osfec_sync_error |
24 h | Last failure message, shown on the settings screen |
osfec_map_<key> |
6 h | Slug → raw value map for a meta key, e.g. osfec_map_city |
osfec_map_type_options |
6 h | Type slug → label |
osfec_map_subtypes |
6 h | Subtype slug → label |
osfec_update_latest |
12 h (1 h after a failure) | plugin/latest payload from the license server. A site transient |
osfec_update_info |
12 h (1 h after a failure) | plugin/info payload, feeding the “View details” screen. A site transient |
The slug maps are flushed at the end of every successful run, so a new city shows up in the filter dropdowns as soon as
the run that imported it finishes.
#Scheduled events
| Hook | Schedule | Argument |
|---|---|---|
osfec_sync_start |
osfec_6h (every 6 hours) |
— |
osfec_sync_page |
single event, 30 s after the previous page | zero-based page number |
osfec_license_check |
daily |
— |
OSFEC_API::PER_PAGE is 100.
#Change detection
_osfec_hash is md5( OSFEC_MAPPER_VERSION . wp_json_encode( $offer ) ). When the stored hash matches, the offer is
counted as unchanged and nothing is written.
OSFEC_MAPPER_VERSION is a constant in the main plugin file. Bump it whenever the mapping changes: every hash then
differs and the whole catalogue is re-imported on the next run.
#Constants
| Constant | Value |
|---|---|
OSFEC_VERSION |
Plugin version, also the asset cache buster |
OSFEC_FILE |
Main plugin file |
OSFEC_PATH |
Plugin directory, trailing slash |
OSFEC_URL |
Plugin URL, trailing slash |
OSFEC_MAPPER_VERSION |
Mapping revision, part of the hash |
OSFEC_BASENAME |
plugin_basename() of the main file, the key core uses in the update transient |
#Class map
| Class | Responsibility |
|---|---|
OSFEC_CPT |
Post type and rewrite rules |
OSFEC_API |
Esti HTTP client: fetch_page(), fetch_agents(), fetch_dictionary() |
OSFEC_Mapper |
Payload → post fields and meta. No side effects |
OSFEC_Sync |
Run loop, cron, upsert, cleanup |
OSFEC_Log |
Run records and the log screen |
OSFEC_License |
Key verification, the License tab, the gate in front of every run |
OSFEC_Updater |
Updates from the license server: the update transient and plugins_api |
OSFEC_Settings |
Settings screen, sanitization, manual sync |
OSFEC_Query |
GET parameters → WP_Query arguments |
OSFEC_Frontend |
template_include, thumbnails, assets, shortcode, block |
OSFEC_Schema |
JSON-LD in wp_head |
Every class exposes a static init() that registers its own hooks; the main file calls them in order on
plugins_loaded.
#No hooks of its own
The plugin fires no custom actions and applies no custom filters. Extension points are: the template overrides, the
osfec_* helper functions, the shortcode and block attributes, the CSS, and WordPress’s own hooks (pre_get_posts,
template_include, the_content, …). See Building a custom UI.