diff --git a/README.md b/README.md index 783d419..2041f2d 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,13 @@ In the root folder of this app, there are two files: config.php and config.t2t - config.t2t is for tweaking your wiki syntax. You can define more tags (using the txt2tags rules) for specific behaviors. +The pheditor.php tool at the root of the site allows you to edit all the necessary configuration files. You can remove or rename it from its interface. The password is the same as the one defined during installation. + + ## Documentation - * Official documentation: https://github.com/farvardin/lionwiki-t2t + * Official documentation: https://lionwiki-t2t.sourceforge.io/ * More about txt2tags: https://github.com/farvardin/whatistxt2tags @@ -57,7 +60,7 @@ You can install multiple instance of this app. ## Limitations -* No different user accounts. Configuration only by ssh. +* No different user accounts. ## Additional information @@ -104,6 +107,6 @@ MIT license ## Links * Report a bug: https://github.com/YunoHost-Apps/lionwiki-t2t_ynh/issues - * App website: https://github.com/farvardin/lionwiki-t2t + * App website: https://lionwiki-t2t.sourceforge.io/ * YunoHost website: https://yunohost.org/ diff --git a/README_fr.md b/README_fr.md index b902386..52f2ace 100644 --- a/README_fr.md +++ b/README_fr.md @@ -30,10 +30,12 @@ Dans le dossier racine de l'application il y a 2 fichiers, config.php et config. - config.t2t est pour customiser votre syntaxe wiki. Vous pouvez définir là de nouvelles balises (utilisant les règles txt2tags) pour des comportements spécifiques. +- L'outil pheditor.php à la racine du site permet d'éditer tous les fichiers de configuration nécessaires. Vous pouvez le retirer ou le renommer depuis son interface. Le mot de passe est le même que celui définit lors de l'installation. + ## Documentation - * Documentation officielle: https://github.com/farvardin/lionwiki-t2t + * Documentation officielle: https://lionwiki-t2t.sourceforge.io/ * À propos de txt2tags: https://github.com/farvardin/whatistxt2tags ## Caractéristiques spécifiques YunoHost @@ -54,7 +56,8 @@ Vous pouvez passer le wiki en mode privé ou public, selon vos usages. ## Limitations -* Un seul compte utilisateur. Configuration seulement par fichier éditable en ssh. +* Un seul compte utilisateur. + ## Informations additionnelles @@ -97,7 +100,7 @@ Licence MIT ## Liens * Signaler un bug: https://github.com/YunoHost-Apps/lionwiki-t2t_ynh/issues - * Site de l'application: https://github.com/farvardin/lionwiki-t2t + * Site de l'application: https://lionwiki-t2t.sourceforge.io/ * Site web YunoHost: https://yunohost.org/ diff --git a/conf/app.src b/conf/app.src index efc4d34..aa6e7b0 100644 --- a/conf/app.src +++ b/conf/app.src @@ -1,5 +1,5 @@ SOURCE_URL=https://sourceforge.net/projects/lionwiki-t2t/files/lionwiki-t2t.zip -SOURCE_SUM=7512d7a35e846f19fbce6a3276594f37afba746db513a25378f1460d4d52c30a +SOURCE_SUM=e2b1cbb539375ebb53a2be3a3df766517039258f1dbeea8bba449cbbabed168c SOURCE_SUM_PRG=sha256sum SOURCE_FORMAT=zip SOURCE_IN_SUBDIR=true diff --git a/conf/config.php b/conf/config.php index f5c04aa..ab573c9 100644 --- a/conf/config.php +++ b/conf/config.php @@ -1,28 +1,54 @@ - + + + + + {PAGE_TITLE_HEAD - }{WIKI_TITLE} + + + + + {HEAD} + + + + + +
+ +
+
+

{PAGE_TITLE} {( plugin:VERSIONS_LIST )}

