artabro/wire/modules/System/SystemNotifications/Notification.php
2024-08-27 11:35:37 +02:00

447 lines
12 KiB
PHP

<?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;
}
}