--- /dev/null
+<?php
+require_once (__DIR__ . '/../src/grauphel/Autoloader.php');
+grauphel\Autoloader::register();
+
+//OCP\App::registerAdmin( 'apptemplate', 'settings' );
+
+OCP\App::addNavigationEntry( array(
+ 'id' => 'grauphel',
+ 'order' => 2342,
+ 'href' => OCP\Util::linkTo( 'grauphel', 'index.php' ),
+ 'icon' => OCP\Util::imagePath( 'grauphel', 'notes.png' ),
+ 'name' => 'Tomboy notes'
+));
--- /dev/null
+<?php
+namespace OCA\Grauphel\AppInfo;
+use \OCP\AppFramework\App;
+
+class Application extends App
+{
+ public function __construct(array $urlParams=array())
+ {
+ parent::__construct('grauphel', $urlParams);
+
+ $container = $this->getContainer();
+
+ /**
+ * Controllers
+ */
+ $container->registerService(
+ 'ApiController',
+ function($c) {
+ return new \OCA\Grauphel\Controller\ApiController(
+ $c->query('AppName'),
+ $c->query('Request')
+ );
+ }
+ );
+ $container->registerService(
+ 'AccessController',
+ function($c) {
+ return new \OCA\Grauphel\Controller\AccessController(
+ $c->query('AppName'),
+ $c->query('Request')
+ );
+ }
+ );
+ $container->registerService(
+ 'OAuthController',
+ function($c) {
+ return new \OCA\Grauphel\Controller\OAuthController(
+ $c->query('AppName'),
+ $c->query('Request')
+ );
+ }
+ );
+ }
+}
+?>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<database>
+ <name>*dbname*</name>
+ <create>true</create>
+ <overwrite>false</overwrite>
+ <charset>utf8</charset>
+ <table>
+ <name>*dbprefix*grauphel_oauth_tokens</name>
+ <declaration>
+ <field>
+ <name>token_id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <length>11</length>
+ </field>
+ <field>
+ <name>token_user</name>
+ <type>text</type>
+ <notnull>false</notnull>
+ <length>64</length>
+ </field>
+ <field>
+ <name>token_type</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>16</length>
+ </field>
+ <field>
+ <name>token_key</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>128</length>
+ </field>
+ <field>
+ <name>token_secret</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>128</length>
+ </field>
+ <field>
+ <name>token_verifier</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>128</length>
+ </field>
+ <field>
+ <name>token_callback</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>2048</length>
+ </field>
+ </declaration>
+ </table>
+
+ <table>
+ <name>*dbprefix*grauphel_notes</name>
+ <declaration>
+ <field>
+ <name>note_id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <length>11</length>
+ </field>
+ <field>
+ <name>note_user</name>
+ <type>text</type>
+ <notnull>false</notnull>
+ <length>64</length>
+ </field>
+
+ <field>
+ <name>note_guid</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>128</length>
+ </field>
+
+ <field>
+ <name>note_create_date</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>32</length>
+ </field>
+ <field>
+ <name>note_last_change_date</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>32</length>
+ </field>
+ <field>
+ <name>note_last_metadata_change_date</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>32</length>
+ </field>
+
+ <field>
+ <name>note_title</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>1024</length>
+ </field>
+ <field>
+ <name>note_content</name>
+ <type>clob</type>
+ <notnull>true</notnull>
+ </field>
+ <field>
+ <name>note_content_version</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>16</length>
+ </field>
+ <field>
+ <name>note_open_on_startup</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <length>1</length>
+ </field>
+ <field>
+ <name>note_pinned</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <length>1</length>
+ </field>
+ <field>
+ <name>note_tags</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>1024</length>
+ </field>
+ </declaration>
+ </table>
+
+ <table>
+ <name>*dbprefix*grauphel_syncdata</name>
+ <declaration>
+ <field>
+ <name>syncdata_id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <length>11</length>
+ </field>
+ <field>
+ <name>syncdata_user</name>
+ <type>text</type>
+ <notnull>false</notnull>
+ <length>64</length>
+ </field>
+ <field>
+ <name>syncdata_current_sync_guid</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>64</length>
+ </field>
+ <field>
+ <name>syncdata_latest_sync_revision</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <length>11</length>
+ </field>
+ </declaration>
+ </table>
+</database>
--- /dev/null
+<?xml version="1.0"?>
+<info>
+ <id>grauphel</id>
+ <name>Grauphel: Tomboy note server</name>
+ <description>Tomboy REST API server to sync notes between devices</description>
+ <version>0.1</version>
+ <licence>AGPL3 or later</licence>
+ <author>Christian Weiske</author>
+ <requiremin>7</requiremin>
+</info>
--- /dev/null
+<?php
+namespace OCA\Grauphel\AppInfo;
+
+$application = new Application();
+$application->registerRoutes(
+ $this,
+ array(
+ 'routes' => array(
+ array(
+ 'url' => '/test',
+ 'name' => 'access#test',
+ ),
+
+ array(
+ 'url' => '/authorize',
+ 'name' => 'access#authorize',
+ 'verb' => 'GET',
+ ),/*
+ array(
+ 'url' => '/authorize',
+ 'name' => 'access#authorize',
+ 'verb' => 'POST',
+ ),*/
+ array(
+ 'url' => '/login',
+ 'name' => 'access#login',
+ 'verb' => 'GET',
+ ),
+
+ array(
+ 'url' => '/oauth/access_token',
+ 'name' => 'oauth#accessToken',
+ 'verb' => 'POST',
+ ),
+ array(
+ 'url' => '/oauth/authorize',
+ 'name' => 'oauth#authorize',
+ 'verb' => 'GET',
+ ),
+ array(
+ 'url' => '/oauth/request_token',
+ 'name' => 'oauth#requestToken',
+ 'verb' => 'POST',
+ ),
+
+ array(
+ 'url' => '/api/1.0',
+ 'name' => 'api#index',
+ 'verb' => 'GET',
+ ),
+ array(
+ 'url' => '/api/1.0/{user}/note/{guid}',
+ 'name' => 'api#note',
+ 'verb' => 'GET',
+ ),
+ array(
+ 'url' => '/api/1.0/{user}/notes',
+ 'name' => 'api#notes',
+ 'verb' => 'GET',
+ ),
+ array(
+ 'url' => '/api/1.0/{user}/notes',
+ 'name' => 'api#notes',
+ 'verb' => 'POST',
+ ),
+ array(
+ 'url' => '/api/1.0/{user}',
+ 'name' => 'api#user',
+ 'verb' => 'GET',
+ ),
+ )
+ )
+);
+
+?>
--- /dev/null
+<?php
+/**
+ * Part of grauphel
+ *
+ * PHP version 5
+ *
+ * @category Tools
+ * @package Grauphel
+ * @author Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link http://cweiske.de/grauphel.htm
+ */
+namespace OCA\Grauphel\Controller;
+use \OCP\AppFramework\Controller;
+
+/**
+ * Login and authorization handling
+ *
+ * @category Tools
+ * @package Grauphel
+ * @author Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version Release: @package_version@
+ * @link http://cweiske.de/grauphel.htm
+ */
+class AccessController extends Controller
+{
+ public function login($returnUrl = null)
+ {
+ $returnUrl = $this->loadReturnUrl($returnUrl);
+
+ if (isset($_POST['user']) && trim($_POST['user']) != '') {
+ $this->deps->frontend->setUser(trim($_POST['user']));
+ header('Location: ' . $returnUrl);
+ exit(0);
+ }
+
+ $hFormUrl = htmlspecialchars(
+ $this->deps->urlGen->addParams(
+ $this->deps->urlGen->accessLogin(),
+ array('returnurl' => $returnUrl)
+ )
+ );
+ //FIXME: do some real login
+ header('HTTP/1.0 200 OK');
+
+ echo <<<HTM
+<html>
+ <head>
+ <title>grauphel login</title>
+ </head>
+ <body>
+ <form method="post" action="$hFormUrl">
+ <p>
+ Log into <em>grauphel</em>:
+ </p>
+ <label>
+ User name:
+ <input id="user" type="text" name="user" size="20" value=""/>
+ </label>
+ <input type="submit" value="Login" />
+ </form>
+ <script type="text/javascript">
+//FIXME
+/*
+document.getElementById('user').value = 'cweiske';
+document.forms[0].submit();
+/**/
+ </script>
+ </body>
+</html>
+HTM;
+ exit(0);
+ }
+
+ public function authorize($returnUrl = null)
+ {
+ var_dump('asd');die();
+ $returnUrl = $this->loadReturnUrl($returnUrl);
+
+ if (isset($_POST['auth'])) {
+ if ($_POST['auth'] == 'ok') {
+ $this->deps->frontend->setAuth(true);
+ } else if ($_POST['auth'] == 'cancel') {
+ $this->deps->frontend->setAuth(false);
+ }
+ header('Location: ' . $returnUrl);
+ exit(0);
+ }
+
+ header('HTTP/1.0 200 OK');
+ $hFormUrl = htmlspecialchars(
+ $this->deps->urlGen->addParams(
+ $this->deps->urlGen->accessAuthorize(),
+ array('returnurl' => $returnUrl)
+ )
+ );
+
+ echo <<<HTM
+<html>
+ <head>
+ <title>grauphel authorization</title>
+ </head>
+ <body>
+ <form method="post" action="$hFormUrl">
+ <p>
+ Shall application FIXME get full access to the notes?
+ </p>
+ <button type="submit" name="auth" value="ok">Yes, authorize</button>
+ <button type="submit" name="auth" value="cancel">No, decline</button>
+ </body>
+</html>
+HTM;
+ exit(0);
+ }
+
+ protected function loadReturnUrl($returnUrl = null)
+ {
+ if ($returnUrl === null) {
+ if (isset($_GET['returnurl'])) {
+ $returnUrl = $_GET['returnurl'];
+ } else {
+ $returnUrl = $this->deps->urlGen->index();
+ }
+ }
+ return $returnUrl;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ * @PublicPage
+ */
+ public function test()
+ {
+ var_dump('asd');die();
+ $this->registerResponder('xml', function($value) {
+ return new XMLResponse($value);
+ });
+ return array('foo' => 'bar');
+ }
+}
+?>
--- /dev/null
+<?php
+/**
+ * Part of grauphel
+ *
+ * PHP version 5
+ *
+ * @category Tools
+ * @package Grauphel
+ * @author Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link http://cweiske.de/grauphel.htm
+ */
+namespace OCA\Grauphel\Controller;
+use \OCP\AppFramework\Controller;
+
+/**
+ * Tomboy's REST API
+ *
+ * @category Tools
+ * @package Grauphel
+ * @author Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version Release: @package_version@
+ * @link http://cweiske.de/grauphel.htm
+ */
+class ApiController extends Controller
+{
+ /**
+ * /api/1.0
+ */
+ public function index()
+ {
+ var_dump('asd');die();
+ $authenticated = false;
+ $oauth = new OAuth();
+ $oauth->setDeps($this->deps);
+ $urlGen = $this->deps->urlGen;
+
+ try {
+ $provider = new \OAuthProvider();
+ $oauth->registerHandler($provider)
+ ->registerAccessTokenHandler($provider);
+ $provider->checkOAuthRequest($urlGen->fullPath());
+ $authenticated = true;
+ $token = $this->deps->tokens->load('access', $provider->token);
+ $username = $token->user;
+
+ } catch (OAuth_Exception $e) {
+ $this->deps->renderer->errorOut($e->getMessage());
+ } catch (\OAuthException $e) {
+ if ($e->getCode() != OAUTH_PARAMETER_ABSENT) {
+ $oauth->error($e);
+ }
+ }
+
+ $data = array(
+ 'oauth_request_token_url' => $urlGen->oauthRequestToken(),
+ 'oauth_authorize_url' => $urlGen->oauthAuthorize(),
+ 'oauth_access_token_url' => $urlGen->oauthAccessToken(),
+ 'api-version' => '1.0',
+ );
+
+ if ($authenticated) {
+ $data['user-ref'] = array(
+ 'api-ref' => $urlGen->user($username),
+ 'href' => $urlGen->userHtml($username),
+ );
+ }
+
+ $this->deps->renderer->sendJson($data);
+ }
+
+ /**
+ * GET /api/1.0/$user/notes/$noteguid
+ */
+ public function note()
+ {
+ $username = $this->deps->urlGen->loadUsername();
+ $guid = $this->deps->urlGen->loadGuid();
+ $oauth = new OAuth();
+ $oauth->setDeps($this->deps);
+ $oauth->verifyOAuthUser($username, $this->deps->urlGen->note($username, $guid));
+
+ $note = $this->deps->notes->load($username, $guid, false);
+ if ($note === null) {
+ header('HTTP/1.0 404 Not Found');
+ header('Content-type: text/plain');
+ echo "Note does not exist\n";
+ exit(1);
+ }
+
+ $data = array('note' => array($note));
+ $this->deps->renderer->sendJson($data);
+ }
+
+ /**
+ * GET|PUT /api/1.0/$user/notes
+ */
+ public function notes()
+ {
+ $username = $this->deps->urlGen->loadUsername();
+ $oauth = new OAuth();
+ $oauth->setDeps($this->deps);
+ $oauth->verifyOAuthUser($username, $this->deps->urlGen->notes($username));
+
+ $syncdata = $this->deps->notes->loadSyncData($username);
+
+ $this->handleNoteSave($username, $syncdata);
+
+ $since = null;
+ if (isset($_GET['since'])) {
+ $since = (int) $_GET['since'];
+ }
+
+ if (isset($_GET['include_notes']) && $_GET['include_notes']) {
+ $notes = $this->deps->notes->loadNotesFull($username, $since);
+ } else {
+ $notes = $this->deps->notes->loadNotesOverview($username, $since);
+ }
+
+ //work around bug https://bugzilla.gnome.org/show_bug.cgi?id=734313
+ foreach ($notes as $note) {
+ if (isset($note->{'note-content-version'})) {
+ $note->{'note-content-version'} = 0.3;
+ }
+ }
+
+ $data = array(
+ 'latest-sync-revision' => $syncdata->latestSyncRevision,
+ 'notes' => $notes,
+ );
+ $this->deps->renderer->sendJson($data);
+ }
+
+ protected function handleNoteSave($username, $syncdata)
+ {
+ if ($_SERVER['REQUEST_METHOD'] != 'PUT') {
+ return;
+ }
+
+ $data = file_get_contents('php://input');
+ $putObj = json_decode($data);
+ if ($putObj === NULL) {
+ errorOut('Invalid JSON data in PUT request');
+ }
+
+ //structural validation
+ if (!isset($putObj->{'latest-sync-revision'})) {
+ errorOut('Missing "latest-sync-revision"');
+ }
+ if (!isset($putObj->{'note-changes'})) {
+ errorOut('Missing "note-changes"');
+ }
+ foreach ($putObj->{'note-changes'} as $note) {
+ if (!isset($note->guid) || $note->guid == '') {
+ errorOut('Missing "guid" on note');
+ }
+ }
+
+ //content validation
+ if ($putObj->{'latest-sync-revision'} != $syncdata->latestSyncRevision +1
+ && $syncdata->latestSyncRevision != -1
+ ) {
+ errorOut('Wrong "latest-sync-revision". You are not up to date.');
+ }
+
+ //update
+ ++$syncdata->latestSyncRevision;
+ foreach ($putObj->{'note-changes'} as $noteUpdate) {
+ $note = $this->deps->notes->load($username, $noteUpdate->guid);
+ if (isset($noteUpdate->command) && $noteUpdate->command == 'delete') {
+ $this->deps->notes->delete($username, $noteUpdate->guid);
+ } else {
+ $this->deps->notes->update(
+ $note, $noteUpdate, $syncdata->latestSyncRevision
+ );
+ $this->deps->notes->save($username, $note);
+ }
+ }
+
+ $this->deps->notes->saveSyncData($username, $syncdata);
+ }
+
+ /**
+ * GET /api/1.0/$user
+ */
+ public function user()
+ {
+ $username = $this->deps->urlGen->loadUsername();
+
+ $oauth = new OAuth();
+ $oauth->setDeps($this->deps);
+ $oauth->verifyOAuthUser($username, $this->deps->urlGen->user($username));
+
+ $syncdata = $this->deps->notes->loadSyncData($username);
+
+ $data = array(
+ 'user-name' => $username,
+ 'first-name' => null,
+ 'last-name' => null,
+ 'notes-ref' => array(
+ 'api-ref' => $this->deps->urlGen->notes($username),
+ 'href' => null,
+ ),
+ 'latest-sync-revision' => $syncdata->latestSyncRevision,
+ 'current-sync-guid' => $syncdata->currentSyncGuid,
+ );
+ $this->deps->renderer->sendJson($data);
+ }
+}
+?>
--- /dev/null
+<?php
+/**
+ * Part of grauphel
+ *
+ * PHP version 5
+ *
+ * @category Tools
+ * @package Grauphel
+ * @author Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link http://cweiske.de/grauphel.htm
+ */
+namespace OCA\Grauphel\Controller;
+use \OCP\AppFramework\Controller;
+
+/**
+ * OAuth handling
+ *
+ * @category Tools
+ * @package Grauphel
+ * @author Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version Release: @package_version@
+ * @link http://cweiske.de/grauphel.htm
+ */
+class OAuthController extends Controller
+{
+ /**
+ * Handle out an access token after verifying the verification token
+ * OAuth step 3 of 3
+ */
+ public function accessTokenAction()
+ {
+ $oauth = new OAuth();
+ $oauth->setDeps($this->deps);
+
+ try {
+ $provider = new \OAuthProvider();
+ $oauth->registerHandler($provider)
+ ->registerVerificationTokenHandler($provider);
+ $provider->checkOAuthRequest($this->deps->urlGen->oauthAccessToken());
+
+ $token = $this->deps->tokens->loadAndDelete('verify', $provider->token);
+
+ $newToken = new OAuth_Token('access');
+ $newToken->tokenKey = 'a' . bin2hex($provider->generateToken(8));
+ $newToken->secret = 's' . bin2hex($provider->generateToken(8));
+ $newToken->user = $token->user;
+ $this->deps->tokens->store($newToken);
+
+ $this->deps->renderer->sendFormData(
+ array(
+ 'oauth_token' => $newToken->tokenKey,
+ 'oauth_token_secret' => $newToken->secret,
+ )
+ );
+ exit(0);
+ } catch (\OAuthException $e) {
+ $oauth->error($e);
+ }
+ }
+
+ /**
+ * Log the user in and let him authorize that the app may access notes
+ * OAuth step 2 of 3
+ */
+ public function authorizeAction()
+ {
+ $oauth = new OAuth();
+ $oauth->setDeps($this->deps);
+
+ if (!isset($_REQUEST['oauth_token'])) {
+ $this->deps->renderer->errorOut('oauth_token missing');
+ }
+ if (!$oauth->validateToken($_REQUEST['oauth_token'])) {
+ $this->deps->renderer->errorOut('Invalid token string');
+ }
+
+ $reqToken = $_REQUEST['oauth_token'];
+
+ try {
+ $token = $this->deps->tokens->load('temp', $reqToken);
+ } catch (OAuth_Exception $e) {
+ $this->deps->renderer->errorOut($e->getMessage());
+ }
+
+ $authState = $this->deps->frontend->doAuthorize(
+ $this->deps->urlGen->current()
+ );
+ if ($authState === null) {
+ //this should not happen; doAuthorize() must block
+ exit(1);
+ }
+
+ try {
+ $token = $this->deps->tokens->loadAndDelete('temp', $reqToken);
+ } catch (OAuth_Exception $e) {
+ $this->deps->renderer->errorOut($e->getMessage());
+ }
+
+ if ($authState === false) {
+ //user declined
+
+ //http://wiki.oauth.net/w/page/12238543/ProblemReporting
+ $callback = $this->deps->urlGen->addParams(
+ $token->callback,
+ array(
+ 'oauth_token' => $token->tokenKey,
+ 'oauth_problem' => 'permission_denied',
+ )
+ );
+ header('Location: ' . $callback, true, 302);
+ exit(0);
+ }
+
+ //the user is logged in and authorized
+ $provider = new \OAuthProvider();
+
+ $newToken = new OAuth_Token('verify');
+ $newToken->tokenKey = $token->tokenKey;
+ $newToken->secret = $token->secret;
+ $newToken->verifier = 'v' . bin2hex($provider->generateToken(8));
+ $newToken->user = $this->deps->frontend->getUser();
+
+ $this->deps->tokens->store($newToken);
+
+ //redirect
+ //FIXME: if no callback is given, show the token to the user
+ $callback = $this->deps->urlGen->addParams(
+ $token->callback,
+ array(
+ 'oauth_token' => $newToken->tokenKey,
+ 'oauth_verifier' => $newToken->verifier
+ )
+ );
+
+ header('Location: ' . $callback, true, 302);
+ exit();
+ }
+
+ /**
+ * Create and return a request token.
+ * OAuth step 1 of 3
+ */
+ public function requestTokenAction()
+ {
+ $oauth = new OAuth();
+ $oauth->setDeps($this->deps);
+
+ try {
+ $provider = new \OAuthProvider();
+ $oauth->registerHandler($provider);
+ $provider->isRequestTokenEndpoint(true);
+ $provider->checkOAuthRequest($this->deps->urlGen->oauthRequestToken());
+
+ //store token + callback URI for later
+ $token = new OAuth_Token('temp');
+ $token->tokenKey = 'r' . bin2hex($provider->generateToken(8));
+ $token->secret = 's' . bin2hex($provider->generateToken(8));
+ $token->callback = $provider->callback;
+
+ $this->deps->tokens->store($token);
+
+ $this->deps->renderer->sendFormData(
+ array(
+ 'oauth_token' => $token->tokenKey,
+ 'oauth_token_secret' => $token->secret,
+ 'oauth_callback_confirmed' => 'TRUE'
+ )
+ );
+ } catch (\OAuthException $e) {
+ $oauth->error($e);
+ }
+ }
+}
+?>
--- /dev/null
+<?php
+
+// Check if we are a user
+OCP\User::checkLoggedIn();
+
+$somesetting = OCP\Config::getSystemValue( "somesetting", '' );
+OCP\App::setActiveNavigationEntry( 'grauphel' );
+$tmpl = new OCP\Template( 'grauphel', 'main', 'user' );
+$tmpl->assign( 'somesetting', $somesetting );
+$tmpl->printPage();
--- /dev/null
+../../grauphel/src
\ No newline at end of file
--- /dev/null
+<h1>tomboy notes</h1>
+
+<?php p($l->t('Some Setting'));?>: "<?php p($_['somesetting']); ?>"
--- /dev/null
+<form id="apptemplate">
+ <div class="section">
+ <h2><?php p($l->t('App Template'));?></h2>
+ <input type="text" name="somesetting" id="somesetting" value="<?php p($_['url']); ?>" placeholder="<?php p($l->t('Some Setting'));?>" />
+ <br />
+ <span class="msg"></span>
+ </div>
+</form>