diff --git a/assets/js/Components/FilesModal.js b/assets/js/Components/FilesModal.js index f34e5888..79c2f6cc 100644 --- a/assets/js/Components/FilesModal.js +++ b/assets/js/Components/FilesModal.js @@ -270,6 +270,12 @@ class FilesModal extends React.Component { return } + const userConfirmed = window.confirm(this.props.t('msg.confirmation')); + + if (!userConfirmed) { + return; + } + let api = new Api(this.props.settings) api.postFile(activeFile, this.state.replaceFileObj) .then((responsStr) => responsStr.json()) diff --git a/src/Lms/Canvas/CanvasApi.php b/src/Lms/Canvas/CanvasApi.php index 828c83c6..c901a7ce 100644 --- a/src/Lms/Canvas/CanvasApi.php +++ b/src/Lms/Canvas/CanvasApi.php @@ -106,7 +106,7 @@ public function apiPost($url, $options, $sendAuthorized = true) } // Posts a file to Canvas - public function apiFilePost(string $url, array $options, string $filepath, string $newFileName, $moduleInstances = null, $courseId = null, $replacePages = null, $replaceAssignments = null) : LmsResponse + public function apiFilePost(string $url, array $options, string $filepath, string $newFileName, $changeReferences = null) : LmsResponse { $fileResponse = $this->apiGet($url); $file = $fileResponse->getContent(); @@ -122,8 +122,6 @@ public function apiFilePost(string $url, array $options, string $filepath, strin $endpointResponse = $this->apiPost($options['postUrl'], ['query' => $endpointOptions], true); $endpointContent = $endpointResponse->getContent(); - $this->apiDelete($url); - // TODO: handle failed call $formFields = $endpointContent['upload_params']; @@ -137,79 +135,13 @@ public function apiFilePost(string $url, array $options, string $filepath, strin $fileResponseContent = $fileResponse->getContent(); - if($moduleInstances) { - foreach ($moduleInstances as $moduleInstance) { - $response = $this->apiPost("courses/{$courseId}/modules/{$moduleInstance}/items", [ - 'json' => [ - 'module_item' => [ - 'title' => $newFileName, - 'type' => 'File', - 'content_id' => $fileResponseContent['id'], - ], - ], - ]); - - } - } - - if($replacePages) { - foreach ($replacePages as $page) { - $dom = new \DOMDocument(); - $dom->loadHTML($page['body']); - $anchors = $dom->getElementsByTagName('a'); - foreach ($anchors as $anchor) { - preg_match('/files\/(\d+)/', $anchor->getAttribute('href'), $matches); - if (isset($matches[1]) && $matches[1] == $file['id']) { - $anchor->setAttribute('href', $fileResponseContent['url']); - $anchor->setAttribute('title', $newFileName); - $anchor->nodeValue = $newFileName; - } - $page['body'] = $dom->saveHTML(); - $response = $this->apiPut("courses/{$courseId}/pages/{$page['page_id']}", [ - 'json' => [ - 'wiki_page' => [ - 'title' => $page['title'], - 'body' => $page['body'], - 'editing_roles' => $page['editing_roles'], - 'published' => $page['published'], - 'front_page' => $page['front_page'], - 'hide_from_students' => $page['hide_from_students'], - 'notify_of_update' => $page['notify_of_update'], - 'attachments' => [ - [ - 'url' => $fileResponseContent['url'], - 'filename' => $newFileName, - ], - ], - ], - ], - ]); - } - } - } - - if ($replaceAssignments) { - foreach ($replaceAssignments as $assignment) { - $dom = new \DOMDocument(); - $dom->loadHTML($assignment['description']); - $anchors = $dom->getElementsByTagName('a'); - foreach ($anchors as $anchor) { - preg_match('/files\/(\d+)/', $anchor->getAttribute('href'), $matches); - if (isset($matches[1]) && $matches[1] == $file['id']) { - $anchor->setAttribute('href', $fileResponseContent['url']); - $anchor->setAttribute('title', $newFileName); - $anchor->nodeValue = $newFileName; - } - $assignment['description'] = $dom->saveHTML(); - $response = $this->apiPut("courses/{$courseId}/assignments/{$assignment['id']}", [ - 'json' => [ - 'assignment' => [ - 'description' => $assignment['description'], - ], - ], - ]); - } - } + if($changeReferences) { + $this->changeLinksInSyllabus($changeReferences['course_id'], $file['id'], $fileResponseContent['url'], $newFileName); + $this->changeModuleItemInstances($changeReferences['course_id'], $changeReferences['modules'], $newFileName, $fileResponseContent['id']); + $this->changeLinksInPages($changeReferences['course_id'], $file['id'], $changeReferences['pages'], $fileResponseContent['url'], $newFileName); + $this->changeLinksInAssignments($changeReferences['course_id'], $file['id'], $changeReferences['assignments'], $fileResponseContent['url'], $newFileName); + $this->changeLinksInQuizzes($changeReferences['course_id'], $file['id'], $changeReferences['quizzes'], $fileResponseContent['url'], $newFileName); + $this->changeLinksInQuizQuestions($changeReferences['course_id'], $file['id'], $changeReferences['quizQuestions'], $fileResponseContent['url'], $newFileName); } return $fileResponse; @@ -266,6 +198,18 @@ public function apiDelete($url) { } + public function getCourse($courseId, $includeSyllabus = false) + { + if ($includeSyllabus) { + $url = "courses/{$courseId}?include[]=syllabus_body"; + } + else { + $url = "courses/{$courseId}"; + } + $response = $this->apiGet($url); + return $response->getContent(); + } + public function listModules($courseId) { $url = "courses/{$courseId}/modules"; @@ -317,4 +261,175 @@ public function listAssignments($courseId) return $response->getContent(); } + public function listQuizzes($courseId) + { + $url = "courses/{$courseId}/quizzes"; + $response = $this->apiGet($url); + return $response->getContent(); + } + + public function listQuizQuestions($courseId, $quizId) + { + $url = "courses/{$courseId}/quizzes/{$quizId}/questions"; + $response = $this->apiGet($url); + return $response->getContent(); + } + + public function updateCourse($courseId, $options) + { + $url = "courses/{$courseId}"; + $response = $this->apiPut($url, $options); + return $response->getContent(); + } + + public function uploadModuleItem($courseId, $moduleId, $options) + { + $url = "courses/{$courseId}/modules/{$moduleId}/items"; + $response = $this->apiPost($url, $options); + return $response->getContent(); + } + + public function updatePage($courseId, $pageId, $options) + { + $url = "courses/{$courseId}/pages/{$pageId}"; + $response = $this->apiPut($url, $options); + return $response->getContent(); + } + + public function updateAssignment($courseId, $assignmentId, $options) + { + $url = "courses/{$courseId}/assignments/{$assignmentId}"; + $response = $this->apiPut($url, $options); + return $response->getContent(); + } + + public function updateQuiz($courseId, $quizId, $options) + { + $url = "courses/{$courseId}/quizzes/{$quizId}"; + $response = $this->apiPut($url, $options); + return $response->getContent(); + } + + public function updateQuizQuestion($courseId, $quizId, $questionId, $options) + { + $url = "courses/{$courseId}/quizzes/{$quizId}/questions/{$questionId}"; + $response = $this->apiPut($url, $options); + return $response->getContent(); + } + + /********************** + * PROTECTED FUNCTIONS + **********************/ + + + protected function changeLinksInSyllabus($courseId, $oldFileId, $newUrl, $newFileName) + { + $course = $this->getCourse($courseId, true); + $dom = new \DOMDocument(); + + $dom->loadHTML($course['syllabus_body']); + $anchors = $dom->getElementsByTagName('a'); + $newSyllabus = $this->changeAnchorTags($dom, $oldFileId, $newUrl, $newFileName); + if ($newSyllabus != $course['syllabus_body']) { + $this->updateCourse($courseId, ['json' => ['course' => ['syllabus_body' => $newSyllabus]]]); + } + } + + protected function changeModuleItemInstances($courseId, $moduleInstances, $newFileName, $newFileId) { + foreach ($moduleInstances as $moduleInstance) { + $this->uploadModuleItem($courseId, $moduleInstance, [ + 'json' => [ + 'module_item' => [ + 'title' => $newFileName, + 'type' => 'File', + 'content_id' => $newFileId, + ], + ], + ]); + } + } + + protected function changeLinksInPages($courseId, $oldFileId, $pages, $newUrl, $newFileName) { + foreach ($pages as $page) { + $dom = new \DOMDocument(); + $dom->loadHTML($page['body']); + $newPage = $this->changeAnchorTags($dom, $oldFileId, $newUrl, $newFileName); + if ($newPage != $page['body']) { + $this->updatePage($courseId, $page['page_id'], [ + 'json' => [ + 'wiki_page' => [ + 'body' => $newPage, + ], + ], + ]); + } + } + } + + protected function changeLinksInAssignments($courseId, $oldFileId, $assignments, $newUrl, $newFileName) { + foreach ($assignments as $assignment) { + $dom = new \DOMDocument(); + $dom->loadHTML($assignment['description']); + $newAssignment = $this->changeAnchorTags($dom, $oldFileId, $newUrl, $newFileName); + if ($newAssignment != $assignment['description']) { + $this->updateAssignment($courseId, $assignment['id'], [ + 'json' => [ + 'assignment' => [ + 'description' => $newAssignment, + ], + ], + ]); + } + } + } + + protected function changeLinksInQuizzes($courseId, $oldFileId, $quizzes, $newUrl, $newFileName) { + foreach ($quizzes as $quiz) { + $dom = new \DOMDocument(); + $dom->loadHTML($quiz['description']); + $newQuiz = $this->changeAnchorTags($dom, $oldFileId, $newUrl, $newFileName); + if ($newQuiz != $quiz['description']) { + $this->updateQuiz($courseId, $quiz['id'], [ + 'json' => [ + 'quiz' => [ + 'description' => $newQuiz, + 'notify_of_update' => $quiz['notify_of_update'], + ], + ], + ]); + } + } + } + + public function changeLinksInQuizQuestions($courseId, $oldFileId, $replaceQuizQuestions, $newUrl, $newFileName) { + foreach ($replaceQuizQuestions as $quizQuestion) { + $dom = new \DOMDocument(); + $dom->loadHTML($quizQuestion['question_text']); + $newQuestion = $this->changeAnchorTags($dom, $oldFileId, $newUrl, $newFileName); + if ($newQuestion != $quizQuestion['question_text']) { + $this->updateQuizQuestion($courseId, $quizQuestion['quiz_id'], $quizQuestion['id'], [ + 'json' => [ + 'question' => [ + 'question_text' => $newQuestion, + ], + ], + ]); + } + } + } + + protected function changeAnchorTags($dom, $fileId, $newUrl, $newFileName) { + $anchors = $dom->getElementsByTagName('a'); + foreach ($anchors as $anchor) { + preg_match('/files\/(\d+)/', $anchor->getAttribute('href'), $matches); + if (isset($matches[1]) && $matches[1] == $fileId) { + $anchor->setAttribute('href', $newUrl); + $anchor->setAttribute('title', $newFileName); + $anchor->nodeValue = $newFileName; + } + } + + return $dom->saveHTML(); + } + } diff --git a/src/Lms/Canvas/CanvasLms.php b/src/Lms/Canvas/CanvasLms.php index ee5a3f0a..52c2a297 100644 --- a/src/Lms/Canvas/CanvasLms.php +++ b/src/Lms/Canvas/CanvasLms.php @@ -379,9 +379,8 @@ public function postFileItem(FileItem $file, string $newFileName) $modules = $canvasApi->listModules($file->getCourse()->getLmsCourseId()); $pages = $canvasApi->listPages($file->getCourse()->getLmsCourseId()); $assignments = $canvasApi->listAssignments($file->getCourse()->getLmsCourseId()); - $refillModules = []; - $changePages = []; - $changeAssignments = []; + $quizzes = $canvasApi->listQuizzes($file->getCourse()->getLmsCourseId()); + $changeReferences = ['course_id' => $file->getCourse()->getLmsCourseId(),'modules' => [], 'pages' => [], 'assignments' => [], 'quizzes' => [], 'quizQuestions' => []]; // Delete any existing module items with the same file name foreach ($modules as $module) { @@ -391,28 +390,39 @@ public function postFileItem(FileItem $file, string $newFileName) foreach ($instances as $instance) { $canvasApi->deleteModuleItem($file->getCourse()->getLmsCourseId(), $module['id'], $instance['id']); } - $refillModules[] = $module['id']; + $changeReferences['modules'][] = $module['id']; } } // Find all wiki pages that contain this file in their HTML foreach ($pages as $page) { - $dom = new \DOMDocument(); - $dom->loadHTML($page['body']); - $anchors = $dom->getElementsByTagName('a'); - foreach ($anchors as $anchor) { - preg_match('/files\/(\d+)/', $anchor->getAttribute('href'), $matches); - if(isset($matches[1]) && $matches[1] == $file->getLmsFileId()) { - $changePages[] = $page; - break; - } + if ($this->fileIsInLink($file->getLmsFileId(), $page['body'])) { + $changeReferences['pages'][] = $page; } } // Find all assignments that contain this file in their description HTML foreach ($assignments as $assignment) { if ($this->fileIsInLink($file->getLmsFileId(), $assignment['description'])) { - $changeAssignments[] = $assignment; + $changeReferences['assignments'][] = $assignment; + } + } + + // Find all quizzes that contain this file in their description HTML + foreach ($quizzes as $quiz) { + if ($this->fileIsInLink($file->getLmsFileId(), $quiz['description'])) { + $changeReferences['quizzes'][] = $quiz; + } + + // Check quiz questions for references to the file + $questions[] = $canvasApi->listQuizQuestions($file->getCourse()->getLmsCourseId(), $quiz['id']); + + $questions = $questions[0]; + + foreach ($questions as $question) { + if ($this->fileIsInLink($file->getLmsFileId(), $question['question_text'])) { + $changeReferences['quizQuestions'][] = $question; + } } } @@ -422,7 +432,7 @@ public function postFileItem(FileItem $file, string $newFileName) 'postUrl' => "courses/{$file->getCourse()->getLmsCourseId()}/files" ]; - $fileResponse = $canvasApi->apiFilePost($url, $options, $filepath, $newFileName, $refillModules, $file->getCourse()->getLmsCourseId(), $changePages, $changeAssignments); + $fileResponse = $canvasApi->apiFilePost($url, $options, $filepath, $newFileName, $changeReferences); $fileObj = $fileResponse->getContent(); if (isset($fileObj['id'])) { diff --git a/translations/en.json b/translations/en.json index 498fbecc..a09bc7ec 100644 --- a/translations/en.json +++ b/translations/en.json @@ -127,6 +127,7 @@ "menu.full_rescan": "Force Full Rescan", "msg.no_permissions": "You do not have permission to access the specified course.", + "msg.confirmation": "Are you sure you want to replace this file? This action cannot be undone.", "msg.course_scanning": "Course scanning...", "msg.no_new_content": "Course scan complete. No new content found.", "msg.new_content": "Course scan complete. New content has been added.", @@ -175,7 +176,7 @@ "label.next_file": "Next File", "label.upload": "Upload", "label.replace": "Replace File", - "label.replace.desc": "Replace the current version of this file here.", + "label.replace.desc": "Replace the current version of this file here. This modal will replace all instances of the file in assignments, pages, modules, syllabus, quizzes and quiz questions. This action cannot be undone.", "label.loading": "Loading", "label.loading_reports": "Loading report history", "label.loading_users": "Loading users",