diff options
| author | Christian Weiske <cweiske@cweiske.de> | 2014-10-23 23:07:01 +0200 |
|---|---|---|
| committer | Christian Weiske <cweiske@cweiske.de> | 2014-10-23 23:07:01 +0200 |
| commit | 8ee6bfe97633d31c6b89cebbc434837eca04d6dd (patch) | |
| tree | 3d346e6d6b18e608813924b784ebda1d6520beef | |
| parent | a6e6dc9e303ccddf1cb6e699f5e43295af361e0f (diff) | |
| download | grauphel-8ee6bfe97633d31c6b89cebbc434837eca04d6dd.tar.gz grauphel-8ee6bfe97633d31c6b89cebbc434837eca04d6dd.zip | |
note preview
| -rwxr-xr-x | appinfo/database.xml | 1 | ||||
| -rw-r--r-- | appinfo/routes.php | 5 | ||||
| -rw-r--r-- | controller/guicontroller.php | 48 | ||||
| -rw-r--r-- | grauphel.css | 30 | ||||
| -rw-r--r-- | lib/converter/html.php | 197 | ||||
| -rw-r--r-- | lib/notestorage.php | 22 | ||||
| -rw-r--r-- | templates/gui-note.php | 17 | ||||
| -rw-r--r-- | templates/tag.php | 28 | ||||
| -rw-r--r-- | tests/Lib/Converter/HtmlTest.php | 30 | ||||
| -rw-r--r-- | tests/data/formattest.html | 36 | ||||
| -rw-r--r-- | tests/data/formattest.tomboynotecontent | 36 | ||||
| -rw-r--r-- | tests/data/xss.html | 11 | ||||
| -rw-r--r-- | tests/data/xss.tomboynotecontent | 11 |
13 files changed, 465 insertions, 7 deletions
diff --git a/appinfo/database.xml b/appinfo/database.xml index 7419ee7..4738d60 100755 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="UTF-8" ?> <database> + <!-- http://www.wiltonhotel.com/_ext/pear/docs/MDB2/docs/xml_schema_documentation.html --> <name>*dbname*</name> <create>true</create> <overwrite>false</overwrite> diff --git a/appinfo/routes.php b/appinfo/routes.php index 19a4b93..ddfc90b 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -69,6 +69,11 @@ $application->registerRoutes( 'verb' => 'GET', ), array( + 'url' => '/note/{guid}', + 'name' => 'gui#note', + 'verb' => 'GET', + ), + array( 'url' => '/tokens', 'name' => 'gui#tokens', 'verb' => 'GET', diff --git a/controller/guicontroller.php b/controller/guicontroller.php index 6f0a15c..97a7f10 100644 --- a/controller/guicontroller.php +++ b/controller/guicontroller.php @@ -17,6 +17,7 @@ use \OCP\AppFramework\Controller; use \OCP\AppFramework\Http\TemplateResponse; use \OCA\Grauphel\Lib\Client; use \OCA\Grauphel\Lib\TokenStorage; +use \OCA\Grauphel\Lib\Response\ErrorResponse; /** * Owncloud frontend @@ -73,6 +74,47 @@ class GuiController extends Controller } /** + * Show contents of a note + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function note($guid) + { + $res = new TemplateResponse('grauphel', 'gui-note'); + + $note = $this->getNotes()->load($guid, false); + if ($note === null) { + return new ErrorResponse('Note does not exist'); + } + + $converter = new \OCA\Grauphel\Lib\Converter\Html(); + $converter->internalLinkHandler = array($this, 'noteLinkHandler'); + $res->setParams( + array( + 'note' => $note, + 'note-content' => $converter->convert( + $note->{'note-content'} + ), + ) + ); + + $this->addNavigation($res); + return $res; + } + + public function noteLinkHandler($noteTitle) + { + $guid = $this->getNotes()->loadGuidByTitle($noteTitle); + if ($guid === null) { + return '#'; + } + return $this->urlGen->linkToRoute( + 'grauphel.gui.note', array('guid' => $guid) + ); + } + + /** * Show all notes of a tag * * @NoAdminRequired @@ -81,6 +123,12 @@ class GuiController extends Controller public function tag($rawtag) { $notes = $this->getNotes()->loadNotesOverview(null, $rawtag); + usort( + $notes, + function($noteA, $noteB) { + return strcmp($noteA['title'], $noteB['title']); + } + ); $res = new TemplateResponse('grauphel', 'tag'); $res->setParams( diff --git a/grauphel.css b/grauphel.css index 6ba1cde..1ad5a2d 100644 --- a/grauphel.css +++ b/grauphel.css @@ -135,6 +135,9 @@ a.action.delete, table.table form button.action.delete { a.action { line-height: 30px; } +a.cellclick { + display: block; +} table.table form { display: inline; } @@ -142,3 +145,30 @@ table.table form button.action { border: none; background-color: transparent; } + +.muted { + color: #999; +} + +.note-content { + padding-top: 2ex; +} + +.note-content .strikethrough { + text-decoration: line-through; +} +.note-content .highlight { + background-color: yellow; +} +.note-content .small { + font-size: 80%; +} +.note-content .large { + font-size: 150%; +} +.note-content .huge { + font-size: 200%; +} +.note-content a { + text-decoration: underline; +}
\ No newline at end of file diff --git a/lib/converter/html.php b/lib/converter/html.php new file mode 100644 index 0000000..1723a5b --- /dev/null +++ b/lib/converter/html.php @@ -0,0 +1,197 @@ +<?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\Lib\Converter; +use \XMLReader; + +/** + * Convert Tomboy note XML to HTML + * + * Tomboy already ships with a converter: + * https://git.gnome.org/browse/tomboy/tree/Tomboy/Addins/ExportToHtml/ExportToHtml.xsl + * We cannot use it since we want nice callbacks, and we do not want to rely + * on the PHP XSL extension, and we have to fix the links. + * + * @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 Html +{ + protected static $tagMap = array( + 'list' => 'ul', + 'list-item' => 'li', + 'bold' => 'b', + 'italic' => 'i', + 'monospace' => 'tt', + ); + + protected static $styleClassMap = array( + 'strikethrough' => 'strikethrough', + 'highlight' => 'highlight', + 'size:small' => 'small', + 'size:large' => 'large', + 'size:huge' => 'huge', + ); + + public $internalLinkHandler; + + + + public function __construct() + { + $this->internalLinkHandler = array($this, 'internalLinkHandler'); + } + + /** + * Converts the tomboy note XML into HTML + * + * @param string $xmlContent Tomboy note content + * + * @return string HTML + */ + public function convert($xmlContent) + { + if (strpos($xmlContent, '</link:internal><link:internal>') !== false) { + $xmlContent = $this->fixNastyLinks($xmlContent); + } + + $html = ''; + $reader = new XMLReader(); + $reader->xml( + '<?xml version="1.0" encoding="utf-8"?>' . "\n" + . '<content xmlns:size="size" xmlns:link="link">' + . $xmlContent + . '</content>' + ); + + $withinLink = false; + $store = &$html; + while ($reader->read()) { + switch ($reader->nodeType) { + case XMLReader::ELEMENT: + //echo $reader->name . "\n"; + if (isset(static::$tagMap[$reader->name])) { + $store .= '<' . static::$tagMap[$reader->name] . '>'; + } else if (isset(static::$styleClassMap[$reader->name])) { + $store .= '<span class="' + . static::$styleClassMap[$reader->name] + . '">'; + } else if (substr($reader->name, 0, 5) == 'link:') { + $withinLink = true; + $linkText = ''; + $store = &$linkText; + } + break; + case XMLReader::END_ELEMENT: + if (isset(static::$tagMap[$reader->name])) { + $store .= '</' . static::$tagMap[$reader->name] . '>'; + } else if (isset(static::$styleClassMap[$reader->name])) { + $store .= '</span>'; + } else if (substr($reader->name, 0, 5) == 'link:') { + $withinLink = false; + $store = &$html; + $linkUrl = htmlspecialchars_decode(strip_tags($linkText)); + if ($reader->name == 'link:internal') { + $linkUrl = call_user_func($this->internalLinkHandler, $linkUrl); + } else { + $linkUrl = $this->fixLinkUrl($linkUrl); + } + $store .= '<a href="' . htmlspecialchars($linkUrl) . '">' + . $linkText + . '</a>'; + } + break; + case XMLReader::TEXT: + case XMLReader::SIGNIFICANT_WHITESPACE: + $store .= nl2br(htmlspecialchars($reader->value)); + break; + default: + throw new \Exception( + 'Unsupported XML node type: ' . $reader->nodeType + ); + } + } + + $html = str_replace("</ul><br />\n", "</ul>\n", $html); + + return $html; + } + + /** + * Fixes external URLs without a protocol + * + * @param string $linkUrl URL to fix + * + * @return string Fixed URL + */ + protected function fixLinkUrl($linkUrl) + { + if ($linkUrl{0} == '/') { + //Unix file path + $linkUrl = 'file://' . $linkUrl; + } + return $linkUrl; + } + + /** + * Dummy internal link handler that simply adds ".htm" to the note title + * + * @param string $linkUrl Title of page that is linked + * + * @return string URL to link to + */ + public function internalLinkHandler($linkUrl) + { + return $linkUrl . '.htm'; + } + + /** + * Re-arranges the XML of formatted links to that clean link tags can + * be generated. + * + * Tomboy 1.15.2 allows link formatting, and the resulting XML is a + * mess of multiple(!) link tags that are within or around other formatting + * tags. + * + * This method tries to re-arrange the links so that only a single link tag + * appears with all the formatting inside. + * + * @param string $xmlContent Tomboy note content + * + * @return string XML content, with re-arranged link tags. + */ + protected function fixNastyLinks($xmlContent) + { + preg_match_all( + '#(?:<.*>)?<link:internal>.+</link:internal><link:internal>.+</link:internal>#U', + $xmlContent, + $matches + ); + + foreach ($matches[0] as $nastyLink) { + $cleaner = str_replace('</link:internal><link:internal>', '', $nastyLink); + $cleaner = preg_replace('#<([a-z]+)><(link:internal)>#U', '<\2><\1>', $cleaner); + $cleaner = preg_replace('#</(link:internal)></([a-z]+)>#U', '</\2></\1>', $cleaner); + $cleaner = str_replace('</link:internal><link:internal>', '', $cleaner); + $xmlContent = str_replace($nastyLink, $cleaner, $xmlContent); + } + + return $xmlContent; + } +} +?> diff --git a/lib/notestorage.php b/lib/notestorage.php index fb68030..7ecf049 100644 --- a/lib/notestorage.php +++ b/lib/notestorage.php @@ -246,6 +246,28 @@ class NoteStorage } /** + * Load a GUID of a note by the note title + * + * @param string $title Note title + * + * @return string GUID, NULL if note could not be found + */ + public function loadGuidByTitle($title) + { + $row = \OC_DB::executeAudited( + 'SELECT note_guid FROM `*PREFIX*grauphel_notes`' + . ' WHERE `note_user` = ? AND `note_title` = ?', + array($this->username, $title) + )->fetchRow(); + + if ($row === false) { + return null; + } + + return $row['note_guid']; + } + + /** * Save a note into storage. * * @param object $note Note to save diff --git a/templates/gui-note.php b/templates/gui-note.php new file mode 100644 index 0000000..a7e0587 --- /dev/null +++ b/templates/gui-note.php @@ -0,0 +1,17 @@ +<link rel="stylesheet" href="<?php p(OCP\Util::linkTo('grauphel','grauphel.css')); ?>" type="text/css"/> + +<?php /** @var $l OC_L10N */ ?> +<?php $_['appNavigation']->printPage(); ?> + +<script type="text/javascript" src="<?php p(OCP\Util::linkTo('grauphel','js/grauphel.js')); ?>"></script> + +<div id="app-content" class="content"> + <h1><?php p($_['note']->title); ?></h1> + <p class="muted"> + Last modified: + <?php p(\OCP\Util::formatDate(strtotime($_['note']->{'last-change-date'}))); ?> + </p> + <div class="note-content"> + <?php echo $_['note-content']; ?> + </div> +</div> diff --git a/templates/tag.php b/templates/tag.php index 68fa813..8c7eecc 100644 --- a/templates/tag.php +++ b/templates/tag.php @@ -3,14 +3,28 @@ <?php /** @var $l OC_L10N */ ?> <?php $_['appNavigation']->printPage(); ?> -<div id="app-content" class="content"> +<div id="app-content" class="list"> <h1>Notebook: <?php p($_['tag']); ?></h1> - <p class="error"> - Work in progress: You can't do anything here. - </p> - <ul> + + <table class="table" id="grauphel-notes"> + <thead> + <tr> + <th>Title</th> + <th>Last change</th> + </tr> + </thead> + <tbody> + <?php foreach ($_['notes'] as $note) { ?> - <li data-id="<?php p($note['guid']); ?>"><a href="#"><?php p($note['title']); ?></a></li> + <tr id="note-<?php p($note['guid']); ?>"> + <td> + <a class="cellclick" href="<?php p(OCP\Util::linkToRoute('grauphel.gui.note', array('guid' => $note['guid']))); ?>"><?php p($note['title']); ?></a> + </td> + <td> + </td> + </tr> <?php } ?> - </ul> + + </tbody> + </table> </div> diff --git a/tests/Lib/Converter/HtmlTest.php b/tests/Lib/Converter/HtmlTest.php new file mode 100644 index 0000000..0a66ee4 --- /dev/null +++ b/tests/Lib/Converter/HtmlTest.php @@ -0,0 +1,30 @@ +<?php +require_once __DIR__ . '/../../../lib/converter/html.php'; + +class Lib_Converter_HtmlTest extends PHPUnit_Framework_TestCase +{ + public function testConvert() + { + $input = file_get_contents(__DIR__ . '/../../data/formattest.tomboynotecontent'); + + $converter = new OCA\Grauphel\Lib\Converter\Html(); + $output = $converter->convert($input); + $this->assertEquals( + file_get_contents(__DIR__ . '/../../data/formattest.html'), + $output + ); + } + + public function testXSS() + { + $input = file_get_contents(__DIR__ . '/../../data/xss.tomboynotecontent'); + + $converter = new OCA\Grauphel\Lib\Converter\Html(); + $output = $converter->convert($input); + $this->assertEquals( + file_get_contents(__DIR__ . '/../../data/xss.html'), + $output + ); + } +} +?>
\ No newline at end of file diff --git a/tests/data/formattest.html b/tests/data/formattest.html new file mode 100644 index 0000000..5bc3709 --- /dev/null +++ b/tests/data/formattest.html @@ -0,0 +1,36 @@ +Eine Zeile Text.<br /> +Zeilenumbruch.<br /> +Noch ein Zeilenumbruch.<br /> +<br /> +Eine Leerzeile obendrüber.<br /> +<br /> +Jetzt kommt eine Liste:<br /> +<ul><li>eins<br /> +</li><li>zwei<br /> +<ul><li>zwei-eins<br /> +<ul><li>zwei-eins-eins<br /> +</li><li>zwei-eins-zwei<br /> +</li></ul></li><li>zwei-zwei<br /> +</li></ul></li><li>drei<br /> +<ul><li>drei-eins<br /> +<ul><li>drei-eins-eins<br /> +<ul><li>drei-eins-eins-eins</li></ul></li></ul></li></ul></li></ul> +Formatierungen:<br /> +<b>Fetter Text</b><br /> +<i>Kursiver Text</i><br /> +<span class="strikethrough">Durchgestrichener Text</span><br /> +<span class="highlight">Hervorgehobener Text</span><br /> +<tt>Feste Breite</tt><br /> +<br /> +Schriftgrößen:<br /> +<span class="small">klein</span><br /> +normal<br /> +<span class="large">groß</span><br /> +<span class="huge">riesig</span><br /> +<br /> +Links:<br /> +<ul><li><a href="Test.htm">Test</a><br /> +</li><li><a href="http://cweiske.de/?foo#bar">http://cweiske.de/?foo#bar</a><br /> +</li><li><a href="file:///home/cweiske/fam.jpg">/home/cweiske/fam.jpg</a></li></ul> +<br /> +Ende.<br /> diff --git a/tests/data/formattest.tomboynotecontent b/tests/data/formattest.tomboynotecontent new file mode 100644 index 0000000..8f0b017 --- /dev/null +++ b/tests/data/formattest.tomboynotecontent @@ -0,0 +1,36 @@ +Eine Zeile Text. +Zeilenumbruch. +Noch ein Zeilenumbruch. + +Eine Leerzeile obendrüber. + +Jetzt kommt eine Liste: +<list><list-item dir="ltr">eins +</list-item><list-item dir="ltr">zwei +<list><list-item dir="ltr">zwei-eins +<list><list-item dir="ltr">zwei-eins-eins +</list-item><list-item dir="ltr">zwei-eins-zwei +</list-item></list></list-item><list-item dir="ltr">zwei-zwei +</list-item></list></list-item><list-item dir="ltr">drei +<list><list-item dir="ltr">drei-eins +<list><list-item dir="ltr">drei-eins-eins +<list><list-item dir="ltr">drei-eins-eins-eins</list-item></list></list-item></list></list-item></list></list-item></list> +Formatierungen: +<bold>Fetter Text</bold> +<italic>Kursiver Text</italic> +<strikethrough>Durchgestrichener Text</strikethrough> +<highlight>Hervorgehobener Text</highlight> +<monospace>Feste Breite</monospace> + +Schriftgrößen: +<size:small>klein</size:small> +normal +<size:large>groß</size:large> +<size:huge>riesig</size:huge> + +Links: +<list><list-item dir="ltr"><link:internal>Test</link:internal> +</list-item><list-item dir="ltr"><link:url>http://cweiske.de/?foo#bar</link:url> +</list-item><list-item dir="ltr"><link:url>/home/cweiske/fam.jpg</link:url></list-item></list> + +Ende. diff --git a/tests/data/xss.html b/tests/data/xss.html new file mode 100644 index 0000000..1908ce2 --- /dev/null +++ b/tests/data/xss.html @@ -0,0 +1,11 @@ +<h1>Hallo!</h1><br /> +Was ist denn <a href="foo">bar<br /> +<br /> +Link mit fett drin: <a href="Formattest.htm">Format<b>te</b>st</a>.<br /> +<ul><li>Liste<br /> +</li><li>ein <b>bisschen</b> fett<br /> +</li><li>mo<tt>nospac</tt>ed<br /> +</li><li></li></ul></div></div></div></div></div></li></ul> +<br /> +Link zum <a href="Formattest.htm"><span class="highlight">Form</span>a<tt>tt</tt>e<span class="huge">st</span></a>.<br /> +Link mit <a href="Sonder">zeichen.htm">Sonder">zeichen</a>.<br /> diff --git a/tests/data/xss.tomboynotecontent b/tests/data/xss.tomboynotecontent new file mode 100644 index 0000000..6a86314 --- /dev/null +++ b/tests/data/xss.tomboynotecontent @@ -0,0 +1,11 @@ +<h1>Hallo!</h1> +Was ist denn <a href="foo">bar + +Link mit fett drin: <link:internal>Format<bold>te</bold></link:internal><link:internal>st</link:internal>. +<list><list-item dir="ltr">Liste +</list-item><list-item dir="ltr">ein <bold>bisschen</bold> fett +</list-item><list-item dir="ltr">mo<monospace>nospac</monospace>ed +</list-item><list-item dir="ltr"></li></ul></div></div></div></div></div></list-item></list> + +Link zum <highlight><link:internal>Form</link:internal></highlight><link:internal>a<monospace>tt</monospace></link:internal><link:internal>e<size:huge>st</size:huge></link:internal>. +Link mit <link:internal>Sonder">zeichen</link:internal>. |
