| | |
| | | +-----------------------------------------------------------------------+ |
| | | | program/steps/mail/func.inc | |
| | | | | |
| | | | This file is part of the RoundCube Webmail client | |
| | | | Copyright (C) 2005-2010, RoundCube Dev. - Switzerland | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 2005-2010, Roundcube Dev. - Switzerland | |
| | | | Licensed under the GNU GPL | |
| | | | | |
| | | | PURPOSE: | |
| | |
| | | |
| | | $EMAIL_ADDRESS_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9][a-z0-9\-\.]*\\.[a-z]{2,5})'; |
| | | |
| | | // actions that do not require imap connection |
| | | $NOIMAP_ACTIONS = array('addcontact', 'autocomplete', 'upload', 'display-attachment', 'remove-attachment'); |
| | | // actions that do not require imap connection here |
| | | $NOIMAP_ACTIONS = array('addcontact', 'autocomplete', 'upload', 'display-attachment', 'remove-attachment', 'get'); |
| | | |
| | | // always instantiate imap object (but not yet connect to server) |
| | | $RCMAIL->imap_init(); |
| | |
| | | && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false) |
| | | $a_show_cols[$f] = 'to'; |
| | | |
| | | // make sure 'threads' column is present |
| | | // make sure 'threads' and 'subject' columns are present |
| | | if (!in_array('subject', $a_show_cols)) |
| | | array_unshift($a_show_cols, 'subject'); |
| | | if (!in_array('threads', $a_show_cols)) |
| | | array_unshift($a_show_cols, 'threads'); |
| | | |
| | |
| | | |
| | | /** |
| | | * return javascript commands to add rows to the message list |
| | | * or to replace the whole list (IE only) |
| | | */ |
| | | function rcmail_js_message_list($a_headers, $insert_top=FALSE, $head_replace=FALSE) |
| | | function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null) |
| | | { |
| | | global $CONFIG, $IMAP, $RCMAIL, $OUTPUT; |
| | | |
| | | if (!empty($_SESSION['list_attrib']['columns'])) |
| | | $a_show_cols = $_SESSION['list_attrib']['columns']; |
| | | else |
| | | $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject'); |
| | | if (empty($a_show_cols)) { |
| | | if (!empty($_SESSION['list_attrib']['columns'])) |
| | | $a_show_cols = $_SESSION['list_attrib']['columns']; |
| | | else |
| | | $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject'); |
| | | } |
| | | else { |
| | | if (!is_array($a_show_cols)) |
| | | $a_show_cols = preg_split('/[\s,;]+/', strip_quotes($a_show_cols)); |
| | | $head_replace = true; |
| | | } |
| | | |
| | | $mbox = $IMAP->get_mailbox_name(); |
| | | $delim = $IMAP->get_hierarchy_delimiter(); |
| | | |
| | | // make sure 'threads' and 'subject' columns are present |
| | | if (!in_array('subject', $a_show_cols)) |
| | | array_unshift($a_show_cols, 'subject'); |
| | | if (!in_array('threads', $a_show_cols)) |
| | | array_unshift($a_show_cols, 'threads'); |
| | | |
| | | $_SESSION['list_attrib']['columns'] = $a_show_cols; |
| | | |
| | | // show 'to' instead of 'from' in sent/draft messages |
| | | if ((strpos($mbox.$delim, $CONFIG['sent_mbox'].$delim)===0 || strpos($mbox.$delim, $CONFIG['drafts_mbox'].$delim)===0) |
| | | && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false) |
| | | $a_show_cols[$f] = 'to'; |
| | | |
| | | // make sure 'threads' column is present |
| | | if (!in_array('threads', $a_show_cols)) |
| | | array_unshift($a_show_cols, 'threads'); |
| | | // Make sure there are no duplicated columns (#1486999) |
| | | $a_show_cols = array_unique($a_show_cols); |
| | | |
| | | // Plugins may set header's list_cols/list_flags and other rcube_mail_header variables |
| | | // and list columns |
| | |
| | | ); |
| | | $html = preg_replace($html_search, $html_replace, $html); |
| | | |
| | | // PCRE errors handling (#1486856), should we use something like for every preg_* use? |
| | | if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) { |
| | | $errstr = "Could not clean up HTML message! PCRE Error: $preg_error."; |
| | | |
| | | if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) |
| | | $errstr .= " Consider raising pcre.backtrack_limit!"; |
| | | if ($preg_error == PREG_RECURSION_LIMIT_ERROR) |
| | | $errstr .= " Consider raising pcre.recursion_limit!"; |
| | | |
| | | raise_error(array('code' => 600, 'type' => 'php', |
| | | 'line' => __LINE__, 'file' => __FILE__, |
| | | 'message' => $errstr), true, false); |
| | | return ''; |
| | | } |
| | | |
| | | // fix (unknown/malformed) HTML tags before "wash" |
| | | $html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', 'rcmail_html_tag_callback', $html); |
| | | |
| | |
| | | |
| | | // plaintext postprocessing |
| | | if ($part->ctype_secondary == 'plain') |
| | | $body = rcmail_plain_body($body); |
| | | $body = rcmail_plain_body($body, $part->ctype_parameters['format'] == 'flowed'); |
| | | |
| | | // allow post-processing of the message body |
| | | $data = $RCMAIL->plugins->exec_hook('message_part_after', array('type' => $part->ctype_secondary, 'body' => $body) + $data); |
| | |
| | | /** |
| | | * Handle links and citation marks in plain text message |
| | | * |
| | | * @param string Plain text string |
| | | * @param string Plain text string |
| | | * @param boolean Text uses format=flowed |
| | | * |
| | | * @return string Formatted HTML string |
| | | */ |
| | | function rcmail_plain_body($body) |
| | | function rcmail_plain_body($body, $flowed=false) |
| | | { |
| | | global $RCMAIL; |
| | | |
| | | // make links and email-addresses clickable |
| | | $replacements = new rcube_string_replacer; |
| | | $replacer = new rcube_string_replacer; |
| | | |
| | | // search for patterns like links and e-mail addresses |
| | | $body = preg_replace_callback($replacements->link_pattern, array($replacements, 'link_callback'), $body); |
| | | $body = preg_replace_callback($replacements->mailto_pattern, array($replacements, 'mailto_callback'), $body); |
| | | $body = preg_replace_callback($replacer->link_pattern, array($replacer, 'link_callback'), $body); |
| | | $body = preg_replace_callback($replacer->mailto_pattern, array($replacer, 'mailto_callback'), $body); |
| | | |
| | | // split body into single lines |
| | | $a_lines = preg_split('/\r?\n/', $body); |
| | | $q_lines = array(); |
| | | $quote_level = 0; |
| | | $last = -1; |
| | | |
| | | // find/mark quoted lines... |
| | | for ($n=0, $cnt=count($a_lines); $n < $cnt; $n++) { |
| | | $q = 0; |
| | | |
| | | if ($a_lines[$n][0] == '>' && preg_match('/^(>+\s*)+/', $a_lines[$n], $regs)) { |
| | | $q = strlen(preg_replace('/\s/', '', $regs[0])); |
| | | $a_lines[$n] = substr($a_lines[$n], strlen($regs[0])); |
| | | $a_lines[$n] = substr($a_lines[$n], strlen($regs[0])); |
| | | |
| | | if ($q > $quote_level) |
| | | $q_lines[$n]['quote'] = $q - $quote_level; |
| | | $a_lines[$n] = $replacer->get_replacement($replacer->add( |
| | | str_repeat('<blockquote>', $q - $quote_level))) . $a_lines[$n]; |
| | | else if ($q < $quote_level) |
| | | $q_lines[$n]['endquote'] = $quote_level - $q; |
| | | $a_lines[$n] = $replacer->get_replacement($replacer->add( |
| | | str_repeat('</blockquote>', $quote_level - $q))) . $a_lines[$n]; |
| | | else if ($flowed) { |
| | | // previous line is flowed |
| | | if (isset($a_lines[$last]) && $a_lines[$n] |
| | | && $a_lines[$last][strlen($a_lines[$last])-1] == ' ') { |
| | | // merge lines |
| | | $a_lines[$last] .= $a_lines[$n]; |
| | | unset($a_lines[$n]); |
| | | } |
| | | else |
| | | $last = $n; |
| | | } |
| | | } |
| | | else if ($quote_level > 0) |
| | | $q_lines[$n]['endquote'] = $quote_level; |
| | | else { |
| | | $q = 0; |
| | | if ($flowed) { |
| | | // sig separator - line is fixed |
| | | if ($a_lines[$n] == '-- ') { |
| | | $last = $n; |
| | | } |
| | | else { |
| | | // remove space-stuffing |
| | | if ($a_lines[$n][0] == ' ') |
| | | $a_lines[$n] = substr($a_lines[$n], 1); |
| | | |
| | | // previous line is flowed? |
| | | if (isset($a_lines[$last]) && $a_lines[$n] |
| | | && $a_lines[$last] != '-- ' |
| | | && $a_lines[$last][strlen($a_lines[$last])-1] == ' ' |
| | | ) { |
| | | $a_lines[$last] .= $a_lines[$n]; |
| | | unset($a_lines[$n]); |
| | | } |
| | | else { |
| | | $last = $n; |
| | | } |
| | | } |
| | | if ($quote_level > 0) |
| | | $a_lines[$last] = $replacer->get_replacement($replacer->add( |
| | | str_repeat('</blockquote>', $quote_level))) . $a_lines[$last]; |
| | | } |
| | | else if ($quote_level > 0) |
| | | $a_lines[$n] = $replacer->get_replacement($replacer->add( |
| | | str_repeat('</blockquote>', $quote_level))) . $a_lines[$n]; |
| | | } |
| | | |
| | | $quote_level = $q; |
| | | } |
| | | |
| | | // quote plain text |
| | | $body = Q(join("\n", $a_lines), 'replace', false); |
| | | $body = Q(join("\n", $a_lines), '', false); |
| | | |
| | | // colorize signature |
| | | if (($sp = strrpos($body, '-- ')) !== false) |
| | | if (($sp == 0 || $body[$sp-1] == "\n") && $body[$sp+3] == "\n") { |
| | | $body = substr($body, 0, max(0, $sp)) |
| | | .'<span class="sig">'.substr($body, $sp).'</span>'; |
| | | $len = strlen($body); |
| | | while (($sp = strrpos($body, "-- \n", $sp ? -$len+$sp-1 : 0)) !== false) { |
| | | if ($sp == 0 || $body[$sp-1] == "\n") { |
| | | // do not touch blocks with more that X lines |
| | | if (substr_count($body, "\n", $sp) < $RCMAIL->config->get('sig_max_lines', 15)) |
| | | $body = substr($body, 0, max(0, $sp)) |
| | | .'<span class="sig">'.substr($body, $sp).'</span>'; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | // colorize quoted lines |
| | | $a_lines = preg_split('/\n/', $body); |
| | | foreach ($q_lines as $i => $q) |
| | | if ($q['quote']) |
| | | $a_lines[$i] = str_repeat('<blockquote>', $q['quote']) . $a_lines[$i]; |
| | | else if ($q['endquote']) |
| | | $a_lines[$i] = str_repeat('</blockquote>', $q['endquote']) . $a_lines[$i]; |
| | | // insert url/mailto links and citation tags |
| | | $body = $replacer->resolve($body); |
| | | |
| | | // insert the links for urls and mailtos |
| | | $body = $replacements->resolve(join("\n", $a_lines)); |
| | | |
| | | return $body; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * add a string to the replacement array and return a replacement string |
| | | */ |
| | | function rcmail_str_replacement($str, &$rep) |
| | | { |
| | | static $count = 0; |
| | | $rep[$count] = stripslashes($str); |
| | | return "##string_replacement{".($count++)."}##"; |
| | | } |
| | | |
| | | |
| | |
| | | */ |
| | | function rcmail_message_body($attrib) |
| | | { |
| | | global $CONFIG, $OUTPUT, $MESSAGE, $IMAP, $REMOTE_OBJECTS; |
| | | global $CONFIG, $OUTPUT, $MESSAGE, $IMAP, $RCMAIL, $REMOTE_OBJECTS; |
| | | |
| | | if (!is_array($MESSAGE->parts) && empty($MESSAGE->body)) |
| | | return ''; |
| | |
| | | rcmail_message_error($MESSAGE->uid); |
| | | } |
| | | |
| | | // re-format format=flowed content |
| | | if ($part->ctype_secondary == "plain" && $part->ctype_parameters['format'] == "flowed") |
| | | $part->body = rcube_message::unfold_flowed($part->body); |
| | | $plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array( |
| | | 'part' => $part, 'prefix' => '')); |
| | | |
| | | $body = rcmail_print_body($part, array('safe' => $safe_mode, 'plain' => !$CONFIG['prefer_html'])); |
| | | |
| | | if ($part->ctype_secondary == 'html') |
| | | $out .= html::div('message-htmlpart', rcmail_html4inline($body, $attrib['id'], 'div.rcmBody')); |
| | | if ($part->ctype_secondary == 'html') { |
| | | $body = rcmail_html4inline($body, $attrib['id'], 'rcmBody', $attrs); |
| | | $div_attr = array('class' => 'message-htmlpart'); |
| | | $style = array(); |
| | | |
| | | if (!empty($attrs)) { |
| | | foreach ($attrs as $a_idx => $a_val) |
| | | $style[] = $a_idx . ': ' . $a_val; |
| | | if (!empty($style)) |
| | | $div_attr['style'] = implode('; ', $style); |
| | | } |
| | | |
| | | $out .= html::div($div_attr, $plugin['prefix'] . $body); |
| | | } |
| | | else |
| | | $out .= html::div('message-part', $body); |
| | | $out .= html::div('message-part', $plugin['prefix'] . $body); |
| | | } |
| | | } |
| | | } |
| | | else |
| | | $out .= html::div('message-part', html::tag('pre', array(), |
| | | else { |
| | | $plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array( |
| | | 'part' => $MESSAGE, 'prefix' => '')); |
| | | |
| | | $out .= html::div('message-part', $plugin['prefix'] . html::tag('pre', array(), |
| | | rcmail_plain_body(Q($MESSAGE->body, 'strict', false)))); |
| | | } |
| | | |
| | | $ctype_primary = strtolower($MESSAGE->structure->ctype_primary); |
| | | $ctype_secondary = strtolower($MESSAGE->structure->ctype_secondary); |
| | |
| | | // list images after mail body |
| | | if ($CONFIG['inline_images'] |
| | | && $ctype_primary == 'multipart' |
| | | && !empty($MESSAGE->attachments) |
| | | && !strstr($message_body, '<html')) |
| | | && !empty($MESSAGE->attachments)) |
| | | { |
| | | foreach ($MESSAGE->attachments as $attach_prop) { |
| | | if (strpos($attach_prop->mimetype, 'image/') === 0) { |
| | | // Content-Type: image/*... |
| | | if (preg_match('/^image\//i', $attach_prop->mimetype) || |
| | | // ...or known file extension: many clients are using application/octet-stream |
| | | ($attach_prop->filename && |
| | | preg_match('/^application\/octet-stream$/i', $attach_prop->mimetype) && |
| | | preg_match('/\.(jpg|jpeg|png|gif|bmp)$/i', $attach_prop->filename)) |
| | | ) { |
| | | $out .= html::tag('hr') . html::p(array('align' => "center"), |
| | | html::img(array( |
| | | 'src' => $MESSAGE->get_part_url($attach_prop->mime_id), |
| | |
| | | /** |
| | | * modify a HTML message that it can be displayed inside a HTML page |
| | | */ |
| | | function rcmail_html4inline($body, $container_id, $body_id='') |
| | | { |
| | | function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null) |
| | | { |
| | | $last_style_pos = 0; |
| | | $body_lc = strtolower($body); |
| | | $cont_id = $container_id.($body_id ? ' div.'.$body_id : ''); |
| | | |
| | | // find STYLE tags |
| | | while (($pos = strpos($body_lc, '<style', $last_style_pos)) && ($pos2 = strpos($body_lc, '</style>', $pos))) |
| | | { |
| | | { |
| | | $pos = strpos($body_lc, '>', $pos)+1; |
| | | |
| | | // replace all css definitions with #container [def] |
| | | $styles = rcmail_mod_css_styles(substr($body, $pos, $pos2-$pos), $container_id.($body_id ? ' '.$body_id : '')); |
| | | $styles = rcmail_mod_css_styles( |
| | | substr($body, $pos, $pos2-$pos), $cont_id); |
| | | |
| | | $body = substr($body, 0, $pos) . $styles . substr($body, $pos2); |
| | | $body_lc = strtolower($body); |
| | | $last_style_pos = $pos2; |
| | | } |
| | | } |
| | | |
| | | // modify HTML links to open a new window if clicked |
| | | $GLOBALS['rcmail_html_container_id'] = $container_id; |
| | | $body = preg_replace_callback('/<(a|link)\s+([^>]+)>/Ui', 'rcmail_alter_html_link', $body); |
| | | unset($GLOBALS['rcmail_html_container_id']); |
| | | |
| | | $out = preg_replace(array( |
| | | $body = preg_replace(array( |
| | | // add comments arround html and other tags |
| | | '/(<!DOCTYPE[^>]*>)/i', |
| | | '/(<\?xml[^>]*>)/i', |
| | |
| | | '<!--\\1-->', |
| | | '<?', |
| | | '?>', |
| | | '<div class="rcmBody"\\1>', |
| | | '<div class="'.$body_id.'"\\1>', |
| | | '</div>', |
| | | ), |
| | | $body); |
| | | |
| | | $attributes = array(); |
| | | |
| | | // Handle body attributes that doesn't play nicely with div elements |
| | | if (preg_match('/<div class="' . preg_quote($body_id, '/') . '" ([^>]+)/', $body, $m)) { |
| | | $attrs = $m[0]; |
| | | // Get bgcolor, we'll set it as background-color of the message container |
| | | if (preg_match('/bgcolor=["\']*([a-z0-9#]+)["\']*/', $attrs, $mb)) { |
| | | $attributes['background-color'] = $mb[1]; |
| | | $attrs = preg_replace('/bgcolor=["\']*([a-z0-9#]+)["\']*/', '', $attrs); |
| | | } |
| | | // Get background, we'll set it as background-image of the message container |
| | | if (preg_match('/background=["\']*([^"\'>\s]+)["\']*/', $attrs, $mb)) { |
| | | $attributes['background-image'] = 'url('.$mb[1].')'; |
| | | $attrs = preg_replace('/background=["\']*([^"\'>\s]+)["\']*/', '', $attrs); |
| | | } |
| | | if (!empty($attributes)) |
| | | $body = preg_replace('/<div class="' . preg_quote($body_id, '/') . '" [^>]+/', rtrim($attrs), $body, 1); |
| | | |
| | | // handle body styles related to background image |
| | | if ($attributes['background-image']) { |
| | | // get body style |
| | | if (preg_match('/#'.preg_quote($cont_id, '/').'\s+\{([^}]+)}/i', $body, $m)) { |
| | | // get background related style |
| | | if (preg_match_all('/(background-position|background-repeat)\s*:\s*([^;]+);/i', $m[1], $ma, PREG_SET_ORDER)) { |
| | | foreach ($ma as $style) |
| | | $attributes[$style[1]] = $style[2]; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | // make sure there's 'rcmBody' div, we need it for proper css modification |
| | | // its name is hardcoded in rcmail_message_body() also |
| | | if (!preg_match('/<div class="rcmBody"/', $out)) |
| | | $out = '<div class="rcmBody">' . $out . '</div>'; |
| | | else |
| | | $body = '<div class="' . $body_id . '">' . $body . '</div>'; |
| | | |
| | | return $out; |
| | | } |
| | | return $body; |
| | | } |
| | | |
| | | |
| | | /** |
| | |
| | | return; |
| | | |
| | | $rcmail = rcmail::get_instance(); |
| | | $rcmail->plugins->exec_hook('cleanup_attachments',array()); |
| | | $rcmail->plugins->exec_hook('attachments_cleanup', array()); |
| | | $rcmail->session->remove('compose'); |
| | | } |
| | | |
| | |
| | | // remove MDN headers after sending |
| | | unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']); |
| | | |
| | | // get all recipients |
| | | if ($headers['Cc']) |
| | | $mailto .= $headers['Cc']; |
| | | if ($headers['Bcc']) |
| | | $mailto .= $headers['Bcc']; |
| | | if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m)) |
| | | $mailto = implode(', ', array_unique($m[1])); |
| | | |
| | | if ($CONFIG['smtp_log']) { |
| | | write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s", |
| | | $RCMAIL->user->get_username(), |
| | |
| | | 'From' => $sender, |
| | | 'To' => $message->headers->mdn_to, |
| | | 'Subject' => rcube_label('receiptread') . ': ' . $message->subject, |
| | | 'Message-ID' => sprintf('<%s@%s>', md5(uniqid('rcmail'.mt_rand(),true)), $RCMAIL->config->mail_domain($_SESSION['imap_host'])), |
| | | 'Message-ID' => rcmail_gen_message_id(), |
| | | 'X-Sender' => $identity['email'], |
| | | 'References' => trim($message->headers->references . ' ' . $message->headers->messageID), |
| | | ); |
| | |
| | | "\t" . rcube_label("sent") . ': ' . format_date($message->headers->date, $RCMAIL->config->get('date_long')) . "\r\n" . |
| | | "\r\n" . rcube_label("receiptnote") . "\r\n"; |
| | | |
| | | $ua = $RCMAIL->config->get('useragent', "RoundCube Webmail (Version ".RCMAIL_VERSION.")"); |
| | | $ua = $RCMAIL->config->get('useragent', "Roundcube Webmail (Version ".RCMAIL_VERSION.")"); |
| | | $report = "Reporting-UA: $ua\r\n"; |
| | | |
| | | if ($message->headers->to) |
| | |
| | | return false; |
| | | } |
| | | |
| | | // Returns unique Message-ID |
| | | function rcmail_gen_message_id() |
| | | { |
| | | global $RCMAIL; |
| | | |
| | | $local_part = md5(uniqid('rcmail'.mt_rand(),true)); |
| | | $domain_part = $RCMAIL->user->get_username('domain'); |
| | | |
| | | // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924) |
| | | if (!preg_match('/\.[a-z]+$/i', $domain_part)) { |
| | | if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST'])) |
| | | && preg_match('/\.[a-z]+$/i', $host)) { |
| | | $domain_part = $host; |
| | | } |
| | | else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME'])) |
| | | && preg_match('/\.[a-z]+$/i', $host)) { |
| | | $domain_part = $host; |
| | | } |
| | | } |
| | | |
| | | return sprintf('<%s@%s>', $local_part, $domain_part); |
| | | } |
| | | |
| | | // Returns RFC2822 formatted current date in user's timezone |
| | | function rcmail_user_date() |
| | | { |
| | |
| | | |
| | | $date = time() + $tz * 60 * 60; |
| | | $date = gmdate('r', $date); |
| | | $date = preg_replace('/[+-][0-9]{4}$/', sprintf('%+05d', $tz * 100), $date); |
| | | $tz = sprintf('%+05d', intval($tz) * 100 + ($tz - intval($tz)) * 60); |
| | | $date = preg_replace('/[+-][0-9]{4}$/', $tz, $date); |
| | | |
| | | return $date; |
| | | } |