system.module

  1. 7.x modules/system/system.module
  2. 6.x modules/system/system.module
  3. 5.x modules/system/system.module

File

modules/system/system.module
View source
  1. <?php
  2. /**
  3. * Implementation of hook_perm().
  4. * Expects to return an array of permissions recognized by
  5. * this module.
  6. *
  7. * Ex: $a = array(
  8. * "deCanDoSomething" => array (
  9. * "title" => "Can Do Something",
  10. * "description" => "Allow the user to do something."
  11. * )
  12. * );
  13. *
  14. */
  15. function system_perm() {
  16. $perms = array (
  17. "access_logged_in_content" => array(
  18. "title" => t("Access logged-in content"),
  19. "description" => t("This should be given to all authenticated users. It simply means
  20. the user is allowed to view the logged-in area of FlightPath."),
  21. ),
  22. "administer_modules" => array(
  23. "title" => t("Administer modules"),
  24. "description" => t("This will allow a user to install, enable, disable, and uninstall modules."),
  25. "admin_restricted" => TRUE, // means only appears for admin (user_id == 1)
  26. ),
  27. "clear_system_cache" => array(
  28. "title" => t("Clear system cache"),
  29. "description" => t("This will allow a to clear the system cache, including the menu router. Give to developers and/or admin users."),
  30. ),
  31. "access_popup_report_contact" => array(
  32. "title" => t("Access and Submit the report-contact form"),
  33. "description" => t("Only give to authenticated users. If allowed, users will see a link
  34. to 'Contact the FlightPath Production Team' at the bottom of most pages."),
  35. ),
  36. "run_cron" => array(
  37. "title" => t("Run Cron"),
  38. "description" => t("The user may run hook_cron functions at will. Causes a new menu link to appear
  39. on the admin page."),
  40. ),
  41. "de_can_administer_system_settings" => array(
  42. "title" => t("Administer System settings"),
  43. "description" => t("This allows the user to access and edit any of the FlightPath
  44. system settings."),
  45. ),
  46. "de_can_administer_school_data" => array(
  47. "title" => t("Administer School settings"),
  48. "description" => t("This allows the user to access and edit the 'Configure school settings' for FlightPath.
  49. For example, describing college and subject codes, policies for repeats, etc."),
  50. ),
  51. "view_fpm_debug" => array(
  52. "title" => t("View debug output from the fpm() function"),
  53. "description" => t("The user may view debug output from the fpm() function.
  54. Useful for developers."),
  55. "admin_restricted" => TRUE, // means only appears for admin (user_id == 1)
  56. ),
  57. "administer_ban_ip_settings" => array(
  58. "title" => t("Administer the ban IP settings"),
  59. "description" => t("The user may administer the settings related to when an IP address gets banned from the system."),
  60. "admin_restricted" => TRUE, // means only appears for admin (user_id == 1)
  61. ),
  62. "view_system_status" => array(
  63. "title" => t("View system status"),
  64. "description" => t("The user may view the update status and other requirements of the system."),
  65. ),
  66. "execute_php" => array(
  67. "title" => t("Execute PHP code"),
  68. "description" => t("WARNING: This is a very VERY powerful and DANGEROUS permission. Only give it to
  69. developers. An 'Execute PHP' link will appear on the admin menu, which
  70. lets the user execute any arbitrary PHP code."),
  71. "admin_restricted" => TRUE, // means only appears for admin (user_id == 1)
  72. ),
  73. );
  74. return $perms;
  75. }
  76. /**
  77. * Implements hook flightpath_can_assign_course_to_degree_id
  78. *
  79. */
  80. function system_flightpath_can_assign_course_to_degree_id($degree_id, Course $course) {
  81. // If this course has already been assigned to another degree, should we allow it to be assigned to THIS degree?
  82. $this_major_code = fp_get_degree_major_code($degree_id);
  83. $temp = explode("|", $this_major_code);
  84. $this_first_part = trim($temp[0]); // get just the first part. Ex, ENGL|_something => ENGL
  85. // If the configuration is such that a course cannot be assigned to a degree's tracks, then no.
  86. // Example: if a course has been assigned to ENGL, then it can't also get assigned to ENGL|_track1
  87. if (variable_get_for_school("prevent_course_assignment_to_both_degree_and_track", "yes", $course->school_id) == 'yes') {
  88. // Begin by checking to see what degrees the course has already been assigned to (if any)
  89. if (count($course->assigned_to_degree_ids_array)) {
  90. foreach ($course->assigned_to_degree_ids_array as $d_id) {
  91. $m_code = fp_get_degree_major_code($d_id);
  92. if ($m_code) {
  93. $temp = explode("|", $this_major_code);
  94. $m_code_first_part = trim($temp[0]); // get just the first part. Ex, ENGL|_something => ENGL
  95. // At this point, we have a major code for a degree which this course has already been assigned.
  96. // Ex: ENGL or ENGL|minor
  97. // If this degree is a track, the variable $this_major_code should equal ENGL|_whatever or, ENGL|minor_whatever.
  98. // If that condition is true, then we must return FALSE.
  99. // We will look for the presence of an underscore and a pipe symbol first, and see if this_first_part == m_code_first_part
  100. if (strstr($this_major_code, "_") && strstr($this_major_code, "|") && $this_first_part == $m_code_first_part) {
  101. // Now, if this_major_code CONTAINS m_code, then that means our condition
  102. // is true.
  103. if (strstr($this_major_code, $m_code)) {
  104. return FALSE; // meaning, no, we cannot assign this course!
  105. }
  106. }
  107. }
  108. }
  109. } //if count(assigned to degree ids array)
  110. } // if variable_get ...
  111. // Default, return TRUE
  112. return TRUE;
  113. } // hook_flightpath_can_assign_course_to_degree_id
  114. /**
  115. * Implements hook_fp_get_student_majors.
  116. *
  117. * In our case, we will use our database method and get directly from our student_degrees table.
  118. */
  119. function system_fp_get_student_majors($student_cwid, $bool_return_as_full_record = FALSE, $perform_join_with_degrees = TRUE, $bool_skip_directives = TRUE, $bool_check_for_allow_dynamic = TRUE, $school_id = 0) {
  120. $db = get_global_database_handler();
  121. $rtn = $db->get_student_majors_from_db($student_cwid, $bool_return_as_full_record, $perform_join_with_degrees, $bool_skip_directives, $bool_check_for_allow_dynamic, $school_id);
  122. return $rtn;
  123. }
  124. /**
  125. * Return an array containing the roles which have been assigned to
  126. * a specific user.
  127. */
  128. function system_get_roles_for_user($user_id) {
  129. $rtn = array();
  130. $res = db_query("SELECT * FROM user_roles a, roles b
  131. WHERE user_id = '?'
  132. AND a.rid = b.rid ", $user_id);
  133. while ($cur = db_fetch_array($res)) {
  134. $rtn[$cur["rid"]] = $cur["name"];
  135. }
  136. // Is this person in the users table? If so, they will get the rid 2 (authenticated)
  137. // If not, they will get the role 1 (anonymous)
  138. $res2 = db_query("SELECT user_id FROM users WHERE user_id = '?' AND user_id <> '0' ", $user_id);
  139. if (db_num_rows($res2) > 0) {
  140. $rtn[2] = t("authenticated user");
  141. }
  142. else {
  143. $rtn[1] = t("anonymous user");
  144. }
  145. return $rtn;
  146. }
  147. /**
  148. * This function will attempt to confirm that "clean URLs" is functioning, and
  149. * allowed on this server.
  150. *
  151. * Returns TRUE or FALSE
  152. */
  153. function system_check_clean_urls() {
  154. // Are clean-url's enabled?
  155. // We will do this by trying to retrieve a test URL, built into index.php.
  156. // If we can get a success message back from "http://example.com/flightpath/test-clean-urls/check", then
  157. // we are good to go.
  158. // First, figure out the base URL.
  159. $protocol = strpos(strtolower($_SERVER['SERVER_PROTOCOL']),'https') === FALSE ? 'http' : 'https';
  160. if ($protocol == 'http') {
  161. // Check to see if we have a REQUEST_SCHEME of https...
  162. if (($_SERVER['REQUEST_SCHEME'] ?? '') == 'https') {
  163. $protocol = "https";
  164. }
  165. }
  166. $host = $_SERVER['HTTP_HOST'];
  167. $script = $_SERVER['SCRIPT_NAME'];
  168. $base_url = $protocol . "://" . $host . $script;
  169. $base_url = str_replace("/install.php", "", $base_url);
  170. $base_url = str_replace("/index.php", "", $base_url);
  171. // Try to get our test URL's success message...
  172. $res = fp_http_request($base_url . '/test-clean-urls/check');
  173. if ($res->code != 200) {
  174. // There was an error or some other problem!
  175. // But wait-- did we get redirected?
  176. if (isset($res->redirect_code) && $res->redirect_code == 200) {
  177. return TRUE; // it's OK after all!
  178. }
  179. return FALSE;
  180. }
  181. // If we made it here, it must have worked.
  182. return TRUE;
  183. }
  184. /**
  185. * Hook block regions.
  186. *
  187. * This function simply defines which block regions we will handle. Each
  188. * block section should have a unique machine name, so it is best to namespace it with the
  189. * name of the module, then page or tab it appears on.
  190. *
  191. * The array looks like this:
  192. * return array(
  193. * "system_main" => array(
  194. * "title" => t("Main Tab"),
  195. * "regions" => array (
  196. * "left_col" => array("title" => t("Left Column")),
  197. * "right_col" => array("title" => t("Right Column")),
  198. * ),
  199. * ),
  200. * );
  201. *
  202. *
  203. * REMEMBER to make these machine-names, so only alpha numeric and underscores!
  204. */
  205. function system_block_regions() {
  206. return array(
  207. "system_main" => array(
  208. "title" => t("Main Tab"),
  209. "regions" => array (
  210. "left_col" => array("title" => t("Left Column")),
  211. "right_col" => array("title" => t("Right Column")),
  212. ),
  213. ),
  214. "system_login" => array(
  215. "title" => t("Login Page"),
  216. "regions" => array (
  217. "top" => array("title" => t("Top")),
  218. "left_col" => array("title" => t("Left Column")),
  219. "right_col" => array("title" => t("Right Column")),
  220. "bottom" => array("title" => t("Bottom")),
  221. ),
  222. ),
  223. );
  224. }
  225. function system_menu() {
  226. $items = array();
  227. $items["main"] = array(
  228. "title" => "Dashboard",
  229. "page_callback" => "system_display_dashboard_page",
  230. "access_callback" => TRUE,
  231. "type" => MENU_TYPE_NORMAL_ITEM,
  232. "weight" => 10,
  233. "page_settings" => array(
  234. "screen_mode" => "not_advising",
  235. ),
  236. );
  237. $items["render-advising-snapshot-for-iframe"] = array(
  238. "title" => "Dashboard",
  239. "page_callback" => "system_render_advising_snapshop_for_iframe",
  240. "access_arguments" => array("access_logged_in_content"),
  241. "type" => MENU_TYPE_NORMAL_ITEM,
  242. "weight" => 10,
  243. "page_settings" => array(
  244. "screen_mode" => "not_advising",
  245. ),
  246. );
  247. $items['login-help'] = array(
  248. "title" => "Login Help",
  249. "page_callback" => "system_display_login_help_page",
  250. "access_callback" => TRUE,
  251. );
  252. $items["install-finished"] = array(
  253. "title" => "Installation Finished",
  254. "page_callback" => "system_display_install_finished_page",
  255. "access_callback" => TRUE,
  256. "type" => MENU_TYPE_CALLBACK,
  257. );
  258. $items["login"] = array(
  259. "title" => "Login",
  260. "page_callback" => "system_display_login_page",
  261. "access_callback" => TRUE,
  262. "type" => MENU_TYPE_NORMAL_ITEM,
  263. );
  264. $items["mfa-login"] = array(
  265. "title" => "Multi-Factor Authentication",
  266. "page_callback" => "fp_render_form",
  267. "page_arguments" => array("system_mfa_login_form"),
  268. "access_callback" => TRUE,
  269. "type" => MENU_TYPE_NORMAL_ITEM,
  270. );
  271. $items["disable-login"] = array(
  272. "title" => "Login Disabled",
  273. "page_callback" => "system_display_disable_login_page",
  274. "access_callback" => TRUE,
  275. "type" => MENU_TYPE_NORMAL_ITEM,
  276. );
  277. $items["disable-student-login"] = array(
  278. "title" => "Student Logins Disabled",
  279. "page_callback" => "system_display_disable_login_page",
  280. "page_arguments" => array("student"),
  281. "access_callback" => TRUE,
  282. "type" => MENU_TYPE_NORMAL_ITEM,
  283. );
  284. $items["admin-tools/clear-cache"] = array(
  285. "title" => t("Clear all cache"),
  286. "description" => t("Clear and reset all cached items in FlightPath, including menu items, advising, etc."),
  287. "page_callback" => "system_perform_clear_cache",
  288. "access_arguments" => array("clear_system_cache"),
  289. "type" => MENU_TYPE_NORMAL_ITEM,
  290. "page_settings" => array(
  291. "menu_icon" => fp_get_module_path('system') . "/icons/arrow_refresh.png",
  292. ),
  293. );
  294. $items["admin/db-updates"] = array(
  295. "title" => "Run DB updates?",
  296. "page_callback" => "fp_render_form",
  297. "page_arguments" => array("system_confirm_db_updates_form"),
  298. "access_arguments" => array("administer_modules"),
  299. "type" => MENU_TYPE_NORMAL_ITEM,
  300. );
  301. $items["admin/completed-db-updates"] = array(
  302. "title" => "Database updates completed",
  303. "page_callback" => "system_display_completed_db_updates",
  304. "access_arguments" => array("administer_modules"),
  305. "page_settings" => array(
  306. "page_show_title" => TRUE,
  307. ),
  308. "type" => MENU_TYPE_NORMAL_ITEM,
  309. );
  310. //administer_ban_ip_settings
  311. $items["admin/config/ban-ip"] = array(
  312. "title" => "Ban IP settings",
  313. "description" => "Configure settings for how FlightPath bans IP addresses.",
  314. "page_callback" => "fp_render_form",
  315. "page_arguments" => array("system_ban_ip_settings_form", "system_settings"),
  316. "access_arguments" => array("administer_ban_ip_settings"),
  317. "page_settings" => array(
  318. "page_hide_report_error" => TRUE,
  319. "menu_icon" => fp_get_module_path('system') . "/icons/server_edit.png",
  320. "menu_links" => array(
  321. 0 => array(
  322. "text" => "Admin Console",
  323. "path" => "admin-tools/admin",
  324. "query" => "de_catalog_year=%DE_CATALOG_YEAR%",
  325. ),
  326. ),
  327. ),
  328. "type" => MENU_TYPE_NORMAL_ITEM,
  329. "tab_parent" => "admin-tools/admin",
  330. );
  331. $items["system/uninstall-module"] = array(
  332. "page_callback" => "system_handle_uninstall_module",
  333. "page_arguments" => array(2),
  334. "access_arguments" => array("administer_modules"),
  335. "type" => MENU_TYPE_CALLBACK,
  336. );
  337. $items["system-handle-form-submit"] = array(
  338. "page_callback" => "system_handle_form_submit",
  339. "access_callback" => TRUE,
  340. "type" => MENU_TYPE_CALLBACK,
  341. );
  342. $items["logout"] = array(
  343. "title" => "Logout",
  344. "page_callback" => "system_handle_logout",
  345. "access_callback" => TRUE,
  346. "type" => MENU_TYPE_CALLBACK,
  347. );
  348. $items["popup-report-contact"] = array(
  349. "title" => "Report/Contact",
  350. "page_callback" => "fp_render_form",
  351. "page_arguments" => array("system_popup_report_contact_form"),
  352. "access_arguments" => array('access_popup_report_contact'),
  353. "page_settings" => array(
  354. "page_is_popup" => TRUE,
  355. "page_hide_report_error" => TRUE,
  356. ),
  357. "type" => MENU_TYPE_CALLBACK,
  358. );
  359. $items["popup-contact-form/thank-you"] = array(
  360. "title" => "Report/Contact",
  361. "page_callback" => "system_popup_report_contact_thank_you",
  362. "access_callback" => TRUE,
  363. "page_settings" => array(
  364. "page_is_popup" => TRUE,
  365. "page_hide_report_error" => TRUE,
  366. ),
  367. "type" => MENU_TYPE_CALLBACK,
  368. );
  369. //////////////// Config (admin console main menu) /////////////
  370. $items["admin/config/run-cron"] = array(
  371. "title" => "Run cron now",
  372. "description" => "Run the normal cron operations right away",
  373. "page_callback" => "system_perform_run_cron",
  374. "access_arguments" => array("run_cron"),
  375. "page_settings" => array(
  376. "menu_icon" => fp_get_module_path('system') . "/icons/clock.png",
  377. ),
  378. "type" => MENU_TYPE_NORMAL_ITEM,
  379. );
  380. $items["admin/config/status"] = array(
  381. "title" => "System status",
  382. "description" => "View important notifications and updates for your installation of " . variable_get("system_name", "FlightPath"),
  383. "page_callback" => "system_display_status_page",
  384. "access_arguments" => array("view_system_status"),
  385. "page_settings" => array(
  386. "page_show_title" => TRUE,
  387. "menu_icon" => fp_get_module_path('system') . "/icons/application_view_list.png",
  388. "menu_links" => array(
  389. 0 => array(
  390. "text" => "Admin Console",
  391. "path" => "admin-tools/admin",
  392. "query" => "de_catalog_year=%DE_CATALOG_YEAR%",
  393. ),
  394. ),
  395. ),
  396. "type" => MENU_TYPE_NORMAL_ITEM,
  397. "tab_parent" => "admin-tools/admin",
  398. );
  399. $items["admin/config/system-settings"] = array(
  400. "title" => "System settings",
  401. "description" => "Configure settings for FlightPath",
  402. "page_callback" => "fp_render_form",
  403. "page_arguments" => array("system_settings_form", "system_settings"),
  404. "access_arguments" => array("de_can_administer_system_settings"),
  405. "page_settings" => array(
  406. "page_hide_report_error" => TRUE,
  407. "menu_icon" => fp_get_module_path('system') . "/icons/cog.png",
  408. "menu_links" => array(
  409. 0 => array(
  410. "text" => "Admin Console",
  411. "path" => "admin-tools/admin",
  412. "query" => "de_catalog_year=%DE_CATALOG_YEAR%",
  413. ),
  414. ),
  415. ),
  416. "type" => MENU_TYPE_NORMAL_ITEM,
  417. "tab_parent" => "admin-tools/admin",
  418. );
  419. $items["admin/config/school-data"] = array(
  420. "title" => "Configure school settings",
  421. "description" => "Configure school-specific data and settings",
  422. "page_callback" => "fp_render_form",
  423. "page_arguments" => array("system_school_data_form", "system_settings"),
  424. "access_arguments" => array("de_can_administer_school_data"),
  425. "page_settings" => array(
  426. "page_hide_report_error" => TRUE,
  427. "menu_icon" => fp_get_module_path('system') . "/icons/cog_edit.png",
  428. "menu_links" => array(
  429. 0 => array(
  430. "text" => "Admin Console",
  431. "path" => "admin-tools/admin",
  432. "query" => "de_catalog_year=%DE_CATALOG_YEAR%",
  433. ),
  434. ),
  435. ),
  436. "type" => MENU_TYPE_NORMAL_ITEM,
  437. "tab_parent" => "admin-tools/admin",
  438. );
  439. $items["admin/config/modules"] = array(
  440. "title" => "Modules",
  441. "description" => "Manage which modules are enabled for your site",
  442. "page_callback" => "fp_render_form",
  443. "page_arguments" => array("system_modules_form"),
  444. "access_arguments" => array("administer_modules"),
  445. "page_settings" => array(
  446. "page_hide_report_error" => TRUE,
  447. "menu_icon" => fp_get_module_path('system') . "/icons/bricks.png",
  448. "menu_links" => array(
  449. 0 => array(
  450. "text" => "Admin Console",
  451. "path" => "admin-tools/admin",
  452. "query" => "de_catalog_year=%DE_CATALOG_YEAR%",
  453. ),
  454. ),
  455. ),
  456. "type" => MENU_TYPE_NORMAL_ITEM,
  457. "tab_parent" => "admin-tools/admin",
  458. );
  459. $items["admin/config/clear-menu-cache"] = array(
  460. "title" => "Clear menu cache",
  461. "description" => "Clear and rebuild menus and URLs",
  462. "page_callback" => "system_perform_clear_menu_cache",
  463. "access_arguments" => array("clear_system_cache"),
  464. "type" => MENU_TYPE_NORMAL_ITEM,
  465. "page_settings" => array(
  466. "menu_icon" => fp_get_module_path('system') . "/icons/arrow_refresh.png",
  467. ),
  468. );
  469. $items["admin/config/execute-php"] = array(
  470. "title" => "Execute PHP",
  471. "description" => "Execute arbitrary PHP on your server. Caution: could be dangerous if not understood",
  472. "page_callback" => "fp_render_form",
  473. "page_arguments" => array("system_execute_php_form", "system_settings"),
  474. "access_arguments" => array("execute_php"),
  475. "page_settings" => array(
  476. "menu_icon" => fp_get_module_path('system') . "/icons/page_white_php.png",
  477. "page_hide_report_error" => TRUE,
  478. "menu_links" => array(
  479. 0 => array(
  480. "text" => "Admin Console",
  481. "path" => "admin-tools/admin",
  482. "query" => "de_catalog_year=%DE_CATALOG_YEAR%",
  483. ),
  484. ),
  485. ),
  486. "type" => MENU_TYPE_NORMAL_ITEM,
  487. "tab_parent" => "admin-tools/admin",
  488. );
  489. return $items;
  490. }
  491. /**
  492. * This form allows the user (an administrator) to configure how
  493. * FlightPath bans IP addresses.
  494. */
  495. function system_ban_ip_settings_form() {
  496. $form = array();
  497. $ban_ip_file_path = fp_get_files_path() . '/private/banned_ips.txt';
  498. $contents = 'File not found. Save form to create file.';
  499. if (file_exists($ban_ip_file_path)) {
  500. $contents = file_get_contents($ban_ip_file_path);
  501. }
  502. $form['mark_top'] = array(
  503. 'type' => 'markup',
  504. 'value' => "<p>". t('FlightPath is able to ban visitors by their IP addresses. Use this form to configure which
  505. IP addresses you wish to ban.') . "</p>
  506. <p>
  507. " . fp_render_c_fieldset("<strong>" . t("Ban IP file path:") . "</strong> $ban_ip_file_path</b>
  508. <br><textarea class='readonly' readonly=readonly rows='10' cols='80'>$contents</textarea>
  509. ", "Current contents of Ban IP file", TRUE) . "
  510. " . fp_render_c_fieldset("
  511. <code>
  512. sudo apt install php8.2-apcu (or use whichever version of PHP you are using. Ex: 8.3, 8.1, etc.)
  513. <br>sudo systemctl restart apache2
  514. </code><br><br>Be sure to restart Apache once this change is in place, and ensure evertyhing still works correctly.
  515. ", "Install the PHP APCu package for best performance (see Debian/Ubuntu instructions)", TRUE) . "
  516. </p>
  517. <p><b>You must save this form to create the banned_ips.txt file.</b></p>",
  518. );
  519. $form['ban_ip_file_path'] = array(
  520. 'type' => 'value',
  521. 'value' => $ban_ip_file_path,
  522. );
  523. // auto-ban settings.
  524. $form['system_enable_autoban_ip'] = array(
  525. 'type' => 'checkbox',
  526. 'label' => 'Enable auto-ban functionality?',
  527. 'value' => variable_get('system_enable_autoban_ip', TRUE),
  528. 'description' => t('If checked, FlightPath will automatically ban IP addresses which
  529. trigger too many (7) page_not_found or access_denied events within a given
  530. time period (2 seconds).'),
  531. );
  532. $form['system_manually_add_ip'] = array(
  533. 'label' => t('Manually add an IP address to the ban list:'),
  534. 'type' => 'text',
  535. );
  536. $options = array('never' => 'Never (permanently ban IP addresses)');
  537. $options["1 HOUR"] = "1 hour";
  538. for ($t = 2; $t <= 22; $t = $t + 2) {
  539. $options["$t HOURS"] = "$t hours";
  540. }
  541. $options["1 DAY"] = "1 day";
  542. for ($t = 2; $t <= 6; $t = $t + 1) {
  543. $options["$t DAYS"] = "$t days";
  544. }
  545. $options["1 WEEK"] = "1 week";
  546. for ($t = 2; $t <= 100; $t = $t + 2) {
  547. $options["$t WEEKS"] = "$t weeks";
  548. }
  549. $form['system_ban_ip_expire'] = array(
  550. 'label' => t('How long to wait until we remove a banned IP address?'),
  551. 'type' => 'select',
  552. 'hide_please_select' => 'TRUE',
  553. 'options' => $options,
  554. 'value' => variable_get('system_ban_ip_expire', '2 WEEKS'),
  555. );
  556. return $form;
  557. } // system_ban_ip_settings_form
  558. function system_ban_ip_settings_form_validate($form, &$form_state) {
  559. // Make sure our ban_ips.info file exists!
  560. $p = $form_state['values']['ban_ip_file_path'];
  561. if (!file_exists($p)) {
  562. $res = file_put_contents($p, "");
  563. if ($res === FALSE) {
  564. form_error('', t("Unable to create ban ip file: %p. Make sure the directory is writable by the web server. This
  565. file must be in place before making changes to Apache.", array('%p' => $p)));
  566. }
  567. }
  568. $manual_ip = $form_state['values']['system_manually_add_ip'] ?? '';
  569. if ($manual_ip != '' && !filter_var($manual_ip, FILTER_VALIDATE_IP)) {
  570. form_error('system_manually_add_ip', t('Sorry, the IP address you entered does not appear to be valid. Please try again.'));
  571. }
  572. }
  573. function system_ban_ip_settings_form_submit($form, &$form_state) {
  574. $manual_ip = trim($form_state['values']['system_manually_add_ip'] ?? '');
  575. $ban_file = $form_state['values']['ban_ip_file_path'];
  576. if ($manual_ip != '') {
  577. // Erase it from the ban_file if its already there
  578. $c = file_get_contents($ban_file);
  579. $c = str_replace("$manual_ip", '', $c);
  580. //$c = str_replace("\r\n", "\n", $c);
  581. $c = str_replace(" ", '', $c);
  582. $c = str_replace(" \n", "", $c);
  583. $c = str_replace("\n\n", "", $c);
  584. $c = trim($c);
  585. // Put into our file
  586. $res = file_put_contents($ban_file, "$c\n$manual_ip");
  587. if ($res === FALSE) {
  588. fp_add_message("Unable to write to the ban ip file. File permission issue?", 'error');
  589. }
  590. // Reset our apcu cache, if installed.
  591. if (function_exists('apcu_store')) {
  592. apcu_delete('banned_ips'); // force reload of banned ips next time a page is loaded.
  593. }
  594. // Also put in our table with current timestamp, so cron can expire it later.
  595. db_query("DELETE FROM banned_ips WHERE ip_address = ?", array($manual_ip));
  596. db_query("INSERT INTO banned_ips (ip_address, posted_ts) VALUES (?, ?)", array($manual_ip, time()));
  597. }
  598. }
  599. /**
  600. * Adds IP address to our banned_ip list.
  601. * @param unknown $ip
  602. */
  603. function system_ban_ip($ip, $comment = "") {
  604. $ban_ip_file_path = fp_get_files_path() . '/private/banned_ips.txt';
  605. $res = file_put_contents($ban_ip_file_path, "\n$ip", FILE_APPEND | LOCK_EX);
  606. if ($res === FALSE) {
  607. watchdog("ban_ip", "Unable to write to ban ip file. Possible file permission issue?", array(), WATCHDOG_ERROR);
  608. print "failed";
  609. return FALSE;
  610. }
  611. // Reset our apcu cache, if installed.
  612. if (function_exists('apcu_store')) {
  613. apcu_delete('banned_ips'); // force reload of banned ips next time a page is loaded.
  614. }
  615. // Also put in our table with current timestamp, so cron can expire it later (if setup).
  616. // Use REPLACE to avoid mysql errors for duplicate IPs
  617. db_query("REPLACE INTO banned_ips (ip_address, posted_ts) VALUES (?, ?)", array($ip, time()));
  618. watchdog("ban_ip", "Banned IP address: %ip; %comment", array("%ip" => $ip, "%comment" => $comment));
  619. return TRUE;
  620. } // system_ban_ip
  621. /**
  622. * This is called by theme.inc's functions display_not_found and display_access_denied.
  623. *
  624. * We want to see if we should ban this visitor's IP address for racking up too many page_not_found's or access_denied
  625. */
  626. function system_check_should_ban_ip() {
  627. global $user;
  628. $ip = $_SERVER["REMOTE_ADDR"] ?? '';
  629. if (empty($ip)) return FALSE;
  630. // Criteria: user is anonymous, and has had more than X "not found" or "access denied" attempts within Y minutes.
  631. if (intval($user->id ?? 0) === 0) {
  632. // check if autoban is enabled
  633. if (variable_get('system_enable_autoban_ip', TRUE) == FALSE) return;
  634. $max_allowed = 7; // TODO: a setting?
  635. $seconds = 2; // TODO: a setting?
  636. // Perform simple query on watchdog table
  637. $count = db_result(db_query("SELECT count(wid) as mycount FROM watchdog
  638. WHERE user_id = 0
  639. AND ip = ?
  640. AND `type` IN ('page_not_found', 'access_denied')
  641. AND `timestamp` > ?",
  642. array($ip, strtotime("NOW - $seconds SECONDS"))));
  643. /*
  644. // We also want to see if they did
  645. $safecount = db_result(db_query("SELECT count(wid) as mycount FROM watchdog
  646. WHERE user_id = 0
  647. AND ip = ?
  648. AND `type` NOT IN ('page_not_found', 'access_denied')
  649. AND `timestamp` > ?",
  650. array($ip, strtotime("NOW - $seconds SECONDS"))));
  651. if ($safecount) {
  652. if (intval($safecount) > 0) return;
  653. }
  654. */
  655. if ($count) {
  656. $count = intval($count);
  657. if ($count >= $max_allowed) {
  658. // Yes, we should ban them.
  659. system_ban_ip($ip, "Exceeded $max_allowed forbidden events in the past $seconds seconds.");
  660. sleep(2); // Force their browser or script or whatever to sleep for 2 seconds.
  661. }
  662. }
  663. }
  664. } // system_check_should_ban_ip
  665. /**
  666. * This page will be shown when the user clicks the "Need Help Logging In?" link on the login page.
  667. */
  668. function system_display_login_help_page() {
  669. //First, are we meant to redirect to a different piece of content? This is configured in the System Settings.
  670. $cid = intval(variable_get("login_help_cid", "0"));
  671. if ($cid > 0) {
  672. fp_goto("content/$cid");
  673. return;
  674. }
  675. // Else, display a generic message here.
  676. $rtn = "";
  677. $rtn .= t("If you need help logging in to FlightPath (eg, you forgot your password or do not have access), contact
  678. the system administrator or your IT department.");
  679. return $rtn;
  680. }
  681. /**
  682. * Used by the menu to determine if the user can access some basic information about the student (like Profile page, etc)
  683. */
  684. function system_can_access_student($student_id = "") {
  685. global $current_student_id, $user;
  686. if ($student_id == "") $student_id = $current_student_id;
  687. // must be logged in first...
  688. if (!user_has_permission("access_logged_in_content")) return FALSE;
  689. if ($student_id == "" || $student_id === 0) return FALSE;
  690. if ($user->id == 1) return TRUE; // the admin user.
  691. // Can the user view ANY advising session?
  692. if (user_has_permission("view_any_advising_session")) return TRUE;
  693. // can the user only see their own advisees, and is this student one of their advisees?
  694. if (user_has_permission("view_advisee_advising_session")) {
  695. // Is the student_id in their list of advisees?
  696. $advisees = advise_get_advisees();
  697. if (in_array($student_id, $advisees)) return TRUE;
  698. }
  699. // Is this user viewing THEIR OWN advising session?
  700. if (user_has_permission("view_own_advising_session")) {
  701. if ($student_id == $user->cwid && ($student_id != "" && $student_id !== 0)) return TRUE;
  702. }
  703. // All else fails, return FALSE
  704. return FALSE;
  705. }
  706. function system_display_disable_login_page($type = "all") {
  707. $rtn = "";
  708. if ($type == "all") {
  709. $rtn .= "<h2>Logins Currently Disabled</h2>
  710. We're sorry, but logins are disabled at this time due to maintenance on FlightPath.
  711. <br><br>Please try again later.
  712. <br><br>
  713. ";
  714. }
  715. if ($type == "student") {
  716. $rtn .= t("We're sorry, but student logins are disabled at this time.");
  717. }
  718. $rtn .= " " . l("Return to login page", "<front>") . "";
  719. return $rtn;
  720. }
  721. function system_execute_php_form() {
  722. $form = array();
  723. $m = 0;
  724. $form["mark" . $m++] = array(
  725. "value" => t("Use this form to execute arbitrary PHP code. <b>DO NOT</b>
  726. type php tags (&lt;php ?&gt;). Be careful! Entering bad code
  727. here can harm your site. Only use if you know what you are doing."),
  728. );
  729. $form["system_execute_php"] = array(
  730. "type" => "textarea",
  731. "label" => t("Enter PHP code here:"),
  732. "value" => variable_get("system_execute_php", ""),
  733. "rows" => 20,
  734. );
  735. return $form;
  736. }
  737. function system_execute_php_form_submit($form, $form_state) {
  738. $code = trim($form_state["values"]["system_execute_php"]);
  739. if ($code == "") return;
  740. if (user_has_permission("execute_php")) { // double-check one more time on this, just in case.
  741. try {
  742. $res = eval($code);
  743. // Check for errors less than PHP 7.
  744. if ($res === FALSE &&($error = error_get_last())) {
  745. fp_add_message("Error: " . $error["message"] . ". See line: " . $error["line"], "error");
  746. }
  747. }
  748. catch (ParseError $e) {
  749. // Catches PHP 7+ ParseError exceptions...
  750. fp_add_message("Exception detected: " . $e->getMessage() . ". See line: " . $e->getLine(), "error");
  751. }
  752. }
  753. }
  754. /**
  755. * Display a confirmation form before we run the db updates (hook_updates)
  756. *
  757. * @return unknown
  758. */
  759. function system_confirm_db_updates_form() {
  760. $form = array();
  761. $m = 0;
  762. $form["mark" . $m++] = array(
  763. "value" => t("Are you sure you wish to run the database updates?
  764. This will find modules which have been updated, and now need to
  765. make database changes.") . "
  766. <br><br>
  767. " . t("You should back up your entire database first, just in case a problem
  768. occurs!"),
  769. );
  770. $form["submit_btn"] = array(
  771. "type" => "submit",
  772. "spinner" => TRUE,
  773. "value" => t("Continue"),
  774. "prefix" => "<hr>",
  775. "suffix" => "&nbsp; &nbsp; <a href='javascript: history.go(-1);'>" . t("Cancel") . "</a>",
  776. );
  777. $form["mark" . $m++] = array(
  778. "value" => t("Press only once, as this make take several moments to run."),
  779. );
  780. return $form;
  781. }
  782. /**
  783. * Perform the actual hook_update calls here, send the user to a completed page.
  784. *
  785. * @param unknown_type $form
  786. * @param unknown_type $form_state
  787. */
  788. function system_confirm_db_updates_form_submit($form, $form_state) {
  789. // Since this could take a little while, let's use the batch system.
  790. $modules = array();
  791. // We need to find modules whose schema in their .info file
  792. // is different than what's in the database.
  793. $module_dirs = array();
  794. $module_dirs[] = array("start" => "modules", "type" => t("Core"));
  795. $module_dirs[] = array("start" => "custom/modules", "type" => t("Custom"));
  796. // We will also add any directories which begin with an underscore in the custom/modules directory.
  797. // For example: custom/modules/_contrib
  798. // Let's find such directories now.
  799. $dir_files = scandir("custom/modules");
  800. foreach ($dir_files as $file) {
  801. if ($file == '.' || $file == '..') continue;
  802. if (substr($file, 0, 1) == '_' && is_dir("custom/modules/$file")) {
  803. $module_dirs[] = array("start" => "custom/modules/$file", "type" => t("Custom/$file"));
  804. }
  805. }
  806. foreach ($module_dirs as $module_dir) {
  807. $start_dir = $module_dir["start"];
  808. if ($dh = opendir($start_dir)) {
  809. while ($file = readdir($dh)) {
  810. if ($file == "." || $file == "..") continue;
  811. if (is_dir($start_dir . "/" . $file)) {
  812. // Okay, now look inside and see if there is a .info file.
  813. if (file_exists("$start_dir/$file/$file.info")) {
  814. $module = $file;
  815. $info_contents = file_get_contents("$start_dir/$file/$file.info");
  816. // From the info_contents variable, split up and place into an array.
  817. $info_details_array = array();
  818. $lines = explode("\n", $info_contents);
  819. foreach ($lines as $line) {
  820. if (trim($line) == "") continue;
  821. $temp = explode("=", trim($line));
  822. $info_details_array[trim($temp[0])] = trim(substr($line, strlen($temp[0]) + 1));
  823. }
  824. $path = "$start_dir/$file";
  825. $res = db_query("SELECT * FROM modules WHERE path = ? ", $path);
  826. $cur = db_fetch_array($res);
  827. if ($cur) {
  828. $info_details_array["enabled"] = intval($cur["enabled"]);
  829. // Does this module need to run db updates?
  830. if (@$cur["enabled"] == 1 && @intval($cur["schema"]) != @intval($info_details_array["schema"]) && @$info_details_array["schema"] != "") {
  831. // Add to our list of modules to run in our batch operations.
  832. $modules[] = array(
  833. 'module' => $module,
  834. 'path' => $path,
  835. 'cur_schema' => intval($cur['schema']),
  836. 'schema' => intval($info_details_array['schema']),
  837. );
  838. } // if enabled & schema != db schema
  839. } // if cur
  840. } // if fileexists
  841. }//if isdir(file)
  842. }// while file = readdir()
  843. } // if opendir
  844. } // foreach module dirs as module_dir
  845. // Clear our cache
  846. //fp_clear_cache();
  847. //fp_goto("admin/completed-db-updates");
  848. // Okay, set up the batch....
  849. $batch = array(
  850. "operation" => array("system_perform_db_updates_perform_batch_operation", array($modules)),
  851. "title" => t("Performing Database Updates"),
  852. "progress_message" => "Processing @current of @total...",
  853. "display_percent" => TRUE,
  854. );
  855. $batch["finished_callback"] = array("system_finished_db_updates_finished");
  856. watchdog("admin", "Ran DB updates for modules");
  857. // Set the batch...
  858. batch_set($batch);
  859. }
  860. function system_finished_db_updates_finished($batch) {
  861. // Clear our cache, since menu options might have changed.
  862. fp_clear_cache();
  863. fp_goto("admin/config/modules");
  864. }
  865. /**
  866. * Performs db updates ONE module at a time.
  867. */
  868. function system_perform_db_updates_perform_batch_operation(&$batch, $modules) { // if this is our first time through, let's init our values.
  869. if (!isset($batch["results"]["total"])) {
  870. // Our first time through. Let's start up.
  871. $batch["results"]["total"] = count($modules);
  872. $batch["results"]["current"] = 0;
  873. $batch["results"]["finished"] = FALSE;
  874. }
  875. $t = intval($batch["results"]["current"]);
  876. if (!isset($modules[$t])) {
  877. $modules[$t] = array(); // not set, so just make it a blank array.
  878. }
  879. $module = fp_trim(@$modules[$t]['module']);
  880. $path = @$modules[$t]['path'];
  881. $cur_schema = @$modules[$t]['cur_schema'];
  882. $schema = @$modules[$t]['schema'];
  883. if ($module) {
  884. // If the module has a .install file, begin by including it.
  885. if (include_module_install($module, $path)) {
  886. // Include the original module file first.
  887. include_module($module, TRUE, $path);
  888. // Now, we can call hook_update, if it exists.
  889. if (function_exists($module . '_update')) {
  890. call_user_func_array($module . '_update', array($cur_schema, $schema));
  891. }
  892. }
  893. // Okay, update the modules table for this module, and set schema to correct version.
  894. $res = db_query("UPDATE modules
  895. SET `schema` = ?
  896. WHERE path = ? LIMIT 1 ", $schema, $path);
  897. fp_add_message(t("The module %module has run its DB updates.", array("%module" => $module)));
  898. }
  899. // Update our values.
  900. $batch["results"]["current"] = $t + 1; // go to next one.
  901. // Have we finished?
  902. if (intval($batch["results"]["current"]) >= intval($batch["results"]["total"])) {
  903. $batch["results"]["finished"] = TRUE;
  904. }
  905. } // system_perform_db_updates_perform_batch_operation
  906. /**
  907. * Once db updates are run, display contents of this page.
  908. *
  909. */
  910. function system_display_completed_db_updates() {
  911. $rtn = "";
  912. $rtn .= t("Database updates have been completed. If you do not see
  913. any errors displayed, it means everything was run correctly.");
  914. $rtn .= "<br><br>
  915. <ul>";
  916. $rtn .= "<li>" . l(t("Return to Admin"), "admin-tools/admin") . "</li>
  917. <li>" . l(t("Return to Modules page"), "admin/config/modules") . "</li>
  918. </ul>";
  919. return $rtn;
  920. }
  921. /**
  922. * This page is displayed to the user once FlightPath has been installed.
  923. */
  924. function system_display_install_finished_page() {
  925. $rtn = "";
  926. // Rebuild one more time
  927. menu_rebuild_cache(FALSE);
  928. fp_show_title(TRUE);
  929. $rtn .= t("Your new installation of FlightPath is now complete.
  930. <br><br>
  931. As a security precaution, you should:
  932. <ul>
  933. <li>change the permissions
  934. on custom/settings.php so that it cannot be read or written to by unauthorized
  935. users.</li>
  936. <li>You should also rename or remove install.php so that web visitors cannot
  937. access it.</li>
  938. </ul>
  939. If you need to re-install FlightPath, delete custom/settings.php, and drop all of the tables
  940. in the database, then re-access install.php.") . "<br><br>";
  941. $rtn .= l(t("Access your new FlightPath site now."), "<front>");
  942. return $rtn;
  943. }
  944. /**
  945. * This is the thank you page you see after submitting the contact form.
  946. */
  947. function system_popup_report_contact_thank_you() {
  948. $rtn = "";
  949. $rtn .= "<p>";
  950. $rtn .= t("Thank you for submitting to the @FlightPath Production Team. We
  951. have received your comment and will review it shortly.", array("@FlightPath" => variable_get("system_name", "FlightPath"))) . "<br><br>";
  952. $rtn .= t("You may now close this window.");
  953. $rtn .= "</p>";
  954. $rtn .= "<p>" . "<a href='javascript:parent.fpCloseLargeIframeDialog();' class='button'>" . t("Close Window") . "</a></p>";
  955. return $rtn;
  956. }
  957. /**
  958. * This is the form which lets users send an email to the FlightPath production
  959. * team,
  960. */
  961. function system_popup_report_contact_form() {
  962. $form = array();
  963. fp_set_title("");
  964. $m = 0;
  965. $form["mark" . $m++] = array(
  966. "value" => "<p>" . t("If you've noticed an error or have a suggestion, use this
  967. form to contact the @FlightPath Production Team.", array("@FlightPath" => variable_get("system_name", "FlightPath"))) . "</p>",
  968. );
  969. $form["category"] = array(
  970. "type" => "select",
  971. "label" => t("Please select a category"),
  972. "options" => array(
  973. t("Dashboard") => t("Dashboard"),
  974. t("Appointments") => t("Appointments"),
  975. t("Advising") => t("Advising"),
  976. t("Degree plan") => t("Degree plan"),
  977. t("What If?") => t("What If?"),
  978. t("Searching") => t("Searching"),
  979. t("Comments") => t("Comments"),
  980. t("Audit") => t("Audit"),
  981. t("Engagements") => t("Engagements"),
  982. t("Sending/Receiving Text") => t("Sending/Receiving Text"),
  983. t("Reports") => t("Reports/Analytics"),
  984. t("Other") => t("Other"),
  985. ),
  986. );
  987. $form["comment"] = array(
  988. "type" => "textarea",
  989. "rows" => 7,
  990. "label" => t("Comment:"),
  991. );
  992. $form["submit"] = array(
  993. "type" => "submit",
  994. "spinner" => TRUE,
  995. "value" => t("Send email"),
  996. );
  997. $form["#redirect"] = array("path" => "popup-contact-form/thank-you");
  998. return $form;
  999. }
  1000. function system_popup_report_contact_form_submit($form, $form_state) {
  1001. global $user;
  1002. $category = filter_markup($form_state["values"]["category"],'plain');
  1003. $comment = filter_markup($form_state["values"]["comment"], 'plain');
  1004. $possible_student = filter_markup(@$_SESSION['last_student_selected'], 'plain');
  1005. $user_roles = filter_markup(implode(", ", $user->roles), 'plain');
  1006. $datetime = date("Y-m-d H:i:s", convert_time(strtotime("now")));
  1007. //$headers = "From: FlightPath-noreply@noreply.com\n";
  1008. $subject = t("FLIGHTPATH REPORT CONTACT") . " - $category ";
  1009. $msg = "";
  1010. $msg .= t("You have received a new report/contact on") . " $datetime.\n";
  1011. $msg .= t("Name:") . " $user->f_name $user->l_name ($user->name) CWID: $user->cwid \n" . t("User roles:") . " $user_roles \n\n";
  1012. $msg .= t("Category:") . " $category \n";
  1013. $msg .= t("Last Student Selected:") . " $possible_student \n";
  1014. $msg .= t("Comment:") . " \n $comment \n\n";
  1015. $msg .= "------------------------------------------------ \n";
  1016. $themd5 = md5($user->name . $user->cwid . $comment . $user_roles . $category);
  1017. if (!isset($_SESSION["da_error_report_md5"]) || $_SESSION["da_error_report_md5"] != $themd5)
  1018. { // Helps stop people from resubmitting over and over again
  1019. // (by hitting refresh, or by malicious intent)..
  1020. $to = variable_get("contact_email_address", "");
  1021. if ($to != "") {
  1022. fp_mail($to,$subject,$msg);
  1023. }
  1024. }
  1025. $_SESSION["da_error_report_md5"] = $themd5;
  1026. watchdog("admin", "Sent message with popup report contact form: Category: $category; Comment: $comment");
  1027. }
  1028. /**
  1029. * This form is for the school-data, like subject code descriptions, colleges, etc.
  1030. *
  1031. */
  1032. function system_school_data_form($school_id = 0) {
  1033. $form = array();
  1034. $m = 0;
  1035. $school_id = intval($school_id);
  1036. $fs = ""; // The field name suffix. We will add this to the end of all of our field names. If this is the default school, leave blank.
  1037. if (module_enabled("schools")) {
  1038. $school_name = schools_get_school_name_for_id($school_id);
  1039. fp_set_title(t("Configure %school school settings", array('%school' => $school_name)));
  1040. if ($school_id !== 0) {
  1041. $fs = "~~school_" . $school_id;
  1042. }
  1043. }
  1044. $form['school_id'] = array(
  1045. 'type' => 'hidden',
  1046. 'value' => $school_id,
  1047. );
  1048. $form["school_initials" . $fs] = array(
  1049. "type" => "textfield",
  1050. "size" => 20,
  1051. "label" => t("School initials:"),
  1052. "value" => variable_get_for_school("school_initials", "DEMO", $school_id, TRUE),
  1053. "description" => t("Ex: UFP or BQCC"),
  1054. );
  1055. $form["school_name" . $fs] = array(
  1056. "type" => "textfield",
  1057. "size" => 20,
  1058. "label" => t("School name:"),
  1059. "value" => variable_get_for_school("school_name", "Demo University", $school_id, TRUE),
  1060. "description" => t("Ex: University of FlightPath"),
  1061. );
  1062. $form["earliest_catalog_year" . $fs] = array(
  1063. "type" => "textfield",
  1064. "size" => 20,
  1065. "label" => t("Earliest catalog year:"),
  1066. "value" => variable_get_for_school("earliest_catalog_year", "2006", $school_id, TRUE),
  1067. "description" => t("This is the earliest possible catalog year FlightPath may look
  1068. up. Typically, this is the earliest year for which you have
  1069. data (ie, when FlightPath went online.
  1070. If FlightPath cannot figure out a catalog year to use,
  1071. it will default to this one. Ex: 2006"),
  1072. );
  1073. $form["graduate_level_course_num" . $fs] = array(
  1074. "type" => "textfield",
  1075. "size" => 20,
  1076. "label" => t("Graduate level course num:"),
  1077. "value" => variable_get_for_school("graduate_level_course_num", "5000", $school_id, TRUE),
  1078. "description" => t("This is the course num which means 'graduate level', meaning
  1079. anything above it is considered a graduate level course. Ex: 5000"),
  1080. );
  1081. $form["hiding_grades_message" . $fs] = array(
  1082. "type" => "textarea",
  1083. "label" => t("Hiding grades message:"),
  1084. "value" => variable_get_for_school("hiding_grades_message", "", $school_id, TRUE),
  1085. "description" => t("This message will be displayed when any course on the page
  1086. has its bool_hide_grade set to TRUE. If you are not using
  1087. this functionality, you can leave this blank."),
  1088. );
  1089. $form["show_group_titles_on_view" . $fs] = array(
  1090. "type" => "select",
  1091. "label" => t("Show group titles on View and What If screens?"),
  1092. "hide_please_select" => TRUE,
  1093. "options" => array("no" => t("No"), "yes" => t("Yes")),
  1094. "value" => variable_get_for_school("show_group_titles_on_view", "no", $school_id, TRUE),
  1095. "description" => t("If set to Yes, then group titles will be displayed in the View
  1096. and What if screens, similar to how they are displayed when printing.
  1097. If unsure what to select, select 'No'."),
  1098. );
  1099. $form['max_allowed_selections_in_what_if' . $fs] = array(
  1100. 'type' => 'textfield',
  1101. 'label' => t("Maximum number of allowed selections in What If:"),
  1102. 'value' => variable_get_for_school('max_allowed_selections_in_what_if', 5, $school_id, TRUE),
  1103. 'description' => t("Selecting multiple degrees and options for What If can significantly impact server performance. It is recommended
  1104. to limit the number of selections to no more than 5. If you are unsure what to put here, enter '5'."),
  1105. );
  1106. $form['show_both_undergrad_and_grad_degrees_in_what_if' . $fs] = array(
  1107. 'type' => 'select',
  1108. 'label' => t("Show both undergraduate and graduate degrees for every student in What If?"),
  1109. 'options' => array('no' => t('No (default behavior)'), 'yes' => t('Yes')),
  1110. 'hide_please_select' => TRUE,
  1111. 'value' => variable_get_for_school('show_both_undergrad_and_grad_degrees_in_what_if', 'no', $school_id, TRUE),
  1112. 'description' => t("Normally on the What If selection screen, undergrad students can only select from undergrad degrees, and
  1113. graduate students can only select from graduate degrees. However, if this is set to 'Yes', then
  1114. students will be able to see and select from any degree in What If. If unsure what to select,
  1115. choose 'No'."),
  1116. );
  1117. $form["show_level_3_on_what_if_selection" . $fs] = array(
  1118. "type" => "select",
  1119. "label" => t("Show level-3 degree options on What If selection screen?"),
  1120. "hide_please_select" => TRUE,
  1121. "options" => array("no" => t("No"), "yes" => t("Yes")),
  1122. "value" => variable_get_for_school("show_level_3_on_what_if_selection", "yes", $school_id, TRUE),
  1123. "description" => t("If set to Yes, then level 3 Track/Options will appear on the What If
  1124. selection screen, if a degree is selected with available options.
  1125. Setting to 'no' gives behavior more like FlightPath 4.
  1126. If unsure what to select, select 'No'."),
  1127. );
  1128. $form["course_repeat_policy" . $fs] = array(
  1129. "type" => "select",
  1130. "label" => t("Course repeat policy:"),
  1131. "options" => array("most_recent_exclude_previous" => t("most recent, exclude previous attempts"),
  1132. "most_recent_do_not_exclude" => t("most recent, do not exclude previous attempts - \"beta\" feature"),
  1133. "best_grade_exclude_others" => t("best grade, exclude other attempts - \"beta\" feature")),
  1134. "value" => variable_get_for_school("course_repeat_policy", "most_recent_exclude_previous", $school_id, TRUE),
  1135. "hide_please_select" => TRUE,
  1136. "description" => t("This setting affects the course repeat policy for FlightPath for normal courses (courses which are not allowed to be repeated normally).
  1137. <br><b>If you are unsure what to select</b>, choose 'most recent, exclude previous attempts'.
  1138. <br>Please see the
  1139. <b><a href='http://getflightpath.com/node/1085' target='_blank'>FlightPath documentation</a></b>
  1140. on how to set up this field."),
  1141. );
  1142. $form["what_if_catalog_year_mode" . $fs] = array(
  1143. "type" => "select",
  1144. "label" => t("What If mode default catalog year:"),
  1145. "options" => array("current" => t("Current catalog year only"),
  1146. "student" => t("Student catalog year only"),
  1147. ),
  1148. "value" => variable_get_for_school("what_if_catalog_year_mode", "current", $school_id, TRUE),
  1149. "hide_please_select" => TRUE,
  1150. "description" => t("What should be the default catalog year that What If pulls degrees from? For some schools,
  1151. changing majors means moving to the current catalog year. However, at other schools, students
  1152. may remain in their current catalog year when they change majors. If you are unsure what
  1153. to select, choose 'Current catalog year only.'"),
  1154. );
  1155. $form["ignore_courses_from_hour_counts" . $fs] = array(
  1156. "type" => "textfield",
  1157. "label" => t("Ignore courses from hour counts (CSV):"),
  1158. "value" => variable_get_for_school("ignore_courses_from_hour_counts", "", $school_id, TRUE),
  1159. "description" => t("List courses, separated by comma,
  1160. which should be ignored in hours counts. This helps
  1161. remedial courses from being applied to hour counts.
  1162. <br><b>These courses will automatically be assigned the requirement type code 'x'.</b>
  1163. <br>
  1164. Ex: MATH 093, ENGL 090, UNIV 1001"),
  1165. );
  1166. $form["term_id_structure" . $fs] = array(
  1167. "type" => "textarea",
  1168. "label" => t("Structure of term ID's:"),
  1169. "value" => variable_get_for_school("term_id_structure", "", $school_id, TRUE),
  1170. "description" => t("Use this space to define termID structures, one per line.
  1171. Please see the
  1172. <b><a href='http://getflightpath.com/node/1085' target='_blank'>FlightPath documentation</a></b>
  1173. on how to set up this field.") . "
  1174. <br>&nbsp; &nbsp; &nbsp; " . t("Like so: Structure, Short Desc, Long Desc, Abbr Desc, Year Adjustment") . "
  1175. <br>" . t("Ex:") . "
  1176. <br>&nbsp; &nbsp; &nbsp; [Y4]60, Spring, Spring of [Y4], Spr '[Y2], [Y]
  1177. <br>&nbsp; &nbsp; &nbsp; [Y4]40, Fall, Fall of [Y4-1], Fall '[Y2-1], [Y-1]",
  1178. );
  1179. // Let's load the subjects...
  1180. $subjects = "";
  1181. $query = "SELECT DISTINCT b.subject_id, a.title, a.college FROM draft_courses b LEFT JOIN subjects a
  1182. ON (a.subject_id = b.subject_id AND a.school_id = b.school_id)
  1183. WHERE exclude = 0
  1184. AND b.school_id = ?
  1185. ORDER BY b.subject_id
  1186. ";
  1187. $result = db_query($query, $school_id);
  1188. while ($cur = db_fetch_array($result))
  1189. {
  1190. $title = fp_trim($cur["title"]);
  1191. $subject_id = fp_trim($cur["subject_id"]);
  1192. $college = fp_trim($cur["college"]);
  1193. $subjects .= $subject_id . " ~ " . $college . " ~ " . $title . "\n";
  1194. }
  1195. $form["subjects" . $fs] = array(
  1196. "type" => "textarea",
  1197. "label" => t("Subjects:"),
  1198. "value" => $subjects,
  1199. "rows" => 15,
  1200. "description" => t("Enter subjects known to FlightPath (for use
  1201. in popups and the Course Search, for example), one per line
  1202. in this format:") . "<br>SUBJ ~ COLLEGE ~ Title<br>" . t("For example:") . "
  1203. <br>&nbsp; ACCT ~ BA ~ Accounting<br>&nbsp; BIOL ~ AS ~ Biology<br>" . t("Notice
  1204. the separator between the code, college, and title is 1 tilde (~). Whatespace is ignored.
  1205. <br><b>Important:</b> This field cannot be set up until you have your courses
  1206. fully entered. Once that occurs, the course
  1207. subjects will automatically appear in this box, where you can then assign the college code
  1208. and subject title."),
  1209. );
  1210. // Load the colleges...
  1211. $colleges = "";
  1212. $res = db_query("SELECT * FROM colleges WHERE school_id = ? ORDER BY college_code", array($school_id));
  1213. while ($cur = db_fetch_array($res)) {
  1214. $colleges .= $cur["college_code"] . " ~ " . $cur["title"] . "\n";
  1215. }
  1216. $form["colleges" . $fs] = array(
  1217. "type" => "textarea",
  1218. "label" => t("Colleges:"),
  1219. "value" => $colleges,
  1220. "description" => t("Enter colleges known to FlightPath, one per line, in this format:
  1221. ") . "<br>COLLEGE_CODE ~ Title<br>" . t("For example:") . "
  1222. <br>&nbsp; AE ~ College of Arts, Science, and Education
  1223. <br>&nbsp; PY ~ College of Pharmacy<br>" . t("Notice
  1224. the separator between the code and title is 1 tilde (~). Whitespace is ignored."),
  1225. );
  1226. // Load the degree_college data....
  1227. $degree_college = "";
  1228. $res = db_query("SELECT DISTINCT(major_code) FROM draft_degrees WHERE school_id = ? ORDER BY major_code", array($school_id));
  1229. while ($cur = db_fetch_array($res)) {
  1230. $major_code = $cur["major_code"];
  1231. // Is there an assigned college already?
  1232. $res2 = db_query("SELECT college_code FROM degree_college WHERE major_code = ? AND school_id = ? ", $major_code, $school_id);
  1233. $cur2 = db_fetch_array($res2);
  1234. $college_code = "";
  1235. if ($cur2) $college_code = $cur2["college_code"];
  1236. $degree_college .= $major_code . " ~ " . $college_code . "\n";
  1237. }
  1238. $form["degree_college" . $fs] = array(
  1239. "type" => "textarea",
  1240. "label" => t("Degree Colleges:"),
  1241. "value" => $degree_college,
  1242. "rows" => 15,
  1243. "description" => t("Enter the degree's college, one per line, in this format:
  1244. ") . "<br>MAJOR_CODE ~ COLLEGE_CODE<br>" . t("For example:") . "
  1245. <br>&nbsp; ACCT ~ AE
  1246. <br>&nbsp; BUSN ~ SB<br>" . t("Notice
  1247. the separator between the codes is 1 tilde (~). Whitespace is ignored.
  1248. <br><b>Important:</b> This field cannot be set up until you have your degrees
  1249. entered. Once that occurs, the degree major codes
  1250. will automatically appear in this box, where you can then assign the college code.
  1251. "),
  1252. );
  1253. $form['departments' . $fs] = array(
  1254. "type" => 'textarea',
  1255. 'label' => t("Departments:"),
  1256. 'value' => variable_get_for_school('departments', '', $school_id, TRUE),
  1257. 'rows' => 15,
  1258. 'description' => t("Enter each department, one per line, in this format:
  1259. <br>DEPT_CODE ~ Department Name
  1260. <br>
  1261. For example:
  1262. <br> &nbsp; FINAID ~ Financial Aid
  1263. <br> &nbsp; REGST ~ Registrar
  1264. <br><b>Important:</b> The DEPT_CODE must be <strong>unique</strong> and contain only
  1265. letters, numbers, and underscores. The Department Name must not contain any tildes (~) or line breaks, and should be relatively short.
  1266. <br>The separator between the DEPT_CODE and the Department Name is a single tilde (~)."),
  1267. );
  1268. // How many decimal places allowed in substitutions?
  1269. $form["sub_hours_decimals_allowed" . $fs] = array(
  1270. "type" => "select",
  1271. "label" => t("Substitution hours decimal places allowed:"),
  1272. "options" => array(1 => t("1 (ex: 1.1 hrs)"), 2 => t("2 (ex: 1.15 hrs)"), 3 => t("3 (ex: 1.155 hrs)"), 4 => t("4 (ex: 1.1556 hrs)")),
  1273. "value" => variable_get_for_school("sub_hours_decimals_allowed", 2, $school_id, TRUE),
  1274. "no_please_select" => TRUE,
  1275. "description" => t("For hours with decimals (like 2.25 hours), how many numbers
  1276. after the decimal place will be allowed? Affects users performing
  1277. substitutions. For example, if you select \"2\" here, then
  1278. a value of 1.2555 will be rejected, and the user will be asked to re-enter.
  1279. 1.25, would be accepted, since it has 2 decimal places.
  1280. <br>If you are unsure what to select, set to 2."),
  1281. );
  1282. // Auto-correct course titles?
  1283. $form["autocapitalize_course_titles" . $fs] = array(
  1284. "type" => "select",
  1285. "label" => t("Auto-capitalize course titles?"),
  1286. "options" => array("yes" => "Yes", "no" => "No"),
  1287. "hide_please_select" => TRUE,
  1288. "value" => variable_get_for_school("autocapitalize_course_titles", "yes", $school_id, TRUE),
  1289. "description" => t("If set to yes, course titles in FlightPath will be run through a capitalization
  1290. filter, so that 'INTRO TO ACCOUNTING' becomes 'Intro to Accounting'.
  1291. Generally, this makes the course names more attractive, but it can
  1292. incorrectly capitalize acronyms and initials. Disable if you would like
  1293. complete control over capitalization.
  1294. <br>If unsure, set to Yes."),
  1295. );
  1296. // Auto-correct institution names?
  1297. $form["autocapitalize_institution_names" . $fs] = array(
  1298. "type" => "select",
  1299. "label" => t("Auto-capitalize institution names?"),
  1300. "options" => array("yes" => "Yes", "no" => "No"),
  1301. "hide_please_select" => TRUE,
  1302. "value" => variable_get_for_school("autocapitalize_institution_names", "yes", $school_id, TRUE),
  1303. "description" => t("If set to yes, transfer institution names in
  1304. FlightPath will be run through a capitalization
  1305. filter, so that 'UNIVERSITY OF LOUISIANA AT MONROE'
  1306. becomes 'University of Louisiana at Monroe'.
  1307. Like the course title setting above, this is to make
  1308. inconsistent or unattractive capitalization prettier.
  1309. Disable if you would like
  1310. complete control over capitalization.
  1311. <br>If unsure, set to Yes."),
  1312. );
  1313. // Only allow ghost subs for fellow ghost hours?
  1314. $form["restrict_ghost_subs_to_ghost_hours" . $fs] = array(
  1315. "type" => "select",
  1316. "label" => t("Restrict ghost substitutions to courses with zero hours only?"),
  1317. "options" => array("yes" => "Yes", "no" => "No"),
  1318. "hide_please_select" => TRUE,
  1319. "value" => variable_get_for_school("restrict_ghost_subs_to_ghost_hours", "yes", $school_id, TRUE),
  1320. "description" => t("If set to yes, courses with \"ghost\" hours may only be
  1321. substituted for other courses with \"ghost\" hours. What this
  1322. means is that if a course is worth zero hours, it may only be
  1323. subbed for a requirement worth zero hours, and it will not appear
  1324. as an option for substitutions of courses worth more than zero hours.
  1325. This will not affect old subs; only new ones.
  1326. <br>If unsure, set to Yes."),
  1327. );
  1328. $form["initial_student_course_sort_policy" . $fs] = array(
  1329. "type" => "select",
  1330. "label" => t("Initial student course sort policy:"),
  1331. "options" => array("alpha" => "Alphabetical sort [default]", "grade" => "Best grade first"),
  1332. "hide_please_select" => TRUE,
  1333. "value" => variable_get_for_school("initial_student_course_sort_policy", "alpha", $school_id, TRUE),
  1334. "description" => t("Student courses are sorted more than once as they are evaluated by FlightPath.
  1335. By default, they are sorted alphabetically first. If you change this to best-grade-first,
  1336. courses will be initally sorted according to the grade they earned, in the order defined in 'Grade order CSV' below.
  1337. Any student grades not defined below will be considered the lowest possible grade."),
  1338. );
  1339. $form["grade_order" . $fs] = array(
  1340. "type" => "textfield",
  1341. "label" => t("Grade order (CSV):"),
  1342. "value" => variable_get_for_school("grade_order", "E,AMID,BMID,CMID,DMID,FMID,A,B,C,D,F,W,I", $school_id, TRUE),
  1343. "description" => t("List all possible grades, separated by comma, from highest to lowest. This is
  1344. used if you select 'Best Grade first' order above, but also is used in determining
  1345. if a course fulfills a minimum grade requirement.
  1346. <br>Ex: AMID,BMID,CMID,DMID,FMID,A,B,C,D,F,W,I"),
  1347. );
  1348. $form["minimum_passing_grade" . $fs] = array(
  1349. "type" => "textfield",
  1350. "size" => 3,
  1351. "label" => t("Minimum passing grade:"),
  1352. "value" => variable_get_for_school("minimum_passing_grade", "D", $school_id, TRUE),
  1353. "description" => t("Enter a grade which is the default minimum grade a student must have earned
  1354. for the course to be considered for credit. This will affect course requirements
  1355. on degree plans which do not have a min grade set. This value will be used
  1356. by default. If unsure, enter D."),
  1357. );
  1358. $form["retake_grades" . $fs] = array(
  1359. "type" => "textfield",
  1360. "label" => t("Retake grades (CSV):"),
  1361. "value" => variable_get_for_school("retake_grades", "F,W,I", $school_id, TRUE),
  1362. "description" => t("List grades, separated by comma, which means 'the student must
  1363. retake this course. They did not earn credit.' Ex: F,W,I"),
  1364. );
  1365. $form["withdrew_grades" . $fs] = array(
  1366. "type" => "textfield",
  1367. "label" => t("Withdrew grades (CSV):"),
  1368. "value" => variable_get_for_school("withdrew_grades", "W", $school_id, TRUE),
  1369. "description" => t("List grades, separated by comma, which means 'the student withdrew
  1370. from this course. They did not earn credit.' Ex: W,WD,WF. If not sure
  1371. what to enter here, just enter 'W'."),
  1372. );
  1373. $form["enrolled_grades" . $fs] = array(
  1374. "type" => "textfield",
  1375. "label" => t("Enrolled grades (CSV):"),
  1376. "value" => variable_get_for_school("enrolled_grades", "E", $school_id, TRUE),
  1377. "description" => t("List grades, separated by comma, which means 'the student is
  1378. currently enrolled in this course.' Ex: E,AMID,BMID "),
  1379. );
  1380. $form["minimum_substitutable_grade" . $fs] = array(
  1381. "type" => "textfield",
  1382. "size" => 3,
  1383. "label" => t("Minimum substitutable grade:"),
  1384. "value" => variable_get_for_school("minimum_substitutable_grade", "D", $school_id, TRUE),
  1385. "description" => t("Enter a grade which is the minimum grade a student must have earned
  1386. for the course to be allowed in a substitution. This will affect
  1387. new substitutions, not old ones. If unsure, enter D."),
  1388. );
  1389. $form["group_min_grades" . $fs] = array(
  1390. "type" => "textfield",
  1391. "label" => t("Group requirement min grades (CSV):"),
  1392. "value" => variable_get_for_school("group_min_grades", "D,C,B,A", $school_id, TRUE),
  1393. "description" => t("List grades, separated by comma, which should appear in the min grade pulldown when setting a group requirement
  1394. in a degree (this also sets the order in which they will appear). If unsure what to enter, use: D,C,B,A"),
  1395. );
  1396. $form["calculate_cumulative_hours_and_gpa" . $fs] = array(
  1397. "label" => t("Calculate student cumulative hours and GPA?"),
  1398. "type" => "select",
  1399. "hide_please_select" => TRUE,
  1400. "options" => array("no" => t("No"), "yes" => t("Yes")),
  1401. "value" => variable_get_for_school("calculate_cumulative_hours_and_gpa", 'no', $school_id, TRUE),
  1402. "description" => t("If set to Yes, student cumulative hours and GPA will not be read from the
  1403. 'students' database table, but will instead be calculated on the fly
  1404. each time a student is loaded. If unsure what to do, set to Yes."),
  1405. );
  1406. $form['numeric_to_letter_grades' . $fs] = array(
  1407. "label" => t("Numeric to Letter Grades:"),
  1408. "type" => "textarea",
  1409. "value" => variable_get_for_school("numeric_to_letter_grades", "", $school_id, TRUE),
  1410. "description" => t("If your school supports numeric grades in your SIS (ex: 91, 80, 65, etc), then they must be converted to
  1411. letter grades (A, B, D, etc) for FlightPath. Use this box to define what numeric range translates to which
  1412. letter grade. If you are unsure what to enter here, or if your school does not use numeric grades, leave this blank.
  1413. <br>Enter in the form of: MIN ~ MAX ~ GRADE
  1414. <br>Ex:
  1415. <br>&nbsp; 0 ~ 59.99 ~ F
  1416. <br>&nbsp; 60 ~ 69.99 ~ D
  1417. <br>&nbsp; 70 ~ 79.99 ~ C
  1418. <br>&nbsp; 80 ~ 89.99 ~ B
  1419. <br>&nbsp; 90 ~ 100.99 ~ A
  1420. <br>&nbsp; 101 ~ 999 ~ A &nbsp; &nbsp; <em>(In case scores above 100 are possible)</em>"),
  1421. );
  1422. $form["quality_points_grades" . $fs] = array(
  1423. "label" => t("Quality points and grades:"),
  1424. "type" => "textarea",
  1425. "value" => variable_get_for_school("quality_points_grades", "A ~ 4\nB ~ 3\nC ~ 2\nD ~ 1\nF ~ 0\nI ~ 0", $school_id, TRUE),
  1426. "description" => t("Enter a grade, and how many quality points it is worth, separated by
  1427. tilde (~), one per line. You must include every grade which should count
  1428. for (or against) a GPA calculation, even if it is worth zero points. For example,
  1429. if an 'F' should cause a GPA to lower (which normally it would), it should be
  1430. listed here. If a 'W' grade should simply be ignored, then DO NOT list it here.
  1431. Any grade you do not list here will be IGNORED in all GPA calculations.") . "
  1432. <br>
  1433. Ex:<blockquote style='margin-top:0; font-family: Courier New, monospace;'>
  1434. A ~ 4<br>B ~ 3<br>C ~ 2<br>D ~ 1<br>F ~ 0<br>I ~ 0</blockquote>",
  1435. );
  1436. $form["requirement_types" . $fs] = array(
  1437. "label" => t("Requirement types and codes:"),
  1438. "type" => "textarea",
  1439. "value" => variable_get_for_school("requirement_types", "g ~ General Requirements\nc ~ Core Requirements\ne ~ Electives\nm ~ Major Requirements\ns ~ Supporting Requirements\nx ~ Additional Requirements", $school_id, TRUE),
  1440. "description" => t("Enter requirement type codes and descriptions, separated by a tilde (~), one
  1441. per line. <b>You may not use the code 'u'</b> as that is reserved in FlightPath.
  1442. <b>You should define what 'x' means</b>, but be aware that the code 'x' will always
  1443. designate a course whose hours should be ignored from GPA calculations.
  1444. <b>You should define what 'm' means</b>, as this is the default code applied
  1445. to a requirement if one is not entered. <b>You should define what
  1446. 'e' means</b>, as this is also the code given to courses whose types we cannot
  1447. figure out, perhaps because of a typo or intentionally. Ex: Electives.
  1448. This list also defines the order in which they will appear on screen in
  1449. Type View. By convention, codes should be lower case single-letters.") . "
  1450. <br>Ex:
  1451. <div style='padding-left: 20px; font-family: Courier New, monospace'>
  1452. g ~ General Requirements<br>
  1453. c ~ Core Requirements<br>
  1454. e ~ Electives<br>
  1455. m ~ Major Requirements<br>
  1456. s ~ Supporting Requirements<br>
  1457. x ~ Additional Requirements
  1458. </div>
  1459. Please see the
  1460. <b><a href='http://getflightpath.com/node/1085' target='_blank'>FlightPath documentation</a></b>
  1461. for more information on how to set up this field.
  1462. ",
  1463. );
  1464. // Check to make sure the gd extension is loaded, since that will be required to display
  1465. // the pie charts...
  1466. if (!extension_loaded('gd') && !extension_loaded('gd2')) {
  1467. $form["mark_no_gd_library"] = array(
  1468. "value" => "<p class='hypo'><b>" . t("Note: it appears your web server does not have the 'GD' library
  1469. enabled for PHP. This is required to make the pie charts show up
  1470. correctly. Contact your server administrator about enabling the 'GD'
  1471. library.") . "</b></p>",
  1472. );
  1473. }
  1474. $form["pie_chart_config" . $fs] = array(
  1475. "label" => t("Pie chart configuration:"),
  1476. "type" => "textarea",
  1477. "value" => variable_get_for_school("pie_chart_config", "c ~ Core Requirements\nm ~ Major Requirements\ndegree ~ Degree Progress", $school_id, TRUE),
  1478. "description" => t("Enter configuration data for the pie charts which graph a student's progress
  1479. through their degree. Enter the requirement type code, pie chart label, and optional
  1480. colors separated by tilde (~). Requirement types not found for a student will be skipped
  1481. and the chart will not be drawn. <b>Enter 'degree' for total progress.</b>") . "
  1482. <br>Ex: CODE ~ LABEL ~ [optional: UNFINISHED COLOR ~ PROGRESS COLOR ]
  1483. <div style='padding-left: 20px; font-family: Courier New, monospace'>
  1484. c ~ Core Requirements ~ 660000 ~ FFCC33<br>
  1485. m ~ Major Requirements ~ 660000 ~ 93D18B<br>
  1486. degree ~ Degree Progress ~ 660000 ~ 5B63A5
  1487. </div>",
  1488. );
  1489. $form["pie_chart_gpa" . $fs] = array(
  1490. "label" => t("Should pie charts show GPAs?"),
  1491. "type" => "select",
  1492. "options" => array("no" => "No", "yes" => "Yes"),
  1493. "value" => variable_get_for_school("pie_chart_gpa", "no", $school_id, TRUE),
  1494. "hide_please_select" => TRUE,
  1495. "description" => t("If set to 'Yes', the GPA will be displayed below each pie chart on the View and What If screens.
  1496. If unsure what to select, choose 'no'."),
  1497. );
  1498. $form["developmentals_title" . $fs] = array(
  1499. "label" => t("Developmentals semester block title:"),
  1500. "type" => "textfield",
  1501. "value" => variable_get_for_school("developmentals_title", t("Developmental Requirements", $school_id)),
  1502. "description" => t("This is the title of the Developmental Requirements semester block,
  1503. which appears on a student's degree plan, near the bottom, when they
  1504. have remedial courses they are required to take. If you are
  1505. unsure what to enter, use 'Developmental Requirements'."),
  1506. );
  1507. $form["developmentals_notice" . $fs] = array(
  1508. "label" => t("Developmentals notice text:"),
  1509. "type" => "textarea",
  1510. "value" => variable_get_for_school("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."), $school_id, TRUE),
  1511. "description" => t("The text you enter here will be displayed below the Developmentals semester
  1512. block, explaining to the student what these courses are. For example:
  1513. 'According to our records, you are required to complete the course(s) listed
  1514. above.'"),
  1515. );
  1516. $form["graduate_level_codes" . $fs] = array(
  1517. "type" => "textfield",
  1518. "label" => t("Graduate level codes (CSV):"),
  1519. "value" => variable_get_for_school("graduate_level_codes", "GR", $school_id, TRUE),
  1520. "description" => t("List level codes, separated by comma, for both students, courses, and degrees, which should be considered at the Graduate level. If you do not need
  1521. to distinguish between graduate and undergraduate credit, leave this field blank.<br>If unsure, set to GR."),
  1522. );
  1523. $form["disallow_graduate_credits" . $fs] = array(
  1524. "type" => "select",
  1525. "label" => t("Disallow automatic use of graduate credits?"),
  1526. "options" => array("yes" => "Yes", "no" => "No"),
  1527. "hide_please_select" => TRUE,
  1528. "value" => variable_get_for_school("disallow_graduate_credits", "yes", $school_id, TRUE),
  1529. "description" => t("If set to yes, FlightPath will not automatically use graduate credits (based on the level code the student's credit
  1530. is given in the database) to populate elective groups or on the degree plan. They may still be substituted using the
  1531. substitution system however. In order for this setting to work, the 'Graduate course level codes' field must be set above.
  1532. <br>If unsure, set to Yes."),
  1533. );
  1534. $form["display_graduate_credits_block" . $fs] = array(
  1535. "type" => "select",
  1536. "label" => t("Display graduate credits in their own semester block?"),
  1537. "options" => array("yes" => "Yes", "no" => "No"),
  1538. "hide_please_select" => TRUE,
  1539. "value" => variable_get_for_school("display_graduate_credits_block", "yes", $school_id, TRUE),
  1540. "description" => t("If set to yes, FlightPath will display graduate credits in their own block, and NOT in Excess credits. The graduate block details
  1541. are set below.
  1542. <br>If unsure, set to Yes."),
  1543. );
  1544. $form["graduate_credits_block_title" . $fs] = array(
  1545. "label" => t("Graduate Credits block title:"),
  1546. "type" => "textfield",
  1547. "value" => variable_get_for_school("graduate_credits_block_title", t("Graduate Credits"), $school_id, TRUE),
  1548. "description" => t("This is the title of the Graduate Credits semester block (setting above),
  1549. which appears on a student's degree plan, near the bottom, when they
  1550. have graduate credits in their history (based on the credit's level code). If you are
  1551. unsure what to enter, use 'Graduate Credits'."),
  1552. );
  1553. $form["graduate_credits_block_notice" . $fs] = array(
  1554. "label" => t("Graduate Credits block notice text:"),
  1555. "type" => "textarea",
  1556. "value" => variable_get_for_school("graduate_credits_block_notice", t("These courses may not be used for undergraduate credit."), $school_id, TRUE),
  1557. "description" => t("The text you enter here will be displayed below the Gradute Credits semester
  1558. block, explaining to the student what these courses are. For example:
  1559. 'These courses may not be used for undergraduate credit.'"),
  1560. );
  1561. $form["exclude_majors_from_appears_in_counts" . $fs] = array(
  1562. "label" => t("Exclude major codes from \"appears in\" counts (CSV):"),
  1563. "type" => "textfield",
  1564. "maxlength" => 1000,
  1565. "value" => variable_get_for_school("exclude_majors_from_appears_in_counts", "", $school_id, TRUE),
  1566. "description" => t('When a course appears in more than one degree, it is given an extra CSS class
  1567. denoting that. This fields lets you enter major codes for degrees, separated by commas,
  1568. for any degrees you do not wish to be counted toward the "appears in" counts.
  1569. <br>&nbsp; &nbsp; Ex: UGELEC, ACCTB
  1570. <br>If you are unsure what to enter, leave this field blank.'),
  1571. );
  1572. $form["group_full_at_min_hours" . $fs] = array(
  1573. "label" => t("Groups should be considered 'full' when min hours are met or exceeded?"),
  1574. "type" => "select",
  1575. "options" => array("yes" => "Yes", "no" => "No"),
  1576. "value" => variable_get_for_school("group_full_at_min_hours", "yes", $school_id, TRUE),
  1577. "hide_please_select" => TRUE,
  1578. "description" => t("If a group has been added to a degree plan with 'min hours', should FlightPath consider the group
  1579. 'full', and stop assigning courses to it, once the assigned courses meets or goes over the min hours value,
  1580. even if the max hours have not been fulfilled? This
  1581. only affects groups which have been added to a degree plan with min hours set. Ex: 3-6 hours.
  1582. If you are unsure what to enter, select 'Yes'"),
  1583. );
  1584. $form["remove_advised_when_course_taken" . $fs] = array(
  1585. "label" => t("Remove an advised course when a student enrolls in it (or completes it), for the same term?"),
  1586. "type" => "select",
  1587. "options" => array("yes" => "Yes", "no" => "No"),
  1588. "value" => variable_get_for_school("remove_advised_when_course_taken", "no", $school_id, TRUE),
  1589. "hide_please_select" => TRUE,
  1590. "description" => t("If a student has been advised into a course, and then enrolls in that course before the next
  1591. advising term begins, should the advised course (and checkbox) be removed? This would also affect
  1592. courses the student completes within that term. The default is 'No', meaning advising checkboxes in View
  1593. will continue to show, even if the student has enrolled or completes the course that term. The checkboxes
  1594. will disappear when the advising term is no longer available for advising.
  1595. Select 'Yes' if you wish to have FlightPath hide advising checkboxes on the View screen when a student
  1596. is enrolled or completes a course within the same advising term. If you are unsure what to enter, select 'No'."),
  1597. );
  1598. $form["prevent_course_assignment_to_both_degree_and_track" . $fs] = array(
  1599. "label" => t("Prevent a course assignment to both a degree and its track(s)?"),
  1600. "type" => "select",
  1601. "options" => array("yes" => "Yes", "no" => "No"),
  1602. "value" => variable_get_for_school("prevent_course_assignment_to_both_degree_and_track", "yes", $school_id, TRUE),
  1603. "hide_please_select" => TRUE,
  1604. "description" => t("If set to 'Yes' (default), then FlightPath will not allow the same course to be assigned to both a Level-1 degree
  1605. and its tracks. For example, if a student completes ENGL 101, and it can be assigned to the major COMPSCI, then
  1606. it cannot also be assigned to the track COMPSCI|_OPT1. If you are unsure what to select, leave this set to 'Yes'."),
  1607. );
  1608. $form["group_list_course_show_repeat_information" . $fs] = array(
  1609. "label" => t("Display 'Repeat Information' for a course in a group's course list?"),
  1610. "type" => "select",
  1611. "options" => array("yes" => "Yes", "no" => "No"),
  1612. "value" => variable_get_for_school("group_list_course_show_repeat_information", "yes", $school_id, TRUE),
  1613. "hide_please_select" => TRUE,
  1614. "description" => t("If set to 'Yes' (default), FlightPath will how many times a course may be repeated when viewing a list
  1615. of a Group's courses in the popup. If set to 'No', repeat information will not be displayed, and instead
  1616. the course's normal hour information is displayed. If you
  1617. are unsure what to select, leave this set to 'Yes'."),
  1618. );
  1619. $form["degree_requirement_sort_policy" . $fs] = array(
  1620. "type" => "select",
  1621. "label" => t("Degree requirement sort policy:"),
  1622. "options" => array("alpha" => "Alphabetical sort (default)", "database" => "As entered in database [beta]"),
  1623. "hide_please_select" => TRUE,
  1624. "value" => variable_get_for_school("degree_requirement_sort_policy", "alpha", $school_id, TRUE),
  1625. "description" => t("How should degree course requirements appear to the end user? By default, they will be sorted into alphabetical order.
  1626. However, if you wish them to appear in the order the were entered on the Edit Degree form, select 'As entered...'.
  1627. <br>If unsure, select 'Alphabetical sort'."),
  1628. );
  1629. $form["group_requirement_sort_policy" . $fs] = array(
  1630. "type" => "select",
  1631. "label" => t("Group requirement sort policy:"),
  1632. "options" => array("alpha" => "Alphabetical sort (default)", "database" => "As entered in database [beta]"),
  1633. "hide_please_select" => TRUE,
  1634. "value" => variable_get_for_school("group_requirement_sort_policy", "alpha", $school_id, TRUE),
  1635. "description" => t("How should group course requirements appear to the end user in the popup dialog window? By default, they will be sorted into alphabetical order.
  1636. However, if you wish them to appear in the order the were entered on the Edit Group form, select 'As entered...'.
  1637. <br>If unsure, select 'Alphabetical sort'."),
  1638. );
  1639. return $form;
  1640. }
  1641. /**
  1642. * Uses the "exclude_majors...." setting, but converts them into an array of degree_ids.
  1643. */
  1644. function system_get_exclude_degree_ids_from_appears_in_counts($school_id) {
  1645. $rtn = array();
  1646. // Have we already cached this for this page load?
  1647. if (isset($GLOBALS["exclude_degree_ids_from_appears_in_counts"][$school_id])) {
  1648. return $GLOBALS["exclude_degree_ids_from_appears_in_counts"][$school_id];
  1649. }
  1650. $db = get_global_database_handler();
  1651. $majors = csv_to_array(variable_get_for_school("exclude_majors_from_appears_in_counts", "", $school_id));
  1652. foreach ($majors as $major_code) {
  1653. $rtn = array_merge($rtn, $db->get_degree_ids($major_code));
  1654. }
  1655. $GLOBALS["exclude_degree_ids_from_appears_in_counts"][$school_id] = $rtn; // cache for next time.
  1656. return $rtn;
  1657. } //system_get_exclude_degree_ids_from_appears_in_counts
  1658. /**
  1659. * Validate handler for the school_data_form.
  1660. *
  1661. * Most of our data can be saved as simple system_settings, but for the others,
  1662. * we want to save them to special tables, then remove them from the form_state so
  1663. * they don't get saved to the variables table, taking up a lot of space.
  1664. *
  1665. * @param unknown_type $form
  1666. * @param unknown_type $form_state
  1667. */
  1668. function system_school_data_form_validate($form, &$form_state) {
  1669. $school_id = intval($form_state['values']['school_id']);
  1670. $fs = "";
  1671. if ($school_id !== 0) {
  1672. $fs = "~~school_" . $school_id;
  1673. }
  1674. // Subjects...
  1675. db_query("DELETE FROM subjects WHERE school_id = ?", $school_id);
  1676. $subjects = trim($form_state["values"]["subjects" . $fs]);
  1677. $lines = explode("\n", $subjects);
  1678. foreach ($lines as $line) {
  1679. $temp = explode("~", $line);
  1680. db_query("INSERT INTO subjects (subject_id, college, title, school_id)
  1681. VALUES (?, ?, ?, ?) ", strtoupper(trim($temp[0] ?? '')), strtoupper(trim($temp[1] ?? '')), trim($temp[2] ?? ''), $school_id);
  1682. }
  1683. // Remove the data from our form_state, so it isn't saved twice
  1684. unset($form_state["values"]["subjects" . $fs]);
  1685. // Colleges...
  1686. db_query("DELETE FROM colleges WHERE school_id = ?", $school_id);
  1687. $contents = trim($form_state["values"]["colleges" . $fs]);
  1688. $lines = explode("\n", $contents);
  1689. foreach ($lines as $line) {
  1690. $temp = explode("~", $line);
  1691. db_query("INSERT INTO colleges (college_code, title, school_id)
  1692. VALUES (?, ?, ?) ", strtoupper(trim($temp[0] ?? '')), trim($temp[1] ?? ''), $school_id);
  1693. }
  1694. // Remove the data from our form_state, so it isn't saved twice
  1695. unset($form_state["values"]["colleges" . $fs]);
  1696. // Degree College...
  1697. db_query("DELETE FROM degree_college WHERE school_id = ?", $school_id);
  1698. $contents = trim($form_state["values"]["degree_college" . $fs]);
  1699. $lines = explode("\n", $contents);
  1700. foreach ($lines as $line) {
  1701. $temp = explode("~", $line);
  1702. db_query("INSERT INTO degree_college (major_code, college_code, school_id)
  1703. VALUES (?, ?, ?) ", strtoupper(trim($temp[0] ?? '')), strtoupper(trim($temp[1] ?? '')), $school_id);
  1704. }
  1705. // Remove the data from our form_state, so it isn't saved twice
  1706. unset($form_state["values"]["degree_college" . $fs]);
  1707. watchdog("system", "Updated school settings (school_id: $school_id)");
  1708. }
  1709. /**
  1710. * Returns back an array (suitable for FAPI) of the available themes in the system.
  1711. */
  1712. function system_get_available_themes() {
  1713. $rtn = array();
  1714. // First, search for themes in our core folder. Themes must have a .info file which matches
  1715. // their folder name, just like modules.
  1716. $theme_dirs = array();
  1717. $theme_dirs[] = array("start" => "themes", "type" => t("Core"));
  1718. $theme_dirs[] = array("start" => "custom/themes", "type" => t("Custom"));
  1719. foreach ($theme_dirs as $theme_dir) {
  1720. $start_dir = $theme_dir["start"];
  1721. $type_dir = $theme_dir['type'];
  1722. if ($dh = @opendir($start_dir)) {
  1723. $dir_files = scandir($start_dir);
  1724. foreach ($dir_files as $file) {
  1725. if ($file == "." || $file == "..") continue;
  1726. if (is_dir($start_dir . "/" . $file)) {
  1727. // Okay, now look inside and see if there is a .info file.
  1728. if (file_exists("$start_dir/$file/$file.info")) {
  1729. $theme = $file;
  1730. $info_contents = file_get_contents("$start_dir/$file/$file.info");
  1731. // From the info_contents variable, split up and place into an array.
  1732. $info_details_array = array("name" => t("Name Not Set. Configure theme's .info file."), "path" => "", "module" => "",
  1733. "schema" => "", "core" => "", "description" => "",
  1734. "requires" => "", "version" => "",
  1735. "required" => "", );
  1736. $lines = explode("\n", $info_contents);
  1737. foreach ($lines as $line) {
  1738. if (trim($line) == "") continue;
  1739. $temp = explode("=", trim($line));
  1740. $info_details_array[trim($temp[0])] = trim(substr($line, strlen($temp[0]) + 1));
  1741. }
  1742. $path = "$start_dir/$file";
  1743. $rtn[$path] = $info_details_array['name'] . "<div style='font-size: 0.8em; font-style: italic; padding-left: 40px;'>{$info_details_array['description']}
  1744. <br>(Type: $type_dir &nbsp; &nbsp; Location: $path)</div>";
  1745. } // if file_exists
  1746. } //if is_dir
  1747. } //foreach dir_files as $file
  1748. } // if we can opendir
  1749. } // foreach theme_dirs as theme_dir
  1750. return $rtn;
  1751. }
  1752. /**
  1753. * Returns the "whitelist" or "allow list" (from system settings) as an array. If empty, it will return FALSE
  1754. */
  1755. function system_get_user_whitelist() {
  1756. $rtn = array();
  1757. $list = trim(variable_get('user_whitelist', ''));
  1758. if (!$list) return FALSE;
  1759. $lines = explode("\n", $list);
  1760. foreach ($lines as $line) {
  1761. $line = trim($line);
  1762. if ($line == "") continue;
  1763. // If the first char is a # then its a comment, skip it.
  1764. if (substr($line, 0, 1) == '#') continue;
  1765. // Otherwise, we can add to our rtn array.
  1766. $rtn[] = $line;
  1767. // To make sure we catch all occurances, also force lower-case (for emails)
  1768. $rtn[] = strtolower($line);
  1769. } // foreach
  1770. if (count($rtn) == 0) return FALSE;
  1771. return $rtn;
  1772. }
  1773. /**
  1774. * This is the "system settings" form.
  1775. */
  1776. function system_settings_form() {
  1777. $form = array();
  1778. $m = 0;
  1779. $form["mark" . $m++] = array(
  1780. "value" => t("Use this form to alter the various system settings in FlightPath.
  1781. Before making changes, it is always good policy to first back up your database."),
  1782. );
  1783. $form["mark" . $m++] = array(
  1784. "value" => "<p><div style='font-size:0.8em;'>" . t("Your site requires a cron job in order to perform routine tasks. This
  1785. is accomplished by having your server access the following URL every so often
  1786. (like once an hour):") . "<br>&nbsp; &nbsp; <i>" . $GLOBALS["fp_system_settings"]["base_url"] . "/cron.php?t=" . $GLOBALS["fp_system_settings"]["cron_security_token"] . "</i>
  1787. <br>" . t("Example linux cron command:") . "&nbsp; <i>wget -O - -q -t 1 http://ABOVE_URL</i></div></p>",
  1788. );
  1789. $form["maintenance_mode"] = array(
  1790. "label" => t("Set maintenance mode?"),
  1791. "type" => "checkbox",
  1792. "value" => variable_get("maintenance_mode", FALSE),
  1793. "description" => t("If checked, a message will display on every page stating
  1794. that the system is currently undergoing routine maintenance."),
  1795. );
  1796. $form["disable_login_except_admin"] = array(
  1797. "type" => "select",
  1798. "label" => t("Disable all new logins (except admin user)?"),
  1799. "hide_please_select" => TRUE,
  1800. "options" => array("no" => t("No"), "yes" => t("Yes")),
  1801. "value" => variable_get('disable_login_except_admin', 'no'),
  1802. "description" => t("If set to Yes, then when normal users attempt to log in, they will be
  1803. sent back to the login page, with a message displayed explaning that
  1804. logins are disabled. Admin will still be able to log in. This
  1805. is useful when trying to perform maintenance on FlightPath. If unsure
  1806. what to select, select 'No'."),
  1807. );
  1808. $form["disable_student_logins"] = array(
  1809. "type" => "select",
  1810. "label" => t("Disable all new student logins?"),
  1811. "hide_please_select" => TRUE,
  1812. "options" => array("no" => t("No"), "yes" => t("Yes")),
  1813. "value" => variable_get('disable_student_logins', 'no'),
  1814. "description" => t("If set to Yes, then when student users (not specified in the whitelist below) attempt to log in, they will be
  1815. sent back to the login page, with a message displayed explaning that
  1816. student logins are disabled. Admin and faculty/staff will still be able to log in.
  1817. If unsure what to select, select 'No'."),
  1818. );
  1819. $form["user_whitelist"] = array(
  1820. "type" => "textarea",
  1821. "label" => t("Only allow certain users to log in (allow list):"),
  1822. "value" => variable_get('user_whitelist', ''),
  1823. "description" => t("You may explicitly state which users are allowed to log in to FlightPath at this time.
  1824. Enter usernames, email addresses, or CWIDs, one per line. Users who are not part of this \"allow list\"
  1825. will be returned to the login screen, with a message stating that the system is only allowing
  1826. certain users to log in at this time.
  1827. <br>Note: the admin user
  1828. will always be able to log in. To disable, simply erase the contents of
  1829. this box and save."),
  1830. );
  1831. $form['mfa_enabled'] = array(
  1832. 'type' => 'select',
  1833. 'label' => t("Enable multi-factor authentication?"),
  1834. 'options' => array('no' => 'No (default)', 'yes' => 'Yes'),
  1835. 'hide_please_select' => TRUE,
  1836. 'value' => variable_get("mfa_enabled", "no"),
  1837. 'description' => t("If enabled, local users in FlightPath (like admin) will be emailed a validation code upon logging in, if and only if they have
  1838. an email address saved for their user account. This will not affect users which use an alternate method of logging in, such
  1839. as SSO, LDAP, etc. If unsure what to select, set this value to 'No'."),
  1840. );
  1841. $form["system_name"] = array(
  1842. "type" => "textfield",
  1843. "label" => t("System Name:"),
  1844. "value" => variable_get("system_name", "FlightPath"),
  1845. "description" => t("This is the name of this software system. Ex: FlightPath. This setting allows you to re-name this
  1846. system for you school. You will also need to create new themes, and edit where the name FlightPath
  1847. is hard-coded in the template files. This will only change the name FlightPath in user-facing pages,
  1848. it will still appear in admin sections. After changing this value, clear your cache, as several
  1849. menu items will need to be updated."),
  1850. );
  1851. $form['system_timezone'] = array(
  1852. 'type' => 'select',
  1853. 'label' => t('System timezone:'),
  1854. 'options' => get_timezones(),
  1855. 'value' => variable_get('system_timezone', 'America/Chicago'),
  1856. );
  1857. $form['system_default_student_load_tab'] = array(
  1858. 'type' => 'select',
  1859. 'label' => t('Default tab to view when loading a new student:'),
  1860. 'options' => array('profile' => t('Student Profile'), 'engagements' => t("Engagements"), 'degree' => t('Degree')),
  1861. 'value' => variable_get('system_default_student_load_tab', 'profile'),
  1862. 'hide_please_select' => TRUE,
  1863. 'description' => t("Unless overridden by the user's settings, this is the tab which
  1864. the user will see when pulling up a new student for advising.
  1865. <br>If unsure what to select, chose 'Student Profile'."),
  1866. );
  1867. // Can we support clean_urls?
  1868. $bool_support_clean = system_check_clean_urls();
  1869. $form["support_clean_urls"] = array(
  1870. "type" => "hidden",
  1871. "value" => ($bool_support_clean) ? "yes" : "no",
  1872. );
  1873. if ($bool_support_clean) {
  1874. // Give the option to change ONLY if we can support clean URLs
  1875. $form["clean_urls"] = array(
  1876. "type" => "checkbox",
  1877. "label" => t("Enable 'Clean URLs?'"),
  1878. "value" => variable_get("clean_urls", FALSE),
  1879. "description" => t("Your server supports 'clean URLs', which eliminates 'index.php?q=' from your URLs, making them
  1880. more readable. It is recommended you leave this feature enabled. For more information, see: http://getflightpath.com/node/5."),
  1881. );
  1882. }
  1883. else {
  1884. // Server does not support clean URLs.
  1885. $form["support_clean_markup"] = array(
  1886. "value" => "<p><b>Clean URLs:</b> This server <u>does not support</u> clean URLs. If you are using an Apache-compatible server,
  1887. make sure that your .htaccess file is properly configured. For more information, see: http://getflightpath.com/node/5.</p>",
  1888. );
  1889. }
  1890. $form["theme"] = array(
  1891. "type" => "radios",
  1892. "label" => t("Theme:"),
  1893. "options" => system_get_available_themes(),
  1894. "value" => variable_get("theme", "themes/fp_clean"),
  1895. "description" => t("Select the theme you wish to use. Ex: Classic (themes/fp_clean)"),
  1896. );
  1897. $form['external_css'] = array(
  1898. 'type' => 'textfield',
  1899. 'label' => t("External/Additional CSS file(s):"),
  1900. 'value' => variable_get("external_css", ""),
  1901. "description" => t("Enter the URL to one or more external or internal CSS files (separated by comma). Be aware
  1902. that due to the ordering of when your CSS file is loaded, you may need to use the !important keyword on some styles.
  1903. <br>If using an external source, your URL should begin with https:// and may not contain any queries (ex: ?a=b).
  1904. <br>If you are unsure what to enter here, leave it blank."),
  1905. );
  1906. $form['logo_image_url'] = array(
  1907. 'type' => 'textfield',
  1908. 'label' => t("Logo image URL:"),
  1909. 'value' => variable_get("logo_image_url", ""),
  1910. "description" => t("Enter the URL to a logo image. This is normally the \"FlightPath\" banner image seen in the upper left corner of every page.
  1911. <br>The image should be in a horizontal layout. Images with a height of at least 70px work best, as that is the height
  1912. it will be resized to using CSS.
  1913. <br>If using an external source, your URL should begin with https:// and may not contain any queries (ex: ?a=b).
  1914. <br>If you are unsure what to enter here, leave it blank to use the default logo."),
  1915. );
  1916. $form['public_files_allowed_extensions'] = array(
  1917. 'type' => 'textfield',
  1918. 'label' => t('Allowed file extensions for public file uploads:'),
  1919. 'value' => variable_get("public_files_allowed_extensions", "css, txt, pdf, doc, docx, csv, xls, xlsx, ppt, pptx, rtf, odt, jpg, jpeg, png, gif, zip, 7z"),
  1920. 'description' => t('The Content module permits "public" (non-encrypted) files to be uploaded and stored on the web server.
  1921. Enter the entensions, separated by comma, that you wish to allow. Do not enter periods before the extension.
  1922. If you are unsure what to enter, use the
  1923. following:
  1924. <br>css, txt, pdf, doc, docx, csv, xls, xlsx, ppt, pptx, rtf, odt, jpg, jpeg, png, gif, zip, 7z'),
  1925. );
  1926. $form["contact_email_address"] = array(
  1927. "type" => "textfield",
  1928. "label" => t("Contact email address:"),
  1929. "value" => variable_get("contact_email_address", ""),
  1930. "description" => t("Enter the email address to mail when a user accesses the
  1931. Contact FlightPath Production Team popup. Leave blank to disable the link to the popup."),
  1932. );
  1933. $form["notify_apply_draft_changes_email_address"] = array(
  1934. "type" => "textfield",
  1935. "label" => t("Notify apply draft changes email address:"),
  1936. "value" => variable_get("notify_apply_draft_changes_email_address", ""),
  1937. "description" => t("Enter 1 or more email addresses (separated by comma) to notify when
  1938. draft changes are applied from the admin console.
  1939. Leave blank to disable."),
  1940. );
  1941. $form["notify_mysql_error_email_address"] = array(
  1942. "type" => "textfield",
  1943. "label" => t("Notify MySQL error email address:"),
  1944. "value" => variable_get("notify_mysql_error_email_address", ""),
  1945. "description" => t("Enter 1 or more email addresses (separated by comma) to notify when
  1946. a mysql error occurs.
  1947. Leave blank to disable."),
  1948. );
  1949. $form["notify_php_error_email_address"] = array(
  1950. "type" => "textfield",
  1951. "label" => t("Notify PHP error email address:"),
  1952. "value" => variable_get("notify_php_error_email_address", ""),
  1953. "description" => t("Enter 1 or more email addresses (separated by comma) to notify of
  1954. PHP warnings or errors. Leave blank to disable. Recommendation: disable
  1955. on development, but enable on production.
  1956. <br><b>Note:</b> Messages will be stored in the temporary directory (below) and emailed out
  1957. as a digest every cron run."),
  1958. );
  1959. $form["admin_transfer_passcode"] = array(
  1960. "type" => "textfield",
  1961. "label" => t("Admin Apply Draft password:"),
  1962. "value" => variable_get("admin_transfer_passcode", "changeMe"),
  1963. "description" => t("Enter a password which an admin user must enter
  1964. in order to apply draft changes to FlightPath.
  1965. This is an added security measure. Ex: p@ssWord569w"),
  1966. );
  1967. $options = array(
  1968. "90" => t("90 days"),
  1969. "180" => t("180 days"),
  1970. "365" => t("1 year"),
  1971. "548" => t("1.5 years"),
  1972. "730" => t("2 years"),
  1973. "912" => t("2.5 years"),
  1974. "1095" => t("3 years"),
  1975. "1460" => t("4 years"),
  1976. "1825" => t("5 years"),
  1977. "2190" => t("6 years"),
  1978. "2555" => t("7 years"),
  1979. "2920" => t("8 years"),
  1980. "3285" => t("9 years"),
  1981. "3650" => t("10 years"),
  1982. "never" => t("Never - Do not trim log table"),
  1983. );
  1984. $form["max_watchdog_age"] = array(
  1985. "type" => "select",
  1986. "label" => t("Max watchdog (log) entry age:"),
  1987. "hide_please_select" => TRUE,
  1988. "options" => $options,
  1989. "value" => variable_get("max_watchdog_age", "1095"),
  1990. "description" => t("Keep entries in the watchdog log tables until they are this old.
  1991. Entries older than this will be deleted at every cron run.
  1992. For example, if you only want to keep log entries for 1 year, then
  1993. set this to 1 year.
  1994. <b>Warning:</b> the Stats module uses data in this table to create
  1995. statistics and reports about use of FlightPath. Once data is removed from the
  1996. watchdog table, it cannot be retrieved again.
  1997. <br>If you are unsure what to put here, select '3 years'."),
  1998. );
  1999. $form['max_watchdog_debug_age'] = array(
  2000. "type" => "select",
  2001. "label" => t("Max watchdog (log) DEBUG entry age:"),
  2002. "hide_please_select" => TRUE,
  2003. "options" => array('7' => t('7 days'), '15' => t('15 days'), '30' => t('30 days'), '60' => t('60 days'), '90' => t('90 days'), '180' => t('180 days'), '365' => t('1 year'), 'never' => t("Never - do not remove debug entries from log table")),
  2004. "value" => variable_get("max_watchdog_debug_age", "30"),
  2005. "description" => t("This is similar to the setting above, but this sets how long to keep 'debug' messages in the watchdog (logs).
  2006. Debug events are generally useful for tracking down issues or problems, and are not used in any official reporting. Removing them
  2007. helps reduce the size of the watchdog table.
  2008. <br>If unsure what to choose, select '1 year'."),
  2009. );
  2010. $form['max_watchdog_error_age'] = array(
  2011. "type" => "select",
  2012. "label" => t("Max watchdog (log) ERROR entry age:"),
  2013. "hide_please_select" => TRUE,
  2014. "options" => array('7' => t('7 days'), '15' => t('15 days'), '30' => t('30 days'), '60' => t('60 days'), '90' => t('90 days'), '180' => t('180 days'), '365' => t('1 year'), 'never' => t("Never - do not remove error entries from log table")),
  2015. "value" => variable_get("max_watchdog_error_age", "90"),
  2016. "description" => t("This is similar to the setting above, but this sets how long to keep 'error' messages in the watchdog (logs).
  2017. Error messages are useful for finding bugs or database issues, but are not used in any official reporting. Removing them
  2018. helps reduce the size of the watchdog table.
  2019. <br>If unsure what to choose, select '1 year'."),
  2020. );
  2021. $form["admin_degrees_default_allow_dynamic"] = array(
  2022. "type" => "textfield",
  2023. "size" => 5,
  2024. "label" => t("Default 'Allow Dynamic' value for new degrees:"),
  2025. "value" => variable_get("admin_degrees_default_allow_dynamic", "1"),
  2026. "description" => t("When creating a new degree, this is the default value to set for 'Allow Dynamic'. If set to 1 (the number one), it means
  2027. the degree is allowed to be dynamic, meaning it can be combined with other 'dynamic' degrees. If it is set to 0 (zero), it
  2028. means the degree is not allowed to be combined with anything else. If you are unsure what to enter here, type 1 (one)."),
  2029. );
  2030. $form["degree_classifications_level_1"] = array(
  2031. "label" => t("Degree Classifications - Level 1:"),
  2032. "type" => "textarea",
  2033. "rows" => 3,
  2034. "value" => variable_get("degree_classifications_level_1", "MAJOR ~ Major"),
  2035. "description" => t("Enter the 'level 1' (ie, top level) degree classifications, one per line, in the following format:
  2036. <br>&nbsp; &nbsp; MACHINE_NAME ~ Title
  2037. <br> Example: MAJOR ~ Major
  2038. <br>These are degrees which might be combined with
  2039. another degree, as in a double-major, or selected on their own for graduation.
  2040. For example, a degree in Computer Science, by itself would be
  2041. classified as a 'Major' by most universities. If you are unsure what to enter,
  2042. use: MAJOR ~ Major"),
  2043. );
  2044. $form["degree_classifications_level_2"] = array(
  2045. "label" => t("Degree Classifications - Level 2:"),
  2046. "type" => "textarea",
  2047. "rows" => 3,
  2048. "value" => variable_get("degree_classifications_level_2", "MINOR ~ Minor"),
  2049. "description" => t("Enter the 'level 2' degree classifications, one per line, in the following format:
  2050. <br>&nbsp; &nbsp; MACHINE_NAME ~ Title
  2051. <br> Example: MINOR ~ Minor
  2052. <br>These are degrees which might be combined with another degree
  2053. but are not selected by themselves for graduation. Most universities
  2054. would consider this type to be a 'Minor'. For example, a student
  2055. might Major in Computer Science, with a Minor in Math. In this instance,
  2056. Math would be classified by this level. If you are unsuare what to enter, use:
  2057. MINOR ~ Minor"),
  2058. );
  2059. $form["degree_classifications_level_3"] = array(
  2060. "label" => t("Degree Classifications - Level 3 (Add-on degrees, attached to other degrees):"),
  2061. "type" => "textarea",
  2062. "rows" => 3,
  2063. "value" => variable_get("degree_classifications_level_3", "CONC ~ Concentration"),
  2064. "description" => t("Enter the 'level 3' degree classifications, one per line, in the following format:
  2065. <br>&nbsp; &nbsp; MACHINE_NAME ~ Title
  2066. <br> Example: CONC ~ Concentration
  2067. <br>These are degree plans which are only ever 'attached' to other degree plans as an add-on option
  2068. to the student.
  2069. For example, Computer Science might have an Option or Track or Concentration in 'Business'.
  2070. The Business concentration would ONLY be selectable if the student were already majoring in Computer Science,
  2071. therefor it would fall into this classification.
  2072. If unsure what to enter here, use: CONC ~ Concentration"),
  2073. );
  2074. $form["enable_legacy_concentrations"] = array(
  2075. "label" => t("Enable legacy concentrations?"),
  2076. "type" => "checkbox",
  2077. "value" => variable_get("enable_legacy_concentrations", FALSE),
  2078. "description" => t("If checked, FlightPath will treat a degree with a concentration as its own unique major_code, rather
  2079. than the concentration being treated as a Level-3 degree. This is how concentrations were handled in FlightPath 4x and
  2080. before-- as entirely separate degrees.<br><a href='https://getflightpath.com/node/13467' target='_blank'>See this documentation page for more details.</a>
  2081. Note that enabling this setting can cause extra confusion and data entry when creating degrees.
  2082. If you are unsure what to do, leave this unchecked."),
  2083. );
  2084. $form["allowed_student_ranks"] = array(
  2085. "type" => "textfield",
  2086. "label" => t("Allowed student ranks (CSV):"),
  2087. "value" => variable_get("allowed_student_ranks", "FR, SO, JR, SR"),
  2088. "description" => t("This is a list of which student ranks (aka Classifications) are allowed into FlightPath.
  2089. It should be separated by commas.
  2090. This also affects which students you may search for on the Advisees
  2091. tab. Ex: FR, SO, JR, SR"),
  2092. );
  2093. $form["rank_descriptions"] = array(
  2094. "type" => "textarea",
  2095. "label" => t("Rank descriptions:"),
  2096. "rows" => 8,
  2097. "value" => variable_get("rank_descriptions", "FR ~ Freshman\nSO ~ Sophomore\nJR ~ Junior\nSR ~ Senior\nPR ~ Professional\nGR ~ Graduate"),
  2098. "description" => t("Enter the rank code (from above) and the description which should appear on screen, in the format:
  2099. RANK ~ DESC, one per line.
  2100. <br>Ex:
  2101. <br>&nbsp; FR ~ Freshman
  2102. <br>&nbsp; SO ~ Sophomore
  2103. <br>&nbsp; JR ~ Junior
  2104. <br>&nbsp; SR ~ Senior"),
  2105. );
  2106. $form["not_allowed_student_message"] = array(
  2107. "type" => "textarea",
  2108. "label" => t("Not allowed student message:"),
  2109. "value" => variable_get("not_allowed_student_message", ""),
  2110. "description" => t("When a student is NOT allowed into FlightPath because of their
  2111. rank, this message will be displayed."),
  2112. );
  2113. $form['login_help_cid'] = array(
  2114. 'label' => t("Enter the 'Need Help Logging In?' Content ID number:"),
  2115. 'type' => 'textfield',
  2116. 'size' => 5,
  2117. 'value' => variable_get("login_help_cid", "0"),
  2118. 'description' => t("Enter the Content ID number of the web page you'd like the visitor to see if they click the 'Need Help Logging In?' link
  2119. on the login page. If you leave this blank, a generic message will be shown. To customize, visit the Content sectiona and
  2120. create a new 'Page'. Once you save, you will see the Content ID number at the end of the URL. Ex: flightpath_url/content/543 means
  2121. that 543 is the Content ID."),
  2122. );
  2123. $form['logout_message'] = array(
  2124. 'label' => t("Log out message:"),
  2125. 'type' => 'textarea',
  2126. 'value' => variable_get("logout_message", "You have been logged out of FlightPath."),
  2127. 'description' => t("This message displays to the user when they have logged out of FlightPath. If unsure what to enter, use the following:
  2128. <br>&nbsp; &nbsp; <i>You have been logged out of FlightPath.</i>
  2129. <br><b>Note:</b> You may use basic HTML in this field to add bold, italics, or links."),
  2130. );
  2131. $form['recalculate_alert_badge_seconds'] = array(
  2132. 'label' => t('How often should we recalculate the alert "bell" count?'),
  2133. 'type' => 'select',
  2134. 'hide_please_select' => TRUE,
  2135. 'options' => array( 1 => "1 second (recalculate on every page load)",
  2136. 15 => "15 seconds",
  2137. 30 => "30 seconds (default)",
  2138. 60 => "1 minute",
  2139. 300 => "5 minutes",
  2140. 600 => "10 minutes",
  2141. 1200 => "20 minutes",
  2142. ),
  2143. 'value' => variable_get('recalculate_alert_badge_seconds', 30),
  2144. 'description' => t('The alert "bell" at the top-right of the screen will display a notification graphic if there is something important for the user to look at.
  2145. For example, a new email or text message sent by the student. Please select how often we should check to see
  2146. if there is anything new. The alert count will be automatically recalculated when new content is created or deleted.
  2147. <br>This process may cause delays in page loads. If you notice slow page loads, set this time higher.
  2148. <br>If unsure, set to <em>30 seconds</em>.'),
  2149. );
  2150. $form["delete_flagged_data_from_db"] = array(
  2151. "label" => t("Permanently delete the following from the database once it has been flagged for deletion:"),
  2152. "type" => "checkboxes",
  2153. 'options' => array(
  2154. 'advising_sessions' => 'Advising sessions',
  2155. 'advising_comments' => 'Advising comments',
  2156. 'student_substitutions' => 'Student substitutions',
  2157. 'student_unassign_group' => 'Student course movements/unassignments',
  2158. 'student_unassign_transfer_eqv' => 'Student unassignments for transfer course eqvs',
  2159. 'content' => 'Content items (eg, Pages, Appointments, Alerts, etc.)',
  2160. ),
  2161. "value" => variable_get("delete_flagged_data_from_db", array()),
  2162. "description" => t('Normally, FlightPath <b>does not</b> actually delete certain "deleted" data, like those listed above. Instead, they are
  2163. flagged as "deleted" and do not display except in database reports. However, by checking the
  2164. boxes above, FlightPath will permanently delete such flagged data from the database every so often as part
  2165. of routine cleanup operations designed to save space.
  2166. <br>Note: This would make a complete history of
  2167. those actions unrecoverable.
  2168. <br><b>If you are unsure what to do, leave all boxes unchecked.</b>'),
  2169. );
  2170. return $form;
  2171. }
  2172. /**
  2173. * Extra validate handler for our system settings form.
  2174. */
  2175. function system_settings_form_validate($form, &$form_state) {
  2176. // Nothing here at the moment.
  2177. }
  2178. /**
  2179. * Extra submit handler for the system_settings_form
  2180. *
  2181. * @param unknown_type $form
  2182. * @param unknown_type $form_state
  2183. */
  2184. function system_settings_form_submit($form, &$form_state) {
  2185. // Empty for now.
  2186. }
  2187. /**
  2188. * Implementation of hook_cron
  2189. *
  2190. * We will perform operations necessary for keep FlightPath's tables in shape.
  2191. *
  2192. */
  2193. function system_cron() {
  2194. // Should we trim the watchdog table of extra entries? Only once every so often, not every cron run.
  2195. $last_run = intval(variable_get("system_last_run_trim_watchdog", 0));
  2196. $check_against = strtotime("NOW - 7 DAYS"); // don't run any more often than once every 7 days
  2197. $c = 0;
  2198. if ($check_against > $last_run) {
  2199. // Should we "trim" the watchdog table of old entries?
  2200. $max_age_days = variable_get("max_watchdog_age", "1095");
  2201. if ($max_age_days != "never" && ($max_age_days*1) > 0) {
  2202. // Okay, let's trim the table.
  2203. $max_timestamp = strtotime("$max_age_days DAYS AGO");
  2204. $result = db_query("DELETE FROM watchdog WHERE `timestamp` < ? ", $max_timestamp);
  2205. $rows = db_affected_rows($result);
  2206. if ($rows > 0) {
  2207. watchdog("system", t("@rows old rows (older than @max days) trimmed from watchdog table on system cron run."), array("@rows" => $rows, "@max" => $max_age_days), WATCHDOG_DEBUG);
  2208. }
  2209. }
  2210. // Should we trim the watchdog table of DEBUG records?
  2211. $max_age_days = intval(variable_get("max_watchdog_debug_age", "30"));
  2212. if ($max_age_days != "never" && ($max_age_days) > 0) {
  2213. // Okay, let's trim the table.
  2214. $max_timestamp = strtotime("$max_age_days DAYS AGO");
  2215. $result = db_query("DELETE FROM watchdog WHERE `timestamp` < ? AND severity = ? ", $max_timestamp, WATCHDOG_DEBUG);
  2216. $rows = db_affected_rows($result);
  2217. if ($rows > 0) {
  2218. watchdog("system", t("@rows old 'debug' rows (older than @max days) trimmed from watchdog table on system cron run."), array("@rows" => $rows, "@max" => $max_age_days), WATCHDOG_DEBUG);
  2219. }
  2220. }
  2221. // Should we trim the watchdog table of ERROR records?
  2222. $max_age_days = intval(variable_get("max_watchdog_error_age", "30"));
  2223. if ($max_age_days != "never" && ($max_age_days) > 0) {
  2224. // Okay, let's trim the table.
  2225. $max_timestamp = strtotime("$max_age_days DAYS AGO");
  2226. $result = db_query("DELETE FROM watchdog WHERE `timestamp` < ? AND severity = ? ", $max_timestamp, WATCHDOG_ERROR);
  2227. $rows = db_affected_rows($result);
  2228. if ($rows > 0) {
  2229. watchdog("system", t("@rows old 'error' rows (older than @max days) trimmed from watchdog table on system cron run."), array("@rows" => $rows, "@max" => $max_age_days), WATCHDOG_DEBUG);
  2230. }
  2231. }
  2232. variable_set("system_last_run_trim_watchdog", time());
  2233. } // check against > last_run, so we should do it.
  2234. // Should we delete from user_attributes any mda_validation_codes which are older than X hours?
  2235. $max_age_hours = 1;
  2236. // Okay, let's trim the table.
  2237. $max_timestamp = strtotime("NOW - $max_age_hours HOURS");
  2238. $result = db_query("DELETE FROM user_attributes WHERE `name` = 'mfa_validation_code' AND `updated` < ? ", $max_timestamp);
  2239. $rows = db_affected_rows($result);
  2240. if ($rows > 0) {
  2241. watchdog("system", t("@rows old 'mfa_validation_code' rows (older than @max hours) trimmed from user_attributes table on system cron run."), array("@rows" => $rows, "@max" => $max_age_hours), WATCHDOG_DEBUG);
  2242. }
  2243. // Should we email someone of php errors?
  2244. $tomail = trim(variable_get("notify_php_error_email_address", ""));
  2245. if ($tomail != "") {
  2246. // We should add this to our "to email" file, which will get emailed to this mail address later on.
  2247. $error_files_dir = fp_get_files_path() . "/private";
  2248. // Make sure our log file exists
  2249. if (file_exists($error_files_dir . '/fp_php_errors_to_email_log.txt')) {
  2250. // is there anything for us to email?
  2251. $emsg = file_get_contents($error_files_dir . '/fp_php_errors_to_email_log.txt');
  2252. if ($emsg && fp_trim($emsg) != "") {
  2253. $hostname = php_uname('n');
  2254. fp_mail($tomail, "PHP Error(s) in FlightPath ($hostname)", $emsg);
  2255. // Make the file blank.
  2256. file_put_contents($error_files_dir . '/fp_php_errors_to_email_log.txt', '');
  2257. }
  2258. }
  2259. }
  2260. ////////////
  2261. // Remove expired banned ips, if any exist.
  2262. $ban_expire = variable_get('system_ban_ip_expire', '2 WEEKS');
  2263. if ($ban_expire != 'never') {
  2264. $ban_ip_file_path = fp_get_files_path() . '/private/banned_ips.txt';
  2265. if (file_exists($ban_ip_file_path)) {
  2266. $max_timestamp = strtotime("NOW - $ban_expire");
  2267. $res = db_query("SELECT * FROM banned_ips WHERE posted_ts < ?", array($max_timestamp));
  2268. while ($cur = db_fetch_object($res)) {
  2269. $ip = $cur->ip_address;
  2270. // Erase this ip address from our ban file.
  2271. $c = file_get_contents($ban_ip_file_path);
  2272. $c = str_replace("$ip", '', $c);
  2273. $c = str_replace(" ", '', $c);
  2274. $c = str_replace(" \n", "", $c);
  2275. $c = str_replace("\n\n", "", $c);
  2276. $c = trim($c);
  2277. // Write back into our file
  2278. $respc = file_put_contents($ban_ip_file_path, $c);
  2279. if ($respc === FALSE) {
  2280. watchdog("system", "Unable to write to the ban ip file $ban_ip_file_path. Trying to remove $ip. File permission issue?", array(), WATCHDOG_ERROR);
  2281. }
  2282. // remove from table
  2283. db_query("DELETE FROM banned_ips WHERE ip_address = ?", array($ip));
  2284. } // while cur
  2285. } // if file exists
  2286. } // ban_expire != 'never'
  2287. } // hook_cron
  2288. /**
  2289. * Intercepts form submissions from forms built with the form API.
  2290. */
  2291. function system_handle_form_submit() {
  2292. $callback = $_REQUEST["callback"] ?? '';
  2293. $form_type = $_REQUEST["form_type"] ?? '';
  2294. if (!is_string($callback)) $callback = '';
  2295. if (!is_string($form_type)) $form_type = '';
  2296. watchdog('system', "handle_form_submit callback:$callback, form_type:$form_type", array());
  2297. $form_include = @$_REQUEST["form_include"];
  2298. $form_token = @$_REQUEST["form_token"];
  2299. // Make sure the form_token is valid!
  2300. if ($form_token != md5($callback . fp_token())) {
  2301. watchdog('system', "handle_form_submit - Error; invalid form token. Got: $form_token. Expected: " . md5($callback . fp_token()), array(), WATCHDOG_ERROR);
  2302. die(t("Sorry, but you have encountered an error. A form submission was flagged
  2303. as possibly being an invalid or forged submission. This may constitute a bug
  2304. in the system. Please report this error to your Systems Administrator."));
  2305. }
  2306. if ($form_include != "") {
  2307. // This is a file we need to include in order to complete the submission process.
  2308. // We will also make sure that we only allow certain file extensions to be included.
  2309. $allowed_ext = array(
  2310. "php",
  2311. "inc",
  2312. "class",
  2313. "module",
  2314. );
  2315. $temp = explode(".", $form_include);
  2316. $test_ext = trim($temp[count($temp) - 1]);
  2317. if (!in_array($test_ext, $allowed_ext)) {
  2318. watchdog('system', "handle_form_submit - file type $test_ext not allowed to be included in form submission.", array(), WATCHDOG_ERROR);
  2319. fp_add_message(t("Include file type (%ext) not allowed in form submission. Allowed extensions: .php, .inc, .class, .module.", array("%ext" => $test_ext)), "error");
  2320. fp_goto("<front>");
  2321. return;
  2322. }
  2323. // We need to make sure, before we include this file, that it is something only available from within the main FlightPath directory.
  2324. $absolute_path = realpath($form_include);
  2325. $absolute_path = str_replace("\\", "/", $absolute_path);
  2326. // In order for us to proceed, the $absolute_path must BEGIN with our base FlightPath installation directory.
  2327. $file_system_path = $GLOBALS['fp_system_settings']['file_system_path'];
  2328. if (substr($absolute_path, 0, strlen($file_system_path)) != $file_system_path) {
  2329. watchdog('system', "handle_form_submit - Include file outside of FlightPath installation directory.
  2330. <br>FlightPath directory path: %fsp
  2331. <br>Include file path: %ap", array("%fsp" => $file_system_path, "%ap" => $absolute_path), WATCHDOG_ERROR);
  2332. fp_add_message(t("Include file in form submission is outside of the FlightPath installation directory.
  2333. <br>FlightPath directory path: %fsp
  2334. <br>Include file path: %ap", array("%fsp" => $file_system_path, "%ap" => $absolute_path)), "error");
  2335. fp_goto("<front>");
  2336. return;
  2337. }
  2338. include_once($form_include);
  2339. }
  2340. // We need to make sure the user has permission to submit this form!
  2341. $form_path = $_REQUEST["form_path"];
  2342. // Check the menu router table for whatever the permissions were for this
  2343. // path, if any.
  2344. if ($form_path != "") {
  2345. // For the sake of makeing sure our wildcards get replaced correctly,
  2346. // temporarily set $_REQUEST['q'] to our $form_q.
  2347. $form_q = base64_decode($_REQUEST["form_q_64"]);
  2348. $temp_q = $_REQUEST['q'];
  2349. $_REQUEST['q'] = $form_q;
  2350. $router_item = menu_get_item($form_path) ;
  2351. if (!menu_check_user_access($router_item)) {
  2352. // The user does NOT have access to submit this form! The fact that
  2353. // it has made it this far means this may be some sort of hacking attempt.
  2354. watchdog('system', "handle_form_submit - Insufficient permissions to submit form.", array(), WATCHDOG_ERROR);
  2355. die(t("Sorry, but you have encountered an error. A form submission was flagged
  2356. as possibly being an invalid or having insufficient permissions to submit.
  2357. This may constitute a bug in the system.
  2358. Please report this error to your Systems Administrator."));
  2359. }
  2360. // I don't think this is needed, just causes problems! // $_REQUEST['q'] = $temp_q; // set back to original, just in case.
  2361. }
  2362. // Let's get our set of allowed values, by looking at the original form,
  2363. // and grab what's in the POST which matches the name.
  2364. $values = array();
  2365. $safe_values = array(); // will be the same as $values, but anything of type password will not be included.
  2366. if (function_exists($callback)) {
  2367. // Get any params for the callback, or, an empty array.
  2368. $form_params = @unserialize(base64_decode($_REQUEST['form_params']));
  2369. if (!$form_params) {
  2370. $form_params = array();
  2371. }
  2372. // Actually get the form now.
  2373. $form = fp_get_form($callback, $form_params);
  2374. foreach ($form as $name => $element) {
  2375. // Save to our $values array, but we don't care about markup.
  2376. if (@$element["type"] != "" && @$element["type"] != "markup" && @$element["type"] != "markup_no_wrappers") {
  2377. $values[$name] = @$_POST[$name];
  2378. // Save to safe_values, too, if this is not a password field.
  2379. if (@$element["type"] != "password") {
  2380. $safe_values[$name] = @$_POST[$name];
  2381. }
  2382. // If this is a checkbox, and we have any value in the POST, it should
  2383. // be saved as boolean TRUE
  2384. if (isset($element["type"]) && $element["type"] == "checkbox") {
  2385. if (isset($_POST[$name]) && $_POST[$name] === "1") {
  2386. $values[$name] = TRUE;
  2387. }
  2388. else {
  2389. $values[$name] = FALSE;
  2390. }
  2391. }
  2392. }
  2393. // Do we need to alter the value from the POST?
  2394. // If this element is a cfieldset, it may contain other elements. We should get
  2395. // those values too.
  2396. if (isset($element["elements"])) {
  2397. foreach ($element["elements"] as $k => $v) {
  2398. foreach ($element["elements"][$k] as $cname => $celement) {
  2399. // Save to our $values array, but we don't care about markup.
  2400. if (@$celement["type"] != "" && @$celement["type"] != "markup" && @$element["type"] != "markup_no_wrappers") {
  2401. $values[$cname] = @$_POST[$cname];
  2402. // Save to save_values, too, if this is not a password field.
  2403. if (@$celement["type"] != "password") {
  2404. $safe_values[$cname] = @$_POST[$cname];
  2405. }
  2406. // If this is a checkbox, and we have any value in the POST, it should
  2407. // be saved as boolean TRUE
  2408. if (isset($celement["type"]) && $celement["type"] == "checkbox") {
  2409. if (isset($_POST[$cname]) && $_POST[$cname] === "1") {
  2410. $values[$cname] = TRUE;
  2411. }
  2412. else {
  2413. $values[$cname] = FALSE;
  2414. }
  2415. }
  2416. }
  2417. }
  2418. }
  2419. }
  2420. }
  2421. }
  2422. // Does the form have any defined submit_handler's? If not, let's assign it the
  2423. // default of callback_submit().
  2424. $submit_handlers = $form["#submit_handlers"];
  2425. if (!is_array($submit_handlers)) $submit_handlers = array();
  2426. // If the submit_handlers is empty, then add our default submit handler. We don't
  2427. // want to do this if the user went out of their way to enter a different handler.
  2428. if (count($submit_handlers) == 0) {
  2429. array_push($submit_handlers, $callback . "_submit");
  2430. }
  2431. // Does the form have any defined validate_handler's? This works exactly like the submit handler.
  2432. $validate_handlers = $form["#validate_handlers"];
  2433. if (!is_array($validate_handlers)) $validate_handlers = array();
  2434. if (count($validate_handlers) == 0) {
  2435. array_push($validate_handlers, $callback . "_validate");
  2436. }
  2437. // Let's store our values in the SESSION in case we need them later on.
  2438. // But only if this is NOT a system_settings form!
  2439. if ($form_type != "system_settings") {
  2440. // Do not store any "password" field, for security, so it isn't stored
  2441. // in the server's session file in plain text.
  2442. // For this reason we will use the $safe_values array we created earlier.
  2443. $_SESSION["fp_form_submissions"][$callback]["values"] = $safe_values;
  2444. }
  2445. $form_state = array("values" => $values, "POST" => $_POST);
  2446. // Let's pass this through our default form validator (mainly to check for required fields
  2447. // which do not have values entered)
  2448. form_basic_validate($form, $form_state);
  2449. if (!form_has_errors()) {
  2450. // Let's now pass it through all of our custom validators, if there are any.
  2451. foreach ($validate_handlers as $validate_callback) {
  2452. if (function_exists($validate_callback)) {
  2453. call_user_func_array($validate_callback, array(&$form, &$form_state));
  2454. // Should we stop checking the other validator functions?
  2455. if (isset($GLOBALS['form_error_stop_further_validators']) && $GLOBALS['form_error_stop_further_validators'] === TRUE) {
  2456. unset($GLOBALS['form_error_stop_further_validators']);
  2457. break;
  2458. }
  2459. }
  2460. }
  2461. }
  2462. if (!form_has_errors()) {
  2463. // No errors from the validate, so let's continue.
  2464. // Is this a "system settings" form, or a normal form?
  2465. if ($form_type == "system_settings") {
  2466. // This is system settings, so let's save all of our values to the variables table.
  2467. // Write our values array to our variable table.
  2468. foreach ($form_state["values"] as $name => $val) {
  2469. variable_set($name, $val);
  2470. }
  2471. fp_add_message(t("Settings saved successfully."));
  2472. }
  2473. // Let's go through the form's submit handlers now.
  2474. foreach ($submit_handlers as $submit_callback) {
  2475. if (function_exists($submit_callback)) {
  2476. call_user_func_array($submit_callback, array(&$form, &$form_state));
  2477. }
  2478. }
  2479. }
  2480. // Figure out where we are supposed to redirect the user.
  2481. $redirect_path = $redirect_query = "";
  2482. if (!form_has_errors() && isset($form["#redirect"]) && is_array($form["#redirect"])) {
  2483. $redirect_path = $form["#redirect"]["path"];
  2484. $redirect_query = (string) @$form["#redirect"]["query"];
  2485. }
  2486. else {
  2487. $redirect_path = @$_REQUEST["default_redirect_path"];
  2488. $redirect_query = (string) @$_REQUEST["default_redirect_query"];
  2489. // To help prevent directory traversal attacks, the redirect_path cannot contain periods (.) and semi-colons, and other trouble characters
  2490. $redirect_path = str_replace(".", "", $redirect_path);
  2491. $redirect_path = str_replace(";", "", $redirect_path);
  2492. $redirect_path = str_replace("'", "", $redirect_path);
  2493. $redirect_path = str_replace('"', "", $redirect_path);
  2494. $redirect_path = str_replace(' ', "", $redirect_path);
  2495. }
  2496. // Was scroll_top set? Meaning, are we meant to scroll to a specific position when the page loads?
  2497. if (isset($_REQUEST["scroll_top"])) {
  2498. $scroll_top = @floatval($_REQUEST["scroll_top"]);
  2499. if ($scroll_top > 0) {
  2500. if ($redirect_query != "") $redirect_query .= "&"; // not blank? Add this as another property with &.
  2501. $redirect_query .= "scroll_top=" . $scroll_top;
  2502. }
  2503. }
  2504. // If there is a Batch process we need to do, do it here instead of the fp_goto.
  2505. if (isset($_SESSION["fp_batch_id"]) && function_exists("batch_menu")) {
  2506. $batch_id = $_SESSION["fp_batch_id"];
  2507. unset($_SESSION["fp_batch_id"]);
  2508. batch_start_batch_from_form_submit($batch_id, $redirect_path, $redirect_query);
  2509. return;
  2510. }
  2511. else if (isset($_SESSION["fp_batch_id"]) && !function_exists("batch_menu")) {
  2512. // We requested a batch action, but the batch module is not installed.
  2513. watchdog('system', "handle_form_submit - Batch process attempted, but batch module not enabled", array(), WATCHDOG_ERROR);
  2514. fp_add_message(t("A batch process was attempted, but it appears that the Batch module is not enabled. Please contact your FlightPath administrator."), "error");
  2515. unset($_SESSION["fp_batch_id"]);
  2516. }
  2517. // Okay, go back to where we were!
  2518. fp_goto($redirect_path, $redirect_query);
  2519. }
  2520. function system_handle_logout() {
  2521. global $user;
  2522. $user_name = $user->name;
  2523. $uid = $user->id;
  2524. // Finish up logging out.
  2525. // In an effort to mimimize a bug in Safari, we will
  2526. // overwrite the SESSION variables, then perform a few other operations,
  2527. // to make sure they are well and truly destroyed.
  2528. foreach ($_SESSION as $key => $val) {
  2529. $_SESSION[$key] = "x";
  2530. }
  2531. foreach ($_SESSION as $key => $val) {
  2532. $_SESSION[$key] = FALSE;
  2533. }
  2534. $_SESSION = array();
  2535. if (isset($_COOKIE[session_name()])) // remove cookie by setting it to expire, if it's there.
  2536. {
  2537. $cookie_expires = time() - 3600;
  2538. setcookie(session_name(), "", $cookie_expires, '/');
  2539. }
  2540. // unset cookies from https://stackoverflow.com/questions/2310558/how-to-delete-all-cookies-of-my-website-in-php
  2541. // We won't use $_COOKIE for this, as we might get an array for the $val, if the cookie was set using array notation. This
  2542. // code snippit should fix that.
  2543. if (isset($_SERVER['HTTP_COOKIE'])) {
  2544. $cookies = explode(';', $_SERVER['HTTP_COOKIE']);
  2545. foreach($cookies as $cookie) {
  2546. $parts = explode('=', $cookie);
  2547. $name = trim($parts[0]);
  2548. // Only do this for non-mfa related cookies.
  2549. if (!str_starts_with($name, "flightpath_mfa_remember")) {
  2550. setcookie($name, '', time() - 3600);
  2551. setcookie($name, '', time() - 3600, '/');
  2552. }
  2553. }
  2554. }
  2555. // I know this is repetitive, but I want to make ABSOLUTELY SURE
  2556. // I am removing the session by removing it, creating a new one, then killing that one too.
  2557. session_destroy();
  2558. session_commit();
  2559. session_start();
  2560. session_destroy();
  2561. session_commit();
  2562. // Check for hook_user_logout
  2563. $modules = modules_implement_hook("user_logout");
  2564. foreach($modules as $module) {
  2565. call_user_func($module . '_user_logout');
  2566. }
  2567. watchdog("logout", "@user has logged out", array("@user" => "$user_name ($uid)"));
  2568. fp_goto("<front>", "logout=true");
  2569. }
  2570. /**
  2571. * This function will clear our various caches by calling
  2572. * on the hook_clear_cache in each module.
  2573. */
  2574. function system_perform_clear_cache() {
  2575. fp_clear_cache();
  2576. fp_goto("admin-tools");
  2577. }
  2578. /**
  2579. * Called from menu, will run hook_cron() for all modules.
  2580. */
  2581. function system_perform_run_cron() {
  2582. // Keep the script from timing out prematurely...
  2583. set_time_limit(99999); // around 27 hours.
  2584. watchdog("cron", "Cron run started", array(), WATCHDOG_DEBUG);
  2585. invoke_hook("cron");
  2586. watchdog("cron", "Cron run completed", array(), WATCHDOG_DEBUG);
  2587. variable_set("cron_last_run", time());
  2588. fp_add_message(t("Cron run completed successfully."));
  2589. fp_goto("admin-tools/admin");
  2590. }
  2591. /**
  2592. * This page displayes the results of each module's hook_status.
  2593. *
  2594. */
  2595. function system_display_status_page() {
  2596. $rtn = "";
  2597. $pol = "";
  2598. fp_add_css(fp_get_module_path("system") . "/css/style.css");
  2599. $status_array = invoke_hook("status"); // get everyone's hook_status.
  2600. $rtn .= "<p>" . t("This page will show you important status messages
  2601. about FlightPath. For example, what modules (if any) have
  2602. an update available.") . "</p>";
  2603. $rtn .= "<table width='100%' border='1' class='status-table' cellpadding='4'>
  2604. <tr class='header-row'>
  2605. <th width='10%' class='package-header'>" . t("Package/Module") . "</th>
  2606. <th>" . t("Status") . "</th>
  2607. </tr>
  2608. <tr class='status-row'>
  2609. <td>" . t("PHP") . "</td>
  2610. <td>" . t("Version:") . ' ' . phpversion() . "</td>
  2611. </tr>
  2612. ";
  2613. foreach ($status_array as $module => $details) {
  2614. $pol = ($pol == "even") ? "odd" : "even";
  2615. if (@$details["severity"] == "") $details["severity"] = "normal";
  2616. $rtn .= "<tr class='status-row status-row-$pol'>
  2617. <td valign='top' class='module-name'>$module</td>
  2618. <td valign='top' class='module-status module-status-" . $details["severity"] . "'>
  2619. " . $details["status"] . "
  2620. </td>
  2621. </tr>";
  2622. }
  2623. $rtn .= "</table>";
  2624. return $rtn;
  2625. }
  2626. /**
  2627. * Implementation of hook_status
  2628. * Expected return is array(
  2629. * "severity" => "normal" or "warning" or "alert",
  2630. * "status" => "A message to display to the user.",
  2631. * );
  2632. */
  2633. function system_status() {
  2634. $rtn = array();
  2635. $rtn["severity"] = "normal";
  2636. $rtn["status"] = "";
  2637. // Check on the last time cron was run; make sure it's working properly.
  2638. $last_run = convert_time(variable_get("cron_last_run", 0));
  2639. // Report on current details about FlightPath.
  2640. $fpversion = FLIGHTPATH_VERSION;
  2641. if ($fpversion == "%FP_VERSION%") {
  2642. // This means you are using a version not downloaded from getflightpath.com. Probably directly from a git repository.
  2643. $fpversion = "GitRepo";
  2644. }
  2645. $rtn["status"] .= "<p>" . t("FlightPath version:") . " " . FLIGHTPATH_CORE . "-" . $fpversion . "</p>";
  2646. if ($last_run < strtotime("-2 DAY")) {
  2647. $rtn["severity"] = "alert";
  2648. $rtn["status"] .= t("Cron hasn't run in over 2 days. For your installation of FlightPath
  2649. to function properly, cron.php must be accessed routinely. At least once per day is recommended.
  2650. Set for more frequently if making use of text messaging, emails, or notifications in FlightPath.
  2651. For example, every 10 minutes.");
  2652. }
  2653. else {
  2654. $rtn["status"] .= t("Cron was last run on %date", array("%date" => format_date($last_run)));
  2655. }
  2656. $cron_url = $GLOBALS["fp_system_settings"]["base_url"] . "/cron.php?t=" . $GLOBALS["fp_system_settings"]["cron_security_token"];
  2657. $rtn["status"] .= "<p style='font-size: 0.8em;'>" . t("Your site's cron URL is:");
  2658. $rtn["status"] .= "&nbsp; <i>" . $cron_url . "</i>
  2659. <br>" . t("Ex linux cron command (every 10 min):") . "&nbsp; <i style='background-color: beige;'>*/10 * * * * wget -O - -q -t 1 $cron_url</i>";
  2660. $rtn["status"] .= "</p>";
  2661. return $rtn;
  2662. }
  2663. /**
  2664. * Implements hook_clear_cache
  2665. * Take care of clearing caches managed by this module
  2666. */
  2667. function system_clear_cache() {
  2668. unset($_SESSION["fp_form_submissions"]);
  2669. unset($_SESSION["fp_db_host"]);
  2670. unset($_SESSION["fp_draft_mode"]);
  2671. unset($_SESSION["fp_simple_degree_plan_cache_for_student"]);
  2672. unset($_SESSION['fp_alert_count_by_type']);
  2673. unset($_SESSION['fp_alert_count_by_type_last_check']);
  2674. if (function_exists('apcu_fetch')) {
  2675. apcu_clear_cache();
  2676. }
  2677. menu_rebuild_cache();
  2678. system_rebuild_css_js_query_string();
  2679. }
  2680. /**
  2681. * This function will recreate the dummy query string we add to the end of css and js files.
  2682. *
  2683. */
  2684. function system_rebuild_css_js_query_string() {
  2685. // A dummy query string gets added to the URLs for css and javascript files,
  2686. // to give us control over browser caching. When this value changes (cause we
  2687. // cleared the cache, updated a module, etc) it tells the browser to get a new
  2688. // copy of our css and js files.
  2689. // This idea, like many other ideas in FlightPath, was borrowed from Drupal.
  2690. // The timestamp is converted to base 36 in order to make it more compact.
  2691. // This gives us a random-looking string of 6 numbers and letters.
  2692. variable_set('css_js_query_string', base_convert(time(), 10, 36));
  2693. }
  2694. /**
  2695. * Clears only the menu cache
  2696. */
  2697. function system_perform_clear_menu_cache() {
  2698. menu_rebuild_cache();
  2699. fp_goto("admin-tools/admin");
  2700. }
  2701. /**
  2702. * Display the "login" page. This is the default page displayed
  2703. * to the user, at /login, if they have not logged in yet.
  2704. *
  2705. * This page is meant to be displayed in conjunction with blocks, so the user can
  2706. * easily define their own messages and such.
  2707. *
  2708. * @return unknown
  2709. */
  2710. function system_display_login_page() {
  2711. $rtn = "";
  2712. fp_add_css(fp_get_module_path("system") . "/css/style.css");
  2713. $login_form = fp_render_form("system_login_form");
  2714. $rtn .= "<noscript>
  2715. <div style='padding: 5px; background-color: red; color: white; font-size: 12pt; font-weight: bold;'>
  2716. " . t("@FlightPath requires JavaScript to be enabled in order to
  2717. function correctly. Please enable JavaScript on your browser
  2718. before continuing.", array("@FlightPath" => variable_get("system_name", "FlightPath"))) . "</div>
  2719. </noscript>";
  2720. $rtn .= "<div class='login-content-div'>";
  2721. $rtn .= "
  2722. <div class='left-side-content'>
  2723. $login_form
  2724. </div>";
  2725. $rtn .= "</div>";
  2726. return $rtn;
  2727. }
  2728. function system_mfa_login_form() {
  2729. $form = array();
  2730. $db_row = $_SESSION['mfa__form_state_db_row'];
  2731. $email = $db_row['email'];
  2732. $obf_email = substr($email, 0, 5) . str_repeat("*", strlen($email) - 5);
  2733. $form['mark_top_msg'] = array(
  2734. 'value' => "<strong>" . t("A message has been sent to your email address (%obfemail) with a code to continue. Please enter the code below.", array("%obfemail" => $obf_email)) . "</strong>
  2735. <p>" . t("Check your Spam or Bulk Mail folder if you do not receive the email within 5 minutes.") . "</p>
  2736. <p>" . t("To recreate and resend the validation code, simply return to the login page and try again.") . "</p>
  2737. <hr>",
  2738. 'weight' => 10,
  2739. );
  2740. $form['mfa_code'] = array(
  2741. 'type' => 'textfield',
  2742. 'label' => t("Code:"),
  2743. 'weight' => 20,
  2744. 'required' => TRUE,
  2745. );
  2746. $form['mfa_remember'] = array(
  2747. 'type' => 'checkbox',
  2748. 'label' => t("Do not ask again for this browser/device for 30 days?"),
  2749. 'weight' => 30,
  2750. );
  2751. $form['submit_btn'] = array(
  2752. 'type' => 'submit',
  2753. 'value' => t("Submit"),
  2754. 'spinner' => TRUE,
  2755. 'weight' => 200,
  2756. );
  2757. return $form;
  2758. } // system_mfa_login_form
  2759. function system_mfa_login_form_validate($form, $form_state) {
  2760. $db_row = $_SESSION['mfa__form_state_db_row'];
  2761. $user_id = $db_row['user_id'];
  2762. // Validate the code.
  2763. $db_code = user_get_attribute($user_id, "mfa_validation_code", FALSE);
  2764. if (intval($form_state['values']['mfa_code']) !== intval($db_code)) {
  2765. form_error('mfa_code', t("Sorry, but the code you entered is not the same that was sent to your address. Try again. If you are unable to log in, have a systems
  2766. administrator reset your password."));
  2767. return;
  2768. }
  2769. } // mfa_login_form_validate
  2770. function system_mfa_login_form_submit($form, $form_state) {
  2771. // If we made it here, the user is allowed to log in.
  2772. $db_row = $_SESSION['mfa__form_state_db_row'];
  2773. $user_id = $db_row['user_id'];
  2774. $mfa_remember = intval($form_state['values']['mfa_remember']);
  2775. // If we should remember for 30 days, then set cookie.
  2776. if ($mfa_remember == TRUE) {
  2777. setcookie("flightpath_mfa_remember__" . $user_id, "yes", strtotime("NOW + 30 DAYS"));
  2778. }
  2779. else {
  2780. // Clear the cookie
  2781. setcookie("flightpath_mfa_remember__" . $user_id, "no", 1);
  2782. }
  2783. // Actually log in the user.
  2784. $account = system_perform_user_login($user_id);
  2785. // Watchdog
  2786. watchdog("mfa-login", "@user has logged in via mfa. CWID: @cwid", array("@user" => "$account->name ($account->id)", "@cwid" => $account->cwid));
  2787. fp_goto("<front>");
  2788. } // .. submit
  2789. /**
  2790. * This draws the form which facilitates logins.
  2791. */
  2792. function system_login_form() {
  2793. $form = array();
  2794. fp_set_title("");
  2795. $bool_clear_cookies = FALSE;
  2796. // If we are coming from having just logged out, display a message.
  2797. if (isset($_REQUEST["logout"]) && $_REQUEST["logout"] == "true") {
  2798. $x = variable_get("logout_message", "You have been logged out of FlightPath.");
  2799. fp_add_message(filter_markup($x));
  2800. }
  2801. // Are we here because the user was not found in the whitelist?
  2802. if (isset($_REQUEST['wlist']) && $_REQUEST['wlist'] == 'notfound') {
  2803. fp_add_message(t("Sorry, but only certain users are allowed access at this time. If you believe you need access, please contact your system administrator."), 'error', TRUE);
  2804. }
  2805. // Are we here because the user was not found at all?
  2806. if (isset($_REQUEST['user']) && $_REQUEST['user'] == 'notfound') {
  2807. fp_add_message(t("Sorry, but the user you specified could not be found in FlightPath's database. If you believe you need access, please contact your system administrator."), 'error', TRUE);
  2808. }
  2809. // Are we here because only the admin user is allowed in?
  2810. if (isset($_REQUEST['user']) && $_REQUEST['user'] == 'adminonly') {
  2811. fp_add_message(t("Sorry, but logins are disabled at this time while maintenance is being performed. Please try again later."), 'error', TRUE);
  2812. }
  2813. // Are we here because only the user's rank is not allowed?
  2814. if (isset($_REQUEST['user']) && $_REQUEST['user'] == 'rank') {
  2815. $allowed_ranks_str = variable_get("allowed_student_ranks", "FR, SO, JR, SR");
  2816. fp_add_message(t("Sorry, your rank/classification is not allowed. At this time this system is only available to students
  2817. in the following ranks/classifications: @ranks_str", array("@ranks_str" => $allowed_ranks_str)), 'error', TRUE);
  2818. }
  2819. // Are we here because the user was not found in the whitelist?
  2820. if (isset($_REQUEST['user']) && $_REQUEST['user'] == 'disabled') {
  2821. fp_add_message(t("Sorry, but the user you specified has been marked as disabled. If you believe you need access, please contact your system administrator."), 'error', TRUE);
  2822. }
  2823. // Are we here because the user is trying to do a zoom installation from the marketplace?
  2824. if (module_enabled('zoomapi') && isset($_REQUEST['zoom_install']) && $_REQUEST['zoom_install'] == 'marketplace') {
  2825. fp_add_message(t("To install FlightPath Academics to your Zoom account (which allows for automatic meeting requests through appointments), please
  2826. sign in below."));
  2827. fp_add_message(t("You may be asked to sign into your Zoom account and authorize FlightPath.<br>You will be returned to FlightPath afterwards."));
  2828. $form['zoom_install'] = array(
  2829. 'type' => 'hidden',
  2830. 'value' => 'marketplace',
  2831. );
  2832. }
  2833. $form["user"] = array(
  2834. "label" => t("User:"),
  2835. "type" => "textfield",
  2836. "size" => 30,
  2837. "required" => TRUE,
  2838. "description" => t("Enter your user name or email address."),
  2839. );
  2840. $form["password"] = array(
  2841. "label" => t("Password:"),
  2842. "type" => "password",
  2843. "size" => 30,
  2844. "required" => TRUE,
  2845. );
  2846. $form["submit"] = array(
  2847. "type" => "submit",
  2848. "value" => t("Log in"),
  2849. "suffix" => "<div id='login-form-forgot-password'>" . l(t("Need help logging in?"), 'login-help') ."</div>",
  2850. );
  2851. $form["#attributes"] = array("onSubmit" => "showUpdate(true);");
  2852. return $form;
  2853. }
  2854. /**
  2855. * Validate function for the login form.
  2856. * This is where we will do all of the lookups to verify username
  2857. * and password. If you want to write your own login handler (like for LDAP)
  2858. * this is the function you would duplicate in a custom module, then use hook_form_alter
  2859. * to make your function be the validator, not this one.
  2860. *
  2861. * We will simply verify the password, then let the submit handler take over from there.
  2862. */
  2863. function system_login_form_validate($form, &$form_state) {
  2864. $user = trim($form_state["values"]["user"]);
  2865. // If the $user is an email address, then find out the user it actually belongs to.
  2866. if (filter_var($user, FILTER_VALIDATE_EMAIL)) {
  2867. // This appears to be the user's email address. Convert to their username
  2868. // instead.
  2869. // Force email addresses to be lowercase.
  2870. $test = db_result(db_query("SELECT user_name FROM users WHERE email = ?", array(strtolower($user))));
  2871. if ($test) {
  2872. $user = $test;
  2873. $form_state["values"]["user"] = $test;
  2874. }
  2875. }
  2876. $password = $form_state["values"]["password"];
  2877. // If the GRANT_FULL_ACCESS is turned on, skip trying to validate
  2878. if ($GLOBALS["fp_system_settings"]["GRANT_FULL_ACCESS"] == TRUE) {
  2879. $form_state["passed_authentication"] = TRUE;
  2880. $form_state["db_row"]["user_id"] = 1;
  2881. $form_state["db_row"]["user_name"] = "FULL ACCESS USER";
  2882. return;
  2883. }
  2884. // Otherwise, check the table normally.
  2885. /*
  2886. $res = db_query("SELECT * FROM users WHERE user_name = '?' AND password = '?' AND is_disabled = '0' ", $user, md5($password));
  2887. if (db_num_rows($res) == 0) {
  2888. form_error("password", t("Sorry, but that username and password combination could not
  2889. be found. Please check your spelling and try again."));
  2890. return;
  2891. }
  2892. */
  2893. $res = db_query("SELECT * FROM users WHERE user_name = ? AND is_disabled = '0' ", $user);
  2894. $cur = db_fetch_array($res);
  2895. // Check the user's password is valid.
  2896. $stored_hash = @$cur["password"];
  2897. if (!user_check_password($password, $stored_hash)) {
  2898. watchdog("login", "@user has not logged in. Username/password could not be verified. Incorrect password?", array("@user" => $user), WATCHDOG_ALERT);
  2899. form_error("password", t("Sorry, but that username and password combination could not
  2900. be found. Please check your spelling and try again."));
  2901. return;
  2902. }
  2903. // Have we disabled all logins except for "admin" (user id = 1)?
  2904. if (intval($cur['user_id']) !== 1 && variable_get('disable_login_except_admin', 'no') == 'yes') {
  2905. watchdog("login", "@user has not logged in. All logins except admin are disabled.", array("@user" => $user), WATCHDOG_ALERT);
  2906. fp_goto("disable-login");
  2907. return;
  2908. }
  2909. // If this is a student, does this student have an accepted "allowed rank" (ie, FR, SO, JR, etc)?
  2910. $allowed_ranks_str = variable_get("allowed_student_ranks", "FR, SO, JR, SR");
  2911. $allowed_ranks = csv_to_array($allowed_ranks_str);
  2912. if (intval($cur['is_student']) === 1) {
  2913. $rank_code = db_result(db_query("SELECT rank_code FROM students WHERE cwid = ?", array($cur['cwid'])));
  2914. if (!in_array($rank_code, $allowed_ranks)) {
  2915. form_error("password", t("Sorry, your rank/classification is %rc. At this time FlightPath is only available to students
  2916. in the following ranks/classifications: @ranks_str", array("%rc" => $rank_code, "@ranks_str" => $allowed_ranks_str)));
  2917. watchdog("login", "@user has not logged in. User rank/classification is %rc. At this time FlightPath is only available to students
  2918. in the following ranks/classifications: @ranks_str", array("@user" => $user, "%rc" => $rank_code, "@ranks_str" => $allowed_ranks_str), WATCHDOG_ALERT);
  2919. return;
  2920. }
  2921. }
  2922. // Do we have a "whitelist" and is this user part of it? Note: ignore if we are admin.
  2923. $bool_pass_whitelist_test = FALSE;
  2924. $list = system_get_user_whitelist();
  2925. if (intval($cur['user_id']) !== 1 && $list) {
  2926. if (!in_array($cur['user_name'], $list) && !in_array($cur['cwid'], $list) && ($cur['email'] != '' && !in_array($cur['email'], $list))) {
  2927. form_error("password", t("Sorry, but only certain users are allowed access at this time. If you believe you need access, please contact your system administrator."));
  2928. watchdog("login", "@user has not logged in. Only certain users allowed at this time.", array("@user" => $user), WATCHDOG_ALERT);
  2929. return;
  2930. }
  2931. else {
  2932. // user is listed in the whitelist.
  2933. $bool_pass_whitelist_test = TRUE;
  2934. }
  2935. }
  2936. else {
  2937. // There was no whitelist.
  2938. $bool_pass_whitelist_test = TRUE;
  2939. }
  2940. // Have we disabled all student logins AND this student was not in the whitelist?
  2941. if (intval($cur['is_student']) == 1 && variable_get('disable_student_logins', 'no') == 'yes') {
  2942. if ($list && $bool_pass_whitelist_test == FALSE || !$list) {
  2943. // There was a whitelist and we didn't pass, OR, there was no whitelist.
  2944. watchdog("login", "@user has not logged in. Student logins are disabled.", array("@user" => $user), WATCHDOG_ALERT);
  2945. fp_goto("disable-student-login");
  2946. return;
  2947. }
  2948. }
  2949. // otherwise, we know it must be correct. Continue.
  2950. $form_state["db_row"] = $cur;
  2951. // If we made it here, then the user successfully authenticated.
  2952. $form_state["passed_authentication"] = TRUE;
  2953. // It will now proceed to the submit handler.
  2954. }
  2955. /**
  2956. * Submit handler for login form.
  2957. * If we are here, it probably means we have indeed authenticated. Just in case, we will
  2958. * test the form_state["passed_authentication"] value, which we expect to have been
  2959. * set in our validate handler.
  2960. *
  2961. * We will now proceed to actually log the user into the system.
  2962. *
  2963. */
  2964. function system_login_form_submit($form, &$form_state) {
  2965. $user = $form_state["values"]["user"];
  2966. $password = $form_state["values"]["password"];
  2967. $passed = $form_state["passed_authentication"];
  2968. // Special case (if we have the zoomapi module enabled). This
  2969. // lets us tell if we are trying to install zoom from the marketplace.
  2970. $zoom_install = @$form_state['values']['zoom_install'];
  2971. // Used later when we do a fp_goto.
  2972. $db_row = $form_state["db_row"];
  2973. $user_id = $db_row['user_id'];
  2974. $email = trim($db_row['email']);
  2975. if (!$passed) {
  2976. fp_add_message(t("Sorry, there has been an error while trying to authenticate the user."));
  2977. watchdog("login", "@user has not logged in. Error while trying to authenticate. Wrong password?", array("@user" => $user), WATCHDOG_ALERT);
  2978. return;
  2979. }
  2980. // if we have MFA turned on AND the user has an email address saved, then we should redirect the user now to the MFA form.
  2981. // Also check to see if we have "mfa_remember" cookie set, and is it not expired.
  2982. $mfa_enabled = variable_get("mfa_enabled", "no");
  2983. if ($email && $mfa_enabled === "yes" && (!isset($_COOKIE['flightpath_mfa_remember__' . $user_id]) || $_COOKIE['flightpath_mfa_remember__' . $user_id] !== 'yes')) {
  2984. // Craft the query so we can use it.
  2985. $_SESSION['mfa__form_state_db_row'] = $db_row;
  2986. // Create validation code
  2987. $mfa_code = mt_rand(100000, 999999);
  2988. user_set_attribute($user_id, "mfa_validation_code", $mfa_code);
  2989. $system_name = variable_get('system_name', 'FlightPath');
  2990. // Send validation code to email.
  2991. notify_by_mail($email, "$system_name - Validation Code", t("Your multi-factor validation code is: <strong>@code</strong>
  2992. \n\n<br><br>This code will remain valid for approximately one hour.", array("@code" => $mfa_code)));
  2993. fp_goto("mfa-login");
  2994. return;
  2995. }
  2996. // Actually log in the user.
  2997. $account = system_perform_user_login($db_row['user_id']);
  2998. // Watchdog
  2999. watchdog("login", "@user has logged in. CWID: @cwid", array("@user" => "$account->name ($account->id)", "@cwid" => $account->cwid));
  3000. if ($zoom_install == 'marketplace' && module_enabled('zoomapi')) {
  3001. fp_goto(zoomapi_get_zoom_install_url($account->id, FALSE, TRUE));
  3002. die();
  3003. }
  3004. fp_goto("<front>");
  3005. }
  3006. /**
  3007. * Actually performs the logging in of a user with user_id.
  3008. */
  3009. function system_perform_user_login($user_id) {
  3010. $_SESSION["fp_logged_in"] = TRUE;
  3011. // Set up a new $account object.
  3012. $account = new stdClass();
  3013. $account = fp_load_user($user_id);
  3014. // Set the $account to the SESSION.
  3015. $_SESSION["fp_user_object"] = $account;
  3016. db_query("UPDATE users SET last_login = ? WHERE user_id = ?", array(time(), $user_id));
  3017. return $account;
  3018. }
  3019. /**
  3020. * Formerly part of the FlightPath class, this function will read in or reload the course inventory into a
  3021. * file, which then goes into the SESSION to make it faster to access.
  3022. */
  3023. function system_reload_and_cache_course_inventory() {
  3024. // Load from file. If not there, or if we cannot unserialize, then we will rebuild cache and save new file.
  3025. if (file_exists(fp_get_files_path() . "/cache_data/courses_serialized.info")) {
  3026. if ($_SESSION["fp_cache_course_inventory"] = file_get_contents(fp_get_files_path() . "/cache_data/courses_serialized.info"))
  3027. {
  3028. if ($GLOBALS["fp_course_inventory"] = unserialize($_SESSION["fp_cache_course_inventory"])) {
  3029. $last_generated = intval(variable_get('cache_course_inventory_last_generated', 0));
  3030. $_SESSION['fp_cache_course_inventory_last_generated'] = $last_generated;
  3031. //fpm('reloading from file');
  3032. return;
  3033. }
  3034. }
  3035. }
  3036. $array_valid_names_by_course = array();
  3037. //fpm('rebuilding course cache');
  3038. $GLOBALS["cache_course_inventory"] = FALSE;
  3039. //fpm("LIMIT $limit_start, $limit_size");
  3040. // To save memory, we're only going to keep a certain number of catalog years in the cache, and even then, only up to a max number of rows.
  3041. $start_year = intval(date('Y', strtotime('NOW + 1 YEAR'))); // start with one year into the future.
  3042. $end_year = intval(date('Y', strtotime('NOW - 10 YEARS'))); // end with 10 years into the past
  3043. $in_years = "";
  3044. for ($t = $end_year; $t <= $start_year; $t++) {
  3045. $in_years .= $t . ",";
  3046. }
  3047. $in_years .= "1900"; // add in the 1900 year as well.
  3048. // For speed and accuracy, ignore the "excluded" courses.
  3049. $result = db_query("SELECT * FROM courses
  3050. WHERE delete_flag = 0
  3051. AND catalog_year IN ($in_years)
  3052. AND exclude = 0
  3053. ORDER BY catalog_year DESC
  3054. LIMIT 50000");
  3055. while($cur = db_fetch_array($result))
  3056. {
  3057. $course_id = $cur["course_id"];
  3058. //$this->db->load_course_descriptive_data(null, $course_id);
  3059. $title = $cur["title"];
  3060. $description = trim($cur["description"]);
  3061. $subject_id = trim(strtoupper($cur["subject_id"]));
  3062. $course_num = trim(strtoupper($cur["course_num"]));
  3063. $cache_catalog_year = $cur['catalog_year'];
  3064. $min_hours = $cur["min_hours"];
  3065. $max_hours = $cur["max_hours"];
  3066. $repeat_hours = $cur["repeat_hours"];
  3067. if ($repeat_hours*1 == 0)
  3068. {
  3069. $repeat_hours = $max_hours;
  3070. }
  3071. $db_exclude = $cur["exclude"];
  3072. $db_school_id = $cur['school_id'];
  3073. $data_entry_comment = $cur["data_entry_comment"];
  3074. // Now, lets get a list of all the valid names for this course.
  3075. // In other words, all the non-excluded names. For most
  3076. // courses, this will just be one name. But for cross-listed
  3077. // courses, this will be 2 or more (probably just 2 though).
  3078. // Example: MATH 373 and CSCI 373 are both valid names for that course.
  3079. if (!isset($array_valid_names_by_course[$course_id])) {
  3080. $array_valid_names = array();
  3081. $res2 = db_query("SELECT * FROM courses
  3082. WHERE course_id = ?
  3083. AND delete_flag = 0 ", $course_id);
  3084. while($cur2 = db_fetch_array($res2))
  3085. {
  3086. $si = $cur2["subject_id"];
  3087. $cn = $cur2["course_num"];
  3088. if (in_array("$si~$cn", $array_valid_names))
  3089. {
  3090. continue;
  3091. }
  3092. $array_valid_names[] = "$si~$cn";
  3093. }
  3094. $array_valid_names_by_course[$course_id] = $array_valid_names;
  3095. }
  3096. $array_valid_names = $array_valid_names_by_course[$course_id];
  3097. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["subject_id"] = $subject_id;
  3098. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["course_num"] = $course_num;
  3099. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["title"] = $title;
  3100. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["description"] = $description;
  3101. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["min_hours"] = $min_hours;
  3102. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["max_hours"] = $max_hours;
  3103. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["repeat_hours"] = $repeat_hours;
  3104. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["db_exclude"] = $db_exclude;
  3105. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["school_id"] = $db_school_id;
  3106. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["array_valid_names"] = $array_valid_names;
  3107. $cache_catalog_year = 0;
  3108. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["subject_id"] = $subject_id;
  3109. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["course_num"] = $course_num;
  3110. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["title"] = $title;
  3111. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["description"] = $description;
  3112. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["min_hours"] = $min_hours;
  3113. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["max_hours"] = $max_hours;
  3114. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["repeat_hours"] = $repeat_hours;
  3115. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["db_exclude"] = $db_exclude;
  3116. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["school_id"] = $db_school_id;
  3117. $GLOBALS["fp_course_inventory"][$course_id][$cache_catalog_year]["array_valid_names"] = $array_valid_names;
  3118. $GLOBALS["cache_course_inventory"] = TRUE;
  3119. } // while cur
  3120. // Should we re-cache the course inventory? If there have been any changes
  3121. // to it, then we will see that in a GLOBALS variable...
  3122. $_SESSION["fp_cache_course_inventory"] = "";
  3123. if ($GLOBALS["cache_course_inventory"] === TRUE)
  3124. {
  3125. $_SESSION["fp_cache_course_inventory"] = serialize($GLOBALS["fp_course_inventory"]);
  3126. }
  3127. // Save to file.
  3128. if (!is_dir(fp_get_files_path() . "/cache_data")) {
  3129. $x = mkdir(fp_get_files_path() . "/cache_data");
  3130. if (!$x) {
  3131. fpm("Cannot create cache_data directory under custom/files. Permission error?");
  3132. watchdog("system", "Cannot create cache_data directory under custom/files. Permission error?", array(), WATCHDOG_ERROR);
  3133. }
  3134. }
  3135. // It is named .info because in our htaccess, it already says that file extension cannot be downloaded.
  3136. $x = file_put_contents(fp_get_files_path() . "/cache_data/courses_serialized.info", $_SESSION["fp_cache_course_inventory"]);
  3137. if ($x === FALSE) {
  3138. fpm("Cannot create cache_data/courses_serialized.info under custom/files. Permission error?");
  3139. watchdog("system", "Cannot create cache_data/courses_serialized.info under custom/files. Permission error?", array(), WATCHDOG_ERROR);
  3140. }
  3141. // We also want to add in a custom .htaccess file, for security.
  3142. file_put_contents(fp_get_files_path() . "/cache_data/.htaccess", system_get_private_htaccess());
  3143. // Also put in when we LAST performed this operation in a variable for reading later on.
  3144. $last_generated = time();
  3145. $_SESSION['fp_cache_course_inventory_last_generated'] = $last_generated;
  3146. variable_set('cache_course_inventory_last_generated', $last_generated);
  3147. } // system_reload_and_cache_course_inventory
  3148. /**
  3149. * Should the course inventory get reloaded from file? If so, return TRUE.
  3150. */
  3151. function system_check_course_inventory_should_be_reloaded() {
  3152. $x = intval($_SESSION['fp_cache_course_inventory_last_generated']);
  3153. $last_generated = intval(variable_get('cache_course_inventory_last_generated', 0));
  3154. if ($x !== $last_generated) {
  3155. return TRUE;
  3156. }
  3157. return FALSE;
  3158. }
  3159. /**
  3160. * Returns back a string for an .htaccess file which doesn't allow Apache to serve any
  3161. * files from that directory. Used in the /files/cache_data directory and other
  3162. * directories as needed.
  3163. */
  3164. function system_get_private_htaccess() {
  3165. $x = "
  3166. # Deny all requests from Apache 2.4+.
  3167. <IfModule mod_authz_core.c>
  3168. Require all denied
  3169. </IfModule>
  3170. # Deny all requests from Apache 2.0-2.2.
  3171. <IfModule !mod_authz_core.c>
  3172. Deny from all
  3173. </IfModule>
  3174. # Turn off all options we don't need.
  3175. Options None
  3176. Options +FollowSymLinks
  3177. # Set the catch-all handler to prevent scripts from being executed.
  3178. # This is borrowed from Drupal SA-2006-006
  3179. SetHandler FlightPath_Security_Do_Not_Remove_See_SA_2006_006
  3180. <Files *>
  3181. # Override the handler again if we're run later in the evaluation list.
  3182. # This is borrowed from Drupal SA-2006-006
  3183. SetHandler FlightPath_Security_Do_Not_Remove_See_SA_2006_006
  3184. </Files>
  3185. # If we know how to do it safely, disable the PHP engine entirely.
  3186. <IfModule mod_php5.c>
  3187. php_flag engine off
  3188. </IfModule>
  3189. ";
  3190. return trim($x);
  3191. } // system_get_private_htaccess()
  3192. /**
  3193. * This is the "dashboard" page for FlightPath, which replaces the "main" page from FP 5.
  3194. */
  3195. function system_display_dashboard_page () {
  3196. global $user;
  3197. $rtn = "";
  3198. fp_set_title('');
  3199. $render = array();
  3200. $render['#id'] = 'system_display_dashboard_page';
  3201. // If we are not logged in, then we need to re-direct the user to
  3202. // the login page!
  3203. if (!isset($_SESSION["fp_logged_in"]) || $_SESSION["fp_logged_in"] != TRUE) {
  3204. $query = "";
  3205. if (isset($_REQUEST["logout"])) $query = "logout=" . $_REQUEST["logout"];
  3206. // Since we are not logged in, and are headed to the login page, also clear out any advising variables we might have.
  3207. foreach ($_REQUEST as $key => $val) {
  3208. unset($_REQUEST[$key]);
  3209. unset($_GET[$key]);
  3210. unset($_POST[$key]);
  3211. }
  3212. global $current_student_id;
  3213. $current_student_id = ""; // clear this so the fp_goto doesn't try to add it.
  3214. @session_destroy(); // In a rare occasion, the session hasn't had time to initialize yet, so this destroy triggers a warning. The @ suppresses it.
  3215. session_commit();
  3216. fp_goto("login", $query);
  3217. return;
  3218. }
  3219. fp_add_css(fp_get_module_path("system") . "/css/style.css");
  3220. // It's a cheap hack, but when we don't have anything to show, the boxes get too small. We're going to force some spaces
  3221. // in that case, and we can tell it to display:none if we don't need it anymore in CSS.
  3222. $force_spaces = "<span class='force-spaces'>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
  3223. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
  3224. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
  3225. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
  3226. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
  3227. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
  3228. </span>";
  3229. //////////////////////////////////////////////////////////
  3230. // To cut down on how long it takes to load huge groups
  3231. // like Free Electives, we will the course inventory from cache here.
  3232. if (@$_SESSION["fp_cached_inventory_flag_one"] != TRUE)
  3233. {
  3234. system_reload_and_cache_course_inventory();
  3235. }
  3236. $today = date("D, F jS, Y", convert_time(time()));
  3237. $dname = $user->name;
  3238. if ($user->f_name != "" || $user->l_name != "") {
  3239. $dname = trim($user->f_name . " " . $user->l_name) . " ($user->name)";
  3240. }
  3241. $render['inner_wrapper_start'] = array(
  3242. 'value' => "<div class='dashboard-inner-wrapper'>",
  3243. 'weight' => 10,
  3244. );
  3245. //$rtn .= "<div class='dashboard-inner-wrapper'>";
  3246. $render["welcome_block"] = array('value' => "<div class='dash-welcome-block'>
  3247. <div class='welcome-section'>
  3248. " . t("<h1>Welcome @dname!</h1>
  3249. <h2>Today is @today</h2>", array("@dname" => $dname, "@today" => $today)) . "
  3250. </div>
  3251. </div>",
  3252. 'weight' => 20);
  3253. /*
  3254. $rtn .= "<div class='dash-welcome-block'>
  3255. <div class='welcome-section'>
  3256. <h1>Welcome $dname!</h1>
  3257. <h2>Today is $today</h2>
  3258. </div>
  3259. </div>";
  3260. */
  3261. // Load announcements as HTML
  3262. $announcements = "";
  3263. $announcements .= "
  3264. <div class='announcements-feed-block dash-feed-block'>
  3265. " . fp_render_section_title(t("Announcements")) . "
  3266. <div class='contents'>";
  3267. $res = db_query("SELECT DISTINCT(a.cid) FROM content__announcement a, content n
  3268. WHERE a.vid = n.vid
  3269. AND a.cid = n.cid
  3270. AND n.delete_flag = 0
  3271. AND n.published = 1
  3272. ORDER BY field__activity_datetime DESC, a.vid DESC
  3273. LIMIT 7");
  3274. $bool_is_empty = TRUE;
  3275. while ($cur = db_fetch_object($res)) {
  3276. $cid = $cur->cid;
  3277. $content = content_load($cid);
  3278. // is this "faculty" visibility? If so, do we have access to view?
  3279. if ($content->field__visibility['value'] == 'faculty' && !user_has_permission('can_view_faculty_engagements')) {
  3280. continue;
  3281. }
  3282. $disp_date = date("m/d/Y g:ia", convert_time(strtotime($content->field__activity_datetime['value'])));
  3283. $bool_is_empty = FALSE;
  3284. $announcements .= "<div class='feed-item'>
  3285. <div class='feed-item-title'>$content->title</div>
  3286. <div class='feed-item-desc'>{$content->field__msg['display_value']}</div>
  3287. <div class='feed-item-datetime'>$disp_date</div>
  3288. </div>";
  3289. } // while
  3290. if ($bool_is_empty) {
  3291. $announcements .= "<div class='empty'>
  3292. <p>" . t("Sorry, there are no announcements available at this time.") . "</p>
  3293. </div>";
  3294. }
  3295. $announcements .= "$force_spaces</div> <!-- contents -->
  3296. </div> <!-- feed block --> ";
  3297. // Build up the "appoinments" HTML
  3298. $appointments = "";
  3299. $appointments .= "<div class='appointments-feed-block dash-feed-block'>
  3300. " . fp_render_section_title(t("Upcoming Appointments")) . "
  3301. <div class='contents'>";
  3302. $upcoming = calendar_get_upcoming_appointments_for_cwid($user->cwid);
  3303. $bool_is_empty = TRUE;
  3304. foreach ($upcoming as $details) {
  3305. $thedate = format_date(convert_time($details['utc_start_ts']), 'long_no_year');
  3306. $use_name = $details['faculty_name'];
  3307. if ($user->is_faculty) {
  3308. $use_name = $details['student_name'];
  3309. }
  3310. $bool_is_empty = FALSE;
  3311. $msg = t("You have an appointment with @fn on @td.", array("@fn" => $use_name, "@td" => $thedate));
  3312. $appointments .= "<div class='feed-item'>
  3313. <div class='feed-item-icon'><i class='fa fa-calendar'></i></div>
  3314. <div class='feed-item-title'>$use_name</div>
  3315. <div class='feed-item-desc'>$msg</div>
  3316. </div>";
  3317. }
  3318. if ($bool_is_empty) {
  3319. $appointments .= "<div class='empty'>
  3320. <p>" . t("You have no upcoming appointments within the next 5 days.") . "</p>
  3321. </div>
  3322. $force_spaces";
  3323. }
  3324. $appointments .= "
  3325. </div> <!-- contents -->
  3326. </div> <!-- feed-block -->
  3327. ";
  3328. $render['#user_is_faculty'] = $user->is_faculty;
  3329. if ($user->is_faculty) {
  3330. $render['dash_left_wrapper'] = array('value' => "<div class='dash-box dash-left'>", 'weight' => 30);
  3331. //$rtn .= "<div class='dash-box dash-left'>";
  3332. $render["appointments"] = array('value' => $appointments, 'weight' => 40);
  3333. //$rtn .= $appointments;
  3334. if (user_has_permission('can_view_advisee_activity_records')) {
  3335. $render["activity_feed_block_top"] = array('value' => "<div class='activity-feed-block dash-feed-block'>
  3336. " . fp_render_section_title("Advisee Activity Feed") . "
  3337. <div class='contents'>",
  3338. 'weight' => 50);
  3339. /*
  3340. $rtn .= "<div class='activity-feed-block dash-feed-block'>
  3341. " . fp_render_section_title("Advisee Activity Feed") . "
  3342. <div class='contents'>";
  3343. */
  3344. $activity = "";
  3345. // Needs to only be within my advisees list....
  3346. $adv_array = student_search_display_my_advisees(TRUE);
  3347. $student_ids = array_keys($adv_array);
  3348. $students_line = "'" . join("','", $student_ids) . "'";
  3349. $icons = array(
  3350. 'alert' => 'fa-bell-o',
  3351. 'mail' => 'fa-envelope-o',
  3352. 'comment' => 'fa-comment-o',
  3353. 'calendar' => 'fa-calendar-o',
  3354. );
  3355. $res = db_query("SELECT DISTINCT(a.cid) FROM content__activity_record a, content n
  3356. WHERE a.vid = n.vid
  3357. AND a.cid = n.cid
  3358. AND n.delete_flag = 0
  3359. AND n.published = 1
  3360. AND field__student_id IN ($students_line)
  3361. ORDER BY updated DESC, a.vid DESC
  3362. LIMIT 10");
  3363. $bool_is_empty = TRUE;
  3364. while ($cur = db_fetch_object($res)) {
  3365. $cid = $cur->cid;
  3366. $content = content_load($cid);
  3367. $student_name = fp_get_student_name($content->field__student_id['value'], TRUE);
  3368. $disp_date = date("m/d/Y g:ia", convert_time($content->updated));
  3369. $icon = $icons[$content->field__activity_type['value']];
  3370. $bool_is_empty = FALSE;
  3371. $activity .= "<div class='feed-item'>
  3372. <div class='feed-item-icon'><i class='fa $icon'></i></div>
  3373. <div class='feed-item-title'>$student_name</div>
  3374. <div class='feed-item-desc'>$content->title</div>
  3375. <div class='feed-item-datetime'>$disp_date</div>
  3376. </div>";
  3377. } // while
  3378. if (!$bool_is_empty) {
  3379. $activity .= "<div class='activity-view-all'>" . l(t("View All"), "advisee-activities", '', array('class' => 'button')) . "</div>";
  3380. }
  3381. else {
  3382. $activity .= "<div class='empty'>
  3383. <p>" . t("There is no student activity to report at this time.") . "</p>
  3384. </div>$force_spaces";
  3385. }
  3386. $render['close_activity_feed_block'] = array('value' => "$activity</div>", 'weight' => 60);
  3387. $render['close_activities_feed_block'] = array('value' => "</div> <!-- feed-block --> ", 'weight' => 70);
  3388. $render['close_left_dash_wrapper'] = array('value' => "</div> <!-- dash-box --> ", 'weight' => 80);
  3389. } // if user has permission can_view_advisee_activity_records
  3390. $render['dash_right_wrapper'] = array('value' => "<div class='dash-box dash-right'>", 'weight' => 90);
  3391. //$rtn .= "<div class='dash-box dash-right'>";
  3392. $advising_term_id = variable_get("advising_term_id", "");
  3393. $advising_term_desc = get_term_description($advising_term_id, FALSE, $user->school_id);
  3394. $url = fp_url("render-advising-snapshot-for-iframe", "window_mode=popup&fp_messages=none");
  3395. // Show slightly different if we have the schools module enabled
  3396. if (module_enabled("schools")) {
  3397. $advising_term_desc = "Current Terms";
  3398. // Get all the school ids this user is allowed to search.
  3399. $school_ids = student_search_get_school_ids_user_is_allowed_to_search();
  3400. $school_id_list = join(",", $school_ids);
  3401. $url = fp_url("render-advising-snapshot-for-iframe", "window_mode=popup&fp_messages=none&school_id_list=$school_id_list");
  3402. }
  3403. $render['advising_snapshot'] = array('value' => "<div class='snapshot-feed-block dash-feed-block'>
  3404. " . fp_render_section_title(t("Advising Snapshot for ") . $advising_term_desc) . "
  3405. <div class='contents'>
  3406. <iframe src='$url' frameborder=0 width=100% height=85></iframe>
  3407. </div>
  3408. </div>",
  3409. 'weight' => 100);
  3410. /// Do announcements under.
  3411. $render['announcements'] = array('value' => $announcements, 'weight' => 110);
  3412. $render['close_right_dash_wrapper'] = array('value' => "</div>", 'weight' => 120);
  3413. } // if is_faculty
  3414. else if ($user->is_student) {
  3415. $render['dash_left_wrapper'] = array('value' => "<div class='dash-box dash-left'>", 'weight' => 30);
  3416. $render["appointments"] = array('value' => $appointments, 'weight' => 40);
  3417. //$rtn .= "<div class='dash-box dash-left'>";
  3418. //$rtn .= $appointments;
  3419. fp_add_js(fp_get_module_path('advise') . '/js/advise.js');
  3420. $render['recent_advising_history_top'] = array('value' => "<div class='advising-history-feed-block dash-feed-block'>
  3421. " . fp_render_section_title(t("Recent Advising History")) . "
  3422. <div class='contents'>", 'weight' => 50);
  3423. // TODO: For the student advisings, we want to group together terms that were advised at the same time.
  3424. $res = db_query("SELECT * FROM advising_sessions
  3425. WHERE student_id = ?
  3426. AND is_draft = 0
  3427. AND is_empty = 0
  3428. AND delete_flag = 0
  3429. ORDER BY `posted` DESC, `term_id` DESC
  3430. LIMIT 5", $user->cwid);
  3431. $c = 0;
  3432. while($cur = db_fetch_array($res)) {
  3433. $dt = date("n/j/y g:ia",$cur['posted']);
  3434. $fac_name = fp_get_faculty_name($cur['faculty_id'], FALSE);
  3435. $html = "";
  3436. $turl = fp_url("advise/popup-display-summary", "advising_session_id=" . $cur['advising_session_id']);
  3437. $advising_session_id_array[] = $cur['advising_session_id'];
  3438. $term = get_term_description($cur['term_id'], FALSE, $user->school_id);
  3439. $link = "popupLargeIframeDialog(\"" . $turl . "\",\"" . t("Advising Session @term - @date", array("@term" => $term, "@date" => $dt)) . "\",\"\");";
  3440. $html .= "<div class='feed-item'>
  3441. <div class='feed-item-icon'><i class='fa fa-graduation-cap'></i></div>
  3442. <div class='feed-item-title'>Advised by $fac_name</div>
  3443. <a href='javascript:$link'>
  3444. <div class='feed-item-desc'>$term</div>
  3445. </a>
  3446. <div class='feed-item-datetime'>$dt</div>
  3447. </div>";
  3448. $render['recent_advising_history_row_' . $cur['advising_session_id']] = array('value' => $html, 'weight' => (200 + $c++));
  3449. }
  3450. $render['close_advising_history_contents'] = array('value' => "</div> <!-- contents -->", 'weight' => 300);
  3451. $render['close_advising_sessions_feed_block'] = array('value' => "</div> <!-- feed-block --> ", 'weight' => 310);
  3452. $render['close_left_dash_wrapper'] = array('value' => "</div> <!-- dash-box --> ", 'weight' => 320);
  3453. $render['dash_box_right_wrapper'] = array('value' => "<div class='dash-box dash-right'>", 'weight' => 330);
  3454. $render['announcements'] = array('value' => $announcements, 'weight' => 340);
  3455. $render['close_right_dash_box'] = array('value' => "</div>", 'weight' => 350);
  3456. } // if is_student
  3457. watchdog("display_dashboard", "", array());
  3458. $rtn = fp_render_content($render);
  3459. return $rtn;
  3460. } // display_dashboard_page
  3461. /**
  3462. * This is meant to be a widget which shows in the dashboard of the advising user, within an iframe, since it can
  3463. * take a while to load.
  3464. */
  3465. function system_render_advising_snapshop_for_iframe() {
  3466. $rtn = "";
  3467. fp_add_css(fp_get_module_path("system") . "/css/style.css");
  3468. if (!isset($_SESSION["fp_pie_chart_token"])) {
  3469. $_SESSION["fp_pie_chart_token"] = md5(fp_token());
  3470. }
  3471. $school_ids = array(0);
  3472. if (isset($_REQUEST['school_id_list'])) {
  3473. $school_ids = explode(",", $_REQUEST['school_id_list']);
  3474. }
  3475. $selected_school_id = $school_ids[0];
  3476. if (isset($_REQUEST['selected_school_id'])) $selected_school_id = intval($_REQUEST['selected_school_id']);
  3477. // Get total number of advisees VS number that have been advised for current term.
  3478. $adv_array = student_search_display_my_advisees(TRUE, NULL, $selected_school_id, 9999999); // We want to get ALL advisees, so we set the limit very high.
  3479. $total = count($adv_array);
  3480. $advised_count = 0;
  3481. $advised_percent = 0;
  3482. if ($total > 0) {
  3483. foreach ($adv_array as $details) {
  3484. if (@$details['advised_image'] != "") {
  3485. $advised_count++;
  3486. }
  3487. }
  3488. $advised_percent = round($advised_count/$total * 100, 2) ;
  3489. $unfinished = 100 - $advised_percent;
  3490. $pie_chart_url_advised_percent = base_path() . "/libraries/pchart/fp_pie_chart.php?size=75&radius=35&progress=$advised_percent&unfinished=$unfinished&unfinished_col=cccccc&progress_col=5780FF&token=" . $_SESSION["fp_pie_chart_token"];
  3491. $advising_term_id = variable_get_for_school("advising_term_id", "", $selected_school_id);
  3492. $advising_term_desc = get_term_description($advising_term_id, FALSE, $selected_school_id);
  3493. // If we have more than one school, then we should also display a selector which auto-submits when changed.
  3494. $school_selector_html = "";
  3495. if (count($school_ids) > 1 && module_enabled("schools")) {
  3496. fp_add_js(fp_get_module_path("system") . "/js/snapshot.js");
  3497. $url = fp_url("render-advising-snapshot-for-iframe");
  3498. $school_selector_html .= "<div class='snapshot-school-selector'>
  3499. <form action='$url' method='GET' id='snapshot-school-selector-form'>
  3500. <input type='hidden' name='window_mode' value='popup'>
  3501. <input type='hidden' name='school_id_list' value='" . join(",", $school_ids) . "'>
  3502. <strong>School: </strong>
  3503. <select name='selected_school_id' id='selected_school_id'>";
  3504. foreach ($school_ids as $school_id) {
  3505. $sel = "";
  3506. if (intval($school_id) === $selected_school_id) $sel = "selected";
  3507. $school_selector_html .= "<option value='$school_id' $sel>" . schools_get_school_name_for_id($school_id) . "</option>";
  3508. }
  3509. $school_selector_html .= "</select>
  3510. </form>
  3511. </div>";
  3512. }
  3513. $rtn .= "<div class='snapshot-in-iframe'>
  3514. $school_selector_html
  3515. <div class='pie-image'>
  3516. <img src='$pie_chart_url_advised_percent'>
  3517. </div>
  3518. <div class='pie-term-title'>$advising_term_desc ($advising_term_id)</div>
  3519. <div class='pie-term-caption'>" . t("You have advised %p of your advisees @math", array("%p" => "$advised_percent%", "@math" => "($advised_count/$total)")) . "</div>
  3520. </div>
  3521. ";
  3522. } // if total > 0
  3523. else {
  3524. // Meaning, the user does not have any advisees assigned to them.
  3525. $rtn .= "<div class='snapshot-in-iframe'>
  3526. <div class='pie-term-title'>" . t("No Advisees") . "</div>
  3527. <div class='pie-term-caption'>" . t("You do not have any advisees assigned to you at this time.") . "</div>
  3528. </div>
  3529. ";
  3530. }
  3531. return $rtn;
  3532. } // system_render_advising_snapshop_for_iframe
  3533. /**
  3534. * Called on every page load.
  3535. */
  3536. function system_init() {
  3537. // Let's see if the $user object (for the logged-in user) has been set up.
  3538. global $user;
  3539. $user = new stdClass();
  3540. if (!isset($_SESSION["fp_user_object"])) {
  3541. $_SESSION["fp_user_object"] = new stdClass();
  3542. }
  3543. if (!isset($_SESSION["fp_user_object"]->roles[1])) $_SESSION["fp_user_object"]->roles[1] = "";
  3544. if (@$_SESSION["fp_logged_in"] == TRUE) {
  3545. // Make sure it doesn't have the anonymous user role (rid == 1).
  3546. if ($_SESSION["fp_user_object"]->roles[1] == "anonymous user") {
  3547. unset($_SESSION["fp_user_object"]->roles[1]);
  3548. }
  3549. $user = $_SESSION["fp_user_object"];
  3550. // To make sure we pick up the user's newest permissions, re-load
  3551. // the user here.
  3552. $user = fp_load_user($user->id);
  3553. }
  3554. else {
  3555. // User is anonymous, so set it up as such.
  3556. $user = fp_load_user(0);
  3557. }
  3558. // Are we in maintenance mode? If so, display a message.
  3559. if (variable_get("maintenance_mode", FALSE)) {
  3560. fp_add_message(t("@FlightPath is currently undergoing routine maintenance.
  3561. During this time, some data may appear incomplete.
  3562. We apologize for the inconvenience and appreciate your patience.", array("@FlightPath" => variable_get("system_name", "FlightPath"))), "status", TRUE);
  3563. }
  3564. // Is there an urgent message to display?
  3565. $urgent_msg = variable_get("urgent_msg", "");
  3566. if ($urgent_msg) {
  3567. fp_add_message("<b>" . t("Important Message:") . "</b> " . $urgent_msg, "status", TRUE);
  3568. }
  3569. // Since current_student_id is coming from the REQUEST, sanitize it.
  3570. //$current_student_id = "";
  3571. $current_student_id = (string) ($_REQUEST['current_student_id'] ?? '');
  3572. if (!is_string($current_student_id)) $current_student_id = '';
  3573. $current_student_id = str_replace("'", "", $current_student_id); // remove single quotes
  3574. $current_student_id = str_replace('"', "", $current_student_id); // remove back quotes
  3575. $current_student_id = str_replace(';', "", $current_student_id); // remove semicolons
  3576. // Add in our custom JS settings.
  3577. $settings = array(
  3578. "themeLocation" => fp_theme_location(),
  3579. "currentStudentId" => $current_student_id,
  3580. "basePath" => base_path(),
  3581. // Add in the popup window options....
  3582. "popupAdminWinOptions" => variable_get("popup_admin_win_options", "toolbar=no,status=2,scrollbars=yes,resizable=yes,width=600,height=400"), // used by admin groups, edit definitions, degrees, and popup contact form.
  3583. "popupAdviseWinOptions" => variable_get("popup_advise_win_options", "toolbar=no,status=2,scrollbars=yes,resizable=yes,width=460,height=375"), // the work-horse of most of the advising popups. course desc, subs, etc.
  3584. "popupPrintWinOptions" => variable_get("popup_print_win_options", "toolbar=no,status=2,scrollbars=yes,resizable=yes,width=750,height=600"), // any printable screen is displayed in this.
  3585. );
  3586. fp_add_js($settings, "setting");
  3587. fp_add_js(fp_get_module_path("system") . "/js/system.js");
  3588. }
  3589. /**
  3590. * This is the form which an admin may use to manage the modules
  3591. * in the system.
  3592. */
  3593. function system_modules_form() {
  3594. $form = array();
  3595. $m = 0;
  3596. fp_add_css(fp_get_module_path("system") . "/css/style.css");
  3597. $form["mark" . $m++] = array(
  3598. "value" => t("Use this form to enable or disable modules. This scans the /modules/ and then /custom/modules/
  3599. directories.") . "
  3600. " . l(t("Run DB updates?"), "admin/db-updates") . "<br><br>",
  3601. );
  3602. // Begin by scanning the /modules/ directory. Anything in there
  3603. // cannot be disabled.
  3604. $module_dirs = array();
  3605. $module_dirs[] = array("start" => "modules", "type" => t("Core"));
  3606. $module_dirs[] = array("start" => "custom/modules", "type" => t("Custom"));
  3607. // We will also add any directories which begin with an underscore in the custom/modules directory.
  3608. // For example: custom/modules/_contrib
  3609. // Let's find such directories now.
  3610. $dir_files = scandir("custom/modules");
  3611. foreach ($dir_files as $file) {
  3612. if ($file == '.' || $file == '..') continue;
  3613. if (substr($file, 0, 1) == '_' && is_dir("custom/modules/$file")) {
  3614. $module_dirs[] = array("start" => "custom/modules/$file", "type" => t("Custom/$file"));
  3615. }
  3616. }
  3617. foreach ($module_dirs as $module_dir) {
  3618. $start_dir = $module_dir["start"];
  3619. if ($dh = opendir($start_dir)) {
  3620. //$pC .= "<div class='fp-system-modules-type'>{$module_dir["type"]}</div>
  3621. // <table class='fp-system-modules-table' cellpadding='0' cellspacing='0'>";
  3622. $form["mark" . $m++] = array(
  3623. "value" => "<div class='fp-system-modules-type'>{$module_dir["type"]}</div>
  3624. <table class='fp-system-modules-table' cellpadding='0' cellspacing='0'>",
  3625. );
  3626. $pol = "even";
  3627. $dir_files = scandir($start_dir);
  3628. foreach ($dir_files as $file) {
  3629. if ($file == "." || $file == "..") continue;
  3630. if (is_dir($start_dir . "/" . $file)) {
  3631. // Okay, now look inside and see if there is a .info file.
  3632. if (file_exists("$start_dir/$file/$file.info")) {
  3633. $module = $file;
  3634. $info_contents = file_get_contents("$start_dir/$file/$file.info");
  3635. // From the info_contents variable, split up and place into an array.
  3636. $info_details_array = array("path" => "", "module" => "",
  3637. "schema" => "", "core" => "", "description" => "",
  3638. "requires" => "", "version" => "",
  3639. "required" => "", );
  3640. $lines = explode("\n", $info_contents);
  3641. foreach ($lines as $line) {
  3642. if (trim($line) == "") continue;
  3643. $temp = explode("=", trim($line));
  3644. $info_details_array[trim($temp[0])] = trim(substr($line, strlen($temp[0]) + 1));
  3645. }
  3646. $path = "$start_dir/$file";
  3647. $info_details_array["path"] = $path;
  3648. $info_details_array["module"] = $module;
  3649. // Expected keys:
  3650. // name, description, version, core, requires (csv), requred (true or false)
  3651. $checked = "";
  3652. $form["mark" . $m++] = array(
  3653. "value" => "<tr class='fp-system-modules-row fp-system-modules-row-$pol'>
  3654. <td width='35%'>",
  3655. );
  3656. // the Checkbox.
  3657. // Should it be checked? We can check the modules table to see if it's enabled/installed or not.
  3658. $installation_status = "";
  3659. $default_value = array();
  3660. $res = db_query("SELECT * FROM modules WHERE path = '?' ", $path);
  3661. $cur = db_fetch_array($res);
  3662. if ($cur) {
  3663. $info_details_array["enabled"] = $cur["enabled"];
  3664. if ($cur["enabled"] == "1") {
  3665. // Yes, it is checked!
  3666. $default_value = array($module => $module);
  3667. }
  3668. else if ($cur["enabled"] == "") {
  3669. $installation_status = t("not installed");
  3670. }
  3671. else if ($cur["enabled"] == "0") {
  3672. $installation_status = fp_get_js_confirm_link(t("Are you sure you wish to uninstall @module?\\nThis may remove saved data belonging to the module.", array("@module" => $module)),
  3673. ' window.location="' . fp_url("system/uninstall-module", "module=$module&path=" . urlencode($path) . "") . '"; ', t("uninstall?"));
  3674. }
  3675. // Does this module need to run db updates?
  3676. if ($cur["enabled"] == "1" && $cur["schema"] != $info_details_array["schema"] && $info_details_array["schema"] != "") {
  3677. $installation_status = "<b>" . l(t("Run db updates"), "admin/db-updates") . "</b>";
  3678. // Let's also make sure to enable a message at the top of the screen, letting the user
  3679. // know that there are needed updates.
  3680. fp_add_message("<b>" . t("Note:") . "</b> " . t("There are modules which have been updated. Please back up your database,
  3681. then run the DB Updates function below as soon as possible."), "error", TRUE);
  3682. }
  3683. }
  3684. $attributes = array();
  3685. if ($info_details_array["required"]) {
  3686. // This is a required module; it cannot be unchecked.
  3687. $attributes["disabled"] = "disabled";
  3688. }
  3689. $bool_overriding = FALSE;
  3690. // Did this module already exist in $form? In other words,
  3691. // is the module overriding a core module? If so, we need to know
  3692. // so we can display something special.
  3693. if (isset($form["cb__$module"])) {
  3694. $bool_overriding = TRUE;
  3695. }
  3696. $requires = "";
  3697. // If this module requires a higher core version of FlightPath than what we
  3698. // are running, disable and explain to the user.
  3699. if (FLIGHTPATH_VERSION != '%FP_VERSION%' && $info_details_array["requires core version"]) {
  3700. // Test to see if the current version is >= to the required core version.
  3701. if (version_compare(FLIGHTPATH_VERSION, $info_details_array["requires core version"], "<")) {
  3702. // No, it's LESS than the required version! We shouldn't be able to use this module!
  3703. $attributes["disabled"] = "disabled";
  3704. $requires .= "<div style='color: red;'>" . t("This module requires
  3705. that you run FlightPath version %fpv or higher.
  3706. You are instead running version %fpov. Please update
  3707. your core copy of FlightPath before attempting to install this
  3708. module.", array('%fpv' => $info_details_array["requires core version"],
  3709. '%fpov' => FLIGHTPATH_VERSION)) . "</div>";
  3710. }
  3711. }
  3712. // Let's see if this module is for the wrong core entirely.
  3713. if ($info_details_array["core"]) {
  3714. // Test to see if we are not the correct core version.
  3715. if (strtolower(FLIGHTPATH_CORE) != strtolower($info_details_array["core"])) {
  3716. // Nope, the wrong core version!
  3717. $attributes["disabled"] = "disabled";
  3718. $requires .= "<div style='color: red;'>" . t("This module requires
  3719. that you run FlightPath core version %fpv.
  3720. You are instead running version %fpov. Please either download
  3721. the correct version of this module for your FlightPath core version,
  3722. or update FlightPath to the required core version.", array('%fpv' => $info_details_array["core"],
  3723. '%fpov' => FLIGHTPATH_CORE)) . "</div>";
  3724. }
  3725. }
  3726. $form["cb__$module"] = array(
  3727. "type" => "checkboxes",
  3728. "options" => array($module => $info_details_array["name"]),
  3729. "value" => $default_value,
  3730. "suffix" => "<div class='fp-system-modules-machine-name'>$file</div>
  3731. <div class='fp-system-modules-installation-status'>$installation_status</div>
  3732. ",
  3733. "attributes" => $attributes,
  3734. );
  3735. // hidden variable containing the details about this module, for later use on submit.
  3736. $form["module_details__$module"] = array(
  3737. "type" => "hidden",
  3738. "value" => urlencode(serialize($info_details_array)),
  3739. );
  3740. // Version & descr.
  3741. if ($info_details_array["requires"] != "") {
  3742. $requires .= "<div class='fp-system-modules-requires hypo'>
  3743. <b>" . t("Requires:") . "</b> {$info_details_array["requires"]}
  3744. </div>";
  3745. }
  3746. // if we are overriding a module, then display something special.
  3747. if ($bool_overriding) {
  3748. $form["mark" . $m++] = array(
  3749. "value" => "<em>" . t("Overriding core module:") . "<br>{$info_details_array["name"]}</em>
  3750. <div class='fp-system-modules-machine-name'>$file</div>
  3751. <div class='fp-system-modules-installation-status'>
  3752. " . t("Use checkbox in Core section above to manage module") . "
  3753. </div>",
  3754. );
  3755. }
  3756. $form["mark" . $m++] = array(
  3757. "value" => " </td>
  3758. <td width='5%' >{$info_details_array["version"]}</td>
  3759. <td >{$info_details_array["description"]}$requires</td>
  3760. </tr>
  3761. ",
  3762. );
  3763. $pol = ($pol == "even") ? "odd" : "even";
  3764. } // if file_exists (info file)
  3765. } // if is_dir
  3766. } // while file=readdir
  3767. $form["mark" . $m++] = array(
  3768. "value" => "</table>",
  3769. );
  3770. } // if opendir($startdir)
  3771. }// foreach moduledirs
  3772. $form["submit"] = array(
  3773. "type" => "submit",
  3774. "spinner" => TRUE,
  3775. "value" => t("Submit"),
  3776. "prefix" => "<hr>",
  3777. );
  3778. return $form;
  3779. }
  3780. /**
  3781. * Submit handler for the modules form.
  3782. */
  3783. function system_modules_form_submit($form, $form_state) {
  3784. // Go through all of the checkboxes which we have "module_details" for. If there is NOT a corresponding
  3785. // checkbox, it means it wasn't checked, and should be disabled in the database. Otherwise, it means it WAS
  3786. // checked, and should be enabled/installed.
  3787. $did_something = FALSE;
  3788. foreach ($form_state["values"] as $key => $value) {
  3789. if (strstr($key, "module_details__")) {
  3790. if ($module_details = unserialize(urldecode($value))) {
  3791. $module = $module_details["module"];
  3792. // Disabling a module.
  3793. if (@$module_details["enabled"] == "1" && !isset($form_state["values"]["cb__$module"])) {
  3794. // So it WAS enabled, but now the checkbox wasn't checked. So disable it!
  3795. system_disable_module($module_details);
  3796. $did_something = TRUE;
  3797. }
  3798. // Enabling a module
  3799. if (@$module_details["enabled"] != "1" && isset($form_state["values"]["cb__$module"])) {
  3800. system_enable_module($module_details);
  3801. $did_something = TRUE;
  3802. }
  3803. }
  3804. }
  3805. }
  3806. if ($did_something) {
  3807. // Refetch all of the modules from the modules table.
  3808. fp_rebuild_modules_list();
  3809. // We should clear the cache if we did something.
  3810. fp_clear_cache();
  3811. watchdog("admin", "Saved system modules form (enabled or diabled module)");
  3812. }
  3813. }
  3814. /**
  3815. * Called from the menu (ie, a URL) this function will uninstall a module.
  3816. *
  3817. */
  3818. function system_handle_uninstall_module() {
  3819. $module = $_REQUEST["module"];
  3820. // First, let's get information about this module from the db.
  3821. $res = db_query("SELECT * FROM modules WHERE name = '?' ", $module);
  3822. $cur = db_fetch_array($res);
  3823. // Make sure it is not currently enabled.
  3824. if ($cur["enabled"] == "1") {
  3825. fp_add_message(t("Module %module not yet disabled. Disable first, then try to uninstall.", array("%module" => $module)));
  3826. return;
  3827. }
  3828. // Let's see if we can call hook_uninstall for this module.
  3829. if (include_module($module, TRUE, $cur["path"])) {
  3830. if (include_module_install($module, $cur["path"])) {
  3831. if (function_exists($module . "_uninstall")) {
  3832. call_user_func($module . "_uninstall");
  3833. }
  3834. }
  3835. }
  3836. // Remove from the database.
  3837. $res = db_query("DELETE FROM modules WHERE name = '?' ", $module);
  3838. fp_add_message(t("Uninstalled %module.", array("%module" => $module)));
  3839. fp_goto("admin/config/modules");
  3840. }
  3841. /**
  3842. * Handles the enabling (and possible installation) of module.
  3843. */
  3844. function system_enable_module($module_details) {
  3845. $module = $module_details["module"];
  3846. $path = $module_details["path"];
  3847. $bool_call_hook_install = FALSE;
  3848. // Do we need to attempt to call the hook_install function?
  3849. if (@$module_details["enabled"] == "") {
  3850. // Wasn't in the database, so we need to install it.
  3851. $schema = 0;
  3852. if (isset($module_details['schema'])) $schema = $module_details['schema'];
  3853. // Add to our table.
  3854. // (delete anything all ready there first)
  3855. $res = db_query("DELETE FROM modules WHERE `name` = ? ", $module);
  3856. // Now, add back into the table.
  3857. $res = db_query("INSERT INTO modules (`name`, `path`, `version`, `requires`, `enabled`, `type`, `schema`, `info`)
  3858. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  3859. ", $module, $path, @$module_details["version"], @$module_details["required"], 0, "module",
  3860. @intval($schema), serialize($module_details));
  3861. $bool_call_hook_install = TRUE;
  3862. fp_add_message(t("The module %module has been installed.", array("%module" => $module)));
  3863. }
  3864. // If the module has a .install file, begin by including it.
  3865. if (include_module_install($module, $path)) {
  3866. // Include the original module file first.
  3867. include_module($module, TRUE, $path);
  3868. if ($bool_call_hook_install) {
  3869. // call hook_install if it exists.
  3870. if (function_exists($module . '_install')) {
  3871. call_user_func($module . '_install');
  3872. }
  3873. }
  3874. // Now, we can call hook_enable, if it exists.
  3875. if (function_exists($module . '_enable')) {
  3876. call_user_func($module . '_enable');
  3877. }
  3878. }
  3879. // Update our table.
  3880. $res = db_query("UPDATE modules SET `enabled` = '1' WHERE `name` = ? ", $module);
  3881. fp_add_message(t("The module %module has been enabled.", array("%module" => $module)));
  3882. }
  3883. /**
  3884. * Handles the disabling of the module in question.
  3885. */
  3886. function system_disable_module($module_details) {
  3887. $module = $module_details["module"];
  3888. $path = $module_details["path"];
  3889. // This module cannot be disabled!
  3890. if ($module_details["required"] == TRUE) {
  3891. return;
  3892. }
  3893. // If the module has a "hook_disable" in it's path/module.install file, include and call it.
  3894. if (include_module_install($module, $path) && function_exists($module . '_disable')) {
  3895. call_user_func($module . '_disable');
  3896. }
  3897. // Disable it in the modules table.
  3898. $res = db_query("UPDATE modules
  3899. SET enabled = '0'
  3900. WHERE name = '?' ", $module);
  3901. fp_add_message(t("The module %module has been disabled.", array("%module" => $module)));
  3902. }

Functions

Namesort descending Description
system_ban_ip Adds IP address to our banned_ip list.
system_ban_ip_settings_form This form allows the user (an administrator) to configure how FlightPath bans IP addresses.
system_ban_ip_settings_form_submit
system_ban_ip_settings_form_validate
system_block_regions Hook block regions.
system_can_access_student Used by the menu to determine if the user can access some basic information about the student (like Profile page, etc)
system_check_clean_urls This function will attempt to confirm that "clean URLs" is functioning, and allowed on this server.
system_check_course_inventory_should_be_reloaded Should the course inventory get reloaded from file? If so, return TRUE.
system_check_should_ban_ip This is called by theme.inc's functions display_not_found and display_access_denied.
system_clear_cache Implements hook_clear_cache Take care of clearing caches managed by this module
system_confirm_db_updates_form Display a confirmation form before we run the db updates (hook_updates)
system_confirm_db_updates_form_submit Perform the actual hook_update calls here, send the user to a completed page.
system_cron Implementation of hook_cron
system_disable_module Handles the disabling of the module in question.
system_display_completed_db_updates Once db updates are run, display contents of this page.
system_display_dashboard_page This is the "dashboard" page for FlightPath, which replaces the "main" page from FP 5.
system_display_disable_login_page
system_display_install_finished_page This page is displayed to the user once FlightPath has been installed.
system_display_login_help_page This page will be shown when the user clicks the "Need Help Logging In?" link on the login page.
system_display_login_page Display the "login" page. This is the default page displayed to the user, at /login, if they have not logged in yet.
system_display_status_page This page displayes the results of each module's hook_status.
system_enable_module Handles the enabling (and possible installation) of module.
system_execute_php_form
system_execute_php_form_submit
system_finished_db_updates_finished
system_flightpath_can_assign_course_to_degree_id Implements hook flightpath_can_assign_course_to_degree_id
system_fp_get_student_majors Implements hook_fp_get_student_majors.
system_get_available_themes Returns back an array (suitable for FAPI) of the available themes in the system.
system_get_exclude_degree_ids_from_appears_in_counts Uses the "exclude_majors...." setting, but converts them into an array of degree_ids.
system_get_private_htaccess Returns back a string for an .htaccess file which doesn't allow Apache to serve any files from that directory. Used in the /files/cache_data directory and other directories as needed.
system_get_roles_for_user Return an array containing the roles which have been assigned to a specific user.
system_get_user_whitelist Returns the "whitelist" or "allow list" (from system settings) as an array. If empty, it will return FALSE
system_handle_form_submit Intercepts form submissions from forms built with the form API.
system_handle_logout
system_handle_uninstall_module Called from the menu (ie, a URL) this function will uninstall a module.
system_init Called on every page load.
system_login_form This draws the form which facilitates logins.
system_login_form_submit Submit handler for login form. If we are here, it probably means we have indeed authenticated. Just in case, we will test the form_state["passed_authentication"] value, which we expect to have been set in our validate handler.
system_login_form_validate Validate function for the login form. This is where we will do all of the lookups to verify username and password. If you want to write your own login handler (like for LDAP) this is the function you would duplicate in a custom module, then use…
system_menu
system_mfa_login_form
system_mfa_login_form_submit
system_mfa_login_form_validate
system_modules_form This is the form which an admin may use to manage the modules in the system.
system_modules_form_submit Submit handler for the modules form.
system_perform_clear_cache This function will clear our various caches by calling on the hook_clear_cache in each module.
system_perform_clear_menu_cache Clears only the menu cache
system_perform_db_updates_perform_batch_operation Performs db updates ONE module at a time.
system_perform_run_cron Called from menu, will run hook_cron() for all modules.
system_perform_user_login Actually performs the logging in of a user with user_id.
system_perm Implementation of hook_perm(). Expects to return an array of permissions recognized by this module.
system_popup_report_contact_form This is the form which lets users send an email to the FlightPath production team,
system_popup_report_contact_form_submit
system_popup_report_contact_thank_you This is the thank you page you see after submitting the contact form.
system_rebuild_css_js_query_string This function will recreate the dummy query string we add to the end of css and js files.
system_reload_and_cache_course_inventory Formerly part of the FlightPath class, this function will read in or reload the course inventory into a file, which then goes into the SESSION to make it faster to access.
system_render_advising_snapshop_for_iframe This is meant to be a widget which shows in the dashboard of the advising user, within an iframe, since it can take a while to load.
system_school_data_form This form is for the school-data, like subject code descriptions, colleges, etc.
system_school_data_form_validate Validate handler for the school_data_form.
system_settings_form This is the "system settings" form.
system_settings_form_submit Extra submit handler for the system_settings_form
system_settings_form_validate Extra validate handler for our system settings form.
system_status Implementation of hook_status Expected return is array( "severity" => "normal" or "warning" or "alert", "status" => "A message to display to the user.", );