Completed
Push — master ( 5ecbea...2fd033 )
by
unknown
06:19 queued 15s
created

NotesService::getAll()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 21
Code Lines 15

Duplication

Lines 5
Ratio 23.81 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 5
loc 21
rs 8.7624
cc 5
eloc 15
nc 8
nop 1
1
<?php
2
/**
3
 * Nextcloud - 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
12
namespace OCA\Notes\Service;
13
14
use OCP\Files\FileInfo;
15
use OCP\IL10N;
16
use OCP\Files\IRootFolder;
17
use OCP\Files\Folder;
18
19
use OCA\Notes\Db\Note;
20
21
/**
22
 * Class NotesService
23
 *
24
 * @package OCA\Notes\Service
25
 */
26
class NotesService {
27
28
    private $l10n;
29
    private $root;
30
31
    /**
32
     * @param IRootFolder $root
33
     * @param IL10N $l10n
34
     */
35
    public function __construct (IRootFolder $root, IL10N $l10n) {
36
        $this->root = $root;
37
        $this->l10n = $l10n;
38
    }
39
40
41
    /**
42
     * @param string $userId
43
     * @return array with all notes in the current directory
44
     */
45
    public function getAll ($userId){
46
        $notesFolder = $this->getFolderForUser($userId);
47
        $notes = $this->gatherNoteFiles($notesFolder);
48
        $filesById = [];
49
        foreach($notes as $note) {
50
            $filesById[$note->getId()] = $note;
51
        }
52
        $tagger = \OC::$server->getTagManager()->load('files');
53 View Code Duplication
        if($tagger===null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
54
            $tags = [];
55
        } else {
56
            $tags = $tagger->getTagsForObjects(array_keys($filesById));
57
        }
58
59
        $notes = [];
60
        foreach($filesById as $id=>$file) {
61
            $notes[] = Note::fromFile($file, $notesFolder, array_key_exists($id, $tags) ? $tags[$id] : []);
62
        }
63
64
        return $notes;
65
    }
66
67
68
    /**
69
     * Used to get a single note by id
70
     * @param int $id the id of the note to get
71
     * @param string $userId
72
     * @throws NoteDoesNotExistException if note does not exist
73
     * @return Note
74
     */
75
    public function get ($id, $userId) {
76
        $folder = $this->getFolderForUser($userId);
77
        return Note::fromFile($this->getFileById($folder, $id), $folder, $this->getTags($id));
78
    }
79
80
    private function getTags ($id) {
81
        $tagger = \OC::$server->getTagManager()->load('files');
82 View Code Duplication
        if($tagger===null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
83
            $tags = [];
84
        } else {
85
            $tags = $tagger->getTagsForObjects([$id]);
86
        }
87
        return array_key_exists($id, $tags) ? $tags[$id] : [];
88
    }
89
90
    /**
91
     * Creates a note and returns the empty note
92
     * @param string $userId
93
     * @see update for setting note content
94
     * @return Note the newly created note
95
     */
96
    public function create ($userId) {
97
        $title = $this->l10n->t('New note');
98
        $folder = $this->getFolderForUser($userId);
99
100
        // check new note exists already and we need to number it
101
        // pass -1 because no file has id -1 and that will ensure
102
        // to only return filenames that dont yet exist
103
        $path = $this->generateFileName($folder, $title, "txt", -1);
104
        $file = $folder->newFile($path);
105
106
        return Note::fromFile($file, $folder);
107
    }
108
109
110
    /**
111
     * Updates a note. Be sure to check the returned note since the title is
112
     * dynamically generated and filename conflicts are resolved
113
     * @param int $id the id of the note used to update
114
     * @param string $content the content which will be written into the note
115
     * the title is generated from the first line of the content
116
     * @param int $mtime time of the note modification (optional)
117
     * @throws NoteDoesNotExistException if note does not exist
118
     * @return \OCA\Notes\Db\Note the updated note
119
     */
120
    public function update ($id, $content, $userId, $mtime=0) {
121
        $notesFolder = $this->getFolderForUser($userId);
122
        $file = $this->getFileById($notesFolder, $id);
123
        $folder = $file->getParent();
124
125
        // generate content from the first line of the title
126
        $splitContent = preg_split("/\R/", $content, 2);
127
        $title = $splitContent[0];
128
129
        if(!$title) {
130
            $title = $this->l10n->t('New note');
131
        }
132
133
        // prevent directory traversal
134
        $title = str_replace(array('/', '\\'), '',  $title);
135
        // remove hash and space characters from the beginning of the filename
136
        // in case of markdown
137
        $title = ltrim($title, ' #');
138
        // using a maximum of 100 chars should be enough
139
        $title = mb_substr($title, 0, 100, "UTF-8");
140
141
        // generate filename if there were collisions
142
        $currentFilePath = $file->getPath();
143
        $basePath = pathinfo($file->getPath(), PATHINFO_DIRNAME);
144
        $fileExtension = pathinfo($file->getName(), PATHINFO_EXTENSION);
145
        $newFilePath = $basePath . '/' . $this->generateFileName($folder, $title, $fileExtension, $id);
146
147
        // if the current path is not the new path, the file has to be renamed
148
        if($currentFilePath !== $newFilePath) {
149
            $file->move($newFilePath);
150
        }
151
152
        $file->putContent($content);
153
154
        if($mtime) {
155
            $file->touch($mtime);
156
        }
157
158
        return Note::fromFile($file, $notesFolder, $this->getTags($id));
159
    }
160
161
162
    /**
163
     * Set or unset a note as favorite.
164
     * @param int $id the id of the note used to update
165
     * @param boolean $favorite whether the note should be a favorite or not
166
     * @throws NoteDoesNotExistException if note does not exist
167
     * @return boolean the new favorite state of the note
168
     */
169
    public function favorite ($id, $favorite, $userId){
170
        $folder = $this->getFolderForUser($userId);
171
        $file = $this->getFileById($folder, $id);
172
        if(!$this->isNote($file)) {
173
            throw new NoteDoesNotExistException();
174
        }
175
        $tagger = \OC::$server->getTagManager()->load('files');
176
        if($favorite)
177
            $tagger->addToFavorites($id);
178
        else
179
            $tagger->removeFromFavorites($id);
180
181
        $tags = $tagger->getTagsForObjects([$id]);
182
        return array_key_exists($id, $tags) && in_array(\OC\Tags::TAG_FAVORITE, $tags[$id]);
183
    }
184
185
186
    /**
187
     * Deletes a note
188
     * @param int $id the id of the note which should be deleted
189
     * @param string $userId
190
     * @throws NoteDoesNotExistException if note does not
191
     * exist
192
     */
193
    public function delete ($id, $userId) {
194
        $folder = $this->getFolderForUser($userId);
195
        $file = $this->getFileById($folder, $id);
196
        $file->delete();
197
    }
198
199
200
    /**
201
     * @param Folder $folder
202
     * @param int $id
203
     * @throws NoteDoesNotExistException
204
     * @return \OCP\Files\File
205
     */
206
    private function getFileById ($folder, $id) {
207
        $file = $folder->getById($id);
208
209
        if(count($file) <= 0 || !$this->isNote($file[0])) {
210
            throw new NoteDoesNotExistException();
211
        }
212
        return $file[0];
213
    }
214
215
216
    /**
217
     * @param string $userId the user id
218
     * @return Folder
219
     */
220
    private function getFolderForUser ($userId) {
221
        $path = '/' . $userId . '/files/Notes';
222
        if ($this->root->nodeExists($path)) {
223
            $folder = $this->root->get($path);
224
        } else {
225
            $folder = $this->root->newFolder($path);
226
        }
227
        return $folder;
228
    }
229
230
231
    /**
232
     * get path of file and the title.txt and check if they are the same
233
     * file. If not the title needs to be renamed
234
     *
235
     * @param Folder $folder a folder to the notes directory
236
     * @param string $title the filename which should be used
237
     * @param string $extension the extension which should be used
238
     * @param int $id the id of the note for which the title should be generated
239
     * used to see if the file itself has the title and not a different file for
240
     * checking for filename collisions
241
     * @return string the resolved filename to prevent overwriting different
242
     * files with the same title
243
     */
244
    private function generateFileName (Folder $folder, $title, $extension, $id) {
245
        $path = $title . '.' . $extension;
246
247
        // if file does not exist, that name has not been taken. Similar we don't
248
        // need to handle file collisions if it is the filename did not change
249
        if (!$folder->nodeExists($path) || $folder->get($path)->getId() === $id) {
250
            return $path;
251
        } else {
252
            // increments name (2) to name (3)
253
            $match = preg_match('/\((?P<id>\d+)\)$/', $title, $matches);
254
            if($match) {
255
                $newId = ((int) $matches['id']) + 1;
256
                $newTitle = preg_replace('/(.*)\s\((\d+)\)$/',
257
                    '$1 (' . $newId . ')', $title);
258
            } else {
259
                $newTitle = $title . ' (2)';
260
            }
261
            return $this->generateFileName($folder, $newTitle, $extension, $id);
262
        }
263
    }
264
265
266
	/**
267
	 * gather note files in given directory and all subdirectories
268
	 * @param Folder $folder
269
	 * @return array
270
	 */
271
	private function gatherNoteFiles ($folder) {
272
		$notes = [];
273
		$nodes = $folder->getDirectoryListing();
274
		foreach($nodes as $node) {
275
			if($node->getType() === FileInfo::TYPE_FOLDER) {
276
				$notes = array_merge($notes, $this->gatherNoteFiles($node));
277
				continue;
278
			}
279
			if($this->isNote($node)) {
280
				$notes[] = $node;
281
			}
282
		}
283
		return $notes;
284
	}
285
286
287
    /**
288
     * test if file is a note
289
     *
290
     * @param \OCP\Files\File $file
291
     * @return bool
292
     */
293
    private function isNote($file) {
294
        $allowedExtensions = ['txt', 'org', 'markdown', 'md', 'note'];
295
296
        if($file->getType() !== 'file') return false;
297
        if(!in_array(
298
            pathinfo($file->getName(), PATHINFO_EXTENSION),
299
            $allowedExtensions
300
        )) return false;
301
302
        return true;
303
    }
304
305
}
306