diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..0d603085 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,90 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + +permissions: + contents: read + +jobs: + quality: + name: Quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + tools: composer:v2 + + - name: Validate Composer files + working-directory: src + run: composer validate --no-check-publish + + - name: Lint PHP + run: git ls-files -z '*.php' | xargs -0 -n1 php -l + + - name: Validate Docker Compose configuration + run: docker compose config --quiet + + php-syntax: + name: PHP ${{ matrix.php }} syntax + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: ["7.4", "8.1"] + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + + - name: Lint PHP + run: git ls-files -z '*.php' | xargs -0 -n1 php -l + + wordpress-tests: + name: WordPress ${{ matrix.wordpress }} tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + wordpress: ["6.7", "latest"] + services: + mariadb: + image: mariadb:10.11 + env: + MYSQL_DATABASE: wordpress_test + MYSQL_ROOT_PASSWORD: root + ports: + - 3306:3306 + options: >- + --health-cmd="healthcheck.sh --connect --innodb_initialized" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + steps: + - uses: actions/checkout@v4 + + - uses: shivammathur/setup-php@v2 + with: + php-version: "8.1" + extensions: mysqli + tools: composer:v2, phpunit:9.6 + + - name: Install Composer dependencies + working-directory: src + run: composer install --no-interaction --no-progress --prefer-dist + + - name: Install WordPress test library + working-directory: src + run: bash tests/install-wp-tests.sh wordpress_test root root 127.0.0.1 ${{ matrix.wordpress }} true + + - name: Run tests + working-directory: src + run: phpunit diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bf365074..00000000 --- a/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -dist: trusty -language: php - -php: - - 5.6 - - 7.1 - -env: - - WP_VERSION=latest WP_MULTISITE=0 - - WP_VERSION=latest WP_MULTISITE=1 - - WP_VERSION=4.0 WP_MULTISITE=0 - - WP_VERSION=4.0 WP_MULTISITE=1 - -before_script: - - composer self-update - - make deps - - chmod +x src/tests/init.sh && bash src/tests/init.sh $TRAVIS_PHP_VERSION $WP_VERSION - -script: cd src && phpunit diff --git a/src/assets/css/simply-rets-client.css b/src/assets/css/simply-rets-client.css index fae3731d..4bdc54b2 100644 --- a/src/assets/css/simply-rets-client.css +++ b/src/assets/css/simply-rets-client.css @@ -36,6 +36,24 @@ sizes with this class. ``` */ +/* +Accessibility: Text meant only for screen readers. +Matches the standard WordPress screen-reader-text class. +*/ +.screen-reader-text { + border: 0; + clip: rect(1px, 1px, 1px, 1px); + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + word-wrap: normal !important; +} + .sr-listing { position: relative; display: block; @@ -187,6 +205,8 @@ div#sr-contact-form-error { background-color: #eee; } +.sr-primary-data h2, +.sr-primary-data h3, .sr-primary-data h4 { padding-left: 15px; margin-top: 10px !important; @@ -615,6 +635,7 @@ Most of these elements also have id's for you to use specifically. clear: both; } +#sr-search-wrapper h2, #sr-search-wrapper h3 { margin-top: 0px; } @@ -938,6 +959,8 @@ elsewhere, this should be a breeze. clear: both; } + .sr-primary-data h2, + .sr-primary-data h3, .sr-primary-data h4 { padding-right: 15px; } diff --git a/src/assets/js/simply-rets-client.js b/src/assets/js/simply-rets-client.js index 615f1c92..15780b04 100644 --- a/src/assets/js/simply-rets-client.js +++ b/src/assets/js/simply-rets-client.js @@ -133,6 +133,18 @@ var normalizeListingPhotoUrl = function (url) { } } +var escapeHtml = function (value) { + return String(value).replace(/[&<>"']/g, function (character) { + return { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }[character]; + }); +} + var genMarkerPopup = function ( listing, @@ -155,7 +167,7 @@ var genMarkerPopup = function ( var sqft = listing.property.area || "n/a"; var mlnum = listing.listingId || "n/a"; var price = listing.listPrice || "Unknown"; - var addr = listing.address.full || "Unknown"; + var addr = escapeHtml(listing.address.full || "Unknown"); var photo = listing.photos.length > 1 ? normalizeListingPhotoUrl(listing.photos[0]) : 'https://s3-us-west-2.amazonaws.com/simplyrets/trial/properties/defprop.jpg'; @@ -194,7 +206,7 @@ var genMarkerPopup = function ( ' ' + '
' + '
' + - ' View Details' + + ' View Details for ' + addr + '' + '
' + ''; diff --git a/src/simply-rets-maps.php b/src/simply-rets-maps.php index 6cd23f18..601da531 100644 --- a/src/simply-rets-maps.php +++ b/src/simply-rets-maps.php @@ -164,8 +164,9 @@ public static function infoWindowMarkup(
- View Details + class='sr-iw-inner__details-link' + aria-label=""> + View Details
diff --git a/src/simply-rets-openhouses.php b/src/simply-rets-openhouses.php index 53430299..f6f158df 100644 --- a/src/simply-rets-openhouses.php +++ b/src/simply-rets-openhouses.php @@ -105,6 +105,18 @@ public static function openHousesSearchResults($search_response, $settings) { return SrMessages::noResultsMsg($res); } else { + $results_heading_raw = apply_filters('simplyrets_results_heading_tag', 'h2'); + $results_heading = tag_escape($results_heading_raw); + if (empty($results_heading)) { + $results_heading = 'h2'; + } + $show_results_heading = apply_filters('simplyrets_show_results_heading', false); + $results_heading_text = apply_filters('simplyrets_openhouses_heading_text', 'Open Houses'); + + if ($show_results_heading && !empty($res)) { + $markup .= "<{$results_heading} class='sr-listings-heading screen-reader-text'>" . esc_html($results_heading_text) . ""; + } + // Generate markup for each open house result foreach ($res as $idx => $oh) { $markup .= SimplyRetsOpenHouses::openHouseSearchResultMarkup( @@ -191,6 +203,12 @@ public static function openHouseSearchResultMarkup($openhouse, $settings) { $sqft = !empty($living_area) ? "SqFt: {$living_area} sqft
" : ""; + $listing_heading_raw = apply_filters('simplyrets_listing_heading_tag', 'h4'); + $listing_heading = tag_escape($listing_heading_raw); + if (empty($listing_heading)) { + $listing_heading = 'h4'; + } + ob_start(); ?>
@@ -207,12 +225,12 @@ class="sr-photo"
-

+ < class="sr-listing-address"> - -

+ >
@@ -231,7 +249,7 @@ class="sr-photo"
clearfix - More details + More details diff --git a/src/simply-rets-renderer.php b/src/simply-rets-renderer.php index 31443f98..5689cc60 100644 --- a/src/simply-rets-renderer.php +++ b/src/simply-rets-renderer.php @@ -1052,6 +1052,20 @@ public static function srResidentialResultsGenerator($request_response, $setting $map->setAutoZoom(true); } + $listing_heading_raw = apply_filters('simplyrets_listing_heading_tag', 'h4'); + $listing_heading = tag_escape($listing_heading_raw); + if (empty($listing_heading)) { + $listing_heading = 'h4'; + } + + $results_heading_raw = apply_filters('simplyrets_results_heading_tag', 'h2'); + $results_heading = tag_escape($results_heading_raw); + if (empty($results_heading)) { + $results_heading = 'h2'; + } + $show_results_heading = apply_filters('simplyrets_show_results_heading', false); + $results_heading_text = apply_filters('simplyrets_results_heading_text', 'Listings'); + $resultsMarkup = ""; foreach ($response as $listing) { $listing_uid = $listing->mlsId; @@ -1258,11 +1272,11 @@ public static function srResidentialResultsGenerator($request_response, $setting . '
' . ' ' . '
' @@ -1280,7 +1294,7 @@ public static function srResidentialResultsGenerator($request_response, $setting . '
' . '
' . ' ' - . ' More details' + . ' More details ' . esc_html('for ' . $full_address) . '' . ' ' . ' ' . $compliance_markup @@ -1304,11 +1318,11 @@ public static function srResidentialResultsGenerator($request_response, $setting . '
' . ' ' . '
' @@ -1326,7 +1340,7 @@ public static function srResidentialResultsGenerator($request_response, $setting . '
' . '
' . ' ' - . ' More details' + . ' More details ' . esc_html('for ' . $full_address) . '' . ' ' . ' ' . $compliance_markup @@ -1336,8 +1350,12 @@ public static function srResidentialResultsGenerator($request_response, $setting } } + $results_heading_markup = ($show_results_heading && !empty($response)) + ? "<{$results_heading} class='sr-listings-heading screen-reader-text'>" . esc_html($results_heading_text) . "" + : ""; $markupGridViewClass = $grid_view == true ? "sr-listings-grid-view" : ""; $resultsMarkup = "
" + . $results_heading_markup . "{$resultsMarkup}" . "
"; $markerCount > 0 ? $mapMarkup = $mapHelper->render($map) : $mapMarkup = ''; @@ -1474,6 +1492,8 @@ public static function srWidgetListingGenerator($response, $settings) { public static function srContactFormMarkup($listing) { $custom_form = get_option('sr_leadcapture_custom_form', false); + static $form_instance = 0; + $form_id = 'sr-contact-form-' . ++$form_instance; // Use custom form if configured if ($custom_form != false) { @@ -1485,36 +1505,42 @@ public static function srContactFormMarkup($listing) { // Process any form submissions $submission_message = SimplyRetsContactForm::srContactFormDeliver(); + $contact_heading_raw = apply_filters('simplyrets_contact_heading_tag', 'h3'); + $contact_heading = tag_escape($contact_heading_raw); + if (empty($contact_heading)) { + $contact_heading = 'h3'; + } + // Default lead capture form $markup = ''; $markup .= '
'; $markup .= '
'; $markup .= $submission_message; - $markup .= '

Contact us about this listing

'; + $markup .= '<' . $contact_heading . '>Contact us about this listing'; $markup .= '
'; $markup .= wp_nonce_field('sr_contact_action', 'sr_contact_nonce', true, false); $markup .= '

'; $markup .= ''; - $markup .= 'Your Name (required)
'; - $markup .= 'Your Name (required)
'; + $markup .= ''; $markup .= '

'; $markup .= '

'; - $markup .= 'Your Email (required)
'; - $markup .= 'Your Email (required)
'; + $markup .= ''; $markup .= '

'; $markup .= '

'; - $markup .= 'Subject (required)
'; - $markup .= 'Subject (required)
'; + $markup .= ''; $markup .= '

'; $markup .= '

'; - $markup .= 'Your Message (required)
'; - $markup .= ''; $markup .= '

'; @@ -1653,6 +1679,8 @@ public static function renderMapSearchForm($atts) { $MLS_text = SrUtils::mkMLSText(); if (!empty($atts['search_form'])) { + static $form_instance = 0; + $form_id = 'sr-map-search-form-' . ++$form_instance; $single_vendor = SrUtils::isSingleVendor(); $allVendors = get_option('sr_adv_search_meta_vendors', array()); @@ -1678,21 +1706,31 @@ public static function renderMapSearchForm($atts) { } } + $search_heading_raw = apply_filters('simplyrets_search_heading_tag', 'h3'); + $search_heading = tag_escape($search_heading_raw); + if (empty($search_heading)) { + $search_heading = 'h3'; + } + $search_form = '
' . '
' - . '

Search Listings

' + . ' <' . $search_heading . ' class="sr-search-heading">Search Listings' . ' ' . ' ' . '
' . '
' - . ' Search keywords' + . ' ' . '
' . '
' - . ' ' . ' ' . $type_options . ' ' @@ -1700,22 +1738,28 @@ public static function renderMapSearchForm($atts) { . '
' . '
' . '
' - . ' ' + . ' ' + . ' ' . '
' . '
' - . ' ' + . ' ' + . ' ' . '
' . '
' - . ' ' + . ' ' + . ' ' . '
' . '
' - . ' ' + . ' ' + . ' ' . '
' . '
' - . ' ' + . ' ' + . ' ' . '
' . '
' - . ' ' + . ' ' + . ' ' . '
' . '
' . ' ' @@ -1726,8 +1770,8 @@ public static function renderMapSearchForm($atts) { . '
' . ' ' . '
' - . ' ' - . ' ' . ' ' . ' ' . ' ' @@ -1759,6 +1803,8 @@ public static function renderMapSearchForm($atts) { public static function renderSearchForm($atts) { ob_start(); + static $form_instance = 0; + $form_id = 'sr-search-form-' . ++$form_instance; $home_url = get_home_url(); $singleVendor = SrUtils::isSingleVendor(); $MLS_text = SrUtils::mkMLSText(); @@ -1935,6 +1981,12 @@ public static function renderSearchForm($atts) { $q_placeholder = "Subdivision, Zipcode, " . $MLS_text . " area, " . $MLS_text . " #, etc"; + $search_heading_raw = apply_filters('simplyrets_search_heading_tag', 'h3'); + $search_heading = tag_escape($search_heading_raw); + if (empty($search_heading)) { + $search_heading = 'h3'; + } + if (array_key_exists('advanced', $attributes) && ($attributes['advanced'] == 'true' || $attributes['advanced'] == 'True')) { ?> @@ -1942,21 +1994,25 @@ public static function renderSearchForm($atts) { -

Advanced Listings Search

+ < class="sr-search-heading">Advanced Listings Search>
- Search keywords +
-
- - + + + +
-
-