From a0afb05ca1600f0d7fa40d711f912d51b670bc33 Mon Sep 17 00:00:00 2001 From: Adam Franco Date: Mon, 16 Dec 2024 16:29:05 -0500 Subject: [PATCH] #47: Initial stage of porting the Export system. Config edit screen loads. Much more to do still. --- application/controllers/ExportController.php | 511 ------------------ application/views/scripts/admin/export.phtml | 54 -- {docroot/javascript => assets}/export.js | 75 ++- .../Export.css => assets/styles/export.css | 0 importmap.php | 4 + src/Controller/AdminExports.php | 508 ++++++++++++++++- templates/admin/export_config.html.twig | 53 ++ templates/admin/index.html.twig | 4 +- .../admin/subject_department_select.html.twig | 13 + templates/base.html.twig | 7 + 10 files changed, 633 insertions(+), 596 deletions(-) delete mode 100644 application/views/scripts/admin/export.phtml rename {docroot/javascript => assets}/export.js (84%) rename docroot/StyleSheets/midd/Export.css => assets/styles/export.css (100%) create mode 100644 templates/admin/export_config.html.twig create mode 100644 templates/admin/subject_department_select.html.twig diff --git a/application/controllers/ExportController.php b/application/controllers/ExportController.php index d27ec627..19ef57c7 100644 --- a/application/controllers/ExportController.php +++ b/application/controllers/ExportController.php @@ -41,515 +41,4 @@ public function init() throw new PermissionDeniedException('You are not authorized to administer this application.'.$admins[1]); } } - - /** - * Echo JSON data of latest revision for a particular archive configuration. - * - * @return void - * - * @since 1/23/18 - */ - public function latestrevisionAction() - { - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - - $db = Zend_Registry::get('db'); - $query = - 'SELECT - * - FROM archive_configuration_revisions a - INNER JOIN ( - SELECT - arch_conf_id, - MAX(last_saved) as latest - FROM archive_configuration_revisions - GROUP BY arch_conf_id - ) b ON a.arch_conf_id = b.arch_conf_id and a.last_saved = b.latest - WHERE a.arch_conf_id = ?'; - $stmt = $db->prepare($query); - $stmt->execute([filter_input(\INPUT_GET, 'configId', \FILTER_SANITIZE_SPECIAL_CHARS)]); - $latestRevision = $stmt->fetch(); - echo $latestRevision['json_data']; - - return $latestRevision['json_data']; - } - - /** - * Display revision history for a given archive configuration. - * - * @return void - * - * @since 1/24/18 - */ - public function revisionhistoryAction() - { - $request = $this->getRequest(); - if (!$request->getParam('config') || -1 === $request->getParam('config')) { - header('HTTP/1.1 400 Bad Request'); - echo 'A config ID must be specified.'; - exit; - } - $this->view->configId = filter_var($request->getParam('config'), \FILTER_SANITIZE_NUMBER_INT); - $db = Zend_Registry::get('db'); - $query = 'SELECT label FROM archive_configurations WHERE id = ?'; - $stmt = $db->prepare($query); - $stmt->execute([$this->view->configId]); - $this->view->configLabel = $stmt->fetch()['label']; - - $query = 'SELECT * FROM archive_configuration_revisions WHERE arch_conf_id = ? ORDER BY last_saved DESC'; - $stmt = $db->prepare($query); - $stmt->execute([$this->view->configId]); - $this->view->revisions = $stmt->fetchAll(); - } - - /** - * Display diff between two revisions. - * - * @return void - * - * @since 1/25/18 - */ - public function revisiondiffAction() - { - if (!$this->_getParam('rev1') || !$this->_getParam('rev2')) { - header('HTTP/1.1 400 Bad Request'); - echo 'This route requires two revision IDs'; - exit; - } else { - $db = Zend_Registry::get('db'); - $query = 'SELECT * FROM archive_configuration_revisions WHERE id = ?'; - $stmt = $db->prepare($query); - $stmt->execute([filter_var($this->_getParam('rev1'), \FILTER_SANITIZE_NUMBER_INT)]); - $this->rev1 = $stmt->fetch(); - $stmt = $db->prepare($query); - $stmt->execute([filter_var($this->_getParam('rev2'), \FILTER_SANITIZE_NUMBER_INT)]); - $this->rev2 = $stmt->fetch(); - - $this->view->text1 = $this->rev1['json_data']; - $this->view->text2 = $this->rev2['json_data']; - $this->view->time1 = $this->rev1['last_saved']; - $this->view->time2 = $this->rev2['last_saved']; - } - } - - /** - * Display revision JSON data. - * - * @return void - * - * @since 1/25/18 - */ - public function viewjsonAction() - { - $request = $this->getRequest(); - if (!$request->getParam('revision') || -1 === $request->getParam('revision')) { - header('HTTP/1.1 400 Bad Request'); - echo 'This route requires a revision ID'; - exit; - } - - $db = Zend_Registry::get('db'); - $query = 'SELECT * FROM archive_configuration_revisions WHERE id = ?'; - $stmt = $db->prepare($query); - $stmt->execute([filter_var($this->_getParam('revision'), \FILTER_SANITIZE_NUMBER_INT)]); - $this->view->revision = $stmt->fetch(); - } - - /** - * Provide interface for creating a new archive configuration. - * - * @return void - * - * @since 1/23/18 - */ - public function newconfigAction() - { - $lookupSession = $this->_helper->osid->getCourseManager()->getCourseCatalogLookupSession(); - $this->view->catalogs = $lookupSession->getCourseCatalogs(); - } - - /** - * Delete an archive configuration. - * - * @return void - * - * @since 1/23/18 - */ - public function deleteconfigAction() - { - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - - // Delete revisions that depend on this config. - $db = Zend_Registry::get('db'); - $query = 'DELETE FROM archive_configuration_revisions WHERE arch_conf_id = ?'; - $stmt = $db->prepare($query); - $stmt->execute([filter_var($this->getRequest()->getPost('configId'), \FILTER_SANITIZE_NUMBER_INT)]); - - // Delete this config. - $query = 'DELETE FROM archive_configurations WHERE id = ?'; - $stmt = $db->prepare($query); - $stmt->execute([$this->getRequest()->getPost('configId')]); - } - - /** - * Insert a new archive configuration into the database. - * - * @return void - * - * @since 1/23/18 - */ - public function insertconfigAction() - { - if ($this->getRequest()->isPost()) { - $safeLabel = filter_input(\INPUT_POST, 'label', \FILTER_SANITIZE_SPECIAL_CHARS); - $safeCatalogId = filter_input(\INPUT_POST, 'catalog_id', \FILTER_SANITIZE_SPECIAL_CHARS); - - $db = Zend_Registry::get('db'); - $query = - 'INSERT INTO archive_configurations (id, label, catalog_id) - VALUES (NULL,:label,:catalogId)'; - $stmt = $db->prepare($query); - $stmt->execute([':label' => $safeLabel, ':catalogId' => $safeCatalogId]); - } - - $this->_helper->redirector('export', 'admin'); - } - - /** - * Echo JSON data of all archive export jobs. - * - * @return void - * - * @since 1/23/18 - */ - public function listjobsAction() - { - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - - $db = Zend_Registry::get('db'); - $configs = $db->query('SELECT * FROM archive_configurations')->fetchAll(); - $revisions = $db->query('SELECT `id`, `note`, `last_saved`, `arch_conf_id` FROM archive_configuration_revisions ORDER BY last_saved DESC')->fetchAll(); - $jobs = $db->query('SELECT * FROM archive_jobs ORDER BY terms DESC')->fetchAll(); - - $data = []; - $data[] = ['configs' => $configs]; - $data[] = ['revisions' => $revisions]; - $data[] = ['jobs' => $jobs]; - - echo json_encode($data); - } - - /** - * Provide an interface for creating a new archive export job. - * - * @return void - * - * @since 1/23/18 - */ - public function newjobAction() - { - $request = $this->getRequest(); - - $db = Zend_Registry::get('db'); - $this->view->configs = $db->query('SELECT * FROM archive_configurations')->fetchAll(); - - $this->view->config = null; - if ($request->getParam('config')) { - foreach ($this->view->configs as $config) { - if ($config['label'] === $request->getParam('config')) { - $this->view->config = $config; - } - } - } - } - - /** - * Delete an archive export job. - * - * @return void - * - * @since 1/23/18 - */ - public function deletejobAction() - { - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - - // Delete revisions that depend on this config. - $db = Zend_Registry::get('db'); - $query = 'DELETE FROM archive_jobs WHERE id = ?'; - $stmt = $db->prepare($query); - $stmt->execute([filter_var($this->getRequest()->getPost('jobId'), \FILTER_SANITIZE_NUMBER_INT)]); - } - - /** - * Insert a new archive export job into the DB. - * - * @return void - * - * @since 1/23/18 - */ - public function insertjobAction() - { - if ($this->getRequest()->isPost()) { - $safeConfigId = filter_input(\INPUT_POST, 'configId', \FILTER_SANITIZE_SPECIAL_CHARS); - $safeExportPath = filter_input(\INPUT_POST, 'export_path', \FILTER_SANITIZE_SPECIAL_CHARS); - $safeTerms = filter_input(\INPUT_POST, 'terms', \FILTER_SANITIZE_SPECIAL_CHARS); - - $db = Zend_Registry::get('db'); - $query = - 'INSERT INTO archive_jobs (id, active, export_path, config_id, revision_id, terms) - VALUES (NULL, 1, :export_path, :config_id, NULL, :terms)'; - $stmt = $db->prepare($query); - $stmt->execute([':export_path' => $safeExportPath, ':config_id' => $safeConfigId, ':terms' => $safeTerms]); - } - - $this->_helper->redirector('schedule', 'admin'); - } - - /** - * Update an existing archive export job. - * - * @return void - * - * @since 1/23/18 - */ - public function updatejobAction() - { - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - - if ($this->getRequest()->isPost()) { - $safeId = filter_input(\INPUT_POST, 'jobId', \FILTER_SANITIZE_SPECIAL_CHARS); - $safeActive = filter_input(\INPUT_POST, 'active', \FILTER_SANITIZE_SPECIAL_CHARS); - $safeExportPath = filter_input(\INPUT_POST, 'export_path', \FILTER_SANITIZE_SPECIAL_CHARS); - $safeConfigId = filter_input(\INPUT_POST, 'config_id', \FILTER_SANITIZE_SPECIAL_CHARS); - $safeRevisionId = filter_input(\INPUT_POST, 'revision_id', \FILTER_SANITIZE_SPECIAL_CHARS); - if ('latest' === $safeRevisionId) { - $safeRevisionId = null; - } - $safeTerms = filter_input(\INPUT_POST, 'terms', \FILTER_SANITIZE_SPECIAL_CHARS); - - $db = Zend_Registry::get('db'); - $query = - 'UPDATE archive_jobs - SET active = :active, export_path = :export_path, config_id = :config_id, revision_id = :revision_id, terms = :terms - WHERE id = :id'; - $stmt = $db->prepare($query); - $stmt->execute([':id' => $safeId, ':active' => $safeActive, ':export_path' => $safeExportPath, ':config_id' => $safeConfigId, ':revision_id' => $safeRevisionId, ':terms' => $safeTerms]); - } - } - - /** - * Echo JSON data of all archive configuration revisions. - * - * @return void - * - * @since 1/23/18 - */ - public function listrevisionsAction() - { - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - - $db = Zend_Registry::get('db'); - $revisions = $db->query('SELECT * FROM archive_configuration_revisions ORDER BY last_saved DESC')->fetchAll(); - - echo json_encode($revisions); - } - - /** - * Revert to an older revision by re-inserting it with new timestamp. - * - * @return void - * - * @since 1/25/18 - */ - public function reverttorevisionAction() - { - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - - $safeRevisionId = filter_input(\INPUT_POST, 'revId', \FILTER_SANITIZE_SPECIAL_CHARS); - - $db = Zend_Registry::get('db'); - $query = 'SELECT * FROM archive_configuration_revisions WHERE id=:id'; - $stmt = $db->prepare($query); - $stmt->execute([':id' => $safeRevisionId]); - $oldRevision = $stmt->fetch(); - $note = 'Revert to revision: '.$safeRevisionId.' from '.$oldRevision['last_saved']; - - $query = - 'INSERT INTO archive_configuration_revisions (`arch_conf_id`, `note`, `last_saved`, `user_id`, `user_disp_name`, `json_data`) - VALUES ( - :configId, - :note, - CURRENT_TIMESTAMP, - :userId, - :userDN, - :jsonData)'; - $stmt = $db->prepare($query); - $stmt->execute([':configId' => $oldRevision['arch_conf_id'], ':note' => $note, ':userId' => $this->_helper->auth()->getUserId(), ':userDN' => $this->_helper->auth()->getUserDisplayName(), ':jsonData' => $oldRevision['json_data']]); - } - - /** - * Insert new archive configuration revision to the DB. - * - * @return void - * - * @since 1/23/18 - */ - public function insertrevisionAction() - { - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - - if (!$this->getRequest()->isPost()) { - echo 'This route requires a POST request'; - - return; - } - - $safeConfigId = filter_input(\INPUT_POST, 'configId', \FILTER_SANITIZE_SPECIAL_CHARS); - $safeNote = filter_input(\INPUT_POST, 'note', \FILTER_SANITIZE_SPECIAL_CHARS); - $jsonArray = json_decode($this->getRequest()->getPost('jsonData')); - foreach ($jsonArray as $key => $value) { - $value = filter_var($value, \FILTER_SANITIZE_SPECIAL_CHARS); - } - $safeJsonData = json_encode($jsonArray, \JSON_PRETTY_PRINT); - - $db = Zend_Registry::get('db'); - $query = - 'INSERT INTO archive_configuration_revisions (`arch_conf_id`, `note`, `last_saved`, `user_id`, `user_disp_name`, `json_data`) - VALUES ( - :configId, - :note, - CURRENT_TIMESTAMP, - :userId, - :userDN, - :jsonData)'; - $stmt = $db->prepare($query); - $stmt->execute([':configId' => $safeConfigId, ':note' => $safeNote, ':userId' => $this->_helper->auth()->getUserId(), ':userDN' => $this->_helper->auth()->getUserDisplayName(), ':jsonData' => $safeJsonData]); - - return $this->getRequest()->getPost(); - } - - /** - * Echo HTML for a course list dropdown menu based on an archive configuration ID. - * - * @return void - * - * @since 1/23/18 - */ - public function generatecourselistAction() - { - $request = $this->getRequest(); - - if ($request->getParam('catalogId')) { - $catalogId = $this->_helper->osidId->fromString($request->getParam('catalogId')); - $this->departmentType = new phpkit_type_URNInetType('urn:inet:middlebury.edu:genera:topic.department'); - $this->subjectType = new phpkit_type_URNInetType('urn:inet:middlebury.edu:genera:topic.subject'); - - $topicSearchSession = $this->_helper->osid->getCourseManager()->getTopicSearchSessionForCatalog($catalogId); - $topicQuery = $topicSearchSession->getTopicQuery(); - $topicQuery->matchGenusType($this->departmentType, true); - if (isset($termId) && $topicQuery->hasRecordType($this->termType)) { - $record = $topicQuery->getTopicQueryRecord($this->termType); - $record->matchTermId($termId, true); - } - $search = $topicSearchSession->getTopicSearch(); - $order = $topicSearchSession->getTopicSearchOrder(); - $order->orderByDisplayName(); - $search->orderTopicResults($order); - $searchResults = $topicSearchSession->getTopicsBySearch($topicQuery, $search); - $departments = $searchResults->getTopics(); - - $topicQuery = $topicSearchSession->getTopicQuery(); - $topicQuery->matchGenusType($this->subjectType, true); - if (isset($termId) && $topicQuery->hasRecordType($this->termType)) { - $record = $topicQuery->getTopicQueryRecord($this->termType); - $record->matchTermId($termId, true); - } - $search = $topicSearchSession->getTopicSearch(); - $order = $topicSearchSession->getTopicSearchOrder(); - $order->orderByDisplayName(); - $search->orderTopicResults($order); - $searchResults = $topicSearchSession->getTopicsBySearch($topicQuery, $search); - $subjects = $searchResults->getTopics(); - - $sectionInput = " - '; - - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - - echo $sectionInput; - } else { - echo 'Invalid request. Please provide a catalogId'; - } - } - - /** - * Echo whether a user-entered term ID is valid. - * - * @return void - * - * @since 1/23/18 - */ - // TODO - return instead of echo? - public function validtermAction() - { - $request = $this->getRequest(); - - if (!$request->getParam('catalogId')) { - echo 'No catalog specified!'; - exit; - } - if (!$request->getParam('term')) { - echo 'No term specified!'; - exit; - } - - $catalogId = $this->_helper->osidId->fromString($request->getParam('catalogId')); - $this->termLookupSession = $this->_helper->osid->getCourseManager()->getTermLookupSessionForCatalog($catalogId); - - try { - $termString = 'term.'.$request->getParam('term'); - $termId = $this->_helper->osidId->fromString($termString); - } catch (osid_InvalidArgumentException $e) { - header('HTTP/1.1 400 Bad Request'); - echo 'The term id specified was not of the correct format.'; - exit; - } catch (osid_NotFoundException $e) { - echo 'not valid'; - } - - if ($this->termLookupSession->getTerm($termId)) { - echo 'valid'; - } else { - echo 'not valid'; - } - - $this->_helper->layout()->disableLayout(); - $this->_helper->viewRenderer->setNoRender(true); - } } diff --git a/application/views/scripts/admin/export.phtml b/application/views/scripts/admin/export.phtml deleted file mode 100644 index 1c823f67..00000000 --- a/application/views/scripts/admin/export.phtml +++ /dev/null @@ -1,54 +0,0 @@ -headLink(array('rel' => 'stylesheet', 'href' => $this->baseUrl('/StyleSheets/midd/Export.css'), 'media' => 'all')); - $this->headScript()->appendFile($this->baseUrl('javascript/export.js')); - $this->headScript()->appendFile($this->baseUrl('javascript/jquery-ui-1.12.1/jquery-ui.min.js')); -?> - -

