<?php

/**
* class that handles most ticket related functions.
* the ticket class is used for most ticketing related functions, it also holds some wrapper functions.
* @author Daan Janssens, mentored by Matthew Lagoe
*/
class Ticket{

    private $tId; /**< The id of ticket */
    private $timestamp; /**< Timestamp of the ticket */
    private $title; /**< Title of the ticket */
    private $status; /**< Status of the ticket (0 = waiting on user reply, 1 = waiting on support, (2= not used atm), 3 = closed */
    private $queue; /**< (not in use atm) */
    private $ticket_category; /**< the id of the category belonging to the ticket */
    private $author; /**< The ticket_users id */
    private $priority; /**< The priority of the ticket where 0 = low, 3= supadupahigh */

    ////////////////////////////////////////////Functions////////////////////////////////////////////////////


    /**
    * check if a ticket exists.
    * @param $id the id of the ticket to be checked.
    * @return true if the ticket exists, else false.
    */
    public static function ticketExists($id) {
        $dbl = new DBLayer("lib");
        //check if ticket exists
        if(  $dbl->select("`ticket`", array('ticket_id' => $id), "`TId` = :ticket_id")->rowCount() ){
            return true;
        }else{
            return false;
        }
    }


    /**
    * return an array of the possible statuses
    * @return an array containing the string values that represent the different statuses.
    */
    public static function getStatusArray() {
        return Array("Waiting on user reply","Waiting on support","Waiting on Dev reply","Closed");
    }


    /**
    * return an array of the possible priorities
    * @return an array containing the string values that represent the different priorities.
    */
    public static function getPriorityArray() {
        return Array("Low","Normal","High","Super Dupa High");
    }


    /**
    * return an entire ticket.
    * returns the ticket object and an array of all replies to that ticket.
    * @param $id the id of the ticket.
    * @param $view_as_admin true if the viewer of the ticket is a mod, else false (depending on this it will also show the hidden comments)
    * @return an array containing the 'ticket_obj' and a 'reply_array', which is an array containing all replies to that ticket.
    */
    public static function getEntireTicket($id,$view_as_admin) {
        $ticket = new Ticket();
        $ticket->load_With_TId($id);
        $reply_array = Ticket_Reply::getRepliesOfTicket($id, $view_as_admin);
        return Array('ticket_obj' => $ticket,'reply_array' => $reply_array);
    }


    /**
    * return all tickets of a specific user.
    * an array of all tickets created by a specific user are returned by this function.
    * @param $author the id of the user of whom we want all tickets from.
    * @return an array containing all ticket objects related to a user.
    */
    public static function getTicketsOf($author) {
        $dbl = new DBLayer("lib");
        $statement = $dbl->execute("SELECT * FROM ticket INNER JOIN ticket_user ON ticket.Author = ticket_user.TUserId and ticket_user.ExternId=:id", array('id' => $author));
        $row = $statement->fetchAll();
        $result = Array();
        foreach($row as $ticket){
            $instance = new self();
            $instance->setTId($ticket['TId']);
            $instance->setTimestamp($ticket['Timestamp']);
            $instance->setTitle($ticket['Title']);
            $instance->setStatus($ticket['Status']);
            $instance->setQueue($ticket['Queue']);
            $instance->setTicket_Category($ticket['Ticket_Category']);
            $instance->setAuthor($ticket['Author']);
            $result[] = $instance;
        }
        return $result;
    }



