* Copyright 2010-2011, Alain Peyrat - Alcatel-Lucent
*
* This file is part of FusionForge. FusionForge is free software;
* you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software
* Foundation; either version 2 of the Licence, or (at your option)
* any later version.
*
* FusionForge is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with FusionForge; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/**
* htpasswd_apr1_md5($plainpasswd) - generate htpasswd md5 format password
*
* From http://www.php.net/manual/en/function.crypt.php#73619
*/
function htpasswd_apr1_md5($plainpasswd) {
$salt = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"), 0, 8);
$len = strlen($plainpasswd);
$text = $plainpasswd.'$apr1$'.$salt;
$bin = pack("H32", md5($plainpasswd.$salt.$plainpasswd));
$tmp = '';
for($i = $len; $i > 0; $i -= 16) { $text .= substr($bin, 0, min(16, $i)); }
for($i = $len; $i > 0; $i >>= 1) { $text .= ($i & 1) ? chr(0) : $plainpasswd{0}; }
$bin = pack("H32", md5($text));
for($i = 0; $i < 1000; $i++) {
$new = ($i & 1) ? $plainpasswd : $bin;
if ($i % 3) $new .= $salt;
if ($i % 7) $new .= $plainpasswd;
$new .= ($i & 1) ? $bin : $plainpasswd;
$bin = pack("H32", md5($new));
}
for ($i = 0; $i < 5; $i++) {
$k = $i + 6;
$j = $i + 12;
if ($j == 16) $j = 5;
$tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
}
$tmp = chr(0).chr(0).$bin[11].$tmp;
$tmp = strtr(strrev(substr(base64_encode($tmp), 2)),
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
return "$"."apr1"."$".$salt."$".$tmp;
}
/**
* is_utf8($string) - utf-8 detection
*
* From http://www.php.net/manual/en/function.mb-detect-encoding.php#85294
*/
function is_utf8($str) {
$c=0; $b=0;
$bits=0;
$len=strlen($str);
for($i=0; $i<$len; $i++){
$c=ord($str[$i]);
if($c > 128){
if(($c >= 254)) return false;
elseif($c >= 252) $bits=6;
elseif($c >= 248) $bits=5;
elseif($c >= 240) $bits=4;
elseif($c >= 224) $bits=3;
elseif($c >= 192) $bits=2;
else return false;
if(($i+$bits) > $len) return false;
while($bits > 1){
$i++;
$b=ord($str[$i]);
if($b < 128 || $b > 191) return false;
$bits--;
}
}
}
return true;
}
function util_strip_unprintable(&$data) {
if (is_array($data)) {
foreach ($data as $key => &$value) {
util_strip_unprintable($value);
}
}
else {
$data = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $data);
}
return $data;
}
/**
* removeCRLF() - remove any Carriage Return-Line Feed from a string.
* That function is useful to remove the possibility of a CRLF Injection when sending mail
* All the data that we will send should be passed through that function
*
* @param string The string that we want to empty from any CRLF
*/
function util_remove_CRLF($str) {
return strtr($str, "\015\012", ' ');
}
/**
* util_check_fileupload() - determines if a filename is appropriate for upload
*
* @param array The uploaded file as returned by getUploadedFile()
*/
function util_check_fileupload($filename) {
/* Empty file is a valid file.
This is because this function should be called
unconditionally at the top of submit action processing
and many forms have optional file upload. */
if ($filename == 'none' || $filename == '') {
return true;
}
/* This should be enough... */
if (!is_uploaded_file($filename)) {
return false;
}
/* ... but we'd rather be paranoic */
if (strstr($filename, '..')) {
return false;
}
if (!is_file($filename)) {
return false;
}
if (!file_exists($filename)) {
return false;
}
if ((dirname($filename) != '/tmp') &&
(dirname($filename) != "/var/tmp")) {
return false;
}
return true;
}
/**
* util_check_url() - determines if given URL is valid.
*
* Currently, test is very basic, only the protocol is
* checked, allowed values are: http, https, ftp.
*
* @param string The URL
* @return boolean true if valid, false if not valid.
*/
function util_check_url($url) {
return (preg_match('/^(http|https|ftp):\/\//', $url) > 0);
}
/**
* util_send_message() - Send email
* This function should be used in place of the PHP mail() function
*
* @param string The email recipients address
* @param string The email subject
* @param string The body of the email message
* @param string The optional email sender address. Defaults to 'noreply@'
* @param string The addresses to blind-carbon-copy this message
* @param string The optional email sender name. Defaults to ''
* @param boolean Whether to send plain text or html email
*
*/
function util_send_message($to,$subject,$body,$from='',$BCC='',$sendername='',$extra_headers='',$send_html_email=false) {
if (!$to) {
$to='noreply@'.forge_get_config('web_host');
}
if (!$from) {
$from='noreply@'.forge_get_config('web_host');
}
$charset = _('UTF-8');
if (!$charset) {
$charset = 'UTF-8';
}
$body2 = '';
if ($extra_headers) {
$body2 .= $extra_headers."\n";
}
$body2 .= "To: $to".
"\nFrom: ".util_encode_mailaddr($from,$sendername,$charset);
if (forge_get_config('bcc_all_emails') != '') {
$BCC.=",".forge_get_config('bcc_all_emails');
}
if(!empty($BCC)) {
$body2 .= "\nBCC: $BCC";
}
$send_html_email?$type="html":$type="plain";
$body2 .= "\n".util_encode_mimeheader("Subject", $subject, $charset).
"\nContent-type: text/$type; charset=$charset".
"\n\n".
util_convert_body($body, $charset);
if (!forge_get_config('sendmail_path')){
$sys_sendmail_path="/usr/sbin/sendmail";
}
$handle = popen(forge_get_config('sendmail_path')." -f'$from' -t -i", 'w');
fwrite ($handle, $body2);
pclose($handle);
}
/**
* util_encode_mailaddr() - Encode email address to MIME format
*
* @param string The email address
* @param string The email's owner name
* @param string The converting charset
*
*/
function util_encode_mailaddr($email,$name,$charset) {
if (function_exists('mb_convert_encoding') && trim($name) != "") {
$name = "=?".$charset."?B?".
base64_encode(mb_convert_encoding(
$name,$charset,"UTF-8")).
"?=";
}
return $name." <".$email."> ";
}
/**
* util_encode_mimeheader() - Encode mimeheader
*
* @param string The name of the header (e.g. "Subject")
* @param string The email subject
* @param string The converting charset (like ISO-2022-JP)
* @return string The MIME encoded subject
*
*/
function util_encode_mimeheader($headername,$str,$charset) {
if (function_exists('mb_internal_encoding') &&
function_exists('mb_encode_mimeheader')) {
$x = mb_internal_encoding();
mb_internal_encoding("UTF-8");
$y = mb_encode_mimeheader($headername . ": " . $str,
$charset, "Q");
mb_internal_encoding($x);
return $y;
}
if (!function_exists('mb_convert_encoding')) {
return $headername . ": " . $str;
}
return $headername . ": " . "=?".$charset."?B?".
base64_encode(mb_convert_encoding(
$str,$charset,"UTF-8")).
"?=";
}
/**
* util_convert_body() - Convert body of the email message
*
* @param string The body of the email message
* @param string The charset of the email message
* @return string The converted body of the email message
*
*/
function util_convert_body($str,$charset) {
if (!function_exists('mb_convert_encoding') || $charset == 'UTF-8') {
return $str;
}
return mb_convert_encoding($str,$charset,"UTF-8");
}
function util_send_jabber($to,$subject,$body) {
if (!forge_get_config('use_jabber')) {
return;
}
$JABBER = new Jabber();
if (!$JABBER->Connect()) {
echo ' Unable to connect';
return false;
}
//$JABBER->SendAuth();
//$JABBER->AccountRegistration();
if (!$JABBER->SendAuth()) {
echo ' Auth Failure';
$JABBER->Disconnect();
return false;
//or die("Couldn't authenticate!");
}
$JABBER->SendPresence(NULL, NULL, "online");
$body=htmlspecialchars($body);
$to_arr=explode(',',$to);
for ($i=0; $iSending Jabbers To: '.$to_arr[$i];
if (!$JABBER->SendMessage($to_arr[$i], "normal", NULL, array("body" => $body,"subject"=>$subject))) {
echo ' Error Sending to '.$to_arr[$i];
}
}
}
$JABBER->CruiseControl(2);
$JABBER->Disconnect();
}
/**
* util_handle_message() - a convenience wrapper which sends messages
* to either a jabber account or email account or both, depending on
* user preferences
*
* @param array array of user_id's from the user table
* @param string subject of the message
* @param string the message body
* @param string a comma-separated list of email address
* @param string a comma-separated list of jabber address
* @param string From header
*/
function util_handle_message($id_arr,$subject,$body,$extra_emails='',$extra_jabbers='',$from='') {
$address=array();
if (count($id_arr) < 1) {
} else {
$res = db_query_params ('SELECT user_id,jabber_address,email,jabber_only FROM users WHERE user_id = ANY ($1)',
array (db_int_array_to_any_clause ($id_arr))) ;
$rows = db_numrows($res) ;
for ($i=0; $i<$rows; $i++) {
if (db_result($res, $i, 'user_id') == 100) {
// Do not send messages to "Nobody"
continue;
}
//
// Build arrays of the jabber address
//
if (db_result($res,$i,'jabber_address')) {
$address['jabber_address'][]=db_result($res,$i,'jabber_address');
if (db_result($res,$i,'jabber_only') != 1) {
$address['email'][]=db_result($res,$i,'email');
}
} else {
$address['email'][]=db_result($res,$i,'email');
}
}
if (isset ($address['email']) && count($address['email']) > 0) {
$extra_emails=implode($address['email'],',').',' . $extra_emails;
}
if (isset ($address['jabber_address']) && count($address['jabber_address']) > 0) {
$extra_jabbers=implode($address['jabber_address'],',').','.$extra_jabbers;
}
}
if ($extra_emails) {
util_send_message('',$subject,$body,$from,$extra_emails);
}
if ($extra_jabbers) {
util_send_jabber($extra_jabbers,$subject,$body);
}
}
/**
* util_unconvert_htmlspecialchars() - Unconverts a string converted with htmlspecialchars()
*
* @param string The string to unconvert
* @returns The unconverted string
*
*/
function util_unconvert_htmlspecialchars($string) {
return html_entity_decode($string, ENT_QUOTES, "UTF-8");
}
/**
* util_result_columns_to_assoc() - Takes a result set and turns the column pair into an associative array
*
* @param string The result set ID
* @param int The column key
* @param int The optional column value
* @returns An associative array
*
*/
function util_result_columns_to_assoc($result, $col_key=0, $col_val=1) {
$rows=db_numrows($result);
if ($rows > 0) {
$arr=array();
for ($i=0; $i<$rows; $i++) {
$arr[db_result($result,$i,$col_key)]=db_result($result,$i,$col_val);
}
} else {
$arr=array();
}
return $arr;
}
/**
* util_result_column_to_array() - Takes a result set and turns the optional column into an array
*
* @param int The result set ID
* @param int The column
* @resturns An array
*
*/
function &util_result_column_to_array($result, $col=0) {
/*
Takes a result set and turns the optional column into
an array
*/
$rows=db_numrows($result);
if ($rows > 0) {
$arr=array();
for ($i=0; $i<$rows; $i++) {
$arr[$i]=db_result($result,$i,$col);
}
} else {
$arr=array();
}
return $arr;
}
/**
* util_line_wrap() - Automatically linewrap text
*
* @param string The text to wrap
* @param int The number of characters to wrap - Default is 80
* @param string The line break to use - Default is '\n'
* @returns The wrapped text
*
*/
function util_line_wrap ($text, $wrap = 80, $break = "\n") {
return wordwrap($text, $wrap, $break, false);
}
/**
* util_make_links() - Turn URL's into HREF's.
*
* @param string The URL
* @returns The HREF'ed URL
*
*/
function util_make_links($data='') {
if(empty($data)) {
return $data;
}
$withPattern = 0;
for ($i = 0; $i < 5; $i++) {
$randPattern = rand(10000, 30000);
if (! preg_match("/$randPattern/", $data)) {
$withPattern = 1;
break;
}
}
if ($withPattern) {
/*
while(preg_match('/]*>[^<]*<\/a>/i', $data, $part)) {
$mem[] = $part[0];
$data = preg_replace('/]*>[^<]*<\/a>/i', $randPattern, $data, 1);
}
*/
$mem = array();
while(preg_match('/]*>.*<\/a>/siU', $data, $part)) {
$mem[] = $part[0];
$data = preg_replace('/]*>.*<\/a>/siU', $randPattern, $data, 1);
}
while(preg_match('/]*\/>/siU', $data, $part)) {
$mem[] = $part[0];
$data = preg_replace('/]*\/>/siU', $randPattern, $data, 1);
}
$data = str_replace('>', "\1", $data);
$data = preg_replace("#([ \t]|^)www\.#i"," http://www.",$data);
$data = preg_replace("#([[:alnum:]]+)://([^[:space:]<\1]*)([[:alnum:]\#?/&=])#i", "\\1://\\2\\3", $data);
$data = preg_replace("#([[:space:]]|^)(([a-z0-9_]|\\-|\\.)+@([^[:space:]<\1]*)([[:alnum:]-]))#i", "\\1\\2", $data);
$data = str_replace("\1", '>', $data);
for ($i = 0; $i < count($mem); $i++) {
$data = preg_replace("/$randPattern/", $mem[$i], $data, 1);
}
return($data);
}
$lines = split("\n",$data);
$newText = "";
while ( list ($key, $line) = each ($lines)) {
// Do not scan lines if they already have hyperlinks.
// Avoid problem with text written with an WYSIWYG HTML editor.
if (eregi(']*)>.*', $line, $linePart)) {
if (eregi('href="[^"]*"', $linePart[1])) {
$newText .= $line;
continue;
}
}
// Skip tag also
if (eregi(']*)/>', $line, $linePart)) {
if (eregi('href="[^"]*"', $linePart[1])) {
$newText .= $line;
continue;
}
}
// When we come here, we usually have form input
// encoded in entities. Our aim is to NOT include
// angle brackets in the URL
// (RFC2396; http://www.w3.org/Addressing/URL/5.1_Wrappers.html)
$line = str_replace('>', "\1", $line);
$line = preg_replace("/([ \t]|^)www\./i", " http://www.", $line);
$line = preg_replace("/([[:alnum:]]+):\/\/([^[:space:]<\1]*)([[:alnum:]#?\/&=])/i",
"\\1://\\2\\3", $line);
$line = preg_replace(
"/([[:space:]]|^)(([a-z0-9_]|\\-|\\.)+@([^[:space:]]*)([[:alnum:]-]))/i",
"\\1\\2",
$line
);
$line = str_replace("\1", '>', $line);
$newText .= $line;
}
return $newText;
}
/**
* show_priority_colors_key() - Show the priority colors legend
*
*/
function show_priority_colors_key() {
echo '
';
}
/**
* utils_buildcheckboxarray() - Build a checkbox array
*
* @param int Number of options to be in the array
* @param string The name of the checkboxes
* @param array An array of boxes to be pre-checked
*
*/
function utils_buildcheckboxarray($options, $name, $checked_array) {
$option_count = count($options);
$checked_count = count($checked_array);
for ($i = 1; $i <= $option_count; $i++) {
echo '
'.$options[$i];
}
}
/**
* utils_requiredField() - Adds the required field marker
*
* @return a string holding the HTML to mark a required field
*/
function utils_requiredField() {
return '*';
}
/**
* GraphResult() - Takes a database result set and builds a graph.
* The first column should be the name, and the second column should be the values
* Be sure to include HTL_Graphs.php before using this function
*
* @author Tim Perdue tperdue@valinux.com
* @param int The databse result set ID
* @param string The title of the graph
*
*/
function GraphResult($result, $title) {
$rows = db_numrows($result);
if ((!$result) || ($rows < 1)) {
echo 'None Found.';
} else {
$names = array();
$values = array();
for ($j=0; $j < db_numrows($result); $j++) {
if (db_result($result, $j, 0) != '' && db_result($result, $j, 1) != '' ) {
$names[$j] = db_result($result, $j, 0);
$values[$j] = db_result($result, $j, 1);
}
}
/*
This is another function detailed below
*/
GraphIt($names, $values, $title);
}
}
/**
* GraphIt() - Build a graph
*
* @author Tim Perdue tperdue@valinux.com
* @param array An array of names
* @param array An array of values
* @param string The title of the graph
*
*/
function GraphIt($name_string, $value_string, $title) {
global $HTML;
$counter = count($name_string);
/*
Can choose any color you wish
*/
$bars = array();
for ($i = 0; $i < $counter; $i++) {
$bars[$i] = $HTML->COLOR_LTBACK1;
}
$counter = count($value_string);
/*
Figure the max_value passed in, so scale can be determined
*/
$max_value = 0;
for ($i = 0; $i < $counter; $i++) {
if ($value_string[$i] > $max_value) {
$max_value = $value_string[$i];
}
}
if ($max_value < 1) {
$max_value = 1;
}
/*
I want my graphs all to be 800 pixels wide, so that is my divisor
*/
$scale = (400/$max_value);
/*
I create a wrapper table around the graph that holds the title
*/
$title_arr = array();
$title_arr[] = $title;
echo $GLOBALS['HTML']->listTableTop ($title_arr);
echo '
';
/*
Create an associate array to pass in. I leave most of it blank
*/
$vals = array(
'vlabel'=>'',
'hlabel'=>'',
'type'=>'',
'cellpadding'=>'',
'cellspacing'=>'0',
'border'=>'',
'width'=>'',
'background'=>'',
'vfcolor'=>'',
'hfcolor'=>'',
'vbgcolor'=>'',
'hbgcolor'=>'',
'vfstyle'=>'',
'hfstyle'=>'',
'noshowvals'=>'',
'scale'=>$scale,
'namebgcolor'=>'',
'valuebgcolor'=>'',
'namefcolor'=>'',
'valuefcolor'=>'',
'namefstyle'=>'',
'valuefstyle'=>'',
'doublefcolor'=>'');
/*
This is the actual call to the HTML_Graphs class
*/
html_graph($name_string, $value_string, $bars, $vals);
echo '
';
echo $GLOBALS['HTML']->listTableBottom();
}
/**
* ShowResultSet() - Show a generic result set
* Very simple, plain way to show a generic result set
*
* @param int The result set ID
* @param string The title of the result set
* @param bool The option to turn URL's into links
* @param bool The option to display headers
* @param array The db field name -> label mapping
* @param array Don't display these cols
*
*/
function ShowResultSet($result,$title='',$linkify=false,$displayHeaders=true,$headerMapping=array(), $excludedCols=array()) {
global $group_id,$HTML;
if($result) {
$rows = db_numrows($result);
$cols = db_numfields($result);
echo '
")));
}
/* takes a string and returns it HTML encoded, URIs made to hrefs */
function util_uri_grabber($unencoded_string, $tryaidtid=false) {
/* escape all ^A and ^B as ^BX^B and ^BY^B, respectively */
$s = str_replace("\x01", "\x02X\x02", str_replace("\x02", "\x02Y\x02",
$unencoded_string));
/* replace all URIs with ^AURI^A */
$s = preg_replace(
'|([a-zA-Z][a-zA-Z0-9+.-]*:[#0-9a-zA-Z;/?:@&=+$,_.!~*\'()%-]+)|',
"\x01\$1\x01", $s);
if (!$s)
return htmlentities($unencoded_string, ENT_QUOTES, "UTF-8");
/* encode the string */
$s = htmlentities($s, ENT_QUOTES, "UTF-8");
/* convert 「^Afoo^A」 to 「foo」 */
$s = preg_replace('|\x01([^\x01]+)\x01|',
'$1', $s);
if (!$s)
return htmlentities($unencoded_string, ENT_QUOTES, "UTF-8");
// /* convert [#123] to links if found */
// if ($tryaidtid)
// $s = util_tasktracker_links($s);
/* convert ^BX^B and ^BY^B back to ^A and ^B, respectively */
$s = str_replace("\x02Y\x02", "\x02", str_replace("\x02X\x02", "\x01",
$s));
/* return the final result */
return $s;
}
function util_html_encode($s) {
return htmlspecialchars($s, ENT_QUOTES, "UTF-8");
}
/* secure a (possibly already HTML encoded) string */
function util_html_secure($s) {
return util_html_encode(util_unconvert_htmlspecialchars($s));
}
/* return integral value (ℕ₀) of passed string if it matches, or false */
function util_nat0(&$s) {
if (!isset($s)) {
/* unset variable */
return false;
}
if (is_array($s)) {
if (count($s) == 1) {
/* one-element array */
return util_nat0($s[0]);
}
/* not one element, or element not at [0] */
return false;
}
if (!is_numeric($s)) {
/* not numeric */
return false;
}
$num = (int)$s;
if ($num >= 0) {
/* number element of ℕ₀ */
$text = (string)$num;
if ($text == $s) {
/* number matches its textual representation */
return ($num);
}
/* doesn't match, like 0123 or 1.2 or " 1" */
}
/* or negative */
return false;
}
/**
* util_negociate_alternate_content_types() - Manage content-type negociation based on 'script_accepted_types' hooks
* @param string $script
* @param string $default_content_type
* @param string $forced_content_type
* @return string
*/
function util_negociate_alternate_content_types($script, $default_content_type, $forced_content_type=false) {
$content_type = $default_content_type;
// we can force the content-type to be returned automaticall if necessary
if ($forced_content_type) {
// TODO ideally, in this case we could try and apply the negociation to see if it matches
// one provided by the hooks, but negotiateMimeType() doesn't allow this so for the moment,
// we just force it whatever the hooks support
$content_type = $forced_content_type;
}
else {
// Invoke plugins' hooks 'script_accepted_types' to discover which alternate content types they would accept for /users/...
$hook_params = array();
$hook_params['script'] = $script;
$hook_params['accepted_types'] = array();
plugin_hook_by_reference('script_accepted_types', $hook_params);
if (count($hook_params['accepted_types'])) {
// By default, text/html is accepted
$accepted_types = array($default_content_type);
$new_accepted_types = $hook_params['accepted_types'];
$accepted_types = array_merge($accepted_types, $new_accepted_types);
// PEAR::HTTP (for negotiateMimeType())
require_once 'HTTP.php';
// negociate accepted content-type depending on the preferred ones declared by client
$http=new HTTP();
$content_type = $http->negotiateMimeType($accepted_types, false);
}
}
return $content_type;
}
/**
* util_gethref() - Construct a hypertext reference
*
* @param string $baseurl
* (optional) base URL (absolute or relative);
* urlencoded, but not htmlencoded
* (default (falsy): PHP_SELF)
* @param array $args
* (optional) associative array of unencoded query parameters;
* false values are ignored
* @param bool $ashtml
* (optional) htmlencode the result?
* (default: true)
* @param string $sep
* (optional) argument separator ('&' or ';')
* (default: '&')
* @return string
* URL, possibly htmlencoded
*/
function util_gethref($baseurl=false, $args=array(), $ashtml=true, $sep='&') {
$rv = $baseurl ? $baseurl : getStringFromServer('PHP_SELF');
$pfx = '?';
foreach ($args as $k => $v) {
if ($v === false) {
continue;
}
$rv .= $pfx . urlencode($k) . '=' . urlencode($v);
$pfx = $sep;
}
return ($ashtml ? util_html_encode($rv) : $rv);
}
/**
* util_sanitise_multiline_submission() – Convert text to ASCII CR-LF
*
* @param string $text
* input string to sanitise
* @return string
* sanitised string: CR, LF or CR-LF converted to CR-LF
*/
function util_sanitise_multiline_submission($text) {
/* convert all CR-LF into LF */
$text = preg_replace("/\015+\012+/m", "\012", $text);
/* convert all CR or LF into CR-LF */
$text = preg_replace("/[\012\015]/m", "\015\012", $text);
return $text;
}
// Local Variables:
// mode: php
// c-file-style: "bsd"
// End: