Location: PHPKode > projects > ZenTrack - project/bug tracking software > zentrack_2.6.4/includes/ZenSearch.php
<? /* -*- Mode: C; c-basic-indent: 3; indent-tabs-mode: nil -*- ex: set tabstop=3 expandtab: */ 
if( !ZT_DEFINED ) { die("Illegal Access"); }

/**
 * Contains the ZenSearch class
 */
 
/**
 * The ZenSearch class is used for creating generic search results from
 * any data type in the zt system.  
 *
 * The table to be searched must contain a corresponding field map and view map
 *
 * The view map must contain an entry which designates the source table.
 */
class ZenSearch {
  
  /**
   * Construct a new search object.
   *
   * @param string $view the view map entry to examine
   * @param ZenFieldMap $map instance of the ZenFieldMap class
   */
  function ZenSearch() {
    $this->_order = false;
    $this->_andor = 'AND';
    $this->_comps = array();
    $this->_total = 0;
  }
  
  /**
   * Initialize the ZenSearch from a field map
   */
  function initFromMap($view, &$map) {
    Zen::addDebug('ZenSearch::initFromMap', $view, 3);
    $this->_table = $map->getViewProp($view, 'table');
    $this->_fields = $map->listFieldsForView($view);
  }
  
  /**
   * Initialize from generic input data
   */
  function init($table, $fields) {
    Zen::addDebug('ZenSearch::init', "$table(".join(',',$fields).")", 3);
    $this->_table = $table;
    $this->_fields = $fields;
  }
  
  /**
   * Set the comparison method to be 'OR' instead of 'AND', meaning that
   * any field which matches returns a result instead of requiring all
   * fields to match to produce a result.
   */
   function matchAnyField() {
     Zen::addDebug('ZenSearch::matchAnyField', 'set to on', 3);
     $this->_andor = 'OR';
   }
  
  /**
   * Set the sort order for our method.  The text passed here is directly
   * inserted into the sql query without checks or validation.
   *
   * @param string $order the order by clause for sql query
   */
  function sortby( $order ) {
    Zen::addDebug('ZenSearch::sortby', $order, 3);
    $this->_order = $order;
  }
  
  /**
   * Set the comparators to use with each field.
   *
   * The associative should contain (string)field -> (string)comparator, 
   * where comparator represents one of the following:
   * <pre>
   *   eq     value matches text exactly
   *   co     value contains text
   *   bw     value begins with text
   *   ew     value ends with text
   *   gt     value is greater than integer value of text
   *   ge     value is greater than or equal to integer value of text
   *   lt     value is less than integer value of text
   *   le     value is less than or equal to integer value of text
   *   ne     value is not equal to text
   *   nc     value does not contain text
   *   bt     value is "between" two date timestamps (see search() method)
   * </ul>
   *
   * A special case occurs when the comparator for a field is set to "between"
   * and an array with exactly two elements is provided for the value.  In 
   * this special case, the values are assumed to be (int)timestamps which represent
   * a minimum (inclusive) and maximum (exclusive) date value that the field should
   * fall between.
   *
   * If the comparators are not provided, or a particular field does not appear
   * in the list, then the comparator defaults to 'co' for text fields and 'eq'
   * for all others.
   *
   * @param array $comps see description above
   */
  function setComparators( $comps ) {
    $this->_comps = $comps;
  }
  
