<?php
#BEGIN_LICENSE
#-------------------------------------------------------------------------
# Module: CGClienttickets (c) 2014 by Robert Campbell
#         (calguy1000@cmsmadesimple.org)
#  An addon module for CMS Made Simple to allow interaction with customer
#  or admin created tickets about a specific data item.
#
#-------------------------------------------------------------------------
# CMS - CMS Made Simple is (c) 2005 by Ted Kulp (wishy@cmsmadesimple.org)
# This projects homepage is: http://www.cmsmadesimple.org
#
#-------------------------------------------------------------------------
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# However, as a special exception to the GPL, this software is distributed
# as an addon module to CMS Made Simple.  You may not use this software
# in any Non GPL version of CMS Made simple, or in any version of CMS
# Made simple that does not indicate clearly and obviously in its admin
# section that the site was built with CMS Made simple.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# Or read it online: http://www.gnu.org/licenses/licenses.html#GPL
#
#-------------------------------------------------------------------------
#END_LICENSE

/**
 * This file defines the ticket class.
 *
 * @package CGClientTickets
 * @category Application
 * @author  calguy1000 <calguy1000@cmsmadesimple.org>
 * @copyright Copyright 2010 by Robert Campbell
 */

namespace CGClientTickets;

/**
 * Each message is tied to a ticket.  It has a sender, and a group, and regular message components. Also one or more files can be attached to a message.
 *
 * @property-read int $id The message id.
 * @property int $ticket_id The ticket that this message is associated with.
 * @property int $sender The person who sent the message.  Positive values indicate an FEU uid.  Negative values indicate an admin uid.
 * @property int $dest   The person who will receive the message. Positive values indicate an FEU uid.  Negative value indicates a CMSMS admin group id.
 * @property string $subject The subject of the message.
 * @property string $body The body of the message, which can contain HTML text.
 * @property-read string int $sent The unix timestamp of the time when this message was sent.
 * @property-read int $viewed The unix timestamp of the time when this message was viewed.
 * @property-read int $viewed-by The person who viewed the message.  Positive values indicate an FEU uid.  Negative values indicate an admin uid.
 * @property-read int $created The date that this message was first saved to the database.
 * @property-read int $modified The date that this message was last saved to th the database.
 */
class message extends DataObject
{
    /**
     * @ignore
     */
    static private $_persist_actor;

    /**
     * @ignore
     */
    static private $_notify_actor;

    /**
     * @ignore
     */
    private $_data = array('id'=>null,'ticket_id'=>null,'sender'=>null,'dest'=>null,'subject'=>null,'body'=>null,'extra'=>null,'viewed'=>null,'viewed_by'=>null,
                           'admin_viewed'=>null,'sent'=>null,'created'=>null,'modified'=>null);

    /**
     * @ignore
     */
    private $_dirty;

    /**
     * @ignore
     */
    private $_attach_queue = array();

    /**
     * @ignore
     */
    public function __get($key)
    {
        switch( $key ) {
        case 'id':
        case 'ticket_id':
        case 'sender':
        case 'dest':
        case 'subject':
        case 'body':
        case 'sent':
        case 'viewed':
        case 'viewed_by':
        case 'admin_viewed':
        case 'created':
        case 'modified':
            return $this->_data[$key];
            break;

        case 'extra':
            return serialize($this->_data['extra']);

        default:
            throw new \LogicException($key.' is an invalid member of '.__CLASS__);
        }
    }

    /**
     * @ignore
     */
    public function __set($key,$val)
    {
        switch( $key ) {
        case 'ticket_id':
        case 'sender':
        case 'dest':
            $val = (int) $val;
            $this->_data[$key] = $val;
            $this->_dirty = TRUE;
            break;

        case 'subject':
        case 'body':
            $val = trim($val);
            $this->_data[$key] = $val;
            $this->_dirty = TRUE;
            break;

        case 'extra':
            $this->_data[$key] = unserialize($val);
            $this->_dirty = TRUE;
            break;

        case 'viewed':
        case 'viewed_by':
        case 'admin_viewed':
        case 'sent':
        case 'id':
        case 'created':
        case 'modified':
            throw new \LogicException($key.' cannot be modified in a '.__CLASS__);

        default:
            throw new \LogicException($key.' is an invalid member of '.__CLASS__);
        }
    }

