diff --git a/democracy/api/sql/BETA_CODE_DELETE.php b/democracy/api/sql/BETA_CODE_DELETE.php new file mode 100644 index 0000000..030f12d --- /dev/null +++ b/democracy/api/sql/BETA_CODE_DELETE.php @@ -0,0 +1,9 @@ + @@ -64,7 +63,7 @@
-
+
DEMOCRACY Logo

DEMOCRACY

prototyp_access

@@ -116,6 +115,8 @@
Sobald Du im AppStore freigeschaltet wurdest, erhälst Du eine EMail mit einer genauen Anleitung von uns.
+
+ Bitte überprüfe auch Deinen Spam-Ordner!
    @@ -131,6 +132,8 @@
    Sobald Du im PlayStore freigeschaltet wurdest, erhälst Du eine EMail mit einer genauen Anleitung von uns.
    +
    + Bitte überprüfe auch Deinen Spam-Ordner!
      diff --git a/democracy/sai/saimod_beta/autoload.inc b/democracy/sai/saimod_beta/autoload.inc index 67746c5..9cdeb6e 100644 --- a/democracy/sai/saimod_beta/autoload.inc +++ b/democracy/sai/saimod_beta/autoload.inc @@ -3,3 +3,4 @@ require_once dirname(__FILE__).'/sql/autoload.inc'; // \SYSTEM\autoload::registerFolder(dirname(__FILE__).'/sql','SQL'); \SYSTEM\autoload::registerFolder(dirname(__FILE__),'SAI'); \SYSTEM\SAI\sai::register('\SAI\saimod_beta'); +\SYSTEM\autoload::registerFolder(dirname(__FILE__).'/cannon'); \ No newline at end of file diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/.gitignore b/democracy/sai/saimod_beta/cannon/Pear/Mail/.gitignore new file mode 100644 index 0000000..869f498 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/.gitignore @@ -0,0 +1,4 @@ +# composer related +composer.lock +composer.phar +vendor diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/.travis.yml b/democracy/sai/saimod_beta/cannon/Pear/Mail/.travis.yml new file mode 100644 index 0000000..c150272 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/.travis.yml @@ -0,0 +1,19 @@ +language: php +sudo: false +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - nightly +install: + - pear list + - pear channel-update pear.php.net + - pear upgrade --force pear/pear + - pear list + - pear upgrade --force xml_util + - pear install --force --alldeps package.xml + - pear list +script: + - pear run-tests -d tests/ diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/LICENSE b/democracy/sai/saimod_beta/cannon/Pear/Mail/LICENSE new file mode 100644 index 0000000..9aee685 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 1997-2017, Chuck Hagenbuch +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail.php b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail.php new file mode 100644 index 0000000..4858f3f --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail.php @@ -0,0 +1,267 @@ + + * @copyright 1997-2017 Chuck Hagenbuch + * @license http://opensource.org/licenses/BSD-3-Clause New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/Mail/ + */ + +//require_once 'PEAR.php'; + +/** + * PEAR's Mail:: interface. Defines the interface for implementing + * mailers under the PEAR hierarchy, and provides supporting functions + * useful in multiple mailer backends. + * + * @version $Revision$ + * @package Mail + */ +class Mail +{ + /** + * Line terminator used for separating header lines. + * @var string + */ + public $sep = "\r\n"; + + /** + * Provides an interface for generating Mail:: objects of various + * types + * + * @param string $driver The kind of Mail:: object to instantiate. + * @param array $params The parameters to pass to the Mail:: object. + * + * @return object Mail a instance of the driver class or if fails a PEAR Error + */ + public static function factory($driver, $params = array()) + { + $driver = strtolower($driver); + @include_once 'Mail/' . $driver . '.php'; + $class = 'Mail_' . $driver; + if (class_exists($class)) { + $mailer = new $class($params); + return $mailer; + } else { + return PEAR::raiseError('Unable to find class for driver ' . $driver); + } + } + + /** + * Implements Mail::send() function using php's built-in mail() + * command. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. This may contain recipients not + * specified in the headers, for Bcc:, resending + * messages, etc. + * + * @param array $headers The array of headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array value + * is the header value (ie, 'test'). The header + * produced from those values would be 'Subject: + * test'. + * + * @param string $body The full text of the message body, including any + * Mime parts, etc. + * + * @return mixed Returns true on success, or a PEAR_Error + * containing a descriptive error message on + * failure. + * + * @deprecated use Mail_mail::send instead + */ + public function send($recipients, $headers, $body) + { + if (!is_array($headers)) { + return PEAR::raiseError('$headers must be an array'); + } + + $result = $this->_sanitizeHeaders($headers); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + // if we're passed an array of recipients, implode it. + if (is_array($recipients)) { + $recipients = implode(', ', $recipients); + } + + // get the Subject out of the headers array so that we can + // pass it as a seperate argument to mail(). + $subject = ''; + if (isset($headers['Subject'])) { + $subject = $headers['Subject']; + unset($headers['Subject']); + } + + // flatten the headers out. + list(, $text_headers) = Mail::prepareHeaders($headers); + + return mail($recipients, $subject, $body, $text_headers); + } + + /** + * Sanitize an array of mail headers by removing any additional header + * strings present in a legitimate header's value. The goal of this + * filter is to prevent mail injection attacks. + * + * @param array $headers The associative array of headers to sanitize. + */ + protected function _sanitizeHeaders(&$headers) + { + foreach ($headers as $key => $value) { + $headers[$key] = + preg_replace('=((||0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', + null, $value); + } + } + + /** + * Take an array of mail headers and return a string containing + * text usable in sending a message. + * + * @param array $headers The array of headers to prepare, in an associative + * array, where the array key is the header name (ie, + * 'Subject'), and the array value is the header + * value (ie, 'test'). The header produced from those + * values would be 'Subject: test'. + * + * @return mixed Returns false if it encounters a bad address, + * otherwise returns an array containing two + * elements: Any From: address found in the headers, + * and the plain text version of the headers. + */ + protected function prepareHeaders($headers) + { + $lines = array(); + $from = null; + + foreach ($headers as $key => $value) { + if (strcasecmp($key, 'From') === 0) { + include_once 'Mail/RFC822.php'; + $parser = new Mail_RFC822(); + $addresses = $parser->parseAddressList($value, 'localhost', false); + if (is_a($addresses, 'PEAR_Error')) { + return $addresses; + } + + $from = $addresses[0]->mailbox . '@' . $addresses[0]->host; + + // Reject envelope From: addresses with spaces. + if (strstr($from, ' ')) { + return false; + } + + $lines[] = $key . ': ' . $value; + } elseif (strcasecmp($key, 'Received') === 0) { + $received = array(); + if (is_array($value)) { + foreach ($value as $line) { + $received[] = $key . ': ' . $line; + } + } + else { + $received[] = $key . ': ' . $value; + } + // Put Received: headers at the top. Spam detectors often + // flag messages with Received: headers after the Subject: + // as spam. + $lines = array_merge($received, $lines); + } else { + // If $value is an array (i.e., a list of addresses), convert + // it to a comma-delimited string of its elements (addresses). + if (is_array($value)) { + $value = implode(', ', $value); + } + $lines[] = $key . ': ' . $value; + } + } + + return array($from, join($this->sep, $lines)); + } + + /** + * Take a set of recipients and parse them, returning an array of + * bare addresses (forward paths) that can be passed to sendmail + * or an smtp server with the rcpt to: command. + * + * @param mixed Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. + * + * @return mixed An array of forward paths (bare addresses) or a PEAR_Error + * object if the address list could not be parsed. + */ + protected function parseRecipients($recipients) + { + include_once 'Mail/RFC822.php'; + + // if we're passed an array, assume addresses are valid and + // implode them before parsing. + if (is_array($recipients)) { + $recipients = implode(', ', $recipients); + } + + // Parse recipients, leaving out all personal info. This is + // for smtp recipients, etc. All relevant personal information + // should already be in the headers. + $Mail_RFC822 = new Mail_RFC822(); + $addresses = $Mail_RFC822->parseAddressList($recipients, 'localhost', false); + + // If parseAddressList() returned a PEAR_Error object, just return it. + if (is_a($addresses, 'PEAR_Error')) { + return $addresses; + } + + $recipients = array(); + if (is_array($addresses)) { + foreach ($addresses as $ob) { + $recipients[] = $ob->mailbox . '@' . $ob->host; + } + } + + return $recipients; + } + +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/RFC822.php b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/RFC822.php new file mode 100644 index 0000000..509197c --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/RFC822.php @@ -0,0 +1,929 @@ + + * @author Chuck Hagenbuch + * @author Chuck Hagenbuch + * @version $Revision$ + * @license BSD + * @package Mail + */ +class Mail_RFC822 { + + /** + * The address being parsed by the RFC822 object. + * @var string $address + */ + var $address = ''; + + /** + * The default domain to use for unqualified addresses. + * @var string $default_domain + */ + var $default_domain = 'localhost'; + + /** + * Should we return a nested array showing groups, or flatten everything? + * @var boolean $nestGroups + */ + var $nestGroups = true; + + /** + * Whether or not to validate atoms for non-ascii characters. + * @var boolean $validate + */ + var $validate = true; + + /** + * The array of raw addresses built up as we parse. + * @var array $addresses + */ + var $addresses = array(); + + /** + * The final array of parsed address information that we build up. + * @var array $structure + */ + var $structure = array(); + + /** + * The current error message, if any. + * @var string $error + */ + var $error = null; + + /** + * An internal counter/pointer. + * @var integer $index + */ + var $index = null; + + /** + * The number of groups that have been found in the address list. + * @var integer $num_groups + * @access public + */ + var $num_groups = 0; + + /** + * A variable so that we can tell whether or not we're inside a + * Mail_RFC822 object. + * @var boolean $mailRFC822 + */ + var $mailRFC822 = true; + + /** + * A limit after which processing stops + * @var int $limit + */ + var $limit = null; + + /** + * Sets up the object. The address must either be set here or when + * calling parseAddressList(). One or the other. + * + * @param string $address The address(es) to validate. + * @param string $default_domain Default domain/host etc. If not supplied, will be set to localhost. + * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing. + * @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance. + * + * @return object Mail_RFC822 A new Mail_RFC822 object. + */ + public function __construct($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) + { + if (isset($address)) $this->address = $address; + if (isset($default_domain)) $this->default_domain = $default_domain; + if (isset($nest_groups)) $this->nestGroups = $nest_groups; + if (isset($validate)) $this->validate = $validate; + if (isset($limit)) $this->limit = $limit; + } + + /** + * Starts the whole process. The address must either be set here + * or when creating the object. One or the other. + * + * @param string $address The address(es) to validate. + * @param string $default_domain Default domain/host etc. + * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing. + * @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance. + * + * @return array A structured array of addresses. + */ + public function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null) + { + if (!isset($this) || !isset($this->mailRFC822)) { + $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit); + return $obj->parseAddressList(); + } + + if (isset($address)) $this->address = $address; + if (isset($default_domain)) $this->default_domain = $default_domain; + if (isset($nest_groups)) $this->nestGroups = $nest_groups; + if (isset($validate)) $this->validate = $validate; + if (isset($limit)) $this->limit = $limit; + + $this->structure = array(); + $this->addresses = array(); + $this->error = null; + $this->index = null; + + // Unfold any long lines in $this->address. + $this->address = preg_replace('/\r?\n/', "\r\n", $this->address); + $this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address); + + while ($this->address = $this->_splitAddresses($this->address)); + + if ($this->address === false || isset($this->error)) { + require_once 'PEAR.php'; + return PEAR::raiseError($this->error); + } + + // Validate each address individually. If we encounter an invalid + // address, stop iterating and return an error immediately. + foreach ($this->addresses as $address) { + $valid = $this->_validateAddress($address); + + if ($valid === false || isset($this->error)) { + //require_once 'PEAR.php'; + return PEAR::raiseError($this->error); + } + + if (!$this->nestGroups) { + $this->structure = array_merge($this->structure, $valid); + } else { + $this->structure[] = $valid; + } + } + + return $this->structure; + } + + /** + * Splits an address into separate addresses. + * + * @param string $address The addresses to split. + * @return boolean Success or failure. + */ + protected function _splitAddresses($address) + { + if (!empty($this->limit) && count($this->addresses) == $this->limit) { + return ''; + } + + if ($this->_isGroup($address) && !isset($this->error)) { + $split_char = ';'; + $is_group = true; + } elseif (!isset($this->error)) { + $split_char = ','; + $is_group = false; + } elseif (isset($this->error)) { + return false; + } + + // Split the string based on the above ten or so lines. + $parts = explode($split_char, $address); + $string = $this->_splitCheck($parts, $split_char); + + // If a group... + if ($is_group) { + // If $string does not contain a colon outside of + // brackets/quotes etc then something's fubar. + + // First check there's a colon at all: + if (strpos($string, ':') === false) { + $this->error = 'Invalid address: ' . $string; + return false; + } + + // Now check it's outside of brackets/quotes: + if (!$this->_splitCheck(explode(':', $string), ':')) { + return false; + } + + // We must have a group at this point, so increase the counter: + $this->num_groups++; + } + + // $string now contains the first full address/group. + // Add to the addresses array. + $this->addresses[] = array( + 'address' => trim($string), + 'group' => $is_group + ); + + // Remove the now stored address from the initial line, the +1 + // is to account for the explode character. + $address = trim(substr($address, strlen($string) + 1)); + + // If the next char is a comma and this was a group, then + // there are more addresses, otherwise, if there are any more + // chars, then there is another address. + if ($is_group && substr($address, 0, 1) == ','){ + $address = trim(substr($address, 1)); + return $address; + + } elseif (strlen($address) > 0) { + return $address; + + } else { + return ''; + } + + // If you got here then something's off + return false; + } + + /** + * Checks for a group at the start of the string. + * + * @param string $address The address to check. + * @return boolean Whether or not there is a group at the start of the string. + */ + protected function _isGroup($address) + { + // First comma not in quotes, angles or escaped: + $parts = explode(',', $address); + $string = $this->_splitCheck($parts, ','); + + // Now we have the first address, we can reliably check for a + // group by searching for a colon that's not escaped or in + // quotes or angle brackets. + if (count($parts = explode(':', $string)) > 1) { + $string2 = $this->_splitCheck($parts, ':'); + return ($string2 !== $string); + } else { + return false; + } + } + + /** + * A common function that will check an exploded string. + * + * @param array $parts The exloded string. + * @param string $char The char that was exploded on. + * @return mixed False if the string contains unclosed quotes/brackets, or the string on success. + */ + protected function _splitCheck($parts, $char) + { + $string = $parts[0]; + + for ($i = 0; $i < count($parts); $i++) { + if ($this->_hasUnclosedQuotes($string) + || $this->_hasUnclosedBrackets($string, '<>') + || $this->_hasUnclosedBrackets($string, '[]') + || $this->_hasUnclosedBrackets($string, '()') + || substr($string, -1) == '\\') { + if (isset($parts[$i + 1])) { + $string = $string . $char . $parts[$i + 1]; + } else { + $this->error = 'Invalid address spec. Unclosed bracket or quotes'; + return false; + } + } else { + $this->index = $i; + break; + } + } + + return $string; + } + + /** + * Checks if a string has unclosed quotes or not. + * + * @param string $string The string to check. + * @return boolean True if there are unclosed quotes inside the string, + * false otherwise. + */ + protected function _hasUnclosedQuotes($string) + { + $string = trim($string); + $iMax = strlen($string); + $in_quote = false; + $i = $slashes = 0; + + for (; $i < $iMax; ++$i) { + switch ($string[$i]) { + case '\\': + ++$slashes; + break; + + case '"': + if ($slashes % 2 == 0) { + $in_quote = !$in_quote; + } + // Fall through to default action below. + + default: + $slashes = 0; + break; + } + } + + return $in_quote; + } + + /** + * Checks if a string has an unclosed brackets or not. IMPORTANT: + * This function handles both angle brackets and square brackets; + * + * @param string $string The string to check. + * @param string $chars The characters to check for. + * @return boolean True if there are unclosed brackets inside the string, false otherwise. + */ + protected function _hasUnclosedBrackets($string, $chars) + { + $num_angle_start = substr_count($string, $chars[0]); + $num_angle_end = substr_count($string, $chars[1]); + + $this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]); + $this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]); + + if ($num_angle_start < $num_angle_end) { + $this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')'; + return false; + } else { + return ($num_angle_start > $num_angle_end); + } + } + + /** + * Sub function that is used only by hasUnclosedBrackets(). + * + * @param string $string The string to check. + * @param integer &$num The number of occurences. + * @param string $char The character to count. + * @return integer The number of occurences of $char in $string, adjusted for backslashes. + */ + protected function _hasUnclosedBracketsSub($string, &$num, $char) + { + $parts = explode($char, $string); + for ($i = 0; $i < count($parts); $i++){ + if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i])) + $num--; + if (isset($parts[$i + 1])) + $parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1]; + } + + return $num; + } + + /** + * Function to begin checking the address. + * + * @param string $address The address to validate. + * @return mixed False on failure, or a structured array of address information on success. + */ + protected function _validateAddress($address) + { + $is_group = false; + $addresses = array(); + + if ($address['group']) { + $is_group = true; + + // Get the group part of the name + $parts = explode(':', $address['address']); + $groupname = $this->_splitCheck($parts, ':'); + $structure = array(); + + // And validate the group part of the name. + if (!$this->_validatePhrase($groupname)){ + $this->error = 'Group name did not validate.'; + return false; + } else { + // Don't include groups if we are not nesting + // them. This avoids returning invalid addresses. + if ($this->nestGroups) { + $structure = new stdClass; + $structure->groupname = $groupname; + } + } + + $address['address'] = ltrim(substr($address['address'], strlen($groupname . ':'))); + } + + // If a group then split on comma and put into an array. + // Otherwise, Just put the whole address in an array. + if ($is_group) { + while (strlen($address['address']) > 0) { + $parts = explode(',', $address['address']); + $addresses[] = $this->_splitCheck($parts, ','); + $address['address'] = trim(substr($address['address'], strlen(end($addresses) . ','))); + } + } else { + $addresses[] = $address['address']; + } + + // Trim the whitespace from all of the address strings. + array_map('trim', $addresses); + + // Validate each mailbox. + // Format could be one of: name + // geezer@domain.com + // geezer + // ... or any other format valid by RFC 822. + for ($i = 0; $i < count($addresses); $i++) { + if (!$this->validateMailbox($addresses[$i])) { + if (empty($this->error)) { + $this->error = 'Validation failed for: ' . $addresses[$i]; + } + return false; + } + } + + // Nested format + if ($this->nestGroups) { + if ($is_group) { + $structure->addresses = $addresses; + } else { + $structure = $addresses[0]; + } + + // Flat format + } else { + if ($is_group) { + $structure = array_merge($structure, $addresses); + } else { + $structure = $addresses; + } + } + + return $structure; + } + + /** + * Function to validate a phrase. + * + * @param string $phrase The phrase to check. + * @return boolean Success or failure. + */ + protected function _validatePhrase($phrase) + { + // Splits on one or more Tab or space. + $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY); + + $phrase_parts = array(); + while (count($parts) > 0){ + $phrase_parts[] = $this->_splitCheck($parts, ' '); + for ($i = 0; $i < $this->index + 1; $i++) + array_shift($parts); + } + + foreach ($phrase_parts as $part) { + // If quoted string: + if (substr($part, 0, 1) == '"') { + if (!$this->_validateQuotedString($part)) { + return false; + } + continue; + } + + // Otherwise it's an atom: + if (!$this->_validateAtom($part)) return false; + } + + return true; + } + + /** + * Function to validate an atom which from rfc822 is: + * atom = 1* + * + * If validation ($this->validate) has been turned off, then + * validateAtom() doesn't actually check anything. This is so that you + * can split a list of addresses up before encoding personal names + * (umlauts, etc.), for example. + * + * @param string $atom The string to check. + * @return boolean Success or failure. + */ + protected function _validateAtom($atom) + { + if (!$this->validate) { + // Validation has been turned off; assume the atom is okay. + return true; + } + + // Check for any char from ASCII 0 - ASCII 127 + if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) { + return false; + } + + // Check for specials: + if (preg_match('/[][()<>@,;\\:". ]/', $atom)) { + return false; + } + + // Check for control characters (ASCII 0-31): + if (preg_match('/[\\x00-\\x1F]+/', $atom)) { + return false; + } + + return true; + } + + /** + * Function to validate quoted string, which is: + * quoted-string = <"> *(qtext/quoted-pair) <"> + * + * @param string $qstring The string to check + * @return boolean Success or failure. + */ + protected function _validateQuotedString($qstring) + { + // Leading and trailing " + $qstring = substr($qstring, 1, -1); + + // Perform check, removing quoted characters first. + return !preg_match('/[\x0D\\\\"]/', preg_replace('/\\\\./', '', $qstring)); + } + + /** + * Function to validate a mailbox, which is: + * mailbox = addr-spec ; simple address + * / phrase route-addr ; name and route-addr + * + * @param string &$mailbox The string to check. + * @return boolean Success or failure. + */ + public function validateMailbox(&$mailbox) + { + // A couple of defaults. + $phrase = ''; + $comment = ''; + $comments = array(); + + // Catch any RFC822 comments and store them separately. + $_mailbox = $mailbox; + while (strlen(trim($_mailbox)) > 0) { + $parts = explode('(', $_mailbox); + $before_comment = $this->_splitCheck($parts, '('); + if ($before_comment != $_mailbox) { + // First char should be a (. + $comment = substr(str_replace($before_comment, '', $_mailbox), 1); + $parts = explode(')', $comment); + $comment = $this->_splitCheck($parts, ')'); + $comments[] = $comment; + + // +2 is for the brackets + $_mailbox = substr($_mailbox, strpos($_mailbox, '('.$comment)+strlen($comment)+2); + } else { + break; + } + } + + foreach ($comments as $comment) { + $mailbox = str_replace("($comment)", '', $mailbox); + } + + $mailbox = trim($mailbox); + + // Check for name + route-addr + if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') { + $parts = explode('<', $mailbox); + $name = $this->_splitCheck($parts, '<'); + + $phrase = trim($name); + $route_addr = trim(substr($mailbox, strlen($name.'<'), -1)); + + if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) { + return false; + } + + // Only got addr-spec + } else { + // First snip angle brackets if present. + if (substr($mailbox, 0, 1) == '<' && substr($mailbox, -1) == '>') { + $addr_spec = substr($mailbox, 1, -1); + } else { + $addr_spec = $mailbox; + } + + if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) { + return false; + } + } + + // Construct the object that will be returned. + $mbox = new stdClass(); + + // Add the phrase (even if empty) and comments + $mbox->personal = $phrase; + $mbox->comment = isset($comments) ? $comments : array(); + + if (isset($route_addr)) { + $mbox->mailbox = $route_addr['local_part']; + $mbox->host = $route_addr['domain']; + $route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : ''; + } else { + $mbox->mailbox = $addr_spec['local_part']; + $mbox->host = $addr_spec['domain']; + } + + $mailbox = $mbox; + return true; + } + + /** + * This function validates a route-addr which is: + * route-addr = "<" [route] addr-spec ">" + * + * Angle brackets have already been removed at the point of + * getting to this function. + * + * @param string $route_addr The string to check. + * @return mixed False on failure, or an array containing validated address/route information on success. + */ + protected function _validateRouteAddr($route_addr) + { + // Check for colon. + if (strpos($route_addr, ':') !== false) { + $parts = explode(':', $route_addr); + $route = $this->_splitCheck($parts, ':'); + } else { + $route = $route_addr; + } + + // If $route is same as $route_addr then the colon was in + // quotes or brackets or, of course, non existent. + if ($route === $route_addr){ + unset($route); + $addr_spec = $route_addr; + if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) { + return false; + } + } else { + // Validate route part. + if (($route = $this->_validateRoute($route)) === false) { + return false; + } + + $addr_spec = substr($route_addr, strlen($route . ':')); + + // Validate addr-spec part. + if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) { + return false; + } + } + + if (isset($route)) { + $return['adl'] = $route; + } else { + $return['adl'] = ''; + } + + $return = array_merge($return, $addr_spec); + return $return; + } + + /** + * Function to validate a route, which is: + * route = 1#("@" domain) ":" + * + * @param string $route The string to check. + * @return mixed False on failure, or the validated $route on success. + */ + protected function _validateRoute($route) + { + // Split on comma. + $domains = explode(',', trim($route)); + + foreach ($domains as $domain) { + $domain = str_replace('@', '', trim($domain)); + if (!$this->_validateDomain($domain)) return false; + } + + return $route; + } + + /** + * Function to validate a domain, though this is not quite what + * you expect of a strict internet domain. + * + * domain = sub-domain *("." sub-domain) + * + * @param string $domain The string to check. + * @return mixed False on failure, or the validated domain on success. + */ + protected function _validateDomain($domain) + { + // Note the different use of $subdomains and $sub_domains + $subdomains = explode('.', $domain); + + while (count($subdomains) > 0) { + $sub_domains[] = $this->_splitCheck($subdomains, '.'); + for ($i = 0; $i < $this->index + 1; $i++) + array_shift($subdomains); + } + + foreach ($sub_domains as $sub_domain) { + if (!$this->_validateSubdomain(trim($sub_domain))) + return false; + } + + // Managed to get here, so return input. + return $domain; + } + + /** + * Function to validate a subdomain: + * subdomain = domain-ref / domain-literal + * + * @param string $subdomain The string to check. + * @return boolean Success or failure. + */ + protected function _validateSubdomain($subdomain) + { + if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){ + if (!$this->_validateDliteral($arr[1])) return false; + } else { + if (!$this->_validateAtom($subdomain)) return false; + } + + // Got here, so return successful. + return true; + } + + /** + * Function to validate a domain literal: + * domain-literal = "[" *(dtext / quoted-pair) "]" + * + * @param string $dliteral The string to check. + * @return boolean Success or failure. + */ + protected function _validateDliteral($dliteral) + { + return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && ((! isset($matches[1])) || $matches[1] != '\\'); + } + + /** + * Function to validate an addr-spec. + * + * addr-spec = local-part "@" domain + * + * @param string $addr_spec The string to check. + * @return mixed False on failure, or the validated addr-spec on success. + */ + protected function _validateAddrSpec($addr_spec) + { + $addr_spec = trim($addr_spec); + + // Split on @ sign if there is one. + if (strpos($addr_spec, '@') !== false) { + $parts = explode('@', $addr_spec); + $local_part = $this->_splitCheck($parts, '@'); + $domain = substr($addr_spec, strlen($local_part . '@')); + + // No @ sign so assume the default domain. + } else { + $local_part = $addr_spec; + $domain = $this->default_domain; + } + + if (($local_part = $this->_validateLocalPart($local_part)) === false) return false; + if (($domain = $this->_validateDomain($domain)) === false) return false; + + // Got here so return successful. + return array('local_part' => $local_part, 'domain' => $domain); + } + + /** + * Function to validate the local part of an address: + * local-part = word *("." word) + * + * @param string $local_part + * @return mixed False on failure, or the validated local part on success. + */ + protected function _validateLocalPart($local_part) + { + $parts = explode('.', $local_part); + $words = array(); + + // Split the local_part into words. + while (count($parts) > 0) { + $words[] = $this->_splitCheck($parts, '.'); + for ($i = 0; $i < $this->index + 1; $i++) { + array_shift($parts); + } + } + + // Validate each word. + foreach ($words as $word) { + // word cannot be empty (#17317) + if ($word === '') { + return false; + } + // If this word contains an unquoted space, it is invalid. (6.2.4) + if (strpos($word, ' ') && $word[0] !== '"') + { + return false; + } + + if ($this->_validatePhrase(trim($word)) === false) return false; + } + + // Managed to get here, so return the input. + return $local_part; + } + + /** + * Returns an approximate count of how many addresses are in the + * given string. This is APPROXIMATE as it only splits based on a + * comma which has no preceding backslash. Could be useful as + * large amounts of addresses will end up producing *large* + * structures when used with parseAddressList(). + * + * @param string $data Addresses to count + * @return int Approximate count + */ + public function approximateCount($data) + { + return count(preg_split('/(?@. This can be sufficient for most + * people. Optional stricter mode can be utilised which restricts + * mailbox characters allowed to alphanumeric, full stop, hyphen + * and underscore. + * + * @param string $data Address to check + * @param boolean $strict Optional stricter mode + * @return mixed False if it fails, an indexed array + * username/domain if it matches + */ + public function isValidInetAddress($data, $strict = false) + { + $regex = $strict ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i'; + if (preg_match($regex, trim($data), $matches)) { + return array($matches[1], $matches[2]); + } else { + return false; + } + } + +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/mail.php b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/mail.php new file mode 100644 index 0000000..ee1ecef --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/mail.php @@ -0,0 +1,168 @@ + + * @copyright 2010-2017 Chuck Hagenbuch + * @license http://opensource.org/licenses/BSD-3-Clause New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/Mail/ + */ + +/** + * internal PHP-mail() implementation of the PEAR Mail:: interface. + * @package Mail + * @version $Revision$ + */ +class Mail_mail extends Mail { + + /** + * Any arguments to pass to the mail() function. + * @var string + */ + var $_params = ''; + + /** + * Constructor. + * + * Instantiates a new Mail_mail:: object based on the parameters + * passed in. + * + * @param array $params Extra arguments for the mail() function. + */ + public function __construct($params = null) + { + // The other mail implementations accept parameters as arrays. + // In the interest of being consistent, explode an array into + // a string of parameter arguments. + if (is_array($params)) { + $this->_params = join(' ', $params); + } else { + $this->_params = $params; + } + + /* Because the mail() function may pass headers as command + * line arguments, we can't guarantee the use of the standard + * "\r\n" separator. Instead, we use the system's native line + * separator. */ + if (defined('PHP_EOL')) { + $this->sep = PHP_EOL; + } else { + $this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n"; + } + } + + /** + * Implements Mail_mail::send() function using php's built-in mail() + * command. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. This may contain recipients not + * specified in the headers, for Bcc:, resending + * messages, etc. + * + * @param array $headers The array of headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array value + * is the header value (ie, 'test'). The header + * produced from those values would be 'Subject: + * test'. + * + * @param string $body The full text of the message body, including any + * Mime parts, etc. + * + * @return mixed Returns true on success, or a PEAR_Error + * containing a descriptive error message on + * failure. + */ + public function send($recipients, $headers, $body) + { + if (!is_array($headers)) { + return PEAR::raiseError('$headers must be an array'); + } + + $result = $this->_sanitizeHeaders($headers); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + // If we're passed an array of recipients, implode it. + if (is_array($recipients)) { + $recipients = implode(', ', $recipients); + } + + // Get the Subject out of the headers array so that we can + // pass it as a seperate argument to mail(). + $subject = ''; + if (isset($headers['Subject'])) { + $subject = $headers['Subject']; + unset($headers['Subject']); + } + + // Also remove the To: header. The mail() function will add its own + // To: header based on the contents of $recipients. + unset($headers['To']); + + // Flatten the headers out. + $headerElements = $this->prepareHeaders($headers); + if (is_a($headerElements, 'PEAR_Error')) { + return $headerElements; + } + list(, $text_headers) = $headerElements; + + // We only use mail()'s optional fifth parameter if the additional + // parameters have been provided and we're not running in safe mode. + if (empty($this->_params) || ini_get('safe_mode')) { + $result = mail($recipients, $subject, $body, $text_headers); + } else { + $result = mail($recipients, $subject, $body, $text_headers, + $this->_params); + } + + // If the mail() function returned failure, we need to create a + // PEAR_Error object and return it instead of the boolean result. + if ($result === false) { + $result = PEAR::raiseError('mail() returned failure'); + } + + return $result; + } + +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/mock.php b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/mock.php new file mode 100644 index 0000000..24d46a8 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/mock.php @@ -0,0 +1,142 @@ + + * @copyright 2010-2017 Chuck Hagenbuch + * @license http://opensource.org/licenses/BSD-3-Clause New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/Mail/ + */ + +/** + * Mock implementation of the PEAR Mail:: interface for testing. + * @access public + * @package Mail + * @version $Revision$ + */ +class Mail_mock extends Mail { + + /** + * Array of messages that have been sent with the mock. + * + * @var array + */ + public $sentMessages = array(); + + /** + * Callback before sending mail. + * + * @var callback + */ + protected $_preSendCallback; + + /** + * Callback after sending mai. + * + * @var callback + */ + protected $_postSendCallback; + + /** + * Constructor. + * + * Instantiates a new Mail_mock:: object based on the parameters + * passed in. It looks for the following parameters, both optional: + * preSendCallback Called before an email would be sent. + * postSendCallback Called after an email would have been sent. + * + * @param array Hash containing any parameters. + */ + public function __construct($params) + { + if (isset($params['preSendCallback']) && + is_callable($params['preSendCallback'])) { + $this->_preSendCallback = $params['preSendCallback']; + } + + if (isset($params['postSendCallback']) && + is_callable($params['postSendCallback'])) { + $this->_postSendCallback = $params['postSendCallback']; + } + } + + /** + * Implements Mail_mock::send() function. Silently discards all + * mail. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. This may contain recipients not + * specified in the headers, for Bcc:, resending + * messages, etc. + * + * @param array $headers The array of headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array value + * is the header value (ie, 'test'). The header + * produced from those values would be 'Subject: + * test'. + * + * @param string $body The full text of the message body, including any + * Mime parts, etc. + * + * @return mixed Returns true on success, or a PEAR_Error + * containing a descriptive error message on + * failure. + */ + public function send($recipients, $headers, $body) + { + if ($this->_preSendCallback) { + call_user_func_array($this->_preSendCallback, + array(&$this, $recipients, $headers, $body)); + } + + $entry = array('recipients' => $recipients, 'headers' => $headers, 'body' => $body); + $this->sentMessages[] = $entry; + + if ($this->_postSendCallback) { + call_user_func_array($this->_postSendCallback, + array(&$this, $recipients, $headers, $body)); + } + + return true; + } + +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/null.php b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/null.php new file mode 100644 index 0000000..5e3ecb6 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/null.php @@ -0,0 +1,85 @@ + + * @copyright 2010-2017 Phil Kernick + * @license http://opensource.org/licenses/BSD-3-Clause New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/Mail/ + */ + +/** + * Null implementation of the PEAR Mail:: interface. + * @access public + * @package Mail + * @version $Revision$ + */ +class Mail_null extends Mail { + + /** + * Implements Mail_null::send() function. Silently discards all + * mail. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. This may contain recipients not + * specified in the headers, for Bcc:, resending + * messages, etc. + * + * @param array $headers The array of headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array value + * is the header value (ie, 'test'). The header + * produced from those values would be 'Subject: + * test'. + * + * @param string $body The full text of the message body, including any + * Mime parts, etc. + * + * @return mixed Returns true on success, or a PEAR_Error + * containing a descriptive error message on + * failure. + */ + public function send($recipients, $headers, $body) + { + return true; + } + +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/sendmail.php b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/sendmail.php new file mode 100644 index 0000000..7e8f804 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/sendmail.php @@ -0,0 +1,199 @@ + + * @author Chuck Hagenbuch + * @copyright 2010-2017 Chuck Hagenbuch + * @license http://opensource.org/licenses/BSD-3-Clause New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/Mail/ + */ + +/** + * Sendmail implementation of the PEAR Mail:: interface. + * @access public + * @package Mail + * @version $Revision$ + */ +class Mail_sendmail extends Mail { + + /** + * The location of the sendmail or sendmail wrapper binary on the + * filesystem. + * @var string + */ + var $sendmail_path = '/usr/sbin/sendmail'; + + /** + * Any extra command-line parameters to pass to the sendmail or + * sendmail wrapper binary. + * @var string + */ + var $sendmail_args = '-i'; + + /** + * Constructor. + * + * Instantiates a new Mail_sendmail:: object based on the parameters + * passed in. It looks for the following parameters: + * sendmail_path The location of the sendmail binary on the + * filesystem. Defaults to '/usr/sbin/sendmail'. + * + * sendmail_args Any extra parameters to pass to the sendmail + * or sendmail wrapper binary. + * + * If a parameter is present in the $params array, it replaces the + * default. + * + * @param array $params Hash containing any parameters different from the + * defaults. + */ + public function __construct($params) + { + if (isset($params['sendmail_path'])) { + $this->sendmail_path = $params['sendmail_path']; + } + if (isset($params['sendmail_args'])) { + $this->sendmail_args = $params['sendmail_args']; + } + + /* + * Because we need to pass message headers to the sendmail program on + * the commandline, we can't guarantee the use of the standard "\r\n" + * separator. Instead, we use the system's native line separator. + */ + if (defined('PHP_EOL')) { + $this->sep = PHP_EOL; + } else { + $this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n"; + } + } + + /** + * Implements Mail::send() function using the sendmail + * command-line binary. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. This may contain recipients not + * specified in the headers, for Bcc:, resending + * messages, etc. + * + * @param array $headers The array of headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array value + * is the header value (ie, 'test'). The header + * produced from those values would be 'Subject: + * test'. + * + * @param string $body The full text of the message body, including any + * Mime parts, etc. + * + * @return mixed Returns true on success, or a PEAR_Error + * containing a descriptive error message on + * failure. + */ + public function send($recipients, $headers, $body) + { + if (!is_array($headers)) { + return PEAR::raiseError('$headers must be an array'); + } + + $result = $this->_sanitizeHeaders($headers); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + $recipients = $this->parseRecipients($recipients); + if (is_a($recipients, 'PEAR_Error')) { + return $recipients; + } + $recipients = implode(' ', array_map('escapeshellarg', $recipients)); + + $headerElements = $this->prepareHeaders($headers); + if (is_a($headerElements, 'PEAR_Error')) { + return $headerElements; + } + list($from, $text_headers) = $headerElements; + + /* Since few MTAs are going to allow this header to be forged + * unless it's in the MAIL FROM: exchange, we'll use + * Return-Path instead of From: if it's set. */ + if (!empty($headers['Return-Path'])) { + $from = $headers['Return-Path']; + } + + if (!isset($from)) { + return PEAR::raiseError('No from address given.'); + } elseif (strpos($from, ' ') !== false || + strpos($from, ';') !== false || + strpos($from, '&') !== false || + strpos($from, '`') !== false) { + return PEAR::raiseError('From address specified with dangerous characters.'); + } + + $from = escapeshellarg($from); // Security bug #16200 + + $mail = @popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w'); + if (!$mail) { + return PEAR::raiseError('Failed to open sendmail [' . $this->sendmail_path . '] for execution.'); + } + + // Write the headers following by two newlines: one to end the headers + // section and a second to separate the headers block from the body. + fputs($mail, $text_headers . $this->sep . $this->sep); + + fputs($mail, $body); + $result = pclose($mail); + if (version_compare(phpversion(), '4.2.3') == -1) { + // With older php versions, we need to shift the pclose + // result to get the exit code. + $result = $result >> 8 & 0xFF; + } + + if ($result != 0) { + return PEAR::raiseError('sendmail returned error code ' . $result, + $result); + } + + return true; + } + +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/smtp.php b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/smtp.php new file mode 100644 index 0000000..511a803 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/smtp.php @@ -0,0 +1,459 @@ + + * @author Chuck Hagenbuch + * @copyright 2010-2017 Chuck Hagenbuch + * @license http://opensource.org/licenses/BSD-3-Clause New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/Mail/ + */ + +/** Error: Failed to create a Net_SMTP object */ +define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000); + +/** Error: Failed to connect to SMTP server */ +define('PEAR_MAIL_SMTP_ERROR_CONNECT', 10001); + +/** Error: SMTP authentication failure */ +define('PEAR_MAIL_SMTP_ERROR_AUTH', 10002); + +/** Error: No From: address has been provided */ +define('PEAR_MAIL_SMTP_ERROR_FROM', 10003); + +/** Error: Failed to set sender */ +define('PEAR_MAIL_SMTP_ERROR_SENDER', 10004); + +/** Error: Failed to add recipient */ +define('PEAR_MAIL_SMTP_ERROR_RECIPIENT', 10005); + +/** Error: Failed to send data */ +define('PEAR_MAIL_SMTP_ERROR_DATA', 10006); + +/** + * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class. + * @access public + * @package Mail + * @version $Revision$ + */ +class Mail_smtp extends Mail { + + /** + * SMTP connection object. + * + * @var object + * @access private + */ + var $_smtp = null; + + /** + * The list of service extension parameters to pass to the Net_SMTP + * mailFrom() command. + * + * @var array + */ + var $_extparams = array(); + + /** + * The SMTP host to connect to. + * + * @var string + */ + var $host = 'localhost'; + + /** + * The port the SMTP server is on. + * + * @var integer + */ + var $port = 25; + + /** + * Should SMTP authentication be used? + * + * This value may be set to true, false or the name of a specific + * authentication method. + * + * If the value is set to true, the Net_SMTP package will attempt to use + * the best authentication method advertised by the remote SMTP server. + * + * @var mixed + */ + var $auth = false; + + /** + * The username to use if the SMTP server requires authentication. + * + * @var string + */ + var $username = ''; + + /** + * The password to use if the SMTP server requires authentication. + * + * @var string + */ + var $password = ''; + + /** + * Hostname or domain that will be sent to the remote SMTP server in the + * HELO / EHLO message. + * + * @var string + */ + var $localhost = 'localhost'; + + /** + * SMTP connection timeout value. NULL indicates no timeout. + * + * @var integer + */ + var $timeout = null; + + /** + * Turn on Net_SMTP debugging? + * + * @var boolean $debug + */ + var $debug = false; + + /** + * Indicates whether or not the SMTP connection should persist over + * multiple calls to the send() method. + * + * @var boolean + */ + var $persist = false; + + /** + * Use SMTP command pipelining (specified in RFC 2920) if the SMTP server + * supports it. This speeds up delivery over high-latency connections. By + * default, use the default value supplied by Net_SMTP. + * + * @var boolean + */ + var $pipelining; + + /** + * The list of socket options + * + * @var array + */ + var $socket_options = array(); + + /** + * Constructor. + * + * Instantiates a new Mail_smtp:: object based on the parameters + * passed in. It looks for the following parameters: + * host The server to connect to. Defaults to localhost. + * port The port to connect to. Defaults to 25. + * auth SMTP authentication. Defaults to none. + * username The username to use for SMTP auth. No default. + * password The password to use for SMTP auth. No default. + * localhost The local hostname / domain. Defaults to localhost. + * timeout The SMTP connection timeout. Defaults to none. + * verp Whether to use VERP or not. Defaults to false. + * DEPRECATED as of 1.2.0 (use setMailParams()). + * debug Activate SMTP debug mode? Defaults to false. + * persist Should the SMTP connection persist? + * pipelining Use SMTP command pipelining + * + * If a parameter is present in the $params array, it replaces the + * default. + * + * @param array Hash containing any parameters different from the + * defaults. + */ + public function __construct($params) + { + if (isset($params['host'])) $this->host = $params['host']; + if (isset($params['port'])) $this->port = $params['port']; + if (isset($params['auth'])) $this->auth = $params['auth']; + if (isset($params['username'])) $this->username = $params['username']; + if (isset($params['password'])) $this->password = $params['password']; + if (isset($params['localhost'])) $this->localhost = $params['localhost']; + if (isset($params['timeout'])) $this->timeout = $params['timeout']; + if (isset($params['debug'])) $this->debug = (bool)$params['debug']; + if (isset($params['persist'])) $this->persist = (bool)$params['persist']; + if (isset($params['pipelining'])) $this->pipelining = (bool)$params['pipelining']; + if (isset($params['socket_options'])) $this->socket_options = $params['socket_options']; + // Deprecated options + if (isset($params['verp'])) { + $this->addServiceExtensionParameter('XVERP', is_bool($params['verp']) ? null : $params['verp']); + } + } + + /** + * Destructor implementation to ensure that we disconnect from any + * potentially-alive persistent SMTP connections. + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * Implements Mail::send() function using SMTP. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of recipients, + * each RFC822 valid. This may contain recipients not + * specified in the headers, for Bcc:, resending + * messages, etc. + * + * @param array $headers The array of headers to send with the mail, in an + * associative array, where the array key is the + * header name (e.g., 'Subject'), and the array value + * is the header value (e.g., 'test'). The header + * produced from those values would be 'Subject: + * test'. + * + * @param string $body The full text of the message body, including any + * MIME parts, etc. + * + * @return mixed Returns true on success, or a PEAR_Error + * containing a descriptive error message on + * failure. + */ + public function send($recipients, $headers, $body) + { + $result = $this->send_or_fail($recipients, $headers, $body); + + /* If persistent connections are disabled, destroy our SMTP object. */ + if ($this->persist === false) { + $this->disconnect(); + } + + return $result; + } + + protected function send_or_fail($recipients, $headers, $body) + { + /* If we don't already have an SMTP object, create one. */ + $result = $this->getSMTPObject(); + if (PEAR::isError($result)) { + return $result; + } + + if (!is_array($headers)) { + return PEAR::raiseError('$headers must be an array'); + } + + $this->_sanitizeHeaders($headers); + + $headerElements = $this->prepareHeaders($headers); + if (is_a($headerElements, 'PEAR_Error')) { + $this->_smtp->rset(); + return $headerElements; + } + list($from, $textHeaders) = $headerElements; + + /* Since few MTAs are going to allow this header to be forged + * unless it's in the MAIL FROM: exchange, we'll use + * Return-Path instead of From: if it's set. */ + if (!empty($headers['Return-Path'])) { + $from = $headers['Return-Path']; + } + + if (!isset($from)) { + $this->_smtp->rset(); + return PEAR::raiseError('No From: address has been provided', + PEAR_MAIL_SMTP_ERROR_FROM); + } + + $params = null; + if (!empty($this->_extparams)) { + foreach ($this->_extparams as $key => $val) { + $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val); + } + } + if (PEAR::isError($res = $this->_smtp->mailFrom($from, ltrim($params)))) { + $error = $this->_error("Failed to set sender: $from", $res); + $this->_smtp->rset(); + return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_SENDER); + } + + $recipients = $this->parseRecipients($recipients); + if (is_a($recipients, 'PEAR_Error')) { + $this->_smtp->rset(); + return $recipients; + } + + foreach ($recipients as $recipient) { + $res = $this->_smtp->rcptTo($recipient); + if (is_a($res, 'PEAR_Error')) { + $error = $this->_error("Failed to add recipient: $recipient", $res); + $this->_smtp->rset(); + return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_RECIPIENT); + } + } + + /* Send the message's headers and the body as SMTP data. */ + $res = $this->_smtp->data($body, $textHeaders); + list(,$args) = $this->_smtp->getResponse(); + + if (preg_match("/ queued as (.*)/", $args, $queued)) { + $this->queued_as = $queued[1]; + } + + /* we need the greeting; from it we can extract the authorative name of the mail server we've really connected to. + * ideal if we're connecting to a round-robin of relay servers and need to track which exact one took the email */ + $this->greeting = $this->_smtp->getGreeting(); + + if (is_a($res, 'PEAR_Error')) { + $error = $this->_error('Failed to send data', $res); + $this->_smtp->rset(); + return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_DATA); + } + + return true; + } + + /** + * Connect to the SMTP server by instantiating a Net_SMTP object. + * + * @return mixed Returns a reference to the Net_SMTP object on success, or + * a PEAR_Error containing a descriptive error message on + * failure. + * + * @since 1.2.0 + */ + public function getSMTPObject() + { + if (is_object($this->_smtp) !== false) { + return $this->_smtp; + } + $this->_smtp = new Net_SMTP($this->host, + $this->port, + $this->localhost, + $this->pipelining, + 0, + $this->socket_options); + + /* If we still don't have an SMTP object at this point, fail. */ + if (is_object($this->_smtp) === false) { + return PEAR::raiseError('Failed to create a Net_SMTP object', + PEAR_MAIL_SMTP_ERROR_CREATE); + } + + /* Configure the SMTP connection. */ + if ($this->debug) { + $this->_smtp->setDebug(true); + } + + /* Attempt to connect to the configured SMTP server. */ + if (PEAR::isError($res = $this->_smtp->connect($this->timeout))) { + $error = $this->_error('Failed to connect to ' . + $this->host . ':' . $this->port, + $res); + return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_CONNECT); + } + + /* Attempt to authenticate if authentication has been enabled. */ + if ($this->auth) { + $method = is_string($this->auth) ? $this->auth : ''; + + if (PEAR::isError($res = $this->_smtp->auth($this->username, + $this->password, + $method))) { + $error = $this->_error("$method authentication failure", + $res); + $this->_smtp->rset(); + return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_AUTH); + } + } + + return $this->_smtp; + } + + /** + * Add parameter associated with a SMTP service extension. + * + * @param string Extension keyword. + * @param string Any value the keyword needs. + * + * @since 1.2.0 + */ + public function addServiceExtensionParameter($keyword, $value = null) + { + $this->_extparams[$keyword] = $value; + } + + /** + * Disconnect and destroy the current SMTP connection. + * + * @return boolean True if the SMTP connection no longer exists. + * + * @since 1.1.9 + */ + public function disconnect() + { + /* If we have an SMTP object, disconnect and destroy it. */ + if (is_object($this->_smtp) && $this->_smtp->disconnect()) { + $this->_smtp = null; + } + + /* We are disconnected if we no longer have an SMTP object. */ + return ($this->_smtp === null); + } + + /** + * Build a standardized string describing the current SMTP error. + * + * @param string $text Custom string describing the error context. + * @param object $error Reference to the current PEAR_Error object. + * + * @return string A string describing the current SMTP error. + * + * @since 1.1.7 + */ + protected function _error($text, $error) + { + /* Split the SMTP response into a code and a response string. */ + list($code, $response) = $this->_smtp->getResponse(); + + /* Build our standardized error string. */ + return $text + . ' [SMTP: ' . $error->getMessage() + . " (code: $code, response: $response)]"; + } + +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/smtpmx.php b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/smtpmx.php new file mode 100644 index 0000000..e26db1b --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/Mail/smtpmx.php @@ -0,0 +1,504 @@ + + * @copyright 2010-2017 gERD Schaufelberger + * @license http://opensource.org/licenses/BSD-3-Clause New BSD License + * @version CVS: $Id$ + * @link http://pear.php.net/package/Mail/ + */ + +require_once 'Net/SMTP.php'; + +/** + * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class. + * + * + * @access public + * @author gERD Schaufelberger + * @package Mail + * @version $Revision$ + */ +class Mail_smtpmx extends Mail { + + /** + * SMTP connection object. + * + * @var object + * @access private + */ + var $_smtp = null; + + /** + * The port the SMTP server is on. + * @var integer + * @see getservicebyname() + */ + var $port = 25; + + /** + * Hostname or domain that will be sent to the remote SMTP server in the + * HELO / EHLO message. + * + * @var string + * @see posix_uname() + */ + var $mailname = 'localhost'; + + /** + * SMTP connection timeout value. NULL indicates no timeout. + * + * @var integer + */ + var $timeout = 10; + + /** + * use either PEAR:Net_DNS or getmxrr + * + * @var boolean + */ + var $withNetDns = true; + + /** + * PEAR:Net_DNS_Resolver + * + * @var object + */ + var $resolver; + + /** + * Whether to use VERP or not. If not a boolean, the string value + * will be used as the VERP separators. + * + * @var mixed boolean or string + */ + var $verp = false; + + /** + * Whether to use VRFY or not. + * + * @var boolean $vrfy + */ + var $vrfy = false; + + /** + * Switch to test mode - don't send emails for real + * + * @var boolean $debug + */ + var $test = false; + + /** + * Turn on Net_SMTP debugging? + * + * @var boolean $peardebug + */ + var $debug = false; + + /** + * internal error codes + * + * translate internal error identifier to PEAR-Error codes and human + * readable messages. + * + * @var boolean $debug + * @todo as I need unique error-codes to identify what exactly went wrond + * I did not use intergers as it should be. Instead I added a "namespace" + * for each code. This avoids conflicts with error codes from different + * classes. How can I use unique error codes and stay conform with PEAR? + */ + var $errorCode = array( + 'not_connected' => array( + 'code' => 1, + 'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.' + ), + 'failed_vrfy_rcpt' => array( + 'code' => 2, + 'msg' => 'Recipient "{RCPT}" could not be veryfied.' + ), + 'failed_set_from' => array( + 'code' => 3, + 'msg' => 'Failed to set sender: {FROM}.' + ), + 'failed_set_rcpt' => array( + 'code' => 4, + 'msg' => 'Failed to set recipient: {RCPT}.' + ), + 'failed_send_data' => array( + 'code' => 5, + 'msg' => 'Failed to send mail to: {RCPT}.' + ), + 'no_from' => array( + 'code' => 5, + 'msg' => 'No from address has be provided.' + ), + 'send_data' => array( + 'code' => 7, + 'msg' => 'Failed to create Net_SMTP object.' + ), + 'no_mx' => array( + 'code' => 8, + 'msg' => 'No MX-record for {RCPT} found.' + ), + 'no_resolver' => array( + 'code' => 9, + 'msg' => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"' + ), + 'failed_rset' => array( + 'code' => 10, + 'msg' => 'RSET command failed, SMTP-connection corrupt.' + ), + ); + + /** + * Constructor. + * + * Instantiates a new Mail_smtp:: object based on the parameters + * passed in. It looks for the following parameters: + * mailname The name of the local mail system (a valid hostname which matches the reverse lookup) + * port smtp-port - the default comes from getservicebyname() and should work fine + * timeout The SMTP connection timeout. Defaults to 30 seconds. + * vrfy Whether to use VRFY or not. Defaults to false. + * verp Whether to use VERP or not. Defaults to false. + * test Activate test mode? Defaults to false. + * debug Activate SMTP and Net_DNS debug mode? Defaults to false. + * netdns whether to use PEAR:Net_DNS or the PHP build in function getmxrr, default is true + * + * If a parameter is present in the $params array, it replaces the + * default. + * + * @access public + * @param array Hash containing any parameters different from the + * defaults. + * @see _Mail_smtpmx() + */ + function __construct($params) + { + if (isset($params['mailname'])) { + $this->mailname = $params['mailname']; + } else { + // try to find a valid mailname + if (function_exists('posix_uname')) { + $uname = posix_uname(); + $this->mailname = $uname['nodename']; + } + } + + // port number + if (isset($params['port'])) { + $this->_port = $params['port']; + } else { + $this->_port = getservbyname('smtp', 'tcp'); + } + + if (isset($params['timeout'])) $this->timeout = $params['timeout']; + if (isset($params['verp'])) $this->verp = $params['verp']; + if (isset($params['test'])) $this->test = $params['test']; + if (isset($params['peardebug'])) $this->test = $params['peardebug']; + if (isset($params['netdns'])) $this->withNetDns = $params['netdns']; + } + + /** + * Constructor wrapper for PHP4 + * + * @access public + * @param array Hash containing any parameters different from the defaults + * @see __construct() + */ + function Mail_smtpmx($params) + { + $this->__construct($params); + register_shutdown_function(array(&$this, '__destruct')); + } + + /** + * Destructor implementation to ensure that we disconnect from any + * potentially-alive persistent SMTP connections. + */ + function __destruct() + { + if (is_object($this->_smtp)) { + $this->_smtp->disconnect(); + $this->_smtp = null; + } + } + + /** + * Implements Mail::send() function using SMTP direct delivery + * + * @access public + * @param mixed $recipients in RFC822 style or array + * @param array $headers The array of headers to send with the mail. + * @param string $body The full text of the message body, + * @return mixed Returns true on success, or a PEAR_Error + */ + function send($recipients, $headers, $body) + { + if (!is_array($headers)) { + return PEAR::raiseError('$headers must be an array'); + } + + $result = $this->_sanitizeHeaders($headers); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + // Prepare headers + $headerElements = $this->prepareHeaders($headers); + if (is_a($headerElements, 'PEAR_Error')) { + return $headerElements; + } + list($from, $textHeaders) = $headerElements; + + // use 'Return-Path' if possible + if (!empty($headers['Return-Path'])) { + $from = $headers['Return-Path']; + } + if (!isset($from)) { + return $this->_raiseError('no_from'); + } + + // Prepare recipients + $recipients = $this->parseRecipients($recipients); + if (is_a($recipients, 'PEAR_Error')) { + return $recipients; + } + + foreach ($recipients as $rcpt) { + list($user, $host) = explode('@', $rcpt); + + $mx = $this->_getMx($host); + if (is_a($mx, 'PEAR_Error')) { + return $mx; + } + + if (empty($mx)) { + $info = array('rcpt' => $rcpt); + return $this->_raiseError('no_mx', $info); + } + + $connected = false; + foreach ($mx as $mserver => $mpriority) { + $this->_smtp = new Net_SMTP($mserver, $this->port, $this->mailname); + + // configure the SMTP connection. + if ($this->debug) { + $this->_smtp->setDebug(true); + } + + // attempt to connect to the configured SMTP server. + $res = $this->_smtp->connect($this->timeout); + if (is_a($res, 'PEAR_Error')) { + $this->_smtp = null; + continue; + } + + // connection established + if ($res) { + $connected = true; + break; + } + } + + if (!$connected) { + $info = array( + 'host' => implode(', ', array_keys($mx)), + 'port' => $this->port, + 'rcpt' => $rcpt, + ); + return $this->_raiseError('not_connected', $info); + } + + // Verify recipient + if ($this->vrfy) { + $res = $this->_smtp->vrfy($rcpt); + if (is_a($res, 'PEAR_Error')) { + $info = array('rcpt' => $rcpt); + return $this->_raiseError('failed_vrfy_rcpt', $info); + } + } + + // mail from: + $args['verp'] = $this->verp; + $res = $this->_smtp->mailFrom($from, $args); + if (is_a($res, 'PEAR_Error')) { + $info = array('from' => $from); + return $this->_raiseError('failed_set_from', $info); + } + + // rcpt to: + $res = $this->_smtp->rcptTo($rcpt); + if (is_a($res, 'PEAR_Error')) { + $info = array('rcpt' => $rcpt); + return $this->_raiseError('failed_set_rcpt', $info); + } + + // Don't send anything in test mode + if ($this->test) { + $result = $this->_smtp->rset(); + $res = $this->_smtp->rset(); + if (is_a($res, 'PEAR_Error')) { + return $this->_raiseError('failed_rset'); + } + + $this->_smtp->disconnect(); + $this->_smtp = null; + return true; + } + + // Send data + $res = $this->_smtp->data($body, $textHeaders); + if (is_a($res, 'PEAR_Error')) { + $info = array('rcpt' => $rcpt); + return $this->_raiseError('failed_send_data', $info); + } + + $this->_smtp->disconnect(); + $this->_smtp = null; + } + + return true; + } + + /** + * Recieve mx rexords for a spciefied host + * + * The MX records + * + * @access private + * @param string $host mail host + * @return mixed sorted + */ + function _getMx($host) + { + $mx = array(); + + if ($this->withNetDns) { + $res = $this->_loadNetDns(); + if (is_a($res, 'PEAR_Error')) { + return $res; + } + + $response = $this->resolver->query($host, 'MX'); + if (!$response) { + return false; + } + + foreach ($response->answer as $rr) { + if ($rr->type == 'MX') { + $mx[$rr->exchange] = $rr->preference; + } + } + } else { + $mxHost = array(); + $mxWeight = array(); + + if (!getmxrr($host, $mxHost, $mxWeight)) { + return false; + } + for ($i = 0; $i < count($mxHost); ++$i) { + $mx[$mxHost[$i]] = $mxWeight[$i]; + } + } + + asort($mx); + return $mx; + } + + /** + * initialize PEAR:Net_DNS_Resolver + * + * @access private + * @return boolean true on success + */ + function _loadNetDns() + { + if (is_object($this->resolver)) { + return true; + } + + if (!include_once 'Net/DNS.php') { + return $this->_raiseError('no_resolver'); + } + + $this->resolver = new Net_DNS_Resolver(); + if ($this->debug) { + $this->resolver->test = 1; + } + + return true; + } + + /** + * raise standardized error + * + * include additional information in error message + * + * @access private + * @param string $id maps error ids to codes and message + * @param array $info optional information in associative array + * @see _errorCode + */ + function _raiseError($id, $info = array()) + { + $code = $this->errorCode[$id]['code']; + $msg = $this->errorCode[$id]['msg']; + + // include info to messages + if (!empty($info)) { + $search = array(); + $replace = array(); + + foreach ($info as $key => $value) { + array_push($search, '{' . strtoupper($key) . '}'); + array_push($replace, $value); + } + + $msg = str_replace($search, $replace, $msg); + } + + return PEAR::raiseError($msg, $code); + } + +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/README.rst b/democracy/sai/saimod_beta/cannon/Pear/Mail/README.rst new file mode 100644 index 0000000..9697e63 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/README.rst @@ -0,0 +1,53 @@ +**** +Mail +**** +Class that provides multiple interfaces for sending emails. + +PEAR's Mail package defines an interface for implementing mailers under the +PEAR hierarchy. +It also provides supporting functions useful to multiple mailer backends. + +Currently supported backends include: + +- PHP's native ``mail()`` function +- sendmail +- SMTP + +This package also provides a `RFC 822`__ email address list validation utility class. + +Use Mail in combination with `Mail_Mime`__ to send HTML emails or emails with +attachments - have a look at the example__. + +__ https://tools.ietf.org/html/rfc822 +__ http://pear.php.net/package/Mail_Mime +__ http://pear.php.net/manual/en/package.mail.mail-mime.example.php + +============ +Installation +============ + +PEAR +==== +:: + + $ pear install mail + +Composer +======== +:: + + $ composer require pear/mail + +===== +Links +===== +Homepage + http://pear.php.net/package/Mail +Source code + https://github.com/pear/Mail +Issue tracker + http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Mail +Unit test status + https://travis-ci.org/pear/Mail +Packagist + https://packagist.org/packages/pear/mail diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/composer.json b/democracy/sai/saimod_beta/cannon/Pear/Mail/composer.json new file mode 100644 index 0000000..a167cc6 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/composer.json @@ -0,0 +1,46 @@ +{ + "authors": [ + { + "email": "chuck@horde.org", + "name": "Chuck Hagenbuch", + "role": "Lead" + }, + { + "email": "richard@phpguru.org", + "name": "Richard Heyes", + "role": "Developer" + }, + { + "email": "alec@alec.pl", + "name": "Aleksander Machniak", + "role": "Developer" + } + ], + "autoload": { + "psr-0": { + "Mail": "./" + } + }, + "description": "Class that provides multiple interfaces for sending emails.", + "homepage": "http://pear.php.net/package/Mail", + "include-path": [ + "./" + ], + "license": "BSD-2-Clause", + "name": "pear/mail", + "support": { + "issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Mail", + "source": "https://github.com/pear/Mail" + }, + "type": "library", + "require": { + "php": ">=5.2.1", + "pear/pear-core-minimal": "~1.9" + }, + "require-dev": { + "pear/pear": "*" + }, + "suggest": { + "pear/net_smtp": "Install optionally via your project's composer.json" + } +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/mime.php b/democracy/sai/saimod_beta/cannon/Pear/Mail/mime.php new file mode 100644 index 0000000..c3da7bb --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/mime.php @@ -0,0 +1,1616 @@ += 5 + * + * LICENSE: This LICENSE is in the BSD license style. + * Copyright (c) 2002-2003, Richard Heyes + * Copyright (c) 2003-2006, PEAR + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name of the authors, nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Mail + * @package Mail_Mime + * @author Richard Heyes + * @author Tomas V.V. Cox + * @author Cipriano Groenendal + * @author Sean Coates + * @author Aleksander Machniak + * @copyright 2003-2006 PEAR + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/Mail_mime + * + * This class is based on HTML Mime Mail class from + * Richard Heyes which was based also + * in the mime_mail.class by Tobias Ratschiller + * and Sascha Schumann + */ + + +//require_once 'PEAR.php'; +require_once 'mimePart.php'; + + +/** + * The Mail_Mime class provides an OO interface to create MIME + * enabled email messages. This way you can create emails that + * contain plain-text bodies, HTML bodies, attachments, inline + * images and specific headers. + * + * @category Mail + * @package Mail_Mime + * @author Richard Heyes + * @author Tomas V.V. Cox + * @author Cipriano Groenendal + * @author Sean Coates + * @copyright 2003-2006 PEAR + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/Mail_mime + */ +class Mail_mime +{ + /** + * Contains the plain text part of the email + * + * @var string + */ + protected $txtbody; + + /** + * Contains the html part of the email + * + * @var string + */ + protected $htmlbody; + + /** + * Contains the text/calendar part of the email + * + * @var string + */ + protected $calbody; + + /** + * list of the attached images + * + * @var array + */ + protected $html_images = array(); + + /** + * list of the attachements + * + * @var array + */ + protected $parts = array(); + + /** + * Headers for the mail + * + * @var array + */ + protected $headers = array(); + + /** + * Build parameters + * + * @var array + */ + protected $build_params = array( + // What encoding to use for the headers + // Options: quoted-printable or base64 + 'head_encoding' => 'quoted-printable', + // What encoding to use for plain text + // Options: 7bit, 8bit, base64, or quoted-printable + 'text_encoding' => 'quoted-printable', + // What encoding to use for html + // Options: 7bit, 8bit, base64, or quoted-printable + 'html_encoding' => 'quoted-printable', + // What encoding to use for calendar part + // Options: 7bit, 8bit, base64, or quoted-printable + 'calendar_encoding' => 'quoted-printable', + // The character set to use for html + 'html_charset' => 'ISO-8859-1', + // The character set to use for text + 'text_charset' => 'ISO-8859-1', + // The character set to use for calendar part + 'calendar_charset' => 'UTF-8', + // The character set to use for headers + 'head_charset' => 'ISO-8859-1', + // End-of-line sequence + 'eol' => "\r\n", + // Delay attachment files IO until building the message + 'delay_file_io' => false, + // Default calendar method + 'calendar_method' => 'request', + // multipart part preamble (RFC2046 5.1.1) + 'preamble' => '', + ); + + + /** + * Constructor function + * + * @param mixed $params Build parameters that change the way the email + * is built. Should be an associative array. + * See $_build_params. + * + * @return void + */ + public function __construct($params = array()) + { + // Backward-compatible EOL setting + if (is_string($params)) { + $this->build_params['eol'] = $params; + } else if (defined('MAIL_MIME_CRLF') && !isset($params['eol'])) { + $this->build_params['eol'] = MAIL_MIME_CRLF; + } + + // Update build parameters + if (!empty($params) && is_array($params)) { + $this->build_params = array_merge($this->build_params, $params); + } + } + + /** + * Set build parameter value + * + * @param string $name Parameter name + * @param string $value Parameter value + * + * @return void + * @since 1.6.0 + */ + public function setParam($name, $value) + { + $this->build_params[$name] = $value; + } + + /** + * Get build parameter value + * + * @param string $name Parameter name + * + * @return mixed Parameter value + * @since 1.6.0 + */ + public function getParam($name) + { + return isset($this->build_params[$name]) ? $this->build_params[$name] : null; + } + + /** + * Accessor function to set the body text. Body text is used if + * it's not an html mail being sent or else is used to fill the + * text/plain part that emails clients who don't support + * html should show. + * + * @param string $data Either a string or the file name with the contents + * @param bool $isfile If true the first param should be treated + * as a file name, else as a string (default) + * @param bool $append If true the text or file is appended to + * the existing body, else the old body is + * overwritten + * + * @return mixed True on success or PEAR_Error object + */ + public function setTXTBody($data, $isfile = false, $append = false) + { + return $this->setBody('txtbody', $data, $isfile, $append); + } + + /** + * Get message text body + * + * @return string Text body + * @since 1.6.0 + */ + public function getTXTBody() + { + return $this->txtbody; + } + + /** + * Adds a html part to the mail. + * + * @param string $data Either a string or the file name with the contents + * @param bool $isfile A flag that determines whether $data is a + * filename, or a string(false, default) + * + * @return bool True on success or PEAR_Error object + */ + public function setHTMLBody($data, $isfile = false) + { + return $this->setBody('htmlbody', $data, $isfile); + } + + /** + * Get message HTML body + * + * @return string HTML body + * @since 1.6.0 + */ + public function getHTMLBody() + { + return $this->htmlbody; + } + + /** + * Function to set a body of text/calendar part (not attachment) + * + * @param string $data Either a string or the file name with the contents + * @param bool $isfile If true the first param should be treated + * as a file name, else as a string (default) + * @param bool $append If true the text or file is appended to + * the existing body, else the old body is + * overwritten + * @param string $method iCalendar object method + * @param string $charset iCalendar character set + * @param string $encoding Transfer encoding + * + * @return mixed True on success or PEAR_Error object + * @since 1.9.0 + */ + public function setCalendarBody($data, $isfile = false, $append = false, + $method = 'request', $charset = 'UTF-8', $encoding = 'quoted-printable' + ) { + $result = $this->setBody('calbody', $data, $isfile, $append); + + if ($result === true) { + $this->build_params['calendar_method'] = $method; + $this->build_params['calendar_charset'] = $charset; + $this->build_params['calendar_encoding'] = $encoding; + } + } + + /** + * Get body of calendar part + * + * @return string Calendar part body + * @since 1.9.0 + */ + public function getCalendarBody() + { + return $this->calbody; + } + + /** + * Adds an image to the list of embedded images. + * Images added this way will be added as related parts of the HTML message. + * + * To correctly match the HTML image with the related attachment + * HTML should refer to it by a filename (specified in $file or $name + * arguments) or by cid: (specified in $content_id arg). + * + * @param string $file The image file name OR image data itself + * @param string $c_type The content type + * @param string $name The filename of the image. Used to find + * the image in HTML content. + * @param bool $isfile Whether $file is a filename or not. + * Defaults to true + * @param string $content_id Desired Content-ID of MIME part + * Defaults to generated unique ID + * + * @return bool True on success + */ + public function addHTMLImage($file, + $c_type = 'application/octet-stream', + $name = '', + $isfile = true, + $content_id = null + ) { + $bodyfile = null; + + if ($isfile) { + // Don't load file into memory + if ($this->build_params['delay_file_io']) { + $filedata = null; + $bodyfile = $file; + } else { + if (self::isError($filedata = $this->file2str($file))) { + return $filedata; + } + } + + $filename = $name ? $name : $file; + } else { + $filedata = $file; + $filename = $name; + } + + if (!$content_id) { + $content_id = preg_replace('/[^0-9a-zA-Z]/', '', uniqid(time(), true)); + } + + $this->html_images[] = array( + 'body' => $filedata, + 'body_file' => $bodyfile, + 'name' => $filename, + 'c_type' => $c_type, + 'cid' => $content_id + ); + + return true; + } + + /** + * Adds a file to the list of attachments. + * + * @param mixed $file The file name or the file contents itself, + * it can be also Mail_mimePart object + * @param string $c_type The content type + * @param string $name The filename of the attachment + * Only use if $file is the contents + * @param bool $isfile Whether $file is a filename or not. Defaults to true + * @param string $encoding The type of encoding to use. Defaults to base64. + * Possible values: 7bit, 8bit, base64 or quoted-printable. + * @param string $disposition The content-disposition of this file + * Defaults to attachment. + * Possible values: attachment, inline. + * @param string $charset The character set of attachment's content. + * @param string $language The language of the attachment + * @param string $location The RFC 2557.4 location of the attachment + * @param string $n_encoding Encoding of the attachment's name in Content-Type + * By default filenames are encoded using RFC2231 method + * Here you can set RFC2047 encoding (quoted-printable + * or base64) instead + * @param string $f_encoding Encoding of the attachment's filename + * in Content-Disposition header. + * @param string $description Content-Description header + * @param string $h_charset The character set of the headers e.g. filename + * If not specified, $charset will be used + * @param array $add_headers Additional part headers. Array keys can be in form + * of : + * + * @return mixed True on success or PEAR_Error object + */ + public function addAttachment($file, + $c_type = 'application/octet-stream', + $name = '', + $isfile = true, + $encoding = 'base64', + $disposition = 'attachment', + $charset = '', + $language = '', + $location = '', + $n_encoding = null, + $f_encoding = null, + $description = '', + $h_charset = null, + $add_headers = array() + ) { + if ($file instanceof Mail_mimePart) { + $this->parts[] = $file; + return true; + } + + $bodyfile = null; + + if ($isfile) { + // Don't load file into memory + if ($this->build_params['delay_file_io']) { + $filedata = null; + $bodyfile = $file; + } else { + if (self::isError($filedata = $this->file2str($file))) { + return $filedata; + } + } + // Force the name the user supplied, otherwise use $file + $filename = ($name ? $name : $this->basename($file)); + } else { + $filedata = $file; + $filename = $name; + } + + if (!strlen($filename)) { + $msg = "The supplied filename for the attachment can't be empty"; + return self::raiseError($msg); + } + + $this->parts[] = array( + 'body' => $filedata, + 'body_file' => $bodyfile, + 'name' => $filename, + 'c_type' => $c_type, + 'charset' => $charset, + 'encoding' => $encoding, + 'language' => $language, + 'location' => $location, + 'disposition' => $disposition, + 'description' => $description, + 'add_headers' => $add_headers, + 'name_encoding' => $n_encoding, + 'filename_encoding' => $f_encoding, + 'headers_charset' => $h_charset, + ); + + return true; + } + + /** + * Checks if the current message has many parts + * + * @return bool True if the message has many parts, False otherwise. + * @since 1.9.0 + */ + public function isMultipart() + { + return count($this->parts) > 0 || count($this->html_images) > 0 + || (strlen($this->htmlbody) > 0 && strlen($this->txtbody) > 0); + } + + /** + * Get the contents of the given file name as string + * + * @param string $file_name Path of file to process + * + * @return string Contents of $file_name + */ + protected function file2str($file_name) + { + // Check state of file and raise an error properly + if (!file_exists($file_name)) { + return self::raiseError('File not found: ' . $file_name); + } + if (!is_file($file_name)) { + return self::raiseError('Not a regular file: ' . $file_name); + } + if (!is_readable($file_name)) { + return self::raiseError('File is not readable: ' . $file_name); + } + + // Temporarily reset magic_quotes_runtime and read file contents + if ($magic_quote_setting = get_magic_quotes_runtime()) { + @ini_set('magic_quotes_runtime', 0); + } + + $cont = file_get_contents($file_name); + + if ($magic_quote_setting) { + @ini_set('magic_quotes_runtime', $magic_quote_setting); + } + + return $cont; + } + + /** + * Adds a text subpart to the mimePart object and + * returns it during the build process. + * + * @param mixed $obj The object to add the part to, or + * anything else if a new object is to be created. + * + * @return object The text mimePart object + */ + protected function addTextPart($obj = null) + { + return $this->addBodyPart($obj, $this->txtbody, 'text/plain', 'text'); + } + + /** + * Adds a html subpart to the mimePart object and + * returns it during the build process. + * + * @param mixed $obj The object to add the part to, or + * anything else if a new object is to be created. + * + * @return object The html mimePart object + */ + protected function addHtmlPart($obj = null) + { + return $this->addBodyPart($obj, $this->htmlbody, 'text/html', 'html'); + } + + /** + * Adds a calendar subpart to the mimePart object and + * returns it during the build process. + * + * @param mixed $obj The object to add the part to, or + * anything else if a new object is to be created. + * + * @return object The text mimePart object + */ + protected function addCalendarPart($obj = null) + { + $ctype = 'text/calendar; method='. $this->build_params['calendar_method']; + + return $this->addBodyPart($obj, $this->calbody, $ctype, 'calendar'); + } + + /** + * Creates a new mimePart object, using multipart/mixed as + * the initial content-type and returns it during the + * build process. + * + * @param array $params Additional part parameters + * + * @return object The multipart/mixed mimePart object + */ + protected function addMixedPart($params = array()) + { + $params['content_type'] = 'multipart/mixed'; + $params['eol'] = $this->build_params['eol']; + + // Create empty multipart/mixed Mail_mimePart object to return + return new Mail_mimePart('', $params); + } + + /** + * Adds a multipart/alternative part to a mimePart + * object (or creates one), and returns it during + * the build process. + * + * @param mixed $obj The object to add the part to, or + * anything else if a new object is to be created. + * + * @return object The multipart/mixed mimePart object + */ + protected function addAlternativePart($obj = null) + { + $params['content_type'] = 'multipart/alternative'; + $params['eol'] = $this->build_params['eol']; + + if (is_object($obj)) { + $ret = $obj->addSubpart('', $params); + } else { + $ret = new Mail_mimePart('', $params); + } + + return $ret; + } + + /** + * Adds a multipart/related part to a mimePart + * object (or creates one), and returns it during + * the build process. + * + * @param mixed $obj The object to add the part to, or + * anything else if a new object is to be created + * + * @return object The multipart/mixed mimePart object + */ + protected function addRelatedPart($obj = null) + { + $params['content_type'] = 'multipart/related'; + $params['eol'] = $this->build_params['eol']; + + if (is_object($obj)) { + $ret = $obj->addSubpart('', $params); + } else { + $ret = new Mail_mimePart('', $params); + } + + return $ret; + } + + /** + * Adds an html image subpart to a mimePart object + * and returns it during the build process. + * + * @param object $obj The mimePart to add the image to + * @param array $value The image information + * + * @return object The image mimePart object + */ + protected function addHtmlImagePart($obj, $value) + { + $params['content_type'] = $value['c_type']; + $params['encoding'] = 'base64'; + $params['disposition'] = 'inline'; + $params['filename'] = $value['name']; + $params['cid'] = $value['cid']; + $params['body_file'] = $value['body_file']; + $params['eol'] = $this->build_params['eol']; + + if (!empty($value['name_encoding'])) { + $params['name_encoding'] = $value['name_encoding']; + } + if (!empty($value['filename_encoding'])) { + $params['filename_encoding'] = $value['filename_encoding']; + } + + return $obj->addSubpart($value['body'], $params); + } + + /** + * Adds an attachment subpart to a mimePart object + * and returns it during the build process. + * + * @param object $obj The mimePart to add the image to + * @param mixed $value The attachment information array or Mail_mimePart object + * + * @return object The image mimePart object + */ + protected function addAttachmentPart($obj, $value) + { + if ($value instanceof Mail_mimePart) { + return $obj->addSubpart($value); + } + + $params['eol'] = $this->build_params['eol']; + $params['filename'] = $value['name']; + $params['encoding'] = $value['encoding']; + $params['content_type'] = $value['c_type']; + $params['body_file'] = $value['body_file']; + $params['disposition'] = isset($value['disposition']) ? + $value['disposition'] : 'attachment'; + + // content charset + if (!empty($value['charset'])) { + $params['charset'] = $value['charset']; + } + // headers charset (filename, description) + if (!empty($value['headers_charset'])) { + $params['headers_charset'] = $value['headers_charset']; + } + if (!empty($value['language'])) { + $params['language'] = $value['language']; + } + if (!empty($value['location'])) { + $params['location'] = $value['location']; + } + if (!empty($value['name_encoding'])) { + $params['name_encoding'] = $value['name_encoding']; + } + if (!empty($value['filename_encoding'])) { + $params['filename_encoding'] = $value['filename_encoding']; + } + if (!empty($value['description'])) { + $params['description'] = $value['description']; + } + if (is_array($value['add_headers'])) { + $params['headers'] = $value['add_headers']; + } + + return $obj->addSubpart($value['body'], $params); + } + + /** + * Returns the complete e-mail, ready to send using an alternative + * mail delivery method. Note that only the mailpart that is made + * with Mail_Mime is created. This means that, + * YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF + * using the $headers parameter! + * + * @param string $separation The separation between these two parts. + * @param array $params The Build parameters passed to the + * get() function. See get() for more info. + * @param array $headers The extra headers that should be passed + * to the headers() method. + * See that function for more info. + * @param bool $overwrite Overwrite the existing headers with new. + * + * @return mixed The complete e-mail or PEAR error object + */ + public function getMessage($separation = null, $params = null, $headers = null, + $overwrite = false + ) { + if ($separation === null) { + $separation = $this->build_params['eol']; + } + + $body = $this->get($params); + + if (self::isError($body)) { + return $body; + } + + return $this->txtHeaders($headers, $overwrite) . $separation . $body; + } + + /** + * Returns the complete e-mail body, ready to send using an alternative + * mail delivery method. + * + * @param array $params The Build parameters passed to the + * get() method. See get() for more info. + * + * @return mixed The e-mail body or PEAR error object + * @since 1.6.0 + */ + public function getMessageBody($params = null) + { + return $this->get($params, null, true); + } + + /** + * Writes (appends) the complete e-mail into file. + * + * @param string $filename Output file location + * @param array $params The Build parameters passed to the + * get() method. See get() for more info. + * @param array $headers The extra headers that should be passed + * to the headers() function. + * See that function for more info. + * @param bool $overwrite Overwrite the existing headers with new. + * + * @return mixed True or PEAR error object + * @since 1.6.0 + */ + public function saveMessage($filename, $params = null, $headers = null, $overwrite = false) + { + // Check state of file and raise an error properly + if (file_exists($filename) && !is_writable($filename)) { + return self::raiseError('File is not writable: ' . $filename); + } + + // Temporarily reset magic_quotes_runtime and read file contents + if ($magic_quote_setting = get_magic_quotes_runtime()) { + @ini_set('magic_quotes_runtime', 0); + } + + if (!($fh = fopen($filename, 'ab'))) { + return self::raiseError('Unable to open file: ' . $filename); + } + + // Write message headers into file (skipping Content-* headers) + $head = $this->txtHeaders($headers, $overwrite, true); + if (fwrite($fh, $head) === false) { + return self::raiseError('Error writing to file: ' . $filename); + } + + fclose($fh); + + if ($magic_quote_setting) { + @ini_set('magic_quotes_runtime', $magic_quote_setting); + } + + // Write the rest of the message into file + $res = $this->get($params, $filename); + + return $res ? $res : true; + } + + /** + * Writes (appends) the complete e-mail body into file or stream. + * + * @param mixed $filename Output filename or file pointer where to save + * the message instead of returning it + * @param array $params The Build parameters passed to the + * get() method. See get() for more info. + * + * @return mixed True or PEAR error object + * @since 1.6.0 + */ + public function saveMessageBody($filename, $params = null) + { + if (!is_resource($filename)) { + // Check state of file and raise an error properly + if (!file_exists($filename) || !is_writable($filename)) { + return self::raiseError('File is not writable: ' . $filename); + } + + if (!($fh = fopen($filename, 'ab'))) { + return self::raiseError('Unable to open file: ' . $filename); + } + } else { + $fh = $filename; + } + + // Temporarily reset magic_quotes_runtime and read file contents + if ($magic_quote_setting = get_magic_quotes_runtime()) { + @ini_set('magic_quotes_runtime', 0); + } + + // Write the rest of the message into file + $res = $this->get($params, $fh, true); + + if (!is_resource($filename)) { + fclose($fh); + } + + if ($magic_quote_setting) { + @ini_set('magic_quotes_runtime', $magic_quote_setting); + } + + return $res ? $res : true; + } + + /** + * Builds the multipart message from the list ($this->parts) and + * returns the mime content. + * + * @param array $params Build parameters that change the way the email + * is built. Should be associative. See $_build_params. + * @param mixed $filename Output filename or file pointer where to save + * the message instead of returning it + * @param boolean $skip_head True if you want to return/save only the message + * without headers + * + * @return mixed The MIME message content string, null or PEAR error object + */ + public function get($params = null, $filename = null, $skip_head = false) + { + if (!empty($params) && is_array($params)) { + $this->build_params = array_merge($this->build_params, $params); + } + + if (isset($this->headers['From'])) { + // Bug #11381: Illegal characters in domain ID + if (preg_match('#(@[0-9a-zA-Z\-\.]+)#', $this->headers['From'], $matches)) { + $domainID = $matches[1]; + } else { + $domainID = '@localhost'; + } + + foreach ($this->html_images as $i => $img) { + $cid = $this->html_images[$i]['cid']; + if (!preg_match('#'.preg_quote($domainID).'$#', $cid)) { + $this->html_images[$i]['cid'] = $cid . $domainID; + } + } + } + + if (count($this->html_images) && isset($this->htmlbody)) { + foreach ($this->html_images as $key => $value) { + $rval = preg_quote($value['name'], '#'); + $regex = array( + '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' . $rval . '\3#', + '#(?i)url(?-i)\(\s*(["\']?)' . $rval . '\1\s*\)#', + ); + + $rep = array( + '\1\2=\3cid:' . $value['cid'] .'\3', + 'url(\1cid:' . $value['cid'] . '\1)', + ); + + $this->htmlbody = preg_replace($regex, $rep, $this->htmlbody); + $this->html_images[$key]['name'] + = $this->basename($this->html_images[$key]['name']); + } + } + + $this->checkParams(); + + $attachments = count($this->parts) > 0; + $html_images = count($this->html_images) > 0; + $html = strlen($this->htmlbody) > 0; + $calendar = strlen($this->calbody) > 0; + $has_text = strlen($this->txtbody) > 0; + $text = !$html && $has_text; + $mixed_params = array('preamble' => $this->build_params['preamble']); + + switch (true) { + case $calendar && !$attachments && !$text && !$html: + $message = $this->addCalendarPart(); + break; + + case $calendar && !$attachments: + $message = $this->addAlternativePart($mixed_params); + if ($has_text) { + $this->addTextPart($message); + } + if ($html) { + $this->addHtmlPart($message); + } + $this->addCalendarPart($message); + break; + + case $text && !$attachments: + $message = $this->addTextPart(); + break; + + case !$text && !$html && $attachments: + $message = $this->addMixedPart($mixed_params); + for ($i = 0; $i < count($this->parts); $i++) { + $this->addAttachmentPart($message, $this->parts[$i]); + } + break; + + case $text && $attachments: + $message = $this->addMixedPart($mixed_params); + $this->addTextPart($message); + for ($i = 0; $i < count($this->parts); $i++) { + $this->addAttachmentPart($message, $this->parts[$i]); + } + break; + + case $html && !$attachments && !$html_images: + if ($has_text) { + $message = $this->addAlternativePart(); + $this->addTextPart($message); + $this->addHtmlPart($message); + } else { + $message = $this->addHtmlPart(); + } + break; + + case $html && !$attachments && $html_images: + // * Content-Type: multipart/alternative; + // * text + // * Content-Type: multipart/related; + // * html + // * image... + if ($has_text) { + $message = $this->addAlternativePart(); + $this->addTextPart($message); + + $ht = $this->addRelatedPart($message); + $this->addHtmlPart($ht); + for ($i = 0; $i < count($this->html_images); $i++) { + $this->addHtmlImagePart($ht, $this->html_images[$i]); + } + } else { + // * Content-Type: multipart/related; + // * html + // * image... + $message = $this->addRelatedPart(); + $this->addHtmlPart($message); + for ($i = 0; $i < count($this->html_images); $i++) { + $this->addHtmlImagePart($message, $this->html_images[$i]); + } + } + /* + // #13444, #9725: the code below was a non-RFC compliant hack + // * Content-Type: multipart/related; + // * Content-Type: multipart/alternative; + // * text + // * html + // * image... + $message = $this->addRelatedPart(); + if ($has_text) { + $alt = $this->addAlternativePart($message); + $this->addTextPart($alt); + $this->addHtmlPart($alt); + } else { + $this->addHtmlPart($message); + } + for ($i = 0; $i < count($this->html_images); $i++) { + $this->addHtmlImagePart($message, $this->html_images[$i]); + } + */ + break; + + case $html && $attachments && !$html_images: + $message = $this->addMixedPart($mixed_params); + if ($has_text) { + $alt = $this->addAlternativePart($message); + $this->addTextPart($alt); + $this->addHtmlPart($alt); + } else { + $this->addHtmlPart($message); + } + for ($i = 0; $i < count($this->parts); $i++) { + $this->addAttachmentPart($message, $this->parts[$i]); + } + break; + + case $html && $attachments && $html_images: + $message = $this->addMixedPart($mixed_params); + if ($has_text) { + $alt = $this->addAlternativePart($message); + $this->addTextPart($alt); + $rel = $this->addRelatedPart($alt); + } else { + $rel = $this->addRelatedPart($message); + } + $this->addHtmlPart($rel); + for ($i = 0; $i < count($this->html_images); $i++) { + $this->addHtmlImagePart($rel, $this->html_images[$i]); + } + for ($i = 0; $i < count($this->parts); $i++) { + $this->addAttachmentPart($message, $this->parts[$i]); + } + break; + } + + if (!isset($message)) { + return null; + } + + // Use saved boundary + if (!empty($this->build_params['boundary'])) { + $boundary = $this->build_params['boundary']; + } else { + $boundary = null; + } + + // Write output to file + if ($filename) { + // Append mimePart message headers and body into file + $headers = $message->encodeToFile($filename, $boundary, $skip_head); + if (self::isError($headers)) { + return $headers; + } + $this->headers = array_merge($this->headers, $headers); + return null; + } else { + $output = $message->encode($boundary, $skip_head); + if (self::isError($output)) { + return $output; + } + $this->headers = array_merge($this->headers, $output['headers']); + return $output['body']; + } + } + + /** + * Returns an array with the headers needed to prepend to the email + * (MIME-Version and Content-Type). Format of argument is: + * $array['header-name'] = 'header-value'; + * + * @param array $xtra_headers Assoc array with any extra headers (optional) + * (Don't set Content-Type for multipart messages here!) + * @param bool $overwrite Overwrite already existing headers. + * @param bool $skip_content Don't return content headers: Content-Type, + * Content-Disposition and Content-Transfer-Encoding + * + * @return array Assoc array with the mime headers + */ + public function headers($xtra_headers = null, $overwrite = false, $skip_content = false) + { + // Add mime version header + $headers['MIME-Version'] = '1.0'; + + // Content-Type and Content-Transfer-Encoding headers should already + // be present if get() was called, but we'll re-set them to make sure + // we got them when called before get() or something in the message + // has been changed after get() [#14780] + if (!$skip_content) { + $headers += $this->contentHeaders(); + } + + if (!empty($xtra_headers)) { + $headers = array_merge($headers, $xtra_headers); + } + + if ($overwrite) { + $this->headers = array_merge($this->headers, $headers); + } else { + $this->headers = array_merge($headers, $this->headers); + } + + $headers = $this->headers; + + if ($skip_content) { + unset($headers['Content-Type']); + unset($headers['Content-Transfer-Encoding']); + unset($headers['Content-Disposition']); + } else if (!empty($this->build_params['ctype'])) { + $headers['Content-Type'] = $this->build_params['ctype']; + } + + $encodedHeaders = $this->encodeHeaders($headers); + return $encodedHeaders; + } + + /** + * Get the text version of the headers + * (usefull if you want to use the PHP mail() function) + * + * @param array $xtra_headers Assoc array with any extra headers (optional) + * (Don't set Content-Type for multipart messages here!) + * @param bool $overwrite Overwrite the existing headers with new. + * @param bool $skip_content Don't return content headers: Content-Type, + * Content-Disposition and Content-Transfer-Encoding + * + * @return string Plain text headers + */ + public function txtHeaders($xtra_headers = null, $overwrite = false, $skip_content = false) + { + $headers = $this->headers($xtra_headers, $overwrite, $skip_content); + + // Place Received: headers at the beginning of the message + // Spam detectors often flag messages with it after the Subject: as spam + if (isset($headers['Received'])) { + $received = $headers['Received']; + unset($headers['Received']); + $headers = array('Received' => $received) + $headers; + } + + $ret = ''; + $eol = $this->build_params['eol']; + + foreach ($headers as $key => $val) { + if (is_array($val)) { + foreach ($val as $value) { + $ret .= "$key: $value" . $eol; + } + } else { + $ret .= "$key: $val" . $eol; + } + } + + return $ret; + } + + /** + * Sets message Content-Type header. + * Use it to build messages with various content-types e.g. miltipart/raport + * not supported by contentHeaders() function. + * + * @param string $type Type name + * @param array $params Hash array of header parameters + * + * @return void + * @since 1.7.0 + */ + public function setContentType($type, $params = array()) + { + $header = $type; + + $eol = !empty($this->build_params['eol']) + ? $this->build_params['eol'] : "\r\n"; + + // add parameters + $token_regexp = '#([^\x21\x23-\x27\x2A\x2B\x2D' + . '\x2E\x30-\x39\x41-\x5A\x5E-\x7E])#'; + + if (is_array($params)) { + foreach ($params as $name => $value) { + if ($name == 'boundary') { + $this->build_params['boundary'] = $value; + } + if (!preg_match($token_regexp, $value)) { + $header .= ";$eol $name=$value"; + } else { + $value = addcslashes($value, '\\"'); + $header .= ";$eol $name=\"$value\""; + } + } + } + + // add required boundary parameter if not defined + if (stripos($type, 'multipart/') === 0) { + if (empty($this->build_params['boundary'])) { + $this->build_params['boundary'] = '=_' . md5(rand() . microtime()); + } + + $header .= ";$eol boundary=\"".$this->build_params['boundary']."\""; + } + + $this->build_params['ctype'] = $header; + } + + /** + * Sets the Subject header + * + * @param string $subject String to set the subject to. + * + * @return void + */ + public function setSubject($subject) + { + $this->headers['Subject'] = $subject; + } + + /** + * Set an email to the From (the sender) header + * + * @param string $email The email address to use + * + * @return void + */ + public function setFrom($email) + { + $this->headers['From'] = $email; + } + + /** + * Add an email to the To header + * (multiple calls to this method are allowed) + * + * @param string $email The email direction to add + * + * @return void + */ + public function addTo($email) + { + if (isset($this->headers['To'])) { + $this->headers['To'] .= ", $email"; + } else { + $this->headers['To'] = $email; + } + } + + /** + * Add an email to the Cc (carbon copy) header + * (multiple calls to this method are allowed) + * + * @param string $email The email direction to add + * + * @return void + */ + public function addCc($email) + { + if (isset($this->headers['Cc'])) { + $this->headers['Cc'] .= ", $email"; + } else { + $this->headers['Cc'] = $email; + } + } + + /** + * Add an email to the Bcc (blank carbon copy) header + * (multiple calls to this method are allowed) + * + * @param string $email The email direction to add + * + * @return void + */ + public function addBcc($email) + { + if (isset($this->headers['Bcc'])) { + $this->headers['Bcc'] .= ", $email"; + } else { + $this->headers['Bcc'] = $email; + } + } + + /** + * Since the PHP send function requires you to specify + * recipients (To: header) separately from the other + * headers, the To: header is not properly encoded. + * To fix this, you can use this public method to encode + * your recipients before sending to the send function. + * + * @param string $recipients A comma-delimited list of recipients + * + * @return string Encoded data + */ + public function encodeRecipients($recipients) + { + $input = array('To' => $recipients); + $retval = $this->encodeHeaders($input); + + return $retval['To'] ; + } + + /** + * Encodes headers as per RFC2047 + * + * @param array $input The header data to encode + * @param array $params Extra build parameters + * + * @return array Encoded data + */ + protected function encodeHeaders($input, $params = array()) + { + $build_params = $this->build_params; + + if (!empty($params)) { + $build_params = array_merge($build_params, $params); + } + + foreach ($input as $hdr_name => $hdr_value) { + if (is_array($hdr_value)) { + foreach ($hdr_value as $idx => $value) { + $input[$hdr_name][$idx] = $this->encodeHeader( + $hdr_name, $value, + $build_params['head_charset'], $build_params['head_encoding'] + ); + } + } else if ($hdr_value !== null) { + $input[$hdr_name] = $this->encodeHeader( + $hdr_name, $hdr_value, + $build_params['head_charset'], $build_params['head_encoding'] + ); + } else { + unset($input[$hdr_name]); + } + } + + return $input; + } + + /** + * Encodes a header as per RFC2047 + * + * @param string $name The header name + * @param string $value The header data to encode + * @param string $charset Character set name + * @param string $encoding Encoding name (base64 or quoted-printable) + * + * @return string Encoded header data (without a name) + * @since 1.5.3 + */ + public function encodeHeader($name, $value, $charset, $encoding) + { + return Mail_mimePart::encodeHeader( + $name, $value, $charset, $encoding, $this->build_params['eol'] + ); + } + + /** + * Get file's basename (locale independent) + * + * @param string $filename Filename + * + * @return string Basename + */ + protected function basename($filename) + { + // basename() is not unicode safe and locale dependent + if (stristr(PHP_OS, 'win') || stristr(PHP_OS, 'netware')) { + return preg_replace('/^.*[\\\\\\/]/', '', $filename); + } else { + return preg_replace('/^.*[\/]/', '', $filename); + } + } + + /** + * Get Content-Type and Content-Transfer-Encoding headers of the message + * + * @return array Headers array + */ + protected function contentHeaders() + { + $attachments = count($this->parts) > 0; + $html_images = count($this->html_images) > 0; + $html = strlen($this->htmlbody) > 0; + $calendar = strlen($this->calbody) > 0; + $has_text = strlen($this->txtbody) > 0; + $text = !$html && $has_text; + $headers = array(); + + // See get() + switch (true) { + case $calendar && !$attachments && !$html && !$has_text: + $headers['Content-Type'] = 'text/calendar'; + break; + + case $calendar && !$attachments: + $headers['Content-Type'] = 'multipart/alternative'; + break; + + case $text && !$attachments: + $headers['Content-Type'] = 'text/plain'; + break; + + case !$text && !$html && $attachments: + case $text && $attachments: + case $html && $attachments && !$html_images: + case $html && $attachments && $html_images: + $headers['Content-Type'] = 'multipart/mixed'; + break; + + case $html && !$attachments && !$html_images && $has_text: + case $html && !$attachments && $html_images && $has_text: + $headers['Content-Type'] = 'multipart/alternative'; + break; + + case $html && !$attachments && !$html_images && !$has_text: + $headers['Content-Type'] = 'text/html'; + break; + + case $html && !$attachments && $html_images && !$has_text: + $headers['Content-Type'] = 'multipart/related'; + break; + + default: + return $headers; + } + + $this->checkParams(); + + $eol = !empty($this->build_params['eol']) + ? $this->build_params['eol'] : "\r\n"; + + if ($headers['Content-Type'] == 'text/plain') { + // single-part message: add charset and encoding + if ($this->build_params['text_charset']) { + $charset = 'charset=' . $this->build_params['text_charset']; + // place charset parameter in the same line, if possible + // 26 = strlen("Content-Type: text/plain; ") + $headers['Content-Type'] + .= (strlen($charset) + 26 <= 76) ? "; $charset" : ";$eol $charset"; + } + + $headers['Content-Transfer-Encoding'] + = $this->build_params['text_encoding']; + } else if ($headers['Content-Type'] == 'text/html') { + // single-part message: add charset and encoding + if ($this->build_params['html_charset']) { + $charset = 'charset=' . $this->build_params['html_charset']; + // place charset parameter in the same line, if possible + $headers['Content-Type'] + .= (strlen($charset) + 25 <= 76) ? "; $charset" : ";$eol $charset"; + } + $headers['Content-Transfer-Encoding'] + = $this->build_params['html_encoding']; + } + else if ($headers['Content-Type'] == 'text/calendar') { + // single-part message: add charset and encoding + if ($this->build_params['calendar_charset']) { + $charset = 'charset=' . $this->build_params['calendar_charset']; + $headers['Content-Type'] .= "; $charset"; + } + + if ($this->build_params['calendar_method']) { + $method = 'method=' . $this->build_params['calendar_method']; + $headers['Content-Type'] .= "; $method"; + } + + $headers['Content-Transfer-Encoding'] + = $this->build_params['calendar_encoding']; + } else { + // multipart message: and boundary + if (!empty($this->build_params['boundary'])) { + $boundary = $this->build_params['boundary']; + } else if (!empty($this->headers['Content-Type']) + && preg_match('/boundary="([^"]+)"/', $this->headers['Content-Type'], $m) + ) { + $boundary = $m[1]; + } else { + $boundary = '=_' . md5(rand() . microtime()); + } + + $this->build_params['boundary'] = $boundary; + $headers['Content-Type'] .= ";$eol boundary=\"$boundary\""; + } + + return $headers; + } + + /** + * Validate and set build parameters + * + * @return void + */ + protected function checkParams() + { + $encodings = array('7bit', '8bit', 'base64', 'quoted-printable'); + + $this->build_params['text_encoding'] + = strtolower($this->build_params['text_encoding']); + $this->build_params['html_encoding'] + = strtolower($this->build_params['html_encoding']); + $this->build_params['calendar_encoding'] + = strtolower($this->build_params['calendar_encoding']); + + if (!in_array($this->build_params['text_encoding'], $encodings)) { + $this->build_params['text_encoding'] = '7bit'; + } + if (!in_array($this->build_params['html_encoding'], $encodings)) { + $this->build_params['html_encoding'] = '7bit'; + } + if (!in_array($this->build_params['calendar_encoding'], $encodings)) { + $this->build_params['calendar_encoding'] = '7bit'; + } + + // text body + if ($this->build_params['text_encoding'] == '7bit' + && !preg_match('/ascii/i', $this->build_params['text_charset']) + && preg_match('/[^\x00-\x7F]/', $this->txtbody) + ) { + $this->build_params['text_encoding'] = 'quoted-printable'; + } + // html body + if ($this->build_params['html_encoding'] == '7bit' + && !preg_match('/ascii/i', $this->build_params['html_charset']) + && preg_match('/[^\x00-\x7F]/', $this->htmlbody) + ) { + $this->build_params['html_encoding'] = 'quoted-printable'; + } + // calendar body + if ($this->build_params['calendar_encoding'] == '7bit' + && !preg_match('/ascii/i', $this->build_params['calendar_charset']) + && preg_match('/[^\x00-\x7F]/', $this->calbody) + ) { + $this->build_params['calendar_encoding'] = 'quoted-printable'; + } + } + + /** + * Set body of specified message part + * + * @param string $type One of: txtbody, calbody, htmlbody + * @param string $data Either a string or the file name with the contents + * @param bool $isfile If true the first param should be treated + * as a file name, else as a string (default) + * @param bool $append If true the text or file is appended to + * the existing body, else the old body is + * overwritten + * + * @return mixed True on success or PEAR_Error object + */ + protected function setBody($type, $data, $isfile = false, $append = false) + { + if ($isfile) { + $data = $this->file2str($data); + if (self::isError($data)) { + return $data; + } + } + + if (!$append) { + $this->{$type} = $data; + } else { + $this->{$type} .= $data; + } + + return true; + } + + /** + * Adds a subpart to the mimePart object and + * returns it during the build process. + * + * @param mixed $obj The object to add the part to, or + * anything else if a new object is to be created. + * @param string $body Part body + * @param string $ctype Part content type + * @param string $type Internal part type + * + * @return object The mimePart object + */ + protected function addBodyPart($obj, $body, $ctype, $type) + { + $params['content_type'] = $ctype; + $params['encoding'] = $this->build_params[$type . '_encoding']; + $params['charset'] = $this->build_params[$type . '_charset']; + $params['eol'] = $this->build_params['eol']; + + if (is_object($obj)) { + $ret = $obj->addSubpart($body, $params); + } else { + $ret = new Mail_mimePart($body, $params); + } + + return $ret; + } + + /** + * PEAR::isError implementation + * + * @param mixed $data Object + * + * @return bool True if object is an instance of PEAR_Error + */ + public static function isError($data) + { + // PEAR::isError() is not PHP 5.4 compatible (see Bug #19473) + if (is_a($data, 'PEAR_Error')) { + return true; + } + + return false; + } + + /** + * PEAR::raiseError implementation + * + * @param string $message A text error message + * + * @return PEAR_Error Instance of PEAR_Error + */ + public static function raiseError($message) + { + // PEAR::raiseError() is not PHP 5.4 compatible + return new PEAR_Error($message); + } +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/mimePart.php b/democracy/sai/saimod_beta/cannon/Pear/Mail/mimePart.php new file mode 100644 index 0000000..eec899c --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/mimePart.php @@ -0,0 +1,1276 @@ + + * Copyright (c) 2003-2006, PEAR + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name of the authors, nor the names of its contributors + * may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Mail + * @package Mail_Mime + * @author Richard Heyes + * @author Cipriano Groenendal + * @author Sean Coates + * @author Aleksander Machniak + * @copyright 2003-2006 PEAR + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/Mail_mime + */ + +/** + * Require PEAR + * + * This package depends on PEAR to raise errors. + */ +//require_once 'PEAR.php'; + +/** + * The Mail_mimePart class is used to create MIME E-mail messages + * + * This class enables you to manipulate and build a mime email + * from the ground up. The Mail_Mime class is a userfriendly api + * to this class for people who aren't interested in the internals + * of mime mail. + * This class however allows full control over the email. + * + * @category Mail + * @package Mail_Mime + * @author Richard Heyes + * @author Cipriano Groenendal + * @author Sean Coates + * @author Aleksander Machniak + * @copyright 2003-2006 PEAR + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/Mail_mime + */ +class Mail_mimePart +{ + /** + * The encoding type of this part + * + * @var string + */ + protected $encoding; + + /** + * An array of subparts + * + * @var array + */ + protected $subparts; + + /** + * The output of this part after being built + * + * @var string + */ + protected $encoded; + + /** + * Headers for this part + * + * @var array + */ + protected $headers; + + /** + * The body of this part (not encoded) + * + * @var string + */ + protected $body; + + /** + * The location of file with body of this part (not encoded) + * + * @var string + */ + protected $body_file; + + /** + * The short text of multipart part preamble (RFC2046 5.1.1) + * + * @var string + */ + protected $preamble; + + /** + * The end-of-line sequence + * + * @var string + */ + protected $eol = "\r\n"; + + + /** + * Constructor. + * + * Sets up the object. + * + * @param string $body The body of the mime part if any. + * @param array $params An associative array of optional parameters: + * content_type - The content type for this part eg multipart/mixed + * encoding - The encoding to use, 7bit, 8bit, + * base64, or quoted-printable + * charset - Content character set + * cid - Content ID to apply + * disposition - Content disposition, inline or attachment + * filename - Filename parameter for content disposition + * description - Content description + * name_encoding - Encoding of the attachment name (Content-Type) + * By default filenames are encoded using RFC2231 + * Here you can set RFC2047 encoding (quoted-printable + * or base64) instead + * filename_encoding - Encoding of the attachment filename (Content-Disposition) + * See 'name_encoding' + * headers_charset - Charset of the headers e.g. filename, description. + * If not set, 'charset' will be used + * eol - End of line sequence. Default: "\r\n" + * headers - Hash array with additional part headers. Array keys + * can be in form of : + * body_file - Location of file with part's body (instead of $body) + * preamble - short text of multipart part preamble (RFC2046 5.1.1) + */ + public function __construct($body = '', $params = array()) + { + if (!empty($params['eol'])) { + $this->eol = $params['eol']; + } else if (defined('MAIL_MIMEPART_CRLF')) { // backward-copat. + $this->eol = MAIL_MIMEPART_CRLF; + } + + // Additional part headers + if (!empty($params['headers']) && is_array($params['headers'])) { + $headers = $params['headers']; + } + + foreach ($params as $key => $value) { + switch ($key) { + case 'encoding': + $this->encoding = $value; + $headers['Content-Transfer-Encoding'] = $value; + break; + + case 'cid': + $headers['Content-ID'] = '<' . $value . '>'; + break; + + case 'location': + $headers['Content-Location'] = $value; + break; + + case 'body_file': + $this->body_file = $value; + break; + + case 'preamble': + $this->preamble = $value; + break; + + // for backward compatibility + case 'dfilename': + $params['filename'] = $value; + break; + } + } + + // Default content-type + if (empty($params['content_type'])) { + $params['content_type'] = 'text/plain'; + } + + // Content-Type + $headers['Content-Type'] = $params['content_type']; + if (!empty($params['charset'])) { + $charset = "charset={$params['charset']}"; + // place charset parameter in the same line, if possible + if ((strlen($headers['Content-Type']) + strlen($charset) + 16) <= 76) { + $headers['Content-Type'] .= '; '; + } else { + $headers['Content-Type'] .= ';' . $this->eol . ' '; + } + $headers['Content-Type'] .= $charset; + + // Default headers charset + if (!isset($params['headers_charset'])) { + $params['headers_charset'] = $params['charset']; + } + } + + // header values encoding parameters + $h_charset = !empty($params['headers_charset']) ? $params['headers_charset'] : 'US-ASCII'; + $h_language = !empty($params['language']) ? $params['language'] : null; + $h_encoding = !empty($params['name_encoding']) ? $params['name_encoding'] : null; + + if (!empty($params['filename'])) { + $headers['Content-Type'] .= ';' . $this->eol; + $headers['Content-Type'] .= $this->buildHeaderParam( + 'name', $params['filename'], $h_charset, $h_language, $h_encoding + ); + } + + // Content-Disposition + if (!empty($params['disposition'])) { + $headers['Content-Disposition'] = $params['disposition']; + if (!empty($params['filename'])) { + $headers['Content-Disposition'] .= ';' . $this->eol; + $headers['Content-Disposition'] .= $this->buildHeaderParam( + 'filename', $params['filename'], $h_charset, $h_language, + !empty($params['filename_encoding']) ? $params['filename_encoding'] : null + ); + } + + // add attachment size + $size = $this->body_file ? filesize($this->body_file) : strlen($body); + if ($size) { + $headers['Content-Disposition'] .= ';' . $this->eol . ' size=' . $size; + } + } + + if (!empty($params['description'])) { + $headers['Content-Description'] = $this->encodeHeader( + 'Content-Description', $params['description'], $h_charset, $h_encoding, + $this->eol + ); + } + + // Search and add existing headers' parameters + foreach ($headers as $key => $value) { + $items = explode(':', $key); + if (count($items) == 2) { + $header = $items[0]; + $param = $items[1]; + if (isset($headers[$header])) { + $headers[$header] .= ';' . $this->eol; + } + $headers[$header] .= $this->buildHeaderParam( + $param, $value, $h_charset, $h_language, $h_encoding + ); + unset($headers[$key]); + } + } + + // Default encoding + if (!isset($this->encoding)) { + $this->encoding = '7bit'; + } + + // Assign stuff to member variables + $this->encoded = array(); + $this->headers = $headers; + $this->body = $body; + } + + /** + * Encodes and returns the email. Also stores + * it in the encoded member variable + * + * @param string $boundary Pre-defined boundary string + * + * @return An associative array containing two elements, + * body and headers. The headers element is itself + * an indexed array. On error returns PEAR error object. + */ + public function encode($boundary=null) + { + $encoded =& $this->encoded; + + if (count($this->subparts)) { + $boundary = $boundary ? $boundary : '=_' . md5(rand() . microtime()); + $eol = $this->eol; + + $this->headers['Content-Type'] .= ";$eol boundary=\"$boundary\""; + + $encoded['body'] = ''; + + if ($this->preamble) { + $encoded['body'] .= $this->preamble . $eol . $eol; + } + + for ($i = 0; $i < count($this->subparts); $i++) { + $encoded['body'] .= '--' . $boundary . $eol; + $tmp = $this->subparts[$i]->encode(); + if (is_a($tmp, 'PEAR_Error')) { + return $tmp; + } + foreach ($tmp['headers'] as $key => $value) { + $encoded['body'] .= $key . ': ' . $value . $eol; + } + $encoded['body'] .= $eol . $tmp['body'] . $eol; + } + + $encoded['body'] .= '--' . $boundary . '--' . $eol; + } else if ($this->body) { + $encoded['body'] = $this->getEncodedData($this->body, $this->encoding); + } else if ($this->body_file) { + // Temporarily reset magic_quotes_runtime for file reads and writes + if ($magic_quote_setting = get_magic_quotes_runtime()) { + @ini_set('magic_quotes_runtime', 0); + } + $body = $this->getEncodedDataFromFile($this->body_file, $this->encoding); + if ($magic_quote_setting) { + @ini_set('magic_quotes_runtime', $magic_quote_setting); + } + + if (is_a($body, 'PEAR_Error')) { + return $body; + } + $encoded['body'] = $body; + } else { + $encoded['body'] = ''; + } + + // Add headers to $encoded + $encoded['headers'] =& $this->headers; + + return $encoded; + } + + /** + * Encodes and saves the email into file or stream. + * Data will be appended to the file/stream. + * + * @param mixed $filename Existing file location + * or file pointer resource + * @param string $boundary Pre-defined boundary string + * @param boolean $skip_head True if you don't want to save headers + * + * @return array An associative array containing message headers + * or PEAR error object + * @since 1.6.0 + */ + public function encodeToFile($filename, $boundary = null, $skip_head = false) + { + if (!is_resource($filename)) { + if (file_exists($filename) && !is_writable($filename)) { + $err = self::raiseError('File is not writeable: ' . $filename); + return $err; + } + + if (!($fh = fopen($filename, 'ab'))) { + $err = self::raiseError('Unable to open file: ' . $filename); + return $err; + } + } else { + $fh = $filename; + } + + // Temporarily reset magic_quotes_runtime for file reads and writes + if ($magic_quote_setting = get_magic_quotes_runtime()) { + @ini_set('magic_quotes_runtime', 0); + } + + $res = $this->encodePartToFile($fh, $boundary, $skip_head); + + if (!is_resource($filename)) { + fclose($fh); + } + + if ($magic_quote_setting) { + @ini_set('magic_quotes_runtime', $magic_quote_setting); + } + + return is_a($res, 'PEAR_Error') ? $res : $this->headers; + } + + /** + * Encodes given email part into file + * + * @param string $fh Output file handle + * @param string $boundary Pre-defined boundary string + * @param boolean $skip_head True if you don't want to save headers + * + * @return array True on sucess or PEAR error object + */ + protected function encodePartToFile($fh, $boundary = null, $skip_head = false) + { + $eol = $this->eol; + + if (count($this->subparts)) { + $boundary = $boundary ? $boundary : '=_' . md5(rand() . microtime()); + $this->headers['Content-Type'] .= ";$eol boundary=\"$boundary\""; + } + + if (!$skip_head) { + foreach ($this->headers as $key => $value) { + fwrite($fh, $key . ': ' . $value . $eol); + } + $f_eol = $eol; + } else { + $f_eol = ''; + } + + if (count($this->subparts)) { + if ($this->preamble) { + fwrite($fh, $f_eol . $this->preamble . $eol); + $f_eol = $eol; + } + + for ($i = 0; $i < count($this->subparts); $i++) { + fwrite($fh, $f_eol . '--' . $boundary . $eol); + $res = $this->subparts[$i]->encodePartToFile($fh); + if (is_a($res, 'PEAR_Error')) { + return $res; + } + $f_eol = $eol; + } + + fwrite($fh, $eol . '--' . $boundary . '--' . $eol); + } else if ($this->body) { + fwrite($fh, $f_eol); + fwrite($fh, $this->getEncodedData($this->body, $this->encoding)); + } else if ($this->body_file) { + fwrite($fh, $f_eol); + $res = $this->getEncodedDataFromFile( + $this->body_file, $this->encoding, $fh + ); + if (is_a($res, 'PEAR_Error')) { + return $res; + } + } + + return true; + } + + /** + * Adds a subpart to current mime part and returns + * a reference to it + * + * @param mixed $body The body of the subpart or Mail_mimePart object + * @param array $params The parameters for the subpart, same + * as the $params argument for constructor + * + * @return Mail_mimePart A reference to the part you just added. + */ + public function addSubpart($body, $params = null) + { + if ($body instanceof Mail_mimePart) { + $part = $body; + } else { + $part = new Mail_mimePart($body, $params); + } + + $this->subparts[] = $part; + + return $part; + } + + /** + * Returns encoded data based upon encoding passed to it + * + * @param string $data The data to encode. + * @param string $encoding The encoding type to use, 7bit, base64, + * or quoted-printable. + * + * @return string Encoded data string + */ + protected function getEncodedData($data, $encoding) + { + switch ($encoding) { + case 'quoted-printable': + return self::quotedPrintableEncode($data, 76, $this->eol); + break; + + case 'base64': + return rtrim(chunk_split(base64_encode($data), 76, $this->eol)); + break; + + case '8bit': + case '7bit': + default: + return $data; + } + } + + /** + * Returns encoded data based upon encoding passed to it + * + * @param string $filename Data file location + * @param string $encoding The encoding type to use, 7bit, base64, + * or quoted-printable. + * @param resource $fh Output file handle. If set, data will be + * stored into it instead of returning it + * + * @return string Encoded data or PEAR error object + */ + protected function getEncodedDataFromFile($filename, $encoding, $fh = null) + { + if (!is_readable($filename)) { + $err = self::raiseError('Unable to read file: ' . $filename); + return $err; + } + + if (!($fd = fopen($filename, 'rb'))) { + $err = self::raiseError('Could not open file: ' . $filename); + return $err; + } + + $data = ''; + + switch ($encoding) { + case 'quoted-printable': + while (!feof($fd)) { + $buffer = self::quotedPrintableEncode(fgets($fd), 76, $this->eol); + if ($fh) { + fwrite($fh, $buffer); + } else { + $data .= $buffer; + } + } + break; + + case 'base64': + while (!feof($fd)) { + // Should read in a multiple of 57 bytes so that + // the output is 76 bytes per line. Don't use big chunks + // because base64 encoding is memory expensive + $buffer = fread($fd, 57 * 9198); // ca. 0.5 MB + $buffer = base64_encode($buffer); + $buffer = chunk_split($buffer, 76, $this->eol); + if (feof($fd)) { + $buffer = rtrim($buffer); + } + + if ($fh) { + fwrite($fh, $buffer); + } else { + $data .= $buffer; + } + } + break; + + case '8bit': + case '7bit': + default: + while (!feof($fd)) { + $buffer = fread($fd, 1048576); // 1 MB + if ($fh) { + fwrite($fh, $buffer); + } else { + $data .= $buffer; + } + } + } + + fclose($fd); + + if (!$fh) { + return $data; + } + } + + /** + * Encodes data to quoted-printable standard. + * + * @param string $input The data to encode + * @param int $line_max Optional max line length. Should + * not be more than 76 chars + * @param string $eol End-of-line sequence. Default: "\r\n" + * + * @return string Encoded data + */ + public static function quotedPrintableEncode($input , $line_max = 76, $eol = "\r\n") + { + /* + // imap_8bit() is extremely fast, but doesn't handle properly some characters + if (function_exists('imap_8bit') && $line_max == 76) { + $input = preg_replace('/\r?\n/', "\r\n", $input); + $input = imap_8bit($input); + if ($eol != "\r\n") { + $input = str_replace("\r\n", $eol, $input); + } + return $input; + } + */ + $lines = preg_split("/\r?\n/", $input); + $escape = '='; + $output = ''; + + foreach ($lines as $idx => $line) { + $newline = ''; + $i = 0; + + while (isset($line[$i])) { + $char = $line[$i]; + $dec = ord($char); + $i++; + + if (($dec == 32) && (!isset($line[$i]))) { + // convert space at eol only + $char = '=20'; + } elseif ($dec == 9 && isset($line[$i])) { + ; // Do nothing if a TAB is not on eol + } elseif (($dec == 61) || ($dec < 32) || ($dec > 126)) { + $char = $escape . sprintf('%02X', $dec); + } elseif (($dec == 46) && (($newline == '') + || ((strlen($newline) + strlen("=2E")) >= $line_max)) + ) { + // Bug #9722: convert full-stop at bol, + // some Windows servers need this, won't break anything (cipri) + // Bug #11731: full-stop at bol also needs to be encoded + // if this line would push us over the line_max limit. + $char = '=2E'; + } + + // Note, when changing this line, also change the ($dec == 46) + // check line, as it mimics this line due to Bug #11731 + // EOL is not counted + if ((strlen($newline) + strlen($char)) >= $line_max) { + // soft line break; " =\r\n" is okay + $output .= $newline . $escape . $eol; + $newline = ''; + } + + $newline .= $char; + } // end of for + + $output .= $newline . $eol; + unset($lines[$idx]); + } + + // Don't want last crlf + $output = substr($output, 0, -1 * strlen($eol)); + + return $output; + } + + /** + * Encodes the parameter of a header. + * + * @param string $name The name of the header-parameter + * @param string $value The value of the paramter + * @param string $charset The characterset of $value + * @param string $language The language used in $value + * @param string $encoding Parameter encoding. If not set, parameter value + * is encoded according to RFC2231 + * @param int $maxLength The maximum length of a line. Defauls to 75 + * + * @return string + */ + protected function buildHeaderParam($name, $value, $charset = null, + $language = null, $encoding = null, $maxLength = 75 + ) { + // RFC 2045: + // value needs encoding if contains non-ASCII chars or is longer than 78 chars + if (!preg_match('#[^\x20-\x7E]#', $value)) { + $token_regexp = '#([^\x21\x23-\x27\x2A\x2B\x2D' + . '\x2E\x30-\x39\x41-\x5A\x5E-\x7E])#'; + if (!preg_match($token_regexp, $value)) { + // token + if (strlen($name) + strlen($value) + 3 <= $maxLength) { + return " {$name}={$value}"; + } + } else { + // quoted-string + $quoted = addcslashes($value, '\\"'); + if (strlen($name) + strlen($quoted) + 5 <= $maxLength) { + return " {$name}=\"{$quoted}\""; + } + } + } + + // RFC2047: use quoted-printable/base64 encoding + if ($encoding == 'quoted-printable' || $encoding == 'base64') { + return $this->buildRFC2047Param($name, $value, $charset, $encoding); + } + + // RFC2231: + $encValue = preg_replace_callback( + '/([^\x21\x23\x24\x26\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E])/', + array($this, 'encodeReplaceCallback'), $value + ); + $value = "$charset'$language'$encValue"; + + $header = " {$name}*={$value}"; + if (strlen($header) <= $maxLength) { + return $header; + } + + $preLength = strlen(" {$name}*0*="); + $maxLength = max(16, $maxLength - $preLength - 3); + $maxLengthReg = "|(.{0,$maxLength}[^\%][^\%])|"; + + $headers = array(); + $headCount = 0; + while ($value) { + $matches = array(); + $found = preg_match($maxLengthReg, $value, $matches); + if ($found) { + $headers[] = " {$name}*{$headCount}*={$matches[0]}"; + $value = substr($value, strlen($matches[0])); + } else { + $headers[] = " {$name}*{$headCount}*={$value}"; + $value = ''; + } + $headCount++; + } + + $headers = implode(';' . $this->eol, $headers); + return $headers; + } + + /** + * Encodes header parameter as per RFC2047 if needed + * + * @param string $name The parameter name + * @param string $value The parameter value + * @param string $charset The parameter charset + * @param string $encoding Encoding type (quoted-printable or base64) + * @param int $maxLength Encoded parameter max length. Default: 76 + * + * @return string Parameter line + */ + protected function buildRFC2047Param($name, $value, $charset, + $encoding = 'quoted-printable', $maxLength = 76 + ) { + // WARNING: RFC 2047 says: "An 'encoded-word' MUST NOT be used in + // parameter of a MIME Content-Type or Content-Disposition field", + // but... it's supported by many clients/servers + $quoted = ''; + + if ($encoding == 'base64') { + $value = base64_encode($value); + $prefix = '=?' . $charset . '?B?'; + $suffix = '?='; + + // 2 x SPACE, 2 x '"', '=', ';' + $add_len = strlen($prefix . $suffix) + strlen($name) + 6; + $len = $add_len + strlen($value); + + while ($len > $maxLength) { + // We can cut base64-encoded string every 4 characters + $real_len = floor(($maxLength - $add_len) / 4) * 4; + $_quote = substr($value, 0, $real_len); + $value = substr($value, $real_len); + + $quoted .= $prefix . $_quote . $suffix . $this->eol . ' '; + $add_len = strlen($prefix . $suffix) + 4; // 2 x SPACE, '"', ';' + $len = strlen($value) + $add_len; + } + $quoted .= $prefix . $value . $suffix; + + } else { + // quoted-printable + $value = $this->encodeQP($value); + $prefix = '=?' . $charset . '?Q?'; + $suffix = '?='; + + // 2 x SPACE, 2 x '"', '=', ';' + $add_len = strlen($prefix . $suffix) + strlen($name) + 6; + $len = $add_len + strlen($value); + + while ($len > $maxLength) { + $length = $maxLength - $add_len; + // don't break any encoded letters + if (preg_match("/^(.{0,$length}[^\=][^\=])/", $value, $matches)) { + $_quote = $matches[1]; + } + + $quoted .= $prefix . $_quote . $suffix . $this->eol . ' '; + $value = substr($value, strlen($_quote)); + $add_len = strlen($prefix . $suffix) + 4; // 2 x SPACE, '"', ';' + $len = strlen($value) + $add_len; + } + + $quoted .= $prefix . $value . $suffix; + } + + return " {$name}=\"{$quoted}\""; + } + + /** + * Encodes a header as per RFC2047 + * + * @param string $name The header name + * @param string $value The header data to encode + * @param string $charset Character set name + * @param string $encoding Encoding name (base64 or quoted-printable) + * @param string $eol End-of-line sequence. Default: "\r\n" + * + * @return string Encoded header data (without a name) + * @since 1.6.1 + */ + public static function encodeHeader($name, $value, $charset = 'ISO-8859-1', + $encoding = 'quoted-printable', $eol = "\r\n" + ) { + // Structured headers + $comma_headers = array( + 'from', 'to', 'cc', 'bcc', 'sender', 'reply-to', + 'resent-from', 'resent-to', 'resent-cc', 'resent-bcc', + 'resent-sender', 'resent-reply-to', + 'mail-reply-to', 'mail-followup-to', + 'return-receipt-to', 'disposition-notification-to', + ); + $other_headers = array( + 'references', 'in-reply-to', 'message-id', 'resent-message-id', + ); + + $name = strtolower($name); + + if (in_array($name, $comma_headers)) { + $separator = ','; + } else if (in_array($name, $other_headers)) { + $separator = ' '; + } + + if (!$charset) { + $charset = 'ISO-8859-1'; + } + + // exploding quoted strings as well as some regexes below do not + // work properly with some charset e.g. ISO-2022-JP, we'll use UTF-8 + $mb = $charset != 'UTF-8' && function_exists('mb_convert_encoding'); + + // Structured header (make sure addr-spec inside is not encoded) + if (!empty($separator)) { + // Simple e-mail address regexp + $email_regexp = '([^\s<]+|("[^\r\n"]+"))@\S+'; + + if ($mb) { + $value = mb_convert_encoding($value, 'UTF-8', $charset); + } + + $parts = Mail_mimePart::explodeQuotedString("[\t$separator]", $value); + $value = ''; + + foreach ($parts as $part) { + $part = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $part); + $part = trim($part); + + if (!$part) { + continue; + } + if ($value) { + $value .= $separator == ',' ? $separator . ' ' : ' '; + } else { + $value = $name . ': '; + } + + // let's find phrase (name) and/or addr-spec + if (preg_match('/^<' . $email_regexp . '>$/', $part)) { + $value .= $part; + } else if (preg_match('/^' . $email_regexp . '$/', $part)) { + // address without brackets and without name + $value .= $part; + } else if (preg_match('/<*' . $email_regexp . '>*$/', $part, $matches)) { + // address with name (handle name) + $address = $matches[0]; + $word = str_replace($address, '', $part); + $word = trim($word); + + // check if phrase requires quoting + if ($word) { + // non-ASCII: require encoding + if (preg_match('#([^\s\x21-\x7E]){1}#', $word)) { + if ($word[0] == '"' && $word[strlen($word)-1] == '"') { + // de-quote quoted-string, encoding changes + // string to atom + $word = substr($word, 1, -1); + $word = preg_replace('/\\\\([\\\\"])/', '$1', $word); + } + if ($mb) { + $word = mb_convert_encoding($word, $charset, 'UTF-8'); + } + + // find length of last line + if (($pos = strrpos($value, $eol)) !== false) { + $last_len = strlen($value) - $pos; + } else { + $last_len = strlen($value); + } + + $word = Mail_mimePart::encodeHeaderValue( + $word, $charset, $encoding, $last_len, $eol + ); + } else if (($word[0] != '"' || $word[strlen($word)-1] != '"') + && preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $word) + ) { + // ASCII: quote string if needed + $word = '"'.addcslashes($word, '\\"').'"'; + } + } + + $value .= $word.' '.$address; + } else { + if ($mb) { + $part = mb_convert_encoding($part, $charset, 'UTF-8'); + } + // addr-spec not found, don't encode (?) + $value .= $part; + } + + // RFC2822 recommends 78 characters limit, use 76 from RFC2047 + $value = wordwrap($value, 76, $eol . ' '); + } + + // remove header name prefix (there could be EOL too) + $value = preg_replace( + '/^'.$name.':('.preg_quote($eol, '/').')* /', '', $value + ); + } else { + // Unstructured header + // non-ASCII: require encoding + if (preg_match('#([^\s\x21-\x7E]){1}#', $value)) { + if ($value[0] == '"' && $value[strlen($value)-1] == '"') { + if ($mb) { + $value = mb_convert_encoding($value, 'UTF-8', $charset); + } + // de-quote quoted-string, encoding changes + // string to atom + $value = substr($value, 1, -1); + $value = preg_replace('/\\\\([\\\\"])/', '$1', $value); + if ($mb) { + $value = mb_convert_encoding($value, $charset, 'UTF-8'); + } + } + + $value = Mail_mimePart::encodeHeaderValue( + $value, $charset, $encoding, strlen($name) + 2, $eol + ); + } else if (strlen($name.': '.$value) > 78) { + // ASCII: check if header line isn't too long and use folding + $value = preg_replace('/\r?\n[\s\t]*/', $eol . ' ', $value); + $tmp = wordwrap($name . ': ' . $value, 78, $eol . ' '); + $value = preg_replace('/^' . $name . ':\s*/', '', $tmp); + // hard limit 998 (RFC2822) + $value = wordwrap($value, 998, $eol . ' ', true); + } + } + + return $value; + } + + /** + * Explode quoted string + * + * @param string $delimiter Delimiter expression string for preg_match() + * @param string $string Input string + * + * @return array String tokens array + */ + protected static function explodeQuotedString($delimiter, $string) + { + $result = array(); + $strlen = strlen($string); + $quoted_string = '"(?:[^"\\\\]|\\\\.)*"'; + + for ($p=$i=0; $i < $strlen; $i++) { + if ($string[$i] === '"') { + $r = preg_match("/$quoted_string/", $string, $matches, 0, $i); + if (!$r || empty($matches[0])) { + break; + } + $i += strlen($matches[0]) - 1; + } else if (preg_match("/$delimiter/", $string[$i])) { + $result[] = substr($string, $p, $i - $p); + $p = $i + 1; + } + } + $result[] = substr($string, $p); + return $result; + } + + /** + * Encodes a header value as per RFC2047 + * + * @param string $value The header data to encode + * @param string $charset Character set name + * @param string $encoding Encoding name (base64 or quoted-printable) + * @param int $prefix_len Prefix length. Default: 0 + * @param string $eol End-of-line sequence. Default: "\r\n" + * + * @return string Encoded header data + * @since 1.6.1 + */ + public static function encodeHeaderValue($value, $charset, $encoding, $prefix_len = 0, $eol = "\r\n") + { + // #17311: Use multibyte aware method (requires mbstring extension) + if ($result = Mail_mimePart::encodeMB($value, $charset, $encoding, $prefix_len, $eol)) { + return $result; + } + + // Generate the header using the specified params and dynamicly + // determine the maximum length of such strings. + // 75 is the value specified in the RFC. + $encoding = $encoding == 'base64' ? 'B' : 'Q'; + $prefix = '=?' . $charset . '?' . $encoding .'?'; + $suffix = '?='; + $maxLength = 75 - strlen($prefix . $suffix); + $maxLength1stLine = $maxLength - $prefix_len; + + if ($encoding == 'B') { + // Base64 encode the entire string + $value = base64_encode($value); + + // We can cut base64 every 4 characters, so the real max + // we can get must be rounded down. + $maxLength = $maxLength - ($maxLength % 4); + $maxLength1stLine = $maxLength1stLine - ($maxLength1stLine % 4); + + $cutpoint = $maxLength1stLine; + $output = ''; + + while ($value) { + // Split translated string at every $maxLength + $part = substr($value, 0, $cutpoint); + $value = substr($value, $cutpoint); + $cutpoint = $maxLength; + // RFC 2047 specifies that any split header should + // be separated by a CRLF SPACE. + if ($output) { + $output .= $eol . ' '; + } + $output .= $prefix . $part . $suffix; + } + $value = $output; + } else { + // quoted-printable encoding has been selected + $value = Mail_mimePart::encodeQP($value); + + // This regexp will break QP-encoded text at every $maxLength + // but will not break any encoded letters. + $reg1st = "|(.{0,$maxLength1stLine}[^\=][^\=])|"; + $reg2nd = "|(.{0,$maxLength}[^\=][^\=])|"; + + if (strlen($value) > $maxLength1stLine) { + // Begin with the regexp for the first line. + $reg = $reg1st; + $output = ''; + while ($value) { + // Split translated string at every $maxLength + // But make sure not to break any translated chars. + $found = preg_match($reg, $value, $matches); + + // After this first line, we need to use a different + // regexp for the first line. + $reg = $reg2nd; + + // Save the found part and encapsulate it in the + // prefix & suffix. Then remove the part from the + // $value_out variable. + if ($found) { + $part = $matches[0]; + $len = strlen($matches[0]); + $value = substr($value, $len); + } else { + $part = $value; + $value = ''; + } + + // RFC 2047 specifies that any split header should + // be separated by a CRLF SPACE + if ($output) { + $output .= $eol . ' '; + } + $output .= $prefix . $part . $suffix; + } + $value = $output; + } else { + $value = $prefix . $value . $suffix; + } + } + + return $value; + } + + /** + * Encodes the given string using quoted-printable + * + * @param string $str String to encode + * + * @return string Encoded string + * @since 1.6.0 + */ + public static function encodeQP($str) + { + // Bug #17226 RFC 2047 restricts some characters + // if the word is inside a phrase, permitted chars are only: + // ASCII letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_" + + // "=", "_", "?" must be encoded + $regexp = '/([\x22-\x29\x2C\x2E\x3A-\x40\x5B-\x60\x7B-\x7E\x80-\xFF])/'; + $str = preg_replace_callback( + $regexp, array('Mail_mimePart', 'qpReplaceCallback'), $str + ); + + return str_replace(' ', '_', $str); + } + + /** + * Encodes the given string using base64 or quoted-printable. + * This method makes sure that encoded-word represents an integral + * number of characters as per RFC2047. + * + * @param string $str String to encode + * @param string $charset Character set name + * @param string $encoding Encoding name (base64 or quoted-printable) + * @param int $prefix_len Prefix length. Default: 0 + * @param string $eol End-of-line sequence. Default: "\r\n" + * + * @return string Encoded string + * @since 1.8.0 + */ + public static function encodeMB($str, $charset, $encoding, $prefix_len=0, $eol="\r\n") + { + if (!function_exists('mb_substr') || !function_exists('mb_strlen')) { + return; + } + + $encoding = $encoding == 'base64' ? 'B' : 'Q'; + // 75 is the value specified in the RFC + $prefix = '=?' . $charset . '?'.$encoding.'?'; + $suffix = '?='; + $maxLength = 75 - strlen($prefix . $suffix); + + // A multi-octet character may not be split across adjacent encoded-words + // So, we'll loop over each character + // mb_stlen() with wrong charset will generate a warning here and return null + $length = mb_strlen($str, $charset); + $result = ''; + $line_length = $prefix_len; + + if ($encoding == 'B') { + // base64 + $start = 0; + $prev = ''; + + for ($i=1; $i<=$length; $i++) { + // See #17311 + $chunk = mb_substr($str, $start, $i-$start, $charset); + $chunk = base64_encode($chunk); + $chunk_len = strlen($chunk); + + if ($line_length + $chunk_len == $maxLength || $i == $length) { + if ($result) { + $result .= "\n"; + } + $result .= $chunk; + $line_length = 0; + $start = $i; + } else if ($line_length + $chunk_len > $maxLength) { + if ($result) { + $result .= "\n"; + } + if ($prev) { + $result .= $prev; + } + $line_length = 0; + $start = $i - 1; + } else { + $prev = $chunk; + } + } + } else { + // quoted-printable + // see encodeQP() + $regexp = '/([\x22-\x29\x2C\x2E\x3A-\x40\x5B-\x60\x7B-\x7E\x80-\xFF])/'; + + for ($i=0; $i<=$length; $i++) { + $char = mb_substr($str, $i, 1, $charset); + // RFC recommends underline (instead of =20) in place of the space + // that's one of the reasons why we're not using iconv_mime_encode() + if ($char == ' ') { + $char = '_'; + $char_len = 1; + } else { + $char = preg_replace_callback( + $regexp, array('Mail_mimePart', 'qpReplaceCallback'), $char + ); + $char_len = strlen($char); + } + + if ($line_length + $char_len > $maxLength) { + if ($result) { + $result .= "\n"; + } + $line_length = 0; + } + + $result .= $char; + $line_length += $char_len; + } + } + + if ($result) { + $result = $prefix + .str_replace("\n", $suffix.$eol.' '.$prefix, $result).$suffix; + } + + return $result; + } + + /** + * Callback function to replace extended characters (\x80-xFF) with their + * ASCII values (RFC2047: quoted-printable) + * + * @param array $matches Preg_replace's matches array + * + * @return string Encoded character string + */ + protected static function qpReplaceCallback($matches) + { + return sprintf('=%02X', ord($matches[1])); + } + + /** + * Callback function to replace extended characters (\x80-xFF) with their + * ASCII values (RFC2231) + * + * @param array $matches Preg_replace's matches array + * + * @return string Encoded character string + */ + protected static function encodeReplaceCallback($matches) + { + return sprintf('%%%02X', ord($matches[1])); + } + + /** + * PEAR::raiseError implementation + * + * @param string $message A text error message + * + * @return PEAR_Error Instance of PEAR_Error + */ + public static function raiseError($message) + { + // PEAR::raiseError() is not PHP 5.4 compatible + return new PEAR_Error($message); + } +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/package.xml b/democracy/sai/saimod_beta/cannon/Pear/Mail/package.xml new file mode 100644 index 0000000..7392648 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/package.xml @@ -0,0 +1,80 @@ + + + Mail + pear.php.net + Class that provides multiple interfaces for sending emails + PEAR's Mail package defines an interface for implementing mailers under the PEAR hierarchy. It also provides supporting functions useful to multiple mailer backends. Currently supported backends include: PHP's native mail() function, sendmail, and SMTP. This package also provides a RFC822 email address list validation utility class. + + Chuck Hagenbuch + chagenbu + chuck@horde.org + no + + + Richard Heyes + richard + richard@phpguru.org + no + + + Aleksander Machniak + alec + alec@alec.pl + yes + + 2017-04-11 + + 1.4.1 + 1.3.0 + + + stable + stable + + New BSD License + +* Loosen recognition of "queued as" server response (PR #10) + +* Bug #20463: domain-literal parsing error +* Bug #20513: Mail_smtp::send() doesn't close socket for smtp connection + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2.1 + + + 1.5.6 + + + + + Net_SMTP + pear.php.net + 1.4.1 + + + + + diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/13659.phpt b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/13659.phpt new file mode 100644 index 0000000..0b57a72 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/13659.phpt @@ -0,0 +1,25 @@ +--TEST-- +Mail: Test for bug #13659 +--FILE-- + (test)'; +$parser = new Mail_RFC822(); +$result = $parser->parseAddressList($address, 'anydomain.com', TRUE); + +if (!PEAR::isError($result) && is_array($result) && is_object($result[0])) + if ($result[0]->personal == '"Test Student"' && + $result[0]->mailbox == "test" && + $result[0]->host == "mydomain.com" && + is_array($result[0]->comment) && $result[0]->comment[0] == 'test') + { + print("OK"); + } + + +?> +--EXPECT-- +OK diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/9137.phpt b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/9137.phpt new file mode 100644 index 0000000..ba54c05 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/9137.phpt @@ -0,0 +1,33 @@ +--TEST-- +Mail: Test for bug #9137 +--FILE-- + 'John Doe', 'email' => 'test@example.com'), + array('name' => 'John Doe\\', 'email' => 'test@example.com'), + array('name' => 'John "Doe', 'email' => 'test@example.com'), + array('name' => 'John "Doe\\', 'email' => 'test@example.com'), +); + +for ($i = 0; $i < count($addresses); $i++) { + // construct the address + $address = "\"" . addslashes($addresses[$i]['name']) . "\" ". + "<".$addresses[$i]['email'].">"; + + $parsedAddresses = Mail_RFC822::parseAddressList($address); + if (is_a($parsedAddresses, 'PEAR_Error')) { + echo $address." :: Failed to validate\n"; + } else { + echo $address." :: Parsed\n"; + } +} + +--EXPECT-- +"John Doe" :: Parsed +"John Doe\\" :: Parsed +"John \"Doe" :: Parsed +"John \"Doe\\" :: Parsed diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/9137_2.phpt b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/9137_2.phpt new file mode 100644 index 0000000..cc9dcbc --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/9137_2.phpt @@ -0,0 +1,35 @@ +--TEST-- +Mail: Test for bug #9137, take 2 +--FILE-- + '"John Doe" '), + array('raw' => '"John Doe' . chr(92) . '" '), + array('raw' => '"John Doe' . chr(92) . chr(92) . '" '), + array('raw' => '"John Doe' . chr(92) . chr(92) . chr(92) . '" '), + array('raw' => '"John Doe' . chr(92) . chr(92) . chr(92) . chr(92) . '" '), + array('raw' => '"John Doe '), +); + +for ($i = 0; $i < count($addresses); $i++) { + // construct the address + $address = $addresses[$i]['raw']; + $parsedAddresses = Mail_RFC822::parseAddressList($address); + if (PEAR::isError($parsedAddresses)) { + echo $address." :: Failed to validate\n"; + } else { + echo $address." :: Parsed\n"; + } +} + +--EXPECT-- +"John Doe" :: Parsed +"John Doe\" :: Failed to validate +"John Doe\\" :: Parsed +"John Doe\\\" :: Failed to validate +"John Doe\\\\" :: Parsed +"John Doe :: Failed to validate diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/Makefile b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/Makefile new file mode 100644 index 0000000..bb6b1be --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/Makefile @@ -0,0 +1,8 @@ + +clean: + rm -f *.log *.php *.diff *.exp *.out + + + +test: + cd .. && pear run-tests tests/*.phpt && cd tests; diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/bug17178.phpt b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/bug17178.phpt new file mode 100644 index 0000000..4572dda --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/bug17178.phpt @@ -0,0 +1,11 @@ +--TEST-- +Mail_RFC822::parseAddressList does not accept RFC-valid group syntax +--FILE-- +parseAddressList($address, null, true, true)); + +/* Address groups. */ +$address = 'My Group: "Richard" (A comment), ted@example.com (Ted Bloggs), Barney;'; +print_r($parser->parseAddressList($address, null, true, true)); + +/* A valid address with spaces in the local part. */ +$address = '<"Jon Parise"@php.net>'; +print_r($parser->parseAddressList($address, null, true, true)); + +/* An invalid address with spaces in the local part. */ +$address = ''; +$result = $parser->parseAddressList($address, null, true, true); +if (is_a($result, 'PEAR_Error')) echo $result->getMessage() . "\n"; + +/* A valid address with an uncommon TLD. */ +$address = 'jon@host.longtld'; +$result = $parser->parseAddressList($address, null, true, true); +if (is_a($result, 'PEAR_Error')) echo $result->getMessage() . "\n"; + +--EXPECT-- +Array +( + [0] => stdClass Object + ( + [personal] => + [comment] => Array + ( + ) + + [mailbox] => user + [host] => example.com + ) + +) +Array +( + [0] => stdClass Object + ( + [groupname] => My Group + [addresses] => Array + ( + [0] => stdClass Object + ( + [personal] => "Richard" + [comment] => Array + ( + [0] => A comment + ) + + [mailbox] => richard + [host] => localhost + ) + + [1] => stdClass Object + ( + [personal] => + [comment] => Array + ( + [0] => Ted Bloggs + ) + + [mailbox] => ted + [host] => example.com + ) + + [2] => stdClass Object + ( + [personal] => + [comment] => Array + ( + ) + + [mailbox] => Barney + [host] => localhost + ) + + ) + + ) + +) +Array +( + [0] => stdClass Object + ( + [personal] => + [comment] => Array + ( + ) + + [mailbox] => "Jon Parise" + [host] => php.net + ) + +) +Validation failed for: diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/smtp_error.phpt b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/smtp_error.phpt new file mode 100644 index 0000000..4e8e5a6 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/smtp_error.phpt @@ -0,0 +1,30 @@ +--TEST-- +Mail: SMTP Error Reporting +--SKIPIF-- +packageExists('Net_SMTP')) die("skip\n"); +--FILE-- + 'bogus.host.tld'); + +/* Create our SMTP-based mailer object. */ +$mailer = Mail::factory('smtp', $params); + +/* Attempt to send an empty message in order to trigger an error. */ +$e = $mailer->send(array(), array(), ''); +if (is_a($e, 'PEAR_Error')) { + $err = $e->getMessage(); + if (preg_match('/Failed to connect to bogus.host.tld:25 \[SMTP: Failed to connect socket:.*/i', $err)) { + echo "OK"; + } +} + +--EXPECT-- +OK \ No newline at end of file diff --git a/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/validateQuotedString.phpt b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/validateQuotedString.phpt new file mode 100644 index 0000000..8b892bd --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Mail/tests/validateQuotedString.phpt @@ -0,0 +1,24 @@ +--TEST-- +Mail_RFC822::parseAddressList simple tests +--FILE-- +, postmaster@example.com, root'; + +$address_array = Mail_RFC822::parseAddressList($address_string, "example.com"); + +foreach ($address_array as $val) { + echo "mailbox : " . $val->mailbox . "\n"; + echo "host : " . $val->host . "\n"; + echo "personal: " . $val->personal . "\n"; +} +--EXPECT-- +mailbox : doe +host : example.com +personal: "Joe Doe \(from Somewhere\)" +mailbox : postmaster +host : example.com +personal: +mailbox : root +host : example.com +personal: diff --git a/democracy/sai/saimod_beta/cannon/Pear/Net/SMTP.php b/democracy/sai/saimod_beta/cannon/Pear/Net/SMTP.php new file mode 100644 index 0000000..5cb8b9f --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Net/SMTP.php @@ -0,0 +1,1274 @@ + | +// | Jon Parise | +// | Damian Alejandro Fernandez Sosa | +// +----------------------------------------------------------------------+ + +require_once 'Socket.php'; + +/** + * Provides an implementation of the SMTP protocol using PEAR's + * Net_Socket class. + * + * @package Net_SMTP + * @author Chuck Hagenbuch + * @author Jon Parise + * @author Damian Alejandro Fernandez Sosa + * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause + * + * @example basic.php A basic implementation of the Net_SMTP package. + */ +class Net_SMTP +{ + /** + * The server to connect to. + * @var string + */ + public $host = 'localhost'; + + /** + * The port to connect to. + * @var int + */ + public $port = 25; + + /** + * The value to give when sending EHLO or HELO. + * @var string + */ + public $localhost = 'localhost'; + + /** + * List of supported authentication methods, in preferential order. + * @var array + */ + public $auth_methods = array(); + + /** + * Use SMTP command pipelining (specified in RFC 2920) if the SMTP + * server supports it. + * + * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(), + * somlFrom() and samlFrom() do not wait for a response from the + * SMTP server but return immediately. + * + * @var bool + */ + public $pipelining = false; + + /** + * Number of pipelined commands. + * @var int + */ + protected $pipelined_commands = 0; + + /** + * Should debugging output be enabled? + * @var boolean + */ + protected $debug = false; + + /** + * Debug output handler. + * @var callback + */ + protected $debug_handler = null; + + /** + * The socket resource being used to connect to the SMTP server. + * @var resource + */ + protected $socket = null; + + /** + * Array of socket options that will be passed to Net_Socket::connect(). + * @see stream_context_create() + * @var array + */ + protected $socket_options = null; + + /** + * The socket I/O timeout value in seconds. + * @var int + */ + protected $timeout = 0; + + /** + * The most recent server response code. + * @var int + */ + protected $code = -1; + + /** + * The most recent server response arguments. + * @var array + */ + protected $arguments = array(); + + /** + * Stores the SMTP server's greeting string. + * @var string + */ + protected $greeting = null; + + /** + * Stores detected features of the SMTP server. + * @var array + */ + protected $esmtp = array(); + + /** + * Instantiates a new Net_SMTP object, overriding any defaults + * with parameters that are passed in. + * + * If you have SSL support in PHP, you can connect to a server + * over SSL using an 'ssl://' prefix: + * + * // 465 is a common smtps port. + * $smtp = new Net_SMTP('ssl://mail.host.com', 465); + * $smtp->connect(); + * + * @param string $host The server to connect to. + * @param integer $port The port to connect to. + * @param string $localhost The value to give when sending EHLO or HELO. + * @param boolean $pipelining Use SMTP command pipelining + * @param integer $timeout Socket I/O timeout in seconds. + * @param array $socket_options Socket stream_context_create() options. + * + * @since 1.0 + */ + public function __construct($host = null, $port = null, $localhost = null, + $pipelining = false, $timeout = 0, $socket_options = null + ) { + if (isset($host)) { + $this->host = $host; + } + if (isset($port)) { + $this->port = $port; + } + if (isset($localhost)) { + $this->localhost = $localhost; + } + + $this->pipelining = $pipelining; + $this->socket = new Net_Socket(); + $this->socket_options = $socket_options; + $this->timeout = $timeout; + + /* Include the Auth_SASL package. If the package is available, we + * enable the authentication methods that depend upon it. */ + /*if (@include_once dirname(__FILE__).'/Auth/SASL.php') { + $this->setAuthMethod('CRAM-MD5', array($this, 'authCramMD5')); + $this->setAuthMethod('DIGEST-MD5', array($this, 'authDigestMD5')); + }*/ + + /* These standard authentication methods are always available. */ + $this->setAuthMethod('LOGIN', array($this, 'authLogin'), false); + $this->setAuthMethod('PLAIN', array($this, 'authPlain'), false); + } + + /** + * Set the socket I/O timeout value in seconds plus microseconds. + * + * @param integer $seconds Timeout value in seconds. + * @param integer $microseconds Additional value in microseconds. + * + * @since 1.5.0 + */ + public function setTimeout($seconds, $microseconds = 0) + { + return $this->socket->setTimeout($seconds, $microseconds); + } + + /** + * Set the value of the debugging flag. + * + * @param boolean $debug New value for the debugging flag. + * @param callback $handler Debug handler callback + * + * @since 1.1.0 + */ + public function setDebug($debug, $handler = null) + { + $this->debug = $debug; + $this->debug_handler = $handler; + } + + /** + * Write the given debug text to the current debug output handler. + * + * @param string $message Debug mesage text. + * + * @since 1.3.3 + */ + protected function debug($message) + { + if ($this->debug) { + if ($this->debug_handler) { + call_user_func_array( + $this->debug_handler, array(&$this, $message) + ); + } else { + echo "DEBUG: $message\n"; + } + } + } + + /** + * Send the given string of data to the server. + * + * @param string $data The string of data to send. + * + * @return mixed The number of bytes that were actually written, + * or a PEAR_Error object on failure. + * + * @since 1.1.0 + */ + protected function send($data) + { + $this->debug("Send: $data"); + + $result = $this->socket->write($data); + if (!$result || PEAR::isError($result)) { + $msg = $result ? $result->getMessage() : "unknown error"; + return PEAR::raiseError("Failed to write to socket: $msg"); + } + + return $result; + } + + /** + * Send a command to the server with an optional string of + * arguments. A carriage return / linefeed (CRLF) sequence will + * be appended to each command string before it is sent to the + * SMTP server - an error will be thrown if the command string + * already contains any newline characters. Use send() for + * commands that must contain newlines. + * + * @param string $command The SMTP command to send to the server. + * @param string $args A string of optional arguments to append + * to the command. + * + * @return mixed The result of the send() call. + * + * @since 1.1.0 + */ + protected function put($command, $args = '') + { + if (!empty($args)) { + $command .= ' ' . $args; + } + + if (strcspn($command, "\r\n") !== strlen($command)) { + return PEAR::raiseError('Commands cannot contain newlines'); + } + + return $this->send($command . "\r\n"); + } + + /** + * Read a reply from the SMTP server. The reply consists of a response + * code and a response message. + * + * @param mixed $valid The set of valid response codes. These + * may be specified as an array of integer + * values or as a single integer value. + * @param bool $later Do not parse the response now, but wait + * until the last command in the pipelined + * command group + * + * @return mixed True if the server returned a valid response code or + * a PEAR_Error object is an error condition is reached. + * + * @since 1.1.0 + * + * @see getResponse + */ + protected function parseResponse($valid, $later = false) + { + $this->code = -1; + $this->arguments = array(); + + if ($later) { + $this->pipelined_commands++; + return true; + } + + for ($i = 0; $i <= $this->pipelined_commands; $i++) { + while ($line = $this->socket->readLine()) { + $this->debug("Recv: $line"); + + /* If we receive an empty line, the connection was closed. */ + if (empty($line)) { + $this->disconnect(); + return PEAR::raiseError('Connection was closed'); + } + + /* Read the code and store the rest in the arguments array. */ + $code = substr($line, 0, 3); + $this->arguments[] = trim(substr($line, 4)); + + /* Check the syntax of the response code. */ + if (is_numeric($code)) { + $this->code = (int)$code; + } else { + $this->code = -1; + break; + } + + /* If this is not a multiline response, we're done. */ + if (substr($line, 3, 1) != '-') { + break; + } + } + } + + $this->pipelined_commands = 0; + + /* Compare the server's response code with the valid code/codes. */ + if (is_int($valid) && ($this->code === $valid)) { + return true; + } elseif (is_array($valid) && in_array($this->code, $valid, true)) { + return true; + } + + return PEAR::raiseError('Invalid response code received from server', $this->code); + } + + /** + * Issue an SMTP command and verify its response. + * + * @param string $command The SMTP command string or data. + * @param mixed $valid The set of valid response codes. These + * may be specified as an array of integer + * values or as a single integer value. + * + * @return mixed True on success or a PEAR_Error object on failure. + * + * @since 1.6.0 + */ + public function command($command, $valid) + { + if (PEAR::isError($error = $this->put($command))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse($valid))) { + return $error; + } + + return true; + } + + /** + * Return a 2-tuple containing the last response from the SMTP server. + * + * @return array A two-element array: the first element contains the + * response code as an integer and the second element + * contains the response's arguments as a string. + * + * @since 1.1.0 + */ + public function getResponse() + { + return array($this->code, join("\n", $this->arguments)); + } + + /** + * Return the SMTP server's greeting string. + * + * @return string A string containing the greeting string, or null if + * a greeting has not been received. + * + * @since 1.3.3 + */ + public function getGreeting() + { + return $this->greeting; + } + + /** + * Attempt to connect to the SMTP server. + * + * @param int $timeout The timeout value (in seconds) for the + * socket connection attempt. + * @param bool $persistent Should a persistent socket connection + * be used? + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.0 + */ + public function connect($timeout = null, $persistent = false) + { + $this->greeting = null; + + $result = $this->socket->connect( + $this->host, $this->port, $persistent, $timeout, $this->socket_options + ); + + if (PEAR::isError($result)) { + return PEAR::raiseError( + 'Failed to connect socket: ' . $result->getMessage() + ); + } + + /* + * Now that we're connected, reset the socket's timeout value for + * future I/O operations. This allows us to have different socket + * timeout values for the initial connection (our $timeout parameter) + * and all other socket operations. + */ + if ($this->timeout > 0) { + if (PEAR::isError($error = $this->setTimeout($this->timeout))) { + return $error; + } + } + + if (PEAR::isError($error = $this->parseResponse(220))) { + return $error; + } + + /* Extract and store a copy of the server's greeting string. */ + list(, $this->greeting) = $this->getResponse(); + + if (PEAR::isError($error = $this->negotiate())) { + return $error; + } + + return true; + } + + /** + * Attempt to disconnect from the SMTP server. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.0 + */ + public function disconnect() + { + if (PEAR::isError($error = $this->put('QUIT'))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(221))) { + return $error; + } + if (PEAR::isError($error = $this->socket->disconnect())) { + return PEAR::raiseError( + 'Failed to disconnect socket: ' . $error->getMessage() + ); + } + + return true; + } + + /** + * Attempt to send the EHLO command and obtain a list of ESMTP + * extensions available, and failing that just send HELO. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * + * @since 1.1.0 + */ + protected function negotiate() + { + if (PEAR::isError($error = $this->put('EHLO', $this->localhost))) { + return $error; + } + + if (PEAR::isError($this->parseResponse(250))) { + /* If the EHLO failed, try the simpler HELO command. */ + if (PEAR::isError($error = $this->put('HELO', $this->localhost))) { + return $error; + } + if (PEAR::isError($this->parseResponse(250))) { + return PEAR::raiseError('HELO was not accepted', $this->code); + } + + return true; + } + + foreach ($this->arguments as $argument) { + $verb = strtok($argument, ' '); + $len = strlen($verb); + $arguments = substr($argument, $len + 1, strlen($argument) - $len - 1); + $this->esmtp[$verb] = $arguments; + } + + if (!isset($this->esmtp['PIPELINING'])) { + $this->pipelining = false; + } + + return true; + } + + /** + * Returns the name of the best authentication method that the server + * has advertised. + * + * @return mixed Returns a string containing the name of the best + * supported authentication method or a PEAR_Error object + * if a failure condition is encountered. + * @since 1.1.0 + */ + protected function getBestAuthMethod() + { + $available_methods = explode(' ', $this->esmtp['AUTH']); + + foreach ($this->auth_methods as $method => $callback) { + if (in_array($method, $available_methods)) { + return $method; + } + } + + return PEAR::raiseError('No supported authentication methods'); + } + + /** + * Attempt to do SMTP authentication. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $method The requested authentication method. If none is + * specified, the best supported method will be used. + * @param bool $tls Flag indicating whether or not TLS should be attempted. + * @param string $authz An optional authorization identifier. If specified, this + * identifier will be used as the authorization proxy. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.0 + */ + public function auth($uid, $pwd , $method = '', $tls = true, $authz = '') + { + /* We can only attempt a TLS connection if one has been requested, + * we're running PHP 5.1.0 or later, have access to the OpenSSL + * extension, are connected to an SMTP server which supports the + * STARTTLS extension, and aren't already connected over a secure + * (SSL) socket connection. */ + if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') + && extension_loaded('openssl') && isset($this->esmtp['STARTTLS']) + && strncasecmp($this->host, 'ssl://', 6) !== 0 + ) { + /* Start the TLS connection attempt. */ + if (PEAR::isError($result = $this->put('STARTTLS'))) { + return $result; + } + if (PEAR::isError($result = $this->parseResponse(220))) { + return $result; + } + if (isset($this->socket_options['ssl']['crypto_method'])) { + $crypto_method = $this->socket_options['ssl']['crypto_method']; + } else { + /* STREAM_CRYPTO_METHOD_TLS_ANY_CLIENT constant does not exist + * and STREAM_CRYPTO_METHOD_SSLv23_CLIENT constant is + * inconsistent across PHP versions. */ + $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT + | @STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT + | @STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + } + if (PEAR::isError($result = $this->socket->enableCrypto(true, $crypto_method))) { + return $result; + } elseif ($result !== true) { + return PEAR::raiseError('STARTTLS failed'); + } + + /* Send EHLO again to recieve the AUTH string from the + * SMTP server. */ + $this->negotiate(); + } + + if (empty($this->esmtp['AUTH'])) { + return PEAR::raiseError('SMTP server does not support authentication'); + } + + /* If no method has been specified, get the name of the best + * supported method advertised by the SMTP server. */ + if (empty($method)) { + if (PEAR::isError($method = $this->getBestAuthMethod())) { + /* Return the PEAR_Error object from _getBestAuthMethod(). */ + return $method; + } + } else { + $method = strtoupper($method); + if (!array_key_exists($method, $this->auth_methods)) { + return PEAR::raiseError("$method is not a supported authentication method"); + } + } + + if (!isset($this->auth_methods[$method])) { + return PEAR::raiseError("$method is not a supported authentication method"); + } + + if (!is_callable($this->auth_methods[$method], false)) { + return PEAR::raiseError("$method authentication method cannot be called"); + } + + if (is_array($this->auth_methods[$method])) { + list($object, $method) = $this->auth_methods[$method]; + $result = $object->{$method}($uid, $pwd, $authz, $this); + } else { + $func = $this->auth_methods[$method]; + $result = $func($uid, $pwd, $authz, $this); + } + + /* If an error was encountered, return the PEAR_Error object. */ + if (PEAR::isError($result)) { + return $result; + } + + return true; + } + + /** + * Add a new authentication method. + * + * @param string $name The authentication method name (e.g. 'PLAIN') + * @param mixed $callback The authentication callback (given as the name of a + * function or as an (object, method name) array). + * @param bool $prepend Should the new method be prepended to the list of + * available methods? This is the default behavior, + * giving the new method the highest priority. + * + * @return mixed True on success or a PEAR_Error object on failure. + * + * @since 1.6.0 + */ + public function setAuthMethod($name, $callback, $prepend = true) + { + if (!is_string($name)) { + return PEAR::raiseError('Method name is not a string'); + } + + if (!is_string($callback) && !is_array($callback)) { + return PEAR::raiseError('Method callback must be string or array'); + } + + if (is_array($callback)) { + if (!is_object($callback[0]) || !is_string($callback[1])) { + return PEAR::raiseError('Bad mMethod callback array'); + } + } + + if ($prepend) { + $this->auth_methods = array_merge( + array($name => $callback), $this->auth_methods + ); + } else { + $this->auth_methods[$name] = $callback; + } + + return true; + } + + /** + * Authenticates the user using the DIGEST-MD5 method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.1.0 + */ + protected function authDigestMD5($uid, $pwd, $authz = '') + { + if (PEAR::isError($error = $this->put('AUTH', 'DIGEST-MD5'))) { + return $error; + } + /* 334: Continue authentication request */ + if (PEAR::isError($error = $this->parseResponse(334))) { + /* 503: Error: already authenticated */ + if ($this->code === 503) { + return true; + } + return $error; + } + + $auth_sasl = new Auth_SASL; + $digest = $auth_sasl->factory('digest-md5'); + $challenge = base64_decode($this->arguments[0]); + $auth_str = base64_encode( + $digest->getResponse($uid, $pwd, $challenge, $this->host, "smtp", $authz) + ); + + if (PEAR::isError($error = $this->put($auth_str))) { + return $error; + } + /* 334: Continue authentication request */ + if (PEAR::isError($error = $this->parseResponse(334))) { + return $error; + } + + /* We don't use the protocol's third step because SMTP doesn't + * allow subsequent authentication, so we just silently ignore + * it. */ + if (PEAR::isError($error = $this->put(''))) { + return $error; + } + /* 235: Authentication successful */ + if (PEAR::isError($error = $this->parseResponse(235))) { + return $error; + } + } + + /** + * Authenticates the user using the CRAM-MD5 method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.1.0 + */ + protected function authCRAMMD5($uid, $pwd, $authz = '') + { + if (PEAR::isError($error = $this->put('AUTH', 'CRAM-MD5'))) { + return $error; + } + /* 334: Continue authentication request */ + if (PEAR::isError($error = $this->parseResponse(334))) { + /* 503: Error: already authenticated */ + if ($this->code === 503) { + return true; + } + return $error; + } + + $auth_sasl = new Auth_SASL; + $challenge = base64_decode($this->arguments[0]); + $cram = $auth_sasl->factory('cram-md5'); + $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge)); + + if (PEAR::isError($error = $this->put($auth_str))) { + return $error; + } + + /* 235: Authentication successful */ + if (PEAR::isError($error = $this->parseResponse(235))) { + return $error; + } + } + + /** + * Authenticates the user using the LOGIN method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.1.0 + */ + protected function authLogin($uid, $pwd, $authz = '') + { + if (PEAR::isError($error = $this->put('AUTH', 'LOGIN'))) { + return $error; + } + /* 334: Continue authentication request */ + if (PEAR::isError($error = $this->parseResponse(334))) { + /* 503: Error: already authenticated */ + if ($this->code === 503) { + return true; + } + return $error; + } + + if (PEAR::isError($error = $this->put(base64_encode($uid)))) { + return $error; + } + /* 334: Continue authentication request */ + if (PEAR::isError($error = $this->parseResponse(334))) { + return $error; + } + + if (PEAR::isError($error = $this->put(base64_encode($pwd)))) { + return $error; + } + + /* 235: Authentication successful */ + if (PEAR::isError($error = $this->parseResponse(235))) { + return $error; + } + + return true; + } + + /** + * Authenticates the user using the PLAIN method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.1.0 + */ + protected function authPlain($uid, $pwd, $authz = '') + { + if (PEAR::isError($error = $this->put('AUTH', 'PLAIN'))) { + return $error; + } + /* 334: Continue authentication request */ + if (PEAR::isError($error = $this->parseResponse(334))) { + /* 503: Error: already authenticated */ + if ($this->code === 503) { + return true; + } + return $error; + } + + $auth_str = base64_encode($authz . chr(0) . $uid . chr(0) . $pwd); + + if (PEAR::isError($error = $this->put($auth_str))) { + return $error; + } + + /* 235: Authentication successful */ + if (PEAR::isError($error = $this->parseResponse(235))) { + return $error; + } + + return true; + } + + /** + * Send the HELO command. + * + * @param string $domain The domain name to say we are. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.0 + */ + public function helo($domain) + { + if (PEAR::isError($error = $this->put('HELO', $domain))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(250))) { + return $error; + } + + return true; + } + + /** + * Return the list of SMTP service extensions advertised by the server. + * + * @return array The list of SMTP service extensions. + * @since 1.3 + */ + public function getServiceExtensions() + { + return $this->esmtp; + } + + /** + * Send the MAIL FROM: command. + * + * @param string $sender The sender (reverse path) to set. + * @param string $params String containing additional MAIL parameters, + * such as the NOTIFY flags defined by RFC 1891 + * or the VERP protocol. + * + * If $params is an array, only the 'verp' option + * is supported. If 'verp' is true, the XVERP + * parameter is appended to the MAIL command. + * If the 'verp' value is a string, the full + * XVERP=value parameter is appended. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.0 + */ + public function mailFrom($sender, $params = null) + { + $args = "FROM:<$sender>"; + + /* Support the deprecated array form of $params. */ + if (is_array($params) && isset($params['verp'])) { + if ($params['verp'] === true) { + $args .= ' XVERP'; + } elseif (trim($params['verp'])) { + $args .= ' XVERP=' . $params['verp']; + } + } elseif (is_string($params) && !empty($params)) { + $args .= ' ' . $params; + } + + if (PEAR::isError($error = $this->put('MAIL', $args))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { + return $error; + } + + return true; + } + + /** + * Send the RCPT TO: command. + * + * @param string $recipient The recipient (forward path) to add. + * @param string $params String containing additional RCPT parameters, + * such as the NOTIFY flags defined by RFC 1891. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * + * @since 1.0 + */ + public function rcptTo($recipient, $params = null) + { + $args = "TO:<$recipient>"; + if (is_string($params)) { + $args .= ' ' . $params; + } + + if (PEAR::isError($error = $this->put('RCPT', $args))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(array(250, 251), $this->pipelining))) { + return $error; + } + + return true; + } + + /** + * Quote the data so that it meets SMTP standards. + * + * This is provided as a separate public function to facilitate + * easier overloading for the cases where it is desirable to + * customize the quoting behavior. + * + * @param string &$data The message text to quote. The string must be passed + * by reference, and the text will be modified in place. + * + * @since 1.2 + */ + public function quotedata(&$data) + { + /* Because a single leading period (.) signifies an end to the + * data, legitimate leading periods need to be "doubled" ('..'). */ + $data = preg_replace('/^\./m', '..', $data); + + /* Change Unix (\n) and Mac (\r) linefeeds into CRLF's (\r\n). */ + $data = preg_replace('/(?:\r\n|\n|\r(?!\n))/', "\r\n", $data); + } + + /** + * Send the DATA command. + * + * @param mixed $data The message data, either as a string or an open + * file resource. + * @param string $headers The message headers. If $headers is provided, + * $data is assumed to contain only body data. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.0 + */ + public function data($data, $headers = null) + { + /* Verify that $data is a supported type. */ + if (!is_string($data) && !is_resource($data)) { + return PEAR::raiseError('Expected a string or file resource'); + } + + /* Start by considering the size of the optional headers string. We + * also account for the addition 4 character "\r\n\r\n" separator + * sequence. */ + $size = $headers_size = (is_null($headers)) ? 0 : strlen($headers) + 4; + + if (is_resource($data)) { + $stat = fstat($data); + if ($stat === false) { + return PEAR::raiseError('Failed to get file size'); + } + $size += $stat['size']; + } else { + $size += strlen($data); + } + + /* RFC 1870, section 3, subsection 3 states "a value of zero indicates + * that no fixed maximum message size is in force". Furthermore, it + * says that if "the parameter is omitted no information is conveyed + * about the server's fixed maximum message size". */ + $limit = (isset($this->esmtp['SIZE'])) ? $this->esmtp['SIZE'] : 0; + if ($limit > 0 && $size >= $limit) { + $this->disconnect(); + return PEAR::raiseError('Message size exceeds server limit'); + } + + /* Initiate the DATA command. */ + if (PEAR::isError($error = $this->put('DATA'))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(354))) { + return $error; + } + + /* If we have a separate headers string, send it first. */ + if (!is_null($headers)) { + $this->quotedata($headers); + if (PEAR::isError($result = $this->send($headers . "\r\n\r\n"))) { + return $result; + } + + /* Subtract the headers size now that they've been sent. */ + $size -= $headers_size; + } + + /* Now we can send the message body data. */ + if (is_resource($data)) { + /* Stream the contents of the file resource out over our socket + * connection, line by line. Each line must be run through the + * quoting routine. */ + while (strlen($line = fread($data, 8192)) > 0) { + /* If the last character is an newline, we need to grab the + * next character to check to see if it is a period. */ + while (!feof($data)) { + $char = fread($data, 1); + $line .= $char; + if ($char != "\n") { + break; + } + } + $this->quotedata($line); + if (PEAR::isError($result = $this->send($line))) { + return $result; + } + } + + $last = $line; + } else { + /* + * Break up the data by sending one chunk (up to 512k) at a time. + * This approach reduces our peak memory usage. + */ + for ($offset = 0; $offset < $size;) { + $end = $offset + 512000; + + /* + * Ensure we don't read beyond our data size or span multiple + * lines. quotedata() can't properly handle character data + * that's split across two line break boundaries. + */ + if ($end >= $size) { + $end = $size; + } else { + for (; $end < $size; $end++) { + if ($data[$end] != "\n") { + break; + } + } + } + + /* Extract our chunk and run it through the quoting routine. */ + $chunk = substr($data, $offset, $end - $offset); + $this->quotedata($chunk); + + /* If we run into a problem along the way, abort. */ + if (PEAR::isError($result = $this->send($chunk))) { + return $result; + } + + /* Advance the offset to the end of this chunk. */ + $offset = $end; + } + + $last = $chunk; + } + + /* Don't add another CRLF sequence if it's already in the data */ + $terminator = (substr($last, -2) == "\r\n" ? '' : "\r\n") . ".\r\n"; + + /* Finally, send the DATA terminator sequence. */ + if (PEAR::isError($result = $this->send($terminator))) { + return $result; + } + + /* Verify that the data was successfully received by the server. */ + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { + return $error; + } + + return true; + } + + /** + * Send the SEND FROM: command. + * + * @param string $path The reverse path to send. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.2.6 + */ + public function sendFrom($path) + { + if (PEAR::isError($error = $this->put('SEND', "FROM:<$path>"))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { + return $error; + } + + return true; + } + + /** + * Send the SOML FROM: command. + * + * @param string $path The reverse path to send. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.2.6 + */ + public function somlFrom($path) + { + if (PEAR::isError($error = $this->put('SOML', "FROM:<$path>"))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { + return $error; + } + + return true; + } + + /** + * Send the SAML FROM: command. + * + * @param string $path The reverse path to send. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.2.6 + */ + public function samlFrom($path) + { + if (PEAR::isError($error = $this->put('SAML', "FROM:<$path>"))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { + return $error; + } + + return true; + } + + /** + * Send the RSET command. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.0 + */ + public function rset() + { + if (PEAR::isError($error = $this->put('RSET'))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(250, $this->pipelining))) { + return $error; + } + + return true; + } + + /** + * Send the VRFY command. + * + * @param string $string The string to verify + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.0 + */ + public function vrfy($string) + { + /* Note: 251 is also a valid response code */ + if (PEAR::isError($error = $this->put('VRFY', $string))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(array(250, 252)))) { + return $error; + } + + return true; + } + + /** + * Send the NOOP command. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.0 + */ + public function noop() + { + if (PEAR::isError($error = $this->put('NOOP'))) { + return $error; + } + if (PEAR::isError($error = $this->parseResponse(250))) { + return $error; + } + + return true; + } + + /** + * Backwards-compatibility method. identifySender()'s functionality is + * now handled internally. + * + * @return boolean This method always return true. + * + * @since 1.0 + */ + public function identifySender() + { + return true; + } +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/Net/Socket.php b/democracy/sai/saimod_beta/cannon/Pear/Net/Socket.php new file mode 100644 index 0000000..4564c3d --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/Net/Socket.php @@ -0,0 +1,738 @@ + + * @author Chuck Hagenbuch + * @copyright 1997-2017 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause + * @link http://pear.php.net/packages/Net_Socket + */ + +require_once dirname(__FILE__).'/../PEAR.php'; + +define('NET_SOCKET_READ', 1); +define('NET_SOCKET_WRITE', 2); +define('NET_SOCKET_ERROR', 4); + +/** + * Generalized Socket class. + * + * @category Net + * @package Net_Socket + * @author Stig Bakken + * @author Chuck Hagenbuch + * @copyright 1997-2017 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause + * @link http://pear.php.net/packages/Net_Socket + */ +class Net_Socket extends PEAR +{ + /** + * Socket file pointer. + * @var resource $fp + */ + public $fp = null; + + /** + * Whether the socket is blocking. Defaults to true. + * @var boolean $blocking + */ + public $blocking = true; + + /** + * Whether the socket is persistent. Defaults to false. + * @var boolean $persistent + */ + public $persistent = false; + + /** + * The IP address to connect to. + * @var string $addr + */ + public $addr = ''; + + /** + * The port number to connect to. + * @var integer $port + */ + public $port = 0; + + /** + * Number of seconds to wait on socket operations before assuming + * there's no more data. Defaults to no timeout. + * @var integer|float $timeout + */ + public $timeout = null; + + /** + * Number of bytes to read at a time in readLine() and + * readAll(). Defaults to 2048. + * @var integer $lineLength + */ + public $lineLength = 2048; + + /** + * The string to use as a newline terminator. Usually "\r\n" or "\n". + * @var string $newline + */ + public $newline = "\r\n"; + + /** + * Connect to the specified port. If called when the socket is + * already connected, it disconnects and connects again. + * + * @param string $addr IP address or host name (may be with protocol prefix). + * @param integer $port TCP port number. + * @param boolean $persistent (optional) Whether the connection is + * persistent (kept open between requests + * by the web server). + * @param integer $timeout (optional) Connection socket timeout. + * @param array $options See options for stream_context_create. + * + * @access public + * + * @return boolean|PEAR_Error True on success or a PEAR_Error on failure. + */ + public function connect( + $addr, + $port = 0, + $persistent = null, + $timeout = null, + $options = null + ) { + if (is_resource($this->fp)) { + @fclose($this->fp); + $this->fp = null; + } + + if (!$addr) { + return $this->raiseError('$addr cannot be empty'); + } else { + if (strspn($addr, ':.0123456789') === strlen($addr)) { + $this->addr = strpos($addr, ':') !== false ? '[' . $addr . ']' : $addr; + } else { + $this->addr = $addr; + } + } + + $this->port = $port % 65536; + + if ($persistent !== null) { + $this->persistent = $persistent; + } + + $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen'; + $errno = 0; + $errstr = ''; + + if (function_exists('error_clear_last')) { + error_clear_last(); + } else { + $old_track_errors = @ini_set('track_errors', 1); + } + + if ($timeout <= 0) { + $timeout = @ini_get('default_socket_timeout'); + } + + if ($options && function_exists('stream_context_create')) { + $context = stream_context_create($options); + + // Since PHP 5 fsockopen doesn't allow context specification + if (function_exists('stream_socket_client')) { + $flags = STREAM_CLIENT_CONNECT; + + if ($this->persistent) { + $flags = STREAM_CLIENT_PERSISTENT; + } + + $addr = $this->addr . ':' . $this->port; + $fp = @stream_socket_client($addr, $errno, $errstr, + $timeout, $flags, $context); + } else { + $fp = @$openfunc($this->addr, $this->port, $errno, + $errstr, $timeout, $context); + } + } else { + $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout); + } + + if (!$fp) { + if ($errno === 0 && !strlen($errstr)) { + $errstr = ''; + if (isset($old_track_errors)) { + $errstr = $php_errormsg ?: ''; + @ini_set('track_errors', $old_track_errors); + } else { + $lastError = error_get_last(); + if (isset($lastError['message'])) { + $errstr = $lastError['message']; + } + } + } + + return $this->raiseError($errstr, $errno); + } + + if (isset($old_track_errors)) { + @ini_set('track_errors', $old_track_errors); + } + + $this->fp = $fp; + $this->setTimeout(); + + return $this->setBlocking($this->blocking); + } + + /** + * Disconnects from the peer, closes the socket. + * + * @access public + * @return mixed true on success or a PEAR_Error instance otherwise + */ + public function disconnect() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + @fclose($this->fp); + $this->fp = null; + + return true; + } + + /** + * Set the newline character/sequence to use. + * + * @param string $newline Newline character(s) + * @return boolean True + */ + public function setNewline($newline) + { + $this->newline = $newline; + + return true; + } + + /** + * Find out if the socket is in blocking mode. + * + * @access public + * @return boolean The current blocking mode. + */ + public function isBlocking() + { + return $this->blocking; + } + + /** + * Sets whether the socket connection should be blocking or + * not. A read call to a non-blocking socket will return immediately + * if there is no data available, whereas it will block until there + * is data for blocking sockets. + * + * @param boolean $mode True for blocking sockets, false for nonblocking. + * + * @access public + * @return mixed true on success or a PEAR_Error instance otherwise + */ + public function setBlocking($mode) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $this->blocking = $mode; + stream_set_blocking($this->fp, (int)$this->blocking); + + return true; + } + + /** + * Sets the timeout value on socket descriptor, + * expressed in the sum of seconds and microseconds + * + * @param integer $seconds Seconds. + * @param integer $microseconds Microseconds, optional. + * + * @access public + * @return mixed True on success or false on failure or + * a PEAR_Error instance when not connected + */ + public function setTimeout($seconds = null, $microseconds = null) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + if ($seconds === null && $microseconds === null) { + $seconds = (int)$this->timeout; + $microseconds = (int)(($this->timeout - $seconds) * 1000000); + } else { + $this->timeout = $seconds + $microseconds / 1000000; + } + + if ($this->timeout > 0) { + return stream_set_timeout($this->fp, (int)$seconds, (int)$microseconds); + } else { + return false; + } + } + + /** + * Sets the file buffering size on the stream. + * See php's stream_set_write_buffer for more information. + * + * @param integer $size Write buffer size. + * + * @access public + * @return mixed on success or an PEAR_Error object otherwise + */ + public function setWriteBuffer($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $returned = stream_set_write_buffer($this->fp, $size); + if ($returned === 0) { + return true; + } + + return $this->raiseError('Cannot set write buffer.'); + } + + /** + * Returns information about an existing socket resource. + * Currently returns four entries in the result array: + * + *

      + * timed_out (bool) - The socket timed out waiting for data
      + * blocked (bool) - The socket was blocked
      + * eof (bool) - Indicates EOF event
      + * unread_bytes (int) - Number of bytes left in the socket buffer
      + *

      + * + * @access public + * @return mixed Array containing information about existing socket + * resource or a PEAR_Error instance otherwise + */ + public function getStatus() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return stream_get_meta_data($this->fp); + } + + /** + * Get a specified line of data + * + * @param int $size Reading ends when size - 1 bytes have been read, + * or a newline or an EOF (whichever comes first). + * If no size is specified, it will keep reading from + * the stream until it reaches the end of the line. + * + * @access public + * @return mixed $size bytes of data from the socket, or a PEAR_Error if + * not connected. If an error occurs, FALSE is returned. + */ + public function gets($size = null) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + if (null === $size) { + return @fgets($this->fp); + } else { + return @fgets($this->fp, $size); + } + } + + /** + * Read a specified amount of data. This is guaranteed to return, + * and has the added benefit of getting everything in one fread() + * chunk; if you know the size of the data you're getting + * beforehand, this is definitely the way to go. + * + * @param integer $size The number of bytes to read from the socket. + * + * @access public + * @return string $size bytes of data from the socket, or a PEAR_Error if + * not connected. + */ + public function read($size) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @fread($this->fp, $size); + } + + /** + * Write a specified amount of data. + * + * @param string $data Data to write. + * @param integer $blocksize Amount of data to write at once. + * NULL means all at once. + * + * @access public + * @return mixed If the socket is not connected, returns an instance of + * PEAR_Error. + * If the write succeeds, returns the number of bytes written. + * If the write fails, returns false. + * If the socket times out, returns an instance of PEAR_Error. + */ + public function write($data, $blocksize = null) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + if (null === $blocksize && !OS_WINDOWS) { + $written = @fwrite($this->fp, $data); + + // Check for timeout or lost connection + if ($written === false) { + $meta_data = $this->getStatus(); + + if (!is_array($meta_data)) { + return $meta_data; // PEAR_Error + } + + if (!empty($meta_data['timed_out'])) { + return $this->raiseError('timed out'); + } + } + + return $written; + } else { + if (null === $blocksize) { + $blocksize = 1024; + } + + $pos = 0; + $size = strlen($data); + while ($pos < $size) { + $written = @fwrite($this->fp, substr($data, $pos, $blocksize)); + + // Check for timeout or lost connection + if ($written === false) { + $meta_data = $this->getStatus(); + + if (!is_array($meta_data)) { + return $meta_data; // PEAR_Error + } + + if (!empty($meta_data['timed_out'])) { + return $this->raiseError('timed out'); + } + + return $written; + } + + $pos += $written; + } + + return $pos; + } + } + + /** + * Write a line of data to the socket, followed by a trailing newline. + * + * @param string $data Data to write + * + * @access public + * @return mixed fwrite() result, or PEAR_Error when not connected + */ + public function writeLine($data) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return fwrite($this->fp, $data . $this->newline); + } + + /** + * Tests for end-of-file on a socket descriptor. + * + * Also returns true if the socket is disconnected. + * + * @access public + * @return bool + */ + public function eof() + { + return (!is_resource($this->fp) || feof($this->fp)); + } + + /** + * Reads a byte of data + * + * @access public + * @return integer 1 byte of data from the socket, or a PEAR_Error if + * not connected. + */ + public function readByte() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return ord(@fread($this->fp, 1)); + } + + /** + * Reads a word of data + * + * @access public + * @return integer 1 word of data from the socket, or a PEAR_Error if + * not connected. + */ + public function readWord() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 2); + + return (ord($buf[0]) + (ord($buf[1]) << 8)); + } + + /** + * Reads an int of data + * + * @access public + * @return integer 1 int of data from the socket, or a PEAR_Error if + * not connected. + */ + public function readInt() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 4); + + return (ord($buf[0]) + (ord($buf[1]) << 8) + + (ord($buf[2]) << 16) + (ord($buf[3]) << 24)); + } + + /** + * Reads a zero-terminated string of data + * + * @access public + * @return string, or a PEAR_Error if + * not connected. + */ + public function readString() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $string = ''; + while (($char = @fread($this->fp, 1)) !== "\x00") { + $string .= $char; + } + + return $string; + } + + /** + * Reads an IP Address and returns it in a dot formatted string + * + * @access public + * @return string Dot formatted string, or a PEAR_Error if + * not connected. + */ + public function readIPAddress() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $buf = @fread($this->fp, 4); + + return sprintf('%d.%d.%d.%d', ord($buf[0]), ord($buf[1]), + ord($buf[2]), ord($buf[3])); + } + + /** + * Read until either the end of the socket or a newline, whichever + * comes first. Strips the trailing newline from the returned data. + * + * @access public + * @return string All available data up to a newline, without that + * newline, or until the end of the socket, or a PEAR_Error if + * not connected. + */ + public function readLine() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $line = ''; + + $timeout = time() + $this->timeout; + + while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { + $line .= @fgets($this->fp, $this->lineLength); + if (substr($line, -1) == "\n") { + return rtrim($line, $this->newline); + } + } + + return $line; + } + + /** + * Read until the socket closes, or until there is no more data in + * the inner PHP buffer. If the inner buffer is empty, in blocking + * mode we wait for at least 1 byte of data. Therefore, in + * blocking mode, if there is no data at all to be read, this + * function will never exit (unless the socket is closed on the + * remote end). + * + * @access public + * + * @return string All data until the socket closes, or a PEAR_Error if + * not connected. + */ + public function readAll() + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $data = ''; + $timeout = time() + $this->timeout; + + while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) { + $data .= @fread($this->fp, $this->lineLength); + } + + return $data; + } + + /** + * Runs the equivalent of the select() system call on the socket + * with a timeout specified by tv_sec and tv_usec. + * + * @param integer $state Which of read/write/error to check for. + * @param integer $tv_sec Number of seconds for timeout. + * @param integer $tv_usec Number of microseconds for timeout. + * + * @access public + * @return False if select fails, integer describing which of read/write/error + * are ready, or PEAR_Error if not connected. + */ + public function select($state, $tv_sec, $tv_usec = 0) + { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + $read = null; + $write = null; + $except = null; + if ($state & NET_SOCKET_READ) { + $read[] = $this->fp; + } + if ($state & NET_SOCKET_WRITE) { + $write[] = $this->fp; + } + if ($state & NET_SOCKET_ERROR) { + $except[] = $this->fp; + } + if (false === ($sr = stream_select($read, $write, $except, + $tv_sec, $tv_usec)) + ) { + return false; + } + + $result = 0; + if (count($read)) { + $result |= NET_SOCKET_READ; + } + if (count($write)) { + $result |= NET_SOCKET_WRITE; + } + if (count($except)) { + $result |= NET_SOCKET_ERROR; + } + + return $result; + } + + /** + * Turns encryption on/off on a connected socket. + * + * @param bool $enabled Set this parameter to true to enable encryption + * and false to disable encryption. + * @param integer $type Type of encryption. See stream_socket_enable_crypto() + * for values. + * + * @see http://se.php.net/manual/en/function.stream-socket-enable-crypto.php + * @access public + * @return false on error, true on success and 0 if there isn't enough data + * and the user should try again (non-blocking sockets only). + * A PEAR_Error object is returned if the socket is not + * connected + */ + public function enableCrypto($enabled, $type) + { + if (version_compare(phpversion(), '5.1.0', '>=')) { + if (!is_resource($this->fp)) { + return $this->raiseError('not connected'); + } + + return @stream_socket_enable_crypto($this->fp, $enabled, $type); + } else { + $msg = 'Net_Socket::enableCrypto() requires php version >= 5.1.0'; + + return $this->raiseError($msg); + } + } + +} diff --git a/democracy/sai/saimod_beta/cannon/Pear/PEAR.php b/democracy/sai/saimod_beta/cannon/Pear/PEAR.php new file mode 100644 index 0000000..ba754e5 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/Pear/PEAR.php @@ -0,0 +1,1113 @@ + + * @author Stig Bakken + * @author Tomas V.V.Cox + * @author Greg Beaver + * @copyright 1997-2010 The Authors + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @link http://pear.php.net/package/PEAR + * @since File available since Release 0.1 + */ + +/**#@+ + * ERROR constants + */ +define('PEAR_ERROR_RETURN', 1); +define('PEAR_ERROR_PRINT', 2); +define('PEAR_ERROR_TRIGGER', 4); +define('PEAR_ERROR_DIE', 8); +define('PEAR_ERROR_CALLBACK', 16); +/** + * WARNING: obsolete + * @deprecated + */ +define('PEAR_ERROR_EXCEPTION', 32); +/**#@-*/ + +if (substr(PHP_OS, 0, 3) == 'WIN') { + define('OS_WINDOWS', true); + define('OS_UNIX', false); + define('PEAR_OS', 'Windows'); +} else { + define('OS_WINDOWS', false); + define('OS_UNIX', true); + define('PEAR_OS', 'Unix'); // blatant assumption +} + +$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN; +$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE; +$GLOBALS['_PEAR_destructor_object_list'] = array(); +$GLOBALS['_PEAR_shutdown_funcs'] = array(); +$GLOBALS['_PEAR_error_handler_stack'] = array(); + +@ini_set('track_errors', true); + +/** + * Base class for other PEAR classes. Provides rudimentary + * emulation of destructors. + * + * If you want a destructor in your class, inherit PEAR and make a + * destructor method called _yourclassname (same name as the + * constructor, but with a "_" prefix). Also, in your constructor you + * have to call the PEAR constructor: $this->PEAR();. + * The destructor method will be called without parameters. Note that + * at in some SAPI implementations (such as Apache), any output during + * the request shutdown (in which destructors are called) seems to be + * discarded. If you need to get any debug information from your + * destructor, use error_log(), syslog() or something similar. + * + * IMPORTANT! To use the emulated destructors you need to create the + * objects by reference: $obj =& new PEAR_child; + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Greg Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/package/PEAR + * @see PEAR_Error + * @since Class available since PHP 4.0.2 + * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear + */ +class PEAR +{ + /** + * Whether to enable internal debug messages. + * + * @var bool + * @access private + */ + var $_debug = false; + + /** + * Default error mode for this object. + * + * @var int + * @access private + */ + var $_default_error_mode = null; + + /** + * Default error options used for this object when error mode + * is PEAR_ERROR_TRIGGER. + * + * @var int + * @access private + */ + var $_default_error_options = null; + + /** + * Default error handler (callback) for this object, if error mode is + * PEAR_ERROR_CALLBACK. + * + * @var string + * @access private + */ + var $_default_error_handler = ''; + + /** + * Which class to use for error objects. + * + * @var string + * @access private + */ + var $_error_class = 'PEAR_Error'; + + /** + * An array of expected errors. + * + * @var array + * @access private + */ + var $_expected_errors = array(); + + /** + * List of methods that can be called both statically and non-statically. + * @var array + */ + protected static $bivalentMethods = array( + 'setErrorHandling' => true, + 'raiseError' => true, + 'throwError' => true, + 'pushErrorHandling' => true, + 'popErrorHandling' => true, + ); + + /** + * Constructor. Registers this object in + * $_PEAR_destructor_object_list for destructor emulation if a + * destructor object exists. + * + * @param string $error_class (optional) which class to use for + * error objects, defaults to PEAR_Error. + * @access public + * @return void + */ + function __construct($error_class = null) + { + $classname = strtolower(get_class($this)); + if ($this->_debug) { + print "PEAR constructor called, class=$classname\n"; + } + + if ($error_class !== null) { + $this->_error_class = $error_class; + } + + while ($classname && strcasecmp($classname, "pear")) { + $destructor = "_$classname"; + if (method_exists($this, $destructor)) { + global $_PEAR_destructor_object_list; + $_PEAR_destructor_object_list[] = $this; + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + break; + } else { + $classname = get_parent_class($classname); + } + } + } + + /** + * Only here for backwards compatibility. + * E.g. Archive_Tar calls $this->PEAR() in its constructor. + * + * @param string $error_class Which class to use for error objects, + * defaults to PEAR_Error. + */ + public function PEAR($error_class = null) + { + self::__construct($error_class); + } + + /** + * Destructor (the emulated type of...). Does nothing right now, + * but is included for forward compatibility, so subclass + * destructors should always call it. + * + * See the note in the class desciption about output from + * destructors. + * + * @access public + * @return void + */ + function _PEAR() { + if ($this->_debug) { + printf("PEAR destructor called, class=%s\n", strtolower(get_class($this))); + } + } + + public function __call($method, $arguments) + { + if (!isset(self::$bivalentMethods[$method])) { + trigger_error( + 'Call to undefined method PEAR::' . $method . '()', E_USER_ERROR + ); + } + return call_user_func_array( + array(get_class(), '_' . $method), + array_merge(array($this), $arguments) + ); + } + + public static function __callStatic($method, $arguments) + { + if (!isset(self::$bivalentMethods[$method])) { + trigger_error( + 'Call to undefined method PEAR::' . $method . '()', E_USER_ERROR + ); + } + return call_user_func_array( + array(get_class(), '_' . $method), + array_merge(array(null), $arguments) + ); + } + + /** + * If you have a class that's mostly/entirely static, and you need static + * properties, you can use this method to simulate them. Eg. in your method(s) + * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar'); + * You MUST use a reference, or they will not persist! + * + * @param string $class The calling classname, to prevent clashes + * @param string $var The variable to retrieve. + * @return mixed A reference to the variable. If not set it will be + * auto initialised to NULL. + */ + public static function &getStaticProperty($class, $var) + { + static $properties; + if (!isset($properties[$class])) { + $properties[$class] = array(); + } + + if (!array_key_exists($var, $properties[$class])) { + $properties[$class][$var] = null; + } + + return $properties[$class][$var]; + } + + /** + * Use this function to register a shutdown method for static + * classes. + * + * @param mixed $func The function name (or array of class/method) to call + * @param mixed $args The arguments to pass to the function + * + * @return void + */ + public static function registerShutdownFunc($func, $args = array()) + { + // if we are called statically, there is a potential + // that no shutdown func is registered. Bug #6445 + if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) { + register_shutdown_function("_PEAR_call_destructors"); + $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true; + } + $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args); + } + + /** + * Tell whether a value is a PEAR error. + * + * @param mixed $data the value to test + * @param int $code if $data is an error object, return true + * only if $code is a string and + * $obj->getMessage() == $code or + * $code is an integer and $obj->getCode() == $code + * + * @return bool true if parameter is an error + */ + public static function isError($data, $code = null) + { + if (!is_a($data, 'PEAR_Error')) { + return false; + } + + if (is_null($code)) { + return true; + } elseif (is_string($code)) { + return $data->getMessage() == $code; + } + + return $data->getCode() == $code; + } + + /** + * Sets how errors generated by this object should be handled. + * Can be invoked both in objects and statically. If called + * statically, setErrorHandling sets the default behaviour for all + * PEAR objects. If called in an object, setErrorHandling sets + * the default behaviour for that object. + * + * @param object $object + * Object the method was called on (non-static mode) + * + * @param int $mode + * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION. + * + * @param mixed $options + * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one + * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * + * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected + * to be the callback function or method. A callback + * function is a string with the name of the function, a + * callback method is an array of two elements: the element + * at index 0 is the object, and the element at index 1 is + * the name of the method to call in the object. + * + * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is + * a printf format string used when printing the error + * message. + * + * @access public + * @return void + * @see PEAR_ERROR_RETURN + * @see PEAR_ERROR_PRINT + * @see PEAR_ERROR_TRIGGER + * @see PEAR_ERROR_DIE + * @see PEAR_ERROR_CALLBACK + * @see PEAR_ERROR_EXCEPTION + * + * @since PHP 4.0.5 + */ + protected static function _setErrorHandling( + $object, $mode = null, $options = null + ) { + if ($object !== null) { + $setmode = &$object->_default_error_mode; + $setoptions = &$object->_default_error_options; + } else { + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + } + + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + } + + /** + * This method is used to tell which errors you expect to get. + * Expected errors are always returned with error mode + * PEAR_ERROR_RETURN. Expected error codes are stored in a stack, + * and this method pushes a new element onto it. The list of + * expected errors are in effect until they are popped off the + * stack with the popExpect() method. + * + * Note that this method can not be called statically + * + * @param mixed $code a single error code or an array of error codes to expect + * + * @return int the new depth of the "expected errors" stack + * @access public + */ + function expectError($code = '*') + { + if (is_array($code)) { + array_push($this->_expected_errors, $code); + } else { + array_push($this->_expected_errors, array($code)); + } + return count($this->_expected_errors); + } + + /** + * This method pops one element off the expected error codes + * stack. + * + * @return array the list of error codes that were popped + */ + function popExpect() + { + return array_pop($this->_expected_errors); + } + + /** + * This method checks unsets an error code if available + * + * @param mixed error code + * @return bool true if the error code was unset, false otherwise + * @access private + * @since PHP 4.3.0 + */ + function _checkDelExpect($error_code) + { + $deleted = false; + foreach ($this->_expected_errors as $key => $error_array) { + if (in_array($error_code, $error_array)) { + unset($this->_expected_errors[$key][array_search($error_code, $error_array)]); + $deleted = true; + } + + // clean up empty arrays + if (0 == count($this->_expected_errors[$key])) { + unset($this->_expected_errors[$key]); + } + } + + return $deleted; + } + + /** + * This method deletes all occurrences of the specified element from + * the expected error codes stack. + * + * @param mixed $error_code error code that should be deleted + * @return mixed list of error codes that were deleted or error + * @access public + * @since PHP 4.3.0 + */ + function delExpect($error_code) + { + $deleted = false; + if ((is_array($error_code) && (0 != count($error_code)))) { + // $error_code is a non-empty array here; we walk through it trying + // to unset all values + foreach ($error_code as $key => $error) { + $deleted = $this->_checkDelExpect($error) ? true : false; + } + + return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } elseif (!empty($error_code)) { + // $error_code comes alone, trying to unset it + if ($this->_checkDelExpect($error_code)) { + return true; + } + + return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME + } + + // $error_code is empty + return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME + } + + /** + * This method is a wrapper that returns an instance of the + * configured error class with this object's default error + * handling applied. If the $mode and $options parameters are not + * specified, the object's defaults are used. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT, + * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE, + * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION. + * + * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter + * specifies the PHP-internal error level (one of + * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR). + * If $mode is PEAR_ERROR_CALLBACK, this + * parameter specifies the callback function or + * method. In other error modes this parameter + * is ignored. + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @param string $error_class The returned error object will be + * instantiated from this class, if specified. + * + * @param bool $skipmsg If true, raiseError will only pass error codes, + * the error message parameter will be dropped. + * + * @return object a PEAR error object + * @see PEAR::setErrorHandling + * @since PHP 4.0.5 + */ + protected static function _raiseError($object, + $message = null, + $code = null, + $mode = null, + $options = null, + $userinfo = null, + $error_class = null, + $skipmsg = false) + { + // The error is yet a PEAR error object + if (is_object($message)) { + $code = $message->getCode(); + $userinfo = $message->getUserInfo(); + $error_class = $message->getType(); + $message->error_message_prefix = ''; + $message = $message->getMessage(); + } + + if ( + $object !== null && + isset($object->_expected_errors) && + count($object->_expected_errors) > 0 && + count($exp = end($object->_expected_errors)) + ) { + if ($exp[0] == "*" || + (is_int(reset($exp)) && in_array($code, $exp)) || + (is_string(reset($exp)) && in_array($message, $exp)) + ) { + $mode = PEAR_ERROR_RETURN; + } + } + + // No mode given, try global ones + if ($mode === null) { + // Class error handler + if ($object !== null && isset($object->_default_error_mode)) { + $mode = $object->_default_error_mode; + $options = $object->_default_error_options; + // Global error handler + } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) { + $mode = $GLOBALS['_PEAR_default_error_mode']; + $options = $GLOBALS['_PEAR_default_error_options']; + } + } + + if ($error_class !== null) { + $ec = $error_class; + } elseif ($object !== null && isset($object->_error_class)) { + $ec = $object->_error_class; + } else { + $ec = 'PEAR_Error'; + } + + if ($skipmsg) { + $a = new $ec($code, $mode, $options, $userinfo); + } else { + $a = new $ec($message, $code, $mode, $options, $userinfo); + } + + return $a; + } + + /** + * Simpler form of raiseError with fewer options. In most cases + * message, code and userinfo are enough. + * + * @param mixed $message a text error message or a PEAR error object + * + * @param int $code a numeric error code (it is up to your class + * to define these if you want to use codes) + * + * @param string $userinfo If you need to pass along for example debug + * information, this parameter is meant for that. + * + * @return object a PEAR error object + * @see PEAR::raiseError + */ + protected static function _throwError($object, $message = null, $code = null, $userinfo = null) + { + if ($object !== null) { + $a = $object->raiseError($message, $code, null, null, $userinfo); + return $a; + } + + $a = PEAR::raiseError($message, $code, null, null, $userinfo); + return $a; + } + + public static function staticPushErrorHandling($mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + $stack[] = array($def_mode, $def_options); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $def_mode = $mode; + $def_options = $options; + break; + + case PEAR_ERROR_CALLBACK: + $def_mode = $mode; + // class/object method callback + if (is_callable($options)) { + $def_options = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + $stack[] = array($mode, $options); + return true; + } + + public static function staticPopErrorHandling() + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + $setmode = &$GLOBALS['_PEAR_default_error_mode']; + $setoptions = &$GLOBALS['_PEAR_default_error_options']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + switch ($mode) { + case PEAR_ERROR_EXCEPTION: + case PEAR_ERROR_RETURN: + case PEAR_ERROR_PRINT: + case PEAR_ERROR_TRIGGER: + case PEAR_ERROR_DIE: + case null: + $setmode = $mode; + $setoptions = $options; + break; + + case PEAR_ERROR_CALLBACK: + $setmode = $mode; + // class/object method callback + if (is_callable($options)) { + $setoptions = $options; + } else { + trigger_error("invalid error callback", E_USER_WARNING); + } + break; + + default: + trigger_error("invalid error mode", E_USER_WARNING); + break; + } + return true; + } + + /** + * Push a new error handler on top of the error handler options stack. With this + * you can easily override the actual error handler for some code and restore + * it later with popErrorHandling. + * + * @param mixed $mode (same as setErrorHandling) + * @param mixed $options (same as setErrorHandling) + * + * @return bool Always true + * + * @see PEAR::setErrorHandling + */ + protected static function _pushErrorHandling($object, $mode, $options = null) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + if ($object !== null) { + $def_mode = &$object->_default_error_mode; + $def_options = &$object->_default_error_options; + } else { + $def_mode = &$GLOBALS['_PEAR_default_error_mode']; + $def_options = &$GLOBALS['_PEAR_default_error_options']; + } + $stack[] = array($def_mode, $def_options); + + if ($object !== null) { + $object->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + $stack[] = array($mode, $options); + return true; + } + + /** + * Pop the last error handler used + * + * @return bool Always true + * + * @see PEAR::pushErrorHandling + */ + protected static function _popErrorHandling($object) + { + $stack = &$GLOBALS['_PEAR_error_handler_stack']; + array_pop($stack); + list($mode, $options) = $stack[sizeof($stack) - 1]; + array_pop($stack); + if ($object !== null) { + $object->setErrorHandling($mode, $options); + } else { + PEAR::setErrorHandling($mode, $options); + } + return true; + } + + /** + * OS independent PHP extension load. Remember to take care + * on the correct extension name for case sensitive OSes. + * + * @param string $ext The extension name + * @return bool Success or not on the dl() call + */ + public static function loadExtension($ext) + { + if (extension_loaded($ext)) { + return true; + } + + // if either returns true dl() will produce a FATAL error, stop that + if ( + function_exists('dl') === false || + ini_get('enable_dl') != 1 + ) { + return false; + } + + if (OS_WINDOWS) { + $suffix = '.dll'; + } elseif (PHP_OS == 'HP-UX') { + $suffix = '.sl'; + } elseif (PHP_OS == 'AIX') { + $suffix = '.a'; + } elseif (PHP_OS == 'OSX') { + $suffix = '.bundle'; + } else { + $suffix = '.so'; + } + + return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix); + } +} + +function _PEAR_call_destructors() +{ + global $_PEAR_destructor_object_list; + if (is_array($_PEAR_destructor_object_list) && + sizeof($_PEAR_destructor_object_list)) + { + reset($_PEAR_destructor_object_list); + + $destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo'); + + if ($destructLifoExists) { + $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list); + } + + while (list($k, $objref) = each($_PEAR_destructor_object_list)) { + $classname = get_class($objref); + while ($classname) { + $destructor = "_$classname"; + if (method_exists($objref, $destructor)) { + $objref->$destructor(); + break; + } else { + $classname = get_parent_class($classname); + } + } + } + // Empty the object list to ensure that destructors are + // not called more than once. + $_PEAR_destructor_object_list = array(); + } + + // Now call the shutdown functions + if ( + isset($GLOBALS['_PEAR_shutdown_funcs']) && + is_array($GLOBALS['_PEAR_shutdown_funcs']) && + !empty($GLOBALS['_PEAR_shutdown_funcs']) + ) { + foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) { + call_user_func_array($value[0], $value[1]); + } + } +} + +/** + * Standard PEAR error class for PHP 4 + * + * This class is supserseded by {@link PEAR_Exception} in PHP 5 + * + * @category pear + * @package PEAR + * @author Stig Bakken + * @author Tomas V.V. Cox + * @author Gregory Beaver + * @copyright 1997-2006 The PHP Group + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: @package_version@ + * @link http://pear.php.net/manual/en/core.pear.pear-error.php + * @see PEAR::raiseError(), PEAR::throwError() + * @since Class available since PHP 4.0.2 + */ +class PEAR_Error +{ + var $error_message_prefix = ''; + var $mode = PEAR_ERROR_RETURN; + var $level = E_USER_NOTICE; + var $code = -1; + var $message = ''; + var $userinfo = ''; + var $backtrace = null; + + /** + * PEAR_Error constructor + * + * @param string $message message + * + * @param int $code (optional) error code + * + * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN, + * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER, + * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION + * + * @param mixed $options (optional) error level, _OR_ in the case of + * PEAR_ERROR_CALLBACK, the callback function or object/method + * tuple. + * + * @param string $userinfo (optional) additional user/debug info + * + * @access public + * + */ + function __construct($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + if ($mode === null) { + $mode = PEAR_ERROR_RETURN; + } + $this->message = $message; + $this->code = $code; + $this->mode = $mode; + $this->userinfo = $userinfo; + + $skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace'); + + if (!$skiptrace) { + $this->backtrace = debug_backtrace(); + if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) { + unset($this->backtrace[0]['object']); + } + } + + if ($mode & PEAR_ERROR_CALLBACK) { + $this->level = E_USER_NOTICE; + $this->callback = $options; + } else { + if ($options === null) { + $options = E_USER_NOTICE; + } + + $this->level = $options; + $this->callback = null; + } + + if ($this->mode & PEAR_ERROR_PRINT) { + if (is_null($options) || is_int($options)) { + $format = "%s"; + } else { + $format = $options; + } + + printf($format, $this->getMessage()); + } + + if ($this->mode & PEAR_ERROR_TRIGGER) { + trigger_error($this->getMessage(), $this->level); + } + + if ($this->mode & PEAR_ERROR_DIE) { + $msg = $this->getMessage(); + if (is_null($options) || is_int($options)) { + $format = "%s"; + if (substr($msg, -1) != "\n") { + $msg .= "\n"; + } + } else { + $format = $options; + } + printf($format, $msg); + exit($code); + } + + if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) { + call_user_func($this->callback, $this); + } + + if ($this->mode & PEAR_ERROR_EXCEPTION) { + trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING); + eval('$e = new Exception($this->message, $this->code);throw($e);'); + } + } + + /** + * Only here for backwards compatibility. + * + * Class "Cache_Error" still uses it, among others. + * + * @param string $message Message + * @param int $code Error code + * @param int $mode Error mode + * @param mixed $options See __construct() + * @param string $userinfo Additional user/debug info + */ + public function PEAR_Error( + $message = 'unknown error', $code = null, $mode = null, + $options = null, $userinfo = null + ) { + self::__construct($message, $code, $mode, $options, $userinfo); + } + + /** + * Get the error mode from an error object. + * + * @return int error mode + * @access public + */ + function getMode() + { + return $this->mode; + } + + /** + * Get the callback function/method from an error object. + * + * @return mixed callback function or object/method array + * @access public + */ + function getCallback() + { + return $this->callback; + } + + /** + * Get the error message from an error object. + * + * @return string full error message + * @access public + */ + function getMessage() + { + return ($this->error_message_prefix . $this->message); + } + + /** + * Get error code from an error object + * + * @return int error code + * @access public + */ + function getCode() + { + return $this->code; + } + + /** + * Get the name of this error/exception. + * + * @return string error/exception name (type) + * @access public + */ + function getType() + { + return get_class($this); + } + + /** + * Get additional user-supplied information. + * + * @return string user-supplied information + * @access public + */ + function getUserInfo() + { + return $this->userinfo; + } + + /** + * Get additional debug information supplied by the application. + * + * @return string debug information + * @access public + */ + function getDebugInfo() + { + return $this->getUserInfo(); + } + + /** + * Get the call backtrace from where the error was generated. + * Supported with PHP 4.3.0 or newer. + * + * @param int $frame (optional) what frame to fetch + * @return array Backtrace, or NULL if not available. + * @access public + */ + function getBacktrace($frame = null) + { + if (defined('PEAR_IGNORE_BACKTRACE')) { + return null; + } + if ($frame === null) { + return $this->backtrace; + } + return $this->backtrace[$frame]; + } + + function addUserInfo($info) + { + if (empty($this->userinfo)) { + $this->userinfo = $info; + } else { + $this->userinfo .= " ** $info"; + } + } + + function __toString() + { + return $this->getMessage(); + } + + /** + * Make a string representation of this object. + * + * @return string a string with an object summary + * @access public + */ + function toString() + { + $modes = array(); + $levels = array(E_USER_NOTICE => 'notice', + E_USER_WARNING => 'warning', + E_USER_ERROR => 'error'); + if ($this->mode & PEAR_ERROR_CALLBACK) { + if (is_array($this->callback)) { + $callback = (is_object($this->callback[0]) ? + strtolower(get_class($this->callback[0])) : + $this->callback[0]) . '::' . + $this->callback[1]; + } else { + $callback = $this->callback; + } + return sprintf('[%s: message="%s" code=%d mode=callback '. + 'callback=%s prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + $callback, $this->error_message_prefix, + $this->userinfo); + } + if ($this->mode & PEAR_ERROR_PRINT) { + $modes[] = 'print'; + } + if ($this->mode & PEAR_ERROR_TRIGGER) { + $modes[] = 'trigger'; + } + if ($this->mode & PEAR_ERROR_DIE) { + $modes[] = 'die'; + } + if ($this->mode & PEAR_ERROR_RETURN) { + $modes[] = 'return'; + } + return sprintf('[%s: message="%s" code=%d mode=%s level=%s '. + 'prefix="%s" info="%s"]', + strtolower(get_class($this)), $this->message, $this->code, + implode("|", $modes), $levels[$this->level], + $this->error_message_prefix, + $this->userinfo); + } +} + +/* + * Local Variables: + * mode: php + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ diff --git a/democracy/sai/saimod_beta/cannon/mailcannon.cli.php b/democracy/sai/saimod_beta/cannon/mailcannon.cli.php new file mode 100644 index 0000000..d320909 --- /dev/null +++ b/democracy/sai/saimod_beta/cannon/mailcannon.cli.php @@ -0,0 +1,13 @@ +0) { + $silent ? null : print "Dont send count: " . count($dont_send) . "\n"; + } else { + $silent ? null : print "No dont sends\n";} + + // remove unsubscribes from to_proto + $to_addresses = array(); + foreach($to_proto as $addr){ + if( strlen($addr[0]) > 0 && + substr($addr[0], 0,1) !== '#' && + !in_array($addr[0], $dont_send)){ + $to_addresses[] = $addr;} + } + $silent ? null : print "Reduced to list count " . count($to_addresses)."\n\n"; + + if($smtp){ + $mail = Mail::factory('smtp', $smtp); + } else { + $mail = Mail::factory('mail');} + + $silent ? null : print "Running...\n"; + $count = 0; + foreach ($to_addresses as $addr) { + $to = $addr; + // load text & html file + if(!file_exists($text_file)){ + $silent ? null : print "Skipping ".$to." - No Text found:".$text_file." \n"; + $text = file_get_contents($text_file); + } else { + $text = file_get_contents($text_file);} + $html = file_exists($html_file) ? file_get_contents($html_file) : NULL; + + //Calculate replacements + $replace = array(); + foreach($replacements as $key=>$value){ + // value + if( array_key_exists('value', $value)){ + // value attribute + if( array_key_exists('attribute', $value['value'])){ + if(count($addr) <= $value['value']['attribute']){ + if(!array_key_exists('*',$value)){ + die('No Default Value found for "'.$key.'" - required on "'.$to.'" - dieing!');} + $replace[$key] = $value['*']; + } else { + $replace[$key] = $addr[$value['value']['attribute']]; + } + // value text + } elseif(array_key_exists('text', $value['value'])){ + $replace[$key] = $value['value']['text']; + // value template + } elseif(array_key_exists('template', $value['value'])){ + $tpl = $value['value']['template']; + if(!file_exists($tpl)){ + die('TPL File "'.$tpl.'" not found for "'.$key.'" - required on "'.$to.'" - dieing!');} + $replace[$key] = file_get_contents($tpl); + } + continue; + } + // selector attribute + if( array_key_exists('selector', $value) && + array_key_exists('attribute', $value['selector']) && + array_key_exists('values', $value)){ + if(count($addr) <= $value['selector']['attribute']){ + if(!array_key_exists('*',$value)){ + die('No Default Value found for "'.$key.'" - required on "'.$to.'" - dieing!');} + $replace[$key] = $value['*']; + } else { + $replace[$key] = $value['values'][$addr[$value['selector']['attribute']]]; + } + continue; + } + } + + //Replace + $preg_search = array(); + $preg_replace = array(); + foreach($replace as $key=>$value){ + $preg_search[] = '/\${'.$key.'}/'; + $preg_replace[] = $value; + } + $text = @preg_replace($preg_search, $preg_replace, $text); + $html = @preg_replace($preg_search, $preg_replace, $html); + + //Send mail + $mime = new Mail_mime("\n"); + if($text){ + $mime->setTXTBody($text); + } + // attach images + foreach($images as $name=>$image){ + $mime->addHTMLImage($image, 'image/png', $name, true, $name);} + // attach other + foreach($attachments as $name=>$attachment){ + $mime->addAttachment($attachment,'application/pdf',$name);} + + if($html){ + $mime->setHTMLBody($html);} + + $mime_params = array( 'text_encoding' => '7bit', + 'text_charset' => 'UTF-8', + 'html_charset' => 'UTF-8', + 'head_charset' => 'UTF-8'); + + $body = $mime->get($mime_params); + $hdrs = $mime->headers(array( 'From' => $from, + 'Subject' => $subject, + 'Reply-to' => $from, + 'Return-Path' => $from, + 'Message-ID' => self::generateMessageID(), + 'Date' => date('r', time()))); + + $recipients = array( 'To' => $to); + $succ = $mail->send($recipients, $hdrs, $body); + if (PEAR::isError($succ)) { + print 'Error Sending HTML message to ' . $to . ' ' . $succ->getMessage() . "\n"; + } else { + if($unsubscribe_list) { + file_put_contents($unsubscribe_list, $to.PHP_EOL , FILE_APPEND | LOCK_EX); + } + if($html){ + $silent ? null : print 'Sending HTML message to ' . $to . "\n"; + } else { + $silent ? null : print 'Sending TXT message to ' . $to . "\n"; + } + } + $count += 1; + sleep($delay); + } + $silent ? null : print "Sent " . $count . " emails\n"; + $silent ? null : print "Done\n"; + } + + /** + * @see http://www.jwz.org/doc/mid.html + */ + public static function generateMessageID() + { + return sprintf( + "<%s.%s@%s>", + base_convert(microtime(), 10, 36), + base_convert(bin2hex(openssl_random_pseudo_bytes(8)), 16, 36), + "democracy-deutschland.de" + ); + } +} \ No newline at end of file diff --git a/democracy/sai/saimod_beta/js/saimod_beta.js b/democracy/sai/saimod_beta/js/saimod_beta.js index aa61ce1..bca256f 100644 --- a/democracy/sai/saimod_beta/js/saimod_beta.js +++ b/democracy/sai/saimod_beta/js/saimod_beta.js @@ -3,6 +3,7 @@ function init_saimod_beta_all() { $('#tabs_beta li a').each(function(){ $(this).removeClass('active');}); $('#menu_tag_beta').addClass('active'); + email_delete(); }; function init_saimod_beta_code() { @@ -32,7 +33,6 @@ function init_saimod_beta_code() { }); $('.code_setcomment').click(function(){ var code = $(this).attr('code'); - var email = $(this).attr('email'); var i = $(this).attr('i'); var comment = $('#comment_'+i).val(); $.ajax({ @@ -54,6 +54,26 @@ function init_saimod_beta_code() { } }); }); + $('.code_delete').click(function(){ + var code = $(this).attr('code'); + $.ajax({ + async: true, + url: this.endpoint, + type: 'GET', + dataType: 'JSON', + data: { + sai_mod: '.SAI.saimod_beta', + action: 'code_delete', + code: code + }, + success: function(){ + system.reload(); + }, + error: function(){ + alert('Something happend - try again!'); + } + }); + }); } function init_saimod_beta_store_android() { $("#table_beta_store_android").tablesorter(); @@ -90,6 +110,7 @@ function init_saimod_beta_store_android() { document.execCommand("copy"); $temp.remove(); }); + email_delete(); }; function init_saimod_beta_store_ios() { $("#table_beta_store_ios").tablesorter(); @@ -126,6 +147,7 @@ function init_saimod_beta_store_ios() { document.execCommand("copy"); $temp.remove(); }); + email_delete(); }; function init_saimod_beta_mail() { $("#table_beta_mail").tablesorter(); @@ -156,4 +178,30 @@ function init_saimod_beta_mail() { } }); }); -}; \ No newline at end of file + email_delete(); +}; + +function email_delete(){ + $('.email_delete').click(function(){ + if (confirm('Are you sure you want to delete this user PERMANENTLY?')) { + var email = $(this).attr('email'); + $.ajax({ + async: true, + url: this.endpoint, + type: 'GET', + dataType: 'JSON', + data: { + sai_mod: '.SAI.saimod_beta', + action: 'email_delete', + email: email + }, + success: function(){ + system.reload(); + }, + error: function(){ + alert('Something happend - try again!'); + } + }); + } + }); +} \ No newline at end of file diff --git a/democracy/sai/saimod_beta/saimod_beta.php b/democracy/sai/saimod_beta/saimod_beta.php index 0fe1947..d4245f3 100644 --- a/democracy/sai/saimod_beta/saimod_beta.php +++ b/democracy/sai/saimod_beta/saimod_beta.php @@ -61,6 +61,7 @@ class saimod_beta extends \SYSTEM\SAI\sai_module{ while($row = $beta->next()){ $row['i'] = $i++; $row['generated'] = \SYSTEM\time::time_ago_string(strtotime($row['createdAt'])); + $row['code_delete_disabled'] = $row['count'] == 0 ? '' : 'disabled'; $vars['data'] .= \SYSTEM\PAGE\replace::replaceFile((new \PSAI('saimod_beta/tpl/code_tr.tpl'))->SERVERPATH(),$row); } $vars = array_merge($vars, \SYSTEM\PAGE\text::tag('time')); @@ -78,6 +79,16 @@ class saimod_beta extends \SYSTEM\SAI\sai_module{ return \JsonResult::ok(); } + public static function sai_mod__SAI_saimod_beta_action_code_delete($code){ + \SQL\BETA_CODE_DELETE::QI(array($code)); + return \JsonResult::ok(); + } + + public static function sai_mod__SAI_saimod_beta_action_email_delete($email){ + \SQL\BETA_EMAIL_DELETE::QI(array($email)); + return \JsonResult::ok(); + } + public static function sai_mod__SAI_saimod_beta_action_store_android(){ $vars = array(); @@ -213,7 +224,7 @@ class saimod_beta extends \SYSTEM\SAI\sai_module{ } public static function sai_mod__SAI_saimod_beta_action_email($email,$android,$ios){ - require((new \SYSTEM\PROOT('PHPMailer-master/PHPMailerAutoload.php'))->SERVERPATH()); + /*require((new \SYSTEM\PROOT('PHPMailer-master/PHPMailerAutoload.php'))->SERVERPATH()); date_default_timezone_set('Europe/Berlin'); $mail = new \PHPMailer; @@ -246,7 +257,27 @@ class saimod_beta extends \SYSTEM\SAI\sai_module{ //send the message, check for errors if(!$mail->send()){ - throw new \SYSTEM\LOG\ERROR("Mailer Error: " . $mail->ErrorInfo);} + throw new \SYSTEM\LOG\ERROR("Mailer Error: " . $mail->ErrorInfo);}*/ + + $bcc = null; + $delay = 0; + $from = 'Prototyp | DEMOCRACY '; + $subject = '📱 DEMOCRACY: Dein Prototyp Zugang'; + $html_file = $android ? (new \PSAI('saimod_beta/tpl/mail_android.tpl'))->SERVERPATH() : (new \PSAI('saimod_beta/tpl/mail_ios.tpl'))->SERVERPATH(); + $text_file = $android ? (new \PSAI('saimod_beta/tpl/mail_android.txt'))->SERVERPATH() : (new \PSAI('saimod_beta/tpl/mail_ios.txt'))->SERVERPATH(); + $to = $email; + $unsubscribe_list = null; + $images = ["democracy_logo" => (new \PSAI('saimod_beta/img/logo.png'))->SERVERPATH()]; + $attachments = []; + $replacements = []; + $smtp = [ "host" => "ssl://atmanspacher.eu", + "port" => 465, + "auth" => true, + "username" => "prototyping@democracy-deutschland.de", + "password" => "7$7ar0pZ" + ]; + $silent = true; + \mailcannon::fire($bcc, $delay, $from, $subject, $html_file, $text_file, $to, $unsubscribe_list, $images, $attachments, $replacements,$smtp, $silent); \SQL\BETA_MAIL::QI(array($email)); diff --git a/democracy/sai/saimod_beta/sql/mysql/system_api.sql b/democracy/sai/saimod_beta/sql/mysql/system_api.sql index a0e1223..340b0a9 100644 --- a/democracy/sai/saimod_beta/sql/mysql/system_api.sql +++ b/democracy/sai/saimod_beta/sql/mysql/system_api.sql @@ -9,4 +9,7 @@ REPLACE INTO `system_api` (`ID`, `group`, `type`, `parentID`, `parentValue`, `na REPLACE INTO `system_api` (`ID`, `group`, `type`, `parentID`, `parentValue`, `name`, `verify`) VALUES (7040, 42, 2, 7000, 'email', 'email', 'STRING'); REPLACE INTO `system_api` (`ID`, `group`, `type`, `parentID`, `parentValue`, `name`, `verify`) VALUES (7041, 42, 2, 7000, 'email', 'android', 'BOOL'); -REPLACE INTO `system_api` (`ID`, `group`, `type`, `parentID`, `parentValue`, `name`, `verify`) VALUES (7042, 42, 2, 7000, 'email', 'ios', 'BOOL'); \ No newline at end of file +REPLACE INTO `system_api` (`ID`, `group`, `type`, `parentID`, `parentValue`, `name`, `verify`) VALUES (7042, 42, 2, 7000, 'email', 'ios', 'BOOL'); + +REPLACE INTO `system_api` (`ID`, `group`, `type`, `parentID`, `parentValue`, `name`, `verify`) VALUES (7050, 42, 2, 7000, 'code_delete', 'code', 'STRING'); +REPLACE INTO `system_api` (`ID`, `group`, `type`, `parentID`, `parentValue`, `name`, `verify`) VALUES (7060, 42, 2, 7000, 'email_delete', 'email', 'STRING'); \ No newline at end of file diff --git a/democracy/sai/saimod_beta/tpl/all.tpl b/democracy/sai/saimod_beta/tpl/all.tpl index cb994c5..f5c090d 100644 --- a/democracy/sai/saimod_beta/tpl/all.tpl +++ b/democracy/sai/saimod_beta/tpl/all.tpl @@ -10,6 +10,7 @@ Store Email Comment + Action ${data} diff --git a/democracy/sai/saimod_beta/tpl/all_tr.tpl b/democracy/sai/saimod_beta/tpl/all_tr.tpl index 5f20c2d..94837df 100644 --- a/democracy/sai/saimod_beta/tpl/all_tr.tpl +++ b/democracy/sai/saimod_beta/tpl/all_tr.tpl @@ -7,4 +7,5 @@  ${stored_time}  ${emailed_time} ${comment} + \ No newline at end of file diff --git a/democracy/sai/saimod_beta/tpl/code_tr.tpl b/democracy/sai/saimod_beta/tpl/code_tr.tpl index 5aebd48..8d0de6c 100644 --- a/democracy/sai/saimod_beta/tpl/code_tr.tpl +++ b/democracy/sai/saimod_beta/tpl/code_tr.tpl @@ -6,5 +6,8 @@ ${generated} ${count} - + + + + \ No newline at end of file diff --git a/democracy/sai/saimod_beta/tpl/mail_android.tpl b/democracy/sai/saimod_beta/tpl/mail_android.tpl index d280edd..93517b4 100644 --- a/democracy/sai/saimod_beta/tpl/mail_android.tpl +++ b/democracy/sai/saimod_beta/tpl/mail_android.tpl @@ -9,18 +9,18 @@

    - im folgenden erklären wir Dir, wie du zur 1. Version von DEMOCRACY gelangst. + im folgenden erklären wir Dir, wie Du zur Beta-Version von DEMOCRACY gelangst.


    Anleitung für Android
      -
    1. Öffne diesen Link mit Deinem Handy: https://play.google.com/apps/testing/de.democracydeutschland.app
    2. +
    3. Öffne diesen Link mit Deinem Handy: https://play.google.com/apps/testing/de.democracydeutschland.app.beta
    4. Drücke den Button ganz unten auf dem es heißt: Tester werden
    5. Eine neue Ansicht lädt, drücke nun die App DEMOCRACY bei Google Play herunterladen.
    6. Du wirst in den Google Play Store weitergeleitet und kannst die App dort herunter laden
    7. -
    8. Öffne DEMOCRACY und teste – wie du Bugs korrekt reportest, entnehme bitte der Anleitung zum Bug-Report
    9. -
    10. Solltest Du beim Öffnen des Links von Schritt 1 eine Fehlermeldung bekommen, in der es heißt App nicht verfügbar, hast du dich entweder mit einer anderen als uns zur Verfügung gestellten Email-Adresse angemeldet oder uns die falsche Email-Adresse zur Verfügung gestellt. Gerne kannst du uns mit einer Mail an prototyping@democracy-deutschland.de deine richtige Adresse zur Verfügung stellen.
    11. +
    12. Öffne DEMOCRACY und teste – wie Du Bugs korrekt reportest, entnehme bitte der Anleitung zum Bug-Report
    13. +
    14. Solltest Du beim Öffnen des Links von Schritt 1 eine Fehlermeldung bekommen, in der es heißt App nicht verfügbar, hast Du Dich entweder mit einer anderen als uns zur Verfügung gestellten Email-Adresse angemeldet oder uns die falsche Email-Adresse zur Verfügung gestellt. Gerne kannst Du uns mit einer Mail an prototyping@democracy-deutschland.de deine richtige Adresse zur Verfügung stellen.

    @@ -34,17 +34,17 @@

    - Wie du das machst? + Wie Du das machst?
    Zunächst: Mach aus der unten stehenden Liste bitte nur das, was Dir zumutbar erscheint. Im Optimalfall:

      -
    1. Mach einen Screenshot (n Android: An- und Leiser-Taste gleichzeitig drücken oder An-Taste drücken und Screenshot auswählen)
    2. +
    3. Mach einen Screenshot (in Android: An- und Leiser-Taste gleichzeitig drücken oder An-Taste drücken und Screenshot auswählen)
    4. Markiere die fehlerhaften Bereiche in dem Screenshot (z.B Roter Kreis um den Fehler)
    5. Beschreib das Problem möglichst genau in wenigen Sätzen
    6. -
    7. Gib eine Schritt-für-Schritt-Anleitung, wie es zu dem Problem gekommen ist, und wenn es dir für notwendig erscheint
    8. +
    9. Gib eine Schritt-für-Schritt-Anleitung, wie es zu dem Problem gekommen ist, und wenn es Dir für notwendig erscheint
    10. Klassifiziere das Problem in einen Fehlerbericht und/oder einen Verbesserungsvorschlag
    11. Schicke uns den (bearbeiteten) Screenshot, die Fehlerbeschreibung sowie Deine Plattform (iOS/Android) und Deine Gerätebezeichnung (z.B. iPhone SE) per E-Mail an prototyping@democracy-deutschland.de oder optional
    12. Erstelle ein Issue für das Problem und lade den Screenshot auf Github hoch https://github.com/demokratie-live/democracy-client/issues
    13. diff --git a/democracy/sai/saimod_beta/tpl/mail_android.txt b/democracy/sai/saimod_beta/tpl/mail_android.txt new file mode 100644 index 0000000..11f5cc7 --- /dev/null +++ b/democracy/sai/saimod_beta/tpl/mail_android.txt @@ -0,0 +1,30 @@ +Hallo lieber Prototyp-Tester, + +im folgenden erklären wir Dir, wie Du zur Beta-Version von DEMOCRACY gelangst. + +Anleitung für Android +* Öffne diesen Link mit Deinem Handy: https://play.google.com/apps/testing/de.democracydeutschland.app.beta +* Drücke den Button ganz unten auf dem es heißt: Tester werden +* Eine neue Ansicht lädt, drücke nun die App DEMOCRACY bei Google Play herunterladen. +* Du wirst in den Google Play Store weitergeleitet und kannst die App dort herunter laden +* Öffne DEMOCRACY und teste – wie Du Bugs korrekt reportest, entnehme bitte der Anleitung zum Bug-Report +* Solltest Du beim Öffnen des Links von Schritt 1 eine Fehlermeldung bekommen, in der es heißt App nicht verfügbar, hast Du Dich entweder mit einer anderen als uns zur Verfügung gestellten Email-Adresse angemeldet oder uns die falsche Email-Adresse zur Verfügung gestellt. Gerne kannst Du uns mit einer Mail an prototyping@democracy-deutschland.de deine richtige Adresse zur Verfügung stellen. + +Anleitung zum Bug-Report +* Ein qualifiziertes Feedback ist notwendig, um Fehler zu beheben. Deshalb gib uns möglichst viele Informationen zu den von Dir gefunden Fehlern oder Verbesserungsvorschlägen. +* Wie Du das machst? +* Zunächst: Mach aus der unten stehenden Liste bitte nur das, was Dir zumutbar erscheint. Im Optimalfall: + +* Mach einen Screenshot (in Android: An- und Leiser-Taste gleichzeitig drücken oder An-Taste drücken und Screenshot auswählen) +* Markiere die fehlerhaften Bereiche in dem Screenshot (z.B Roter Kreis um den Fehler) +* Beschreib das Problem möglichst genau in wenigen Sätzen +* Gib eine Schritt-für-Schritt-Anleitung, wie es zu dem Problem gekommen ist, und wenn es Dir für notwendig erscheint +* Klassifiziere das Problem in einen Fehlerbericht und/oder einen Verbesserungsvorschlag +* Schicke uns den (bearbeiteten) Screenshot, die Fehlerbeschreibung sowie Deine Plattform (iOS/Android) und Deine Gerätebezeichnung (z.B. iPhone SE) per E-Mail an prototyping@democracy-deutschland.de oder optional +* Erstelle ein Issue für das Problem und lade den Screenshot auf Github hoch https://github.com/demokratie-live/democracy-client/issues + + +DEMOCRACY Deutschland e.V. +IBAN: DE33 5003 1000 1049 7560 00 +BIC: TRODDEF1 +mobil +49 176 470 40 213 \ No newline at end of file diff --git a/democracy/sai/saimod_beta/tpl/mail_ios.tpl b/democracy/sai/saimod_beta/tpl/mail_ios.tpl index 86ff57a..dce1fe7 100644 --- a/democracy/sai/saimod_beta/tpl/mail_ios.tpl +++ b/democracy/sai/saimod_beta/tpl/mail_ios.tpl @@ -9,18 +9,18 @@

    - im folgenden erklären wir Dir, wie du zur 1. Version von DEMOCRACY gelangst. + im folgenden erklären wir Dir, wie Du zur Beta-Version von DEMOCRACY gelangst.


    Anleitung für iOS
      -
    1. Du erhälst in den folgenden Minuten eine Email von TestFlight mit der Betreffzeile DEMOCRACY Deutschland e.V. has invited you to test "DEMOCRACY App"
    2. +
    3. Du erhälst in den folgenden Minuten eine Email von TestFlight mit der Betreffzeile DEMOCRACY Deutschland e.V. has invited you to test "DEMOCRACY Beta"
    4. Öffne diese Mail, klicke den blauen Button View in TestFlight und folge der aufgeführten Anleitung, also
    5. -
    6. Lade dir TestFlight im AppStore herunter
    7. +
    8. Lade Dir TestFlight im AppStore herunter
    9. Öffne TestFlight und Drücke “Redeem”
    10. Gib den Bestätigungscode, also z.B. QJHFFNDF ein und lade Dir DEMOCRACY herunter.
    11. -
    12. Öffne DEMOCRACY und teste – wie du Bugs korrekt reportest, entnehme bitte der Anleitung zum Bug-Report
    13. +
    14. Öffne DEMOCRACY und teste – wie Du Bugs korrekt reportest, entnehme bitte der Anleitung zum Bug-Report

    @@ -34,7 +34,7 @@

    - Wie du das machst? + Wie Du das machst?
    Zunächst: Mach aus der unten stehenden Liste bitte nur das, was Dir zumutbar erscheint. Im Optimalfall: @@ -44,7 +44,7 @@
  1. Mach einen Screenshot (in iOS: Stand-by- und Home-Taste gleichzeitig drücken)
  2. Markiere die fehlerhaften Bereiche in dem Screenshot (z.B Roter Kreis um den Fehler)
  3. Beschreib das Problem möglichst genau in wenigen Sätzen
  4. -
  5. Gib eine Schritt-für-Schritt-Anleitung, wie es zu dem Problem gekommen ist, und wenn es dir für notwendig erscheint
  6. +
  7. Gib eine Schritt-für-Schritt-Anleitung, wie es zu dem Problem gekommen ist, und wenn es Dir für notwendig erscheint
  8. Klassifiziere das Problem in einen Fehlerbericht und/oder einen Verbesserungsvorschlag
  9. Schicke uns den (bearbeiteten) Screenshot, die Fehlerbeschreibung sowie Deine Plattform (iOS/Android) und Deine Gerätebezeichnung (z.B. iPhone SE) per E-Mail an prototyping@democracy-deutschland.de oder optional
  10. Erstelle ein Issue für das Problem und lade den Screenshot auf Github hoch https://github.com/demokratie-live/democracy-client/issues
  11. diff --git a/democracy/sai/saimod_beta/tpl/mail_ios.txt b/democracy/sai/saimod_beta/tpl/mail_ios.txt new file mode 100644 index 0000000..a968057 --- /dev/null +++ b/democracy/sai/saimod_beta/tpl/mail_ios.txt @@ -0,0 +1,30 @@ +Hallo lieber Prototyp-Tester, + +im folgenden erklären wir Dir, wie Du zur Beta-Version von DEMOCRACY gelangst. + +Anleitung für iOS +* Du erhälst in den folgenden Minuten eine Email von TestFlight mit der Betreffzeile DEMOCRACY Deutschland e.V. has invited you to test "DEMOCRACY Beta" +* Öffne diese Mail, klicke den blauen Button View in TestFlight und folge der aufgeführten Anleitung, also +* Lade Dir TestFlight im AppStore herunter +* Öffne TestFlight und Drücke “Redeem” +* Gib den Bestätigungscode, also z.B. QJHFFNDF ein und lade Dir DEMOCRACY herunter. +* Öffne DEMOCRACY und teste – wie Du Bugs korrekt reportest, entnehme bitte der Anleitung zum Bug-Report + +Anleitung zum Bug-Report +* Ein qualifiziertes Feedback ist notwendig, um Fehler zu beheben. Deshalb gib uns möglichst viele Informationen zu den von Dir gefunden Fehlern oder Verbesserungsvorschlägen. +* Wie Du das machst? +* Zunächst: Mach aus der unten stehenden Liste bitte nur das, was Dir zumutbar erscheint. Im Optimalfall: + +* Mach einen Screenshot (in iOS: Stand-by- und Home-Taste gleichzeitig drücken) +* Markiere die fehlerhaften Bereiche in dem Screenshot (z.B Roter Kreis um den Fehler) +* Beschreib das Problem möglichst genau in wenigen Sätzen +* Gib eine Schritt-für-Schritt-Anleitung, wie es zu dem Problem gekommen ist, und wenn es Dir für notwendig erscheint +* Klassifiziere das Problem in einen Fehlerbericht und/oder einen Verbesserungsvorschlag +* Schicke uns den (bearbeiteten) Screenshot, die Fehlerbeschreibung sowie Deine Plattform (iOS/Android) und Deine Gerätebezeichnung (z.B. iPhone SE) per E-Mail an prototyping@democracy-deutschland.de oder optional +* Erstelle ein Issue für das Problem und lade den Screenshot auf Github hoch https://github.com/demokratie-live/democracy-client/issues + + +DEMOCRACY Deutschland e.V. +IBAN: DE33 5003 1000 1049 7560 00 +BIC: TRODDEF1 +mobil +49 176 470 40 213 \ No newline at end of file diff --git a/democracy/sai/saimod_beta/tpl/mail_tr.tpl b/democracy/sai/saimod_beta/tpl/mail_tr.tpl index e49a67e..b5d4f79 100644 --- a/democracy/sai/saimod_beta/tpl/mail_tr.tpl +++ b/democracy/sai/saimod_beta/tpl/mail_tr.tpl @@ -6,5 +6,8 @@  ${stored_time}  ${mailed_time} ${comment} - + + + + \ No newline at end of file diff --git a/democracy/sai/saimod_beta/tpl/store_android_tr.tpl b/democracy/sai/saimod_beta/tpl/store_android_tr.tpl index 54f5967..8563027 100644 --- a/democracy/sai/saimod_beta/tpl/store_android_tr.tpl +++ b/democracy/sai/saimod_beta/tpl/store_android_tr.tpl @@ -6,5 +6,8 @@  ${redeemed_time}  ${stored_time} ${comment} - + + + + \ No newline at end of file diff --git a/democracy/sai/saimod_beta/tpl/store_ios_tr.tpl b/democracy/sai/saimod_beta/tpl/store_ios_tr.tpl index fd32b55..c4b5abc 100644 --- a/democracy/sai/saimod_beta/tpl/store_ios_tr.tpl +++ b/democracy/sai/saimod_beta/tpl/store_ios_tr.tpl @@ -6,5 +6,8 @@  ${redeemed_time}  ${stored_time} ${comment} - + + + + \ No newline at end of file