Location: PHPKode > projects > ZenTrack - project/bug tracking software > zentrack_2.6.4/includes/ZenHttpApi.php
<?

/**
 * Reads parms from _GET or _POST and generates transactions, based on
 * the action specified by caller.
 */
class ZenHttpApi {
  
  /**
   * A basic factory pattern which abstracts the implementation details
   * and allows easy updates in the future, such as expanding this to
   * use different classes for xml and json
   *
   * This method automatically calls authenticate()!!
   *
   * @param string $format either 'xml' or 'json' for now
   * @return ZenHttpApi instance
   */
  function getInstance($format) {
    global $zen;
    global $map;
    $api = new ZenHttpApi($format, $zen, $map);
    $api->authenticate(false);
    return $api;
  }
  
  /**
   * Constructor
   * @param string $format
   * @param zenTrack $zen
   * @param ZenFieldMap $map
   */
  function ZenHttpApi($format, &$zen, &$map) {
    $this->format = $format;
    $this->authed = false;
    $this->zen = $zen;
    $this->map = $map;
    $this->actions = array();
    // parse class and determine implemented action names
    foreach( get_class_methods($this) as $m ) {
      if( preg_match("@^_api_([\w_]+)$@", $m, $matches) ) {
        $this->actions[] = $matches[1];
      }
    }
  }
  
  /**
   * Shortcut to call an action. The action must be one of the api calls
   * described at http://dev.zentrack.net/devwiki/index.php?title=ZT_API
   * @param string $action see description
   * @return mixed
   */
  function doAction($action) {
    // replace 'help' commands with the 'test' command
    if( $action == 'help' ) { $action = 'test'; }
    
    // check for actions requiring a post op and return a meaningful message
    // if the client tries a get instead
    if( empty($_POST) && !in_array($action, $this->actions_gettable) ) {
      return $this->_errTrxn("{$action} must be called via POST");
    }
    
    // check for actions requiring authentication (most of them) and make sure we're authenticated
    if( !$this->authed && !in_array($action, $this->actions_noauth) ) {
      return $this->_errTrxn("Invalid authentication");
    }
    
    // check to make sure authenticated user is allowed to perform action provided
    if( ($e = $this->_checkAccess($action)) !== true ) {
      return $e;
    }
    
    // strip invalid chars and avoid XSS attacks
    $action = preg_replace('/\W/', '', $action);
    $m = "_api_{$action}";
    if( !$action ) {
      return $this->_errTrxn("Must declare an action");
    }
    else if( method_exists($this, $m) ) {
      return $this->$m();
    }
    else {
      return $this->_errTrxn("Invalid action: $action");
    }
  }
  
  /**
   * Authenticate user login. If not successful,
   * returns a transaction that can be sent to client informing them of the error
   *
   * <p>Calling this method multiple times does not hurt, nor does it authenticate
   * more than once.
   *
   * @param boolean $trxn if called internally, no need to build trxn, so setting this to false skips it
   * @return mixed (string)trxn if $trxn = true or (boolean)true if authenticated
   */
  function authenticate($trxn) {
    if( !$this->user ) {
      
      // look up user and perform authtication, but only if it hasn't already
      // been tried
      $u = $this->getParm('user', true);
      $p = $this->getParm('encpass');
      if( !$p ) { $p = md5($this->getParm('passphrase', true)); }
      
      if( $u ) {
        $this->user = $this->zen->get_user_by_login($u);
        $this->user['ip_address'] = $_SERVER['REMOTE_ADDR'];
        $this->user['homebinname'] = $this->zen->getBinName($this->user['homebin']);
        $this->uid = $this->user['user_id'];
        $this->ubins = $this->zen->getUsersBins($this->uid);

        //don't send this back to client ever!
        $encp = $this->user['passphrase'];
        unset($this->user['passphrase']); 
        if( $this->user && strlen($p) && $p == $encp) {
          $this->authed = true;
        }
      }
    }
    
    if( $trxn ) {
      // this is an actual API call to authenticate, so return results
      // never reveal which one is invalid! never return user object without a login first!
      return $this->_generateTransaction(
        ($this->authed ? "Authenticated" : "Invalid username or password"),
        $this->authed,
        $this->authed? $this->user : null);
    }
    else { 
      // this is just an internal auth, so no results needed
      return $this->authed;
    }
  }
  
