Subversion Repositories web.active

Rev

Blame | Last modification | View Log | Download

<?php namespace ProcessWire;

/**
 * An individual notification item to be part of a NotificationArray for a Page
 * 
 * @class Notification
 * 
 * @property int $pages_id  page ID notification is for (likely a User page)
 * @property int $sort  sort value, as required by Fieldtype
 * @property int $src_id  page ID when notification was generated
 * @property string $title  title/headline
 * @property int $flags  flags: see flag constants 
 * @property int $created  datetime created (unix timestamp)
 * @property int $modified  datetime created (unix timestamp)
 * @property int $qty  quantity of times this notification has been repeated
 * @property array $flagNames Notification flag names
 * 
 * data encoded vars, all optional
 * ===============================
 * @property int $id  unique ID (among others the user may have)
 * @property string $text  extended text
 * @property string $html  extended text as HTML markup 
 * @property string $from  "from" text where applicable, like a class name
 * @property string $icon  fa-icon when applicable
 * @property string $href  clicking notification goes to this URL
 * @property int $progress  progress percent 0-100
 * @property int $expires  datetime after which will automatically be deleted
 *
 */
class Notification extends WireData {

    /**
     * Flag constants for Notification objects
     * 
     * Note that flags 2-32 line up with the same flags from Notice objects
     * 
     */

    const flagDebug = 2;        // Show/save only when the system is in debug mode
    const flagLog = 8;          // save to log and show
    const flagLogOnly = 16;     // save to log but don't show
    const flagAllowMarkup = 32; // allow markup in the title
    
    const flagMessage = 64;     // informational
    const flagWarning = 4;      // warning 
    const flagError = 128;      // error 
    
    const flagNotice = 256;     // Show only as a single-request notice (not stored in DB)
    const flagSession = 512;    // Notification lasts for only this session (not stored in DB)
    const flagEmail = 1024;     // title and body will also be emailed to user (if page is user)
    const flagOpen = 2048;      // notification will automatically open the text/html area (no click required)
    
    const flagNoGhost = 4096;   // disable showing of a notification ghost
    const flagAnnoy = 8192;     // rather than just update bug counter, notification will pop up at top of screen
    const flagShown = 16384;    // has this flag once the notification has been sent to the UI at least once
    const flagAlert = 32768;    // show an alert that requires acknowledgement (use with flagSession only)

    /**
     * Provides a name for each of the flags
     * 
     * @var array
     * 
     */
    static protected $_flagNames = array(
        self::flagDebug => 'debug',
        self::flagLog => 'log', 
        self::flagLogOnly => 'log-only', 
        self::flagAllowMarkup => 'markup', 
        self::flagMessage => 'message',
        self::flagWarning => 'warning',
        self::flagError => 'error',
        self::flagNotice => 'notice',
        self::flagSession => 'session',
        self::flagEmail => 'email',
        self::flagOpen => 'open',
        self::flagNoGhost => 'no-ghost', 
        self::flagAnnoy => 'annoy',
        self::flagShown => 'shown', 
        self::flagAlert => 'alert', 
        );

    /**
     * Page that this Notification belongs to
     * 
     * @var Page
     * 
     */
    protected $page; 

    /**
     * Construct a new Notification
     *
     */
    public function __construct() {

        // db native vars
        $this->set('pages_id', 0);  // page ID notification is for (likely a User page)
        $this->set('sort', 0);      // sort value, as required by Fieldtype
        $this->set('src_id', 0);    // page ID when notification was generated
        $this->set('title', '');    // title/headline
        $this->set('flags', 0);     // flags: see flag constants 
        $this->set('created', 0);   // datetime created (unix timestamp)
        $this->set('modified', 0);  // datetime created (unix timestamp)
        $this->set('qty', 1);       // quantity of times this notification has been repeated

        // data encoded vars, all optional
        $this->set('id', '');       // unique ID (among others the user may have)
        $this->set('text', '');     // extended text
        $this->set('html', '');     // extended text as HTML markup 
        $this->set('from', '');     // "from" text where applicable, like a class name
        $this->set('icon', '');     // fa-icon when applicable
        $this->set('href', '');     // clicking notification goes to this URL
        $this->set('progress', 0);  // progress percent 0-100
        $this->set('expires', 0);   // datetime after which will automatically be deleted
        
    }