+
+ + {
ERROR
} + {CONTENT} + {plugin:TAG_LIST} + {CONTENT_FORM} + + + +
+ + + + + + + + + + + +
{RENAME_TEXT }{RENAME_INPUT }{plugin:TOOLBAR_TEXTAREA}{SHOW_PAGE   }{SYNTAX}
{CONTENT_TEXTAREA}
{FORM_PASSWORD}{ FORM_PASSWORD_INPUT}{ plugin:CAPTCHA_QUESTION}{ plugin:CAPTCHA_INPUT}{ EDIT_SUMMARY_TEXT}{ EDIT_SUMMARY_INPUT}{ CONTENT_SUBMIT}{ CONTENT_PREVIEW}{plugin:RESIZE_TEXTAREA}
+ {/CONTENT_FORM}
+ + +
{SEARCH_FORM}{SEARCH_INPUT}{SEARCH_SUBMIT}{/SEARCH_FORM} Powered by LionWiki-t2t +
+ +
+ +
+ + diff --git a/conf/ggp.less b/conf/ggp.less new file mode 100755 index 0000000..91019d7 --- /dev/null +++ b/conf/ggp.less @@ -0,0 +1,547 @@ +/*----- + © 2012 GGP & al.jes, certains droits réservés… + http://geekygoblin.org + http://aljes.me + Cette oeuvre est libre, vous pouvez la copier, la diffuser et la modifier selon les termes de la licence Art Libre + http://www.artlibre.org + http://geekygoblin.org/mentions.htm +-----*/ + +/* 0 - Préliminaires */ + + + + +/* font colors:*/ +@MainColor: #__YNH_COLOR__; +/* or D17732 */ +@FontColor: darken(desaturate(@MainColor, 80%),30%) ; +@SecondaryColor: spin(@LinkColor, 25) ; +@LinkColor: @MainColor; +@LinkColorHover: spin(@LinkColor, 180) ; + +/* background color: */ +@BackgroundColor: #fAfAfA; + +/*@BackgroundColor: contrast(@FontColor,#444,#999) ;*/ +/*@BackgroundColor: contrast(@FontColor) ; */ + +/*@BackgroundColor: mix(@MainColor, @LinkColor) ; */ + + + + + + +/* for diff tool */ +@RedBrick: #7F4736; + +/* INITIAL SETTINGS +@MainColor: #292929 ; +@SecondaryColor: #42C200 ; +@LinkColor: #328cc1 ; +@LinkColorHover: #d9b310 ; +@BackgroundColor: #FAFAFA ; +*/ + + +/* 1 - Typographie */ + +@MainFontFamily: Fengardo; +@MainFontFamilyEdit:Fengardo; + +@font-face { + font-family: @MainFontFamily; + font-style: normal; + font-weight: normal; + src: url(../fonts/fengardoneue-regular.woff) format('woff'), url(../fonts/fengardoneue-regular.otf) format('opentype'), url(../fonts/fengardoneue-regular.svg) format('svg'); +} + +@font-face { + font-family: Fengardo; + font-style: italic; + src: url(../fonts/fengardoneue-italic.otf) format('opentype'); +} + +@font-face { + font-family: Fengardo; + font-weight: bold; + /*font-weight: 500;*/ + src: url(../fonts/fengardoneue-black.woff) format('woff'), url(../fonts/fengardoneue-black.otf) format('opentype'), url(../fonts/fengardoneue-black.svg) format('svg'); +} + +/* 2 - Général */ + +#header li {float: left; list-style: none; position: relative; width: 25%; } + +/*ul li {list-style: none;} +ul li::before {content: "– ";}*/ +/*hr::after {content: "⁂";}*/ +/* 3 - Titres */ +/* 4 - Menus */ +/* 5 - Colophon */ + +* { + border: 1; + box-sizing: border-box; + font: inherit; + font-size: 100%; + line-height: 1.5 !important; + max-width: 100%; + /*outline: 0; + text-decoration: none;*/ + vertical-align: baseline; +} +html { + background-color: @BackgroundColor; + color: @FontColor; + font-family: Fengardo, sans-serif; + font-size: 125%; + -epub-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; + text-align: justify; +} +body { + margin: 0 auto; + max-width: 700px; + width: 85%; +} +img.border { + border: solid 1px @MainColor; +} +em, i { + font-style: italic; +} +strong, b { + font-weight: bold; +} + +b i { + font-weight: bolder; + font-weight: 900; + font-style: italic; +} + +li { + ul { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + margin-left: 0rem; + } + ol { + margin-bottom: 0; + margin-left: 2rem; + } + dl { + margin-bottom: 0; + margin-left: 2rem; + } +} +dd { + ul { + margin-bottom: 0; + margin-left: 2rem; + } + ol { + margin-bottom: 0; + margin-left: 2rem; + } + dl { + margin-bottom: 0; + margin-left: 2rem; + } +} +blockquote { + margin-left: 1rem; + p { + &::before { + content: "— "; + } + } +} +q { + &::before { + content: "« "; + } + &::after { + content: " »"; + } +} +code { + font-family: monospace; + font-size: 0.8rem; + -epub-hyphens: none; + -moz-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} +pre { + code { + display: block; + overflow: auto; + } +} +a { + /* border-bottom: 1px solid @SecondaryColor; */ + color: inherit; + margin-bottom: -1px; + text-decoration: none; + color: @LinkColor; + &:hover { + border-color: inherit; + color: @LinkColorHover; + text-decoration: underline; + } + &:focus { + border-color: inherit; + color: @LinkColorHover; + text-decoration: none; + } +} +abbr { + border-bottom: 1px dashed @SecondaryColor; + margin-bottom: -1px; +} +acronym { + border-bottom: 1px dashed @SecondaryColor; + margin-bottom: -1px; +} +a.definition { + border-bottom: 1px dashed @SecondaryColor; + margin-bottom: -1px; +} +a.no-border { + border: 0; +} +hr { + border: 1; + /*height: 1rem;*/ + margin: 1rem auto; + width: 1; +} +.skip { + font-size: 0.7rem; + margin-bottom: 0.3rem; + text-align: right; + p { + margin: 0; + } + a { + margin-left: 1rem; + } +} + +/* disable par-edit visibility anytime */ + +h2 .par-edit,h3 .par-edit,h4 .par-edit,h5 .par-edit,h6 .par-edit{visibility:hidden;font-size:x-small;} + +h2:hover .par-edit,h3:hover .par-edit,h4:hover .par-edit,h5:hover .par-edit,h6:hover .par-edit{visibility:visible} + + +h1 { + font-family: Fengardo; + text-align: center; + font-size: 1.6rem; + font-weight: bold; + margin: 1.1rem 0; + text-transform: uppercase; +} +h2 { + text-align: center; + font-size: 1.5rem; + font-weight: bold; + margin: 1rem 0 0.5rem 0; + font-variant: small-caps; +} +h3 { + text-align: left; + font-size: 1.3rem; + font-weight: bold; + margin: 0.7rem 0 0 0; + font-variant: small-caps; +} +h4 { + text-align: left; + font-size: 1.2rem; + margin: 0.8rem 0 0 0; + font-style: italic; + font-variant: normal; + font-weight: normal; + text-transform: none; +} +h5 { + text-align: left; + font-size: 1.1rem; + margin: 0.9rem 0 0 0; + font-style: italic; + font-variant: normal; + font-weight: normal; + text-transform: none; +} +h6 { + text-align: left; + font-size: 1rem; + font-style: italic; + font-variant: normal; + font-weight: normal; + text-transform: none; +} + +table { + border-collapse: collapse; + empty-cells: show; + border-spacing: 0; + border: 2px solid #999; + margin: 10px 10px 20px 50px; + padding: 10px 10px 10px 10px; +} + +th, +td { + padding: .3em .5em; + margin: 5; + vertical-align: top; + border: 1px solid #999; + text-align: left; +} +.form th, +td { + padding: .3em .5em; + /*margin: 0;*/ + vertical-align: top; + border: 1px solid #999; + text-align: left; +} +th { + font-weight: bold; + background-color: #ddd; +} +[dir=rtl] td, +[dir=rtl] th { + text-align: right; +} + + +.sub { + font-size: 1rem; + font-style: italic; + font-variant: normal; + font-weight: normal; + text-transform: none; +} +.menu { + text-align: center; + li { + display: inline-block; + list-style: none; + margin: 0 1rem; + &::before { + content: ""; + } + } +} +.table { + li { + list-style-type: lower-roman; + } +} +.sommaire { + li { + list-style-type: none; + } +} +.colophon { + font-size: 0.6rem; + margin-top: 1rem; + text-align: center; +} +.logo { + font-size: 0.6rem; + margin-top: 1rem; + text-align: center; + a { + border: 0; + } + img { + width: 20%; + } +} + +#editor table { + border: 0px solid #999; +} + + +#editor td { + border: 0px solid #999; +} + + + +/* LionWiki specific */ + +a.pending { + color: @RedBrick; +} +h2 span.par-edit, h3 span.par-edit, h4 span.par-edit, h5 span.par-edit, h6 span.par-edit { + /*float: right;*/ + display: none; + font-size: small; +} +h2:hover span.par-edit, h3:hover span.par-edit, h4:hover span.par-edit, h5:hover span.par-edit, h6:hover span.par-edit { + display: inline; + font-size: small; +} +h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover { + background-color: transparent; +} +.pageLinks { + padding-left: 1em; + padding-right: 1em; + margin-top: 0; + margin-bottom: 0; +} +.pageLinks a { + font-weight: bold; + text-decoration: none; +} +#headerLinks td { + border-bottom: 1px dashed #ccc !important; +} +#footerLinks td { + border-top: 1px dashed #ccc !important; +} +#mainContent { + padding: 1em; + background-color: white; +} +#mainContent h2:first-child { + margin-top: 0px; +} +.error { + color: #F25A5A; + font-weight: bold; +} +.contentTextarea { + width: 90%; + font-family: @MainFontFamilyEdit; + font-size: 105%; +} +#diff { + white-space: pre-wrap; + word-wrap: break-word; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + width: 97%; +} +#diff ins { + color: green; + text-decoration: none; + font-weight: bold; +} +#diff del { + color: red; + text-decoration: line-through; +} +#diff .orig { + color: #666; +} +#toc { + /*border: 1px dashed #11141A;*/ + margin: 2em 1em 2em 1em; + padding: 5px 5px 5px 5px; + /*float: right;*/ + padding-right: 2em; + /*text-align: right;*/ + list-style: none; + background: #eee; + clear: none; + display: block; +} +#toc ul { + list-style: none; + padding-left: 1em; +} +#toc li { + /*font-size: 11px;*/ + padding-left: 1em; +} + +/* Plugins LionWiki*/ + +.tagList { + padding: 0.2em 0.4em 0.2em 0.4em; + margin-top: 0.5em; + border: 1px dashed #31343A; + background: #eee; + clear: right; +} +.tagCloud { + float: right; + width: 200px; + padding: 0.5em; + margin: 1em; + border: 1px dashed #31343A; + background: #eee; + clear: right; +} +.pageVersionsList { + letter-spacing: 0px; + font-variant: normal; + font-size: 12px; +} +table.wikitable { + border-collapse: collapse; + border: 1px solid #ccc; +} +table.wikitable td { + border: 1px solid #ccc; + text-align: center; + vertical-align: middle; + padding: 2px; +} +table.wikitable td.em { + background: #ccc; + font-weight: bold; +} +table, #previewPane table { + border-collapse: collapse; + empty-cells: show; + border-spacing: 0; + border: 1px solid #999; +} +.form table { + border-collapse: collapse; + empty-cells: hide; + border-spacing: 0; + border: 0px solid #fff; +} +th, td, #previewPane th, #previewPane td { + padding: .3em .5em; + margin: 0; + vertical-align: top; + border: 1px solid #999; + text-align: left; +} +.form th, td { + padding: .3em .5em; + margin: 0; + vertical-align: top; + border: 0px solid #999; + text-align: left; +} +th, thead, #previewPane th, #previewPane thead { + font-weight: bold; + background-color: #ddd; +} +[dir=rtl] td, [dir=rtl] th { + text-align: right; +} +.resizeTextarea a { + text-decoration: none; +} + + diff --git a/conf/minimaxing.html b/conf/minimaxing.html index ff802d7..e669377 100755 --- a/conf/minimaxing.html +++ b/conf/minimaxing.html @@ -10,7 +10,7 @@ - + @@ -18,6 +18,8 @@ + + diff --git a/conf/minimaxing.less b/conf/minimaxing.less index 3d9963e..dcfcd3b 100755 --- a/conf/minimaxing.less +++ b/conf/minimaxing.less @@ -62,8 +62,8 @@ You can create your own manuscript / handwriting font on http://www.myscriptfont @HeaderColor: (@MainColor - #111); /* or a complementary color: */ -@HeaderColor: (#ffffff - @MainColor - (#ffffff - @MainColor)/3); - +/*@HeaderColor: (#ffffff - @MainColor - (#ffffff - @MainColor)/3); */ +@HeaderColor: spin(@MainColor, 180); @MainFontColor: (#878e83 - #111); @@ -386,7 +386,8 @@ th, thead, #previewPane th, #previewPane thead { body { font-size: @MainFontSize; font-family: sans-serif; - background-color: (@Bg-extra-light/1.1 + #111); + /*background-color: (@Bg-extra-light/1.1 + #111);*/ + background: #fff; font-family: @MainFontFamily; color: @MainFontColor; } @@ -468,7 +469,7 @@ p { #main-row strong, b { - color: #555; + /*color: #555;*/ } @@ -924,6 +925,8 @@ a.toolbarTextareaItem b { #main { background: #fff; padding: 3% 0 3% 0; + margin-left: 8px; + margin-right: 4px; } #main .controls { } @@ -1079,6 +1082,9 @@ a.toolbarTextareaItem b { #main { /*overflow: auto;*/ + background: #fff; + margin-left: 8px; + margin-right: 4px; } /* from 5grid/core.css */ @@ -1140,4 +1146,4 @@ font-weight: bold; .mycontent tr:hover, .mycontent tr:hover td { background-color: (@TableColor + #111) /*rgba(16,80,112,0.3)*/ -} \ No newline at end of file +} diff --git a/conf/pheditor.php b/conf/pheditor.php new file mode 100755 index 0000000..080471b --- /dev/null +++ b/conf/pheditor.php @@ -0,0 +1,1417 @@ + 3 && time() - $log[$_SERVER['REMOTE_ADDR']]['time'] < 86400) { + die('This IP address is blocked due to unsuccessful login attempts.'); + } + + foreach ($log as $key => $value) { + if (time() - $value['time'] > 86400) { + unset($log[$key]); + + $log_updated = true; + } + } + + if (isset($log_updated)) { + file_put_contents(LOG_FILE, serialize($log)); + } +} + +session_set_cookie_params(86400, dirname($_SERVER['REQUEST_URI'])); +session_name('pheditor'); +session_start(); + +if (empty(PASSWORD) === false && (isset($_SESSION['pheditor_admin']) === false || $_SESSION['pheditor_admin'] !== true)) { + if (isset($_POST['pheditor_password']) && empty($_POST['pheditor_password']) === false) { + if (hash('sha512', $_POST['pheditor_password']) === PASSWORD) { + $_SESSION['pheditor_admin'] = true; + + redirect(); + } else { + $error = 'The entry password is not correct.'; + + $log = file_exists(LOG_FILE) ? unserialize(file_get_contents(LOG_FILE)) : array(); + + if (isset($log[$_SERVER['REMOTE_ADDR']]) === false) { + $log[$_SERVER['REMOTE_ADDR']] = array('num' => 0, 'time' => 0); + } + + $log[$_SERVER['REMOTE_ADDR']]['num'] += 1; + $log[$_SERVER['REMOTE_ADDR']]['time'] = time(); + + file_put_contents(LOG_FILE, serialize($log)); + } + } else if (isset($_POST['action'])) { + header('HTTP/1.0 403 Forbidden'); + + die('Your session has expired.'); + } + + die('Pheditor

