state = $state; } } abstract class BoltUnit { public $name; // logical name. abstract function walk( $old_stack, &$new_stack, $next, &$item, &$frac_done ); // multi-purpose function for traversing a course. // if $old_stack is null // set up initial state for this unit. // append frames to $new_stack for this unit and descendants // $next is ignored // $item is the initial item // return is ignored // else // The first frame of $old_stack is for this unit. // Check for name mismatch (if changed course). // Append frames to $new_stack for this unit and descendants // if $next, the bottom-level non-item unit should increment. // return value: true if the caller should increment // frac_done: Fraction done (of this unit and any subunits) abstract function is_item(); } // An iterator represents a user's position in a course. // It is stored in the database, and the course may change underneath it. // class BoltIter { public $stack; // array of BoltFrame public $top; // point to the start of a course; set up stack. // function __construct($top) { $this->top = $top; $this->stack = null; } // get current item // function at(&$frac_done) { $new_stack = array(); $this->top->walk($this->stack, $new_stack, false, $item, $frac_done); $this->stack = $new_stack; return $item; } // move to the next item (and return it) // return true if we're off the end // function next(&$frac_done) { $item = null; $new_stack = array(); $this->top->walk($this->stack, $new_stack, true, $item, $frac_done); $this->stack = $new_stack; return $item; } } class BoltSeq extends BoltUnit { public $units; function __construct($n, $u) { $this->name = $n; $this->units = $u; } function walk($old_stack, &$new_stack, $next, &$item, &$frac_done) { //echo "call to walk() for $this->name: next: $next\n"; $n = count($this->units); if ($old_stack) { //echo "old stack: \n"; //var_dump($old_stack); //echo "------------\n"; $frame = $old_stack[0]; $state = $frame->state; $i = $state->i; // first, look up unit by name // $child = null; for ($j=0; $j<$n; $j++) { $c = $this->units[$j]; if ($c->name == $state->child_name) { $child = $c; break; } } // if not there, look up by index // if (!$child) { if ($i >= $n) { // and if index is too big, use last unit // $i = $n-1; } $child = $this->units[$i]; } // at this point, $child is the right unit // if ($next) { $inc = false; if ($child->is_item()) { $inc = true; } else { array_shift($old_stack); $inc = $child->walk( $old_stack, $new_stack, true, $item, $frac_done ); } if ($inc) { $i++; if ($i == $n) { $frac_done = 1; return true; } } } } else { $i = 0; } $frac_done = $i/$n; $child = $this->units[$i]; $state->i = $i; $state->child_name = $child->name; $frame = new BoltFrame($state); $new_stack[] = $frame; if ($child->is_item()) { $item = $child; } else { $child->walk(null, $new_stack, false, $item, $f); $frac_done += $f*(1/$n); } } function is_item() { return false; } } class BoltItem extends BoltUnit { public $filename; function __construct($name, $filename) { $this->filename = $filename; $this->name = $name; } function begin() { return array(new BoltFrame($this)); } function unit_list() { return array(&$this); } function is_item() { return true; } function walk($old_stack, &$new_stack, $next, &$item, &$frac_done) { echo "SHOULDN'T BE HERE\n"; } } class BoltLesson extends BoltItem { } class BoltExercise extends BoltItem { } function enum_course($course) { $iter = new BoltIter($course); while (1) { $x = $iter->at(); if (!$x) break; echo "at: $x->url\n"; $x = $iter->next(); if (!$x) break; echo "next: $x->filename\n"; } echo "course over\n"; } function info_incomplete($user) { if (!$user->bolt->birth_year) return true; if (!$user->bolt->sex) return true; return false; } function birth_year_select($user) { $this_year = date("Y"); $x = "\n"; return $x; } function sex_select($user) { $x = "\n"; return $x; } function request_info($user, $course) { page_head("Student info"); echo "
id> "; start_table(); row2("Birth year", birth_year_select($user)); row2("Sex", sex_select($user)); row2("", ""); end_table(); echo "
\n"; page_tail(); } ?>