Real-World Use Cases

Full Stack Scenarios: From Jira Ticket to Senior Developer Solution

MDL-101 Course Completion Certificate Generator
Story Medium
Product Requirements

User Story: As a student, I want to download a PDF certificate automatically when I complete a course, so I can prove my attendance.

  • Certificate must be generated as PDF.
  • Must include Student Name, Course Name, and Date.
  • Only accessible if "Course Completion" criteria are met.
  • Admin should be able to upload a background image.
Technical Challenge

Generating PDFs in PHP can be resource-intensive. We need to hook into Moodle's completion tracking system and ensure the file is served securely (not publicly accessible).

👨‍💻 Senior Developer Solution

Architecture: Create a `local_certificate` plugin or a `mod_certificate` activity. Using `mod_certificate` is better for course placement.

Implementation Steps:

  1. Library: Use Moodle's built-in `TCPDF` wrapper (`$pdf = new pdf();`).
  2. Access Control: In `view.php`, check `$completion = $info->get_data($cm, true);` before generating.
  3. File Serving: Use `send_temp_file()` or `pluginfile.php` API to deliver the PDF.

// mod/certificate/view.php
require_once('../../config.php');
require_once($CFG->libdir . '/pdflib.php');
$id = required_param('id', PARAM_INT);
$cm = get_coursemodule_from_id('certificate', $id, 0, false, MUST_EXIST);
$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
require_login($course, true, $cm);

// Check completion
$completion = new completion_info($course);
if (!$completion->is_course_complete($USER->id)) {
    print_error('notcompleted', 'mod_certificate');
}

// Generate PDF
$doc = new pdf();
$doc->AddPage();
$doc->SetFont('helvetica', 'B', 20);
$doc->Write(0, 'Certificate of Completion');
$doc->Ln(20);
$doc->SetFont('helvetica', '', 14);
$doc->Write(0, 'This is to certify that ' . fullname($USER) . ' has completed the course.');
$doc->Output('certificate.pdf', 'D');
                    

Optimization: Cache the generated PDF in Moodle's `filedir` so it doesn't regenerate on every click. Use Adhoc Task to email it upon completion event `\core\event\course_completed`.

MDL-102 Unlock Grades Only After Payment
Story High
Product Requirements

User Story: As a university admin, I want students to see their final grades only if they have paid their tuition fees.

  • Integrate with external Payment Gateway (Stripe/PayPal).
  • If unpaid, the Gradebook view should show a "Payment Required" message.
  • Payment status is stored in a custom user profile field or external DB.
Technical Challenge

Moodle's Gradebook is core functionality. Modifying core code is forbidden. We need a non-intrusive way to intercept the grade display.

👨‍💻 Senior Developer Solution

Architecture: We cannot easily "hide" the gradebook without core hacks. The best approach is a Report Plugin or a Theme Override, but the most robust way is a Core Report Callback or restricting access to the course itself. However, for *just* grades:

Better Approach: Use a local plugin that subscribes to the event `\core\event\user_graded` (for notifications) or intercepting the UI via a Theme Renderer Override for the grade report.

Solution: Override the Grade Report Renderer


// theme/university/renderers.php
namespace theme_university\output;
use core_grades\output\general_action_bar;

class core_grades_renderer extends general_action_bar {
    public function render_grade_report($report) {
        global $USER, $DB;
        
        // Check custom profile field for payment status
        // Assuming fieldid 5 is 'paid_status'
        $has_paid = $DB->get_field('user_info_data', 'data', ['userid'=>$USER->id, 'fieldid'=>5]); 
        
        if ($has_paid !== '1') {
            return $this->output->notification('🔒 Grades are locked until tuition fees are settled.', 'warning');
        }
        
        return parent::render_grade_report($report);
    }
}
                    

Note: This is tricky. A safer backend approach is to use Availability API. Create a custom availability condition `availability_payment`. Teachers add this restriction to the "Final Exam" or the Course Section containing grades.

MDL-103 Mandatory Course Evaluation to View Grades
Story Medium
Product Requirements

User Story: Students must complete the "End of Course Survey" before they can view their final grade or download their certificate.

  • Survey is a Moodle "Feedback" activity.
  • Grades/Certificate activity must be hidden/unavailable until Feedback is submitted.
Technical Challenge

Standard Moodle "Restrict Access" allows unlocking based on activity completion. This is a configuration task, but if we need a custom logic (e.g., "Survey from external system"), we need code.

👨‍💻 Senior Developer Solution

Architecture: Use Moodle's Availability API (`availability_custom`).