Pheditor

' . (isset($error) ? '

' . $error . '

' : null) . '

'); +} + +if (isset($_GET['logout'])) { + unset($_SESSION['pheditor_admin']); + + redirect(); +} + +$permissions = explode(',', PERMISSIONS); +$permissions = array_map('trim', $permissions); +$permissions = array_filter($permissions); + +if (count($permissions) < 1) { + $permissions = explode(',', 'newfile,newdir,editfile,deletefile,deletedir,renamefile,renamedir,changepassword,uploadfile'); +} + +if (isset($_POST['action'])) { + header('Content-Type: application/json'); + + if (isset($_POST['file']) && empty($_POST['file']) === false) { + $_POST['file'] = urldecode($_POST['file']); + + if (empty(PATTERN_FILES) === false && !preg_match(PATTERN_FILES, basename($_POST['file']))) { + die(json_error('Invalid file pattern')); + } + + if (strpos($_POST['file'], '../') !== false || strpos($_POST['file'], '..\'') !== false) { + die(json_error('Invalid file pattern')); + } + } + + switch ($_POST['action']) { + case 'open': + $_POST['file'] = urldecode($_POST['file']); + + if (isset($_POST['file']) && file_exists(MAIN_DIR . $_POST['file'])) { + die(json_success('OK', [ + 'data' => file_get_contents(MAIN_DIR . $_POST['file']), + ])); + } + break; + + case 'save': + $file = MAIN_DIR . $_POST['file']; + + if (isset($_POST['file']) && isset($_POST['data']) && (file_exists($file) === false || is_writable($file))) { + if (file_exists($file) === false) { + if (in_array('newfile', $permissions) !== true) { + die(json_error('Permission denied', true)); + } + + file_put_contents($file, $_POST['data']); + + echo json_success('File saved successfully'); + } else if (is_writable($file) === false) { + echo json_error('File is not writable'); + } else { + if (in_array('editfile', $permissions) !== true) { + die(json_error('Permission denied')); + } + + if (file_exists($file)) { + file_to_history($file); + } + + file_put_contents($file, $_POST['data']); + + echo json_success('File saved successfully'); + } + } + break; + + case 'make-dir': + if (in_array('newdir', $permissions) !== true) { + die(json_error('Permission denied')); + } + + $dir = MAIN_DIR . $_POST['dir']; + + if (file_exists($dir) === false) { + mkdir($dir); + + echo json_success('Directory created successfully'); + } else { + echo json_error('Directory already exists'); + } + break; + + case 'reload': + echo json_success('OK', [ + 'data' => files(MAIN_DIR), + ]); + break; + + case 'password': + if (in_array('changepassword', $permissions) !== true) { + die(json_error('Permission denied')); + } + + if (isset($_POST['password']) && empty($_POST['password']) === false) { + $contents = file(__FILE__); + + foreach ($contents as $key => $line) { + if (strpos($line, 'define(\'PASSWORD\'') !== false) { + $contents[$key] = "define('PASSWORD', '" . hash('sha512', $_POST['password']) . "');\n"; + + break; + } + } + + file_put_contents(__FILE__, implode($contents)); + + echo json_success('Password changed successfully'); + } + break; + + case 'delete': + if (isset($_POST['path']) && file_exists(MAIN_DIR . $_POST['path'])) { + $path = MAIN_DIR . $_POST['path']; + + if ($_POST['path'] == '/') { + echo json_error('Unable to delete main directory'); + } else if (is_dir($path)) { + if (count(scandir($path)) !== 2) { + echo json_error('Directory is not empty'); + } else if (is_writable($path) === false) { + echo json_error('Unable to delete directory'); + } else { + if (in_array('deletedir', $permissions) !== true) { + die(json_error('Permission denied')); + } + + rmdir($path); + + echo json_success('Directory deleted successfully'); + } + } else { + file_to_history($path); + + if (is_writable($path)) { + if (in_array('deletefile', $permissions) !== true) { + die(json_error('Permission denied')); + } + + unlink($path); + + echo json_success('File deleted successfully'); + } else { + echo json_error('Unable to delete file'); + } + } + } + break; + + case 'rename': + if (isset($_POST['path']) && file_exists(MAIN_DIR . $_POST['path']) && isset($_POST['name']) && empty($_POST['name']) === false) { + $path = MAIN_DIR . $_POST['path']; + $new_path = str_replace(basename($path), '', dirname($path)) . DS . $_POST['name']; + + if ($_POST['path'] == '/') { + echo json_error('Unable to rename main directory'); + } else if (is_dir($path)) { + if (in_array('renamedir', $permissions) !== true) { + die(json_error('Permission denied')); + } + + if (is_writable($path) === false) { + echo json_error('Unable to rename directory'); + } else { + rename($path, $new_path); + + echo json_success('Directory renamed successfully'); + } + } else { + if (in_array('renamefile', $permissions) !== true) { + die(json_error('Permission denied')); + } else if (empty(PATTERN_FILES) === false && !preg_match(PATTERN_FILES, $_POST['name'])) { + die(json_error('Invalid file pattern: ' . htmlspecialchars($_POST['name']))); + } + + file_to_history($path); + + if (is_writable($path)) { + rename($path, $new_path); + + echo json_success('File renamed successfully'); + } else { + echo json_error('Unable to rename file'); + } + } + } + break; + + case 'upload-file': + $files = isset($_FILES['uploadfile']) ? $_FILES['uploadfile'] : []; + $destination = isset($_POST['destination']) ? rtrim($_POST['destination']) : null; + + if (empty($destination) === false && (strpos($destination, '/..') !== false || strpos($destination, '\\..') !== false)) { + die(json_error('Invalid file destination')); + } + + $destination = MAIN_DIR . $destination; + + if (file_exists($destination) === false || is_dir($destination) === false) { + die(json_error('File destination does not exists')); + } + + if (is_writable($destination) !== true) { + die(json_error('File destination is not writable')); + } + + if (is_array($files) && count($files) > 0) { + for ($i = 0; $i < count($files['name']); $i += 1) { + if (empty(PATTERN_FILES) === false && !preg_match(PATTERN_FILES, $files['name'][$i])) { + die(json_error('Invalid file pattern: ' . htmlspecialchars($files['name'][$i]))); + } + + move_uploaded_file($files['tmp_name'][$i], $destination . '/' . $files['name'][$i]); + } + + echo json_success('File' . (count($files['name']) > 1 ? 's' : null) . ' uploaded successfully'); + } + break; + + case 'terminal': + if (in_array('terminal', $permissions) !== false && isset($_POST['command'], $_POST['dir'])) { + if (function_exists('shell_exec') === false) { + echo json_error("shell_exec function is disabled\n"); + + exit; + } + + set_time_limit(15); + + $command = $_POST['command']; + $dir = $_POST['dir']; + + $command_found = false; + + foreach (explode(',', TERMINAL_COMMANDS) as $value) { + $value = trim($value); + + if (strlen($command) >= strlen($value) && substr($command, 0, strlen($value)) == $value) { + $command_found = true; + + break; + } + } + + if ($command_found === false) { + echo json_error("Command not allowed\n"); + + exit; + } + + $output = shell_exec((empty($dir) ? null : 'cd ' . $dir . ' && ') . $command . ' && echo \ ; pwd'); + $output = trim($output); + + if (empty($output)) { + $output = null; + $dir = null; + } else { + $output = explode("\n", $output); + $dir = end($output); + + unset($output[count($output) - 1]); + + $output = implode("\n", $output); + $output = trim($output) . "\n"; + $output = htmlspecialchars($output); + } + + echo json_success('OK', ['result' => $output, 'dir' => $dir]); + } + break; + } + + exit; +} + +function files($dir, $first = true) +{ + $data = ''; + + if ($first === true) { + $data .= ''; + } + + return $data; +} + +function redirect($address = null) +{ + if (empty($address)) { + $address = $_SERVER['PHP_SELF']; + } + + header('Location: ' . $address); + exit; +} + +function file_to_history($file) +{ + if (is_numeric(MAX_HISTORY_FILES) && MAX_HISTORY_FILES > 0) { + $file_dir = dirname($file); + $file_name = basename($file); + $file_history_dir = HISTORY_PATH . str_replace(MAIN_DIR, '', $file_dir); + + foreach ([HISTORY_PATH, $file_history_dir] as $dir) { + if (file_exists($dir) === false || is_dir($dir) === false) { + mkdir($dir, 0777, true); + } + } + + $history_files = scandir($file_history_dir); + + foreach ($history_files as $key => $history_file) { + if (in_array($history_file, ['.', '..', '.DS_Store'])) { + unset($history_files[$key]); + } + } + + $history_files = array_values($history_files); + + if (count($history_files) >= MAX_HISTORY_FILES) { + foreach ($history_files as $key => $history_file) { + if ($key < 1) { + unlink($file_history_dir . DS . $history_file); + unset($history_files[$key]); + } else { + rename($file_history_dir . DS . $history_file, $file_history_dir . DS . $file_name . '.' . ($key - 1)); + } + } + } + + copy($file, $file_history_dir . DS . $file_name . '.' . count($history_files)); + } +} + +function json_error($message, $params = []) +{ + return json_encode(array_merge([ + 'error' => true, + 'message' => $message, + ], $params), JSON_UNESCAPED_UNICODE); +} + +function json_success($message, $params = []) +{ + return json_encode(array_merge([ + 'error' => false, + 'message' => $message, + ], $params), JSON_UNESCAPED_UNICODE); +} + +?> + + + + + + + Pheditor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+

