f7cloud_client/apps/notes/lib/Service/NotesService.php
root 8b6a0139db f7cloud_client
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 22:59:26 +00:00

281 lines
8.4 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2016-2024 F7cloud GmbH and F7cloud contributors
* SPDX-FileCopyrightText: 2013 Bernhard Posselt <nukeawhale@gmail.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Notes\Service;
use OCP\Files\File;
use OCP\Files\FileInfo;
use OCP\Files\Folder;
use OCP\Files\NotPermittedException;
class NotesService {
private MetaService $metaService;
private SettingsService $settings;
private NoteUtil $noteUtil;
public function __construct(
MetaService $metaService,
SettingsService $settings,
NoteUtil $noteUtil,
) {
$this->metaService = $metaService;
$this->settings = $settings;
$this->noteUtil = $noteUtil;
}
public function getAll(string $userId, bool $autoCreateNotesFolder = false) : array {
$customExtension = $this->getCustomExtension($userId);
try {
$notesFolder = $this->getNotesFolder($userId, $autoCreateNotesFolder);
$data = self::gatherNoteFiles($customExtension, $notesFolder);
$fileIds = array_keys($data['files']);
// pre-load tags for all notes (performance improvement)
$this->noteUtil->getTagService()->loadTags($fileIds);
$notes = array_map(function (File $file) use ($notesFolder) : Note {
return new Note($file, $notesFolder, $this->noteUtil);
}, $data['files']);
} catch (NotesFolderException $e) {
$notes = [];
$data = [ 'categories' => [] ];
}
return [ 'notes' => $notes, 'categories' => $data['categories'] ];
}
public function getTopNotes(string $userId) : array {
$notes = $this->getAll($userId)['notes'];
usort($notes, function (Note $a, Note $b) {
$favA = $a->getFavorite();
$favB = $b->getFavorite();
if ($favA === $favB) {
return $b->getModified() - $a->getModified();
} else {
return $favA > $favB ? -1 : 1;
}
});
return $notes;
}
public function countNotes(string $userId) : int {
$customExtension = $this->getCustomExtension($userId);
try {
$notesFolder = $this->getNotesFolder($userId, false);
$data = self::gatherNoteFiles($customExtension, $notesFolder);
return count($data['files']);
} catch (NotesFolderException $e) {
return 0;
}
}
/**
* @throws NoteDoesNotExistException
*/
public function get(string $userId, int $id) : Note {
$customExtension = $this->getCustomExtension($userId);
$notesFolder = $this->getNotesFolder($userId);
$note = new Note(self::getFileById($customExtension, $notesFolder, $id), $notesFolder, $this->noteUtil);
$this->metaService->update($userId, $note);
return $note;
}
public function search(string $userId, string $search) : array {
$terms = preg_split('/\s+/', $search);
$notes = $this->getAll($userId)['notes'];
return array_values(array_filter(
$notes,
function (Note $note) use ($terms) : bool {
return $this->searchTermsInNote($note, $terms);
}
));
}
private function searchTermsInNote(Note $note, array $terms) : bool {
try {
$d = $note->getData();
$strings = [ $d['title'], $d['category'], $d['content'] ];
foreach ($terms as $term) {
if (!$this->searchTermInData($strings, $term)) {
return false;
}
}
return true;
} catch (\Throwable $e) {
return false;
}
}
private function searchTermInData(array $strings, string $term) : bool {
foreach ($strings as $str) {
if (stripos($str, $term) !== false) {
return true;
}
}
return false;
}
/**
* @throws \OCP\Files\NotPermittedException
*/
public function create(string $userId, string $title, string $category) : Note {
// get folder based on category
$notesFolder = $this->getNotesFolder($userId);
$folder = $this->noteUtil->getCategoryFolder($notesFolder, $category);
$this->noteUtil->ensureSufficientStorage($folder, 1);
// get file name
$fileSuffix = $this->settings->get($userId, 'fileSuffix');
if ($fileSuffix === 'custom') {
$fileSuffix = $this->settings->get($userId, 'customSuffix');
}
$filename = $this->noteUtil->generateFileName($folder, $title, $fileSuffix, -1);
// create file
$file = $folder->newFile($filename);
return new Note($file, $notesFolder, $this->noteUtil);
}
/**
* @throws NoteDoesNotExistException if note does not exist
*/
public function delete(string $userId, int $id) {
$customExtension = $this->getCustomExtension($userId);
$notesFolder = $this->getNotesFolder($userId);
$file = self::getFileById($customExtension, $notesFolder, $id);
$this->noteUtil->ensureNoteIsWritable($file);
$parent = $file->getParent();
$file->delete();
$this->noteUtil->deleteEmptyFolder($parent, $notesFolder);
}
public function getTitleFromContent(string $content) : string {
$content = $this->noteUtil->stripMarkdown($content);
return $this->noteUtil->getSafeTitle($content);
}
private function getNotesFolder(string $userId, bool $create = true) : Folder {
return $this->noteUtil->getOrCreateNotesFolder($userId, $create);
}
/**
* gather note files in given directory and all subdirectories
*/
private static function gatherNoteFiles(
string $customExtension,
Folder $folder,
string $categoryPrefix = '',
) : array {
$data = [
'files' => [],
'categories' => [],
];
$nodes = $folder->getDirectoryListing();
foreach ($nodes as $node) {
if ($node->getType() === FileInfo::TYPE_FOLDER && $node instanceof Folder) {
$subCategory = $categoryPrefix . $node->getName();
$data['categories'][] = $subCategory;
$data_sub = self::gatherNoteFiles($customExtension, $node, $subCategory . '/');
$data['files'] = $data['files'] + $data_sub['files'];
$data['categories'] = $data['categories'] + $data_sub['categories'];
} elseif (self::isNote($node, $customExtension)) {
$data['files'][$node->getId()] = $node;
}
}
return $data;
}
/**
* test if file is a note
*/
private static function isNote(FileInfo $file, string $customExtension) : bool {
static $allowedExtensions = ['txt', 'org', 'markdown', 'md', 'note'];
$ext = strtolower(pathinfo($file->getName(), PATHINFO_EXTENSION));
return $file->getType() === 'file' && (in_array($ext, $allowedExtensions) || $ext === $customExtension);
}
/**
* Retrieve the value of user defined files extension
*/
private function getCustomExtension(string $userId) {
$suffix = $this->settings->get($userId, 'customSuffix');
return ltrim($suffix, '.');
}
/**
* @throws NoteDoesNotExistException
*/
private static function getFileById(string $customExtension, Folder $folder, int $id) : File {
$file = $folder->getById($id);
if (!array_key_exists(0, $file) || !($file[0] instanceof File) || !self::isNote($file[0], $customExtension)) {
throw new NoteDoesNotExistException();
}
return $file[0];
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @return \OCP\Files\File
*/
public function getAttachment(string $userId, int $noteid, string $path) : File {
$note = $this->get($userId, $noteid);
$notesFolder = $this->getNotesFolder($userId);
$path = str_replace('\\', '/', $path); // change windows style path
$p = explode('/', $note->getCategory());
// process relative target path
foreach (explode('/', $path) as $f) {
if ($f == '..') {
array_pop($p);
} elseif ($f !== '') {
array_push($p, $f);
}
}
$targetNode = $notesFolder->get(implode('/', $p));
assert($targetNode instanceof \OCP\Files\File);
return $targetNode;
}
/**
* @param $userId
* @param $noteid
* @param $fileDataArray
* @throws NotPermittedException
* @throws ImageNotWritableException
* https://github.com/f7cloud/deck/blob/master/lib/Service/AttachmentService.php
*/
public function createImage(string $userId, int $noteid, $fileDataArray) {
$note = $this->get($userId, $noteid);
$notesFolder = $this->getNotesFolder($userId);
$parent = $this->noteUtil->getCategoryFolder($notesFolder, $note->getCategory());
// try to generate long id, if not available on system fall back to a shorter one
try {
$filename = bin2hex(random_bytes(16));
} catch (\Exception $e) {
$filename = uniqid();
}
$parts = explode('.', $fileDataArray['name']);
$filename .= '.' . end($parts);
if ($fileDataArray['tmp_name'] === '') {
throw new ImageNotWritableException();
}
// read uploaded file from disk
$fp = fopen($fileDataArray['tmp_name'], 'r');
$content = fread($fp, $fileDataArray['size']);
fclose($fp);
$result = [];
$result['filename'] = $filename;
$this->noteUtil->getRoot()->newFile($parent->getPath() . '/' . $filename, $content);
return $result;
}
}