  function _api_authenticate() {
    return $this->authenticate(true);
  }
  
  /**
   * Approve a ticket
   *
   * @return string formatted transaction string
   */
  function _api_approve() {
    //todo: make this work with contacts
    $comments = $this->getParm('text');
    $id = $this->getNumParm('id', true);
    $res = $this->zen->approve_ticket($id, $this->uid, $comments);
    return $this->_generateTransaction("Approve Ticket",($res? true : false),false,$id);
  }
  
  /**
   * Close a ticket
   *
   * @return string formatted transaction string
   */
  function _api_close() {
    //todo: make this work with contacts
    $comments = $this->getParm('text');
    $hours = $this->getNumParm('hours');
    $id = $this->getNumParm('id', true);
    $res = $this->zen->close_ticket($id, $this->uid, $hours, $comments);
    return $this->_generateTransaction("Close Ticket",($res? true : false),false,$id);
  }

  /**
   * Returns configuration settings as described in the API; a list of
   * commands that can be called and the ZT version number, etc.
   *
   * @return string formatted transaction string
   */
  function _api_config() {
    $vals = array();
    $vals = $this->_getDataTypes();
    $vals['version'] = $this->zen->getSetting("version_xx");
    $vals['actions'] = $this->actions;
    $vals['ticket_fields'] = $this->_getApiFields();
    return $this->_generateTransaction("System Config", true, $vals);
  }

  /**
   * Create a new ticket
   *
   * @return string formatted transaction string
   */
  function _api_create() {
    //check user is allowed to do this
    $bin_id = $this->getIdParm('bin', 'bins', true);
    if( !$bin_id ) { return $this->_errTrxn("No valid bin provided"); }
    else if( !$this->zen->checkAccess($this->uid, $bin_id, 'create') ) {
      return $this->_errTrxn("User not allowed to create tickets in this bin");
    }
    
    $errs = array();
    $custom = array();
    $contacts = array();
    $vals = array(
        'creator_id' => $this->uid,
        'bin_id'     => $bin_id,
        'otime'      => time() );
    $api_fields = $this->_getApiFields();
    $ticket_fields = $this->_getApiFields('ticket_create');
    foreach($ticket_fields as $f=>$fld) {
      // skip vals we created above, if they contain data
      //todo: make contacts and notify work here
      if( !empty($vals[$f]) || $f == 'notify' || $f == 'contacts' ) { continue; }

      // we check to make sure all the required create fields are in the api,
      // otherwise there's a problem we don't know how to resolve (user must
      // fill in a field but isn't allowed to view it)
      if( !array_key_exists($f, $api_fields) ) {
        if( $fld['required'] ) {
          Zen::addDebug("ZenHttpApi::create", "A required field for ticket create($f) is not in the api_view field map", 1);
        }
        continue;
      }
      
      if( $f != 'project_id' && strpos($f, '_id') === strlen($f)-3 ) {
        $type = substr($f, 0, -3)."s";
        $val = $this->getIdParm($f, $type, $fld['required']);
      }
      else if( $f == 'priority' ) {
        $val = $this->getIdParm($f, 'priorities', $fld['required']);
      }
      else {
        $val = $this->getParm($f, $fld['required']);
      }
      
      if( !$val && $fld['required'] ) {
        $errs[] = "{$fld['label']} is required";
      }
      else if( ZenFieldMap::isVariableField($f) ) {
        $custom[$f] = $val;
      }
      else {
        $vals[$f] = $val;
      }
    }
    
    /*
    //todo: make these come from field map
    //todo: can cheat and use global $map!
    $vals["title"] = $this->getParm("title");
    $vals["bin_id"]      = $this->getParm("bin");
    $vals["description"] = $this->getParm("description");
    $vals["creator_id"]  = $this->getParm("creator_id");
    $vals["priority"]  = 1;
    $vals["type_id"]   = 2;
    $vals["system_id"] = 1;
    */
    
    $id = false;
    if( empty($errs) ) {
      $errs = $this->zen->add_new_ticket($id, 
        array('standard'=>$vals, 'varfield'=>$custom, 'contacts'=>$contacts), 
        "Created from IP {$_SERVER['REMOTE_ADDR']}");
    }
    
    if( !empty($errs) ) {
      return $this->_generateTransaction(
        "Create Ticket Failed",
        false,
        $errs,
        $id,
        'error');
    }
    
    return $this->_generateTransaction(
      ($id? "Created ticket #{$id}" : "Failed to create ticket"),
      ($id? true : false),
      ($id? $this->zen->get_ticket($id) : null),
      $id);
  }
  
