Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: ["7.4", "8.1"]
php: ["7.0", "7.4", "8.1"]
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -85,6 +85,13 @@ jobs:
working-directory: src
run: bash tests/install-wp-tests.sh wordpress_test root root 127.0.0.1 ${{ matrix.wordpress }} true

- name: Install WordPress test polyfills
run: |
mkdir -p /tmp/wordpress-test-tools
composer require --working-dir=/tmp/wordpress-test-tools --no-interaction --no-progress yoast/phpunit-polyfills:^4.0 phpunit/phpunit:^9.6

- name: Run tests
working-directory: src
env:
WP_TESTS_PHPUNIT_POLYFILLS_PATH: /tmp/wordpress-test-tools/vendor/yoast/phpunit-polyfills
run: phpunit
7 changes: 7 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Unreleased

* FIX: Resolve Plugin Check findings while preserving existing markup and customization hooks
* FIX: Support open-house timezone conversion on WordPress 4.7 through 5.2
* IMPROVE: Bundle the existing fallback listing photo and use cached WordPress counts for Legacy Pages
* REQUIRE: WordPress 4.7 or later; PHP 7.0.27 or later remains supported

## 3.3.0

* FIX: Escape API error messages to prevent reflected cross-site scripting on listing pages
Expand Down
Binary file added src/assets/img/default-property.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
convertWarningsToExceptions="true"
>
<testsuites>
<testsuite>
<testsuite name="SimplyRETS">
<directory prefix="test-" suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
Expand Down
12 changes: 10 additions & 2 deletions src/readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
Author: SimplyRETS
Contributors: SimplyRETS
Tags: real estate, idx, mls, rets, reso web api
Requires at least: 3.0.1
Tested up to: 7.0
Requires at least: 4.7
Requires PHP: 7.0.27
Tested up to: 7.1
Stable tag: 3.3.0
License: GPLv3
License URI: http://www.gnu.org/licenses/gpl-3.0.html
Expand Down Expand Up @@ -235,6 +236,13 @@ listing sidebar widget.

== Changelog ==

= Unreleased =

* FIX: Resolve Plugin Check findings while preserving existing markup and customization hooks
* FIX: Support open-house timezone conversion on WordPress 4.7 through 5.2
* IMPROVE: Bundle the existing fallback listing photo and use cached WordPress counts for Legacy Pages
* REQUIRE: WordPress 4.7 or later; PHP 7.0.27 or later remains supported

= 3.3.0 =

* FIX: Escape API error messages to prevent reflected cross-site scripting on listing pages
Expand Down
15 changes: 10 additions & 5 deletions src/simply-rets-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,24 @@

