Skip to content

Commit 85a6fef

Browse files
committed
filepath-length - Allow config of folder and filename lengths
1 parent b61f870 commit 85a6fef

File tree

3 files changed

+84
-3
lines changed

3 files changed

+84
-3
lines changed

classes/export_trait.php

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ trait export_trait {
4141
* @var string|null
4242
*/
4343
public ?string $subdirectory = null;
44+
/**
45+
* Maximum character length of category folder names.
46+
*
47+
* @var int
48+
*/
49+
public int $maxcatlength;
50+
/**
51+
* Maximum character length of question file name.
52+
*
53+
* @var int
54+
*/
55+
public int $maxqlength;
4456
/**
4557
* Obtain a list of questions from Moodle and loop through them.
4658
* If the question is not already in the manifest then create any necessary folders
@@ -75,6 +87,11 @@ public function export_to_repo() {
7587
* @return void
7688
*/
7789
public function export_to_repo_main_process(object $moodlequestionlist): void {
90+
if (is_file(__DIR__.'/../cli/config.php')) {
91+
include(__DIR__.'/../cli/config.php');
92+
}
93+
$this->maxcatlength = $this->maxcatlength ?? $maxcatlength ?? 200;
94+
$this->maxqlength = $this->maxqlength ?? $maxqlength ?? 230;
7895
// Make top folder in case we don't have any questions.
7996
if (!is_dir(dirname($this->manifestpath) . '/top')) {
8097
mkdir(dirname($this->manifestpath) . '/top');
@@ -157,15 +174,17 @@ public function export_to_repo_main_process(object $moodlequestionlist): void {
157174
// Sanitise individual parts of subcategory.
158175
$sanitizedsubcat = '/' . implode('/',
159176
array_map(
160-
fn($x) => preg_replace(cli_helper::BAD_CHARACTERS, '-', $x),
177+
fn($x) => preg_replace(cli_helper::BAD_CHARACTERS, '-',
178+
substr($x, 0, $this->maxcatlength)),
161179
explode('/', $this->subcategory)
162180
)
163181
);
164182
// Create directory structure for category if it doesn't.
165183
$targettopfound = false;
166184
$currentdirectory = null;
167185
foreach ($directorylist as $categorydirectory) {
168-
$categorydirectory = preg_replace(cli_helper::BAD_CHARACTERS, '-', $categorydirectory);
186+
$categorydirectory = preg_replace(cli_helper::BAD_CHARACTERS, '-',
187+
substr($categorydirectory, 0, $this->maxcatlength));
169188
$categorysofar .= "/{$categorydirectory}";
170189
if ($this->targetdirectory && !$targettopfound) {
171190
if (strpos($categorysofar, $sanitizedsubcat) === 0) {
@@ -222,7 +241,7 @@ public function export_to_repo_main_process(object $moodlequestionlist): void {
222241
$filetype = 'yml';
223242
}
224243

225-
$sanitisedqname = preg_replace(cli_helper::BAD_CHARACTERS, '-', substr($qname, 0, 230));
244+
$sanitisedqname = preg_replace(cli_helper::BAD_CHARACTERS, '-', substr($qname, 0, $this->maxqlength));
226245
$holdername = $sanitisedqname;
227246
$i = 2;
228247
while (file_exists("{$bottomdirectory}/{$sanitisedqname}.{$filetype}")) {

cli/config_sample.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,9 @@ $ignorecat = null;
6868

6969
// Should questions be exported as YAML difference files?
7070
$useyaml = false;
71+
72+
// Max character length of category folder names and question file names.
73+
// You may need to reduce these on Windows so that overall filepath for questions does not excede the system limit.
74+
// Be careful with truncation of category names - duplicate truncated categories with the same parent category will be merged.
75+
$maxcatlength = 200;
76+
$maxqlength = 230;

tests/create_repo_test.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,4 +443,60 @@ public function test_targeted_process_with_named_subcategory(): void {
443443
$this->assertEquals("top", $manifest->context->defaultsubdirectory);
444444
$this->assertEquals(123, $manifest->context->defaultsubcategoryid);
445445
}
446+
447+
/**
448+
* Test the full process with subcategoryid and truncated file names.
449+
*/
450+
public function test_process_with_subcategory_id_and_truncation(): void {
451+
$this->options['qcategoryid'] = 123;
452+
$this->clihelper = $this->getMockBuilder(\qbank_gitsync\cli_helper::class)->onlyMethods([
453+
'get_arguments', 'check_context',
454+
])->setConstructorArgs([[]])->getMock();
455+
$this->clihelper->expects($this->any())->method('get_arguments')->will($this->returnValue($this->options));
456+
$this->clihelper->expects($this->any())->method('check_context')->willReturn(
457+
json_decode('{"contextinfo":{"contextlevel": "module", "categoryname":"", "coursename":"Course 1",
458+
"modulename":"Module 1", "instanceid":"", "qcategoryname":"top/Default for Test 1/sub 2",
459+
"qcategoryid":123},
460+
"questions": []}')
461+
);
462+
$this->createrepo = $this->getMockBuilder(\qbank_gitsync\create_repo::class)->onlyMethods([
463+
'get_curl_request', 'call_exit',
464+
])->setConstructorArgs([$this->clihelper, $this->moodleinstances])->getMock();
465+
466+
$this->createrepo->curlrequest = $this->curl;
467+
$this->createrepo->listcurlrequest = $this->listcurl;
468+
469+
$this->listcurl->expects($this->exactly(1))->method('execute')->willReturn(
470+
'{"contextinfo":{"contextlevel": "module", "categoryname":"", "coursename":"Course 1",
471+
"modulename":"Module 1", "instanceid":"", "qcategoryname":"top/Default for Test 1/sub 2"},
472+
"questions": [{"questionbankentryid": "3", "name": "Three", "questioncategory": ""},
473+
{"questionbankentryid": "4", "name": "Four", "questioncategory": ""}]}'
474+
);
475+
$this->curl->expects($this->exactly(2))->method('execute')->willReturnOnConsecutiveCalls(
476+
'{"question": "<quiz><question type=\"category\"><category><text>top/Default for Test 1/sub 2' .
477+
'</text></category></question><question><name><text>Three</text></name></question></quiz>"' .
478+
', "version": "1"}',
479+
'{"question": "<quiz><question type=\"category\"><category><text>top/Default for Test 1/sub 2' .
480+
'</text></category></question><question><name><text>Four</text></name></question></quiz>"' .
481+
', "version": "1"}',
482+
);
483+
$this->createrepo->maxcatlength = 5;
484+
$this->createrepo->maxqlength = 3;
485+
$this->createrepo->process($this->clihelper, $this->moodleinstances);
486+
487+
// Check question files exist.
488+
$this->assertStringContainsString('Three', file_get_contents($this->rootpath . '/top/Defau/sub-2/Thr.xml'));
489+
$this->assertStringContainsString('Four', file_get_contents($this->rootpath . '/top/Defau/sub-2/Fou.xml'));
490+
491+
// Check category files exist.
492+
$this->assertStringContainsString('top/Default for Test 1/sub 2',
493+
file_get_contents($this->rootpath . '/top/Defau/sub-2/' . cli_helper::CATEGORY_FILE . '.xml'));
494+
$this->assertStringContainsString('top/Default for Test 1/sub 2',
495+
file_get_contents($this->rootpath . '/top/Defau/sub-2/' . cli_helper::CATEGORY_FILE . '.xml'));
496+
497+
$this->expectOutputRegex('/^\nAdded 2 questions.\n$/s');
498+
$manifest = $this->createrepo->manifestcontents;
499+
$this->assertEquals("top/Defau/sub-2", $manifest->context->defaultsubdirectory);
500+
$this->assertEquals(123, $manifest->context->defaultsubcategoryid);
501+
}
446502
}

0 commit comments

Comments
 (0)