  /**
   * Return a list of valid users.  Only returns user data for users
   * the logged in account can see; all others should be shown as anonymous
   *
   * <p>A special filter is applied by using the api_view field map's 
   * user_filter setting. That can be one of several things:
   * <ul>
   *   <li>empty - no filter applied
   *   <li>string - anything with 'notes' field exactly matching string
   *   <li>/string/ - a regular expression to match
   * </ul>
   *
   * <p>Additionally, users with the same home bin can be included by checking
   * the special "user_filter_homebin" setting in api_view field map.
   */
  function _api_list_users() {
    $vars = $this->zen->get_users($this->ubins);
    
    $filter = $this->map->getViewProp('api_view', 'user_filter');
    $use_homebin = $this->map->getViewProp('api_view', 'user_filter_homebin');
    
    // if the filter is on, check our user list and strip results that don't match
    if( $filter ) {
      // regexp filters are specified by placing the string inside /.../ marks
      $regexp = preg_match("@^/.*/$@", $filter);
      $users = array();
      foreach($vars as $u) {
        // if use_homebin is specified, and the user's bin matches logged in user,
        // we ignore the filter
        if( $this->user['homebin'] == $u['homebin'] ||
            (!$regexp && $u['notes'] == $filter) || 
            ($regexp && preg_match($filter, $u['notes'])) ) {
          $users[] = $u;
        }
      }
    }
    // if the filter is off, just return the whole list
    else { $users = $vars; }
    
    return $this->_generateTransaction(
      "List Users",
      true,
      $users,
      null,
      'user',
      count($users)
      );
  }
  
  /**
   * Add a log to ticket
   *
   * @return string formatted transaction string
   */
  function _api_log() {
    $id = $this->getNumParm('id', true);
    $text = $this->getParm('text', true);
    $hours = $this->getFloatParm('hours');
    $task = $this->getAlphanumParm('task');
    $parms = array(
      'user_id' => $this->uid,
      'action'  => $task? $task : "LOG",
      'entry'   => $text,
      'hours'   => $hours);
    $lid = $this->zen->add_log($id, $parms);
    return $this->_generateTransaction(
      "New Log Entry", 
      ($lid? true : false),
      array("log_id" => $lid),
      $id);
  }
  
  /**
   * Add a recipient to notify list
   *
   * @return string formatted transaction string
   */
  function _api_notify_add() {
    //todo: implement
    //todo: avoid duplicates!
    return $this->_notReadyTrxn('notify_add');
  }
  
  /**
   * Remove a notify recipient
   *
   * @return string formatted transaction string
   */
  function _api_notify_delete() {
    //todo: implement
    return $this->_notReadyTrxn('notify_delete');
  }
  
  /**
   * Return a list of recent log entries. The default number returned is 25
   * thought it can be overridden by passing limit as an arg.
   *
   * @return string formatted transaction string
   */
  function _api_recent_logs() {
    // restrict logs to bins user can see
    $parms = array( "bin_id" => array("bin_id", "IN", $this->ubins) );
    
    $syslogs = $this->getNumParm('syslogs');
    if( !syslogs ) {
      $parms['user_id'] = array('user_id', '>', 0);
    }

    // determine if we're paging
    $page = $this->getNumParm('page');
    if( !$page ) { $page = 0; }

    // the default limit is 25, unless one is passed as an arg
    $limit = $this->getNumParm('limit');
    if( !$limit ) { $limit = 25; }
    
    // a client can override the default behavior and make it an or
    $or = $this->getNumParm('or');

    // get the logs
    $logs = $this->zen->search_logs($parms, ($or? 'OR' : 'AND'), $page, $limit);
    
    return $this->_generateTransaction(
        'Recent Logs',
        is_array($logs),
        $logs,
        false,
        'log',
        $this->zen->total_records
      );
  }
  
