Improve user option search
authorMatthias Schmidt <gravatronics@live.com>
Sat, 3 May 2014 18:14:47 +0000 (20:14 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Sat, 3 May 2014 18:14:47 +0000 (20:14 +0200)
Searching certain user options can be enabled/disabled making it possible to search for "empty" user options.

17 files changed:
com.woltlab.wcf/templates/multiSelectSearchableOptionType.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/radioButtonSearchableOptionType.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/selectSearchableOptionType.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/textSearchableOptionType.tpl [new file with mode: 0644]
com.woltlab.wcf/userOption.xml
wcfsetup/install/files/acp/templates/multiSelectSearchableOptionType.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/radioButtonSearchableOptionType.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/selectSearchableOptionType.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/textSearchableOptionType.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/form/UserSearchForm.class.php
wcfsetup/install/files/lib/system/option/BirthdayOptionType.class.php
wcfsetup/install/files/lib/system/option/MultiSelectOptionType.class.php
wcfsetup/install/files/lib/system/option/RadioButtonOptionType.class.php
wcfsetup/install/files/lib/system/option/SelectOptionType.class.php
wcfsetup/install/files/lib/system/option/TextOptionType.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

diff --git a/com.woltlab.wcf/templates/multiSelectSearchableOptionType.tpl b/com.woltlab.wcf/templates/multiSelectSearchableOptionType.tpl
new file mode 100644 (file)
index 0000000..67531cf
--- /dev/null
@@ -0,0 +1,21 @@
+<label><input type="checkbox" id="search_{$option->optionName}" name="searchOptions[{$option->optionName}]"{if $searchOption} checked="checked"{/if} /> {lang}wcf.user.option.searchRadioButtonOption{/lang}</label>
+<select id="{$option->optionName}" name="values[{$option->optionName}][]" multiple="multiple" size="{if $selectOptions|count > 10}10{else}{@$selectOptions|count}{/if}"{if !$searchOption} disabled="disabled"{/if}>
+       {foreach from=$selectOptions key=key item=selectOption}
+               <option value="{$key}"{if $key|in_array:$value} selected="selected"{/if}>{lang}{@$selectOption}{/lang}</option>
+       {/foreach}
+</select>
+
+<script data-relocate="true">
+//<![CDATA[
+$(function() {
+       $('#search_{$option->optionName}').change(function(event) {
+               if ($(event.currentTarget).prop('checked')) {
+                       $('#{$option->optionName}').enable();
+               }
+               else {
+                       $('#{$option->optionName}').disable();
+               }
+       });
+});
+//]]>
+</script>
diff --git a/com.woltlab.wcf/templates/radioButtonSearchableOptionType.tpl b/com.woltlab.wcf/templates/radioButtonSearchableOptionType.tpl
new file mode 100644 (file)
index 0000000..b52bcd6
--- /dev/null
@@ -0,0 +1,19 @@
+<label><input type="checkbox" id="search_{$option->optionName}" name="searchOptions[{$option->optionName}]"{if $searchOption} checked="checked"{/if} /> {lang}wcf.user.option.searchRadioButtonOption{/lang}</label>
+{foreach from=$selectOptions key=key item=selectOption}
+       <label><input type="radio" name="values[{$option->optionName}]" value="{$key}" {if $value == $key} checked="checked"{/if} {if $disableOptions[$key]|isset || $enableOptions[$key]|isset}class="jsEnablesOptions" data-disable-options="[ {@$disableOptions[$key]}]" data-enable-options="[ {@$enableOptions[$key]}]"{/if} /> {lang}{@$selectOption}{/lang}</label>
+{/foreach}
+
+<script data-relocate="true">
+//<![CDATA[
+$(function() {
+       $('#search_{$option->optionName}').change(function(event) {
+               if ($(event.currentTarget).prop('checked')) {
+                       $('input[name="values[{$option->optionName}]"]').enable();
+               }
+               else {
+                       $('input[name="values[{$option->optionName}]"]').disable();
+               }
+       });
+});
+//]]>
+</script>
diff --git a/com.woltlab.wcf/templates/selectSearchableOptionType.tpl b/com.woltlab.wcf/templates/selectSearchableOptionType.tpl
new file mode 100644 (file)
index 0000000..cb54c04
--- /dev/null
@@ -0,0 +1,22 @@
+<label><input type="checkbox" id="search_{$option->optionName}" name="searchOptions[{$option->optionName}]"{if $searchOption} checked="checked"{/if} /> {lang}wcf.user.option.searchRadioButtonOption{/lang}</label>
+<select id="{$option->optionName}" name="values[{$option->optionName}]"{if !$searchOption} disabled="disabled"{/if}>
+       {if !$allowEmptyValue|empty}<option value="">{lang}wcf.global.noSelection{/lang}</option>{/if}
+       {foreach from=$selectOptions key=key item=selectOption}
+               <option value="{$key}"{if $value == $key} selected="selected"{/if}>{lang}{@$selectOption}{/lang}</option>
+       {/foreach}
+</select>
+
+<script data-relocate="true">
+//<![CDATA[
+$(function() {
+       $('#search_{$option->optionName}').change(function(event) {
+               if ($(event.currentTarget).prop('checked')) {
+                       $('#{$option->optionName}').enable();
+               }
+               else {
+                       $('#{$option->optionName}').disable();
+               }
+       });
+});
+//]]>
+</script>
diff --git a/com.woltlab.wcf/templates/textSearchableOptionType.tpl b/com.woltlab.wcf/templates/textSearchableOptionType.tpl
new file mode 100644 (file)
index 0000000..945e836
--- /dev/null
@@ -0,0 +1,17 @@
+<label><input type="checkbox" id="search_{$option->optionName}" name="searchOptions[{$option->optionName}]"{if $searchOption} checked="checked"{/if} /> {lang}wcf.user.option.searchTextOption{/lang}</label>
+<input type="{@$inputType}" id="{$option->optionName}" name="values[{$option->optionName}]" value="{$value}"{if $inputClass} class="{@$inputClass}"{/if}{if !$searchOption} disabled="disabled"{/if} />
+
+<script data-relocate="true">
+//<![CDATA[
+$(function() {
+       $('#search_{$option->optionName}').change(function(event) {
+               if ($(event.currentTarget).prop('checked')) {
+                       $('#{$option->optionName}').enable();
+               }
+               else {
+                       $('#{$option->optionName}').disable();
+               }
+       });
+});
+//]]>
+</script>
index 08a21e0feb04d8676edf5f947b86c46560a263ec..331acd191ef95947e7ee2d77a2f9c03b0556a4eb 100644 (file)
@@ -81,7 +81,7 @@
                        
                        <option name="gender">
                                <categoryname>profile.personal</categoryname>
-                               <optiontype>radioButton</optiontype>
+                               <optiontype>select</optiontype>
                                <outputclass>wcf\system\option\user\SelectOptionsUserOptionOutput</outputclass>
                                <selectoptions>
                                        <![CDATA[0:wcf.global.noDeclaration
diff --git a/wcfsetup/install/files/acp/templates/multiSelectSearchableOptionType.tpl b/wcfsetup/install/files/acp/templates/multiSelectSearchableOptionType.tpl
new file mode 100644 (file)
index 0000000..67531cf
--- /dev/null
@@ -0,0 +1,21 @@
+<label><input type="checkbox" id="search_{$option->optionName}" name="searchOptions[{$option->optionName}]"{if $searchOption} checked="checked"{/if} /> {lang}wcf.user.option.searchRadioButtonOption{/lang}</label>
+<select id="{$option->optionName}" name="values[{$option->optionName}][]" multiple="multiple" size="{if $selectOptions|count > 10}10{else}{@$selectOptions|count}{/if}"{if !$searchOption} disabled="disabled"{/if}>
+       {foreach from=$selectOptions key=key item=selectOption}
+               <option value="{$key}"{if $key|in_array:$value} selected="selected"{/if}>{lang}{@$selectOption}{/lang}</option>
+       {/foreach}
+</select>
+
+<script data-relocate="true">
+//<![CDATA[
+$(function() {
+       $('#search_{$option->optionName}').change(function(event) {
+               if ($(event.currentTarget).prop('checked')) {
+                       $('#{$option->optionName}').enable();
+               }
+               else {
+                       $('#{$option->optionName}').disable();
+               }
+       });
+});
+//]]>
+</script>
diff --git a/wcfsetup/install/files/acp/templates/radioButtonSearchableOptionType.tpl b/wcfsetup/install/files/acp/templates/radioButtonSearchableOptionType.tpl
new file mode 100644 (file)
index 0000000..b52bcd6
--- /dev/null
@@ -0,0 +1,19 @@
+<label><input type="checkbox" id="search_{$option->optionName}" name="searchOptions[{$option->optionName}]"{if $searchOption} checked="checked"{/if} /> {lang}wcf.user.option.searchRadioButtonOption{/lang}</label>
+{foreach from=$selectOptions key=key item=selectOption}
+       <label><input type="radio" name="values[{$option->optionName}]" value="{$key}" {if $value == $key} checked="checked"{/if} {if $disableOptions[$key]|isset || $enableOptions[$key]|isset}class="jsEnablesOptions" data-disable-options="[ {@$disableOptions[$key]}]" data-enable-options="[ {@$enableOptions[$key]}]"{/if} /> {lang}{@$selectOption}{/lang}</label>
+{/foreach}
+
+<script data-relocate="true">
+//<![CDATA[
+$(function() {
+       $('#search_{$option->optionName}').change(function(event) {
+               if ($(event.currentTarget).prop('checked')) {
+                       $('input[name="values[{$option->optionName}]"]').enable();
+               }
+               else {
+                       $('input[name="values[{$option->optionName}]"]').disable();
+               }
+       });
+});
+//]]>
+</script>
diff --git a/wcfsetup/install/files/acp/templates/selectSearchableOptionType.tpl b/wcfsetup/install/files/acp/templates/selectSearchableOptionType.tpl
new file mode 100644 (file)
index 0000000..cb54c04
--- /dev/null
@@ -0,0 +1,22 @@
+<label><input type="checkbox" id="search_{$option->optionName}" name="searchOptions[{$option->optionName}]"{if $searchOption} checked="checked"{/if} /> {lang}wcf.user.option.searchRadioButtonOption{/lang}</label>
+<select id="{$option->optionName}" name="values[{$option->optionName}]"{if !$searchOption} disabled="disabled"{/if}>
+       {if !$allowEmptyValue|empty}<option value="">{lang}wcf.global.noSelection{/lang}</option>{/if}
+       {foreach from=$selectOptions key=key item=selectOption}
+               <option value="{$key}"{if $value == $key} selected="selected"{/if}>{lang}{@$selectOption}{/lang}</option>
+       {/foreach}
+</select>
+
+<script data-relocate="true">
+//<![CDATA[
+$(function() {
+       $('#search_{$option->optionName}').change(function(event) {
+               if ($(event.currentTarget).prop('checked')) {
+                       $('#{$option->optionName}').enable();
+               }
+               else {
+                       $('#{$option->optionName}').disable();
+               }
+       });
+});
+//]]>
+</script>
diff --git a/wcfsetup/install/files/acp/templates/textSearchableOptionType.tpl b/wcfsetup/install/files/acp/templates/textSearchableOptionType.tpl
new file mode 100644 (file)
index 0000000..945e836
--- /dev/null
@@ -0,0 +1,17 @@
+<label><input type="checkbox" id="search_{$option->optionName}" name="searchOptions[{$option->optionName}]"{if $searchOption} checked="checked"{/if} /> {lang}wcf.user.option.searchTextOption{/lang}</label>
+<input type="{@$inputType}" id="{$option->optionName}" name="values[{$option->optionName}]" value="{$value}"{if $inputClass} class="{@$inputClass}"{/if}{if !$searchOption} disabled="disabled"{/if} />
+
+<script data-relocate="true">
+//<![CDATA[
+$(function() {
+       $('#search_{$option->optionName}').change(function(event) {
+               if ($(event.currentTarget).prop('checked')) {
+                       $('#{$option->optionName}').enable();
+               }
+               else {
+                       $('#{$option->optionName}').disable();
+               }
+       });
+});
+//]]>
+</script>
index 4a367803514bd2332968af47421c8c2a9a909c30..7e9ebc6abb2b2b6f59bf9341eee28616a4b53f03 100644 (file)
@@ -172,6 +172,11 @@ class UserSearchForm extends UserOptionListForm {
                // dynamic fields
                $this->buildDynamicConditions();
                
+               // if no conditions exists, no need to send query
+               if (!count($this->conditions->getParameters())) {
+                       return;
+               }
+               
                // do search
                $statement = WCF::getDB()->prepareStatement($sql.$this->conditions, $this->maxResults);
                $statement->execute($this->conditions->getParameters());
index b751a59c0279de3398f6e96b486f3d7c929f4468..ebb982a1ee255a255a5bafdbfe793651e23f7818 100644 (file)
@@ -19,8 +19,7 @@ use wcf\util\DateUtil;
  */
 class BirthdayOptionType extends DateOptionType {
        /**
-        * input css class
-        * @var string
+        * @see \wcf\system\option\TextOptionType::$inputClass
         */
        protected $inputClass = 'birthday';
        
@@ -67,18 +66,28 @@ class BirthdayOptionType extends DateOptionType {
         * @see \wcf\system\option\ISearchableUserOption::getCondition()
         */
        public function getCondition(PreparedStatementConditionBuilder &$conditions, Option $option, $value) {
-               if (empty($value['ageFrom']) || empty($value['ageTo'])) return false;
+               if (empty($value['ageFrom']) && empty($value['ageTo'])) return false;
                
                $ageFrom = intval($value['ageFrom']);
                $ageTo = intval($value['ageTo']);
                if ($ageFrom < 0 || $ageFrom > 120) return false;
                if ($ageTo < 0 || $ageTo > 120) return false;
-               if (!$ageFrom || !$ageTo) return false;
                
                $dateFrom = DateUtil::getDateTimeByTimestamp(TIME_NOW)->sub(new \DateInterval('P'.($ageTo + 1).'Y'))->add(new \DateInterval('P1D'));
                $dateTo = DateUtil::getDateTimeByTimestamp(TIME_NOW)->sub(new \DateInterval('P'.$ageFrom.'Y'));
                
-               $conditions->add("option_value.userOption".User::getUserOptionID('birthdayShowYear')." = ? AND option_value.userOption".$option->optionID." BETWEEN DATE(?) AND DATE(?)", array(1, $dateFrom->format('Y-m-d'), $dateTo->format('Y-m-d')));
+               $conditions->add('option_value.userOption'.User::getUserOptionID('birthdayShowYear').' = ?', array(1));
+               
+               if ($ageFrom && $ageTo) {
+                       $conditions->add('option_value.userOption'.$option->optionID.' BETWEEN DATE(?) AND DATE(?)', array($dateFrom->format('Y-m-d'), $dateTo->format('Y-m-d')));
+               }
+               else if ($ageFrom) {
+                       $conditions->add('option_value.userOption'.$option->optionID.' BETWEEN DATE(?) AND DATE(?)', array('1893-01-01', $dateTo->format('Y-m-d')));
+               }
+               else {
+                       $conditions->add('option_value.userOption'.$option->optionID.' BETWEEN DATE(?) AND DATE(?)', array($dateFrom->format('Y-m-d'), DateUtil::getDateTimeByTimestamp(TIME_NOW)->add(new \DateInterval('P1D'))->format('Y-m-d')));
+               }
+               
                return true;
        }
 }
index 5b265baeac181bf63642fbb0b6851427c66f3273..99d749c8ef6f7ceee12d8aa97b471399e28d299f 100644 (file)
@@ -29,6 +29,19 @@ class MultiSelectOptionType extends SelectOptionType {
                return WCF::getTPL()->fetch('multiSelectOptionType');
        }
        
+       /**
+        * @see \wcf\system\option\ISearchableUserOption::getSearchFormElement()
+        */
+       public function getSearchFormElement(Option $option, $value) {
+               WCF::getTPL()->assign(array(
+                       'option' => $option,
+                       'searchOption' => isset($_POST['searchOptions'][$option->optionName]),
+                       'selectOptions' => $option->parseSelectOptions(),
+                       'value' => (!is_array($value) ? explode("\n", $value) : $value)
+               ));
+               return WCF::getTPL()->fetch('multiSelectSearchableOptionType');
+       }
+       
        /**
         * @see \wcf\system\option\IOptionType::validate()
         */
@@ -54,9 +67,10 @@ class MultiSelectOptionType extends SelectOptionType {
         * @see \wcf\system\option\ISearchableUserOption::getCondition()
         */
        public function getCondition(PreparedStatementConditionBuilder &$conditions, Option $option, $value) {
+               if (!isset($_POST['searchOptions'][$option->optionName])) return false;
+               
                if (!is_array($value) || empty($value)) return false;
                $value = ArrayUtil::trim($value);
-               if (empty($value)) return false;
                
                $conditions->add("option_value.userOption".$option->optionID." REGEXP '".'(^|\n)'.implode('\n([^\n]*\n)*', array_map('escapeString', $value)).'($|\n)'."'");
                return true;
index b8ed8254954334de29458ea0c074cfa2ec476f8c..149689b24854084ea6e91fec7b72795a4e4d2079 100644 (file)
@@ -69,6 +69,9 @@ class RadioButtonOptionType extends AbstractOptionType implements ISearchableUse
         * @see \wcf\system\option\ISearchableUserOption::getSearchFormElement()
         */
        public function getSearchFormElement(Option $option, $value) {
+               $this->templateName = 'radioButtonSearchableOptionType';
+               WCF::getTPL()->assign('searchOption', empty($_POST) || isset($_POST['searchOptions'][$option->optionName]));
+               
                return $this->getFormElement($option, $value);
        }
        
@@ -76,10 +79,9 @@ class RadioButtonOptionType extends AbstractOptionType implements ISearchableUse
         * @see \wcf\system\option\ISearchableUserOption::getCondition()
         */
        public function getCondition(PreparedStatementConditionBuilder &$conditions, Option $option, $value) {
-               $value = StringUtil::trim($value);
-               if (!$value) return false;
+               if (!isset($_POST['searchOptions'][$option->optionName])) return false;
                
-               $conditions->add("option_value.userOption".$option->optionID." = ?", array($value));
+               $conditions->add("option_value.userOption".$option->optionID." = ?", array(StringUtil::trim($value)));
                return true;
        }
 }
index 8b92366114b4312240738a562108d93e4730522f..4bc814079725d71976437f089791f85199d6ed4c 100644 (file)
@@ -20,7 +20,6 @@ class SelectOptionType extends RadioButtonOptionType {
         * @see \wcf\system\option\IOptionType::getFormElement()
         */
        public function getFormElement(Option $option, $value) {
-               // get options
                $options = $this->parseEnableOptions($option);
                
                WCF::getTPL()->assign(array(
@@ -38,8 +37,17 @@ class SelectOptionType extends RadioButtonOptionType {
         * @see \wcf\system\option\ISearchableUserOption::getSearchFormElement()
         */
        public function getSearchFormElement(Option $option, $value) {
-               $this->allowEmptyValue = true;
-               return $this->getFormElement($option, $value);
+               $options = $this->parseEnableOptions($option);
+               
+               WCF::getTPL()->assign(array(
+                       'disableOptions' => $options['disableOptions'],
+                       'enableOptions' => $options['enableOptions'],
+                       'option' => $option,
+                       'searchOption' => isset($_POST['searchOptions'][$option->optionName]),
+                       'selectOptions' => $option->parseSelectOptions(),
+                       'value' => $value
+               ));
+               return WCF::getTPL()->fetch('selectSearchableOptionType');
        }
        
        /**
index 1c6cd31bf6898afd7305167c6b9bf89773428abb..71f4d6988a9332368fae733a2cbe5ca15cedd1f0 100644 (file)
@@ -46,17 +46,30 @@ class TextOptionType extends AbstractOptionType implements ISearchableUserOption
         * @see \wcf\system\option\ISearchableUserOption::getSearchFormElement()
         */
        public function getSearchFormElement(Option $option, $value) {
-               return $this->getFormElement($option, $value);
+               WCF::getTPL()->assign(array(
+                       'option' => $option,
+                       'inputType' => $this->inputType,
+                       'inputClass' => $this->inputClass,
+                       'searchOption' => isset($_POST['searchOptions'][$option->optionName]),
+                       'value' => $value
+               ));
+               return WCF::getTPL()->fetch('textSearchableOptionType');
        }
        
        /**
         * @see \wcf\system\option\ISearchableUserOption::getCondition()
         */
        public function getCondition(PreparedStatementConditionBuilder &$conditions, Option $option, $value) {
+               if (!isset($_POST['searchOptions'][$option->optionName])) return false;
+               
                $value = StringUtil::trim($value);
-               if (empty($value)) return false;
+               if ($value == '') {
+                       $conditions->add("option_value.userOption".$option->optionID." = ?", array(''));
+               }
+               else {
+                       $conditions->add("option_value.userOption".$option->optionID." LIKE ?", array('%'.addcslashes($value, '_%').'%'));
+               }
                
-               $conditions->add("option_value.userOption".$option->optionID." LIKE ?", array('%'.addcslashes($value, '_%').'%'));
                return true;
        }
        
@@ -86,7 +99,7 @@ class TextOptionType extends AbstractOptionType implements ISearchableUserOption
         * 
         * @param       \wcf\data\option\Option         $option
         * @param       string                          $newValue
-        * @return                                      string
+        * @return      string
         */
        protected function getContent(Option $option, $newValue) {
                if ($option->contentpattern) {
index 3e3f1fb53dc7fdf502c9d1d4f8ce079e964746eb..977a5e05d35fcf17608ff002a64b8d7c064ee9f1 100644 (file)
@@ -2602,6 +2602,9 @@ Möchten Sie diese E-Mail-Benachrichtigung in Zukunft nicht mehr erhalten, könn
                <item name="wcf.user.option.googlePlus"><![CDATA[Google+]]></item>
                <item name="wcf.user.option.googlePlus.description"><![CDATA[21-stellige Google-Plus-ID]]></item>
                <item name="wcf.user.option.canWriteProfileComments"><![CDATA[Kann Pinnwand-Kommentare schreiben]]></item>
+               
+               <item name="wcf.user.option.searchRadioButtonOption"><![CDATA[Auswahl des Benutzers bei „{lang}wcf.user.option.{$option->optionName}{/lang}“:]]></item>
+               <item name="wcf.user.option.searchTextOption"><![CDATA[„{lang}wcf.user.option.{$option->optionName}{/lang}“ enthält:]]></item>
        </category>
        
        <category name="wcf.user.mail">
index c0d791bcc2568bb62c908e950c185db16643e21c..b79060a6086fcaf64e6bc3a22281217cced18144 100644 (file)
@@ -2575,6 +2575,9 @@ If you do not want to receive further email notifications for this event, you ca
                <item name="wcf.user.option.googlePlus"><![CDATA[Google+]]></item>
                <item name="wcf.user.option.googlePlus.description"><![CDATA[21 digits Google Plus user ID]]></item>
                <item name="wcf.user.option.canWriteProfileComments"><![CDATA[Can Write Comments on My Wall]]></item>
+               
+               <item name="wcf.user.option.searchRadioButtonOption"><![CDATA[User's selection for “{lang}wcf.user.option.{$option->optionName}{/lang}”:]]></item>
+               <item name="wcf.user.option.searchTextOption"><![CDATA[“{lang}wcf.user.option.{$option->optionName}{/lang}” contains:]]></item>
        </category>
        
        <category name="wcf.user.mail">