    /**
    * function that creates a new ticket.
    * A new ticket will be created, in case the extra_info != 0 and the http request came from ingame, then a ticket_info page will be created.
    * A log entry will be written, depending on the $real_authors value. In case the for_support_group parameter is set, the ticket will be forwarded immediately.
    * Also the mail handler will create a new email that will be sent to the author to notify him that his ticket is freshly created.
    * @param $title the title we want to give to the ticket.
    * @param $content the content we want to give to the starting post of the ticket.
    * @param $category the id of the category that should be related to the ticket.
    * @param $author the person who's id will be stored in the database as creator of the ticket.
    * @param $real_author should be the same id, or a moderator/admin who creates a ticket for another user (this is used for logging purposes).
    * @param $for_support_group in case you directly want to forward the ticket after creating it. (default value = 0 =  don't forward)
    * @param $extra_info used for creating an ticket_info page related to the ticket, this only happens when the ticket is made ingame.
    * @return the created tickets id.
    */
    public static function create_Ticket( $title, $content, $category, $author, $real_author, $for_support_group = 0, $extra_info = 0) {

        //create the new ticket!
        $ticket = new Ticket();
        $values = array("Title" => $title, "Timestamp"=>0,  "Status"=> 1, "Queue"=> 0, "Ticket_Category" => $category, "Author" => $author, "Priority" => 0);
        $ticket->set($values);
        $ticket->create();
        $ticket_id = $ticket->getTId();

        //if ingame then add an extra info
        if(Helpers::check_if_game_client() && $extra_info != 0){
            $extra_info['Ticket'] = $ticket_id;
            Ticket_Info::create_Ticket_Info($extra_info);
        }

        //write a log entry
        if ( $author == $real_author){
            Ticket_Log::createLogEntry( $ticket_id, $author, 1);
        }else{
            Ticket_Log::createLogEntry( $ticket_id, $real_author, 2, $author);
        }
        Ticket_Reply::createReply($content, $author, $ticket_id, 0, $author);

        //forwards the ticket directly after creation to the supposed support group
        if($for_support_group){
            Ticket::forwardTicket(0, $ticket_id, $for_support_group);
        }

        //send email that new ticket has been created
        Mail_Handler::send_ticketing_mail($ticket->getAuthor(), $ticket, $content, "NEW", $ticket->getForwardedGroupId());
        return $ticket_id;

    }


    /**
    * updates the ticket's status.
    * A log entry about this will be created only if the newStatus is different from the current status.
    * @param $ticket_id the id of the ticket of which we want to change the status.
    * @param $newStatus the new status value (integer)
    * @param $author the user (id) that performed the update status action
    */
    public static function updateTicketStatus( $ticket_id, $newStatus, $author) {

        $ticket = new Ticket();
        $ticket->load_With_TId($ticket_id);
        if ($ticket->getStatus() != $newStatus){
            $ticket->setStatus($newStatus);
            Ticket_Log::createLogEntry( $ticket_id, $author, 5, $newStatus);
        }
        $ticket->update();

    }


    /**
    * updates the ticket's status & priority.
    * A log entry about this will be created only if the newStatus is different from the current status and also when the newPriority is different from the current priority.
    * @todo break this function up into a updateStatus (already exists) and updatePriority function and perhaps write a wrapper function for the combo.
    * @param $ticket_id the id of the ticket of which we want to change the status & priority
    * @param $newStatus the new status value (integer)
    * @param $newPriority the new priority value (integer)
    * @param $author the user (id) that performed the update
    */
    public static function updateTicketStatusAndPriority( $ticket_id, $newStatus, $newPriority, $author) {

        $ticket = new Ticket();
        $ticket->load_With_TId($ticket_id);
        if ($ticket->getStatus() != $newStatus){
            $ticket->setStatus($newStatus);
            Ticket_Log::createLogEntry( $ticket_id, $author, 5, $newStatus);
        }
        if ($ticket->getPriority() != $newPriority){
            $ticket->setPriority($newPriority);
            Ticket_Log::createLogEntry( $ticket_id, $author, 6, $newPriority);
        }
        $ticket->update();

    }


    /**
    * return the latest reply of a ticket
    * @param $ticket_id the id of the ticket.
    * @return a ticket_reply object.
    */
    public static function getLatestReply( $ticket_id) {
        $dbl = new DBLayer("lib");
        $statement = $dbl->execute("SELECT * FROM ticket_reply WHERE Ticket =:id ORDER BY TReplyId DESC LIMIT 1 ", array('id' => $ticket_id));
        $reply = new Ticket_Reply();
        $reply->set($statement->fetch());
        return $reply;
    }

