3 * FusionForge miscellaneous utils
5 * Copyright 1999-2001, VA Linux Systems, Inc.
6 * Copyright 2009-2011, Roland Mas
7 * Copyright 2009-2011, Franck Villaume - Capgemini
8 * Copyright (c) 2010, 2011, 2012
9 * Thorsten Glaser <t.glaser@tarent.de>
10 * Copyright 2010-2012, Alain Peyrat - Alcatel-Lucent
11 * Copyright 2013, Franck Villaume - TrivialDev
13 * This file is part of FusionForge. FusionForge is free software;
14 * you can redistribute it and/or modify it under the terms of the
15 * GNU General Public License as published by the Free Software
16 * Foundation; either version 2 of the Licence, or (at your option)
19 * FusionForge is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License along
25 * with FusionForge; if not, write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
30 * htpasswd_apr1_md5 - generate htpasswd md5 format password
32 * @param string the plain string password
33 * @return string the apr1 string passwords
35 * From http://www.php.net/manual/en/function.crypt.php#73619
37 function htpasswd_apr1_md5($plainpasswd) {
38 $salt = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"), 0, 8);
39 $len = strlen($plainpasswd);
40 $text = $plainpasswd.'$apr1$'.$salt;
41 $bin = pack("H32", md5($plainpasswd.$salt.$plainpasswd));
43 for ($i = $len; $i > 0; $i -= 16) {
44 $text .= substr($bin, 0, min(16, $i));
46 for ($i = $len; $i > 0; $i >>= 1) {
47 $text .= ($i & 1)? chr(0) : $plainpasswd{0};
49 $bin = pack("H32", md5($text));
50 for ($i = 0; $i < 1000; $i++) {
51 $new = ($i & 1)? $plainpasswd : $bin;
52 if ($i % 3) $new .= $salt;
53 if ($i % 7) $new .= $plainpasswd;
54 $new .= ($i & 1)? $bin : $plainpasswd;
55 $bin = pack("H32", md5($new));
57 for ($i = 0; $i < 5; $i++) {
61 $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
63 $tmp = chr(0).chr(0).$bin[11].$tmp;
64 $tmp = strtr(strrev(substr(base64_encode($tmp), 2)),
65 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
66 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
67 return "$"."apr1"."$".$salt."$".$tmp;
71 * is_utf8 - utf-8 detection
73 * @param string the string to analyze
75 * From http://www.php.net/manual/en/function.mb-detect-encoding.php#85294
77 function is_utf8($str) {
81 for($i=0; $i<$len; $i++){
84 if(($c >= 254)) return false;
85 elseif($c >= 252) $bits=6;
86 elseif($c >= 248) $bits=5;
87 elseif($c >= 240) $bits=4;
88 elseif($c >= 224) $bits=3;
89 elseif($c >= 192) $bits=2;
91 if(($i+$bits) > $len) return false;
95 if($b < 128 || $b > 191) return false;
104 * util_strip_unprintable - ???
109 function util_strip_unprintable(&$data) {
110 if (is_array($data)) {
111 foreach ($data as $key => &$value) {
112 util_strip_unprintable($value);
115 $data = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $data);
121 * removeCRLF - remove any Carriage Return-Line Feed from a string.
122 * That function is useful to remove the possibility of a CRLF Injection when sending mail
123 * All the data that we will send should be passed through that function
125 * @param string $str The string that we want to empty from any CRLF
128 function util_remove_CRLF($str) {
129 return strtr($str, "\015\012", ' ');
133 * util_check_fileupload - determines if a filename is appropriate for upload
135 * @param array $filename The uploaded file as returned by getUploadedFile()
138 function util_check_fileupload($filename) {
140 /* Empty file is a valid file.
141 This is because this function should be called
142 unconditionally at the top of submit action processing
143 and many forms have optional file upload. */
144 if ($filename == 'none' || $filename == '') {
148 /* This should be enough... */
149 if (!is_uploaded_file($filename)) {
152 /* ... but we'd rather be paranoid */
153 if (strstr($filename, '..')) {
156 if (!is_file($filename)) {
159 if (!file_exists($filename)) {
162 if ((dirname($filename) != '/tmp') &&
163 (dirname($filename) != "/var/tmp")) {
170 * util_check_url - determines if given URL is valid.
172 * Currently, test is very basic, only the protocol is
173 * checked, allowed values are: http, https, ftp.
175 * @param string $url The URL
176 * @return bool true if valid, false if not valid.
178 function util_check_url($url) {
179 return (preg_match('/^(http|https|ftp):\/\//', $url) > 0);
183 * util_send_message - Send email
184 * This function should be used in place of the PHP mail() function
186 * @param string $to The email recipients address
187 * @param string $subject The email subject
188 * @param string $body The body of the email message
189 * @param string $from The optional email sender address. Defaults to 'noreply@'
190 * @param string $BCC The addresses to blind-carbon-copy this message (comma-separated)
191 * @param string $sendername The optional email sender name. Defaults to ''
192 * @param bool|string $extra_headers
193 * @param bool $send_html_email Whether to send plain text or html email
194 * @param string $CC The addresses to carbon-copy this message (comma-separated)
196 function util_send_message($to, $subject, $body, $from = '', $BCC = '', $sendername = '', $extra_headers = '',
197 $send_html_email = false, $CC = '') {
199 $to = 'noreply@'.forge_get_config('web_host');
202 $from = 'noreply@'.forge_get_config('web_host');
205 $charset = _('UTF-8');
211 if ($extra_headers) {
212 $body2 .= $extra_headers."\n";
215 "\nFrom: ".util_encode_mailaddr($from, $sendername, $charset);
216 if (forge_get_config('bcc_all_emails') != '') {
217 $BCC .= ",".forge_get_config('bcc_all_emails');
220 $body2 .= "\nBCC: $BCC";
223 $body2 .= "\nCC: $CC";
225 $send_html_email? $type = "html" : $type = "plain";
226 $body2 .= "\n".util_encode_mimeheader("Subject", $subject, $charset).
227 "\nContent-type: text/$type; charset=$charset".
229 util_convert_body($body, $charset);
231 if (!forge_get_config('sendmail_path')){
232 $sys_sendmail_path="/usr/sbin/sendmail";
235 $handle = popen(forge_get_config('sendmail_path')." -f'$from' -t -i", 'w');
236 fwrite($handle, $body2);
241 * util_encode_mailaddr - Encode email address to MIME format
243 * @param string $email The email address
244 * @param string $name The email's owner name
245 * @param string $charset The converting charset
248 function util_encode_mailaddr($email, $name, $charset) {
249 if (function_exists('mb_convert_encoding') && trim($name) != "") {
250 $name = "=?".$charset."?B?".
251 base64_encode(mb_convert_encoding(
252 $name, $charset, "UTF-8")).
256 return $name." <".$email.">";
260 * util_encode_mimeheader - Encode mimeheader
262 * @param string $headername The name of the header (e.g. "Subject")
263 * @param string $str The email subject
264 * @param string $charset The converting charset (like ISO-2022-JP)
265 * @return string The MIME encoded subject
268 function util_encode_mimeheader($headername, $str, $charset) {
269 if (function_exists('mb_internal_encoding') &&
270 function_exists('mb_encode_mimeheader')) {
271 $x = mb_internal_encoding();
272 mb_internal_encoding("UTF-8");
273 $y = mb_encode_mimeheader($headername.": ".$str,
275 mb_internal_encoding($x);
279 if (!function_exists('mb_convert_encoding')) {
280 return $headername.": ".$str;
283 return $headername.": "."=?".$charset."?B?".
284 base64_encode(mb_convert_encoding(
285 $str, $charset, "UTF-8")).
290 * util_convert_body - Convert body of the email message
292 * @param string $str The body of the email message
293 * @param string $charset The charset of the email message
294 * @return string The converted body of the email message
297 function util_convert_body($str, $charset) {
298 if (!function_exists('mb_convert_encoding') || $charset == 'UTF-8') {
302 return mb_convert_encoding($str, $charset, "UTF-8");
306 * util_handle_message - a convenience wrapper which sends messages
307 * to an email account
309 * @param array $id_arr array of user_id's from the user table
310 * @param string $subject subject of the message
311 * @param string $body the message body
312 * @param string $extra_emails a comma-separated list of email address
313 * @param string $dummy1 ignored (no longer used)
314 * @param string $from From header
316 function util_handle_message($id_arr, $subject, $body, $extra_emails = '', $dummy1 = '', $from = '') {
319 if (count($id_arr) < 1) {
322 $res = db_query_params('SELECT user_id,email FROM users WHERE user_id = ANY ($1)',
323 array(db_int_array_to_any_clause($id_arr)));
324 $rows = db_numrows($res);
326 for ($i = 0; $i < $rows; $i++) {
327 if (db_result($res, $i, 'user_id') == 100) {
328 // Do not send messages to "Nobody"
331 $address['email'][] = db_result($res,$i,'email');
333 if (isset ($address['email']) && count($address['email']) > 0) {
334 $extra_emails = implode($address['email'], ',').','.$extra_emails;
338 util_send_message('', $subject, $body, $from, $extra_emails);
343 * util_unconvert_htmlspecialchars - Unconverts a string converted with htmlspecialchars()
345 * @param string $string The string to unconvert
346 * @return string The unconverted string
349 function util_unconvert_htmlspecialchars($string) {
350 return html_entity_decode($string, ENT_QUOTES, "UTF-8");
354 * util_result_columns_to_assoc - Takes a result set and turns the column pair into an associative array
356 * @param string $result The result set ID
357 * @param int $col_key The column key
358 * @param int $col_val The optional column value
359 * @return array An associative array
362 function util_result_columns_to_assoc($result, $col_key = 0, $col_val = 1) {
363 $rows = db_numrows($result);
367 for ($i = 0; $i < $rows; $i++) {
368 $arr[db_result($result, $i, $col_key)] = db_result($result, $i, $col_val);
377 * util_result_column_to_array - Takes a result set and turns the optional column into an array
379 * @param int $result The result set ID
380 * @param int $col The column
384 function &util_result_column_to_array($result, $col = 0) {
386 Takes a result set and turns the optional column into
389 $rows = db_numrows($result);
393 for ($i = 0; $i < $rows; $i++) {
394 $arr[$i] = db_result($result, $i, $col);
403 * util_line_wrap - Automatically linewrap text
405 * @param string $text The text to wrap
406 * @param int $wrap The number of characters to wrap - Default is 80
407 * @param string $break The line break to use - Default is '\n'
408 * @return string The wrapped text
411 function util_line_wrap($text, $wrap = 80, $break = "\n") {
412 return wordwrap($text, $wrap, $break, false);
416 * util_make_links - Turn URL's into HREF's.
418 * @param string $data The URL
419 * @return mixed|string The HREF'ed URL
422 function util_make_links($data = '') {
427 for ($i = 0; $i < 5; $i++) {
428 $randPattern = rand(10000, 30000);
429 if (!preg_match("/$randPattern/", $data)) {
436 while(preg_match('/<a [^>]*>[^<]*<\/a>/i', $data, $part)) {
438 $data = preg_replace('/<a [^>]*>[^<]*<\/a>/i', $randPattern, $data, 1);
442 while (preg_match('/<a [^>]*>.*<\/a>/siU', $data, $part)) {
444 $data = preg_replace('/<a [^>]*>.*<\/a>/siU', $randPattern, $data, 1);
446 while (preg_match('/<img [^>]*\/>/siU', $data, $part)) {
448 $data = preg_replace('/<img [^>]*\/>/siU', $randPattern, $data, 1);
450 $data = str_replace('>', "\1", $data);
451 $data = preg_replace("#([ \t]|^)www\.#i", " http://www.", $data);
452 $data = preg_replace("#([[:alnum:]]+)://([^[:space:]<\1]*)([[:alnum:]\#?/&=])#i", "<a href=\"\\1://\\2\\3\" target=\"_new\">\\1://\\2\\3</a>", $data);
453 $data = preg_replace("#([[:space:]]|^)(([a-z0-9_]|\\-|\\.)+@([^[:space:]<\1]*)([[:alnum:]-]))#i", "\\1<a href=\"mailto:\\2\" target=\"_new\">\\2</a>", $data);
454 $data = str_replace("\1", '>', $data);
455 for ($i = 0; $i < count($mem); $i++) {
456 $data = preg_replace("/$randPattern/", $mem[$i], $data, 1);
461 $lines = split("\n", $data);
463 while (list ($key, $line) = each($lines)) {
464 // Do not scan lines if they already have hyperlinks.
465 // Avoid problem with text written with an WYSIWYG HTML editor.
466 if (eregi('<a ([^>]*)>.*</a>', $line, $linePart)) {
467 if (eregi('href="[^"]*"', $linePart[1])) {
473 // Skip </img> tag also
474 if (eregi('<img ([^>]*)/>', $line, $linePart)) {
475 if (eregi('href="[^"]*"', $linePart[1])) {
481 // When we come here, we usually have form input
482 // encoded in entities. Our aim is to NOT include
483 // angle brackets in the URL
484 // (RFC2396; http://www.w3.org/Addressing/URL/5.1_Wrappers.html)
485 $line = str_replace('>', "\1", $line);
486 $line = preg_replace("/([ \t]|^)www\./i", " http://www.", $line);
487 $line = preg_replace("/([[:alnum:]]+):\/\/([^[:space:]<\1]*)([[:alnum:]#?\/&=])/i",
488 "<a href=\"\\1://\\2\\3\" target=\"_new\">\\1://\\2\\3</a>", $line);
489 $line = preg_replace(
490 "/([[:space:]]|^)(([a-z0-9_]|\\-|\\.)+@([^[:space:]]*)([[:alnum:]-]))/i",
491 "\\1<a href=\"mailto:\\2\" target=\"_new\">\\2</a>",
494 $line = str_replace("\1", '>', $line);
501 * show_priority_colors_key - Show the priority colors legend
503 * @return string html code
506 function show_priority_colors_key() {
507 echo '<p><strong> '._('Priority Colors')._(':').'</strong>';
508 for ($i = 1; $i < 6; $i++) {
509 echo ' <span class="priority'.$i.'">'.$i.'</span>';
515 * utils_buildcheckboxarray - Build a checkbox array
517 * @param int $options Number of options to be in the array
518 * @param string $name The name of the checkboxes
519 * @param array $checked_array An array of boxes to be pre-checked
522 function utils_buildcheckboxarray($options, $name, $checked_array) {
523 $option_count = count($options);
524 $checked_count = count($checked_array);
526 for ($i = 1; $i <= $option_count; $i++) {
528 <br /><input type="checkbox" name="'.$name.'" value="'.$i.'"';
529 for ($j = 0; $j < $checked_count; $j++) {
530 if ($i == $checked_array[$j]) {
534 echo ' /> '.$options[$i];
539 * utils_requiredField - Adds the required field marker
541 * @return string A string holding the HTML to mark a required field
543 function utils_requiredField() {
544 return '<span class="requiredfield">*</span>';
548 * GraphResult - Takes a database result set and builds a graph.
549 * The first column should be the name, and the second column should be the values
550 * Be sure to include HTL_Graphs.php before using this function
552 * @author Tim Perdue tperdue@valinux.com
553 * @param int $result The databse result set ID
554 * @param string $title The title of the graph
557 function GraphResult($result, $title) {
558 $rows = db_numrows($result);
560 if ((!$result) || ($rows < 1)) {
566 for ($j = 0; $j < db_numrows($result); $j++) {
567 if (db_result($result, $j, 0) != '' && db_result($result, $j, 1) != '') {
568 $names[$j] = db_result($result, $j, 0);
569 $values[$j] = db_result($result, $j, 1);
574 This is another function detailed below
576 GraphIt($names, $values, $title);
581 * GraphIt() - Build a graph
583 * @author Tim Perdue tperdue@valinux.com
584 * @param array $name_string An array of names
585 * @param array $value_string An array of values
586 * @param string $title The title of the graph
587 * @return string html code
590 function GraphIt($name_string, $value_string, $title) {
593 $counter = count($name_string);
596 Can choose any color you wish
600 for ($i = 0; $i < $counter; $i++) {
601 $bars[$i] = $HTML->COLOR_LTBACK1;
604 $counter = count($value_string);
607 Figure the max_value passed in, so scale can be determined
612 for ($i = 0; $i < $counter; $i++) {
613 if ($value_string[$i] > $max_value) {
614 $max_value = $value_string[$i];
618 if ($max_value < 1) {
623 I want my graphs all to be 800 pixels wide, so that is my divisor
626 $scale = (400/$max_value);
629 I create a wrapper table around the graph that holds the title
632 $title_arr = array();
633 $title_arr[] = $title;
635 echo $GLOBALS['HTML']->listTableTop($title_arr);
638 Create an associate array to pass in. I leave most of it blank
646 'cellspacing' => '0',
664 'doublefcolor'=> '');
667 This is the actual call to the HTML_Graphs class
670 html_graph($name_string, $value_string, $bars, $vals);
674 <!-- end outer graph table -->';
675 echo $GLOBALS['HTML']->listTableBottom();
679 * ShowResultSet - Show a generic result set
680 * Very simple, plain way to show a generic result set
682 * @param int $result The result set ID
683 * @param string $title The title of the result set
684 * @param bool $linkify The option to turn URL's into links
685 * @param bool $displayHeaders The option to display headers
686 * @param array $headerMapping The db field name -> label mapping
687 * @param array $excludedCols Don't display these cols
689 function ShowResultSet($result, $title = '', $linkify = false, $displayHeaders = true, $headerMapping = array(), $excludedCols = array()) {
690 global $group_id, $HTML;
693 $rows = db_numrows($result);
694 $cols = db_numfields($result);
696 echo '<table class="fullwidth">'."\n";
698 /* Create the headers */
699 $headersCellData = array();
700 $colsToKeep = array();
701 for ($i = 0; $i < $cols; $i++) {
702 $fieldName = db_fieldname($result, $i);
703 if (in_array($fieldName, $excludedCols)) {
707 if (isset($headerMapping[$fieldName])) {
708 if (is_array($headerMapping[$fieldName])) {
709 $headersCellData[] = $headerMapping[$fieldName];
711 $headersCellData[] = array($headerMapping[$fieldName]);
714 $headersCellData[] = array($fieldName);
718 /* Create the title */
719 if (strlen($title) > 0) {
720 $titleCellData = array();
721 $titleCellData[] = array($title, 'colspan="'.count($headersCellData).'"');
722 echo $HTML->multiTableRow('', $titleCellData, TRUE);
725 /* Display the headers */
726 if ($displayHeaders) {
727 echo $HTML->multiTableRow('', $headersCellData, TRUE);
730 /* Create the rows */
731 for ($j = 0; $j < $rows; $j++) {
732 echo '<tr '.$HTML->boxGetAltRowStyle($j).'>';
733 for ($i = 0; $i < $cols; $i++) {
734 if (in_array($i, $colsToKeep)) {
735 if ($linkify && $i == 0) {
736 $link = '<a href="'.getStringFromServer('PHP_SELF').'?';
738 if ($linkify == "bug_cat") {
739 $link .= 'group_id='.$group_id.'&bug_cat_mod=y&bug_cat_id='.db_result($result, $j, 'bug_category_id').'">';
740 } elseif ($linkify == "bug_group") {
741 $link .= 'group_id='.$group_id.'&bug_group_mod=y&bug_group_id='.db_result($result, $j, 'bug_group_id').'">';
742 } elseif ($linkify == "patch_cat") {
743 $link .= 'group_id='.$group_id.'&patch_cat_mod=y&patch_cat_id='.db_result($result, $j, 'patch_category_id').'">';
744 } elseif ($linkify == "support_cat") {
745 $link .= 'group_id='.$group_id.'&support_cat_mod=y&support_cat_id='.db_result($result, $j, 'support_category_id').'">';
746 } elseif ($linkify == "pm_project") {
747 $link .= 'group_id='.$group_id.'&project_cat_mod=y&project_cat_id='.db_result($result, $j, 'group_project_id').'">';
749 $link = $linkend = '';
752 $link = $linkend = '';
754 echo '<td>'.$link.db_result($result, $j, $i).$linkend.'</td>';
766 * validate_email - Validate an email address
768 * @param string $address The address string to validate
769 * @return bool true on success/false on error
772 function validate_email($address) {
773 if (version_compare(PHP_VERSION, '5.2.0', '>=')) {
774 if (filter_var($address, FILTER_VALIDATE_EMAIL)) {
780 if (preg_match("/^[-!#$%&\'*+\\.\/0-9=?A-Z^_`a-z{|}~]+@[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.[-!#$%&\'*+\\.\/0-9=?A-Z^_`a-z{|}~]+$/", $address)) {
789 * validate_emails - Validate a list of e-mail addresses
791 * @param string $addresses E-mail list
792 * @param string $separator Separator
793 * @return array Array of invalid e-mail addresses (if empty, all addresses are OK)
795 function validate_emails($addresses, $separator = ',') {
796 if (strlen($addresses) == 0) return array();
798 $emails = explode($separator, $addresses);
801 if (is_array($emails)) {
802 foreach ($emails as $email) {
803 $email = trim($email); // This is done so we can validate lists like "a@b.com, c@d.com"
804 if (!validate_email($email)) $ret[] = $email;
811 * util_is_valid_filename - Verifies whether a file has a valid filename
813 * @param string $file The file to verify
814 * @return bool true on success/false on error
817 function util_is_valid_filename($file) {
819 $invalidchars = preg_replace("/[-A-Z0-9+_\. ~]/i", "", $file);
821 if (!empty($invalidchars)) {
824 if (strstr($file, '..')) {
833 * util_is_valid_repository_name - Verifies whether a repository name is valid
835 * @param string $file name to verify
836 * @return bool true on success/false on error
839 function util_is_valid_repository_name ($file) {
841 $invalidchars = preg_replace("/[-A-Z0-9+_\.]/i","",$file);
843 if (!empty($invalidchars)) {
846 if (strstr($file,'..')) {
853 * valid_hostname - Validates a hostname string to make sure it doesn't contain invalid characters
855 * @param string $hostname The optional hostname string
856 * @return bool true on success/false on failure
859 function valid_hostname($hostname = "xyz") {
862 $invalidchars = preg_replace("/[-A-Z0-9\.]/i", "", $hostname);
864 if (!empty($invalidchars)) {
868 //double dot, starts with a . or -
869 if (preg_match("/\.\./", $hostname) || preg_match("/^\./", $hostname) || preg_match("/^\-/", $hostname)) {
873 $multipoint = explode(".", $hostname);
875 if (!(is_array($multipoint)) || ((count($multipoint) - 1) < 1)) {
885 * human_readable_bytes - Translates an integer representing bytes to a human-readable format.
887 * Format file size in a human-readable way
888 * such as "xx Megabytes" or "xx Mo"
890 * @author Andrea Paleni <andreaSPAMLESS_AT_SPAMLESScriticalbit.com>
893 * @param int $bytes is the size
894 * @param bool $base10 enable base 10 representation, otherwise default base 2 is used
895 * @param int $round number of fractional digits
896 * @param array $labels strings associated to each 2^10 or 10^3(base10==true) multiple of base units
899 function human_readable_bytes($bytes, $base10 = false, $round = 0, $labels = array()) {
904 return "-".human_readable_bytes(-$bytes, $base10, $round);
907 $labels = array(_('bytes'), _('kB'), _('MB'), _('GB'), _('TB'));
911 $labels = array(_('bytes'), _('KiB'), _('MiB'), _('GiB'), _('TiB'));
915 $log = (int)(log10($bytes)/log10($base));
917 foreach ($labels as $p => $lab) {
922 if ($lab != _("bytes") and $lab != _("kB") and $lab != _("KiB")) {
925 $text = round($bytes/pow($base, $pow), $round)." ".$lab;
932 * ls - lists a specified directory and returns an array of files
933 * @param string $dir the path of the directory to list
934 * @param bool $filter whether to filter out directories and illegal filenames
935 * @return array array of file names.
937 function &ls($dir, $filter = false) {
940 if (is_dir($dir) && ($h = opendir($dir))) {
941 while (($f = readdir($h)) !== false) {
945 if (!util_is_valid_filename($f) ||
946 !is_file($dir."/".$f)
958 * readfile_chunked - replacement for readfile
960 * @param string $filename The file path
961 * @param bool $returnBytes Whether to return bytes served or just a bool
964 function readfile_chunked($filename, $returnBytes = true) {
965 $chunksize = 1*(1024*1024); // 1MB chunks
969 $handle = fopen($filename, 'rb');
970 if ($handle === false) {
975 while (!feof($handle)) {
976 $buffer = fread($handle, $chunksize);
981 $byteCounter += strlen($buffer);
985 $status = fclose($handle);
986 if ($returnBytes && $status) {
987 return $byteCounter; // return num. bytes delivered like readfile() does.
993 * util_is_root_dir - Checks if a directory points to the root dir
995 * @param string $dir Directory
998 function util_is_root_dir($dir) {
999 return !preg_match('/[^\\/]/', $dir);
1003 * util_is_dot_or_dotdot - Checks if a directory points to . or ..
1005 * @param string $dir Directory
1008 function util_is_dot_or_dotdot($dir) {
1009 return preg_match('/^\.\.?$/', trim($dir, '/'));
1013 * util_containts_dot_or_dotdot - Checks if a directory containts . or ..
1015 * @param string $dir Directory
1018 function util_containts_dot_or_dotdot($dir) {
1019 foreach (explode('/', $dir) as $sub_dir) {
1020 if (util_is_dot_or_dotdot($sub_dir))
1028 * util_secure_filename - Returns a secured file name
1030 * @param string $file Filename
1031 * @return string Filename
1033 function util_secure_filename($file) {
1034 $f = preg_replace("/[^-A-Z0-9_\.]/i", '', $file);
1035 if (util_containts_dot_or_dotdot($f))
1036 $f = preg_replace("/\./", '_', $f);
1043 * util_strip_accents - Remove accents from given text.
1045 * @param string $text Text
1048 function util_strip_accents($text) {
1049 $find = utf8_decode($text);
1050 $find = strtr($find,
1051 utf8_decode('àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ'),
1052 'aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY');
1053 return utf8_encode($find);
1057 * normalized_urlprefix - Constructs the forge's URL prefix out of forge_get_config('url_prefix')
1061 function normalized_urlprefix() {
1062 $prefix = forge_get_config('url_prefix');
1063 $prefix = preg_replace("/^\//", "", $prefix);
1064 $prefix = preg_replace("/\/$/", "", $prefix);
1065 $prefix = "/$prefix/";
1066 if ($prefix == '//')
1072 * util_url_prefix - Return URL prefix (http:// or https://)
1074 * @param string $prefix (optional) : 'http' or 'https' to force it
1075 * @return string URL prefix
1077 function util_url_prefix($prefix = '') {
1078 if ($prefix == 'http' || $prefix == 'https' ) {
1079 return $prefix . '://';
1082 if (forge_get_config('use_ssl')) {
1091 * util_make_base_url - Construct the base URL http[s]://forge_name[:port]
1093 * @param string $prefix (optional) : 'http' or 'https' to force it
1094 * @return string base URL
1096 function util_make_base_url($prefix = '') {
1097 $url = util_url_prefix($prefix);
1098 $url .= forge_get_config('web_host');
1099 if (forge_get_config('https_port') && (forge_get_config('https_port') != 443)) {
1100 $url .= ":".forge_get_config('https_port');
1106 * util_make_url - Construct full URL from a relative path
1108 * @param string $path (optional)
1109 * @param string $prefix (optional) : 'http' or 'https' to force it
1110 * @return string URL
1112 function util_make_url($path = '', $prefix = '') {
1113 $url = util_make_base_url($prefix).util_make_uri($path) ;
1118 * util_find_relative_referer - Find the relative URL from full URL, removing http[s]://forge_name[:port]
1120 * @param string $url URL
1123 function util_find_relative_referer($url) {
1124 $relative_url = str_replace(util_make_base_url(), '', $url);
1125 //now remove previous feedback, error_msg or warning_msg
1126 $relative_url = preg_replace('/&error_msg=.*&/', '&', $relative_url);
1127 $relative_url = preg_replace('/&warning_msg=.*&/', '&', $relative_url);
1128 $relative_url = preg_replace('/&feedback=.*&/', '&', $relative_url);
1129 $relative_url = preg_replace('/&error_msg=.*/', '', $relative_url);
1130 $relative_url = preg_replace('/&warning_msg=.*/', '', $relative_url);
1131 $relative_url = preg_replace('/&feedback=.*/', '', $relative_url);
1132 return $relative_url;
1136 * util_make_uri - Construct proper (relative) URI (prepending prefix)
1138 * @param string $path
1139 * @return string URI
1141 function util_make_uri($path) {
1142 $path = preg_replace('/^\//', '', $path);
1143 $uri = normalized_urlprefix();
1149 * util_make_link - Construct proper (relative) URI from path & text
1151 * @param string $path
1152 * @param string $text
1153 * @param array|bool $extra_params
1154 * @param bool $absolute
1155 * @return string URI
1157 function util_make_link($path, $text, $extra_params = false, $absolute = false) {
1158 global $use_tooltips;
1160 if (is_array($extra_params)) {
1161 foreach ($extra_params as $key => $value) {
1162 if ($key != 'title') {
1163 $ep .= "$key=\"$value\" ";
1165 if ($key == 'title' && $use_tooltips) {
1166 $ep .= "$key=\"$value\" ";
1171 return '<a '.$ep.'href="'.$path.'">'.$text.'</a>';
1173 return '<a '.$ep.'href="'.util_make_uri($path).'">'.$text.'</a>';
1178 * util_make_link_u - Create an HTML link to a user's profile page
1180 * @param string $username
1181 * @param int $user_id
1182 * @param string $text
1185 function util_make_link_u($username, $user_id, $text) {
1186 return '<a href="'.util_make_url_u($username, $user_id).'">'.$text.'</a>';
1190 * util_display_user - Display username with link to a user's profile page
1191 * and icon face if possible.
1193 * @param string $username
1194 * @param int $user_id
1195 * @param string $text
1196 * @param string $size
1199 function util_display_user($username, $user_id, $text, $size = 'xs') {
1200 // Invoke user_link_with_tooltip plugin
1201 $hook_params = array('resource_type' => 'user', 'username' => $username, 'user_id' => $user_id, 'size' => $size, 'user_link' => '');
1202 plugin_hook_by_reference('user_link_with_tooltip', $hook_params);
1203 if ($hook_params['user_link'] != '') {
1204 return $hook_params['user_link'];
1207 // If no plugin replaced it, then back to default standard link
1209 // Invoke user_logo plugin (see gravatar plugin for instance)
1210 $params = array('user_id' => $user_id, 'size' => $size, 'content' => '');
1211 plugin_hook_by_reference('user_logo', $params);
1213 $url = '<a href="'.util_make_url_u($username, $user_id).'">'.$text.'</a>';
1214 if ($params['content']) {
1215 return $params['content'].$url.'<div class="new_line"></div>';
1221 * util_make_url_u - Create URL for user's profile page
1223 * @param string $username
1224 * @param int $user_id
1225 * @return string URL
1227 function util_make_url_u($username, $user_id) {
1228 if (isset ($GLOBALS['sys_noforcetype']) && $GLOBALS['sys_noforcetype']) {
1229 return util_make_url("/developer/?user_id=$user_id");
1231 return util_make_url("/users/$username/");
1236 * util_make_link_g - Create a HTML link to a project's page
1238 * @param string $group_name
1239 * @param int $group_id
1240 * @param string $text
1243 function util_make_link_g($group_name, $group_id, $text) {
1244 $hook_params = array();
1245 $hook_params['resource_type'] = 'group';
1246 $hook_params['group_name'] = $group_name;
1247 $hook_params['group_id'] = $group_id;
1248 $hook_params['link_text'] = $text;
1249 $hook_params['group_link'] = '';
1250 plugin_hook_by_reference('project_link_with_tooltip', $hook_params);
1251 if ($hook_params['group_link'] != '') {
1252 return $hook_params['group_link'];
1255 return '<a href="'.util_make_url_g($group_name, $group_id).'">'.$text.'</a>';
1259 * util_make_url_g - Create URL for a project's page
1261 * @param string $group_name
1262 * @param int $group_id
1265 function util_make_url_g($group_name, $group_id) {
1266 if (isset ($GLOBALS['sys_noforcetype']) && $GLOBALS['sys_noforcetype']) {
1267 return util_make_url("/project/?group_id=$group_id");
1269 return util_make_url("/projects/$group_name/");
1273 function util_ensure_value_in_set($value, $set) {
1274 if (in_array($value, $set)) {
1282 * check_email_available - ???
1284 * @param Group $group
1285 * @param string $email
1286 * @param string $response
1289 function check_email_available($group, $email, &$response) {
1290 // Check if a mailing list with same name already exists
1291 if ($group->usesMail()) {
1292 $mlFactory = new MailingListFactory($group);
1293 if (!$mlFactory || !is_object($mlFactory) || $mlFactory->isError()) {
1294 $response .= $mlFactory->getErrorMessage();
1297 $mlArray = $mlFactory->getMailingLists();
1298 if ($mlFactory->isError()) {
1299 $response .= $mlFactory->getErrorMessage();
1302 for ($j = 0; $j < count($mlArray); $j++) {
1303 $currentList =& $mlArray[$j];
1304 if ($email == $currentList->getName()) {
1305 $response .= _('Error: a mailing list with the same email address already exists.');
1311 // Check if a forum with same name already exists
1312 if ($group->usesForum()) {
1313 $ff = new ForumFactory($group);
1314 if (!$ff || !is_object($ff) || $ff->isError()) {
1315 $response .= $ff->getErrorMessage();
1318 $farr = $ff->getForums();
1319 $prefix = $group->getUnixName().'-';
1320 for ($j = 0; $j < count($farr); $j++) {
1321 if (is_object($farr[$j])) {
1322 if ($email == $prefix.$farr[$j]->getName()) {
1323 $response .= _('Error: a forum with the same email address already exists.');
1330 // Email is available
1335 * Adds the Javascript file to the list to be used
1338 function use_javascript($js) {
1339 return $GLOBALS['HTML']->addJavascript($js);
1342 function use_stylesheet($css, $media = '') {
1343 return $GLOBALS['HTML']->addStylesheet($css, $media);
1346 // array_replace_recursive only appeared in PHP 5.3.0
1347 if (!function_exists('array_replace_recursive')) {
1349 * Replaces elements from passed arrays into the first array recursively
1350 * @param array $a1 The array in which elements are replaced.
1351 * @param array $a2 The array from which elements will be extracted.
1352 * @return array Returns an array, or NULL if an error occurs.
1354 function array_replace_recursive($a1, $a2) {
1357 if (!is_array($a2)) {
1361 foreach ($a2 as $k => $v) {
1362 if (!is_array($v) ||
1363 !isset($result[$k]) || !is_array($result[$k])) {
1367 $result[$k] = array_replace_recursive($result[$k], $v);
1374 // json_encode only appeared in PHP 5.2.0
1375 if (!function_exists('json_encode')) {
1376 require_once $gfcommon.'include/minijson.php';
1377 function json_encode($a1) {
1378 return minijson_encode($a1);
1382 /* returns an integer from http://forge/foo/bar.php/123 or false */
1383 function util_path_info_last_numeric_component() {
1384 if (!isset($_SERVER['PATH_INFO']))
1388 foreach (str_split($_SERVER['PATH_INFO']) as $x) {
1392 } elseif ($ok == false) {
1393 ; /* need reset using slash */
1394 } elseif ((ord($x) >= 48) && (ord($x) <= 57)) {
1395 $rv = $rv * 10 + ord($x) - 48;
1405 function get_cvs_binary_version() {
1406 $string = `cvs --version 2>/dev/null | grep ^Concurrent.Versions.System.*client/server`;
1407 if (preg_match('/^Concurrent Versions System .CVS. 1.11.[0-9]*/', $string)) {
1409 } elseif (preg_match('/^Concurrent Versions System .CVS. 1.12.[0-9]*/', $string)) {
1416 /* get a backtrace as string */
1417 function debug_string_backtrace() {
1419 debug_print_backtrace();
1420 $trace = ob_get_contents();
1423 // Remove first item from backtrace as it's this function
1424 // which is redundant.
1425 $trace = preg_replace('/^#0\s+'.__FUNCTION__."[^\n]*\n/", '',
1428 // Renumber backtrace items.
1429 $trace = preg_replace('/^#(\d+)/me', '\'#\' . ($1 - 1)', $trace);
1434 function util_ini_get_bytes($id) {
1435 $val = trim(ini_get($id));
1436 $last = strtolower($val[strlen($val)-1]);
1448 function util_get_maxuploadfilesize() {
1449 $postmax = util_ini_get_bytes('post_max_size');
1450 $maxfile = util_ini_get_bytes('upload_max_filesize');
1452 return min($postmax, $maxfile);
1455 function util_get_compressed_file_extension() {
1456 $m = forge_get_config('compression_method');
1457 if (preg_match('/^gzip\b/', $m)) {
1459 } elseif (preg_match('/^bzip2\b/', $m)) {
1461 } elseif (preg_match('/^lzma\b/', $m)) {
1463 } elseif (preg_match('/^xz\b/', $m)) {
1465 } elseif (preg_match('/^cat\b/', $m)) {
1468 return '.compressed';
1472 /* return $1 if $1 is set, ${2:-false} otherwise */
1473 function util_ifsetor(&$val, $default = false) {
1474 return (isset($val) ? $val : $default);
1477 function util_randbytes($num = 6) {
1480 // Let's try /dev/urandom first
1481 $f = @fopen("/dev/urandom", "rb");
1483 $b .= @fread($f, $num);
1487 // Hm. No /dev/urandom? Try /dev/random.
1488 if (strlen($b) < $num) {
1489 $f = @fopen("/dev/random", "rb");
1491 $b .= @fread($f, $num);
1496 // Still no luck? Fall back to PHP's built-in PRNG
1497 while (strlen($b) < $num) {
1498 $b .= uniqid(mt_rand(), true);
1501 $b = substr($b, 0, $num);
1505 /* maximum: 2^31 - 1 due to PHP weakness */
1506 function util_randnum($min = 0, $max = 32767) {
1507 $ta = unpack("L", util_randbytes(4));
1508 $n = $ta[1] & 0x7FFFFFFF;
1509 $v = $n % (1 + $max - $min);
1513 // sys_get_temp_dir() is only available for PHP >= 5.2.1
1514 if (!function_exists('sys_get_temp_dir')) {
1515 function sys_get_temp_dir() {
1516 if ($temp = getenv('TMP')) return $temp;
1517 if ($temp = getenv('TEMP')) return $temp;
1518 if ($temp = getenv('TMPDIR')) return $temp;
1523 /* convert '\n' to <br /> or </p><p> */
1524 function util_pwrap($encoded_string) {
1525 return str_replace("<p></p>", "",
1526 str_replace("<br /></p>", "</p>",
1527 str_replace("<p><br />", "<p>",
1528 "<p>".str_replace("<br /><br />", "</p><p>",
1529 implode("<br />", explode("\n",
1530 $encoded_string)))."</p>")));
1533 /* takes a string and returns it HTML encoded, URIs made to hrefs */
1534 function util_uri_grabber($unencoded_string, $tryaidtid = false) {
1535 /* escape all ^A and ^B as ^BX^B and ^BY^B, respectively */
1536 $s = str_replace("\x01", "\x02X\x02", str_replace("\x02", "\x02Y\x02",
1537 $unencoded_string));
1538 /* replace all URIs with ^AURI^A */
1540 '|([a-zA-Z][a-zA-Z0-9+.-]*:[#0-9a-zA-Z;/?:@&=+$,_.!~*\'()%-]+)|',
1543 return htmlentities($unencoded_string, ENT_QUOTES, "UTF-8");
1544 /* encode the string */
1545 $s = htmlentities($s, ENT_QUOTES, "UTF-8");
1546 /* convert 「^Afoo^A」 to 「<a href="foo">foo</a>」 */
1547 $s = preg_replace('|\x01([^\x01]+)\x01|',
1548 '<a href="$1">$1</a>', $s);
1550 return htmlentities($unencoded_string, ENT_QUOTES, "UTF-8");
1551 // /* convert [#123] to links if found */
1553 // $s = util_tasktracker_links($s);
1554 /* convert ^BX^B and ^BY^B back to ^A and ^B, respectively */
1555 $s = str_replace("\x02Y\x02", "\x02", str_replace("\x02X\x02", "\x01",
1557 /* return the final result */
1561 function util_html_encode($s) {
1562 return htmlspecialchars($s, ENT_QUOTES, "UTF-8");
1565 /* secure a (possibly already HTML encoded) string */
1566 function util_html_secure($s) {
1567 return util_html_encode(util_unconvert_htmlspecialchars($s));
1570 /* return integral value (ℕ₀) of passed string if it matches, or false */
1571 function util_nat0(&$s) {
1573 /* unset variable */
1577 if (count($s) == 1) {
1578 /* one-element array */
1579 return util_nat0($s[0]);
1581 /* not one element, or element not at [0] */
1584 if (!is_numeric($s)) {
1590 /* number element of ℕ₀ */
1591 $text = (string)$num;
1593 /* number matches its textual representation */
1596 /* doesn't match, like 0123 or 1.2 or " 1" */
1603 * util_negociate_alternate_content_types() - Manage content-type negociation based on 'script_accepted_types' hooks
1604 * @param string $script
1605 * @param string $default_content_type
1606 * @param string|bool $forced_content_type
1609 function util_negociate_alternate_content_types($script, $default_content_type, $forced_content_type=false) {
1611 $content_type = $default_content_type;
1613 // we can force the content-type to be returned automatically if necessary
1614 if ($forced_content_type) {
1615 // TODO ideally, in this case we could try and apply the negociation to see if it matches
1616 // one provided by the hooks, but negotiateMimeType() doesn't allow this so for the moment,
1617 // we just force it whatever the hooks support
1618 $content_type = $forced_content_type;
1620 // Invoke plugins' hooks 'script_accepted_types' to discover which alternate content types they would accept for /users/...
1621 $hook_params = array();
1622 $hook_params['script'] = $script;
1623 $hook_params['accepted_types'] = array();
1625 plugin_hook_by_reference('script_accepted_types', $hook_params);
1627 if (count($hook_params['accepted_types'])) {
1628 // By default, text/html is accepted
1629 $accepted_types = array($default_content_type);
1630 $new_accepted_types = $hook_params['accepted_types'];
1631 $accepted_types = array_merge($accepted_types, $new_accepted_types);
1633 // PEAR::HTTP (for negotiateMimeType())
1634 require_once 'HTTP.php';
1636 // negociate accepted content-type depending on the preferred ones declared by client
1638 $content_type = $http->negotiateMimeType($accepted_types, false);
1641 return $content_type;
1645 * util_gethref() - Construct a hypertext reference
1647 * @param string $baseurl
1648 * (optional) base URL (absolute or relative);
1649 * urlencoded, but not htmlencoded
1650 * (default (falsy): PHP_SELF)
1651 * @param array $args
1652 * (optional) associative array of unencoded query parameters;
1653 * false values are ignored
1654 * @param bool $ashtml
1655 * (optional) htmlencode the result?
1657 * @param string $sep
1658 * (optional) argument separator ('&' or ';')
1660 * @return string URL, possibly htmlencoded
1662 function util_gethref($baseurl = '', $args = array(), $ashtml = true, $sep = '&') {
1663 $rv = $baseurl? $baseurl : getStringFromServer('PHP_SELF');
1665 foreach ($args as $k => $v) {
1669 $rv .= $pfx.urlencode($k).'='.urlencode($v);
1672 return ($ashtml? util_html_encode($rv) : $rv);
1676 * util_sanitise_multiline_submission() – Convert text to ASCII CR-LF
1678 * @param string $text
1679 * input string to sanitise
1681 * sanitised string: CR, LF or CR-LF converted to CR-LF
1683 function util_sanitise_multiline_submission($text) {
1684 /* convert all CR-LF into LF */
1685 $text = preg_replace("/\015+\012+/m", "\012", $text);
1686 /* convert all CR or LF into CR-LF */
1687 $text = preg_replace("/[\012\015]/m", "\015\012", $text);
1692 function util_is_html($string) {
1693 return (strip_tags(util_unconvert_htmlspecialchars($string)) != $string);
1696 function util_init_messages() {
1697 global $feedback, $warning_msg, $error_msg;
1699 if (PHP_SAPI == 'cli') {
1700 $feedback = $warning_msg = $error_msg = '';
1702 $feedback = getStringFromCookie('feedback', '');
1703 if ($feedback) setcookie('feedback', '', time()-3600, '/');
1705 $warning_msg = getStringFromCookie('warning_msg', '');
1706 if ($warning_msg) setcookie('warning_msg', '', time()-3600, '/');
1708 $error_msg = getStringFromCookie('error_msg', '');
1709 if ($error_msg) setcookie('error_msg', '', time()-3600, '/');
1713 function util_save_messages() {
1714 global $feedback, $warning_msg, $error_msg;
1716 setcookie('feedback', $feedback, time() + 10, '/');
1717 setcookie('warning_msg', $warning_msg, time() + 10, '/');
1718 setcookie('error_msg', $error_msg, time() + 10, '/');
1722 * util_create_file_with_contents() — Securely create (or replace) a file with given contents
1724 * @param string $path Path of the file to be created
1725 * @param string $contents Contents of the file
1727 * @return boolean FALSE on error
1729 function util_create_file_with_contents($path, $contents) {
1730 if (file_exists($path) && !unlink($path)) {
1733 $handle = fopen($path, "x+");
1734 if ($handle == false) {
1737 fwrite($handle, $contents);
1743 * Create a directory in the system temp directory with a hard-to-predict name.
1744 * Does not have the guarantees of the actual BSD libc function or Python tempfile function.
1745 * @param string $suffix Append to the new directory's name
1746 * @param string $prefix Prepend to the new directory's name
1747 * @return string The path of the new directory.
1749 * Mostly taken from https://gist.github.com/1407245 as a "temporary"
1750 * workaround to https://bugs.php.net/bug.php?id=49211
1752 function util_mkdtemp($suffix = '', $prefix = 'tmp') {
1753 $tempdir = sys_get_temp_dir();
1754 for ($i=0; $i<5; $i++) {
1755 $id = strtr(base64_encode(util_randbytes(6)), '+/', '-_');
1756 $path = "{$tempdir}/{$prefix}{$id}{$suffix}";
1757 if (mkdir($path, 0700)) {
1765 * Run a function with only the permissions of a given Unix user
1766 * Function can be an anonymous
1767 * Optional arguments in an array
1768 * @param string $username Unix user name
1769 * @param function $function function to run (possibly anonymous)
1770 * @param array $params parameters
1771 * @return boolean true on success, false on error
1773 function util_sudo_effective_user($username, $function, $params=array()) {
1774 $saved_egid = posix_getegid();
1775 $saved_euid = posix_geteuid();
1777 $userinfo = posix_getpwnam($username);
1778 if ($userinfo === false) {
1781 if (posix_setegid($userinfo['gid']) &&
1782 ($saved_euid != 0 || posix_initgroups($username, $userinfo['gid'])) &&
1783 posix_seteuid($userinfo['uid'])) {
1787 posix_setegid($saved_egid);
1788 posix_seteuid($saved_euid);
1789 if ($saved_euid == 0)
1790 posix_initgroups("root", 0);
1796 // c-file-style: "bsd"