_DegreePlan.php

  1. 5.x classes/_DegreePlan.php
  2. 4.x classes/_DegreePlan.php

Classes

NameDescription
_DegreePlan

File

classes/_DegreePlan.php
View source
  1. <?php
  2. class _DegreePlan extends stdClass
  3. {
  4. const DEGREE_ID_FOR_COMBINED_DEGREE = -1001;
  5. const SEMESTER_NUM_FOR_COURSES_ADDED = -88;
  6. const GROUP_ID_FOR_COURSES_ADDED = -88;
  7. const SEMESTER_NUM_FOR_DEVELOPMENTALS = -55;
  8. public $major_code, $title, $degree_type, $degree_level, $degree_class, $short_description, $long_description;
  9. public $list_semesters, $list_groups, $db, $degree_id, $catalog_year, $is_combined_dynamic_degree_plan, $combined_degree_ids_array;
  10. public $track_code, $track_title, $track_description, $student_array_significant_courses;
  11. public $bool_has_tracks, $array_semester_titles, $db_exclude, $db_allow_dynamic, $db_override_degree_hours, $db_advising_weight;
  12. public $public_notes_array;
  13. public $total_major_hours, $total_core_hours, $total_degree_hours;
  14. public $fulfilled_major_hours, $fulfilled_core_hours, $fulfilled_degree_hours;
  15. public $major_qpts_hours, $core_qpts_hours, $degree_qpts_hours;
  16. public $major_qpts, $degree_qpts, $core_qpts;
  17. public $db_track_selection_config, $track_selection_config_array;
  18. public $required_course_id_array; // We will keep track of every course which this degree lists as a requirement, even in groups.
  19. // looks like: required_course_id_array[$course_id][$degree_id][$group_id] = TRUE;
  20. public $gpa_calculations, $bool_calculated_progess_hours;
  21. public $bool_use_draft, $bool_loaded_descriptive_data;
  22. public $extra_data_array; // This is an array meant for any generic "extra data" we want to include, from custom modules.
  23. /**
  24. * $major_code ACCT, CSCI, etc.
  25. * $title Accounting, Computer Science, etc.
  26. * $degree_type BBA, BS, AS, etc.
  27. * $short_description These are a text description of this degree plan. Useful
  28. * for descriptions of "Tracks" or "Options." The short
  29. * $long_description one appears in a pull down, the long one is a more
  30. * complete text description. Will probably be unused
  31. * by most degrees.
  32. * $list_semesters A list of semesters that this DegreePlan requires.
  33. * $list_degree_plans If this degree plan has multiple tracks or options, then
  34. * they would be spelled out as other degree plans, and listed
  35. * here. For example, Biology has multiple "tracks" which,
  36. * internally, should be treated as different degree plans.
  37. **/
  38. function __construct($degree_id = "", DatabaseHandler $db = NULL, $bool_load_minimal = FALSE, $array_significant_courses = FALSE, $bool_use_draft = FALSE) {
  39. $this->list_semesters = new ObjList();
  40. $this->list_groups = new GroupList();
  41. $this->bool_use_draft = $bool_use_draft;
  42. // Always override if the global variable is set.
  43. if (@$GLOBALS["fp_advising"]["bool_use_draft"] == true) {
  44. $this->bool_use_draft = true;
  45. }
  46. $this->required_course_id_array = array();
  47. $this->public_notes_array = array();
  48. $this->extra_data_array = array();
  49. $this->db_advising_weight = 0;
  50. $this->track_selection_config_array = array();
  51. $this->db = $db;
  52. if ($db == NULL)
  53. {
  54. $this->db = get_global_database_handler();
  55. }
  56. $this->student_array_significant_courses = $array_significant_courses;
  57. if ($degree_id != "")
  58. {
  59. $this->degree_id = $degree_id;
  60. $this->load_descriptive_data();
  61. if (!$bool_load_minimal)
  62. {
  63. $this->load_degree_plan();
  64. }
  65. // Add the "Add a Course" semester to the semester list.
  66. $this->add_semester_courses_added();
  67. }
  68. if ($this->degree_level == "") {
  69. $this->degree_level = "UG"; // undergrad by default
  70. }
  71. } // __construct
  72. /**
  73. * Given a group_id, find out if this group contains a course which appears in other degrees. Return the max number.
  74. */
  75. function get_max_course_appears_in_degrees_count($test_group_id) {
  76. // array is sectioned like: course_id | degree_id | group_id. Group id = 0 means "on the bare degree plan"
  77. // ex: $this->required_course_id_array[$course_c->course_id][$this->degree_id][0] = TRUE;
  78. $exclude_degree_ids = system_get_exclude_degree_ids_from_appears_in_counts();
  79. $courses = array();
  80. foreach ($this->required_course_id_array as $course_id => $temp1) {
  81. foreach ($this->required_course_id_array[$course_id] as $degree_id => $temp2) {
  82. // Is this an excluded degree? If so, skip it.
  83. if (in_array($degree_id, $exclude_degree_ids)) continue;
  84. foreach ($this->required_course_id_array[$course_id][$degree_id] as $group_id => $val) {
  85. // Is this the group we are looking for?
  86. if ($group_id != $test_group_id) continue;
  87. // Otherwise, yes, we are in the right group. Let's keep track of what courses we have in this group:
  88. $courses[$course_id] = TRUE;
  89. }
  90. }
  91. }
  92. $course_count = array();
  93. // Okay, now what we want to do is find out, how many different degrees do these courses appear in?
  94. foreach ($this->required_course_id_array as $course_id => $temp1) {
  95. if (!isset($courses[$course_id])) continue; // wasn't in our list, so skip.
  96. $course_count[$course_id] = 0;
  97. foreach ($this->required_course_id_array[$course_id] as $degree_id => $temp2) {
  98. $course_count[$course_id]++;
  99. }
  100. }
  101. // Okay, coming out of this, we can sort the courses_degrees array, and return the highest number.
  102. rsort($course_count);
  103. //fpm($course_count);
  104. return @$course_count[0]; // first element should be the highest value.
  105. } //get_max_course_appears_in_degrees_count
  106. /**
  107. * Calculate and store progress hour information. Stores in the $this->gpa_calculations array.
  108. *
  109. *
  110. * @param $bool_get_local_only_hours - If set to TRUE, then "local" courses (non-transfer) will be separated into their own indexes.
  111. * @param $types - the type codes we care about. If left as an emtpy array, it will get all the types defined + "degree" for degree total.
  112. * The array structure should be "code" => "code". Ex: array('a' => 'a')
  113. *
  114. */
  115. function calculate_progress_hours($bool_get_local_only_hours = FALSE, $types = array())
  116. {
  117. // Let's go through our requirement types by code, and collect calcuations on them
  118. // in the gpa_calculations array.
  119. if (count($types) == 0) {
  120. // Wasn't set, so use ALL of the known requirement types.
  121. $types = fp_get_requirement_types();
  122. }
  123. // Add a pseudo-code in for "degree", which the functions will convert into a blank.
  124. $types["degree"] = t("Degree (total)");
  125. // We want to do this for all possible degrees.
  126. $all_degree_ids = array();
  127. $all_degree_ids[0] = 0; // Add in the default "0" degree, meaning, don't look for a specific degree_id.
  128. if ($this->degree_id != DegreePlan::DEGREE_ID_FOR_COMBINED_DEGREE) {
  129. // Not a combined degree, just use the current degree_id.
  130. // $all_degree_ids[] = $this->degree_id; // comment out... not needed?
  131. }
  132. else {
  133. // Add in all the degrees we are combined with.
  134. $all_degree_ids = array_merge($all_degree_ids, $this->combined_degree_ids_array);
  135. }
  136. foreach ($all_degree_ids as $degree_id) {
  137. foreach ($types as $code => $desc) {
  138. // Make sure to skip appropriate codes we don't care about.
  139. if ($code == 'x') continue;
  140. $this->gpa_calculations[$degree_id][$code]["total_hours"] = $this->get_progress_hours($code, TRUE, FALSE, FALSE, $degree_id);
  141. $this->gpa_calculations[$degree_id][$code]["fulfilled_hours"] = $this->get_progress_hours($code, FALSE, FALSE, FALSE, $degree_id);
  142. $this->gpa_calculations[$degree_id][$code]["qpts_hours"] = $this->get_progress_hours($code, FALSE, TRUE, FALSE, $degree_id);
  143. if ($bool_get_local_only_hours) {
  144. // Get ONLY local hours, too....
  145. $this->gpa_calculations[$degree_id][$code . "_local"]["total_hours"] = $this->get_progress_hours($code, TRUE, FALSE, TRUE, $degree_id);
  146. $this->gpa_calculations[$degree_id][$code . "_local"]["fulfilled_hours"] = $this->get_progress_hours($code, FALSE, FALSE, TRUE, $degree_id);
  147. $this->gpa_calculations[$degree_id][$code . "_local"]["qpts_hours"] = $this->get_progress_hours($code, FALSE, TRUE, TRUE, $degree_id);
  148. }
  149. } // foreach types as code
  150. } //foreach all_degree_ids
  151. // Note that we have run this function for this degree.
  152. $this->bool_calculated_progess_hours = TRUE;
  153. }// calculate progress hours
  154. /**
  155. * Calculate the quality points of our completed courses, so we can use
  156. * that to figure out GPA.
  157. *
  158. */
  159. function calculate_progress_quality_points($bool_get_local_only_hours = FALSE, $types = array()) {
  160. // Let's go through our requirement types by code, and collect calcuations on them
  161. // in the gpa_calculations array.
  162. if (count($types) == 0) {
  163. $types = fp_get_requirement_types();
  164. }
  165. // Add a pseudo-code in for "degree", which the functions will convert into a blank.
  166. $types["degree"] = "Degree (total)";
  167. // We want to do this for all possible degrees.
  168. $all_degree_ids = array();
  169. $all_degree_ids[0] = 0; // Add in the default "0" degree, meaning, don't look for a specific degree_id.
  170. if ($this->degree_id != DegreePlan::DEGREE_ID_FOR_COMBINED_DEGREE) {
  171. // Not a combined degree, just use the current degree_id.
  172. // $all_degree_ids[] = $this->degree_id; // Not needed?
  173. }
  174. else {
  175. $all_degree_ids = array_merge($all_degree_ids, $this->combined_degree_ids_array);
  176. }
  177. foreach ($all_degree_ids as $degree_id) {
  178. foreach ($types as $code => $desc) {
  179. // Make sure to skip appropriate codes we don't care about.
  180. if ($code == 'x') continue;
  181. $this->gpa_calculations[$degree_id][$code]["qpts"] = $this->get_progress_quality_points($code, FALSE, $degree_id);
  182. if ($bool_get_local_only_hours) {
  183. // Get only local courses, too...
  184. $this->gpa_calculations[$degree_id][$code . "_local"]["qpts"] = $this->get_progress_quality_points($code, TRUE, $degree_id);
  185. }
  186. } //foreach types as code
  187. } //foreach all_degree_ids
  188. } // calculate progess quality points
  189. /**
  190. * Returns the number of hours required (or fulfilled) in a degree plan
  191. * for courses & groups with the specified requirement_type.
  192. * ex: "m", "s", etc. leave blank for ALL required hours.
  193. * if boolRequiredHours is FALSE, then we will only look for the courses
  194. * which got fulfilled.
  195. *
  196. *
  197. *
  198. */
  199. function get_progress_hours($requirement_type = "", $bool_required_hours_only = TRUE, $bool_qpts_grades_only = FALSE, $bool_exclude_all_transfer_credits = FALSE, $req_by_degree_id = 0)
  200. {
  201. if ($requirement_type == "degree") $requirement_type = "";
  202. $hours = 0;
  203. $this->list_semesters->reset_counter();
  204. while ($this->list_semesters->has_more())
  205. {
  206. $sem = $this->list_semesters->get_next();
  207. if ($bool_required_hours_only == TRUE)
  208. {
  209. $hours += $sem->list_courses->count_hours($requirement_type, TRUE, FALSE, FALSE, FALSE, $req_by_degree_id); // do not exclude transfer credits, since this is for required hours only.
  210. } else {
  211. $temp = $sem->list_courses->count_credit_hours($requirement_type, TRUE, TRUE, $bool_qpts_grades_only, $bool_exclude_all_transfer_credits, $req_by_degree_id);
  212. $hours += $temp;
  213. }
  214. }
  215. // Also, add in groups matching this requirement type.
  216. $this->list_groups->reset_counter();
  217. while ($this->list_groups->has_more())
  218. {
  219. $g = $this->list_groups->get_next();
  220. if ($g->group_id < 0)
  221. { // Skip Add a course group.
  222. continue;
  223. }
  224. // Make sure the group doesn't have a type of 'x' assigned to it, which means we should
  225. // skip it.
  226. if ($g->requirement_type == 'x') continue;
  227. // If req_by_degree_id is set, make sure this group is assigned to that degree.
  228. if ($req_by_degree_id != 0 && $g->req_by_degree_id != $req_by_degree_id) continue;
  229. $g_hours = $g->hours_required;
  230. // use the min hours if it is set.
  231. if ($g->min_hours_allowed > 0) {
  232. $g_hours = $g->min_hours_allowed;
  233. }
  234. if ($bool_required_hours_only == false)
  235. { // only count the fulfilled hours, then.
  236. $g_hours = $g->get_fulfilled_hours(true, false, true, -1, true, $bool_qpts_grades_only, $requirement_type, $bool_exclude_all_transfer_credits);
  237. }
  238. if ($requirement_type == "")
  239. {
  240. $hours += $g_hours;
  241. }
  242. else {
  243. // A requirement is specified, so make sure
  244. // the group is of this requirement.
  245. if (!isset($g->hours_required_by_type[$requirement_type])) $g->hours_required_by_type[$requirement_type] = 0;
  246. if ($bool_required_hours_only == true)
  247. { // make sure it's of the right type.
  248. //$g_hours = $g->hours_required_by_type[$requirement_type]*1;
  249. // it should just be any hours_required, instead of by type, since a group can only have 1 type.
  250. if ($g->requirement_type == $requirement_type) {
  251. $g_hours = $g->hours_required;
  252. // use the min hours if it is set.
  253. if ($g->min_hours_allowed > 0) {
  254. $g_hours = $g->min_hours_allowed;
  255. }
  256. $hours += $g_hours;
  257. continue;
  258. }
  259. }
  260. if ($g->requirement_type == $requirement_type)
  261. {
  262. $hours += $g_hours;
  263. }
  264. }
  265. }
  266. return $hours;
  267. }
  268. /**
  269. * Similar to get_progress_hours, this will return back the quality points a student has earned
  270. * towards this degree. It can then be used to calculate GPA.
  271. *
  272. * @param unknown_type $requirement_type
  273. * @param unknown_type $bool_required_hours_only
  274. * @return unknown
  275. */
  276. function get_progress_quality_points($requirement_type = "", $bool_exclude_all_transfer_credits = FALSE, $req_by_degree_id = 0) {
  277. // Returns the number of hours required (or fulfilled) in a degree plan
  278. // for courses & groups with the specified requirement_type.
  279. // ex: "m", "s", etc. leave blank for ALL required hours.
  280. // if boolRequiredHours is FALSE, then we will only look for the courses
  281. // which got fulfilled.
  282. if ($requirement_type == "degree") $requirement_type = "";
  283. $points = 0;
  284. $this->list_semesters->reset_counter();
  285. while ($this->list_semesters->has_more())
  286. {
  287. $sem = $this->list_semesters->get_next();
  288. $p = $sem->list_courses->count_credit_quality_points($requirement_type, true, true, $bool_exclude_all_transfer_credits, $req_by_degree_id);
  289. $points = $points + $p;
  290. }
  291. // Also, add in groups matching this requirement type.
  292. $this->list_groups->reset_counter();
  293. while ($this->list_groups->has_more())
  294. {
  295. $g = $this->list_groups->get_next();
  296. if ($g->group_id < 0)
  297. { // Skip Add a course group.
  298. continue;
  299. }
  300. // Make sure the group doesn't have a type of 'x' assigned to it, which means we should
  301. // skip it.
  302. if ($g->requirement_type == 'x') continue;
  303. // if req_by_degree_id is set, make sure the group belongs to that degree id!
  304. if ($req_by_degree_id != 0 && $g->req_by_degree_id != $req_by_degree_id) continue;
  305. $g_points = $g->get_fulfilled_quality_points(TRUE, -1, TRUE, TRUE, $requirement_type, $bool_exclude_all_transfer_credits);
  306. $points = $points + $g_points;
  307. }
  308. return $points;
  309. }
  310. /**
  311. * Loads the "ancillary" information about our degree plan, including advising weight, track selection config, etc.
  312. *
  313. */
  314. function load_degree_plan_ancillary() {
  315. $degree_id = $this->degree_id;
  316. $old_semester = "";
  317. $table_name1 = "degrees";
  318. $table_name2 = "degree_requirements";
  319. if ($this->bool_use_draft) {
  320. $table_name1 = "draft_$table_name1";
  321. $table_name2 = "draft_$table_name2";
  322. }
  323. // We want to get some of the data for this degree.
  324. $res = db_query("SELECT * FROM $table_name1 WHERE degree_id = ?", $this->degree_id);
  325. if ($res) {
  326. $cur = db_fetch_array($res);
  327. $this->title = @$cur["title"];
  328. $this->major_code = @$cur["major_code"];
  329. $this->degree_level = @strtoupper(trim($cur["degree_level"]));
  330. if ($this->degree_level == "") {
  331. $this->degree_level = "UG"; // undergrad by default
  332. }
  333. $this->degree_class = @$cur["degree_class"];
  334. $this->db_override_degree_hours = @$cur["override_degree_hours"];
  335. $this->db_advising_weight = @intval($cur["advising_weight"]);
  336. $data_entry_value = @trim($cur['data_entry_value']);
  337. $this->db_track_selection_config = @trim($cur["track_selection_config"]);
  338. $this->parse_track_selection_config(); // load into the track_selection_config_array as needed.
  339. }
  340. } // load_degree_plan_ancillary
  341. /**
  342. * Load our complete degree plan, including all courses and groups.
  343. *
  344. */
  345. function load_degree_plan() {
  346. // Load this degree plan from the database and fully
  347. // assemble it.
  348. $degree_id = $this->degree_id;
  349. $old_semester = "";
  350. $table_name1 = "degrees";
  351. $table_name2 = "degree_requirements";
  352. if ($this->bool_use_draft) {
  353. $table_name1 = "draft_$table_name1";
  354. $table_name2 = "draft_$table_name2";
  355. }
  356. // Degrees we should exclude from the "appears in" counts. Used later...
  357. $exclude_degree_ids = system_get_exclude_degree_ids_from_appears_in_counts();
  358. $res = $this->db->db_query("SELECT * FROM $table_name1 a, $table_name2 b
  359. WHERE a.degree_id = '?'
  360. AND a.degree_id = b.degree_id
  361. ORDER BY semester_num ", $this->degree_id);
  362. while ($cur = $this->db->db_fetch_array($res))
  363. {
  364. $this->title = $cur["title"];
  365. $this->major_code = $cur["major_code"];
  366. $this->degree_level = strtoupper(trim($cur["degree_level"]));
  367. if ($this->degree_level == "") {
  368. $this->degree_level = "UG"; // undergrad by default
  369. }
  370. $this->degree_class = $cur["degree_class"];
  371. $this->db_override_degree_hours = $cur["override_degree_hours"];
  372. $this->db_advising_weight = intval($cur["advising_weight"]);
  373. $data_entry_value = trim($cur['data_entry_value']);
  374. $this->db_track_selection_config = trim($cur["track_selection_config"]);
  375. $this->parse_track_selection_config(); // load into the track_selection_config_array as needed.
  376. $semester_num = $cur["semester_num"];
  377. if ($semester_num != $old_semester)
  378. {
  379. // This is a new semester object we are dealing with.
  380. $old_semester = $semester_num;
  381. $obj_semester = new Semester($semester_num);
  382. $obj_semester->title = trim(@$this->array_semester_titles[$semester_num]);
  383. if ($obj_semester->title == "") {
  384. $obj_semester->assign_title();
  385. }
  386. $this->list_semesters->add($obj_semester);
  387. }
  388. if ($cur["course_id"]*1 > 0)
  389. {
  390. // A course is the next degree requirement.
  391. //if ($this->bool_use_draft) $cat_year = $this->catalog_year;
  392. $cat_year = $this->catalog_year;
  393. $course_c = new Course($cur["course_id"], false, $this->db, false, $cat_year, $this->bool_use_draft);
  394. $course_c->assigned_to_semester_num = $semester_num;
  395. $course_c->min_grade = trim(strtoupper($cur["course_min_grade"]));
  396. if ($course_c->min_grade == "")
  397. { // By default, all courses have a
  398. // min grade requirement of D.
  399. $course_c->min_grade = "D";
  400. }
  401. $course_c->requirement_type = trim($cur["course_requirement_type"]);
  402. // Set which degree_id this course is a requirement of (for multiple degrees)
  403. $course_c->req_by_degree_id = $this->degree_id;
  404. //adminDebug($course_c->to_string() . $course_c->getCatalogHours());
  405. $obj_semester->list_courses->add($course_c);
  406. if (!in_array($this->degree_id, $exclude_degree_ids)) {
  407. // array is sectioned like: course_id | degree_id | group_id. Group id = 0 means "on the bare degree plan"
  408. $this->required_course_id_array[$course_c->course_id][$this->degree_id][0] = TRUE;
  409. }
  410. } // if course_id > 0
  411. if ($cur["group_id"]*1 > 0)
  412. {
  413. // A group is the next degree requirement.
  414. //$group_g = new Group($cur["group_id"], $this->db, $semester_num);
  415. $title = "";
  416. $icon_filename = "";
  417. // Add the real Group (with all the courses, branches, etc)
  418. // to the DegreePlan's group list!
  419. // First, see if this group alread exists. If it does,
  420. // simply add the number of hours required to it. If not,
  421. // create it fresh.
  422. //if ($new_group = $this->find_group($cur["group_id"]))
  423. if ($new_group = $this->find_group($cur["group_id"] . '_' . $this->degree_id)) // group_id's will always have db_group_id _ degree_id from now on...
  424. {
  425. // Was already there (probably in another semester),
  426. // so, just increment the required hours.
  427. if (!isset($new_group->hours_required_by_type[$cur["group_requirement_type"]])) {
  428. $new_group->hours_required_by_type[$cur["group_requirement_type"]] = 0;
  429. }
  430. $new_group->hours_required = $new_group->hours_required + ($cur["group_hours_required"] * 1);
  431. $new_group->hours_required_by_type[$cur["group_requirement_type"]] += ($cur["group_hours_required"] * 1);
  432. //Set which degree_id this is required by.
  433. $new_group->req_by_degree_id = $this->degree_id;
  434. $title = $new_group->title;
  435. $icon_filename = $new_group->icon_filename;
  436. }
  437. else {
  438. // Was not already there; insert it.
  439. $group_n = new Group($cur["group_id"] . '_' . $this->degree_id, $this->db, $semester_num, $this->student_array_significant_courses, $this->bool_use_draft, $cur["group_requirement_type"]);
  440. $group_n->hours_required = $cur["group_hours_required"] * 1;
  441. if (!isset($group_n->hours_required_by_type[$cur["group_requirement_type"]])) $group_n->hours_required_by_type[$cur["group_requirement_type"]] = 0;
  442. $group_n->hours_required_by_type[$cur["group_requirement_type"]] += $group_n->hours_required;
  443. $group_n->set_req_by_degree_id($this->degree_id);
  444. if (trim($cur["group_min_grade"]) != "")
  445. {
  446. $group_n->assign_min_grade(trim(strtoupper($cur["group_min_grade"])));
  447. }
  448. $title = $group_n->title;
  449. $icon_filename = $group_n->icon_filename;
  450. $this->list_groups->add($group_n);
  451. }
  452. // Add a placeholder to the Semester....
  453. $group_g = new Group();
  454. $group_g->bool_use_draft = $this->bool_use_draft;
  455. $group_g->group_id = $cur["group_id"] . '_' . $this->degree_id;
  456. $group_g->set_req_by_degree_id($this->degree_id);
  457. $group_g->load_descriptive_data();
  458. $group_g->set_requirement_type($cur["group_requirement_type"]);
  459. if (trim($cur["group_min_grade"]) != "")
  460. {
  461. $group_g->assign_min_grade(trim(strtoupper($cur["group_min_grade"])));
  462. }
  463. $group_g->assigned_to_semester_num = $semester_num;
  464. $group_g->title = "$title";
  465. $group_g->icon_filename = $icon_filename;
  466. $group_g->hours_required = intval($cur["group_hours_required"]);
  467. $group_g->min_hours_allowed = intval($cur["group_min_hours_allowed"]);
  468. $group_g->bool_placeholder = true;
  469. $obj_semester->list_groups->add($group_g);
  470. }// if group_id > 0
  471. } // while db results
  472. $this->list_groups->sort_priority();
  473. if (!in_array($this->degree_id, $exclude_degree_ids)) {
  474. $group_course_id_array = $this->list_groups->get_group_course_id_array();
  475. // Add to our required_course_id_array.
  476. foreach($group_course_id_array as $group_id => $details) {
  477. foreach ($group_course_id_array[$group_id] as $course_id => $val) {
  478. $this->required_course_id_array[$course_id][$this->degree_id][$group_id] = $val;
  479. }
  480. }
  481. }
  482. // When we load this degree plan, let's also check for any hooks.
  483. // Since this class might be used outside of FP, only do this if we know
  484. // that the bootstrap.inc file has been executed.
  485. if ($GLOBALS["fp_bootstrap_loaded"] == TRUE) {
  486. invoke_hook("degree_plan_load", array(&$this));
  487. }
  488. } // load_degree_plan
  489. /**
  490. * Add another degree's required_course_id_array onto this one's.
  491. */
  492. function add_to_required_course_id_array($req_course_id_array) {
  493. foreach ($req_course_id_array as $course_id => $details) {
  494. foreach ($req_course_id_array[$course_id] as $degree_id => $details2) {
  495. foreach ($req_course_id_array[$course_id][$degree_id] as $group_id => $val) {
  496. $this->required_course_id_array[$course_id][$degree_id][$group_id] = $val;
  497. }
  498. }
  499. }
  500. } // add_to_required_course_id_array
  501. /**
  502. * This function will parse through the db_track_selection_config string and
  503. * populate the track_selection_config_array.
  504. *
  505. * We assume the string looks like this:
  506. *
  507. * CLASS ~ MIN ~ MAX ~ DEFAULT_CSV
  508. *
  509. * ex:
  510. * CONCENTRATION ~ 0 ~ 1 ~
  511. * EMPHASIS ~ 1 ~ 1 ~ ART|_SCULT, ART|_PAINT
  512. *
  513. *
  514. *
  515. */
  516. function parse_track_selection_config() {
  517. $lines = explode("\n", $this->db_track_selection_config);
  518. foreach ($lines as $line) {
  519. $line = trim($line);
  520. if ($line == "") continue; // blank line, skip it.
  521. if (substr($line, 0, 1) == "#") continue; // this is a comment, skip it.
  522. $temp = explode("~", $line);
  523. $machine_name = @trim($temp[0]);
  524. $min = @intval($temp[1]);
  525. $max = @intval($temp[2]);
  526. $default_csv = @trim($temp[3]);
  527. $this->track_selection_config_array[$machine_name] = array(
  528. "machine_name" => $machine_name,
  529. "min_tracks" => $min,
  530. "max_tracks" => $max,
  531. "default_tracks" => $default_csv,
  532. );
  533. }
  534. } // parse... config
  535. function get_title($bool_include_track = false)
  536. {
  537. // This will return the title of this degree, possibly
  538. // including the track's title as well.
  539. $rtn = $this->title;
  540. if ($bool_include_track == true)
  541. {
  542. if ($this->track_title != "")
  543. {
  544. $rtn .= " with " . $this->track_title . "";
  545. }
  546. }
  547. return $rtn;
  548. }
  549. /**
  550. * Returns back a CSV of all the major codes that this degree comprises
  551. */
  552. function get_major_code_csv() {
  553. if (!$this->is_combined_dynamic_degree_plan) return $this->major_code; // just a basic single non-combined degree.
  554. // Otherwise, we should assume this is a combined dynamic degree.
  555. $rtn = "";
  556. foreach ($this->combined_degree_ids_array as $degree_id) {
  557. $t_degree_plan = new DegreePlan($degree_id);
  558. $rtn .= $t_degree_plan->major_code . ",";
  559. }
  560. $rtn = rtrim($rtn, ","); // remove last comma, if its there.
  561. return $rtn;
  562. }
  563. function get_track_title($bool_include_classification = FALSE) {
  564. $this->load_descriptive_data();
  565. $ttitle = trim($this->track_title);
  566. if ($ttitle == "") return FALSE; // there is no track?
  567. if ($bool_include_classification) {
  568. $details = fp_get_degree_classification_details($this->degree_class);
  569. $ttitle .= " (" . $details["title"] . ")";
  570. }
  571. return $ttitle;
  572. }
  573. function get_title2($bool_include_classification = FALSE, $bool_include_track_title = FALSE, $bool_include_html = TRUE)
  574. {
  575. // This will simply return the degree's title. If it does not
  576. // exist, it will try to find another degree with the same major_code.
  577. // This is to fix the problem with students with catalog years outside
  578. // of FlightPath's database, but with major codes that have titles.
  579. if (!$this->bool_loaded_descriptive_data) {
  580. $this->load_descriptive_data();
  581. }
  582. $dtitle = "";
  583. if ($this->title != "") {
  584. $dtitle = $this->title;
  585. if ($bool_include_html) {
  586. $dtitle = "<span class='deg-title'>$this->title</span>";
  587. }
  588. }
  589. else {
  590. // Still no title? Try to load ANY degree title with this degree's
  591. // major_code.
  592. $table_name = "degrees";
  593. if ($this->bool_use_draft) {$table_name = "draft_$table_name";}
  594. $res = $this->db->db_query("SELECT title FROM $table_name
  595. WHERE major_code = ?
  596. ORDER BY catalog_year DESC LIMIT 1", $this->major_code);
  597. $cur = $this->db->db_fetch_array($res);
  598. $this->title = $cur["title"];
  599. if ($bool_include_html) {
  600. $dtitle = "<span class='deg-title'>$this->title</span>";
  601. }
  602. else {
  603. $dtitle = $this->title;
  604. }
  605. }
  606. if ($bool_include_track_title && $this->track_title != "") {
  607. if ($bool_include_html) {
  608. $dtitle .= "<span class='level-3-raquo'>&raquo;</span>";
  609. $dtitle .= "<span class='deg-track-title'>$this->track_title</span>";
  610. }
  611. else {
  612. $dtitle .= $this->track_title;
  613. }
  614. }
  615. if ($bool_include_classification && $this->degree_class != "") {
  616. $details = fp_get_degree_classification_details($this->degree_class);
  617. if ($bool_include_html) {
  618. $dtitle .= " <span class='deg-class-title'>(" . $details["title"] . ")</span>";
  619. }
  620. else {
  621. $dtitle .= " (" . $details["title"] . ")";
  622. }
  623. }
  624. return $dtitle;
  625. }
  626. function load_descriptive_data()
  627. {
  628. $this->bool_loaded_descriptive_data = TRUE;
  629. // If we already have this in our cache, then look there...
  630. $cache_name = 'degreeplan_cache';
  631. if ($this->bool_use_draft) {
  632. $cache_name = 'degreeplan_cache_draft';
  633. }
  634. if (isset($GLOBALS[$cache_name][$this->degree_id])) {
  635. $this->array_semester_titles = $GLOBALS[$cache_name][$this->degree_id]['array_semester_titles'];
  636. $this->bool_has_tracks = $GLOBALS[$cache_name][$this->degree_id]['bool_has_tracks'];
  637. $this->catalog_year = $GLOBALS[$cache_name][$this->degree_id]['catalog_year'];
  638. $this->db_advising_weight = $GLOBALS[$cache_name][$this->degree_id]['db_advising_weight'];
  639. $this->db_allow_dynamic = $GLOBALS[$cache_name][$this->degree_id]['db_allow_dynamic'];
  640. $this->db_exclude = $GLOBALS[$cache_name][$this->degree_id]['db_exclude'];
  641. $this->db_override_degree_hours = $GLOBALS[$cache_name][$this->degree_id]['db_override_degree_hours'];
  642. $this->degree_class = $GLOBALS[$cache_name][$this->degree_id]['degree_class'];
  643. $this->degree_level = $GLOBALS[$cache_name][$this->degree_id]['degree_level'];
  644. if ($this->degree_level == "") {
  645. $this->degree_level = "UG"; // undergrad by default
  646. }
  647. $this->degree_type = $GLOBALS[$cache_name][$this->degree_id]['degree_type'];
  648. $this->major_code = $GLOBALS[$cache_name][$this->degree_id]['major_code'];
  649. $this->public_notes_array = $GLOBALS[$cache_name][$this->degree_id]['public_notes_array'];
  650. $this->title = $GLOBALS[$cache_name][$this->degree_id]['title'];
  651. $this->track_code = $GLOBALS[$cache_name][$this->degree_id]['track_code'];
  652. $this->track_description = $GLOBALS[$cache_name][$this->degree_id]['track_description'];
  653. $this->track_title = $GLOBALS[$cache_name][$this->degree_id]['track_title'];
  654. return;
  655. }
  656. $table_name = "degrees";
  657. if ($this->bool_use_draft) {$table_name = "draft_$table_name";}
  658. $res = $this->db->db_query("SELECT * FROM $table_name
  659. WHERE degree_id = ? ", $this->degree_id);
  660. if ($this->db->db_num_rows($res) > 0)
  661. {
  662. $cur = $this->db->db_fetch_array($res);
  663. $this->major_code = $cur["major_code"];
  664. $this->degree_level = strtoupper(trim($cur["degree_level"]));
  665. if ($this->degree_level == "") {
  666. $this->degree_level = "UG"; // undergrad by default
  667. }
  668. $this->degree_class = $cur["degree_class"];
  669. $this->db_override_degree_hours = $cur["override_degree_hours"];
  670. $this->title = $cur["title"];
  671. $this->public_notes_array[$this->degree_id] = $cur["public_note"];
  672. $this->catalog_year = $cur["catalog_year"];
  673. $this->degree_type = trim($cur["degree_type"]);
  674. $this->db_exclude = trim($cur["exclude"]);
  675. $this->db_allow_dynamic = trim($cur["allow_dynamic"]);
  676. $this->db_advising_weight = intval($cur["advising_weight"]);
  677. // Get the semester titles.
  678. $temp = trim($cur["semester_titles_csv"]);
  679. $this->array_semester_titles = explode(",",$temp);
  680. $just_major_code = $this->major_code;
  681. if (strstr($this->major_code, "_"))
  682. {
  683. // This means that there is a track. Get all the information
  684. // you can about it.
  685. $temp = explode("_", $this->major_code);
  686. $this->track_code = trim($temp[1]);
  687. $just_major_code = trim($temp[0]); // Don't change major_code value-- causes a bug in FP5
  688. // The major_code might now have a | at the very end. If so,
  689. // get rid of it.
  690. $just_major_code = rtrim($just_major_code, "|");
  691. // Now, look up information on the track.
  692. $table_name = "degree_tracks";
  693. if ($this->bool_use_draft) {$table_name = "draft_$table_name";}
  694. static $degree_track_cache = array();
  695. $cur = null;
  696. if (isset($degree_track_cache[$table_name][$this->major_code][$this->track_code][$this->catalog_year])) {
  697. $cur = $degree_track_cache[$table_name][$this->major_code][$this->track_code][$this->catalog_year];
  698. }
  699. else {
  700. $res = $this->db->db_query("SELECT track_title, track_description FROM $table_name
  701. WHERE major_code = ?
  702. AND track_code = ?
  703. AND catalog_year = ? ", $just_major_code, $this->track_code, $this->catalog_year);
  704. $cur = $this->db->db_fetch_array($res);
  705. $degree_track_cache[$table_name][$just_major_code][$this->track_code][$this->catalog_year] = $cur;
  706. }
  707. $this->track_title = $cur["track_title"];
  708. $this->track_description = $cur["track_description"];
  709. }
  710. // Does this major have any tracks at all? If so, set a bool.
  711. if ($this->db->get_degree_tracks($just_major_code, $this->catalog_year))
  712. {
  713. $this->bool_has_tracks = true;
  714. }
  715. }
  716. // Add to our GLOBALS cache for this degree's descriptive data.
  717. $GLOBALS[$cache_name][$this->degree_id] = array(
  718. 'array_semester_titles' => $this->array_semester_titles,
  719. 'bool_has_tracks' => $this->bool_has_tracks,
  720. 'catalog_year' => $this->catalog_year,
  721. 'db_advising_weight' => $this->db_advising_weight,
  722. 'db_allow_dynamic' => $this->db_allow_dynamic,
  723. 'db_exclude' => $this->db_exclude,
  724. 'db_override_degree_hours' => $this->db_override_degree_hours,
  725. 'degree_class' => $this->degree_class,
  726. 'degree_level' => $this->degree_level,
  727. 'degree_type' => $this->degree_type,
  728. 'major_code' => $this->major_code,
  729. 'public_notes_array' => $this->public_notes_array,
  730. 'title' => $this->title,
  731. 'track_code' => $this->track_code,
  732. 'track_description' => $this->track_description,
  733. 'track_title' => $this->track_title
  734. );
  735. }
  736. function get_advised_courses_list()
  737. {
  738. // Return a courseList object containing every course
  739. // in this degreePlan which is marked as boolAdvisedToTake=true.
  740. $rtn_list = new CourseList();
  741. $this->list_semesters->reset_counter();
  742. while ($this->list_semesters->has_more())
  743. {
  744. $semester = $this->list_semesters->get_next();
  745. $rtn_list->add_list($semester->list_courses->get_advised_courses_list());
  746. }
  747. $rtn_list->add_list($this->list_groups->get_advised_courses_list());
  748. return $rtn_list;
  749. }
  750. /**
  751. * Look through our list of semesters for the one with this semester_num, and return it, or return FALSE
  752. */
  753. function get_semester($semester_num) {
  754. $this->list_semesters->reset_counter();
  755. while ($this->list_semesters->has_more()) {
  756. $sem = $this->list_semesters->get_next();
  757. if ($sem->semester_num == $semester_num) {
  758. return $sem;
  759. }
  760. }
  761. return FALSE;
  762. }
  763. /**
  764. * Returns a simple array with values seperated by " ~~ "
  765. * in this order: track_code ~~ track_title ~~ trackDesc ~~ track's degree id
  766. *
  767. * @return array
  768. */
  769. function get_available_tracks()
  770. {
  771. $rtn_array = array();
  772. $rtn_array[] = " ~~ None ~~ Select this option to display
  773. the base degree plan (may not be available for all majors).";
  774. $table_name = "degree_tracks";
  775. $table_name2 = "degrees";
  776. if ($this->bool_use_draft) {$table_name = "draft_$table_name";}
  777. if ($this->bool_use_draft) {$table_name2 = "draft_$table_name2";}
  778. $res = db_query("SELECT track_code, track_title, track_description FROM $table_name
  779. WHERE major_code = '?'
  780. AND catalog_year = '?'
  781. ORDER BY track_title ", $this->major_code, $this->catalog_year);
  782. while($cur = db_fetch_array($res))
  783. {
  784. $track_code = $cur["track_code"];
  785. $track_title = $cur["track_title"];
  786. $track_description = $cur["track_description"];
  787. //adminDebug($track_code);
  788. // Let's also get the degree_id for this particular track.
  789. $track_degree_id = $this->db->get_degree_id($this->major_code . "|_" . $track_code, $this->catalog_year, $this->bool_use_draft);
  790. // Also find out what is the degree_class for this degree_id.
  791. $degree_class = @trim(db_result(db_query("SELECT degree_class FROM $table_name2
  792. WHERE degree_id = ?", $track_degree_id)));
  793. $rtn_array[] = "$track_code ~~ $track_title ~~ $track_description ~~ $track_degree_id ~~ $degree_class";
  794. }
  795. if (count($rtn_array) > 1) // we're going to have at least 1 because of the "none" option. Let's skip that one.
  796. {
  797. return $rtn_array;
  798. } else {
  799. return false;
  800. }
  801. }
  802. function add_semester_developmental($student_id)
  803. {
  804. // This will add the developmental courses in as
  805. // a semester. Will check the studentID to see if any
  806. // developmentals are required.
  807. // -55 is the developmental semester.
  808. $sem = new Semester(DegreePlan::SEMESTER_NUM_FOR_DEVELOPMENTALS);
  809. $sem->title = variable_get("developmentals_title", t("Developmental Requirements"));
  810. $is_empty = true;
  811. $temp_array = $this->db->get_developmental_requirements($student_id);
  812. // We expect this to give us back an array like:
  813. // 0 => ART~101
  814. // 1 => MATH~090
  815. foreach($temp_array as $temp_course_name) {
  816. $temp = explode("~", $temp_course_name);
  817. $c = new Course($this->db->get_course_id($temp[0], $temp[1]));
  818. $c->min_grade = "C";
  819. $c->requirement_type = "dev";
  820. $sem->list_courses->add($c);
  821. $is_empty = false;
  822. }
  823. $sem->notice = variable_get("developmentals_notice", t("According to our records, you are required to complete the course(s) listed above. For some transfer students, your record may not be complete. If you have any questions, please ask your advisor."));
  824. if (!$is_empty)
  825. {
  826. $this->list_semesters->add($sem);
  827. }
  828. }
  829. function add_semester_courses_added()
  830. {
  831. // The "Add a Course" box on screen is really just a
  832. // semester, with the number -88, with a single group,
  833. // also numbered -88.
  834. $semester_courses_added = new Semester(DegreePlan::SEMESTER_NUM_FOR_COURSES_ADDED);
  835. $semester_courses_added->title = t("Courses Added by Advisor");
  836. // Now, we want to add the Add a Course group...
  837. $g = new Group();
  838. $g->group_id = DegreePlan::GROUP_ID_FOR_COURSES_ADDED;
  839. // Since it would take a long time during page load, we will
  840. // leave this empty of courses for now. It doesn't matter anyway,
  841. // as we will not be checking this group for course membership
  842. // anyway. We only need to load it in the popup.
  843. $g->hours_required = 99999; // Nearly infinite selections may be made.
  844. $g->assigned_to_semester_num = DegreePlan::SEMESTER_NUM_FOR_COURSES_ADDED;
  845. $semester_courses_added->list_groups->add($g);
  846. $this->list_semesters->add($semester_courses_added);
  847. // Also, add it to the list of groups OUTSIDE of semesters.
  848. $this->list_groups->add($g);
  849. }
  850. function find_group($group_id)
  851. {
  852. // Locate the group with group_id in the
  853. // list of groups, and return it.
  854. $this->list_groups->reset_counter();
  855. while($this->list_groups->has_more())
  856. {
  857. $group = $this->list_groups->get_next();
  858. if ($group->group_id == $group_id)
  859. {
  860. return $group;
  861. }
  862. if (!$group->list_groups->is_empty)
  863. {
  864. $group->list_groups->reset_counter();
  865. while($group->list_groups->has_more())
  866. {
  867. $branch = $group->list_groups->get_next();
  868. if ($branch->group_id == $group_id)
  869. {
  870. return $branch;
  871. }
  872. }
  873. }
  874. }
  875. return false;
  876. }
  877. function find_placeholder_group($group_id, $semester_num)
  878. {
  879. // Locate the group within the semesters that matches
  880. // this group_id and semesterNum. The assumption here
  881. // is that no one semester will list the same
  882. // group twice. In other words, Core Fine Arts
  883. // can only have 1 entry for Freshman Year.
  884. // Create a dummy semester with the correct semesterNum...
  885. $new_semester = new Semester($semester_num);
  886. // Create dummy group as well... don't use the constructor, just
  887. // set the group_id manually to same time. (no DB calls)
  888. $new_group = new Group();
  889. $new_group->group_id = $group_id;
  890. //print_pre($this->list_semesters->to_string());
  891. // Find the semester in the list of semesters with this same semesterNum...
  892. if (!$semester = $this->list_semesters->find_match($new_semester))
  893. {
  894. // The semester wasn't found!
  895. return false;
  896. }
  897. // Okay, now go through $semester and find the group_id...
  898. if (!$group = $semester->list_groups->find_match($new_group))
  899. {
  900. // It wasn't found in the top-level groups. Look one deeper...
  901. if (!$semester->list_groups->is_empty)
  902. {
  903. $semester->list_groups->reset_counter();
  904. while($semester->list_groups->has_more())
  905. {
  906. $group = $semester->list_groups->get_next();
  907. if ($g = $group->list_groups->find_match($new_group))
  908. {
  909. //$g->assign_to_semester($semester_num);
  910. return $g;
  911. }
  912. }
  913. }
  914. } else {
  915. // Meaning, we found it!
  916. //$group->assign_to_semester($semester_num);
  917. return $group;
  918. }
  919. return false;
  920. }
  921. /**
  922. * If degree_id != 0, then we will remove any course from the finished list that is NOT in the degree plan.
  923. * 0 means "give me all of matches back"
  924. */
  925. function find_courses($course_id, $group_id = 0, $semester_num, $degree_id = 0)
  926. {
  927. // This will locate a course within the degree plan, and return
  928. // back either that course object, or FALSE.
  929. $new_course = new Course($course_id);
  930. $new_semester = new Semester($semester_num);
  931. $rtn_course_list = new CourseList();
  932. // Okay, if the course is within a group, then
  933. // we can first use the find_group method.
  934. if ($group_id != "" && $group_id != 0)
  935. {
  936. if ($group = $this->find_group($group_id))
  937. {
  938. if (!($group->list_courses->is_empty))
  939. {
  940. if ($cL = $group->find_courses($new_course))
  941. {
  942. $rtn_course_list->add_list($cL);
  943. }
  944. }
  945. if (!($group->list_groups->is_empty))
  946. {
  947. // Look within each sub group for the course...
  948. $group->list_groups->reset_counter();
  949. while($group->list_groups->has_more())
  950. {
  951. $branch = $group->list_groups->get_next();
  952. if (!$branch->list_courses->is_empty)
  953. {
  954. if ($cL = $branch->find_courses($new_course))
  955. {
  956. $rtn_course_list->add_list($cL);
  957. }
  958. }
  959. // Here we can look for groups within groups...
  960. }
  961. }
  962. }
  963. return $rtn_course_list;
  964. } else if ($semester_num != -1) {
  965. // No group specified. This course is on the
  966. // bare degree plan. We were given a specific semester,
  967. // so try to find it there...
  968. if ($semester = $this->list_semesters->find_match($new_semester))
  969. {
  970. if ($cL = $semester->list_courses->find_all_matches($new_course))
  971. {
  972. if ($degree_id != 0) {
  973. // Trim $cL of any courses NOT in our supplied degree_id.
  974. $cL->remove_courses_not_in_degree($degree_id);
  975. if ($cL->get_size() == 0) return FALSE; // we removed them all!
  976. }
  977. $rtn_course_list->add_list($cL);
  978. return $rtn_course_list;
  979. }
  980. }
  981. } else if ($semester_num == -1)
  982. {
  983. // Meaning, we do not know which semester it goes in, so
  984. // attempt all semesters, and return with the first instance.
  985. $this->list_semesters->reset_counter();
  986. while($this->list_semesters->has_more())
  987. {
  988. $sem = $this->list_semesters->get_next();
  989. if ($cL = $sem->list_courses->find_all_matches($new_course))
  990. {
  991. $rtn_course_list->add_list($cL);
  992. return $rtn_course_list;
  993. }
  994. }
  995. }
  996. return FALSE;
  997. }
  998. function to_string()
  999. {
  1000. // Output this degree plan object in a helpful manner.
  1001. $rtn = "";
  1002. $rtn .= "Degree Plan: $this->title ($this->major_code) \n";
  1003. $rtn .= $this->list_semesters->to_string();
  1004. $rtn .= "----------------------------------------- \n";
  1005. $rtn .= "-- ALL GROUPS \n";
  1006. $rtn .= $this->list_groups->to_string();
  1007. return $rtn;
  1008. }
  1009. } // end class DegreePlan