You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

428 lines
12 KiB
PHP

<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class Note {
private $noteid;
private $ownerid;
private $content = "";
private $title = "";
private $modified = "";
private $color = "FFFFFF";
private $favorite = false;
/**
* Create a new Note object.
* @param string $content Note content in Markdown
* @param string $color Hex color "RRGGBB"
* @param int $ownerid The owner's user ID
*/
public function __construct(string $content, string $color = "FFFFFF", int $ownerid = null, int $noteid = null) {
$this->content = $content;
$this->color = $color;
$this->ownerid = $ownerid;
$this->noteid = $noteid;
}
/**
* Load a note from the database and return it.
* @global type $database
* @param int $noteid
* @return \Note
* @throws NoSuchNoteException When the note ID isn't found.
*/
public static function loadNote(int $noteid): Note {
global $database;
if (!$database->has('notes', ['noteid' => $noteid])) {
throw new NoSuchNoteException();
}
$notedata = $database->get('notes', ['noteid', 'ownerid', 'color', 'content', 'title', 'modified', 'favorite'], ['noteid' => $noteid]);
$note = new Note($notedata['content'], $notedata['color'], $notedata['ownerid'], $notedata['noteid']);
//$note->setTitle(is_null($notedata['title']) ? "" : $notedata['title']);
$note->setModified(is_null($notedata['modified']) ? date("Y-m-d H:i:s") : $notedata['modified']);
$note->setFavorite($notedata['favorite'] == true);
return $note;
}
/**
* Save the note to the database.
* @global type $database
* @param bool $saveas If true, save the note under a new ID. Forced to
* true if the current note ID is missing or invalid.
* @return int The database ID of the saved note
* @throws Exception If there is no note owner set.
*/
public function saveNote(bool $saveas = false): int {
global $database;
$data = [
'ownerid' => $this->getOwnerID(),
'color' => $this->getColor(),
'content' => $this->getText(),
'title' => $this->getTitle(),
'modified' => $this->getModified(),
'favorite' => $this->getFavorite() ? 1 : 0
];
// We can't UPDATE the database, so use save as for INSERT
if (empty($this->noteid) || !$database->has('notes', ['noteid' => $this->noteid])) {
$saveas = true;
}
if (empty($this->ownerid)) {
throw new Exception("No owner set.");
}
if ($saveas) {
$database->insert('notes', $data);
$this->noteid = $database->id();
} else {
$database->update('notes', $data, ['noteid' => $this->noteid]);
}
return $this->noteid;
}
/**
* Delete this note from the database.
* @global type $database
*/
public function deleteNote() {
global $database;
$database->delete('notes', ['noteid' => $this->noteid]);
}
/**
* Get the Markdown content of the note.
* @return string
*/
public function getText(): string {
return $this->content;
}
/**
* Get the HTML render of the note.
* @param bool $fragment Get just the HTML content, instead of a whole HTML5 file
* @return string
*/
public function getHTML(bool $fragment = true): string {
$parsedown = new ParsedownCheckbox();
$html = $parsedown->text($this->content);
$safehtml = Htmlawed::filter($html, ['safe' => 1]);
if ($fragment) {
return $safehtml;
}
$document = "<!DOCTYPE html>\n"
. "<meta charset=\"UTF-8\">\n"
. "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
. "<meta name=\"author\" content=\"" . htmlentities($this->getOwner()->getName()) . "\">\n"
. "<meta name=\"generator\" content=\"" . SITE_TITLE . "\">\n"
. "<link rel=\"schema.dcterms\" href=\"http://purl.org/dc/terms/\">\n"
. "<meta name=\"dcterms.modified\" content=\"" . date("Y-m-d", strtotime($this->getModified())) . "\">\n"
. "\n"
. "<title>" . $this->getCleanTitle() . "</title>\n"
. "\n"
. $safehtml;
return $document;
}
/**
* Get the note ID.
* @return int
*/
public function getID(): int {
return $this->noteid;
}
/**
* Get the note color as RRGGBB hex.
* @return string
*/
public function getColor(): string {
$this->setColor($this->color);
return $this->color;
}
/**
* Get the owner UID
* @return int
*/
public function getOwnerID(): int {
return $this->ownerid;
}
/**
* Get the owner as a User object
* @return \User
*/
public function getOwner(): User {
return new User($this->ownerid);
}
/**
* Get the note title
* @return string
*/
public function getTitle(): string {
global $Strings;
$title = $this->title;
if (empty($title)) {
if (empty($this->getText())) {
$title = $Strings->get("New note", false);
} else {
$title = explode("\n", $this->getText())[0];
}
}
return $title;
}
/**
* Get the note title stripped of Markdown and trimmed
* @return string
*/
public function getCleanTitle(): string {
$title = $this->getTitle();
$title = str_replace("*", "", $title);
$title = str_replace("#", "", $title);
$title = str_replace("_", "", $title);
$title = str_replace("`", "", $title);
return trim($title);
}
/**
* Get the last modified date/time as "Y-m-d H:i:s"
* @return string
*/
public function getModified(): string {
if (empty($this->modified) || strtotime($this->modified) == 0) {
return date("Y-m-d H:i:s");
}
return date("Y-m-d H:i:s", strtotime($this->modified));
}
/**
* Get if the note is favorited (starred).
* @return bool
*/
public function getFavorite(): bool {
return $this->favorite;
}
/**
* Get the text color for this note, based on the background color.
* Thanks to https://stackoverflow.com/a/8468448
* @return string
*/
public function getTextColor(): string {
$bg = $this->getColor();
$r = hexdec(substr($bg, 0, 2));
$g = hexdec(substr($bg, 2, 2));
$b = hexdec(substr($bg, 4, 2));
$contrast = sqrt(
$r * $r * .241 +
$g * $g * .691 +
$b * $b * .068
);
if ($contrast > 130) {
return '000000';
} else {
return 'FFFFFF';
}
}
/**
* Set the note content
* @param string $markdown
*/
public function setText(string $markdown) {
$this->content = $markdown;
}
/**
* Set the note color to a hex string
* @param string $color "RRGGBB"
*/
public function setColor(string $color) {
$color = strtoupper($color);
$color = str_replace("#", "", $color);
// Make sure we have a valid RRGGBB hex
if (!preg_match("/[A-F0-9]{6}/", $color)) {
$color = "FFFFFF";
}
$this->color = $color;
}
/**
* Set the owner by UID
* @param int $uid
*/
public function setOwnerID(int $uid) {
$this->ownerid = $uid;
}
/**
* Set the owner
* @param User $user
*/
public function setOwner(User $owner) {
$this->ownerid = $owner->getUID();
}
/**
* Set the note title
* @param string $title
*/
public function setTitle(string $title) {
$this->title = $title;
}
/**
* Set the last modified date/time
* @param string $datetime
*/
public function setModified(string $datetime) {
if (is_numeric($datetime)) {
$this->modified = date("Y-m-d H:i:s", $datetime);
return;
}
$this->modified = date("Y-m-d H:i:s", strtotime($datetime));
}
/**
* Set the note as favorite or not
* @param bool $favorite
*/
public function setFavorite(bool $favorite) {
$this->favorite = $favorite;
}
/**
* Convert normal lines of text to checklist items.
*/
public function toChecklist() {
$text = explode("\n", $this->getText());
for ($i = 0; $i < count($text); $i++) {
if (preg_match("/^[^\s\=\#\-<](.+)/", $text[$i])) {
if (count($text) > $i && preg_match("/^[\=-](.+)/", $text[$i + 1])) {
// Don't do it if the next line makes this one a heading
continue;
}
$text[$i] = "- [ ] " . $text[$i];
}
}
$this->setText(implode("\n", $text));
}
/**
* Toggle the checked status of a checklist item.
* @param string $item The text of the item to toggle.
*/
public function toggleChecklistItem(string $item) {
$text = explode("\n", $this->getText());
$item = trim(str_replace("\n", "", $item));
for ($i = 0; $i < count($text); $i++) {
if (!preg_match("/^- \[[Xx ]\] .*/", $text[$i])) {
continue;
}
$linecleaned = trim(preg_replace("/^- \[[Xx ]\] /", "", $text[$i]));
if ($item != $linecleaned) {
continue;
}
if (preg_match("/^- \[[Xx]\] .*/", $text[$i])) {
$text[$i] = preg_replace("/^- \[[Xx]\] /", "- [ ] ", $text[$i]);
} else if (preg_match("/^- \[ \] .*/", $text[$i])) {
$text[$i] = preg_replace("/^- \[ \] /", "- [x] ", $text[$i]);
}
}
$this->setText(implode("\n", $text));
}
/**
* Get this note as an array.
* @return string
*/
public function toArray(): array {
$owner = new User($this->ownerid);
$arr = [
'noteid' => $this->getID(),
'color' => $this->getColor(),
'content' => $this->getText(),
'html' => $this->getHTML(true),
'title' => $this->getTitle(),
'modified' => $this->getModified(),
'favorite' => $this->getFavorite(),
'owner' => [
'uid' => $owner->getUID(),
'username' => $owner->getUsername(),
'name' => $owner->getName()
]
];
return $arr;
}
/**
* Get an array suitable for returning via the Nextcloud Notes API
* https://github.com/nextcloud/notes/wiki/Notes-0.2#get-a-note
* @return array
*/
public function toNextcloud(): array {
return [
"id" => $this->getID(),
"modified" => strtotime($this->getModified()),
"title" => $this->getTitle(),
"category" => null,
"content" => $this->getText(),
"favorite" => $this->getFavorite()
];
}
/**
* Check whether the given User has read access to this note.
* @param User $user
*/
public function hasReadAccess(User $user): bool {
if ($this->getOwnerID() == $user->getUID()) {
return true;
}
return false;
}
/**
* Check whether the given User has write access to this note.
* @param User $user
*/
public function hasWriteAccess(User $user): bool {
if ($this->getOwnerID() == $user->getUID()) {
return true;
}
return false;
}
/**
* Read an array with the structure from toArray().
* @param array $arr
* @return \Note A Note constructed from the array data.
*/
public static function fromArray(array $arr): Note {
$note = new Note($arr['content'], $arr['color'], $arr['owner']['uid'], $arr['noteid']);
$note->setTitle($arr['title']);
$note->setModified($arr['modified']);
$note->setFavorite($arr['favorite']);
return $note;
}
}