Completed
Pull Request — master (#249)
by
unknown
03:38
created

NotesService   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 235
Duplicated Lines 4.26 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 82.91%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 34
c 3
b 0
f 0
lcom 1
cbo 2
dl 10
loc 235
ccs 97
cts 117
cp 0.8291
rs 9.2

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B getAll() 5 18 5
A get() 0 4 1
A getTags() 5 9 3
A create() 0 10 1
B update() 0 30 3
A favorite() 0 14 3
A delete() 0 5 1
A getFileById() 0 7 3
A getFolderForUser() 0 9 2
A generateFileName() 0 19 4
A gatherNoteFiles() 0 15 4
A isNote() 0 9 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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) {
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...
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) {
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...
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
}