    /**
    * return the attachments list
    * @param $ticket_id the id of the ticket.
    * @return a ticket_reply object.
    */
    public static function getAttachments( $ticket_id) {
        $dbl = new DBLayer("lib");
        $statement = $dbl->select("`ticket_attachments`",array('ticket_TId' => $ticket_id), "`ticket_TId` =:ticket_TId ORDER BY Timestamp DESC");
        $fetchall = $statement->fetchall();
        $base = 0;
        foreach ($fetchall as &$value) {
            $webUser = new WebUsers($value['Uploader']);
            $fetchall[$base]['Username'] = $webUser->getUsername();
            
            $bytes = $fetchall[$base]['Filesize'];
            $precision = 2;
            $units = array('B', 'KB', 'MB', 'GB', 'TB');
         
            $bytes = max($bytes, 0);
            $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
            $pow = min($pow, count($units) - 1);
         
            $bytes /= pow(1024, $pow);
            
            $fetchall[$base]['Filesize'] = round($bytes, $precision) . ' ' . $units[$pow];;
            $base++;
        }
        return $fetchall;
    }
    
    /**
    * create a new reply for a ticket.
    * A reply will only be added if the content isn't empty and if the ticket isn't closed.
    * The ticket creator will be notified by email that someone else replied on his ticket.
    * @param $content the content of the reply
    * @param $author the author of the reply
    * @param $ticket_id the id of the ticket to which we want to add the reply.
    * @param $hidden boolean that specifies if the reply should only be shown to mods/admins or all users.
    */
    public static function createReply($content, $author, $ticket_id, $hidden){
        //if not empty
        if(! ( Trim ( $content ) === '' )){
            $content = filter_var($content, FILTER_SANITIZE_STRING);
            $ticket = new Ticket();
            $ticket->load_With_TId($ticket_id);
            //if status is not closed
            if($ticket->getStatus() != 3){
                Ticket_Reply::createReply($content, $author, $ticket_id, $hidden, $ticket->getAuthor());

                //notify ticket author that a new reply is added!
                if($ticket->getAuthor() != $author){
                    Mail_Handler::send_ticketing_mail($ticket->getAuthor(), $ticket, $content, "REPLY", $ticket->getForwardedGroupId());
                }


            }else{
                //TODO: Show error message that ticket is closed
            }
        }else{
            //TODO: Show error content is empty
        }
    }


    /**
    * assign a ticket to a user.
    * Checks if the ticket exists, if so then it will try to assign the user to it, a log entry will be written about this.
    * @param $user_id the id of user trying to be assigned to the ticket.
    * @param $ticket_id the id of the ticket that we try to assign to the user.
    * @return SUCCESS_ASSIGNED, TICKET_NOT_EXISTING or ALREADY_ASSIGNED
    */
    public static function assignTicket($user_id, $ticket_id){
        if(self::ticketExists($ticket_id)){
            $returnvalue = Assigned::assignTicket($user_id, $ticket_id);
            Ticket_Log::createLogEntry( $ticket_id, $user_id, 7);
            return $returnvalue;
        }else{
            return "TICKET_NOT_EXISTING";
        }
    }


    /**
    * unassign a ticket of a user.
    * Checks if the ticket exists, if so then it will try to unassign the user of it, a log entry will be written about this.
    * @param $user_id the id of user trying to be assigned to the ticket.
    * @param $ticket_id the id of the ticket that we try to assign to the user.
    * @return SUCCESS_UNASSIGNED, TICKET_NOT_EXISTING or NOT_ASSIGNED
    */
    public static function unAssignTicket($user_id, $ticket_id){
        if(self::ticketExists($ticket_id)){
            $returnvalue = Assigned::unAssignTicket($user_id, $ticket_id);
            Ticket_Log::createLogEntry( $ticket_id, $user_id, 9);
            return $returnvalue;
        }else{
            return "TICKET_NOT_EXISTING";
        }
    }


    /**
    * forward a ticket to a specific support group.
    * Checks if the ticket exists, if so then it will try to forward the ticket to the support group specified, a log entry will be written about this.
    * if no log entry should be written then the user_id should be 0, else te $user_id will be used in the log to specify who forwarded it.
    * @param $user_id the id of user trying to forward the ticket.
    * @param $ticket_id the id of the ticket that we try to forward to a support group.
    * @param $group_id the id of the support group.
    * @return SUCCESS_FORWARDED, TICKET_NOT_EXISTING or INVALID_SGROUP
    */
    public static function forwardTicket($user_id, $ticket_id, $group_id){
        if(self::ticketExists($ticket_id)){
            if(isset($group_id) && $group_id != ""){
                //forward the ticket
                $returnvalue = Forwarded::forwardTicket($group_id, $ticket_id);

                if($user_id != 0){
                    //unassign the ticket incase the ticket is assined to yourself
                    self::unAssignTicket($user_id, $ticket_id);
                    //make a log entry of this action
                    Ticket_Log::createLogEntry( $ticket_id, $user_id, 8, $group_id);
                }
                return $returnvalue;
            }else{
                return "INVALID_SGROUP";
            }
        }else{
            return "TICKET_NOT_EXISTING";
        }
    }