    /*
     * Fluent interface methods
     * 
     */

    public function title($value) { return $this->set('title', $value); }
    public function text($value) { return $this->set('text', $value); }
    public function html($value) { return $this->set('html', $value); }
    public function from($value) { return $this->set('from', $value); }
    public function icon($value) { return $this->set('icon', $value); }
    public function href($value) { return $this->set('href', $value); }
    public function progress($value) { return $this->set('progress', $value); }
    public function expires($value) { return $this->set('expires', $value); }
    public function flag($value) { return $this->setFlag($value, true); }
    public function flags($value) { return $this->setFlags($value, true); }

    /**
     * Does this Notification match the given flag name(s)?
     * 
     * @param string $name
     * @return bool
     * 
     */
    public function is($name) {
        $flags = $this->flagNamesToFlags($name); 
        $is = 0;
        foreach($flags as $flag) { 
            if($this->flags & $flag) $is++;
        }
        return $is == count($flags); 
    }

    /**
     * Given a flag name, return the corresponding flag value
     * 
     * @param string $name
     * @return int mixed
     * @throws WireException if given unknown flag
     * 
     */
    protected function flagNameToFlag($name) {
        if(is_string($name)) {
            $flag = array_search($name, self::$_flagNames); 
            if(!$flag) throw new WireException("Unknown flag: $name"); 
        } else {
            $flag = $name;
            if(!isset(self::$_flagNames[$flag])) throw new WireException("Unknown flag: $flag"); 
        }
        return $flag;
    }

    /**
     * Given multiple space separated flag names, return array of flag values
     * 
     * @param string $names space separted, will also accept CSV
     * @return array of flag name => flag value
     * 
     */
    protected function flagNamesToFlags($names) {
        if(strpos($names, ',') !== false) $names = str_replace(',', ' ', $names); 
        $names = explode(' ', $names); 
        $flags = array();
        foreach($names as $name) {
            if(empty($name)) continue; 
            $flag = $this->flagNameToFlag($name); 
            if($flag) $flags[$name] = $flag;
        }
        return $flags; 
    }

    /**
     * Set a named flag
     *
     * @param string|int $name Flag to set
     * @param bool $add True to add flag, false to remove
     * @return self
     *
     */
    public function setFlag($name, $add = true) {

        $flag = ctype_digit("$name") ? (int) $name : $this->flagNameToFlag($name);  
        $flags = parent::get('flags'); 

        if($add) {
            // add flag
            if($flags & $flag) {
                // flag is already set
            } else {
                $flags = $flags | $flag;
                parent::set('flags', $flags); 
            }
        } else {
            // remove flag
            if($flags & $flag) {
                // flag is set, remove it
                $flags = $flags & ~$flag;
                parent::set('flags', $flags); 
            } else {
                // flag is not set
            }
        }

        return $this; 
    }

    /**
     * Add the given flag name(s) (shortcut for setFlag)
     * 
     * @param string $name One or more space-separated flag names
     * @return self
     * 
     */
    public function addFlag($name) {
        return $this->setFlags($name, true); 
    }

    /**
     * Remove the given flag name(s) (shortcut for setFlag)
     * 
     * @param string $name One or more space-separated flag names
     * @return self
     * 
     */
    public function removeFlag($name) {
        return $this->setFlags($name, false); 
    }

    /**
     * Set multiple flags
     * 
     * @param string $names space separated string of flag names
     * @param bool $add True to add, false to remove
     * @return self
     * 
     */
    public function setFlags($names, $add = true) {
        
        if(ctype_digit("$names")) {
            // likely a flag or combined flags in bitmask
            $flags = (int) $names;
            // iterate through known flags to see which are set
            foreach(self::$_flagNames as $flag => $name) {
                // if it's a recognized/valid flag, set it 
                if($flags & $flag) $this->setFlag($flag, $add); 
            }
            return $this;
        }
    
        // optimization if this was called with just one flag name
        if(strpos($names, ',') === false && strpos($names, ' ') === false) {
            $this->setFlag($names, $add); 
        }
    
        // named flags
        $flags = $this->flagNamesToFlags($names); 
        foreach($flags as $name => $flag) {
            $this->setFlag($flag, $add);    
        }
        
        return $this; 
    }