Implementation:

  1. If using standard Feedback module: No code needed! Set "Restrict Access" on the Certificate: "Must match: Activity Completion 'Course Feedback' is marked complete".
  2. If using External Survey Tool (e.g., SurveyMonkey): Build a custom availability plugin `availability_externalsurvey`.

// availability/condition/externalsurvey/classes/condition.php
namespace availability_externalsurvey;

class condition extends \core_availability\condition {
    public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
        // Check Moodle Cache first
        $cache = \cache::make('availability_externalsurvey', 'status');
        $status = $cache->get($userid);

        if ($status === false) {
            // Call External API
            $status = \local_integration\api::check_survey_status($userid, $this->courseid);
            $cache->set($userid, $status);
        }

        return $status === 'completed';
    }
    
    public function get_description($full, $not, \core_availability\info $info) {
        return "You must complete the external survey to access this.";
    }
}
                    

Optimization: Cache the external API response in MUC (Moodle Universal Cache) to avoid calling the SurveyMonkey API on every page load.

MDL-104 Video Assignment with Auto-Transcoding
Story High
Product Requirements

User Story: Students upload raw video files (MOV, AVI). The system must convert them to MP4 (H.264) for web playback automatically.

  • Support large file uploads (up to 2GB).
  • Background processing (don't make user wait).
  • Notify user when processing is done.
Technical Challenge

PHP cannot handle video conversion efficiently. We need `ffmpeg` and asynchronous processing.

👨‍💻 Senior Developer Solution

Architecture: Use Adhoc Tasks (Moodle's Job Queue). Never transcode during the HTTP request.

Implementation Plan:

  1. Upload: User uploads file to `mod_assign`.
  2. Event Observer: Listen for `\mod_assign\event\assessable_submitted`.
  3. Queue Task: Create `\local_video\task\transcode_video` and push to queue.
  4. Worker: The cron job picks up the task, runs `ffmpeg` via `exec()` or sends to AWS MediaConvert.

// local/video/classes/task/transcode_video.php
namespace local_video\task;

class transcode_video extends \core\task\adhoc_task {
    public function execute() {
        global $DB;        
        
        $data = $this->get_custom_data();
        $fileid = $data->fileid;        
        
        // Get file from Moodle File API
        $fs = get_file_storage();
        $file = $fs->get_file_by_id($fileid);
        
        if (!$file) {
            return; 
        }

        $input = $file->copy_content_to_temp();
        $output = $input . '_converted.mp4';        
        
        // Run FFMPEG (ensure it's installed on server)
        $cmd = "ffmpeg -i " . escapeshellarg($input) . " -vcodec h264 -acodec aac " . escapeshellarg($output);
        exec($cmd, $out, $return);
        
        if ($return === 0) {
            // Success: Replace original or save as new file
            $file->delete(); // Remove original large file
            
            $record = [
                'contextid' => $file->get_contextid(),
                'component' => $file->get_component(),
                'filearea'  => $file->get_filearea(),
                'itemid'    => $file->get_itemid(),
                'filepath'  => $file->get_filepath(),
                'filename'  => 'video.mp4'
            ];
            $fs->create_file_from_pathname($record, $output);            
            
            // Notify User
            \core\notification::add('Video processed successfully!', 'success');
        }
    }
}
                    

Optimization: Offload to AWS Lambda or S3 Event Triggers to keep the Moodle server CPU load low.

MDL-105 AI-Powered Quiz Question Generator
Story Medium
Product Requirements

User Story: Teachers want to click a button "Generate Questions" inside a Quiz, provide a topic, and get 5 multiple-choice questions automatically.

  • Integrate with OpenAI API (GPT-4).
  • Insert questions directly into the Question Bank.
Technical Challenge

Integrating external API securely and mapping the JSON response to Moodle's complex Question Bank database structure (`mdl_question`, `mdl_question_answers`).

👨‍💻 Senior Developer Solution

Architecture: Create a local plugin that adds a UI to the Question Bank or a new Question Import Format.

Implementation:

  1. UI: Add a button to the Question Bank view using `core_question_bank_plugins`.
  2. API Call: Send prompt to OpenAI. Request JSON format.
  3. Parsing: Use `question_import` classes to save data.

// local/aiquiz/classes/generator.php
namespace local_aiquiz;

class generator {
    public function generate_questions($topic, $count = 5) {
        global $DB;   
        
        $prompt = "Create $count Moodle XML format multiple choice questions about '$topic'. Wrap in  tags.";
        
        // Call OpenAI (Pseudo-code)
        $client = new \local_aiquiz\openai_client(get_config('local_aiquiz', 'apikey'));
        $xml_content = $client->completion($prompt);
        
        // Parse XML
        $qformat = new \qformat_xml();
        $questions = $qformat->readquestions($xml_content);
        
        // Save to Question Bank
        foreach ($questions as $q) {
            $q->category = 1; // Default category ID
            question_save_qtype_options($q);
        }
        
        return count($questions);
    }
}
                    

Nice to have: Add a "Review" step where the teacher can edit the AI suggestions before saving to DB.

MDL-106 Student Progress Tracker Block
Story Medium
Product Requirements

User Story: Students need a visual dashboard block showing their progress across ALL enrolled courses with a circular progress bar.

  • Must be a "Block" plugin (`block_progress`).
  • Show % complete for each active course.
  • Use Chart.js or CSS for visuals.
Technical Challenge

Calculating progress for all courses on every page load is a performance killer. `completion_info` is heavy.

👨‍💻 Senior Developer Solution

Architecture: Block Plugin + Caching.

Implementation:

  1. Data Retrieval: `enrol_get_my_courses()`.
  2. Calculation: Loop courses, get `completion_info`.
  3. Caching: Store the result in `MUC` (Session or Application cache) for 1 hour or until `\core\event\course_completion_updated` is fired.

// blocks/myprogress/block_myprogress.php
class block_myprogress extends block_base {
    public function get_content() {
        global $USER;
        
        if ($this->content !== null) {
            return $this->content;
        }
        
        $this->content = new stdClass;
        
        // Try to get from Cache first
        $cache = \cache::make('block_myprogress', 'userprogress');
        $data = $cache->get($USER->id);
        
        if (!$data) {
            $courses = enrol_get_my_courses();
            $data = [];
            foreach ($courses as $c) {
                $info = new \completion_info($c);
                if ($info->is_enabled()) {
                    $data[] = [
                        'name' => $c->fullname,
                        'percent' => $info->get_progress_all()
                    ];
                }
            }
            // Cache for 1 hour
            $cache->set($USER->id, $data);
        }
        
        $this->content->text = $this->render_chart($data);
        return $this->content;
    }
    
    private function render_chart($data) {
        // Return HTML/JS for Chart.js
        return '';
    }
}
                    
MDL-107 Sync Users with HR System (SAP/Workday)
Story High
Product Requirements

User Story: When a new employee is added to SAP, they should automatically have an account in Moodle and be enrolled in "Onboarding 101".

  • One-way sync: SAP -> Moodle.
  • Run nightly.
  • Handle updates (name changes) and deletions (suspend user).
Technical Challenge

Handling thousands of users efficiently. Avoiding "zombie" accounts.

👨‍💻 Senior Developer Solution

Architecture: Use an Auth Plugin (`auth_sap`) or Enrol Plugin (`enrol_sap`). Best practice: Scheduled Task consuming an API or CSV.

Implementation:

  1. Scheduled Task: `\auth_sap\task\sync_users`.
  2. Batch Processing: Don't process one by one. Fetch delta (changes only) if possible.
  3. User Creation: Use `user_create_user()` core function.
  4. Enrollment: Use `enrol_manual->enrol_user()`.

// auth/sap/classes/task/sync_users.php
namespace auth_sap\task;

class sync_users extends \core\task\scheduled_task {
    public function execute() {
        global $DB;
        
        // Fetch from SAP API
        $sap_api = new \auth_sap\api_client();
        $users = $sap_api->get_new_employees();
        
        foreach ($users as $u) {
            // Check if user exists
            if (!$existing = $DB->get_record('user', ['username' => $u['id']])) {
                $user = new \stdClass();
                $user->username = $u['id'];
                $user->auth = 'sap';
                $user->firstname = $u['first'];
                $user->lastname = $u['last'];
                $user->email = $u['email'];
                $user->confirmed = 1;
                
                try {
                    $id = user_create_user($user);
                    
                    // Auto enrol in Onboarding Course (ID: 1)
                    $enrol = enrol_get_plugin('manual');
                    $instance = $DB->get_record('enrol', ['courseid'=>1, 'enrol'=>'manual']);
                    if ($instance) {
                        $enrol->enrol_user($instance, $id, 5); // 5 = Student role
                    }
                    
                    mtrace("Created user $u->id");
                } catch (\Exception $e) {
                    mtrace("Error creating user: " . $e->getMessage());
                }
            }
        }
    }
}
                    

Optimization: Use CLI script for large syncs to avoid PHP timeouts. Log every action for audit trails.

MDL-108 Dark Mode Toggle for University Branding
Story Medium
Product Requirements

User Story: Users want to toggle between Light and Dark mode. The preference should be remembered across sessions.

  • Button in the navbar.
  • Persist preference in User Preferences (DB).
  • Must work with Boost child theme.
Technical Challenge

CSS variables are easy, but persistence requires AJAX to save state to the backend so it applies when they log in on a different device.

👨‍💻 Senior Developer Solution

Architecture: Child Theme of Boost + AMD Module (JS) + User Preference API.

Implementation:

  1. SCSS: Define `:root` variables for colors. `[data-theme="dark"] { --bg: #000; }`.
  2. JS (AMD): Handle click, toggle attribute on `body`, send AJAX to save.
  3. PHP: In `theme_config.php` or `lib.php`, inject the saved class/attribute on page load.

// theme/university/amd/src/darkmode.js
define(['jquery', 'core/ajax', 'core/str'], function($, Ajax, Str) {
    return {
        init: function() {
            $('#theme-toggle').on('click', function(e) {
                e.preventDefault();
                
                var body = $('body');
                var currentMode = body.attr('data-theme');
                var newMode = currentMode === 'dark' ? 'light' : 'dark';
                
                // Apply immediately for UX
                body.attr('data-theme', newMode);
                
                // Persist to DB
                Ajax.call([{
                    methodname: 'core_user_update_user_preferences',
                    args: { 
                        preferences: [{ 
                            type: 'theme_university_mode', 
                            value: newMode 
                        }] 
                    }
                }]);
            });
        }
    };
});
                    
MDL-109 Optimize Slow Course Page (1000+ Activities)
Story High
Product Requirements

User Story: The "Engineering 101" course takes 15 seconds to load. It has 50 sections and 1000+ resources. It needs to load in under 3 seconds.

Technical Challenge

Moodle loads all activities in the DOM by default. Rendering 1000+ activity icons and completion checkboxes kills the server and browser.

👨‍💻 Senior Developer Solution

Architecture: "One Section Per Page" setting is the easy fix. If "All Sections" is required, we need Lazy Loading.

Implementation:

  1. Configuration: Change Course Format to "One Section per page".
  2. Code Fix (Course Format Plugin): If using a custom format (e.g., `format_tiles`), implement AJAX loading for sections. Only render the visible section.
  3. Database: Check for N+1 queries. Ensure `get_fast_modinfo()` is being used and cached properly.

// format/tiles/format.php
// Instead of printing all sections:
if ($course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
    // Only print current section
    print_section($course, $thissection);
} else {
    // Use AJAX to fetch content when user clicks a tile
    echo "
Load Content
"; // JS will handle the click: // fetch('ajax/get_section_content.php?id=' + id).then(...) }

Developer Note: Always check the "Performance Info" in footer. If DB queries are > 100, you have a plugin problem.

MDL-110 GDPR Data Export & Anonymization Tool
Story High
Product Requirements

User Story: To comply with GDPR, we need a button to "Anonymize Old Users" (inactive > 2 years). Their grades must remain for statistics, but names/emails must be scrubbed.

Technical Challenge

Moodle has a "Data Privacy" tool, but it deletes users. We need to keep the record ID but scramble the PII (Personally Identifiable Information).

👨‍💻 Senior Developer Solution

Architecture: Custom Admin Tool (`tool_anonymize`).

Implementation:

  1. Selection: Query users with `lastaccess < (time() - 2*YEAR)`.
  2. Anonymization: Update `mdl_user` table. Set `firstname = 'Anonymized'`, `lastname = 'User'`, `email = md5(id)@deleted.com`.
  3. Cleanup: Delete profile pictures, custom fields.
  4. Logging: Log the action in `mdl_logstore_standard_log`.

// admin/tool/anonymize/classes/task/scrub_users.php
namespace tool_anonymize\task;

class scrub_users extends \core\task\scheduled_task {
    public function execute() {
        global $DB;
        
        // 2 years in seconds
        $cutoff = time() - (2 * 365 * 24 * 60 * 60);
        
        $sql = "SELECT id FROM {user} WHERE lastaccess < :time AND deleted = 0 AND id > 2"; // Skip guest/admin
        $users = $DB->get_records_sql($sql, ['time' => $cutoff]);

        foreach ($users as $u) {
            $update = new \stdClass();
            $update->id = $u->id;
            $update->firstname = 'Anonymized';
            $update->lastname = 'User ' . $u->id;
            $update->email = $u->id . '@anonymized.local';
            $update->picture = 0;
            $update->description = '';
            
            // Use core function to ensure cache clearing and events
            user_update_user($update);
            
            // Kill sessions
            \core\session\manager::kill_user_sessions($u->id);
            
            mtrace("Anonymized user $u->id");
        }
    }
}
                    

Warning: This is irreversible. Always backup DB first.