SamC

Members
  • Content count

    154
  • Joined

  • Last visited

Community Reputation

86 Excellent

About SamC

  • Rank
    Sr. Member

Profile Information

  • Gender
  • Location
    Surrey, UK

Recent Profile Visitors

309 profile views
  1. @giannisok a new one already? Only just got my head round the first one so my PHP journey continues. Thanks for sharing.
  2. @LostKobrakai thanks you're a gent. I applied similar functions to my other GET values: // GET towns array // // this one freaks if no towns are selected // Fatal error: Uncaught TypeError: Argument 1 passed to ProcessWire\Sanitizer::options() must // be of the type array, null given // $allowedTowns = array_values($uniqueTowns); $towns = $sanitizer->options($input->get->town, $allowedTowns); // GET building type $allowedTypes = array_values($uniqueBuildingTypes); $buildingType = $sanitizer->option($input->get->type, $allowedTypes); // GET (sanitized) min area $allowedMinAreas = array_keys($minAreas); $minArea = $sanitizer->option($input->get->minArea, $allowedMinAreas); // GET max area $allowedMaxAreas = array_keys($maxAreas); $maxArea = $sanitizer->option($input->get->maxArea, $allowedMaxAreas); So, nearly works. --EDIT-- I was thinking: // GET towns array if($input->get->town) { $allowedTowns = array_values($uniqueTowns); $towns = $sanitizer->options($input->get->town, $allowedTowns); } ...but the the code inside the if() statement isn't sanitized.
  3. Ok, so I did this (parts from two different files but run in the following order): $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' ]; <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) { echo "<option value='{$value}'>{$label}</option>"; } ?> </select> // GET (sanitized) min area $allowedMinAreas = array_keys($minAreas); // Return $input->get->minArea if it exists in the $allowedMinAreas array of values, or null if it doesn't. $minArea = $sanitizer->option($input->get->minArea, $allowedMinAreas); Now, I get the following results after submitting the form, and secondly after changing the value in the URL itself: search/?type=&minArea=4000 // Selector used for this page: template=building-entry,buildingArea>=4000 // after changing URL manually to a number NOT in the 'Min area' select list // a user can't change the values in the list, but they can modify the URL search/?type=&minArea=4200 // Selector used for this page: template=building-entry So I think it's working correctly Two things I still don't understand. What am I sanitizing? There's no input from a user on a select list. Am I correct in assuming it's to stop someone from changing the value in the URL (like I did above in my second result). Secondly, in the case of my checkboxes, those town names are obtained from text fields within the CMS. If a building-entry exists, and has a unique town name, it ends up as a new checkbox (the building-entry becomes searchable). So in this case, when do I sanitize? BEFORE the checkboxes are even rendered on the screen to a user? Or only when obtaining the values via $input->get? Or both even? Quote from $sanitizer api page: https://processwire.com/api/variables/sanitizer/#options-that-may-be-provided-to-the-text-and-textarea-functions I think these are the only issues I have with sanitizer so far, otherwise, pleased to have learned something new! Getting there, slowly but surely.
  4. Thanks @LostKobrakai, I'll give it a try.
  5. My selects are populated with this data in search-form.php <?php // get all buildings $buildings = $pages->find("template=building-entry"); // set up variables $towns = []; $buildingTypes = []; $minAreas = [ '500', '1000', '2000', '3000', '4000', '5000', '6000', '7000', '8000', '9000', '10000', '50000' ]; $maxAreas = [ '500', '1000', '2000', '3000', '4000', '5000', '6000', '7000', '8000', '9000', '10000', '60000', '100000' ]; //get unique towns into array foreach ($buildings as $building) { if(!in_array($building->addressBuildingTown, $towns)) { $towns[] = $building->addressBuildingTown; } $uniqueTowns = $towns; sort($uniqueTowns); //get unique building types into array if(!in_array($building->buildingType->title, $buildingTypes)) { $buildingTypes[] = $building->buildingType->title; } $uniqueBuildingTypes = $buildingTypes; sort($uniqueBuildingTypes); } ?> <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) { echo "<input type='checkbox' name='town[]' value='{$town}'>{$town}<br>"; } ?> </p> <p> <label for='search_type'>Building type</label> <select id='search_type' name='type'> <option value=''>Any</option> <?php foreach ($uniqueBuildingTypes as $type) { echo "<option value='{$type}'>{$type}</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 $minArea) { $formatMinArea = number_format($minArea); echo "<option value='{$minArea}'>{$formatMinArea} sq ft.</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 $maxArea) { $formatMaxArea = number_format($maxArea); echo "<option value='{$maxArea}'>{$formatMaxArea} sq ft.</option>"; } ?> </select> </p> <p><button type='submit' id='search_submit' name='submit' value='1'>Search</button></</p> </form> Looks like I'm missing something in my $minAreas and $maxAreas arrays. So are we saying I should change the key/values to: // FROM $minAreas = [ '500', '1000', '2000', '3000', '4000', '5000', '6000', '7000', '8000', '9000', '10000', '50000' ]; // TO $minAreas = [ 500 => '500 sq ft', 1000 => '1000 sq ft', 2000 => '2000 sq ft', 3000 => '3000 sq ft', 4000 => '4000 sq ft', 5000 => '5000 sq ft', 6000 => '6000 sq ft', 7000 => '7000 sq ft', 8000 => '8000 sq ft', 9000 => '9000 sq ft', 10000 => '10000 sq ft', 50000 => '50000 sq ft' ]; // use keys to make whitelist $allowed = array_keys($minAreas); // [500, 1000] // on search.php $minArea = $input->get->options("minArea", $allowed); Where does sanitizer come in here? Maybe I'm just being slow but still not fully getting this.
  6. This is what I was thinking ^ shouldn't need first(). I use similar code for my main heading image. i thought this would work: <div class='uk-child-width-1-2@s uk-child-width-1-2@m uk-grid-match uk-margin-large-bottom' pw-append='content' uk-grid> <?php foreach(page->children as $post): ?> <a class='uk-link-reset' href='<?php echo $post->url; ?>'> <div class='uk-card uk-card-default uk-card-hover uk-card-body'> <h3 class='uk-card-title uk-margin-remove'><?php echo $post->title; ?></h3> <img src='<?php echo $post->images->url ?>'> </div> </a> <?php endforeach; ?> Maybe check setup > fields > Edit Field: images > details ...and make sure Formatted value is 'Automatic' (so a single item when set to 1 or null if empty).
  7. Thanks guys. Not sure I get how to use this though: // Return $value if it exists in the $allowed array of values, or null if it doesn't. $sanitizer->option($value, $allowed) // Return array of given $values that that also exist in $allowed array whitelist of values. $sanitizer->options($values, $allowed) Do I make a whitelist? The code for the search.php is: <?php // catchall selector for empty fields $theSelector = "template=building-entry"; // start building selectors $sorting = "sort=addressBuildingTown"; $townSelector = "addressBuildingTown"; $buildingTypeSelector = "buildingType"; $areaSelector = "buildingArea"; // used to output 'filtered by' above results $summary = [ "towns" => "", "type" => "", "min" => "", "max" => "", ]; // GET towns array $towns = $input->get->town; // GET building type $buildingType = $input->get->type; // GET min area $minArea = $input->get->minArea; // GET max area $maxArea = $input->get->maxArea; // if a town has been selected if (count($towns)) { 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; } } echo "<h1>Summary array: "; print_r($summary); echo "</h1>"; ?> <?php echo "Selector used for this page: " . $theSelector; ?> <h1>Filtered by: <br> <?php $str = ""; if ($summary["towns"]) { $str .= "Towns: " . str_replace("|", ", ", $summary["towns"]) . "<br>"; } if($summary["type"]) { $str .= "Type: " . $summary["type"] . "<br>"; } if ($summary["min"]) { $str .= "Min area: " . $summary["min"] . "<br>"; } if($summary["max"]) { $str .= "Max area: " . $summary["max"] . "<br>"; } echo $str; ?> </h1> <?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->buildingType->title; ?></a></p> <p>AREA: <?php echo $building->buildingArea; ?></p> <?php echo $building->buidingDescription; ?> <?php endforeach; ?>
  8. @psquaretrader I think you maybe need curly braces here: echo "<img src='{$img->url}'>"; ?>
  9. So I've got my search form working ok now. I haven't run any of this through sanitizer yet though and could do with help choosing which functions are required. I'm over here having a read through them: https://processwire.com/api/variables/sanitizer/ The items I GET are: // search.php // GET towns array $towns = $input->get->town; // array - maybe $sanitizer->array($value) // GET building type $buildingType = $input->get->type; // dropdown select. Is a string? $sanitizer->text($value) perhaps or $sanitizer->string($value) // GET min area $minArea = $input->get->minArea; // integer - maybe $sanitizer->int($value) // GET max area $maxArea = $input->get->maxArea; // integer - maybe $sanitizer->int($value) Not entirely sure which to choose. The checkboxes and dropdowns are created from values entered in the admin area so I guess they could be entered incorrectly. Does that mean I should run sanitizer on the select list options too on my actual form before they are populated? Thanks for any advice. I can post the entire code of search-form.php and search.php if necessary, I thought that might distract a bit in this post. search-form.php: search.php:
  10. I didn't think of looking at the source on the actual page itself, I only referenced the file I downloaded.
  11. Thanks @rick I get what you meant. I use if sendMe=1 for validation on submission of my contact form. The input submit code in this post is a different site where I'm filtering buildings on a page (after form submit). I literally just got into manually creating forms a week or so ago (I used to use a Drupal module for this). I took that for granted, but I've learned so much in the past two days building this small test site, it's been well worth it. Used str_replace, rtrim, strtolower, in_array, learned about creating/adding to/looping php arrays, differences between post and get, getting input after form submissions, building PW selectors from dynamic data. Been a great couple of days! Finally graduating to more dynamic things now.
  12. @rick thanks but I tried that earlier already but it changed the text on the button to '1'. I still can't explain why skyscrapers code: <p><input type='submit' id='search_submit' name='submit' value='Search' /></p> ..results in a URL with &submit=1 on the end?! Unless I'm looking at old code, the site is the demo one online, not locally installed. Got the files from here, this is the search form: https://github.com/ryancramerdesign/SkyscrapersProfile/blob/master/site/templates/includes/search_form.php --edit-- Just had a thought, my contact form is a button rather than input: <button type="submit" name="sendMe" value="1">SEND MESSAGE</button> Maybe that would serve me better. Or maybe it doesn't matter because I'm the only one that'll ever look at the URL and say "I wish that said 1 instead of submit"...
  13. I've been trying to expand my knowledge with PHP and do something a bit more complex than the usual static sites. I created a bunch of pages 'building-entry' with title, town, etc... but just concentrating on the town at the moment. The option values (checkboxes here) are populated by the town names of pages that exist in the system, I haven't predefined these anywhere. Got this so far: // search-form.php <?php // get all buildings $buildings = $pages->find("template=building-entry"); $towns = []; //get unique towns into array foreach ($buildings as $building) { if(!in_array($building->addressBuildingTown, $towns)) { $towns[] = $building->addressBuildingTown; } $uniqueTowns = $towns; sort($uniqueTowns); } ?> <form id='building_search' method='get' action='<?php echo $config->urls->root?>search/'> <h3>Building search</h3> <p> <label for='search_town'>Town</label><br> <?php foreach ($uniqueTowns as $town) { $optionValue = str_replace(' ', '-', strtolower($town)); echo "<input type='checkbox' name='town[]' value='{$optionValue}'>{$town}<br>"; } ?> </p> <p><input type='submit' id='search_submit' name='submit' value='Search' /></p> </form> ...which goes to: // search.php // yet to add sanitizer <?php $towns = $input->get->town; print_r($towns); ?> So when I select a couple of values and submit the form, on search.php it outputs: Array ( [0] => town-1 [1] => town-2 ) So it appears I am getting the right values. The URL is a bit funky though, I end up with: mydemo.com/search/?town%5B%5D=town-1&town%5B%5D=town-2=&submit=Search Is this the right way to go about this? Is there a way to get a more friendly URL? I'm going to use these values to create a selector to list all buildings in a town(s). I was also looking at the search form in skyscrapers which after a search has a URL like: http://demo.processwire.com/search/?keywords=&city=austin&height=&floors=&year=&submit=1 // note &submit=1 Why does mine (which has the same line of code as skyscapers): <p><input type='submit' id='search_submit' name='submit' value='Search' /></p> ..end in &submit=Search I would have though mine would be the same? Thanks for any advice on the above.
  14. @danielsl I noticed this so I looked at the code more closely. I modified it to this, where it checks if the form has been sent, then validates, if the fields are ok, it checks whether recaptcha has been done. <?php require(dirname(__FILE__) . "/../../vendor/vlucas/valitron/src/Valitron/Validator.php"); require(dirname(__FILE__) . '/../../vendor/google/recaptcha/src/ReCaptcha/ReCaptcha.php'); $googleSiteKey = 'xxxxxx'; $googleSecretKey = 'xxxxxx'; $contactFormRecipient = 'my_email'; $contactPageID = '1022'; $v = new \Valitron\Validator(array( 'name' => $sanitizer->text($input->post->name), 'email' => $sanitizer->email($input->post->email), 'message' => $sanitizer->text($input->post->message) ) ); $v->rule('required', ['name', 'email', 'message']); $v->rule('email', 'email'); if ($input->post->sendMe) { if ($v->validate()) { $reCaptcha = new \ReCaptcha\ReCaptcha($googleSecretKey); $resp = $reCaptcha->verify($input->post->{'g-recaptcha-response'}, $_SERVER["REMOTE_ADDR"]); if ($resp->isSuccess()) { $name = $input->post->name; $email = $input->post->email; $message = $input->post->message; $message = " <html> <body> <p><b>From:</b></p> <p>{$name}</p> <p><b>Email:</b></p> <p>{$email}</p> <p><b>Message:</b></p> <p>{$message}</p> </body> </html>"; $mail = wireMail(); $mail->to($contactFormRecipient) ->from($email) ->subject('Contact form submission') ->bodyHTML($message) ->send(); $session->flashMessage = '<h2>Thanks for the message. I will get in touch with you shortly.</h2>'; $session->sent = true; $session->redirect($pages->get($contactPageID)->url); } else { $session->flashMessage = '<h2>Recaptcha must be complete.</h2>'; } } else { $session->flashMessage = '<h2>Please fill out the fields correctly.</h2>'; } } ?> <div class="body-row"> <div class="wrapper"> <?php if($session->flashMessage):?> <div class="alert <?=!$session->sent && (!$v->validate() || !$resp->isSuccess()) ? 'alert-danger' : 'alert-success'?>" role="alert"> <?php echo $session->flashMessage;?> </div> <?php endif;?> <form id="contact-form" method="post"> <div class="<?=$v->errors('name') ? 'has-error' : ''?>"> <label for="name">Name (required)</label> <input class="form-control" name="name" id="name" type="text" value="<?=$sanitizer->text($input->post->name)?>"> </div> <div class="<?=$v->errors('email') ? 'has-error' : ''?>"> <label for="email">Email (required)</label> <input class="form-control" name="email" id="email" type="text" value="<?=$sanitizer->text($input->post->email)?>"> </div> <div class="<?=$v->errors('message') ? 'has-error' : ''?>"> <label for="message">Message (required)</label> <textarea class="form-control" name="message" id="message"><?=$sanitizer->text($input->post->message)?></textarea> </div> <div> <!-- Google Recaptcha code START --> <div class="g-recaptcha" data-sitekey="<?=$googleSiteKey?>"></div> <script type="text/javascript" src="https://www.google.com/recaptcha/api.js"> </script> <!-- Google Recaptcha code END --> </div> <div class="centered"> <button type="submit" name="sendMe" value="1">SEND MESSAGE</button> </div> </form> </div> </div> <?php $session->remove('flashMessage'); $session->sent = false; ?> Maybe not the best solution but it seems to work. Maybe some other members can help clean up my logic. Anyway, hope this helps.
  15. I am building a little demo site with properties to try and get some search functionality going (this may become a real project but even if not, it's good to try something more difficult than I'm currently doing). Property - Type (residential/office/medical - predefined options list) - Area (integer - sq ft) - Location (number, street, town, county, postcode) - Image (image field) - Google map with marker Search (dropdowns x 4 with submit button) - Type - Location (town only) - Min area (sq ft) - Max area (sq ft) Now the top two search dropdowns only really need to list data which actually exists in the system, I don't see much adding a bunch of predefined towns only for the search result to come back empty if no buildings match. Maybe some logic to check every page of type 'building', get town, add to list, if town is already in the list, don't add and also maybe even a (3) or (4) or whatever next to towns to show how many buildings are available. I want to set it up so the address data can be entered when creating a 'building' page, but also used to populate the town dropdown, but also the address to be used to show a map marker where the building is physically. I was thinking I could use fields (a single string might be input incorrectly, missing comma or something, possibly making post processing more tricky): 1) addressNumber (integer) 2) addressStreet (string) 3) addressTown (string) 4) addressCounty (dropdown of all UK counties) 5) addressPostcode (would this be an integer or a string?) Seems I'm a bit stuck at the first hurdle. I'm concerned that setting up the fields wrong at the beginning could mean a lot of manual work later on so thought I'd ask for some advice first. Once I get the data structure sorted, I can create a few dummy buildings and then get to work on a page which lists ALL buildings (in my case home.php), then a search form (goes to search.php) will filter the results. It's like the skyscraper demo site which I can use for reference. I see however, that skyscrapers uses a google map to get the address after typing it into a single text field. I need a map also but this may not be the best solution for me as each building page needs to output certain details ie. TOWN: addressTown (which would link to search page to show all other buildings in this town). Would this not be easier if each part of the address was split up rather than a single field? I could maybe dynamically add the map marker (from the address fields) on the page itself when the building page is rendered. Not sure about this either. Hopefully I'm making some sense here. Never built anything like this before so any help is greatly appreciated. Thanks.