Advanced search improvements (restore 2018-04-10 revisions : r5635..r5641)

* Add 'search_manual_submit' config parameter to manage auto-submit
* bugfixes
** date i18n is now handled (using two new options `datepicker.dateFormat` and `datepicker.timeFormat` computed from `AttributeDateTime::GetFormat()->ToDatePicker()`
** handling of `empty` `not empty` operator titles
*** it led to tohers bugfixes and a redesign of the `_computeTitle` (from overwriting to extension using ie `_computeBetweenDaysOperatorTitle`
*** some bug still remain, because autocomplete needs to been finished before checking on them
* Promote 'friendlyname' in the 'most popular' list
* bugfixes
** filters (criterions, enum and FK without autocomplete) now ignore accent on matching with user input
** this is done by using a pre-existing tool used only by the portal, so it was moved to the core (latinize.min.js)
* Integration with manual submit parameter on client side.
* bugfixes : history/breadcrumb had several c[menu] appended (one for each refresh)
* Fix various sanity bugs

SVN:trunk[5632]
This commit is contained in:
Pierre Goiffon
2018-04-12 08:54:05 +00:00
parent 757130847f
commit efa7a4ee55
20 changed files with 259 additions and 253 deletions

View File

@@ -30,6 +30,8 @@ use AttributeEnum;
use Combodo\iTop\Application\Search\AjaxSearchException;
use Combodo\iTop\Application\Search\CriterionConversionAbstract;
use Combodo\iTop\Application\Search\SearchForm;
use Exception;
use MetaModel;
class CriterionToOQL extends CriterionConversionAbstract
{
@@ -59,6 +61,7 @@ class CriterionToOQL extends CriterionConversionAbstract
$aMappedOperators = array(
self::OP_CONTAINS => 'ContainsToOql',
self::OP_EQUALS => 'EqualsToOql',
self::OP_STARTS_WITH => 'StartsWithToOql',
self::OP_ENDS_WITH => 'EndsWithToOql',
self::OP_EMPTY => 'EmptyToOql',
@@ -110,6 +113,8 @@ class CriterionToOQL extends CriterionConversionAbstract
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
if (empty($sValue)) return "1";
return "({$sRef} LIKE '%{$sValue}%')";
}
@@ -118,6 +123,8 @@ class CriterionToOQL extends CriterionConversionAbstract
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
if (empty($sValue)) return "1";
return "({$sRef} LIKE '{$sValue}%')";
}
@@ -126,9 +133,21 @@ class CriterionToOQL extends CriterionConversionAbstract
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
if (empty($sValue)) return "1";
return "({$sRef} LIKE '%{$sValue}')";
}
protected static function EqualsToOql($sRef, $aCriteria)
{
$aValues = self::GetValues($aCriteria);
$sValue = self::GetValue($aValues, 0);
if (empty($sValue)) return "1";
return "({$sRef} = '{$sValue}')";
}
protected static function EmptyToOql($sRef, $aCriteria)
{
if (isset($aCriteria['widget']))
@@ -164,7 +183,7 @@ class CriterionToOQL extends CriterionConversionAbstract
$bFilterOnUndefined = false;
try
{
$aAttributeDefs = \MetaModel::ListAttributeDefs($sClass);
$aAttributeDefs = MetaModel::ListAttributeDefs($sClass);
if (array_key_exists($sAttCode, $aAttributeDefs))
{
$oAttDef = $aAttributeDefs[$sAttCode];
@@ -272,17 +291,29 @@ class CriterionToOQL extends CriterionConversionAbstract
$sStartDate = $aValues[0]['value'];
if (!empty($sStartDate))
{
$oDate = $oFormat->parse($sStartDate);
$sStartDate = $oDate->format($sAttributeClass::GetSQLFormat());
$aOQL[] = "({$sRef} >= '$sStartDate')";
try
{
$oDate = $oFormat->parse($sStartDate);
$sStartDate = $oDate->format($sAttributeClass::GetSQLFormat());
$aOQL[] = "({$sRef} >= '$sStartDate')";
}
catch (Exception $e)
{
}
}
$sEndDate = $aValues[1]['value'];
if (!empty($sEndDate))
{
$oDate = $oFormat->parse($sEndDate);
$sEndDate = $oDate->format($sAttributeClass::GetSQLFormat());
$aOQL[] = "({$sRef} <= '$sEndDate')";
try
{
$oDate = $oFormat->parse($sEndDate);
$sEndDate = $oDate->format($sAttributeClass::GetSQLFormat());
$aOQL[] = "({$sRef} <= '$sEndDate')";
}
catch (Exception $e)
{
}
}
$sOQL = implode(' AND ', $aOQL);

View File

@@ -197,6 +197,8 @@ class CriterionToSearchForm extends CriterionConversionAbstract
if (($a['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW) &&
($b['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW))
{
if (!isset($a['label'])) return -1;
if (!isset($b['label'])) return 1;
return strcmp($a['label'], $b['label']);
}
if ($a['widget'] === AttributeDefinition::SEARCH_WIDGET_TYPE_RAW)
@@ -207,6 +209,8 @@ class CriterionToSearchForm extends CriterionConversionAbstract
return 1;
}
if (!isset($a['label'])) return -1;
if (!isset($b['label'])) return 1;
return strcmp($a['label'], $b['label']);
});
@@ -564,7 +568,7 @@ class CriterionToSearchForm extends CriterionConversionAbstract
$aCriteria['values'] = array();
}
// Convention for 'undefined' enums
$aCriteria['values'][] = array('value' => 'null', 'label' => 'null');
$aCriteria['values'][] = array('value' => 'null', 'label' => Dict::S('Enum:Undefined'));
}
break;
default:

View File

@@ -27,6 +27,7 @@ abstract class CriterionConversionAbstract
{
const OP_CONTAINS = 'contains';
const OP_EQUALS = '=';
const OP_STARTS_WITH = 'starts_with';
const OP_ENDS_WITH = 'ends_with';
const OP_EMPTY = 'empty';

View File

@@ -90,12 +90,17 @@ class CriterionParser
$aExpression = array();
foreach($aAnd as $aCriteria)
{
$aExpression[] = CriterionToOQL::Convert($aCriteria);
$sExpression = CriterionToOQL::Convert($aCriteria);
if ($sExpression !== '1')
{
$aExpression[] = $sExpression;
}
}
if (empty($aExpression))
{
return '';
return '1';
}
return '('.implode(" AND ", $aExpression).')';

View File

@@ -131,8 +131,39 @@ class SearchForm
{
$sClassesCombo = MetaModel::GetName($sClassName);
}
$bAutoSubmit = true;
$mSubmitParam = utils::GetConfig()->Get('search_manual_submit');
if (is_array($mSubmitParam))
{
// List of classes
if (isset($mSubmitParam[$sClassName]))
{
$bAutoSubmit = !$mSubmitParam[$sClassName];
}
else
{
// Serach for child classes
foreach($mSubmitParam as $sConfigClass => $bFlag)
{
$aChildClasses = MetaModel::EnumChildClasses($sConfigClass);
if (in_array($sClassName, $aChildClasses))
{
$bAutoSubmit = !$bFlag;
break;
}
}
}
}
else if ($mSubmitParam !== false)
{
$bAutoSubmit = false;
}
$sAction = (isset($aExtraParams['action'])) ? $aExtraParams['action'] : utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
$sStyle = ($bOpen == 'true') ? '' : 'closed';
$sStyle .= ($bAutoSubmit === true) ? '' : ' no_auto_submit';
$sHtml .= "<form id=\"fs_{$sSearchFormId}\" action=\"{$sAction}\" class=\"{$sStyle}\">\n"; // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php)
$sHtml .= "<h2 class=\"sf_title\"><span class=\"sft_long\">" . Dict::Format('UI:SearchFor_Class_Objects', $sClassesCombo) . "</span><span class=\"sft_short\">" . Dict::S('UI:SearchToggle') . "</span>";
$sHtml .= "<a class=\"sft_toggler fa fa-caret-down pull-right\" href=\"#\" title=\"" . Dict::S('UI:Search:Toggle') . "\"></a>";
@@ -168,7 +199,10 @@ class SearchForm
}
$oBaseSearch = $oSearch->DeepClone();
$oBaseSearch->ResetCondition();
if (method_exists($oSearch, 'GetCriteria'))
{
$oBaseSearch->ResetCondition();
}
$sBaseOQL = str_replace(' WHERE 1', '', $oBaseSearch->ToOQL());
if (isset($aExtraParams['table_inner_id']))
@@ -195,13 +229,25 @@ class SearchForm
$aMonthsShort = array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'),
Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short'));
// $sDateTimeFormat = \AttributeDateTime::GetFormat()->ToMomentJS('%s');
// $iDateTimeSeparatorPos = strpos($sDateTimeFormat, ' ');
// $sDateFormat = substr($sDateTimeFormat, 0, $iDateTimeSeparatorPos);
// $sTimeFormat = substr($sDateTimeFormat, $iDateTimeSeparatorPos + 1);
$sDateTimeFormat = \AttributeDateTime::GetFormat()->ToDatePicker();
$iDateTimeSeparatorPos = strpos($sDateTimeFormat, ' ');
$sDateFormat = substr($sDateTimeFormat, 0, $iDateTimeSeparatorPos);
$sTimeFormat = substr($sDateTimeFormat, $iDateTimeSeparatorPos + 1);
$aSearchParams = array(
'criterion_outer_selector' => "#fs_{$sSearchFormId}_criterion_outer",
'result_list_outer_selector' => "#{$aExtraParams['result_list_outer_selector']}",
'data_config_list_selector' => "#{$sDataConfigListSelector}",
'endpoint' => utils::GetAbsoluteUrlAppRoot().'pages/ajax.searchform.php',
'init_opened' => $bOpen,
'auto_submit' => false, // TODO: Change this so it takes the configuration parameter value for the current class.
'auto_submit' => $bAutoSubmit,
'list_params' => $aListParams,
'search' => array(
'has_hidden_criteria' => (array_key_exists('hidden_criteria', $aListParams) && !empty($aListParams['hidden_criteria'])),
@@ -217,6 +263,9 @@ class SearchForm
'dayNamesMin' => $aDaysMin,
'monthNamesShort' => $aMonthsShort,
'firstDay' => (int) Dict::S('Calendar-FirstDayOfWeek'),
// 'format' => \AttributeDateTime::GetFormat()->ToDatePicker()
'dateFormat' => $sDateFormat,
'timeFormat' => $sTimeFormat,
),
),
);
@@ -286,6 +335,7 @@ class SearchForm
{
$aAttributeDefs = MetaModel::ListAttributeDefs($sClass);
$aList = MetaModel::GetZListItems($sClass, 'standard_search');
$bHasFriendlyname = false;
foreach($aList as $sAttCode)
{
if (array_key_exists($sAttCode, $aAttributeDefs))
@@ -294,6 +344,18 @@ class SearchForm
$aZList = $this->AppendField($sClass, $sAlias, $sAttCode, $oAttDef, $aZList);
unset($aAttributeDefs[$sAttCode]);
}
if ($sAttCode == 'friendlyname')
{
$bHasFriendlyname = true;
}
}
if (!$bHasFriendlyname)
{
// Add friendlyname to the most popular
$sAttCode = 'friendlyname';
$oAttDef = $aAttributeDefs[$sAttCode];
$aZList = $this->AppendField($sClass, $sAlias, $sAttCode, $oAttDef, $aZList);
unset($aAttributeDefs[$sAttCode]);
}
$aZList = $this->AppendId($sClass, $sAlias, $aZList);
uasort($aZList, function ($aItem1, $aItem2) {
@@ -373,40 +435,44 @@ class SearchForm
*/
public function GetCriterion($oSearch, $aFields, $aArgs = array(), $bIsRemovable = true)
{
$oExpression = $oSearch->GetCriteria();
if (!empty($aArgs))
{
$aArgs = MetaModel::PrepareQueryArguments($aArgs);
$sOQL = $oExpression->Render($aArgs);
$oExpression = Expression::FromOQL($sOQL);
}
$aOrCriterion = array();
$aORExpressions = Expression::Split($oExpression, 'OR');
$bIsEmptyExpression = true;
foreach($aORExpressions as $oORSubExpr)
{
$aAndCriterion = array();
$aAndExpressions = Expression::Split($oORSubExpr, 'AND');
foreach($aAndExpressions as $oAndSubExpr)
{
if (($oAndSubExpr instanceof TrueExpression) || ($oAndSubExpr->Render() == 1))
{
continue;
}
$aAndCriterion[] = $oAndSubExpr->GetCriterion($oSearch);
$bIsEmptyExpression = false;
}
$aAndCriterion = CriterionToSearchForm::Convert($aAndCriterion, $aFields, $oSearch->GetJoinedClasses(), $bIsRemovable);
$aOrCriterion[] = array('and' => $aAndCriterion);
}
if ($bIsEmptyExpression)
if (method_exists($oSearch, 'GetCriteria'))
{
// Add default criterion
$aOrCriterion = $this->GetDefaultCriterion($oSearch);
$oExpression = $oSearch->GetCriteria();
if (!empty($aArgs))
{
$aArgs = MetaModel::PrepareQueryArguments($aArgs);
$sOQL = $oExpression->Render($aArgs);
$oExpression = Expression::FromOQL($sOQL);
}
$aORExpressions = Expression::Split($oExpression, 'OR');
$bIsEmptyExpression = true;
foreach($aORExpressions as $oORSubExpr)
{
$aAndCriterion = array();
$aAndExpressions = Expression::Split($oORSubExpr, 'AND');
foreach($aAndExpressions as $oAndSubExpr)
{
if (($oAndSubExpr instanceof TrueExpression) || ($oAndSubExpr->Render() == 1))
{
continue;
}
$aAndCriterion[] = $oAndSubExpr->GetCriterion($oSearch);
$bIsEmptyExpression = false;
}
$aAndCriterion = CriterionToSearchForm::Convert($aAndCriterion, $aFields, $oSearch->GetJoinedClasses(), $bIsRemovable);
$aOrCriterion[] = array('and' => $aAndCriterion);
}
if ($bIsEmptyExpression)
{
// Add default criterion
$aOrCriterion = $this->GetDefaultCriterion($oSearch);
}
}
return array('or' => $aOrCriterion);