במסגרת העברת אתר Moodle ישן עם גרסה 3.9 לאתר חדש עם גרסה 4.4, היה צורך להגדיר הפניות 301 עבור קישורים ישנים, כך שמשתמשים יוכלו להגיע לתכנים המתאימים באתר החדש. ההפניות הללו חשובות כדי למנוע שגיאות 404 ולעזור לשמור על חווית משתמש רציפה.
אחת הבעיות המרכזיות הייתה כיצד למצוא את כל המודולים – הפעילויות והמשאבים – והתכנים הישנים מהאתר הישן, ולהפנות אותם למודולים המתאימים באתר החדש. בייחוד, היה צורך למצוא את שמות המודולים והתכנים הקשורים אליהם, וליצור כתובות URL חדשות עבור כל אחד מהם באתר החדש.
לצורך כך, יצרתי מספר שאילתות שמחזירות את המידע הנחוץ על המודולים והקטעים באתר Moodle הישן. לאחר מכן, יצרתי סקריפט שמייצר קובץ הפניות 301 שאותו ניתן להכניס לקובץ מתאים בשרת האינטרנט.
שאילתות SQL
כדי להעביר את התכנים, היה צורך לאתר את כל המודולים והתכנים מהאתר הישן ולהתאים אותם לכתובות URL חדשות באתר החדש.
SELECT cs.id, section, NAME, c.id AS Course_ID,c.shortname
FROM mdl_course_sections cs
INNER JOIN mdl_course c ON c.id=cs.course
WHERE course IN (139,160)
ORDER BY c.shortname, section;
השאילתה מחזירה את פרטי פרקי הקורס בטבלת mdl_course_sections
עבור קורס עם מזהה 1843, תוך שילוב עם פרטי הקורס מטבלת mdl_course
, וממיינת את התוצאות לפי שם הקורס הקצר ומספר הפרק.
את השאילה הזאת צריך להריץ גם ב-DB הישן וגם ב-DB החדש, כשכמובן מעדכנים את ה-id-ים של הקורסים בכל הרצה של שאילתה. כך נראה פלט לדוגמה:
ID | Section | NAME | Course_ID | Shortname |
---|---|---|---|---|
1 | 0 | Introduction | 1843 | CS101 |
2 | 1 | Week 1: Basics | 1843 | CS101 |
3 | 2 | Week 2: Advanced Topics | 1843 | CS101 |
את הפלט של ה-DB הישן שומרים בקובץ old_courses_sections.csv ואת הפלט של ה-DB החדש שומרים בקובץ new_courses_sections.csv.
השאילתה השנייה מחזירה את שמות המודולים והקורסים הרלוונטיים, יחד עם שמות התכנים המיוחדים שנמצאים בכל מודול:
SELECT cm.id, m.name AS module_name, c.fullname AS course_name, cm.instance,
CASE
WHEN m.name = 'assign' THEN (SELECT a.name FROM mdl_assign a WHERE a.id = cm.instance)
WHEN m.name = 'quiz' THEN (SELECT q.name FROM mdl_quiz q WHERE q.id = cm.instance)
WHEN m.name = 'url' THEN (SELECT u.name FROM mdl_url u WHERE u.id = cm.instance)
WHEN m.name = 'book' THEN (SELECT b.name FROM mdl_book b WHERE b.id = cm.instance)
WHEN m.name = 'page' THEN (SELECT p.name FROM mdl_page p WHERE p.id = cm.instance)
WHEN m.name = 'forum' THEN (SELECT f.name FROM mdl_forum f WHERE f.id = cm.instance)
WHEN m.name = 'hvp' THEN (SELECT h.name FROM mdl_hvp h WHERE h.id = cm.instance)
WHEN m.name = 'questionnaire' THEN (SELECT q.name FROM mdl_questionnaire q WHERE q.id = cm.instance)
WHEN m.name = 'choice' THEN (SELECT c.name FROM mdl_choice c WHERE c.id = cm.instance)
WHEN m.name = 'folder' THEN (SELECT f.name FROM mdl_folder f WHERE f.id = cm.instance)
WHEN m.name = 'resource' THEN (SELECT r.name FROM mdl_resource r WHERE r.id = cm.instance)
ELSE NULL
END AS module_content
FROM mdl_course_modules cm
JOIN mdl_modules m ON cm.module = m.id
JOIN mdl_course c ON cm.course = c.id
WHERE m.name != 'activequiz' AND c.id IN (139,160)
ORDER BY course_name, module_name, module_content;
השאילתה מחזירה לי את כל המודולים ותוכני המודול באתר Moodle לפי הקורסים שציינתי. אני מסננת את המודולים שאינם רלוונטיים כמו ‘activequiz’, ובאמצעות שימוש ב-CASE, אני מאחזרת את שמות התכנים לפי סוג המודול (לדוגמה, בחנים, מטלות, ספרים ועוד). לפי סוג המודול (m.name
), מתבצעת שאילתה פנימית כדי לאחזר את שם התוכן של המודול מתוך הטבלה המתאימה, למשל:
- אם המודול הוא ‘assign’, השם יילקח מטבלת
mdl_assign
- אם המודול הוא ‘quiz’, השם יילקח מטבלת
mdl_quiz
- וכן הלאה עבור סוגי המודולים השונים
גם את השאילה הזאת מריצים ב-DB הישן וב-DB בחדש, ושומרים כל אחד מהקבצים כ-old_courses_modules.csv וכ-new_courses_modules.csv בהתאמה. כך נראה פלט לדוגמה:
Module ID | Module Name | Course Name | Instance ID | Module Content |
---|---|---|---|---|
1001 | assign | Introduction to Programming | 45 | Assignment 1 |
1002 | quiz | Introduction to Programming | 22 | Midterm Exam |
1003 | url | Data Structures | 13 | Reference Link |
1004 | book | Advanced Algorithms | 29 | Chapter 1: Basics |
יצירת הפניות 301
לאחר שאספתי את כל הנתונים הנחוצים בעזרת השאילתות, יצרתי סקריפט שמייצר קובץ הפניות 301. הסקריפט עובד כך שהוא מקשר בין כל כתובת URL ישנה לכתובת החדשה באתר Moodle החדש.
<?php
define('CLI_SCRIPT', true);
global $CFG;
require(__DIR__ . '/../../config.php');
require_once($CFG->libdir . '/clilib.php');
$old_module_csv_file = '/html/tmpCoursesBackup/old_courses_modules.csv';
$new_module_csv_file = '/html/tmpCoursesBackup/new_courses_modules.csv';
$old_section_csv_file = '/html/tmpCoursesBackup/old_courses_sections.csv';
$new_section_csv_file = '/html/tmpCoursesBackup/new_courses_sections.csv';
$module_output_file = '/html/tmpCoursesBackup/301_module_redirects.txt';
$section_output_file = '/html/tmpCoursesBackup/301_section_redirects.txt';
$html_file = '/html/tmpCoursesBackup/301_redirects.html';
$old_domain = 'https://example.ort.org.il';
$new_domain = 'https://example-new.ort.org.il';
function read_csv($file) {
if (!file_exists($file)) {
cli_error("Error: File does not exist: $file");
}
if (!is_readable($file)) {
cli_error("Error: File is not readable: $file");
}
$data = [];
$handle = @fopen($file, "r");
if ($handle === FALSE) {
cli_error("Error opening file: " . error_get_last()['message']);
}
while (($row = fgetcsv($handle, 1000, ",")) !== FALSE) {
$data[] = $row;
}
fclose($handle);
return $data;
}
function generate_module_redirects($old_data, $new_data) {
global $old_domain, $new_domain;
$redirects = [];
foreach ($old_data as $i => $old_row) {
if ($i === 0) continue; // Skip header row
$new_row = $new_data[$i];
// Extracting values
list($old_module_id, $old_module_name, $old_course_name) = $old_row;
list($new_module_id, $new_module_name, $new_course_name) = $new_row;
// Module URL
$redirects[] = "Redirect 301 $old_domain/mod/$old_module_name/view.php?id=$old_module_id $new_domain/mod/$new_module_name/view.php?id=$new_module_id";
}
return $redirects;
}
function generate_section_redirects($old_data, $new_data) {
global $old_domain, $new_domain;
$redirects = [];
foreach ($old_data as $i => $old_row) {
if ($i === 0) continue; // Skip header row
list($old_section_id, $old_section_order, $old_section_name, $old_course_id, $old_course_shortname) = $old_row;
list($new_section_id, $new_section_order, $new_section_name, $new_course_id, $new_course_shortname) = $new_data[$i];
$redirects[] = "Redirect 301 $old_domain/course/view.php?id=$old_course_id§ionid=$old_section_id $new_domain/course/view.php?id=$new_course_id§ionid=$new_section_id";
$redirects[] = "Redirect 301 $old_domain/course/view.php?id=$old_course_id§ion=$old_section_order $new_domain/course/view.php?id=$new_course_id§ion=$old_section_order";
}
return $redirects;
}
function read_redirects($file) {
if (!file_exists($file)) {
cli_error("Error: File does not exist: $file");
}
if (!is_readable($file)) {
cli_error("Error: File is not readable: $file");
}
$data = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if ($data === FALSE) {
cli_error("Error reading file: " . error_get_last()['message']);
}
return $data;
}
function generate_html($redirects) {
$html = "<!DOCTYPE html>\n<html>\n<head>\n<title>301 Redirects</title>\n</head>\n<body>\n";
$html .= "<h1>301 Redirects</h1>\n<ul>\n";
foreach ($redirects as $redirect) {
if (preg_match('/Redirect 301 (.+?) (.+)/', $redirect, $matches)) {
$old_url = htmlspecialchars($matches[1]);
$new_url = htmlspecialchars($matches[2]);
$html .= "<li><a href=\"$old_url\">$old_url</a> → <a href=\"$new_url\">$new_url</a></li>\n";
}
}
$html .= "</ul>\n</body>\n</html>";
return $html;
}
// Main execution
cli_writeln("Reading old course module data from: $old_module_csv_file");
$old_module_data = read_csv($old_module_csv_file);
cli_writeln("Reading new course module data from: $new_module_csv_file");
$new_module_data = read_csv($new_module_csv_file);
if (count($old_module_data) !== count($new_module_data)) {
cli_error("Error: The number of rows in old and new module CSV files do not match.");
}
cli_writeln("Generating 301 module redirects...");
$module_redirects = generate_module_redirects($old_module_data, $new_module_data);
// Write module redirects to file
cli_writeln("Writing 301 module redirects to: $module_output_file");
file_put_contents($module_output_file, implode("\n", $module_redirects));
cli_writeln("301 module redirects have been written to $module_output_file");
cli_writeln("Reading old course section data from: $old_section_csv_file");
$old_section_data = read_csv($old_section_csv_file);
cli_writeln("Reading new course section data from: $new_section_csv_file");
$new_section_data = read_csv($new_section_csv_file);
if (count($old_section_data) !== count($new_section_data)) {
cli_error("Error: The number of rows in old and new section CSV files do not match.");
}
cli_writeln("Generating 301 section redirects...");
$section_redirects = generate_section_redirects($old_section_data, $new_section_data);
// Write section redirects to file
cli_writeln("Writing 301 section redirects to: $section_output_file");
file_put_contents($section_output_file, implode("\n", $section_redirects));
cli_writeln("301 section redirects have been written to $section_output_file");
// Combine module and section redirects
$all_redirects = array_merge($module_redirects, $section_redirects);
// Generate HTML file with clickable links
cli_writeln("Generating HTML file with clickable links...");
$html_content = generate_html($all_redirects);
file_put_contents($html_file, $html_content);
cli_writeln("HTML file with clickable links has been written to $html_file");
cli_writeln("Process completed successfully.");
מבנה הסקריפט:
הסקריפט מחולק למספר שלבים עיקריים:
- קריאת קבצי CSV – הפונקציה
read_csv
אחראית על קריאת הנתונים מקבצי ה-CSV ומחזירה אותם כמערך דו-ממדי. אם אחד הקבצים לא קיים או לא קריא, היא תציג הודעת שגיאה ותפסיק את הריצה. - יצירת הפניות 301 עבור מודולים – הפונקציה
generate_module_redirects
יוצרת רשימת הפניות 301 עבור כל מודול ישן לעומת החדש. היא בודקת שכל שורה בקובץ הישן תואמת לשורה בקובץ החדש, ומשתמשת במזהי המודולים ליצירת כתובות ההפניה. - יצירת הפניות 301 עבור קטעים (Sections) – בדומה למודולים, הפונקציה
generate_section_redirects
יוצרת הפניות 301 עבור הקטעים, תוך התייחסות למספרי הקטעים (sections) הישנים והחדשים. - יצירת קובץ HTML – הפונקציה
generate_html
יוצרת קובץ HTML שמכיל רשימה של כל ההפניות בפורמט לחיץ. כל הפניה כוללת קישור ישן וקישור חדש. - כתיבת הפניות לקבצים – הסקריפט יוצר שני קבצי טקסט, אחד עבור המודולים (
301_module_redirects.txt
) ואחד עבור הקטעים (301_section_redirects.txt
), ומאחד את שניהם לקובץ HTML שמאפשר לראות את כל ההפניות בצורה מסודרת.
סיכום
בהעברת אתר Moodle מגרסה 3.9 לגרסה 4.4, היה צורך להקים מערכת הפניות 301 עבור קישורים ישנים, כדי להבטיח שימשיכו להיות גישה לתכנים המתאימים באתר החדש. השימוש בשאילתות SQL לשם הפקת נתונים על מודולים וקטעים מהאתר הישן והחדש, ושימוש בסקריפט ליצירת קובץ הפניות, אפשר להבטיח שמירה על חווית משתמש חלקה ומניעת שגיאות 404. ההנחות כוללות את יצירת הפניות מדויקות בין ה-URLs הישנים לחדשים, ולוודא שהקישורים מובילים למיקומים הנכונים. תהליך זה שומר על שלמות התוכן ומייעל את המעבר בין הגרסאות, מה שמסייע לשמור על שביעות רצון המשתמשים והמשכיות העבודה באתר.