    /**
     * @ignore
     */
    public function __isset($key)
    {
        switch( $key ) {
        case 'id':
        case 'ticket_id':
        case 'sender':
        case 'dest':
        case 'subject':
        case 'body':
        case 'viewed':
        case 'viewed_by':
        case 'admin_viewed':
        case 'sent':
        case 'created':
        case 'modified':
            return isset($this->_data[$key]);
            break;

        default:
            throw new \LogicException($key.' is an invalid member of '.__CLASS__);
        }
    }

    /**
     * @ignore
     */
    public function __unset($key)
    {
        switch( $key ) {
        case 'ticket_id':
        case 'sender':
        case 'dest':
        case 'subject':
        case 'body':
            $this->_data[$key] = null;
            $this->_dirty = TRUE;
            break;

        case 'viewed':
        case 'viewed_by':
        case 'admin_viewed':
        case 'sent':
        case 'id':
        case 'created':
        case 'modified':
            throw new \LogicException($key.' cannot be modified in a '.__CLASS__);

        default:
            throw new \LogicException($key.' is an invalid member of '.__CLASS__);
        }
    }

    /**
     * Validate this message before it is saved to the database.
     * This method will throw exceptions if the content of the message are invalid.
     */
    public function validate()
    {
        if( $this->ticket_id < 1 ) throw new \CmsInvalidDataException('Ticket id must be valid for a '.__CLASS__);
        if( $this->sender == 0 ) throw new \CmsInvalidDataException('Sender id must be valid for a '.__CLASS__);
        if( $this->dest == 0 ) throw new \CmsInvalidDataException('destination id must be valid for a '.__CLASS__);
        if( trim(strip_tags($this->body)) == '' )  throw new \CmsInvalidDataException('Please specify some valid message content');
    }

    //
    // I/O methods
    //

    /**
     * Get the object that has the responsibility of saving this object.
     *
     * @return Actor\PersistActor
     */
    public static function get_persist_actor()
    {
        return self::$_persist_actor;
    }

    /**
     * Set the object that has responsibility of saving and reading this object from a persistent store.
     *
     * @param Actor\PersistActor $actor
     */
    public static function set_persist_actor(Actor\PersistActor $actor)
    {
        self::$_persist_actor = $actor;
    }

    /**
     * Get the object that has responsibility for sending notifications related to this message.
     *
     * @return Actor\NotifyActor;
     */
    public static function get_notify_actor()
    {
        return self::$_notify_actor;
    }

    /**
     * Set the object that has responsibility for sending notifications related to this message.
     *
     * @return Actor\NotifyActor $actor
     */
    public static function set_notify_actor(Actor\NotifyActor $actor)
    {
        self::$_notify_actor = $actor;
    }

    /**
     * Set this message as viewed.
     *
     * @param int $uid The userid of the individual that viewed the message.  A positive value indicates an FEU user id.  A negative value indicates an admin user id.
     */
    public function set_viewed($uid)
    {
        if( $uid == 0 ) throw new \LogicException('Invalid UID');
        if( $uid < 0 ) {
            $this->_data['admin_viewed'] = time();
        }
        else {
            $this->_data['viewed'] = time();
        }
        $this->_data['viewed_by'] = $uid;
        $this->_dirty = TRUE;
    }

    /**
     * Load a message with the specified id.
     * This utilizes the persist actor.
     *
     * @param int $id The message id.
     * @return message
     */
    public static function load_by_id($id)
    {
        $obj = self::get_persist_actor()->load_by_id($id);
        return $obj;
    }

    /**
     * Fill this object from array data.
     *
     * @internal
     * @param array $data
     */
    public function private_fill($data)
    {
        foreach( $data as $key => $val ) {
            if( array_key_exists($key,$this->_data) ) {
                switch( $key ) {
                case 'extra':
                    $val = unserialize($val);
                default:
                    $this->_data[$key] = $val;
                }
            }
        }
    }

    /**
     * Save the object.
     * This utilizes the persist actor.
     *
     * After saving the object, any queued attachments will be copied into the appropriate attachment directory.
     */
    public function save()
    {
        if( !$this->_dirty ) return;
        $this->validate();
        self::get_persist_actor()->save($this);

        $tmp = self::get_persist_actor()->get_saved_id();
        if( $this->id > 0 && $this->id != $tmp ) throw new \RuntimeException('Error: problem with saved object id');
        $this->_data['id'] = $tmp;

        if( is_array($this->_attach_queue) && count($this->_attach_queue) ) {
            foreach( $this->_attach_queue as $attach ) {
                $this->attach_file($attach[0],$attach[1]);
            }
            $this->_attach_queue = array();
        }

        $mod = \cms_utils::get_module(MOD_CGCLIENTTICKETS);
        $mod->SendEvent($mod::EVENT_NEWTICKETMSG,array('message'=>$this));

        $this->_dirty = FALSE;
    }