  /**
   * Perform a search using values from the array provided.  The array is
   * an associative array of (string)field -> (mixed)value.
   *
   * The value can be an array of (string)choices or a (string)value.
   *
   * A special case occurs when the comparator for a field is set to "bt" (between)
   * and an array with exactly two elements is provided for the value.  In 
   * this special case, the values are assumed to be (int)timestamps which represent
   * a minimum (inclusive) and maximum (exclusive) date value that the field should
   * fall between.
   *
   * @see ZenSearch::setComparators()
   * @see ZenSearch::matchAnyField()
   * 
   * @param array $vals a key/value pair of (string)field -> (mixed)value to be matched (see above)
   * @param string $order ORDER BY clause
   * @return array containing rows of key/value pairs representing the columns of the table and their values
   */
   function search( $vals, $limit = 0, $offset = 0 ) {
     global $zen;
     // generate search params
     $parms = array();
     foreach($this->_fields as $f) {
       // skip missing fields
       if( !array_key_exists($f, $vals) ) { continue; }
       
       // set up our value
       $v = $vals[$f];
       
       // create a comparator
       $c = $this->_comp($f);
       if( is_array($v) && count($v) == 2 && $c == 'bt' ) {
         // this special case occurs when using a 'between' comparator
         // with two dates.  We create a sub-select to add into the query
         // instead of the normal process
         $parms[] = array('AND', array( array($f, '>=', $v[0]), array($f, '<', $v[0]) ));
       }
       else {
         if( is_array($v) ) {
           $c = ($c == '!=')? '!IN' : 'IN';
         }
         if( $c == 'contains' ) { $v = str_replace('*', '%', $v); } 
         // generate our parameter
         $parms[] = array($f, $c, $v);
       }
     }
     
     // set the table, we may need a join
     $table = $this->_table;
     
     // always observe bin access rights
     $login_id = ZenSessionManager::getSession('login_id');
     $validBins = $zen->getUsersBins($login_id, 'level_view');
     if($this->_table == $zen->table_tickets || $this->_table == $zen->table_bins) {
       $bins = $this->_inClause('bin_id', $validBins);
       $this->_mergeParmSets( $bins, 'OR', $parms );
     }
     else if( $table == $zen->table_users ) {
       $vusers = $this->_findValidUsers($validBins);
       $users = $this->_inClause('user_id', $vusers);
       $this->_mergeParmSets( $users, 'OR', $parms );
     }
     
     if( count($parms) ) {
       $where = ' WHERE '.$zen->recursiveSearchClause($parms, $this->_andor);
     }
     
     // create sql query
     $query = "SELECT * FROM $table $where ORDER BY ".$this->_order();
     
     // get results and return
     if( $limit ) {
       $res = $zen->db_getLimitedIndex($query, $limit, $offset);
       $this->_total = count($res);
       if( $this->_total >= $limit || $offset + $limit > $this->_total ) {
         $this->_total = $zen->db_get("SELECT COUNT(*) FROM ".$this->_table." $where");
       }
     }
     else {
       $res = $zen->db_queryIndexed($query);
       $this->_total = count($res);
     }
     Zen::addDebug('ZenSearch::search', "[".count($res)."]$query", 3);
     return $res;
   }
   
   /**
    * Find the valid users for this set of bins; not the most effecient, but
    * it is reliable.
    */
   function _findValidUsers($validBins) {
     global $zen;
     $vusers = $zen->get_users($validBins, 'level_user');
     $ids = array();
     foreach($vusers as $v) {
       $ids[] = $v['user_id'];
     }
     return $ids;
   }
   
   /**
    * Given a new set of parms and an old one, join them with an AND clause
    * if the second set of parms is empty, then just return the first set,
    * joined with the clause provided.
    */
   function _mergeParmSets($newparms, $new_andor, &$parms) {
     if( !empty($parms) ) {
       $bv = array($new_andor, $newparms);
       $parms = array( $bv, array($this->_andor, $parms) );
       $this->_andor = 'AND';
     }
     else {
       $parms = $newparms;
       $this->_andor = $new_andor;
     }
   }
   
   /**
    * Enter values into an IN clause, if more than 100 values exist, then
    * split the values by 100s and create seperate in clauses for sql server
    * and db2 compatability
    */
   function _inClause( $field, $vals ) {
     if( count($vals) < 100 ) {
       return array( array($field, 'IN', $vals) );
     }
     $newvals = array();
     $i=0;
     $j = 100;
     while( $i < count($vals) ) {
       // observe upper limit (may not be exactly 100 records)
       if( $i+$j >= count($vals) ) { $j = count($vals)-$i; }
       $newvals[] = array($field, 'IN', array_slice($vals, $i, $j));
       $i = $i+$j;
     }
     return $newvals;
   }
   
   /**
    * If $limit was used in search() function, this will return the total
    * number of records that matched the query, if $limit was not used, 
    * this will always be the same as the number of rows in the query
    */
   function getTotal() { return $this->_total; }
   
   /**
    * Create a default ordering scheme for the current table
    */
   function _order() {
     if( $this->_order ) { return $this->_order; }
     switch(str_replace('ZENTRACK_', '', $this->_table)) {
       case 'USERS':
         return 'lname, fname';
       case 'TICKETS':
         return 'priority, otime desc';
       default:
         return 'priority DESC, name';
     }
   }
   
   /**
    * Generate a comparator for a given field based on the values in the
    * comparator given or a default value ('co' for strings and 'eq' for others)
    */
   function _comp( $field ) {
     $comparator = array_key_exists($field, $this->_comps)? $this->_comps[$field] : false;
     if( !$comparator ) {
       // create default comparator
       $fp = getFmFieldProps($this->_view, $field);
       if( $fp['data_type'] == 'string' || $fp['data_type'] == 'text' ) {
         $comparator = 'co';
       }
       else {
         $comparator = 'eq';
       }
     }
     switch($comparator) {
       case "eq": return "=";
       case "co": return "contains";
       case "bw": return "b";
       case "ew": return "e";
       case "gt": return ">";
       case "ge": return "<";
       case "lt": return "<=";
       case "le": return ">=";
       case "ne": return "!=";
       case "nc": return "";
       case "bt": return "bt";
       default: return "eq";
     }
   }
   
   var $_map;
   var $_view;
   var $_fields;
   var $_table;
   var $_order;
   var $_comps;
   var $_andor;
   var $_total;

}

?>
Return current item: ZenTrack - project/bug tracking software