  /**
   * Search for matching tickets
   *
   * @return string formatted transaction string
   */
  function _api_search() {
    $parms = array();
    foreach($this->_getApiFields() as $f=>$fld) {
      $v = $this->getParm($f, $fld['required']);
      if( $v ) {
        $parms[$f] = $this->_searchParm($f, $v);
      }
    }
    
    $sort = $this->getParm('sortby');
    if( $sort ) { $sort = preg_replace('@[^\w_,]@', '', $sort); }
    else { $sort = 'status DESC, ctime DESC'; }
    
    $limit = $this->getNumParm('limit');
    
    $tickets = $this->zen->search_tickets($parms, 'OR', 0, $sort, $limit);

    // The client will need the entire user, not just the id
    // so we'll look up each ticket's user and add them to the results
    foreach($tickets as $tid=>$t) {
      if( !empty($t['user_id']) ) {
        $tickets[$tid]['user'] = $this->zen->getUser($t['user_id'], $this->usercols);
      }
      // we don't want to store the user_id twice
      unset($tickets[$tid]['user_id']);
    }

    return $this->_generateTransaction(
        'Search Results',
        true,
        $tickets,
        false,
        'ticket',
        $this->zen->total_records
      );
  }
  
  /**
   * Return a summary about opened tickets and so on
   *
   * @return string formatted transaction string
   */
  function _api_summary() {
    //todo: implement (maybe remove?)
    return $this->_notReadyTrxn('summary');
  }
  
  /**
   * Just return a generic result for testing
   */
  function _api_test() {
    return $this->_generateTransaction(
        "Test successful",
        true,
        array_merge($_GET, $_POST),
        false,
        'arg'
      );
  }
  
  /**
   * Return a list of translation strings
   *
   * @return string formatted transaction string
   */
  function _api_translations() {
    //todo: implement (just return all translations for now)
    return $this->_notReadyTrxn('translations');
  }
  
  /**
   * View a ticket
   *
   * @return string formatted transaction string
   */
  function _api_view() {
    $id = $this->getNumParm('id', true);
    $ticket = $this->zen->get_ticket($id);
    if( $this->getParm('logs') ) {
      $ticket['logs'] = $this->zen->get_logs($id);
    }
    return $this->_generateTransaction(
        ($ticket? "Ticket results" : "Couldn't find ticket"),
        ($ticket? true : false),
        $ticket,
        $id
      );
  }
  
  /**
   * View a log for ticket
   *
   * @return string formatted transaction
   */
  function _api_view_log() {
    $id = $this->getNumParm('id', true);
    $logs = $this->zen->get_logs($id);
    return $this->_generateTransaction(
      ($logs? "Logs for ticket #{$id}" : "No logs found for ticket #{$id}"),
      ($logs? true : false),
      $logs,
      $id,
      'log');
  }
  
  /**
   * View a ticket
   *
   * @return string formatted transaction string
   */
  function _api_view_list() {
    //todo: this should check for contact user and use the 'contact' parm if it exists
    //todo: which requires an entirely different lookup strategy
    
    $parms["user_id"] = $this->uid;
    $bin = $this->getIdParm('bin', 'bins');
    if( $bin ) { $parms['bin_id'] = $bin; }
    $result = $this->zen->get_tickets($parms);
    return $this->_generateTransaction(
        ($tickets? "Ticket results" : "No tickets found"),
        ($tickets? true : false),
        $result,
        false,
        'ticket',
        $this->zen->total_records
      );
  }
  
  /**
   * generate the formatted results suitable for returning to client
   * @param string $message
   * @param boolean $success
   * @param array $results indexed array of values to put in the results node
   * @param int $id the id of ticket if provided
   * @param string $defaultResultsNode a name for the results nodes in xml, see {@link self::_genXmlNodeName()}
   * @return string either json or xml format
   */
  function _generateTransaction($message, $success, $results = false, $id = false, 
                                $defaultResultsNode = false, $rows = false) {
    if( $this->format == 'xml' ) {
      return $this->_generateXmlResult($message, $success, $results, $rows, $id, $defaultResultsNode);
    }
    else {
      return $this->_generateJsonResult($message, $success, $results, $rows, $id);
    }
  }
  
