diff --git a/newupdatedFile/Dockerfile b/newupdatedFile/Dockerfile deleted file mode 100644 index 769692cacaafff370cf1d82b518c74135b7acd3e..0000000000000000000000000000000000000000 --- a/newupdatedFile/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -FROM debian:bookworm - -# Install Apache, PHP, and other required extensions -RUN apt-get update && apt-get install -y \ - apache2 \ - php \ - php-cli \ - php-gd \ - php-mysql \ - php-intl \ - php-curl \ - php-xml \ - php-xmlrpc \ - php-soap \ - php-zip \ - php-mbstring \ - unzip \ - git \ - && rm -rf /var/lib/apt/lists/* - -# Add custom PHP settings for Moodle -# We copy it to both the apache2 and cli config directories -COPY moodle.ini /etc/php/8.2/apache2/conf.d/99-moodle.ini -COPY moodle.ini /etc/php/8.2/cli/conf.d/99-moodle.ini - -# Download and unpack Moodle -ARG MOODLE_VERSION=403 -RUN rm -rf /var/www/html -RUN git clone --depth 1 --branch MOODLE_${MOODLE_VERSION}_STABLE https://github.com/moodle/moodle.git /var/www/html/ - -# Create the moodledata directory -RUN mkdir /var/www/moodledata && chown -R www-data:www-data /var/www/moodledata - -# Set correct permissions -WORKDIR /var/www/html -RUN chown -R www-data:www-data /var/www/html - -# Copy a startup script -COPY --chown=root:root docker-entrypoint.sh / -ENTRYPOINT ["/docker-entrypoint.sh"] -CMD ["apache2ctl", "-D", "FOREGROUND"] diff --git a/newupdatedFile/docker-compose.yml b/newupdatedFile/docker-compose.yml deleted file mode 100644 index 828f0b310bbb84f6449313ccaace3e4fcb19119c..0000000000000000000000000000000000000000 --- a/newupdatedFile/docker-compose.yml +++ /dev/null @@ -1,58 +0,0 @@ -version: '3.8' - -services: - db: - image: mariadb:10.6 - container_name: moodle-db-official - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci - volumes: - - db_data:/var/lib/mysql - environment: - - MARIADB_ROOT_PASSWORD=verysecretpassword - - MARIADB_DATABASE=moodle - - MARIADB_USER=moodleuser - - MARIADB_PASSWORD=moodlepassword - restart: unless-stopped - - web: - build: . # This tells it to use the Dockerfile in this directory - container_name: moodle-web-official - volumes: - # Mount our local plugin for easy installation - - ./dta.zip:/tmp/dta.zip - # Mount the moodledata directory as a named volume - - moodledata:/var/www/moodledata - ports: - - "8080:80" - depends_on: - - db - restart: unless-stopped - - backend: - container_name: backend - image: hftstuttgart/cota-backend:latest - user: "${EUSER}:${EGID}" - ports: # Add this section - - "8081:8080" - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - '/tmp/dta-tests:/tmp/dta-tests' - restart: unless-stopped - # This service can be reached by the 'web' container at http://backend:[port] - - cache: - container_name: cachedta - image: nginx:alpine - ports: - - '82:8080' - volumes: - - './conf:/etc/nginx/conf.d' # Nginx configs go here - depends_on: - - backend - restart: unless-stopped - -volumes: - db_data: - moodledata: diff --git a/newupdatedFile/docker-entrypoint.sh b/newupdatedFile/docker-entrypoint.sh deleted file mode 100644 index 49e734c7b77a07fadf4dc11fb19c7e095ad9f34d..0000000000000000000000000000000000000000 --- a/newupdatedFile/docker-entrypoint.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -set -e - -# Apache gets grumpy about PID files pre-existing -rm -f /var/run/apache2/apache2.pid - -exec "$@" diff --git a/newupdatedFile/dta.zip b/newupdatedFile/dta.zip deleted file mode 100644 index 8de309aa041c025b22d6260e77ee494e9282ea4d..0000000000000000000000000000000000000000 Binary files a/newupdatedFile/dta.zip and /dev/null differ diff --git a/newupdatedFile/full_output.log b/newupdatedFile/full_output.log deleted file mode 100644 index 5cf34a51f1b64f9e2046357be328ef23a82875df..0000000000000000000000000000000000000000 --- a/newupdatedFile/full_output.log +++ /dev/null @@ -1,119 +0,0 @@ -DEBUG: Data object being sent to add_moduleinfo(): -stdClass Object -( - [course] => 4 - [modulename] => assign - [name] => Fully Automated DTA Assignment - [intro] => This assignment was created by the final automation script. - [introformat] => 1 - [section] => 1 - [visible] => 1 - [assignsubmission_dta_enabled] => 1 - [assignsubmission_file_enabled] => 1 - [dta] => 1 - [tests_draft_dta] => 121728303 - [assignsubmission_file_maxsizebytes] => 0 - [cmidnumber] => - [assignsubmission_file_maxfiles] => 20 - [assignsubmission_onlinetext_enabled] => 0 - [submissiondrafts] => 0 - [requiresubmissionstatement] => 0 - [requireallteammemberssubmit] => 0 - [teamsubmission] => 0 - [sendnotifications] => 1 - [sendlatenotifications] => 0 - [sendstudentnotifications] => 1 - [grade] => 100 - [blindmarking] => 0 - [markingworkflow] => 0 - [markingallocation] => 0 - [attemptreopenmethod] => none - [allowsubmissionsfromdate] => 1753175949 - [duedate] => 1753780749 - [cutoffdate] => 0 - [gradingduedate] => 0 - [module] => 1 - [timemodified] => 1753175949 - [alwaysshowdescription] => 0 - [completionsubmit] => 0 -) - -DEBUG: Returned object from add_moduleinfo(): -stdClass Object -( - [course] => 4 - [modulename] => assign - [name] => Fully Automated DTA Assignment - [intro] => This assignment was created by the final automation script. - [introformat] => 1 - [section] => 1 - [visible] => 1 - [assignsubmission_dta_enabled] => 1 - [assignsubmission_file_enabled] => 1 - [dta] => 1 - [tests_draft_dta] => 121728303 - [assignsubmission_file_maxsizebytes] => 0 - [cmidnumber] => - [assignsubmission_file_maxfiles] => 20 - [assignsubmission_onlinetext_enabled] => 0 - [submissiondrafts] => 0 - [requiresubmissionstatement] => 0 - [requireallteammemberssubmit] => 0 - [teamsubmission] => 0 - [sendnotifications] => 1 - [sendlatenotifications] => 0 - [sendstudentnotifications] => 1 - [grade] => 100 - [blindmarking] => 0 - [markingworkflow] => 0 - [markingallocation] => 0 - [attemptreopenmethod] => none - [allowsubmissionsfromdate] => 1753175949 - [duedate] => 1753780749 - [cutoffdate] => 0 - [gradingduedate] => 0 - [module] => 1 - [timemodified] => 1753175949 - [alwaysshowdescription] => 0 - [completionsubmit] => 0 - [instance] => 3 - [coursemodule] => 6 - [groupingid] => 0 - [completion] => 0 - [completionview] => 0 - [completionexpected] => 0 - [completionpassgrade] => 0 - [completiongradeitemnumber] => - [conditiongradegroup] => Array - ( - ) - - [conditionfieldgroup] => Array - ( - ) - - [visibleoncoursepage] => 1 - [downloadcontent] => 1 - [groupmode] => 0 - [beforemod] => - [gradingman] => grading_manager Object - ( - [context:protected] => core\context\module Object - ( - [_id:protected] => 19 - [_contextlevel:protected] => 70 - [_instanceid:protected] => 6 - [_path:protected] => /1/3/18/19 - [_depth:protected] => 4 - [_locked:protected] => 0 - ) - - [component:protected] => mod_assign - [area:protected] => - [areacache:grading_manager:private] => - ) - - [showgradingmanagement] => -) - -3 diff --git a/newupdatedFile/moodle.ini b/newupdatedFile/moodle.ini deleted file mode 100644 index cc0cce84fe925de7cd0e8cb446e2adbd59e172e2..0000000000000000000000000000000000000000 --- a/newupdatedFile/moodle.ini +++ /dev/null @@ -1 +0,0 @@ -max_input_vars = 5000 diff --git a/newupdatedFile/nginx.conf b/newupdatedFile/nginx.conf deleted file mode 100644 index 7d7cb391ec0adbe3d85d0229c2eb75998e69490b..0000000000000000000000000000000000000000 --- a/newupdatedFile/nginx.conf +++ /dev/null @@ -1,34 +0,0 @@ -worker_processes 5; -worker_rlimit_nofile 8192; - -events { - worker_connections 4096; -} - -http { - default_type application/octet-stream; - sendfile on; - tcp_nopush on; - server_names_hash_bucket_size 128; - - proxy_cache_path /opt/cache levels=1:2 keys_zone=dta_cache:10m max_size=10g inactive=60m use_temp_path=off; - - server { - listen 8080; - - location /v1/task { - proxy_cache dta_cache; - proxy_cache_background_update on; - proxy_cache_lock on; - proxy_cache_key "$scheme$request_uri"; - proxy_cache_valid 200 302 1d; - proxy_cache_methods GET HEAD POST; - - proxy_pass http://backendcomposedta:8080/v1/task; - } - - location /v1/unittest { - proxy_pass http://backendcomposedta:8080/v1/unittest; - } - } -} diff --git a/newupdatedFile/student-dir.txt b/newupdatedFile/student-dir.txt deleted file mode 100644 index 50f42b17fa8843ac424912a4f87c1fd0359107ca..0000000000000000000000000000000000000000 --- a/newupdatedFile/student-dir.txt +++ /dev/null @@ -1 +0,0 @@ -dta::https://transfer.hft-stuttgart.de/gitlab/cota/examples/cota-submission-calc-jdk.git::none::none::/ diff --git a/newupdatedFile/teacher-dta-dir-test-runner-jdk21.txt b/newupdatedFile/teacher-dta-dir-test-runner-jdk21.txt deleted file mode 100644 index f5c9b2fa0e96d4ce2a5ed278de1938068677dc83..0000000000000000000000000000000000000000 --- a/newupdatedFile/teacher-dta-dir-test-runner-jdk21.txt +++ /dev/null @@ -1 +0,0 @@ -dta::https://transfer.hft-stuttgart.de/gitlab/cota/examples/cota-test-course-jdk.git::none::none::/topic02/exercise04::cota-runner-jdk21-junit5 \ No newline at end of file diff --git a/newupdatedFile/test-moodle.sh b/newupdatedFile/test-moodle.sh deleted file mode 100644 index 8e94cd020e4d6d15c16fe6cbd8033d6060bd3fe9..0000000000000000000000000000000000000000 --- a/newupdatedFile/test-moodle.sh +++ /dev/null @@ -1,524 +0,0 @@ -#!/bin/bash -set -e - -# ============================================================================== -# DEFINITIVE Moodle CI/CD AUTOMATION SCRIPT V10 (ROBUST ERROR CHECKING) -# ============================================================================== - -# --- Configuration (UPDATE THESE!) --- -MOODLE_URL="http://localhost:8080" -BACKEND_URL="http://localhost:8081" -MOODLE_ADMIN_USER="user" -MOODLE_ADMIN_PASS="QAYwsx@12345" -# Automation User (Course creator) -AUTOMATION_USER_NAME="maaeruser" -AUTOMATION_USER_PASS="AutomationPassword123" -# Student User (created by script) -STUDENT_USER_NAME="tstudent" -STUDENT_USER_PASS="StudentPassword123" -DTA_CONFIG_FILE="teacher-dta-dir-test-runner-jdk21.txt" -DTA_STUDENT_SUBMISSION_FILE="student-dir.txt" -# --- Static Configuration --- -MOODLE_CONTAINER="moodle-web-official" -MOODLE_SYSTEM_USER="www-data" -MOODLE_ROOT_IN_CONTAINER="/var/www/html" -PLUGIN_ZIP_NAME="dta.zip" -CUSTOM_SERVICE_NAME="dta_automation_service" -DB_CONTAINER_NAME="moodle-db-official" -DB_ROOT_PASSWORD="verysecretpassword" -DB_NAME="moodle" - -# ============================================================================== -# SCRIPT EXECUTION -# ============================================================================== - -echo "🚀 Starting Definitive Moodle Automation Script..." - -# --- Pre-flight Checks & Plugin Install (Step 1) --- -if ! docker ps --format '{{.Names}}' | grep -qw "$MOODLE_CONTAINER"; then echo "❌ Moodle container not running." && exit 1; fi -if [ ! -f "$PLUGIN_ZIP_NAME" ]; then echo "❌ Plugin ZIP file not found." && exit 1; fi -if [ ! -f "$DTA_CONFIG_FILE" ]; then echo "❌ DTA config file not found." && exit 1; fi -echo "✅ Pre-flight checks passed." - -echo "📦 Step 1: Installing custom plugin..." -# docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" bash -c "set -e; mkdir -p '$MOODLE_ROOT_IN_CONTAINER/mod/assign/submission'; unzip -o '/tmp/$PLUGIN_ZIP_NAME' -d '$MOODLE_ROOT_IN_CONTAINER/mod/assign/submission'; php '$MOODLE_ROOT_IN_CONTAINER/admin/cli/upgrade.php' --non-interactive; php '$MOODLE_ROOT_IN_CONTAINER/admin/cli/purge_caches.php'" -echo "✅ Plugin installed." - -echo " -> Enabling Web Service " -docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/cfg.php" --name=enablemobilewebservice --set=1 - -# --- Step 2: Create and Configure a Custom Web Service --- -echo " -> Clearing cURL security settings..." -docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/cfg.php" --name=curlsecurityblockedhosts --set="" -docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/cfg.php" --name=curlsecurityallowedport --set="" - -echo " -> Enabling Web Service File Uploads (System-wide)..." -docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/cfg.php" --name=webserviceuploaddisabled --set=0 -# --- THIS IS THE FINAL FIX --- -# Enable username/password authentication for web service token generation -echo " -> Enabling password authentication for web services..." -docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/cfg.php" --name=enablewsauthpassword --set=1 - -# --- Step 4: Create Dedicated API Role and Automation User --- -echo "🧑‍💻 Step 4: Creating dedicated API Role and Automation User..." -# This is a more advanced version of the user creation script -PHP_USER_SCRIPT_PATH="/tmp/create_api_user.php" -LOCAL_PHP_USER_SCRIPT_PATH="/tmp/local_create_api_user.php" -cat > "$LOCAL_PHP_USER_SCRIPT_PATH" <libdir.'/clilib.php'); -require_once(\$CFG->dirroot.'/user/lib.php'); -global \$DB; - -\$username = \$argv[1]; \$password = \$argv[2]; -if (!\$DB->record_exists('user', ['username' => \$username])) { - \$user = new stdClass(); - \$user->username = \$username; \$user->firstname = 'API'; \$user->lastname = 'User'; - \$user->email = 'api.user@example.com'; \$user->confirmed = 1; - \$user->mnethostid = \$CFG->mnet_localhost_id; \$user->auth = 'manual'; - \$userid = user_create_user(\$user, false, false); - \$user = \$DB->get_record('user', ['id' => \$userid]); - \update_internal_user_password(\$user, \$password); - echo "Created user '\$username' with ID: \$userid\n"; - - // Create a new role for API users if it doesn't exist - \$rolename = 'API User'; - \$apicapability = 'moodle/webservice:createtoken'; - \$restapicapability = 'webservice/rest:use'; - \$coursecapability = 'moodle/course:create'; - \$enrolcapability = 'enrol/manual:enrol'; - if (!\$apirole = \$DB->get_record('role', ['shortname' => 'apiuser'])) { - \$apiroleid = create_role(\$rolename, 'apiuser', 'Role for API access'); - \$apirole = \$DB->get_record('role', ['id' => \$apiroleid]); - echo "Created role '\$rolename' with ID: \$apiroleid\n"; - } - // Ensure the capability is assigned to the role - \$context = \core\context\system::instance(); - assign_capability(\$apicapability, CAP_ALLOW, \$apirole->id, \$context->id); - assign_capability(\$restapicapability, CAP_ALLOW, \$apirole->id, \$context->id); - assign_capability(\$coursecapability, CAP_ALLOW, \$apirole->id, \$context->id); - assign_capability(\$enrolcapability, CAP_ALLOW, \$apirole->id, \$context->id); - echo "Assigned capability '\$apicapability' to role '\$rolename'\n"; - - // Assign our automation user to this new role at the system level - role_assign(\$apirole->id, \$userid, \$context->id); -// Get the IDs for the roles we need to assign -\$managerroleid = \$DB->get_field('role', 'id', ['shortname' => 'manager']); -\$coursecreatorroleid = \$DB->get_field('role', 'id', ['shortname' => 'coursecreator']); -\$systemcontext = \core\context\system::instance(); - -// Assign the 'Manager' and 'Course creator' roles to the new user at the system context -role_assign(\$managerroleid, \$userid, \$systemcontext->id); -role_assign(\$coursecreatorroleid, \$userid, \$systemcontext->id); - echo "Assigned user '\$username' to role '\$rolename'\n"; -} else { - \$userid = \$DB->get_field('user', 'id', ['username' => \$username]); - echo "User '\$username' already exists with ID: \$userid\n"; -} - -// Create Student User -\$student_username = \$argv[3]; \$student_password = \$argv[4]; -if (!\$DB->record_exists('user', ['username' => \$student_username])) { - \$student = new stdClass(); \$student->username = \$student_username; \$student->firstname = 'Test'; \$student->lastname = 'Student'; - \$student->email = 'student@example.com'; \$student->confirmed = 1; \$student->mnethostid = \$CFG->mnet_localhost_id; \$student->auth = 'manual'; - \$studentid = user_create_user(\$student, false, false); - \$student = \$DB->get_record('user', ['id' => \$studentid]); - \update_internal_user_password(\$student, \$student_password); - echo "Created user '\$student_username' with ID: \$studentid\n"; - - // Create a new role for API users if it doesn't exist - \$rolename = 'API Student user'; - \$apicapability = 'moodle/webservice:createtoken'; - \$restapicapability = 'webservice/rest:use'; - if (!\$apirole = \$DB->get_record('role', ['shortname' => 'apistudentuser'])) { - \$apiroleid = create_role(\$rolename, 'apistudentuser', 'Role for API access'); - \$apirole = \$DB->get_record('role', ['id' => \$apiroleid]); - echo "Created role '\$rolename' with ID: \$apiroleid\n"; - } - // Ensure the capability is assigned to the role - \$context = \core\context\system::instance(); - assign_capability(\$apicapability, CAP_ALLOW, \$apirole->id, \$context->id); - assign_capability(\$restapicapability, CAP_ALLOW, \$apirole->id, \$context->id); - echo "Assigned capability '\$apicapability' to role '\$rolename'\n"; - - // Assign our automation user to this new role at the system level - role_assign(\$apirole->id, \$studentid, \$context->id); - echo "Assigned user '\$username' to role '\$rolename'\n"; -} -EOF -docker cp "$LOCAL_PHP_USER_SCRIPT_PATH" "$MOODLE_CONTAINER:$PHP_USER_SCRIPT_PATH" -docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$PHP_USER_SCRIPT_PATH" "$AUTOMATION_USER_NAME" "$AUTOMATION_USER_PASS" "$STUDENT_USER_NAME" "$STUDENT_USER_PASS" -docker exec "$MOODLE_CONTAINER" rm "$PHP_USER_SCRIPT_PATH" -rm "$LOCAL_PHP_USER_SCRIPT_PATH" -echo "✅ Automation and student users are configured." - -echo "⚙️ Step 2: Creating and configuring a dedicated web service..." -docker exec -i "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" < Purging Moodle caches.." -docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/purge_caches.php" -sleep 15 - -# --- Step 6: Assign System Roles via API (The Definitive Fix) --- -echo "🛡️ Step 6: Assigning System Roles to Automation User..." -# We use the main site admin's token for this one-time setup task, as it's guaranteed to have permissions. -ADMIN_SETUP_TOKEN_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/login/token.php" --data-urlencode "username=$MOODLE_ADMIN_USER" --data-urlencode "password=$MOODLE_ADMIN_PASS" --data-urlencode "service=moodle_mobile_app") -ADMIN_SETUP_TOKEN=$(echo "$ADMIN_SETUP_TOKEN_RESPONSE" | jq -r '.token') - -# Get IDs needed for role assignment -AUTOMATION_USER_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_user WHERE username='$AUTOMATION_USER_NAME';") -MANAGER_ROLE_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_role WHERE shortname='manager';") -COURSE_CREATOR_ROLE_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_role WHERE shortname='coursecreator';") -SYSTEM_CONTEXT_ID=10 # The ID for the System context is always 1 - -# Assign Manager and Course Creator roles to the automation user -curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \ - -d "wstoken=$ADMIN_SETUP_TOKEN" -d "wsfunction=core_role_assign_roles" -d "moodlewsrestformat=json" \ - -d "assignments[0][roleid]=$MANAGER_ROLE_ID" -d "assignments[0][userid]=$AUTOMATION_USER_ID" -d "assignments[0][contextid]=$SYSTEM_CONTEXT_ID" \ - -d "assignments[1][roleid]=$COURSE_CREATOR_ROLE_ID" -d "assignments[1][userid]=$AUTOMATION_USER_ID" -d "assignments[1][contextid]=$SYSTEM_CONTEXT_ID" > /dev/null -echo "✅ Assigned System Roles to Automation User." -docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/purge_caches.php" -sleep 15 - -# --- THIS IS THE CRITICAL FIX --- -echo " -> Purging Moodle caches to apply service changes..." -docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/purge_caches.php" -echo "✅ Caches purged." -sleep 5 - - -echo "🔐 Step 3: Obtaining API Token...$AUTOMATION_USER_NAME..$AUTOMATION_USER_PASS" -TOKEN_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/login/token.php" \ - --data-urlencode "username=$AUTOMATION_USER_NAME" \ - --data-urlencode "password=$AUTOMATION_USER_PASS" \ - --data-urlencode "service=$CUSTOM_SERVICE_NAME") - -# Check for a Moodle error in the response FIRST -if echo "$TOKEN_RESPONSE" | jq -e 'if type=="object" and .error then true else false end' > /dev/null; then - echo "❌ Moodle API returned an error when requesting a token:" - echo "$TOKEN_RESPONSE" | jq . - exit 1 -fi - -TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.token') -echo "✅ Successfully obtained API token. $TOKEN" - - -# --- Step 4: Create a Course --- -echo "📚 Step 4: Creating a new course..." -COURSE_SHORTNAME="DTA-Course-$(date +%s)" -COURSE_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \ - -d "wstoken=$TOKEN" -d "wsfunction=core_course_create_courses" -d "moodlewsrestformat=json" \ - -d "courses[0][fullname]=DTA Test Course" -d "courses[0][shortname]=$COURSE_SHORTNAME" -d "courses[0][categoryid]=1") - -# --- THIS IS THE ROBUST ERROR CHECK --- -# First, check if the response is a Moodle exception object. -# The `jq -e` command sets an exit code, which works perfectly with `if`. -if echo "$COURSE_RESPONSE" | jq -e 'if type=="object" and .exception then true else false end' > /dev/null; then - echo "❌ Moodle API returned an exception during course creation:" - echo "$COURSE_RESPONSE" | jq . # Pretty-print the JSON error - exit 1 -fi - -# If not an exception, try to parse the ID. This handles both single-object and array responses. -NEW_COURSE_ID=$(echo "$COURSE_RESPONSE" | jq -r 'if type=="array" then .[0].id else .id end') - -if [ -z "$NEW_COURSE_ID" ] || [ "$NEW_COURSE_ID" == "null" ]; then - echo "❌ Failed to parse a valid Course ID from the API response." - echo "Full API Response:" - echo "$COURSE_RESPONSE" | jq . - exit 1 -fi -echo "✅ Successfully created course '$COURSE_SHORTNAME' with ID: $NEW_COURSE_ID" -# --- END OF ROBUST ERROR CHECK --- - -# --- Step 5: Enroll Admin User into the New Course --- -echo "🧑‍🏫 Step 5: Enrolling you as a teacher in the new course..." -# First, get the User ID for your admin user -AUTOMATION_USER_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_user WHERE username='$AUTOMATION_USER_NAME';") -# The roleid for a Teacher is typically 3 (editingteacher) or 4 (teacher). We'll use 3. -TEACHER_ROLE_ID=3 - -# Add the enrol_manual_enrol_users function to our service -docker exec -i "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -e "INSERT IGNORE INTO mdl_external_services_functions (externalserviceid, functionname) VALUES ((SELECT id FROM mdl_external_services WHERE shortname = '$CUSTOM_SERVICE_NAME'), 'enrol_manual_enrol_users');" -# Purge cache to recognize the new function -docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$MOODLE_ROOT_IN_CONTAINER/admin/cli/purge_caches.php" > /dev/null - -ENROL_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \ - -d "wstoken=$TOKEN" -d "wsfunction=enrol_manual_enrol_users" -d "moodlewsrestformat=json" \ - -d "enrolments[0][roleid]=$TEACHER_ROLE_ID" \ - -d "enrolments[0][userid]=$AUTOMATION_USER_ID" \ - -d "enrolments[0][courseid]=$NEW_COURSE_ID") - -# A successful response is null, so we check for an exception instead. -if echo "$ENROL_RESPONSE" | jq -e 'if type=="object" and .exception then true else false end' > /dev/null; then - echo "❌ Moodle API returned an exception during user enrollment:" - echo "$ENROL_RESPONSE" | jq . - exit 1 -fi -echo "✅ Successfully enrolled you in the course." - -# --- Step 5: Upload Configuration File for DTA Plugin --- -echo "📤 Step 5: Uploading config file for DTA plugin..." -FILE_UPLOAD_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/upload.php" \ - -F "token=$TOKEN" -F "filearea=draft" -F "itemid=0" -F "file=@$DTA_CONFIG_FILE") -DTA_FILE_ITEMID=$(echo "$FILE_UPLOAD_RESPONSE" | jq -r 'if type=="array" then .[0].itemid else .itemid end') - -if [ -z "$DTA_FILE_ITEMID" ] || [ "$DTA_FILE_ITEMID" == "null" ]; then - echo "❌ Failed to upload DTA config file. Response:" - echo "$FILE_UPLOAD_RESPONSE" | jq . - exit 1 -fi -echo "✅ DTA config file uploaded. Received Item ID: $DTA_FILE_ITEMID" - -# --- Step 6: Create Assignment via Custom PHP Script --- -echo "📝 Step 6: Creating assignment with detailed error logging..." -PHP_ASSIGN_SCRIPT_PATH="/tmp/create_assignment.php" -LOCAL_PHP_ASSIGN_SCRIPT_PATH="/tmp/local_create_assignment.php" - -# --- This PHP script now has a try/catch block for detailed errors --- -cat > "$LOCAL_PHP_ASSIGN_SCRIPT_PATH" <dirroot.'/course/modlib.php'); -global \$DB, \$USER; - -// programmatically "log in" as the admin user -// This gives file_save_draft_area_files the user context it needs. -\$adminusername = "$AUTOMATION_USER_NAME"; -\$adminuser = \$DB->get_record('user', ['username' => \$adminusername, 'deleted' => 0], '*', MUST_EXIST); -\core\session\manager::set_user(\$adminuser); -// The global $USER object is now populated. - -if (count(\$argv) < 3) { exit("Error: Course ID and DTA Item ID are required.\n"); } -\$courseid = (int)\$argv[1]; -\$dta_itemid = (int)\$argv[2]; - -\$course = \$DB->get_record('course', ['id' => \$courseid], '*', MUST_EXIST); - -\$data = new stdClass(); - -// --- Core Module Details --- -\$data->course = \$courseid; -\$data->modulename = 'assign'; -\$data->name = 'Fully Automated DTA Assignment'; -\$data->intro = 'This assignment was created by the final automation script.'; -\$data->introformat = FORMAT_HTML; -\$data->section = 1; -\$data->visible = 1; - -// --- Submission Settings --- -\$data->assignsubmission_dta_enabled = 1; -\$data->assignsubmission_file_enabled = 1; - -// --- THIS IS FIX #1: Correct property name for the DTA file manager --- -// This assumes the form element for your file manager is named 'package_filemanager' -// The final property name becomes assignsubmission_dta_package_filemanager -\$data->dta = 1; // Explicitly enable the dta group of settings -\$data->tests_draft_dta = \$dta_itemid; -\$data->assignsubmission_file_maxsizebytes = 0; -\$data->cmidnumber = ''; -\$data->assignsubmission_file_maxfiles = 20; - -\$data->assignsubmission_onlinetext_enabled = 0; -\$data->submissiondrafts = 0; // Final submissions cannot be edited -\$data->requiresubmissionstatement = 0; // The direct fix for the error -\$data->requireallteammemberssubmit = 0; -\$data->teamsubmission = 0; - -// --- Notification Settings --- -\$data->sendnotifications = 1; -\$data->sendlatenotifications = 0; -\$data->sendstudentnotifications = 1; - -// --- Grading Settings --- -\$data->grade = 100; // Max grade -\$data->blindmarking = 0; -\$data->markingworkflow = 0; -\$data->markingallocation = 0; -\$data->attemptreopenmethod = 'none'; - -// --- Date Settings --- -\$data->allowsubmissionsfromdate = time(); -\$data->duedate = time() + (7 * 24 * 60 * 60); // Due in 7 days -\$data->cutoffdate = 0; // No hard cutoff -\$data->gradingduedate = 0; - -// --- Moodle Magic - these fields are necessary but we don't need to change them --- -\$data->module = \$DB->get_field('modules', 'id', ['name' => 'assign'], MUST_EXIST); -\$data->timemodified = time(); -\$data->alwaysshowdescription = 0; -\$data->completionsubmit = 0; - -// --- DEBUG: Print the exact data object we are sending --- -fwrite(STDERR, "DEBUG: Data object being sent to add_moduleinfo():\n"); -fwrite(STDERR, print_r(\$data, true) . "\n"); - -try { - // Attempt to create the assignment - \$cm = add_moduleinfo(\$data, \$course); - fwrite(STDERR, "DEBUG: Returned object from add_moduleinfo():\n"); - fwrite(STDERR, print_r(\$cm, true) . "\n"); - if (!\$cm || !is_object(\$cm) || !\$cm->coursemodule) { - fwrite(STDERR, "Error: add_moduleinfo() returned invalid object\n"); - exit(1); - } - // Some versions: id = course module id, instance = assignment id - \$assignmentid = property_exists(\$cm, 'instance') ? \$cm->instance : null; - \$coursemoduleid = property_exists(\$cm, 'coursemodule') ? \$cm->coursemodule : null; - - if (!\$assignmentid) { - fwrite(STDERR, "Error: Could not extract assignment ID\n"); - exit(1); - } - - rebuild_course_cache(\$course->id, true); - - // ✅ Echo only the assignment ID (for Bash) - echo \$assignmentid . "\n"; - -} catch (Exception \$e) { - // If anything goes wrong, catch the exception and print all details - echo "\n!!!!!! SCRIPT FAILED: An Exception was caught! !!!!!!\n\n"; - echo "Error Type: " . get_class(\$e) . "\n"; - echo "Error Message: " . \$e->getMessage() . "\n\n"; - - // Moodle's database exceptions often have extra useful debug info - if (!empty(\$e->debuginfo)) { - echo "Moodle Debug Info:\n" . \$e->debuginfo . "\n\n"; - } - - echo "Stack Trace:\n" . \$e->getTraceAsString() . "\n"; - exit(1); // Exit with an error code -} -EOF - -docker cp "$LOCAL_PHP_ASSIGN_SCRIPT_PATH" "$MOODLE_CONTAINER:$PHP_ASSIGN_SCRIPT_PATH" -NEW_ASSIGNMENT_ID=$(docker exec -u "$MOODLE_SYSTEM_USER" "$MOODLE_CONTAINER" php "$PHP_ASSIGN_SCRIPT_PATH" "$NEW_COURSE_ID" "$DTA_FILE_ITEMID" 2>&1 | tee full_output.log | tail -n1) -docker exec "$MOODLE_CONTAINER" rm "$PHP_ASSIGN_SCRIPT_PATH" -rm "$LOCAL_PHP_ASSIGN_SCRIPT_PATH" -echo " -> Created assignment with ID: $NEW_ASSIGNMENT_ID" -echo "✅ Test scenario created." - -# --- Step 8: Student Submission Workflow --- -echo "🧑‍🎓 Step 8: Beginning student submission workflow..." -STUDENT_USER_ID=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT id FROM mdl_user WHERE username='$STUDENT_USER_NAME';") -# Role 5 is the standard 'Student' role -STUDENT_ROLE_ID=5 -STUDENT_ENROL_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \ - -d "wstoken=$TOKEN" -d "wsfunction=enrol_manual_enrol_users" -d "moodlewsrestformat=json" \ - -d "enrolments[0][roleid]=$STUDENT_ROLE_ID" \ - -d "enrolments[0][userid]=$STUDENT_USER_ID" \ - -d "enrolments[0][courseid]=$NEW_COURSE_ID") -echo " -> Enrolled student in course." - -echo " -> Getting token for student..." -STUDENT_TOKEN_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/login/token.php" \ - --data-urlencode "username=$STUDENT_USER_NAME" --data-urlencode "password=$STUDENT_USER_PASS" --data-urlencode "service=$CUSTOM_SERVICE_NAME") -STUDENT_TOKEN=$(echo "$STUDENT_TOKEN_RESPONSE" | jq -r '.token') -if [ -z "$STUDENT_TOKEN" ] || [ "$STUDENT_TOKEN" == "null" ]; then echo "❌ Failed to get API token for student. Response: $STUDENT_TOKEN_RESPONSE" && exit 1; fi -echo " -> Successfully obtained token for student. $STUDENT_TOKEN" - -echo " -> Uploading student submission file..." -STUDENT_FILE_UPLOAD_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/upload.php" -F "token=$STUDENT_TOKEN" -F "filearea=draft" -F "itemid=0" -F "file=@$DTA_STUDENT_SUBMISSION_FILE") -STUDENT_FILE_ITEMID=$(echo "$STUDENT_FILE_UPLOAD_RESPONSE" | jq -r 'if type=="array" then .[0].itemid else .itemid end') -echo " -> Student file uploaded. Item ID: $STUDENT_FILE_ITEMID" -echo " -> Saving final submission..." - -# ============================================================================== -# CORRECTED SUBMISSION STEP (V12 - USER SPECIFIED) -# ============================================================================== -# We call the external backend directly and pass the student's wstoken as a form -# field, as required by this specific custom plugin's workflow. - -echo " -> Sending student file directly to the DTA Backend for testing..." -BACKEND_RESPONSE=$(curl --fail -sS -L -X POST "$BACKEND_URL/v1/task/$SUBMISSION_ID" \ - --form "taskFile=@\"$DTA_STUDENT_SUBMISSION_FILE\"" \ - --form "assignmentId=\"$NEW_ASSIGNMENT_ID\"" \ - --form "wstoken=\"$STUDENT_TOKEN\"") - -# Check if the backend returned a valid JSON test report -if ! echo "$BACKEND_RESPONSE" | jq -e '.results' > /dev/null; then - echo "❌ The DTA backend did not return a valid test report. Response:" - echo "$BACKEND_RESPONSE" - exit 1 -fi -echo "✅ File successfully sent to backend for grading." -echo " -> Backend Test Report Summary:" -echo "$BACKEND_RESPONSE" | jq '.results[] | .name + ": " + .failureType' - -# --- Step 9: Verify Grade and Feedback in Moodle Database --- -echo "🧐 Step 9: Verifying grade and feedback has been posted back to Moodle..." -echo " -> Waiting 15 seconds for DTA backend to make the callback..." -sleep 15 - -# Query the database directly to see if the summary record was created by the backend -SUMMARY_COUNT=$(docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -se "SELECT COUNT(*) FROM mdl_assignsubmission_dta_summary WHERE submission_id = '$SUBMISSION_ID';") - -if [ "$SUMMARY_COUNT" -eq 0 ]; then - echo "❌ ERROR: No feedback summary found in the database for submission ID $SUBMISSION_ID." - echo "The backend callback likely failed. Check the backend logs:" - docker logs backend - exit 1 -fi - -echo "--- FINAL MOODLE DATABASE STATUS ---" -echo "✅ Found DTA summary record in the database." -echo "Detailed Test Results from mdl_assignsubmission_dta_result:" -docker exec "$DB_CONTAINER_NAME" mysql -u root -p"$DB_ROOT_PASSWORD" "$DB_NAME" -e "SELECT name, state, failure_type, failure_reason FROM mdl_assignsubmission_dta_result WHERE submission_id = '$SUBMISSION_ID';" - -# Final check on the core Moodle grading status -STATUS_RESPONSE=$(curl --fail -sS -L -X POST "$MOODLE_URL/webservice/rest/server.php" \ - -d "wstoken=$TOKEN" \ - -d "wsfunction=mod_assign_get_submission_status" \ - -d "moodlewsrestformat=json" \ - -d "assignid=$NEW_ASSIGNMENT_ID" \ - -d "userid=$STUDENT_USER_ID") -GRADE_STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.lastattempt.gradingstatus') - -if [ "$GRADE_STATUS" == "graded" ]; then - echo "🎉 SUCCESS! The submission is marked as 'graded' in the Moodle GUI." -else - echo "⚠️ WARNING: The submission status is '$GRADE_STATUS', not 'graded'. The backend wrote to its custom tables but may have failed to update the core gradebook." - exit 1 -fi - - -echo "🎉🎉 Moodle automation complete. The environment is fully configured. 🎉🎉"