SamC Posted March 30, 2017 Share Posted March 30, 2017 I've got two files. _search-form.php renders a search form (funny that!) and search.php renders the results. I have a few pre-requisites: 1) The search form select box for towns must be made up of only towns which exist in a field ($addressBuildingTown), on a building page (building-entry), in the system. 2) The values in the selectors must remain after submission, so I need to use a whitelist to GET the current values. _search-form.php loads above search.php 1) _search-form.php sets up the values which are populated into the actual form (that the user sees), 2) It also gets the values from the URL, sanitizes them (compares with an allowed list), then adds to a whitelist 3) The whitelist is used for when search.php is loaded so the select lists/checkboxes show the last selected values The problems: 1) _search-form.php appears on the homepage. At this URL 'mysite.com' there are no values TO get in the first place which kicked up some errors about arrays being required by sanitizer, which leads onto 2... 2) I had to use 'if ($input->get->town)' so that part of code doesn't run when on a page which includes the search form, but has yet to be submitted. Is this not dodgy? The value used in this check is not sanitized. 3) search.php uses a variable called '$theSelector' which is constructed in _search.php. The whitelist works though, even the checkboxes which I was pleased with, took a while to work out how to get an array of checkbox values into the whitelist and check a value against them. So, my form 'works' but HAS to be above search.php or I get a bunch of errors which isn't very modular. What if I wanted to shift it to the bottom? if I did that, how would the '$theSelector' variable ever make it to search.php? Would just be undefined. Anyway, here's the code so far: // search-form.php <?php namespace ProcessWire; ?> <?php // get all buildings $buildings = $pages->find("template=building-entry"); // get categories as pagearray of Ids sorted by title $buildingTypes = $pages->find("template=category-entry, sort=title"); // modify to standard array of Ids $buildingTypes = $buildingTypes->getArray(); //get unique towns into standard array $uniqueTowns = array_unique($buildings->explode("addressBuildingTown")); sort($uniqueTowns); // set up variables $minAreas = [ 500 => '500 sq ft', 1000 => '1,000 sq ft', 2000 => '2,000 sq ft', 3000 => '3,000 sq ft', 4000 => '4,000 sq ft', 5000 => '5,000 sq ft', 6000 => '6,000 sq ft', 7000 => '7,000 sq ft', 8000 => '8,000 sq ft', 9000 => '9,000 sq ft', 10000 => '10,000 sq ft', 50000 => '50,000 sq ft' ]; $maxAreas = [ 500 => '500 sq ft', 1000 => '1,000 sq ft', 2000 => '2,000 sq ft', 3000 => '3,000 sq ft', 4000 => '4,000 sq ft', 5000 => '5,000 sq ft', 6000 => '6,000 sq ft', 7000 => '7,000 sq ft', 8000 => '8,000 sq ft', 9000 => '9,000 sq ft', 10000 => '10,000 sq ft', 60000 => '60,000 sq ft', 100000 => '100,000 sq ft' ]; ?> <?php // catchall selector for empty fields $theSelector = "template=building-entry"; // start building selectors $sorting = "sort=addressBuildingTown"; $townSelector = "addressBuildingTown"; $buildingTypeSelector = "category"; $areaSelector = "buildingArea"; // used to output 'filtered by' above results // unused at the moment $summary = [ "towns" => "", "type" => "", "min" => "", "max" => "", ]; // Array to hold towns from GET $towns = []; // is this right? if ($input->get->town) { $allowedTowns = array_values($uniqueTowns); $towns = $sanitizer->options($input->get->town, $allowedTowns); $input->whitelist("towns", $towns); } // GET building type $allowedTypes = array_values($buildingTypes); $buildingType = $sanitizer->option($input->get->type, $allowedTypes); $input->whitelist("buildingType", $buildingType); // GET (sanitized) min area and add to whitelist $allowedMinAreas = array_keys($minAreas); $minArea = $sanitizer->option($input->get->minArea, $allowedMinAreas); $input->whitelist("minArea", $minArea); // GET max area $allowedMaxAreas = array_keys($maxAreas); $maxArea = $sanitizer->option($input->get->maxArea, $allowedMaxAreas); $input->whitelist("maxArea", $maxArea); // if a town has been selected if ($towns) { $townSelectorPart = ""; foreach ($towns as $town) { $townSelectorPart .= $town . "|"; } $townSelectorPart = rtrim($townSelectorPart, "|"); $summary["towns"] = $townSelectorPart; $theSelector .= "," . $townSelector . "=" . $townSelectorPart; } // if a building type has been selected if ($buildingType) { $summary["type"] = $buildingType; $theSelector .= "," . $buildingTypeSelector . "=" . $buildingType; } // if min or max area has been selected if ($minArea || $maxArea) { // if min area selected if ($minArea) { $summary["min"] = $minArea; $minAreaSelectorPart = $areaSelector . ">={$minArea}"; $theSelector .= "," . $minAreaSelectorPart; } // if max area selected if ($maxArea) { $summary["max"] = $maxArea; $maxAreaSelectorPart = $areaSelector . "<={$maxArea}"; $theSelector .= "," . $maxAreaSelectorPart; } } ?> // render the actual form <form id='building_search' method='get' action='<?php echo $config->urls->root?>search/'> <h3>Building search</h3> <p> <label for='search_town'>Filter by town</label><br> <?php foreach ($uniqueTowns as $town) { $selected = ""; if ($input->get->town) { $selected = $town == in_array($town, $input->whitelist("towns")) ? " checked" : ''; } echo "<input type='checkbox' name='town[]' value='{$town}'{$selected}>{$town}<br>"; } ?> </p> <p> <label for='search_type'>Building type</label> <select id='search_type' name='type'> <option value=''>Any</option> <?php foreach ($buildingTypes as $type) { $selected = $type == $input->whitelist("buildingType") ? "selected='selected'" : ''; echo "<option {$selected} value='{$type}'>{$type->title}</option>"; } ?> </select> </p> <p> <label for='search_min_area'>Min area</label> <select id='search_min_area' name='minArea'> <option value=''>No min</option> <?php foreach($minAreas as $value => $label) { $selected = $value == $input->whitelist("minArea") ? "selected='selected'" : ''; echo "<option {$selected} value='{$value}'>{$label}</option>"; } ?> </select> </p> <p> <label for='search_max_area'>Max area</label> <select id='search_max_area' name='maxArea'> <option value=''>No max</option> <?php foreach($maxAreas as $value => $label) { $selected = $value == $input->whitelist("maxArea") ? "selected='selected'" : ''; echo "<option {$selected} value='{$value}'>{$label}</option>"; } ?> </select> </p> <p><button type='submit' id='search_submit' name='submit' value='1'>Search</button></</p> </form> // search.php <?php namespace ProcessWire; ?> <?php $buildings = $pages->find($theSelector . "," . $sorting); foreach ($buildings as $building): ?> <h2><?php echo $building->title; ?></h2> <?php if ($building->addressBuildingName): ?> <p>BUILDING NAME: <?php echo $building->addressBuildingName; ?></p> <?php endif; ?> <p>ADDRESS: <?php echo $building->addressBuildingNumber . ' ' . $building->addressBuildingStreet . ', '; ?> <a href='#'><?php echo $building->addressBuildingTown; ?></a> </p> // URL segments are next on the todo list for the following <p>TYPE: <a href="<?php echo $building->category->url; ?>"><?php echo $building->category->title; ?></a></p> <p>AREA: <?php echo $building->buildingArea; ?></p> <?php echo $building->buidingDescription; ?> <?php endforeach; ?> Hope I'm making sense here. Thanks for any advice. Link to comment Share on other sites More sharing options...
Robin S Posted March 30, 2017 Share Posted March 30, 2017 Looks mostly good to me. My thoughts... If I'm understanding your code correctly (it would be clearer if you used a separate forum code block for each separate file) I think the code that builds the selector would be better placed in your search template (search.php) rather than in your search-form.php include. I think this would solve some of your concerns, and also allows you to create links to the search results page that don't come via the search form (e.g. suppose you had a page that listed a link for each town to show results for that town). If your search form appears on the search results page you just need to make sure the code that builds the selector is above the search-form.php include so that the whitelist variables have been populated. This part... $townSelectorPart = ""; foreach ($towns as $town) { $townSelectorPart .= $town . "|"; } $townSelectorPart = rtrim($townSelectorPart, "|"); ...could be shortened to... $townSelectorPart = implode('|', $towns); 33 minutes ago, SamC said: I had to use 'if ($input->get->town)' so that part of code doesn't run when on a page which includes the search form, but has yet to be submitted. Is this not dodgy? The value used in this check is not sanitized. I think it's okay to use an unsanitized GET variable if you are using it only to check if that variable is present. 1 Link to comment Share on other sites More sharing options...
SamC Posted March 31, 2017 Author Share Posted March 31, 2017 Thanks @Robin S I did a little bit of re-arranging (and used implode). Works when I do this: // _search-form.php <?php namespace ProcessWire; ?> <?php // get all buildings $buildings = $pages->find("template=building-entry"); // get categories as pagearray of Ids sorted by title $buildingTypes = $pages->find("template=category-entry, sort=title"); // modify to standard array of Ids $buildingTypes = $buildingTypes->getArray(); //get unique towns into standard array $uniqueTowns = array_unique($buildings->explode("addressBuildingTown")); sort($uniqueTowns); // set up variables $minAreas = [ 500 => '500 sq ft', 1000 => '1,000 sq ft', 2000 => '2,000 sq ft', 3000 => '3,000 sq ft', 4000 => '4,000 sq ft', 5000 => '5,000 sq ft', 6000 => '6,000 sq ft', 7000 => '7,000 sq ft', 8000 => '8,000 sq ft', 9000 => '9,000 sq ft', 10000 => '10,000 sq ft', 50000 => '50,000 sq ft' ]; $maxAreas = [ 500 => '500 sq ft', 1000 => '1,000 sq ft', 2000 => '2,000 sq ft', 3000 => '3,000 sq ft', 4000 => '4,000 sq ft', 5000 => '5,000 sq ft', 6000 => '6,000 sq ft', 7000 => '7,000 sq ft', 8000 => '8,000 sq ft', 9000 => '9,000 sq ft', 10000 => '10,000 sq ft', 60000 => '60,000 sq ft', 100000 => '100,000 sq ft' ]; // Array to hold towns from GET $towns = []; if ($input->get->town) { $allowedTowns = array_values($uniqueTowns); $towns = $sanitizer->options($input->get->town, $allowedTowns); $input->whitelist("towns", $towns); } // sanitizing and adding to whitelist have to be here // or I get errors when trying to pre-populate the // form with previosuly submitted values // GET building type $allowedTypes = array_values($buildingTypes); $buildingType = $sanitizer->option($input->get->type, $allowedTypes); $input->whitelist("buildingType", $buildingType); // GET (sanitized) min area and add to whitelist $allowedMinAreas = array_keys($minAreas); $minArea = $sanitizer->option($input->get->minArea, $allowedMinAreas); $input->whitelist("minArea", $minArea); // GET max area $allowedMaxAreas = array_keys($maxAreas); $maxArea = $sanitizer->option($input->get->maxArea, $allowedMaxAreas); $input->whitelist("maxArea", $maxArea); ?> <form id='building_search' method='get' action='<?php echo $config->urls->root?>search/'> <h3>Building search</h3> <p> <label for='search_town'>Filter by town</label><br> <?php foreach ($uniqueTowns as $town) { $selected = ""; if ($input->get->town) { // in_array() expects parameter 2 to be array, null given $selected = $town == in_array($town, $input->whitelist("towns")) ? " checked" : ''; } echo "<input type='checkbox' name='town[]' value='{$town}'{$selected}>{$town}<br>"; } ?> </p> <p> <label for='search_type'>Building type</label> <select id='search_type' name='type'> <option value=''>Any</option> <?php foreach ($buildingTypes as $type) { $selected = $type == $input->whitelist("buildingType") ? "selected='selected'" : ''; echo "<option {$selected} value='{$type}'>{$type->title}</option>"; } ?> </select> </p> <p> <label for='search_min_area'>Min area</label> <select id='search_min_area' name='minArea'> <option value=''>No min</option> <?php foreach($minAreas as $value => $label) { $selected = $value == $input->whitelist("minArea") ? "selected='selected'" : ''; echo "<option {$selected} value='{$value}'>{$label}</option>"; } ?> </select> </p> <p> <label for='search_max_area'>Max area</label> <select id='search_max_area' name='maxArea'> <option value=''>No max</option> <?php foreach($maxAreas as $value => $label) { $selected = $value == $input->whitelist("maxArea") ? "selected='selected'" : ''; echo "<option {$selected} value='{$value}'>{$label}</option>"; } ?> </select> </p> <p><button type='submit' id='search_submit' name='submit' value='1'>Search</button></</p> </form> // search.php <?php namespace ProcessWire; ?> <?php // start building the selector // catchall selector for empty fields $theSelector = "template=building-entry"; // start building selectors $sorting = "sort=addressBuildingTown"; $townSelector = "addressBuildingTown"; $buildingTypeSelector = "category"; $areaSelector = "buildingArea"; // used to output 'filtered by' above results $summary = [ "towns" => "", "type" => "", "min" => "", "max" => "", ]; // if a town has been selected if ($towns) { $townSelectorPart = implode('|', $towns); $summary["towns"] = $townSelectorPart; $theSelector .= "," . $townSelector . "=" . $townSelectorPart; } // if a building type has been selected if ($buildingType) { $summary["type"] = $buildingType; $theSelector .= "," . $buildingTypeSelector . "=" . $buildingType; } // if min or max area has been selected if ($minArea || $maxArea) { // if min area selected if ($minArea) { $summary["min"] = $minArea; $minAreaSelectorPart = $areaSelector . ">={$minArea}"; $theSelector .= "," . $minAreaSelectorPart; } // if max area selected if ($maxArea) { $summary["max"] = $maxArea; $maxAreaSelectorPart = $areaSelector . "<={$maxArea}"; $theSelector .= "," . $maxAreaSelectorPart; } } ?> <?php $buildings = $pages->find($theSelector . "," . $sorting); foreach ($buildings as $building): ?> <h2><?php echo $building->title; ?></h2> <?php if ($building->addressBuildingName): ?> <p>BUILDING NAME: <?php echo $building->addressBuildingName; ?></p> <?php endif; ?> <p>ADDRESS: <?php echo $building->addressBuildingNumber . ' ' . $building->addressBuildingStreet . ', '; ?> <a href='#'><?php echo $building->addressBuildingTown; ?></a> </p> <p>TYPE: <a href="<?php echo $building->category->url; ?>"><?php echo $building->category->title; ?></a></p> <p>AREA: <?php echo $building->buildingArea; ?></p> <?php echo $building->buidingDescription; ?> <?php endforeach; ?> Makes more sense now 9 hours ago, Robin S said: allows you to create links to the search results page that don't come via the search form (e.g. suppose you had a page that listed a link for each town to show results for that town ^ this is exactly what I want to do as well, nice one. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now