  function _generateXmlResult($message, $success, $results = false, $rows = false, 
                              $id = false, $defaultResultsNode = false) {
    $txt = "<?xml version=\"1.0\"?>\n<zenapi_results>\n";
    $message = $this->_escapeParm($message);
    $txt .= "  <message>$message</message>\n";
    $vals = array("success" => $success, "results" => $results);
    if( $rows !== false ) { $vals['total_rows'] = $rows; }
    $d = $this->getNumParm('debug');
    if( $d ) {
      $messages = $this->zen->debugMessages;
      if( !empty($messages) ) {
        $vals['debug_messages'] = array();
        foreach($messages as $m) {
          if( $m[2] <= $d ) {
            $msg = is_array($m[1])? join(",", $m[1]) : $m[1];
            $vals['debug_messages'][] = array('method'=>$m[0], 'severity'=>$m[2], 'message'=>$msg);
          }
        }
      }
    }
    if( $id ) {  $vals['id'] = $id; }
    $txt .= $this->_genXmlParms( $vals, $defaultResultsNode );
    $txt .= "</zenapi_results>\n";
    return $txt;
  }
  
  /**
   * generate formatted parameters for use in xml transaction
   * @param array $parms indexed array of key/value pairs, the key becomes the xml node
   * @param string $default a name for the results nodes in xml, see {@link self::_genXmlNodeName()}
   * @return string
   */
  function _genXmlParms($parms, $default = false, $parent = false, $indent = 2) {
    $txt = '';
    foreach($parms as $key=>$val) {
      // if a prefix is provided, prefer it to a numeric node name
      list($nodeOpen, $nodeClose) = $this->_genXmlNodeName($key, $default, $parent);
      $txt .= str_pad('', $indent, ' ', STR_PAD_LEFT);
      if( is_array($val) ) {
        if( count($val) ) {
          $txt .= "<{$nodeOpen}>\n";
          $txt .= $this->_genXmlParms($val, $default, $key, $indent+2);
          $txt .= str_pad('', $indent, ' ', STR_PAD_LEFT)."</{$nodeClose}>\n";
        }
        else {
          $txt .= "<{$nodeOpen} />\n";
        }
      }
      else {
        $v = $this->_escapeParm($val);
        if( is_null($v) ) {
          $txt .= "<{$nodeOpen} />\n";
        }
        else {
          $txt .= "<{$nodeOpen}>$v</{$nodeClose}>\n";
        }
      }
    }
    return $txt;
  }
  
  /**
   * Generate an xml node name.
   *
   * <p>For a non-numeric key, returns the key (it's a suitable node name), unless
   * the node name is 'results', in which case it tries to formulate a suitable
   * name.
   *
   * <p>This uses the nodename provided, or it
   * tries to generate a meaningful node name using the parent node's name
   * or it will simply return the numeric key if necessary.
   *
   * <p>For the 'results' node, the children can be given a meaningful name
   * using the $default variable.
   *
   * <p>For data types (priorities, tasks, bins, etc) the ids are automagically
   * preserved as the node names.
   *
   * @param mixed $key (string)name of this node or (int)array index
   * @param string $parentNode name of the parent node (false for root nodes)
   * @param string $default a default node name to use (see description)
   * @return string
   */
  function _genXmlNodeName($key, $default = false, $parentNode = false) {
    if( !is_numeric($key) ) {
      // if the key isn't a number, it's already a good node name
      return array($key, $key);
    }

    if( $parentNode == 'results' && $default ) {
      // the nodes inside 'results' can be speficially set to a meaningful name
      // by passing the value as the parent here
      return array($default, $default);
    }
    
    // for data types, the node name is an id, so don't replace it
    $datatype = $parentNode && in_array($parentNode, array('priorities', 'bins', 'tasks', 'types', 'systems'));

    if( $parentNode == 'priorities' ) {
      // can't strip the 's' to make this singular, so manually specify it
      $n = 'priority';
    }
    else if( !is_numeric($parentNode) && preg_match('@s$@', $parentNode) ) {
      // we try to derive a meaningful node name from the parent tag if it's plural
      $n = substr($parentNode,0,-1);
    }
    else {
      $n = 'value';
    }
    
    return $datatype? array("$n id=\"$key\"", $n) : array($n, $n);
  }
  
