<?

RegEnum
("RefBookQuerySortType", "Тип сортировки по полю",
        
"rqstUnsorted",         "Сортировка по полю не осуществляется",
        
"rqstAscending",        "Сортировка по возрастанию",
        
"rqstDescending",       "Сортировка по убыванию"
       
);
       
RegEnum("RefBookQueryCriterionOperator", "Тип оператора критерия отбора информации справочников",
        
"qcoNone",         "Не указан",
        
"qcoEqual",        "Равно",
        
"qcoNotEqual",     "Не равно",
        
"qcoMore",         "Больше",
        
"qcoMoreEqual",    "Больше или равно",
        
"qcoLess",         "Меньше",
        
"qcoLessEqual",    "Меньше или равно",
        
"qcoBetween",      "Между",
        
"qcoIn",           "Входит в массив",
        
"qcoNotIn",        "Не входит в массив",
        
"qcoLike",         "Совпадает с",
        
"qcoNotLike",      "Не совпадает с",
        
"qcoBeginFrom",    "Начинается с символа",
        
"qcoNotBeginFrom", "Начинается не с символа"
       
);

/**
@desc Элемент фильтра отбора информации справочников
@prop Name    Имя переменной фильтра
@prop Value   Значения соответствующей переменной
@link RefBookQuery
*/
class RefBookQueryFilterItem extends Record {
  public
$Name;
  public
$Value;
  
  
/**
  * @return integer
  * @param integer $Field
  * @desc Определение типа свойства класса
  */
  
static function GetType($Field){
    switch(
$Field){
      case
ID_NAME:           return FT_STRING;
      case
ID_VALUE:          return FT_STRING;
      default:                return
parent::GetType($Field);
    }
  }
}
       
/**
@desc Элемент критерия отбора информации справочников
@prop Operator Оператор критерия
@prop Value1   Значения критерия
@prop Value2   Значения критерия
@link RefBookQuery
@link RefBookQueryCriterion
*/
class RefBookQueryCriterionItem extends Object {
  public
$Operator;
  public
$Value1 = "";
  public
$Value2 = "";

  
/**
  * @server
  * @return void
  * @desc Конструктор
  */
  
function __construct(){
    
parent::__construct();
  }

  
/**
  * @return integer
  * @param integer $Field
  * @desc Определение типа свойства класса
  */
  
static function GetType($Field){
    switch(
$Field){
      case
ID_OPERATOR:       return ID_REFBOOKQUERYCRITERIONOPERATOR;
      case
ID_VALUE1:         return FT_STRING;
      case
ID_VALUE2:         return FT_STRING;
      default:                return
parent::GetType($Field);
    }
  }
}

/**
@desc Критерий выбора информации справочника
*/
class RefBookQueryCriterion extends Object {
  public
$Item1;
  public
$Item2;
  public
$Item3;
  public
$Item4;
  
  
/**
  * @server
  * @return void
  * @desc Конструктор
  */
  
function __construct(){
    
parent::__construct();
    
$this->Item1 = new RefBookQueryCriterionItem;
    
$this->Item2 = new RefBookQueryCriterionItem;
    
$this->Item3 = new RefBookQueryCriterionItem;
    
$this->Item4 = new RefBookQueryCriterionItem;
  }

  
/**
  * @return integer
  * @param integer $Field
  * @desc Определение типа свойства класса
  */
  
static function GetType($Field){
    switch(
$Field){
      case
ID_ITEM1:   return ID_REFBOOKQUERYCRITERIONITEM;
      case
ID_ITEM2:   return ID_REFBOOKQUERYCRITERIONITEM;
      case
ID_ITEM3:   return ID_REFBOOKQUERYCRITERIONITEM;
      case
ID_ITEM4:   return ID_REFBOOKQUERYCRITERIONITEM;
      default:         return
parent::GetType($Field);
    }
  }
}

