Ejemplo n.º 1
0
Archivo: mailer.php Proyecto: rair/yacs
 /**
  * build and transmit a complex e-mail messages
  *
  * This function allows for individual posts, textual and HTML messages, and attached files.
  *
  * For this to work, e-mail service has to be explicitly activated in the
  * main configuration panel, at [script]control/configure.php[/script].
  *
  * You can refer to local images in HTML parts, and the function will automatically attach these
  * to the message, else mail clients would not display them correctly.
  *
  * The message actually sent has a complex structure, with several parts assembled together,
  * as [explained at altepeter.net|http://altepeter.net/tech/articles/html-emails].
  *
  * @link http://altepeter.net/tech/articles/html-emails
  *
  * Several recipients can be provided as a list of addresses separated by
  * commas. For bulk posts, recipients can be transmitted as an array of strings.
  * In all cases, this function sends one separate message per recipient.
  *
  * This function will ensure that only one mail message is send to a recipient,
  * by maintaining an internal list of addresses that have been processed.
  * Therefore, if this function is called several times, with some repeated recipients,
  * those will receive only the first message, and other messages to the same address
  * will be dropped.
  *
  * Bracketed recipients, such as ##Foo Bar <*****@*****.**>##, are handled properly,
  * meaning ##foo@bar.com## is transmitted to the mailing function, while
  * the string ##To: Foo Bar <*****@*****.**>## is added to headers.
  *
  * If an array of messages is provided to the function, it is turned to a multi-part
  * message, as in the following example:
  *
  * [php]
  * $message = array();
  * $message['text/plain; charset=utf-8'] = 'This is a plain message';
  * $message['text/html'] = '<html><head><body>This is an HTML message</body></html>';
  * Mailer::post($from, $to, $subject, $message);
  * [/php]
  *
  * It is recommended to begin with the bare text, and to have the rich format part coming
  * after, as in the example. Also, if you don't provide a charset, then UTF-8 is used.
  *
  * Long lines of text/plain parts are wrapped according to
  * [link=Dan's suggestion]http://mailformat.dan.info/body/linelength.html[/link].
  *
  * @link http://mailformat.dan.info/body/linelength.html Dan's Mail Format Site: Body: Line Length
  *
  * Message parts are encoded either as quoted-printable (textual entities) or as base-64 (others).
  *
  * A list of files to be attached to the message can be provided as in the following example:
  *
  * [php]
  * $attachments = array();
  * $attachments[] = 'special/report.pdf';
  * $attachments[] = 'skins/my_skin/newsletters/image.png';
  * Mailer::post($from, $to, $subject, $message, $attachments);
  * [/php]
  *
  * Files are named from the installation directory of yacs, as visible in the examples.
  *
  * This function returns the number of successful posts,
  * and populates the error context, where applicable.
  *
  * @param string sender address
  * @param mixed recipient address(es)
  * @param string subject
  * @param mixed actual message, either a string, or an array of message parts
  * @param array attachments, if any
  * @param mixed additional headers, if any
  * @return the number of actual posts, or 0
  *
  * @see articles/mail.php
  * @see letters/new.php
  * @see users/mail.php
  */
 public static function post($from, $to, $subject, $message, $attachments = NULL, $headers = '')
 {
     global $context;
     // ensure that we have a sender
     if (!$from) {
         $from = Mailer::get_from_recipient();
     }
     // email services have to be activated
     if (!isset($context['with_email']) || $context['with_email'] != 'Y') {
         Logger::error(i18n::s('E-mail has not been enabled on this system.'));
         return 0;
         // check sender address
     } elseif (!$from) {
         Logger::error(i18n::s('Empty sender address'));
         return 0;
         // check recipient address
     } elseif (!$to) {
         Logger::error(i18n::s('Empty recipient address'));
         return 0;
         // check mail subject
     } elseif (!$subject) {
         Logger::error(i18n::s('No subject'));
         return 0;
         // check mail content
     } elseif (!$message) {
         Logger::error(i18n::s('No message'));
         return 0;
     }
     // the end of line string for mail messages
     if (!defined('M_EOL')) {
         define('M_EOL', "\n");
     }
     // encode the subject line
     $subject = Mailer::encode_subject($subject);
     // make some text out of an array
     if (is_array($headers)) {
         $headers = implode(M_EOL, $headers);
     }
     // From: header
     if (!preg_match('/^From: /im', $headers)) {
         $headers .= M_EOL . 'From: ' . $from;
     }
     // Reply-To: header
     if (!preg_match('/^Reply-To: /im', $headers)) {
         $headers .= M_EOL . 'Reply-To: ' . $from;
     }
     // Return-Path: header --to process errors
     if (!preg_match('/^Return-Path: /im', $headers)) {
         $headers .= M_EOL . 'Return-Path: ' . $from;
     }
     // Message-ID: header --helps to avoid spam filters
     if (!preg_match('/^Message-ID: /im', $headers)) {
         $headers .= M_EOL . 'Message-ID: <uniqid.' . uniqid() . '@' . $context['host_name'] . '>';
     }
     // MIME-Version: header
     if (!preg_match('/^MIME-Version: /im', $headers)) {
         $headers .= M_EOL . 'MIME-Version: 1.0';
     }
     // arrays are easier to manage
     if (is_string($message)) {
         // turn HTML entities to UTF-8
         $message = Safe::html_entity_decode($message, ENT_QUOTES, 'UTF-8');
         $copy = $message;
         $message = array();
         $message['text/plain; charset=utf-8'] = $copy;
         unset($copy);
     }
     // turn attachments to some array too
     if (is_string($attachments)) {
         $attachments = array($attachments);
     } elseif (!is_array($attachments)) {
         $attachments = array();
     }
     // we only consider objects from this server
     $my_prefix = $context['url_to_home'] . $context['url_to_root'];
     // transcode objects that will be transmitted along the message (i.e., images)
     foreach ($message as $type => $part) {
         // search throughout the full text
         $head = 0;
         while ($head = strpos($part, ' src="', $head)) {
             $head += strlen(' src="');
             // a link has been found
             if ($tail = strpos($part, '"', $head + 1)) {
                 $reference = substr($part, $head, $tail - $head);
                 // remember local links only
                 if (!strncmp($reference, $my_prefix, strlen($my_prefix))) {
                     // local name
                     $name = urldecode(substr($reference, strlen($my_prefix)));
                     // content-id to be used instead of the link
                     $cid = sprintf('%u@%s', crc32($name), $context['host_name']);
                     // transcode the link in this part
                     $part = substr($part, 0, $head) . 'cid:' . $cid . substr($part, $tail);
                     // remember to put content in attachments of this message, if not done yet
                     if (!in_array($name, $attachments)) {
                         $attachments[] = $name;
                     }
                 }
             }
         }
         // remember the transcoded part
         $message[$type] = $part;
     }
     // we need some boundary string
     if (count($message) + count($attachments) > 1) {
         $boundary = md5(time());
     }
     // wrapping threshold
     if (!defined('WRAPPING_LENGTH')) {
         define('WRAPPING_LENGTH', 70);
     }
     // combine message parts
     $content_type = '';
     $body = '';
     foreach ($message as $type => $part) {
         // quote textual entities
         if (!strncmp($type, 'text/', 5)) {
             $content_encoding = 'quoted-printable';
             $part = quoted_printable_encode($part);
             // encode everything else
         } else {
             $content_encoding = 'base64';
             $part = chunk_split(base64_encode($content), 76, M_EOL);
         }
         // only one part
         if (count($message) == 1) {
             $content_type = $type;
             $body = $part;
             // one part among several
         } else {
             // let user agent select among various alternatives
             if (!$content_type) {
                 $content_type = 'multipart/alternative; boundary="' . $boundary . '-internal"';
             }
             // introduction to assembled parts
             if (!$body) {
                 $body = 'This is a multi-part message in MIME format.';
             }
             // this part only --second EOL is part of the boundary chain
             $body .= M_EOL . M_EOL . '--' . $boundary . '-internal' . M_EOL . 'Content-Type: ' . $type . M_EOL . 'Content-Transfer-Encoding: ' . $content_encoding . M_EOL . M_EOL . $part;
         }
     }
     // finalize the body
     if (count($message) > 1) {
         $body .= M_EOL . M_EOL . '--' . $boundary . '-internal--';
     }
     // a mix of things
     if (count($attachments)) {
         // encoding is irrelevant if there are multiple parts
         if (!strncmp($content_type, 'multipart/', 10)) {
             $content_encoding = '';
         } else {
             $content_encoding = M_EOL . 'Content-Transfer-Encoding: ' . $content_encoding;
         }
         // identify the main part of the overall message
         $content_start = 'mainpart';
         // the current body becomes the first part of a larger message
         $body = 'This is a multi-part message in MIME format.' . M_EOL . M_EOL . '--' . $boundary . '-external' . M_EOL . 'Content-Type: ' . $content_type . $content_encoding . M_EOL . 'Content-ID: <' . $content_start . '>' . M_EOL . M_EOL . $body;
         // message parts should be considered as an aggregate whole --see RFC 2387
         $content_type = 'multipart/related; type="multipart/alternative"; boundary="' . $boundary . '-external"';
         $content_encoding = '';
         // process every file
         foreach ($attachments as $name => $content) {
             // read external file content
             if (preg_match('/^[0-9]+$/', $name)) {
                 // only a file name has been provided
                 $name = $content;
                 // read file content from the file system
                 if (!($content = Safe::file_get_contents($name))) {
                     continue;
                 }
             }
             // file name is the file type
             if (preg_match('/name="(.+)?"/', $name, $matches)) {
                 $type = $name;
                 $name = $matches[1];
             } else {
                 $type = Files::get_mime_type($name);
             }
             // a unique id for for this file
             $cid = sprintf('%u@%s', crc32($name), $context['host_name']);
             // set a name that avoids problems
             $basename = utf8::to_ascii(basename($name));
             // headers for one file
             $body .= M_EOL . M_EOL . '--' . $boundary . '-external' . M_EOL . 'Content-Type: ' . $type . M_EOL . 'Content-Disposition: inline; filename="' . str_replace('"', '', $basename) . '"' . M_EOL . 'Content-ID: <' . $cid . '>';
             // transfer textual entities as they are
             if (!strncmp($type, 'text/', 5)) {
                 $body .= M_EOL . 'Content-Transfer-Encoding: quoted-printable' . M_EOL . M_EOL . quoted_printable_encode($content);
                 // encode everything else
             } else {
                 $body .= M_EOL . 'Content-Transfer-Encoding: base64' . M_EOL . M_EOL . chunk_split(base64_encode($content), 76, M_EOL);
             }
         }
         // the closing boundary
         $body .= M_EOL . M_EOL . '--' . $boundary . '-external--';
     }
     // Content-Type: header
     if ($content_type && !preg_match('/^Content-Type: /im', $headers)) {
         $headers .= M_EOL . 'Content-Type: ' . $content_type;
     }
     // Content-Transfer-Encoding: header
     if (!isset($boundary) && $content_encoding && !preg_match('/^Content-Transfer-Encoding: /im', $headers)) {
         $headers .= M_EOL . 'Content-Transfer-Encoding: ' . $content_encoding;
     }
     // Start: header
     if (isset($boundary) && isset($content_start) && $content_start && !preg_match('/^Start: /im', $headers)) {
         $headers .= M_EOL . 'Start: ' . $content_start;
     }
     // X-Mailer: header --helps to avoid spam filters
     if (!preg_match('/^X-Mailer: /im', $headers)) {
         $headers .= M_EOL . 'X-Mailer: yacs';
     }
     // strip leading spaces and newlines
     $headers = trim($headers);
     // make an array of recipients
     if (!is_array($to)) {
         $to = Mailer::explode_recipients($to);
     }
     // the list of recipients contacted during overall script execution
     if (!isset($context['mailer_recipients'])) {
         $context['mailer_recipients'] = array();
     }
     // process every recipient
     $posts = 0;
     foreach ($to as $recipient) {
         // clean the provided string
         $recipient = trim(str_replace(array("\r\n", "\r", "\n", "\t"), ' ', $recipient));
         // this e-mail address has already been processed
         if (in_array($recipient, $context['mailer_recipients'])) {
             if (isset($context['debug_mail']) && $context['debug_mail'] == 'Y') {
                 Logger::remember('shared/mailer.php: Skipping recipient already processed', $recipient, 'debug');
             }
             continue;
             // remember this recipient
         } else {
             $context['mailer_recipients'][] = $recipient;
         }
         // queue the message
         Mailer::queue($recipient, $subject, $body, $headers);
         $posts++;
     }
     // track last submission
     include_once $context['path_to_root'] . 'shared/values.php';
     Values::set('mailer.last.queued', $subject . ' (' . $posts . ' recipients)');
     // return the number of actual posts
     return $posts;
 }
