diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..cfd4538 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +Copyright (c) 2013 Dev7studios + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/PicoEditor.php b/PicoEditor.php new file mode 100644 index 0000000..3c91b97 --- /dev/null +++ b/PicoEditor.php @@ -0,0 +1,417 @@ + true, + * 'password' => 'YOUR SHA-512 PASSWORD', + * 'url' => 'custom-admin-url' + * ); + * + * 3) Create your SHA-512 hased password using a site like: @link{http://crypo.in.ua/tools/eng_sha512.php} + * 4) Finally, visit http://yoursite.com/?admin and login. + * 5) That's it! + * + * @author Tyler Heshka + * @author Gilbert Pellegrom + * @link http://pico.dev7studios.com + * @license http://opensource.org/licenses/MIT + * @version 1.1 + * + */ +class PicoEditor extends AbstractPicoPlugin +{ + /** + * plugin is disabled by default. + * + * @see AbstractPicoPlugin::$enabled + */ + protected $enabled = false; + + /** + * login status + */ + private $is_admin; + + /** + * logging out + */ + private $is_logout; + + /** + * path to this plugin directory + * + * @see PicoEditor::onConfigLoaded() + */ + private $plugin_path; + + /** + * content directory + * + * @see Pico::getConfig() + */ + private $contentDir; + + /** + * content file extension + * + * @see Pico::getConfig() + */ + private $contentExt; + + /** + * Pico Editor password + */ + private $password; + + /** + * url rewriting enabled? + */ + private $urlRewriting; + + /** + * custom admin url + */ + private $adminUrl; + + /** + * Triggered after Pico reads its configuration. + * + * @see Pico::getConfig() + * + * @param array &$config array of config variables + */ + public function onConfigLoaded(&$config) + { + // not seeking admin page + $this->is_admin = false; + // not logging out + $this->is_logout = false; + // path to the plugin, used for rendering templates + $this->plugin_path = dirname(__FILE__); + // Pico's content dir + $this->contentDir = $config['content_dir']; + // Pico's content extention + $this->contentExt = $config['content_ext']; + // check config for url rewriting + if (isset($config['rewrite_url']) && + !empty($config['rewrite_url']) && + $config['rewrite_url'] == true) { + $this->urlRewriting = '/'; + } else { + $this->urlRewriting = '/?'; + } + // check configuration for password + if (isset($config['PicoEditor']['password']) && + !empty($config['PicoEditor']['password'])) { + $this->password = $config['PicoEditor']['password']; + } + // check configuration for custom admin url + if (isset($config['PicoEditor']['url']) && + !empty($config['PicoEditor']['url'])) { + $this->adminUrl = $config['PicoEditor']['url']; + } + // check for session + if (!isset($_SESSION)) { + session_start(); + } + } + + /** + * Triggered after Pico evaluated the request URL. + * + * @see Pico::getBaseUrl() + * @see Pico::getRequestUrl() + * + * @param string &$url request URL + */ + public function onRequestUrl(&$url) + { + // are we looking for admin? + if ($url == $this->adminUrl) { + $this->is_admin = true; + } + // are we looking for admin/new? + if ($url == 'admin/new') { + $this->doNew(); + } + // are we looking for admin/open? + if ($url == 'admin/open') { + $this->doOpen(); + } + // are we looking for admin/save? + if ($url == 'admin/save') { + $this->doSave(); + } + // are we looking for admin/delete? + if ($url == 'admin/delete') { + $this->doDelete(); + } + // are we looking for admin/logout? + if ($url == 'admin/logout') { + $this->is_logout = true; + } + } + + /** + * Triggered before Pico renders the page. + * + * @see Pico::getTwig() + * + * @param Twig_Environment &$twig twig template engine + * @param array &$twigVariables variables passed to the template + * @param string &$templateName name of the template to render + */ + public function onPageRendering(&$twig, &$twigVariables, &$templateName) + { + // LOGGING OUT + if ($this->is_logout) { + // destory the current session + session_destroy(); + // redirect to the login page... + header('Location: '.$twigVariables['base_url']. $this->urlRewriting .$this->adminUrl); + // don't continue to render template + exit; + } + + //LOGGING IN + if ($this->is_admin) { + // override 404 header + header($_SERVER['SERVER_PROTOCOL'].' 200 OK'); + + // have twig look for templates in our plugin directory + $loader = new Twig_Loader_Filesystem($this->plugin_path); + $twig->setLoader($loader); + + // customizable endpoint used in editor's template + $twigVariables['editor_url'] = $this->adminUrl; + + // check if no password exists + if (!$this->password) { + // set the error message + $twigVariables['login_error'] = 'No password set!'; + // render the login view + echo $twig->render('views/login.twig', $twigVariables); // Render login.twig + // don't continue to render template + exit; + } + // if no current session exists, + if (!isset($_SESSION['pico_logged_in']) || !$_SESSION['pico_logged_in']) { + // check that user is POSTing a password + if (isset($_POST['password'])) { + // does the password match the hashed password? + if (hash('sha512', $_POST['password']) == $this->password) { + // login success + $_SESSION['pico_logged_in'] = true; + } else { + // login failure + $twigVariables['login_error'] = 'Invalid password.'; + // render the login view + echo $twig->render('views/login.twig', $twigVariables); // Render login.twig + // don't continue to render template + exit; + } + } else { + // user did not submit a password. + echo $twig->render('views/login.twig', $twigVariables); // Render login.twig + // don't continue to render template + exit; + } + } + // session exists, render the editor... + echo $twig->render('views/editor.twig', $twigVariables); // Render editor.twig + // don't continue to render template + exit; + } + } + + /** + * Check the login status before manipulating files... + * + * @param bool $SESSION['pico_logged_in'] login status + */ + private function doCheckLogin() + { + if (!isset($_SESSION['pico_logged_in']) || + !$_SESSION['pico_logged_in']) { + die(json_encode(array('error' => 'Error: Unathorized'))); + } + } + + /** + * Create new file. + * + * @param void + * @return json/array the contents of the new file + */ + private function doNew() + { + /** + * TODO: Create new files in sub directories + */ + + // check for logged in + $this->doCheckLogin(); + // sanitize post title + $title = isset($_POST['title']) && $_POST['title'] ? strip_tags($_POST['title']) : ''; + // get base name + $file = $this->slugify(basename($title)); + // die if error... + if (!$file) { + die(json_encode(array('error' => 'Error: Invalid file name'))); + } + // clear errors + $error = ''; + // set file extension + $file .= $this->contentExt; + // the file content + $content = '--- +Title: '.$title.' +Description: +Author: +Date: '.date('Y/m/d').' +Robots: noindex,nofollow +Template: +---'; + // check for duplicates + if (file_exists($this->contentDir.$file)) { + $error = 'Error: A post already exists with this title'; + } else { + // save the file + file_put_contents($this->contentDir.$file, $content); + } + // return results + die(json_encode(array( + 'title' => $title, + 'content' => $content, + 'file' => basename(str_replace($this->contentExt, '', $file)), + 'error' => $error, + ))); + } + + /** + * Open a file. + * + * @param string $POST['file'] the file to open + */ + private function doOpen() + { + /** + * TODO: Error when opening files that reside in a sub/folder; what is + * causing this, and how can it be fixed? + */ + + $this->doCheckLogin(); + // check file url not blank + $file_url = isset($_POST['file']) && $_POST['file'] ? $_POST['file'] : ''; + // get the base filename + $file = urldecode(basename($file_url)); + // no file requested + if (!$file) { + die('Open Error: Invalid file '.$file.' at the URL: '.$file_url); + } + // append the content extension + $file .= $this->contentExt; + // does the file exist, or die + if (file_exists($this->contentDir.$file)) { + // open the file + die(file_get_contents($this->contentDir.$file)); + } else { + die('Open Error: Invalid file '.$file.' at the URL: '.$file_url); + } + } + + /** + * Save changes to a file. + * + * @param string $POST['file'] the file to save + * @param string $POST['contents'] the contents to save + */ + private function doSave() + { + /** + * TODO: save files that reside in sub directories + */ + + $this->doCheckLogin(); + // check file url not blank + $file_url = isset($_POST['file']) && $_POST['file'] ? $_POST['file'] : ''; + // get the base filename + $file = urldecode(basename($file_url)); + // no file requested + if (!$file) { + die('Save Error: Invalid file'); + } + // no content sent + $content = isset($_POST['content']) && $_POST['content'] ? $_POST['content'] : ''; + if (!$content) { + die('Save Error: Invalid content'); + } + // append the content extension + $file .= $this->contentExt; + // save the file + file_put_contents($this->contentDir.$file, $content); + // show the saved contents + die($content); + } + + /** + * Delete a file. + * + * @param string $POST['file'] the file to delete + */ + private function doDelete() + { + /** + * TODO: delete files that reside in sub directories + */ + + $this->doCheckLogin(); + // check file url not blank + $file_url = isset($_POST['file']) && $_POST['file'] ? $_POST['file'] : ''; + // get the base filename + $file = urldecode(basename($file_url)); + // no file was requested + if (!$file) { + die('Delete Error: Invalid file'); + } + // append the content extension + $file .= $this->contentExt; + // if file exists, + if (file_exists($this->contentDir.$file)) { + // delete the file + die(unlink($this->contentDir.$file)); + } + } + + /** + * Create a url-friendly post slug + * + * @param string &$output contents which will be sent to the user + */ + private function slugify($text) + { + // replace non letter or digits by - + $text = preg_replace('~[^\\pL\d]+~u', '-', $text); + // trim + $text = trim($text, '-'); + // transliterate + $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); + // lowercase + $text = strtolower($text); + // remove unwanted characters + $text = preg_replace('~[^-\w]+~', '', $text); + // in case of empty text + if (empty($text)) { + return 'n-a'; + } + // return result + return $text; + } +} diff --git a/README.md b/README.md index 0e09b63..b831f00 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,20 @@ Provides an online Markdown editor and file manager for Pico. Install ------- -1. Extract download an copy the "pico_editor" folder to your Pico install "plugins" folder -2. Open the pico_editor_config.php file and insert your sha1 hashed password -3. Visit http://www.yoursite.com/admin and login -4. Thats it :) \ No newline at end of file +1. Extract a copy the "PicoEditor" folder to your Pico install "plugins" folder + - or `git clone https://github.com/theshka/Pico-Editor-Plugin.git PicoEditor` +2. Place the following in your `config/config.php` file +```php +// Pico Editor Configuration +$config['PicoEditor'] = array( + 'enabled' => true, + 'password' => 'YOUR SHA-512 PASSWORD', + 'url' => 'custom-admin-url' +); +``` +3. Create your `SHA-512` hashed password (http://crypo.in.ua/tools/eng_sha512.php) +4. Visit http://yoursite.com/?custom-admin-url and login +5. Thats it :) + +--- +Forked from: https://github.com/gilbitron/Pico-Editor-Plugin diff --git a/pico_editor.css b/assets/PicoEditor.css similarity index 78% rename from pico_editor.css rename to assets/PicoEditor.css index 1a5f5be..278bab9 100644 --- a/pico_editor.css +++ b/assets/PicoEditor.css @@ -72,7 +72,7 @@ label, input[type=button], input[type=submit], button { cursor: pointer; } -/* make buttons play nice in IE: +/* make buttons play nice in IE: www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ */ button { width: auto; @@ -86,8 +86,8 @@ img { /* Input Styles /*---------------------------------------------*/ -input, -textarea, +input, +textarea, select { font: 14px/20px "Helvetica Neue", Helvetica, Arial, sans-serif; color: #55606E; @@ -120,7 +120,7 @@ label { color: #555; } body { font: 14px/1.8em 'Open Sans', Helvetica, Arial, Helvetica, sans-serif; color: #222; - background: #F9FBFC; + background: #2EAE9B; overflow: hidden; -webkit-font-smoothing: antialiased; -webkit-box-sizing: border-box; @@ -143,8 +143,39 @@ a:hover, a:active { text-decoration: none; } +.wrapper { + background: #707070; + position: absolute; + top: 50%; + left: 0; + width: 100%; + height: 300px; + margin-top: -200px; + overflow: hidden; +} +.wrapper.form-success .container h1 { + -webkit-transform: translateY(85px); + -ms-transform: translateY(85px); + transform: translateY(85px); +} +.container { + max-width: 600px; + margin: 0 auto; + padding: 80px 0; + height: 400px; + text-align: center; +} +.container h1 { + font-size: 40px; + -webkit-transition-duration: 1s; + transition-duration: 1s; + -webkit-transition-timing-function: ease-in-put; + transition-timing-function: ease-in-put; + font-weight: 200; +} + .btn { - display: inline-block; + display: inline-block; line-height: 20px; padding: 1px 7px; text-shadow: #FFF 0 1px 0; @@ -171,6 +202,7 @@ a:hover, a:active { } #sidebar { + background: #fff; position: absolute; top: 0; left: 0; @@ -197,8 +229,8 @@ a:hover, a:active { -moz-box-shadow: 0px 1px 1px rgba(0,0,0,0.07); box-shadow: 0px 1px 1px rgba(0,0,0,0.07); } -#sidebar .controls .new { - float: right; +#sidebar .controls .new { + float: right; margin-top: 5px; } #sidebar .nav { @@ -206,9 +238,9 @@ a:hover, a:active { margin: 38px 0 25px 0; padding: 0; } -#sidebar li { +#sidebar li { line-height: 25px; - position: relative; + position: relative; border-bottom: 1px solid #DCE0E3; } #sidebar li a { @@ -248,6 +280,7 @@ a:hover, a:active { #sidebar li:hover .delete { opacity: 1; } #main { + background: #fff; position: absolute; top: 0; left: 0; @@ -285,28 +318,54 @@ a:hover, a:active { /* Login Styles /*---------------------------------------------*/ - -#login-form { - width: 300px; - margin: 200px auto; +#login .container h1 { + color: #fff; } -#login-form h1 { - font-size: 20px; - font-weight: bold; - margin-bottom: 20px; +#login-form form { + padding: 20px 0; + position: relative; + z-index: 2; } #login-form input { - margin-right: 5px; - width: 227px; -} -#login-form .btn { - width: auto; - padding-left: 10px; - padding-right: 10px; -} -#login-form .error { - color: red; - margin-bottom: 10px; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + outline: 0; + border: 1px solid rgba(255, 255, 255, 0.4); + background-color: rgba(255, 255, 255, 1); + width: 250px; + border-radius: 3px; + padding: 10px 15px; + margin: 0 auto 10px auto; + display: block; + text-align: center; + font-size: 18px; + color: white; + -webkit-transition-duration: 0.25s; + transition-duration: 0.25s; + font-weight: 300; +} +#login-form input:hover { + background-color: rgba(255, 255, 255, 0.4); +} +#login-form input:focus { + background-color: white; + width: 300px; + color: #53e3a6; +} +#login-form button { + outline: 0; + background-color: white; + border: 0; + padding: 10px 15px; + color: #53e3a6; + border-radius: 3px; + width: 250px; + cursor: pointer; + font-size: 18px; +} +#login-form button:hover { + background-color: #f5f7f9; } /* Misc Styles @@ -324,4 +383,4 @@ a:hover, a:active { position: relative; top: 1px; z-index: 0; -} \ No newline at end of file +} diff --git a/epiceditor/images/edit.png b/assets/epiceditor/images/edit.png similarity index 100% rename from epiceditor/images/edit.png rename to assets/epiceditor/images/edit.png diff --git a/epiceditor/images/fullscreen.png b/assets/epiceditor/images/fullscreen.png similarity index 100% rename from epiceditor/images/fullscreen.png rename to assets/epiceditor/images/fullscreen.png diff --git a/epiceditor/images/preview.png b/assets/epiceditor/images/preview.png similarity index 100% rename from epiceditor/images/preview.png rename to assets/epiceditor/images/preview.png diff --git a/epiceditor/js/epiceditor.js b/assets/epiceditor/js/epiceditor.js similarity index 100% rename from epiceditor/js/epiceditor.js rename to assets/epiceditor/js/epiceditor.js diff --git a/epiceditor/js/epiceditor.min.js b/assets/epiceditor/js/epiceditor.min.js similarity index 100% rename from epiceditor/js/epiceditor.min.js rename to assets/epiceditor/js/epiceditor.min.js diff --git a/epiceditor/themes/base/epiceditor.css b/assets/epiceditor/themes/base/epiceditor.css similarity index 100% rename from epiceditor/themes/base/epiceditor.css rename to assets/epiceditor/themes/base/epiceditor.css diff --git a/epiceditor/themes/editor/epic-dark.css b/assets/epiceditor/themes/editor/epic-dark.css similarity index 100% rename from epiceditor/themes/editor/epic-dark.css rename to assets/epiceditor/themes/editor/epic-dark.css diff --git a/epiceditor/themes/editor/epic-light.css b/assets/epiceditor/themes/editor/epic-light.css similarity index 100% rename from epiceditor/themes/editor/epic-light.css rename to assets/epiceditor/themes/editor/epic-light.css diff --git a/epiceditor/themes/preview/bartik.css b/assets/epiceditor/themes/preview/bartik.css similarity index 100% rename from epiceditor/themes/preview/bartik.css rename to assets/epiceditor/themes/preview/bartik.css diff --git a/epiceditor/themes/preview/github.css b/assets/epiceditor/themes/preview/github.css similarity index 100% rename from epiceditor/themes/preview/github.css rename to assets/epiceditor/themes/preview/github.css diff --git a/epiceditor/themes/preview/preview-dark.css b/assets/epiceditor/themes/preview/preview-dark.css similarity index 100% rename from epiceditor/themes/preview/preview-dark.css rename to assets/epiceditor/themes/preview/preview-dark.css diff --git a/fonts/icomoon.dev.svg b/assets/fonts/icomoon.dev.svg similarity index 100% rename from fonts/icomoon.dev.svg rename to assets/fonts/icomoon.dev.svg diff --git a/fonts/icomoon.eot b/assets/fonts/icomoon.eot similarity index 100% rename from fonts/icomoon.eot rename to assets/fonts/icomoon.eot diff --git a/fonts/icomoon.svg b/assets/fonts/icomoon.svg similarity index 100% rename from fonts/icomoon.svg rename to assets/fonts/icomoon.svg diff --git a/fonts/icomoon.ttf b/assets/fonts/icomoon.ttf similarity index 100% rename from fonts/icomoon.ttf rename to assets/fonts/icomoon.ttf diff --git a/fonts/icomoon.woff b/assets/fonts/icomoon.woff similarity index 100% rename from fonts/icomoon.woff rename to assets/fonts/icomoon.woff diff --git a/license.txt b/license.txt deleted file mode 100644 index 7160258..0000000 --- a/license.txt +++ /dev/null @@ -1,22 +0,0 @@ - Copyright (c) 2013 Dev7studios - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. diff --git a/login.html b/login.html deleted file mode 100644 index 35dc798..0000000 --- a/login.html +++ /dev/null @@ -1,20 +0,0 @@ - - -
- -