Completed
Pull Request — master (#52)
by Joas
33:32
created

NotesService::getFolderForUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 7
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 9
rs 9.6666
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\Files\Node;
16
use OCP\Files\NotFoundException;
17
use OCP\IL10N;
18
use OCP\Files\IRootFolder;
19
use OCP\Files\Folder;
20
21
use OCA\Notes\Db\Note;
22
23
/**
24
 * Class NotesService
25
 *
26
 * @package OCA\Notes\Service
27
 */
28
class NotesService {
29
30
    private $l10n;
31
    private $root;
32
33
    /**
34
     * @param IRootFolder $root
35
     * @param IL10N $l10n
36
     */
37
    public function __construct (IRootFolder $root, IL10N $l10n) {
38
        $this->root = $root;
39
        $this->l10n = $l10n;
40
    }
41
42
43
    /**
44
     * @param string $userId
45
     * @return array with all notes in the current directory
46
     */
47
    public function getAll ($userId){
48
        $notesFolder = $this->getFolderForUser($userId);
49
        $notes = $this->gatherNoteFiles($notesFolder);
50
        $filesById = [];
51
        foreach($notes as $note) {
52
            $filesById[$note->getId()] = $note;
53
        }
54
        $tagger = \OC::$server->getTagManager()->load('files');
55 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...
56
            $tags = [];
57
        } else {
58
            $tags = $tagger->getTagsForObjects(array_keys($filesById));
59
        }
60
61
        $notes = [];
62
        foreach($filesById as $id=>$file) {
63
            $notes[] = Note::fromFile($file, $notesFolder, array_key_exists($id, $tags) ? $tags[$id] : []);
64
        }
65
66
        return $notes;
67
    }
68
69
70
    /**
71
     * Used to get a single note by id
72
     * @param int $id the id of the note to get
73
     * @param string $userId
74
     * @throws NoteDoesNotExistException if note does not exist
75
     * @return Note
76
     */
77
    public function get ($id, $userId) {
78
        $folder = $this->getFolderForUser($userId);
79
        return Note::fromFile($this->getFileById($folder, $id), $folder, $this->getTags($id));
80
    }
81
82
    private function getTags ($id) {
83
        $tagger = \OC::$server->getTagManager()->load('files');
84 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...
85
            $tags = [];
86
        } else {
87
            $tags = $tagger->getTagsForObjects([$id]);
88
        }
89
        return array_key_exists($id, $tags) ? $tags[$id] : [];
90
    }
91
92
    /**
93
     * Creates a note and returns the empty note
94
     * @param string $userId
95
     * @see update for setting note content
96
     * @return Note the newly created note
97
     */
98
    public function create ($userId) {
99
        $title = $this->l10n->t('New note');
100
        $folder = $this->getFolderForUser($userId);
101
102
        // check new note exists already and we need to number it
103
        // pass -1 because no file has id -1 and that will ensure
104
        // to only return filenames that dont yet exist
105
        $path = $this->generateFileName($folder, $title, "txt", -1);
106
        $file = $folder->newFile($path);
107
108
        return Note::fromFile($file, $folder);
109
    }
110
111
112
    /**
113
     * Updates a note. Be sure to check the returned note since the title is
114
     * dynamically generated and filename conflicts are resolved
115
     * @param int $id the id of the note used to update
116
     * @param string $content the content which will be written into the note
117
     * the title is generated from the first line of the content
118
     * @param int $mtime time of the note modification (optional)
119
     * @throws NoteDoesNotExistException if note does not exist
120
     * @return \OCA\Notes\Db\Note the updated note
121
     */
122
    public function update ($id, $content, $userId, $mtime=0) {
123
        $notesFolder = $this->getFolderForUser($userId);
124
        $file = $this->getFileById($notesFolder, $id);
125
        $folder = $file->getParent();
126
127
        // generate content from the first line of the title
128
        $splitContent = preg_split("/\R/", $content, 2);
129
        $title = $splitContent[0];
130
131
        if(!$title) {
132
            $title = $this->l10n->t('New note');
133
        }
134
135
        // prevent directory traversal
136
        $title = str_replace(array('/', '\\'), '',  $title);
137
        // remove hash and space characters from the beginning of the filename
138
        // in case of markdown
139
        $title = ltrim($title, ' #');
140
        // using a maximum of 100 chars should be enough
141
        $title = mb_substr($title, 0, 100, "UTF-8");
142
143
        // generate filename if there were collisions
144
        $currentFilePath = $file->getPath();
145
        $basePath = pathinfo($file->getPath(), PATHINFO_DIRNAME);
146
        $fileExtension = pathinfo($file->getName(), PATHINFO_EXTENSION);
147
        $newFilePath = $basePath . '/' . $this->generateFileName($folder, $title, $fileExtension, $id);
148
149
        // if the current path is not the new path, the file has to be renamed
150
        if($currentFilePath !== $newFilePath) {
151
            $file->move($newFilePath);
152
        }
153
154
        $file->putContent($content);
155
156
        if($mtime) {
157
            $file->touch($mtime);
158
        }
159
160
        return Note::fromFile($file, $notesFolder, $this->getTags($id));
161
    }
162
163
164
    /**
165
     * Set or unset a note as favorite.
166
     * @param int $id the id of the note used to update
167
     * @param boolean $favorite whether the note should be a favorite or not
168
     * @throws NoteDoesNotExistException if note does not exist
169
     * @return boolean the new favorite state of the note
170
     */