Ejemplo n.º 2
0
Archivo: pdf.php Proyecto: rair/yacs
 /**
  * encode a sequence of HTML tags, or plain text, to PDF
  *
  * @param string the text to append
  * @return the content of PDF
  * @see articles/fetch_as_pdf.php
  */
 function encode($text)
 {
     global $context;
     //
     // meta information
     //
     // encode it to iso8859 -- sorry
     // document title
     if ($context['page_title']) {
         $this->SetTitle(utf8::to_iso8859(Safe::html_entity_decode($context['page_title'], ENT_COMPAT, 'ISO-8859-15')));
     }
     // document author
     if ($context['page_author']) {
         $this->SetAuthor(utf8::to_iso8859(Safe::html_entity_decode($context['page_author'], ENT_COMPAT, 'ISO-8859-15')));
     }
     // document subject
     if ($context['subject']) {
         $this->SetSubject(utf8::to_iso8859(Safe::html_entity_decode($context['subject'], ENT_COMPAT, 'ISO-8859-15')));
     }
     // document creator (typically, the tool used to produce the document)
     $this->SetCreator('yacs');
     //
     // PDF content
     //
     // start the rendering engine
     $this->AliasNbPages();
     $this->AddPage();
     $this->SetFont('Arial', 'B', 16);
     // reference view.php instead of ourself to achieve correct links
     $text = str_replace('/fetch_as_pdf.php', '/view.php', $text);
     // remove all unsupported tags
     $text = strip_tags($text, "<a><b><blockquote><br><code><div><em><font><h1><h2><h3><h4><hr><i><img><li><p><pre><strong><table><tr><tt><u><ul>");
     // spaces instead of carriage returns
     $text = str_replace("\n", ' ', $text);
     // transcode to ISO8859-15 characters
     $text = utf8::to_iso8859(Safe::html_entity_decode($text, ENT_COMPAT, 'ISO-8859-15'));
     // locate every HTML/XML tag
     $areas = preg_split('/<(.*)>/U', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
     $height = 5;
     $link = '';
     foreach ($areas as $index => $entity) {
         // a tag entity
         if ($index % 2) {
             @(list($tag, $attributes) = explode(' ', $entity, 2));
             switch (strtolower($tag)) {
                 case 'a':
                     if (preg_match('/href="(.*)"/i', $attributes, $matches)) {
                         $link = $matches[1];
                         // suppress local references (eg, in table of content)
                         if (preg_match('/(#.*)/', $link)) {
                             $link = '';
                         } elseif ($link[0] == '/') {
                             $link = $context['url_to_home'] . $link;
                         }
                     }
                     break;
                 case 'b':
                     $this->SetFont('', 'B');
                     break;
                 case '/b':
                     $this->SetFont('', '');
                     break;
                 case 'blockquote':
                     $this->Ln($height);
                     break;
                 case '/blockquote':
                     $this->Ln($height);
                     break;
                 case 'br':
                     $this->Ln($height);
                     break;
                 case 'code':
                     $this->SetFont('Courier', '', 11);
                     $this->SetFontSize(11);
                     break;
                 case '/code':
                     $this->SetFont('Times', '', 12);
                     $this->SetFontSize(12);
                     break;
                 case 'div':
                 case '/div':
                     $this->Ln($height);
                     break;
                 case 'em':
                     $this->SetFont('', 'I');
                     break;
                 case '/em':
                     $this->SetFont('', '');
                     break;
                 case 'font':
                     if (preg_match('/color="#(.{6})"/i', $attributes, $matches)) {
                         $color = $matches[1];
                         $r = hexdec($color[0] . $color[1]);
                         $g = hexdec($color[2] . $color[3]);
                         $b = hexdec($color[4] . $color[5]);
                         $this->SetTextColor($r, $g, $b);
                     }
                     break;
                 case 'font':
                     $this->SetFont('Times', '', 12);
                     $this->SetTextColor(0, 0, 0);
                     $this->SetFontSize(12);
                     break;
                 case 'h1':
                     $this->Ln(10);
                     $this->SetTextColor(150, 0, 0);
                     $this->SetFontSize(22);
                     $height = 8;
                     break;
                 case 'h2':
                     $this->Ln(8);
                     $this->SetFontSize(18);
                     $height = 6;
                     break;
                 case 'h3':
                     $this->Ln(6);
                     $this->SetFontSize(16);
                     $height = 5;
                     break;
                 case 'h4':
                     $this->Ln(6);
                     $this->SetTextColor(102, 0, 0);
                     $this->SetFontSize(14);
                     $height = 5;
                     break;
                 case '/h1':
                 case '/h2':
                 case '/h3':
                 case '/h4':
                     $this->Ln($height);
                     $this->SetFont('Times', '', 12);
                     $this->SetTextColor(0, 0, 0);
                     $this->SetFontSize(12);
                     $height = 5;
                     break;
                 case 'hr':
                     $this->Ln($height + 2);
                     $this->Line($this->GetX(), $this->GetY(), $this->GetX() + 187, $this->GetY());
                     $this->Ln(3);
                     break;
                 case 'i':
                     $this->SetFont('', 'I');
                     break;
                 case '/i':
                     $this->SetFont('', '');
                     break;
                 case 'img':
                     // only accept JPG and PNG
                     if (preg_match('/src="([^"]+\\.(jpg|jpeg|png))"/i', $attributes, $matches)) {
                         $image = $matches[1];
                         // map on a file
                         $image = preg_replace('/^' . preg_quote($context['url_to_home'] . $context['url_to_root'], '/') . '/', $context['path_to_root'], $image);
                         // include the image only if the file exists
                         if ($attributes = Safe::GetImageSize($image)) {
                             // insert an image at 72 dpi -- the k factor
                             $this->Image($image, $this->GetX(), $this->GetY(), $attributes[0] / $this->k, $attributes[1] / $this->k);
                             // make room for the image
                             $this->y += 3 + $attributes[1] / $this->k;
                         }
                     }
                     break;
                 case 'li':
                     $this->Ln($height);
                     break;
                 case '/li':
                     break;
                 case 'p':
                 case '/p':
                     $this->Ln($height);
                     break;
                 case 'pre':
                     $this->SetFont('Courier', '', 11);
                     $this->SetFontSize(11);
                     $preformatted = TRUE;
                     break;
                 case '/pre':
                     $this->SetFont('Times', '', 12);
                     $this->SetFontSize(12);
                     $preformatted = FALSE;
                     break;
                 case 'strong':
                     $this->SetFont('', 'B');
                     break;
                 case '/strong':
                     $this->SetFont('', '');
                     break;
                 case 'table':
                     $this->Ln($height);
                     break;
                 case '/table':
                     $this->Ln($height);
                     break;
                 case 'tr':
                     $this->Ln($height + 2);
                     $this->Line($this->GetX(), $this->GetY(), $this->GetX() + 187, $this->GetY());
                     $this->Ln(3);
                     break;
                 case 'tt':
                     $this->SetFont('Courier', '', 11);
                     $this->SetFontSize(11);
                     break;
                 case '/tt':
                     $this->SetFont('Times', '', 12);
                     $this->SetFontSize(12);
                     break;
                 case 'u':
                     $this->SetFont('', 'U');
                     break;
                 case '/u':
                     $this->SetFont('', '');
                     break;
                 case 'ul':
                     break;
                 case '/ul':
                     break;
             }
             // a textual entity
         } else {
             // we have to write a link
             if ($link) {
                 // a blue underlined link
                 $this->SetTextColor(0, 0, 255);
                 $this->SetFont('', 'U');
                 $this->Write($height, $entity, $link);
                 $link = '';
                 $this->SetTextColor(0, 0, 0);
                 $this->SetFont('', '');
                 // regular text
             } else {
                 $this->Write($height, $entity);
             }
         }
     }
     // return the PDF content as a string
     return $this->Output('dummy', 'S');
 }
Ejemplo n.º 3
0
    return;
}
// keep only titles and ISO8859-1 labels
$titles = array();
$links = array();
$count = 0;
foreach ($items as $url => $label) {
    // we are not interested into all attributes
    if (is_array($label)) {
        $label = $label[1];
    }
    // strip codes
    include_once '../../codes/codes.php';
    $label = Codes::strip($label);
    // remove every html tag
    $label = strip_tags(Safe::html_entity_decode($label));
    // remember this
    $titles[$count] = $label;
    $links[$count] = $url;
    $count++;
}
// cache handling --except on scripts/validate.php
if (!headers_sent() && (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'GET')) {
    // this is a schockwave object
    Safe::header('Content-Type: application/x-shockwave-flash');
    // enable 30-minute caching (30*60 = 1800), even through https, to help IE6 on download
    http::expire(1800);
    // the original content
    $page = '';
    for ($index = 0; $index < $count; $index++) {
        $page .= $titles[$index] . ':' . $links[$index] . ':';