misc.inc

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

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.
filter_markup Filter string with possible HTML, allowing only certain tags, and removing dangerous attributes.
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_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_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_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_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_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_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.
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.
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 *