Pheditor

+
+
+
+ + +
+ +
+ Password   Logout +
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+ + +
+
+
+
+
+ + + + Terminal +
+
+
+

+									
+								
+
+
+
+
+ + +
+ +
+ +
+ +
+ + + + +
+ + + + diff --git a/manifest.json b/manifest.json index 01ebbc5..c6ea3fc 100644 --- a/manifest.json +++ b/manifest.json @@ -6,7 +6,7 @@ "en": "A lightweight wiki-style CMS using the txt2tags syntax", "fr": "Un CMS léger, géré sous forme de wiki utilisant la syntaxe txt2tags" }, - "version": "3.2.11b~ynh3", + "version": "3.2.11b~ynh4", "url": "https://lionwiki-t2t.sourceforge.io/", "license": "MIT", "maintainer": { @@ -112,7 +112,7 @@ "fr": "Utilisez une valeur hexadecimale (6 chiffres sans # devant)" }, "example": "D17732", - "default": "D17732" + "default": "328cc1" } ] } diff --git a/scripts/install b/scripts/install index 1cec91b..17eec16 100644 --- a/scripts/install +++ b/scripts/install @@ -186,6 +186,8 @@ cp ../conf/config.php $final_path/ cp ../conf/minimaxing.less $final_path/templates/minimaxing/ cp ../conf/minimaxing.css $final_path/templates/minimaxing/minimaxing_org.css +cp ../conf/pheditor.php $final_path/ + # we already use the default html template #cp ../conf/minimaxing.html $final_path/templates/ @@ -195,11 +197,19 @@ ynh_replace_string --match_string="__YNH_PASSWORD__" --replace_string="$password # Set the "admin" user (not used, admin password = user password, you can modify it later) # ynh_replace_string --match_string="__YNH_ADMINPASSWORD__" --replace_string="$password" --target_file="$final_path/config.php" +# set user password in pheditor + +pheditorhash=`python -c 'import hashlib; import sys; a=str(sys.argv[1]); print(hashlib.sha512(a.encode("UTF-8")).hexdigest())' $password` + +ynh_replace_string --match_string="__YNH_PASSWORD__" --replace_string="$pheditorhash" --target_file="$final_path/pheditor.php" + + ynh_replace_string --match_string="__YNH_LANG__" --replace_string="$language" --target_file="$final_path/config.php" ynh_replace_string --match_string="__YNH_LABEL__" --replace_string="$wiki" --target_file="$final_path/config.php" + #ynh_replace_string --match_string="__YNH_COLOR__" --replace_string="$color" --target_file="$final_path/templates/minimaxing/minimaxing.less" # test if the color was correctly set (6 hexadecimal values) @@ -208,8 +218,10 @@ ynh_replace_string --match_string="__YNH_LABEL__" --replace_string="$wiki" -- if echo "$color" | grep -q -E '[A-Fa-f0-9]{6}' then ynh_replace_string --match_string="__YNH_COLOR__" --replace_string="$color" --target_file="$final_path/templates/minimaxing/minimaxing.less" + ynh_replace_string --match_string="__YNH_COLOR__" --replace_string="$color" --target_file="$final_path/templates/ggp/ggp.less" else ynh_replace_string --match_string="__YNH_COLOR__" --replace_string="555555" --target_file="$final_path/templates/minimaxing/minimaxing.less" + ynh_replace_string --match_string="__YNH_COLOR__" --replace_string="555555" --target_file="$final_path/templates/ggp/ggp.less" fi # soon lessc will require the --js option @@ -217,6 +229,8 @@ fi lessc $final_path/templates/minimaxing/minimaxing.less > $final_path/templates/minimaxing/minimaxing.css +lessc $final_path/templates/ggp/ggp.less > $final_path/templates/ggp/ggp.css + #ynh_replace_string --match_string="__YNH_COLOR__" --replace_string="$color" --target_file="$final_path/templates/minimaxing/minimaxing.css" @@ -317,6 +331,9 @@ chown -R root: $final_path chown -R $app:root $final_path/var chown -R $app:root $final_path/templates +# write everything, even config files, for pheditor: +chown -R $app:root $final_path/ + # Allow access to public assets like style sheets find $final_path/templates -type f -print0 | xargs -0 chmod 0644 find $final_path/templates -type d -print0 | xargs -0 chmod 0755