    ////////////////////////////////////////////Methods////////////////////////////////////////////////////

    /**
    * A constructor.
    * Empty constructor
    */
    public function __construct() {

    }


    /**
    * sets the object's attributes.
    * @param $values should be an array of the form array('TId' => ticket_id, 'Title' => title, 'Status'=> status, 'Timestamp' => ts, 'Queue' => queue,
    * 'Ticket_Category' => tc, 'Author' => author, 'Priority' => priority).
    */
    public function set($values){
        if(isset($values['TId'])){
            $this->tId = $values['TId'];
        }
        $this->title = $values['Title'];
        $this->status = $values['Status'];
        $this->timestamp = $values['Timestamp'];
        $this->queue = $values['Queue'];
        $this->ticket_category = $values['Ticket_Category'];
        $this->author = $values['Author'];
        $this->priority = $values['Priority'];
    }


    /**
    * creates a new 'ticket' entry.
    * this method will use the object's attributes for creating a new 'ticket' entry in the database.
    */
    public function create(){
        $dbl = new DBLayer("lib");
        $this->tId = $dbl->executeReturnId("ticket", Array('Title' => $this->title, 'Status' => $this->status, 'Queue' => $this->queue, 'Ticket_Category' => $this->ticket_category, 'Author' => $this->author, 'Priority' => $this->priority), array('Timestamp'=>'now()'));
    }


    /**
    * loads the object's attributes.
    * loads the object's attributes by giving a TId (ticket id).
    * @param $id the id of the ticket that should be loaded
    */
    public function load_With_TId( $id) {
        $dbl = new DBLayer("lib");
        $statement = $dbl->select("ticket", array('id' => $id), "TId=:id");
        $row = $statement->fetch();
        $this->tId = $row['TId'];
        $this->timestamp = $row['Timestamp'];
        $this->title = $row['Title'];
        $this->status = $row['Status'];
        $this->queue = $row['Queue'];
        $this->ticket_category = $row['Ticket_Category'];
        $this->author = $row['Author'];
        $this->priority = $row['Priority'];
    }


    /**
    * update the objects attributes to the db.
    */
    public function update(){
        $dbl = new DBLayer("lib");
        $dbl->update("ticket", Array('Timestamp' => $this->timestamp, 'Title' => $this->title, 'Status' => $this->status, 'Queue' => $this->queue, 'Ticket_Category' => $this->ticket_category, 'Author' => $this->author, 'Priority' => $this->priority), "TId=$this->tId");
    }


    /**
    * check if a ticket has a ticket_info page or not.
    * @return true or false
    */
    public function hasInfo(){
        return Ticket_Info::TicketHasInfo($this->getTId());
    }


    ////////////////////////////////////////////Getters////////////////////////////////////////////////////

    /**
    * get tId attribute of the object.
    */
    public function getTId(){
        return $this->tId;
    }

    /**
    * get timestamp attribute of the object in the format defined in the outputTime function of the Helperclass.
    */
    public function getTimestamp(){
        return Helpers::outputTime($this->timestamp);
    }

    /**
    * get title attribute of the object.
    */
    public function getTitle(){
        return $this->title;
    }

    /**
    * get status attribute of the object.
    */
    public function getStatus(){
        return $this->status;
    }

    /**
    * get status attribute of the object in the form of text (string).
    */
    public function getStatusText(){
        $statusArray = Ticket::getStatusArray();
        return $statusArray[$this->getStatus()];
    }

    /**
    * get category attribute of the object in the form of text (string).
    */
    public function getCategoryName(){
        $category = Ticket_Category::constr_TCategoryId($this->getTicket_Category());
        return $category->getName();
    }

    /**
    * get queue attribute of the object.
    */
    public function getQueue(){
        return $this->queue;
    }

