$last_dot ) { $value = str_replace( '.', '', $value ); $value = str_replace( ',', '.', $value ); } else { $value = str_replace( ',', '', $value ); } } elseif ( false !== $last_comma ) { $value = str_replace( '.', '', $value ); $value = str_replace( ',', '.', $value ); } else { $parts = explode( '.', $value ); if ( count( $parts ) > 2 ) { $decimal = array_pop( $parts ); $value = implode( '', $parts ) . '.' . $decimal; } } if ( ! is_numeric( $value ) ) { return trim( (string) $price ); } return number_format_i18n( (float) $value, 0 ); } /** * Register widget files */ function register_hello_world_widget( $widgets_manager ) { require_once( __DIR__ . '/widgets/apartaments.php' ); require_once( __DIR__ . '/widgets/parking-spots.php' ); $widgets_manager->register( new \Elementor_Apartaments() ); $widgets_manager->register( new \Elementor_Parking_Spots() ); } add_action( 'elementor/widgets/register', 'register_hello_world_widget' ); /** * Register scripts/styles only. * Do not enqueue them globally. */ function elementor_addon_register_assets() { // Swiper CSS wp_register_style( 'elementor-addon-swiper', plugins_url( 'plugins/swiper/swiper-bundle.min.css', __FILE__ ), [], '11.0.0' ); // Fancybox CSS wp_register_style( 'elementor-addon-fancybox', 'https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css', [], '5.0' ); // Widget CSS wp_register_style( 'elementor-addon-main-css', plugins_url( 'assets/css/main.css', __FILE__ ), [ 'elementor-addon-swiper', 'elementor-addon-fancybox' ], '1.0.0' ); // Swiper JS wp_register_script( 'elementor-addon-swiper', plugins_url( 'plugins/swiper/swiper-bundle.min.js', __FILE__ ), [], '11.0.0', true ); // Fancybox JS wp_register_script( 'elementor-addon-fancybox', 'https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js', [], '5.0', true ); // Widget JS wp_register_script( 'elementor-addon-main-js', plugins_url( 'assets/js/main.js', __FILE__ ), [ 'jquery', 'elementor-addon-swiper', 'elementor-addon-fancybox' ], '1.0.0', true ); } add_action( 'wp_enqueue_scripts', 'elementor_addon_register_assets' ); /** * PrzekaĹĽ dane do JS (ajaxUrl + nonce) gdy skrypt jest enqueue'owany przez widget. */ function elementor_addon_localize_scripts() { wp_localize_script( 'elementor-addon-main-js', 'apartamentsData', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'apartamenty_price_history_nonce' ), ] ); } add_action( 'wp_enqueue_scripts', 'elementor_addon_localize_scripts', 20 ); /** * Włącza wsparcie "Atrybuty wpisu" (menu_order) dla CPT apartamenty, * aby moĹĽna byĹ‚o rÄ™cznie ustawiać kolejność. */ function elementor_addon_enable_apartamenty_menu_order( $args, $post_type ) { if ( 'apartamenty' !== $post_type ) { return $args; } $supports = $args['supports'] ?? []; if ( true === $supports || false === $supports ) { $supports = [ 'title', 'editor' ]; } if ( ! is_array( $supports ) ) { $supports = (array) $supports; } if ( ! in_array( 'page-attributes', $supports, true ) ) { $supports[] = 'page-attributes'; } $args['supports'] = $supports; return $args; } add_filter( 'register_post_type_args', 'elementor_addon_enable_apartamenty_menu_order', 20, 2 ); // =========================================================== // HISTORIA CEN — TABELA DB // =========================================================== /** * Tworzy tabelÄ™ wp_price_history jeĹ›li nie istnieje lub wersja DB jest stara. */ function elementor_addon_create_price_history_table() { global $wpdb; $table_name = $wpdb->prefix . 'price_history'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE {$table_name} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, post_id BIGINT UNSIGNED NOT NULL, price VARCHAR(50) NOT NULL DEFAULT '', price_m2 VARCHAR(50) NOT NULL DEFAULT '', floor_space VARCHAR(50) NOT NULL DEFAULT '', recorded_at DATE NOT NULL, PRIMARY KEY (id), UNIQUE KEY unique_daily (post_id, recorded_at), KEY idx_post_id (post_id) ) ENGINE=InnoDB {$charset_collate};"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( $sql ); } /** * Tworzy tabelÄ™ wp_parking_price_history dla miejsc postojowych. */ function elementor_addon_create_parking_price_history_table() { global $wpdb; $table_name = $wpdb->prefix . 'parking_price_history'; $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE {$table_name} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, parking_type VARCHAR(50) NOT NULL, price VARCHAR(50) NOT NULL DEFAULT '', price_m2 VARCHAR(50) NOT NULL DEFAULT '', recorded_at DATE NOT NULL, PRIMARY KEY (id), UNIQUE KEY unique_daily (parking_type, recorded_at), KEY idx_type (parking_type) ) ENGINE=InnoDB {$charset_collate};"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( $sql ); } /** * Tworzy obie tabele przy aktywacji pluginu. */ function elementor_addon_create_tables() { elementor_addon_create_price_history_table(); elementor_addon_create_parking_price_history_table(); update_option( 'elementor_addon_db_version', '1.1' ); } register_activation_hook( __FILE__, 'elementor_addon_create_tables' ); /** * Sprawdza wersjÄ™ DB przy kaĹĽdym init i tworzy tabele jeĹ›li brakuje. */ function elementor_addon_maybe_update_db() { if ( get_option( 'elementor_addon_db_version' ) !== '1.1' ) { elementor_addon_create_tables(); } } add_action( 'init', 'elementor_addon_maybe_update_db' ); // =========================================================== // HISTORIA CEN — CRON DZIENNY // =========================================================== /** * Rejestruje WP Cron jeĹ›li jeszcze nie zaplanowany. */ function elementor_addon_schedule_cron() { if ( ! wp_next_scheduled( 'apartamenty_record_prices' ) ) { wp_schedule_event( time(), 'daily', 'apartamenty_record_prices' ); } } add_action( 'wp', 'elementor_addon_schedule_cron' ); /** * Zapisuje aktualne ceny wszystkich apartamentĂłw do tabeli historii. * UĹĽywa INSERT IGNORE — jeden rekord na apartament na dzieĹ„. */ function elementor_addon_record_prices() { global $wpdb; delete_transient( 'apartamenty_price_xml_cache' ); $table_name = $wpdb->prefix . 'price_history'; $today = current_time( 'Y-m-d' ); $apartaments = new WP_Query( [ 'post_type' => 'apartamenty', 'post_status' => 'publish', 'posts_per_page' => -1, 'fields' => 'ids', ] ); if ( empty( $apartaments->posts ) ) { return; } foreach ( $apartaments->posts as $post_id ) { $price = get_post_meta( $post_id, 'information_price', true ); $price_m2 = get_post_meta( $post_id, 'information_price_m2', true ); $floor_space = get_post_meta( $post_id, 'information_floor_space', true ); $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO {$table_name} (post_id, price, price_m2, floor_space, recorded_at) VALUES (%d, %s, %s, %s, %s)", $post_id, (string) $price, (string) $price_m2, (string) $floor_space, $today ) ); } // Miejsca postojowe - zapis cen z ACF options (pola w grupach) $parking_table = $wpdb->prefix . 'parking_price_history'; $parking_groups = [ 'zwykle' => 'miejsce_postojowe_zwykle', 'rodzinne' => 'miejsce_postojowe_rodzinne', ]; foreach ( $parking_groups as $type => $group_name ) { $group = get_field( $group_name, 'option' ) ?: []; $price = $group[ $group_name . '_cena' ] ?? ''; $price_m2 = $group[ $group_name . '_cena_m2' ] ?? ''; if ( $price || $price_m2 ) { $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO {$parking_table} (parking_type, price, price_m2, recorded_at) VALUES (%s, %s, %s, %s)", $type, (string) $price, (string) $price_m2, $today ) ); } } } add_action( 'apartamenty_record_prices', 'elementor_addon_record_prices' ); // =========================================================== // HISTORIA CEN - AJAX ENDPOINT // =========================================================== /** * Zwraca historię cen dla apartamentu jako JSON. * Wymaga nonce: apartamenty_price_history_nonce */ function elementor_addon_get_price_history_ajax() { global $wpdb; if ( ! check_ajax_referer( 'apartamenty_price_history_nonce', 'nonce', false ) ) { wp_send_json_error( [ 'message' => 'Invalid nonce' ] ); die(); } $post_id = absint( $_POST['post_id'] ?? 0 ); if ( ! $post_id ) { wp_send_json_error( [ 'message' => 'Invalid post_id' ] ); die(); } $table_name = $wpdb->prefix . 'price_history'; $history = $wpdb->get_results( $wpdb->prepare( "SELECT recorded_at, price, price_m2, floor_space FROM {$table_name} WHERE post_id = %d ORDER BY recorded_at DESC LIMIT 50", $post_id ) ); wp_send_json_success( [ 'title' => get_the_title( $post_id ), 'price' => get_post_meta( $post_id, 'information_price', true ), 'price_m2' => get_post_meta( $post_id, 'information_price_m2', true ), 'floor_space' => get_post_meta( $post_id, 'information_floor_space', true ), 'history' => $history, ] ); } add_action( 'wp_ajax_apartamenty_get_price_history', 'elementor_addon_get_price_history_ajax' ); add_action( 'wp_ajax_nopriv_apartamenty_get_price_history', 'elementor_addon_get_price_history_ajax' ); /** * Zwraca historię cen dla miejsca postojowego jako JSON. * Parametr: parking_type ('zwykle' lub 'rodzinne') */ function elementor_addon_get_parking_price_history_ajax() { global $wpdb; if ( ! check_ajax_referer( 'apartamenty_price_history_nonce', 'nonce', false ) ) { wp_send_json_error( [ 'message' => 'Invalid nonce' ] ); die(); } $parking_type = sanitize_text_field( $_POST['parking_type'] ?? '' ); if ( ! in_array( $parking_type, [ 'zwykle', 'rodzinne' ], true ) ) { wp_send_json_error( [ 'message' => 'Invalid parking_type' ] ); die(); } $labels = [ 'zwykle' => 'Miejsce postojowe pojedyncze', 'rodzinne' => 'Miejsce postojowe rodzinne', ]; $group_names = [ 'zwykle' => 'miejsce_postojowe_zwykle', 'rodzinne' => 'miejsce_postojowe_rodzinne', ]; $table_name = $wpdb->prefix . 'parking_price_history'; $history = $wpdb->get_results( $wpdb->prepare( "SELECT recorded_at, price, price_m2 FROM {$table_name} WHERE parking_type = %s ORDER BY recorded_at DESC LIMIT 50", $parking_type ) ); $group_name = $group_names[ $parking_type ]; $group = get_field( $group_name, 'option' ) ?: []; wp_send_json_success( [ 'title' => $labels[ $parking_type ], 'price' => $group[ $group_name . '_cena' ] ?? '', 'price_m2' => $group[ $group_name . '_cena_m2' ] ?? '', 'history' => $history, ] ); } add_action( 'wp_ajax_parking_get_price_history', 'elementor_addon_get_parking_price_history_ajax' ); add_action( 'wp_ajax_nopriv_parking_get_price_history', 'elementor_addon_get_parking_price_history_ajax' ); // =========================================================== // JAWNOŚĆ CEN — XML ENDPOINTS // =========================================================== /** * Rejestruje reguĹ‚y przepisywania dla endpointĂłw XML. */ function apartamenty_xml_rewrite_rules() { add_rewrite_rule( '^ceny-mieszkan\.(xml|md5)$', 'index.php?apartamenty_xml=$matches[1]', 'top' ); add_rewrite_rule( '^dane-gov-pl\.(xml|md5)$', 'index.php?apartamenty_datagov=$matches[1]', 'top' ); } add_action( 'init', 'apartamenty_xml_rewrite_rules', 10 ); /** * Dodaje query vars dla endpointĂłw XML. */ function apartamenty_xml_query_vars( $vars ) { $vars[] = 'apartamenty_xml'; $vars[] = 'apartamenty_datagov'; return $vars; } add_filter( 'query_vars', 'apartamenty_xml_query_vars' ); /** * Generuje XML z cenami wszystkich apartamentĂłw. * Wynik cachowany w transiencie na 1 godzinÄ™. * * @return string XML jako string */ function apartamenty_generate_price_xml() { $cached = get_transient( 'apartamenty_price_xml_cache' ); if ( false !== $cached ) { return $cached; } global $wpdb; $table_name = $wpdb->prefix . 'price_history'; $query = new WP_Query( [ 'post_type' => 'apartamenty', 'post_status' => 'publish', 'posts_per_page' => -1, 'orderby' => [ 'menu_order' => 'ASC', 'title' => 'ASC', ], ] ); $x = function( $val ) { return htmlspecialchars( (string) $val, ENT_XML1, 'UTF-8' ); }; $xml = '' . "\n"; $xml .= '' . "\n"; if ( $query->have_posts() ) { foreach ( $query->posts as $post ) { $post_id = $post->ID; $type = get_post_meta( $post_id, 'information_type', true ); $floor = get_post_meta( $post_id, 'information_floor', true ); $floor_space = get_post_meta( $post_id, 'information_floor_space', true ); $price = get_post_meta( $post_id, 'information_price', true ); $price_m2 = get_post_meta( $post_id, 'information_price_m2', true ); $status = get_post_meta( $post_id, 'information_status', true ); $history = $wpdb->get_results( $wpdb->prepare( "SELECT recorded_at, price, price_m2 FROM {$table_name} WHERE post_id = %d ORDER BY recorded_at DESC", $post_id ) ); $last_update = ! empty( $history ) ? $history[0]->recorded_at : date( 'Y-m-d' ); $xml .= "\t" . '' . "\n"; $xml .= "\t\t" . $x( $post->post_title ) . "\n"; $xml .= "\t\t" . $x( $type ) . "\n"; $xml .= "\t\t" . $x( $floor ) . "\n"; $xml .= "\t\t" . $x( $floor_space ) . "\n"; $xml .= "\t\t" . $x( $status ) . "\n"; $xml .= "\t\t" . $x( $price ) . "\n"; $xml .= "\t\t" . $x( $price_m2 ) . "\n"; $xml .= "\t\t" . $x( $last_update ) . "\n"; $xml .= "\t\t\n"; foreach ( $history as $row ) { $xml .= "\t\t\t" . '' . "\n"; $xml .= "\t\t\t\t" . $x( $row->price ) . "\n"; $xml .= "\t\t\t\t" . $x( $row->price_m2 ) . "\n"; $xml .= "\t\t\t\n"; } $xml .= "\t\t\n"; $xml .= "\t\n"; } } // Miejsca postojowe $parking_table = $wpdb->prefix . 'parking_price_history'; $parking_configs = [ 'zwykle' => [ 'label' => 'Miejsce postojowe zwykĹ‚e', 'group_name' => 'miejsce_postojowe_zwykle', ], 'rodzinne' => [ 'label' => 'Miejsce postojowe rodzinne', 'group_name' => 'miejsce_postojowe_rodzinne', ], ]; $xml .= "\t\n"; foreach ( $parking_configs as $type => $cfg ) { $group = get_field( $cfg['group_name'], 'option' ) ?: []; $price = $group[ $cfg['group_name'] . '_cena' ] ?? ''; $price_m2 = $group[ $cfg['group_name'] . '_cena_m2' ] ?? ''; $p_history = $wpdb->get_results( $wpdb->prepare( "SELECT recorded_at, price, price_m2 FROM {$parking_table} WHERE parking_type = %s ORDER BY recorded_at DESC", $type ) ); $last_update = ! empty( $p_history ) ? $p_history[0]->recorded_at : date( 'Y-m-d' ); $xml .= "\t\t" . '' . "\n"; $xml .= "\t\t\t" . $x( $cfg['label'] ) . "\n"; $xml .= "\t\t\t" . $x( $price ) . "\n"; $xml .= "\t\t\t" . $x( $price_m2 ) . "\n"; $xml .= "\t\t\t" . $x( $last_update ) . "\n"; $xml .= "\t\t\t\n"; foreach ( $p_history as $row ) { $xml .= "\t\t\t\t" . '' . "\n"; $xml .= "\t\t\t\t\t" . $x( $row->price ) . "\n"; $xml .= "\t\t\t\t\t" . $x( $row->price_m2 ) . "\n"; $xml .= "\t\t\t\t\n"; } $xml .= "\t\t\t\n"; $xml .= "\t\t\n"; } $xml .= "\t\n"; $xml .= ''; set_transient( 'apartamenty_price_xml_cache', $xml, HOUR_IN_SECONDS ); return $xml; } /** * Generuje XML katalogu dane.gov.pl zgodny z XSD portalu. * * @return string XML jako string */ function apartamenty_generate_datagov_xml() { $resource_url = home_url( '/ceny-mieszkan.xml' ); $last_update = date( 'Y-m-d\T00:00:00.000\Z' ); $x = function( $val ) { return htmlspecialchars( (string) $val, ENT_XML1, 'UTF-8' ); }; $xml = '' . "\n"; $xml .= '' . "\n"; $xml .= "\t\n"; $xml .= "\t\twyszynskiego12-ceny-mieszkan-v1\n"; $xml .= "\t\t\n"; $xml .= "\t\t\t<polish>Ceny mieszkań – Wyszyńskiego 12</polish>\n"; $xml .= "\t\t\n"; $xml .= "\t\t\n"; $xml .= "\t\t\tHistoria cen lokali mieszkalnych w inwestycji przy ul. Wyszyńskiego 12. Dane aktualizowane codziennie zgodnie z ustawą o jawności cen.\n"; $xml .= "\t\t\n"; $xml .= "\t\tdaily\n"; $xml .= "\t\tREGI\n"; $xml .= "\t\t\n"; $xml .= "\t\t\t\n"; $xml .= "\t\t\t\twyszynskiego12-ceny-xml-v1\n"; $xml .= "\t\t\t\t" . $x( $resource_url ) . "\n"; $xml .= "\t\t\t\t\n"; $xml .= "\t\t\t\t\t<polish>Cennik lokali XML</polish>\n"; $xml .= "\t\t\t\t\n"; $xml .= "\t\t\t\t\n"; $xml .= "\t\t\t\t\tPlik XML z aktualnym cennikiem i historią zmian cen wszystkich lokali\n"; $xml .= "\t\t\t\t\n"; $xml .= "\t\t\t\tremote\n"; $xml .= "\t\t\t\t" . $x( $last_update ) . "\n"; $xml .= "\t\t\t\n"; $xml .= "\t\t\n"; $xml .= "\t\t\n"; $xml .= "\t\t\tmieszkania\n"; $xml .= "\t\t\tceny\n"; $xml .= "\t\t\tdeweloper\n"; $xml .= "\t\t\tjawność cen\n"; $xml .= "\t\t\thistoria cen\n"; $xml .= "\t\t\n"; $xml .= "\t\n"; $xml .= ''; return $xml; } /** * ObsĹ‚uguje ĹĽÄ…dania do endpointĂłw XML — wysyĹ‚a odpowiedĹş i koĹ„czy. */ function apartamenty_xml_template_redirect() { $xml_type = get_query_var( 'apartamenty_xml' ); $datagov_type = get_query_var( 'apartamenty_datagov' ); if ( $xml_type ) { $content = apartamenty_generate_price_xml(); if ( 'xml' === $xml_type ) { header( 'Content-Type: application/xml; charset=UTF-8' ); echo $content; } else { header( 'Content-Type: text/plain; charset=UTF-8' ); echo md5( $content ); } exit; } if ( $datagov_type ) { $content = apartamenty_generate_datagov_xml(); if ( 'xml' === $datagov_type ) { header( 'Content-Type: application/xml; charset=UTF-8' ); echo $content; } else { header( 'Content-Type: text/plain; charset=UTF-8' ); echo md5( $content ); } exit; } } add_action( 'template_redirect', 'apartamenty_xml_template_redirect', 1 ); // =========================================================== // JAWNOŚĆ CEN — STRONA ADMINISTRACYJNA // =========================================================== /** * Rejestruje stronÄ™ Jawność Cen w menu NarzÄ™dzia wp-admin. */ function apartamenty_jawnosc_cen_menu() { add_management_page( 'Jawność Cen', 'Jawność Cen', 'manage_options', 'jawnosc-cen', 'apartamenty_jawnosc_cen_page' ); } add_action( 'admin_menu', 'apartamenty_jawnosc_cen_menu' ); /** * Renderuje stronÄ™ administracyjnÄ… z URL-ami do zgĹ‚oszenia. */ function apartamenty_jawnosc_cen_page() { $url_ceny = esc_url( home_url( '/ceny-mieszkan.xml' ) ); $url_datagov = esc_url( home_url( '/dane-gov-pl.xml' ) ); ?>

Jawność Cen — Wyszyńskiego 12

Dane aktualizowane codziennie przez WP Cron. Zgłoś URL katalogu dane.gov.pl do administratora portalu: kontakt@dane.gov.pl

Plik URL Akcje
Plik cen (dane) OtwĂłrz XML
Katalog dane.gov.pl
(zgłoś Ministerstwu)
OtwĂłrz XML