Jump to content

[Solved] How to process "get" input values from a group of checkboxes (multiple combination)


Federico
 Share

Recommended Posts

Hello processwire community,

this is my very first post, I am newbie in php - trying to test a simple search form which allow frontend user to search and filter data. Macro steps are as follow:

1) Create the form with options as checkboxes, allowing the user to choose multiple values selection within multiple checkbox groups;

2) Create the pw process code in order to revert back the selected items (as a result of multiple checkbox filter combination), allowing pagination as some results may be more than 50 items;

---

Here some of the most related topics I've found over PW forum:

https://processwire.com/talk/topic/7282-paging-issue-with-html-form-check-boxes/

https://processwire.com/talk/topic/3472-enable-pagination-for-search-results/#comment-38214

https://processwire.com/talk/topic/1883-how-to-use-input-whitelist-with-checkboxes-or-any-array/

https://processwire.com/talk/topic/1547-getting-multiple-checkbox-values-within-processwire/

https://processwire.com/talk/topic/1034-search-form-with-multiple-fields/

https://processwire.com/talk/topic/10193-multiselect-search/

---

Now, the html form works just fine (code below), it brings all checked values to the url string. Essentially I have four different groups of checkboxes:

  • cb_sectors[] (multiple pages as categories);
  • cb_expertise[] (multiple pages as categories);
  • cb_status[] (multiple pages as categories); 
  • cb_year[] (integer).

The user may select multiple values within the above four checkbox groups, thus creating even quite complex combination.

<form name='search' id='search-works' method='get' role='form' action='<?php echo $pages->get('/search/')->url; ?>'>

    <div class="collapsible-header">Sector</div>
        <?php foreach($pages->get("/sectors")->children as $sec) echo "
            <p class='checkbox'>
            <input type='checkbox' name='cb_sectors[]' value='{$sec->name}' id='{$sec->name}'/>
            <label for='{$sec->name}'>{$sec->title}</label>
            </p>
        " ?>

    <div class="collapsible-header">Status</div>
        <?php foreach($pages->get("/taxonomy/Status")->children as $st) echo "
            <p class='checkbox'>
            <input type='checkbox' name='cb_status[]' value='{$st->name}' id='{$st->name}' />
            <label for='{$st->name}'>{$st->title}</label>
            </p>
        " ?>

    <div class="collapsible-header no-padding">Expertise</div>
        <?php foreach($pages->get("/expertise")->children as $cb_expertise)
            $checked = $cb_expertise->name == $input->whitelist->cb_expertise ? " selected='selected' " : '';
        echo "
            <p class='checkbox'>
            <input type='checkbox' name='cb_expertise[]' value='{$cb_expertise->name}' id='{$cb_expertise->name}' $checked/>
            <label for='{$cb_expertise->name}'>{$cb_expertise->title}</label>
            </p>
            "
        ?>

    <div class="collapsible-header no-padding">Year</div>
        <?php
        // generate a range of years from '09 to '17, or maybe better doing it via pages as years?
        for($year = 2009; $year <= 2017; $year += 1){
            echo "
            <p class='checkbox'>
            <input type='checkbox' name='cb_year[]' value='$year' id='$year' />
            <label for='$year'>{$year}</label>
            </p>
            ";
        } ?>

    <input class="no-class" type="submit" id="search-submit" name="submit" value="Search">
</form>

The question is then mostly focusing on the second step, the pw process code:

As some previous posts on this topic say - I should (in sequence) sanitize, whitelist and validate the results before pass them to the final output (correct me if I'm wrong). The thing is that I cannot find a way to get all values in a array and sanitize/whitelist/validate them -> some post suggest to use arraytoCSV as a way to let PW remember the filtered items while moving through pagination. Is arraytoCSV the best way to sanitize and whitelist the user input combination?

The following code simply get selected values coming from the above form. As you can see, no sanitize nor whitelist nor validation is in place, as without the array the sanitizing function gives back only the last selected value (not the full combination for every group of checkboxes).

Can you please help me implementing an array within the following code and the way to sanitize - whitelist - validate "get" values? I know for most of you is just as simple as drinking coffe, but would be great if you could drive me in the right direction. Thank you in advance!

<?php namespace ProcessWire;

if(count($input->get)) {

    if($input->get->cb_expertise) {
        foreach ($input->get->cb_expertise as $expertise) {
            // here we are just ensuring submitted products are in fact valid
            echo $expertise;
        }
    }
    if($input->get->cb_sectors) {
        foreach($input->get->cb_sectors as $sector) {
        // here we are just ensuring submitted products are in fact valid
        echo $sector;
        }
    }
    if($input->get->cb_status) {
        foreach($input->get->cb_status as $status) {
            // here we are just ensuring submitted products are in fact valid
            echo $status;
        }
    }
    if($input->get->cb_year) {
        foreach($input->get->cb_year as $year) {
            // here we are just ensuring submitted products are in fact valid
            echo $year;
        }
    }
}

?>
Spoiler

 

 

Link to comment
Share on other sites

Hi @Federico, welcome to the PW forums. :)

I'm not really clear about what you're asking but here goes...

On 28/01/2017 at 0:27 AM, Federico said:

The thing is that I cannot find a way to get all values in a array and sanitize/whitelist/validate them

You want an array of all GET variables? You can get that by using $input->get() without any argument.

foreach($input->get() as $key => $value) {
    // sanitize each $value as needed, etc
}

But you probably don't want to treat each variable the same, pass it through the same sanitizer, etc. So I think it's more typical to check for the exact named variables you are expecting and ignore anything else that might be there. So for example, for "year" you are expecting an integer so you would apply $sanitizer->inUnsigned().

P.S. take a look at the search template for the Skyscrapers demo - it's a really good demonstration of the basic principles.

  • Like 1
Link to comment
Share on other sites

Thank you @Robin S for your inputs! 

After tons of tentatives, I've got the whole search engine work. However, now the last missing part: let the pagination work with the search results, as by moving from the page nr. 2 onward, the selectors are not kept in memory/session thus no filter anymore. This is the code:  

<?php if(count($input->get)) {

    $selector = ''; //Selector to store selected inputs 
    $selector .= "template=PageSingleWork, "; //Start concatenation, picking up all pages using this template

    if ($input->get->cb_expertise) {
        foreach($input->get->cb_expertise as $expertise) {
            // Sanitize it
            $exp = $sanitizer->selectorValue($input->get->cb_expertise);
            // Add it to the whitelist... with the same name as the get var!
            $input->whitelist('expertise', $exp);
            $selector .= "expertise=$expertise, ";
        }
    }
    if ($input->get->cb_sectors) {
        foreach($input->get->cb_sectors as $sector) {
            // Sanitize it
            $sec = $sanitizer->selectorValue($input->get->cb_sectors);
            // Add it to the whitelist... with the same name as the get var!
            $input->whitelist('sectors', $sec);
            $selector .= "sectors=$sector, ";
        }
    }
	$works = $pages->find("$selector, limit=3"); //process the inputs to get filtered works
    foreach($works as $work) {
    //here some work...
    }
	$pagination = $works->renderPager();
	echo $pagination;
}
else {
	$works = $pages->find("limit=3"); //process the inputs to get all works as default
    foreach($works as $work) {
    //here some work...
    }
	$pagination = $works->renderPager();
	echo $pagination;
}

 

Something wrong with the whitelist? Probably I have to put whitelist code also outside the foreach statement, in order to keep individual values?

Or probably I have to declare the whitelist even in the html form? 

much appreciated, really

 

Link to comment
Share on other sites

6 hours ago, Federico said:

However, now the last missing part: let the pagination work with the search results, as by moving from the page nr. 2 onward, the selectors are not kept in memory/session thus no filter anymore.

You shouldn't need to do anything special to get pagination to work with search results.

Make sure you have page numbers enabled for your search template:

2017-01-30_090241.png