    /**
     * Set a value to the Notification
     * 
     * Note: setting the 'expires' value accepts either a future date, or a quantity of seconds 
     * in the future relative to now. 
     * 
     * @param string $key
     * @param mixed $value
     * @return self|Notification|WireData
     *
     */
    public function set($key, $value) {
 
        if($key == 'page') {
            $this->page = $value; 
            return $this; 

        } else if($key == 'created' || $key == 'modified' || $key == 'expires') {
            // convert date string to unix timestamp
            if($value && !ctype_digit("$value")) $value = strtotime($value);    

            // sanitized date value is always an integer
            $value = (int) $value; 
            
            if($key == 'expires' && $value > 0 && $value < strtotime("-10 YEARS")) {
                // assume this is a time relative to now
                $value = time() + $value;
            }

        } else if($key == 'title') {
            if($this->flags & self::flagAllowMarkup) {
                // accept value as-is
            } else {
                // regular text sanitizer
                $value = $this->sanitizer->text($value);
            }
                
        } else if($key == 'from') {
            // regular text sanitizer
            $value = $this->sanitizer->text($value); 

        } else if($key == 'text') {
            // regular text sanitizer
            $value = $this->sanitizer->textarea($value); 

        } else if(in_array($key, array('pages_id', 'sort', 'src_id', 'flags', 'progress'))) {
            $value = (int) $value; 
        }

        return parent::set($key, $value); 
    }

    /**
     * Return an ID string/hash unique to this Notification within the page that its on
     * 
     * The text/html, modified date, expires date, and icon may change without affecting the id. 
     * 
     * @return mixed|null|string
     * 
     */
    public function getID() {

        $id = parent::get('id'); 
        if($id) return $id; 

        $id =   parent::get('title') . ',' . 
                parent::get('created') . ',' . 
                parent::get('from') . ',' . 
                parent::get('src_id') . ',' . 
                ($this->page ? $this->page->id : '?'); // . ',' . 
                //parent::get('flags');

        return 'noID' . md5($id); 
    }
    
    /**
     * Return an string hash for comparing other notifications to see if they contain the same content
     * 
     * Hash specifically excludes consideration of dates (created, modified, expires)
     *
     * @return string
     *
     */
    public function getHash() {

        $id =   trim(parent::get('title')) . ',' .
                // parent::get('from') . ',' .
                // parent::get('src_id') . ',' .
                // ($this->page ? $this->page->id : '?') . ',' . 
                // parent::get('flags') . ',' . 
                // parent::get('icon') . ',' . 
                trim(parent::get('text')) . ',' . 
                trim(parent::get('html'));

        return md5($id);
    }

    /**
     * Retrieve a value from the Notification
     * 
     * @param string $key
     * @return mixed
     *
     */
    public function get($key) {

        if($key == 'id') return $this->getID();
        if($key == 'page') return $this->page; 
        if($key == 'hash') return $this->getHash();

        if($key == 'flagNames') {
            $flags = parent::get('flags');
            $flagNames = array();
            foreach(self::$_flagNames as $val => $name) {
                if($flags & $val) $flagNames[$val] = $name;
            }
            return $flagNames;
        }

        $value = parent::get($key); 

        // if the page's output formatting is on, then we'll return formatted values
        if($this->page && $this->page->of()) {

            if($key == 'created' || $key == 'expires' || $key == 'modified') {
                // format a unix timestamp to a date string
                $value = date('Y-m-d H:i:s', $value);               

            } else if($key == 'title' || $key == 'text' || $key == 'from') {
                // return entity encoded versions of strings
                if($key == 'title' && ($this->flags & self::flagAllowMarkup)) {
                    // leave title alone when markup is allowed
                } else {
                    $value = $this->sanitizer->entities($value); 
                }
            }
        } else {
            if($key == 'created' && !$value) $value = time();
        }

        return $value; 
    }

    /**
     * Is this Notification expired?
     * 
     * @return bool
     * 
     */
    public function isExpired() {
        return ($this->expires > 0 && $this->expires <= time()); 
    }

    /**
     * String value of a Notification
     * 
     * @return string
     * 
     */
    public function __toString() {
        $str = $this->title; 
        $str .= " (" . implode(', ', $this->get('flagNames')) . ")";
        return $str; 
    }


}