171
    public function favorite ($id, $favorite, $userId){
172
        $folder = $this->getFolderForUser($userId);
173
        $file = $this->getFileById($folder, $id);
174
        if(!$this->isNote($file)) {
175
            throw new NoteDoesNotExistException();
176
        }
177
        $tagger = \OC::$server->getTagManager()->load('files');
178
        if($favorite)
179
            $tagger->addToFavorites($id);
180
        else
181
            $tagger->removeFromFavorites($id);
182
183
        $tags = $tagger->getTagsForObjects([$id]);
184
        return array_key_exists($id, $tags) && in_array(\OC\Tags::TAG_FAVORITE, $tags[$id]);
185
    }
186
187
188
    /**
189
     * Deletes a note
190
     * @param int $id the id of the note which should be deleted
191
     * @param string $userId
192
     * @throws NoteDoesNotExistException if note does not
193
     * exist
194
     */
195
    public function delete ($id, $userId) {
196
        $folder = $this->getFolderForUser($userId);
197
        $file = $this->getFileById($folder, $id);
198
        $file->delete();
199
    }
200
201
202
    /**
203
     * @param Folder $folder
204
     * @param int $id
205
     * @throws NoteDoesNotExistException
206
     * @return \OCP\Files\File
207
     */
208
    private function getFileById ($folder, $id) {
209
        $file = $folder->getById($id);
210
211
        echo PHP_EOL;
212
        echo 'This is NotesService->getFileById() for folder '.$folder->getInternalPath().' with requested File ID '.$id.PHP_EOL;
213
        $files = $folder->getDirectoryListing();
214
        if(is_array($files)) {
215
            echo count($files).' result(s) from Folder->getDirectoryListing():'.PHP_EOL;
216 View Code Duplication
            foreach($files as $f) {
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...
217
                echo $f->getPath().' (ID '.$f->getId().')'.PHP_EOL;
218
            }
219
        }
220
        echo count($file).' result(s) from Folder->getById('.$id.'):'.PHP_EOL;
221 View Code Duplication
        foreach($file as $f) {
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...
222
             echo $f->getPath().' (ID '.$f->getId().')'.PHP_EOL;
223
        }
224
225
        if(count($file) <= 0 || !$this->isNote($file[0])) {
226
            echo '=> NoteDoesNotExistException'.PHP_EOL;
227
            throw new NoteDoesNotExistException('ID='.$id . (count($file) ? $file[0]->getPath().' is no note' : ' not found'));
228
        } else echo '=> OK'.PHP_EOL;
229
        return $file[0];
230
    }
231
232
233
    /**
234
     * @param string $userId the user id
235
     * @return Folder|Node
236
     */
237
    private function getFolderForUser ($userId) {
238
        $userFolder = $this->root->getUserFolder($userId);
239
        $path = 'Notes';
240
        try {
241
            return $userFolder->get($path);
242
        } catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
243
            return $userFolder->newFolder($path);
244
        }
245
    }
246
247
248
    /**
249
     * get path of file and the title.txt and check if they are the same
250
     * file. If not the title needs to be renamed
251
     *
252
     * @param Folder $folder a folder to the notes directory
253
     * @param string $title the filename which should be used
254
     * @param string $extension the extension which should be used
255
     * @param int $id the id of the note for which the title should be generated
256
     * used to see if the file itself has the title and not a different file for
257
     * checking for filename collisions
258
     * @return string the resolved filename to prevent overwriting different
259
     * files with the same title
260
     */
261
    private function generateFileName (Folder $folder, $title, $extension, $id) {
262
        $path = $title . '.' . $extension;
263
264
        // if file does not exist, that name has not been taken. Similar we don't
265
        // need to handle file collisions if it is the filename did not change
266
        if (!$folder->nodeExists($path) || $folder->get($path)->getId() === $id) {
267
            return $path;
268
        } else {
269
            // increments name (2) to name (3)
270
            $match = preg_match('/\((?P<id>\d+)\)$/', $title, $matches);
271
            if($match) {
272
                $newId = ((int) $matches['id']) + 1;
273
                $newTitle = preg_replace('/(.*)\s\((\d+)\)$/',
274
                    '$1 (' . $newId . ')', $title);
275
            } else {
276
                $newTitle = $title . ' (2)';
277
            }
278
            return $this->generateFileName($folder, $newTitle, $extension, $id);
279
        }
280
    }
281
282
283
	/**
284
	 * gather note files in given directory and all subdirectories
285
	 * @param Folder $folder
286
	 * @return array
287
	 */
288
	private function gatherNoteFiles ($folder) {
289
		$notes = [];
290
		$nodes = $folder->getDirectoryListing();
291
		foreach($nodes as $node) {
292
			if($node->getType() === FileInfo::TYPE_FOLDER) {
293
				$notes = array_merge($notes, $this->gatherNoteFiles($node));
294
				continue;
295
			}
296
			if($this->isNote($node)) {
297
				$notes[] = $node;
298
			}
299
		}
300
		return $notes;
301
	}
302
303
304
    /**
305
     * test if file is a note
306
     *
307
     * @param \OCP\Files\File $file
308
     * @return bool
309
     */
310
    private function isNote($file) {
311
        $allowedExtensions = ['txt', 'org', 'markdown', 'md', 'note'];
312
313
        if($file->getType() !== 'file') return false;
314
        if(!in_array(
315
            pathinfo($file->getName(), PATHINFO_EXTENSION),
316
            $allowedExtensions
317
        )) return false;
318
319
        return true;
320
    }
321
322
}
323