@@ -120,17 +120,17 @@ function update_question_types_internal() {
120120 */
121121function update_question_types_with_qbank () {
122122 global $ DB ;
123- mtrace ("CodeRunner prototypes set up using qbank " );
123+ mtrace ("CodeRunner prototypes set up using qbank. " );
124124 $ topcategory = moodle5_top_category ();
125125 $ topcategorycontextid = $ topcategory ->contextid ;
126126 $ oldprototypecategory = get_old_prototype_category ();
127127 if ($ oldprototypecategory ) {
128- mtrace ("CodeRunner is moving old CR_PROTOTYPES category to new qbank category " );
128+ mtrace ("CodeRunner is moving old CR_PROTOTYPES category to new qbank category. " );
129129 $ sourcecategoryid = $ oldprototypecategory ->id ;
130130 question_move_category_to_context ($ sourcecategoryid , $ sourcecategoryid , $ topcategorycontextid );
131131 $ DB ->set_field ('question_categories ' , 'parent ' , $ topcategory ->id , ['parent ' => $ sourcecategoryid ]);
132132 }
133- mtrace ("CodeRunner: replacing existing prototypes with new ones " );
133+ mtrace ("CodeRunner: replacing existing prototypes with new ones. " );
134134 delete_existing_prototypes ($ topcategorycontextid );
135135 $ prototypescategory = find_or_make_prototype_category ($ topcategorycontextid , $ topcategory ->id );
136136 load_new_prototypes ($ topcategorycontextid , $ prototypescategory );
@@ -142,10 +142,10 @@ function update_question_types_with_qbank() {
142142 * @return bool true if successful
143143 */
144144function update_question_types_legacy () {
145- mtrace ("CodeRunner prototypes set up in system context " );
145+ mtrace ("CodeRunner prototypes set up in system context. " );
146146 $ systemcontext = context_system::instance ();
147147 $ systemcontextid = $ systemcontext ->id ;
148- mtrace ("CodeRunner: replacing existing prototypes with new ones " );
148+ mtrace ("CodeRunner: replacing existing prototypes with new ones. " );
149149 delete_existing_prototypes ($ systemcontextid );
150150 if (function_exists ('question_get_top_category ' )) { // Moodle version >= 3.5.
151151 $ parentid = get_top_id ($ systemcontextid );
@@ -185,6 +185,55 @@ function get_old_prototype_category() {
185185 }
186186}
187187
188+ /**
189+ * Get the existing system question bank instance for Moodle 4.6+.
190+ * This is a helper function to avoid duplicating the qbank lookup logic.
191+ *
192+ * @return object|null The question bank module instance, or null if not found/applicable
193+ */
194+ function get_system_question_bank () {
195+ if (!qtype_coderunner_util::using_mod_qbank ()) {
196+ return null ;
197+ }
198+ try {
199+ $ course = get_site ();
200+ return question_bank_helper::get_default_open_instance_system_type ($ course );
201+ } catch (Exception $ e ) {
202+ return null ;
203+ }
204+ }
205+
206+ /**
207+ * Get the context ID where CodeRunner prototypes are stored.
208+ * For Moodle 4.6+: Returns the front-page question bank context ID (if it exists)
209+ * For Moodle <4.6: Returns the system context ID (if CR_PROTOTYPES category exists)
210+ *
211+ * @return int|null The context ID, or null if prototypes context doesn't exist yet
212+ */
213+ function get_prototype_contextid () {
214+ global $ DB ;
215+
216+ if (qtype_coderunner_util::using_mod_qbank ()) {
217+ // Moodle 4.6+ - prototypes are in front-page question bank.
218+ $ qbank = get_system_question_bank ();
219+ if ($ qbank && $ qbank ->context ) {
220+ return $ qbank ->context ->id ;
221+ }
222+ } else {
223+ // Moodle <4.6 - prototypes are in system context.
224+ $ systemcontext = context_system::instance ();
225+ // Verify CR_PROTOTYPES category exists in system context.
226+ $ prototypecat = $ DB ->get_record ('question_categories ' , [
227+ 'contextid ' => $ systemcontext ->id ,
228+ 'name ' => 'CR_PROTOTYPES ' ,
229+ ]);
230+ if ($ prototypecat ) {
231+ return $ systemcontext ->id ;
232+ }
233+ }
234+ return null ;
235+ }
236+
188237/**
189238 * If we're on Moodle 4.6 or later we can't use the
190239 * old system of storing prototypes in a category in the system context. Instead
@@ -198,9 +247,9 @@ function moodle5_top_category() {
198247 $ course = get_site ();
199248 $ bankname = get_string ('systembank ' , 'question ' );
200249 try {
201- $ newmod = question_bank_helper:: get_default_open_instance_system_type ( $ course );
250+ $ newmod = get_system_question_bank ( );
202251 if (!$ newmod ) {
203- mtrace ('CodeRunner: creating new system question bank ' );
252+ mtrace ('CodeRunner: creating new system question bank. ' );
204253 $ newmod = question_bank_helper::create_default_open_instance ($ course , $ bankname , question_bank_helper::TYPE_SYSTEM );
205254 }
206255 } catch (Exception $ e ) {
@@ -233,8 +282,10 @@ function delete_existing_prototypes($contextid) {
233282 }
234283
235284 // Clean up any orphaned version records from previous upgrades that used
236- // the old direct deletion method. Only clean up records that were originally
237- // BUILTIN_PROTOTYPE questions by checking the naming pattern.
285+ // the old direct deletion method. These orphaned versions are guaranteed to be
286+ // from BUILTIN prototypes because the old buggy code only deleted questions with
287+ // names matching 'BUILTIN_PROTOTYPE_%', so user-defined prototypes were never
288+ // deleted and thus have no orphaned version records.
238289 $ orphanedversions = $ DB ->get_records_sql (
239290 "SELECT DISTINCT qv.id
240291 FROM {question_versions} qv
@@ -243,18 +294,12 @@ function delete_existing_prototypes($contextid) {
243294 LEFT JOIN {question} q ON q.id = qv.questionid
244295 WHERE qc.contextid = ?
245296 AND qc.name = 'CR_PROTOTYPES'
246- AND q.id IS NULL
247- AND EXISTS (
248- SELECT 1 FROM {question_versions} qv2
249- JOIN {question} q2 ON q2.id = qv2.questionid
250- WHERE qv2.questionbankentryid = qbe.id
251- AND q2.name LIKE 'BUILT%IN_PROTOTYPE_%'
252- ) " ,
297+ AND q.id IS NULL " ,
253298 [$ contextid ]
254299 );
255300
256301 if (!empty ($ orphanedversions )) {
257- mtrace (" Cleaning up " . count ($ orphanedversions ) . " orphaned prototype version records " );
302+ mtrace (" Cleaning up " . count ($ orphanedversions ) . " orphaned prototype version records. " );
258303 foreach ($ orphanedversions as $ versionrecord ) {
259304 $ DB ->delete_records ('question_versions ' , ['id ' => $ versionrecord ->id ]);
260305 }
@@ -274,7 +319,7 @@ function delete_existing_prototypes($contextid) {
274319 );
275320
276321 if (!empty ($ orphanedentries )) {
277- mtrace (" Cleaning up " . count ($ orphanedentries ) . " orphaned prototype bank entries " );
322+ mtrace (" Cleaning up " . count ($ orphanedentries ) . " orphaned prototype bank entries. " );
278323 foreach ($ orphanedentries as $ entryrecord ) {
279324 $ DB ->delete_records ('question_bank_entries ' , ['id ' => $ entryrecord ->id ]);
280325 }
0 commit comments