summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/app/controllers/admin/Course_admin.php13
-rw-r--r--public/app/routes/backend.php12
-rw-r--r--public/app/views/admin/lessons.php11
-rw-r--r--public/assets/js/admin/edit_lesson.js2
-rw-r--r--public/assets/js/admin/lessons.js386
-rw-r--r--tests/service/lorem-md.php71
6 files changed, 485 insertions, 10 deletions
diff --git a/public/app/controllers/admin/Course_admin.php b/public/app/controllers/admin/Course_admin.php
index 90aaa53..bb4deb0 100644
--- a/public/app/controllers/admin/Course_admin.php
+++ b/public/app/controllers/admin/Course_admin.php
@@ -105,6 +105,19 @@ class Course_admin {
## -------------------------------------------------------------------------
+ /** all lessons
+ *
+ */
+ public static function all_lessons()
+ {
+ Render::json(Registry::use('database')->runQuery(
+ "SELECT lesson.*, lesson_privilege.privilege_id FROM lesson
+ LEFT JOIN lesson_privilege ON lesson_privilege.lesson_id = lesson.id",
+ []
+ ));
+ }
+
+
/** get_lesson
*
* @param $id (int) : lesson's id
diff --git a/public/app/routes/backend.php b/public/app/routes/backend.php
index 7f4717d..5d6189d 100644
--- a/public/app/routes/backend.php
+++ b/public/app/routes/backend.php
@@ -135,18 +135,26 @@ Route::add('/admin/api/lesson/([0-9]*)', function ($id) {
Course_admin::get_lesson($id);
});
-
+// ajax: add lesson
+// ---
Route::add('/admin/api/lesson/add', function () {
Auth::allowRoles([1, 2, 3]); // allow few admin-panel roles
Course_admin::add_lesson();
}, 'post');
-
+// ajax: update lesson
+// ---
Route::add('/admin/api/lesson/update', function () {
Auth::allowRoles([1, 2, 3]); // allow few admin-panel roles
Course_admin::update_lesson();
}, 'post');
+// ajax: get all lessons
+// ---
+Route::add('/admin/api/lessons', function () {
+ Auth::allowRoles([1, 2, 3]); // allow few admin-panel roles
+ Course_admin::all_lessons();
+});
// admin files /////////////////////////////////////////////////////////////////
diff --git a/public/app/views/admin/lessons.php b/public/app/views/admin/lessons.php
index 6e7afeb..d1cd3ae 100644
--- a/public/app/views/admin/lessons.php
+++ b/public/app/views/admin/lessons.php
@@ -6,9 +6,6 @@
<a type="button" class="btn pull-right" href="/admin/edit_lesson">
<i class="fas fa-plus"></i> &nbsp; Νέο Μάθημα
</a>
- <button type="button" class="btn"
- data-bs-toggle="modal" data-bs-target="#manageLessons" data-action="preview">
- test</button>
</div>
</div>
@@ -20,8 +17,9 @@
<table id="dt-lessons" class="table table-responsive dt-table" style="width:100%">
<thead>
+ <th class="dt-id">α/α</th>
<th class="dt-title">Τίτλος</th>
- <th class="dt-breadcrumb">Κεφάλαιο</th>
+ <th class="dt-course">Κεφάλαιο</th>
<th class="dt-privileges">Πρόσβαση</th>
<th class="dt-actions"></th>
</thead>
@@ -29,7 +27,6 @@
</div>
-
</div>
</div>
</div><!-- /manage -->
@@ -62,9 +59,9 @@
</div>
<div class="row form-group">
- <label class="control-label col-sm-12" for="parent_id">Γονική Κατηγορία</label>
+ <label class="control-label col-sm-12" for="course_id">Κεφάλαιο</label>
<div class="col-sm-12">
- <select class="form-control form-control-sm form-select form-select-sm" id="parent_id" name="parent_id" required></select>
+ <select class="form-control form-control-sm form-select form-select-sm" id="course_id" name="course_id" required></select>
</div>
</div>
diff --git a/public/assets/js/admin/edit_lesson.js b/public/assets/js/admin/edit_lesson.js
index c5b0501..89e464f 100644
--- a/public/assets/js/admin/edit_lesson.js
+++ b/public/assets/js/admin/edit_lesson.js
@@ -154,7 +154,7 @@ $(document).ready(function() {
// inti category (select2)
// ...
$("#course_id").select2({
- placeholder: 'Γονική Κατηγορία',
+ placeholder: 'Κεφάλαιο',
width: '100%'
});
diff --git a/public/assets/js/admin/lessons.js b/public/assets/js/admin/lessons.js
new file mode 100644
index 0000000..8391f4d
--- /dev/null
+++ b/public/assets/js/admin/lessons.js
@@ -0,0 +1,386 @@
+// Globals
+// -----------------------------------------------------------------------------
+var categories = [];
+var privileges = [];
+var lessons = [];
+var table;
+
+
+
+// Supplamentary functions
+// -----------------------------------------------------------------------------
+
+// array.indexOf polyfill
+// --- -- -- - - -
+if (!Array.prototype.indexOf)
+{
+ Array.prototype.indexOf = function(elt /*, from*/)
+ {
+ var len = this.length >>> 0;
+
+ var from = Number(arguments[1]) || 0;
+ from = (from < 0)
+ ? Math.ceil(from)
+ : Math.floor(from);
+ if (from < 0)
+ from += len;
+
+ for (; from < len; from++)
+ {
+ if (from in this &&
+ this[from] === elt)
+ return from;
+ }
+ return -1;
+ };
+}
+
+
+// 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;
+}
+
+
+// function for sorting an array by 'breadcrumb' attribute
+// --- -- -- - - -
+function compare_breadcrumb (a,b) {
+ if ( a.breadcrumb < b.breadcrumb ) { return -1; }
+ if ( a.breadcrumb > b.breadcrumb ) { return 1; }
+ return 0;
+}
+
+// return category record by category-id
+// --- -- -- - - -
+function lesson_record(id) {
+ var record = 0;
+ lessons.forEach(el => { if (el.id == id) record = el; });
+
+ return record;
+}
+
+
+// return children of parent
+// --- -- -- - - -
+function childs_of_parents(list) {
+ // if 'list' is integer then make it an one-item list
+ parents = (Number.isInteger(list)) ? [ list ] : list;
+
+ var childs = [];
+
+ if (parents.length == 0) { return []; }
+
+ parents.forEach( pid => {
+ categories.forEach( el => { if (el.parent == pid) childs.push(el.id)});
+ });
+
+ var grandchilds = childs_of_parents(childs);
+
+ return childs.concat(grandchilds);
+}
+
+
+// create actions html (edit and delete buttons)
+// --- -- -- - - -
+function create_actions_html(el) {
+
+ var pub, del, edit;
+
+ // preapre [show|hide]-file as reference
+ if (el.status == 1) {
+ pub = '<span class="btn btn-sm"><i class="fas fa-eye"></i></span>';
+
+ } else {
+ pub = '<span class="btn btn-sm"><i class="fas fa-eye-slash"></i></span>';
+ }
+
+ del = "";
+
+ fast_edit = [
+ '<button type="button" class="btn btn-sm btn-light"',
+ 'data-bs-toggle="modal" data-bs-target="#manageLessons"',
+ 'data-id="'+ el.id +'">',
+ '<i class="fa-solid fa-bolt"></i>',
+ '</button>'
+ ].join('\n');
+
+ edit = [
+ '<a type="button" class="btn btn-sm btn-warning "',
+ 'href="/admin/edit_lesson/'+ el.id +'">',
+ '<i class="fas fa-pencil-alt"></i>',
+ '</a>'
+ ].join('\n');
+
+ return pub +' '+ fast_edit +' '+ edit +' '+ del;
+}
+
+
+
+$(document).ready(function() {
+
+ // When document is ready
+ // -------------------------------------------------------------------------
+
+
+ // inti category (select2)
+ // ...
+ $("#privilege_id").select2({
+ placeholder: 'Επίπεδο Πρόσβασης',
+ width: '100%'
+ });
+
+ /** init selec2 element (used to specify parent-category)
+ * --- -- -- - - -
+ */
+ $("#course_id").select2({
+ placeholder: 'Κεφάλαιο',
+ dropdownParent: $('#manageLessons'),
+ width: '100%',
+ });
+
+
+ /** init_table
+ * --- -- -- - - -
+ * get categories
+ * constuct categories array
+ * render dataTable
+ */
+ function init_table() {
+
+ // get all categories from server (ajax)
+ $.getJSON( "/admin/api/lessons")
+ .done(function( json ) {
+ // then ...
+
+ // reset categories
+ lessons.length = 0;
+
+ // construct (new) categories data
+ Object.keys(json).forEach( key => {
+ el = json[key];
+ lessons.push({
+ id: parseInt(el.id),
+ title: el.title,
+ course_id: el.course_id,
+ privilege_id: el.privilege_id,
+ actions: create_actions_html(el)
+ });
+ });
+
+ // sort by breadcrumb
+ // categories.sort( compare_breadcrumb );
+
+ // destroy previous datatable table instances
+ $('#dt-lessons').dataTable().fnClearTable();
+ $('#dt-lessons').dataTable().fnDestroy();
+
+ // (re-)create categories datatable
+ table = $('#dt-lessons').DataTable({
+ language: { url: '/libs/DataTables/localization/Greek.json' },
+ data: lessons,
+ ordering: false,
+ columns: [
+ { data: 'id' },
+ { data: 'title' },
+ { data: 'course_id' },
+ { data: 'privilege_id' },
+ { data: 'actions' }
+ ]
+ });
+
+ })
+ .fail(function( jqxhr, textStatus, error ) {
+ console.log( "Request Failed (" + error +")" );
+ });
+
+ }
+
+
+ $.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 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
+
+ set_parent_options( $('#course_id'), categories );
+ set_parent_options( $('#privilege_id'), privileges );
+
+ init_table(); // init table on first run
+ });
+
+
+ /** 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'); }
+ }
+
+
+
+ /** When modal show-up
+ * -------------------------------------------------------------------------
+ * ... prepare the manage-category form
+ * ... update select2 with possible parents
+ */
+ $('#manageCategory').on('show.bs.modal', event => {
+ var button = $(event.relatedTarget); // Button that triggered the modal
+ var id = parseInt( button.data('id') ); // category_id from data-id
+ // var modal = $(this); // modal object
+
+ // update modal literature
+ // ---
+ $('#manageCategory .modal-title').html( // modal title
+ (id) ? 'Ενημέρωση Κατηγορίας' : 'Δημιουργία Κατηγορίας'
+ );
+ $('#manageCategory form input[name=id]').val( id ); // category id (hidden)
+ $('#manageCategory form input[name=label]').val( // category label
+ (id) ? category_record(id).label : ""
+ );
+ prepare_parents_element(id); // prepare <select> for parents
+ })
+
+// /** prepare_parents_element()
+// * ---
+// * @param id (int) : id of selected category
+// * -> Get all posible parents
+// * -> call set_parent_options() to render the options
+// */
+// function prepare_parents_element(id) {
+// var parents = [];
+//
+// if (id) { // get all possible parents
+//
+// // id-category and subcategories are fobiden parents
+// var forbiden = childs_of_parents(id);
+// forbiden.push(id);
+//
+// // filter catogories adn prepare parents
+// categories.forEach( el => {
+// if (!forbiden.includes(el.id)) {
+// parents.push({
+// id: el.id,
+// breadcrumb: el.breadcrumb,
+// });
+// }
+// });
+// set_parent_options(parents, category_record(id).parent);
+//
+//
+// } else { // new record; all categories are possible parrents
+// categories.forEach( el => {
+// parents.push({
+// id: el.id,
+// breadcrumb: el.breadcrumb,
+// });
+// })
+// set_parent_options(parents, -1);
+// }
+// }
+
+ /** set_parent_options()
+ * -> create options for parents <select>
+ * -> choose selected parent
+ * @param parents (array): array of parents
+ * @param selected (int): selected parent
+ */
+ function set_select_options(parents, selected) {
+ // sort parents by breadcrumb
+ parents.sort( compare_breadcrumb );
+
+ // remove any olded options from select2
+ $('#parent_id option').each(function() { $(this).remove(); });
+
+ // first option is the root category
+ $('#parent_id').append('<option value="0">Root</option>')
+
+ // add all other parent options
+ parents.forEach( i => {
+ $('#parent_id').append(`<option value="${i.id}">${i.breadcrumb}</option>`);
+ });
+
+ if (selected == -1) { // new category; remove any parent pre-selection
+ $('#parent_id').val(null).trigger('change');
+
+ } else { // category exists; choose selected parent
+ $('#parent_id').val(selected).trigger('change');
+ }
+ }
+
+
+
+
+ // When form is submited
+ // -------------------------------------------------------------------------
+
+ $('#manageCategory form').submit( event => {
+ event.preventDefault();
+
+ // prepare data to POST
+ var data = {
+ id: parseInt($('#manageCategory form input[name=id]').val()),
+ label: $('#manageCategory form input[name=label]').val(),
+ parent_id: parseInt($('#parent_id').select2('data')[0].id)
+ };
+
+ // select action url (add or update)
+ var request = '/admin/api/categories/' + ((data.id == 0) ? 'add' : 'update');
+
+ // send POST request
+ $.post(request, data)
+ .done(function( data ) {
+
+ $('#manageCategory').modal('hide'); // when done, close modal
+
+ init_table(); // reload table of categories
+ });
+
+ });
+
+});
diff --git a/tests/service/lorem-md.php b/tests/service/lorem-md.php
new file mode 100644
index 0000000..c7b8d0f
--- /dev/null
+++ b/tests/service/lorem-md.php
@@ -0,0 +1,71 @@
+<?php
+
+use Registry;
+
+define("LIMIT", 50); // number of random records to make
+
+function lorem_md() {
+ // create curl resource
+ $ch = curl_init();
+
+ // set url
+ curl_setopt($ch, CURLOPT_URL, "https://jaspervdj.be/lorem-markdownum/markdown.txt");
+
+ //return the transfer as a string
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+
+ // $output contains the output string
+ $output = curl_exec($ch);
+
+
+ $title = substr($output, 2, strpos( $output, "\n" )-1);
+ $body = trim(substr($output, strpos( $output, "\n" )+1));
+ $intro = str_replace("\n", " ", (substr($body, 3, strpos( $body, "\n" )-2) . $title));
+
+
+
+ // close curl resource to free up system resources
+ curl_close($ch);
+
+ return [
+ 'title' => $title,
+ 'intro' => $intro,
+ 'body' => $body
+ ];
+
+}
+
+$rand_content = lorem_md();
+
+
+for ($i=0; $i < LIMIT ; $i++) {
+
+ $rand_content = lorem_md();
+
+ $id = Registry::use('database')->query(
+ "INSERT INTO lesson (title, course_id, intro, `body`, `status`)
+ VALUES (:title, :courseid, :intro, :body, :status)",
+ [
+ ':title' => $rand_content['title'],
+ ':courseid' => rand(3,11),
+ ':intro' => $rand_content['intro'],
+ ':body' => $rand_content['body'],
+ 'status' => rand(0,1)
+ ]
+ )->lastInsertID();
+
+ Registry::use('database')->runQuery(
+ "INSERT INTO lesson_privilege (lesson_id, privilege_id)
+ VALUES (:lesson_id, :privilege_id)",
+ [
+ ':lesson_id' => $id,
+ ':privilege_id' => rand(1,2)
+ ]
+ );
+
+}
+
+
+?>
+
+done! \ No newline at end of file