<?php
/*
* SCROTT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to UNLICENSE
*/
require_once "class/obj.class.php";
require_once "class/user.class.php";
require_once "class/pad.class.php";
require_once "class/stage.class.php";
require_once "class/issue.class.php";
/*
* This class models issue activity, private messaging, pad discussions,
* and system and object log messages.
*/
class mesg extends obj
{
/*
* Constants used for uploading attachments
*/
public const ATTACH_MAXSIZE = 536870912; // 512Mb
/*
* Constructor
*/
public function __construct(?string $guid = NULL)
{
$this->fields['mesgs'] = array(
"guid",
"author",
"mesg",
"attachment",
);
parent::__construct($guid);
try
{
$this->expectType("log");
}
catch (Exception $e)
{
$this->expectType("mesg");
}
}
/*
* Initialize a new regular message.
*/
public static function initNew(string $message, user $author, obj $parent) : mesg
{
$mesg = new mesg();
$mesg->setOwner($author);
$mesg->setParent($parent);
$mesg->name = "message " . $mesg->guid;
$mesg->objtype = "mesg";
$mesg->setAuthor($author);
$mesg->mesg = $message;
$mesg->saveObj();
return $mesg;
}
/*
* Initialize a new pad discussion thread.
*/
public static function initNewDiscussion(string $name, string $message, user $author,
pad $parent) : mesg
{
$mesg = new mesg();
$mesg->setOwner($author);
$mesg->setParent($parent);
$mesg->name = $name;
$mesg->objtype = "mesg";
$mesg->setAuthor($author);
$mesg->mesg = $message;
$mesg->saveObj();
return $mesg;
}
/*
* Initialize a new private message (user-to-user).
*/
public static function initNewPM(string $name, string $message, user $author,
user $rcpt) : mesg
{
$mesg = new mesg();
$mesg->setOwner($rcpt);
$mesg->setParent($rcpt);
$mesg->name = $name;
$mesg->objtype = "mesg";
$mesg->setAuthor($author);
$mesg->mesg = $message;
$mesg->saveObj();
return $mesg;
}
/*
* Initialize a new log message.
*/
public static function initNewLog(string $message, user $author, obj $parent) : mesg
{
$owner = $parent->getOwner();
if (!$owner)
$owner = $parent;
$mesg = new mesg();
$mesg->setOwner($owner);
$mesg->setParent($parent);
$mesg->name = "log " . $mesg->guid;
$mesg->objtype = "log";
$mesg->setAuthor($author);
$mesg->mesg = $message;
$mesg->saveObj();
return $mesg;
}
/*
* Initialize a new admin log message.
*/
public static function initNewAdminLog(string $message, user $author) : mesg
{
$mesg = new mesg();
$mesg->saveObj(); // need to get a guid
$mesg->name = "admin log " . $mesg->guid;
$mesg->objtype = "log";
$mesg->setAuthor($author);
$mesg->mesg = $message;
$mesg->saveObj();
return $mesg;
}
/*
* Get an array of messages in the admin log, between two timestamps.
* With no arguments, the entire log is returned. Results are sorted
* in reverse chronological order.
*/
public static function getAdminLog_ordByDatetime(?string $after = NULL,
?string $before = NULL) : array
{
$query = "SELECT guid FROM objects WHERE objtype = 'log' AND " .
"parent = '' ";
if ($after)
$query += "AND created >= '" . database::esc($after) . "' ";
if ($before)
$query += "AND created < '" . database::esc($before) . "' ";
$query += "ORDER BY created DESC";
$res = database::query($query);
$log = array();
foreach ($res as $l)
$log[] = new mesg($l['guid']);
return $log;
}
/*
* Get the author of this message. This user either actually wrote
* this message, or caused it to be generated.
*/
public function getAuthor() : user
{
if (!isset($this->author) || $this->author == "")
return NULL;
return new user($this->author);
}
/*
* Set the author of this message. This should usually only be done
* while constructing a new message or to clear out references to
* a user that got removed.
*/
public function setAuthor(user $author) : void
{
$this->author = $author->guid;
$this->saveObj();
}
/*
* Check whether this message has been seen by the given user
*/
public function seenBy(user $viewer) : bool
{
$query = "SELECT * FROM views WHERE guid = '" . database::esc($this->guid) . "' " .
"AND viewer = '" . database::esc($viewer->guid) . "'";
$res = database::query($query);
return count($res) > 0;
}
/*
* Mark this message as seen by the given user
*/
public function markSeen(user $viewer) : void
{
if (!$this->seenBy($viewer))
{
$query = "INSERT INTO views (guid, viewer) VALUES ('" .
database::esc($this->guid) . "', '" . database::esc($viewer->guid) . "')";
database::query($query);
}
}
/*
* Format message (if auto generated), and fixup content for display in HTML
*/
public function renderMesg() : string
{
if ($this->objtype == "log")
{
$mesg = sprintf($this->mesg, $this->getAuthor()->getDisplayName());
}
else
{
$mesg = nl2br(str_replace(" ", " ", $this->mesg));
$mesg = preg_replace('/^(\s*>.*)$/m', '<span class="text-success">${1}</span>', $mesg);
}
return $mesg;
}
/*
* Get the URL to this message's attachment. If no file is attached,
* NULL is returned.
*/
public function getAttachment() : ?string
{
if (!is_file("dynmic/attach/" . $this->guid))
return NULL;
return ar() . "/df.php?d=attach&f=" . $this->guid;
}
/*
* Set the attached file for this message. Should typically be done
* during new message creation and not changed afterward. Returns
* false if there is a problem saving the attachment.
*/
public function setAttachment(string $file) : bool
{
$path = "dynmic/attach/" . $this->guid;
$origName = "";
$ret = saveIfFile($file, $path, self::ATTACH_MAXSIZE, NULL, $origName);
$this->attachment = $origName;
$this->saveObj();
return $ret;
}
/*
* Promote a pad discussion thread to an issue. This message object
* must be the top-level message (op) of the discussion thread (ie:
* its parent must be a pad). All reply messages to this one are
* retained and will be messages left on the new issue. A new issue
* object is created and this message object will be destroyed. If
* this is not an eligible message for promotion, NULL is returned.
*/
public function makeIssue(stage $parent) : ?issue
{
if ($this->getParent()->objtype != "pad")
return NULL;
$issue = issue::initNew($this->name, $this->getOwner(), $parent);
$issue->created = $this->created;
$issue->description = $this->mesg;
$issue->saveObj();
foreach ($this->getMesgs_ordByDatetime() as $mesg)
$mesg->setParent($issue);
$this->delObj();
return $issue;
}
/*
* Email this message to parents, owners, members. In the case that
* this is an issue message or a reply message, the assignee or original
* author is also included. Attachments are included in mailing. Any
* duplicates in the rcpt list are removed before sending. Success
* or failure is returned.
*/
public function emailMesg() : bool
{
$parent = $this->getParent();
if (!$parent)
return true;
$rcpt = $parent->getMembers();
$rcpt[] = $parent->getOwner();
switch ($parent->objtype)
{
case "user":
$rcpt[] = $parent;
$subj = $this->author . " " . $this->getAuthor()->getDisplayName() . " // " . $this->name;
break;
case "issue":
$rcpt[] = $parent->getAssignee();
$pad = $parent->getPad();
$subj = $parent->guid . " " . $pad->name . " [#" . $parent->numb . "] " . $parent->name;
break;
case "mesg":
case "log":
$rcpt[] = $parent->getAuthor();
$pad = $parent->getParent();
$subj = $parent->guid . " " . $pad->name . " // " . $parent->name;
break;
}
$rcpt = obj::arrayUnique($rcpt);
$author = $this->author;
$rcpt = array_filter($rcpt, function ($val) use($author) { return $val->guid != $author; });
$attachPath = ($this->getAttachment() ? "dynmic/attach/" . $this->guid : NULL);
$mesg = $this->getAuthor()->getDisplayName() . " wrote on " . $this->created . "\n\n";
$mesg .= $this->renderMesg();
foreach ($rcpt as $r)
{
if (!$r->sendEmail($subj, $mesg, $attachPath, $this->attachment))
return false;
}
return true;
}
}
?>