/**
@desc Поле, участвующее в запросе на получение данных из справочника
@prop GUID      Идентификатор поля
@prop Output    Включить данное поле в результат
@prop SortType  Тип сортировки
@prop SortOrder Порядок сортировки
@prop Criteria  Условия
@link RefBookQuery
*/
class RefBookQueryField extends Object {
  public
$GUID;
  public
$Name = "";
  public
$Output = false;
  public
$SortType;
  public
$SortOrder;
  public
$Criterion;
  public
$ColIndex = -1;

  
/**
  * @server
  * @return void
  * @desc Конструктор
  */
  
function __construct(){
    
parent::__construct();
    
$this->Criterion = new RefBookQueryCriterion;
  }

  
/**
  * @return integer
  * @param integer $Field
  * @desc Определение типа свойства класса
  */
  
static function GetType($Field){
    switch(
$Field){
      case
ID_GUID:           return FT_STRING;
      case
ID_NAME:           return FT_STRING;
      case
ID_OUTPUT:         return FT_BOOLEAN;
      case
ID_SORTTYPE:       return ID_REFBOOKQUERYSORTTYPE;
      case
ID_SORTORDER:      return FT_INTEGER;
      case
ID_CRITERION:      return ID_REFBOOKQUERYCRITERION;
      case
ID_COLINDEX:       return FT_INTEGER;
      default:                return
parent::GetType($Field);
    }
  }
}