    /**
    * get ticket_category attribute of the object (int).
    */
    public function getTicket_Category(){
        return $this->ticket_category;
    }

    /**
    * get author attribute of the object (int).
    */
    public function getAuthor(){
        return $this->author;
    }

    /**
    * get priority attribute of the object (int).
    */
    public function getPriority(){
        return $this->priority;
    }

    /**
    * get priority attribute of the object in the form of text (string).
    */
    public function getPriorityText(){
        $priorityArray = Ticket::getPriorityArray();
        return $priorityArray[$this->getPriority()];
    }

    /**
    * get the user assigned to the ticket.
    * or return 0 in case not assigned.
    */
    public function getAssigned(){
        $user_id = Assigned::getUserAssignedToTicket($this->getTId());
        if ($user_id == ""){
            return 0;
        }else{
            return $user_id;
        }
    }

    /**
    * get the name of the support group to whom the ticket is forwarded
    * or return 0 in case not forwarded.
    */
    public function getForwardedGroupName(){
        $group_id = Forwarded::getSGroupOfTicket($this->getTId());
        if ($group_id == ""){
            return 0;
        }else{
            return Support_Group::getGroup($group_id)->getName();
        }
    }

    /**
    * get the id of the support group to whom the ticket is forwarded
    * or return 0 in case not forwarded.
    */
    public function getForwardedGroupId(){
        $group_id = Forwarded::getSGroupOfTicket($this->getTId());
        if ($group_id == ""){
            return 0;
        }else{
            return $group_id;
        }
    }
    ////////////////////////////////////////////Setters////////////////////////////////////////////////////

    /**
    * set tId attribute of the object.
    * @param $id integer id of the ticket
    */
    public function setTId($id){
        $this->tId = $id;
    }

    /**
    * set timestamp attribute of the object.
    * @param $ts timestamp of the ticket
    */
    public function setTimestamp($ts){
        $this->timestamp = $ts;
    }

    /**
    * set title attribute of the object.
    * @param $t title of the ticket
    */
    public function setTitle($t){
        $this->title = $t;
    }

    /**
    * set status attribute of the object.
    * @param $s status of the ticket(int)
    */
    public function setStatus($s){
        $this->status = $s;
    }

    /**
    * set queue attribute of the object.
    * @param $q queue of the ticket
    */
    public function setQueue($q){
        $this->queue = $q;
    }

    /**
    * set ticket_category attribute of the object.
    * @param $tc ticket_category id of the ticket(int)
    */
    public function setTicket_Category($tc){
        $this->ticket_category = $tc;
    }

    /**
    * set author attribute of the object.
    * @param $a author of the ticket
    */
    public function setAuthor($a){
        $this->author = $a;
    }

    /**
    * set priority attribute of the object.
    * @param $p priority of the ticket
    */
    public function setPriority($p){
        $this->priority = $p;
    }

    /**
    * function that creates a ticket Attachment.
    */
    public static function add_Attachment($TId,$filename,$author,$tempFile){

        global $FILE_STORAGE_PATH;
        $length = mt_rand(20, 25);
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$-_.+!*\'(),';
        $randomString = '';
        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[rand(0, strlen($characters) - 1)];
        }
        $targetFile = $FILE_STORAGE_PATH . $randomString . "/" . $filename;
        
        if(file_exists($targetFile)) { return self::add_Attachment($TId,$filename,$author,$tempFile); }
        
        $ticket = new Ticket();
        $ticket->load_With_TId($TId);
    
        //create the attachment!
        try {
            $dbl = new DBLayer("lib");
            $dbl->insert("`ticket_attachments`", Array('ticket_TId' => $TId, 'Filename' => $filename, 'Filesize' => filesize($tempFile), 'Uploader' => $author, 'Path' => $randomString . "/" . $filename));
		}
		catch (Exception $e) {
			return $false;
		}
        

        mkdir($FILE_STORAGE_PATH . $randomString);
        $return = move_uploaded_file($tempFile,$targetFile);
        
        if ($return == false) {
            $dbl->delete("`ticket_attachments`", array('Path' => $randomString . "/" . $filename), "`Path` = :Path");
        }
        
        //write a log entry
        Ticket_Log::createLogEntry( $TId, $author, 10);

        return $return;
    }
    
}