  /**
   * Escape a string of text for use in xml, convert to CDATA tags as needed
   * @param string $txt
   * @return string
   */
  function _escapeParm($txt) {
    if( is_bool($txt) ) {
      return $txt? 1 : null;
    }
    else if( !strlen($txt) ) {
      return null;
    }
    else if( preg_match("/^[\w\d_.,\$ -]+$/", $txt) ) {
      return $txt;
    }
    else {
      $txt = str_replace("]]>", "&#93;&#93;>", $txt);
      return "<![CDATA[$txt]]>";
    }
  }
   
  
  function _generateJsonResult($message, $success, $results = false, $rows = false, $id = false) {
    $vals = array(
            "message" => $message,
            "success" => $success,
            "results" => $results,
          );
    if( $rows !== false ) { $vals['total_rows'] = $rows; }
    if( $id ) { $vals['id'] = $id; }
    return json_encode($vals);
  }
  
  /**
   * Return a data type id from a parameter value, which might be an (int)id,
   * or a (string)match to find
   *
   * @param string $type any value from {@link self::_getDataTypes}, 'user', or 'contact'
   * @see getParm()
   */
  function getIdParm($key, $type, $required = false, $get = false) {
    // get the parameter
    $val = $this->getParm($key, $required, $get);
    
    // check the obvious first
    if( !$val ) { return false; }
    else if( is_numeric($val) ) { return $val; }
    
    // we do case insensitive matching
    $val = strtolower($val);
    
    if( $type == 'user' ) {
      //todo: make this search users by name, initials, and email
      return false;
    }
    else if( $type == 'contact' ) {
      //todo: make this search contacts by name and email
      return false;
    }
    else {
      // we get the data type and look for matches
      $types = $this->_getDataTypes();
      $possibles = array();
      foreach($types[$type] as $k=>$v) {
        $v = strtolower($v);
        if( $v == $val ) {
          // if there is an exact match, return it
          return $k;
        }
        else if( strpos($v, $val) !== false ) {
          // if there is a close match, store it
          $possibles[] = $k;
        }
      }
      // if we got exactly one match, it's still good!
      if( count($possibles) == 1 ) { return $possibles[0]; }
    }
    return false;
  }
  
  /**
   * Return an alphanumeric parameter value
   * @see getParm()
   */
  function getNumParm( $key, $required = false, $get = false ) {
    return $this->zen->checkNum($this->getParm($key, $required, $get));
  }

  /**
   * Return an alphanumeric parameter value
   * @see getParm()
   */
  function getAlphanumParm( $key, $required = false, $get = false ) {
    return $this->zen->checkAlphaNum($this->getParm($key, $required, $get));
  }
  
  /**
   * Return a decimal value
   * @see getParm()
   */
  function getFloatParm($key, $required = false, $get = false) {
    return $this->zen->checkNum($this->getParm($key, $required, $get), true);
  }
  
  /**
   * Get parameter from request scope. Returns null if not found
   *
   * @param string $key
   * @param boolean $get if true, searchs _POST and then _GET, otherwise, only searches _POST 
   * @return mixed value from query or null if not found
   */
  function getParm( $key, $required = false, $get = false ) {
    $val = array_key_exists($key, $_POST)? $_POST[$key] : null;
    if( is_null($val) && $get && array_key_exists($key, $_GET) ) {
      $val = $_GET[$key];
    }
    
    // record args in debug info; great for troubleshooting
    // if the value is required and null, it's an error, otherwise
    // if it's null, it's a warning; otherwise, it's just a debug msg
    $msg = is_null($val)? "$key was not passed by caller" : "$key=$val";
    $lvl = is_null($val)? ($required? 1 : 2) : 3;
    $this->zen->addDebug("ZenHttpApi::getParm", $msg, $lvl);
      
    // return the result
    return $val;
  }
  
  /**
   * Return an error message as a transaction
   * @param string $message
   * @return string
   */
  function _errTrxn($message) {
    return $this->_generateTransaction($message, false);
  }
  
  /**
   * Just a transition method to present a 'not ready' message until all
   * api methods are implemented
   */
  function _notReadyTrxn($method) {
    $method = $this->zen->checkAlphaNum($method);
    return $this->_errTrxn("$method is not implemented yet");
  }
  