Manage Catalog Export Configurations

-
« Back to Administration
- -
-
- -
-

- or - Create a new configuration

-
- -"; - - if($this->catalogId) { - print ""; - print ""; - - print "
Error:
"; - - // Top nav - print ""; - print ""; - print ""; - print ""; - print "Revision history"; - - // Content - print "
"; - print ""; - print "
Error:
"; - - // Bottom nav - print ""; - print ""; - print ""; - print ""; - print "Revision history"; - } - - print ""; -?> diff --git a/docroot/javascript/export.js b/assets/export.js similarity index 84% rename from docroot/javascript/export.js rename to assets/export.js index ebebc3c9..5c0c9932 100644 --- a/docroot/javascript/export.js +++ b/assets/export.js @@ -1,3 +1,6 @@ +import './styles/export.css'; +import $ from 'jquery'; +import 'jquery-ui'; var loading = false; @@ -14,7 +17,7 @@ function generateSection(id, type, input) { } function generateSectionHTML(type, input) { - return "
Type: " + type + "" + input + ""; + return "
Type: " + type + "" + input + ""; } // Cache of courselist data for reuse. @@ -70,16 +73,14 @@ function generateInputTag(type, value, callback) { // Run the request and work through our queue when we get the result back. $.ajax({ - url: "../export/generateCourseList", + url: "../exports/generatecourselist/" + $('#catalogId').val(), type: "GET", - data: { - catalogId: $('#catalogId').val() - }, error: function(error) { throw error; }, success: function(data) { courselist_data[$('#catalogId').val()] = data; + var l; while (l = courselists_to_populate.pop()) { setCourseListInputForData(courselist_data[l['catalog']], l['value'], l['callback']); } @@ -115,9 +116,9 @@ function setCourseListInputForData(data, value, callback) { function generateGroup(id, title, visible) { if (visible) { - return "
  • " + title + "
    show/hide
  • "; + return "
  • " + title + "
    show/hide
  • "; } else { - return "
  • " + title + "
    show/hide
  • "; + return "
  • " + title + "
    show/hide
  • "; } } @@ -168,17 +169,30 @@ function buildList(jsonData, callback) { } function populate() { + $('#config-selector').change(function() { + window.location = this.value; + }); if(!$('#configId').val()) { return; } + $('#save-export-config-button').on('click', function() { + saveJSON(); + }); + $('#reset-export-config-button').on('click', function() { + reset(); + }); + $('#delete-export-config-button').on('click', function() { + deleteConfig($('#configId').val()); + }); + $('#show-hide-export-config-groups-button').on('click', function() { + showHide(); + }); + loading = true; $.ajax({ - url: "../export/latestrevision", + url: "../exports/" + $('#configId').val() + "/latest.json", type: "GET", dataType: "JSON", - data: { - configId: $('#configId').val() - }, success: function(data) { buildList(data, function() { renameGroups(); @@ -259,6 +273,33 @@ function resetEventListeners() { renameGroups(); renameSections(); }); + + // Attach handlers to buttons based on class. + $('.group-toggle-description').unbind('click').on('click', function () { + toggleGroup(this); + }); + $('.add-group-below').unbind('click').on('click', function () { + newGroup(this); + }); + $('.button-section-add').unbind('click').on('click', function () { + newSection(this); + }); + $('.button-delete-section').unbind('click').on('click', function () { + deleteSection(this); + }); + $('.button-delete-group').unbind('click').on('click', function () { + deleteGroup(this); + }); + $('.button-confirm-delete').unbind('click').on('click', function () { + confirmDelete($(this).data('config-id')); + }); + $('.button-cancel-delete').unbind('click').on('click', function () { + cancelDelete(); + }); + $('.select-section-type').unbind('click').on('click', function () { + defineSection(this); + }); + } function showHide() { @@ -406,11 +447,10 @@ function saveJSON() { if(JSONString === "}") JSONString = "{}"; $.ajax({ - url: "../export/insertrevision", + url: "../exports/" + $('#configId').attr('value') + "/insertrevision", type: "POST", dataType: 'json', data: { - configId: $('#configId').attr('value'), note: $('#note').val(), jsonData: JSONString }, @@ -434,12 +474,13 @@ function saveJSON() { function deleteConfig(configId) { if($('#warning-box').length) return; - $('#config-body').append("

    Are you sure you want to delete this configuration? This cannot be undone. All related revisions will be gone as well.

    "); + $('#config-body').append("

    Are you sure you want to delete this configuration? This cannot be undone. All related revisions will be gone as well.

    "); + resetEventListeners(); } function confirmDelete(confId) { $.ajax({ - url: "../export/deleteconfig", + url: "../exports/" + configId + "/deleteconfig", type: "POST", data: { configId: confId @@ -522,7 +563,7 @@ function newGroupFirstSection() { } function newSection(thisButton) { - var newSectionHTML = "
  • "; + var newSectionHTML = "
  • "; if(!thisButton) { if($('#begin-message')) { $('#begin-message').remove(); @@ -532,6 +573,7 @@ function newSection(thisButton) { var li = $(thisButton).parent().parent(); $(newSectionHTML).insertAfter(li); } + resetEventListeners(); renameSections(); } @@ -557,4 +599,5 @@ function deleteSection(thisButton) { $(document).ready(function() { populate(); + resetEventListeners(); }); diff --git a/docroot/StyleSheets/midd/Export.css b/assets/styles/export.css similarity index 100% rename from docroot/StyleSheets/midd/Export.css rename to assets/styles/export.css diff --git a/importmap.php b/importmap.php index 695891b7..a9f1b7c5 100644 --- a/importmap.php +++ b/importmap.php @@ -19,6 +19,10 @@ 'bookmarks' => [ 'path' => 'bookmarks.js', ], + 'export' => [ + 'path' => 'export.js', + 'entrypoint' => true, + ], 'offering_search' => [ 'path' => 'offering_search.js', 'entrypoint' => true, diff --git a/src/Controller/AdminExports.php b/src/Controller/AdminExports.php index 9e3096f6..7fbfe732 100644 --- a/src/Controller/AdminExports.php +++ b/src/Controller/AdminExports.php @@ -2,48 +2,530 @@ namespace App\Controller; +use App\Service\Osid\Runtime; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class AdminExports extends AbstractController { public function __construct( private EntityManagerInterface $entityManager, + private Runtime $osidRuntime, ) { } /** - * Manage archive export configurations. + * List archive export configurations. */ - #[Route('/admin/export', name: 'admin_archive_config')] - public function exportAction() + #[Route('/admin/exports/{exportId}', name: 'admin_exports_config')] + public function listExportConfigs(?int $exportId = null) { + $db = $this->entityManager->getConnection(); + + $data['configs'] = $db->executeQuery('SELECT * FROM archive_configurations')->fetchAllAssociative(); + + $data['selected_config'] = null; + if ($exportId && -1 != $exportId) { + foreach ($data['configs'] as $config) { + if ($config['id'] == $exportId) { + $data['selected_config'] = $config; + } + } + } + + return $this->render('admin/export_config.html.twig', $data); + } + + /** + * Manage catalog archive scheduling. + */ + #[Route('/admin/exports/schedule', name: 'admin_exports_list_schedules')] + public function scheduleAction() + { + } + + /** + * Echo JSON data of latest revision for a particular archive configuration. + */ + #[Route('/admin/exports/{exportId}/latest.json', name: 'admin_exports_config_latest_revision')] + public function latestrevisionAction(int $exportId) + { + $db = $this->entityManager->getConnection(); + $query = + 'SELECT + * + FROM archive_configuration_revisions a + INNER JOIN ( + SELECT + arch_conf_id, + MAX(last_saved) as latest + FROM archive_configuration_revisions + GROUP BY arch_conf_id + ) b ON a.arch_conf_id = b.arch_conf_id and a.last_saved = b.latest + WHERE a.arch_conf_id = ?'; + $stmt = $db->prepare($query); + $stmt->bindValue(1, $exportId); + $result = $stmt->executeQuery(); + $latestRevision = $result->fetchAssociative(); + + return JsonResponse::fromJsonString($latestRevision['json_data']); + } + + /** + * Display revision history for a given archive configuration. + */ + #[Route('/admin/exports/{exportId}/revisions', name: 'admin_exports_config_revisions')] + public function revisionhistoryAction(int $exportId) + { + $request = $this->getRequest(); + if (!$request->getParam('config') || -1 === $request->getParam('config')) { + header('HTTP/1.1 400 Bad Request'); + echo 'A config ID must be specified.'; + exit; + } + $this->view->configId = filter_var($request->getParam('config'), \FILTER_SANITIZE_NUMBER_INT); $db = Zend_Registry::get('db'); + $query = 'SELECT label FROM archive_configurations WHERE id = ?'; + $stmt = $db->prepare($query); + $stmt->execute([$this->view->configId]); + $this->view->configLabel = $stmt->fetch()['label']; + + $query = 'SELECT * FROM archive_configuration_revisions WHERE arch_conf_id = ? ORDER BY last_saved DESC'; + $stmt = $db->prepare($query); + $stmt->execute([$this->view->configId]); + $this->view->revisions = $stmt->fetchAll(); + } - $this->view->configs = $db->query('SELECT * FROM archive_configurations')->fetchAllAssociative(); + /** + * Display diff between two revisions. + * + * @return void + * + * @since 1/25/18 + */ + public function revisiondiffAction() + { + if (!$this->_getParam('rev1') || !$this->_getParam('rev2')) { + header('HTTP/1.1 400 Bad Request'); + echo 'This route requires two revision IDs'; + exit; + } else { + $db = Zend_Registry::get('db'); + $query = 'SELECT * FROM archive_configuration_revisions WHERE id = ?'; + $stmt = $db->prepare($query); + $stmt->execute([filter_var($this->_getParam('rev1'), \FILTER_SANITIZE_NUMBER_INT)]); + $this->rev1 = $stmt->fetch(); + $stmt = $db->prepare($query); + $stmt->execute([filter_var($this->_getParam('rev2'), \FILTER_SANITIZE_NUMBER_INT)]); + $this->rev2 = $stmt->fetch(); + + $this->view->text1 = $this->rev1['json_data']; + $this->view->text2 = $this->rev2['json_data']; + $this->view->time1 = $this->rev1['last_saved']; + $this->view->time2 = $this->rev2['last_saved']; + } + } + + /** + * Display revision JSON data. + * + * @return void + * + * @since 1/25/18 + */ + public function viewjsonAction() + { + $request = $this->getRequest(); + if (!$request->getParam('revision') || -1 === $request->getParam('revision')) { + header('HTTP/1.1 400 Bad Request'); + echo 'This route requires a revision ID'; + exit; + } + + $db = Zend_Registry::get('db'); + $query = 'SELECT * FROM archive_configuration_revisions WHERE id = ?'; + $stmt = $db->prepare($query); + $stmt->execute([filter_var($this->_getParam('revision'), \FILTER_SANITIZE_NUMBER_INT)]); + $this->view->revision = $stmt->fetch(); + } + + /** + * Provide interface for creating a new archive configuration. + * + * @return void + * + * @since 1/23/18 + */ + #[Route('/admin/exports/create', name: 'admin_exports_create')] + public function newconfigAction() + { + $lookupSession = $this->osidRuntime->getCourseManager()->getCourseCatalogLookupSession(); + $this->view->catalogs = $lookupSession->getCourseCatalogs(); + } + + /** + * Delete an archive configuration. + * + * @return void + * + * @since 1/23/18 + */ + public function deleteconfigAction() + { + $this->_helper->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + // Delete revisions that depend on this config. + $db = Zend_Registry::get('db'); + $query = 'DELETE FROM archive_configuration_revisions WHERE arch_conf_id = ?'; + $stmt = $db->prepare($query); + $stmt->execute([filter_var($this->getRequest()->getPost('configId'), \FILTER_SANITIZE_NUMBER_INT)]); + + // Delete this config. + $query = 'DELETE FROM archive_configurations WHERE id = ?'; + $stmt = $db->prepare($query); + $stmt->execute([$this->getRequest()->getPost('configId')]); + } + + /** + * Insert a new archive configuration into the database. + * + * @return void + * + * @since 1/23/18 + */ + public function insertconfigAction() + { + if ($this->getRequest()->isPost()) { + $safeLabel = filter_input(\INPUT_POST, 'label', \FILTER_SANITIZE_SPECIAL_CHARS); + $safeCatalogId = filter_input(\INPUT_POST, 'catalog_id', \FILTER_SANITIZE_SPECIAL_CHARS); + + $db = Zend_Registry::get('db'); + $query = + 'INSERT INTO archive_configurations (id, label, catalog_id) + VALUES (NULL,:label,:catalogId)'; + $stmt = $db->prepare($query); + $stmt->execute([':label' => $safeLabel, ':catalogId' => $safeCatalogId]); + } + + $this->_helper->redirector('export', 'admin'); + } + + /** + * Echo JSON data of all archive export jobs. + * + * @return void + * + * @since 1/23/18 + */ + public function listjobsAction() + { + $this->_helper->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + $db = Zend_Registry::get('db'); + $configs = $db->query('SELECT * FROM archive_configurations')->fetchAll(); + $revisions = $db->query('SELECT `id`, `note`, `last_saved`, `arch_conf_id` FROM archive_configuration_revisions ORDER BY last_saved DESC')->fetchAll(); + $jobs = $db->query('SELECT * FROM archive_jobs ORDER BY terms DESC')->fetchAll(); + + $data = []; + $data[] = ['configs' => $configs]; + $data[] = ['revisions' => $revisions]; + $data[] = ['jobs' => $jobs]; + + echo json_encode($data); + } + + /** + * Provide an interface for creating a new archive export job. + * + * @return void + * + * @since 1/23/18 + */ + public function newjobAction() + { + $request = $this->getRequest(); + + $db = Zend_Registry::get('db'); + $this->view->configs = $db->query('SELECT * FROM archive_configurations')->fetchAll(); $this->view->config = null; - if ($this->_getParam('config') && -1 != $this->_getParam('config')) { + if ($request->getParam('config')) { foreach ($this->view->configs as $config) { - if ($config['id'] == $this->_getParam('config')) { + if ($config['label'] === $request->getParam('config')) { $this->view->config = $config; } } } + } + + /** + * Delete an archive export job. + * + * @return void + * + * @since 1/23/18 + */ + public function deletejobAction() + { + $this->_helper->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + // Delete revisions that depend on this config. + $db = Zend_Registry::get('db'); + $query = 'DELETE FROM archive_jobs WHERE id = ?'; + $stmt = $db->prepare($query); + $stmt->execute([filter_var($this->getRequest()->getPost('jobId'), \FILTER_SANITIZE_NUMBER_INT)]); + } - // If user has selected a configuration to modify, get the latest revision. - if (isset($this->view->config)) { - $catalogId = $this->_helper->osidId->fromString($this->view->config['catalog_id']); - $this->view->catalogId = $this->view->config['catalog_id']; + /** + * Insert a new archive export job into the DB. + * + * @return void + * + * @since 1/23/18 + */ + public function insertjobAction() + { + if ($this->getRequest()->isPost()) { + $safeConfigId = filter_input(\INPUT_POST, 'configId', \FILTER_SANITIZE_SPECIAL_CHARS); + $safeExportPath = filter_input(\INPUT_POST, 'export_path', \FILTER_SANITIZE_SPECIAL_CHARS); + $safeTerms = filter_input(\INPUT_POST, 'terms', \FILTER_SANITIZE_SPECIAL_CHARS); + + $db = Zend_Registry::get('db'); + $query = + 'INSERT INTO archive_jobs (id, active, export_path, config_id, revision_id, terms) + VALUES (NULL, 1, :export_path, :config_id, NULL, :terms)'; + $stmt = $db->prepare($query); + $stmt->execute([':export_path' => $safeExportPath, ':config_id' => $safeConfigId, ':terms' => $safeTerms]); } + + $this->_helper->redirector('schedule', 'admin'); } /** - * Manage catalog archive scheduling. + * Update an existing archive export job. + * + * @return void + * + * @since 1/23/18 */ - #[Route('/admin/export', name: 'admin_archive_schedule')] - public function scheduleAction() + public function updatejobAction() + { + $this->_helper->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + if ($this->getRequest()->isPost()) { + $safeId = filter_input(\INPUT_POST, 'jobId', \FILTER_SANITIZE_SPECIAL_CHARS); + $safeActive = filter_input(\INPUT_POST, 'active', \FILTER_SANITIZE_SPECIAL_CHARS); + $safeExportPath = filter_input(\INPUT_POST, 'export_path', \FILTER_SANITIZE_SPECIAL_CHARS); + $safeConfigId = filter_input(\INPUT_POST, 'config_id', \FILTER_SANITIZE_SPECIAL_CHARS); + $safeRevisionId = filter_input(\INPUT_POST, 'revision_id', \FILTER_SANITIZE_SPECIAL_CHARS); + if ('latest' === $safeRevisionId) { + $safeRevisionId = null; + } + $safeTerms = filter_input(\INPUT_POST, 'terms', \FILTER_SANITIZE_SPECIAL_CHARS); + + $db = Zend_Registry::get('db'); + $query = + 'UPDATE archive_jobs + SET active = :active, export_path = :export_path, config_id = :config_id, revision_id = :revision_id, terms = :terms + WHERE id = :id'; + $stmt = $db->prepare($query); + $stmt->execute([':id' => $safeId, ':active' => $safeActive, ':export_path' => $safeExportPath, ':config_id' => $safeConfigId, ':revision_id' => $safeRevisionId, ':terms' => $safeTerms]); + } + } + + /** + * Echo JSON data of all archive configuration revisions. + * + * @return void + * + * @since 1/23/18 + */ + public function listrevisionsAction() + { + $this->_helper->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + $db = Zend_Registry::get('db'); + $revisions = $db->query('SELECT * FROM archive_configuration_revisions ORDER BY last_saved DESC')->fetchAll(); + + echo json_encode($revisions); + } + + /** + * Revert to an older revision by re-inserting it with new timestamp. + * + * @return void + * + * @since 1/25/18 + */ + public function reverttorevisionAction() + { + $this->_helper->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); + + $safeRevisionId = filter_input(\INPUT_POST, 'revId', \FILTER_SANITIZE_SPECIAL_CHARS); + + $db = Zend_Registry::get('db'); + $query = 'SELECT * FROM archive_configuration_revisions WHERE id=:id'; + $stmt = $db->prepare($query); + $stmt->execute([':id' => $safeRevisionId]); + $oldRevision = $stmt->fetch(); + $note = 'Revert to revision: '.$safeRevisionId.' from '.$oldRevision['last_saved']; + + $query = + 'INSERT INTO archive_configuration_revisions (`arch_conf_id`, `note`, `last_saved`, `user_id`, `user_disp_name`, `json_data`) + VALUES ( + :configId, + :note, + CURRENT_TIMESTAMP, + :userId, + :userDN, + :jsonData)'; + $stmt = $db->prepare($query); + $stmt->execute([':configId' => $oldRevision['arch_conf_id'], ':note' => $note, ':userId' => $this->_helper->auth()->getUserId(), ':userDN' => $this->_helper->auth()->getUserDisplayName(), ':jsonData' => $oldRevision['json_data']]); + } + + /** + * Insert new archive configuration revision to the DB. + */ + #[Route('/admin/exports/{exportId}/insertrevision', name: 'admin_exports_config_insert_revision', methods: ['POST'])] + public function insertrevisionAction(Request $request, int $exportId) { + $safeNote = filter_var($request->get('note'), \FILTER_SANITIZE_SPECIAL_CHARS); + $jsonArray = json_decode($request->get('jsonData')); + foreach ($jsonArray as $key => $value) { + $value = filter_var($value, \FILTER_SANITIZE_SPECIAL_CHARS); + } + $safeJsonData = json_encode($jsonArray, \JSON_PRETTY_PRINT); + + $db = $this->entityManager->getConnection(); + $query = + 'INSERT INTO archive_configuration_revisions (`arch_conf_id`, `note`, `last_saved`, `user_id`, `user_disp_name`, `json_data`) + VALUES ( + :configId, + :note, + CURRENT_TIMESTAMP, + :userId, + :userDN, + :jsonData)'; + $stmt = $db->prepare($query); + $stmt->bindValue('configId', $exportId); + $stmt->bindValue('note', $safeNote); + $stmt->bindValue('userId', $this->getUser()->getUserIdentifier()); + $stmt->bindValue('userDN', $this->getUser()->getName()); + $stmt->bindValue('jsonData', $safeJsonData); + $stmt->executeQuery(); + + $response = new Response('Success'); + $response->headers->set('Content-Type', 'text/plain; charset=utf-8'); + + return $response; + } + + /** + * Echo HTML for a course list dropdown menu based on an archive configuration ID. + * + * @return void + * + * @since 1/23/18 + */ + #[Route('/admin/exports/generatecourselist/{catalogId}', name: 'admin_exports_generate_course_list')] + public function generatecourselistAction(\osid_id_Id $catalogId) + { + $this->departmentType = new \phpkit_type_URNInetType('urn:inet:middlebury.edu:genera:topic.department'); + $this->subjectType = new \phpkit_type_URNInetType('urn:inet:middlebury.edu:genera:topic.subject'); + + $topicSearchSession = $this->osidRuntime->getCourseManager()->getTopicSearchSessionForCatalog($catalogId); + $topicQuery = $topicSearchSession->getTopicQuery(); + $topicQuery->matchGenusType($this->departmentType, true); + if (isset($termId) && $topicQuery->hasRecordType($this->termType)) { + $record = $topicQuery->getTopicQueryRecord($this->termType); + $record->matchTermId($termId, true); + } + $search = $topicSearchSession->getTopicSearch(); + $order = $topicSearchSession->getTopicSearchOrder(); + $order->orderByDisplayName(); + $search->orderTopicResults($order); + $searchResults = $topicSearchSession->getTopicsBySearch($topicQuery, $search); + $departments = $searchResults->getTopics(); + + $topicQuery = $topicSearchSession->getTopicQuery(); + $topicQuery->matchGenusType($this->subjectType, true); + if (isset($termId) && $topicQuery->hasRecordType($this->termType)) { + $record = $topicQuery->getTopicQueryRecord($this->termType); + $record->matchTermId($termId, true); + } + $search = $topicSearchSession->getTopicSearch(); + $order = $topicSearchSession->getTopicSearchOrder(); + $order->orderByDisplayName(); + $search->orderTopicResults($order); + $searchResults = $topicSearchSession->getTopicsBySearch($topicQuery, $search); + $subjects = $searchResults->getTopics(); + + $data = [ + 'subjects' => [], + 'departments' => [], + ]; + while ($subjects->hasNext()) { + $data['subjects'][] = $subjects->getNextTopic(); + } + while ($departments->hasNext()) { + $data['departments'][] = $departments->getNextTopic(); + } + + return $this->render('admin/subject_department_select.html.twig', $data); + } + + /** + * Echo whether a user-entered term ID is valid. + * + * @return void + * + * @since 1/23/18 + */ + // TODO - return instead of echo? + public function validtermAction() + { + $request = $this->getRequest(); + + if (!$request->getParam('catalogId')) { + echo 'No catalog specified!'; + exit; + } + if (!$request->getParam('term')) { + echo 'No term specified!'; + exit; + } + + $catalogId = $this->_helper->osidId->fromString($request->getParam('catalogId')); + $this->termLookupSession = $this->osidRuntime->getCourseManager()->getTermLookupSessionForCatalog($catalogId); + + try { + $termString = 'term.'.$request->getParam('term'); + $termId = $this->_helper->osidId->fromString($termString); + } catch (osid_InvalidArgumentException $e) { + header('HTTP/1.1 400 Bad Request'); + echo 'The term id specified was not of the correct format.'; + exit; + } catch (osid_NotFoundException $e) { + echo 'not valid'; + } + + if ($this->termLookupSession->getTerm($termId)) { + echo 'valid'; + } else { + echo 'not valid'; + } + + $this->_helper->layout()->disableLayout(); + $this->_helper->viewRenderer->setNoRender(true); } } diff --git a/templates/admin/export_config.html.twig b/templates/admin/export_config.html.twig new file mode 100644 index 00000000..c0eb06d4 --- /dev/null +++ b/templates/admin/export_config.html.twig @@ -0,0 +1,53 @@ +{% extends 'base.html.twig' %} + +{% block assets %} + arrow_cross: "{{ asset("images/arrow_cross.png") }}", +{% endblock %} + +{% block importmap %} + {# do NOT call parent() #} + {{ importmap(['app', 'export']) }} +{% endblock %} + +{% block body %} +

    Manage Catalog Export Configurations

    +
    « Back to Administration
    + +
    + +

    - or - Create a new configuration

    +
    + +
    +{% if selected_config and selected_config.id %} + + + +
    Error:
    + + + + + + + Revision history + + +
    + +
    Error:
    + + + + + + + Revision history +{% endif %} +
    +{% endblock %} diff --git a/templates/admin/index.html.twig b/templates/admin/index.html.twig index b1f32946..5962a637 100755 --- a/templates/admin/index.html.twig +++ b/templates/admin/index.html.twig @@ -5,8 +5,8 @@ diff --git a/templates/admin/subject_department_select.html.twig b/templates/admin/subject_department_select.html.twig new file mode 100644 index 00000000..5ff53095 --- /dev/null +++ b/templates/admin/subject_department_select.html.twig @@ -0,0 +1,13 @@ + diff --git a/templates/base.html.twig b/templates/base.html.twig index 628cad5e..e57ebc96 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -8,6 +8,13 @@ {% endblock %} {% block javascripts %} + + {% block importmap %}{{ importmap('app') }}{% endblock %} {% endblock %}