Your pagination links (2, 3, 4, etc) should include the GET query string (e.g. "/search/page2?search=foo&year=1985") - do they in your case?

 

Link to comment
Share on other sites

Thanks again Robin. Yes the template has already the pagination option checked. The weird thing is happening in the url string from page 2 on, as the URL in page 0 is as follow (which is almost ok, although still missing other checkbox empty values):

.../?keywords=&cb_status%5B%5D=ongoing&submit=Search

and from page 1 (or 2) the URL become like this (no more inputs...):

.../?cb_status=array-1

which display clearly all items. It's something I cannot really figure out... I've also tried considering renderToCSV for pagination but no luck

$pagination = $works->renderPager(array('arrayToCSV' => false));

It should be something related to the whitelist, I think i am not storing correctly the get vars

Link to comment
Share on other sites

After several test, I am pretty sure the problem is given by the wrong white listing approach I am using with checkbox values (array). Here again the html checkbox field example (using "get" form):

 <?php foreach($pages->get("/taxonomy/status/")->children() as $st) {
    $checked = $st->name == $input->whitelist->st ? " selected='selected' " : ''; //HERE PART OF THE PROBLEM, IT DOES NOT CONSIDERING ARRAY
    echo "
    <p class='checkbox'>
    	<input type='checkbox' name='st[]' value='{$st->name}' id='{$st->name}' $checked/>
    	<label for='{$st->name}'>{$st->title}</label>
    </p>
    ";
}
?>

and this it's related process form:

<?php
if($input->get->st) {
    foreach($input->get->st as $st) {
        $san = $sanitizer->selectorValue($st);
        $san = $input->whitelist($st = ''); // THIS FOR ARRAY SEEMS NOT WORKING
        $selector .= "projectStatus=$san, ";
        //$input->whitelist('st', $san); //ALTERNATIVE TO THE ABOVE, AGAIN NOT WORKING FOR ARRAY
    }
}
?>

Someone already faced this issue (not storing the vars array in the whitelist or retrieve them) ? There are some posts in this forum but they look a bit outdated in terms of approach and not referring to the PW latest releases

many thanks in advance!

 

Link to comment
Share on other sites

29 minutes ago, Federico said:

$checked = $st->name == $input->whitelist->st ? " selected='selected' " : ''; //HERE PART OF THE PROBLEM, IT DOES NOT CONSIDERING ARRAY

Haven't read or understood your whole post but if $input->whitelist->st is an array, and $st->name I guess is a string, in PHP, you cannot compare an array to a string like that. 

Edited by kongondo
Link to comment
Share on other sites

22 hours ago, Federico said:

$san = $input->whitelist($st = ''); // THIS FOR ARRAY SEEMS NOT WORKING

That isn't valid syntax for whitelist(). See the docs: https://processwire.com/api/ref/wire-input/whitelist/

22 hours ago, Federico said:

//$input->whitelist('st', $san); //ALTERNATIVE TO THE ABOVE, AGAIN NOT WORKING FOR ARRAY

The syntax is valid here, but you'll be overwriting the same whitelist key in each iteration of your loop.

I think you want to set an array to the whitelist key, e.g.

$san_array = array();
foreach($input->get->st as $st) {
    $san = $sanitizer->selectorValue($st);
    $san_array[] = $san;
    $selector .= "projectStatus=$san, ";
}
$input->whitelist('st', $san_array);

// later, call renderPager() with arrayToCSV set false
// or do as Ryan suggests: https://processwire.com/talk/topic/1883-how-to-use-input-whitelist-with-checkboxes-or-any-array/?do=findComment&comment=17706
echo $works->renderPager(array('arrayToCSV' => false));

For the other issue regarding setting the selected attribute of the checkbox you'll want to use in_array.

  • Like 2
Link to comment
Share on other sites

Thank you @kongondo,

it's a newbie mistake and now I am testing it with in_array function, however seems hard to get the "checked" function work. Here the code:

foreach($pages->get("/sectors/")->children() as $sec) {
  
	if(in_array($sec->name, $input->get->cb_sectors)) { //clearly wrong as it loops against empty array...
  	$selected = ' checked ';
  	}

	echo "
	<p class='checkbox'>
  		<input type='checkbox' name='cb_sectors[]' value='{$sec->name}' id='{$sec->name}' $selected/>
  		<label for='{$sec->name}'>{$sec->title}</label>
	</p>
	";
}

 

Thank you @Robin S ,

it works like charm! :) thank you a lot to sort this out - I will post some final code as soon as it will be ready

Link to comment
Share on other sites

2 minutes ago, Federico said:

I guess the $input->get->cb_sectors has to be array since it is checkbox group, but it says in the frontend: "Warning: in_array() expects parameter 2 to be array, null given"

It has to be an array, yes. According to that warning, it says nothing (not empty, but nothing) is being returned by $input->get->cb_sectors. Do you have a typo somewhere? Maybe if we saw the whole code we could better help.

  • Like 1
Link to comment
Share on other sites

Thank you Kongondo for your help, here the html code:

<form name='search' id='search-works' method='get' role='form'>
	<?php 
	foreach($pages->get("/sectors/")->children() as $sec) {
		if(in_array($sec->name, $input->get->cb_sectors)) { //$input->get->cb_sectors is supposed to be an array, but it outputs nothing instead...
			$selected = ' checked ';
		}
		echo "
		<p class='checkbox'>
		<input type='checkbox' name='cb_sectors[]' value='{$sec->name}' id='{$sec->name}' $selected/>
		<label for='{$sec->name}'>{$sec->title}</label>
		</p>
		";
	}
	?>
  <input class="mettiqualcosaqui" type="submit" id="search-submit" name="submit" value="Search">
</form>
                                        

Here the processor code:

<?php
if(count($input->get)) {

  $selector = ''; //Selector to store selected inputs - and it should have "=" as it is the first in the list
  $selector .= "template=PageSingleWork, "; //Start concatenation, picking up all pages using this template

  if ($input->get->cb_sectors) {
      $se_array = array();
      foreach ($input->get->cb_sectors as $se) {
          $sec = $sanitizer->selectorValue($se);
          $se_array[] = $sec;
          $selector .= "sectors=$sec, ";
  	  }
  	  $input->whitelist('cb_sectors', $se_array);
  }

  $works = $pages->find("$selector, limit=2"); //process the inputs to get filtered works
  $filtered = $pages->find($selector); //process the inputs to get filtered works
  $count = count($filtered); //count found works items

  if($count) { //found items and so display them
      echo "Found $count projects matching your search, using selectors ($selector)<br>";
  {
  else { //no search results
      echo "Sorry, no projects were found that match, using selectors ($selector)<br>";
  }

}
else {
// do something
}
?>

I think the code in the frontend does not retrieve the whitelist array correctly, but all seem to be correct..

Link to comment
Share on other sites

You are trying to set the selected attribute of the checkboxes based on $input->get, but typically your search attributes should be set based on $input->whitelist. See the Skyscrapers profile for an example: https://github.com/ryancramerdesign/skyscrapers2/blob/master/includes/search-form.php

So you get the whitelist value for the relevant key, which will be an array because that is what you have set to it. Edit: you should first check it with is_array in case nothing has been set to the whitelist yet. And you use in_array against the array for each checkbox value to see if it should be selected.

 

Link to comment
Share on other sites

I've tried many solution, none of them seem to retrieve correctly the array from the whitelist. The nearest solution to what discussed here is like the following:

if(is_array($input->whitelist('cb_sectors'))) { //check if any array exists 
	if(in_array($cb_sectors->name, $input->whitelist->cb_sectors)) {  //if yes, match the value against the array 
		echo "$cb_sectors->name is checked ";  //just to display if working , but nothing outputs...
	}
}

//alternative to above

if(is_array($input->whitelist->cb_sectors)) { //check if any array exists 
	if(in_array($cb_sectors->name, $input->whitelist->cb_sectors)) {  //if yes, match the value against the array 
		echo "$cb_sectors->name is checked ";  //just to display if working , but nothing outputs...
	}
}

 

Link to comment
Share on other sites

31 minutes ago, Federico said:

I've tried many solution, none of them seem to retrieve correctly the array from the whitelist.

A little debugging is in order then. You have two variables, $input->whitelist('cb_sectors') and $cb_sectors->name, and you want to know what is in them. You can use basic PHP functions such as var_dump() or print_r(), or you can use the much nicer dumping options in Tracy Debugger.

  • Like 2
Link to comment
Share on other sites

Thank you Robin,

I've made some testing and debugging (also with tracy debugger) to see what these variables keep while filtering. However everything seems working ok, except when it comes to retrieve data from the stored whitelist...well it says NULL (e.g. never an array, no data, nothing).

I've only managed to retrieve the array by using $input->get instead of $input->whitelist :

<?php
	foreach($pages->get("/sectors/")->children() as $sec) {
        $attrs = array();
        if (is_array($input->get->cb_sectors)) {
            if(in_array($sec->name, $input->get->cb_sectors)) {
            $selected = ' selected="selected"';
        }
    }
    echo "<option $selected value='{$sec->name}'>{$sec->title}</option>";
}?>

which yes retrieve the array but now the issue is that I am going to select all option instead of the only active ones. Weird...

Link to comment
Share on other sites

If a whitelist value is null then it simply means you have not set anything to it at the time you are checking it.

Going back to the earlier code example, you must set an array to the whitelist...

$san_array = array();
foreach($input->get->st as $st) {
    $san = $sanitizer->selectorValue($st);
    $san_array[] = $san;
    $selector .= "projectStatus=$san, ";
}
$input->whitelist('st', $san_array);

...before you can use the array in $input->whitelist('st') to determine which checkboxes should be selected.

The code above will appear in your search results template, and it must execute before (i.e. above) your code for generating the search inputs. Of course if you are showing your search inputs on other templates besides the search results template then $input->whitelist('st') will be null, but in those cases you don't want any of the inputs to be pre-selected anyway.

  • Like 1
Link to comment
Share on other sites

As you recommended, reversing the position of the php processing code before the html form, made the trick and now the whitelist start populating - sorry to raise such silly issues... :)

maybe you know what is the best approach on defining which input (checkbox in this case) should be marked as "checked" by matching the string value against the whitelist array. I mean, I get the "checked" feature for all the checkboxes, regardless of the options really selected. In_array works perfectly (thanks again!), I think I should first validate which option to check before outputting the $checked variable like the following. Do you have better suggestion?

<form name='search' id='search-works' method='get' role='form'>
	<?php 
	foreach($pages->get("/sectors/")->children() as $sec) {
		if(is_array($input->whitelist->cb_sectors)) { 
			if(in_array($sec->name, $input->whitelist->cb_sectors)) { 
			$selected = ' selected ';
			//$selected['selected'] = 'selected'; //Working  alternative?
			}
		}
		echo "
		<p class='checkbox'>
		<input type='checkbox' name='cb_sectors[]' value='{$sec->name}' id='{$sec->name}' $selected/>
		<label for='{$sec->name}'>{$sec->title}</label>
		</p>
		";
	}
	?>
  <input class="class" type="submit" id="search-submit" name="submit" value="Search">
</form>

 

Link to comment
Share on other sites

You need to initialise/reset the $selected variable in each iteration of the loop. Otherwise two things will happen:

1. If the first child of /sectors/ is not in the whitelist array you will get an undefined variable notice.

2. After one child name is found in the whitelist array and $selected is defined as ' selected ' then all subsequent iterations will output ' selected ', regardless of if that input is in the whitelist array or not. This is the problem you are getting now.

So it should be something like:

foreach($pages->get("/sectors/")->children() as $sec) {
    $selected = '';
    if(is_array($input->whitelist->cb_sectors) && in_array($sec->name, $input->whitelist->cb_sectors)) $selected = ' selected ';
    // echo, etc...
}

 

  • Like 1
Link to comment
Share on other sites

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...