Completed
Pull Request — master (#248)
by
unknown
02:07
created

NotesService   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 98.73%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 28
c 3
b 1
f 0
lcom 1
cbo 2
dl 0
loc 246
ccs 78
cts 79
cp 0.9873
rs 10

12 Methods

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