summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/app/views/admin/admin-menu.php2
-rw-r--r--public/app/views/admin/edit_lesson.php9
-rw-r--r--public/assets/css/overides.css3
-rw-r--r--public/assets/js/admin/edit_lesson.js631
-rw-r--r--tests/dotnet-pass.php6
5 files changed, 640 insertions, 11 deletions
diff --git a/public/app/views/admin/admin-menu.php b/public/app/views/admin/admin-menu.php
index a06d2eb..92c2f54 100644
--- a/public/app/views/admin/admin-menu.php
+++ b/public/app/views/admin/admin-menu.php
@@ -19,7 +19,7 @@
$admin_options = [
'edit_lesson' => 'Νέο Μάθημα',
'lessons' => 'Μαθήματα',
- 'categories' => 'Κατηγορίες',
+ 'categories' => 'Κεφάλαια',
'pages' => 'Σελίδες',
'privileges' => 'Πρόσβαση',
'users' => 'Χρήστες'
diff --git a/public/app/views/admin/edit_lesson.php b/public/app/views/admin/edit_lesson.php
index 30dd42f..4ea8104 100644
--- a/public/app/views/admin/edit_lesson.php
+++ b/public/app/views/admin/edit_lesson.php
@@ -47,9 +47,9 @@
</div>
<div class="row form-group">
- <label class="control-label col-sm-12" for="category_id">Κατηγορία</label>
+ <label class="control-label col-sm-12" for="category_id">Κεφάλαιο</label>
<div class="col-sm-12">
- <select class="form-control form-control-sm" id="category_id" name="category_id" required></select>
+ <select class="form-control form-control-sm" id="course_id" name="course_id" required></select>
</div>
</div>
@@ -78,10 +78,7 @@
<div class="row form-group">
<label class="control-label col-sm-12" for="privilege_id">Επίπεδο Πρόσβασης</label>
<div class="col-sm-12">
- <select class="form-control form-control-sm form-select form-select-sm" name="privilege_id">
- <option>one</option>
- <option>two</option>
- <option>three</option>
+ <select class="form-control form-control-sm form-select form-select-sm" id="privilege_id" name="privilege_id">
</select>
</div>
</div>
diff --git a/public/assets/css/overides.css b/public/assets/css/overides.css
index b217f65..c2ca07d 100644
--- a/public/assets/css/overides.css
+++ b/public/assets/css/overides.css
@@ -80,7 +80,8 @@ ul.select2-results__options li { font-size: 14px; padding: 4px 8px; }
.admin-container select[data-chosen='one'] {
font-weight: 700;
- color: #000;
+ color: #0d6efd;
+ border: 1px solid #0d6efd;
}
/* hack select2 caret styling
diff --git a/public/assets/js/admin/edit_lesson.js b/public/assets/js/admin/edit_lesson.js
index e69de29..a9355ae 100644
--- a/public/assets/js/admin/edit_lesson.js
+++ b/public/assets/js/admin/edit_lesson.js
@@ -0,0 +1,631 @@
+// Globals
+// -----------------------------------------------------------------------------
+
+// NOTE:
+// Variable `entity_id` is defined before evaluating this script
+// id (int) : the id of edited post (or 0 if new post)
+
+var lesson;
+
+var id = (entity_id == 0) ? false : entity_id;
+
+var categories = [
+ // Nope! root can not be a post's category ... { id: 0 , label: 'Ρίζα (root)' }
+];
+
+var privileges = [
+ { id: 0, label: '_Δημόσιο_'}
+];
+
+var files = []; // array of { id:.., title:.., type:.., path:.. } records
+
+// TODO: support tags
+// var tags = []; // array of integers
+
+
+const files_root = 'https://cdn.sklavenitis.co.gr/'; // files root firectory
+
+var pageUrl = new URL(window.location.href);
+
+// an alternative--id when a lesson_id not exists (needed for file-upload)
+// ... consist of a 6-digit date string + random number up to 999
+const yymmdd = new Date().toISOString().slice(2, 10).replaceAll('-','');
+var rnd999 = Math.floor(Math.random() * 1000);
+var altID = (id == 0) ? ( yymmdd +'-'+ rnd999.toString() ) : id;
+
+
+
+
+// Supplamentary functions
+// -----------------------------------------------------------------------------
+
+// function for sorting an array by 'label' attribute
+// --- -- -- - - -
+function compare_label (a,b) {
+ if ( a.label < b.label ) { return -1; }
+ if ( a.label > b.label ) { return 1; }
+ return 0;
+}
+
+// return course record by course-id
+// --- -- -- - - -
+function course_record(id) {
+ var record = 0;
+ categories.forEach(el => { if (el.id == id) record = el; });
+
+ return record;
+}
+
+
+
+
+// Modal HTML Creators
+// -----------------------------------------------------------------------------
+
+const modal_header = (title) => {
+ return [
+ '<div class="modal-header">',
+ '<button type="button" class="close" data-dismiss="modal" aria-label="Close">',
+ '<span aria-hidden="true">&times;</span>',
+ '</button>',
+ '<h4 class="modal-title" id="myModalLabel">',
+ title,
+ '</h4>',
+ '</div>'
+ ].join('\n');
+}
+
+
+const upload_file_form = () => {
+ return [
+ '<form method="POST" id="file_upload_form" name="upload_file_form">',
+
+ '<div class="modal-body">',
+
+ '<input type="hidden" name="folder" value="'+ altID +'">',
+
+ '<div class="form-group">',
+ '<label for="title">Τίτλος/Περιγραφή για το Αρχείο</label>',
+ '<input type="text" class="form-control " value="" name="title" id="title" placeholder="Τίτλος για το Αρχείο" required="">',
+ '</div>',
+
+ '<div class="row">',
+
+ '<div class="form-group col-md-7">',
+ '<label class="control-label" for="file">Εισαγωγή Αρχείου</label>',
+ '<input class="form-control" type="file" id="file" name="file" required="">',
+ '</div>',
+
+ '<div class="form-group col-md-5">',
+ '<label class="control-label" for="category_id">Είδος</label>',
+ '<select class="form-control" id="reference" name="reference" required="">',
+ '<option"></option>',
+ '<option value="1">Παραπομπή (εμφανίζεται)</option>',
+ '<option value="0">Βοηθητικό αρχείο (κρυφό)</option>',
+ '</select>',
+ '</div>',
+
+ '</div>',
+
+ '</div>',
+
+ '<div class="modal-footer">',
+ '<div class="row">',
+ '<div class="form-actions">',
+ '<div class="col-lg-3 col-lg-offset-6 col-md-4 col-md-offset-4 col-xs-6">',
+ '<buton type="button" class="btn btn-default js-modal-close col-xs-12" data-dismiss="modal">',
+ 'Κλείσιμο',
+ '</button>',
+ '</div>',
+ '<div class="col-lg-3 col-md-4 col-xs-6">',
+ '<button class="btn btn-success col-xs-12" type="submit">Καταχώριση</button>',
+ '</div>',
+ '</div>',
+ '</div>',
+ '</div>',
+
+ '</form>'
+ ].join('\n');
+}
+
+
+const preview_article = (title, intro, body) => {
+ return [
+ '<div class="modal-body">',
+ '<div class="post">',
+
+ '<h2>'+ title +'</h2>',
+
+ ((intro == '') ? '' : ('<div class="intro">'+ intro +'</div>')),
+
+ '<div class="article-body">',
+ marked.parse(body, { sanitize: true }),
+ '</div>',
+
+ '</div>',
+ '</div>',
+ '<div class="modal-footer">',
+ '<div class="row">',
+ '<div class="form-actions">',
+ '<div class="col-md-4 col-md-offset-4 col-xs-6 col-xs-offset-3">',
+ '<buton type="button" class="btn btn-default js-modal-close col-xs-12" data-dismiss="modal">',
+ 'Κλείσιμο',
+ '</button>',
+ '</div>',
+ '</div>',
+ '</div>',
+ '</div>'
+ ].join('\n');
+}
+
+
+$(document).ready(function() {
+
+ // When document is ready
+ // -------------------------------------------------------------------------
+
+
+ // inti category (select2)
+ // ...
+ $("#course_id").select2({
+ placeholder: 'Γονική Κατηγορία',
+ width: '100%'
+ });
+
+
+ // // init tags (select2)
+ // // ...
+ // $('#tags').select2({
+ // placeholder: 'Ετικέτες (tags/keywords)',
+ // width: '100%',
+ // tags: true
+ // });
+
+
+ // init editor (tiny MCE)
+ // tinymce.init({
+ // selector: 'form textarea[name=body]',
+ // language: 'el',
+ // menubar: true
+ // });
+ // --- -- -- - - -
+
+
+ /** preload content
+ * --- -- -- - - -
+ * get post
+ * get categories
+ * get tags
+ * update content (form-elements)
+ */
+ if (id) { // if an `id` is given, load post .. then init_form
+
+ $.getJSON( "/admin/api/", { action: "get_post", id: id } )
+ .done( function (data) {
+ post = data; // keep post
+
+ // update fields' values
+ $('input[name=id]').val(post.id); // id
+ $('input[name=title]').val(post.title); // title
+ $('textarea[name=intro]').val(post.intro); // intro
+ $('textarea[name=body]').val(post.body); // body
+ $('input[name=status]').prop( // status
+ 'checked',
+ ((post.status==1)? true : false))
+ .trigger('change');
+
+ $('#post-info').html('Δημιουργία: '+post.creation_date+'<br>Τελευταία Ενημέρωση: '+ post.update_date);
+
+ $('#delete').prop( "disabled", false ); // enable delete button
+
+ // if intro exist, show intro (optinal) field
+ if ((post.intro != null) && (post.intro != '')) {
+ $('.optional label[for=intro]').addClass('show'); // unhide section
+ }
+
+ // parse media files; then draw
+ if (post.medias_json != null) {
+ files = JSON.parse(post.medias_json);
+ $('.optional label[for=media]').addClass('show'); // unhide section
+ }
+ draw_files_list(); // then draw the files list
+
+ init_form_values(); // init all (other) values
+ });
+
+ } else { // else ... just init_form
+ $('#delete').prop( "disabled", true ); // disable delete button for new post attempt
+ init_form_values();
+ }
+
+
+
+ /**
+ * load categories and tags;
+ * if not a new post (id!=0) update form elemnts with post's values
+ */
+ function init_form_values() {
+
+ $.when( // when loaded posts and categories
+
+ $.getJSON( "/admin/api/categories" ),
+
+ $.getJSON( "/admin/api/privileges" )
+
+ ).done( function( categories_response, privileges_response ) {
+
+ // construct categories data
+ // ---
+ Object.keys(categories_response[0]).forEach( key => {
+ cat = categories_response[0][key];
+ categories.push({
+ id: cat.rec.id,
+ label: cat.breadcrumb.replaceAll('\t', ' / ')
+ });
+ });
+ categories.sort( compare_label ); // sort categories by breadcrumb
+
+ // // construct tags data
+ // // ---
+ // Object.keys(tags_response[0]).forEach( key => {
+ // tag = tags_response[0][key];
+ // tags.push({
+ // id: tag.id,
+ // label: tag.name
+ // });
+ // });
+
+ // construct provileges data
+ // ---
+ Object.keys(privileges_response[0]).forEach( key => {
+ priv = privileges_response[0][key];
+ privileges.push({
+ id: priv.id,
+ label: priv.label
+ });
+ });
+ privileges.sort( compare_label ); // sort categories by breadcrumb
+
+ // pass categories and tags options onto select2 elements
+ // --- -- -- - - -
+ if (id == 0) {
+ set_parent_options( $('#course_id'), categories );
+ set_parent_options( $('#privilege_id'), privileges );
+
+ } else {
+ // setup category_id options; select post's chosen category-id
+ set_parent_options( $('#category_id'), categories, post.category_id );
+
+ // parse selected tags
+ // ---
+ var selected_tag_ids = [];
+ if (post.tags_json != null) {
+ var selected_tags = JSON.parse(post.tags_json);
+ selected_tags.forEach( itag => { selected_tag_ids.push(itag.id); })
+ }
+ // setup tags options; select post's chosen tags
+ set_parent_options( $('#tags'), tags, selected_tag_ids );
+
+ // ALSO: if tags selectd, show tags field
+ if (selected_tag_ids.length != 0) { $('.optional label[for=tags]').addClass('show'); }
+ }
+
+ });
+
+ }
+
+
+ /** set_parent_options()
+ *
+ * -> create options for parents <select>
+ * -> choose selected parent
+ *
+ * NOTE:
+ * the targer <select> should be a select2 control
+ *
+ * @param parents (array): array of parents
+ * @param selected (int): selected parent
+ */
+ function set_parent_options(selector, list, selected_value = false) {
+
+ list.forEach( li => { // attach options into <select> control
+ selector.append(`<option value="${li.id}">${li.label}</option>`);
+ });
+
+ if (selected_value !== false) { // mark selected option(s)
+ selector.val(selected_value).trigger('change');
+ }
+ else { selector.val(null).trigger('change'); }
+ }
+
+
+ /** draw_files_list
+ * --- -- -- - - -
+ */
+ function draw_files_list() {
+ var li_list = [];
+ var ref_icon, ref_class;
+
+ files.forEach( item => {
+ // preapre [show|hide]-file as reference
+ if (item.reference == 1) {
+ ref_icon = '<i class="fas fa-eye"></i>';
+ ref_class = 'btn-default';
+
+ } else {
+ ref_icon = '<i class="fas fa-eye-slash"></i>';
+ ref_class = 'btn-disabled';
+ }
+
+ var copy = [ // copy button
+ '<button type="button" class="btn btn-xs btn-default js-copy"',
+ 'data-url="' + item.path + '">',
+ '<i class="far fa-clone"></i>',
+ '</button>'
+ ].join('');
+ var ref = [ // show|hide (is_reference)
+ '<button type="button" class="btn btn-xs '+ ref_class +' js-reference"',
+ 'data-id="' + item.id + '">',
+ ref_icon,
+ '</button>'
+ ].join('');
+ var del = [ // delete button
+ '<button type="button" class="btn btn-xs btn-danger js-delete"',
+ 'data-id="' + item.id + '">',
+ '<i class="fas fa-trash-alt"></i>',
+ '</button>'
+ ].join('');
+ var option = [ // li html
+ '<li>',
+ '<div class="text">' + item.title + '</div>',
+ '<div class="actions">',
+ ref, ' ', copy, ' ', del,
+ '</div>',
+ '</li>'
+ ].join('');
+ li_list.push(option); // push to li_list
+ });
+ $('#files_list').html( li_list.join('\n') );
+
+ }
+
+
+
+ // When form's action-buttons are clicked
+ // -------------------------------------------------------------------------
+
+
+ // form submit
+ // --- -- -- - - -
+ $('.edit-post form').submit( event => {
+ event.preventDefault();
+
+ // rearrange tags to old and new ones
+ // ---
+ var old_tags = [];
+ var new_tags = [];
+ var tags = $('#tags').select2('data');
+ tags.forEach( t => {
+ if (!parseInt(t.id)) {
+ new_tags.push(t.text);
+ } else { old_tags.push(t.id); }
+ })
+
+ var attachments = [];
+ // attachments (array) of "`media_id`;`reference`" strings
+ files.forEach( f => { attachments.push( f.id +';'+ f.reference ) });
+
+ // prepare data to POST
+ // ---
+ var data = {
+ id: parseInt($('.edit-post form input[name=id]').val()),
+ title: $('.edit-post form input[name=title]').val(),
+ category_id: parseInt($('#category_id').select2('data')[0].id),
+ intro: $('.edit-post form textarea[name=intro]').val(),
+ body: $('.edit-post form textarea[name=body]').val(),
+ status: (($('input[name=status]').is(":checked")) ? 1 : 0),
+ tags: old_tags,
+ new_tags: new_tags,
+ media: attachments
+ };
+
+ // select action url (add or update)
+ // ---
+ var request = '/wiki/ajax?action=' + ((data.id == 0) ? 'add_post' : 'update_post');
+
+ // send POST (ajax) request
+ $.post(request, data)
+ .done(function( data ) {
+ window.location.href = '/wiki/admin?action=posts'; // seems ok; bach to articles
+ });
+
+ });
+
+
+ // go-back (to posts)
+ // --- -- -- - - -
+ $('#go-back').click( event => {
+ window.location.href = '/wiki/admin?action=posts';
+ });
+
+
+ // delete
+ // --- -- -- - - -
+ $('#delete').click( event => {
+ // ask before deletion
+ swal({
+ title: 'Είστε βέβαιος;',
+ html: "Ζητήσατε να διαγραφεί το άρθρο.<br>Παρακαλώ επιβεβαιώστε την διαγραφή.<br>ΠΡΟΣΟΧΗ: Δεν θα έχετε την δυνατότητα αναίρεσης!",
+ type: 'warning',
+ showCancelButton: true,
+ confirmButtonColor: '#3085d6',
+ cancelButtonColor: '#d33',
+ cancelButtonText: 'Ακύρωση',
+ confirmButtonText: 'Ναι, να διαγραφεί',
+ showLoaderOnConfirm: true,
+ preConfirm: function() {
+ return new Promise(function(resolve) {
+ $.getJSON( "/wiki/ajax", { action: "delete_post", id: id } )
+ .done( function (data) {
+ // return to posts-administration
+ window.location.href = '/wiki/admin?action=posts';
+ });
+ });
+ },
+ allowOutsideClick: false
+ }).catch(swal.noop);
+
+ });
+
+
+
+ // copy url button
+ // --- -- -- - - -
+ $('.wiki').on('click', 'button.js-copy', function (e) {
+ var copytext = ''; // default: copy nothing
+
+ if ($(this).data('id') !== undefined) {
+ copytext = pageUrl.origin +'/wiki/post?id=' + $(this).data('id');
+ }
+
+ if ($(this).data('url') !== undefined) {
+ copytext = files_root + $(this).data('url');
+ }
+
+ navigator.clipboard
+ .writeText(copytext)
+ .then(() => { // notify the copy event
+ $(this).addClass('copied');
+ setTimeout( () => { $(this).removeClass('copied'); }, 700);
+ })
+ .catch(() => {
+ console.log("error on coping text");
+ });
+ });
+
+
+
+ // delete (remove) file button
+ // --- -- -- - - -
+ $('.wiki').on('click', 'button.js-delete', function (e) {
+ var media_id = $(this).data('id');
+
+ // remove from files array the one with id == media_id
+ // ---
+ var temp = [];
+ files.forEach( it => { if (it.id != media_id) temp.push(it); });
+
+ files = temp; // re-define files
+ draw_files_list(); // then re-draw files list
+ });
+
+
+
+
+
+ // show|hide file as referece button
+ // --- -- -- - - -
+ $('.wiki').on('click', 'button.js-reference', function (e) {
+ var id = $(this).data('id');
+
+ // toggle reference attribute of item with `id` = id
+ // ---
+ var temp = [];
+ files.forEach( it => {
+ if (it.id == id) {
+ it.reference = (it.reference==0) ? 1 : 0;
+ }
+ temp.push(it)
+ });
+
+ files = temp; // re-define files
+ draw_files_list(); // then re-draw files list
+ });
+
+
+
+
+
+ /** When modal show-up
+ * -------------------------------------------------------------------------
+ * ... prepare the manage-category form
+ * ... update select2 with possible parents
+ */
+ $('#editorModal').on('show.bs.modal', event => {
+ let button = $(event.relatedTarget); // Button that triggered the modal
+ let action = button.data('action'); // action
+ // var modal = $(this); // modal object
+
+ // update modal literature
+
+ switch (action) {
+ case 'upload_file':
+ $("#editorModal .modal-content").html(
+ modal_header('Εισαγωγή Αρχείου') + upload_file_form()
+ );
+ break;
+
+ case 'preview':
+ let preview = preview_article(
+ $('.edit-post form input[name=title]').val(),
+ $('.edit-post form textarea[name=intro]').val(),
+ $('.edit-post form textarea[name=body]').val()
+ );
+ $("#editorModal .modal-content").html( modal_header('Προεπισκόπηση') + preview);
+ break;
+
+ default:
+ // do nothing
+ }
+
+ });
+
+
+
+ /** submit upload file event
+ * -------------------------------------------------------------------------
+ */
+ $('#editorModal').on('submit', 'form[name=upload_file_form]', function (event) {
+ event.preventDefault();
+
+ // create the FormData object
+ // --- -- -- - - -
+ var fd = new FormData(document.getElementById('file_upload_form'));
+
+ // post the form (via ajax)
+ $.ajax({
+ url: '/wiki/ajax?action=file_upload',
+ type: 'POST',
+ data: fd,
+ contentType: false,
+ processData: false,
+ dataType: 'json',
+ success: function(response) { // on success
+
+ if (response.success) {
+ files.push({ // ... update files array
+ id: response.id,
+ title: response.title,
+ path: response.path,
+ type: response.type,
+ reference: $('#reference').val()
+ });
+ draw_files_list();
+
+ $('#editorModal').modal('hide');
+
+ } else {
+ console('File not uploaded');
+ }
+ }
+ });
+ });
+
+
+ // TODO:
+ // disable links while previewing the article
+ // OR add a target="_blank"
+
+
+});
+
diff --git a/tests/dotnet-pass.php b/tests/dotnet-pass.php
index eeba77f..191a2d2 100644
--- a/tests/dotnet-pass.php
+++ b/tests/dotnet-pass.php
@@ -95,7 +95,7 @@ trait DotNetHasherChecker
{
return false; // bad size
}
-
+
byte[] salt = new byte[SaltSize];
Buffer.BlockCopy(hashedPassword, 1, salt, 0, salt.Length);
@@ -183,7 +183,7 @@ class SomeUserClass
$h = new SomeUserClass();
$result = $h->Verify_AspNet_HashedPassword(
- '3212',
+ '!$ola_04$',
'AMHM+lMKiSdmucVv8KhQ9yyF/sVj8Pay16HJkBDORJxvHDm0WFP2vww9DBVo3JTEoA=='
);
@@ -500,4 +500,4 @@ namespace Microsoft.AspNetCore.Identity
}
---
-*/ \ No newline at end of file
+*/