Skip to content

[Feature:System] Duplicate Enrollment of Class/Section(s) #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions student_auto_feed/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,10 @@
// 'tmp' or the current semester code.
define('ADD_DROP_FILES_PATH', "path/to/reports/");

/* CRN Copymap ------------------------------------------------------------- */

// Where is the crn copymap CSV located. Set to NULL is this is not used.
define('CRN_COPYMAP_FILE', "path/to/csv");

//EOF
?>
158 changes: 158 additions & 0 deletions student_auto_feed/crn_copymap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/usr/bin/env php
<?php
require __DIR__ . "/config.php";

if (php_sapi_name() !== "cli") {
die("This is a command line tool.\n");
}

if (is_null(CRN_COPYMAP_FILE)) {
die("CRN_COPYMAP_FILE is null. Check config.php.\n");
}

$proc = new crn_copy();
$proc->main();
exit;

class crn_copy {
public $err;

public function __construct() {
$this->err = "";
}

public function __destruct() {
if ($this->err !== "") fprintf(STDERR, $this->err);
}

public function main() {
// Reminder: cli::parse_args() returns an array captured by regex,
// so we need to always look at index [0] when reading $args data.
$args = cli::parse_args();
$args['source']['sections'][0] = $this->get_mappings($args['source']['sections'][0]);
$args['dest']['sections'][0] = $this->get_mappings($args['dest']['sections'][0]);
if (count($args['source']['sections'][0]) !== count($args['dest']['sections'][0])) {
$this->err = "One course has more sections than the other. Sections need to map 1:1.\n";
exit(1);
}

$this->write_mappings($args);
}

private function write_mappings($args) {
$term = $args['term'][0];
$source_course = $args['source']['course'][0];
$source_sections = $args['source']['sections'][0];
$dest_course = $args['dest']['course'][0];
$dest_sections = $args['dest']['sections'][0];

// Insert "_{$term}" right before file extension.
// e.g. "/path/to/crn_copymap.csv" for term f23 becomes "/path/to/crn_copymap_f23.csv"
$filename = preg_replace("/([^\/]+?)(\.[^\/\.]*)?$/", "$1_{$term}$2", CRN_COPYMAP_FILE, 1);

$fh = fopen($filename, "a");
if ($fh === false) {
$this->err = "Could not open crn copymap file for writing.\n";
exit(1);
}

$len = count($source_sections);
for ($i = 0; $i < $len; $i++) {
$row = array($source_course, $source_sections[$i], $dest_course, $dest_sections[$i]);
fputcsv($fh, $row, ",");
}

fclose($fh);
}

private function get_mappings($sections) {
if ($sections === "" || $sections === "all") return array($sections);

$arr = explode(",", $sections);
$expanded = array();
foreach($arr as $val) {
if (preg_match("/(\d+)\-(\d+)/", $val, $matches) === 1) {
$expanded = array_merge($expanded, range((int) $matches[1], (int) $matches[2]));
} else {
$expanded[] = $val;
}
}

return $expanded;
}
}

/** class to parse command line arguments */
class cli {
/** @var string usage help message */
private static $help_usage = "Usage: crn_copymap.php [-h | --help | help] (term) (course-a) (sections) (course-b) (sections)\n";
/** @var string short description help message */
private static $help_short_desc = "Create duplicate enrollment mapping of courses and semesters.\n";
/** @var string long description help message */
private static $help_long_desc = <<<LONG_DESC
Create a mapping of CRNs (course and sections) that are to be duplicated.
This is useful if a professor wishes to have a course enrollment,
by section, duplicated to another course. Particularly when the
duplicated course has no enrollment data provided by IT.\n
LONG_DESC;
/** @var string argument list help message */
private static $help_args_list = <<<ARGS_LIST
Arguments:
-h, --help, help Show this help message.
term Term code of courses and sections being mapped. Required.
course-a Original course
sections Section list, or "all" of preceding course
course-b Course being copied to
sections For course-b, this can be ommited when course-a sections is "all"
ARGS_LIST;

/**
* Parse command line arguments
*
* CLI arguments are captured from global $argv by regular expressions during validation.
*
* @return array cli arguments
*/
public static function parse_args() {
global $argc, $argv;
$matches = array();

switch(true) {
// Check for request for help
case $argc > 1 && ($argv[1] === "-h" || $argv[1] === "--help" || $argv[1] === "help"):
self::print_help();
exit;
// Validate CLI arguments. Something is wrong (invalid) when a case condition is true.
case $argc < 5 || $argc > 6:
case $argv[3] === "all" && (array_key_exists(5, $argv) && $argv[5] !== "all"):
case $argv[3] !== "all" && (!array_key_exists(5, $argv) || $argv[5] === "all"):
case preg_match("/^[a-z][\d]{2}$/", $argv[1], $matches['term']) !== 1:
case preg_match("/^[\w\d\-]+$/", $argv[2], $matches['source']['course']) !== 1:
case preg_match("/^\d+(?:(?:,|\-)\d+)*$|^all$/", $argv[3], $matches['source']['sections']) !== 1:
case preg_match("/^[\w\d\-]+$/", $argv[4], $matches['dest']['course']) !== 1:
case preg_match("/^\d+(?:(?:,|\-)\d+)*$|^(?:all)?$/", $argv[5], $matches['dest']['sections']) !== 1:
self::print_usage();
exit;
}

// $matches['dest']['sections'][0] must be "all" when ['source']['sections'][0] is "all".
if ($matches['source']['sections'][0] === "all") $matches['dest']['sections'][0] = "all";
return $matches;
}

/** Print complete help */
private static function print_help() {
$msg = self::$help_usage . PHP_EOL;
$msg .= self::$help_short_desc . PHP_EOL;
$msg .= self::$help_long_desc . PHP_EOL;
$msg .= self::$help_args_list . PHP_EOL;
print $msg;
}

/** Print CLI usage */
private static function print_usage() {
print self::$help_usage . PHP_EOL;
}
}
// EOF
?>
30 changes: 28 additions & 2 deletions student_auto_feed/readme.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Submitty Student Auto Feed Script
Readme last updated Nov 17, 2021
Readme last updated Sept 1, 2023

This is a code example for any University to use as a basis to have Submitty course's enrollment data added or updated on an automated schedule with a student enrollment CSV datasheet.

Expand Down Expand Up @@ -134,7 +134,7 @@ the first (prior to autofeed) or second (after auto feed) run.

Second cli parameter is the term code.

For example:
For example:
```
$ ./add_drop_report.php 1 f21
```
Expand All @@ -145,3 +145,29 @@ $ ./add_drop_report.php 2 f21
```
Will invoke the _second_ run to create the report of student enrollments for the
Fall 2021 term.

## crn_copymap.php

Create a mapping of CRNs (course, term) that are to be duplicated. This is
useful if a professor wishes to have a course enrollment, by section,
duplicated to another course. Particularly when the duplicated course has
no enrollment data provided by IT.

Sections can be a comma separated list, a range denoted by a hyphen, or the
word "all" for all sections. Note that "all" sections will copy sections
respectively. i.e. section 1 is copied as section 1, section 2 is copied as
section 2, etc.

### Usage
```bash
$ crn_copymap.php (term) (original_course) (original_sections) (copied_course) (copied_sections)
```
For example:
Copy enrollments of term "f23" (fall 2023) of course CSCI 1000,
sections 1, 3, and 5 through 9 to course CSCI 2000 as sections 2, 4, and 6 through 10
respectively.
```bash
$ crn_copymap.php f23 csci1000 1,3,5-9 csci2000 2,4,6-10
```

EOF
Loading