code style and lint errors

This commit is contained in:
korelstar 2019-05-25 08:15:05 +02:00 committed by GitHub
parent 682dc48055
commit 16de16937e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 892 additions and 901 deletions

2
.gitignore vendored
View File

@ -1,10 +1,12 @@
node_modules/ node_modules/
vendor/
*.log *.log
build/ build/
js/ js/
.rvm .rvm
report report
clover.xml clover.xml
composer.lock
# just sane ignores # just sane ignores
.*.sw[po] .*.sw[po]

View File

@ -11,6 +11,7 @@ build:
tests: tests:
override: override:
- php-scrutinizer-run - php-scrutinizer-run
- phpcs-run --standard=phpcs.xml appinfo/ lib/
- eslint-run --ext .js,.vue src - eslint-run --ext .js,.vue src
tools: tools:

View File

@ -49,8 +49,7 @@ script:
# - phpunit -c phpunit.integration.xml # - phpunit -c phpunit.integration.xml
# build js # build js
- make npm-init - make init
- make lint - make lint
- make stylelint
- make build-js-production - make build-js-production

View File

@ -9,32 +9,32 @@ appstore_dir=$(build_dir)/appstore
package_name=$(app_name) package_name=$(app_name)
cert_dir=$(HOME)/.nextcloud/certificates cert_dir=$(HOME)/.nextcloud/certificates
appstore: clean appstore: clean lint build-js-production
mkdir -p $(sign_dir) mkdir -p $(sign_dir)
rsync -a \ rsync -a \
--exclude=.git \ --exclude=.babelrc.js \
--exclude=build \ --exclude=build \
--exclude=.gitignore \
--exclude=.travis.yml \
--exclude=.scrutinizer.yml \
--exclude=CONTRIBUTING.md \
--exclude=composer.json \ --exclude=composer.json \
--exclude=composer.lock \ --exclude=composer.lock \
--exclude=composer.phar \ --exclude=composer.phar \
--exclude=.tx \ --exclude=CONTRIBUTING.md \
--exclude=.editorconfig \
--exclude=.eslintrc.js \
--exclude=.git \
--exclude=.github \
--exclude=.gitignore \
--exclude=l10n/no-php \ --exclude=l10n/no-php \
--exclude=Makefile \ --exclude=Makefile \
--exclude=nbproject \ --exclude=package*.json \
--exclude=screenshots \ --exclude=phpcs.xml \
--exclude=phpunit*xml \ --exclude=phpunit*xml \
--exclude=.scrutinizer.yml \
--exclude=src \
--exclude=.stylelintrc.js \
--exclude=tests \ --exclude=tests \
--exclude=vendor/bin \ --exclude=.travis.yml \
--exclude=js/node_modules \ --exclude=.tx \
--exclude=js/tests \ --exclude=vendor \
--exclude=js/karma.conf.js \
--exclude=js/gulpfile.js \
--exclude=js/bower.json \
--exclude=js/package.json \
$(project_dir) $(sign_dir) $(project_dir) $(sign_dir)
@echo "Signing…" @echo "Signing…"
php ../server/occ integrity:sign-app \ php ../server/occ integrity:sign-app \
@ -51,7 +51,12 @@ appstore: clean
all: dev-setup lint build-js-production test all: dev-setup lint build-js-production test
# Dev env management # Dev env management
dev-setup: clean clean-dev npm-init dev-setup: clean clean-dev init
init: composer-init npm-init
composer-init:
composer install
npm-init: npm-init:
npm install npm install
@ -84,17 +89,27 @@ test-coverage:
npm run test:coverage npm run test:coverage
# Linting # Linting
lint: lint: lint-php lint-js lint-css
lint-php:
vendor/bin/phpcs --standard=phpcs.xml --runtime-set ignore_warnings_on_exit 1 appinfo/ lib/
lint-js:
npm run lint npm run lint
lint-fix: lint-css:
npm run lint:fix
# Style linting
stylelint:
npm run stylelint npm run stylelint
stylelint-fix: # Fix lint
lint-fix: lint-php-fix lint-js-fix lint-css-fix
lint-php-fix:
vendor/bin/phpcbf --standard=phpcs.xml appinfo/ lib/
lint-js-fix:
npm run lint:fix
lint-css-fix:
npm run stylelint:fix npm run stylelint:fix
# Cleaning # Cleaning
@ -104,4 +119,5 @@ clean:
clean-dev: clean-dev:
rm -rf node_modules rm -rf node_modules
rm -rf vendor

View File

@ -17,14 +17,13 @@ $app = new App('notes');
$container = $app->getContainer(); $container = $app->getContainer();
$container->query('OCP\INavigationManager')->add(function () use ($container) { $container->query('OCP\INavigationManager')->add(function () use ($container) {
$urlGenerator = $container->query('OCP\IURLGenerator'); $urlGenerator = $container->query('OCP\IURLGenerator');
$l10n = $container->query('OCP\IL10N'); $l10n = $container->query('OCP\IL10N');
return [ return [
'id' => 'notes', 'id' => 'notes',
'order' => 10, 'order' => 10,
'href' => $urlGenerator->linkToRoute('notes.page.index'), 'href' => $urlGenerator->linkToRoute('notes.page.index'),
'icon' => $urlGenerator->imagePath('notes', 'notes.svg'), 'icon' => $urlGenerator->imagePath('notes', 'notes.svg'),
'name' => $l10n->t('Notes') 'name' => $l10n->t('Notes')
]; ];
}); });

View File

@ -1,5 +1,6 @@
{ {
"require-dev": { "require-dev": {
"christophwurst/nextcloud": "^15.0" "christophwurst/nextcloud": "^15.0",
"squizlabs/php_codesniffer": "3.*"
} }
} }

View File

@ -1,13 +1,4 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright Bernhard Posselt 2012, 2014
*/
namespace OCA\Notes\Controller; namespace OCA\Notes\Controller;
@ -22,15 +13,15 @@ use OCA\Notes\Service\NoteDoesNotExistException;
* @package OCA\Notes\Controller * @package OCA\Notes\Controller
*/ */
trait Errors { trait Errors {
/** /**
* @param $callback * @param $callback
* @return DataResponse * @return DataResponse
*/ */
protected function respond ($callback) { protected function respond($callback) {
try { try {
return new DataResponse($callback()); return new DataResponse($callback());
} catch(NoteDoesNotExistException $ex) { } catch (NoteDoesNotExistException $ex) {
return new DataResponse([], Http::STATUS_NOT_FOUND); return new DataResponse([], Http::STATUS_NOT_FOUND);
} }
} }
} }

View File

