misc.inc

  1. 7.x includes/misc.inc
  2. 6.x includes/misc.inc
  3. 5.x includes/misc.inc

This file contains misc functions for FlightPath

File

includes/misc.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * This file contains misc functions for FlightPath
  5. */
  6. /**
  7. * Returns back the "friendly" timezone string if we have one.
  8. */
  9. function friendly_timezone($str) {
  10. $arr = array(
  11. 'America/Chicago' => 'Central Time - US & Canada',
  12. 'America/Los_Angeles' => 'Pacific Time - US & Canada',
  13. 'America/New_York' => 'Eastern Time - US & Canada',
  14. 'America/Denver' => 'Mountain Time - US & Canada',
  15. 'America/Phoenix' => 'Arizona Time',
  16. 'America/Anchorage' => 'Alaska Time',
  17. 'America/Adak' => 'Hawaii Time',
  18. 'Pacific/Honolulu' => 'Hawaii Time no DST',
  19. );
  20. if (isset($arr[$str])) return $arr[$str];
  21. return $str;
  22. }
  23. // From https://php.watch/versions/8.2/utf8_encode-utf8_decode-deprecated
  24. // In PHP 8.2, utf8_encode and _decode are deprecated. This code replicates
  25. // the same functionality as a drop-in replacement.
  26. function fp_utf8_encode(string $s) {
  27. $s .= $s;
  28. $len = \strlen($s);
  29. for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) {
  30. switch (true) {
  31. case $s[$i] < "\x80": $s[$j] = $s[$i]; break;
  32. case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break;
  33. default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break;
  34. }
  35. }
  36. return substr($s, 0, $j);
  37. }
  38. /**
  39. * @see fp_utf8_encode for an explanation of why this function exists.
  40. */
  41. function fp_utf8_decode(string $string) {
  42. $s = (string) $string;
  43. $len = \strlen($s);
  44. for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) {
  45. switch ($s[$i] & "\xF0") {
  46. case "\xC0":
  47. case "\xD0":
  48. $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F");
  49. $s[$j] = $c < 256 ? \chr($c) : '?';
  50. break;
  51. case "\xF0":
  52. ++$i;
  53. // no break
  54. case "\xE0":
  55. $s[$j] = '?';
  56. $i += 2;
  57. break;
  58. default:
  59. $s[$j] = $s[$i];
  60. }
  61. }
  62. return substr($s, 0, $j);
  63. }
  64. // source: Laravel Framework
  65. // (helper functions if we are not running PHP 8.)
  66. // https://github.com/laravel/framework/blob/8.x/src/Illuminate/Support/Str.php
  67. if (!function_exists('str_starts_with')) {
  68. function str_starts_with($haystack, $needle) {
  69. return (string)$needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0;
  70. }
  71. }
  72. if (!function_exists('str_ends_with')) {
  73. function str_ends_with($haystack, $needle) {
  74. return $needle !== '' && substr($haystack, -strlen($needle)) === (string)$needle;
  75. }
  76. }
  77. if (!function_exists('str_contains')) {
  78. function str_contains($haystack, $needle) {
  79. return $needle !== '' && mb_strpos($haystack, $needle) !== false;
  80. }
  81. }
  82. /**
  83. * Returns back a FAPI-compatible array of all term codes for the specified years, inclusive
  84. */
  85. function fp_get_terms_by_year_range($start_year, $end_year, $school_id = 0, $bool_include_term_id_in_description = TRUE) {
  86. // Let the user select a term for this school (starting at now - $min years, and going out to now + max years)
  87. $options = array();
  88. $temp = get_term_structures($school_id);
  89. for ($year = $start_year; $year <= $end_year; $year++) {
  90. foreach ($temp as $k => $details) {
  91. $term_id = $year . $k;
  92. // Adding year as the first element causes it to separate out the terms by year in the pulldown. Makes it cleaner.
  93. $options[$year][$term_id] = "[$term_id] &nbsp;" . get_term_description($term_id, FALSE, $school_id);
  94. if (!$bool_include_term_id_in_description) {
  95. $options[$year][$term_id] = get_term_description($term_id, FALSE, $school_id);
  96. }
  97. }
  98. } //for t
  99. ksort($options);
  100. return $options;
  101. }
  102. /**
  103. * Returns an array (suitable for form api) of departments on campus which
  104. * faculty/staff can be members of.
  105. */
  106. function fp_get_departments($school_id = 0, $bool_get_all_by_schools = FALSE) {
  107. $rtn = array();
  108. $cache_key = md5(serialize(func_get_args()));
  109. // Get from cache if already loaded once this page load
  110. if (isset($GLOBALS['fp_cache_departments'][$cache_key])) {
  111. return $GLOBALS['fp_cache_departments'][$cache_key];
  112. }
  113. if (!$bool_get_all_by_schools) {
  114. $val = variable_get_for_school('departments', '', $school_id);
  115. $lines = explode("\n", $val);
  116. foreach ($lines as $line) {
  117. $line = trim($line);
  118. if (!$line) continue;
  119. $temp = explode("~", $line);
  120. $rtn[trim($temp[0])] = trim($temp[1]);
  121. }
  122. }
  123. else if (module_enabled('schools') && $bool_get_all_by_schools == TRUE) {
  124. // We should return a multi-dimensional array where the school name is first, followed by dept_code.
  125. // Ex: $rtn['SCHOOL_ABC']['ENGL'] = "English";
  126. // We would need to go through all of our schools in a for loop first.
  127. $schools = schools_get_school_definitions();
  128. foreach ($schools as $school_id => $throw_away) {
  129. $school_code = schools_get_school_code_for_id($school_id);
  130. if ($school_id == 0) $school_code = "- Default -";
  131. $val = variable_get_for_school('departments', '', $school_id);
  132. $lines = explode("\n", $val);
  133. foreach ($lines as $line) {
  134. $line = trim($line);
  135. if (!$line) continue;
  136. $temp = explode("~", $line);
  137. $rtn[$school_code][trim($temp[0])] = trim($temp[1]);
  138. }
  139. }
  140. }
  141. $GLOBALS['fp_cache_departments'][$cache_key] = $rtn; // store in cache
  142. return $rtn;
  143. }
  144. /**
  145. * This function will use the "Numeric to Letter Grade" setting in the School settings to
  146. * translate the given grade (if it is numeric) to a letter grade. Otherwise, it will return
  147. * the grade as-is.
  148. */
  149. function fp_translate_numeric_grade($grade, $school_id = 0) {
  150. $only_grade = $grade;
  151. // We may have included MID at the end of our numeric grade, and if so, it's a midterm grade.
  152. $bool_midterm = FALSE;
  153. if (strstr($grade, "MID")) {
  154. $bool_midterm = TRUE;
  155. $only_grade = trim(str_replace("MID", "", $grade));
  156. }
  157. if (!is_numeric($only_grade)) return $grade; // already a letter, return the original grade.
  158. $translate = array();
  159. // Get our translation array from globals cache, if there.
  160. if (isset($GLOBALS['fp_translate_numeric_grade'][$school_id])) {
  161. $translate = $GLOBALS['fp_translate_numeric_grade'][$school_id];
  162. }
  163. else {
  164. $temp = trim(variable_get_for_school("numeric_to_letter_grades", "", $school_id));
  165. if ($temp === '') return $grade;
  166. $temp = explode("\n", $temp);
  167. foreach ($temp as $line) {
  168. $line = trim($line);
  169. if ($line == "") continue;
  170. $tokens = explode("~", $line);
  171. $low = trim($tokens[0]);
  172. $high = trim($tokens[1]);
  173. $letter = trim($tokens[2]);
  174. $translate[$low][$high] = $letter;
  175. } // foreach
  176. $GLOBALS['fp_translate_numeric_grade'][$school_id] = $translate;
  177. }
  178. // Okay, now that we have our "translate" array, we can do that with the grade.
  179. $grade = floatval($grade);
  180. foreach ($translate as $low => $details) {
  181. foreach ($translate[$low] as $high => $letter) {
  182. if ($grade >= floatval($low) && $grade <= floatval($high)) {
  183. if ($bool_midterm) $letter .= "MID";
  184. return $letter;
  185. }
  186. }
  187. }
  188. // Else, return back the original grade.
  189. return $grade;
  190. }
  191. /**
  192. * Re-order the _FILES array for multiple files, to make it easier to work with. From:
  193. * http://php.net/manual/en/features.file-upload.multiple.php
  194. *
  195. * To use:
  196. * $myfiles = fp_re_array_files($_FILES['fieldname'])
  197. *
  198. *
  199. */
  200. function fp_re_array_files($file_post) {
  201. $file_ary = array();
  202. $file_count = count($file_post['name']);
  203. $file_keys = array_keys($file_post);
  204. for ($i=0; $i<$file_count; $i++) {
  205. foreach ($file_keys as $key) {
  206. $file_ary[$i][$key] = $file_post[$key][$i];
  207. }
  208. }
  209. return $file_ary;
  210. }
  211. // From: https://www.php.net/manual/en/function.timezone-offset-get.php
  212. /**
  213. * Returns the offset from the origin timezone to the remote timezone, in seconds, or false if there is an error
  214. * @param $remote_tz
  215. * @param $origin_tz
  216. * If null the servers current timezone is used as the origin.
  217. * @return int|false
  218. */
  219. function get_timezone_offset($remote_tz, $origin_tz = null) {
  220. if($origin_tz === null) {
  221. if(!is_string($origin_tz = date_default_timezone_get())) {
  222. return false; // A UTC timestamp was returned -- bail out!
  223. }
  224. }
  225. $origin_dtz = new DateTimeZone($origin_tz);
  226. $remote_dtz = new DateTimeZone($remote_tz);
  227. $origin_dt = new DateTime("now", $origin_dtz);
  228. $remote_dt = new DateTime("now", $remote_dtz);
  229. $offset = $origin_dtz->getOffset($origin_dt) - $remote_dtz->getOffset($remote_dt);
  230. return $offset;
  231. }
  232. /**
  233. *
  234. * From: https://stackoverflow.com/questions/1369936/check-to-see-if-a-string-is-serialized
  235. *
  236. *
  237. * Check if a string is serialized
  238. *
  239. * @param string $string
  240. *
  241. * @return bool
  242. */
  243. function is_serialized_string($string)
  244. {
  245. return ($string == 'b:0;' || @unserialize($string) !== false);
  246. }
  247. // From: https://gist.github.com/ryanboswell/cd02add580ddce012469
  248. /**
  249. * The point of this function is to convert between UTC (what we expect all times to start with.). If we're coming
  250. * from the the database or a time() function, it's UTC. The "end_timezone_string" should be the user's preferred timezone.
  251. *
  252. * if end_timezone_string == null, then we will use the user's selected timezone. If that isn't set, we use they system's.
  253. *
  254. * As a convenenience, if the data_format we will get back a formatted date. Otherwise we'll get back a timestamp.
  255. */
  256. function convert_time($time_to_convert = 0, $start_timezone_string = "UTC", $end_timezone_string = NULL, $date_format = null ) {
  257. // We require a start time
  258. if( empty( $time_to_convert ) ){
  259. return false;
  260. }
  261. if ($end_timezone_string == NULL) {
  262. $end_timezone_string = fp_get_user_timezone();
  263. }
  264. // If the two timezones are different, find the offset
  265. if( $start_timezone_string != $end_timezone_string ) {
  266. // Create two timezone objects, one for the start and one for
  267. // the end
  268. $dateTimeZoneStart = new DateTimeZone( $start_timezone_string );
  269. $dateTimeZoneEnd = new DateTimeZone( $end_timezone_string );
  270. // Create two DateTime objects that will contain the same Unix timestamp, but
  271. // have different timezones attached to them.
  272. $dateTimeStart = new DateTime("now", $dateTimeZoneStart );
  273. $dateTimeEnd = new DateTime("now", $dateTimeZoneEnd );
  274. // Calculate the UTC offset for the date/time contained in the $dateTimeStart
  275. // object, but using the timezone rules as defined for the end timezone ($dateTimeEnd)
  276. $timeOffset = $dateTimeZoneEnd->getOffset($dateTimeStart);
  277. // If we are converting FROM non-utc TO UTC, then this logic doesn't work!
  278. // We need to basically grab the reverse logic...
  279. if ($start_timezone_string != 'UTC' && $end_timezone_string == 'UTC') {
  280. $x = $dateTimeZoneStart->getOffset($dateTimeEnd);
  281. $timeOffset = -$x;
  282. }
  283. } else {
  284. // If the timezones are the same, there is no offset
  285. $timeOffset = 0;
  286. }
  287. // Convert the time by the offset
  288. $converted_time = $time_to_convert + $timeOffset;
  289. // If we have no given format, just return the time
  290. if( empty( $date_format ) ) {
  291. return $converted_time;
  292. }
  293. // Convert to the given date format
  294. return date( $date_format, $converted_time );
  295. }
  296. /**
  297. * Returns an array of all timezones PHP recognizes.
  298. * Inspired by code from: https://stackoverflow.com/questions/1727077/generating-a-drop-down-list-of-timezones-with-php
  299. *
  300. * This code will return the common US timezones first, followed by the rest of the timezones that PHP is aware of.
  301. *
  302. */
  303. function get_timezones($bool_include_offset = FALSE) {
  304. $timezones = array();
  305. // These are the common names for the US timezones.
  306. $us_desc_timezones = array(
  307. 'America/New_York' => 'Eastern',
  308. 'America/Chicago' => 'Central',
  309. 'America/Denver' => 'Mountain',
  310. 'America/Phoenix' => 'Mountain no DST',
  311. 'America/Los_Angeles' => 'Pacific',
  312. 'America/Anchorage' => 'Alaska',
  313. 'America/Adak' => 'Hawaii',
  314. 'Pacific/Honolulu' => 'Hawaii no DST',
  315. );
  316. $us_timezones = array_keys($us_desc_timezones);
  317. $timezones = DateTimeZone::listIdentifiers(DateTimeZone::ALL);
  318. // Place the US timezones at the top of the list.
  319. $timezones = array_merge($us_timezones, $timezones);
  320. $timezone_offsets = array();
  321. foreach( $timezones as $timezone )
  322. {
  323. $tz = new DateTimeZone($timezone);
  324. $timezone_offsets[$timezone] = $tz->getOffset(new DateTime);
  325. }
  326. $timezone_list = array();
  327. foreach( $timezone_offsets as $timezone => $offset )
  328. {
  329. $offset_prefix = $offset < 0 ? '-' : '+';
  330. $offset_formatted = gmdate( 'H:i', abs($offset) );
  331. $pretty_offset = $extra = "";
  332. if ($bool_include_offset) $pretty_offset = "(UTC$offset_prefix$offset_formatted) ";
  333. if (isset($us_desc_timezones[$timezone])) {
  334. $extra = " - (" . $us_desc_timezones[$timezone] . ")";
  335. }
  336. $disp_timezone = str_replace("_", " ", $timezone);
  337. $timezone_list[$timezone] = "$pretty_offset$disp_timezone$extra";
  338. }
  339. return $timezone_list;
  340. }
  341. /**
  342. * This is our custom error handler, which will intercept PHP warnings, notices, etc, and let us
  343. * display them, log them, etc.
  344. *
  345. * See https://www.php.net/manual/en/function.set-error-handler.php
  346. */
  347. function _fp_error_handler($error_level, $message, $filename, $line, $context = array()) {
  348. global $user;
  349. // In case we have not loaded bootstrap.inc yet.
  350. @define ('WATCHDOG_NOTICE', 5);
  351. @define ('WATCHDOG_ALERT', 1);
  352. @define ('WATCHDOG_ERROR', 3);
  353. @define ('WATCHDOG_DEBUG', 7);
  354. $PHP_8_0_SUPPRESSED_ERROR = E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE;
  355. $er = error_reporting();
  356. if ($er === 0 || $er === $PHP_8_0_SUPPRESSED_ERROR) { return false;} // suppressed with @-operator (0 for pre-php8, the variable for 8.0)
  357. $err_name = _fp_map_php_error_code($error_level);
  358. if (is_string($err_name) && stristr($err_name, 'notice')) return FALSE; // don't care about Notices.
  359. $watchdog_type = "php_error";
  360. $watchdog_severity = WATCHDOG_ERROR;
  361. if (is_string($err_name) && stristr($err_name, 'warning')) {
  362. $watchdog_type = "php_warning";
  363. $watchdog_severity = WATCHDOG_ALERT;
  364. }
  365. if (is_string($err_name) && stristr($err_name, 'recoverable error')) {
  366. $watchdog_type = "php_warning";
  367. $watchdog_severity = WATCHDOG_ALERT;
  368. }
  369. $arr = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 10); // limit of 10 levels deep so as not to eat up all the memory.
  370. // fpm() only displays for privileged users
  371. // We don't want to bother repeating the same message more than once for errors. The following code
  372. // will prevent that.
  373. $errmsg = "$message<br>... ($line) $filename";
  374. $derr_name = "";
  375. if (is_string($err_name)) {
  376. $errmsg = $err_name . ": $message<br>... ($line) $filename";
  377. $derr_name = $err_name;
  378. }
  379. $errmsg_hash = hash('sha256', $errmsg);
  380. if (!isset($GLOBALS['fp_error_handler__already_shown_error'][$errmsg_hash])) {
  381. fpm($errmsg);
  382. fpm($arr);
  383. $GLOBALS['fp_error_handler__already_shown_error'][$errmsg_hash] = TRUE;
  384. }
  385. else {
  386. return; // we've already displayed this error message; we can harmlessly return.
  387. }
  388. // Before we watchdog or mail this backtrace, make sure no field called "password" is in plain text.
  389. foreach ($arr as $c => $trace) {
  390. if (is_array($trace) && isset($trace['args'])) {
  391. foreach ($trace['args'] as $k => $details) {
  392. if (is_array($details)) {
  393. foreach ($details as $j => $val) {
  394. if (stristr($j, 'password')) {
  395. $arr[$c]['args'][$k][$j] = "--PASSWORD HIDDEN IN LOG--";
  396. }
  397. }
  398. }
  399. }
  400. }
  401. }
  402. $hostname = php_uname('n') . ' - ' . $GLOBALS['fp_system_settings']['base_url'];
  403. $user_name = '';
  404. $user_id = 0;
  405. if (!$user) {
  406. $user_name = 'Anonymous';
  407. }
  408. else {
  409. $user_name = $user->name;
  410. $user_id = $user->id;
  411. }
  412. $msg = "";
  413. $msg .= "USER: $user_name ($user_id) \n";
  414. $msg .= "SERVER: $hostname \n";
  415. $msg .= "DATE: " . format_date(convert_time(time())) . "\n";
  416. $msg .= "SEVERITY: $derr_name \n";
  417. $msg .= "--------------------------\n\n";
  418. $msg .= "$derr_name: $message \n\n";
  419. $msg .= "... ($line) $filename\n\n";
  420. $emsg = $msg; // We don't need the full backtrace for our "email" message.
  421. $msg .= "Backtrace: <pre>\n";
  422. $msg .= print_r($arr, TRUE);
  423. $msg .= "\n\n</pre>";
  424. // Because we can have EXTREMELY long $msg due to the backtrace, limit it to a reasonable number.
  425. if (strlen($msg) > 10000) {
  426. $msg = substr($msg, 0, 10000) . "\n\n\n... truncated to 10,000 characters to save space. Full length was: " . strlen($msg);
  427. }
  428. watchdog($watchdog_type, $msg, array(), $watchdog_severity);
  429. if (@intval($user_id) !== 1) {
  430. // We are NOT the admin user (user id 1). (No need to email, since it would appear on screen with the fpm() calls earlier.
  431. // Should we email someone of php errors?
  432. $tomail = trim(variable_get("notify_php_error_email_address", ""));
  433. if ($tomail != "") {
  434. //fp_mail($tomail, "PHP Error in FlightPath", $msg);
  435. // We should add this to our "to email" file, which will get emailed to this mail address later on.
  436. $error_files_dir = fp_get_files_path() . "/private";
  437. $x = file_put_contents($error_files_dir . '/fp_php_errors_to_email_log.txt', $emsg . "\n------------------------------------------------\n\n", FILE_APPEND);
  438. if (!$x) {
  439. watchdog('system', "Unable to write PHP error to email log file", array(), WATCHDOG_ERROR);
  440. }
  441. }
  442. }
  443. } // _fp_error_handler
  444. /**
  445. * Map an error code into an Error word
  446. *
  447. * @param int $code
  448. * - Error code to map
  449. * @return string
  450. * - String describing the error type
  451. */
  452. function _fp_map_php_error_code($code) {
  453. $error = '';
  454. switch ($code) {
  455. case E_PARSE:
  456. case E_ERROR:
  457. case E_CORE_ERROR:
  458. case E_COMPILE_ERROR:
  459. case E_USER_ERROR:
  460. $error = 'Fatal Error';
  461. break;
  462. case E_WARNING:
  463. case E_USER_WARNING:
  464. case E_COMPILE_WARNING:
  465. case E_RECOVERABLE_ERROR:
  466. $error = 'Warning';
  467. break;
  468. case E_NOTICE:
  469. case E_USER_NOTICE:
  470. $error = 'Notice';
  471. break;
  472. case E_STRICT:
  473. $error = 'Strict';
  474. break;
  475. case E_DEPRECATED:
  476. case E_USER_DEPRECATED:
  477. $error = 'Deprecated';
  478. break;
  479. default :
  480. break;
  481. }
  482. return $error;
  483. }
  484. /**
  485. * Send an email. Drop-in replacement for PHP's mail() command,
  486. * but can use SMTP protocol if enabled.
  487. *
  488. * For attachments (only for use in SMTP), the array can be one of two methods:
  489. *
  490. * (1)
  491. * $arr["full_filename"] => "filename"
  492. * or
  493. * (2)
  494. * $arr['filename'] = "string that makes up attachment"
  495. *
  496. * For method #2, $bool_string_attachment must be set to TRUE.
  497. *
  498. */
  499. function fp_mail($to, $subject, $msg, $bool_html = FALSE, $attachments = array(), $bool_string_attachment = FALSE) {
  500. // TODO: In the future, check to see if there are any other modules which invoke a hook to intercept mail.
  501. // The reason we do this md5 check is so that we don't try to send identical emails over and over. This can happen if we are trying
  502. // to email regarding the mysql server being down, and when we try to do a "watchdog", which has to use the mysql server,
  503. // we try to send ANOTHER error, then we are right back here and try to send ANOTHER email, etc, etc.
  504. $md5_check = md5($to . $subject . $msg . time());
  505. if (isset($_SESSION['fp_mail_last_sent_md5'])) {
  506. if ($_SESSION['fp_mail_last_sent_md5'] == $md5_check) {
  507. return;
  508. }
  509. }
  510. $_SESSION['fp_mail_last_sent_md5'] = $md5_check;
  511. watchdog('fp_mail', t("Sending mail to @to, subject: @subject. Last sent md5: @md5", array("@to" => $to, "@subject" => $subject, "@md5" => $md5_check)), array(), WATCHDOG_DEBUG);
  512. // Before actually sending an email, make sure IF the recipient belongs a user, then that user is not "disabled".
  513. $res = db_query("SELECT * FROM users
  514. WHERE email = ?
  515. ORDER BY is_disabled
  516. LIMIT 1", array(strtolower(trim($to))));
  517. $cur = db_fetch_array($res);
  518. if ($cur) {
  519. if (intval($cur['is_disabled']) === 1) {
  520. watchdog('fp_mail_not_send', "NOT sending to @to, user is marked as is_disabled in users table.", array('@to' => $to), WATCHDOG_DEBUG);
  521. return;
  522. }
  523. }
  524. if (module_enabled('smtp')) {
  525. smtp_mail($to, $subject, $msg, $bool_html, $attachments, $bool_string_attachment);
  526. return;
  527. }
  528. else {
  529. $headers = array();
  530. if ($bool_html) {
  531. // To send HTML mail, the Content-type header must be set
  532. $headers[] = 'MIME-Version: 1.0';
  533. $headers[] = 'Content-type: text/html; charset=iso-8859-1';
  534. }
  535. mail($to, $subject, $msg, implode("\r\n", $headers));
  536. }
  537. } // fp_mail
  538. /**
  539. * Returns the component of the page's path.
  540. *
  541. * When viewing a page at the path "admin/structure/types", for example, arg(0) returns "admin", arg(1) returns "structure", and arg(2) returns "types".
  542. *
  543. */
  544. function arg($index) {
  545. $q = $_REQUEST["q"];
  546. $temp = explode("/", $q);
  547. $rtn = fp_trim(@$temp[$index]);
  548. return $rtn;
  549. }
  550. /**
  551. * This function uses CURL to get the simple contents of a URL, whether http or https.
  552. */
  553. function fp_url_get_contents($url) {
  554. /*
  555. $ch = curl_init();
  556. curl_setopt( $ch, CURLOPT_AUTOREFERER, TRUE );
  557. curl_setopt( $ch, CURLOPT_HEADER, 0 );
  558. curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
  559. curl_setopt( $ch, CURLOPT_URL, $url );
  560. curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, TRUE );
  561. $data = curl_exec( $ch );
  562. curl_close( $ch );
  563. return $data;
  564. */
  565. // Make use of the superior fp_http_request instead.
  566. $res = fp_http_request($url);
  567. if (is_object($res) && isset($res->data)) {
  568. return $res->data;
  569. }
  570. return FALSE;
  571. }
  572. /**
  573. * This function is borrowed from Backdrop version 1.x. We use it to convert the $data (which is in the form of an assoc
  574. * array of keys and values) into an HTTP query string.
  575. *
  576. *
  577. * @param array $data
  578. * @param string $parent
  579. * @return string
  580. */
  581. function fp_http_build_query(array $data, $parent = '') {
  582. $params = array();
  583. foreach ($data as $key => $value) {
  584. $key = $parent ? $parent . rawurlencode('[' . $key . ']') : rawurlencode($key);
  585. // For better readability of paths in query strings, we decode slashes.
  586. $key = str_replace('%2F', '/', $key);
  587. // Recurse into children.
  588. if (is_array($value)) {
  589. $params[] = fp_http_build_query($value, $key);
  590. }
  591. // If a query parameter value is NULL, only append its key.
  592. elseif (!isset($value)) {
  593. $params[] = $key;
  594. }
  595. else {
  596. // For better readability of paths in query strings, we decode slashes.
  597. $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));
  598. }
  599. }
  600. return implode('&', $params);
  601. }
  602. /**
  603. * Send a request through the Internet and return the result as an object.
  604. *
  605. * This is a modified copy of the Drupal 6 function drupal_http_request(),
  606. * taken from here: http://api.drupal.org/api/drupal/includes!common.inc/function/drupal_http_request/6
  607. */
  608. function fp_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3, $timeout = 30.0) {
  609. global $db_prefix;
  610. $result = new stdClass();
  611. $errno = $errstr = '';
  612. // If we are trying to POST something, make sure content-type header is set.
  613. if ($method == 'POST' && !isset($headers['Content-Type']) && !isset($headers['Content-type']) && !isset($headers['content-type'])) {
  614. $headers['Content-type'] = "application/x-www-form-urlencoded";
  615. }
  616. // Parse the URL and make sure we can handle the schema.
  617. $uri = parse_url($url);
  618. if ($uri == FALSE) {
  619. $result->error = 'unable to parse URL';
  620. $result->code = -1001;
  621. return $result;
  622. }
  623. if (!isset($uri['scheme'])) {
  624. $result->error = 'missing schema';
  625. $result->code = -1002;
  626. return $result;
  627. }
  628. timer_start(__FUNCTION__);
  629. switch ($uri['scheme']) {
  630. case 'http':
  631. case 'feed':
  632. $port = isset($uri['port']) ? $uri['port'] : 80;
  633. $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
  634. $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $timeout);
  635. break;
  636. case 'https':
  637. // Note: Only works for PHP 4.3 compiled with OpenSSL.
  638. $port = isset($uri['port']) ? $uri['port'] : 443;
  639. $host = $uri['host'] . ($port != 443 ? ':' . $port : '');
  640. $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $timeout);
  641. break;
  642. default:
  643. $result->error = 'invalid schema ' . $uri['scheme'];
  644. $result->code = -1003;
  645. return $result;
  646. }
  647. // Make sure the socket opened properly.
  648. if (!$fp) {
  649. // When a network error occurs, we use a negative number so it does not
  650. // clash with the HTTP status codes.
  651. $result->code = -$errno;
  652. $result->error = trim($errstr);
  653. // Log that this failed.
  654. watchdog("http_request", "fp_http_request failed! Request URL: @url, Method: @method,
  655. Error code: @ec, Error msg: @em", array('@ec' => $errno, '@em' => $errstr, '@url' => $url, '@method' => $method), WATCHDOG_ERROR);
  656. return $result;
  657. }
  658. // Construct the path to act on.
  659. $path = isset($uri['path']) ? $uri['path'] : '/';
  660. if (isset($uri['query'])) {
  661. $path .= '?' . $uri['query'];
  662. }
  663. // Create HTTP request.
  664. $defaults = array(
  665. // RFC 2616: "non-standard ports MUST, default ports MAY be included".
  666. // We don't add the port to prevent from breaking rewrite rules checking the
  667. // host that do not take into account the port number.
  668. 'Host' => "Host: $host",
  669. 'User-Agent' => 'User-Agent: FlightPath (+https://flightpathacademics.com/)',
  670. );
  671. // Only add Content-Length if we actually have any content or if it is a POST
  672. // or PUT request. Some non-standard servers get confused by Content-Length in
  673. // at least HEAD/GET requests, and Squid always requires Content-Length in
  674. // POST/PUT requests.
  675. // Convert $data to a query string, if it's an array.
  676. if (is_array($data)) {
  677. $data = fp_http_build_query($data);
  678. }
  679. $content_length = 0;
  680. if ($data) {
  681. $content_length = strlen($data);
  682. }
  683. if ($content_length > 0 || $method == 'POST' || $method == 'PUT' || $method == 'DELETE') {
  684. $defaults['Content-Length'] = 'Content-Length: ' . $content_length;
  685. }
  686. // If the server url has a user then attempt to use basic authentication
  687. if (isset($uri['user'])) {
  688. $defaults['Authorization'] = 'Authorization: Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
  689. }
  690. foreach ($headers as $header => $value) {
  691. $defaults[$header] = $header . ': ' . $value;
  692. }
  693. $request = $method . ' ' . $path . " HTTP/1.0\r\n";
  694. $request .= implode("\r\n", $defaults);
  695. $request .= "\r\n\r\n";
  696. $request .= $data;
  697. $result->request = $request;
  698. // Calculate how much time is left of the original timeout value.
  699. $time_left = $timeout - timer_read(__FUNCTION__) / 1000;
  700. if ($time_left > 0) {
  701. stream_set_timeout($fp, floor($time_left), floor(1000000 * fmod($time_left, 1)));
  702. fwrite($fp, $request);
  703. }
  704. // Fetch response.
  705. $response = '';
  706. while (!feof($fp)) {
  707. // Calculate how much time is left of the original timeout value.
  708. $time_left = $timeout - timer_read(__FUNCTION__) / 1000;
  709. if ($time_left <= 0) {
  710. $result->code = HTTP_REQUEST_TIMEOUT;
  711. $result->error = 'request timed out';
  712. return $result;
  713. }
  714. stream_set_timeout($fp, floor($time_left), floor(1000000 * fmod($time_left, 1)));
  715. $chunk = fread($fp, 1024);
  716. $response .= $chunk;
  717. }
  718. fclose($fp);
  719. // Parse response headers from the response body.
  720. // Be tolerant of malformed HTTP responses that separate header and body with
  721. // \n\n or \r\r instead of \r\n\r\n. See http://drupal.org/node/183435
  722. list($split, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
  723. $split = preg_split("/\r\n|\n|\r/", $split);
  724. list($protocol, $code, $status_message) = explode(' ', trim(array_shift($split)), 3);
  725. $result->protocol = $protocol;
  726. $result->status_message = $status_message;
  727. $result->headers = array();
  728. // Parse headers.
  729. while ($line = trim((string) array_shift($split))) {
  730. list($header, $value) = explode(':', $line, 2);
  731. if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
  732. // RFC 2109: the Set-Cookie response header comprises the token Set-
  733. // Cookie:, followed by a comma-separated list of one or more cookies.
  734. $result->headers[$header] .= ',' . trim($value);
  735. }
  736. else {
  737. $result->headers[$header] = trim($value);
  738. }
  739. }
  740. $responses = array(
  741. 100 => 'Continue',
  742. 101 => 'Switching Protocols',
  743. 200 => 'OK',
  744. 201 => 'Created',
  745. 202 => 'Accepted',
  746. 203 => 'Non-Authoritative Information',
  747. 204 => 'No Content',
  748. 205 => 'Reset Content',
  749. 206 => 'Partial Content',
  750. 300 => 'Multiple Choices',
  751. 301 => 'Moved Permanently',
  752. 302 => 'Found',
  753. 303 => 'See Other',
  754. 304 => 'Not Modified',
  755. 305 => 'Use Proxy',
  756. 307 => 'Temporary Redirect',
  757. 400 => 'Bad Request',
  758. 401 => 'Unauthorized',
  759. 402 => 'Payment Required',
  760. 403 => 'Forbidden',
  761. 404 => 'Not Found',
  762. 405 => 'Method Not Allowed',
  763. 406 => 'Not Acceptable',
  764. 407 => 'Proxy Authentication Required',
  765. 408 => 'Request Time-out',
  766. 409 => 'Conflict',
  767. 410 => 'Gone',
  768. 411 => 'Length Required',
  769. 412 => 'Precondition Failed',
  770. 413 => 'Request Entity Too Large',
  771. 414 => 'Request-URI Too Large',
  772. 415 => 'Unsupported Media Type',
  773. 416 => 'Requested range not satisfiable',
  774. 417 => 'Expectation Failed',
  775. 500 => 'Internal Server Error',
  776. 501 => 'Not Implemented',
  777. 502 => 'Bad Gateway',
  778. 503 => 'Service Unavailable',
  779. 504 => 'Gateway Time-out',
  780. 505 => 'HTTP Version not supported',
  781. );
  782. // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  783. // base code in their class.
  784. if (!isset($responses[$code])) {
  785. $code = floor(intval($code) / 100) * 100;
  786. }
  787. switch ($code) {
  788. case 200: // OK
  789. case 304: // Not modified
  790. break;
  791. case 301: // Moved permanently
  792. case 302: // Moved temporarily
  793. case 307: // Moved temporarily
  794. $location = $result->headers['Location'];
  795. $timeout -= timer_read(__FUNCTION__) / 1000;
  796. if ($timeout <= 0) {
  797. $result->code = HTTP_REQUEST_TIMEOUT;
  798. $result->error = 'request timed out';
  799. }
  800. elseif ($retry) {
  801. $result = fp_http_request($result->headers['Location'], $headers, $method, $data, --$retry, $timeout);
  802. $result->redirect_code = $result->code;
  803. }
  804. $result->redirect_url = $location;
  805. break;
  806. default:
  807. $result->error = $status_message;
  808. }
  809. $result->code = $code;
  810. return $result;
  811. }
  812. /**
  813. * Begin a microtime timer for later use.
  814. */
  815. function timer_start($name) {
  816. global $timers;
  817. list($usec, $sec) = explode(' ', microtime());
  818. $timers[$name]['start'] = (float) $usec + (float) $sec;
  819. $timers[$name]['count'] = isset($timers[$name]['count']) ? ++$timers[$name]['count'] : 1;
  820. }
  821. /**
  822. * Works with the timer_start() function to return how long
  823. * it has been since the start.
  824. */
  825. function timer_read($name) {
  826. global $timers;
  827. if (isset($timers[$name]['start'])) {
  828. list($usec, $sec) = explode(' ', microtime());
  829. $stop = (float) $usec + (float) $sec;
  830. $diff = round(($stop - $timers[$name]['start']) * 1000, 2);
  831. if (isset($timers[$name]['time'])) {
  832. $diff += $timers[$name]['time'];
  833. }
  834. return $diff;
  835. }
  836. }
  837. /**
  838. * Returns a random string of length len.
  839. */
  840. function fp_get_random_string($len = 7, $alpha = TRUE, $numeric = TRUE, $symbols = FALSE) {
  841. $base = "";
  842. if ($alpha) {
  843. $base .= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  844. }
  845. if ($numeric) {
  846. $base .= "12345678901234567890";
  847. }
  848. if ($symbols) {
  849. $base .= "!@#$%^&*()_+!@#$%^&*()-=";
  850. }
  851. $str = "";
  852. for ($t = 0; $t < $len; $t++) {
  853. $base = str_shuffle($base);
  854. $str .= $base[0];
  855. }
  856. return $str;
  857. }
  858. /**
  859. * Call all modules which implement hook_clear_cache
  860. */
  861. function fp_clear_cache() {
  862. // Find modules which implement hook_clear_cache
  863. $modules = modules_implement_hook("clear_cache");
  864. foreach ($modules as $module) {
  865. call_user_func($module . '_clear_cache');
  866. }
  867. }
  868. /**
  869. Remove any possiblilty of a malicious attacker trying to inject
  870. nonsense.
  871. From: https://paragonie.com/blog/2015/06/preventing-xss-vulnerabilities-in-php-everything-you-need-know
  872. */
  873. function fp_no_html_xss($string) {
  874. return htmlentities($string, ENT_QUOTES, 'UTF-8');
  875. }
  876. // From: https://gist.github.com/hubgit/1322324
  877. function repair_html($html){
  878. // hide DOM parsing errors
  879. libxml_use_internal_errors(true);
  880. libxml_clear_errors();
  881. // load the possibly malformed HTML into a DOMDocument
  882. $dom = new DOMDocument();
  883. $dom->recover = true;
  884. $rnd = mt_rand(9, 9999) . time(); // just in case we have something else with ID "repair"
  885. $dom->loadHTML('<?xml encoding="UTF-8"><body id="repair' . $rnd . '">' . $html . '</body>'); // input UTF-8
  886. // copy the document content into a new document
  887. $doc = new DOMDocument();
  888. foreach ($dom->getElementById('repair' . $rnd)->childNodes as $child) {
  889. $doc->appendChild($doc->importNode($child, true));
  890. }
  891. // output the new document as HTML
  892. $doc->encoding = 'UTF-8'; // output UTF-8
  893. $doc->formatOutput = false;
  894. return trim($doc->saveHTML());
  895. }
  896. /**
  897. * Convenience function for @see filter_markup($str, "plain")
  898. */
  899. function filter_plain($str, $bool_trim = TRUE) {
  900. $x = filter_markup($str, "plain");
  901. if ($bool_trim) {
  902. $x = fp_trim($x);
  903. }
  904. return $x;
  905. }
  906. /**
  907. * Filter string with possible HTML, allowing only certain tags, and removing dangerous attributes.
  908. *
  909. * $type can be:
  910. * - "plain" - No HTML tags are allowed. Safest.
  911. * - "basic" - Only certain tags allowed, no attributes. Safest. New lines are converted to HTML break tags.
  912. * - "full" - All HTML is allowed through.
  913. *
  914. */
  915. function filter_markup($str, $type = "basic") {
  916. if (!$str) return $str;
  917. if (!is_string($str)) {
  918. return $str;
  919. }
  920. if ($type == 'plain') {
  921. $str = strip_tags($str);
  922. return $str;
  923. }
  924. // If we are here, we're doing something with HTML...
  925. // Fix mismatched HTML (without adding new tags).
  926. $str = repair_html($str);
  927. if ($type == "basic") {
  928. // To reduce extra newlines, remove any newline which is at the END of an existing <br> tag.
  929. $str = str_ireplace("<br>\n", "<br>", $str);
  930. $str = str_ireplace("<br />\n", "<br>", $str);
  931. $allowed_tags = array('a', 'em', 'strong', 'cite',
  932. 'blockquote', 'code', 'ul', 'ol', 'li',
  933. 'dl', 'dt', 'dd', 'span', 'div',
  934. 'b', 'i', 'u', 'br', 'p', 'table', 'tr',
  935. 'td', 'th', 'tbody', );
  936. $str = filter_xss($str, $allowed_tags);
  937. $str = trim($str);
  938. }
  939. if ($type == "full") {
  940. // Essentially, do nothing. All HTML is allowed through.
  941. }
  942. return $str;
  943. }
  944. /**
  945. * When receiving certain input from the user, filter out potential trouble characters
  946. * @param $type This is the type of input we are expecting. For example, the default is "major_code", which means
  947. * the input should exclude any characters not normally found in a major_code.
  948. */
  949. function filter_untrusted_input($string, $type = 'major_code') {
  950. if (!$string) $string = '';
  951. if ($type == 'major_code') {
  952. $string = filter_xss($string, array());
  953. $string = str_replace('"', '', $string);
  954. $string = str_replace("'", '', $string);
  955. $string = str_replace("(", '', $string);
  956. $string = str_replace(")", '', $string);
  957. $string = str_replace("#", '', $string);
  958. $string = str_replace("=", '', $string);
  959. $string = str_replace(" ", '', $string);
  960. $string = str_replace(";", '', $string);
  961. }
  962. return $string;
  963. } // filter_untrusted_input
  964. /**
  965. * This function is taken almost directly from Drupal 7's core code. It is used to help us filter out
  966. * dangerous HTML which the user might type.
  967. * From the D7 documentation:
  968. *
  969. * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
  970. * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses. For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
  971. * This code does four things:
  972. * Removes characters and constructs that can trick browsers.
  973. * Makes sure all HTML entities are well-formed.
  974. * Makes sure all HTML tags and attributes are well-formed.
  975. * Makes sure no HTML tags contain URLs with a disallowed protocol (e.g. javascript:).
  976. *
  977. */
  978. function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'span', 'div')) {
  979. // Only operate on valid UTF-8 strings. This is necessary to prevent cross
  980. // site scripting issues on Internet Explorer 6.
  981. if (!fp_validate_utf8($string)) {
  982. return '';
  983. }
  984. // Store the text format.
  985. filter_xss_split($allowed_tags, TRUE);
  986. // Remove NULL characters (ignored by some browsers).
  987. $string = str_replace(chr(0), '', $string);
  988. // Remove Netscape 4 JS entities.
  989. $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
  990. // Defuse all HTML entities.
  991. $string = str_replace('&', '&amp;', $string);
  992. // Change back only well-formed entities in our whitelist:
  993. // Decimal numeric entities.
  994. $string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string);
  995. // Hexadecimal numeric entities.
  996. $string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
  997. // Named entities.
  998. $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
  999. return preg_replace_callback('%
  1000. (
  1001. <(?=[^a-zA-Z!/]) # a lone <
  1002. | # or
  1003. <!--.*?--> # a comment
  1004. | # or
  1005. <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string
  1006. | # or
  1007. > # just a >
  1008. )%x', 'filter_xss_split', $string);
  1009. }
  1010. /**
  1011. * Like the filter_xss function, this is taken from D7's
  1012. * _filter_xss_split function
  1013. */
  1014. function filter_xss_split($m, $store = FALSE) {
  1015. static $allowed_html;
  1016. if ($store) {
  1017. $allowed_html = array_flip($m);
  1018. return;
  1019. }
  1020. $string = $m[1];
  1021. if (substr($string, 0, 1) != '<') {
  1022. // We matched a lone ">" character.
  1023. return '&gt;';
  1024. }
  1025. elseif (strlen($string) == 1) {
  1026. // We matched a lone "<" character.
  1027. return '&lt;';
  1028. }
  1029. if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
  1030. // Seriously malformed.
  1031. return '';
  1032. }
  1033. $slash = trim($matches[1]);
  1034. $elem = &$matches[2];
  1035. $attrlist = &$matches[3];
  1036. $comment = &$matches[4];
  1037. if ($comment) {
  1038. $elem = '!--';
  1039. }
  1040. if (!isset($allowed_html[strtolower($elem)])) {
  1041. // Disallowed HTML element.
  1042. return '';
  1043. }
  1044. if ($comment) {
  1045. return $comment;
  1046. }
  1047. if ($slash != '') {
  1048. return "</$elem>";
  1049. }
  1050. // Is there a closing XHTML slash at the end of the attributes?
  1051. $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count);
  1052. $xhtml_slash = $count ? ' /' : '';
  1053. // Clean up attributes.
  1054. $attr2 = implode(' ', filter_xss_attributes($attrlist));
  1055. $attr2 = preg_replace('/[<>]/', '', $attr2);
  1056. $attr2 = strlen($attr2) ? ' ' . $attr2 : '';
  1057. return "<$elem$attr2$xhtml_slash>";
  1058. }
  1059. function filter_xss_attributes($attr) {
  1060. $attrarr = array();
  1061. $mode = 0;
  1062. $attrname = '';
  1063. $skip = FALSE;
  1064. while (strlen($attr) != 0) {
  1065. // Was the last operation successful?
  1066. $working = 0;
  1067. switch ($mode) {
  1068. case 0:
  1069. // Attribute name, href for instance.
  1070. if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
  1071. $attrname = strtolower($match[1]);
  1072. $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
  1073. $working = $mode = 1;
  1074. $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
  1075. }
  1076. break;
  1077. case 1:
  1078. // Equals sign or valueless ("selected").
  1079. if (preg_match('/^\s*=\s*/', $attr)) {
  1080. $working = 1;
  1081. $mode = 2;
  1082. $attr = preg_replace('/^\s*=\s*/', '', $attr);
  1083. break;
  1084. }
  1085. if (preg_match('/^\s+/', $attr)) {
  1086. $working = 1;
  1087. $mode = 0;
  1088. if (!$skip) {
  1089. $attrarr[] = $attrname;
  1090. }
  1091. $attr = preg_replace('/^\s+/', '', $attr);
  1092. }
  1093. break;
  1094. case 2:
  1095. // Attribute value, a URL after href= for instance.
  1096. if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
  1097. $thisval = filter_xss_bad_protocol($match[1]);
  1098. if (!$skip) {
  1099. $attrarr[] = "$attrname=\"$thisval\"";
  1100. }
  1101. $working = 1;
  1102. $mode = 0;
  1103. $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
  1104. break;
  1105. }
  1106. if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
  1107. $thisval = filter_xss_bad_protocol($match[1]);
  1108. if (!$skip) {
  1109. $attrarr[] = "$attrname='$thisval'";
  1110. }
  1111. $working = 1;
  1112. $mode = 0;
  1113. $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
  1114. break;
  1115. }
  1116. if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
  1117. $thisval = filter_xss_bad_protocol($match[1]);
  1118. if (!$skip) {
  1119. $attrarr[] = "$attrname=\"$thisval\"";
  1120. }
  1121. $working = 1;
  1122. $mode = 0;
  1123. $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
  1124. }
  1125. break;
  1126. }
  1127. if ($working == 0) {
  1128. // Not well formed; remove and try again.
  1129. $attr = preg_replace('/
  1130. ^
  1131. (
  1132. "[^"]*("|$) # - a string that starts with a double quote, up until the next double quote or the end of the string
  1133. | # or
  1134. \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
  1135. | # or
  1136. \S # - a non-whitespace character
  1137. )* # any number of the above three
  1138. \s* # any number of whitespaces
  1139. /x', '', $attr);
  1140. $mode = 0;
  1141. }
  1142. }
  1143. // The attribute list ends with a valueless attribute like "selected".
  1144. if ($mode == 1 && !$skip) {
  1145. $attrarr[] = $attrname;
  1146. }
  1147. return $attrarr;
  1148. }
  1149. function filter_xss_bad_protocol($string) {
  1150. // Get the plain text representation of the attribute value (i.e. its meaning).
  1151. $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8');
  1152. return htmlspecialchars(fp_strip_dangerous_protocols($string), ENT_QUOTES, 'UTF-8');
  1153. }
  1154. function fp_strip_dangerous_protocols($uri) {
  1155. static $allowed_protocols;
  1156. if (!isset($allowed_protocols)) {
  1157. $allowed_protocols = array_flip(array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'tel', 'telnet', 'webcal'));
  1158. }
  1159. // Iteratively remove any invalid protocol found.
  1160. do {
  1161. $before = $uri;
  1162. $colonpos = strpos($uri, ':');
  1163. if ($colonpos > 0) {
  1164. // We found a colon, possibly a protocol. Verify.
  1165. $protocol = substr($uri, 0, $colonpos);
  1166. // If a colon is preceded by a slash, question mark or hash, it cannot
  1167. // possibly be part of the URL scheme. This must be a relative URL, which
  1168. // inherits the (safe) protocol of the base document.
  1169. if (preg_match('![/?#]!', $protocol)) {
  1170. break;
  1171. }
  1172. // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3
  1173. // (URI Comparison) scheme comparison must be case-insensitive.
  1174. if (!isset($allowed_protocols[strtolower($protocol)])) {
  1175. $uri = substr($uri, $colonpos + 1);
  1176. }
  1177. }
  1178. } while ($before != $uri);
  1179. return $uri;
  1180. }
  1181. function fp_validate_utf8($text) {
  1182. if (strlen($text) == 0) {
  1183. return TRUE;
  1184. }
  1185. // With the PCRE_UTF8 modifier 'u', preg_match() fails silently on strings
  1186. // containing invalid UTF-8 byte sequences. It does not reject character
  1187. // codes above U+10FFFF (represented by 4 or more octets), though.
  1188. return (preg_match('/^./us', $text) == 1);
  1189. }
  1190. /**
  1191. * Simple function to convert a string into a machine-readable string.
  1192. *
  1193. * Useful for making possibly unsafe text work as an array index, a CSS class, etc. Replaces
  1194. * "bad" characters, or characters which might not be allowed for variables, for example,
  1195. * into underscores (_).
  1196. *
  1197. * @param string $str
  1198. * @return string
  1199. */
  1200. function fp_get_machine_readable($str) {
  1201. if (!$str) return (string) $str;
  1202. return preg_replace('@[^a-zA-Z0-9_]+@', '_', $str);
  1203. }
  1204. /////////////////////////////////////////////////////////////////////
  1205. /**
  1206. * Return back an assoc array of our set degree classifications, separated by "level"
  1207. */
  1208. function fp_get_degree_classifications() {
  1209. $rtn = array();
  1210. // Level 1
  1211. $temp = explode("\n", variable_get("degree_classifications_level_1", "MAJOR ~ Major"));
  1212. foreach ($temp as $line) {
  1213. $temp2 = explode("~", $line);
  1214. $machine_name = fp_trim(@$temp2[0]);
  1215. $title = fp_trim(@$temp2[1]);
  1216. if ($machine_name != "") {
  1217. $rtn["levels"][1][$machine_name] = $title;
  1218. $rtn["machine_names"][$machine_name] = $title;
  1219. $rtn["machine_name_to_level_num"][$machine_name] = 1;
  1220. }
  1221. }
  1222. // Level 2
  1223. $temp = explode("\n", variable_get("degree_classifications_level_2", "MINOR ~ Minor"));
  1224. foreach ($temp as $line) {
  1225. $temp2 = explode("~", $line);
  1226. $machine_name = fp_trim(@$temp2[0]);
  1227. $title = fp_trim(@$temp2[1]);
  1228. if ($machine_name != "") {
  1229. $rtn["levels"][2][$machine_name] = $title;
  1230. $rtn["machine_names"][$machine_name] = $title;
  1231. $rtn["machine_name_to_level_num"][$machine_name] = 2;
  1232. }
  1233. }
  1234. // Level 3
  1235. $temp = explode("\n", variable_get("degree_classifications_level_3", "CONC ~ Concentration"));
  1236. foreach ($temp as $line) {
  1237. $temp2 = explode("~", $line);
  1238. $machine_name = fp_trim(@$temp2[0]);
  1239. $title = fp_trim(@$temp2[1]);
  1240. if ($machine_name != "") {
  1241. $rtn["levels"][3][$machine_name] = $title;
  1242. $rtn["machine_names"][$machine_name] = $title;
  1243. $rtn["machine_name_to_level_num"][$machine_name] = 3;
  1244. }
  1245. }
  1246. return $rtn;
  1247. }
  1248. /**
  1249. * Returns back an assoc array for the supplied code. Looks like:
  1250. * $arr["level_num"] = number
  1251. * $arr["title"] = the title
  1252. *
  1253. *
  1254. */
  1255. function fp_get_degree_classification_details($degree_class = "MAJOR", $bool_return_class_code_as_title_if_not_found = TRUE) {
  1256. $rtn = array();
  1257. if ($bool_return_class_code_as_title_if_not_found) {
  1258. // Use the degree_class as title for default, if we can't find it otherwise.
  1259. $rtn["level_num"] = 0;
  1260. $rtn["title"] = $degree_class;
  1261. $rtn["degree_class"] = $degree_class;
  1262. }
  1263. $degree_classifications = fp_get_degree_classifications();
  1264. foreach ($degree_classifications["levels"] as $num => $details) {
  1265. if (isset($details[$degree_class])) {
  1266. $rtn["level_num"] = $num;
  1267. $rtn["title"] = $details[$degree_class];
  1268. $rtn["degree_class"] = $degree_class;
  1269. break;
  1270. }
  1271. }
  1272. return $rtn;
  1273. }
  1274. /**
  1275. * Return an array version of the term_id_structure field from the admin settings
  1276. *
  1277. */
  1278. function get_term_structures($school_id = 0) {
  1279. $rtn = array();
  1280. $temp = trim(variable_get_for_school("term_id_structure", "", $school_id));
  1281. if ($temp == '') return array(); // return an empty array, as this isn't filled out yet.
  1282. $structures = explode("\n", $temp);
  1283. foreach ($structures as $structure) {
  1284. $tokens = explode(",", $structure);
  1285. $term_def = trim($tokens[0] ?? '');
  1286. // Get rid of the replacement pattern.
  1287. // Looks like: [Y4]40. We want the 40.
  1288. // Simply explode on "]"
  1289. $temp = explode("]", $term_def);
  1290. $term_suffix = trim($temp[1] ?? '');
  1291. $rtn[$term_suffix] = array(
  1292. "term_suffix" => $term_suffix,
  1293. "term_def" => $term_def,
  1294. "short" => trim($tokens[1] ?? ''),
  1295. "full" => trim($tokens[2] ?? ''),
  1296. "abbr" => trim($tokens[3] ?? ''),
  1297. "disp_adjust" => trim($tokens[4] ?? ''),
  1298. );
  1299. }
  1300. return $rtn;
  1301. }
  1302. /**
  1303. * Returns back an array of all the available requirement types (by code) that
  1304. * have been defined.
  1305. *
  1306. */
  1307. function fp_get_requirement_types($school_id) {
  1308. $rtn = array();
  1309. if (isset($GLOBALS['fp_temp_cache']['fp_get_requirement_types'][$school_id])) {
  1310. return $GLOBALS['fp_temp_cache']['fp_get_requirement_types'][$school_id];
  1311. }
  1312. $temp = explode("\n", 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));
  1313. foreach ($temp as $line) {
  1314. $line = trim($line);
  1315. if ($line == "") continue;
  1316. $temp = explode("~", $line);
  1317. $code = trim(strtolower($temp[0]));
  1318. $desc = trim($temp[1]);
  1319. $rtn[$code] = $desc;
  1320. }
  1321. // Make sure that code 'x' is set.
  1322. if (!isset($rtn["x"])) {
  1323. $rtn["x"] = t("Additional Requirements");
  1324. }
  1325. // Make sure code 'e' for Electives is set.
  1326. if (!isset($rtn["e"])) {
  1327. $rtn["e"] = t("Electives");
  1328. }
  1329. // Make sure code 'm' is set, for Major Requirements, our default type.
  1330. if (!isset($rtn["m"])) {
  1331. $rtn["m"] = t("Major Requirements");
  1332. }
  1333. $GLOBALS['fp_temp_cache']['fp_get_requirement_types'][$school_id] = $rtn;
  1334. return $rtn;
  1335. }
  1336. /**
  1337. * This function provides a pass-thru to $d = new DegreePlan(args).
  1338. * However, it allows for quick caching look-up, so it should be used when possible instead of $x = new DegreePlan.
  1339. */
  1340. function fp_load_degree($degree_id = "", DatabaseHandler $db = NULL, $bool_load_minimal = false, $array_significant_courses = false, $bool_use_draft = false) {
  1341. // Create a "cache key" based on the arguments, so we can look this degree up faster later.
  1342. $cache_key = md5(serialize(func_get_args()));
  1343. //fpm("degree_id: $degree_id . cache key:" . $cache_key . "+++++++++++++");
  1344. if (isset($GLOBALS['fp_temp_cache']['fp_load_degree'][$cache_key])) {
  1345. $degree = $GLOBALS['fp_temp_cache']['fp_load_degree'][$cache_key];
  1346. //fpm(" ... returning cache");
  1347. return $degree;
  1348. }
  1349. $degree = new DegreePlan($degree_id, $db, $bool_load_minimal, $array_significant_courses, $bool_use_draft);
  1350. // Save to our cache
  1351. $GLOBALS['fp_temp_cache']['fp_load_degree'][$cache_key] = $degree;
  1352. return $degree;
  1353. }
  1354. /**
  1355. * If this function is called, it will override any other page tabs
  1356. * which might be getting constructed. This lets the programmer,
  1357. * at run-time, completely control what tabs are at the top of the page.
  1358. */
  1359. function fp_set_page_tabs($tab_array) {
  1360. $GLOBALS["fp_set_page_tabs"] = $tab_array;
  1361. }
  1362. /**
  1363. * Allows the programmer to define subtabs at the top of the page.
  1364. *
  1365. * @param array $tab_array
  1366. */
  1367. function fp_set_page_sub_tabs($tab_array) {
  1368. $GLOBALS["fp_set_page_sub_tabs"] = $tab_array;
  1369. }
  1370. /**
  1371. * Convenience function to set the basic advising breadcrumbs
  1372. */
  1373. function fp_set_standard_advising_breadcrumbs() {
  1374. global $screen, $current_student_id;
  1375. // Let's set our breadcrumbs
  1376. $db = get_global_database_handler();
  1377. $crumbs = array();
  1378. $crumbs[] = array(
  1379. 'text' => 'Students',
  1380. 'path' => 'student-search',
  1381. );
  1382. $crumbs[] = array(
  1383. 'text' => $db->get_student_name($current_student_id) . " ($current_student_id)",
  1384. 'path' => 'student-profile',
  1385. 'query' => "current_student_id=$current_student_id",
  1386. );
  1387. fp_set_breadcrumbs($crumbs);
  1388. }
  1389. /**
  1390. * Set our breadcrumbs array.
  1391. *
  1392. * We expect the array to look like this:
  1393. * [0]['text'] = "Alerts";
  1394. * [0]['path'] = "my/alerts";
  1395. * [0]['query'] (optional)
  1396. * [0]['attributes'] (optional. Used exactly as in the l() function. @see l() )
  1397. *
  1398. * [1] .... etc.
  1399. * @see fp_render_breadcrumbs();
  1400. *
  1401. *
  1402. * Here is a practical example of how to call this function:
  1403. *
  1404. * $crumbs = array();
  1405. * $crumbs[] = array(
  1406. * 'text' => 'Alerts',
  1407. * 'path' => 'alerts',
  1408. * );
  1409. *
  1410. * fp_set_breadcrumbs($crumbs);
  1411. *
  1412. */
  1413. function fp_set_breadcrumbs($arr = array()) {
  1414. $GLOBALS['fp_breadcrumbs'] = $arr;
  1415. }
  1416. /**
  1417. * Allows the programmer to set the title of the page, overwriting any default title.
  1418. *
  1419. * @param string $title
  1420. */
  1421. function fp_set_title($title) {
  1422. $GLOBALS["fp_set_title"] = $title;
  1423. if ($title == "") {
  1424. fp_show_title(FALSE); // No title to show!
  1425. }
  1426. else {
  1427. fp_show_title(TRUE); // If we are calling this function, we clearly want to display the title.
  1428. }
  1429. }
  1430. /**
  1431. * Add a CSS class to the body tag of the page. Useful for themeing later on.
  1432. *
  1433. * @param String $class
  1434. */
  1435. function fp_add_body_class($class) {
  1436. // Let's sanitize the "class" to make sure it doesn't contain any trouble characters.
  1437. $class = str_replace("'", '', $class);
  1438. $class = str_replace('"', '', $class);
  1439. $class = str_replace('(', '', $class);
  1440. $class = str_replace(')', '', $class);
  1441. $class = str_replace(';', '', $class);
  1442. $class = str_replace('.', '', $class);
  1443. $class = str_replace('<', '', $class);
  1444. $class = str_replace('>', '', $class);
  1445. $class = str_replace('/', '', $class);
  1446. $class = str_replace('\\', '', $class);
  1447. $class = str_replace('#', '', $class);
  1448. $class = str_replace('&', '', $class);
  1449. @$GLOBALS["fp_add_body_classes"] .= " " . $class;
  1450. }
  1451. /**
  1452. * Returns back the site's "token", which is a simply md5 of some randomness.
  1453. * It is used primarily with forms, to ensure against cross-site forgeries.
  1454. * The site's token gets saved to the variables table, for later use. The idea
  1455. * is that every installation of FlightPath has a semi-unique token.
  1456. */
  1457. function fp_token() {
  1458. $site_token = variable_get("site_token", "");
  1459. if ($site_token == "") {
  1460. $site_token = md5("" . time() . rand(1,9999));
  1461. variable_set("site_token", $site_token);
  1462. }
  1463. return $site_token;
  1464. }
  1465. /**
  1466. * This function will provide the session_id as a string, as well as a secret token we can
  1467. * use to make sure the session_id is authentic and came from us and not a hacker.
  1468. */
  1469. function fp_get_session_str() {
  1470. $session_id = session_id(); // Get the PHP session_id
  1471. // TODO: don't use IP as part of this, as it can change if the user switches networks.
  1472. $ip = @$_SERVER["REMOTE_ADDR"];
  1473. if ($ip == "") $ip = "000";
  1474. // NOTE: We cannot use fp_token() here, since the get function (below) is called before the various bootstrap files are loaded.
  1475. // Create a string where we can confirm the ip and server name the session came from.
  1476. // TODO: Might be able to add more entropy later on, as long as it does not involve the database, since bootstrap isn't loaded yet when validating.
  1477. $str = $session_id . "~_" . md5($session_id . $ip . php_uname('n'));
  1478. return $str;
  1479. }
  1480. /**
  1481. * This will validate the session str (@see fp_get_session_str()) and return back either FALSE
  1482. * or the session_id.
  1483. */
  1484. function fp_get_session_id_from_str($str) {
  1485. // We expect $str to look like this:
  1486. // session_id~_md5(session_id . ip . php_uname('n'))
  1487. $temp = explode("~_", $str);
  1488. $session_id = trim($temp[0]);
  1489. $hash = trim($temp[1]);
  1490. $ip = @$_SERVER["REMOTE_ADDR"];
  1491. if ($ip == "") $ip = "000";
  1492. $test_hash = md5($session_id . $ip . php_uname('n'));
  1493. if ($test_hash === $hash) {
  1494. // Success!
  1495. return $session_id;
  1496. }
  1497. return FALSE;
  1498. }
  1499. /**
  1500. * Simple function that adds spaces after commas in CSV strings. Makes them easier to read.
  1501. */
  1502. function fp_space_csv($str) {
  1503. $str = str_replace(",", ", ", $str);
  1504. // Get rid of double spaces we might have introduced.
  1505. $str = str_replace(", ", ", ", $str);
  1506. $str = str_replace(", ", ", ", $str);
  1507. $str = str_replace(", ", ", ", $str);
  1508. $str = trim($str);
  1509. return $str;
  1510. }
  1511. /**
  1512. * Simple function to split a basic CSV string, trim all elements, then return
  1513. * the resulting array.
  1514. */
  1515. function csv_to_array($csv_string) {
  1516. $temp = explode(",", $csv_string);
  1517. $temp = array_map("trim", $temp);
  1518. return $temp;
  1519. }
  1520. /**
  1521. * Splits a basic csv but returns an array suitable for the form_api, retuns assoc array.
  1522. */
  1523. function csv_to_form_api_array($csv_string, $delimeter = ",", $bool_make_keys_machine_readable = TRUE) {
  1524. $rtn = array();
  1525. $temp = explode($delimeter, $csv_string);
  1526. foreach ($temp as $line) {
  1527. $line = trim($line);
  1528. if (!$line) continue;
  1529. $key = strtolower(fp_get_machine_readable($line));
  1530. $rtn[$key] = $line;
  1531. }
  1532. return $rtn;
  1533. }
  1534. /**
  1535. * From https://www.php.net/manual/en/function.str-getcsv.php#117692
  1536. */
  1537. function csv_multiline_to_array($csv_str, $bool_first_row_is_headers = TRUE) {
  1538. $csv = array_map('str_getcsv', explode("\n", trim($csv_str)));
  1539. array_walk($csv, function(&$a) use ($csv) {
  1540. if (count($a) == count($csv[0])) {
  1541. $a = array_combine($csv[0], $a);
  1542. }
  1543. else {
  1544. fpm("Warning: issue converting multiline CSV to an array within FlightPath. Not the same number of elements.");
  1545. }
  1546. });
  1547. if (!$bool_first_row_is_headers) {
  1548. array_shift($csv); # remove column header
  1549. }
  1550. return $csv;
  1551. }
  1552. /**
  1553. * Add a "message" to the top of the screen. Useful for short messages like "You have been logged out"
  1554. * or "Form submitted successfully."
  1555. *
  1556. * @param String $msg
  1557. * This is the string message itself.
  1558. * @param String $type
  1559. * The "type" of message. This string is added as a CSS class to the message, for theming later.
  1560. * Examples: "status", "warning", "error"
  1561. * @param boolean $bool_no_repeat
  1562. * Boolean. Should the message show more than once per page view? Set to TRUE if it should NOT.
  1563. */
  1564. function fp_add_message($msg, $type = "status", $bool_no_repeat = FALSE) {
  1565. $md5 = md5($type . $msg);
  1566. if ($bool_no_repeat && isset($_SESSION["fp_messages"]) && is_array($_SESSION["fp_messages"])) {
  1567. // Make sure this message isn't already in the session.
  1568. foreach($_SESSION["fp_messages"] as $s) {
  1569. if ($s["md5"] == $md5) return;
  1570. }
  1571. }
  1572. $_SESSION["fp_messages"][] = array("type" => $type, "msg" => $msg, "md5" => $md5);
  1573. }
  1574. /*
  1575. Does the string end with the provided needed?
  1576. */
  1577. function fp_str_ends_with ($haystack, $needle) {
  1578. return substr_compare($haystack, $needle, -strlen($needle)) === 0;
  1579. }
  1580. /**
  1581. * Add an extra CSS file to the page with this function.
  1582. * Ex: fp_add_css(fp_get_module_path("admin") . '/css/admin.css');
  1583. *
  1584. * @param String $path_to_css
  1585. */
  1586. function fp_add_css($path_to_css) {
  1587. // Init if needed
  1588. if (!isset($GLOBALS['fp_extra_css'])) $GLOBALS['fp_extra_css'] = array();
  1589. if (!in_array($path_to_css, $GLOBALS['fp_extra_css'])) {
  1590. $GLOBALS["fp_extra_css"][] = $path_to_css;
  1591. }
  1592. }
  1593. /**
  1594. * Add extra javascript to the page.
  1595. *
  1596. * - type = file... $js is expected to be the path to a javascript file.
  1597. * - type = setting... $js is expected to be an associative array of settings.
  1598. * For example: array("my_path" => "blah", "my_color" => "red").
  1599. * They will be available in javascript in the object FlightPath like so:
  1600. * FlightPath.settings.my_color;
  1601. *
  1602. * Ex: fp_add_js(fp_get_module_path("admin") . '/js/admin.js');
  1603. *
  1604. * @see fp_add_css()
  1605. *
  1606. */
  1607. function fp_add_js($js, $type = "file") {
  1608. // Init if needed
  1609. if (!isset($GLOBALS['fp_extra_js'])) $GLOBALS['fp_extra_js'] = array();
  1610. if ($type == "file") {
  1611. if (!in_array($js, $GLOBALS['fp_extra_js'])) {
  1612. $GLOBALS["fp_extra_js"][] = $js;
  1613. }
  1614. }
  1615. if ($type == "setting") {
  1616. if (!isset($GLOBALS["fp_extra_js_settings"])) $GLOBALS["fp_extra_js_settings"] = array();
  1617. // Instead of using array_merge_recursive, this guarantees that keys are not re-used.
  1618. foreach ($js as $key => $val) {
  1619. $GLOBALS["fp_extra_js_settings"][$key] = $val;
  1620. }
  1621. }
  1622. }
  1623. /**
  1624. * This function will create a string from a 1 dimensional assoc array.
  1625. * Ex: arr = array("pet" => "dog", "name" => "Rex")
  1626. * will return: pet_S-dog,name_S-Rex under the default settings.
  1627. *
  1628. * The separator is meant to be a string extremely unlikely to be used in the key or values.
  1629. *
  1630. * Use the fp_explode_assoc function to piece it back together.
  1631. * @see fp_explode_assoc
  1632. */
  1633. function fp_join_assoc($arr, $glue = ",", $assign_sep = "_S-") {
  1634. $rtn = "";
  1635. foreach ($arr as $key => $val) {
  1636. $rtn .= $key . $assign_sep . $val . $glue;
  1637. }
  1638. // Should be an extra glue character at the end we need to trim off.
  1639. $rtn = rtrim($rtn, $glue);
  1640. return $rtn;
  1641. }
  1642. /**
  1643. * Takes a string (created by fp_join_assoc()) and re-creates the 1 dimensional assoc array.
  1644. *
  1645. * The separator is meant to be a string extremely unlikely to be used in the key or values.
  1646. *
  1647. * @see fp_join_assoc()
  1648. */
  1649. function fp_explode_assoc($string, $delim = ",", $assign_sep = "_S-") {
  1650. $rtn = array();
  1651. $temp = explode($delim, $string);
  1652. foreach($temp as $line) {
  1653. $line = trim($line);
  1654. if ($line == "") continue;
  1655. $temp2 = explode($assign_sep, $line);
  1656. if (is_numeric($temp2[1])) {
  1657. $temp2[1] = $temp2[1] * 1; // if its numeric anyway, make it have a numeric type.
  1658. }
  1659. $rtn[$temp2[0]] = $temp2[1];
  1660. }
  1661. return $rtn;
  1662. }
  1663. /**
  1664. * Return the filepath to the module
  1665. *
  1666. * @param string $module
  1667. * @param bool $bool_include_file_system_path
  1668. * @param bool $bool_include_base_path
  1669. * @return string
  1670. */
  1671. function fp_get_module_path($module, $bool_include_file_system_path = FALSE, $bool_include_base_path = TRUE) {
  1672. $p = menu_get_module_path($module, $bool_include_file_system_path);
  1673. if ($bool_include_file_system_path == FALSE && $bool_include_base_path == TRUE) {
  1674. $p = base_path() . "/" . $p;
  1675. }
  1676. return $p;
  1677. }
  1678. /**
  1679. * Convenience function to return the /files system path. Does NOT end with a trailing slash.
  1680. *
  1681. */
  1682. function fp_get_files_path() {
  1683. $p = $GLOBALS["fp_system_settings"]["file_system_path"] . "/custom/files";
  1684. return $p;
  1685. }
  1686. /**
  1687. * Convenience function to return the temporary directory path. Does NOT end with a trailing slash.
  1688. * Ex: /tmp
  1689. */
  1690. function fp_get_tmp_path() {
  1691. return variable_get("tmp_path", "/tmp");
  1692. }
  1693. /**
  1694. * Simply returns TRUE or FALSE if the user is a student. (has the is_student == 1
  1695. *
  1696. * If account is null, the global user will be used.
  1697. */
  1698. function fp_user_is_student($account = null) {
  1699. global $user;
  1700. if ($account == NULL) {
  1701. $account = $user;
  1702. }
  1703. return (bool)$account->is_student;
  1704. }
  1705. /**
  1706. * Simply returns the module's row from the modules table, if it exists.
  1707. *
  1708. * @param string $module
  1709. */
  1710. function fp_get_module_details($module) {
  1711. // Special case if we are looking up flightpath itself
  1712. if ($module == "flightpath") {
  1713. $rtn = array(
  1714. "info" => array("name" => t("FlightPath (Core)")),
  1715. "version" => FLIGHTPATH_VERSION,
  1716. );
  1717. return $rtn;
  1718. }
  1719. $res = db_query("SELECT * FROM modules WHERE name = '?' ", $module);
  1720. $cur = db_fetch_array($res);
  1721. if ($test = unserialize($cur["info"])) {
  1722. $cur["info"] = $test;
  1723. }
  1724. return $cur;
  1725. }
  1726. /**
  1727. * This function will facilitate translations by using hook_translate()
  1728. *
  1729. * Allows variable replacements. Use like this:
  1730. * t("@name's blob", array("@name" => "Richard"));
  1731. * or simply
  1732. * t("My blob"); if you don't need replacements.
  1733. *
  1734. * Not implimented yet.
  1735. */
  1736. function t($str, $vars = array()) {
  1737. $langcode = $GLOBALS["fp_system_settings"]["language"] ?? 'en';
  1738. // First, change $str if any other modules implement hook_translate().
  1739. invoke_hook("translate", array(&$str, $langcode, $vars)); // str is passed by ref, so no return needed.
  1740. if (is_array($vars) && count($vars) > 0) {
  1741. foreach ($vars as $var => $val) {
  1742. // If var begins with %, it means we want to italicize the val.
  1743. if (strstr($var, "%")) {
  1744. $val = "<em>$val</em>";
  1745. }
  1746. if ($val === NULL || $val === FALSE) $val = ""; // change to empty string for PHP 8x
  1747. $str = str_replace($var, $val, $str);
  1748. }
  1749. }
  1750. return $str;
  1751. }
  1752. /**
  1753. * Provides translation functionality when database is not available.
  1754. *
  1755. * TODO: Not implemented yet
  1756. */
  1757. function st($str, $vars = array()) {
  1758. // Not implemented yet. For now, just replicate t().
  1759. if (is_array($vars) && count($vars) > 0) {
  1760. foreach ($vars as $var => $val) {
  1761. // If var begins with %, it means we want to italicize the val.
  1762. if (strstr($var, "%")) {
  1763. $val = "<em>$val</em>";
  1764. }
  1765. $str = str_replace($var, $val, $str);
  1766. }
  1767. }
  1768. return $str;
  1769. }
  1770. /**
  1771. * Shortcut for getting the base_url variable from the global system settings.
  1772. */
  1773. function base_url() {
  1774. $p = $GLOBALS["fp_system_settings"]["base_url"];
  1775. return $p;
  1776. }
  1777. /**
  1778. * Shortcut for getting the base_path variable from the global system settings.
  1779. */
  1780. function base_path() {
  1781. $p = $GLOBALS["fp_system_settings"]["base_path"];
  1782. // the base_path setting isn't set, so just use '.', meaning, start
  1783. // at the currect directory by default.
  1784. if ($p == "") {
  1785. $p = ".";
  1786. }
  1787. // if our base_path is simply "/" (meaning, we are hosted on a bare domain), then we should
  1788. // actually return nothing, so as not to cause errors with other systems.
  1789. if ($p == "/") {
  1790. $p = "";
  1791. }
  1792. return $p;
  1793. }
  1794. /**
  1795. * Convert a term ID into a description. Ex: 20095 = Spring of 2009.
  1796. */
  1797. function get_term_description($term_id, $bool_abbreviate = FALSE, $school_id = 0) {
  1798. // Describe the term in plain english, for displays.
  1799. // Ex: "Fall of 2002."
  1800. $rtn = "";
  1801. if (!$term_id) $term_id = '';
  1802. // If already in our GLOBALS cache, then just return that.
  1803. if (isset($GLOBALS['fp_cache_get_term_description'][$term_id][intval($bool_abbreviate)][$school_id])) {
  1804. return $GLOBALS['fp_cache_get_term_description'][$term_id][intval($bool_abbreviate)][$school_id];
  1805. }
  1806. // See if any modules would like to act on the term_id before we proceed.
  1807. invoke_hook("alter_term_id_prior_to_description", array(&$term_id, &$bool_abbreviate, $school_id));
  1808. if (strstr($term_id, "1111")) {
  1809. return "(data unavailable at this time)";
  1810. }
  1811. $year4 = intval(trim(substr($term_id, 0, 4)));
  1812. $year2 = intval(trim(substr($term_id, 2, 2)));
  1813. $ss = trim(substr($term_id, 4, strlen($term_id) - 4));
  1814. $year4p1 = $year4 + 1;
  1815. $year4m1 = $year4 - 1;
  1816. // left-pad these with 0's if needed.
  1817. $year2p1 = @fp_number_pad($year2 + 1, 2);
  1818. $year2m1 = @fp_number_pad($year2 - 1, 2);
  1819. // Let's look at the term_idStructure setting and attempt to match
  1820. // what we have been supplied.
  1821. // We expect this structure to look something like:
  1822. // [Y4]60, Spring, Spring of [Y4], Spr '[Y2]
  1823. // [Y4]40, Fall, Fall of [Y4-1], Fall '[Y2-1]
  1824. $temp = @variable_get_for_school("term_id_structure", '', $school_id);
  1825. $structures = explode("\n", $temp);
  1826. foreach ($structures as $structure) {
  1827. // Perform the necessary replacement patterns on the structure.
  1828. $structure = str_replace("[Y4]", $year4, $structure);
  1829. $structure = str_replace("[Y2]", $year2, $structure);
  1830. $structure = str_replace("[Y4-1]", $year4m1, $structure);
  1831. $structure = str_replace("[Y2-1]", $year2m1, $structure);
  1832. $structure = str_replace("[Y4+1]", $year4p1, $structure);
  1833. $structure = str_replace("[Y2+1]", $year2p1, $structure);
  1834. // Now, break up the structure to make it easier to work with.
  1835. $tokens = explode(",", $structure);
  1836. $term_def = @trim($tokens[0]);
  1837. $full_description = @trim($tokens[2]);
  1838. $abbr_description = @trim($tokens[3]);
  1839. // Does our term_id match the termDef?
  1840. if ($term_def == $term_id) {
  1841. if ($bool_abbreviate) {
  1842. return $abbr_description;
  1843. }
  1844. else {
  1845. return $full_description;
  1846. }
  1847. }
  1848. }
  1849. // No descr could be found, so just display the term_id itself.
  1850. if (trim($rtn) == "") {
  1851. $rtn = $term_id;
  1852. }
  1853. // Save to our GLOBALS cache
  1854. $GLOBALS['fp_cache_get_term_description'][$term_id][intval($bool_abbreviate)][$school_id] = $rtn;
  1855. return $rtn;
  1856. }
  1857. /**
  1858. * Redirect the user's browser to the specified internal path + query.
  1859. *
  1860. * We will automatically add the current_student_id variable, if it is not present
  1861. * in the query.
  1862. *
  1863. * Example uses:
  1864. * - fp_goto("admin");
  1865. * - fp_goto("test/1234");
  1866. * - fp_goto("test/123", "selection=yes&fruit=apple");
  1867. */
  1868. function fp_goto($path, $query = "") {
  1869. global $current_student_id;
  1870. // Were we sent an array instead of separate values? That's OK if so, let's separate them back out.
  1871. if (is_array($path)) {
  1872. $path = @$path[0];
  1873. $query = (string) @$path[1];
  1874. }
  1875. if ($current_student_id != "" && !strstr($query, "current_student_id=")) {
  1876. // If the query doesn't contain the current_student_id, then add it in.
  1877. $query .= "&current_student_id=$current_student_id";
  1878. }
  1879. // Close the seesion before we try to redirect.
  1880. session_write_close();
  1881. if ($path == "<front>") {
  1882. $path = variable_get("front_page", "main");
  1883. }
  1884. $location = fp_url($path, $query);
  1885. if (str_starts_with($path, "http://") || str_starts_with($path, "https://")) {
  1886. // We are going to an external address.
  1887. if (str_starts_with($query, "&")) {
  1888. // Need to add ? to the query.
  1889. $query = "?fprnd=" . mt_rand(9,999999) . $query;
  1890. }
  1891. $location = $path . $query;
  1892. }
  1893. header('Location: ' . $location);
  1894. exit();
  1895. }
  1896. /**
  1897. * This works like Drupal's l() function for creating links.
  1898. * Ex: l("Click here for course search!", "tools/course-search", "abc=xyz&hello=goodbye", array("class" => "my-class"));
  1899. * Do not include preceeding or trailing slashes.
  1900. */
  1901. function l($text, $path, $query = "", $attributes = array()) {
  1902. $rtn = "";
  1903. if (!$query) $query = ""; // For compatibility with PHP 8.2, make sure it isn't NULL or FALSE
  1904. if ($path == "<front>") {
  1905. $path = variable_get("front_page", "main");
  1906. }
  1907. // Does the path contain possible replacement patterns? (look for %)
  1908. if (strpos($path, "%") !== 0) {
  1909. $path = menu_convert_replacement_pattern($path);
  1910. }
  1911. // Does the query contain possible replacement patterns? (look for %)
  1912. if (strpos($query, "%") !== 0) {
  1913. $query = menu_convert_replacement_pattern($query);
  1914. }
  1915. $rtn .= '<a href="' . fp_url($path, $query) . '" ';
  1916. foreach ($attributes as $key => $value) {
  1917. $rtn .= $key . '="' . $value . '" ';
  1918. }
  1919. $rtn .= ">$text</a>";
  1920. return $rtn;
  1921. }
  1922. /**
  1923. * This convenience function returns the absolute URL to the path requested.
  1924. * @see fp_url()
  1925. */
  1926. function fp_url_absolute($path, $query = "") {
  1927. return base_url() . '/' . fp_url($path, $query, FALSE);
  1928. }
  1929. /**
  1930. * This function will take a path, ex: "admin/config/module"
  1931. * and a query, ex: "nid=5&whatever=yes"
  1932. * And join them together, respecting whether or not clean URL's are enabled.
  1933. */
  1934. function fp_url($path, $query = "", $include_base_path = TRUE) {
  1935. if (!$query) $query = ""; // For compatibility with PHP 8.2, make sure it isn't NULL or FALSE
  1936. if ($path == "<front>") {
  1937. $path = variable_get("default_home_path", "main");
  1938. }
  1939. // If clean URLs are enabled, we should begin with a ?, if not, use an &
  1940. $rtn = "";
  1941. if ($include_base_path) {
  1942. $rtn .= base_path() . "/";
  1943. }
  1944. // Make sure that $rtn isn't now "//". This can happen if our
  1945. // site is hosted on a bare domain. Ex: http://fp.example.com
  1946. // And we have set the base_path to simply "/"
  1947. if ($rtn == "//") $rtn = "/";
  1948. $bool_clean_urls = variable_get("clean_urls", FALSE);
  1949. if (!$bool_clean_urls) {
  1950. // Clean URLs are NOT enabled! Let's make sure the URL contains "index.php?q="
  1951. $rtn .= "index.php?q=";
  1952. }
  1953. $rtn .= $path;
  1954. if ($query != "") {
  1955. // Is there a ? already in the $rtn? If not, add a ?. If so, use a &.
  1956. if (!strstr($rtn, "?")) {
  1957. $rtn .= "?";
  1958. }
  1959. else {
  1960. $rtn .= "&";
  1961. }
  1962. $rtn .= $query;
  1963. }
  1964. return $rtn;
  1965. }
  1966. /**
  1967. * This function will attempt to determine automatically
  1968. * if we are on a mobile device, and should therefor use the mobile
  1969. * theme and layout settings.
  1970. *
  1971. */
  1972. function fp_screen_is_mobile(){
  1973. depricated_message("calling fp_screen_is_mobile is no longer used as of FP 6.x");
  1974. if (isset($GLOBALS["fp_page_is_mobile"])) {
  1975. return $GLOBALS["fp_page_is_mobile"];
  1976. }
  1977. $user_agent = $_SERVER['HTTP_USER_AGENT'];
  1978. $look_for = array(
  1979. "ipod",
  1980. "iphone",
  1981. "android",
  1982. "opera mini",
  1983. "blackberry",
  1984. "(pre\/|palm os|palm|hiptop|avantgo|plucker|xiino|blazer|elaine)",
  1985. "(iris|3g_t|windows ce|opera mobi|windows ce; smartphone;|windows ce; iemobile)",
  1986. "(smartphone|iemobile)",
  1987. );
  1988. $is_mobile = FALSE;
  1989. foreach ($look_for as $test_agent) {
  1990. if (preg_match('/' . $test_agent . '/i',$user_agent)) {
  1991. $is_mobile = TRUE;
  1992. break;
  1993. }
  1994. }
  1995. $GLOBALS["fp_page_is_mobile"] = $is_mobile;
  1996. return $is_mobile;
  1997. } // ends function mobile_device_detect
  1998. /**
  1999. * Simple function that returns TRUE if the module is enabled, FALSE otherwise.
  2000. *
  2001. * We also will use our existing GLOBALS cache.
  2002. */
  2003. function module_enabled($module_name) {
  2004. return (isset($GLOBALS["fp_system_settings"]["modules"][$module_name]));
  2005. }
  2006. /**
  2007. * Return an array of enabled modules which implement the provided hook.
  2008. * Do not include the preceeding "_" on the hook name!
  2009. *
  2010. * We can optionally provide the names of modules to skip. Ex: array('my_module', 'other_module', ... etc)
  2011. *
  2012. */
  2013. function modules_implement_hook($hook = "example_hook_here", $skip_modules = array()) {
  2014. // Going to use a global array to keep track of what hooks exist.
  2015. if (!isset($GLOBALS['hook_cache'])) $GLOBALS['hook_cache'] = array();
  2016. // Have we already cached this list previously?
  2017. if (isset($GLOBALS['hook_cache'][$hook])) return $GLOBALS['hook_cache'][$hook];
  2018. // We have not already cached this, so let's look for it fresh...
  2019. $rtn = array();
  2020. // If we are in the install script, the GLOBALS array won't be set up, since there is no
  2021. // settings file yet. If that's the case, create a blank array so we don't have an issue.
  2022. if (!isset($GLOBALS['fp_system_settings'])) $GLOBALS['fp_system_settings'] = array();
  2023. if (!isset($GLOBALS['fp_system_settings']['modules'])) $GLOBALS['fp_system_settings']['modules'] = array();
  2024. foreach ($GLOBALS["fp_system_settings"]["modules"] as $module => $value) {
  2025. if (isset($value["enabled"]) && $value["enabled"] != "1") {
  2026. // Module is not enabled. Skip it.
  2027. continue;
  2028. }
  2029. if (in_array($module, $skip_modules)) continue;
  2030. if (function_exists($module . '_' . $hook)) {
  2031. $rtn[] = $module;
  2032. }
  2033. }
  2034. $GLOBALS['hook_cache'][$hook] = $rtn;
  2035. return $rtn;
  2036. }
  2037. /**
  2038. * Invokes a hook to get numbers on the total, read, and unread values from our modules, to find out
  2039. * if we need to place a badge on the bell icon at the top of the screen.
  2040. */
  2041. function fp_recalculate_alert_count_by_type($account = NULL) {
  2042. global $user;
  2043. if ($account === NULL) $account = $user;
  2044. if ($account->id == 0) return FALSE;
  2045. $res = invoke_hook("get_alert_count_by_type", array($account));
  2046. $_SESSION['fp_alert_count_by_type'] = $res;
  2047. $_SESSION['fp_alert_count_by_type_last_check'] = time();
  2048. }
  2049. /**
  2050. * This is a simple function which attempts to trim a String. However, if $val is NOT a string, it will
  2051. * cast it to string.
  2052. *
  2053. * This is to prevent deprecated warnings in PHP 8+, and can be used as
  2054. * a drop-in replacement in place of trim()
  2055. */
  2056. function fp_trim($val = NULL) {
  2057. if (is_string($val)) {
  2058. return trim($val);
  2059. }
  2060. if (is_numeric($val)) {
  2061. $val = "" . $val;
  2062. return trim($val);
  2063. }
  2064. if ($val === NULL || $val === FALSE) {
  2065. return '';
  2066. }
  2067. // if $val is an array or object, intentionally return the trim() so it will cause a warning.
  2068. return trim($val);
  2069. }
  2070. /**
  2071. * Returns back the total, read, and unread numbers previously calculated to see if we need to place
  2072. * a badge next to the bell icon at the top of the screen. If unset, we will call the recalculate function.
  2073. */
  2074. function fp_get_alert_count_by_type($account = NULL) {
  2075. global $user;
  2076. if ($account === NULL) $account = $user;
  2077. if ($account->id == 0) return FALSE;
  2078. if (!isset($_SESSION['fp_alert_count_by_type_last_check'])) {
  2079. $_SESSION['fp_alert_count_by_type_last_check'] = 0;
  2080. }
  2081. // Should we recalculate again?
  2082. $test = time() - intval(variable_get('recalculate_alert_badge_seconds', 30));
  2083. if ($_SESSION['fp_alert_count_by_type_last_check'] < $test) {
  2084. unset($_SESSION['fp_alert_count_by_type']); // this will force us to recalculate.
  2085. }
  2086. if (!isset($_SESSION['fp_alert_count_by_type'])) {
  2087. fp_recalculate_alert_count_by_type($account);
  2088. }
  2089. return $_SESSION['fp_alert_count_by_type'];
  2090. }
  2091. /**
  2092. * Invoke all module hooks for the supplied hook.
  2093. */
  2094. function invoke_hook($hook = "example_hook_here", $params = array()) {
  2095. $rtn = array();
  2096. $modules = modules_implement_hook($hook);
  2097. foreach($modules as $module) {
  2098. $rtn[$module] = call_user_func_array($module . "_" . $hook, $params);
  2099. }
  2100. return $rtn;
  2101. }
  2102. /**
  2103. * This is a convenience function which simply passes through to fpm(), due entirely because
  2104. * of typos with fpm().
  2105. *
  2106. * @see fpm()
  2107. */
  2108. function dpm($var, $max_levels = 15) {
  2109. fpm("You typed 'dpm()' instead of 'fpm()'. Make sure to use fpm() next time.");
  2110. fpm($var, $max_levels);
  2111. }
  2112. /**
  2113. * Uses fp_add_message, but in this case, it also adds in the filename and line number
  2114. * which the message came from!
  2115. *
  2116. * Most useful for developers, tracking down issues. It's also only visible to administrators
  2117. * with the "view_fpm_debug" permission. So if you need to display a message only to admins,
  2118. * you can use fpm() as a shortcut.
  2119. *
  2120. * Note: If you attempt to fpm() an array
  2121. * or object with too many levels of nesting, it may run out of memory and your script will die.
  2122. */
  2123. function fpm($var, $max_levels = 15) {
  2124. if (!user_has_permission("view_fpm_debug")) {
  2125. return;
  2126. }
  2127. // Complex variable? Change it to print_r.
  2128. $str = $var;
  2129. if (is_array($str) || is_object($str)) {
  2130. $str = "<div class='fp-html-print-r-wrapper'>" . fp_html_print_r($str, "", 0, $max_levels) . "</div>";
  2131. }
  2132. $arr = debug_backtrace();
  2133. //pretty_print($arr);
  2134. $t = 0;
  2135. if (@$arr[1]['function'] == 'fpmct') {
  2136. $t = 1;
  2137. }
  2138. $file = $arr[$t]["file"];
  2139. if (strlen($file) > 70) {
  2140. $file = "..." . substr($file, strlen($file) - 70);
  2141. }
  2142. $str .= "<div class='fp-message-backtrace'>line {$arr[$t]["line"]}: $file</div>";
  2143. fp_add_message("&bull; " . $str);
  2144. }
  2145. /**
  2146. * Displays a depricated message on screen. Useful for tracking down
  2147. * when depricated functions are being used.
  2148. */
  2149. function depricated_message($str = "A depricated function has been called.") {
  2150. fpm($str);
  2151. fpm(debug_backtrace());
  2152. }
  2153. /**
  2154. * Convenience function, will use fp_debug_ct() to display
  2155. * a message, and the number of miliseconds since its last call.
  2156. */
  2157. function fpmct($val, $var = "") {
  2158. fpm(fp_debug_ct($val, $var));
  2159. }
  2160. /**
  2161. * Similar to print_r, this will return an HTML-friendly
  2162. * click-to-open system similar in design to Krumo.
  2163. */
  2164. function fp_html_print_r($var, $name = "", $cnt = 0, $max_levels = 20) {
  2165. $rtn = "";
  2166. if ($cnt > $max_levels) {
  2167. // Max levels deep. Deeper, and PHP might run
  2168. // out of memory or complain.
  2169. $rtn .= "<div class='fp-html-print-r-too-deep'>
  2170. " . t("Depth too great. To view deeper,
  2171. rephrase your fpm() call, starting at this depth.") . "
  2172. </div>";
  2173. return $rtn;
  2174. }
  2175. $type = gettype($var);
  2176. $rnd = md5(mt_rand(0, 999999) . microtime() . $type . $name);
  2177. if ($type == "boolean") {
  2178. $var = ($var == TRUE) ? "TRUE" : "FALSE";
  2179. }
  2180. $count = "";
  2181. if ($type == "string") {
  2182. $count = " - " . strlen($var) . " " . t("chars");
  2183. }
  2184. if ($type == "array" || $type == "object") {
  2185. if ($type == "array") {
  2186. $count = " - " . count($var) . " " . t("elements");
  2187. }
  2188. if ($type == "object") {
  2189. $count = " - " . get_class($var);
  2190. }
  2191. $rtn .= "<div class='fp-html-print-r-multi-row'>
  2192. <div class='fp-html-print-r-selector'
  2193. onClick='\$(\"#fp-html-print-r-var-value-$rnd\").toggle(\"medium\");'
  2194. >
  2195. <span class='fp-html-print-r-var-name'>$name</span>
  2196. <span class='fp-html-print-r-var-type'>($type$count)</span>
  2197. </div>
  2198. <div class='fp-html-print-r-var-value' id='fp-html-print-r-var-value-$rnd' style='display: none;'>";
  2199. foreach ($var as $key => $value) {
  2200. $rtn .= fp_html_print_r($value, $key, ($cnt + 1), $max_levels);
  2201. }
  2202. $rtn .= "</div>
  2203. </div>";
  2204. }
  2205. else if ($type == "string" && strlen($var) > 50) {
  2206. // If the variable is fairly long, we want to also make it a hide-to-show type field.
  2207. $rtn .= "<div class='fp-html-print-r-multi-row'>
  2208. <div
  2209. onClick='\$(\"#fp-html-print-r-var-value-$rnd\").toggle(\"medium\");'
  2210. >
  2211. <span class='fp-html-print-r-var-name'>$name</span>
  2212. <span class='fp-html-print-r-var-type'>($type$count)</span>
  2213. <span class='fp-html-print-r-var-value-abbr'>" . htmlentities(substr($var, 0, 50)) . "...</span>
  2214. </div>
  2215. <div class='fp-html-print-r-var-value' id='fp-html-print-r-var-value-$rnd' style='display: none;'>
  2216. ";
  2217. $rtn .= htmlentities($var);
  2218. $rtn .= "</div></div>";
  2219. }
  2220. else {
  2221. $html_val = $var;
  2222. if ($type != "resource") {
  2223. $html_val = htmlentities("" . $var);
  2224. }
  2225. $rtn .= "<div class='fp-html-print-r-single-row'>
  2226. <span class='fp-html-print-r-var-name'>$name</span>
  2227. <span class='fp-html-print-r-var-type'>($type$count)</span>
  2228. <span class='fp-html-print-r-var-value'>$html_val</span>
  2229. </div>";
  2230. }
  2231. return $rtn;
  2232. }
  2233. /**
  2234. * This is used usually when being viewed by a mobile device.
  2235. * It will shorten a catalog year range of 2008-2009 to just
  2236. * "08-09" or "2008-09" or even "09-2009".
  2237. *
  2238. *
  2239. * @param string $cat_range - Ex: 2006-2007
  2240. */
  2241. function get_shorter_catalog_year_range($cat_range, $abbr_first = true, $abbr_second = true) {
  2242. $temp = explode("-", $cat_range);
  2243. $first = $temp[0];
  2244. $second = $temp[1];
  2245. if ($abbr_first) {
  2246. $first = substr($first, 2, 2);
  2247. }
  2248. if ($abbr_second) {
  2249. $second = substr($second, 2, 2);
  2250. }
  2251. return "$first-$second";
  2252. }
  2253. /**
  2254. * This will find and include the module in question, calling
  2255. * it's hook_init() function if it has one.
  2256. *
  2257. * Will return TRUE or FALSE for success or failure to include
  2258. * the module.
  2259. *
  2260. * If the use_module_path is set to some value, we will not attempt to use
  2261. * the setting for this module's path. Useful if we do not have the module in our
  2262. * modules table yet.
  2263. *
  2264. * Example use: include_module("course_search");
  2265. *
  2266. * @param string $module
  2267. */
  2268. function include_module($module, $bool_call_init = TRUE, $use_module_path = "") {
  2269. $system_path = trim($GLOBALS["fp_system_settings"]["file_system_path"]);
  2270. $module_path = "";
  2271. if (isset($GLOBALS["fp_system_settings"]["modules"][$module])) {
  2272. $module_path = $GLOBALS["fp_system_settings"]["modules"][$module]["path"];
  2273. }
  2274. if ($use_module_path != "") {
  2275. $module_path = $use_module_path;
  2276. }
  2277. if ($module_path != "") {
  2278. $path = $module_path . "/$module.module";
  2279. if (file_exists($system_path . "/" . $path)) {
  2280. require_once($system_path . "/" . $path);
  2281. }
  2282. else {
  2283. print "<br><b>Could not find module '$module' at '$system_path/$path'</b><br>";
  2284. }
  2285. // Now that we have included it, call the module's hook_init() method.
  2286. if ($bool_call_init) {
  2287. if (function_exists($module . "_init")) {
  2288. call_user_func($module . "_init");
  2289. }
  2290. }
  2291. return TRUE;
  2292. }
  2293. return FALSE;
  2294. }
  2295. /**
  2296. * Find and include the module's .install file, if it exists.
  2297. * Returns TRUE or FALSE if it was able to find & include the file.
  2298. */
  2299. function include_module_install($module, $path) {
  2300. $system_path = trim($GLOBALS["fp_system_settings"]["file_system_path"]);
  2301. $install_path = $path . "/$module.install";
  2302. if (file_exists($system_path . "/" . $install_path)) {
  2303. require_once($system_path . "/" . $install_path);
  2304. return TRUE;
  2305. }
  2306. return FALSE;
  2307. }
  2308. /**
  2309. * Creates a javascript "confirm" link, so when clicked it asks the user a question, then proceeds
  2310. * if they select OK. The main reason I want to do this is so I can pass the $question through
  2311. * my t() function. (do it when you call this function)
  2312. */
  2313. function fp_get_js_confirm_link($question, $action_if_yes, $link_text, $extra_class = "", $link_title = "") {
  2314. $rtn = "";
  2315. $question = fp_reduce_whitespace($question);
  2316. //$question = htmlentities($question, ENT_QUOTES);
  2317. //$question = str_replace("\n", "\\n", $question);
  2318. $question = str_replace("\\n", "<br>", $question);
  2319. $question = str_replace("\n", "<br>", $question);
  2320. $question_64 = base64_encode($question); // convert to base_64 so we can have HTML
  2321. $link_title = htmlentities($link_title, ENT_QUOTES);
  2322. //$rtn .= "<a href='javascript: if(fp_confirm(\"$question\")) { $action_if_yes; }' class='$extra_class' title='$link_title'>$link_text</a>";
  2323. // Using new fp_confirm code
  2324. $action_if_yes_64 = base64_encode($action_if_yes);
  2325. $rtn .= "<a href='javascript: fp_confirm(\"$question_64\",\"base64\",\"$action_if_yes_64\");' class='$extra_class' title='$link_title'>$link_text</a>";
  2326. return $rtn;
  2327. }
  2328. /**
  2329. * Creates a javascript "prompt" link, which will ask the user a question.
  2330. *
  2331. * Similar to the fp_get_js_confirm_link function, but this is a prompt box
  2332. * which lets the user type in a response.
  2333. *
  2334. *
  2335. * @see fp_get_js_confirm_link
  2336. */
  2337. function fp_get_js_prompt_link($question, $default, $action_if_yes, $link_text, $extra_class = "") {
  2338. $rtn = "";
  2339. $question = fp_reduce_whitespace($question);
  2340. $question = htmlentities($question, ENT_QUOTES);
  2341. $question = str_replace("\n", "\\n", $question);
  2342. $rtn .= "<a href='javascript: var response = prompt(\"$question\", \"$default\");
  2343. if (response != null)
  2344. {
  2345. $action_if_yes ;
  2346. }
  2347. ' class='$extra_class'>$link_text</a>";
  2348. return $rtn;
  2349. }
  2350. /**
  2351. * Creates a javascript "alert" link, which tells the user some message with javascript alert().
  2352. *
  2353. * Similar to the fp_get_js_confirm_link function, but this is a simple alert message,
  2354. * with no user input.
  2355. *
  2356. *
  2357. * @see fp_get_js_confirm_link
  2358. */
  2359. function fp_get_js_alert_link($message, $link_text = NULL, $extra_css_class = "", $link_title = "View help for this item") {
  2360. $rtn = "";
  2361. if ($link_text == "" || $link_text == NULL) {
  2362. $link_text = "<span class='pop-q-mark'><i class='fa fa-question-circle'></i></span>";
  2363. }
  2364. $message = str_replace("\n", " ", $message);
  2365. $message = fp_reduce_whitespace($message);
  2366. $rtn .= "<a href='javascript: fp_alert(\"" . base64_encode($message) . "\",\"base64\");' title='$link_title' class='fp-alert-link $extra_css_class'>$link_text</a>";
  2367. return $rtn;
  2368. }
  2369. /**
  2370. * Simple helper function to reduce whitespace (like double-spaces)
  2371. *
  2372. * @param string $str
  2373. */
  2374. function fp_reduce_whitespace($str) {
  2375. // Cheap hack to get rid of whitespace
  2376. for ($t = 0; $t < 5; $t++) {
  2377. $str = str_replace(" ", " ", $str);
  2378. $str = str_replace("\n ", "\n", $str);
  2379. }
  2380. return $str;
  2381. }
  2382. /**
  2383. * Does the user have the specified role?
  2384. */
  2385. function user_has_role($role, $account = NULL) {
  2386. global $user;
  2387. if ($account == NULL) $account = $user;
  2388. // Admin always = TRUE
  2389. if ($account->id == 1) return TRUE;
  2390. // Check for other users...
  2391. if (in_array($role, $account->roles)) return TRUE;
  2392. return FALSE;
  2393. }
  2394. /**
  2395. * Returns TRUE or FALSE if the logged in user has access based on the
  2396. * permission supplied.
  2397. *
  2398. * @param String $permission
  2399. */
  2400. function user_has_permission($permission = "", $account = NULL) {
  2401. global $user;
  2402. if ($account == NULL) $account = $user;
  2403. //fpm("checking permission $permission");
  2404. if ($account == NULL) {
  2405. // The account is STILL null, so return FALSE.
  2406. // Most likely anonymous user.
  2407. return FALSE;
  2408. }
  2409. // If the user is admin (id == 1) then they always have access.
  2410. if ($account->id == 1) return TRUE;
  2411. if (!isset($account->permissions) || !is_array($account->permissions)) return FALSE; // not set up yet; anonymous user most likely.
  2412. // Otherwise, simply check their permissions array.
  2413. if (in_array($permission, $account->permissions)) {
  2414. return TRUE;
  2415. }
  2416. return FALSE;
  2417. }
  2418. /**
  2419. * This function will read through all the modules' permissions and
  2420. * return back an array. Specifically, it retrieves arrays from each
  2421. * modules' hook_perm() function.
  2422. *
  2423. */
  2424. function get_modules_permissions() {
  2425. $rtn = array();
  2426. foreach ($GLOBALS["fp_system_settings"]["modules"] as $module => $value) {
  2427. if (isset($value["disabled"]) && $value["disabled"] == "yes") {
  2428. // Module is not enabled. Skip it.
  2429. continue;
  2430. }
  2431. if (function_exists($module . "_perm")) {
  2432. $rtn[$module][] = call_user_func($module . "_perm");
  2433. }
  2434. }
  2435. return $rtn;
  2436. }
  2437. /**
  2438. * Similar to get_modules_permissions, this will scan through all installed
  2439. * modules' hook_menu() functions, and assemble an array which is sorted
  2440. * by "location" and then by "weight".
  2441. *
  2442. */
  2443. function get_modules_menus() {
  2444. $menus = array();
  2445. foreach ($GLOBALS["fp_system_settings"]["modules"] as $module => $value) {
  2446. if (isset($value["disabled"]) && $value["disabled"] == "yes") {
  2447. // Module is not enabled. Skip it.
  2448. continue;
  2449. }
  2450. if (function_exists($module . "_menu")) {
  2451. $menus[] = call_user_func($module . "_menu");
  2452. }
  2453. }
  2454. // Let's re-order based on weight...
  2455. // Convert to a single dimensional array for easier sorting.
  2456. $temp = array();
  2457. foreach ($menus as $c => $value) {
  2458. foreach ($menus[$c] as $d => $menu_data) {
  2459. $w = $menu_data["weight"];
  2460. if ($w == "") $w = "0";
  2461. // We need to front-pad $w with zeros, so it is the same length
  2462. // for every entry. Otherwise it will not sort correctly.
  2463. $w = fp_number_pad($w, 10);
  2464. $temp[] = "$w~~$c~~$d";
  2465. }
  2466. }
  2467. // Now, sort $temp...
  2468. sort($temp);
  2469. // Now, go back through $temp and get our new array...
  2470. $new_array = array();
  2471. foreach ($temp as $t) {
  2472. $vals = explode("~~", $t);
  2473. $c = $vals[1];
  2474. $d = $vals[2];
  2475. // Place them into subarrays indexed by location
  2476. // @phpstan-ignore-next-line (phpstan gets confused by the complexity of the next line)
  2477. $new_array[$menus[$c][$d]["location"]][] = $menus[$c][$d];
  2478. }
  2479. return $new_array;
  2480. }
  2481. /**
  2482. * Simple function to left padd numbers with 0's.
  2483. * 1 becomes 001
  2484. * 20 becomes 020
  2485. * and so on.
  2486. *
  2487. * @param int $number
  2488. * @param int $len
  2489. * @return String
  2490. */
  2491. function fp_number_pad($number, $len) {
  2492. return str_pad((int) $number, $len, "0", STR_PAD_LEFT);
  2493. }
  2494. /**
  2495. * This simple function will take a number and truncate the number of decimals
  2496. * to the requested places. This can be used in place of number_format(), which *rounds*
  2497. * numbers.
  2498. *
  2499. * For example, number_format(1.99999, 2) gives you 2.00.
  2500. * But THIS function gives:
  2501. * fp_truncate_decimals(1.999999, 2) = 1.99
  2502. *
  2503. *
  2504. * @param int $places
  2505. */
  2506. function fp_truncate_decimals($num, $places = 2) {
  2507. // does $num contain a .? If not, add it on.
  2508. if (!strstr("" . $num, ".")) {
  2509. $num .= ".0";
  2510. }
  2511. // Break it by .
  2512. $temp = explode (".", "" . $num);
  2513. // Get just the decimals and trim 'em
  2514. $decimals = trim(substr($temp[1], 0, $places));
  2515. if (strlen($decimals) < $places) {
  2516. // Padd with zeros on the right!
  2517. $decimals = str_pad($decimals, $places, "0", STR_PAD_RIGHT);
  2518. }
  2519. $new_num = $temp[0] . "." . $decimals;
  2520. return $new_num;
  2521. }
  2522. /**
  2523. * Adapted from https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_query_string_encode/6.x
  2524. */
  2525. function fp_query_string_encode($query, $exclude = array(), $parent = '') {
  2526. $params = array();
  2527. foreach ($query as $key => $value) {
  2528. $key = rawurlencode($key);
  2529. if ($parent) {
  2530. $key = $parent . '[' . $key . ']';
  2531. }
  2532. if (in_array($key, $exclude)) {
  2533. continue;
  2534. }
  2535. if (is_array($value)) {
  2536. $params[] = fp_query_string_encode($value, $exclude, $key);
  2537. }
  2538. else {
  2539. $params[] = $key . '=' . rawurlencode($value);
  2540. }
  2541. }
  2542. return implode('&', $params);
  2543. }
  2544. /**
  2545. * Shortcut to fp_debug_current_time_millis()
  2546. *
  2547. * @see fp_debug_current_time_millis()
  2548. *
  2549. * @param string $debug_val
  2550. * @param mixed $var
  2551. * @return string
  2552. */
  2553. function fp_debug_ct($debug_val = "", $var = "")
  2554. { // Shortcut to the other function.
  2555. return fp_debug_current_time_millis($debug_val, false, $var);
  2556. }
  2557. /**
  2558. * When called repeatedly, this function will display a message along with a milisecond count
  2559. * out to the side. Very useful for developers to time function calls or queries, to see how long they
  2560. * are taking.
  2561. *
  2562. * For example:
  2563. * fp_debug_ct("starting query");
  2564. * db_query(".........") // whatever
  2565. * fp_debug_ct("finished query");
  2566. *
  2567. * On screen, that would display our messages, with time values, so we can see how many milliseconds
  2568. * it took to execute between calls of fp_debug_ct().
  2569. *
  2570. * @param String $debug_val
  2571. * The message to display on screen.
  2572. * @param boolean $show_current_time
  2573. * Should we display the current time as well?
  2574. * @param String $var
  2575. * Optional. Include a variable name so you can have more than one timer running
  2576. * at the same time.
  2577. * @return string
  2578. */
  2579. function fp_debug_current_time_millis($debug_val = "", $show_current_time = true, $var = "")
  2580. {
  2581. // Display the current time in milliseconds, and, if available,
  2582. // show how many milliseconds its been since the last time
  2583. // this function was called. This helps programmers tell how
  2584. // long a particular function takes to run. Just place a call
  2585. // to this function before and after the function call.
  2586. $rtn = "";
  2587. $debug_string = $debug_val;
  2588. if (is_array($debug_val) || is_object($debug_val)) {
  2589. $debug_string = "<pre>" . print_r($debug_val, true) . "</pre>";
  2590. }
  2591. $last_time = @($GLOBALS["current_time_millis" . $var]) * 1; //*1 forces numeric
  2592. $cur_time = microtime(true) * 1000;
  2593. $debug_string = "<span style='color:red;'>DEBUG:</span>
  2594. <span style='color:green;'>$debug_string</span>";
  2595. $rtn .= "<div style='background-color: white;'>$debug_string";
  2596. if ($last_time > 1)
  2597. {
  2598. $diff = round($cur_time - $last_time,2);
  2599. $rtn .= "<span style='color: blue;'> ($diff" . t("ms since last call") . "</span>";
  2600. } else {
  2601. // Start of clock...
  2602. $rtn .= "<span style='color: blue;'> --- </span>";
  2603. }
  2604. $rtn .= "</div>";
  2605. $GLOBALS["current_time_millis" . $var] = $cur_time;
  2606. $GLOBALS["current_time_millis"] = $cur_time;
  2607. return $rtn;
  2608. }

