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-2011, 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($plainpasswd) - generate htpasswd md5 format password
32 * From http://www.php.net/manual/en/function.crypt.php#73619
34 function htpasswd_apr1_md5($plainpasswd) {
35 $salt = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"), 0, 8);
36 $len = strlen($plainpasswd);
37 $text = $plainpasswd.'$apr1$'.$salt;
38 $bin = pack("H32", md5($plainpasswd.$salt.$plainpasswd));
40 for($i = $len; $i > 0; $i -= 16) { $text .= substr($bin, 0, min(16, $i)); }
41 for($i = $len; $i > 0; $i >>= 1) { $text .= ($i & 1) ? chr(0) : $plainpasswd{0}; }
42 $bin = pack("H32", md5($text));
43 for($i = 0; $i < 1000; $i++) {
44 $new = ($i & 1) ? $plainpasswd : $bin;
45 if ($i % 3) $new .= $salt;
46 if ($i % 7) $new .= $plainpasswd;
47 $new .= ($i & 1) ? $bin : $plainpasswd;
48 $bin = pack("H32", md5($new));
50 for ($i = 0; $i < 5; $i++) {
54 $tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
56 $tmp = chr(0).chr(0).$bin[11].$tmp;
57 $tmp = strtr(strrev(substr(base64_encode($tmp), 2)),
58 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
59 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
60 return "$"."apr1"."$".$salt."$".$tmp;
64 * is_utf8($string) - utf-8 detection
66 * From http://www.php.net/manual/en/function.mb-detect-encoding.php#85294
68 function is_utf8($str) {
72 for($i=0; $i<$len; $i++){
75 if(($c >= 254)) return false;
76 elseif($c >= 252) $bits=6;
77 elseif($c >= 248) $bits=5;
78 elseif($c >= 240) $bits=4;
79 elseif($c >= 224) $bits=3;
80 elseif($c >= 192) $bits=2;
82 if(($i+$bits) > $len) return false;
86 if($b < 128 || $b > 191) return false;
94 function util_strip_unprintable(&$data) {
95 if (is_array($data)) {
96 foreach ($data as $key => &$value) {
97 util_strip_unprintable($value);
101 $data = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $data);
107 * removeCRLF() - remove any Carriage Return-Line Feed from a string.
108 * That function is useful to remove the possibility of a CRLF Injection when sending mail
109 * All the data that we will send should be passed through that function
111 * @param string The string that we want to empty from any CRLF
113 function util_remove_CRLF($str) {
114 return strtr($str, "\015\012", ' ');
119 * util_check_fileupload() - determines if a filename is appropriate for upload
121 * @param array The uploaded file as returned by getUploadedFile()
123 function util_check_fileupload($filename) {
125 /* Empty file is a valid file.
126 This is because this function should be called
127 unconditionally at the top of submit action processing
128 and many forms have optional file upload. */
129 if ($filename == 'none' || $filename == '') {
133 /* This should be enough... */
134 if (!is_uploaded_file($filename)) {
137 /* ... but we'd rather be paranoic */
138 if (strstr($filename, '..')) {
141 if (!is_file($filename)) {
144 if (!file_exists($filename)) {
147 if ((dirname($filename) != '/tmp') &&
148 (dirname($filename) != "/var/tmp")) {
155 * util_check_url() - determines if given URL is valid.
157 * Currently, test is very basic, only the protocol is
158 * checked, allowed values are: http, https, ftp.
160 * @param string The URL
161 * @return boolean true if valid, false if not valid.
163 function util_check_url($url) {
164 return (preg_match('/^(http|https|ftp):\/\//', $url) > 0);
168 * util_send_message() - Send email
169 * This function should be used in place of the PHP mail() function
171 * @param string The email recipients address
172 * @param string The email subject
173 * @param string The body of the email message
174 * @param string The optional email sender address. Defaults to 'noreply@'
175 * @param string The addresses to blind-carbon-copy this message
176 * @param string The optional email sender name. Defaults to ''
177 * @param boolean Whether to send plain text or html email
180 function util_send_message($to,$subject,$body,$from='',$BCC='',$sendername='',$extra_headers='',$send_html_email=false) {
184 $to='noreply@'.forge_get_config('web_host');
187 $from='noreply@'.forge_get_config('web_host');
191 $charset = _('UTF-8');
197 if ($extra_headers) {
198 $body2 .= $extra_headers."\n";
201 "\nFrom: ".util_encode_mailaddr($from,$sendername,$charset);
202 if (forge_get_config('bcc_all_emails') != '') {
203 $BCC.=",".forge_get_config('bcc_all_emails');
206 $body2 .= "\nBCC: $BCC";
208 $send_html_email?$type="html":$type="plain";
209 $body2 .= "\n".util_encode_mimeheader("Subject", $subject, $charset).
210 "\nContent-type: text/$type; charset=$charset".
212 util_convert_body($body, $charset);
214 if (!forge_get_config('sendmail_path')){
215 $sys_sendmail_path="/usr/sbin/sendmail";
218 $handle = popen(forge_get_config('sendmail_path')." -f'$from' -t -i", 'w');
219 fwrite ($handle, $body2);
224 * util_encode_mailaddr() - Encode email address to MIME format
226 * @param string The email address
227 * @param string The email's owner name
228 * @param string The converting charset
231 function util_encode_mailaddr($email,$name,$charset) {
232 if (function_exists('mb_convert_encoding') && trim($name) != "") {
233 $name = "=?".$charset."?B?".
234 base64_encode(mb_convert_encoding(
235 $name,$charset,"UTF-8")).
239 return $name." <".$email."> ";
243 * util_encode_mimeheader() - Encode mimeheader
245 * @param string The name of the header (e.g. "Subject")
246 * @param string The email subject
247 * @param string The converting charset (like ISO-2022-JP)
248 * @return string The MIME encoded subject
251 function util_encode_mimeheader($headername,$str,$charset) {
252 if (function_exists('mb_internal_encoding') &&
253 function_exists('mb_encode_mimeheader')) {
254 $x = mb_internal_encoding();
255 mb_internal_encoding("UTF-8");
256 $y = mb_encode_mimeheader($headername . ": " . $str,
258 mb_internal_encoding($x);
262 if (!function_exists('mb_convert_encoding')) {
263 return $headername . ": " . $str;
266 return $headername . ": " . "=?".$charset."?B?".
267 base64_encode(mb_convert_encoding(
268 $str,$charset,"UTF-8")).
273 * util_convert_body() - Convert body of the email message
275 * @param string The body of the email message
276 * @param string The charset of the email message
277 * @return string The converted body of the email message
280 function util_convert_body($str,$charset) {
281 if (!function_exists('mb_convert_encoding') || $charset == 'UTF-8') {
285 return mb_convert_encoding($str,$charset,"UTF-8");
288 function util_send_jabber($to,$subject,$body) {
289 if (!forge_get_config('use_jabber')) {
292 $JABBER = new Jabber();
293 if (!$JABBER->Connect()) {
294 echo '<br />Unable to connect';
297 //$JABBER->SendAuth();
298 //$JABBER->AccountRegistration();
299 if (!$JABBER->SendAuth()) {
300 echo '<br />Auth Failure';
301 $JABBER->Disconnect();
303 //or die("Couldn't authenticate!");
305 $JABBER->SendPresence(NULL, NULL, "online");
307 $body=htmlspecialchars($body);
308 $to_arr=explode(',',$to);
309 for ($i=0; $i<count($to_arr); $i++) {
311 //echo '<br />Sending Jabbers To: '.$to_arr[$i];
312 if (!$JABBER->SendMessage($to_arr[$i], "normal", NULL, array("body" => $body,"subject"=>$subject))) {
313 echo '<br />Error Sending to '.$to_arr[$i];
318 $JABBER->CruiseControl(2);
319 $JABBER->Disconnect();
323 * util_handle_message() - a convenience wrapper which sends messages
324 * to either a jabber account or email account or both, depending on
327 * @param array array of user_id's from the user table
328 * @param string subject of the message
329 * @param string the message body
330 * @param string a comma-separated list of email address
331 * @param string a comma-separated list of jabber address
332 * @param string From header
334 function util_handle_message($id_arr,$subject,$body,$extra_emails='',$extra_jabbers='',$from='') {
337 if (count($id_arr) < 1) {
340 $res = db_query_params ('SELECT user_id,jabber_address,email,jabber_only FROM users WHERE user_id = ANY ($1)',
341 array (db_int_array_to_any_clause ($id_arr))) ;
342 $rows = db_numrows($res) ;
344 for ($i=0; $i<$rows; $i++) {
345 if (db_result($res, $i, 'user_id') == 100) {
346 // Do not send messages to "Nobody"
350 // Build arrays of the jabber address
352 if (db_result($res,$i,'jabber_address')) {
353 $address['jabber_address'][]=db_result($res,$i,'jabber_address');
354 if (db_result($res,$i,'jabber_only') != 1) {
355 $address['email'][]=db_result($res,$i,'email');
358 $address['email'][]=db_result($res,$i,'email');
361 if (isset ($address['email']) && count($address['email']) > 0) {
362 $extra_emails=implode($address['email'],',').',' . $extra_emails;
364 if (isset ($address['jabber_address']) && count($address['jabber_address']) > 0) {
365 $extra_jabbers=implode($address['jabber_address'],',').','.$extra_jabbers;
369 util_send_message('',$subject,$body,$from,$extra_emails);
371 if ($extra_jabbers) {
372 util_send_jabber($extra_jabbers,$subject,$body);
377 * util_unconvert_htmlspecialchars() - Unconverts a string converted with htmlspecialchars()
379 * @param string The string to unconvert
380 * @returns The unconverted string
383 function util_unconvert_htmlspecialchars($string) {
384 return html_entity_decode($string, ENT_QUOTES, "UTF-8");
388 * util_result_columns_to_assoc() - Takes a result set and turns the column pair into an associative array
390 * @param string The result set ID
391 * @param int The column key
392 * @param int The optional column value
393 * @returns An associative array
396 function util_result_columns_to_assoc($result, $col_key=0, $col_val=1) {
397 $rows=db_numrows($result);
401 for ($i=0; $i<$rows; $i++) {
402 $arr[db_result($result,$i,$col_key)]=db_result($result,$i,$col_val);
411 * util_result_column_to_array() - Takes a result set and turns the optional column into an array
413 * @param int The result set ID
414 * @param int The column
418 function &util_result_column_to_array($result, $col=0) {
420 Takes a result set and turns the optional column into
423 $rows=db_numrows($result);
427 for ($i=0; $i<$rows; $i++) {
428 $arr[$i]=db_result($result,$i,$col);
437 * util_line_wrap() - Automatically linewrap text
439 * @param string The text to wrap
440 * @param int The number of characters to wrap - Default is 80
441 * @param string The line break to use - Default is '\n'
442 * @returns The wrapped text
445 function util_line_wrap ($text, $wrap = 80, $break = "\n") {
446 return wordwrap($text, $wrap, $break, false);
450 * util_make_links() - Turn URL's into HREF's.
452 * @param string The URL
453 * @returns The HREF'ed URL
456 function util_make_links($data='') {
461 for ($i = 0; $i < 5; $i++) {
462 $randPattern = rand(10000, 30000);
463 if (! preg_match("/$randPattern/", $data)) {
470 while(preg_match('/<a [^>]*>[^<]*<\/a>/i', $data, $part)) {
472 $data = preg_replace('/<a [^>]*>[^<]*<\/a>/i', $randPattern, $data, 1);
476 while(preg_match('/<a [^>]*>.*<\/a>/siU', $data, $part)) {
478 $data = preg_replace('/<a [^>]*>.*<\/a>/siU', $randPattern, $data, 1);
480 while(preg_match('/<img [^>]*\/>/siU', $data, $part)) {
482 $data = preg_replace('/<img [^>]*\/>/siU', $randPattern, $data, 1);
484 $data = str_replace('>', "\1", $data);
485 $data = preg_replace("#([ \t]|^)www\.#i"," http://www.",$data);
486 $data = preg_replace("#([[:alnum:]]+)://([^[:space:]<\1]*)([[:alnum:]\#?/&=])#i", "<a href=\"\\1://\\2\\3\" target=\"_new\">\\1://\\2\\3</a>", $data);
487 $data = preg_replace("#([[:space:]]|^)(([a-z0-9_]|\\-|\\.)+@([^[:space:]<\1]*)([[:alnum:]-]))#i", "\\1<a href=\"mailto:\\2\" target=\"_new\">\\2</a>", $data);
488 $data = str_replace("\1", '>', $data);
489 for ($i = 0; $i < count($mem); $i++) {
490 $data = preg_replace("/$randPattern/", $mem[$i], $data, 1);
495 $lines = split("\n",$data);
497 while ( list ($key, $line) = each ($lines)) {
498 // Do not scan lines if they already have hyperlinks.
499 // Avoid problem with text written with an WYSIWYG HTML editor.
500 if (eregi('<a ([^>]*)>.*</a>', $line, $linePart)) {
501 if (eregi('href="[^"]*"', $linePart[1])) {
507 // Skip </img> tag also
508 if (eregi('<img ([^>]*)/>', $line, $linePart)) {
509 if (eregi('href="[^"]*"', $linePart[1])) {
515 // When we come here, we usually have form input
516 // encoded in entities. Our aim is to NOT include
517 // angle brackets in the URL
518 // (RFC2396; http://www.w3.org/Addressing/URL/5.1_Wrappers.html)
519 $line = str_replace('>', "\1", $line);
520 $line = preg_replace("/([ \t]|^)www\./i", " http://www.", $line);
521 $line = preg_replace("/([[:alnum:]]+):\/\/([^[:space:]<\1]*)([[:alnum:]#?\/&=])/i",
522 "<a href=\"\\1://\\2\\3\" target=\"_new\">\\1://\\2\\3</a>", $line);
523 $line = preg_replace(
524 "/([[:space:]]|^)(([a-z0-9_]|\\-|\\.)+@([^[:space:]]*)([[:alnum:]-]))/i",
525 "\\1<a href=\"mailto:\\2\" target=\"_new\">\\2</a>",
528 $line = str_replace("\1", '>', $line);
535 * show_priority_colors_key() - Show the priority colors legend
538 function show_priority_colors_key() {
539 echo '<p><strong> '._('Priority Colors')._(':').'</strong>';
540 for ($i=1; $i<6; $i++) {
541 echo ' <span class="priority'.$i.'">'.$i.'</span>';
547 * utils_buildcheckboxarray() - Build a checkbox array
549 * @param int Number of options to be in the array
550 * @param string The name of the checkboxes
551 * @param array An array of boxes to be pre-checked
554 function utils_buildcheckboxarray($options, $name, $checked_array) {
555 $option_count = count($options);
556 $checked_count = count($checked_array);
558 for ($i = 1; $i <= $option_count; $i++) {
560 <br /><input type="checkbox" name="'.$name.'" value="'.$i.'"';
561 for ($j=0; $j < $checked_count; $j++) {
562 if ($i == $checked_array[$j]) {
566 echo ' /> '.$options[$i];
571 * utils_requiredField() - Adds the required field marker
573 * @return a string holding the HTML to mark a required field
575 function utils_requiredField() {
576 return '<span class="requiredfield">*</span>';
580 * GraphResult() - Takes a database result set and builds a graph.
581 * The first column should be the name, and the second column should be the values
582 * Be sure to include HTL_Graphs.php before using this function
584 * @author Tim Perdue tperdue@valinux.com
585 * @param int The databse result set ID
586 * @param string The title of the graph
589 function GraphResult($result, $title) {
590 $rows = db_numrows($result);
592 if ((!$result) || ($rows < 1)) {
598 for ($j=0; $j < db_numrows($result); $j++) {
599 if (db_result($result, $j, 0) != '' && db_result($result, $j, 1) != '' ) {
600 $names[$j] = db_result($result, $j, 0);
601 $values[$j] = db_result($result, $j, 1);
606 This is another function detailed below
608 GraphIt($names, $values, $title);
613 * GraphIt() - Build a graph
615 * @author Tim Perdue tperdue@valinux.com
616 * @param array An array of names
617 * @param array An array of values
618 * @param string The title of the graph
621 function GraphIt($name_string, $value_string, $title) {
624 $counter = count($name_string);
627 Can choose any color you wish
631 for ($i = 0; $i < $counter; $i++) {
632 $bars[$i] = $HTML->COLOR_LTBACK1;
635 $counter = count($value_string);
638 Figure the max_value passed in, so scale can be determined
643 for ($i = 0; $i < $counter; $i++) {
644 if ($value_string[$i] > $max_value) {
645 $max_value = $value_string[$i];
649 if ($max_value < 1) {
654 I want my graphs all to be 800 pixels wide, so that is my divisor
657 $scale = (400/$max_value);
660 I create a wrapper table around the graph that holds the title
663 $title_arr = array();
664 $title_arr[] = $title;
666 echo $GLOBALS['HTML']->listTableTop ($title_arr);
669 Create an associate array to pass in. I leave most of it blank
698 This is the actual call to the HTML_Graphs class
701 html_graph($name_string, $value_string, $bars, $vals);
705 <!-- end outer graph table -->';
706 echo $GLOBALS['HTML']->listTableBottom();
710 * ShowResultSet() - Show a generic result set
711 * Very simple, plain way to show a generic result set
713 * @param int The result set ID
714 * @param string The title of the result set
715 * @param bool The option to turn URL's into links
716 * @param bool The option to display headers
717 * @param array The db field name -> label mapping
718 * @param array Don't display these cols
721 function ShowResultSet($result,$title='',$linkify=false,$displayHeaders=true,$headerMapping=array(), $excludedCols=array()) {
722 global $group_id,$HTML;
725 $rows = db_numrows($result);
726 $cols = db_numfields($result);
728 echo '<table width="100%">';
730 /* Create the headers */
731 $headersCellData = array();
732 $colsToKeep = array();
733 for ($i=0; $i < $cols; $i++) {
734 $fieldName = db_fieldname($result, $i);
735 if(in_array($fieldName, $excludedCols)) {
739 if(isset($headerMapping[$fieldName])) {
740 if(is_array($headerMapping[$fieldName])) {
741 $headersCellData[] = $headerMapping[$fieldName];
743 $headersCellData[] = array($headerMapping[$fieldName]);
747 $headersCellData[] = array($fieldName);
751 /* Create the title */
752 if(strlen($title) > 0) {
753 $titleCellData = array();
754 $titleCellData[] = array($title, 'colspan="'.count($headersCellData).'"');
755 echo $HTML->multiTableRow('', $titleCellData, TRUE);
758 /* Display the headers */
759 if($displayHeaders) {
760 echo $HTML->multiTableRow('', $headersCellData, TRUE);
763 /* Create the rows */
764 for ($j = 0; $j < $rows; $j++) {
765 echo '<tr '. $HTML->boxGetAltRowStyle($j) . '>';
766 for ($i = 0; $i < $cols; $i++) {
767 if(in_array($i, $colsToKeep)) {
768 if ($linkify && $i == 0) {
769 $link = '<a href="'.getStringFromServer('PHP_SELF').'?';
771 if ($linkify == "bug_cat") {
772 $link .= 'group_id='.$group_id.'&bug_cat_mod=y&bug_cat_id='.db_result($result, $j, 'bug_category_id').'">';
773 } else if($linkify == "bug_group") {
774 $link .= 'group_id='.$group_id.'&bug_group_mod=y&bug_group_id='.db_result($result, $j, 'bug_group_id').'">';
775 } else if($linkify == "patch_cat") {
776 $link .= 'group_id='.$group_id.'&patch_cat_mod=y&patch_cat_id='.db_result($result, $j, 'patch_category_id').'">';
777 } else if($linkify == "support_cat") {
778 $link .= 'group_id='.$group_id.'&support_cat_mod=y&support_cat_id='.db_result($result, $j, 'support_category_id').'">';
779 } else if($linkify == "pm_project") {
780 $link .= 'group_id='.$group_id.'&project_cat_mod=y&project_cat_id='.db_result($result, $j, 'group_project_id').'">';
782 $link = $linkend = '';
785 $link = $linkend = '';
787 echo '<td>'.$link . db_result($result, $j, $i) . $linkend.'</td>';
799 * validate_email() - Validate an email address
801 * @param string The address string to validate
802 * @returns true on success/false on error
805 function validate_email ($address) {
806 if (version_compare(PHP_VERSION, '5.2.0', '>=')) {
807 if ( filter_var($address, FILTER_VALIDATE_EMAIL) ) {
815 if ( preg_match( "/^[-!#$%&\'*+\\.\/0-9=?A-Z^_`a-z{|}~]+@[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.[-!#$%&\'*+\\.\/0-9=?A-Z^_`a-z{|}~]+$/", $address) ) {
824 * validate_emails() - Validate a list of e-mail addresses
826 * @param string E-mail list
827 * @param char Separator
828 * @returns array Array of invalid e-mail addresses (if empty, all addresses are OK)
830 function validate_emails($addresses, $separator=',') {
831 if (strlen($addresses) == 0) return array();
833 $emails = explode($separator, $addresses);
836 if (is_array($emails)) {
837 foreach ($emails as $email) {
838 $email = trim($email); // This is done so we can validate lists like "a@b.com, c@d.com"
839 if (!validate_email($email)) $ret[] = $email;
848 * util_is_valid_filename() - Verifies whether a file has a valid filename
850 * @param string The file to verify
851 * @returns true on success/false on error
854 function util_is_valid_filename($file) {
856 $invalidchars = preg_replace("/[-A-Z0-9+_\. ~]/i","",$file);
858 if (!empty($invalidchars)) {
861 if (strstr($file,'..')) {
869 /* util_is_valid_repository_name() - Verifies whether a repository name is valid
871 * @param string The name to verify
872 * @returns true on success/false on error
875 function util_is_valid_repository_name ($file) {
877 $invalidchars = preg_replace("/[-A-Z0-9+_\.]/i","",$file);
879 if (!empty($invalidchars)) {
882 if (strstr($file,'..')) {
889 * valid_hostname() - Validates a hostname string to make sure it doesn't contain invalid characters
891 * @param string The optional hostname string
892 * @returns true on success/false on failur
895 function valid_hostname($hostname = "xyz") {
898 $invalidchars = preg_replace("/[-A-Z0-9\.]/i","",$hostname);
900 if (!empty($invalidchars)) {
904 //double dot, starts with a . or -
905 if ( preg_match("/\.\./",$hostname) || preg_match("/^\./",$hostname) || preg_match("/^\-/",$hostname) ) {
909 $multipoint = explode(".",$hostname);
911 if (!(is_array($multipoint)) || ((count($multipoint) - 1) < 1)) {
921 * human_readable_bytes() - Translates an integer representing bytes to a human-readable format.
923 * Format file size in a human-readable way
924 * such as "xx Megabytes" or "xx Mo"
926 * @author Andrea Paleni <andreaSPAMLESS_AT_SPAMLESScriticalbit.com>
928 * @param int bytes is the size
929 * @param bool base10 enable base 10 representation, otherwise
930 * default base 2 is used
931 * @param int round number of fractional digits
932 * @param array labels strings associated to each 2^10 or
933 * 10^3(base10==true) multiple of base units
935 function human_readable_bytes ($bytes, $base10=false, $round=0, $labels=array()) {
940 return "-" . human_readable_bytes(-$bytes, $base10, $round);
943 $labels = array(_('bytes'), _('kB'), _('MB'), _('GB'), _('TB'));
947 $labels = array(_('bytes'), _('KiB'), _('MiB'), _('GiB'), _('TiB'));
951 $log = (int)(log10($bytes)/log10($base));
953 foreach ($labels as $p=>$lab) {
958 if ($lab != _("bytes") and $lab != _("kB") and $lab != _("KiB")) {
961 $text = round($bytes/pow($base,$pow),$round) . " " . $lab;
968 * ls - lists a specified directory and returns an array of files
969 * @param string the path of the directory to list
970 * @param boolean whether to filter out directories and illegal filenames
971 * @return array array of file names.
973 function &ls($dir,$filter=false) {
976 if (is_dir($dir) && ($h = opendir($dir))) {
977 while (($f = readdir($h)) !== false) {
981 if (!util_is_valid_filename($f) ||
982 !is_file($dir . "/" . $f))
993 * readfile_chunked() - replacement for readfile
995 * @param string The file path
996 * @param bool Whether to return bytes served or just a bool
999 function readfile_chunked($filename, $returnBytes=true) {
1000 $chunksize = 1*(1024*1024); // 1MB chunks
1004 $handle = fopen($filename, 'rb');
1005 if ($handle === false) {
1010 while (!feof($handle)) {
1011 $buffer = fread($handle, $chunksize);
1016 $byteCounter += strlen($buffer);
1020 $status = fclose($handle);
1021 if ($returnBytes && $status) {
1022 return $byteCounter; // return num. bytes delivered like readfile() does.
1028 * util_is_root_dir() - Checks if a directory points to the root dir
1029 * @param string Directory
1032 function util_is_root_dir($dir) {
1033 return !preg_match('/[^\\/]/',$dir);
1037 * util_is_dot_or_dotdot() - Checks if a directory points to . or ..
1038 * @param string Directory
1041 function util_is_dot_or_dotdot($dir) {
1042 return preg_match('/^\.\.?$/',trim($dir, '/'));
1046 * util_containts_dot_or_dotdot() - Checks if a directory containts . or ..
1047 * @param string Directory
1050 function util_containts_dot_or_dotdot($dir) {
1051 foreach (explode('/', $dir) as $sub_dir) {
1052 if (util_is_dot_or_dotdot($sub_dir))
1060 * util_secure_filename() - Returns a secured file name
1061 * @param string Filename
1062 * @return string Filename
1064 function util_secure_filename($file) {
1065 $f = preg_replace("/[^-A-Z0-9_\.]/i", '', $file);
1066 if (util_containts_dot_or_dotdot($f))
1067 $f = preg_replace("/\./", '_', $f);
1074 * util_strip_accents() - Remove accents from given text.
1075 * @param string Text
1078 function util_strip_accents($text) {
1079 $find = utf8_decode($text);
1080 $find = strtr($find,
1081 utf8_decode('àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ'),
1082 'aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY');
1083 return utf8_encode($find);
1087 * Constructs the forge's URL prefix out of forge_get_config('url_prefix')
1091 function normalized_urlprefix() {
1092 $prefix = forge_get_config('url_prefix') ;
1093 $prefix = preg_replace ("/^\//", "", $prefix) ;
1094 $prefix = preg_replace ("/\/$/", "", $prefix) ;
1095 $prefix = "/$prefix/" ;
1096 if ($prefix == '//')
1102 * Return URL prefix (http:// or https://)
1104 * @param string $prefix (optional) : 'http' or 'https' to force it
1105 * @return string URL prefix
1107 function util_url_prefix($prefix = '') {
1108 if ($prefix == 'http' || $prefix == 'https' ) {
1109 return $prefix . '://';
1112 if (forge_get_config('use_ssl')) {
1121 * Construct the base URL http[s]://forge_name[:port]
1123 * @param string $prefix (optional) : 'http' or 'https' to force it
1124 * @return string base URL
1126 function util_make_base_url($prefix = '') {
1127 $url = util_url_prefix($prefix);
1128 $url .= forge_get_config('web_host') ;
1129 if (forge_get_config('https_port') && (forge_get_config('https_port') != 443)) {
1130 $url .= ":".forge_get_config('https_port') ;
1136 * Construct full URL from a relative path
1138 * @param string $path (optional)
1139 * @param string $prefix (optional) : 'http' or 'https' to force it
1140 * @return string URL
1142 function util_make_url($path = '', $prefix = '') {
1143 $url = util_make_base_url($prefix).util_make_uri($path) ;
1148 * Find the relative URL from full URL, removing http[s]://forge_name[:port]
1152 function util_find_relative_referer($url) {
1153 $relative_url = str_replace(util_make_base_url(), '', $url);
1154 //now remove previous feedback, error_msg or warning_msg
1155 $relative_url = preg_replace('/&error_msg=.*&/', '&', $relative_url);
1156 $relative_url = preg_replace('/&warning_msg=.*&/', '&', $relative_url);
1157 $relative_url = preg_replace('/&feedback=.*&/', '&', $relative_url);
1158 $relative_url = preg_replace('/&error_msg=.*/', '', $relative_url);
1159 $relative_url = preg_replace('/&warning_msg=.*/', '', $relative_url);
1160 $relative_url = preg_replace('/&feedback=.*/', '', $relative_url);
1161 return $relative_url;
1165 * Construct proper (relative) URI (prepending prefix)
1167 * @param string $path
1168 * @return string URI
1170 function util_make_uri($path) {
1171 $path = preg_replace('/^\//', '', $path);
1172 $uri = normalized_urlprefix();
1177 function util_make_link($path, $text, $extra_params = false, $absolute = false) {
1178 global $use_tooltips;
1180 if (is_array($extra_params)) {
1181 foreach ($extra_params as $key => $value) {
1182 if ($key != 'title') {
1183 $ep .= "$key=\"$value\" ";
1185 if ($key == 'title' && $use_tooltips) {
1186 $ep .= "$key=\"$value\" ";
1191 return '<a ' . $ep . 'href="' . $path . '">' . $text . '</a>' ;
1193 return '<a ' . $ep . 'href="' . util_make_uri($path) . '">' . $text . '</a>' ;
1198 * Create an HTML link to a user's profile page
1200 * @param string $username
1201 * @param int $user_id
1202 * @param string $text
1205 function util_make_link_u ($username, $user_id,$text) {
1206 return '<a href="' . util_make_url_u ($username, $user_id) . '">' . $text . '</a>';
1210 * Display username with link to a user's profile page
1211 * and icon face if possible.
1213 * @param string $username
1214 * @param int $user_id
1215 * @param string $text
1216 * @param string $size
1219 function util_display_user($username, $user_id, $text, $size='xs') {
1220 // Invoke user_link_with_tooltip plugin
1221 $hook_params = array('resource_type' => 'user', 'username' => $username, 'user_id' => $user_id, 'size' => $size, 'user_link' => '');
1222 plugin_hook_by_reference('user_link_with_tooltip', $hook_params);
1223 if($hook_params['user_link'] != ''){
1224 return $hook_params['user_link'];
1227 // If no plugin replaced it, then back to default standard link
1229 // Invoke user_logo plugin (see gravatar plugin for instance)
1230 $params = array('user_id' => $user_id, 'size' => $size, 'content' => '');
1231 plugin_hook_by_reference('user_logo', $params);
1233 $url = '<a href="' . util_make_url_u ($username, $user_id) . '">' . $text . '</a>';
1234 if ($params['content']) {
1235 return $params['content'].$url.'<div class="new_line"></div>';
1241 * Create URL for user's profile page
1243 * @param string $username
1244 * @param int $user_id
1245 * @return string URL
1247 function util_make_url_u ($username, $user_id) {
1248 if (isset ($GLOBALS['sys_noforcetype']) && $GLOBALS['sys_noforcetype']) {
1249 return util_make_url ("/developer/?user_id=$user_id");
1251 return util_make_url ("/users/$username/");
1256 * Create a HTML link to a project's page
1257 * @param string $groupame
1258 * @param int $group_id
1259 * @param string $text
1262 function util_make_link_g ($groupname, $group_id,$text) {
1263 $hook_params =array();
1264 $hook_params['resource_type'] = 'group';
1265 $hook_params['group_name'] = $groupname;
1266 $hook_params['group_id'] = $group_id;
1267 $hook_params['link_text'] = $text;
1268 $hook_params['group_link'] = '';
1269 plugin_hook_by_reference('project_link_with_tooltip', $hook_params);
1270 if($hook_params['group_link'] != '') {
1271 return $hook_params['group_link'];
1274 return '<a href="' . util_make_url_g ($groupname, $group_id) . '">' . $text . '</a>' ;
1278 * Create URL for a project's page
1280 * @param string $groupame
1281 * @param int $group_id
1284 function util_make_url_g ($groupame, $group_id) {
1285 if (isset ($GLOBALS['sys_noforcetype']) && $GLOBALS['sys_noforcetype']) {
1286 return util_make_url ("/project/?group_id=$group_id");
1288 return util_make_url ("/projects/$groupame/");
1292 function util_ensure_value_in_set ($value, $set) {
1293 if (in_array ($value, $set)) {
1300 function check_email_available($group, $email, &$response) {
1301 // Check if a mailing list with same name already exists
1302 if ($group->usesMail()) {
1303 $mlFactory = new MailingListFactory($group);
1304 if (!$mlFactory || !is_object($mlFactory) || $mlFactory->isError()) {
1305 $response .= $mlFactory->getErrorMessage();
1308 $mlArray = $mlFactory->getMailingLists();
1309 if ($mlFactory->isError()) {
1310 $response .= $mlFactory->getErrorMessage();
1313 for ($j = 0; $j < count($mlArray); $j++) {
1314 $currentList =& $mlArray[$j];
1315 if ($email == $currentList->getName()) {
1316 $response .= _('Error: a mailing list with the same email address already exists.');
1322 // Check if a forum with same name already exists
1323 if ($group->usesForum()) {
1324 $ff = new ForumFactory($group);
1325 if (!$ff || !is_object($ff) || $ff->isError()) {
1326 $response .= $ff->getErrorMessage();
1329 $farr = $ff->getForums();
1330 $prefix = $group->getUnixName() . '-';
1331 for ($j = 0; $j < count($farr); $j++) {
1332 if (is_object($farr[$j])) {
1333 if ($email == $prefix . $farr[$j]->getName()) {
1334 $response .= _('Error: a forum with the same email address already exists.');
1341 // Email is available
1346 * Adds the Javascript file to the list to be used
1349 function use_javascript($js) {
1350 return $GLOBALS['HTML']->addJavascript($js);
1353 function use_stylesheet($css, $media='') {
1354 return $GLOBALS['HTML']->addStylesheet($css, $media);
1357 // array_replace_recursive only appeared in PHP 5.3.0
1358 if (!function_exists('array_replace_recursive')) {
1360 * Replaces elements from passed arrays into the first array recursively
1361 * @param array $a1 The array in which elements are replaced.
1362 * @param array $a2 The array from which elements will be extracted.
1363 * @return Returns an array, or NULL if an error occurs.
1365 function array_replace_recursive ($a1, $a2) {
1368 if (!is_array ($a2)) {
1372 foreach ($a2 as $k => $v) {
1373 if (!is_array ($v) ||
1374 !isset ($result[$k]) || !is_array ($result[$k])) {
1378 $result[$k] = array_replace_recursive ($result[$k],
1386 // json_encode only appeared in PHP 5.2.0
1387 if (!function_exists('json_encode')) {
1388 require_once $gfcommon.'include/minijson.php' ;
1389 function json_encode ($a1) {
1390 return minijson_encode ($a1) ;
1394 /* returns an integer from http://forge/foo/bar.php/123 or false */
1395 function util_path_info_last_numeric_component() {
1396 if (!isset($_SERVER['PATH_INFO']))
1400 foreach (str_split($_SERVER['PATH_INFO']) as $x) {
1404 } elseif ($ok == false) {
1405 ; /* need reset using slash */
1406 } elseif ((ord($x) >= 48) && (ord($x) <= 57)) {
1407 $rv = $rv * 10 + ord($x) - 48;
1417 function get_cvs_binary_version () {
1418 $string = `cvs --version 2>/dev/null | grep ^Concurrent.Versions.System.*client/server` ;
1419 if (preg_match ('/^Concurrent Versions System .CVS. 1.11.[0-9]*/', $string)) {
1421 } elseif (preg_match ('/^Concurrent Versions System .CVS. 1.12.[0-9]*/', $string)) {
1428 /* get a backtrace as string */
1429 function debug_string_backtrace() {
1431 debug_print_backtrace();
1432 $trace = ob_get_contents();
1435 // Remove first item from backtrace as it's this function
1436 // which is redundant.
1437 $trace = preg_replace('/^#0\s+' . __FUNCTION__ . "[^\n]*\n/", '',
1440 // Renumber backtrace items.
1441 $trace = preg_replace('/^#(\d+)/me', '\'#\' . ($1 - 1)', $trace);
1446 function util_ini_get_bytes($id) {
1447 $val = trim(ini_get($id));
1448 $last = strtolower($val[strlen($val)-1]);
1460 function util_get_maxuploadfilesize() {
1461 $postmax = util_ini_get_bytes('post_max_size');
1462 $maxfile = util_ini_get_bytes('upload_max_filesize');
1464 $postfile = (int)(($postmax * 3) / 4);
1466 if ($postfile < $maxfile)
1467 $postfile = $maxfile;
1472 function util_get_compressed_file_extension() {
1473 $m = forge_get_config('compression_method');
1474 if (preg_match ('/^gzip\b/', $m)) {
1476 } elseif (preg_match ('/^bzip2\b/', $m)) {
1478 } elseif (preg_match ('/^lzma\b/', $m)) {
1480 } elseif (preg_match ('/^xz\b/', $m)) {
1482 } elseif (preg_match ('/^cat\b/', $m)) {
1485 return '.compressed';
1489 /* return $1 if $1 is set, ${2:-false} otherwise */
1490 function util_ifsetor(&$val, $default = false) {
1491 return (isset($val) ? $val : $default);
1494 function util_randbytes($num=6) {
1497 // Let's try /dev/urandom first
1498 $f = @fopen("/dev/urandom", "rb");
1500 $b .= @fread($f, $num);
1504 // Hm. No /dev/urandom? Try /dev/random.
1505 if (strlen($b) < $num) {
1506 $f = @fopen("/dev/random", "rb");
1508 $b .= @fread($f, $num);
1513 // Still no luck? Fall back to PHP's built-in PRNG
1514 while (strlen($b) < $num) {
1515 $b .= uniqid(mt_rand(), true);
1518 $b = substr($b, 0, $num);
1522 /* maximum: 2^31 - 1 due to PHP weakness */
1523 function util_randnum($min=0,$max=32767) {
1524 $ta = unpack("L", util_randbytes(4));
1525 $n = $ta[1] & 0x7FFFFFFF;
1526 $v = $n % (1 + $max - $min);
1530 // sys_get_temp_dir() is only available for PHP >= 5.2.1
1531 if ( !function_exists('sys_get_temp_dir')) {
1532 function sys_get_temp_dir() {
1533 if ($temp=getenv('TMP')) return $temp;
1534 if ($temp=getenv('TEMP')) return $temp;
1535 if ($temp=getenv('TMPDIR')) return $temp;
1540 /* convert '\n' to <br /> or </p><p> */
1541 function util_pwrap($encoded_string) {
1542 return str_replace("<p></p>", "",
1543 str_replace("<br /></p>", "</p>",
1544 str_replace("<p><br />", "<p>",
1545 "<p>" . str_replace("<br /><br />", "</p><p>",
1546 implode("<br />", explode("\n",
1547 $encoded_string))) . "</p>")));
1550 /* takes a string and returns it HTML encoded, URIs made to hrefs */
1551 function util_uri_grabber($unencoded_string, $tryaidtid=false) {
1552 /* escape all ^A and ^B as ^BX^B and ^BY^B, respectively */
1553 $s = str_replace("\x01", "\x02X\x02", str_replace("\x02", "\x02Y\x02",
1554 $unencoded_string));
1555 /* replace all URIs with ^AURI^A */
1557 '|([a-zA-Z][a-zA-Z0-9+.-]*:[#0-9a-zA-Z;/?:@&=+$,_.!~*\'()%-]+)|',
1560 return htmlentities($unencoded_string, ENT_QUOTES, "UTF-8");
1561 /* encode the string */
1562 $s = htmlentities($s, ENT_QUOTES, "UTF-8");
1563 /* convert 「^Afoo^A」 to 「<a href="foo">foo</a>」 */
1564 $s = preg_replace('|\x01([^\x01]+)\x01|',
1565 '<a href="$1">$1</a>', $s);
1567 return htmlentities($unencoded_string, ENT_QUOTES, "UTF-8");
1568 // /* convert [#123] to links if found */
1570 // $s = util_tasktracker_links($s);
1571 /* convert ^BX^B and ^BY^B back to ^A and ^B, respectively */
1572 $s = str_replace("\x02Y\x02", "\x02", str_replace("\x02X\x02", "\x01",
1574 /* return the final result */
1578 function util_html_encode($s) {
1579 return htmlspecialchars($s, ENT_QUOTES, "UTF-8");
1582 /* secure a (possibly already HTML encoded) string */
1583 function util_html_secure($s) {
1584 return util_html_encode(util_unconvert_htmlspecialchars($s));
1587 /* return integral value (ℕ₀) of passed string if it matches, or false */
1588 function util_nat0(&$s) {
1590 /* unset variable */
1594 if (count($s) == 1) {
1595 /* one-element array */
1596 return util_nat0($s[0]);
1598 /* not one element, or element not at [0] */
1601 if (!is_numeric($s)) {
1607 /* number element of ℕ₀ */
1608 $text = (string)$num;
1610 /* number matches its textual representation */
1613 /* doesn't match, like 0123 or 1.2 or " 1" */
1620 * util_negociate_alternate_content_types() - Manage content-type negociation based on 'script_accepted_types' hooks
1621 * @param string $script
1622 * @param string $default_content_type
1623 * @param string $forced_content_type
1626 function util_negociate_alternate_content_types($script, $default_content_type, $forced_content_type=false) {
1628 $content_type = $default_content_type;
1630 // we can force the content-type to be returned automaticall if necessary
1631 if ($forced_content_type) {
1632 // TODO ideally, in this case we could try and apply the negociation to see if it matches
1633 // one provided by the hooks, but negotiateMimeType() doesn't allow this so for the moment,
1634 // we just force it whatever the hooks support
1635 $content_type = $forced_content_type;
1638 // Invoke plugins' hooks 'script_accepted_types' to discover which alternate content types they would accept for /users/...
1639 $hook_params = array();
1640 $hook_params['script'] = $script;
1641 $hook_params['accepted_types'] = array();
1643 plugin_hook_by_reference('script_accepted_types', $hook_params);
1645 if (count($hook_params['accepted_types'])) {
1646 // By default, text/html is accepted
1647 $accepted_types = array($default_content_type);
1648 $new_accepted_types = $hook_params['accepted_types'];
1649 $accepted_types = array_merge($accepted_types, $new_accepted_types);
1651 // PEAR::HTTP (for negotiateMimeType())
1652 require_once 'HTTP.php';
1654 // negociate accepted content-type depending on the preferred ones declared by client
1656 $content_type = $http->negotiateMimeType($accepted_types, false);
1659 return $content_type;
1663 * util_gethref() - Construct a hypertext reference
1665 * @param string $baseurl
1666 * (optional) base URL (absolute or relative);
1667 * urlencoded, but not htmlencoded
1668 * (default (falsy): PHP_SELF)
1669 * @param array $args
1670 * (optional) associative array of unencoded query parameters;
1671 * false values are ignored
1672 * @param bool $ashtml
1673 * (optional) htmlencode the result?
1675 * @param string $sep
1676 * (optional) argument separator ('&' or ';')
1679 * URL, possibly htmlencoded
1681 function util_gethref($baseurl=false, $args=array(), $ashtml=true, $sep='&') {
1682 $rv = $baseurl ? $baseurl : getStringFromServer('PHP_SELF');
1684 foreach ($args as $k => $v) {
1688 $rv .= $pfx . urlencode($k) . '=' . urlencode($v);
1691 return ($ashtml ? util_html_encode($rv) : $rv);
1695 * util_sanitise_multiline_submission() – Convert text to ASCII CR-LF
1697 * @param string $text
1698 * input string to sanitise
1700 * sanitised string: CR, LF or CR-LF converted to CR-LF
1702 function util_sanitise_multiline_submission($text) {
1703 /* convert all CR-LF into LF */
1704 $text = preg_replace("/\015+\012+/m", "\012", $text);
1705 /* convert all CR or LF into CR-LF */
1706 $text = preg_replace("/[\012\015]/m", "\015\012", $text);
1711 function util_is_html($string) {
1712 return (strip_tags(util_unconvert_htmlspecialchars($string)) != $string);
1715 function util_init_messages() {
1716 global $feedback, $warning_msg, $error_msg;
1718 if (PHP_SAPI == 'cli') {
1719 $feedback = $warning_msg = $error_msg = '';
1721 $feedback = getStringFromCookie('feedback', '');
1722 if ($feedback) setcookie('feedback', '', time()-3600, '/');
1724 $warning_msg = getStringFromCookie('warning_msg', '');
1725 if ($warning_msg) setcookie('warning_msg', '', time()-3600, '/');
1727 $error_msg = getStringFromCookie('error_msg', '');
1728 if ($error_msg) setcookie('error_msg', '', time()-3600, '/');
1732 function util_save_messages() {
1733 global $feedback, $warning_msg, $error_msg;
1735 setcookie('feedback', $feedback, time() + 10, '/');
1736 setcookie('warning_msg', $warning_msg, time() + 10, '/');
1737 setcookie('error_msg', $error_msg, time() + 10, '/');
1741 * util_create_file_with_contents() — Securely create (or replace) a file with given contents
1743 * @param string $path Path of the file to be created
1744 * @param string $contents Contents of the file
1746 * @return boolean FALSE on error
1748 function util_create_file_with_contents($path, $contents) {
1749 if (file_exists($path) && !unlink($path)) {
1752 $handle = fopen($path, "x+");
1753 if ($handle == false) {
1756 fwrite($handle, $contents);
1762 * Create a directory in the system temp directory with a hard-to-predict name.
1763 * Does not have the guarantees of the actual BSD libc function or Python tempfile function.
1764 * @param string $suffix Append to the new directory's name
1765 * @param string $prefix Prepend to the new directory's name
1766 * @return string The path of the new directory.
1768 * Mostly taken from https://gist.github.com/1407245 as a "temporary"
1769 * workaround to https://bugs.php.net/bug.php?id=49211
1771 function util_mkdtemp($suffix = '', $prefix = 'tmp') {
1772 $tempdir = sys_get_temp_dir();
1773 for ($i=0; $i<5; $i++) {
1774 $id = strtr(base64_encode(util_randbytes(6)), '+/', '-_');
1775 $path = "{$tempdir}/{$prefix}{$id}{$suffix}";
1776 if (mkdir($path, 0700)) {
1784 * Run a function with only the permissions of a given Unix user
1785 * Function can be an anonymous
1786 * Optional arguments in an array
1787 * @param string Unix user name
1788 * @param function function to run (possibly anonymous)
1789 * @param array parameters
1790 * @return boolean true on success, false on error
1792 function util_sudo_effective_user($username, $function, $params=array()) {
1793 $saved_egid = posix_getegid();
1794 $saved_euid = posix_geteuid();
1796 $userinfo = posix_getpwnam($username);
1797 if ($userinfo === False) {
1800 if (posix_setegid($userinfo['gid']) &&
1801 ($saved_euid != 0 || posix_initgroups($username, $userinfo['gid'])) &&
1802 posix_seteuid($userinfo['uid'])) {
1806 posix_setegid($saved_egid);
1807 posix_seteuid($saved_euid);
1808 if ($saved_euid == 0)
1809 posix_initgroups("root", 0);
1814 // c-file-style: "bsd"