Location: PHPKode > projects > ZenTrack - project/bug tracking software > zentrack_2.6.4/includes/egate_utils.php
<?{
  define("ZT_DEFINED",true);
  
  /*
  **  EGATE UTILS
  **  
  **  The funtions that process incoming data
  */
  
  // get configuration
  include("egate_config.php");
  
  // just a list of valid template forms I have created
  $form_template_list = array("create","help","log","move");
  
  // initialize log
  $egate_log = array();
  // initialize the templates
  $email_templates = array();
  
  /**
  * Stores log info
  *
  * @param string/array $text the text to store
  * @param integer $lvl 1-error(system), 2-warning, 3-notice
  */
  function egate_log( $text, $lvl = 3 ) {
    // if we get an array, handle it gracefully
    if( is_array($text) ) {
      foreach($text as $t)
        egate_log($text, $lvl);
      return;
    } 
    // get the log text
    global $egate_log;
    // check for this fatal error, which isn't really an error
    if( preg_match("@mailbox is hide@address.com", $text) ) {
      $text = "Mailbox was empty";
      $egate_log[] = array($text,3);
    }
    else {
      $egate_log[] = array($text,$lvl);
    }
  }
  
  /**
  * returns a list of messages for use by user
  *
  * @param integer lvl 1-errors, 2-warning, 3-notices
  * @return array of log entries
  */
  function egate_fetch_messages( $lvl = '' ) {
    global $egate_log;
    // save time if there is nothing to fetch
    if( $lvl === 0 || !count($egate_log) ) { return; }
    // get appropriate logs
    $vars = null;
    foreach($egate_log as $l) {
      if( $l[1] <= $lvl ) { $vars[] = $l[0]; }
    }
    return $vars;
  }
  
  /**
  * stores footer templates that should be
  * included in an email reply
  *
  * @param string $template is the template name to include
  * @param int $id is the ticket id, if available (for creating subject)
  */
  function egate_store_template( $template, $id = 0 ) {
    global $email_templates;
    // create a subject if appropriate
    $sub = egate_get_subject();
    if( !strlen($sub) ) {
      global $form_template_list;
      $tmp = str_replace(".template","",$template);
      $tmp = str_replace("form_","",$tmp);
      // we only want it if it's a real template value
      if( in_array($tmp,$form_template_list) ) {
        $txt = ucwords(str_replace("_"," ",$tmp));
        if( $id )
          $txt = "#$id: $txt";
        egate_store_subject($txt);
      }
    }
    // store our template
    $email_templates[] = $template;
  }
  
  /**
  * stores the subject of template[somthing] emails to be retrieved by mail fxn
  *
  * @param string $subject of the email
  */
  $template_subject = "";
  function egate_store_subject( $subject ) {
    global $template_subject;
    $template_subject = $subject;
  }
  
  /**
  * retrieves the subject for template emails
  *
  * @return string subject of email
  */
  function egate_get_subject() {
    global $template_subject;
    return $template_subject;
  }
  
  /**
  * returns the email templates which have been collected
  *
  * @return array of template strings
  */
  function egate_fetch_templates() {
    global $email_templates;
    return $email_templates;
  }
  
  /**
  * writes logs to file
  * 
  * Opens the log file and writes all existing entries
  * can also send an email if informed to do so
  * 
  */
  function egate_log_write() {
    global $egate_log;
    global $libDir;
    global $egate_log_level;
    if( count($egate_log) ) {
      $text = "---".date("Y-m-d H:i")."---\n";
      foreach($egate_log as $l) {
        if( $l[1] <= $egate_log_level ) {
          $text .= $l[0]."\n";
        }
      }    
      $fp = fopen("$libDir/logs/egate_log","a");
      fputs($fp,$text);
      fclose($fp);
      $egate_log = array();
    }
  }
  
  // check for proper setup
  if( !file_exists($header_file_location) ) {
    egate_log("ERROR: \$header_file_location not set correctly... exiting",1);
    egate_log_write();
    exit;
  }
  
  // get the system settings
  // and process them, but don't include
  // the headerInc.php file, just get
  // the setting values
  $file = file($header_file_location);
  foreach($file as $f) {
    if( preg_match("/^ *([$]|set_locale)/", $f)) {
      eval($f);
    }
  }
  
  //initialize zen base object
  include_once("$libDir/zenTrack.class.php");
  $zen = new zenTrack( $configFile );
  
  // include the zen objects
  include_once("$libDir/zenTemplate.class.php");
  include_once("$libDir/translator.class.php");
  
  //Create the initialization array for the translator object
  $translator_init = array(
    'domain' => 'translator',
    'path' => "$libDir/translations",
    'locale' => (empty($login_language)? 'english' : $login_language)
    );
  $translator_init['zen'] =& $zen;
  tr($translator_init);
  
  // include the mail decoding functions
  include_once('Mail/mimeDecode.php');
  
  // make sure we are using the email interface
  if( !$zen->settingOn("email_interface_enabled") ) {
    egate_log("ERROR: email ignored, email_interface_enabled = off",1);
    egate_log_write();
    exit;
  }
  
  // determine the egate user's settings and access rights
  $egate_user = $zen->get_user_by_login($egate_account);
  
  // produce an error if the egate user is not in database
  if( !is_array($egate_user) || !count($egate_user) ) {
    egate_log("ERROR: egate user account not found",1);
    egate_log_write();
    exit;    
  }
  
  // get the egate access priviledges
  $egate_user["access"] = $zen->get_access($egate_user["user_id"]);
  
  /**
  * Decode the message and return params
  *
  * @param string $input the raw data
  * @return object params
  */
  function decode_contents($input) {
    global $zen;
    // set up params and execute decoding
    $params['include_bodies'] = TRUE;
    $params['decode_bodies']  = TRUE;
    $params['decode_headers'] = TRUE;
    $decoder = new Mail_mimeDecode($input);
    $structure = $decoder->decode($params);
    
    // record what we recieved
    egate_log( "From: ".$structure->headers["from"]
      ."\nReply-to: ".(empty($structure->headers["reply-to"])? '' : $structure->headers['reply-to'])
      ."\nSubject: ".$structure->headers["subject"], 2 );
    
    return $structure;
  }
  
  /**
  ** returns a list of valid actions for the ticket and user
  **
  ** @param integer $ticket_id the ticket id
  ** @return array of actions which are valid
  */
  function fetch_valid_actions( $ticket_id ) {
    global $egate_user;
    global $zen;
    $vars = array("remove","help","template","options");
    $vals = $zen->listValidActions($ticket_id,$egate_user["user_id"]);
    foreach($vals as $k=>$v) {
      if( $v["egate"] > 0 ) {
        $vars[] = $k;
      }
    }
    natsort($vars);
    return $vars;
  }
  
  /**
  * returns the entries needed to complete the template forms
  *
  * @param array $vals the template vals so far
  * @return array the $vals from input plus the new.template vals
  */
  function fetch_template_vals( $vals ) {
    global $zen;
    global $egate_user;
    $vals["types"] = $zen->getTypes();
    $vals["systems"] = $zen->getSystems();
    $bins = $zen->getUsersBins($egate_user["user_id"],"level_create");
    if( is_array($bins) ) {
      $vals["bins"] = array();
      foreach($bins as $b) {
        $vals["bins"][] = $zen->getBinName($b);
      }
    }
    $vals["priorities"] = $zen->getPriorities();
    $vals["activities"] = $zen->getActivities();
    $vals["default_start_date"] = $zen->getDefaultValue("default_start_date");
    if( strlen($vals["default_start_date"]) )
      $vals["default_start_date"] = $zen->showDate($vals["default_start_date"]);
    $vals["default_deadline"] = $zen->getDefaultValue("default_deadline");
    if( strlen($vals["default_deadline"]) )
      $vals["default_deadline"] = $zen->showDate($vals["default_deadline"]);
    $vals["default_test"] = 
    ($zen->getDefaultValue("default_tested_checked")==" checked ")? "x" : "";
    $vals["default_approve"] = 
    ($zen->getDefaultValue("default_aprv_checked")==" checked ")? "x" : "";    
    return $vals;
  }
  
  /**
   * Parse the fields and format values for db insertion.
   *
   * @param array $ticket the values to be parsed, probably from generate_ticket_attributes()
   * @param int $user_id the user manipulating the ticket
   * @return array containing parsed values
   */
  function process_ticket_fields($ticket, $user_id) {
    global $zen;
     $vals = array("creator_id"=>$user_id,"otime"=>time());
     
    // here we run through all the body elements
    // and prepare the results
    foreach($ticket as $k=>$v) {
      $v = trim($v);
      switch(strtolower($k)) {
      case "parent":
      case "project":
        if( strlen($v) ) {
          $vals["project_id"] = get_ticket_id($v);
	  if( $vals['project_id'] && empty($vals['bin_id']) ) {
	    // set the default bin to the same one as the project
	    // it can still be overwritten by a user parm
	    $project = get_ticket($vals['project_id']);
	    $vals['bin_id'] = $project['bin_id'];
	  }
	}
        break;
      case "title":
        $vals["title"] = $zen->stripPHP($v);
        break;
      case "type":
        $vals["type_id"] = get_type_id("types",$v);
        break;
      case "system":
        $vals["system_id"] = get_type_id("systems",$v);
        break;
      case "owner":
        {
          if( strtolower($v) == "me" || strtolower($v) == "myself" ) {
            $vals["user_id"] = $user_id;
          }
          else if( preg_match("/^[0-9]+$/", $v) ) {
            $user = $zen->get_user($v);
            if( $user ) {
              egate_log("created owner from id '$v': ".($user?$user['user_id']:'<null>'), 3);
              $vals["user_id"] = $user['user_id'];
            }
            else {
              egate_log("could not locate owner by id '$v', skipped",2);
            }
          }
          else if( strpos($v, '@') > 0 ) {
            $user = find_user_id('',$v);
            if( $user ) {
              $vals['user_id'] = $user['user_id'];
              egate_log("created owner from email '$v': ".($vals['user_id']?$vals['user_id']:'<null>'), 3);
            }
            else {
              egate_log("could not create owner from email '$email', skipped",2);
            }
          }
          else if( strlen($v) ) {
            $user = find_user_id('','',$v);
            if( $user ) {
              $vals['user_id'] = $user['user_id'];
              egate_log("created owner by login '$v': ".($vals['user_id']?$vals['user_id']:'<null>'), 3);
            }
            else {
              egate_log("could not create owner from login '$login', skipped", 3);
            }
          }
        }
        break;
      case "bin":
        $vals["bin_id"] = get_type_id("bins",$v);
        break;
      case "priority":
        $vals["priority"] = get_type_id("priorities",$v);
        break;
      case "start":
      case "start_date":
      case "start date":
        if( strlen($v) ) {
          $vals["start_date"] = strtotime($v);
        }
        break;
      case "deadline":
        if( strlen($v) ) {
          $vals["deadline"] = strtotime($v);
        }
        break;
      case "worked":
      case "wkd_hours":
      case "hours worked":
        if( strlen($v) ) {
          $vals['wkd_hours'] = $zen->checkNum($v);
        }
        break;
      case "est_hours":
      case "estimated hours":
      case "estimated":
        if( strlen($v) ) {
          $vals['est_hours'] = $zen->checkNum($v);
        }
      case "testing":
      case "testing required":
      case "testing_required":
        {
          if( $v == 1 )
            $vals["tested"] = 1;
          else
          $vals["tested"] = 0;
        }
        break;
      case "approval required":
      case "approval_required":
      case "approval":
        {
          if( $v == 1 )
            $vals["approved"] = 1;
          else
          $vals["approved"] = 0;
          break;
        }
      case "details":
        $vals["description"] = $zen->stripPHP($v);
        break;
      }      
    }
    
    return $vals;
  }
  
  /**
   * Checks required fields for ticket creation, logs errors, and returns
   * false if a required field is not valid
   * @param array $ticket the attributes to be inserted into ticket
   * @return boolean
   */
  function check_required_ticket_fields($ticket) {
    $success = true;
    $required = array(
      "title",
      "priority",
      "description",
      "bin_id",
      "type_id",
      "system_id",
      "creator_id"
      );
    foreach($required as $r) {
      if( !isset($ticket[$r]) || !$ticket[$r] ) {
        $success = false;
        egate_log( ucfirst(str_replace("_id","",$r))." is required",2);
      }
    }
    return $success;
  }
  
  /**
  * Creates a new ticket entry
  *
  * @param integer $user_id the user id to use for creation 
  * @param string $name the name of the sender
  * @param string $email the email of the sender
  * @param array $ticket the indexed array from generate_ticket_attributes()
  * @param Decode $msg the Decode object obtained from decode_contents()
  * @param array $attachments an array of attachment ids to be linked to the ticket
  * @return integer ticket id or 0 if failed
  */
  function create_new_ticket($user_id, $name, $email, $ticket, $msg) {
    global $zen;
    global $egate_user;

    $vals = process_ticket_fields($ticket, $user_id);
    
    if( check_required_ticket_fields($vals) ) {
      $fullname = ($name)? "\"$name\" <$email>" : $email;
      $notes = ($user_id == $egate_user["user_id"])? "Created by $fullname" : "";
      $id = $zen->add_ticket($vals,$notes);
      if( $id ) {
        egate_log("Ticket created with id #$id",3);
        
        // add user to notify list if they create
        // a ticket through the egate system
        // and default_notify_creator == "on"
        if( $user_id == $egate_user["user_id"] && $zen->settingOn("default_notify_creator") ) {
          $zen->add_to_notify_list( $id, array("name"=>$name,"email"=>$email) );
        }
        
        // add attachments
        processAttachments($id, $user_id, $msg);
        
        return $id;
      }
    }
    egate_log("Ticket create failed",2);
    return false;
  }
  
  /**
  * try to find a user in the system based on their name and email
  *
  * @param string $name is the users name
  * @param string $email is the users email
  * @return integer user_id to use, returns egate_user id if no id found
  */
  function find_user_id($name,$email,$login = '') {
    global $zen;
    global $egate_user;
    // find out what user_id to apply
    // by trying to find this user in
    // the system 
    $users_by_email = '';
    if( $login ) {
      $user_id = $zen->get_user_by_login($login);
      if( $user_id ) {
        egate_log("found user by login  '$login': $user_id",3);
      }
      else if( strpos($login, " ") > 0 ) {
	$vals = explode(" ", $login, 2);
	$parms = array("fname" => $vals[0], "lname" => $vals[1]);
	$users = $zen->search_users($parms);
	if( empty($users) ) {
	  egate_log("no user found with name $login", 2);
	}
	else if( count($users) == 1 ) {
	  $user_id = $users[0]['user_id'];
	}
	else {
	  egate_log("more than one user found with name: $login",2);
	}
      }
      else {
        egate_log("invalid name, doesn't match any login or first/last name: $login",2);
      }
    }
    if( empty($user_id) && $email ) {
      egate_log("locating user by email '$email'", 3);
      $users_by_email = $zen->get_users_by_email($email);
      if( is_array($users_by_email) ) {
        // if we got more than one user for this email address
        // then try looking at the name
        if( count($users_by_email) > 1 && strlen($name) ) {
          $vals = array();
          $users_by_name = $zen->get_users_by_name($name);
          if( is_array($users_by_name) ) {
            // foreach name found, see if this user_id also
            // has the correct email, if so, it's a possible match
            foreach($users_by_name as $u) {
              if( in_array($u,$users_by_email) ) {
                $vals[] = $u;
              }
            }
          }
          // if there are more than one, we can't be sure, so skip
          if( count($vals) == 1 ) {
            $user_id = $vals[0];
          }
          egate_log("multiple users have address '$email', tried to select by name '$name': $user_id",2);
        }
        else if( count($users_by_email) == 1 ) {
          // we only found one, so that must be it
          $user_id = $users_by_email[0];
          egate_log("selected user by email '$email': $user_id",3);
        }
      }
    }
    if( empty($user_id) ) {
      egate_log("selected default user (egate account): {$egate_user['user_id']}",3);
      $user_id = $egate_user["user_id"];
    }
    return $user_id;
  }
  
  /**
  * returns a third param from a ticket subject (or body if found)
  *
  * @param object $params the params from decode_contents()
  * @param string $tag (optional) will search body for this tag and use if found
  * @param string $ticket the body attributes from get_ticket_attributes()
  * @return string value of param or body tag if found
  */
  function get_subject_param( $params, $tag, $ticket ) {
    // search body element for tag
    if( is_array($ticket) && $tag ) {
      if( isset($ticket[$tag]) ) {
        return $ticket[$tag];
      }
    }
    // return tag from subject
    if( $tag == "template" ) {
      preg_match("/template:? ([a-zA-Z0-9_-]+)/", $params->headers["subject"],$matches);
      return trim($matches[1]);
    }
    preg_match("/#[0-9]+:? *[a-zA-Z0-9_-]+ +(.*)/",$params->headers["subject"],$matches);
    if( $matches[1] ) {
      return trim($matches[1]);
    }
    return "";
  }
  
  /**
  * attempts to retrieve a ticket by the title, or by the id
  *
  * @param string $text title or id
  * @return integer id of ticket or 0 if failed
  */
  function get_ticket_id( $text ) {
    global $zen;
    // insure the input is cleaned up
    $text = trim($text);
    // check for #nnnn
    $text = preg_replace("/^#([0-9]+)$/", "\\1", $text);
    if( preg_match("/^[0-9]+$/", $text) && strlen($text) && intval($text) > 0 ) {
      // here we look for an id, if found, we validate it
      $ticket = $zen->get_ticket(intval($text));
      if( is_array($ticket) ) {
        return $ticket["id"];
      }
      else {
        egate_log("Ticket #$text not found",3);
        return 0;
      }
    }
    else {
      if( strlen($text) ) {
        // here we look for a complete title match
        $params = array("title"=>array("title","=",$text));
        $vals = $zen->search_tickets($params);
        if( !is_array($vals) || !count($vals) ) {
          // here we try for a partial title match
          $params["title"][1] = "contains";
          $vals = $zen->search_tickets($params);
        }
      }
      if( is_array($vals) ) {
        if( count($vals) > 1 ) {
          // if we have more than one, we return an error, since
          // we aren't sure what the right one is
          egate_log("More than one ticket found with title $text",2);
          return 0;
        }
        else {
          // otherwise, we have success
          return $vals[0]["id"];
        }
      }
      else {
        egate_log("Title didn't match any active tickets",3);
        return 0;
      }
    }
  }
  
  /**
  * evaluates the users input and determines what id it stands for
  *
  * @param string $type the data type(plural): systems, types, bins, priorities, etc
  * @param string $input the users input
  * @return integer the id to use. returns null on failure
  */
  function get_type_id( $type, $input ) {
    global $zen;
    $id = null;
    $n = "get".ucfirst($type);
    $vals = $zen->$n();
    $input = trim($input);
    if( is_integer($input) && isset($vals["$input"]) ) {
      return $input;
    }
    else if( !is_integer($input) ) {
      foreach($vals as $k=>$v) {
        if( strtolower($v) == strtolower($input) )
          return $k;
      }
    }
    egate_log(($input?$input:"<null>")." was not a valid $type entry",2);    
    return $id;
  }
  
  /**
  * Create a log entry
  *
  * @param string $name the name of the sender
  * @param string $email the email of the sender
  * @param Decode $msg the object returned from decode_contents()
  * @param array $ticket properties of the ticket
  * @param array $body the parsed body properties (from generate_ticket_attributes())
  * @return boolean succeeded
  */
  function egate_log_to_ticket($name,$email,$msg,$ticket,$body) {
    global $zen;
    global $egate_user;
    
    // see if we have a valid system user
    // which we can apply here
    $user_id = find_user_id($name,$email);
    
    $id = $ticket["id"];      
    
    // check and see if this user is on the
    // notify list, otherwise they can't edit
    // anything about this ticket
    $list = $zen->get_notify_recipients($id);
    if( !in_array($email,$list) && find_user_id($name,$email)==$egate_user["user_id"] ) {
      egate_log("Sorry, you aren't on the notify list for this ticket"
        ." and aren't a registered user.",2);
      return false;
    }
    
    // make sure we have a valid string
    if( !isset($body["details"]) ) {
      egate_log("Log entry failed, there was no message to add",2);
      return false;
    }
    
    // format a name entry for logging
    $fullname = !empty($name)? "\"$name\" <$email>" : $email;
    
    $logParams = array(
      "bin_id"    => $ticket["bin_id"],
      "entry"     => $body["details"],
      "user_id"   => $user_id,
      "ticket_id" => $id
      );

    // add any hours assigned to the ticket to the log
    foreach( array('hours', 'worked', 'worked hours', 'wkd_hours', 'hours worked') as $k ) {
      if( isset($body[$k]) ) {
	$logParams['hours'] = $body[$k];
	break;
      }
    }

    $body['hours'] = $zen->checkNum($body["hours"]);
    if( isset($body["activity"]) && in_array(strtoupper($body["activity"]),$zen->getActivities()) ) {
      $logParams["action"] = strtoupper($body["activity"]);
    } else {
      $logParams["action"] = "NOTE";
    }
    $res = $zen->log_ticket($id,$user_id,$logParams["action"],
      $logParams["hours"], $logParams["entry"]);
    
    // log results
    if( $res ) { 
      processAttachments($id, $user_id, $msg, $res);
      egate_log("Log entry added",3); 
    }
    else { 
      egate_log("Log entry failed",2);
      return false;
    }
    
    return true;
  }
  
  /**
   * Fetch an email template, fill it in, and send it off
   */
   function egate_show_template($body,$params) {
     //todo: remove this? keeping it around for now in case we want it
      global $form_template_list;
      $str = strtolower(get_subject_param($params,"template",$body));
      if( in_array($str,$form_template_list) ) {
        egate_store_template("form_$str.template", $id);
        egate_log("returning $str form",3);
      }
      else {
        egate_log("$str is an invalid template",2);
        return false;
      }
      return true;
   }
  
  /**
  * Generate the body text from a multi-part mime message recursively.
  *
  * @param object $msg is the output of decode_contents()
  * @return string containing body
  */
  function getMultipartBody($msg, $try_html = false) {
    $body = '';
    foreach ($msg->parts as $part) {
      // don't read anything that's not text, or attachments, even if they are text
      if( $part->ctype_primary == 'text' && empty($part->disposition) ) {
        // determine what sort of text it is  
        if( $part->ctype_secondary == 'plain' ) {
          $body .= $part->body;
        }
        else if( $try_html && $part->ctype_secondary == 'html' ) {
          $body .= strip_tags($part->body);
        }
      }
      if( !empty($part->parts) ) {
        $body .= getMultipartBody($part, $try_html);
      }
    }
    return $body;
  }
  
  /**
  * Find and parse the body of a message from either plain text or from
  * multi-part mime and attach it to the message object as $msg->body
  *
  * @param object $msg is the output of decode_contents()
  */
  function findMsgBody(&$msg) {
    if( empty($msg->body) ) {
      // try for a text body
      $msg->body = getMultipartBody($msg);
      if( empty($msg->body) ) {
        // if there's no text body, try and read the html body
        $msg->body = getMultipartBody($msg, true);
      }
    }
    
    // trim reply text from email body
    if( strpos($msg->body, '>') !== false ) {
      $msg->body = preg_replace('@^\s*>.*?\hide@address.com', '', $msg->body);
    }
    if( !empty( $egate_originalemail_prefix ) ) {
      $msg->body = preg_replace($egate_originalemail_prefix, "", $msg->body);
    }
    // removes "so-and-so wrote:" lines from the email
    $msg->body = preg_replace("/^[a-zA-Z][a-zA-hide@address.com -]+? wrote: *\r?\n?$/m", "", $msg->body);
    
    // the spacing is very pesky and hard to trim because of \r and . characters. Do this one
    // last time to try and clean it up
    $msg->body = preg_replace("@[\r\n ]+\\.?$@s", "", trim($msg->body));
    
    // replace =20 characters sometimes inserted by M$
    $msg->body = str_replace("=20", "\n", $msg->body);
  }
  
  /**
  * Find and parse attachments connected to the message
  * store them in the attachments dir and db table, attach them to
  * the ticket_id provided.
  *
  * @param int $ticket_id
  * @param int $user_id
  * @param object $msg is the output of decode_contents()
  * @param int $log_id if provided, attachments are added to a specific log
  * @return int number of attachments processed
  */
  function processAttachments($ticket_id, $user_id, $msg, $log_id = '') {
    global $zen;

    if( empty($msg->parts) ) { return 0; }

    // look for new attachments
    $c = 0;
    foreach ($msg->parts as $part) {
      $fileName = '';
      if ($part->ctype_primary == 'image') {
        if( isset($part->ctype_parameters['filename']) ) {
          $fileName = $part->ctype_parameters['filename'];
        } 
        elseif( isset($part->ctype_parameters['name']) ) {
          $fileName = $part->ctype_parameters['name'];
        } 
        else {
          $fileName = "image".$c++.".{$part->ctype_secondary}";
        }
      } 
      elseif( !empty($part->disposition) ) {
        if( isset($part->ctype_parameters['filename']) ) {
          $fileName = $part->ctype_parameters['filename'];
        }
        elseif( isset($part->ctype_parameters['name']) ) {
          $fileName = $part->ctype_parameters['name'];
        } 
        else {
          $fileName = "attachment".$c++.".{$part->ctype_secondary}";
        }
      }
      if( $fileName ) {
        $dir = $zen->attachmentsDir;
        $fileId = $zen->getAttachmentName($ticket_id);
        $fileType = $part->ctype_primary . "/" . $part->ctype_secondary;
        $fileExt = $part->ctype_secondary;
        
        if( $zen->checkAttachmentExt($fileName) ) {
          $fp = fopen("$dir/$fileId", 'w');
          fputs($fp, $part->body);
          fclose($fp);
          egate_log("Adding attachment($dir/$fileId): $fileName", 2);
          if( !$comment ) { $comment = "added via egate"; }
          $parms = array("name" => $fileName, 
                         "filename" => $fileId, 
                         "filetype" => $fileType,
                         "description" => $comment);
          $zen->attach_to_ticket($ticket_id, $user_id, $parms, $log_id);
        }
        else {
          egate_log("Invalid attachment: $fileName(not in allowed types)", 2);
        }
      }
      if( !empty($part->parts) ) { 
        $c += processAttachments($ticket_id, $user_id, $part, $log_id, $comment); 
      }
    }
    return $c;
  }
  
  /**
  * Create an array of parameters that will be passed to the ticket
  * creation process as the attributes for the ticket
  * @param object $msg email properties obtain from decode_contents()
  * @param int $user_id can be null, the id of the user owning from address
  * @return array
  */
  function generate_ticket_attributes($msg, $user_id) {
    global $zen;
    global $egate_default_options;
    global $egate_create_fields;
    global $egate_create_overrides;
    
    // create the body elements
    $body = $egate_default_options;
    $body["title"] = $msg->headers["subject"];
    
    // get the message body and parse it
    findMsgBody($msg);
    $body['details'] = $msg->body;
    
    // add in overrides, if the user has specified any by putting
    // 'field:value' entries at the top of the message body
    if( $egate_create_overrides == 1 && count($egate_create_fields) > 0 ) {
      $i=0;
      $match = '/^ *('.join('|',$egate_create_fields).') *: *(.+)/i';
      while( preg_match( $match, $body['details'], $matches) && $i < 1000 ) {
        $body["{$matches[1]}"] = trim($matches[2]);
        $body['details'] = trim(preg_replace($match, '', $body['details']));
        $i++;
      }
    }
    
    // if there is a user id, he created the ticket
    $body['creator_id'] = $user_id;
    
    return $body;
  }
  
  /**
  * parse out the user's email address and name
  *
  * @param $params is the output from decode_contents()
  * @return array (name,email)
  */
  function get_name_and_email($params) {
    // initialize
    $name = "";
    $email = "";
    
    // set up the return email address
    // and the user's name, if it can be found
    $email = !empty($params->headers["reply-to"])? 
    trim($params->headers["reply-to"]) : trim($params->headers["from"]);
    if( preg_match("/([^<]*)<([a-zA-Z0-hide@address.com]+)>/", $email, $matches) ) {
      $name = trim($matches[1]);
      $email = trim($matches[2]);
    }
    if( !$name ) {
      if( preg_match("/([^<]*)<([a-zA-Z0-hide@address.com]+)>/", $params->headers["from"], $matches) ) {
        $name = trim($matches[1]);
      }
      else {
        $name = "unknown";
      }
    }
    $name = preg_replace('/["\'<>]/',"",$name);
    $email = preg_replace('/["\'<>]/',"",$email);
    return array($name,$email);
  }
  
  /**
  * make an email reply for the sender
  * 
  * this will send a reply to the person
  * who submitted the entry, compliant
  * with $egate_notify_level
  *
  * @param array $recipients is an indexed array of email addresses and names (names optional)
  * @param integer $id is the tickets id
  * @param boolean $success is whether action was completed successfully
  * @param string $action is the action taken
  * @return integer number of emails sent
  */
  function send_reply_mail( $recipients, $id, $success, $action ) {    
    // employ $egate_notify_level (0-3)
    global $egate_notify_level;
    global $libDir;
    global $egate_user;
    global $zen;
    global $egate_bcc_address;
    
    // get the messages
    $messages = egate_fetch_messages($egate_notify_level);
    $templates = egate_fetch_templates();
    
    // send an email if we have data
    if( !empty($messages) || !empty($templates) ) {
      $text = "";
      
      // grab the params
      $valid_actions = fetch_valid_actions($id);      
      $vals = array("success"  =>   $success,
		    "id"       =>   $id,
		    "messages" =>   $messages,
		    "action"   =>   $action,
		    "valid_actions" => $valid_actions);
      
      // create header
      $temp = new zenTemplate("$libDir/templates/email/heading.template");
      $temp->values($vals);
      $text .= $temp->process();
      
      if( $id ) {
        // create a subject
        $ticket = $zen->get_ticket($id);
        if( $ticket ) {
          $subject = "Re: #$id - {$ticket['title']}";
        }
        else {
          $subject = "Re: #$id ($action)";
        }
        $templates[] = "reply.template";
      }
      else if( $action == "template" ) {
        $subject = egate_get_subject();
        // get ticket fields we may need
        $vals = fetch_template_vals($vals);
      }
      else if( $action ) {
        $subject = "Re: $action (".($success? "successful" : "failed").")";
      } else {
        $subject = "request ".($success? "successfull":"failed");
      }
      
      if( empty($templates) ) {
        $templates = array("messages.template");
      }
      
      // include extra footer templates
      // run through templates
      foreach( $templates as $t ) {
        $temp = new zenTemplate("$libDir/templates/email/$t");
        $temp->values($vals);
        $text .= $temp->process();
      }
      
      if( $action != "help" && $action != "template" ) {
        // create footer	
        $temp = new zenTemplate("$libDir/templates/email/footer.template");
        $temp->values($vals);	
        $text .= $temp->process();
      }
      
      $subject = "[".$zen->getSetting("bot_name")."] ".$subject;
      
      // send messages
      $i=0;
      $from_address = $egate_user["email"];
      $from_fname = $egate_user["fname"];
      $from_lname = $egate_user["lname"];
      $bcc = $egate_bcc_address? "Bcc:$egate_bcc_address\r\n" : "";
      foreach($recipients as $r) {
        if( is_array($r) && count($r) && $r["email"] != $egate_user["email"] ) {
          $res = mail($r['email'],$subject,$text,"From: $from_fname $from_lname <$from_address>\r\nReply-to:$from_address\r\n$bcc");
          if( $res )
            $i++;
        }
      }
      return $i;
    }
    // if we skipped the results, we sent 0 emails
    return 0;
  }
  
  function getMultipartContent($msg) {
    $body = '';
    foreach ($msg->parts as $part) {
      if (($part->ctype_primary == 'text') and ($part->ctype_secondary == 'plain')) {
        $body .= $part->body;
      }
      if (isset ($part->parts) and (is_array($part->parts))) {
        $body .= getMultipartContent($part);
      }
    }
    if ($body == "") {
      return false;
    }
    return $body;
  }
  
  /* function checkForMail - Check for the existence of email on the pop3 server. */
  function checkForMail($conn) {
    fputs($conn, "STAT\r\n");
    $output = fgets($conn, 128);
    $ack = strtok($output, " "); // Bleed off +OK
    $numMessages = strtok(" "); // Get what we wanted
    
    egate_log("Ack: $ack, Num Messages: $numMessages\n$output", 3);
    
    if ($numMessages > 0) {
      egate_log("***New mail***",3);
    } else {
      egate_log("***No mail***", 3);
    }
    return $numMessages;
  }
  
  /* function getEmail - Collect each individual message and return to the getMessages function. */
  function getEmail($conn, $num) {
    $message = "";
    fputs($conn, "RETR $num\r\n");
    $output = fgets($conn, 512);
    
    if (strtok($output, "+OK")) {
      while (!ereg("^\.\r\n", $output)) {
        $output = fgets($conn, 512);
        $message .= $output;
      }
    }
    else {
      egate_log($output, 2);
    }
    
    return $message;
  }
  
  /* function deleteMessage - Delete messages from pop3 server after downloading them. */
  function deleteMessage($conn, $message) {
    fputs($conn, "DELE $message\r\n");
  }
  
  /* function popConnect - Connects to the designated pop3 mail server, using the supplied credentials. */
  function popConnect($host, $port, $user, $pass) {
    $errno = "";
    $errstr = "";
    $conn = fsockopen($host, $port, $errno, $errstr, 90);
    if (!$conn) {
      egate_log("Connect Failed: $errstr($errno)",1);
      return false;
    } else {
      egate_log(fgets($conn, 128), 3);
      fputs($conn, "USER $user\r\n");
      egate_log(fgets($conn, 128), 3);
      fputs($conn, "PASS $pass\r\n");
      egate_log(fgets($conn, 128), 3);
      return $conn;
    }
  }
  
  /* function getMessages - Collects email messages from a pop3 server, and returns them as an array of messages.
  *                        Calls function getEmail to download each individual message. Returns an array of
  *                        of email messages. */
  function getMessages($conn) {
    $numMessages = checkForMail($conn);
    
    if ($numMessages > 0) {
      $mailArray = array ();
      for ($i = 0; $i < $numMessages; $i ++) {
        $mailArray[$i] = getEmail($conn, $i + 1);
        deleteMessage($conn, $i + 1);
      }
      return $mailArray;
    }
  }
  
  /* function popDisconnect - Disconnects the pop3 session. */
  function popDisconnect($conn) {
    fputs($conn, "QUIT\r\n");
    egate_log(fgets($conn, 128), 3);
    fclose($conn);
    $conn = 0;
  }
  
  /**
  * @param string $subject
  * @return int ticket id from subject or false if none
  */
  function get_ticket_id_from_subject($subject) {
    preg_match( '@#([0-9]+)@', $subject, $matches );
    if( count($matches) > 1 ) { return $matches[1]; }
    return false;
  }
  
  /**
  * @param int $id
  * @return array containing ticket parms or false if not found
  */
  function get_ticket( $id ) {
    global $zen;
    // make sure we have a valid ticket and action
    $ticket = $zen->get_ticket($id);    
    if( !is_array($ticket) || !count($ticket) ) {
      egate_log("Ticket #$id is not a valid ticket",2);
      return false;
    }
    else { return $ticket; }
  }
  
  /*
  **  This function reads emails and does one of two things, based on the subject
  **  of the email. If the email begins with #nn (where nn is a number), then
  **  this function will add a log entry with the contents of the email as the message.
  **
  **  If the subject does not begin with #nn, then a new ticket is created with
  **  the contents of the email as the descripion of the problem and the subject
  **  as the title.
  **
  **  The default parameters for the ticket are specified in egate_config.php
  **  using the $egate_default_options array.
  **  
  **  All activity is logged to includes/logs/egate_log
  **
  **  Note that if $egate_create_overrrides is set to 1 in egate_config.php, then
  **  users can override any value in the array $egate_create_fields array by putting
  **  "field_name: value" into the email at the beginning of a line
  **
  ** @param string $input the raw email message
  ** @return boolean false on error or true if ok
  */
  function do_helpdesk_message($input) {
    global $egate_user;
    
    // determine if the subject has a ticket id
    $params = decode_contents($input);
    
    // extract the values of interest to us
    $from = empty($params->headers['reply-to'])? $params->headers['from'] : $params->headers['reply-to'];
    $subject = $params->headers['subject'];
    $id = get_ticket_id_from_subject($subject);
    
    if( $id ) { $ticket = get_ticket($id); }
    else { $ticket = false; }
    
    list($name,$email) = get_name_and_email($params);
    $user_id = find_user_id($name,$email);
    $attributes = generate_ticket_attributes($params, $user_id);
    $body = $attributes['details'];
    
    // perform some simple validation
    if( !$from || !$body || !$subject || ($id && !$ticket) ) {
      if( !$from )    { egate_log('Malformed message, missing FROM address', 1);  }
      if( !$subject ) { egate_log('Malformed message, missing Subject', 1);       }
      if( !$body )    { egate_log('Malformed message, missing message body', 1);  }
      if( !$ticket )  { egate_log("Ticket #$id not found", 1); }
      if( !$from ) {
        egate_log("unable to send reply message (no return address)", 1);
        return false;
      }
      $email_subject = "Your message couldn't be processed";
      $email_body = $email_subject . " due to the following errors:\n";
      $error_text = "Unable to process message:\n";
      foreach( egate_fetch_messages(1) as $m ) {
        $error_text .= " - $m\n";
        $email_body .= "\t$m\n";
      }
      $email_body .= "\nThe original message is below...\n------------------\n";
      $email_body .= $input;
      mail($from, $email_subject, $email_body, "From:".$egate_user['email']);
      egate_log($error_text, 1);
      return false;
    }
    
    if( $id ) {
      // we create a log message
      egate_log_to_ticket($name,$email,$params,$ticket,$attributes);
      egate_log("Updated ticket #$id for $user_id/$name/$email",3);
    }
    else {
      // we create a new ticket
      $id = create_new_ticket($user_id, $name, $email, $attributes, $params);
      if( $id ) {
        egate_log("Created ticket #$id for $user_id/$name/$email",3);
      }
      else {
        egate_log("Failed to create ticket",1);
      }
      $rec = array(array("name"=>$name,"email"=>$email));
      send_reply_mail($rec, $id, $id !== false, "new ticket");
    }
    return true;
  }
  
}?>
Return current item: ZenTrack - project/bug tracking software