/**
@desc Объектное представление запроса на получение данных из справочника
@prop Table    Идентификатор таблицы, из которой осуществляется выборка данных
@prop RefField Идентификатор поля, по которому соединяется данная таблица
@prop Fields   Список полей, используемых в запросе
@prop Joins    Список связанных таблиц
@prop Alias    Имя таблицы в запросе. Используется при конвертации в запрос баз данных.

@link RefBook::RefQueryToQuery
*/
class RefBookQuery extends Object {
  public
$Table = "";
  public
$RefField = "";
  public
$Fields;
  public
$Joins;
  public
$Alias = "";

  
/**
  * @server
  * @return void
  * @desc Конструктор
  */
  
function __construct(){
    
parent::__construct();
    
$this->Fields = array();
    
$this->Joins = array();
    
$this->Table = EMPTY_GUID_STR;
    
$this->RefField = EMPTY_GUID_STR;
  }

  
/**
  * @return integer
  * @param integer $Field
  * @desc Определение типа свойства класса
  */
  
static function GetType($Field){
    switch(
$Field){
      case
ID_TABLE:          return FT_STRING;
      case
ID_REFFIELD:       return FT_STRING;
      case
ID_FIELDS:         return FT_ARRAY | ID_REFBOOKQUERYFIELD;
      case
ID_JOINS:          return FT_ARRAY | ID_REFBOOKQUERY;
      case
ID_ALIAS:          return FT_STRING;
      default:                return
parent::GetType($Field);
    }
  }
  
  
/**
  * @server
  * @return string[]
  * @desc Получение массива идентификаторов используемых полей
  * @link RefBookQuery::ConvertToDBQuery
  
  Получение массива идентификаторов полей справочника, используемых
  для постоения запроса, включая ссылочные поля и поля подзапросов.
  */
  
function GetUsedFields(){
    if(
$this->RefField != EMPTY_GUID_STR){
      
$ret = array($this->RefField);
    } else {
      
$ret = array();
    }
    for(
$i = 0; $i < sizeof($this->Fields); $i++){
      
$ret[] = $this->Fields[$i]->GUID;
    }
    for(
$i = 0; $i < sizeof($this->Joins); $i++){
      
$ret = array_merge($ret, $this->Joins[$i]->GetUsedFields());
    }
    return
$ret;
  }
  
  
/**
  * @server
  * @return string[]
  * @param RefBookField[] $Fields
  * @desc Получение порядка сортировки запроса
  */
  
function GetSortOrder($Fields){
    
$ret = array();
    for(
$i = 0; $i < sizeof($this->Fields); $i++){
      if(
$this->Fields[$i]->SortType != rqstUnsorted && array_key_exists($this->Fields[$i]->GUID, $Fields)){
        
$ret[$this->Fields[$i]->SortOrder-1]["name"] = Stick($this->Alias, DBParseWord($Fields[$this->Fields[$i]->GUID]->Name));
        
$ret[$this->Fields[$i]->SortOrder-1]["desc"] = ($this->Fields[$i]->SortType == rqstDescending);
      }
    }
    for(
$i = 0; $i < sizeof($this->Joins); $i++){
      
$ret = $ret + ($this->Joins[$i]->GetSortOrder($Fields));
    }
    return
$ret;
  }
  
  
/**
  * @server
  * @return boolean
  * @param DBWhere $Where
  * @param RefBookField[] $Fields
  * @param RefBookQueryFilterItem[] $Filter
  * @desc Формирование блока условий в SQL запросе
  */
  
function BuildConditions(DBWhere $Where, $Fields, $Filter){
    
// Настраиваем условия выбора
    
$Where->BeginGroup();
    
$HasCondition = false;
    for(
$i = 1; $i <= 4; $i++){
      if(
$this->DoBuildConditions($Where, $Fields, $i, false, $Filter)){
        
$Where->EndGroup();
        
$HasCondition = true;
      }
    }
    if(!
$HasCondition){
      
// Добавляем безобидное условие,
      // для того, чтобы не возникло ошибки в
      // случае отсутствия других условий
      
$Where->ANDItem(Stick($this->Alias, col_ID), wclNotIsNull);
    }
    
$Where->EndGroup();
    if(
is_array($Filter)){
      for(
$i = 0; $i < sizeof($Filter); $i++){
        if(
$Filter[$i]->Name == "ID" && $Filter[$i]->Value != ""){
          
$Where->ANDItem(Stick($this->Alias, col_ID), wclIn, $Filter[$i]->Value, true);
          break;
        }
      }
    }
  }
  
  
/**
  * @server
  * @return boolean
  * @param DBWhere $Where
  * @param RefBookField[] $Fields
  * @param integer $ItemIdx
  * @param boolean $Opened
  * @param RefBookQueryFilterItem[] $Filter
  * @desc Формирование блока условий в SQL запросе
  */
  
function DoBuildConditions(DBWhere $Where, $Fields, $ItemIdx, $Opened, $Filter){
    
$ItemName = sprintf('Item%s', $ItemIdx);
    
    for(
$i = 0; $i < sizeof($this->Fields); $i++){
      
$Item = $this->Fields[$i]->Criterion->$ItemName;
      if(
array_key_exists($this->Fields[$i]->GUID, $Fields)){
        if(
$Item->Operator != qcoNone){
          if(!
$Opened){
            
$Where->BeginGroup(true);
            
$Opened = true;
          }
          
$RefField = $Fields[$this->Fields[$i]->GUID];
          
$v = array();
          
$v[0] = $Item->Value1;
          
$v[1] = $Item->Value2;
          for(
$j = 0; $j < 2; $j++){
            
$str = $v[$j];
            if(
strlen($str) >= 4 && $str{0} == "@"){
              
$tag = substr($str, 1, 2);
              
$val = substr($str, 4);
              
              switch(
$tag){
                case
"DC": $v[$j] = RefBook::CalculateDate(intval($val)); break;
              }
            }
            
$v[$j] = $this->ParseScript($v[$j], $Filter);
          }
          
          
$FieldName = Stick($this->Alias, $RefField->Name);
          
          switch(
$Item->Operator){
            default:           
            case
qcoEqual:     $Clause = wclEqual; break;
            case
qcoNotEqual:  $Clause = wclNotEqual; break;
            case
qcoMore:      $Clause = wclMore; break;
            case
qcoMoreEqual: $Clause = wclMoreEqual; break;
            case
qcoLess:      $Clause = wclLess; break;
            case
qcoLessEqual: $Clause = wclLessEqual; break;
            case
qcoBetween:   $Clause = wclMoreEqual; break;
            case
qcoIn:        $Clause = wclIn; break;
            case
qcoNotIn:     $Clause = wclNotIn; break;
            case
qcoLike:      
            case
qcoNotLike:   
              if(
$Item->Operator == qcoLike){
                
$Clause = wclLike;
              } else {
                
$Clause = wclNotLike;
              }
              
$arr = explode(' ', $v[0]);
              
$arr1 = array();
              for(
$cc = 0; $cc < sizeof($arr); $cc++){
                
$arr[$cc] = trim($arr[$cc]);
                if(
$arr[$cc]!=""){
                  
$arr1[] = "'".EncodeString(str_replace('*', '%', $arr[$cc]))."%'";
                }
              }
              if(
sizeof($arr1) > 0){
                if(
sizeof($arr1) > 1){
                  
$v[0] = $arr1;
                } else {
                  
$v[0] = $arr1[0];
                }               
              } else {
                
$v[0] = "'%'";
              }
              break;
              
            case
qcoBeginFrom:
            case
qcoNotBeginFrom:
              if(
$Item->Operator == qcoBeginFrom){
                
$Clause = wclIn;
              } else {
                
$Clause = wclNotIn;
              }
              
$FieldName = sprintf("ASCII(LEFT(%s, 1))", $RefField->Name);
              
$chars = $v[0];
              
$chArr = array();
              for(
$cc = 0; $cc < strlen($chars); $cc++){
                
$char =  strtolower(trim($chars{$cc}));
                if(
strlen($char) > 0){
                  if(!
in_array(ord($char), $chArr))
                    
$chArr[] = ord($char);
                  
$char = strtoupper($char);
                  if(!
in_array(ord($char), $chArr))
                    
$chArr[] = ord($char);
                }
              }
              
$v[0] = implode(', ', $chArr)." /*".$chars."*/";
              break;
          }
          
          if(
$Item->Operator != qcoBetween && is_array($v[0])){
            
$Where->BeginGroup();
            for(
$k = 0; $k < sizeof($v[0]); $k++){
              
$Where->ORItem($FieldName, $Clause, $v[0][$k], true);
            }
            
$Where->EndGroup();
          } else
            
$Where->ANDItem($FieldName, $Clause, $v[0], true);
          if(
$Item->Operator == qcoBetween){
            
$Where->ANDItem($FieldName, wclLessEqual, $v[1], true);
          }
        }
      }
    }

    for(
$i = 0; $i < sizeof($this->Joins); $i++){
      
$Opened = $this->Joins[$i]->DoBuildConditions($Where, $Fields, $ItemIdx, $Opened, $Filter);
    }
    return
$Opened;
  }
  
  
/**
  * @server
  * @return DBSelect
  * @param DBDriver $Sock
  * @param RBTTypes $Attrs
  * @param boolean $OnlyCalcFields
  * @param boolean $ExcludeConditions
  * @param RefBookQueryFilterItem[] $Filter
  * @desc Преобразование в запрос к базе данных
  */
  
function ConvertToDBQuery(DBDriver $Sock, $Attrs = null, $OnlyCalcFields = false, $ExcludeConditions = false, $Filter = null){
    if(!
IsSubclassOf($Attrs, 'Set')){
      
$Attrs = new RBTTypes();
      
$Attrs->V = RefBook::GetTableAttributes($this->Table);
    }
    
// Получаем идентификаторы всех используемых
    // данным запросом полей.
    
$UsedFieldsIDs = $this->GetUsedFields();
    
// Получаем информацию об используемых полях
    
$UsedFields = RefBook::GetFieldsByIDs($UsedFieldsIDs);
    
// Создаем запрос
    
$Query = $Sock->Query(qtSelect, sprintf("%s %s", RefBook::TableName($this->Table), $this->Alias));
    
$this->BuildDBQuery($Query, $UsedFields, "", $OnlyCalcFields);
    if(!
$ExcludeConditions){
      
// Настраиваем условия отбора
      
$this->BuildConditions($Query->Where, $UsedFields, $Filter);
    }
    
// Настриаваем сортировку
    
$Order = $this->GetSortOrder($UsedFields);
    
ksort($Order);
    for(
$i = 0; $i < sizeof($Order); $i++){
      
$Query->OrderBy($Order[$i]["name"], $Order[$i]["desc"]);
    }
    
// Проверяем наличие в запросе идентификаторов
    
$FieldNames = array();
    for(
$i = 0; $i < sizeof($this->Fields); $i++){
      if(
array_key_exists($this->Fields[$i]->GUID, $UsedFields) && $this->Fields[$i]->Output)
        
$FieldNames[] = strtolower($UsedFields[$this->Fields[$i]->GUID]->Name);
    }
    if(!
in_array("id", $FieldNames)){
      
$Query->AddEx($this->Alias, col_ID, col_ID);
    }
    
    if(
$Attrs->Contains(rtHierarchic)){
      
$Query->AddEx($this->Alias, col_Parent, col_Parent);
    } else {
      
$Query->Add("0", col_Parent);
    }
    if(
$Attrs->Contains(rtDetail)){
      
$Query->AddEx($this->Alias, col_Owner, col_Owner);
    } else {
      
$Query->Add("0", col_Owner);
    }
    return
$Query;
  }

  
/**
  * @server
  * @return void
  * @param DBSelect $Query
  * @param RefBookField[] $Fields
  * @param string $Path
  * @param boolean $OnlyCalcFields
  * @desc Наполнение запросе информацией
  * @link RefBookQuery::ConvertToDBQuery
  */
  
function BuildDBQuery(DBSelect $Query, $Fields, $Path = "", $OnlyCalcFields = false){
    
// Добавляем в запрос поля,
    // которые должны вернуться
    
for($i = 0; $i < sizeof($this->Fields); $i++){
      if(
$this->Fields[$i]->Output){
        if(
array_key_exists($this->Fields[$i]->GUID, $Fields)){
          
$Field = $Fields[$this->Fields[$i]->GUID];
          
$n = Stick($Path, $Field->Name);
          
$this->Fields[$i]->Name = $n;
          if(
$Field->Kind == fkCalculated){
            
$script = $this->ParseScript($Field->SQLScript);
            
$Query->Add(sprintf("(%s)", trim($script)), $n);
          } elseif(!
$OnlyCalcFields) {
            if(
$Field->DataType == fdtGUID){
              
$Query->AddEx($this->Alias, $Field->Name, $n, ftGUID);
            } else {
              
$Query->AddEx($this->Alias, $Field->Name, $n);
            }
          }
        }
      }
    }
    
// Подключаем другие таблицы запроса
    
for($i = 0; $i < sizeof($this->Joins); $i++){
      
$RefField = $Fields[$this->Joins[$i]->RefField];
      
$JoinTable = RefBook::TableName($this->Joins[$i]->Table);
      
$JoinAlias = $this->Joins[$i]->Alias;
      if(
$RefField->Kind == fkReference)
        
$Query->InnerJoin($JoinTable, $JoinAlias, $this->Alias, $RefField->Name, wclEqual, col_ID);
      else
        
$Query->LeftJoin($JoinTable, $JoinAlias, $this->Alias, $RefField->Name, wclEqual, col_ID);
      
$this->Joins[$i]->BuildDBQuery($Query, $Fields, Stick($Path, $RefField->Name), $OnlyCalcFields);
    }
  }
  
  
/**
  * @return string
  * @param string $Script
  * @param RefBookQueryFilterItem[] $Filter
  * @desc Обработка формулы
  */
  
private function ParseScript($Script, $Filter = null){
    
$Script = str_replace("@SELF", $this->Alias, $Script);
    if(
is_array($Filter)){
      for(
$i = 0; $i < sizeof($Filter); $i++){
        
$Script = str_replace("@".$Filter[$i]->Name, $Filter[$i]->Value, $Script);
      }
    }
    return
$Script;
  }
  
  
/**
  * @server
  * @return integer
  * @param string $Name
  * @desc Определение индекса колонки списка указанного поля
  */
  
function GetColIndex($Name){
    
// Ищем указанную колонку в своем списке
    
for($i = 0; $i < sizeof($this->Fields); $i++){
      if(
strtolower($Name) == strtolower($this->Fields[$i]->Name)){
        return
intval($this->Fields[$i]->ColIndex);        
      }
    }
    
// Если пришли сюда, значит колонку не нашли.
    // Будем искать в связанных таблицах
    
for($i = 0; $i < sizeof($this->Joins); $i++){
      
$idx = $this->Joins[$i]->GetColIndex($Name);
      if(
$idx > -1)
        return
$idx;
    }
    return -
1;
  }
}

/**
@desc Путь к элементу обозревателя справочника
@link DesignerRefBookBrowserElement
*/
class RefBookQueryPath extends Object {
  public
$Tables;
  public
$Fields;
  
  
/**
  * @server
  * @return void
  * @desc Конструктор
  */
  
function __construct(){
    
parent::__construct();
    
$this->Tables = array();
    
$this->Fields = array();
  }

  
/**
  * @return integer
  * @param integer $Field
  * @desc Определение типа свойства класса
  */
  
static function GetType($Field){
    switch(
$Field){
      case
ID_TABLES:         return FT_ARRAY | FT_STRING;
      case
ID_FIELDS:         return FT_ARRAY | FT_STRING;
      default:                return
parent::GetType($Field);
    }
  }
}
?>