    /**
     * Delete this message object.
     * This utilizes the persist actor.
     *
     * An exception will be thrown if the object has not already been saved to the database.
     * After deletion this message will be ready for saving again (with a new id).
     */
    public function delete()
    {
        if( $this->id < 1 ) throw new \RuntimeException('Error: Cannot delete a message that has not been saved');

        $mod = \cms_utils::get_module(MOD_CGCLIENTTICKETS);
        $mod->SendEvent($mod::EVENT_DELETETICKETMSG,array('message'=>$this));

        self::get_persist_actor()->delete($this);
        $this->clear_attachments();
        $this->_data['id'] = null;
        $this->_dirty = TRUE;
    }

    //
    // notify methods
    //

    /**
     * Send this message to the appropriate recipients.
     * This utilizes the notify actor.
     */
    public function send()
    {
        $actor = self::get_notify_actor();
        if( !is_object( $actor ) ) throw new \RuntimeException('No notify actor specified... messages cannot be sent');
        $actor->notify($this);
        $this->_data['sent'] = time();
        $this->_dirty = TRUE;
        $this->save();
    }

    //
    // attachment methods
    //

    /**
     * Get the attachment path.
     *
     * @return string
     */
    public function get_attachment_path()
    {
        if( $this->id < 1 ) throw new \RuntimeException('Cannot associate files with a message that has not been saved');
        $config = cmsms()->GetConfig();
        $mod = \cms_utils::get_module(MOD_CGCLIENTTICKETS);
        $path = cms_join_path($config['uploads_path'],'__'.$mod->GetName(),'__'.$this->id);
        return $path;
    }

    /**
     * Test if the current message has attachments.
     *
     * @return bool
     */
    public function has_attachments()
    {
        $tmp = $this->get_attachments();
        if( count($tmp) ) return TRUE;
        return FALSE;
    }

    /**
     * Return any filenames attached to this message.
     *
     * Attachments for a message are stored in a single directory named with respect to the message id.
     * Therefore the message needs to have been saved at least once before a file can be attached.
     *
     * @return string[]
     */
    public function get_attachments()
    {
        $path = $this->get_attachment_path();
        if( !is_dir($path) ) return;
        if( !is_readable($path) ) throw new \RuntimeException('Attachment directory not readable.');

        $out = array();
        $files = glob($path.'/*');
        for( $i = 0; $i < count($files); $i++ ) {
            $bn = basename($files[$i]);
            $out[] = $bn;
        }
        if( count($out) ) return $out;
    }

    /**
     * Permanently remove any files attached to this message.
     */
    public function clear_attachments()
    {
        $path = $this->get_attachment_path();
        if( !is_dir($path) ) return;
        if( !is_writable($path) ) throw new \RuntimeException('Cannot write to attachment directory '.$path);
        \cge_dir::recursive_rmdir($path);
    }

    /**
     * Queue a file for attaching to this message after the message has been saved.
     *
     * @param string $filename The complete path to the file to attach.
     * @param string $destname An optional destination filename for the attachment.
     */
    public function queue_attach($filename,$destname = null)
    {
        if( !$destname ) $destname = basename($filename);
        $destname = basename($destname);

        if( is_array($this->_attach_queue) ) {
            foreach( $this->_attach_queue as &$row )  {
                if( $row[0] == $filename ) {
                    $row[1] = $destname;
                    return; // already in list.
                }
            }
        }

        $this->_attach_queue[] = array($filename,$destname);
    }

    /**
     * Attach a file to the message.
     * The message must have already been saved at least once.
     *
     * @param string $filename The complete path to the file to attach.
     * @param string $destname An optional destination filename for the attachment.
     */
    public function attach_file($filename,$destname = null)
    {
        if( !@is_file($filename) || !@is_readable($filename) ) throw new \RuntimeException('Invalid filename specified '.$filename);
        if( !$destname ) $destname = basename($filename);
        $destname = basename($destname);

        $destpath = $this->get_attachment_path();
        if( !is_dir($destpath) ) \cge_dir::mkdirr($destpath);

        $destname = cms_join_path($destpath,basename($destname));
        if( file_exists($destname) ) {
            // its an overwrite
            if( !is_writable($destname) ) throw new \RuntimeException('Cannot overwrite file: '.$destname.' no write permission');
        }

        @rename($filename,$destname);
    }


} // end of class

#
# EOF
#
?>