Functions

Namesort descending Description
arg Returns the component of the page's path.
base_path Shortcut for getting the base_path variable from the global system settings.
base_url Shortcut for getting the base_url variable from the global system settings.
convert_time The point of this function is to convert between UTC (what we expect all times to start with.). If we're coming from the the database or a time() function, it's UTC. The "end_timezone_string" should be the user's preferred…
csv_multiline_to_array From https://www.php.net/manual/en/function.str-getcsv.php#117692
csv_to_array Simple function to split a basic CSV string, trim all elements, then return the resulting array.
csv_to_form_api_array Splits a basic csv but returns an array suitable for the form_api, retuns assoc array.
depricated_message Displays a depricated message on screen. Useful for tracking down when depricated functions are being used.
dpm This is a convenience function which simply passes through to fpm(), due entirely because of typos with fpm().
filter_markup Filter string with possible HTML, allowing only certain tags, and removing dangerous attributes.
filter_plain Convenience function for
filter_untrusted_input When receiving certain input from the user, filter out potential trouble characters
filter_xss This function is taken almost directly from Drupal 7's core code. It is used to help us filter out dangerous HTML which the user might type. From the D7 documentation:
filter_xss_attributes
filter_xss_bad_protocol
filter_xss_split Like the filter_xss function, this is taken from D7's _filter_xss_split function
fpm Uses fp_add_message, but in this case, it also adds in the filename and line number which the message came from!
fpmct Convenience function, will use fp_debug_ct() to display a message, and the number of miliseconds since its last call.
fp_add_body_class Add a CSS class to the body tag of the page. Useful for themeing later on.
fp_add_css Add an extra CSS file to the page with this function. Ex: fp_add_css(fp_get_module_path("admin") . '/css/admin.css');
fp_add_js Add extra javascript to the page.
fp_add_message Add a "message" to the top of the screen. Useful for short messages like "You have been logged out" or "Form submitted successfully."
fp_clear_cache Call all modules which implement hook_clear_cache
fp_debug_ct Shortcut to fp_debug_current_time_millis()
fp_debug_current_time_millis When called repeatedly, this function will display a message along with a milisecond count out to the side. Very useful for developers to time function calls or queries, to see how long they are taking.
fp_explode_assoc Takes a string (created by fp_join_assoc()) and re-creates the 1 dimensional assoc array.
fp_get_alert_count_by_type Returns back the total, read, and unread numbers previously calculated to see if we need to place a badge next to the bell icon at the top of the screen. If unset, we will call the recalculate function.
fp_get_degree_classifications Return back an assoc array of our set degree classifications, separated by "level"
fp_get_degree_classification_details Returns back an assoc array for the supplied code. Looks like: $arr["level_num"] = number $arr["title"] = the title
fp_get_departments Returns an array (suitable for form api) of departments on campus which faculty/staff can be members of.
fp_get_files_path Convenience function to return the /files system path. Does NOT end with a trailing slash.
fp_get_js_alert_link Creates a javascript "alert" link, which tells the user some message with javascript alert().
fp_get_js_confirm_link Creates a javascript "confirm" link, so when clicked it asks the user a question, then proceeds if they select OK. The main reason I want to do this is so I can pass the $question through my t() function. (do it when you call this function)
fp_get_js_prompt_link Creates a javascript "prompt" link, which will ask the user a question.
fp_get_machine_readable Simple function to convert a string into a machine-readable string.
fp_get_module_details Simply returns the module's row from the modules table, if it exists.
fp_get_module_path Return the filepath to the module
fp_get_random_string Returns a random string of length len.
fp_get_requirement_types Returns back an array of all the available requirement types (by code) that have been defined.
fp_get_session_id_from_str This will validate the session str (or the session_id.
fp_get_session_str This function will provide the session_id as a string, as well as a secret token we can use to make sure the session_id is authentic and came from us and not a hacker.
fp_get_terms_by_year_range Returns back a FAPI-compatible array of all term codes for the specified years, inclusive
fp_get_tmp_path Convenience function to return the temporary directory path. Does NOT end with a trailing slash. Ex: /tmp
fp_goto Redirect the user's browser to the specified internal path + query.
fp_html_print_r Similar to print_r, this will return an HTML-friendly click-to-open system similar in design to Krumo.
fp_http_build_query This function is borrowed from Backdrop version 1.x. We use it to convert the $data (which is in the form of an assoc array of keys and values) into an HTTP query string.
fp_http_request Send a request through the Internet and return the result as an object.
fp_join_assoc This function will create a string from a 1 dimensional assoc array. Ex: arr = array("pet" => "dog", "name" => "Rex") will return: pet_S-dog,name_S-Rex under the default settings.
fp_load_degree This function provides a pass-thru to $d = new DegreePlan(args). However, it allows for quick caching look-up, so it should be used when possible instead of $x = new DegreePlan.
fp_mail Send an email. Drop-in replacement for PHP's mail() command, but can use SMTP protocol if enabled.
fp_no_html_xss Remove any possiblilty of a malicious attacker trying to inject nonsense. From: https://paragonie.com/blog/2015/06/preventing-xss-vulnerabilities-in-php...
fp_number_pad Simple function to left padd numbers with 0's. 1 becomes 001 20 becomes 020 and so on.
fp_query_string_encode Adapted from https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_...
fp_recalculate_alert_count_by_type Invokes a hook to get numbers on the total, read, and unread values from our modules, to find out if we need to place a badge on the bell icon at the top of the screen.
fp_reduce_whitespace Simple helper function to reduce whitespace (like double-spaces)
fp_re_array_files Re-order the _FILES array for multiple files, to make it easier to work with. From: http://php.net/manual/en/features.file-upload.multiple.php
fp_screen_is_mobile This function will attempt to determine automatically if we are on a mobile device, and should therefor use the mobile theme and layout settings.
fp_set_breadcrumbs Set our breadcrumbs array.
fp_set_page_sub_tabs Allows the programmer to define subtabs at the top of the page.
fp_set_page_tabs If this function is called, it will override any other page tabs which might be getting constructed. This lets the programmer, at run-time, completely control what tabs are at the top of the page.
fp_set_standard_advising_breadcrumbs Convenience function to set the basic advising breadcrumbs
fp_set_title Allows the programmer to set the title of the page, overwriting any default title.
fp_space_csv Simple function that adds spaces after commas in CSV strings. Makes them easier to read.
fp_strip_dangerous_protocols
fp_str_ends_with
fp_token Returns back the site's "token", which is a simply md5 of some randomness. It is used primarily with forms, to ensure against cross-site forgeries. The site's token gets saved to the variables table, for later use. The idea is…
fp_translate_numeric_grade This function will use the "Numeric to Letter Grade" setting in the School settings to translate the given grade (if it is numeric) to a letter grade. Otherwise, it will return the grade as-is.
fp_trim This is a simple function which attempts to trim a String. However, if $val is NOT a string, it will cast it to string.
fp_truncate_decimals This simple function will take a number and truncate the number of decimals to the requested places. This can be used in place of number_format(), which *rounds* numbers.
fp_url This function will take a path, ex: "admin/config/module" and a query, ex: "nid=5&whatever=yes" And join them together, respecting whether or not clean URL's are enabled.
fp_url_absolute This convenience function returns the absolute URL to the path requested.
fp_url_get_contents This function uses CURL to get the simple contents of a URL, whether http or https.
fp_user_is_student Simply returns TRUE or FALSE if the user is a student. (has the is_student == 1
fp_utf8_decode
fp_utf8_encode
fp_validate_utf8
friendly_timezone Returns back the "friendly" timezone string if we have one.
get_modules_menus Similar to get_modules_permissions, this will scan through all installed modules' hook_menu() functions, and assemble an array which is sorted by "location" and then by "weight".
get_modules_permissions This function will read through all the modules' permissions and return back an array. Specifically, it retrieves arrays from each modules' hook_perm() function.
get_shorter_catalog_year_range This is used usually when being viewed by a mobile device. It will shorten a catalog year range of 2008-2009 to just "08-09" or "2008-09" or even "09-2009".
get_term_description Convert a term ID into a description. Ex: 20095 = Spring of 2009.
get_term_structures Return an array version of the term_id_structure field from the admin settings
get_timezones Returns an array of all timezones PHP recognizes. Inspired by code from: https://stackoverflow.com/questions/1727077/generating-a-drop-down-list-...
get_timezone_offset Returns the offset from the origin timezone to the remote timezone, in seconds, or false if there is an error
include_module This will find and include the module in question, calling it's hook_init() function if it has one.
include_module_install Find and include the module's .install file, if it exists. Returns TRUE or FALSE if it was able to find & include the file.
invoke_hook Invoke all module hooks for the supplied hook.
is_serialized_string From: https://stackoverflow.com/questions/1369936/check-to-see-if-a-string-is-...
l This works like Drupal's l() function for creating links. Ex: l("Click here for course search!", "tools/course-search", "abc=xyz&hello=goodbye", array("class" => "my-class")); Do not…
modules_implement_hook Return an array of enabled modules which implement the provided hook. Do not include the preceeding "_" on the hook name!
module_enabled Simple function that returns TRUE if the module is enabled, FALSE otherwise.
repair_html
st Provides translation functionality when database is not available.
t This function will facilitate translations by using hook_translate()
timer_read Works with the timer_start() function to return how long it has been since the start.
timer_start Begin a microtime timer for later use.
user_has_permission Returns TRUE or FALSE if the logged in user has access based on the permission supplied.
user_has_role Does the user have the specified role?
_fp_error_handler This is our custom error handler, which will intercept PHP warnings, notices, etc, and let us display them, log them, etc.
_fp_map_php_error_code Map an error code into an Error word *