class SrAdminSettings {

public static function invalidateLegacyPageCounts() {
SimplyRetsSetup::invalidateLegacyPageCounts();
}

public static function canManageLegacyPages() {
$post_type = get_post_type_object('sr-listings');
return $post_type && current_user_can('manage_options') && current_user_can($post_type->cap->edit_posts);
}

public static function hasLegacyPages() {
global $wpdb;
// Look for persisted posts only, including drafts, private pages and trash.
// Automatic drafts are editor placeholders; dynamic routes are not stored.
return $wpdb->get_var($wpdb->prepare(
"SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status NOT IN (%s, %s) LIMIT 1",
'sr-listings', 'auto-draft', 'inherit'
)) !== null;
foreach (wp_count_posts('sr-listings') as $status => $count) {
if ($status !== 'auto-draft' && $status !== 'inherit' && $count > 0) {
return true;
}
}
return false;
}

public static function legacyPagesSettingsUrl() {
Expand Down
33 changes: 28 additions & 5 deletions src/simply-rets-openhouses.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@

class SimplyRetsOpenHouses {

/** Get the site's timezone on WordPress versions before and after 5.3. */
private static function siteTimezone() {
if (function_exists('wp_timezone')) {
return wp_timezone();
}

$timezone_string = get_option('timezone_string');
if (!$timezone_string) {
$offset = (float) get_option('gmt_offset');
$hours = (int) $offset;
$minutes = ($offset - $hours) * 60;
$timezone_string = sprintf(
'%s%02d:%02d',
$offset < 0 ? '-' : '+',
abs($hours),
abs($minutes)
);
}

return new DateTimeZone($timezone_string);
}

/**
* Get open house data for a listingId.
* Return an empty array if no openhouses exist.
Expand Down Expand Up @@ -41,7 +63,8 @@ public static function getOpenHouseDateTimes($openhouse) {
*/

// 1. Get the setting.
$timezone_setting = get_option("sr_date_default_timezone", wp_timezone());
$site_timezone = self::siteTimezone();
$timezone_setting = get_option("sr_date_default_timezone", $site_timezone);
$timezone_name_string = '';

// 2. Reliably get the timezone *string* name
Expand All @@ -50,7 +73,7 @@ public static function getOpenHouseDateTimes($openhouse) {
} elseif (is_string($timezone_setting) && !empty($timezone_setting)) {
$timezone_name_string = $timezone_setting;
} else {
$timezone_name_string = wp_timezone_string();
$timezone_name_string = timezone_name_get($site_timezone);
}

// 3. Create a DateTimeZone object from the string
Expand All @@ -59,7 +82,7 @@ public static function getOpenHouseDateTimes($openhouse) {
// 4. Validation
if ($display_time_zone_object === false) {
// Fall back to the default WP timezone object
$display_time_zone_object = wp_timezone();
$display_time_zone_object = $site_timezone;
}

// 5. Create DateTime objects FROM the UTC string.
Expand Down Expand Up @@ -225,12 +248,12 @@ class="sr-photo"
<div class="sr-listing-data-wrapper">
<div class="sr-primary-data">
<a href="<?php echo esc_url($details_link); ?>">
<<?php echo $listing_heading; ?> class="sr-listing-address">
<<?php echo tag_escape($listing_heading); ?> class="sr-listing-address">
<?php echo esc_html($full_address); ?>
<small class="sr-price">
<i> - <?php echo esc_html($list_price_fmtd); ?></i>
</small>
</<?php echo $listing_heading; ?>>
</<?php echo tag_escape($listing_heading); ?>>
</a>
</div>
<div class="sr-secondary-data">
Expand Down
4 changes: 2 additions & 2 deletions src/simply-rets-renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -1994,7 +1994,7 @@ public static function renderSearchForm($atts) {
<form method="get" class="sr-search" action="<?php echo esc_url($home_url); ?>">
<input type="hidden" name="sr-listings" value="sr-search">
<input type="hidden" name="advanced" value="true">
<<?php echo $search_heading; ?> class="sr-search-heading">Advanced Listings Search</<?php echo $search_heading; ?>>
<<?php echo tag_escape($search_heading); ?> class="sr-search-heading">Advanced Listings Search</<?php echo tag_escape($search_heading); ?>>
<div class="sr-adv-search-minmax sr-adv-search-part">

<div class="sr-adv-search-col1">
Expand Down Expand Up @@ -2177,7 +2177,7 @@ public static function renderSearchForm($atts) {

?>
<div id="sr-search-wrapper">
<<?php echo $search_heading; ?> class="sr-search-heading">Search Listings</<?php echo $search_heading; ?>>
<<?php echo tag_escape($search_heading); ?> class="sr-search-heading">Search Listings</<?php echo tag_escape($search_heading); ?>>
<form method="get" class="sr-search" action="<?php echo esc_url($home_url); ?>">
<input type="hidden" name="sr-listings" value="sr-search">

Expand Down
6 changes: 6 additions & 0 deletions src/simply-rets-setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@

class SimplyRetsSetup {

public static function invalidateLegacyPageCounts() {
// Core clears counts on status transitions, but not every deletion or
// post-type change. Follow post cache invalidation for both cases.
wp_cache_delete('posts-sr-listings', 'counts');
}

public static function srActivate() {
SimplyRetsSetup::srRegisterPostType();
SimplyRetsApiClient::srUpdateAdvSearchOptions();
Expand Down
6 changes: 4 additions & 2 deletions src/simply-rets-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,7 @@ public static function mkMLSText() {

class SrListing {

public static $default_photo =
"https://s3-us-west-2.amazonaws.com/simplyrets/trial/properties/defprop.jpg";
public static $default_photo = '';

public static function normalizeListingPhotoUrl($url) {
$force_https = get_option("sr_listing_force_image_https", false);
Expand Down Expand Up @@ -595,6 +594,9 @@ public static function getBathroomsDisplay(
}


// Initialize the public URL once so existing integrations can still override it.
SrListing::$default_photo = plugins_url('assets/img/default-property.jpg', __FILE__);

class SrMessages {

public static function noResultsMsg($response) {
Expand Down
4 changes: 4 additions & 0 deletions src/simply-rets.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
Description: Show your Real Estate listings on your Wordpress site. SimplyRETS provides a very simple set up and full control over your listings.
Author: SimplyRETS
Version: 3.3.0
Requires at least: 4.7
Requires PHP: 7.0.27
License: GNU General Public License v3 or later

Copyright (c) SimplyRETS 2014 - 2026
Expand Down Expand Up @@ -36,6 +38,8 @@
require_once(plugin_dir_path(__FILE__) . 'simply-rets-contact-form.php');
require_once(plugin_dir_path(__FILE__) . 'simply-rets-analytics.php');

// Post counts must also stay fresh for cron, REST and command-line writes.
add_action('clean_post_cache', array('SimplyRetsSetup', 'invalidateLegacyPageCounts'));

if (is_admin()) {
require_once(plugin_dir_path(__FILE__) . 'simply-rets-admin.php');
Expand Down
8 changes: 7 additions & 1 deletion src/tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
<?php
if (! defined('ABSPATH')) {
// PHPUnit starts before WordPress defines ABSPATH; keep web requests blocked.
if (PHP_SAPI !== 'cli') {
exit;
}

$_tests_dir = getenv('WP_TESTS_DIR');
if (!$_tests_dir) $_tests_dir = '/tmp/wordpress-tests-lib';

$polyfills_path = getenv('WP_TESTS_PHPUNIT_POLYFILLS_PATH');
if ($polyfills_path && !defined('WP_TESTS_PHPUNIT_POLYFILLS_PATH')) {
define('WP_TESTS_PHPUNIT_POLYFILLS_PATH', $polyfills_path);
}

require_once $_tests_dir . '/includes/functions.php';

function _manually_load_plugin() {
Expand Down
36 changes: 34 additions & 2 deletions src/tests/test-legacy-pages.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
if (!defined('ABSPATH')) {
exit;
}
require_once ABSPATH . 'wp-admin/includes/template.php';
require_once dirname(__FILE__) . '/../simply-rets-admin.php';

class LegacyPagesTest extends WP_UnitTestCase {

public function set_up() {
parent::set_up();
require_once ABSPATH . 'wp-admin/includes/template.php';
require_once dirname(__FILE__) . '/../simply-rets-admin.php';
}

private function signIn($role) {
$user_id = $this->factory->user->create(array('role' => $role));
wp_set_current_user($user_id);
Expand Down Expand Up @@ -101,6 +105,34 @@ public function testSynthesizedListingRoutesDoNotTriggerTransition() {
}
}

public function testCachedCountsFollowStatusChangesAndCustomStatuses() {
global $wp_post_statuses;
register_post_status('sr-custom', array('public' => true));
try {
$this->assertFalse(SrAdminSettings::hasLegacyPages());
$post_id = $this->factory->post->create(array('post_type' => 'sr-listings', 'post_status' => 'auto-draft'));
$this->assertFalse(SrAdminSettings::hasLegacyPages());
foreach (array('sr-custom', 'draft', 'private', 'trash') as $status) {
wp_update_post(array('ID' => $post_id, 'post_status' => $status));
$this->assertTrue(SrAdminSettings::hasLegacyPages(), $status);
// Repeated reads exercise WordPress's cached counts.
$this->assertTrue(SrAdminSettings::hasLegacyPages(), $status . ' cached');
}
wp_update_post(array('ID' => $post_id, 'post_status' => 'inherit'));
$this->assertFalse(SrAdminSettings::hasLegacyPages());
wp_update_post(array('ID' => $post_id, 'post_status' => 'publish'));
$this->assertTrue(SrAdminSettings::hasLegacyPages());
wp_update_post(array('ID' => $post_id, 'post_type' => 'page'));
$this->assertFalse(SrAdminSettings::hasLegacyPages());
wp_update_post(array('ID' => $post_id, 'post_type' => 'sr-listings'));
$this->assertTrue(SrAdminSettings::hasLegacyPages());
wp_delete_post($post_id, true);
$this->assertFalse(SrAdminSettings::hasLegacyPages());
} finally {
unset($wp_post_statuses['sr-custom']);
}
}

public function testNoticeAndTabRequireSettingsAndPageEditingPermissions() {
$this->factory->post->create(array('post_type' => 'sr-listings'));
foreach (array('subscriber', 'editor') as $role) {
Expand Down
Loading
Loading