  /**
   * Check that user is allowed to perform action provided
   * @param string $action
   * @return mixed true if allowed or a (string)transaction if not allowed
   */
  function _checkAccess($action) {
    // for access, viewing a log is the same as viewing the ticket
    if( $action == 'view_log' ) { $action = 'view'; }
    
    // we only check ticket actions here
    if( !array_key_exists($action, $this->zen->getActions()) ) { return true; }
    
    // we only need concern ourselves with checking the bin/id if they are set
    // if they aren't set, the user can't retrieve info for something not allowed
    // so we don't concern ourselves
    $id = $this->getNumParm('id');
    if( $id ) {
      if( !$this->zen->actionApplicable($id, $action, $this->uid) ) {
        return $this->_errTrxn("Action not allowed");
      }
    }
    else if( !empty($_POST['bin']) ) {
      //todo: setting a ticket id bypasses this check -- need a fix
      // we check to see if anything is in the bin parm at all
      // now we'll make sure it's a valid bin value
      $bin_id = $this->getIdParm('bin', 'bins', true);
      if( !$bin_id ) { return $this->_errTrxn("Not a valid Bin name or ID"); }
      else {
        $lvl = in_array($action, $this->actions_viewlevel)? 'level_view' : 'level_user';
        if( !$this->zen->checkAccess($this->uid, $bin_id, $lvl) ) {
          return $this->_errTrxn("User doesn't have sufficient access to bin {$bin_id}");
        }
      }
    }
    
    return true;
  }
  
  /**
   * Retrieves data types and caches them for future use
   * @return array (string)type => array( (int)id=>(string)name, ... )
   */
  function _getDataTypes() {
    if( empty($this->dataTypes) ) {
      if( $this->user ) {
        $bins = array();
        $allbins = $this->zen->getBins();
        $ubins = $this->zen->getUsersBins($this->uid);
        foreach($allbins as $k=>$v) {
          if( in_array($k, $ubins) ) { $bins[$k] = $v; }
        }
      }
      else {
        $bins = $this->zen->getBins();
      }
      $this->dataTypes['bins'] = $bins;
      $this->dataTypes['types'] = $this->zen->getTypes();
      $this->dataTypes['priorities'] = $this->zen->getPriorities();
      $this->dataTypes['tasks'] = $this->zen->getTasks();
      $this->dataTypes['systems'] = $this->zen->getSystems();
    }
    return $this->dataTypes;
  }
  
  
  
  /**
   * Retrieve ticket fields which are valid and visible for use by client
   */
  function _getApiFields($view = 'api_view') {
    if( empty($this->fields) ) {
      $this->fields = array();
      foreach($this->map->listFieldsForView($view) as $f) {
        $fld = $this->map->getFieldFromMap($view, $f);
        if( !$fld['is_visible'] ) { continue; }
        $this->fields[$f] = array( 
          'name'     => $fld['field_name'],
          'label'    => $fld['field_label'],
          'required' => $fld['is_required'],
          'type'     => $fld['field_type'] );
      }
    }
    return $this->fields;
  }
  
  function _searchParm($f, $v) {
    if( strpos($v, "\t") !== false ) {
      $op = 'IN';
      $v = explode("\t", $v);
    } 
    else if( preg_match('@^([!]?[=~]|[<>]=?)(.*)@', $v, $matches) ) {
      list($term,$op,$v) = $matches;
    }
    else { $op = '~'; }
    
    if( $op == '!~' ) {
      $op = 'NOT LIKE';
      $v = "%{$v}%";
    }
    else if( $op == '~' ) {
      $op = 'LIKE';
      $v = "%{$v}%";
    }
    
    return array($f, $op, $v);
  }
  
  var $dataTypes = array();
  var $actions;
  var $actions_noauth = array('authenticate', 'translations', 'test');
  var $actions_gettable = array('test', 'translations');
  var $actions_viewlevel = array('notify_add', 'notify_drop', 'search', 'view', 'view_list', 'view_log');
  var $usercols = array('email', 'fname', 'lname', 'initials', 'homebin');
  var $fields = array();
    
  var $authed; //true if authentication has been done successfully
  var $user; // the user who authenticated
  var $uid;
  var $ubins; //bins authenticated user can view
  var $zen; //zenTrack instance
  var $map; //ZenFieldMap instance
}
  
?>
Return current item: ZenTrack - project/bug tracking software