1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* ownCloud - Notes |
4
|
|
|
* |
5
|
|
|
* This file is licensed under the Affero General Public License version 3 or |
6
|
|
|
* later. See the COPYING file. |
7
|
|
|
* |
8
|
|
|
* @author Bernhard Posselt <[email protected]> |
9
|
|
|
* @copyright Bernhard Posselt 2012, 2014 |
10
|
|
|
*/ |
11
|
|
|
namespace OCA\Notes\Service; |
12
|
|
|
use OCP\Files\FileInfo; |
13
|
|
|
use OCP\IL10N; |
14
|
|
|
use OCP\Files\IRootFolder; |
15
|
|
|
use OCP\Files\Folder; |
16
|
|
|
use OCA\Notes\Db\Note; |
17
|
|
|
/** |
18
|
|
|
* Class NotesService |
19
|
|
|
* |
20
|
|
|
* @package OCA\Notes\Service |
21
|
|
|
*/ |
22
|
|
|
class NotesService { |
23
|
|
|
private $l10n; |
24
|
|
|
private $root; |
25
|
|
|
/** |
26
|
|
|
* @param IRootFolder $root |
27
|
|
|
* @param IL10N $l10n |
28
|
|
|
*/ |
29
|
11 |
|
public function __construct (IRootFolder $root, IL10N $l10n) { |
30
|
11 |
|
$this->root = $root; |
31
|
11 |
|
$this->l10n = $l10n; |
32
|
11 |
|
} |
33
|
|
|
/** |
34
|
|
|
* @param string $userId |
35
|
|
|
* @return array with all notes in the current directory |
36
|
|
|
*/ |
37
|
1 |
|
public function getAll ($userId){ |
38
|
1 |
|
$notes = $this->gatherNoteFiles($this->getFolderForUser($userId)); |
39
|
1 |
|
$filesById = []; |
40
|
1 |
|
foreach($notes as $note) { |
41
|
1 |
|
$filesById[$note->getId()] = $note; |
42
|
1 |
|
} |
43
|
1 |
|
$tagger = \OC::$server->getTagManager()->load('files'); |
44
|
1 |
View Code Duplication |
if($tagger==null) { |
|
|
|
|
45
|
1 |
|
$tags = []; |
46
|
1 |
|
} else { |
47
|
|
|
$tags = $tagger->getTagsForObjects(array_keys($filesById)); |
48
|
|
|
} |
49
|
1 |
|
$notes = []; |
50
|
1 |
|
foreach($filesById as $id=>$file) { |
51
|
1 |
|
$notes[] = Note::fromFile($file, array_key_exists($id, $tags) ? $tags[$id] : []); |
52
|
1 |
|
} |
53
|
1 |
|
return $notes; |
54
|
|
|
} |
55
|
|
|
/** |
56
|
|
|
* Used to get a single note by id |
57
|
|
|
* @param int $id the id of the note to get |
58
|
|
|
* @param string $userId |
59
|
|
|
* @throws NoteDoesNotExistException if note does not exist |
60
|
|
|
* @return Note |
61
|
|
|
*/ |
62
|
3 |
|
public function get ($id, $userId) { |
63
|
3 |
|
$folder = $this->getFolderForUser($userId); |
64
|
3 |
|
return Note::fromFile($this->getFileById($folder, $id), $this->getTags($id)); |
65
|
|
|
} |
66
|
1 |
|
private function getTags ($id) { |
67
|
1 |
|
$tagger = \OC::$server->getTagManager()->load('files'); |
68
|
1 |
View Code Duplication |
if($tagger==null) { |
|
|
|
|
69
|
1 |
|
$tags = []; |
70
|
1 |
|
} else { |
71
|
|
|
$tags = $tagger->getTagsForObjects([$id]); |
72
|
|
|
} |
73
|
1 |
|
return array_key_exists($id, $tags) ? $tags[$id] : []; |
74
|
|
|
} |
75
|
|
|
/** |
76
|
|
|
* Creates a note and returns the empty note |
77
|
|
|
* @param string $userId |
78
|
|
|
* @see update for setting note content |
79
|
|
|
* @return Note the newly created note |
80
|
|
|
*/ |
81
|
2 |
|
public function create ($userId) { |
82
|
2 |
|
$title = $this->l10n->t('New note'); |
83
|
2 |
|
$folder = $this->getFolderForUser($userId); |
84
|
|
|
// check new note exists already and we need to number it |
85
|
|
|
// pass -1 because no file has id -1 and that will ensure |
86
|
|
|
// to only return filenames that dont yet exist |
87
|
2 |
|
$path = $this->generateFileName($folder, $title, "txt", -1); |
88
|
2 |
|
$file = $folder->newFile($path); |
89
|
2 |
|
return Note::fromFile($file); |
90
|
|
|
} |
91
|
|
|
/** |
92
|
|
|
* Updates a note. Be sure to check the returned note since the title is |
93
|
|
|
* dynamically generated and filename conflicts are resolved |
94
|
|
|
* @param int $id the id of the note used to update |
95
|
|
|
* @param string $content the content which will be written into the note |
96
|
|
|
* the title is generated from the first line of the content |
97
|
|
|
* @throws NoteDoesNotExistException if note does not exist |
98
|
|
|
* @return \OCA\Notes\Db\Note the updated note |
99
|
|
|
*/ |
100
|
2 |
|
public function update ($id, $content, $userId){ |
101
|
2 |
|
$notesFolder = $this->getFolderForUser($userId); |
102
|
2 |
|
$file = $this->getFileById($notesFolder, $id); |
103
|
2 |
|
$folder = $file->getParent(); |
104
|
|
|
// generate content from the first line of the title |
105
|
2 |
|
$splitContent = explode("\n", $content); |
106
|
2 |
|
$title = $splitContent[0]; |
107
|
2 |
|
if(!$title) { |
108
|
1 |
|
$title = $this->l10n->t('New note'); |
109
|
1 |
|
} |
110
|
|
|
// prevent directory traversal |
111
|
2 |
|
$title = str_replace(array('/', '\\'), '', $title); |
112
|
|
|
// remove hash and space characters from the beginning of the filename |
113
|
|
|
// in case of markdown |
114
|
2 |
|
$title = ltrim($title, ' #'); |
115
|
|
|
// using a maximum of 100 chars should be enough |
116
|
2 |
|
$title = mb_substr($title, 0, 100, "UTF-8"); |
117
|
|
|
// generate filename if there were collisions |
118
|
2 |
|
$currentFilePath = $file->getPath(); |
119
|
2 |
|
$basePath = pathinfo($file->getPath(), PATHINFO_DIRNAME); |
120
|
2 |
|
\OCP\Util::writeLog('notes', $basePath, \OCP\Util::ERROR); |
121
|
2 |
|
$fileExtension = pathinfo($file->getName(), PATHINFO_EXTENSION); |
122
|
2 |
|
$newFilePath = $basePath . '/' . $this->generateFileName($folder, $title, $fileExtension, $id); |
123
|
|
|
// if the current path is not the new path, the file has to be renamed |
124
|
|
|
if($currentFilePath !== $newFilePath) { |
125
|
|
|
$file->move($newFilePath); |
126
|
|
|
} |
127
|
|
|
$file->putContent($content); |
128
|
|
|
return Note::fromFile($file, $this->getTags($id)); |
129
|
|
|
} |
130
|
|
|
/** |
131
|
|
|
* Set or unset a note as favorite. |
132
|
|
|
* @param int $id the id of the note used to update |
133
|
|
|
* @param boolean $favorite whether the note should be a favorite or not |
134
|
|
|
* @throws NoteDoesNotExistException if note does not exist |
135
|
|
|
* @return boolean the new favorite state of the note |
136
|
|
|
*/ |
137
|
|
|
public function favorite ($id, $favorite, $userId){ |
138
|
|
|
$folder = $this->getFolderForUser($userId); |
139
|
|
|
$file = $this->getFileById($folder, $id); |
140
|
|
|
if(!$this->isNote($file)) { |
141
|
|
|
throw new NoteDoesNotExistException(); |
142
|
|
|
} |
143
|
|
|
$tagger = \OC::$server->getTagManager()->load('files'); |
144
|
|
|
if($favorite) |
145
|
|
|
$tagger->addToFavorites($id); |
146
|
|
|
else |
147
|
|
|
$tagger->removeFromFavorites($id); |
148
|
|
|
$tags = $tagger->getTagsForObjects([$id]); |
149
|
|
|
return in_array(\OC\Tags::TAG_FAVORITE, $tags[$id]); |
150
|
|
|
} |
151
|
|
|
/** |
152
|
|
|
* Deletes a note |
153
|
|
|
* @param int $id the id of the note which should be deleted |
154
|
|
|
* @param string $userId |
155
|
|
|
* @throws NoteDoesNotExistException if note does not |
156
|
|
|
* exist |
157
|
|
|
*/ |
158
|
3 |
|
public function delete ($id, $userId) { |
159
|
3 |
|
$folder = $this->getFolderForUser($userId); |
160
|
3 |
|
$file = $this->getFileById($folder, $id); |
161
|
1 |
|
$file->delete(); |
162
|
1 |
|
} |
163
|
|
|
/** |
164
|
|
|
* @param Folder $folder |
165
|
|
|
* @param int $id |
166
|
|
|
* @throws NoteDoesNotExistException |
167
|
|
|
* @return \OCP\Files\File |
168
|
|
|
*/ |
169
|
8 |
|
private function getFileById ($folder, $id) { |
170
|
8 |
|
$file = $folder->getById($id); |
171
|
8 |
|
if(count($file) <= 0 || !$this->isNote($file[0])) { |
172
|
4 |
|
throw new NoteDoesNotExistException(); |
173
|
|
|
} |
174
|
4 |
|
return $file[0]; |
175
|
|
|
} |
176
|
|
|
/** |
177
|
|
|
* @param string $userId the user id |
178
|
|
|
* @return Folder |
179
|
|
|
*/ |
180
|
11 |
|
private function getFolderForUser ($userId) { |
181
|
11 |
|
$path = '/' . $userId . '/files/Notes'; |
182
|
11 |
|
if ($this->root->nodeExists($path)) { |
183
|
11 |
|
$folder = $this->root->get($path); |
184
|
11 |
|
} else { |
185
|
|
|
$folder = $this->root->newFolder($path); |
186
|
|
|
} |
187
|
11 |
|
return $folder; |
188
|
|
|
} |
189
|
|
|
/** |
190
|
|
|
* get path of file and the title.txt and check if they are the same |
191
|
|
|
* file. If not the title needs to be renamed |
192
|
|
|
* |
193
|
|
|
* @param Folder $folder a folder to the notes directory |
194
|
|
|
* @param string $title the filename which should be used |
195
|
|
|
* @param string $extension the extension which should be used |
196
|
|
|
* @param int $id the id of the note for which the title should be generated |
197
|
|
|
* used to see if the file itself has the title and not a different file for |
198
|
|
|
* checking for filename collisions |
199
|
|
|
* @return string the resolved filename to prevent overwriting different |
200
|
|
|
* files with the same title |
201
|
|
|
*/ |
202
|
2 |
|
private function generateFileName (Folder $folder, $title, $extension, $id) { |
203
|
2 |
|
$path = $title . '.' . $extension; |
204
|
|
|
// if file does not exist, that name has not been taken. Similar we don't |
205
|
|
|
// need to handle file collisions if it is the filename did not change |
206
|
2 |
|
if (!$folder->nodeExists($path) || $folder->get($path)->getId() === $id) { |
207
|
2 |
|
return $path; |
208
|
|
|
} else { |
209
|
|
|
// increments name (2) to name (3) |
210
|
1 |
|
$match = preg_match('/\((?P<id>\d+)\)$/', $title, $matches); |
211
|
1 |
|
if($match) { |
212
|
1 |
|
$newId = ((int) $matches['id']) + 1; |
213
|
1 |
|
$newTitle = preg_replace('/(.*)\s\((\d+)\)$/', |
214
|
1 |
|
'$1 (' . $newId . ')', $title); |
215
|
1 |
|
} else { |
216
|
1 |
|
$newTitle = $title . ' (2)'; |
217
|
|
|
} |
218
|
1 |
|
return $this->generateFileName($folder, $newTitle, $extension, $id); |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
/** |
222
|
|
|
* gather note files in given directory and all subdirectories |
223
|
|
|
* @param Folder $folder |
224
|
|
|
* @return array |
225
|
|
|
*/ |
226
|
1 |
|
private function gatherNoteFiles ($folder) { |
227
|
1 |
|
$notes = []; |
228
|
1 |
|
$nodes = $folder->getDirectoryListing(); |
229
|
1 |
|
foreach($nodes as $node) { |
230
|
1 |
|
\OCP\Util::writeLog('notes', $node->getType(), \OCP\Util::ERROR); |
231
|
1 |
|
if($node->getType() === FileInfo::TYPE_FOLDER) { |
232
|
|
|
$notes = array_merge($notes, $this->gatherNoteFiles($node)); |
233
|
|
|
continue; |
234
|
|
|
} |
235
|
1 |
|
if($this->isNote($node)) { |
236
|
1 |
|
$notes[] = $node; |
237
|
1 |
|
} |
238
|
1 |
|
} |
239
|
1 |
|
return $notes; |
240
|
|
|
} |
241
|
|
|
/** |
242
|
|
|
* test if file is a note |
243
|
|
|
* |
244
|
|
|
* @param \OCP\Files\File $file |
245
|
|
|
* @return bool |
246
|
|
|
*/ |
247
|
7 |
|
private function isNote($file) { |
248
|
7 |
|
$allowedExtensions = ['txt', 'org', 'markdown', 'md', 'note']; |
249
|
7 |
|
if($file->getType() !== 'file') return false; |
250
|
7 |
|
if(!in_array( |
251
|
7 |
|
pathinfo($file->getName(), PATHINFO_EXTENSION), |
252
|
|
|
$allowedExtensions |
253
|
7 |
|
)) return false; |
254
|
5 |
|
return true; |
255
|
|
|
} |
256
|
|
|
} |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.