@ -1,13 +1,4 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright Bernhard Posselt 2012, 2014
*/
namespace OCA\Notes\Controller; namespace OCA\Notes\Controller;
@ -28,173 +19,171 @@ use OCA\Notes\Db\Note;
*/ */
class NotesApiController extends ApiController { class NotesApiController extends ApiController {
use Errors; use Errors;
/** @var NotesService */ /** @var NotesService */
private $service; private $service;
/** @var MetaService */ /** @var MetaService */
private $metaService; private $metaService;
/** @var IUserSession */ /** @var IUserSession */
private $userSession; private $userSession;
/** /**
* @param string $AppName * @param string $AppName
* @param IRequest $request * @param IRequest $request
* @param NotesService $service * @param NotesService $service
* @param IUserSession $userSession * @param IUserSession $userSession
*/ */
public function __construct($AppName, IRequest $request, NotesService $service, MetaService $metaService, IUserSession $userSession) { public function __construct($AppName, IRequest $request, NotesService $service, MetaService $metaService, IUserSession $userSession) {
parent::__construct($AppName, $request); parent::__construct($AppName, $request);
$this->service = $service; $this->service = $service;
$this->metaService = $metaService; $this->metaService = $metaService;
$this->userSession = $userSession; $this->userSession = $userSession;
} }
private function getUID() { private function getUID() {
return $this->userSession->getUser()->getUID(); return $this->userSession->getUser()->getUID();
} }
/** /**
* @param Note $note * @param Note $note
* @param string[] $exclude the fields that should be removed from the * @param string[] $exclude the fields that should be removed from the
* notes * notes
* @return Note * @return Note
*/ */
private function excludeFields(Note &$note, array $exclude) { private function excludeFields(Note &$note, array $exclude) {
if(count($exclude) > 0) { if (count($exclude) > 0) {
foreach ($exclude as $field) { foreach ($exclude as $field) {
if(property_exists($note, $field)) { if (property_exists($note, $field)) {
unset($note->$field); unset($note->$field);
} }
} }
} }
return $note; return $note;
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
* @CORS * @CORS
* @NoCSRFRequired * @NoCSRFRequired
* *
* @param string $exclude * @param string $exclude
* @return DataResponse * @return DataResponse
*/ */
public function index($exclude='', $pruneBefore=0) { public function index($exclude = '', $pruneBefore = 0) {
$exclude = explode(',', $exclude); $exclude = explode(',', $exclude);
$now = new \DateTime(); // this must be before loading notes if there are concurrent changes possible $now = new \DateTime(); // this must be before loading notes if there are concurrent changes possible
$notes = $this->service->getAll($this->getUID()); $notes = $this->service->getAll($this->getUID());
$metas = $this->metaService->updateAll($this->getUID(), $notes); $metas = $this->metaService->updateAll($this->getUID(), $notes);
foreach ($notes as $note) { foreach ($notes as $note) {
$lastUpdate = $metas[$note->getId()]->getLastUpdate(); $lastUpdate = $metas[$note->getId()]->getLastUpdate();
if($pruneBefore && $lastUpdate<$pruneBefore) { if ($pruneBefore && $lastUpdate<$pruneBefore) {
$vars = get_object_vars($note); $vars = get_object_vars($note);
unset($vars['id']); unset($vars['id']);
$this->excludeFields($note, array_keys($vars)); $this->excludeFields($note, array_keys($vars));
} else { } else {
$this->excludeFields($note, $exclude); $this->excludeFields($note, $exclude);
} }
} }
$etag = md5(json_encode($notes)); $etag = md5(json_encode($notes));
if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') { if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') {
return new DataResponse([], Http::STATUS_NOT_MODIFIED); return new DataResponse([], Http::STATUS_NOT_MODIFIED);
} }
return (new DataResponse($notes)) return (new DataResponse($notes))
->setLastModified($now) ->setLastModified($now)
->setETag($etag); ->setETag($etag);
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
* @CORS * @CORS
* @NoCSRFRequired * @NoCSRFRequired
* *
* @param int $id * @param int $id
* @param string $exclude * @param string $exclude
* @return DataResponse * @return DataResponse
*/ */
public function get($id, $exclude='') { public function get($id, $exclude = '') {
$exclude = explode(',', $exclude); $exclude = explode(',', $exclude);
return $this->respond(function () use ($id, $exclude) { return $this->respond(function () use ($id, $exclude) {
$note = $this->service->get($id, $this->getUID()); $note = $this->service->get($id, $this->getUID());
$note = $this->excludeFields($note, $exclude); $note = $this->excludeFields($note, $exclude);
return $note; return $note;
}); });
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
* @CORS * @CORS
* @NoCSRFRequired * @NoCSRFRequired
* *
* @param string $content * @param string $content
* @param string $category * @param string $category
* @param int $modified * @param int $modified
* @param boolean $favorite * @param boolean $favorite
* @return DataResponse * @return DataResponse
*/ */
public function create($content, $category=null, $modified=0, $favorite=null) { public function create($content, $category = null, $modified = 0, $favorite = null) {
return $this->respond(function () use ($content, $category, $modified, $favorite) { return $this->respond(function () use ($content, $category, $modified, $favorite) {
$note = $this->service->create($this->getUID()); $note = $this->service->create($this->getUID());
return $this->updateData($note->getId(), $content, $category, $modified, $favorite); return $this->updateData($note->getId(), $content, $category, $modified, $favorite);
}); });
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
* @CORS * @CORS
* @NoCSRFRequired * @NoCSRFRequired
* *
* @param int $id * @param int $id
* @param string $content * @param string $content
* @param string $category * @param string $category
* @param int $modified * @param int $modified
* @param boolean $favorite * @param boolean $favorite
* @return DataResponse * @return DataResponse
*/ */
public function update($id, $content=null, $category=null, $modified=0, $favorite=null) { public function update($id, $content = null, $category = null, $modified = 0, $favorite = null) {
return $this->respond(function () use ($id, $content, $category, $modified, $favorite) { return $this->respond(function () use ($id, $content, $category, $modified, $favorite) {
return $this->updateData($id, $content, $category, $modified, $favorite); return $this->updateData($id, $content, $category, $modified, $favorite);
}); });
} }
/**
* Updates a note, used by create and update
* @param int $id
* @param string $content
* @param int $modified
* @param boolean $favorite
* @return Note
*/
private function updateData($id, $content, $category, $modified, $favorite) {
if($favorite!==null) {
$this->service->favorite($id, $favorite, $this->getUID());
}
if($content===null) {
return $this->service->get($id, $this->getUID());
} else {
return $this->service->update($id, $content, $this->getUID(), $category, $modified);
}
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @param int $id
* @return DataResponse
*/
public function destroy($id) {
return $this->respond(function () use ($id) {
$this->service->delete($id, $this->getUID());
return [];
});
}
/**
* Updates a note, used by create and update
* @param int $id
* @param string|null $content
* @param int $modified
* @param boolean|null $favorite
* @return Note
*/
private function updateData($id, $content, $category, $modified, $favorite) {
if ($favorite!==null) {
$this->service->favorite($id, $favorite, $this->getUID());
}
if ($content===null) {
return $this->service->get($id, $this->getUID());
} else {
return $this->service->update($id, $content, $this->getUID(), $category, $modified);
}
}
/**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @param int $id
* @return DataResponse
*/
public function destroy($id) {
return $this->respond(function () use ($id) {
$this->service->delete($id, $this->getUID());
return [];
});
}
} }

View File

@ -1,13 +1,4 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright Bernhard Posselt 2012, 2014
*/
namespace OCA\Notes\Controller; namespace OCA\Notes\Controller;
@ -27,167 +18,177 @@ use OCA\Notes\Service\SettingsService;
*/ */
class NotesController extends Controller { class NotesController extends Controller {
use Errors; use Errors;
/** @var NotesService */ /** @var NotesService */
private $notesService; private $notesService;
/** @var SettingsService */ /** @var SettingsService */
private $settingsService; private $settingsService;
/** @var IConfig */ /** @var IConfig */
private $settings; private $settings;
/** @var string */ /** @var string */
private $userId; private $userId;
/** @var IL10N */ /** @var IL10N */
private $l10n; private $l10n;
/** /**
* @param string $AppName * @param string $AppName
* @param IRequest $request * @param IRequest $request
* @param NotesService $notesService * @param NotesService $notesService
* @param SettingsService $settingsService * @param SettingsService $settingsService
* @param IConfig $settings * @param IConfig $settings
* @param IL10N $l10n * @param IL10N $l10n
* @param string $UserId * @param string $UserId
*/ */
public function __construct($AppName, public function __construct(
IRequest $request, $AppName,
NotesService $notesService, IRequest $request,
SettingsService $settingsService, NotesService $notesService,
IConfig $settings, SettingsService $settingsService,
IL10N $l10n, IConfig $settings,
$UserId) { IL10N $l10n,
parent::__construct($AppName, $request); $UserId
$this->notesService = $notesService; ) {
$this->settingsService = $settingsService; parent::__construct($AppName, $request);
$this->settings = $settings; $this->notesService = $notesService;
$this->userId = $UserId; $this->settingsService = $settingsService;
$this->l10n = $l10n; $this->settings = $settings;
} $this->userId = $UserId;
$this->l10n = $l10n;
}
/** /**
* @NoAdminRequired * @NoAdminRequired
*/ */
public function index() { public function index() {
$notes = $this->notesService->getAll($this->userId, true); $notes = $this->notesService->getAll($this->userId, true);
$settings = $this->settingsService->getAll($this->userId); $settings = $this->settingsService->getAll($this->userId);
$errorMessage = null; $errorMessage = null;
$lastViewedNote = (int) $this->settings->getUserValue($this->userId, $lastViewedNote = (int) $this->settings->getUserValue(
$this->appName, 'notesLastViewedNote'); $this->userId,
// check if notes folder is accessible $this->appName,
try { 'notesLastViewedNote'
$this->notesService->checkNotesFolder($this->userId); );
if($lastViewedNote) { // check if notes folder is accessible
// check if note exists try {
try { $this->notesService->checkNotesFolder($this->userId);
$this->notesService->get($lastViewedNote, $this->userId); if ($lastViewedNote) {
} catch(\Exception $ex) { // check if note exists
$this->settings->deleteUserValue($this->userId, $this->appName, 'notesLastViewedNote'); try {
$lastViewedNote = 0; $this->notesService->get($lastViewedNote, $this->userId);
$errorMessage = $this->l10n->t('The last viewed note cannot be accessed. ').$ex->getMessage(); } catch (\Exception $ex) {
} $this->settings->deleteUserValue($this->userId, $this->appName, 'notesLastViewedNote');
} $lastViewedNote = 0;
} catch(\Exception $e) { $errorMessage = $this->l10n->t('The last viewed note cannot be accessed. ').$ex->getMessage();
$errorMessage = $this->l10n->t('The notes folder is not accessible: %s', $e->getMessage()); }
} }
} catch (\Exception $e) {
$errorMessage = $this->l10n->t('The notes folder is not accessible: %s', $e->getMessage());
}
return new DataResponse([ return new DataResponse([
'notes' => $notes, 'notes' => $notes,
'settings' => $settings, 'settings' => $settings,
'lastViewedNote' => $lastViewedNote, 'lastViewedNote' => $lastViewedNote,
'errorMessage' => $errorMessage, 'errorMessage' => $errorMessage,
]); ]);
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
* *
* @param int $id * @param int $id
* @return DataResponse * @return DataResponse
*/ */
public function get($id) { public function get($id) {
// save the last viewed note // save the last viewed note
$this->settings->setUserValue( $this->settings->setUserValue(
$this->userId, $this->appName, 'notesLastViewedNote', $id $this->userId,
); $this->appName,
'notesLastViewedNote',
$id
);
return $this->respond(function () use ($id) { return $this->respond(function () use ($id) {
return $this->notesService->get($id, $this->userId); return $this->notesService->get($id, $this->userId);
}); });
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
* *
* @param string $content * @param string $content
*/ */
public function create($content='', $category=null) { public function create($content = '', $category = null) {
$note = $this->notesService->create($this->userId); $note = $this->notesService->create($this->userId);
$note = $this->notesService->update( $note = $this->notesService->update(
$note->getId(), $content, $this->userId, $category $note->getId(),
); $content,
return new DataResponse($note); $this->userId,
} $category
);
return new DataResponse($note);
}
/** /**
* @NoAdminRequired * @NoAdminRequired
* *
* @param int $id * @param int $id
* @param string $content * @param string $content
* @return DataResponse * @return DataResponse
*/ */
public function update($id, $content) { public function update($id, $content) {
return $this->respond(function () use ($id, $content) { return $this->respond(function () use ($id, $content) {
return $this->notesService->update($id, $content, $this->userId); return $this->notesService->update($id, $content, $this->userId);
}); });
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
* *
* @param int $id * @param int $id
* @param string $category * @param string $category
* @return DataResponse * @return DataResponse
*/ */
public function category($id, $category) { public function category($id, $category) {
return $this->respond(function () use ($id, $category) { return $this->respond(function () use ($id, $category) {
$note = $this->notesService->update($id, null, $this->userId, $category); $note = $this->notesService->update($id, null, $this->userId, $category);
return $note->category; return $note->category;
}); });
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
* *
* @param int $id * @param int $id
* @param boolean $favorite * @param boolean $favorite
* @return DataResponse * @return DataResponse
*/ */
public function favorite($id, $favorite) { public function favorite($id, $favorite) {
return $this->respond(function () use ($id, $favorite) { return $this->respond(function () use ($id, $favorite) {
return $this->notesService->favorite($id, $favorite, $this->userId); return $this->notesService->favorite($id, $favorite, $this->userId);
}); });
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
* *
* @param int $id * @param int $id
* @return DataResponse * @return DataResponse
*/ */
public function destroy($id) { public function destroy($id) {
return $this->respond(function () use ($id) { return $this->respond(function () use ($id) {
$this->notesService->delete($id, $this->userId); $this->notesService->delete($id, $this->userId);
return []; return [];
}); });
} }
} }

View File

@ -1,13 +1,4 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright Bernhard Posselt 2012, 2014
*/
namespace OCA\Notes\Controller; namespace OCA\Notes\Controller;
@ -23,33 +14,32 @@ use OCP\IRequest;
*/ */
class PageController extends Controller { class PageController extends Controller {
/** /**
* @param string $AppName * @param string $AppName
* @param IRequest $request * @param IRequest $request
*/ */
public function __construct($AppName, IRequest $request) { public function __construct($AppName, IRequest $request) {
parent::__construct($AppName, $request); parent::__construct($AppName, $request);
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
* @NoCSRFRequired * @NoCSRFRequired
* *
* @return TemplateResponse * @return TemplateResponse
*/ */
public function index() { public function index() {
$response = new TemplateResponse( $response = new TemplateResponse(
$this->appName, $this->appName,
'main', 'main',
[ ] [ ]
); );
$csp = new ContentSecurityPolicy(); $csp = new ContentSecurityPolicy();
$csp->addAllowedImageDomain('*'); $csp->addAllowedImageDomain('*');
$response->setContentSecurityPolicy($csp); $response->setContentSecurityPolicy($csp);
return $response;
}
return $response;
}
} }

View File

@ -1,5 +1,6 @@
<?php <?php
namespace OCA\Notes\Controller; namespace OCA\Notes\Controller;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\IConfig; use OCP\IConfig;
@ -10,8 +11,8 @@ use OCP\Files\IRootFolder;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\JSONResponse;
use OCA\Notes\Service\SettingsService; use OCA\Notes\Service\SettingsService;
class SettingsController extends Controller class SettingsController extends Controller {
{
private $service; private $service;
private $userSession; private $userSession;

View File

@ -1,15 +1,21 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*/
namespace OCA\Notes\Db; namespace OCA\Notes\Db;
use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Entity;
/**
* Class Meta
* @method string getUserId()
* @method void setUserId(string $value)
* @method integer getFileId()
* @method void setFileId(integer $value)
* @method integer getLastUpdate()
* @method void setLastUpdate(integer $value)
* @method string getEtag()
* @method void setEtag(string $value)
* @package OCA\Notes\Db
*/
class Meta extends Entity { class Meta extends Entity {
public $userId; public $userId;

View File

@ -1,29 +1,24 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*/
namespace OCA\Notes\Db; namespace OCA\Notes\Db;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCP\AppFramework\Db\Mapper; use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
class MetaMapper extends Mapper { class MetaMapper extends QBMapper {
public function __construct(IDBConnection $db) { public function __construct(IDBConnection $db) {
parent::__construct($db, 'notes_meta'); parent::__construct($db, 'notes_meta');
} }
public function getAll($userId) { public function getAll($userId) {
$sql = 'SELECT * FROM `*PREFIX*notes_meta` WHERE user_id=?'; $qb = $this->db->getQueryBuilder();
return $this->findEntities($sql, [$userId]); $qb->select('*')
} ->from('*PREFIX*notes_meta')
->where(
public function get($userId, $fileId) { $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
$sql = 'SELECT * FROM `*PREFIX*notes_meta` WHERE user_id=? AND file_id=?'; );
return $this->findEntity($sql, [$userId, $fileId]); return $this->findEntities($qb);
} }
} }

View File

@ -1,20 +1,11 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright Bernhard Posselt 2012, 2014
*/
namespace OCA\Notes\Db; namespace OCA\Notes\Db;
use OCP\Files\File; use OCP\Files\File;
use OCP\Files\Folder; use OCP\Files\Folder;
use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Entity;
use League\Flysystem\FileNotFoundException;
/** /**
* Class Note * Class Note
* @method integer getId() * @method integer getId()
@ -38,87 +29,88 @@ use League\Flysystem\FileNotFoundException;
* @package OCA\Notes\Db * @package OCA\Notes\Db
*/ */
class Note extends Entity { class Note extends Entity {
public $etag; public $etag;
public $modified; public $modified;
public $title; public $title;
public $category; public $category;
public $content = null; public $content = null;
public $favorite = false; public $favorite = false;
public $error = false; public $error = false;
public $errorMessage=''; public $errorMessage='';
public function __construct() { public function __construct() {
$this->addType('modified', 'integer'); $this->addType('modified', 'integer');
$this->addType('favorite', 'boolean'); $this->addType('favorite', 'boolean');
} }
/** /**
* @param File $file * @param File $file
* @return static * @return static
*/ */
public static function fromFile(File $file, Folder $notesFolder, $tags=[], $onlyMeta=false) { public static function fromFile(File $file, Folder $notesFolder, $tags = [], $onlyMeta = false) {
$note = new static(); $note = new static();
$note->setId($file->getId()); $note->setId($file->getId());
if(!$onlyMeta) { if (!$onlyMeta) {
$fileContent=$file->getContent(); $fileContent=$file->getContent();
if($fileContent===false){ if ($fileContent===false) {
throw new FileNotFoundException("File not found"); throw new \Exception("File not found");
} }
$note->setContent(self::convertEncoding($fileContent)); $note->setContent(self::convertEncoding($fileContent));
} }
$note->setModified($file->getMTime()); $note->setModified($file->getMTime());
$note->setTitle(pathinfo($file->getName(),PATHINFO_FILENAME)); // remove extension $note->setTitle(pathinfo($file->getName(), PATHINFO_FILENAME)); // remove extension
$subdir = substr(dirname($file->getPath()), strlen($notesFolder->getPath())+1); $subdir = substr(dirname($file->getPath()), strlen($notesFolder->getPath())+1);
$note->setCategory($subdir ? $subdir : ''); $note->setCategory($subdir ? $subdir : '');
if(is_array($tags) && in_array(\OC\Tags::TAG_FAVORITE, $tags)) { if (is_array($tags) && in_array(\OC\Tags::TAG_FAVORITE, $tags)) {
$note->setFavorite(true); $note->setFavorite(true);
//unset($tags[array_search(\OC\Tags::TAG_FAVORITE, $tags)]); //unset($tags[array_search(\OC\Tags::TAG_FAVORITE, $tags)]);
} }
if(!$onlyMeta) { if (!$onlyMeta) {
$note->updateETag(); $note->updateETag();
} }
$note->resetUpdatedFields(); $note->resetUpdatedFields();
return $note; return $note;
} }
/**
* @param File $file
* @return static
*/
public static function fromException($message,File $file,Folder $notesFolder,$tags=[]){
$note = new static();
$note->setId($file->getId());
$note->setErrorMessage($message);
$note->setError(true);
$note->setContent($message);
$note->setModified(null);
$note->setTitle(pathinfo($file->getName(),PATHINFO_FILENAME)); // remove extension
$subdir = substr(dirname($file->getPath()), strlen($notesFolder->getPath())+1);
$note->setCategory($subdir ? $subdir : null);
if(is_array($tags) && in_array(\OC\Tags::TAG_FAVORITE, $tags)) {
$note->setFavorite(true);
//unset($tags[array_search(\OC\Tags::TAG_FAVORITE, $tags)]);
}
$note->updateETag();
$note->resetUpdatedFields();
return $note;
}
private static function convertEncoding($str) { /**
if(!mb_check_encoding($str, 'UTF-8')) { * @param File $file
$str = mb_convert_encoding($str, 'UTF-8'); * @return static
} */
return $str; public static function fromException($message, File $file, Folder $notesFolder, $tags = []) {
} $note = new static();
$note->setId($file->getId());
$note->setErrorMessage($message);
$note->setError(true);
$note->setContent($message);
$note->setModified(null);
$note->setTitle(pathinfo($file->getName(), PATHINFO_FILENAME)); // remove extension
$subdir = substr(dirname($file->getPath()), strlen($notesFolder->getPath())+1);
$note->setCategory($subdir ? $subdir : null);
if (is_array($tags) && in_array(\OC\Tags::TAG_FAVORITE, $tags)) {
$note->setFavorite(true);
//unset($tags[array_search(\OC\Tags::TAG_FAVORITE, $tags)]);
}
$note->updateETag();
$note->resetUpdatedFields();
return $note;
}
private function updateETag() { private static function convertEncoding($str) {
// collect all relevant attributes if (!mb_check_encoding($str, 'UTF-8')) {
$data = ''; $str = mb_convert_encoding($str, 'UTF-8');
foreach(get_object_vars($this) as $key => $val) { }
if($key!=='etag') { return $str;
$data .= $val; }
}
} private function updateETag() {
$etag = md5($data); // collect all relevant attributes
$this->setEtag($etag); $data = '';
} foreach (get_object_vars($this) as $key => $val) {
if ($key!=='etag') {
$data .= $val;
}
}
$etag = md5($data);
$this->setEtag($etag);
}
} }

View File

@ -1,10 +1,4 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*/
namespace OCA\Notes\Service; namespace OCA\Notes\Service;
@ -29,18 +23,18 @@ class MetaService {
$metas = $this->metaMapper->getAll($userId); $metas = $this->metaMapper->getAll($userId);
$metas = $this->getIndexedArray($metas, 'fileId'); $metas = $this->getIndexedArray($metas, 'fileId');
$notes = $this->getIndexedArray($notes, 'id'); $notes = $this->getIndexedArray($notes, 'id');
foreach($metas as $id=>$meta) { foreach ($metas as $id => $meta) {
if(!array_key_exists($id, $notes)) { if (!array_key_exists($id, $notes)) {
// DELETE obsolete notes // DELETE obsolete notes
$this->metaMapper->delete($meta); $this->metaMapper->delete($meta);
unset($metas[$id]); unset($metas[$id]);
} }
} }
foreach($notes as $id=>$note) { foreach ($notes as $id => $note) {
if(!array_key_exists($id, $metas)) { if (!array_key_exists($id, $metas)) {
// INSERT new notes // INSERT new notes
$metas[$note->getId()] = $this->create($userId, $note); $metas[$note->getId()] = $this->create($userId, $note);
} elseif($note->getEtag()!==$metas[$id]->getEtag()) { } elseif ($note->getEtag()!==$metas[$id]->getEtag()) {
// UPDATE changed notes // UPDATE changed notes
$meta = $metas[$id]; $meta = $metas[$id];
$this->updateIfNeeded($meta, $note); $this->updateIfNeeded($meta, $note);
@ -53,7 +47,7 @@ class MetaService {
$property = ucfirst($property); $property = ucfirst($property);
$getter = 'get'.$property; $getter = 'get'.$property;
$result = array(); $result = array();
foreach($data as $entity) { foreach ($data as $entity) {
$result[$entity->$getter()] = $entity; $result[$entity->$getter()] = $entity;
} }
return $result; return $result;
@ -66,7 +60,7 @@ class MetaService {
} }
private function updateIfNeeded(&$meta, $note) { private function updateIfNeeded(&$meta, $note) {
if($note->getEtag()!==$meta->getEtag()) { if ($note->getEtag()!==$meta->getEtag()) {
$meta->setEtag($note->getEtag()); $meta->setEtag($note->getEtag());
$meta->setLastUpdate(time()); $meta->setLastUpdate(time());
$this->metaMapper->update($meta); $this->metaMapper->update($meta);

View File

@ -1,13 +1,4 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright Bernhard Posselt 2012, 2014
*/
namespace OCA\Notes\Service; namespace OCA\Notes\Service;
@ -18,4 +9,5 @@ use Exception;
* *
* @package OCA\Notes\Service * @package OCA\Notes\Service
*/ */
class NoteDoesNotExistException extends Exception {} class NoteDoesNotExistException extends Exception {
}

View File

@ -1,11 +1,4 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
*/
namespace OCA\Notes\Service; namespace OCA\Notes\Service;
@ -16,4 +9,5 @@ use Exception;
* *
* @package OCA\Notes\Service * @package OCA\Notes\Service
*/ */
class NotesFolderException extends Exception {} class NotesFolderException extends Exception {
}

View File

@ -1,13 +1,4 @@
<?php <?php
/**
* Nextcloud - Notes
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @copyright Bernhard Posselt 2012, 2014
*/
namespace OCA\Notes\Service; namespace OCA\Notes\Service;
@ -17,13 +8,11 @@ use OCP\Files\IRootFolder;
use OCP\Files\Folder; use OCP\Files\Folder;
use OCP\ILogger; use OCP\ILogger;
use OCP\Encryption\Exceptions\GenericEncryptionException; use OCP\Encryption\Exceptions\GenericEncryptionException;
use League\Flysystem\FileNotFoundException;
use OCA\Notes\Db\Note; use OCA\Notes\Db\Note;
use OCA\Notes\Service\SettingsService; use OCA\Notes\Service\SettingsService;
use OCP\IConfig; use OCP\IConfig;
use OCP\IUserSession; use OCP\IUserSession;
/** /**
* Class NotesService * Class NotesService
* *
@ -31,12 +20,12 @@ use OCP\IUserSession;
*/ */
class NotesService { class NotesService {
private $l10n; private $l10n;
private $root; private $root;
private $logger; private $logger;
private $config; private $config;
private $settings; private $settings;
private $appName; private $appName;
/** /**
* @param IRootFolder $root * @param IRootFolder $root
@ -46,374 +35,379 @@ class NotesService {
* @param \OCA\Notes\Service\SettingsService $settings * @param \OCA\Notes\Service\SettingsService $settings
* @param String $appName * @param String $appName
*/ */
public function __construct (IRootFolder $root, IL10N $l10n, ILogger $logger, IConfig $config, SettingsService $settings, $appName) { public function __construct(IRootFolder $root, IL10N $l10n, ILogger $logger, IConfig $config, SettingsService $settings, $appName) {
$this->root = $root; $this->root = $root;
$this->l10n = $l10n; $this->l10n = $l10n;
$this->logger = $logger; $this->logger = $logger;
$this->config = $config; $this->config = $config;
$this->settings = $settings; $this->settings = $settings;
$this->appName = $appName; $this->appName = $appName;
} }
/** /**
* @param string $userId * @param string $userId
* @return array with all notes in the current directory * @return array with all notes in the current directory
*/ */
public function getAll ($userId, $onlyMeta=false) { public function getAll($userId, $onlyMeta = false) {
$notesFolder = $this->getFolderForUser($userId); $notesFolder = $this->getFolderForUser($userId);
$notes = $this->gatherNoteFiles($notesFolder); $notes = $this->gatherNoteFiles($notesFolder);
$filesById = []; $filesById = [];
foreach($notes as $note) { foreach ($notes as $note) {
$filesById[$note->getId()] = $note; $filesById[$note->getId()] = $note;
} }
$tagger = \OC::$server->getTagManager()->load('files'); $tagger = \OC::$server->getTagManager()->load('files');
if($tagger===null) { if ($tagger===null) {
$tags = []; $tags = [];
} else { } else {
$tags = $tagger->getTagsForObjects(array_keys($filesById)); $tags = $tagger->getTagsForObjects(array_keys($filesById));
} }
$notes = []; $notes = [];
foreach($filesById as $id=>$file) { foreach ($filesById as $id => $file) {
$notes[] = $this->getNote($file, $notesFolder, array_key_exists($id, $tags) ? $tags[$id] : [], $onlyMeta); $notes[] = $this->getNote($file, $notesFolder, array_key_exists($id, $tags) ? $tags[$id] : [], $onlyMeta);
} }
return $notes; return $notes;
} }
/** /**
* Used to get a single note by id * Used to get a single note by id
* @param int $id the id of the note to get * @param int $id the id of the note to get
* @param string $userId * @param string $userId
* @throws NoteDoesNotExistException if note does not exist * @throws NoteDoesNotExistException if note does not exist
* @return Note * @return Note
*/ */
public function get ($id, $userId) { public function get($id, $userId) {
$folder = $this->getFolderForUser($userId); $folder = $this->getFolderForUser($userId);
return $this->getNote($this->getFileById($folder, $id), $folder, $this->getTags($id)); return $this->getNote($this->getFileById($folder, $id), $folder, $this->getTags($id));
} }
private function getTags ($id) { private function getTags($id) {
$tagger = \OC::$server->getTagManager()->load('files'); $tagger = \OC::$server->getTagManager()->load('files');
if($tagger===null) { if ($tagger===null) {
$tags = []; $tags = [];
} else { } else {
$tags = $tagger->getTagsForObjects([$id]); $tags = $tagger->getTagsForObjects([$id]);
} }
return array_key_exists($id, $tags) ? $tags[$id] : []; return array_key_exists($id, $tags) ? $tags[$id] : [];
} }
private function getNote($file, $notesFolder, $tags=[], $onlyMeta=false) { private function getNote($file, $notesFolder, $tags = [], $onlyMeta = false) {
$id = $file->getId(); $id = $file->getId();
try { try {
$note = Note::fromFile($file, $notesFolder, $tags, $onlyMeta); $note = Note::fromFile($file, $notesFolder, $tags, $onlyMeta);
} catch(FileNotFoundException $e){ } catch (GenericEncryptionException $e) {
$note = Note::fromException($this->l10n->t('File error').': ('.$file->getName().') '.$e->getMessage(), $file, $notesFolder, array_key_exists($id, $tags) ? $tags[$id] : []); $note = Note::fromException($this->l10n->t('Encryption Error').': ('.$file->getName().') '.$e->getMessage(), $file, $notesFolder, array_key_exists($id, $tags) ? $tags[$id] : []);
} catch(GenericEncryptionException $e) { } catch (\Exception $e) {
$note = Note::fromException($this->l10n->t('Encryption Error').': ('.$file->getName().') '.$e->getMessage(), $file, $notesFolder, array_key_exists($id, $tags) ? $tags[$id] : []); $note = Note::fromException($this->l10n->t('Error').': ('.$file->getName().') '.$e->getMessage(), $file, $notesFolder, array_key_exists($id, $tags) ? $tags[$id] : []);
} catch(\Exception $e) { }
$note = Note::fromException($this->l10n->t('Error').': ('.$file->getName().') '.$e->getMessage(), $file, $notesFolder, array_key_exists($id, $tags) ? $tags[$id] : []); return $note;
} }
return $note;
}
/** /**
* Creates a note and returns the empty note * Creates a note and returns the empty note
* @param string $userId * @param string $userId
* @see update for setting note content * @see update for setting note content
* @return Note the newly created note * @return Note the newly created note
*/ */
public function create ($userId) { public function create($userId) {
$title = $this->l10n->t('New note'); $title = $this->l10n->t('New note');
$folder = $this->getFolderForUser($userId); $folder = $this->getFolderForUser($userId);
// check new note exists already and we need to number it // check new note exists already and we need to number it
// pass -1 because no file has id -1 and that will ensure // pass -1 because no file has id -1 and that will ensure
// to only return filenames that dont yet exist // to only return filenames that dont yet exist
$path = $this->generateFileName($folder, $title, $this->settings->get($userId, 'fileSuffix'), -1); $path = $this->generateFileName($folder, $title, $this->settings->get($userId, 'fileSuffix'), -1);
$file = $folder->newFile($path); $file = $folder->newFile($path);
// If server-side encryption is activated, the server creates an empty file without signature // If server-side encryption is activated, the server creates an empty file without signature
// which leads to an GenericEncryptionException('Missing Signature') afterwards. // which leads to an GenericEncryptionException('Missing Signature') afterwards.
// Saving a space-char (and removing it later) is a working work-around. // Saving a space-char (and removing it later) is a working work-around.
$file->putContent(' '); $file->putContent(' ');
return $this->getNote($file, $folder); return $this->getNote($file, $folder);
} }
/** /**
* Updates a note. Be sure to check the returned note since the title is * Updates a note. Be sure to check the returned note since the title is
* dynamically generated and filename conflicts are resolved * dynamically generated and filename conflicts are resolved
* @param int $id the id of the note used to update * @param int $id the id of the note used to update
* @param string $content the content which will be written into the note * @param string|null $content the content which will be written into the note
* the title is generated from the first line of the content * the title is generated from the first line of the content
* @param int $mtime time of the note modification (optional) * @param string|null $category the category in which the note should be saved
* @throws NoteDoesNotExistException if note does not exist * @param int $mtime time of the note modification (optional)
* @return \OCA\Notes\Db\Note the updated note * @throws NoteDoesNotExistException if note does not exist
*/ * @return \OCA\Notes\Db\Note the updated note
public function update ($id, $content, $userId, $category=null, $mtime=0) { */
$notesFolder = $this->getFolderForUser($userId); public function update($id, $content, $userId, $category = null, $mtime = 0) {
$file = $this->getFileById($notesFolder, $id); $notesFolder = $this->getFolderForUser($userId);
$title = $this->getSafeTitleFromContent( $content===null ? $file->getContent() : $content ); $file = $this->getFileById($notesFolder, $id);
$title = $this->getSafeTitleFromContent($content===null ? $file->getContent() : $content);
// rename/move file with respect to title/category // rename/move file with respect to title/category
// this can fail if access rights are not sufficient or category name is illegal // this can fail if access rights are not sufficient or category name is illegal
try { try {
$currentFilePath = $this->root->getFullPath($file->getPath()); $currentFilePath = $this->root->getFullPath($file->getPath());
$currentBasePath = pathinfo($currentFilePath, PATHINFO_DIRNAME); $currentBasePath = pathinfo($currentFilePath, PATHINFO_DIRNAME);
$fileSuffix = '.' . pathinfo($file->getName(), PATHINFO_EXTENSION); $fileSuffix = '.' . pathinfo($file->getName(), PATHINFO_EXTENSION);
// detect (new) folder path based on category name // detect (new) folder path based on category name
if($category===null) { if ($category===null) {
$basePath = $currentBasePath; $basePath = $currentBasePath;
} else { } else {
$basePath = $notesFolder->getPath(); $basePath = $notesFolder->getPath();
if(!empty($category)) { if (!empty($category)) {
// sanitise path // sanitise path
$cats = explode('/', $category); $cats = explode('/', $category);
$cats = array_map([$this, 'sanitisePath'], $cats); $cats = array_map([$this, 'sanitisePath'], $cats);
$cats = array_filter($cats, function($str) { return !empty($str); }); $cats = array_filter($cats, function ($str) {
$basePath .= '/'.implode('/', $cats); return !empty($str);
} });
} $basePath .= '/'.implode('/', $cats);
$folder = $this->getOrCreateFolder($basePath); }
}
$folder = $this->getOrCreateFolder($basePath);
// assemble new file path // assemble new file path
$newFilePath = $basePath . '/' . $this->generateFileName($folder, $title, $fileSuffix, $id); $newFilePath = $basePath . '/' . $this->generateFileName($folder, $title, $fileSuffix, $id);
// if the current path is not the new path, the file has to be renamed // if the current path is not the new path, the file has to be renamed
if($currentFilePath !== $newFilePath) { if ($currentFilePath !== $newFilePath) {
$file->move($newFilePath); $file->move($newFilePath);
} }
if($currentBasePath !== $basePath) { if ($currentBasePath !== $basePath) {
$this->deleteEmptyFolder($notesFolder, $this->root->get($currentBasePath)); $this->deleteEmptyFolder($notesFolder, $this->root->get($currentBasePath));
} }
} catch(\OCP\Files\NotPermittedException $e) { } catch (\OCP\Files\NotPermittedException $e) {
$this->logger->error('Moving note '.$id.' ('.$title.') to the desired target is not allowed. Please check the note\'s target category ('.$category.').', ['app' => $this->appName]); $this->logger->error('Moving note '.$id.' ('.$title.') to the desired target is not allowed. Please check the note\'s target category ('.$category.').', ['app' => $this->appName]);
} catch(\Exception $e) { } catch (\Exception $e) {
$this->logger->error('Moving note '.$id.' ('.$title.') to the desired target has failed with a '.get_class($e).': '.$e->getMessage(), ['app' => $this->appName]); $this->logger->error('Moving note '.$id.' ('.$title.') to the desired target has failed with a '.get_class($e).': '.$e->getMessage(), ['app' => $this->appName]);
} }
if($content !== null) { if ($content !== null) {
$file->putContent($content); $file->putContent($content);
} }
if($mtime) { if ($mtime) {
$file->touch($mtime); $file->touch($mtime);
} }
return $this->getNote($file, $notesFolder, $this->getTags($id)); return $this->getNote($file, $notesFolder, $this->getTags($id));
} }
/** /**
* Set or unset a note as favorite. * Set or unset a note as favorite.
* @param int $id the id of the note used to update * @param int $id the id of the note used to update
* @param boolean $favorite whether the note should be a favorite or not * @param boolean $favorite whether the note should be a favorite or not
* @throws NoteDoesNotExistException if note does not exist * @throws NoteDoesNotExistException if note does not exist
* @return boolean the new favorite state of the note * @return boolean the new favorite state of the note
*/ */
public function favorite ($id, $favorite, $userId){ public function favorite($id, $favorite, $userId) {
$folder = $this->getFolderForUser($userId); $folder = $this->getFolderForUser($userId);
$file = $this->getFileById($folder, $id); $file = $this->getFileById($folder, $id);
if(!$this->isNote($file)) { if (!$this->isNote($file)) {
throw new NoteDoesNotExistException(); throw new NoteDoesNotExistException();
} }
$tagger = \OC::$server->getTagManager()->load('files'); $tagger = \OC::$server->getTagManager()->load('files');
if($favorite) if ($favorite) {
$tagger->addToFavorites($id); $tagger->addToFavorites($id);
else } else {
$tagger->removeFromFavorites($id); $tagger->removeFromFavorites($id);
}
$tags = $tagger->getTagsForObjects([$id]); $tags = $tagger->getTagsForObjects([$id]);
return array_key_exists($id, $tags) && in_array(\OC\Tags::TAG_FAVORITE, $tags[$id]); return array_key_exists($id, $tags) && in_array(\OC\Tags::TAG_FAVORITE, $tags[$id]);
} }
/** /**
* Deletes a note * Deletes a note
* @param int $id the id of the note which should be deleted * @param int $id the id of the note which should be deleted
* @param string $userId * @param string $userId
* @throws NoteDoesNotExistException if note does not * @throws NoteDoesNotExistException if note does not
* exist * exist
*/ */
public function delete ($id, $userId) { public function delete($id, $userId) {
$notesFolder = $this->getFolderForUser($userId); $notesFolder = $this->getFolderForUser($userId);
$file = $this->getFileById($notesFolder, $id); $file = $this->getFileById($notesFolder, $id);
$parent = $file->getParent(); $parent = $file->getParent();
$file->delete(); $file->delete();
$this->deleteEmptyFolder($notesFolder, $parent); $this->deleteEmptyFolder($notesFolder, $parent);
} }
// removes characters that are illegal in a file or folder name on some operating systems // removes characters that are illegal in a file or folder name on some operating systems
private function sanitisePath($str) { private function sanitisePath($str) {
// remove characters which are illegal on Windows (includes illegal characters on Unix/Linux) // remove characters which are illegal on Windows (includes illegal characters on Unix/Linux)
// prevents also directory traversal by eliminiating slashes // prevents also directory traversal by eliminiating slashes
// see also \OC\Files\Storage\Common::verifyPosixPath(...) // see also \OC\Files\Storage\Common::verifyPosixPath(...)
$str = str_replace(['*', '|', '/', '\\', ':', '"', '<', '>', '?'], '', $str); $str = str_replace(['*', '|', '/', '\\', ':', '"', '<', '>', '?'], '', $str);
// if mysql doesn't support 4byte UTF-8, then remove those characters // if mysql doesn't support 4byte UTF-8, then remove those characters
// see \OC\Files\Storage\Common::verifyPath(...) // see \OC\Files\Storage\Common::verifyPath(...)
if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) { if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) {
$str = preg_replace('%(?: $str = preg_replace('%(?:
\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)%xs', '', $str); )%xs', '', $str);
} }
// prevent file to be hidden // prevent file to be hidden
$str = preg_replace("/^[\. ]+/mu", "", $str); $str = preg_replace("/^[\. ]+/mu", "", $str);
return trim($str); return trim($str);
} }
private function getSafeTitleFromContent($content) { private function getSafeTitleFromContent($content) {
// prepare content: remove markdown characters and empty spaces // prepare content: remove markdown characters and empty spaces
$content = preg_replace("/^\s*[*+-]\s+/mu", "", $content); // list item $content = preg_replace("/^\s*[*+-]\s+/mu", "", $content); // list item
$content = preg_replace("/^#+\s+(.*?)\s*#*$/mu", "$1", $content); // headline $content = preg_replace("/^#+\s+(.*?)\s*#*$/mu", "$1", $content); // headline
$content = preg_replace("/^(=+|-+)$/mu", "", $content); // separate line for headline $content = preg_replace("/^(=+|-+)$/mu", "", $content); // separate line for headline
$content = preg_replace("/(\*+|_+)(.*?)\\1/mu", "$2", $content); // emphasis $content = preg_replace("/(\*+|_+)(.*?)\\1/mu", "$2", $content); // emphasis
// sanitize: prevent directory traversal, illegal characters and unintended file names // sanitize: prevent directory traversal, illegal characters and unintended file names
$content = $this->sanitisePath($content); $content = $this->sanitisePath($content);
// generate title from the first line of the content // generate title from the first line of the content
$splitContent = preg_split("/\R/u", $content, 2); $splitContent = preg_split("/\R/u", $content, 2);
$title = trim($splitContent[0]); $title = trim($splitContent[0]);
// ensure that title is not empty // ensure that title is not empty
if(empty($title)) { if (empty($title)) {
$title = $this->l10n->t('New note'); $title = $this->l10n->t('New note');
} }
// using a maximum of 100 chars should be enough // using a maximum of 100 chars should be enough
$title = mb_substr($title, 0, 100, "UTF-8"); $title = mb_substr($title, 0, 100, "UTF-8");
return $title; return $title;
} }
/** /**
* @param Folder $folder * @param Folder $folder
* @param int $id * @param int $id
* @throws NoteDoesNotExistException * @throws NoteDoesNotExistException
* @return \OCP\Files\File * @return \OCP\Files\File
*/ */
private function getFileById ($folder, $id) { private function getFileById($folder, $id) {
$file = $folder->getById($id); $file = $folder->getById($id);
if(count($file) <= 0 || !$this->isNote($file[0])) { if (count($file) <= 0 || !$this->isNote($file[0])) {
throw new NoteDoesNotExistException(); throw new NoteDoesNotExistException();
} }
return $file[0]; return $file[0];
} }
/** /**
* @param string $userId the user id * @param string $userId the user id
* @return boolean true if folder is accessible, or Exception otherwise * @return boolean true if folder is accessible, or Exception otherwise
*/ */
public function checkNotesFolder($userId) { public function checkNotesFolder($userId) {
$folder = $this->getFolderForUser($userId); $this->getFolderForUser($userId);
return true; return true;
} }
/** /**
* @param string $userId the user id * @param string $userId the user id
* @return Folder * @return Folder
*/ */
private function getFolderForUser ($userId) { private function getFolderForUser($userId) {
$path = '/' . $userId . '/files/' . $this->settings->get($userId, 'notesPath'); $path = '/' . $userId . '/files/' . $this->settings->get($userId, 'notesPath');
try { try {
$folder = $this->getOrCreateFolder($path); $folder = $this->getOrCreateFolder($path);
} catch(\Exception $e) { } catch (\Exception $e) {
throw new NotesFolderException($path); throw new NotesFolderException($path);
} }
return $folder; return $folder;
} }
/** /**
* Finds a folder and creates it if non-existent * Finds a folder and creates it if non-existent
* @param string $path path to the folder * @param string $path path to the folder
* @return Folder * @return Folder
*/ */
private function getOrCreateFolder($path) { private function getOrCreateFolder($path) {
if ($this->root->nodeExists($path)) { if ($this->root->nodeExists($path)) {
$folder = $this->root->get($path); $folder = $this->root->get($path);
} else { } else {
$folder = $this->root->newFolder($path); $folder = $this->root->newFolder($path);
} }
return $folder; return $folder;
} }
/* /*
* Delete a folder and it's parent(s) if it's/they're empty * Delete a folder and it's parent(s) if it's/they're empty
* @param Folder root folder for notes * @param Folder root folder for notes
* @param Folder folder to delete * @param Folder folder to delete
*/ */
private function deleteEmptyFolder(Folder $notesFolder, Folder $folder) { private function deleteEmptyFolder(Folder $notesFolder, Folder $folder) {
$content = $folder->getDirectoryListing(); $content = $folder->getDirectoryListing();
$isEmpty = !count($content); $isEmpty = !count($content);
$isNotesFolder = $folder->getPath()===$notesFolder->getPath(); $isNotesFolder = $folder->getPath()===$notesFolder->getPath();
if($isEmpty && !$isNotesFolder) { if ($isEmpty && !$isNotesFolder) {
$this->logger->info('Deleting empty category folder '.$folder->getPath(), ['app' => $this->appName]); $this->logger->info('Deleting empty category folder '.$folder->getPath(), ['app' => $this->appName]);
$parent = $folder->getParent(); $parent = $folder->getParent();
$folder->delete(); $folder->delete();
$this->deleteEmptyFolder($notesFolder, $parent); $this->deleteEmptyFolder($notesFolder, $parent);
} }
} }
/** /**
* get path of file and the title.txt and check if they are the same * get path of file and the title.txt and check if they are the same
* file. If not the title needs to be renamed * file. If not the title needs to be renamed
* *
* @param Folder $folder a folder to the notes directory * @param Folder $folder a folder to the notes directory
* @param string $title the filename which should be used * @param string $title the filename which should be used
* @param string $suffix the suffix (incl. dot) which should be used * @param string $suffix the suffix (incl. dot) which should be used
* @param int $id the id of the note for which the title should be generated * @param int $id the id of the note for which the title should be generated
* used to see if the file itself has the title and not a different file for * used to see if the file itself has the title and not a different file for
* checking for filename collisions * checking for filename collisions
* @return string the resolved filename to prevent overwriting different * @return string the resolved filename to prevent overwriting different
* files with the same title * files with the same title
*/ */
private function generateFileName (Folder $folder, $title, $suffix, $id) { private function generateFileName(Folder $folder, $title, $suffix, $id) {
$path = $title . $suffix; $path = $title . $suffix;
// if file does not exist, that name has not been taken. Similar we don't // if file does not exist, that name has not been taken. Similar we don't
// need to handle file collisions if it is the filename did not change // need to handle file collisions if it is the filename did not change
if (!$folder->nodeExists($path) || $folder->get($path)->getId() === $id) { if (!$folder->nodeExists($path) || $folder->get($path)->getId() === $id) {
return $path; return $path;
} else { } else {
// increments name (2) to name (3) // increments name (2) to name (3)
$match = preg_match('/\((?P<id>\d+)\)$/u', $title, $matches); $match = preg_match('/\((?P<id>\d+)\)$/u', $title, $matches);
if($match) { if ($match) {
$newId = ((int) $matches['id']) + 1; $newId = ((int) $matches['id']) + 1;
$newTitle = preg_replace('/(.*)\s\((\d+)\)$/u', $newTitle = preg_replace(
'$1 (' . $newId . ')', $title); '/(.*)\s\((\d+)\)$/u',
} else { '$1 (' . $newId . ')',
$newTitle = $title . ' (2)'; $title
} );
return $this->generateFileName($folder, $newTitle, $suffix, $id); } else {
} $newTitle = $title . ' (2)';
} }
return $this->generateFileName($folder, $newTitle, $suffix, $id);
}
}
/** /**
* gather note files in given directory and all subdirectories * gather note files in given directory and all subdirectories
* @param Folder $folder * @param Folder $folder
* @return array * @return array
*/ */
private function gatherNoteFiles ($folder) { private function gatherNoteFiles($folder) {
$notes = []; $notes = [];
$nodes = $folder->getDirectoryListing(); $nodes = $folder->getDirectoryListing();
foreach($nodes as $node) { foreach ($nodes as $node) {
if($node->getType() === FileInfo::TYPE_FOLDER) { if ($node->getType() === FileInfo::TYPE_FOLDER) {
$notes = array_merge($notes, $this->gatherNoteFiles($node)); $notes = array_merge($notes, $this->gatherNoteFiles($node));
continue; continue;
} }
if($this->isNote($node)) { if ($this->isNote($node)) {
$notes[] = $node; $notes[] = $node;
} }
} }
@ -421,23 +415,24 @@ class NotesService {
} }
/** /**
* test if file is a note * test if file is a note
* *
* @param \OCP\Files\File $file * @param \OCP\Files\File $file
* @return bool * @return bool
*/ */
private function isNote($file) { private function isNote($file) {
$allowedExtensions = ['txt', 'org', 'markdown', 'md', 'note']; $allowedExtensions = ['txt', 'org', 'markdown', 'md', 'note'];
if($file->getType() !== 'file') return false; if ($file->getType() !== 'file') {
return false;
$ext = pathinfo($file->getName(), PATHINFO_EXTENSION); }
$iext = strtolower($ext);
if(!in_array($iext, $allowedExtensions)) {
return false;
}
return true;
}
$ext = pathinfo($file->getName(), PATHINFO_EXTENSION);
$iext = strtolower($ext);
if (!in_array($iext, $allowedExtensions)) {
return false;
}
return true;
}
} }

View File

@ -1,6 +1,7 @@
<?php <?php
namespace OCA\Notes\Service; namespace OCA\Notes\Service;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\IConfig; use OCP\IConfig;
@ -10,15 +11,15 @@ use OCP\IUserSession;
use OCP\Files\IRootFolder; use OCP\Files\IRootFolder;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\JSONResponse;
class SettingsService class SettingsService {
{
private $config; private $config;
private $root; private $root;
/* Default values */ /* Default values */
private $defaults = [ private $defaults = [
"notesPath" => "Notes", 'notesPath' => 'Notes',
"fileSuffix" => ".txt", 'fileSuffix' => '.txt',
]; ];
public function __construct( public function __construct(
@ -32,8 +33,8 @@ class SettingsService
*/ */
public function set($uid, $settings) { public function set($uid, $settings) {
// remove illegal, empty and default settings // remove illegal, empty and default settings
foreach($settings as $name => $value) { foreach ($settings as $name => $value) {
if(!array_key_exists($name, $this->defaults) if (!array_key_exists($name, $this->defaults)
|| empty($value) || empty($value)
|| $value === $this->defaults[$name] || $value === $this->defaults[$name]
) { ) {
@ -45,10 +46,10 @@ class SettingsService
public function getAll($uid) { public function getAll($uid) {
$settings = json_decode($this->config->getUserValue($uid, 'notes', 'settings')); $settings = json_decode($this->config->getUserValue($uid, 'notes', 'settings'));
if(is_object($settings)) { if (is_object($settings)) {
// use default for empty settings // use default for empty settings
foreach($this->defaults as $name => $defaultValue) { foreach ($this->defaults as $name => $defaultValue) {
if(!property_exists($settings, $name) || empty($settings->{$name})) { if (!property_exists($settings, $name) || empty($settings->{$name})) {
$settings->{$name} = $defaultValue; $settings->{$name} = $defaultValue;
} }
} }
@ -63,7 +64,7 @@ class SettingsService
*/ */
public function get($uid, $name) { public function get($uid, $name) {
$settings = $this->getAll($uid); $settings = $this->getAll($uid);
if(property_exists($settings, $name)) { if (property_exists($settings, $name)) {
return $settings->{$name}; return $settings->{$name};
} else { } else {
throw new \OCP\PreConditionNotMetException('Setting '.$name.' not found for user '.$uid.'.'); throw new \OCP\PreConditionNotMetException('Setting '.$name.' not found for user '.$uid.'.');

32
phpcs.xml Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0"?>
<ruleset name="MyStandard">
<description>
PSR2 with changes:
* tabs instead of spaces (https://gist.github.com/gsherwood/9d22f634c57f990a7c64)
* bracers on end of line instead new line
</description>
<!-- tabs -->
<arg name="tab-width" value="4"/>
<rule ref="PSR2">
<!-- bracers -->
<exclude name="Squiz.Functions.MultiLineFunctionDeclaration.BraceOnSameLine" />
<exclude name="PSR2.Classes.ClassDeclaration.OpenBraceNewLine" />
<!-- tabs -->
<exclude name="Generic.WhiteSpace.DisallowTabIndent"/>
</rule>
<!-- tabs -->
<rule ref="Generic.WhiteSpace.DisallowSpaceIndent"/>
<rule ref="Generic.WhiteSpace.ScopeIndent">
<properties>
<property name="indent" value="4"/>
<property name="tabIndent" value="true"/>
</properties>
</rule>
<!-- bracers -->
<rule ref="Generic.Functions.OpeningFunctionBraceKernighanRitchie" />
<rule ref="Generic.Classes.OpeningBraceSameLine"/>
</ruleset>