is_modified = false; $args = func_get_args(); $arg_count = func_num_args(); if (method_exists($this, $function = '__construct'.$arg_count)) { call_user_func_array(array($this, $function), $args); } } // ---------------------------------------- // Constructor // // Initiates instance variables and creates classes based // on the xml data received. // // Arguments: // $xml_data is an xml data object supposed to contain relevant // data retrieved from the char_students.year-semester file(s). // ---------------------------------------- public function __construct1($xml_data) { $this->changes = array(); $this->sub_parts = array(); $this->name = $xml_data->getAttribute('name'); $this->grade = $xml_data->getAttribute('grade'); $this->reported = $xml_data->getAttribute('reported'); if ( $xml_data->hasChildNodes() ) { $sub_part_data = $xml_data->getElementsByTagName('lab'); foreach ( $sub_part_data as $sub_part ) { $this->sub_parts[] = new Result_Sub_Part($sub_part); } } } // ---------------------------------------- // Constructor // // Initiates instance variables and creates classes based on // previously extracted data. Not using default parameters to // distinguish the call signature from the other constructor. // // Arguments: // $name is the name of the result part // $grade is the grade to be reported for the whole result part // null if it's not ready for a full grade // $reported_date is the date the result part was reported, null // if not reported. // ---------------------------------------- public function __construct3($name, $grade, $reported_date) { $this->changes = array(); $this->sub_parts = array(); $this->is_new = true; $this->name = $name; $this->grade = $grade; $this->reported = $reported_date; } // ---------------------------------------- // Constructor // // Initiates instance variables and creates classes based on // previously extracted data. Not using default parameters to // distinguish the call signature from the other constructor. // // Arguments: // $name is the name of the result part // $grade is the grade to be reported for the whole result part // null if it's not ready for a full grade // $reported_date is the date the result part was reported, null // if not reported. // $report_name is a default null parameter, if this course part // has a different report name (Ex: DAT1 might be TEN1 in some // courses), this is the parameter to control this // ---------------------------------------- public function __construct4($name, $grade, $reported_date, $report_name) { $this->changes = array(); $this->sub_parts = array(); $this->is_new = true; $this->name = $name; $this->grade = $grade; $this->reported = $reported_date; $this->report_name = $report_name; } // ---------------------------------------- // get_name // // Returns the name of the result part. // ---------------------------------------- public function get_name() { return $this->name; } // ---------------------------------------- // get_grade // // Returns the grade set for the result part. // ---------------------------------------- public function get_grade() { return $this->grade; } // ---------------------------------------- // get_grade_except_u // // Returns the grade set for the result part unless that grade is // U, in which case we return "" (empty string). // ---------------------------------------- public function get_grade_except_u() { if ( ! isset($this->grade) || strcasecmp($this->grade, "U") == 0 ) { return ""; } return $this->grade; } // ---------------------------------------- // set_grade // // Set a new grade for this result part. // // Arguments: // $new_grade - string containing the new grade (String) // ---------------------------------------- public function set_grade($new_grade) { $this->grade = $new_grade; $this->is_modified = true; } // ---------------------------------------- // get_reported // // Returns the reported date for this result part. // ---------------------------------------- public function get_reported() { return $this->reported; } // ---------------------------------------- // set_reported // // Set a new reported date for this result part. // // Arguments: // $reported_date - string containing the date on the format: // "YYYY-MM-DD" (String) // ---------------------------------------- public function set_reported($reported_date) { $this->reported = $reported_date; $this->is_modified = true; } // ---------------------------------------- // get_report_name // // Returns the report name for this result part. // ---------------------------------------- public function get_report_name() { return $this->report_name; } // ---------------------------------------- // set_report_name // // Set a new report namefor this result part. // // Arguments: // $report_name - string containing the new report name, ex: // "TEN1" // ---------------------------------------- public function set_report_name($report_name) { $this->report_name = $report_name; $this->is_modified = true; } // ---------------------------------------- // get_sub_parts // // Returns an array with all the sub parts to this. // ---------------------------------------- public function get_sub_parts() { return $this->sub_parts; } // ---------------------------------------- // has_changes // // Returns true if there are stored changes, false otherwise. // ---------------------------------------- public function has_changes() { return ( ( isset($this->changes) && ( ! empty($this->changes) ) ) || $this->is_modified ) ; } // ---------------------------------------- // is_new // // Returns true if this is a new object (reported, not loaded from // file), false otherwise. // ---------------------------------------- public function is_new() { return $this->is_new; } // ---------------------------------------- // get_sub_part_by_id // // Returns a sub part object matching the $id, null if no such sub // part exists. // // Arguments: // $id - string containing the desired sub part id. Ex: // Ada.O1. (String) // ---------------------------------------- public function get_sub_part_by_id($id) { // ---------- // Ensure that we have sub parts, and if we do: iterate over // them until we find the one corresponding with $id // ---------- if ( isset($this->sub_parts) && ! empty($this->sub_parts) ) { foreach ( $this->sub_parts as $sub ) { if ( strcasecmp($id, $sub->get_id()) == 0 ) { // Match found return $sub; } } } // No match was found return null; } // ---------------------------------------- // insert_new_sub_part_result // // Insert a new, or modify the state of an existing, result sub // part for this student based on extracted data. Will not // overwrite "passed" grade on the normal state, regardless of // what is being reported. Only one of $state or $autostate should // be set. The other one should be set to null. If $state is set, // $autostate will be ignored. // // Arguments: // $id is the new result sub part id // $state is the state to set for the sub part // $autostate is the autostate to set for the new sub part. // // Returns the new result sub part. If a result sub part by this // name already exists, that part will be modified according to // the parameters and returned. // ---------------------------------------- public function insert_new_sub_part_result($id, $state, $autostate) { // We have previously confirmed (to get this far) that // everything is in order with this course part. Regardless if // this part is reported already, we have to be able to work // with sub parts (e.g. reported with grade 3, trying to // increase to 4 or 5). $sub_part = $this->get_sub_part_by_id($id); // ---------- // Check if the sub part exists previously, if not create it. // ---------- if ( ! isset($sub_part) ) { // We had no previous sub part by this name, so we create // it. $sub_part = new Result_Sub_Part($id, $state, $autostate); $this->sub_parts[] = $sub_part; $this->changes[] = $sub_part; return $sub_part; } // ---------- // The part already exists. Check whether we have an // autostate report or a normal state and report the // results. If both have been sent we will give precedence // to $state. // ---------- if ( isset($state) ) { $sub_part->set_state($state); $sub_part->set_auto_state($state); } else { $sub_part->set_state($autostate); $sub_part->set_auto_state($autostate); } // We have mofications to the sub part, or created a new // one, so we store the part as changed to make saving to // file quicker. $this->changes[] = $sub_part; return $sub_part; } // ---------------------------------------- // insert_new_sub_part_result_unsafe // // Insert a new, or modify the state of an existing, result sub // part for this student based on extracted data. WILL ALLOW // OVERWRITING "passed" grade on the normal state, regardless of // what is being reported. Only one of $state or $autostate should // be set. The other one should be set to null. If $state is set, // $autostate will be ignored. // // Arguments: // $id is the new result sub part id // $state is the state to set for the sub part // $autostate is the autostate to set for the new sub part. // // Returns the new result sub part. If a result sub part by this // name already exists, that part will be modified according to // the parameters and returned. // ---------------------------------------- public function insert_new_sub_part_result_unsafe($id, $state, $autostate) { // We have previously confirmed (to get this far) that // everything is in order with this course part. Regardless if // this part is reported already, we have to be able to work // with sub parts (e.g. reported with grade 3, trying to // increase to 4 or 5). $sub_part = $this->get_sub_part_by_id($id); // ---------- // Check if the sub part exists previously, if not create it. // ---------- if ( ! isset($sub_part) ) { // We had no previous sub part by this name, so we create // it. $sub_part = new Result_Sub_Part($id, $state, $autostate); $this->sub_parts[] = $sub_part; $this->changes[] = $sub_part; return $sub_part; } // ---------- // The part already exists. Check whether we have an // autostate report or a normal state and report the // results. If both have been sent we will give precedence // to $state. // ---------- if ( isset($state) ) { // Here we call set_state_unsafe to report the state // regardless of whether the student previously had a // passing mark or not. $sub_part->set_state_unsafe($state); $sub_part->set_auto_state($state); } else { // Even the unchecked method should not downgrade passed // state to an autostate so we do not call the unsafe // method here. This should not be done (ever), but we // make this for safety (might be wrong if "autocorrect" // makes calls to this one in the future). $sub_part->set_state($autostate); $sub_part->set_auto_state($autostate); } // We have mofications to the sub part, or created a new // one, so we store the part as changed to make saving to // file quicker. $this->changes[] = $sub_part; return $sub_part; } // ---------------------------------------- // print_dump // // Print function for debugging / testing. // ---------------------------------------- public function print_dump() { print "\t\tCoures part name: " . $this->name . ", grade: " . $this->grade . ", reported: " . $this->reported . "\n"; foreach ( $this->sub_parts as $sub ) { $sub->print_dump(); } } // ---------------------------------------- // to_json // // Creates a json string representation of the student result. // // Arguments: // $indentation is the count of tabs to indent the json data, // default 0. Used for when the json data should be nested // inside another json object. // // Returns a string containing the json representation of the // student result // ---------------------------------------- public function to_json($indentation = 0) { // Extract and "jsonify" attributes that are always present. $res = str_repeat("\t", $indentation) . "{\n" . str_repeat("\t", $indentation+1) . "\"part name\": \"" . $this->name . "\""; // ---------- // Check if the attributes the can be empty contain data, if // so "jsonify" and add them. // ---------- if ( isset($this->grade) ) { $res .= ",\n" . str_repeat("\t", $indentation) . "\t\"grade\": \"" . $this->grade . "\""; } if ( isset($this->reported) ) { $res .= ",\n" . str_repeat("\t", $indentation) . "\t\"reported\": \"" . $this->reported . "\""; } // ---------- // Check if the students have any sub parts to this result // part, if so append json-representations of those sub parts. // ---------- if ( isset($this->sub_parts) && ( ! empty($this->sub_parts) ) ) { // We have results for this student. $res .= ",\n"; $count = count($this->sub_parts); $res .= str_repeat("\t", $indentation+1) . "\"labs\": [\n"; for ($i = 0; $i < $count; $i++) { $sub_part = $this->sub_parts[$i]; $res .= $sub_part->to_json($indentation + 2); if ( $i < $count - 1 ) { // We're not on the last element $res .= ",\n"; } else { // Last element $res .= "\n"; } } $res .= str_repeat("\t", $indentation + 1) . "]\n"; } else { $res .= "\n"; } $res .= str_repeat("\t", $indentation) . "}"; return $res; } // ---------------------------------------- // to_json_full // // Creates a json string representation of the student result. // // Arguments: // $course_descript is the course description object used to // find all mandatory course sub parts for old students. // $indentation is the count of tabs to indent the json data, // default 0. Used for when the json data should be nested // inside another json object. // // Returns a string containing the json representation of the // student result // ---------------------------------------- public function to_json_full($course_description, $indentation = 0) { $stored = false; // Extract and "jsonify" attributes that are always present. $res = str_repeat("\t", $indentation) . "{\n" . str_repeat("\t", $indentation+1) . "\"part name\": \"" . $this->name . "\""; // ---------- // Check if the attributes the can be empty contain data, if // so "jsonify" and add them. // ---------- if ( isset($this->grade) ) { $res .= ",\n" . str_repeat("\t", $indentation) . "\t\"grade\": \"" . $this->grade . "\""; } if ( isset($this->reported) ) { $res .= ",\n" . str_repeat("\t", $indentation) . "\t\"reported\": \"" . $this->reported . "\""; } // ---------- // Check if the students have any sub parts to this result // part, if so append json-representations of those sub parts. // ---------- if ( isset($this->reported) && ( ! empty($this->reported) ) && ( strcasecmp($this->grade, "U") != 0 ) ) { // Student has already been reported with a grade for this // part. We want to report all mandatory sub parts as // passing, and all non mandatory parts with whatever // state they have. $cd_part = $course_description->find_part_by_name($this->name); if ( $cd_part == null ) { // We have an old course part that no longer exists in // course description. return ""; } $count = 0; $total_subs = count($cd_part->get_sub_parts()); $mandatory_parts = $cd_part->count_mandatory_sub_parts(); if ( $total_subs > 0 && $cd_part->has_mandatory_sub_parts() ) { $res .= ",\n"; $res .= str_repeat("\t", $indentation+1) . "\"labs\": [\n"; $stored = true; } foreach ( $cd_part->get_sub_parts() as $sub ) { if ( $sub->is_mandatory() ) { $count++; $res .= str_repeat("\t", $indentation+2) . "{\n" . str_repeat("\t", $indentation+3) . "\"id\": \"" . $sub->get_name() . "\",\n" . str_repeat("\t", $indentation+3) . "\"state\": \"passed\""; $res .= "\n" . str_repeat("\t", $indentation+2) . "}"; if ( $count < $mandatory_parts ) { $res .= ",\n"; } } } } $sub_count = $this->count_missing_sub_parts($res); if ( isset($this->sub_parts) && ( ! empty($this->sub_parts) ) ) { if ( $stored && $sub_count > 0 ) { $res .= ",\n"; } elseif ( $stored ) { $res .= "\n"; } elseif ( $sub_count > 0 ) { $res .= ",\n"; $res .= str_repeat("\t", $indentation+1) . "\"labs\": [\n"; } $finished = 0; foreach ( $this->sub_parts as $sub_part ) { if ( ! string_contains($res, '"' . $sub_part->get_id() . '"') ) { $finished++; $res .= $sub_part->to_json($indentation + 2); if ( $finished < $sub_count ) { // We're not on the last element $res .= ",\n"; } else { // Last element $res .= "\n"; } } } if ( $stored || count($this->sub_parts) > 0 ) { $res .= str_repeat("\t", $indentation + 1) . "]\n"; } } elseif ( $stored ) { $res .= str_repeat("\t", $indentation + 1) . "]\n"; } else { $res .= "\n"; } $res .= str_repeat("\t", $indentation) . "}"; return $res; } private function count_missing_sub_parts($str) { $count = 0; foreach ( $this->sub_parts as $sub_part ) { if ( ! string_contains($str, '"' . $sub_part->get_id() . '"') ) { $count++; } } return $count; } // ---------------------------------------- // save_results_to_file // // Stores the results for this student result part on the given // file. We know the student exists in active students for this // course // // Arguments: // $xml_path is the DOMDXpath for the xml_handler document. // $xml_document is the DOMDocument in which to store the changes. // $deadlines is an array of deadlines fetched from course info. // $student_elem the student xml element to append parts to. // $uid is the uid of the student, needed to extract xml data. // // Returns true if successfully saved, false otherwise, to handle // errors outside. // ---------------------------------------- public function save_results_to_file($xml_path, $xml_document, $deadlines, $student_elem, $uid) { $clean_save = true; // Extract the student part from the xml document. $xml_element = $xml_path->query("//student[@uid='{$uid}']/part[@name='{$this->name}']"); if ( ! $xml_element->length || ( ! isset($xml_element) ) ) { // The course part did not previously exist in the results // document for this student, so we create it and append // the attributes. $part_elem = $xml_document->createElement('part'); $part_elem->setAttribute('name', $this->name); $part_elem = $student_elem->appendChild($part_elem); } else if ( $xml_element->length > 1 ) { print "ERROR: Student |" . $uid . "| has multiple entries of |" . $this->name . "| in the data files."; } else { // Only one entry of this student / course part // combination existed previously. $part_elem = $xml_element->item(0); } // ---------- // Check the optional attributes and store them if they // have a value. // ---------- if ( isset($this->grade) and ! empty($this->grade) ) { $part_elem->setAttribute('grade', $this->grade); } if ( isset($this->reported) and ! empty($this->reported) ) { $part_elem->setAttribute('reported', $this->reported); } // ---------- // Check if we have any changes, and if so iterate over them // and save those changes. // ---------- if ( isset($this->changes) and ! empty($this->changes) ) { foreach ( $this->changes as $sub ) { if ( $sub->has_changes() or $sub->is_new() ) { if ( ! $sub->save_results_to_file($xml_path, $xml_document, $deadlines, $part_elem, $uid, $this->name) ) { $clean_save = false; } } } } $this->is_modified = false; return $clean_save; } // ---------------------------------------- // End of Result_Part class definition // ---------------------------------------- } // ---------------------------------------------------------------------------- ?>