From cb635858f1e47e9c3e8bb6af056b60f510e35bb6 Mon Sep 17 00:00:00 2001 From: Mathieu Sanchez Date: Tue, 19 Dec 2017 23:42:09 +0100 Subject: [PATCH] Reange and optimize --- index.php | 38 +- src/API/API.php | 31 +- src/API/APIError.php | 152 +- src/API/APIRouter.php | 213 +- src/Autoloader.php | 30 +- src/Config.php | 12 +- src/Controller/Controller.php | 48 +- src/Controller/ControllerSite.php | 98 +- src/Controller/Error.php | 173 +- src/Controller/site/Index.php | 24 +- src/Controller/site/SiteError.php | 183 +- src/Controller/site/SiteRouter.php | 25 +- src/Model/BDD.php | 49 +- src/Model/BDTables.php | 6 +- src/Model/FPDF.php | 3654 ++++++------- src/Model/Logs.php | 316 +- src/Model/Model.php | 72 +- src/View/Site/SiteError.php | 10 +- src/View/Site/tpl/footer.php | 9 +- src/View/Site/tpl/head.php | 23 +- src/lib/functions.php | 484 +- src/lib/mail/PHPMailerAutoload.php | 44 +- src/lib/mail/class.phpmailer.php | 7716 ++++++++++++++-------------- src/lib/mail/class.smtp.php | 2341 ++++----- src/lib/mail/mail_default.html | 3 +- 25 files changed, 7915 insertions(+), 7839 deletions(-) diff --git a/index.php b/index.php index 05d4b7c..f5915ca 100644 --- a/index.php +++ b/index.php @@ -1,4 +1,5 @@ 1 && $pages[count($pages) - 1] == '') -{ - $args = (count($parts) > 1 ? '?'.$parts[1] : ''); - header($_SERVER['SERVER_PROTOCOL'].' 301 Moved Permanently'); - header('Location: /'.rtrim($urlA, '/').$args); - exit(); +if ( count( $pages ) > 1 && $pages[ count( $pages ) - 1 ] == '' ) { + $args = ( count( $parts ) > 1 ? '?' . $parts[1] : '' ); + header( $_SERVER['SERVER_PROTOCOL'] . ' 301 Moved Permanently' ); + header( 'Location: /' . rtrim( $urlA, '/' ) . $args ); + exit(); } -if ($pages[0] == 'api' && isset($pages[1]) && preg_match('#^([a-z]+)$#', $pages[1], $api1) && isset($pages[2]) && preg_match('#^([a-z-]+)$#', $pages[2], $api2)) { - new APIRouter($api1[0], $api2[0]); +if ( $pages[0] == 'api' && isset( $pages[1] ) && preg_match( '#^([a-z]+)$#', $pages[1], $api1 ) && isset( $pages[2] ) && preg_match( '#^([a-z-]+)$#', $pages[2], $api2 ) ) { + new APIRouter( $api1[0], $api2[0] ); -} else if (preg_match('#^test\.dev$#', $_SERVER['SERVER_NAME'])) { - new SiteRouter($pages); +} else if ( preg_match( '#^test\.dev$#', $_SERVER['SERVER_NAME'] ) ) { + new SiteRouter( $pages ); } else { - new Error(404); + new Error( 404 ); } /* diff --git a/src/API/API.php b/src/API/API.php index 0e6f5e9..06d0d68 100644 --- a/src/API/API.php +++ b/src/API/API.php @@ -6,23 +6,24 @@ use base\Controller\Controller; class API extends Controller { - private $declaredFunctions = []; + private $declaredFunctions = []; - /** - * API constructor. - * @param array $declaredFunctions - */ - public function __construct(array $declaredFunctions) { - parent::__construct(); - $this->declaredFunctions = $declaredFunctions; - } + /** + * API constructor. + * + * @param array $declaredFunctions + */ + public function __construct( array $declaredFunctions ) { + parent::__construct(); + $this->declaredFunctions = $declaredFunctions; + } - /** - * @return array - */ - public function getDeclaredFunctions() { - return $this->declaredFunctions; - } + /** + * @return array + */ + public function getDeclaredFunctions() { + return $this->declaredFunctions; + } } ?> \ No newline at end of file diff --git a/src/API/APIError.php b/src/API/APIError.php index c613fe3..64caa0f 100644 --- a/src/API/APIError.php +++ b/src/API/APIError.php @@ -6,81 +6,87 @@ use base\Controller\Controller; class APIError extends Controller { - /** - * APIError constructor. - * @param int $ErrCode - * @param string $devMessage - * @param string $publicMessage - * @param string $code - */ - public function __construct(int $ErrCode = 500, string $devMessage = 'Erreur inconnue', string $publicMessage = 'Une erreur inconnue s\'est produite', string $code = '') { - parent::__construct(); + /** + * APIError constructor. + * + * @param int $ErrCode + * @param string $devMessage + * @param string $publicMessage + * @param string $code + */ + public function __construct( int $ErrCode = 500, string $devMessage = 'Erreur inconnue', string $publicMessage = 'Une erreur inconnue s\'est produite', string $code = '' ) { + parent::__construct(); - $tabCode = [ - //Informational 1xx - 100 => ['label' => '100 Continue', 'msg' => '100 Continue'], - 101 => ['label' => '101 Switching Protocols', 'msg' => '101 Switching Protocols'], - //Successful 2xx - 200 => ['label' => '200 OK', 'msg' => '200 OK'], - 201 => ['label' => '201 Created', 'msg' => '201 Created'], - 202 => ['label' => '202 Accepted', 'msg' => '202 Accepted'], - 203 => ['label' => '203 Non-Authoritative Information', 'msg' => '203 Non-Authoritative Information'], - 204 => ['label' => '204 No Content', 'msg' => '204 No Content'], - 205 => ['label' => '205 Reset Content', 'msg' => '205 Reset Content'], - 206 => ['label' => '206 Partial Content', 'msg' => '206 Partial Content'], - 226 => ['label' => '226 IM Used', 'msg' => '226 IM Used'], - //Redirection 3xx - 300 => ['label' => '300 Multiple Choices', 'msg' => '300 Multiple Choices'], - 301 => ['label' => '301 Moved Permanently', 'msg' => '301 Moved Permanently'], - 302 => ['label' => '302 Found', 'msg' => '302 Found'], - 303 => ['label' => '303 See Other', 'msg' => '303 See Other'], - 304 => ['label' => '304 Not Modified', 'msg' => '304 Not Modified'], - 305 => ['label' => '305 Use Proxy', 'msg' => '305 Use Proxy'], - 306 => ['label' => '306 (Unused)', 'msg' => '306 (Unused)'], - 307 => ['label' => '307 Temporary Redirect', 'msg' => '307 Temporary Redirect'], - //Client Error 4xx - 400 => ['label' => '400 Bad Request', 'msg' => '400 Bad Request'], - 401 => ['label' => '401 Unauthorized', 'msg' => 'Vous n\'êtes pas autorisé à accéder à cette page'], - 402 => ['label' => '402 Payment Required', 'msg' => '402 Payment Required'], - 403 => ['label' => '403 Forbidden', 'msg' => '403 Forbidden'], - 404 => ['label' => '404 Not Found', 'msg' => 'Page non trouvée'], - 405 => ['label' => '405 Method Not Allowed', 'msg' => '405 Method Not Allowed'], - 406 => ['label' => '406 Not Acceptable', 'msg' => '406 Not Acceptable'], - 407 => ['label' => '407 Proxy Authentication Required', 'msg' => '407 Proxy Authentication Required'], - 408 => ['label' => '408 Request Timeout', 'msg' => '408 Request Timeout'], - 409 => ['label' => '409 Conflict', 'msg' => '409 Conflict'], - 410 => ['label' => '410 Gone', 'msg' => '410 Gone'], - 411 => ['label' => '411 Length Required', 'msg' => '411 Length Required'], - 412 => ['label' => '412 Precondition Failed', 'msg' => '412 Precondition Failed'], - 413 => ['label' => '413 Request Entity Too Large', 'msg' => '413 Request Entity Too Large'], - 414 => ['label' => '414 Request-URI Too Long', 'msg' => '414 Request-URI Too Long'], - 415 => ['label' => '415 Unsupported Media Type', 'msg' => '415 Unsupported Media Type'], - 416 => ['label' => '416 Requested Range Not Satisfiable', 'msg' => '416 Requested Range Not Satisfiable'], - 417 => ['label' => '417 Expectation Failed', 'msg' => '417 Expectation Failed'], - 418 => ['label' => '418 I\'m a teapot', 'msg' => '418 I\'m a teapot'], - 422 => ['label' => '422 Unprocessable Entity', 'msg' => '422 Unprocessable Entity'], - 423 => ['label' => '423 Locked', 'msg' => '423 Locked'], - 426 => ['label' => '426 Upgrade Required', 'msg' => '426 Upgrade Required'], - 428 => ['label' => '428 Precondition Required', 'msg' => '428 Precondition Required'], - 429 => ['label' => '429 Too Many Requests', 'msg' => '429 Too Many Requests'], - 431 => ['label' => '431 Request Header Fields Too Large', 'msg' => '431 Request Header Fields Too Large'], - //Server Error 5xx - 500 => ['label' => '500 Internal Server Error', 'msg' => 'Une erreur est survenue'], - 501 => ['label' => '501 Not Implemented', 'msg' => '501 Not Implemented'], - 502 => ['label' => '502 Bad Gateway', 'msg' => '502 Bad Gateway'], - 503 => ['label' => '503 Service Unavailable', 'msg' => '503 Service Unavailable'], - 504 => ['label' => '504 Gateway Timeout', 'msg' => '504 Gateway Timeout'], - 505 => ['label' => '505 HTTP Version Not Supported', 'msg' => '505 HTTP Version Not Supported'], - 506 => ['label' => '506 Variant Also Negotiates', 'msg' => '506 Variant Also Negotiates'], - 510 => ['label' => '510 Not Extended', 'msg' => '510 Not Extended'], - 511 => ['label' => '511 Network Authentication Required', 'msg' => '511 Network Authentication Required'] - ]; + $tabCode = [ + //Informational 1xx + 100 => [ 'label' => '100 Continue', 'msg' => '100 Continue' ], + 101 => [ 'label' => '101 Switching Protocols', 'msg' => '101 Switching Protocols' ], + //Successful 2xx + 200 => [ 'label' => '200 OK', 'msg' => '200 OK' ], + 201 => [ 'label' => '201 Created', 'msg' => '201 Created' ], + 202 => [ 'label' => '202 Accepted', 'msg' => '202 Accepted' ], + 203 => [ 'label' => '203 Non-Authoritative Information', 'msg' => '203 Non-Authoritative Information' ], + 204 => [ 'label' => '204 No Content', 'msg' => '204 No Content' ], + 205 => [ 'label' => '205 Reset Content', 'msg' => '205 Reset Content' ], + 206 => [ 'label' => '206 Partial Content', 'msg' => '206 Partial Content' ], + 226 => [ 'label' => '226 IM Used', 'msg' => '226 IM Used' ], + //Redirection 3xx + 300 => [ 'label' => '300 Multiple Choices', 'msg' => '300 Multiple Choices' ], + 301 => [ 'label' => '301 Moved Permanently', 'msg' => '301 Moved Permanently' ], + 302 => [ 'label' => '302 Found', 'msg' => '302 Found' ], + 303 => [ 'label' => '303 See Other', 'msg' => '303 See Other' ], + 304 => [ 'label' => '304 Not Modified', 'msg' => '304 Not Modified' ], + 305 => [ 'label' => '305 Use Proxy', 'msg' => '305 Use Proxy' ], + 306 => [ 'label' => '306 (Unused)', 'msg' => '306 (Unused)' ], + 307 => [ 'label' => '307 Temporary Redirect', 'msg' => '307 Temporary Redirect' ], + //Client Error 4xx + 400 => [ 'label' => '400 Bad Request', 'msg' => '400 Bad Request' ], + 401 => [ 'label' => '401 Unauthorized', 'msg' => 'Vous n\'êtes pas autorisé à accéder à cette page' ], + 402 => [ 'label' => '402 Payment Required', 'msg' => '402 Payment Required' ], + 403 => [ 'label' => '403 Forbidden', 'msg' => '403 Forbidden' ], + 404 => [ 'label' => '404 Not Found', 'msg' => 'Page non trouvée' ], + 405 => [ 'label' => '405 Method Not Allowed', 'msg' => '405 Method Not Allowed' ], + 406 => [ 'label' => '406 Not Acceptable', 'msg' => '406 Not Acceptable' ], + 407 => [ 'label' => '407 Proxy Authentication Required', 'msg' => '407 Proxy Authentication Required' ], + 408 => [ 'label' => '408 Request Timeout', 'msg' => '408 Request Timeout' ], + 409 => [ 'label' => '409 Conflict', 'msg' => '409 Conflict' ], + 410 => [ 'label' => '410 Gone', 'msg' => '410 Gone' ], + 411 => [ 'label' => '411 Length Required', 'msg' => '411 Length Required' ], + 412 => [ 'label' => '412 Precondition Failed', 'msg' => '412 Precondition Failed' ], + 413 => [ 'label' => '413 Request Entity Too Large', 'msg' => '413 Request Entity Too Large' ], + 414 => [ 'label' => '414 Request-URI Too Long', 'msg' => '414 Request-URI Too Long' ], + 415 => [ 'label' => '415 Unsupported Media Type', 'msg' => '415 Unsupported Media Type' ], + 416 => [ 'label' => '416 Requested Range Not Satisfiable', 'msg' => '416 Requested Range Not Satisfiable' ], + 417 => [ 'label' => '417 Expectation Failed', 'msg' => '417 Expectation Failed' ], + 418 => [ 'label' => '418 I\'m a teapot', 'msg' => '418 I\'m a teapot' ], + 422 => [ 'label' => '422 Unprocessable Entity', 'msg' => '422 Unprocessable Entity' ], + 423 => [ 'label' => '423 Locked', 'msg' => '423 Locked' ], + 426 => [ 'label' => '426 Upgrade Required', 'msg' => '426 Upgrade Required' ], + 428 => [ 'label' => '428 Precondition Required', 'msg' => '428 Precondition Required' ], + 429 => [ 'label' => '429 Too Many Requests', 'msg' => '429 Too Many Requests' ], + 431 => [ 'label' => '431 Request Header Fields Too Large', 'msg' => '431 Request Header Fields Too Large' ], + //Server Error 5xx + 500 => [ 'label' => '500 Internal Server Error', 'msg' => 'Une erreur est survenue' ], + 501 => [ 'label' => '501 Not Implemented', 'msg' => '501 Not Implemented' ], + 502 => [ 'label' => '502 Bad Gateway', 'msg' => '502 Bad Gateway' ], + 503 => [ 'label' => '503 Service Unavailable', 'msg' => '503 Service Unavailable' ], + 504 => [ 'label' => '504 Gateway Timeout', 'msg' => '504 Gateway Timeout' ], + 505 => [ 'label' => '505 HTTP Version Not Supported', 'msg' => '505 HTTP Version Not Supported' ], + 506 => [ 'label' => '506 Variant Also Negotiates', 'msg' => '506 Variant Also Negotiates' ], + 510 => [ 'label' => '510 Not Extended', 'msg' => '510 Not Extended' ], + 511 => [ 'label' => '511 Network Authentication Required', 'msg' => '511 Network Authentication Required' ] + ]; - header('Content-Type: application/json'); - header($_SERVER['SERVER_PROTOCOL'].' '.$tabCode[$ErrCode]['label']); - echo json_encode(['status' => 'echec', 'msg' => $publicMessage, 'devMsg' => $devMessage, 'code' => $code], JSON_PRETTY_PRINT); - exit(); - } + header( 'Content-Type: application/json' ); + header( $_SERVER['SERVER_PROTOCOL'] . ' ' . $tabCode[ $ErrCode ]['label'] ); + echo json_encode( [ + 'status' => 'echec', + 'msg' => $publicMessage, + 'devMsg' => $devMessage, + 'code' => $code + ], JSON_PRETTY_PRINT ); + exit(); + } } ?> \ No newline at end of file diff --git a/src/API/APIRouter.php b/src/API/APIRouter.php index a1e055c..fd25208 100644 --- a/src/API/APIRouter.php +++ b/src/API/APIRouter.php @@ -6,126 +6,127 @@ use base\Config; class APIRouter { - //Les méthodes HTTP gérée par l'API - const HTTP_METHODS = ['GET', 'POST', 'DELETE', 'PUT']; + //Les méthodes HTTP gérée par l'API + const HTTP_METHODS = [ 'GET', 'POST', 'DELETE', 'PUT' ]; - /** - * APIRouter constructor. - * @param string $file - * @param string $action - */ - public function __construct(string $file, string $action) { + /** + * APIRouter constructor. + * + * @param string $file + * @param string $action + */ + public function __construct( string $file, string $action ) { - //On vérifie que les paramètres ne sont pas vides - if (empty($file) || empty($action)) { - new APIError(); - } + //On vérifie que les paramètres ne sont pas vides + if ( empty( $file ) || empty( $action ) ) { + new APIError(); + } - //On vérifie que la classe appelée existe - $fileName = 'API' . ucfirst($file); - if (!file_exists('src/API/' . $fileName . '.php')) { - new APIError(404); - } + //On vérifie que la classe appelée existe + $fileName = 'API' . ucfirst( $file ); + if ( ! file_exists( 'src/API/' . $fileName . '.php' ) ) { + new APIError( 404 ); + } - //On instancie la classe. - $class = '\\' . Config::NAMESPACE . '\\API\\' . $fileName; - $class = new $class($action); + //On instancie la classe. + $class = '\\' . Config::NAMESPACE . '\\API\\' . $fileName; + $class = new $class( $action ); - //On vérifie que l'action demandé est déclarée - if (!array_key_exists($action, $class->getDeclaredFunctions())) { - new APIError(404); - } + //On vérifie que l'action demandé est déclarée + if ( ! array_key_exists( $action, $class->getDeclaredFunctions() ) ) { + new APIError( 404 ); + } - //On reconstruit le nom de la fonction - $array = explode('-', $action); - foreach ($array as $key => $value) { - if ($key == 0) { - $array[$key] = $value; - } else { - $array[$key] = ucfirst($value); - } - } - $function = implode('', $array); //le nom de la fonction + //On reconstruit le nom de la fonction + $array = explode( '-', $action ); + foreach ( $array as $key => $value ) { + if ( $key == 0 ) { + $array[ $key ] = $value; + } else { + $array[ $key ] = ucfirst( $value ); + } + } + $function = implode( '', $array ); //le nom de la fonction - //On vérifie que la fonction existe dans la classe - if (!method_exists($class, $function)) { - new APIError(500, 'La fonction ' . $function . ' n\'existe pas dans la classe ' . get_class($class)); - } + //On vérifie que la fonction existe dans la classe + if ( ! method_exists( $class, $function ) ) { + new APIError( 500, 'La fonction ' . $function . ' n\'existe pas dans la classe ' . get_class( $class ) ); + } - //On vérifie que la méthode d'envoie est référencée - $method = $class->getDeclaredFunctions()[$action]['method']; - if (!in_array($method, self::HTTP_METHODS)) { - new APIError(500, 'méthode http inconnue'); - } + //On vérifie que la méthode d'envoie est référencée + $method = $class->getDeclaredFunctions()[ $action ]['method']; + if ( ! in_array( $method, self::HTTP_METHODS ) ) { + new APIError( 500, 'méthode http inconnue' ); + } - //On vérifie que la méthode requise et la mathode obtenue sont les même - if ($method != $_SERVER['REQUEST_METHOD']) { - new APIError(400, 'La méthode HTTP ne correspond pas à la méthode prévue'); - } + //On vérifie que la méthode requise et la mathode obtenue sont les même + if ( $method != $_SERVER['REQUEST_METHOD'] ) { + new APIError( 400, 'La méthode HTTP ne correspond pas à la méthode prévue' ); + } - //On met les paramètres dans le tableau $data - $params = $class->getDeclaredFunctions()[$action]['params']; - $data = []; - if (!empty($params)) { - if ($method == 'DELETE' || $method == 'PUT') { - parse_str(file_get_contents('php://input'), $data); - } else if($method == 'POST') { - $data = $_POST; - } else if ($method == 'GET') { - $data = $_GET; - } + //On met les paramètres dans le tableau $data + $params = $class->getDeclaredFunctions()[ $action ]['params']; + $data = []; + if ( ! empty( $params ) ) { + if ( $method == 'DELETE' || $method == 'PUT' ) { + parse_str( file_get_contents( 'php://input' ), $data ); + } else if ( $method == 'POST' ) { + $data = $_POST; + } else if ( $method == 'GET' ) { + $data = $_GET; + } - //On boucle sur les paramètres de la doc de la fonction - foreach ($params as $p => $options){ - if(!isset($options['required'])){ - $options['required'] = false; - } - //Si le paramètre est obligatoire et qu'il est vide ou non fourni on lève une erreur 400 BAD REQUEST - if ($options['required'] && (!array_key_exists($p, $data) || (empty($data[$p]) && $data[$p] != '0'))) { - $devMsg = 'Paramètre ' . $p . ' manquant'; - if (isset($options['devMsg'])) { - $devMsg = $options['devMsg']; - } - $publicMsg = 'Des paramètres obligatoires ne sont pas envoyés ou sont vides'; - if (isset($options['publicMsg'])) { - $publicMsg = $options['publicMsg']; - } - $code = ''; - if (isset($options['code'])) { - $code = $options['code']; - } - new APIError(400, $devMsg, $publicMsg, $code); - } + //On boucle sur les paramètres de la doc de la fonction + foreach ( $params as $p => $options ) { + if ( ! isset( $options['required'] ) ) { + $options['required'] = false; + } + //Si le paramètre est obligatoire et qu'il est vide ou non fourni on lève une erreur 400 BAD REQUEST + if ( $options['required'] && ( ! array_key_exists( $p, $data ) || ( empty( $data[ $p ] ) && $data[ $p ] != '0' ) ) ) { + $devMsg = 'Paramètre ' . $p . ' manquant'; + if ( isset( $options['devMsg'] ) ) { + $devMsg = $options['devMsg']; + } + $publicMsg = 'Des paramètres obligatoires ne sont pas envoyés ou sont vides'; + if ( isset( $options['publicMsg'] ) ) { + $publicMsg = $options['publicMsg']; + } + $code = ''; + if ( isset( $options['code'] ) ) { + $code = $options['code']; + } + new APIError( 400, $devMsg, $publicMsg, $code ); + } - //On vérifie que le type donné correspond au typage requis - if (isset($options['type'])) { - if ($options['type'] == 'int') { - if (ctype_digit($data[$p])) { - $data[$p] = (int) $data[$p]; - } else { - new APIError(400, 'Le type donné ne correspond pas au type demandé pour le paramètre '.$p.' : string donné, '.$options['type'].' requis'); - } - } else if ($options['type'] == 'bool') { - if ($data[$p] == 'true' || $data[$p] == '1') { - $data[$p] = true; - } else if ($data[$p] == 'false' || $data[$p] == '0') { - $data[$p] = false; - } else { - new APIError(400, 'Le type donné ne correspond pas au type demandé pour le paramètre '.$p.' : string donné, '.$options['type'].' requis'); - } - } - } + //On vérifie que le type donné correspond au typage requis + if ( isset( $options['type'] ) ) { + if ( $options['type'] == 'int' ) { + if ( ctype_digit( $data[ $p ] ) ) { + $data[ $p ] = (int) $data[ $p ]; + } else { + new APIError( 400, 'Le type donné ne correspond pas au type demandé pour le paramètre ' . $p . ' : string donné, ' . $options['type'] . ' requis' ); + } + } else if ( $options['type'] == 'bool' ) { + if ( $data[ $p ] == 'true' || $data[ $p ] == '1' ) { + $data[ $p ] = true; + } else if ( $data[ $p ] == 'false' || $data[ $p ] == '0' ) { + $data[ $p ] = false; + } else { + new APIError( 400, 'Le type donné ne correspond pas au type demandé pour le paramètre ' . $p . ' : string donné, ' . $options['type'] . ' requis' ); + } + } + } - //Si un paramètre non obligatoire n'est pas donné par l'utilisateur on lui donne la valeur par défaut d'une chaine vide - if (!array_key_exists($p, $data)) { - $data[$p] = ''; - } - } - } + //Si un paramètre non obligatoire n'est pas donné par l'utilisateur on lui donne la valeur par défaut d'une chaine vide + if ( ! array_key_exists( $p, $data ) ) { + $data[ $p ] = ''; + } + } + } - //On appelle la fonction correspondante pour le traitement - $class->$function($data); - } + //On appelle la fonction correspondante pour le traitement + $class->$function( $data ); + } } ?> \ No newline at end of file diff --git a/src/Autoloader.php b/src/Autoloader.php index 09a0920..e13623b 100644 --- a/src/Autoloader.php +++ b/src/Autoloader.php @@ -4,19 +4,21 @@ namespace base; class Autoloader { - /** - * Enregistre notre autoloader - */ - static function register() { - spl_autoload_register(array(__CLASS__, 'autoload')); - } + /** + * Enregistre notre autoloader + */ + static function register() { + spl_autoload_register( array( __CLASS__, 'autoload' ) ); + } - /** - * Inclue le fichier correspondant à notre classe - * @param $class string Le nom de la classe à charger - */ - static function autoload($class) { - if (preg_match('#^' . Config::NAMESPACE . '\\\(.+)$#', $class, $matches)) - require 'src/' . str_replace('\\', '/', $matches[1]) . '.php'; - } + /** + * Inclue le fichier correspondant à notre classe + * + * @param $class string Le nom de la classe à charger + */ + static function autoload( $class ) { + if ( preg_match( '#^' . Config::NAMESPACE . '\\\(.+)$#', $class, $matches ) ) { + require 'src/' . str_replace( '\\', '/', $matches[1] ) . '.php'; + } + } } \ No newline at end of file diff --git a/src/Config.php b/src/Config.php index 22f747c..4abc6c5 100644 --- a/src/Config.php +++ b/src/Config.php @@ -3,12 +3,12 @@ namespace base; class Config { - const SITE_JS_VERSION = '1.00'; - const SITE_CSS_VERSION = '1.00'; + const SITE_JS_VERSION = '1.00'; + const SITE_CSS_VERSION = '1.00'; - const TITLE_HEADER = 'Mon titre de site'; - const DESCRIPTION_HEADER = 'Ma description pour les robots'; - const NAMESPACE = 'base'; + const TITLE_HEADER = 'Mon titre de site'; + const DESCRIPTION_HEADER = 'Ma description pour les robots'; + const NAMESPACE = 'base'; - const FAVICON_PATH = '/img/favicon.png'; + const FAVICON_PATH = '/img/favicon.png'; } \ No newline at end of file diff --git a/src/Controller/Controller.php b/src/Controller/Controller.php index 237807f..4184b89 100644 --- a/src/Controller/Controller.php +++ b/src/Controller/Controller.php @@ -6,35 +6,35 @@ use base\Config; class Controller { - private $data; + private $data; - public function __construct() { - $this->data=[]; - } + public function __construct() { + $this->data = []; + } - protected function view() { - ob_start(); - extract($this->data); - require 'src/View/' . str_replace('\\', '/', preg_replace('#^' . Config::NAMESPACE . '\\\Controller\\\#', '', get_class($this))) . '.php'; - ob_end_flush(); - exit(); - } + protected function view() { + ob_start(); + extract( $this->data ); + require 'src/View/' . str_replace( '\\', '/', preg_replace( '#^' . Config::NAMESPACE . '\\\Controller\\\#', '', get_class( $this ) ) ) . '.php'; + ob_end_flush(); + exit(); + } - protected function addData($data) { - $this->data += $data; - } + protected function addData( $data ) { + $this->data += $data; + } - protected function returnJson($data) { - header('Content-Type: application/json'); - echo json_encode($data); - exit(); - } + protected function returnJson( $data ) { + header( 'Content-Type: application/json' ); + echo json_encode( $data ); + exit(); + } - protected function throwError($msg, $code = '') { - header('Content-Type: application/json'); - echo json_encode(['status' => 'echec', 'msg' => $msg, 'code' => $code], JSON_PRETTY_PRINT); - exit(); - } + protected function throwError( $msg, $code = '' ) { + header( 'Content-Type: application/json' ); + echo json_encode( [ 'status' => 'echec', 'msg' => $msg, 'code' => $code ], JSON_PRETTY_PRINT ); + exit(); + } } ?> \ No newline at end of file diff --git a/src/Controller/ControllerSite.php b/src/Controller/ControllerSite.php index 337aef1..99290c4 100644 --- a/src/Controller/ControllerSite.php +++ b/src/Controller/ControllerSite.php @@ -6,67 +6,67 @@ use base\Config; class ControllerSite { - private $data; - private $head; - private $footer; + private $data; + private $head; + private $footer; - public function __construct() { - $this->data=[]; - $this->head=[]; - $this->footer=[]; - } + public function __construct() { + $this->data = []; + $this->head = []; + $this->footer = []; + } - protected function view($header = true, $footer = true) { - ob_start(); - extract($this->data); + protected function view( $header = true, $footer = true ) { + ob_start(); + extract( $this->data ); - if (empty($this->head['title'])) { - $this->head['title'] = Config::TITLE_HEADER; - } - if (empty($this->head['description'])) { - $this->head['description'] = Config::DESCRIPTION_HEADER; - } - if ($header) { - require 'src/View/Site/tpl/head.php'; - } + if ( empty( $this->head['title'] ) ) { + $this->head['title'] = Config::TITLE_HEADER; + } + if ( empty( $this->head['description'] ) ) { + $this->head['description'] = Config::DESCRIPTION_HEADER; + } + if ( $header ) { + require 'src/View/Site/tpl/head.php'; + } - require 'src/View/'.str_replace('\\', '/', preg_replace('#^' . Config::NAMESPACE . '\\\Controller\\\#', '', get_class($this))) . '.php'; + require 'src/View/' . str_replace( '\\', '/', preg_replace( '#^' . Config::NAMESPACE . '\\\Controller\\\#', '', get_class( $this ) ) ) . '.php'; - if ($footer) { - require 'src/View/Site/tpl/footer.php'; - } + if ( $footer ) { + require 'src/View/Site/tpl/footer.php'; + } - ob_end_flush(); - exit(); - } + ob_end_flush(); + exit(); + } - protected function addHead($head) { - $this->head += $head; - } + protected function addHead( $head ) { + $this->head += $head; + } - protected function addData($data) { - $this->data += $data; - } + protected function addData( $data ) { + $this->data += $data; + } - protected function addFooter($footer) { - $this->footer += $footer; - } + protected function addFooter( $footer ) { + $this->footer += $footer; + } - /** - * @param $data - */ - protected function returnJson($data) { - header('Content-Type: application/json'); - echo json_encode($data); - exit(); - } + /** + * @param $data + */ + protected function returnJson( $data ) { + header( 'Content-Type: application/json' ); + echo json_encode( $data ); + exit(); + } - protected function throwError($msg, $code = '') { - header('Content-Type: application/json'); - echo json_encode(['status' => 'echec', 'msg' => $msg, 'code' => $code], JSON_PRETTY_PRINT); - exit(); - } + protected function throwError( $msg, $code = '' ) { + header( 'Content-Type: application/json' ); + echo json_encode( [ 'status' => 'echec', 'msg' => $msg, 'code' => $code ], JSON_PRETTY_PRINT ); + exit(); + } } ?> \ No newline at end of file diff --git a/src/Controller/Error.php b/src/Controller/Error.php index ed3feff..c6e1cd5 100644 --- a/src/Controller/Error.php +++ b/src/Controller/Error.php @@ -4,95 +4,96 @@ namespace base\Controller; class Error extends Controller { - /** - * Error constructor. - * @param int $ErrCode - * @param string $message - */ - public function __construct($ErrCode = 500, $message = '') { + /** + * Error constructor. + * + * @param int $ErrCode + * @param string $message + */ + public function __construct( $ErrCode = 500, $message = '' ) { - parent::__construct(); + parent::__construct(); - $tabCode = [ - //Informational 1xx - 100 => ['label' => '100 Continue', 'msg' => '100 Continue'], - 101 => ['label' => '101 Switching Protocols', 'msg' => '101 Switching Protocols'], - //Successful 2xx - 200 => ['label' => '200 OK', 'msg' => '200 OK'], - 201 => ['label' => '201 Created', 'msg' => '201 Created'], - 202 => ['label' => '202 Accepted', 'msg' => '202 Accepted'], - 203 => ['label' => '203 Non-Authoritative Information', 'msg' => '203 Non-Authoritative Information'], - 204 => ['label' => '204 No Content', 'msg' => '204 No Content'], - 205 => ['label' => '205 Reset Content', 'msg' => '205 Reset Content'], - 206 => ['label' => '206 Partial Content', 'msg' => '206 Partial Content'], - 226 => ['label' => '226 IM Used', 'msg' => '226 IM Used'], - //Redirection 3xx - 300 => ['label' => '300 Multiple Choices', 'msg' => '300 Multiple Choices'], - 301 => ['label' => '301 Moved Permanently', 'msg' => '301 Moved Permanently'], - 302 => ['label' => '302 Found', 'msg' => '302 Found'], - 303 => ['label' => '303 See Other', 'msg' => '303 See Other'], - 304 => ['label' => '304 Not Modified', 'msg' => '304 Not Modified'], - 305 => ['label' => '305 Use Proxy', 'msg' => '305 Use Proxy'], - 306 => ['label' => '306 (Unused)', 'msg' => '306 (Unused)'], - 307 => ['label' => '307 Temporary Redirect', 'msg' => '307 Temporary Redirect'], - //Client Error 4xx - 400 => ['label' => '400 Bad Request', 'msg' => '400 Bad Request'], - 401 => ['label' => '401 Unauthorized', 'msg' => 'Vous n\'êtes pas autorisé à accéder à cette page'], - 402 => ['label' => '402 Payment Required', 'msg' => '402 Payment Required'], - 403 => ['label' => '403 Forbidden', 'msg' => '403 Forbidden'], - 404 => ['label' => '404 Not Found', 'msg' => 'Page non trouvée'], - 405 => ['label' => '405 Method Not Allowed', 'msg' => '405 Method Not Allowed'], - 406 => ['label' => '406 Not Acceptable', 'msg' => '406 Not Acceptable'], - 407 => ['label' => '407 Proxy Authentication Required', 'msg' => '407 Proxy Authentication Required'], - 408 => ['label' => '408 Request Timeout', 'msg' => '408 Request Timeout'], - 409 => ['label' => '409 Conflict', 'msg' => '409 Conflict'], - 410 => ['label' => '410 Gone', 'msg' => '410 Gone'], - 411 => ['label' => '411 Length Required', 'msg' => '411 Length Required'], - 412 => ['label' => '412 Precondition Failed', 'msg' => '412 Precondition Failed'], - 413 => ['label' => '413 Request Entity Too Large', 'msg' => '413 Request Entity Too Large'], - 414 => ['label' => '414 Request-URI Too Long', 'msg' => '414 Request-URI Too Long'], - 415 => ['label' => '415 Unsupported Media Type', 'msg' => '415 Unsupported Media Type'], - 416 => ['label' => '416 Requested Range Not Satisfiable', 'msg' => '416 Requested Range Not Satisfiable'], - 417 => ['label' => '417 Expectation Failed', 'msg' => '417 Expectation Failed'], - 418 => ['label' => '418 I\'m a teapot', 'msg' => '418 I\'m a teapot'], - 422 => ['label' => '422 Unprocessable Entity', 'msg' => '422 Unprocessable Entity'], - 423 => ['label' => '423 Locked', 'msg' => '423 Locked'], - 426 => ['label' => '426 Upgrade Required', 'msg' => '426 Upgrade Required'], - 428 => ['label' => '428 Precondition Required', 'msg' => '428 Precondition Required'], - 429 => ['label' => '429 Too Many Requests', 'msg' => '429 Too Many Requests'], - 431 => ['label' => '431 Request Header Fields Too Large', 'msg' => '431 Request Header Fields Too Large'], - //Server Error 5xx - 500 => ['label' => '500 Internal Server Error', 'msg' => 'Une erreur est survenue'], - 501 => ['label' => '501 Not Implemented', 'msg' => '501 Not Implemented'], - 502 => ['label' => '502 Bad Gateway', 'msg' => '502 Bad Gateway'], - 503 => ['label' => '503 Service Unavailable', 'msg' => '503 Service Unavailable'], - 504 => ['label' => '504 Gateway Timeout', 'msg' => '504 Gateway Timeout'], - 505 => ['label' => '505 HTTP Version Not Supported', 'msg' => '505 HTTP Version Not Supported'], - 506 => ['label' => '506 Variant Also Negotiates', 'msg' => '506 Variant Also Negotiates'], - 510 => ['label' => '510 Not Extended', 'msg' => '510 Not Extended'], - 511 => ['label' => '511 Network Authentication Required', 'msg' => '511 Network Authentication Required'] - ]; + $tabCode = [ + //Informational 1xx + 100 => [ 'label' => '100 Continue', 'msg' => '100 Continue' ], + 101 => [ 'label' => '101 Switching Protocols', 'msg' => '101 Switching Protocols' ], + //Successful 2xx + 200 => [ 'label' => '200 OK', 'msg' => '200 OK' ], + 201 => [ 'label' => '201 Created', 'msg' => '201 Created' ], + 202 => [ 'label' => '202 Accepted', 'msg' => '202 Accepted' ], + 203 => [ 'label' => '203 Non-Authoritative Information', 'msg' => '203 Non-Authoritative Information' ], + 204 => [ 'label' => '204 No Content', 'msg' => '204 No Content' ], + 205 => [ 'label' => '205 Reset Content', 'msg' => '205 Reset Content' ], + 206 => [ 'label' => '206 Partial Content', 'msg' => '206 Partial Content' ], + 226 => [ 'label' => '226 IM Used', 'msg' => '226 IM Used' ], + //Redirection 3xx + 300 => [ 'label' => '300 Multiple Choices', 'msg' => '300 Multiple Choices' ], + 301 => [ 'label' => '301 Moved Permanently', 'msg' => '301 Moved Permanently' ], + 302 => [ 'label' => '302 Found', 'msg' => '302 Found' ], + 303 => [ 'label' => '303 See Other', 'msg' => '303 See Other' ], + 304 => [ 'label' => '304 Not Modified', 'msg' => '304 Not Modified' ], + 305 => [ 'label' => '305 Use Proxy', 'msg' => '305 Use Proxy' ], + 306 => [ 'label' => '306 (Unused)', 'msg' => '306 (Unused)' ], + 307 => [ 'label' => '307 Temporary Redirect', 'msg' => '307 Temporary Redirect' ], + //Client Error 4xx + 400 => [ 'label' => '400 Bad Request', 'msg' => '400 Bad Request' ], + 401 => [ 'label' => '401 Unauthorized', 'msg' => 'Vous n\'êtes pas autorisé à accéder à cette page' ], + 402 => [ 'label' => '402 Payment Required', 'msg' => '402 Payment Required' ], + 403 => [ 'label' => '403 Forbidden', 'msg' => '403 Forbidden' ], + 404 => [ 'label' => '404 Not Found', 'msg' => 'Page non trouvée' ], + 405 => [ 'label' => '405 Method Not Allowed', 'msg' => '405 Method Not Allowed' ], + 406 => [ 'label' => '406 Not Acceptable', 'msg' => '406 Not Acceptable' ], + 407 => [ 'label' => '407 Proxy Authentication Required', 'msg' => '407 Proxy Authentication Required' ], + 408 => [ 'label' => '408 Request Timeout', 'msg' => '408 Request Timeout' ], + 409 => [ 'label' => '409 Conflict', 'msg' => '409 Conflict' ], + 410 => [ 'label' => '410 Gone', 'msg' => '410 Gone' ], + 411 => [ 'label' => '411 Length Required', 'msg' => '411 Length Required' ], + 412 => [ 'label' => '412 Precondition Failed', 'msg' => '412 Precondition Failed' ], + 413 => [ 'label' => '413 Request Entity Too Large', 'msg' => '413 Request Entity Too Large' ], + 414 => [ 'label' => '414 Request-URI Too Long', 'msg' => '414 Request-URI Too Long' ], + 415 => [ 'label' => '415 Unsupported Media Type', 'msg' => '415 Unsupported Media Type' ], + 416 => [ 'label' => '416 Requested Range Not Satisfiable', 'msg' => '416 Requested Range Not Satisfiable' ], + 417 => [ 'label' => '417 Expectation Failed', 'msg' => '417 Expectation Failed' ], + 418 => [ 'label' => '418 I\'m a teapot', 'msg' => '418 I\'m a teapot' ], + 422 => [ 'label' => '422 Unprocessable Entity', 'msg' => '422 Unprocessable Entity' ], + 423 => [ 'label' => '423 Locked', 'msg' => '423 Locked' ], + 426 => [ 'label' => '426 Upgrade Required', 'msg' => '426 Upgrade Required' ], + 428 => [ 'label' => '428 Precondition Required', 'msg' => '428 Precondition Required' ], + 429 => [ 'label' => '429 Too Many Requests', 'msg' => '429 Too Many Requests' ], + 431 => [ 'label' => '431 Request Header Fields Too Large', 'msg' => '431 Request Header Fields Too Large' ], + //Server Error 5xx + 500 => [ 'label' => '500 Internal Server Error', 'msg' => 'Une erreur est survenue' ], + 501 => [ 'label' => '501 Not Implemented', 'msg' => '501 Not Implemented' ], + 502 => [ 'label' => '502 Bad Gateway', 'msg' => '502 Bad Gateway' ], + 503 => [ 'label' => '503 Service Unavailable', 'msg' => '503 Service Unavailable' ], + 504 => [ 'label' => '504 Gateway Timeout', 'msg' => '504 Gateway Timeout' ], + 505 => [ 'label' => '505 HTTP Version Not Supported', 'msg' => '505 HTTP Version Not Supported' ], + 506 => [ 'label' => '506 Variant Also Negotiates', 'msg' => '506 Variant Also Negotiates' ], + 510 => [ 'label' => '510 Not Extended', 'msg' => '510 Not Extended' ], + 511 => [ 'label' => '511 Network Authentication Required', 'msg' => '511 Network Authentication Required' ] + ]; - if (isset($tabCode[$ErrCode])) { - header($_SERVER['SERVER_PROTOCOL'] . ' ' . $tabCode[$ErrCode]['label']); - if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { - $this->throwError($tabCode[$ErrCode]['msg'], 'Erreur_' . $ErrCode); - } - $erreur = $tabCode[$ErrCode]['msg']; - $this->addData(['erreur' => $erreur]); - } else { - header($_SERVER['SERVER_PROTOCOL'] . ' ' . $tabCode[500]['label']); - if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { - $this->throwError($tabCode[500]['msg'], 'Erreur_500'); - } - $erreur = $tabCode[500]['msg']; - $this->addData(['erreur' => $erreur]); - } + if ( isset( $tabCode[ $ErrCode ] ) ) { + header( $_SERVER['SERVER_PROTOCOL'] . ' ' . $tabCode[ $ErrCode ]['label'] ); + if ( isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'xmlhttprequest' ) { + $this->throwError( $tabCode[ $ErrCode ]['msg'], 'Erreur_' . $ErrCode ); + } + $erreur = $tabCode[ $ErrCode ]['msg']; + $this->addData( [ 'erreur' => $erreur ] ); + } else { + header( $_SERVER['SERVER_PROTOCOL'] . ' ' . $tabCode[500]['label'] ); + if ( isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'xmlhttprequest' ) { + $this->throwError( $tabCode[500]['msg'], 'Erreur_500' ); + } + $erreur = $tabCode[500]['msg']; + $this->addData( [ 'erreur' => $erreur ] ); + } - if (!empty($message)) { - $this->addData(['message' => $message]); - } + if ( ! empty( $message ) ) { + $this->addData( [ 'message' => $message ] ); + } - $this->view(); - } + $this->view(); + } } \ No newline at end of file diff --git a/src/Controller/site/Index.php b/src/Controller/site/Index.php index 2fc9dd9..8859220 100644 --- a/src/Controller/site/Index.php +++ b/src/Controller/site/Index.php @@ -6,22 +6,22 @@ use base\Controller\ControllerSite; class Index extends ControllerSite { - /** - * Index constructor. - */ - public function __construct() { - parent::__construct(); + /** + * Index constructor. + */ + public function __construct() { + parent::__construct(); - $this->addHead([ - ]); + $this->addHead( [ + ] ); - $this->addFooter([ + $this->addFooter( [ - ]); + ] ); - $this->addData([]); - $this->view(); - } + $this->addData( [] ); + $this->view(); + } } ?> \ No newline at end of file diff --git a/src/Controller/site/SiteError.php b/src/Controller/site/SiteError.php index 8c7f788..6be6933 100644 --- a/src/Controller/site/SiteError.php +++ b/src/Controller/site/SiteError.php @@ -6,100 +6,101 @@ use base\Controller\ControllerSite; class SiteError extends ControllerSite { - /** - * SiteError constructor. - * @param int $ErrCode - * @param string $message - */ - public function __construct($ErrCode = 500, $message = '') { - parent::__construct(); + /** + * SiteError constructor. + * + * @param int $ErrCode + * @param string $message + */ + public function __construct( $ErrCode = 500, $message = '' ) { + parent::__construct(); - $tabCode = [ - //Informational 1xx - 100 => ['label' => '100 Continue', 'msg' => '100 Continue'], - 101 => ['label' => '101 Switching Protocols', 'msg' => '101 Switching Protocols'], - //Successful 2xx - 200 => ['label' => '200 OK', 'msg' => '200 OK'], - 201 => ['label' => '201 Created', 'msg' => '201 Created'], - 202 => ['label' => '202 Accepted', 'msg' => '202 Accepted'], - 203 => ['label' => '203 Non-Authoritative Information', 'msg' => '203 Non-Authoritative Information'], - 204 => ['label' => '204 No Content', 'msg' => '204 No Content'], - 205 => ['label' => '205 Reset Content', 'msg' => '205 Reset Content'], - 206 => ['label' => '206 Partial Content', 'msg' => '206 Partial Content'], - 226 => ['label' => '226 IM Used', 'msg' => '226 IM Used'], - //Redirection 3xx - 300 => ['label' => '300 Multiple Choices', 'msg' => '300 Multiple Choices'], - 301 => ['label' => '301 Moved Permanently', 'msg' => '301 Moved Permanently'], - 302 => ['label' => '302 Found', 'msg' => '302 Found'], - 303 => ['label' => '303 See Other', 'msg' => '303 See Other'], - 304 => ['label' => '304 Not Modified', 'msg' => '304 Not Modified'], - 305 => ['label' => '305 Use Proxy', 'msg' => '305 Use Proxy'], - 306 => ['label' => '306 (Unused)', 'msg' => '306 (Unused)'], - 307 => ['label' => '307 Temporary Redirect', 'msg' => '307 Temporary Redirect'], - //Client Error 4xx - 400 => ['label' => '400 Bad Request', 'msg' => '400 Bad Request'], - 401 => ['label' => '401 Unauthorized', 'msg' => '401 Unauthorized'], - 402 => ['label' => '402 Payment Required', 'msg' => '402 Payment Required'], - 403 => ['label' => '403 Forbidden', 'msg' => '403 Forbidden'], - 404 => ['label' => '404 Not Found', 'msg' => 'Page non trouvée'], - 405 => ['label' => '405 Method Not Allowed', 'msg' => '405 Method Not Allowed'], - 406 => ['label' => '406 Not Acceptable', 'msg' => '406 Not Acceptable'], - 407 => ['label' => '407 Proxy Authentication Required', 'msg' => '407 Proxy Authentication Required'], - 408 => ['label' => '408 Request Timeout', 'msg' => '408 Request Timeout'], - 409 => ['label' => '409 Conflict', 'msg' => '409 Conflict'], - 410 => ['label' => '410 Gone', 'msg' => 'Cet artisan n\'est plus plus référencé sur Eldotravo'], - 411 => ['label' => '411 Length Required', 'msg' => '411 Length Required'], - 412 => ['label' => '412 Precondition Failed', 'msg' => '412 Precondition Failed'], - 413 => ['label' => '413 Request Entity Too Large', 'msg' => '413 Request Entity Too Large'], - 414 => ['label' => '414 Request-URI Too Long', 'msg' => '414 Request-URI Too Long'], - 415 => ['label' => '415 Unsupported Media Type', 'msg' => '415 Unsupported Media Type'], - 416 => ['label' => '416 Requested Range Not Satisfiable', 'msg' => '416 Requested Range Not Satisfiable'], - 417 => ['label' => '417 Expectation Failed', 'msg' => '417 Expectation Failed'], - 418 => ['label' => '418 I\'m a teapot', 'msg' => '418 I\'m a teapot'], - 422 => ['label' => '422 Unprocessable Entity', 'msg' => '422 Unprocessable Entity'], - 423 => ['label' => '423 Locked', 'msg' => '423 Locked'], - 426 => ['label' => '426 Upgrade Required', 'msg' => '426 Upgrade Required'], - 428 => ['label' => '428 Precondition Required', 'msg' => '428 Precondition Required'], - 429 => ['label' => '429 Too Many Requests', 'msg' => '429 Too Many Requests'], - 431 => ['label' => '431 Request Header Fields Too Large', 'msg' => '431 Request Header Fields Too Large'], - //Server Error 5xx - 500 => ['label' => '500 Internal Server Error', 'msg' => 'Une erreur est survenue'], - 501 => ['label' => '501 Not Implemented', 'msg' => '501 Not Implemented'], - 502 => ['label' => '502 Bad Gateway', 'msg' => '502 Bad Gateway'], - 503 => ['label' => '503 Service Unavailable', 'msg' => '503 Service Unavailable'], - 504 => ['label' => '504 Gateway Timeout', 'msg' => '504 Gateway Timeout'], - 505 => ['label' => '505 HTTP Version Not Supported', 'msg' => '505 HTTP Version Not Supported'], - 506 => ['label' => '506 Variant Also Negotiates', 'msg' => '506 Variant Also Negotiates'], - 510 => ['label' => '510 Not Extended', 'msg' => '510 Not Extended'], - 511 => ['label' => '511 Network Authentication Required', 'msg' => '511 Network Authentication Required'] - ]; + $tabCode = [ + //Informational 1xx + 100 => [ 'label' => '100 Continue', 'msg' => '100 Continue' ], + 101 => [ 'label' => '101 Switching Protocols', 'msg' => '101 Switching Protocols' ], + //Successful 2xx + 200 => [ 'label' => '200 OK', 'msg' => '200 OK' ], + 201 => [ 'label' => '201 Created', 'msg' => '201 Created' ], + 202 => [ 'label' => '202 Accepted', 'msg' => '202 Accepted' ], + 203 => [ 'label' => '203 Non-Authoritative Information', 'msg' => '203 Non-Authoritative Information' ], + 204 => [ 'label' => '204 No Content', 'msg' => '204 No Content' ], + 205 => [ 'label' => '205 Reset Content', 'msg' => '205 Reset Content' ], + 206 => [ 'label' => '206 Partial Content', 'msg' => '206 Partial Content' ], + 226 => [ 'label' => '226 IM Used', 'msg' => '226 IM Used' ], + //Redirection 3xx + 300 => [ 'label' => '300 Multiple Choices', 'msg' => '300 Multiple Choices' ], + 301 => [ 'label' => '301 Moved Permanently', 'msg' => '301 Moved Permanently' ], + 302 => [ 'label' => '302 Found', 'msg' => '302 Found' ], + 303 => [ 'label' => '303 See Other', 'msg' => '303 See Other' ], + 304 => [ 'label' => '304 Not Modified', 'msg' => '304 Not Modified' ], + 305 => [ 'label' => '305 Use Proxy', 'msg' => '305 Use Proxy' ], + 306 => [ 'label' => '306 (Unused)', 'msg' => '306 (Unused)' ], + 307 => [ 'label' => '307 Temporary Redirect', 'msg' => '307 Temporary Redirect' ], + //Client Error 4xx + 400 => [ 'label' => '400 Bad Request', 'msg' => '400 Bad Request' ], + 401 => [ 'label' => '401 Unauthorized', 'msg' => '401 Unauthorized' ], + 402 => [ 'label' => '402 Payment Required', 'msg' => '402 Payment Required' ], + 403 => [ 'label' => '403 Forbidden', 'msg' => '403 Forbidden' ], + 404 => [ 'label' => '404 Not Found', 'msg' => 'Page non trouvée' ], + 405 => [ 'label' => '405 Method Not Allowed', 'msg' => '405 Method Not Allowed' ], + 406 => [ 'label' => '406 Not Acceptable', 'msg' => '406 Not Acceptable' ], + 407 => [ 'label' => '407 Proxy Authentication Required', 'msg' => '407 Proxy Authentication Required' ], + 408 => [ 'label' => '408 Request Timeout', 'msg' => '408 Request Timeout' ], + 409 => [ 'label' => '409 Conflict', 'msg' => '409 Conflict' ], + 410 => [ 'label' => '410 Gone', 'msg' => 'Cet artisan n\'est plus plus référencé sur Eldotravo' ], + 411 => [ 'label' => '411 Length Required', 'msg' => '411 Length Required' ], + 412 => [ 'label' => '412 Precondition Failed', 'msg' => '412 Precondition Failed' ], + 413 => [ 'label' => '413 Request Entity Too Large', 'msg' => '413 Request Entity Too Large' ], + 414 => [ 'label' => '414 Request-URI Too Long', 'msg' => '414 Request-URI Too Long' ], + 415 => [ 'label' => '415 Unsupported Media Type', 'msg' => '415 Unsupported Media Type' ], + 416 => [ 'label' => '416 Requested Range Not Satisfiable', 'msg' => '416 Requested Range Not Satisfiable' ], + 417 => [ 'label' => '417 Expectation Failed', 'msg' => '417 Expectation Failed' ], + 418 => [ 'label' => '418 I\'m a teapot', 'msg' => '418 I\'m a teapot' ], + 422 => [ 'label' => '422 Unprocessable Entity', 'msg' => '422 Unprocessable Entity' ], + 423 => [ 'label' => '423 Locked', 'msg' => '423 Locked' ], + 426 => [ 'label' => '426 Upgrade Required', 'msg' => '426 Upgrade Required' ], + 428 => [ 'label' => '428 Precondition Required', 'msg' => '428 Precondition Required' ], + 429 => [ 'label' => '429 Too Many Requests', 'msg' => '429 Too Many Requests' ], + 431 => [ 'label' => '431 Request Header Fields Too Large', 'msg' => '431 Request Header Fields Too Large' ], + //Server Error 5xx + 500 => [ 'label' => '500 Internal Server Error', 'msg' => 'Une erreur est survenue' ], + 501 => [ 'label' => '501 Not Implemented', 'msg' => '501 Not Implemented' ], + 502 => [ 'label' => '502 Bad Gateway', 'msg' => '502 Bad Gateway' ], + 503 => [ 'label' => '503 Service Unavailable', 'msg' => '503 Service Unavailable' ], + 504 => [ 'label' => '504 Gateway Timeout', 'msg' => '504 Gateway Timeout' ], + 505 => [ 'label' => '505 HTTP Version Not Supported', 'msg' => '505 HTTP Version Not Supported' ], + 506 => [ 'label' => '506 Variant Also Negotiates', 'msg' => '506 Variant Also Negotiates' ], + 510 => [ 'label' => '510 Not Extended', 'msg' => '510 Not Extended' ], + 511 => [ 'label' => '511 Network Authentication Required', 'msg' => '511 Network Authentication Required' ] + ]; - if (isset($tabCode[$ErrCode])) { - header($_SERVER['SERVER_PROTOCOL'].' '.$tabCode[$ErrCode]['label']); - if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { - $this->throwError('Erreur '.$tabCode[$ErrCode]['label'], 'Erreur_'.$ErrCode); - } - $erreur = $tabCode[$ErrCode]['msg']; - $this->addData(['erreur' => $erreur]); - } else { - header($_SERVER['SERVER_PROTOCOL'].' '.$tabCode[500]['label']); - if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') { - $this->throwError('Erreur '.$tabCode[500]['label'], 'Erreur_500'); - } - $erreur = $tabCode[500]['msg']; - $this->addData(['erreur' => $erreur]); - } + if ( isset( $tabCode[ $ErrCode ] ) ) { + header( $_SERVER['SERVER_PROTOCOL'] . ' ' . $tabCode[ $ErrCode ]['label'] ); + if ( isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'xmlhttprequest' ) { + $this->throwError( 'Erreur ' . $tabCode[ $ErrCode ]['label'], 'Erreur_' . $ErrCode ); + } + $erreur = $tabCode[ $ErrCode ]['msg']; + $this->addData( [ 'erreur' => $erreur ] ); + } else { + header( $_SERVER['SERVER_PROTOCOL'] . ' ' . $tabCode[500]['label'] ); + if ( isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'xmlhttprequest' ) { + $this->throwError( 'Erreur ' . $tabCode[500]['label'], 'Erreur_500' ); + } + $erreur = $tabCode[500]['msg']; + $this->addData( [ 'erreur' => $erreur ] ); + } - if (!empty($message)) { - $this->addData(['message' => $message]); - } + if ( ! empty( $message ) ) { + $this->addData( [ 'message' => $message ] ); + } - $this->addHead([ - 'title' => 'Un erreur est survenue', - 'description' => 'Oops une erreur est survenue', - 'robotNoIndex' => true - ]); + $this->addHead( [ + 'title' => 'Un erreur est survenue', + 'description' => 'Oops une erreur est survenue', + 'robotNoIndex' => true + ] ); - $this->view(); - } + $this->view(); + } } \ No newline at end of file diff --git a/src/Controller/site/SiteRouter.php b/src/Controller/site/SiteRouter.php index 4e034a3..f6634ab 100644 --- a/src/Controller/site/SiteRouter.php +++ b/src/Controller/site/SiteRouter.php @@ -4,20 +4,21 @@ namespace base\Controller\Site; class SiteRouter { - /** - * SiteRouter constructor. - * @param $pages - */ - public function __construct($pages) { + /** + * SiteRouter constructor. + * + * @param $pages + */ + public function __construct( $pages ) { - set_error_handler('errorHandler'); + set_error_handler( 'errorHandler' ); - if ($pages[0] == '') { - new Index(); - } else { - new SiteError(404); - } - } + if ( $pages[0] == '' ) { + new Index(); + } else { + new SiteError( 404 ); + } + } } ?> \ No newline at end of file diff --git a/src/Model/BDD.php b/src/Model/BDD.php index 2011b62..6ebd773 100644 --- a/src/Model/BDD.php +++ b/src/Model/BDD.php @@ -6,36 +6,35 @@ use Exception; use PDO; class BDD { - const SQL_SERVER = 'localhost'; // BDD Server - const SQL_LOGIN = 'root'; // BDD Login - const SQL_PASSWORD = ''; // BDD Password - const SQL_DB = 'base'; // BDD Name + const SQL_SERVER = 'localhost'; // BDD Server + const SQL_LOGIN = 'root'; // BDD Login + const SQL_PASSWORD = ''; // BDD Password + const SQL_DB = 'base'; // BDD Name - private static $bdd; + private static $bdd; - public function __construct() { - try { - $pdo_options = [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING, - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC - ]; + public function __construct() { + try { + $pdo_options = [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING, + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC + ]; - self::$bdd = new PDO('mysql:host=' . self::SQL_SERVER . ';dbname=' . self::SQL_DB.';charset=utf8', - self::SQL_LOGIN, self::SQL_PASSWORD, $pdo_options); - } - catch (Exception $e) { - die('Erreur : ' . $e->getMessage()); - } - } + self::$bdd = new PDO( 'mysql:host=' . self::SQL_SERVER . ';dbname=' . self::SQL_DB . ';charset=utf8', + self::SQL_LOGIN, self::SQL_PASSWORD, $pdo_options ); + } catch ( Exception $e ) { + die( 'Erreur : ' . $e->getMessage() ); + } + } - public static function instance() { - return self::$bdd; - } + public static function instance() { + return self::$bdd; + } - public static function lastInsertId() { - return self::$bdd->lastInsertId(); - } + public static function lastInsertId() { + return self::$bdd->lastInsertId(); + } } ?> \ No newline at end of file diff --git a/src/Model/BDTables.php b/src/Model/BDTables.php index 40fe21b..4637160 100644 --- a/src/Model/BDTables.php +++ b/src/Model/BDTables.php @@ -4,10 +4,10 @@ namespace base\Model; abstract class BDTables { - // const NOM = 'bd_nom' - // Ex : const ABONNEMENT = 'abonnement'; + // const NOM = 'bd_nom' + // Ex : const ABONNEMENT = 'abonnement'; - const LOGS = "logs"; + const LOGS = "logs"; } ?> \ No newline at end of file diff --git a/src/Model/FPDF.php b/src/Model/FPDF.php index 6669470..843e1ce 100644 --- a/src/Model/FPDF.php +++ b/src/Model/FPDF.php @@ -1,1899 +1,1913 @@ _dochecks(); - // Initialization of properties - $this->state = 0; - $this->page = 0; - $this->n = 2; - $this->buffer = ''; - $this->pages = array(); - $this->PageInfo = array(); - $this->fonts = array(); - $this->FontFiles = array(); - $this->encodings = array(); - $this->cmaps = array(); - $this->images = array(); - $this->links = array(); - $this->InHeader = false; - $this->InFooter = false; - $this->lasth = 0; - $this->FontFamily = ''; - $this->FontStyle = ''; - $this->FontSizePt = 12; - $this->underline = false; - $this->DrawColor = '0 G'; - $this->FillColor = '0 g'; - $this->TextColor = '0 g'; - $this->ColorFlag = false; - $this->WithAlpha = false; - $this->ws = 0; - // Font path - if(defined('FPDF_FONTPATH')) - { - $this->fontpath = FPDF_FONTPATH; - if(substr($this->fontpath,-1)!='/' && substr($this->fontpath,-1)!='\\') - $this->fontpath .= '/'; + function __construct( $orientation = 'P', $unit = 'mm', $size = 'A4' ) { + // Some checks + $this->_dochecks(); + // Initialization of properties + $this->state = 0; + $this->page = 0; + $this->n = 2; + $this->buffer = ''; + $this->pages = array(); + $this->PageInfo = array(); + $this->fonts = array(); + $this->FontFiles = array(); + $this->encodings = array(); + $this->cmaps = array(); + $this->images = array(); + $this->links = array(); + $this->InHeader = false; + $this->InFooter = false; + $this->lasth = 0; + $this->FontFamily = ''; + $this->FontStyle = ''; + $this->FontSizePt = 12; + $this->underline = false; + $this->DrawColor = '0 G'; + $this->FillColor = '0 g'; + $this->TextColor = '0 g'; + $this->ColorFlag = false; + $this->WithAlpha = false; + $this->ws = 0; + // Font path + if ( defined( 'FPDF_FONTPATH' ) ) { + $this->fontpath = FPDF_FONTPATH; + if ( substr( $this->fontpath, - 1 ) != '/' && substr( $this->fontpath, - 1 ) != '\\' ) { + $this->fontpath .= '/'; + } + } elseif ( is_dir( dirname( __FILE__ ) . '/font' ) ) { + $this->fontpath = dirname( __FILE__ ) . '/font/'; + } else { + $this->fontpath = ''; + } + // Core fonts + $this->CoreFonts = array( 'courier', 'helvetica', 'times', 'symbol', 'zapfdingbats' ); + // Scale factor + if ( $unit == 'pt' ) { + $this->k = 1; + } elseif ( $unit == 'mm' ) { + $this->k = 72 / 25.4; + } elseif ( $unit == 'cm' ) { + $this->k = 72 / 2.54; + } elseif ( $unit == 'in' ) { + $this->k = 72; + } else { + $this->Error( 'Incorrect unit: ' . $unit ); + } + // Page sizes + $this->StdPageSizes = array( + 'a3' => array( 841.89, 1190.55 ), + 'a4' => array( 595.28, 841.89 ), + 'a5' => array( 420.94, 595.28 ), + 'letter' => array( 612, 792 ), + 'legal' => array( 612, 1008 ) + ); + $size = $this->_getpagesize( $size ); + $this->DefPageSize = $size; + $this->CurPageSize = $size; + // Page orientation + $orientation = strtolower( $orientation ); + if ( $orientation == 'p' || $orientation == 'portrait' ) { + $this->DefOrientation = 'P'; + $this->w = $size[0]; + $this->h = $size[1]; + } elseif ( $orientation == 'l' || $orientation == 'landscape' ) { + $this->DefOrientation = 'L'; + $this->w = $size[1]; + $this->h = $size[0]; + } else { + $this->Error( 'Incorrect orientation: ' . $orientation ); + } + $this->CurOrientation = $this->DefOrientation; + $this->wPt = $this->w * $this->k; + $this->hPt = $this->h * $this->k; + // Page rotation + $this->CurRotation = 0; + // Page margins (1 cm) + $margin = 28.35 / $this->k; + $this->SetMargins( $margin, $margin ); + // Interior cell margin (1 mm) + $this->cMargin = $margin / 10; + // Line width (0.2 mm) + $this->LineWidth = .567 / $this->k; + // Automatic page break + $this->SetAutoPageBreak( true, 2 * $margin ); + // Default display mode + $this->SetDisplayMode( 'default' ); + // Enable compression + $this->SetCompression( true ); + // Set default PDF version number + $this->PDFVersion = '1.3'; } - elseif(is_dir(dirname(__FILE__).'/font')) - $this->fontpath = dirname(__FILE__).'/font/'; - else - $this->fontpath = ''; - // Core fonts - $this->CoreFonts = array('courier', 'helvetica', 'times', 'symbol', 'zapfdingbats'); - // Scale factor - if($unit=='pt') - $this->k = 1; - elseif($unit=='mm') - $this->k = 72/25.4; - elseif($unit=='cm') - $this->k = 72/2.54; - elseif($unit=='in') - $this->k = 72; - else - $this->Error('Incorrect unit: '.$unit); - // Page sizes - $this->StdPageSizes = array('a3'=>array(841.89,1190.55), 'a4'=>array(595.28,841.89), 'a5'=>array(420.94,595.28), - 'letter'=>array(612,792), 'legal'=>array(612,1008)); - $size = $this->_getpagesize($size); - $this->DefPageSize = $size; - $this->CurPageSize = $size; - // Page orientation - $orientation = strtolower($orientation); - if($orientation=='p' || $orientation=='portrait') - { - $this->DefOrientation = 'P'; - $this->w = $size[0]; - $this->h = $size[1]; + + /******************************************************************************* + * Protected methods * + *******************************************************************************/ + + protected function _dochecks() { + // Check mbstring overloading + if ( ini_get( 'mbstring.func_overload' ) & 2 ) { + $this->Error( 'mbstring overloading must be disabled' ); + } + // Ensure runtime magic quotes are disabled + if ( get_magic_quotes_runtime() ) { + @set_magic_quotes_runtime( 0 ); + } } - elseif($orientation=='l' || $orientation=='landscape') - { - $this->DefOrientation = 'L'; - $this->w = $size[1]; - $this->h = $size[0]; + + function Error( $msg ) { + // Fatal error + throw new Exception( 'FPDF error: ' . $msg ); } - else - $this->Error('Incorrect orientation: '.$orientation); - $this->CurOrientation = $this->DefOrientation; - $this->wPt = $this->w*$this->k; - $this->hPt = $this->h*$this->k; - // Page rotation - $this->CurRotation = 0; - // Page margins (1 cm) - $margin = 28.35/$this->k; - $this->SetMargins($margin,$margin); - // Interior cell margin (1 mm) - $this->cMargin = $margin/10; - // Line width (0.2 mm) - $this->LineWidth = .567/$this->k; - // Automatic page break - $this->SetAutoPageBreak(true,2*$margin); - // Default display mode - $this->SetDisplayMode('default'); - // Enable compression - $this->SetCompression(true); - // Set default PDF version number - $this->PDFVersion = '1.3'; -} -function SetMargins($left, $top, $right=null) -{ - // Set left, top and right margins - $this->lMargin = $left; - $this->tMargin = $top; - if($right===null) - $right = $left; - $this->rMargin = $right; -} + protected function _getpagesize( $size ) { + if ( is_string( $size ) ) { + $size = strtolower( $size ); + if ( ! isset( $this->StdPageSizes[ $size ] ) ) { + $this->Error( 'Unknown page size: ' . $size ); + } + $a = $this->StdPageSizes[ $size ]; -function SetLeftMargin($margin) -{ - // Set left margin - $this->lMargin = $margin; - if($this->page>0 && $this->x<$margin) - $this->x = $margin; -} + return array( $a[0] / $this->k, $a[1] / $this->k ); + } else { + if ( $size[0] > $size[1] ) { + return array( $size[1], $size[0] ); + } else { + return $size; + } + } + } -function SetTopMargin($margin) -{ - // Set top margin - $this->tMargin = $margin; -} + function SetMargins( $left, $top, $right = null ) { + // Set left, top and right margins + $this->lMargin = $left; + $this->tMargin = $top; + if ( $right === null ) { + $right = $left; + } + $this->rMargin = $right; + } -function SetRightMargin($margin) -{ - // Set right margin - $this->rMargin = $margin; -} + function SetAutoPageBreak( $auto, $margin = 0 ) { + // Set auto page break mode and triggering margin + $this->AutoPageBreak = $auto; + $this->bMargin = $margin; + $this->PageBreakTrigger = $this->h - $margin; + } -function SetAutoPageBreak($auto, $margin=0) -{ - // Set auto page break mode and triggering margin - $this->AutoPageBreak = $auto; - $this->bMargin = $margin; - $this->PageBreakTrigger = $this->h-$margin; -} + function SetDisplayMode( $zoom, $layout = 'default' ) { + // Set display mode in viewer + if ( $zoom == 'fullpage' || $zoom == 'fullwidth' || $zoom == 'real' || $zoom == 'default' || ! is_string( $zoom ) ) { + $this->ZoomMode = $zoom; + } else { + $this->Error( 'Incorrect zoom display mode: ' . $zoom ); + } + if ( $layout == 'single' || $layout == 'continuous' || $layout == 'two' || $layout == 'default' ) { + $this->LayoutMode = $layout; + } else { + $this->Error( 'Incorrect layout display mode: ' . $layout ); + } + } -function SetDisplayMode($zoom, $layout='default') -{ - // Set display mode in viewer - if($zoom=='fullpage' || $zoom=='fullwidth' || $zoom=='real' || $zoom=='default' || !is_string($zoom)) - $this->ZoomMode = $zoom; - else - $this->Error('Incorrect zoom display mode: '.$zoom); - if($layout=='single' || $layout=='continuous' || $layout=='two' || $layout=='default') - $this->LayoutMode = $layout; - else - $this->Error('Incorrect layout display mode: '.$layout); -} + function SetCompression( $compress ) { + // Set page compression + if ( function_exists( 'gzcompress' ) ) { + $this->compress = $compress; + } else { + $this->compress = false; + } + } -function SetCompression($compress) -{ - // Set page compression - if(function_exists('gzcompress')) - $this->compress = $compress; - else - $this->compress = false; -} + function SetLeftMargin( $margin ) { + // Set left margin + $this->lMargin = $margin; + if ( $this->page > 0 && $this->x < $margin ) { + $this->x = $margin; + } + } -function SetTitle($title, $isUTF8=false) -{ - // Title of document - $this->metadata['Title'] = $isUTF8 ? $title : utf8_encode($title); -} + function SetTopMargin( $margin ) { + // Set top margin + $this->tMargin = $margin; + } -function SetAuthor($author, $isUTF8=false) -{ - // Author of document - $this->metadata['Author'] = $isUTF8 ? $author : utf8_encode($author); -} + function SetRightMargin( $margin ) { + // Set right margin + $this->rMargin = $margin; + } -function SetSubject($subject, $isUTF8=false) -{ - // Subject of document - $this->metadata['Subject'] = $isUTF8 ? $subject : utf8_encode($subject); -} + function SetTitle( $title, $isUTF8 = false ) { + // Title of document + $this->metadata['Title'] = $isUTF8 ? $title : utf8_encode( $title ); + } -function SetKeywords($keywords, $isUTF8=false) -{ - // Keywords of document - $this->metadata['Keywords'] = $isUTF8 ? $keywords : utf8_encode($keywords); -} + function SetAuthor( $author, $isUTF8 = false ) { + // Author of document + $this->metadata['Author'] = $isUTF8 ? $author : utf8_encode( $author ); + } -function SetCreator($creator, $isUTF8=false) -{ - // Creator of document - $this->metadata['Creator'] = $isUTF8 ? $creator : utf8_encode($creator); -} + function SetSubject( $subject, $isUTF8 = false ) { + // Subject of document + $this->metadata['Subject'] = $isUTF8 ? $subject : utf8_encode( $subject ); + } -function AliasNbPages($alias='{nb}') -{ - // Define an alias for total number of pages - $this->AliasNbPages = $alias; -} + function SetKeywords( $keywords, $isUTF8 = false ) { + // Keywords of document + $this->metadata['Keywords'] = $isUTF8 ? $keywords : utf8_encode( $keywords ); + } -function Error($msg) -{ - // Fatal error - throw new Exception('FPDF error: '.$msg); -} + function SetCreator( $creator, $isUTF8 = false ) { + // Creator of document + $this->metadata['Creator'] = $isUTF8 ? $creator : utf8_encode( $creator ); + } -function Close() -{ - // Terminate document - if($this->state==3) - return; - if($this->page==0) - $this->AddPage(); - // Page footer - $this->InFooter = true; - $this->Footer(); - $this->InFooter = false; - // Close page - $this->_endpage(); - // Close document - $this->_enddoc(); -} + function AliasNbPages( $alias = '{nb}' ) { + // Define an alias for total number of pages + $this->AliasNbPages = $alias; + } -function AddPage($orientation='', $size='', $rotation=0) -{ - // Start a new page - if($this->state==3) - $this->Error('The document is closed'); - $family = $this->FontFamily; - $style = $this->FontStyle.($this->underline ? 'U' : ''); - $fontsize = $this->FontSizePt; - $lw = $this->LineWidth; - $dc = $this->DrawColor; - $fc = $this->FillColor; - $tc = $this->TextColor; - $cf = $this->ColorFlag; - if($this->page>0) - { + function PageNo() { + // Get current page number + return $this->page; + } + + function SetDrawColor( $r, $g = null, $b = null ) { + // Set color for all stroking operations + if ( ( $r == 0 && $g == 0 && $b == 0 ) || $g === null ) { + $this->DrawColor = sprintf( '%.3F G', $r / 255 ); + } else { + $this->DrawColor = sprintf( '%.3F %.3F %.3F RG', $r / 255, $g / 255, $b / 255 ); + } + if ( $this->page > 0 ) { + $this->_out( $this->DrawColor ); + } + } + + protected function _out( $s ) { + // Add a line to the document + if ( $this->state == 2 ) { + $this->pages[ $this->page ] .= $s . "\n"; + } elseif ( $this->state == 1 ) { + $this->_put( $s ); + } elseif ( $this->state == 0 ) { + $this->Error( 'No page has been added yet' ); + } elseif ( $this->state == 3 ) { + $this->Error( 'The document is closed' ); + } + } + + protected function _put( $s ) { + $this->buffer .= $s . "\n"; + } + + function SetFillColor( $r, $g = null, $b = null ) { + // Set color for all filling operations + if ( ( $r == 0 && $g == 0 && $b == 0 ) || $g === null ) { + $this->FillColor = sprintf( '%.3F g', $r / 255 ); + } else { + $this->FillColor = sprintf( '%.3F %.3F %.3F rg', $r / 255, $g / 255, $b / 255 ); + } + $this->ColorFlag = ( $this->FillColor != $this->TextColor ); + if ( $this->page > 0 ) { + $this->_out( $this->FillColor ); + } + } + + function SetTextColor( $r, $g = null, $b = null ) { + // Set color for text + if ( ( $r == 0 && $g == 0 && $b == 0 ) || $g === null ) { + $this->TextColor = sprintf( '%.3F g', $r / 255 ); + } else { + $this->TextColor = sprintf( '%.3F %.3F %.3F rg', $r / 255, $g / 255, $b / 255 ); + } + $this->ColorFlag = ( $this->FillColor != $this->TextColor ); + } + + function SetLineWidth( $width ) { + // Set line width + $this->LineWidth = $width; + if ( $this->page > 0 ) { + $this->_out( sprintf( '%.2F w', $width * $this->k ) ); + } + } + + function Line( $x1, $y1, $x2, $y2 ) { + // Draw a line + $this->_out( sprintf( '%.2F %.2F m %.2F %.2F l S', $x1 * $this->k, ( $this->h - $y1 ) * $this->k, $x2 * $this->k, ( $this->h - $y2 ) * $this->k ) ); + } + + function Rect( $x, $y, $w, $h, $style = '' ) { + // Draw a rectangle + if ( $style == 'F' ) { + $op = 'f'; + } elseif ( $style == 'FD' || $style == 'DF' ) { + $op = 'B'; + } else { + $op = 'S'; + } + $this->_out( sprintf( '%.2F %.2F %.2F %.2F re %s', $x * $this->k, ( $this->h - $y ) * $this->k, $w * $this->k, - $h * $this->k, $op ) ); + } + + function SetFontSize( $size ) { + // Set font size in points + if ( $this->FontSizePt == $size ) { + return; + } + $this->FontSizePt = $size; + $this->FontSize = $size / $this->k; + if ( $this->page > 0 ) { + $this->_out( sprintf( 'BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt ) ); + } + } + + function AddLink() { + // Create a new internal link + $n = count( $this->links ) + 1; + $this->links[ $n ] = array( 0, 0 ); + + return $n; + } + + function SetLink( $link, $y = 0, $page = - 1 ) { + // Set destination of internal link + if ( $y == - 1 ) { + $y = $this->y; + } + if ( $page == - 1 ) { + $page = $this->page; + } + $this->links[ $link ] = array( $page, $y ); + } + + function Text( $x, $y, $txt ) { + // Output a string + if ( ! isset( $this->CurrentFont ) ) { + $this->Error( 'No font has been set' ); + } + $s = sprintf( 'BT %.2F %.2F Td (%s) Tj ET', $x * $this->k, ( $this->h - $y ) * $this->k, $this->_escape( $txt ) ); + if ( $this->underline && $txt != '' ) { + $s .= ' ' . $this->_dounderline( $x, $y, $txt ); + } + if ( $this->ColorFlag ) { + $s = 'q ' . $this->TextColor . ' ' . $s . ' Q'; + } + $this->_out( $s ); + } + + protected function _escape( $s ) { + // Escape special characters + if ( strpos( $s, '(' ) !== false || strpos( $s, ')' ) !== false || strpos( $s, '\\' ) !== false || strpos( $s, "\r" ) !== false ) { + return str_replace( array( '\\', '(', ')', "\r" ), array( '\\\\', '\\(', '\\)', '\\r' ), $s ); + } else { + return $s; + } + } + + protected function _dounderline( $x, $y, $txt ) { + // Underline text + $up = $this->CurrentFont['up']; + $ut = $this->CurrentFont['ut']; + $w = $this->GetStringWidth( $txt ) + $this->ws * substr_count( $txt, ' ' ); + + return sprintf( '%.2F %.2F %.2F %.2F re f', $x * $this->k, ( $this->h - ( $y - $up / 1000 * $this->FontSize ) ) * $this->k, $w * $this->k, - $ut / 1000 * $this->FontSizePt ); + } + + function GetStringWidth( $s ) { + // Get width of a string in the current font + $s = (string) $s; + $cw = &$this->CurrentFont['cw']; + $w = 0; + $l = strlen( $s ); + for ( $i = 0; $i < $l; $i ++ ) { + $w += $cw[ $s[ $i ] ]; + } + + return $w * $this->FontSize / 1000; + } + + function MultiCell( $w, $h, $txt, $border = 0, $align = 'J', $fill = false ) { + // Output text with automatic or explicit line breaks + if ( ! isset( $this->CurrentFont ) ) { + $this->Error( 'No font has been set' ); + } + $cw = &$this->CurrentFont['cw']; + if ( $w == 0 ) { + $w = $this->w - $this->rMargin - $this->x; + } + $wmax = ( $w - 2 * $this->cMargin ) * 1000 / $this->FontSize; + $s = str_replace( "\r", '', $txt ); + $nb = strlen( $s ); + if ( $nb > 0 && $s[ $nb - 1 ] == "\n" ) { + $nb --; + } + $b = 0; + if ( $border ) { + if ( $border == 1 ) { + $border = 'LTRB'; + $b = 'LRT'; + $b2 = 'LR'; + } else { + $b2 = ''; + if ( strpos( $border, 'L' ) !== false ) { + $b2 .= 'L'; + } + if ( strpos( $border, 'R' ) !== false ) { + $b2 .= 'R'; + } + $b = ( strpos( $border, 'T' ) !== false ) ? $b2 . 'T' : $b2; + } + } + $sep = - 1; + $i = 0; + $j = 0; + $l = 0; + $ns = 0; + $nl = 1; + while ( $i < $nb ) { + // Get next character + $c = $s[ $i ]; + if ( $c == "\n" ) { + // Explicit line break + if ( $this->ws > 0 ) { + $this->ws = 0; + $this->_out( '0 Tw' ); + } + $this->Cell( $w, $h, substr( $s, $j, $i - $j ), $b, 2, $align, $fill ); + $i ++; + $sep = - 1; + $j = $i; + $l = 0; + $ns = 0; + $nl ++; + if ( $border && $nl == 2 ) { + $b = $b2; + } + continue; + } + if ( $c == ' ' ) { + $sep = $i; + $ls = $l; + $ns ++; + } + $l += $cw[ $c ]; + if ( $l > $wmax ) { + // Automatic line break + if ( $sep == - 1 ) { + if ( $i == $j ) { + $i ++; + } + if ( $this->ws > 0 ) { + $this->ws = 0; + $this->_out( '0 Tw' ); + } + $this->Cell( $w, $h, substr( $s, $j, $i - $j ), $b, 2, $align, $fill ); + } else { + if ( $align == 'J' ) { + $this->ws = ( $ns > 1 ) ? ( $wmax - $ls ) / 1000 * $this->FontSize / ( $ns - 1 ) : 0; + $this->_out( sprintf( '%.3F Tw', $this->ws * $this->k ) ); + } + $this->Cell( $w, $h, substr( $s, $j, $sep - $j ), $b, 2, $align, $fill ); + $i = $sep + 1; + } + $sep = - 1; + $j = $i; + $l = 0; + $ns = 0; + $nl ++; + if ( $border && $nl == 2 ) { + $b = $b2; + } + } else { + $i ++; + } + } + // Last chunk + if ( $this->ws > 0 ) { + $this->ws = 0; + $this->_out( '0 Tw' ); + } + if ( $border && strpos( $border, 'B' ) !== false ) { + $b .= 'B'; + } + $this->Cell( $w, $h, substr( $s, $j, $i - $j ), $b, 2, $align, $fill ); + $this->x = $this->lMargin; + } + + function Cell( $w, $h = 0, $txt = '', $border = 0, $ln = 0, $align = '', $fill = false, $link = '' ) { + // Output a cell + $k = $this->k; + if ( $this->y + $h > $this->PageBreakTrigger && ! $this->InHeader && ! $this->InFooter && $this->AcceptPageBreak() ) { + // Automatic page break + $x = $this->x; + $ws = $this->ws; + if ( $ws > 0 ) { + $this->ws = 0; + $this->_out( '0 Tw' ); + } + $this->AddPage( $this->CurOrientation, $this->CurPageSize, $this->CurRotation ); + $this->x = $x; + if ( $ws > 0 ) { + $this->ws = $ws; + $this->_out( sprintf( '%.3F Tw', $ws * $k ) ); + } + } + if ( $w == 0 ) { + $w = $this->w - $this->rMargin - $this->x; + } + $s = ''; + if ( $fill || $border == 1 ) { + if ( $fill ) { + $op = ( $border == 1 ) ? 'B' : 'f'; + } else { + $op = 'S'; + } + $s = sprintf( '%.2F %.2F %.2F %.2F re %s ', $this->x * $k, ( $this->h - $this->y ) * $k, $w * $k, - $h * $k, $op ); + } + if ( is_string( $border ) ) { + $x = $this->x; + $y = $this->y; + if ( strpos( $border, 'L' ) !== false ) { + $s .= sprintf( '%.2F %.2F m %.2F %.2F l S ', $x * $k, ( $this->h - $y ) * $k, $x * $k, ( $this->h - ( $y + $h ) ) * $k ); + } + if ( strpos( $border, 'T' ) !== false ) { + $s .= sprintf( '%.2F %.2F m %.2F %.2F l S ', $x * $k, ( $this->h - $y ) * $k, ( $x + $w ) * $k, ( $this->h - $y ) * $k ); + } + if ( strpos( $border, 'R' ) !== false ) { + $s .= sprintf( '%.2F %.2F m %.2F %.2F l S ', ( $x + $w ) * $k, ( $this->h - $y ) * $k, ( $x + $w ) * $k, ( $this->h - ( $y + $h ) ) * $k ); + } + if ( strpos( $border, 'B' ) !== false ) { + $s .= sprintf( '%.2F %.2F m %.2F %.2F l S ', $x * $k, ( $this->h - ( $y + $h ) ) * $k, ( $x + $w ) * $k, ( $this->h - ( $y + $h ) ) * $k ); + } + } + if ( $txt !== '' ) { + if ( ! isset( $this->CurrentFont ) ) { + $this->Error( 'No font has been set' ); + } + if ( $align == 'R' ) { + $dx = $w - $this->cMargin - $this->GetStringWidth( $txt ); + } elseif ( $align == 'C' ) { + $dx = ( $w - $this->GetStringWidth( $txt ) ) / 2; + } else { + $dx = $this->cMargin; + } + if ( $this->ColorFlag ) { + $s .= 'q ' . $this->TextColor . ' '; + } + $s .= sprintf( 'BT %.2F %.2F Td (%s) Tj ET', ( $this->x + $dx ) * $k, ( $this->h - ( $this->y + .5 * $h + .3 * $this->FontSize ) ) * $k, $this->_escape( $txt ) ); + if ( $this->underline ) { + $s .= ' ' . $this->_dounderline( $this->x + $dx, $this->y + .5 * $h + .3 * $this->FontSize, $txt ); + } + if ( $this->ColorFlag ) { + $s .= ' Q'; + } + if ( $link ) { + $this->Link( $this->x + $dx, $this->y + .5 * $h - .5 * $this->FontSize, $this->GetStringWidth( $txt ), $this->FontSize, $link ); + } + } + if ( $s ) { + $this->_out( $s ); + } + $this->lasth = $h; + if ( $ln > 0 ) { + // Go to next line + $this->y += $h; + if ( $ln == 1 ) { + $this->x = $this->lMargin; + } + } else { + $this->x += $w; + } + } + + function AcceptPageBreak() { + // Accept automatic page break or not + return $this->AutoPageBreak; + } + + function AddPage( $orientation = '', $size = '', $rotation = 0 ) { + // Start a new page + if ( $this->state == 3 ) { + $this->Error( 'The document is closed' ); + } + $family = $this->FontFamily; + $style = $this->FontStyle . ( $this->underline ? 'U' : '' ); + $fontsize = $this->FontSizePt; + $lw = $this->LineWidth; + $dc = $this->DrawColor; + $fc = $this->FillColor; + $tc = $this->TextColor; + $cf = $this->ColorFlag; + if ( $this->page > 0 ) { + // Page footer + $this->InFooter = true; + $this->Footer(); + $this->InFooter = false; + // Close page + $this->_endpage(); + } + // Start new page + $this->_beginpage( $orientation, $size, $rotation ); + // Set line cap style to square + $this->_out( '2 J' ); + // Set line width + $this->LineWidth = $lw; + $this->_out( sprintf( '%.2F w', $lw * $this->k ) ); + // Set font + if ( $family ) { + $this->SetFont( $family, $style, $fontsize ); + } + // Set colors + $this->DrawColor = $dc; + if ( $dc != '0 G' ) { + $this->_out( $dc ); + } + $this->FillColor = $fc; + if ( $fc != '0 g' ) { + $this->_out( $fc ); + } + $this->TextColor = $tc; + $this->ColorFlag = $cf; + // Page header + $this->InHeader = true; + $this->Header(); + $this->InHeader = false; + // Restore line width + if ( $this->LineWidth != $lw ) { + $this->LineWidth = $lw; + $this->_out( sprintf( '%.2F w', $lw * $this->k ) ); + } + // Restore font + if ( $family ) { + $this->SetFont( $family, $style, $fontsize ); + } + // Restore colors + if ( $this->DrawColor != $dc ) { + $this->DrawColor = $dc; + $this->_out( $dc ); + } + if ( $this->FillColor != $fc ) { + $this->FillColor = $fc; + $this->_out( $fc ); + } + $this->TextColor = $tc; + $this->ColorFlag = $cf; + } + + function Footer() { + // To be implemented in your own inherited class + } + + protected function _endpage() { + $this->state = 1; + } + + protected function _beginpage( $orientation, $size, $rotation ) { + $this->page ++; + $this->pages[ $this->page ] = ''; + $this->state = 2; + $this->x = $this->lMargin; + $this->y = $this->tMargin; + $this->FontFamily = ''; + // Check page size and orientation + if ( $orientation == '' ) { + $orientation = $this->DefOrientation; + } else { + $orientation = strtoupper( $orientation[0] ); + } + if ( $size == '' ) { + $size = $this->DefPageSize; + } else { + $size = $this->_getpagesize( $size ); + } + if ( $orientation != $this->CurOrientation || $size[0] != $this->CurPageSize[0] || $size[1] != $this->CurPageSize[1] ) { + // New size or orientation + if ( $orientation == 'P' ) { + $this->w = $size[0]; + $this->h = $size[1]; + } else { + $this->w = $size[1]; + $this->h = $size[0]; + } + $this->wPt = $this->w * $this->k; + $this->hPt = $this->h * $this->k; + $this->PageBreakTrigger = $this->h - $this->bMargin; + $this->CurOrientation = $orientation; + $this->CurPageSize = $size; + } + if ( $orientation != $this->DefOrientation || $size[0] != $this->DefPageSize[0] || $size[1] != $this->DefPageSize[1] ) { + $this->PageInfo[ $this->page ]['size'] = array( $this->wPt, $this->hPt ); + } + if ( $rotation != 0 ) { + if ( $rotation % 90 != 0 ) { + $this->Error( 'Incorrect rotation value: ' . $rotation ); + } + $this->CurRotation = $rotation; + $this->PageInfo[ $this->page ]['rotation'] = $rotation; + } + } + + function SetFont( $family, $style = '', $size = 0 ) { + // Select a font; size given in points + if ( $family == '' ) { + $family = $this->FontFamily; + } else { + $family = strtolower( $family ); + } + $style = strtoupper( $style ); + if ( strpos( $style, 'U' ) !== false ) { + $this->underline = true; + $style = str_replace( 'U', '', $style ); + } else { + $this->underline = false; + } + if ( $style == 'IB' ) { + $style = 'BI'; + } + if ( $size == 0 ) { + $size = $this->FontSizePt; + } + // Test if font is already selected + if ( $this->FontFamily == $family && $this->FontStyle == $style && $this->FontSizePt == $size ) { + return; + } + // Test if font is already loaded + $fontkey = $family . $style; + if ( ! isset( $this->fonts[ $fontkey ] ) ) { + // Test if one of the core fonts + if ( $family == 'arial' ) { + $family = 'helvetica'; + } + if ( in_array( $family, $this->CoreFonts ) ) { + if ( $family == 'symbol' || $family == 'zapfdingbats' ) { + $style = ''; + } + $fontkey = $family . $style; + if ( ! isset( $this->fonts[ $fontkey ] ) ) { + $this->AddFont( $family, $style ); + } + } else { + $this->Error( 'Undefined font: ' . $family . ' ' . $style ); + } + } + // Select it + $this->FontFamily = $family; + $this->FontStyle = $style; + $this->FontSizePt = $size; + $this->FontSize = $size / $this->k; + $this->CurrentFont = &$this->fonts[ $fontkey ]; + if ( $this->page > 0 ) { + $this->_out( sprintf( 'BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt ) ); + } + } + + function AddFont( $family, $style = '', $file = '' ) { + // Add a TrueType, OpenType or Type1 font + $family = strtolower( $family ); + if ( $file == '' ) { + $file = str_replace( ' ', '', $family ) . strtolower( $style ) . '.php'; + } + $style = strtoupper( $style ); + if ( $style == 'IB' ) { + $style = 'BI'; + } + $fontkey = $family . $style; + if ( isset( $this->fonts[ $fontkey ] ) ) { + return; + } + $info = $this->_loadfont( $file ); + $info['i'] = count( $this->fonts ) + 1; + if ( ! empty( $info['file'] ) ) { + // Embedded font + if ( $info['type'] == 'TrueType' ) { + $this->FontFiles[ $info['file'] ] = array( 'length1' => $info['originalsize'] ); + } else { + $this->FontFiles[ $info['file'] ] = array( 'length1' => $info['size1'], 'length2' => $info['size2'] ); + } + } + $this->fonts[ $fontkey ] = $info; + } + + protected function _loadfont( $font ) { + // Load a font definition file from the font directory + if ( strpos( $font, '/' ) !== false || strpos( $font, "\\" ) !== false ) { + $this->Error( 'Incorrect font definition file name: ' . $font ); + } + include( $this->fontpath . $font ); + if ( ! isset( $name ) ) { + $this->Error( 'Could not include font definition file' ); + } + if ( isset( $enc ) ) { + $enc = strtolower( $enc ); + } + if ( ! isset( $subsetted ) ) { + $subsetted = false; + } + + return get_defined_vars(); + } + + function Header() { + // To be implemented in your own inherited class + } + + function Link( $x, $y, $w, $h, $link ) { + // Put a link on the page + $this->PageLinks[ $this->page ][] = array( + $x * $this->k, + $this->hPt - $y * $this->k, + $w * $this->k, + $h * $this->k, + $link + ); + } + + function Write( $h, $txt, $link = '' ) { + // Output text in flowing mode + if ( ! isset( $this->CurrentFont ) ) { + $this->Error( 'No font has been set' ); + } + $cw = &$this->CurrentFont['cw']; + $w = $this->w - $this->rMargin - $this->x; + $wmax = ( $w - 2 * $this->cMargin ) * 1000 / $this->FontSize; + $s = str_replace( "\r", '', $txt ); + $nb = strlen( $s ); + $sep = - 1; + $i = 0; + $j = 0; + $l = 0; + $nl = 1; + while ( $i < $nb ) { + // Get next character + $c = $s[ $i ]; + if ( $c == "\n" ) { + // Explicit line break + $this->Cell( $w, $h, substr( $s, $j, $i - $j ), 0, 2, '', false, $link ); + $i ++; + $sep = - 1; + $j = $i; + $l = 0; + if ( $nl == 1 ) { + $this->x = $this->lMargin; + $w = $this->w - $this->rMargin - $this->x; + $wmax = ( $w - 2 * $this->cMargin ) * 1000 / $this->FontSize; + } + $nl ++; + continue; + } + if ( $c == ' ' ) { + $sep = $i; + } + $l += $cw[ $c ]; + if ( $l > $wmax ) { + // Automatic line break + if ( $sep == - 1 ) { + if ( $this->x > $this->lMargin ) { + // Move to next line + $this->x = $this->lMargin; + $this->y += $h; + $w = $this->w - $this->rMargin - $this->x; + $wmax = ( $w - 2 * $this->cMargin ) * 1000 / $this->FontSize; + $i ++; + $nl ++; + continue; + } + if ( $i == $j ) { + $i ++; + } + $this->Cell( $w, $h, substr( $s, $j, $i - $j ), 0, 2, '', false, $link ); + } else { + $this->Cell( $w, $h, substr( $s, $j, $sep - $j ), 0, 2, '', false, $link ); + $i = $sep + 1; + } + $sep = - 1; + $j = $i; + $l = 0; + if ( $nl == 1 ) { + $this->x = $this->lMargin; + $w = $this->w - $this->rMargin - $this->x; + $wmax = ( $w - 2 * $this->cMargin ) * 1000 / $this->FontSize; + } + $nl ++; + } else { + $i ++; + } + } + // Last chunk + if ( $i != $j ) { + $this->Cell( $l / 1000 * $this->FontSize, $h, substr( $s, $j ), 0, 0, '', false, $link ); + } + } + + function Ln( $h = null ) { + // Line feed; default value is the last cell height + $this->x = $this->lMargin; + if ( $h === null ) { + $this->y += $this->lasth; + } else { + $this->y += $h; + } + } + + function Image( $file, $x = null, $y = null, $w = 0, $h = 0, $type = '', $link = '' ) { + // Put an image on the page + if ( $file == '' ) { + $this->Error( 'Image file name is empty' ); + } + if ( ! isset( $this->images[ $file ] ) ) { + // First use of this image, get info + if ( $type == '' ) { + $pos = strrpos( $file, '.' ); + if ( ! $pos ) { + $this->Error( 'Image file has no extension and no type was specified: ' . $file ); + } + $type = substr( $file, $pos + 1 ); + } + $type = strtolower( $type ); + if ( $type == 'jpeg' ) { + $type = 'jpg'; + } + $mtd = '_parse' . $type; + if ( ! method_exists( $this, $mtd ) ) { + $this->Error( 'Unsupported image type: ' . $type ); + } + $info = $this->$mtd( $file ); + $info['i'] = count( $this->images ) + 1; + $this->images[ $file ] = $info; + } else { + $info = $this->images[ $file ]; + } + + // Automatic width and height calculation if needed + if ( $w == 0 && $h == 0 ) { + // Put image at 96 dpi + $w = - 96; + $h = - 96; + } + if ( $w < 0 ) { + $w = - $info['w'] * 72 / $w / $this->k; + } + if ( $h < 0 ) { + $h = - $info['h'] * 72 / $h / $this->k; + } + if ( $w == 0 ) { + $w = $h * $info['w'] / $info['h']; + } + if ( $h == 0 ) { + $h = $w * $info['h'] / $info['w']; + } + + // Flowing mode + if ( $y === null ) { + if ( $this->y + $h > $this->PageBreakTrigger && ! $this->InHeader && ! $this->InFooter && $this->AcceptPageBreak() ) { + // Automatic page break + $x2 = $this->x; + $this->AddPage( $this->CurOrientation, $this->CurPageSize, $this->CurRotation ); + $this->x = $x2; + } + $y = $this->y; + $this->y += $h; + } + + if ( $x === null ) { + $x = $this->x; + } + $this->_out( sprintf( 'q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q', $w * $this->k, $h * $this->k, $x * $this->k, ( $this->h - ( $y + $h ) ) * $this->k, $info['i'] ) ); + if ( $link ) { + $this->Link( $x, $y, $w, $h, $link ); + } + } + + function GetPageWidth() { + // Get current page width + return $this->w; + } + + function GetPageHeight() { + // Get current page height + return $this->h; + } + + function GetX() { + // Get x position + return $this->x; + } + + function SetX( $x ) { + // Set x position + if ( $x >= 0 ) { + $this->x = $x; + } else { + $this->x = $this->w + $x; + } + } + + function GetY() { + // Get y position + return $this->y; + } + + function SetXY( $x, $y ) { + // Set x and y positions + $this->SetX( $x ); + $this->SetY( $y, false ); + } + + function SetY( $y, $resetX = true ) { + // Set y position and optionally reset x + if ( $y >= 0 ) { + $this->y = $y; + } else { + $this->y = $this->h + $y; + } + if ( $resetX ) { + $this->x = $this->lMargin; + } + } + + function Output( $dest = '', $name = '', $isUTF8 = false ) { + // Output PDF to some destination + $this->Close(); + if ( strlen( $name ) == 1 && strlen( $dest ) != 1 ) { + // Fix parameter order + $tmp = $dest; + $dest = $name; + $name = $tmp; + } + if ( $dest == '' ) { + $dest = 'I'; + } + if ( $name == '' ) { + $name = 'doc.pdf'; + } + switch ( strtoupper( $dest ) ) { + case 'I': + // Send to standard output + $this->_checkoutput(); + if ( PHP_SAPI != 'cli' ) { + // We send to a browser + header( 'Content-Type: application/pdf' ); + header( 'Content-Disposition: inline; ' . $this->_httpencode( 'filename', $name, $isUTF8 ) ); + header( 'Cache-Control: private, max-age=0, must-revalidate' ); + header( 'Pragma: public' ); + } + echo $this->buffer; + break; + case 'D': + // Download file + $this->_checkoutput(); + header( 'Content-Type: application/x-download' ); + header( 'Content-Disposition: attachment; ' . $this->_httpencode( 'filename', $name, $isUTF8 ) ); + header( 'Cache-Control: private, max-age=0, must-revalidate' ); + header( 'Pragma: public' ); + echo $this->buffer; + break; + case 'F': + // Save to local file + if ( ! file_put_contents( $name, $this->buffer ) ) { + $this->Error( 'Unable to create output file: ' . $name ); + } + break; + case 'S': + // Return as a string + return $this->buffer; + default: + $this->Error( 'Incorrect output destination: ' . $dest ); + } + + return ''; + } + + function Close() { + // Terminate document + if ( $this->state == 3 ) { + return; + } + if ( $this->page == 0 ) { + $this->AddPage(); + } // Page footer $this->InFooter = true; $this->Footer(); $this->InFooter = false; // Close page $this->_endpage(); + // Close document + $this->_enddoc(); } - // Start new page - $this->_beginpage($orientation,$size,$rotation); - // Set line cap style to square - $this->_out('2 J'); - // Set line width - $this->LineWidth = $lw; - $this->_out(sprintf('%.2F w',$lw*$this->k)); - // Set font - if($family) - $this->SetFont($family,$style,$fontsize); - // Set colors - $this->DrawColor = $dc; - if($dc!='0 G') - $this->_out($dc); - $this->FillColor = $fc; - if($fc!='0 g') - $this->_out($fc); - $this->TextColor = $tc; - $this->ColorFlag = $cf; - // Page header - $this->InHeader = true; - $this->Header(); - $this->InHeader = false; - // Restore line width - if($this->LineWidth!=$lw) - { - $this->LineWidth = $lw; - $this->_out(sprintf('%.2F w',$lw*$this->k)); - } - // Restore font - if($family) - $this->SetFont($family,$style,$fontsize); - // Restore colors - if($this->DrawColor!=$dc) - { - $this->DrawColor = $dc; - $this->_out($dc); - } - if($this->FillColor!=$fc) - { - $this->FillColor = $fc; - $this->_out($fc); - } - $this->TextColor = $tc; - $this->ColorFlag = $cf; -} -function Header() -{ - // To be implemented in your own inherited class -} - -function Footer() -{ - // To be implemented in your own inherited class -} - -function PageNo() -{ - // Get current page number - return $this->page; -} - -function SetDrawColor($r, $g=null, $b=null) -{ - // Set color for all stroking operations - if(($r==0 && $g==0 && $b==0) || $g===null) - $this->DrawColor = sprintf('%.3F G',$r/255); - else - $this->DrawColor = sprintf('%.3F %.3F %.3F RG',$r/255,$g/255,$b/255); - if($this->page>0) - $this->_out($this->DrawColor); -} - -function SetFillColor($r, $g=null, $b=null) -{ - // Set color for all filling operations - if(($r==0 && $g==0 && $b==0) || $g===null) - $this->FillColor = sprintf('%.3F g',$r/255); - else - $this->FillColor = sprintf('%.3F %.3F %.3F rg',$r/255,$g/255,$b/255); - $this->ColorFlag = ($this->FillColor!=$this->TextColor); - if($this->page>0) - $this->_out($this->FillColor); -} - -function SetTextColor($r, $g=null, $b=null) -{ - // Set color for text - if(($r==0 && $g==0 && $b==0) || $g===null) - $this->TextColor = sprintf('%.3F g',$r/255); - else - $this->TextColor = sprintf('%.3F %.3F %.3F rg',$r/255,$g/255,$b/255); - $this->ColorFlag = ($this->FillColor!=$this->TextColor); -} - -function GetStringWidth($s) -{ - // Get width of a string in the current font - $s = (string)$s; - $cw = &$this->CurrentFont['cw']; - $w = 0; - $l = strlen($s); - for($i=0;$i<$l;$i++) - $w += $cw[$s[$i]]; - return $w*$this->FontSize/1000; -} - -function SetLineWidth($width) -{ - // Set line width - $this->LineWidth = $width; - if($this->page>0) - $this->_out(sprintf('%.2F w',$width*$this->k)); -} - -function Line($x1, $y1, $x2, $y2) -{ - // Draw a line - $this->_out(sprintf('%.2F %.2F m %.2F %.2F l S',$x1*$this->k,($this->h-$y1)*$this->k,$x2*$this->k,($this->h-$y2)*$this->k)); -} - -function Rect($x, $y, $w, $h, $style='') -{ - // Draw a rectangle - if($style=='F') - $op = 'f'; - elseif($style=='FD' || $style=='DF') - $op = 'B'; - else - $op = 'S'; - $this->_out(sprintf('%.2F %.2F %.2F %.2F re %s',$x*$this->k,($this->h-$y)*$this->k,$w*$this->k,-$h*$this->k,$op)); -} - -function AddFont($family, $style='', $file='') -{ - // Add a TrueType, OpenType or Type1 font - $family = strtolower($family); - if($file=='') - $file = str_replace(' ','',$family).strtolower($style).'.php'; - $style = strtoupper($style); - if($style=='IB') - $style = 'BI'; - $fontkey = $family.$style; - if(isset($this->fonts[$fontkey])) - return; - $info = $this->_loadfont($file); - $info['i'] = count($this->fonts)+1; - if(!empty($info['file'])) - { - // Embedded font - if($info['type']=='TrueType') - $this->FontFiles[$info['file']] = array('length1'=>$info['originalsize']); - else - $this->FontFiles[$info['file']] = array('length1'=>$info['size1'], 'length2'=>$info['size2']); - } - $this->fonts[$fontkey] = $info; -} - -function SetFont($family, $style='', $size=0) -{ - // Select a font; size given in points - if($family=='') - $family = $this->FontFamily; - else - $family = strtolower($family); - $style = strtoupper($style); - if(strpos($style,'U')!==false) - { - $this->underline = true; - $style = str_replace('U','',$style); - } - else - $this->underline = false; - if($style=='IB') - $style = 'BI'; - if($size==0) - $size = $this->FontSizePt; - // Test if font is already selected - if($this->FontFamily==$family && $this->FontStyle==$style && $this->FontSizePt==$size) - return; - // Test if font is already loaded - $fontkey = $family.$style; - if(!isset($this->fonts[$fontkey])) - { - // Test if one of the core fonts - if($family=='arial') - $family = 'helvetica'; - if(in_array($family,$this->CoreFonts)) - { - if($family=='symbol' || $family=='zapfdingbats') - $style = ''; - $fontkey = $family.$style; - if(!isset($this->fonts[$fontkey])) - $this->AddFont($family,$style); + protected function _enddoc() { + $this->_putheader(); + $this->_putpages(); + $this->_putresources(); + // Info + $this->_newobj(); + $this->_put( '<<' ); + $this->_putinfo(); + $this->_put( '>>' ); + $this->_put( 'endobj' ); + // Catalog + $this->_newobj(); + $this->_put( '<<' ); + $this->_putcatalog(); + $this->_put( '>>' ); + $this->_put( 'endobj' ); + // Cross-ref + $offset = $this->_getoffset(); + $this->_put( 'xref' ); + $this->_put( '0 ' . ( $this->n + 1 ) ); + $this->_put( '0000000000 65535 f ' ); + for ( $i = 1; $i <= $this->n; $i ++ ) { + $this->_put( sprintf( '%010d 00000 n ', $this->offsets[ $i ] ) ); } - else - $this->Error('Undefined font: '.$family.' '.$style); + // Trailer + $this->_put( 'trailer' ); + $this->_put( '<<' ); + $this->_puttrailer(); + $this->_put( '>>' ); + $this->_put( 'startxref' ); + $this->_put( $offset ); + $this->_put( '%%EOF' ); + $this->state = 3; } - // Select it - $this->FontFamily = $family; - $this->FontStyle = $style; - $this->FontSizePt = $size; - $this->FontSize = $size/$this->k; - $this->CurrentFont = &$this->fonts[$fontkey]; - if($this->page>0) - $this->_out(sprintf('BT /F%d %.2F Tf ET',$this->CurrentFont['i'],$this->FontSizePt)); -} -function SetFontSize($size) -{ - // Set font size in points - if($this->FontSizePt==$size) - return; - $this->FontSizePt = $size; - $this->FontSize = $size/$this->k; - if($this->page>0) - $this->_out(sprintf('BT /F%d %.2F Tf ET',$this->CurrentFont['i'],$this->FontSizePt)); -} + protected function _putheader() { + $this->_put( '%PDF-' . $this->PDFVersion ); + } -function AddLink() -{ - // Create a new internal link - $n = count($this->links)+1; - $this->links[$n] = array(0, 0); - return $n; -} - -function SetLink($link, $y=0, $page=-1) -{ - // Set destination of internal link - if($y==-1) - $y = $this->y; - if($page==-1) - $page = $this->page; - $this->links[$link] = array($page, $y); -} - -function Link($x, $y, $w, $h, $link) -{ - // Put a link on the page - $this->PageLinks[$this->page][] = array($x*$this->k, $this->hPt-$y*$this->k, $w*$this->k, $h*$this->k, $link); -} - -function Text($x, $y, $txt) -{ - // Output a string - if(!isset($this->CurrentFont)) - $this->Error('No font has been set'); - $s = sprintf('BT %.2F %.2F Td (%s) Tj ET',$x*$this->k,($this->h-$y)*$this->k,$this->_escape($txt)); - if($this->underline && $txt!='') - $s .= ' '.$this->_dounderline($x,$y,$txt); - if($this->ColorFlag) - $s = 'q '.$this->TextColor.' '.$s.' Q'; - $this->_out($s); -} - -function AcceptPageBreak() -{ - // Accept automatic page break or not - return $this->AutoPageBreak; -} - -function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='') -{ - // Output a cell - $k = $this->k; - if($this->y+$h>$this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak()) - { - // Automatic page break - $x = $this->x; - $ws = $this->ws; - if($ws>0) - { - $this->ws = 0; - $this->_out('0 Tw'); + protected function _putpages() { + $nb = $this->page; + for ( $n = 1; $n <= $nb; $n ++ ) { + $this->PageInfo[ $n ]['n'] = $this->n + 1 + 2 * ( $n - 1 ); } - $this->AddPage($this->CurOrientation,$this->CurPageSize,$this->CurRotation); - $this->x = $x; - if($ws>0) - { - $this->ws = $ws; - $this->_out(sprintf('%.3F Tw',$ws*$k)); + for ( $n = 1; $n <= $nb; $n ++ ) { + $this->_putpage( $n ); } + // Pages root + $this->_newobj( 1 ); + $this->_put( '<PageInfo[ $n ]['n'] . ' 0 R '; + } + $this->_put( $kids . ']' ); + $this->_put( '/Count ' . $nb ); + if ( $this->DefOrientation == 'P' ) { + $w = $this->DefPageSize[0]; + $h = $this->DefPageSize[1]; + } else { + $w = $this->DefPageSize[1]; + $h = $this->DefPageSize[0]; + } + $this->_put( sprintf( '/MediaBox [0 0 %.2F %.2F]', $w * $this->k, $h * $this->k ) ); + $this->_put( '>>' ); + $this->_put( 'endobj' ); } - if($w==0) - $w = $this->w-$this->rMargin-$this->x; - $s = ''; - if($fill || $border==1) - { - if($fill) - $op = ($border==1) ? 'B' : 'f'; - else - $op = 'S'; - $s = sprintf('%.2F %.2F %.2F %.2F re %s ',$this->x*$k,($this->h-$this->y)*$k,$w*$k,-$h*$k,$op); - } - if(is_string($border)) - { - $x = $this->x; - $y = $this->y; - if(strpos($border,'L')!==false) - $s .= sprintf('%.2F %.2F m %.2F %.2F l S ',$x*$k,($this->h-$y)*$k,$x*$k,($this->h-($y+$h))*$k); - if(strpos($border,'T')!==false) - $s .= sprintf('%.2F %.2F m %.2F %.2F l S ',$x*$k,($this->h-$y)*$k,($x+$w)*$k,($this->h-$y)*$k); - if(strpos($border,'R')!==false) - $s .= sprintf('%.2F %.2F m %.2F %.2F l S ',($x+$w)*$k,($this->h-$y)*$k,($x+$w)*$k,($this->h-($y+$h))*$k); - if(strpos($border,'B')!==false) - $s .= sprintf('%.2F %.2F m %.2F %.2F l S ',$x*$k,($this->h-($y+$h))*$k,($x+$w)*$k,($this->h-($y+$h))*$k); - } - if($txt!=='') - { - if(!isset($this->CurrentFont)) - $this->Error('No font has been set'); - if($align=='R') - $dx = $w-$this->cMargin-$this->GetStringWidth($txt); - elseif($align=='C') - $dx = ($w-$this->GetStringWidth($txt))/2; - else - $dx = $this->cMargin; - if($this->ColorFlag) - $s .= 'q '.$this->TextColor.' '; - $s .= sprintf('BT %.2F %.2F Td (%s) Tj ET',($this->x+$dx)*$k,($this->h-($this->y+.5*$h+.3*$this->FontSize))*$k,$this->_escape($txt)); - if($this->underline) - $s .= ' '.$this->_dounderline($this->x+$dx,$this->y+.5*$h+.3*$this->FontSize,$txt); - if($this->ColorFlag) - $s .= ' Q'; - if($link) - $this->Link($this->x+$dx,$this->y+.5*$h-.5*$this->FontSize,$this->GetStringWidth($txt),$this->FontSize,$link); - } - if($s) - $this->_out($s); - $this->lasth = $h; - if($ln>0) - { - // Go to next line - $this->y += $h; - if($ln==1) - $this->x = $this->lMargin; - } - else - $this->x += $w; -} -function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false) -{ - // Output text with automatic or explicit line breaks - if(!isset($this->CurrentFont)) - $this->Error('No font has been set'); - $cw = &$this->CurrentFont['cw']; - if($w==0) - $w = $this->w-$this->rMargin-$this->x; - $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; - $s = str_replace("\r",'',$txt); - $nb = strlen($s); - if($nb>0 && $s[$nb-1]=="\n") - $nb--; - $b = 0; - if($border) - { - if($border==1) - { - $border = 'LTRB'; - $b = 'LRT'; - $b2 = 'LR'; + protected function _putpage( $n ) { + $this->_newobj(); + $this->_put( '<_put( '/Parent 1 0 R' ); + if ( isset( $this->PageInfo[ $n ]['size'] ) ) { + $this->_put( sprintf( '/MediaBox [0 0 %.2F %.2F]', $this->PageInfo[ $n ]['size'][0], $this->PageInfo[ $n ]['size'][1] ) ); } - else - { - $b2 = ''; - if(strpos($border,'L')!==false) - $b2 .= 'L'; - if(strpos($border,'R')!==false) - $b2 .= 'R'; - $b = (strpos($border,'T')!==false) ? $b2.'T' : $b2; + if ( isset( $this->PageInfo[ $n ]['rotation'] ) ) { + $this->_put( '/Rotate ' . $this->PageInfo[ $n ]['rotation'] ); } - } - $sep = -1; - $i = 0; - $j = 0; - $l = 0; - $ns = 0; - $nl = 1; - while($i<$nb) - { - // Get next character - $c = $s[$i]; - if($c=="\n") - { - // Explicit line break - if($this->ws>0) - { - $this->ws = 0; - $this->_out('0 Tw'); - } - $this->Cell($w,$h,substr($s,$j,$i-$j),$b,2,$align,$fill); - $i++; - $sep = -1; - $j = $i; - $l = 0; - $ns = 0; - $nl++; - if($border && $nl==2) - $b = $b2; - continue; - } - if($c==' ') - { - $sep = $i; - $ls = $l; - $ns++; - } - $l += $cw[$c]; - if($l>$wmax) - { - // Automatic line break - if($sep==-1) - { - if($i==$j) - $i++; - if($this->ws>0) - { - $this->ws = 0; - $this->_out('0 Tw'); + $this->_put( '/Resources 2 0 R' ); + if ( isset( $this->PageLinks[ $n ] ) ) { + // Links + $annots = '/Annots ['; + foreach ( $this->PageLinks[ $n ] as $pl ) { + $rect = sprintf( '%.2F %.2F %.2F %.2F', $pl[0], $pl[1], $pl[0] + $pl[2], $pl[1] - $pl[3] ); + $annots .= '<_textstring( $pl[4] ) . '>>>>'; + } else { + $l = $this->links[ $pl[4] ]; + if ( isset( $this->PageInfo[ $l[0] ]['size'] ) ) { + $h = $this->PageInfo[ $l[0] ]['size'][1]; + } else { + $h = ( $this->DefOrientation == 'P' ) ? $this->DefPageSize[1] * $this->k : $this->DefPageSize[0] * $this->k; + } + $annots .= sprintf( '/Dest [%d 0 R /XYZ 0 %.2F null]>>', $this->PageInfo[ $l[0] ]['n'], $h - $l[1] * $this->k ); } - $this->Cell($w,$h,substr($s,$j,$i-$j),$b,2,$align,$fill); } - else - { - if($align=='J') - { - $this->ws = ($ns>1) ? ($wmax-$ls)/1000*$this->FontSize/($ns-1) : 0; - $this->_out(sprintf('%.3F Tw',$this->ws*$this->k)); + $this->_put( $annots . ']' ); + } + if ( $this->WithAlpha ) { + $this->_put( '/Group <>' ); + } + $this->_put( '/Contents ' . ( $this->n + 1 ) . ' 0 R>>' ); + $this->_put( 'endobj' ); + // Page content + if ( ! empty( $this->AliasNbPages ) ) { + $this->pages[ $n ] = str_replace( $this->AliasNbPages, $this->page, $this->pages[ $n ] ); + } + $this->_putstreamobject( $this->pages[ $n ] ); + } + + protected function _newobj( $n = null ) { + // Begin a new object + if ( $n === null ) { + $n = ++ $this->n; + } + $this->offsets[ $n ] = $this->_getoffset(); + $this->_put( $n . ' 0 obj' ); + } + + protected function _getoffset() { + return strlen( $this->buffer ); + } + + protected function _textstring( $s ) { + // Format a text string + if ( ! $this->_isascii( $s ) ) { + $s = $this->_UTF8toUTF16( $s ); + } + + return '(' . $this->_escape( $s ) . ')'; + } + + protected function _isascii( $s ) { + // Test if string is ASCII + $nb = strlen( $s ); + for ( $i = 0; $i < $nb; $i ++ ) { + if ( ord( $s[ $i ] ) > 127 ) { + return false; + } + } + + return true; + } + + protected function _UTF8toUTF16( $s ) { + // Convert UTF-8 to UTF-16BE with BOM + $res = "\xFE\xFF"; + $nb = strlen( $s ); + $i = 0; + while ( $i < $nb ) { + $c1 = ord( $s[ $i ++ ] ); + if ( $c1 >= 224 ) { + // 3-byte character + $c2 = ord( $s[ $i ++ ] ); + $c3 = ord( $s[ $i ++ ] ); + $res .= chr( ( ( $c1 & 0x0F ) << 4 ) + ( ( $c2 & 0x3C ) >> 2 ) ); + $res .= chr( ( ( $c2 & 0x03 ) << 6 ) + ( $c3 & 0x3F ) ); + } elseif ( $c1 >= 192 ) { + // 2-byte character + $c2 = ord( $s[ $i ++ ] ); + $res .= chr( ( $c1 & 0x1C ) >> 2 ); + $res .= chr( ( ( $c1 & 0x03 ) << 6 ) + ( $c2 & 0x3F ) ); + } else { + // Single-byte character + $res .= "\0" . chr( $c1 ); + } + } + + return $res; + } + + protected function _putstreamobject( $data ) { + if ( $this->compress ) { + $entries = '/Filter /FlateDecode '; + $data = gzcompress( $data ); + } else { + $entries = ''; + } + $entries .= '/Length ' . strlen( $data ); + $this->_newobj(); + $this->_put( '<<' . $entries . '>>' ); + $this->_putstream( $data ); + $this->_put( 'endobj' ); + } + + protected function _putstream( $data ) { + $this->_put( 'stream' ); + $this->_put( $data ); + $this->_put( 'endstream' ); + } + + protected function _putresources() { + $this->_putfonts(); + $this->_putimages(); + // Resource dictionary + $this->_newobj( 2 ); + $this->_put( '<<' ); + $this->_putresourcedict(); + $this->_put( '>>' ); + $this->_put( 'endobj' ); + } + + protected function _putfonts() { + foreach ( $this->FontFiles as $file => $info ) { + // Font file embedding + $this->_newobj(); + $this->FontFiles[ $file ]['n'] = $this->n; + $font = file_get_contents( $this->fontpath . $file, true ); + if ( ! $font ) { + $this->Error( 'Font file not found: ' . $file ); + } + $compressed = ( substr( $file, - 2 ) == '.z' ); + if ( ! $compressed && isset( $info['length2'] ) ) { + $font = substr( $font, 6, $info['length1'] ) . substr( $font, 6 + $info['length1'] + 6, $info['length2'] ); + } + $this->_put( '<_put( '/Filter /FlateDecode' ); + } + $this->_put( '/Length1 ' . $info['length1'] ); + if ( isset( $info['length2'] ) ) { + $this->_put( '/Length2 ' . $info['length2'] . ' /Length3 0' ); + } + $this->_put( '>>' ); + $this->_putstream( $font ); + $this->_put( 'endobj' ); + } + foreach ( $this->fonts as $k => $font ) { + // Encoding + if ( isset( $font['diff'] ) ) { + if ( ! isset( $this->encodings[ $font['enc'] ] ) ) { + $this->_newobj(); + $this->_put( '<>' ); + $this->_put( 'endobj' ); + $this->encodings[ $font['enc'] ] = $this->n; } - $this->Cell($w,$h,substr($s,$j,$sep-$j),$b,2,$align,$fill); - $i = $sep+1; } - $sep = -1; - $j = $i; - $l = 0; - $ns = 0; - $nl++; - if($border && $nl==2) - $b = $b2; - } - else - $i++; - } - // Last chunk - if($this->ws>0) - { - $this->ws = 0; - $this->_out('0 Tw'); - } - if($border && strpos($border,'B')!==false) - $b .= 'B'; - $this->Cell($w,$h,substr($s,$j,$i-$j),$b,2,$align,$fill); - $this->x = $this->lMargin; -} - -function Write($h, $txt, $link='') -{ - // Output text in flowing mode - if(!isset($this->CurrentFont)) - $this->Error('No font has been set'); - $cw = &$this->CurrentFont['cw']; - $w = $this->w-$this->rMargin-$this->x; - $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; - $s = str_replace("\r",'',$txt); - $nb = strlen($s); - $sep = -1; - $i = 0; - $j = 0; - $l = 0; - $nl = 1; - while($i<$nb) - { - // Get next character - $c = $s[$i]; - if($c=="\n") - { - // Explicit line break - $this->Cell($w,$h,substr($s,$j,$i-$j),0,2,'',false,$link); - $i++; - $sep = -1; - $j = $i; - $l = 0; - if($nl==1) - { - $this->x = $this->lMargin; - $w = $this->w-$this->rMargin-$this->x; - $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; - } - $nl++; - continue; - } - if($c==' ') - $sep = $i; - $l += $cw[$c]; - if($l>$wmax) - { - // Automatic line break - if($sep==-1) - { - if($this->x>$this->lMargin) - { - // Move to next line - $this->x = $this->lMargin; - $this->y += $h; - $w = $this->w-$this->rMargin-$this->x; - $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; - $i++; - $nl++; - continue; + // ToUnicode CMap + if ( isset( $font['uv'] ) ) { + if ( isset( $font['enc'] ) ) { + $cmapkey = $font['enc']; + } else { + $cmapkey = $font['name']; + } + if ( ! isset( $this->cmaps[ $cmapkey ] ) ) { + $cmap = $this->_tounicodecmap( $font['uv'] ); + $this->_putstreamobject( $cmap ); + $this->cmaps[ $cmapkey ] = $this->n; } - if($i==$j) - $i++; - $this->Cell($w,$h,substr($s,$j,$i-$j),0,2,'',false,$link); } - else - { - $this->Cell($w,$h,substr($s,$j,$sep-$j),0,2,'',false,$link); - $i = $sep+1; + // Font object + $this->fonts[ $k ]['n'] = $this->n + 1; + $type = $font['type']; + $name = $font['name']; + if ( $font['subsetted'] ) { + $name = 'AAAAAA+' . $name; } - $sep = -1; - $j = $i; - $l = 0; - if($nl==1) - { - $this->x = $this->lMargin; - $w = $this->w-$this->rMargin-$this->x; - $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize; + if ( $type == 'Core' ) { + // Core font + $this->_newobj(); + $this->_put( '<_put( '/BaseFont /' . $name ); + $this->_put( '/Subtype /Type1' ); + if ( $name != 'Symbol' && $name != 'ZapfDingbats' ) { + $this->_put( '/Encoding /WinAnsiEncoding' ); + } + if ( isset( $font['uv'] ) ) { + $this->_put( '/ToUnicode ' . $this->cmaps[ $cmapkey ] . ' 0 R' ); + } + $this->_put( '>>' ); + $this->_put( 'endobj' ); + } elseif ( $type == 'Type1' || $type == 'TrueType' ) { + // Additional Type1 or TrueType/OpenType font + $this->_newobj(); + $this->_put( '<_put( '/BaseFont /' . $name ); + $this->_put( '/Subtype /' . $type ); + $this->_put( '/FirstChar 32 /LastChar 255' ); + $this->_put( '/Widths ' . ( $this->n + 1 ) . ' 0 R' ); + $this->_put( '/FontDescriptor ' . ( $this->n + 2 ) . ' 0 R' ); + if ( isset( $font['diff'] ) ) { + $this->_put( '/Encoding ' . $this->encodings[ $font['enc'] ] . ' 0 R' ); + } else { + $this->_put( '/Encoding /WinAnsiEncoding' ); + } + if ( isset( $font['uv'] ) ) { + $this->_put( '/ToUnicode ' . $this->cmaps[ $cmapkey ] . ' 0 R' ); + } + $this->_put( '>>' ); + $this->_put( 'endobj' ); + // Widths + $this->_newobj(); + $cw = &$font['cw']; + $s = '['; + for ( $i = 32; $i <= 255; $i ++ ) { + $s .= $cw[ chr( $i ) ] . ' '; + } + $this->_put( $s . ']' ); + $this->_put( 'endobj' ); + // Descriptor + $this->_newobj(); + $s = '< $v ) { + $s .= ' /' . $k . ' ' . $v; + } + if ( ! empty( $font['file'] ) ) { + $s .= ' /FontFile' . ( $type == 'Type1' ? '' : '2' ) . ' ' . $this->FontFiles[ $font['file'] ]['n'] . ' 0 R'; + } + $this->_put( $s . '>>' ); + $this->_put( 'endobj' ); + } else { + // Allow for additional types + $mtd = '_put' . strtolower( $type ); + if ( ! method_exists( $this, $mtd ) ) { + $this->Error( 'Unsupported font type: ' . $type ); + } + $this->$mtd( $font ); } - $nl++; } - else - $i++; - } - // Last chunk - if($i!=$j) - $this->Cell($l/1000*$this->FontSize,$h,substr($s,$j),0,0,'',false,$link); -} - -function Ln($h=null) -{ - // Line feed; default value is the last cell height - $this->x = $this->lMargin; - if($h===null) - $this->y += $this->lasth; - else - $this->y += $h; -} - -function Image($file, $x=null, $y=null, $w=0, $h=0, $type='', $link='') -{ - // Put an image on the page - if($file=='') - $this->Error('Image file name is empty'); - if(!isset($this->images[$file])) - { - // First use of this image, get info - if($type=='') - { - $pos = strrpos($file,'.'); - if(!$pos) - $this->Error('Image file has no extension and no type was specified: '.$file); - $type = substr($file,$pos+1); - } - $type = strtolower($type); - if($type=='jpeg') - $type = 'jpg'; - $mtd = '_parse'.$type; - if(!method_exists($this,$mtd)) - $this->Error('Unsupported image type: '.$type); - $info = $this->$mtd($file); - $info['i'] = count($this->images)+1; - $this->images[$file] = $info; - } - else - $info = $this->images[$file]; - - // Automatic width and height calculation if needed - if($w==0 && $h==0) - { - // Put image at 96 dpi - $w = -96; - $h = -96; - } - if($w<0) - $w = -$info['w']*72/$w/$this->k; - if($h<0) - $h = -$info['h']*72/$h/$this->k; - if($w==0) - $w = $h*$info['w']/$info['h']; - if($h==0) - $h = $w*$info['h']/$info['w']; - - // Flowing mode - if($y===null) - { - if($this->y+$h>$this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak()) - { - // Automatic page break - $x2 = $this->x; - $this->AddPage($this->CurOrientation,$this->CurPageSize,$this->CurRotation); - $this->x = $x2; - } - $y = $this->y; - $this->y += $h; } - if($x===null) - $x = $this->x; - $this->_out(sprintf('q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q',$w*$this->k,$h*$this->k,$x*$this->k,($this->h-($y+$h))*$this->k,$info['i'])); - if($link) - $this->Link($x,$y,$w,$h,$link); -} - -function GetPageWidth() -{ - // Get current page width - return $this->w; -} - -function GetPageHeight() -{ - // Get current page height - return $this->h; -} - -function GetX() -{ - // Get x position - return $this->x; -} - -function SetX($x) -{ - // Set x position - if($x>=0) - $this->x = $x; - else - $this->x = $this->w+$x; -} - -function GetY() -{ - // Get y position - return $this->y; -} - -function SetY($y, $resetX=true) -{ - // Set y position and optionally reset x - if($y>=0) - $this->y = $y; - else - $this->y = $this->h+$y; - if($resetX) - $this->x = $this->lMargin; -} - -function SetXY($x, $y) -{ - // Set x and y positions - $this->SetX($x); - $this->SetY($y,false); -} - -function Output($dest='', $name='', $isUTF8=false) -{ - // Output PDF to some destination - $this->Close(); - if(strlen($name)==1 && strlen($dest)!=1) - { - // Fix parameter order - $tmp = $dest; - $dest = $name; - $name = $tmp; - } - if($dest=='') - $dest = 'I'; - if($name=='') - $name = 'doc.pdf'; - switch(strtoupper($dest)) - { - case 'I': - // Send to standard output - $this->_checkoutput(); - if(PHP_SAPI!='cli') - { - // We send to a browser - header('Content-Type: application/pdf'); - header('Content-Disposition: inline; '.$this->_httpencode('filename',$name,$isUTF8)); - header('Cache-Control: private, max-age=0, must-revalidate'); - header('Pragma: public'); + protected function _tounicodecmap( $uv ) { + $ranges = ''; + $nbr = 0; + $chars = ''; + $nbc = 0; + foreach ( $uv as $c => $v ) { + if ( is_array( $v ) ) { + $ranges .= sprintf( "<%02X> <%02X> <%04X>\n", $c, $c + $v[1] - 1, $v[0] ); + $nbr ++; + } else { + $chars .= sprintf( "<%02X> <%04X>\n", $c, $v ); + $nbc ++; } - echo $this->buffer; - break; - case 'D': - // Download file - $this->_checkoutput(); - header('Content-Type: application/x-download'); - header('Content-Disposition: attachment; '.$this->_httpencode('filename',$name,$isUTF8)); - header('Cache-Control: private, max-age=0, must-revalidate'); - header('Pragma: public'); - echo $this->buffer; - break; - case 'F': - // Save to local file - if(!file_put_contents($name,$this->buffer)) - $this->Error('Unable to create output file: '.$name); - break; - case 'S': - // Return as a string - return $this->buffer; - default: - $this->Error('Incorrect output destination: '.$dest); - } - return ''; -} - -/******************************************************************************* -* Protected methods * -*******************************************************************************/ - -protected function _dochecks() -{ - // Check mbstring overloading - if(ini_get('mbstring.func_overload') & 2) - $this->Error('mbstring overloading must be disabled'); - // Ensure runtime magic quotes are disabled - if(get_magic_quotes_runtime()) - @set_magic_quotes_runtime(0); -} - -protected function _checkoutput() -{ - if(PHP_SAPI!='cli') - { - if(headers_sent($file,$line)) - $this->Error("Some data has already been output, can't send PDF file (output started at $file:$line)"); - } - if(ob_get_length()) - { - // The output buffer is not empty - if(preg_match('/^(\xEF\xBB\xBF)?\s*$/',ob_get_contents())) - { - // It contains only a UTF-8 BOM and/or whitespace, let's clean it - ob_clean(); } + $s = "/CIDInit /ProcSet findresource begin\n"; + $s .= "12 dict begin\n"; + $s .= "begincmap\n"; + $s .= "/CIDSystemInfo\n"; + $s .= "< 0 ) { + $s .= "$nbr beginbfrange\n"; + $s .= $ranges; + $s .= "endbfrange\n"; + } + if ( $nbc > 0 ) { + $s .= "$nbc beginbfchar\n"; + $s .= $chars; + $s .= "endbfchar\n"; + } + $s .= "endcmap\n"; + $s .= "CMapName currentdict /CMap defineresource pop\n"; + $s .= "end\n"; + $s .= "end"; + + return $s; + } + + protected function _putimages() { + foreach ( array_keys( $this->images ) as $file ) { + $this->_putimage( $this->images[ $file ] ); + unset( $this->images[ $file ]['data'] ); + unset( $this->images[ $file ]['smask'] ); + } + } + + protected function _putimage( &$info ) { + $this->_newobj(); + $info['n'] = $this->n; + $this->_put( '<_put( '/Subtype /Image' ); + $this->_put( '/Width ' . $info['w'] ); + $this->_put( '/Height ' . $info['h'] ); + if ( $info['cs'] == 'Indexed' ) { + $this->_put( '/ColorSpace [/Indexed /DeviceRGB ' . ( strlen( $info['pal'] ) / 3 - 1 ) . ' ' . ( $this->n + 1 ) . ' 0 R]' ); + } else { + $this->_put( '/ColorSpace /' . $info['cs'] ); + if ( $info['cs'] == 'DeviceCMYK' ) { + $this->_put( '/Decode [1 0 1 0 1 0 1 0]' ); + } + } + $this->_put( '/BitsPerComponent ' . $info['bpc'] ); + if ( isset( $info['f'] ) ) { + $this->_put( '/Filter /' . $info['f'] ); + } + if ( isset( $info['dp'] ) ) { + $this->_put( '/DecodeParms <<' . $info['dp'] . '>>' ); + } + if ( isset( $info['trns'] ) && is_array( $info['trns'] ) ) { + $trns = ''; + for ( $i = 0; $i < count( $info['trns'] ); $i ++ ) { + $trns .= $info['trns'][ $i ] . ' ' . $info['trns'][ $i ] . ' '; + } + $this->_put( '/Mask [' . $trns . ']' ); + } + if ( isset( $info['smask'] ) ) { + $this->_put( '/SMask ' . ( $this->n + 1 ) . ' 0 R' ); + } + $this->_put( '/Length ' . strlen( $info['data'] ) . '>>' ); + $this->_putstream( $info['data'] ); + $this->_put( 'endobj' ); + // Soft mask + if ( isset( $info['smask'] ) ) { + $dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' . $info['w']; + $smask = array( + 'w' => $info['w'], + 'h' => $info['h'], + 'cs' => 'DeviceGray', + 'bpc' => 8, + 'f' => $info['f'], + 'dp' => $dp, + 'data' => $info['smask'] + ); + $this->_putimage( $smask ); + } + // Palette + if ( $info['cs'] == 'Indexed' ) { + $this->_putstreamobject( $info['pal'] ); + } + } + + protected function _putresourcedict() { + $this->_put( '/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]' ); + $this->_put( '/Font <<' ); + foreach ( $this->fonts as $font ) { + $this->_put( '/F' . $font['i'] . ' ' . $font['n'] . ' 0 R' ); + } + $this->_put( '>>' ); + $this->_put( '/XObject <<' ); + $this->_putxobjectdict(); + $this->_put( '>>' ); + } + + protected function _putxobjectdict() { + foreach ( $this->images as $image ) { + $this->_put( '/I' . $image['i'] . ' ' . $image['n'] . ' 0 R' ); + } + } + + protected function _putinfo() { + $this->metadata['Producer'] = 'FPDF ' . FPDF_VERSION; + $this->metadata['CreationDate'] = 'D:' . @date( 'YmdHis' ); + foreach ( $this->metadata as $key => $value ) { + $this->_put( '/' . $key . ' ' . $this->_textstring( $value ) ); + } + } + + protected function _putcatalog() { + $n = $this->PageInfo[1]['n']; + $this->_put( '/Type /Catalog' ); + $this->_put( '/Pages 1 0 R' ); + if ( $this->ZoomMode == 'fullpage' ) { + $this->_put( '/OpenAction [' . $n . ' 0 R /Fit]' ); + } elseif ( $this->ZoomMode == 'fullwidth' ) { + $this->_put( '/OpenAction [' . $n . ' 0 R /FitH null]' ); + } elseif ( $this->ZoomMode == 'real' ) { + $this->_put( '/OpenAction [' . $n . ' 0 R /XYZ null null 1]' ); + } elseif ( ! is_string( $this->ZoomMode ) ) { + $this->_put( '/OpenAction [' . $n . ' 0 R /XYZ null null ' . sprintf( '%.2F', $this->ZoomMode / 100 ) . ']' ); + } + if ( $this->LayoutMode == 'single' ) { + $this->_put( '/PageLayout /SinglePage' ); + } elseif ( $this->LayoutMode == 'continuous' ) { + $this->_put( '/PageLayout /OneColumn' ); + } elseif ( $this->LayoutMode == 'two' ) { + $this->_put( '/PageLayout /TwoColumnLeft' ); + } + } + + protected function _puttrailer() { + $this->_put( '/Size ' . ( $this->n + 1 ) ); + $this->_put( '/Root ' . $this->n . ' 0 R' ); + $this->_put( '/Info ' . ( $this->n - 1 ) . ' 0 R' ); + } + + protected function _checkoutput() { + if ( PHP_SAPI != 'cli' ) { + if ( headers_sent( $file, $line ) ) { + $this->Error( "Some data has already been output, can't send PDF file (output started at $file:$line)" ); + } + } + if ( ob_get_length() ) { + // The output buffer is not empty + if ( preg_match( '/^(\xEF\xBB\xBF)?\s*$/', ob_get_contents() ) ) { + // It contains only a UTF-8 BOM and/or whitespace, let's clean it + ob_clean(); + } // else // $this->Error("Some data has already been output, can't send PDF file"); - } -} - -protected function _getpagesize($size) -{ - if(is_string($size)) - { - $size = strtolower($size); - if(!isset($this->StdPageSizes[$size])) - $this->Error('Unknown page size: '.$size); - $a = $this->StdPageSizes[$size]; - return array($a[0]/$this->k, $a[1]/$this->k); - } - else - { - if($size[0]>$size[1]) - return array($size[1], $size[0]); - else - return $size; - } -} - -protected function _beginpage($orientation, $size, $rotation) -{ - $this->page++; - $this->pages[$this->page] = ''; - $this->state = 2; - $this->x = $this->lMargin; - $this->y = $this->tMargin; - $this->FontFamily = ''; - // Check page size and orientation - if($orientation=='') - $orientation = $this->DefOrientation; - else - $orientation = strtoupper($orientation[0]); - if($size=='') - $size = $this->DefPageSize; - else - $size = $this->_getpagesize($size); - if($orientation!=$this->CurOrientation || $size[0]!=$this->CurPageSize[0] || $size[1]!=$this->CurPageSize[1]) - { - // New size or orientation - if($orientation=='P') - { - $this->w = $size[0]; - $this->h = $size[1]; - } - else - { - $this->w = $size[1]; - $this->h = $size[0]; - } - $this->wPt = $this->w*$this->k; - $this->hPt = $this->h*$this->k; - $this->PageBreakTrigger = $this->h-$this->bMargin; - $this->CurOrientation = $orientation; - $this->CurPageSize = $size; - } - if($orientation!=$this->DefOrientation || $size[0]!=$this->DefPageSize[0] || $size[1]!=$this->DefPageSize[1]) - $this->PageInfo[$this->page]['size'] = array($this->wPt, $this->hPt); - if($rotation!=0) - { - if($rotation%90!=0) - $this->Error('Incorrect rotation value: '.$rotation); - $this->CurRotation = $rotation; - $this->PageInfo[$this->page]['rotation'] = $rotation; - } -} - -protected function _endpage() -{ - $this->state = 1; -} - -protected function _loadfont($font) -{ - // Load a font definition file from the font directory - if(strpos($font,'/')!==false || strpos($font,"\\")!==false) - $this->Error('Incorrect font definition file name: '.$font); - include($this->fontpath.$font); - if(!isset($name)) - $this->Error('Could not include font definition file'); - if(isset($enc)) - $enc = strtolower($enc); - if(!isset($subsetted)) - $subsetted = false; - return get_defined_vars(); -} - -protected function _isascii($s) -{ - // Test if string is ASCII - $nb = strlen($s); - for($i=0;$i<$nb;$i++) - { - if(ord($s[$i])>127) - return false; - } - return true; -} - -protected function _httpencode($param, $value, $isUTF8) -{ - // Encode HTTP header field parameter - if($this->_isascii($value)) - return $param.'="'.$value.'"'; - if(!$isUTF8) - $value = utf8_encode($value); - if(strpos($_SERVER['HTTP_USER_AGENT'],'MSIE')!==false) - return $param.'="'.rawurlencode($value).'"'; - else - return $param."*=UTF-8''".rawurlencode($value); -} - -protected function _UTF8toUTF16($s) -{ - // Convert UTF-8 to UTF-16BE with BOM - $res = "\xFE\xFF"; - $nb = strlen($s); - $i = 0; - while($i<$nb) - { - $c1 = ord($s[$i++]); - if($c1>=224) - { - // 3-byte character - $c2 = ord($s[$i++]); - $c3 = ord($s[$i++]); - $res .= chr((($c1 & 0x0F)<<4) + (($c2 & 0x3C)>>2)); - $res .= chr((($c2 & 0x03)<<6) + ($c3 & 0x3F)); - } - elseif($c1>=192) - { - // 2-byte character - $c2 = ord($s[$i++]); - $res .= chr(($c1 & 0x1C)>>2); - $res .= chr((($c1 & 0x03)<<6) + ($c2 & 0x3F)); - } - else - { - // Single-byte character - $res .= "\0".chr($c1); } } - return $res; -} -protected function _escape($s) -{ - // Escape special characters - if(strpos($s,'(')!==false || strpos($s,')')!==false || strpos($s,'\\')!==false || strpos($s,"\r")!==false) - return str_replace(array('\\','(',')',"\r"), array('\\\\','\\(','\\)','\\r'), $s); - else - return $s; -} - -protected function _textstring($s) -{ - // Format a text string - if(!$this->_isascii($s)) - $s = $this->_UTF8toUTF16($s); - return '('.$this->_escape($s).')'; -} - -protected function _dounderline($x, $y, $txt) -{ - // Underline text - $up = $this->CurrentFont['up']; - $ut = $this->CurrentFont['ut']; - $w = $this->GetStringWidth($txt)+$this->ws*substr_count($txt,' '); - return sprintf('%.2F %.2F %.2F %.2F re f',$x*$this->k,($this->h-($y-$up/1000*$this->FontSize))*$this->k,$w*$this->k,-$ut/1000*$this->FontSizePt); -} - -protected function _parsejpg($file) -{ - // Extract info from a JPEG file - $a = getimagesize($file); - if(!$a) - $this->Error('Missing or incorrect image file: '.$file); - if($a[2]!=2) - $this->Error('Not a JPEG file: '.$file); - if(!isset($a['channels']) || $a['channels']==3) - $colspace = 'DeviceRGB'; - elseif($a['channels']==4) - $colspace = 'DeviceCMYK'; - else - $colspace = 'DeviceGray'; - $bpc = isset($a['bits']) ? $a['bits'] : 8; - $data = file_get_contents($file); - return array('w'=>$a[0], 'h'=>$a[1], 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'DCTDecode', 'data'=>$data); -} - -protected function _parsepng($file) -{ - // Extract info from a PNG file - $f = fopen($file,'rb'); - if(!$f) - $this->Error('Can\'t open image file: '.$file); - $info = $this->_parsepngstream($f,$file); - fclose($f); - return $info; -} - -protected function _parsepngstream($f, $file) -{ - // Check signature - if($this->_readstream($f,8)!=chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) - $this->Error('Not a PNG file: '.$file); - - // Read header chunk - $this->_readstream($f,4); - if($this->_readstream($f,4)!='IHDR') - $this->Error('Incorrect PNG file: '.$file); - $w = $this->_readint($f); - $h = $this->_readint($f); - $bpc = ord($this->_readstream($f,1)); - if($bpc>8) - $this->Error('16-bit depth not supported: '.$file); - $ct = ord($this->_readstream($f,1)); - if($ct==0 || $ct==4) - $colspace = 'DeviceGray'; - elseif($ct==2 || $ct==6) - $colspace = 'DeviceRGB'; - elseif($ct==3) - $colspace = 'Indexed'; - else - $this->Error('Unknown color type: '.$file); - if(ord($this->_readstream($f,1))!=0) - $this->Error('Unknown compression method: '.$file); - if(ord($this->_readstream($f,1))!=0) - $this->Error('Unknown filter method: '.$file); - if(ord($this->_readstream($f,1))!=0) - $this->Error('Interlacing not supported: '.$file); - $this->_readstream($f,4); - $dp = '/Predictor 15 /Colors '.($colspace=='DeviceRGB' ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w; - - // Scan chunks looking for palette, transparency and image data - $pal = ''; - $trns = ''; - $data = ''; - do - { - $n = $this->_readint($f); - $type = $this->_readstream($f,4); - if($type=='PLTE') - { - // Read palette - $pal = $this->_readstream($f,$n); - $this->_readstream($f,4); + protected function _httpencode( $param, $value, $isUTF8 ) { + // Encode HTTP header field parameter + if ( $this->_isascii( $value ) ) { + return $param . '="' . $value . '"'; } - elseif($type=='tRNS') - { - // Read transparency info - $t = $this->_readstream($f,$n); - if($ct==0) - $trns = array(ord(substr($t,1,1))); - elseif($ct==2) - $trns = array(ord(substr($t,1,1)), ord(substr($t,3,1)), ord(substr($t,5,1))); - else - { - $pos = strpos($t,chr(0)); - if($pos!==false) - $trns = array($pos); - } - $this->_readstream($f,4); + if ( ! $isUTF8 ) { + $value = utf8_encode( $value ); } - elseif($type=='IDAT') - { - // Read image data block - $data .= $this->_readstream($f,$n); - $this->_readstream($f,4); - } - elseif($type=='IEND') - break; - else - $this->_readstream($f,$n+4); - } - while($n); - - if($colspace=='Indexed' && empty($pal)) - $this->Error('Missing palette in '.$file); - $info = array('w'=>$w, 'h'=>$h, 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'FlateDecode', 'dp'=>$dp, 'pal'=>$pal, 'trns'=>$trns); - if($ct>=4) - { - // Extract alpha channel - if(!function_exists('gzuncompress')) - $this->Error('Zlib not available, can\'t handle alpha channel: '.$file); - $data = gzuncompress($data); - $color = ''; - $alpha = ''; - if($ct==4) - { - // Gray image - $len = 2*$w; - for($i=0;$i<$h;$i++) - { - $pos = (1+$len)*$i; - $color .= $data[$pos]; - $alpha .= $data[$pos]; - $line = substr($data,$pos+1,$len); - $color .= preg_replace('/(.)./s','$1',$line); - $alpha .= preg_replace('/.(.)/s','$1',$line); - } - } - else - { - // RGB image - $len = 4*$w; - for($i=0;$i<$h;$i++) - { - $pos = (1+$len)*$i; - $color .= $data[$pos]; - $alpha .= $data[$pos]; - $line = substr($data,$pos+1,$len); - $color .= preg_replace('/(.{3})./s','$1',$line); - $alpha .= preg_replace('/.{3}(.)/s','$1',$line); - } - } - unset($data); - $data = gzcompress($color); - $info['smask'] = gzcompress($alpha); - $this->WithAlpha = true; - if($this->PDFVersion<'1.4') - $this->PDFVersion = '1.4'; - } - $info['data'] = $data; - return $info; -} - -protected function _readstream($f, $n) -{ - // Read n bytes from stream - $res = ''; - while($n>0 && !feof($f)) - { - $s = fread($f,$n); - if($s===false) - $this->Error('Error while reading stream'); - $n -= strlen($s); - $res .= $s; - } - if($n>0) - $this->Error('Unexpected end of stream'); - return $res; -} - -protected function _readint($f) -{ - // Read a 4-byte integer from stream - $a = unpack('Ni',$this->_readstream($f,4)); - return $a['i']; -} - -protected function _parsegif($file) -{ - // Extract info from a GIF file (via PNG conversion) - if(!function_exists('imagepng')) - $this->Error('GD extension is required for GIF support'); - if(!function_exists('imagecreatefromgif')) - $this->Error('GD has no GIF read support'); - $im = imagecreatefromgif($file); - if(!$im) - $this->Error('Missing or incorrect image file: '.$file); - imageinterlace($im,0); - ob_start(); - imagepng($im); - $data = ob_get_clean(); - imagedestroy($im); - $f = fopen('php://temp','rb+'); - if(!$f) - $this->Error('Unable to create memory stream'); - fwrite($f,$data); - rewind($f); - $info = $this->_parsepngstream($f,$file); - fclose($f); - return $info; -} - -protected function _out($s) -{ - // Add a line to the document - if($this->state==2) - $this->pages[$this->page] .= $s."\n"; - elseif($this->state==1) - $this->_put($s); - elseif($this->state==0) - $this->Error('No page has been added yet'); - elseif($this->state==3) - $this->Error('The document is closed'); -} - -protected function _put($s) -{ - $this->buffer .= $s."\n"; -} - -protected function _getoffset() -{ - return strlen($this->buffer); -} - -protected function _newobj($n=null) -{ - // Begin a new object - if($n===null) - $n = ++$this->n; - $this->offsets[$n] = $this->_getoffset(); - $this->_put($n.' 0 obj'); -} - -protected function _putstream($data) -{ - $this->_put('stream'); - $this->_put($data); - $this->_put('endstream'); -} - -protected function _putstreamobject($data) -{ - if($this->compress) - { - $entries = '/Filter /FlateDecode '; - $data = gzcompress($data); - } - else - $entries = ''; - $entries .= '/Length '.strlen($data); - $this->_newobj(); - $this->_put('<<'.$entries.'>>'); - $this->_putstream($data); - $this->_put('endobj'); -} - -protected function _putpage($n) -{ - $this->_newobj(); - $this->_put('<_put('/Parent 1 0 R'); - if(isset($this->PageInfo[$n]['size'])) - $this->_put(sprintf('/MediaBox [0 0 %.2F %.2F]',$this->PageInfo[$n]['size'][0],$this->PageInfo[$n]['size'][1])); - if(isset($this->PageInfo[$n]['rotation'])) - $this->_put('/Rotate '.$this->PageInfo[$n]['rotation']); - $this->_put('/Resources 2 0 R'); - if(isset($this->PageLinks[$n])) - { - // Links - $annots = '/Annots ['; - foreach($this->PageLinks[$n] as $pl) - { - $rect = sprintf('%.2F %.2F %.2F %.2F',$pl[0],$pl[1],$pl[0]+$pl[2],$pl[1]-$pl[3]); - $annots .= '<_textstring($pl[4]).'>>>>'; - else - { - $l = $this->links[$pl[4]]; - if(isset($this->PageInfo[$l[0]]['size'])) - $h = $this->PageInfo[$l[0]]['size'][1]; - else - $h = ($this->DefOrientation=='P') ? $this->DefPageSize[1]*$this->k : $this->DefPageSize[0]*$this->k; - $annots .= sprintf('/Dest [%d 0 R /XYZ 0 %.2F null]>>',$this->PageInfo[$l[0]]['n'],$h-$l[1]*$this->k); - } - } - $this->_put($annots.']'); - } - if($this->WithAlpha) - $this->_put('/Group <>'); - $this->_put('/Contents '.($this->n+1).' 0 R>>'); - $this->_put('endobj'); - // Page content - if(!empty($this->AliasNbPages)) - $this->pages[$n] = str_replace($this->AliasNbPages,$this->page,$this->pages[$n]); - $this->_putstreamobject($this->pages[$n]); -} - -protected function _putpages() -{ - $nb = $this->page; - for($n=1;$n<=$nb;$n++) - $this->PageInfo[$n]['n'] = $this->n+1+2*($n-1); - for($n=1;$n<=$nb;$n++) - $this->_putpage($n); - // Pages root - $this->_newobj(1); - $this->_put('<PageInfo[$n]['n'].' 0 R '; - $this->_put($kids.']'); - $this->_put('/Count '.$nb); - if($this->DefOrientation=='P') - { - $w = $this->DefPageSize[0]; - $h = $this->DefPageSize[1]; - } - else - { - $w = $this->DefPageSize[1]; - $h = $this->DefPageSize[0]; - } - $this->_put(sprintf('/MediaBox [0 0 %.2F %.2F]',$w*$this->k,$h*$this->k)); - $this->_put('>>'); - $this->_put('endobj'); -} - -protected function _putfonts() -{ - foreach($this->FontFiles as $file=>$info) - { - // Font file embedding - $this->_newobj(); - $this->FontFiles[$file]['n'] = $this->n; - $font = file_get_contents($this->fontpath.$file,true); - if(!$font) - $this->Error('Font file not found: '.$file); - $compressed = (substr($file,-2)=='.z'); - if(!$compressed && isset($info['length2'])) - $font = substr($font,6,$info['length1']).substr($font,6+$info['length1']+6,$info['length2']); - $this->_put('<_put('/Filter /FlateDecode'); - $this->_put('/Length1 '.$info['length1']); - if(isset($info['length2'])) - $this->_put('/Length2 '.$info['length2'].' /Length3 0'); - $this->_put('>>'); - $this->_putstream($font); - $this->_put('endobj'); - } - foreach($this->fonts as $k=>$font) - { - // Encoding - if(isset($font['diff'])) - { - if(!isset($this->encodings[$font['enc']])) - { - $this->_newobj(); - $this->_put('<>'); - $this->_put('endobj'); - $this->encodings[$font['enc']] = $this->n; - } - } - // ToUnicode CMap - if(isset($font['uv'])) - { - if(isset($font['enc'])) - $cmapkey = $font['enc']; - else - $cmapkey = $font['name']; - if(!isset($this->cmaps[$cmapkey])) - { - $cmap = $this->_tounicodecmap($font['uv']); - $this->_putstreamobject($cmap); - $this->cmaps[$cmapkey] = $this->n; - } - } - // Font object - $this->fonts[$k]['n'] = $this->n+1; - $type = $font['type']; - $name = $font['name']; - if($font['subsetted']) - $name = 'AAAAAA+'.$name; - if($type=='Core') - { - // Core font - $this->_newobj(); - $this->_put('<_put('/BaseFont /'.$name); - $this->_put('/Subtype /Type1'); - if($name!='Symbol' && $name!='ZapfDingbats') - $this->_put('/Encoding /WinAnsiEncoding'); - if(isset($font['uv'])) - $this->_put('/ToUnicode '.$this->cmaps[$cmapkey].' 0 R'); - $this->_put('>>'); - $this->_put('endobj'); - } - elseif($type=='Type1' || $type=='TrueType') - { - // Additional Type1 or TrueType/OpenType font - $this->_newobj(); - $this->_put('<_put('/BaseFont /'.$name); - $this->_put('/Subtype /'.$type); - $this->_put('/FirstChar 32 /LastChar 255'); - $this->_put('/Widths '.($this->n+1).' 0 R'); - $this->_put('/FontDescriptor '.($this->n+2).' 0 R'); - if(isset($font['diff'])) - $this->_put('/Encoding '.$this->encodings[$font['enc']].' 0 R'); - else - $this->_put('/Encoding /WinAnsiEncoding'); - if(isset($font['uv'])) - $this->_put('/ToUnicode '.$this->cmaps[$cmapkey].' 0 R'); - $this->_put('>>'); - $this->_put('endobj'); - // Widths - $this->_newobj(); - $cw = &$font['cw']; - $s = '['; - for($i=32;$i<=255;$i++) - $s .= $cw[chr($i)].' '; - $this->_put($s.']'); - $this->_put('endobj'); - // Descriptor - $this->_newobj(); - $s = '<$v) - $s .= ' /'.$k.' '.$v; - if(!empty($font['file'])) - $s .= ' /FontFile'.($type=='Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R'; - $this->_put($s.'>>'); - $this->_put('endobj'); - } - else - { - // Allow for additional types - $mtd = '_put'.strtolower($type); - if(!method_exists($this,$mtd)) - $this->Error('Unsupported font type: '.$type); - $this->$mtd($font); + if ( strpos( $_SERVER['HTTP_USER_AGENT'], 'MSIE' ) !== false ) { + return $param . '="' . rawurlencode( $value ) . '"'; + } else { + return $param . "*=UTF-8''" . rawurlencode( $value ); } } -} -protected function _tounicodecmap($uv) -{ - $ranges = ''; - $nbr = 0; - $chars = ''; - $nbc = 0; - foreach($uv as $c=>$v) - { - if(is_array($v)) - { - $ranges .= sprintf("<%02X> <%02X> <%04X>\n",$c,$c+$v[1]-1,$v[0]); - $nbr++; + protected function _parsejpg( $file ) { + // Extract info from a JPEG file + $a = getimagesize( $file ); + if ( ! $a ) { + $this->Error( 'Missing or incorrect image file: ' . $file ); } - else - { - $chars .= sprintf("<%02X> <%04X>\n",$c,$v); - $nbc++; + if ( $a[2] != 2 ) { + $this->Error( 'Not a JPEG file: ' . $file ); } - } - $s = "/CIDInit /ProcSet findresource begin\n"; - $s .= "12 dict begin\n"; - $s .= "begincmap\n"; - $s .= "/CIDSystemInfo\n"; - $s .= "<0) - { - $s .= "$nbr beginbfrange\n"; - $s .= $ranges; - $s .= "endbfrange\n"; - } - if($nbc>0) - { - $s .= "$nbc beginbfchar\n"; - $s .= $chars; - $s .= "endbfchar\n"; - } - $s .= "endcmap\n"; - $s .= "CMapName currentdict /CMap defineresource pop\n"; - $s .= "end\n"; - $s .= "end"; - return $s; -} + if ( ! isset( $a['channels'] ) || $a['channels'] == 3 ) { + $colspace = 'DeviceRGB'; + } elseif ( $a['channels'] == 4 ) { + $colspace = 'DeviceCMYK'; + } else { + $colspace = 'DeviceGray'; + } + $bpc = isset( $a['bits'] ) ? $a['bits'] : 8; + $data = file_get_contents( $file ); -protected function _putimages() -{ - foreach(array_keys($this->images) as $file) - { - $this->_putimage($this->images[$file]); - unset($this->images[$file]['data']); - unset($this->images[$file]['smask']); + return array( + 'w' => $a[0], + 'h' => $a[1], + 'cs' => $colspace, + 'bpc' => $bpc, + 'f' => 'DCTDecode', + 'data' => $data + ); } -} -protected function _putimage(&$info) -{ - $this->_newobj(); - $info['n'] = $this->n; - $this->_put('<_put('/Subtype /Image'); - $this->_put('/Width '.$info['w']); - $this->_put('/Height '.$info['h']); - if($info['cs']=='Indexed') - $this->_put('/ColorSpace [/Indexed /DeviceRGB '.(strlen($info['pal'])/3-1).' '.($this->n+1).' 0 R]'); - else - { - $this->_put('/ColorSpace /'.$info['cs']); - if($info['cs']=='DeviceCMYK') - $this->_put('/Decode [1 0 1 0 1 0 1 0]'); + protected function _parsepng( $file ) { + // Extract info from a PNG file + $f = fopen( $file, 'rb' ); + if ( ! $f ) { + $this->Error( 'Can\'t open image file: ' . $file ); + } + $info = $this->_parsepngstream( $f, $file ); + fclose( $f ); + + return $info; } - $this->_put('/BitsPerComponent '.$info['bpc']); - if(isset($info['f'])) - $this->_put('/Filter /'.$info['f']); - if(isset($info['dp'])) - $this->_put('/DecodeParms <<'.$info['dp'].'>>'); - if(isset($info['trns']) && is_array($info['trns'])) - { + + protected function _parsepngstream( $f, $file ) { + // Check signature + if ( $this->_readstream( $f, 8 ) != chr( 137 ) . 'PNG' . chr( 13 ) . chr( 10 ) . chr( 26 ) . chr( 10 ) ) { + $this->Error( 'Not a PNG file: ' . $file ); + } + + // Read header chunk + $this->_readstream( $f, 4 ); + if ( $this->_readstream( $f, 4 ) != 'IHDR' ) { + $this->Error( 'Incorrect PNG file: ' . $file ); + } + $w = $this->_readint( $f ); + $h = $this->_readint( $f ); + $bpc = ord( $this->_readstream( $f, 1 ) ); + if ( $bpc > 8 ) { + $this->Error( '16-bit depth not supported: ' . $file ); + } + $ct = ord( $this->_readstream( $f, 1 ) ); + if ( $ct == 0 || $ct == 4 ) { + $colspace = 'DeviceGray'; + } elseif ( $ct == 2 || $ct == 6 ) { + $colspace = 'DeviceRGB'; + } elseif ( $ct == 3 ) { + $colspace = 'Indexed'; + } else { + $this->Error( 'Unknown color type: ' . $file ); + } + if ( ord( $this->_readstream( $f, 1 ) ) != 0 ) { + $this->Error( 'Unknown compression method: ' . $file ); + } + if ( ord( $this->_readstream( $f, 1 ) ) != 0 ) { + $this->Error( 'Unknown filter method: ' . $file ); + } + if ( ord( $this->_readstream( $f, 1 ) ) != 0 ) { + $this->Error( 'Interlacing not supported: ' . $file ); + } + $this->_readstream( $f, 4 ); + $dp = '/Predictor 15 /Colors ' . ( $colspace == 'DeviceRGB' ? 3 : 1 ) . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w; + + // Scan chunks looking for palette, transparency and image data + $pal = ''; $trns = ''; - for($i=0;$i_put('/Mask ['.$trns.']'); + $data = ''; + do { + $n = $this->_readint( $f ); + $type = $this->_readstream( $f, 4 ); + if ( $type == 'PLTE' ) { + // Read palette + $pal = $this->_readstream( $f, $n ); + $this->_readstream( $f, 4 ); + } elseif ( $type == 'tRNS' ) { + // Read transparency info + $t = $this->_readstream( $f, $n ); + if ( $ct == 0 ) { + $trns = array( ord( substr( $t, 1, 1 ) ) ); + } elseif ( $ct == 2 ) { + $trns = array( ord( substr( $t, 1, 1 ) ), ord( substr( $t, 3, 1 ) ), ord( substr( $t, 5, 1 ) ) ); + } else { + $pos = strpos( $t, chr( 0 ) ); + if ( $pos !== false ) { + $trns = array( $pos ); + } + } + $this->_readstream( $f, 4 ); + } elseif ( $type == 'IDAT' ) { + // Read image data block + $data .= $this->_readstream( $f, $n ); + $this->_readstream( $f, 4 ); + } elseif ( $type == 'IEND' ) { + break; + } else { + $this->_readstream( $f, $n + 4 ); + } + } while ( $n ); + + if ( $colspace == 'Indexed' && empty( $pal ) ) { + $this->Error( 'Missing palette in ' . $file ); + } + $info = array( + 'w' => $w, + 'h' => $h, + 'cs' => $colspace, + 'bpc' => $bpc, + 'f' => 'FlateDecode', + 'dp' => $dp, + 'pal' => $pal, + 'trns' => $trns + ); + if ( $ct >= 4 ) { + // Extract alpha channel + if ( ! function_exists( 'gzuncompress' ) ) { + $this->Error( 'Zlib not available, can\'t handle alpha channel: ' . $file ); + } + $data = gzuncompress( $data ); + $color = ''; + $alpha = ''; + if ( $ct == 4 ) { + // Gray image + $len = 2 * $w; + for ( $i = 0; $i < $h; $i ++ ) { + $pos = ( 1 + $len ) * $i; + $color .= $data[ $pos ]; + $alpha .= $data[ $pos ]; + $line = substr( $data, $pos + 1, $len ); + $color .= preg_replace( '/(.)./s', '$1', $line ); + $alpha .= preg_replace( '/.(.)/s', '$1', $line ); + } + } else { + // RGB image + $len = 4 * $w; + for ( $i = 0; $i < $h; $i ++ ) { + $pos = ( 1 + $len ) * $i; + $color .= $data[ $pos ]; + $alpha .= $data[ $pos ]; + $line = substr( $data, $pos + 1, $len ); + $color .= preg_replace( '/(.{3})./s', '$1', $line ); + $alpha .= preg_replace( '/.{3}(.)/s', '$1', $line ); + } + } + unset( $data ); + $data = gzcompress( $color ); + $info['smask'] = gzcompress( $alpha ); + $this->WithAlpha = true; + if ( $this->PDFVersion < '1.4' ) { + $this->PDFVersion = '1.4'; + } + } + $info['data'] = $data; + + return $info; } - if(isset($info['smask'])) - $this->_put('/SMask '.($this->n+1).' 0 R'); - $this->_put('/Length '.strlen($info['data']).'>>'); - $this->_putstream($info['data']); - $this->_put('endobj'); - // Soft mask - if(isset($info['smask'])) - { - $dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns '.$info['w']; - $smask = array('w'=>$info['w'], 'h'=>$info['h'], 'cs'=>'DeviceGray', 'bpc'=>8, 'f'=>$info['f'], 'dp'=>$dp, 'data'=>$info['smask']); - $this->_putimage($smask); + + protected function _readstream( $f, $n ) { + // Read n bytes from stream + $res = ''; + while ( $n > 0 && ! feof( $f ) ) { + $s = fread( $f, $n ); + if ( $s === false ) { + $this->Error( 'Error while reading stream' ); + } + $n -= strlen( $s ); + $res .= $s; + } + if ( $n > 0 ) { + $this->Error( 'Unexpected end of stream' ); + } + + return $res; + } + + protected function _readint( $f ) { + // Read a 4-byte integer from stream + $a = unpack( 'Ni', $this->_readstream( $f, 4 ) ); + + return $a['i']; + } + + protected function _parsegif( $file ) { + // Extract info from a GIF file (via PNG conversion) + if ( ! function_exists( 'imagepng' ) ) { + $this->Error( 'GD extension is required for GIF support' ); + } + if ( ! function_exists( 'imagecreatefromgif' ) ) { + $this->Error( 'GD has no GIF read support' ); + } + $im = imagecreatefromgif( $file ); + if ( ! $im ) { + $this->Error( 'Missing or incorrect image file: ' . $file ); + } + imageinterlace( $im, 0 ); + ob_start(); + imagepng( $im ); + $data = ob_get_clean(); + imagedestroy( $im ); + $f = fopen( 'php://temp', 'rb+' ); + if ( ! $f ) { + $this->Error( 'Unable to create memory stream' ); + } + fwrite( $f, $data ); + rewind( $f ); + $info = $this->_parsepngstream( $f, $file ); + fclose( $f ); + + return $info; } - // Palette - if($info['cs']=='Indexed') - $this->_putstreamobject($info['pal']); } -protected function _putxobjectdict() -{ - foreach($this->images as $image) - $this->_put('/I'.$image['i'].' '.$image['n'].' 0 R'); -} - -protected function _putresourcedict() -{ - $this->_put('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); - $this->_put('/Font <<'); - foreach($this->fonts as $font) - $this->_put('/F'.$font['i'].' '.$font['n'].' 0 R'); - $this->_put('>>'); - $this->_put('/XObject <<'); - $this->_putxobjectdict(); - $this->_put('>>'); -} - -protected function _putresources() -{ - $this->_putfonts(); - $this->_putimages(); - // Resource dictionary - $this->_newobj(2); - $this->_put('<<'); - $this->_putresourcedict(); - $this->_put('>>'); - $this->_put('endobj'); -} - -protected function _putinfo() -{ - $this->metadata['Producer'] = 'FPDF '.FPDF_VERSION; - $this->metadata['CreationDate'] = 'D:'.@date('YmdHis'); - foreach($this->metadata as $key=>$value) - $this->_put('/'.$key.' '.$this->_textstring($value)); -} - -protected function _putcatalog() -{ - $n = $this->PageInfo[1]['n']; - $this->_put('/Type /Catalog'); - $this->_put('/Pages 1 0 R'); - if($this->ZoomMode=='fullpage') - $this->_put('/OpenAction ['.$n.' 0 R /Fit]'); - elseif($this->ZoomMode=='fullwidth') - $this->_put('/OpenAction ['.$n.' 0 R /FitH null]'); - elseif($this->ZoomMode=='real') - $this->_put('/OpenAction ['.$n.' 0 R /XYZ null null 1]'); - elseif(!is_string($this->ZoomMode)) - $this->_put('/OpenAction ['.$n.' 0 R /XYZ null null '.sprintf('%.2F',$this->ZoomMode/100).']'); - if($this->LayoutMode=='single') - $this->_put('/PageLayout /SinglePage'); - elseif($this->LayoutMode=='continuous') - $this->_put('/PageLayout /OneColumn'); - elseif($this->LayoutMode=='two') - $this->_put('/PageLayout /TwoColumnLeft'); -} - -protected function _putheader() -{ - $this->_put('%PDF-'.$this->PDFVersion); -} - -protected function _puttrailer() -{ - $this->_put('/Size '.($this->n+1)); - $this->_put('/Root '.$this->n.' 0 R'); - $this->_put('/Info '.($this->n-1).' 0 R'); -} - -protected function _enddoc() -{ - $this->_putheader(); - $this->_putpages(); - $this->_putresources(); - // Info - $this->_newobj(); - $this->_put('<<'); - $this->_putinfo(); - $this->_put('>>'); - $this->_put('endobj'); - // Catalog - $this->_newobj(); - $this->_put('<<'); - $this->_putcatalog(); - $this->_put('>>'); - $this->_put('endobj'); - // Cross-ref - $offset = $this->_getoffset(); - $this->_put('xref'); - $this->_put('0 '.($this->n+1)); - $this->_put('0000000000 65535 f '); - for($i=1;$i<=$this->n;$i++) - $this->_put(sprintf('%010d 00000 n ',$this->offsets[$i])); - // Trailer - $this->_put('trailer'); - $this->_put('<<'); - $this->_puttrailer(); - $this->_put('>>'); - $this->_put('startxref'); - $this->_put($offset); - $this->_put('%%EOF'); - $this->state = 3; -} -} ?> diff --git a/src/Model/Logs.php b/src/Model/Logs.php index 8cd0179..c7bb7d8 100644 --- a/src/Model/Logs.php +++ b/src/Model/Logs.php @@ -6,191 +6,179 @@ use PDO; class Logs { - public $id; - public $level; - public $message; - public $file; - public $line; - public $date; + const ERROR_LEVEL = [ + 1 => 'E_ERROR', + 2 => 'E_WARNING', + 4 => 'E_PARSE', + 8 => 'E_NOTICE', + 16 => 'E_CORE_ERROR', + 32 => 'E_CORE_WARNING', + 64 => 'E_COMPILE_ERROR', + 128 => 'E_COMPILE_WARNING', + 256 => 'E_USER_ERROR', + 512 => 'E_USER_WARNING', + 1024 => 'E_USER_NOTICE', + 2048 => 'E_STRICT', + 4096 => 'E_RECOVERABLE_ERROR', + 8192 => 'E_DEPRECATED', + 16384 => 'E_USER_DEPRECATED', + 32767 => 'E_ALL' + ]; + public $id; + public $level; + public $message; + public $file; + public $line; + public $date; - const ERROR_LEVEL = [ - 1 => 'E_ERROR', - 2 => 'E_WARNING', - 4 => 'E_PARSE', - 8 => 'E_NOTICE', - 16 => 'E_CORE_ERROR', - 32 => 'E_CORE_WARNING', - 64 => 'E_COMPILE_ERROR', - 128 => 'E_COMPILE_WARNING', - 256 => 'E_USER_ERROR', - 512 => 'E_USER_WARNING', - 1024 => 'E_USER_NOTICE', - 2048 => 'E_STRICT', - 4096 => 'E_RECOVERABLE_ERROR', - 8192 => 'E_DEPRECATED', - 16384 => 'E_USER_DEPRECATED', - 32767 => 'E_ALL' - ]; - - /** - * Logs constructor. - * @param int|null $id - * @param string|null $level - * @param string|null $message - * @param string|null $file - * @param string|null $line - * @param string|null $date - */ - public function __construct(int $id = null, string $level = null, string $message = null, string $file = null, string $line = null, string $date = null) - { - if ($id === NULL) { - return; - } - $this->id = $id; - $this->level = $level; - $this->message = $message; - $this->file = $file; - $this->line = $line; - $this->date = $date; - } + /** + * Logs constructor. + * + * @param int|null $id + * @param string|null $level + * @param string|null $message + * @param string|null $file + * @param string|null $line + * @param string|null $date + */ + public function __construct( int $id = null, string $level = null, string $message = null, string $file = null, string $line = null, string $date = null ) { + if ( $id === null ) { + return; + } + $this->id = $id; + $this->level = $level; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->date = $date; + } - public static function insert($level, $message, $file, $line, $date) { - Model::insert(BDTables::LOGS, [ - 'level' => $level, - 'message' => $message, - 'file' => $file, - 'line' => $line, - 'date' => date("Y-m-d H:i:s", strtotime($date)) - ]); - } + public static function insert( $level, $message, $file, $line, $date ) { + Model::insert( BDTables::LOGS, [ + 'level' => $level, + 'message' => $message, + 'file' => $file, + 'line' => $line, + 'date' => date( "Y-m-d H:i:s", strtotime( $date ) ) + ] ); + } - /** - * Retourne un tableau des derniers logs (limite en param) - * @param int $limit - * @return array - */ - public static function getLastLogs(int $limit){ - $req = BDD::instance()->prepare('SELECT * + /** + * Retourne un tableau des derniers logs (limite en param) + * + * @param int $limit + * + * @return array + */ + public static function getLastLogs( int $limit ) { + $req = BDD::instance()->prepare( 'SELECT * FROM ' . BDTables::LOGS . ' ORDER BY date DESC - LIMIT :limit'); - $req->bindValue('limit', $limit, PDO::PARAM_INT); - $req->execute(); - $return = []; + LIMIT :limit' ); + $req->bindValue( 'limit', $limit, PDO::PARAM_INT ); + $req->execute(); + $return = []; - foreach ($req->fetchAll() as $l){ - $log = new Logs($l['id'], $l['level'], $l['message'], $l['file'], $l['line'], $l['date']); - $return[] = $log; - } + foreach ( $req->fetchAll() as $l ) { + $log = new Logs( $l['id'], $l['level'], $l['message'], $l['file'], $l['line'], $l['date'] ); + $return[] = $log; + } - return $return; - } + return $return; + } - /** - * @return int - */ - public function getId() - { - return $this->id; - } + /** + * @return int + */ + public function getId() { + return $this->id; + } - /** - * @param int $id - */ - public function setId($id) - { - $this->id = $id; - } + /** + * @param int $id + */ + public function setId( $id ) { + $this->id = $id; + } - /** - * @return string - */ - public function getLevel() - { - return $this->level; - } + /** + * @return string + */ + public function getLevel() { + return $this->level; + } - /** - * @param string $level - */ - public function setLevel($level) - { - $this->level = $level; - } + /** + * @param string $level + */ + public function setLevel( $level ) { + $this->level = $level; + } - /** - * @return string - */ - public function getMessage() - { - return htmlspecialchars($this->message); - } + /** + * @return string + */ + public function getMessage() { + return htmlspecialchars( $this->message ); + } - /** - * @param string $message - */ - public function setMessage($message) - { - $this->message = $message; - } + /** + * @param string $message + */ + public function setMessage( $message ) { + $this->message = $message; + } - /** - * @return string - */ - public function getFile() - { - return htmlspecialchars($this->file); - } + /** + * @return string + */ + public function getFile() { + return htmlspecialchars( $this->file ); + } - /** - * @param string $file - */ - public function setFile($file) - { - $this->file = $file; - } + /** + * @param string $file + */ + public function setFile( $file ) { + $this->file = $file; + } - /** - * @return string - */ - public function getLine() - { - return htmlspecialchars($this->line); - } + /** + * @return string + */ + public function getLine() { + return htmlspecialchars( $this->line ); + } - /** - * @param string $line - */ - public function setLine($line) - { - $this->line = $line; - } + /** + * @param string $line + */ + public function setLine( $line ) { + $this->line = $line; + } - /** - * @return string - */ - public function getDate() - { - return $this->date; - } + /** + * @return string + */ + public function getDate() { + return $this->date; + } - /** - * @param string $date - */ - public function setDate($date) - { - $this->date = $date; - } + /** + * @param string $date + */ + public function setDate( $date ) { + $this->date = $date; + } - /** - * Retourne le type d'erreur en string (label) - * @return string - */ - public function getErrorLabel() - { - return htmlspecialchars(self::ERROR_LEVEL[$this->level]); - } + /** + * Retourne le type d'erreur en string (label) + * @return string + */ + public function getErrorLabel() { + return htmlspecialchars( self::ERROR_LEVEL[ $this->level ] ); + } } ?> \ No newline at end of file diff --git a/src/Model/Model.php b/src/Model/Model.php index 5b60b4d..770e66b 100644 --- a/src/Model/Model.php +++ b/src/Model/Model.php @@ -4,43 +4,47 @@ namespace base\Model; class Model { - /** - * Crée une reqette d'insertion en base àpartir du nom de la table et d'un tableau associatif et l'exécute - * @param string $tableName - * @param array $data - * @return int lastInsertId - */ - public static function insert(string $tableName, array $data){ - $req = BDD::instance()->prepare('INSERT INTO ' . $tableName . ' (' . implode(', ', array_keys($data)) . ') - VALUES (' . ':' . implode(', :', array_keys($data)) . ')'); - $req->execute($data); - return BDD::lastInsertId(); - } + /** + * Crée une reqette d'insertion en base àpartir du nom de la table et d'un tableau associatif et l'exécute + * + * @param string $tableName + * @param array $data + * + * @return int lastInsertId + */ + public static function insert( string $tableName, array $data ) { + $req = BDD::instance()->prepare( 'INSERT INTO ' . $tableName . ' (' . implode( ', ', array_keys( $data ) ) . ') + VALUES (' . ':' . implode( ', :', array_keys( $data ) ) . ')' ); + $req->execute( $data ); - /** - * Met à jour les données d'une ligne d'un table données - * @param string $tableName - * @param array $data - * @param string $idColumn - * @param int $idValue - */ - public static function update(string $tableName, array $data, string $idColumn, int $idValue){ - $reqStr = 'UPDATE ' . $tableName . ' SET '; - $lastKey = endKey($data); - foreach ($data as $key => $value){ - $reqStr .= $key . ' = :' . $key; - if($key != $lastKey) { - $reqStr .= ', '; - } - } - $reqStr .= ' WHERE ' . $idColumn . ' = :' . $idColumn; - $data[$idColumn] = $idValue; + return BDD::lastInsertId(); + } - //echo $reqStr; exit(); + /** + * Met à jour les données d'une ligne d'un table données + * + * @param string $tableName + * @param array $data + * @param string $idColumn + * @param int $idValue + */ + public static function update( string $tableName, array $data, string $idColumn, int $idValue ) { + $reqStr = 'UPDATE ' . $tableName . ' SET '; + $lastKey = endKey( $data ); + foreach ( $data as $key => $value ) { + $reqStr .= $key . ' = :' . $key; + if ( $key != $lastKey ) { + $reqStr .= ', '; + } + } + $reqStr .= ' WHERE ' . $idColumn . ' = :' . $idColumn; + $data[ $idColumn ] = $idValue; - $req = BDD::instance()->prepare($reqStr); - $req->execute($data); - } + //echo $reqStr; exit(); + + $req = BDD::instance()->prepare( $reqStr ); + $req->execute( $data ); + } } ?> \ No newline at end of file diff --git a/src/View/Site/SiteError.php b/src/View/Site/SiteError.php index 1281cad..a44449b 100644 --- a/src/View/Site/SiteError.php +++ b/src/View/Site/SiteError.php @@ -1,6 +1,6 @@ -
-
-
-
Que voulez-vous faire ?
-
+
+
+
+
Que voulez-vous faire ?
+
diff --git a/src/View/Site/tpl/footer.php b/src/View/Site/tpl/footer.php index 0896a97..bf4256e 100644 --- a/src/View/Site/tpl/footer.php +++ b/src/View/Site/tpl/footer.php @@ -1,7 +1,6 @@ + + + - - - - - + diff --git a/src/View/Site/tpl/head.php b/src/View/Site/tpl/head.php index 0111f2f..ae20ed6 100644 --- a/src/View/Site/tpl/head.php +++ b/src/View/Site/tpl/head.php @@ -15,25 +15,26 @@ <?= $this->head['title'] ?> - - - - - + + + + + - + - - + + - head['robotNoIndex']) && $this->head['robotNoIndex'] == true) { ?> - - + head['robotNoIndex'] ) && $this->head['robotNoIndex'] == true ) { ?> + + diff --git a/src/lib/functions.php b/src/lib/functions.php index 39631e9..f2dd8f3 100644 --- a/src/lib/functions.php +++ b/src/lib/functions.php @@ -1,130 +1,138 @@ "e", "&Ecute;" => "E", "à " => "a" ... - $str = preg_replace('#&([A-za-z])(?:acute|grave|cedil|circ|orn|ring|slash|th|tilde|uml);#', '\1', $str); + // transformer les caractères accentués en entités HTML + $str = htmlentities( $str, ENT_NOQUOTES, $encoding ); + // remplacer les entités HTML pour avoir juste le premier caractères non accentués + // Exemple : "&ecute;" => "e", "&Ecute;" => "E", "à " => "a" ... + $str = preg_replace( '#&([A-za-z])(?:acute|grave|cedil|circ|orn|ring|slash|th|tilde|uml);#', '\1', $str ); - // Remplacer les ligatures tel que : Œ, Æ ... - // Exemple "Å“" => "oe" - $str = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $str); - // Supprimer tout le reste - //$str = preg_replace('#&[^;]+;#', '', $str); - $str = str_replace( array("#", "&", "[", "^", ";", "]"), '', $str); + // Remplacer les ligatures tel que : Œ, Æ ... + // Exemple "Å“" => "oe" + $str = preg_replace( '#&([A-za-z]{2})(?:lig);#', '\1', $str ); + // Supprimer tout le reste + //$str = preg_replace('#&[^;]+;#', '', $str); + $str = str_replace( array( "#", "&", "[", "^", ";", "]" ), '', $str ); - //on passe tout en minuscule - $str = strtolower($str); + //on passe tout en minuscule + $str = strtolower( $str ); - if (substr($str, -1) == '_'){ - $str = substr($str, 0, -1); - } + if ( substr( $str, - 1 ) == '_' ) { + $str = substr( $str, 0, - 1 ); + } - return $str; + return $str; } /** * La fonction darkroom() renomme et redimensionne les photos envoyées lors de l'ajout d'un objet. + * * @param $img String Chemin absolu de l'image d'origine. * @param $to String Chemin absolu de l'image générée (.jpg). * @param $width Int Largeur de l'image générée. Si 0, valeur calculée en fonction de $height. * @param $height Int Hauteur de l'image génétée. Si 0, valeur calculée en fonction de $width. * Si $height = 0 et $width = 0, dimensions conservées mais conversion en .jpg + * * @return bool */ -function darkroom($img, $to, $width = 0, $height = 0, $quality = 100, $useGD = true) { +function darkroom( $img, $to, $width = 0, $height = 0, $quality = 100, $useGD = true ) { - $dimensions = getimagesize($img); - $ratio = $dimensions[0] / $dimensions[1]; + $dimensions = getimagesize( $img ); + $ratio = $dimensions[0] / $dimensions[1]; - // Calcul des dimensions si 0 passé en paramètre - if ($width == 0 && $height == 0) { - $width = $dimensions[0]; - $height = $dimensions[1]; - } else if ($height == 0) { - $height = round($width / $ratio); - } else if ($width == 0) { - $width = round($height * $ratio); - } + // Calcul des dimensions si 0 passé en paramètre + if ( $width == 0 && $height == 0 ) { + $width = $dimensions[0]; + $height = $dimensions[1]; + } else if ( $height == 0 ) { + $height = round( $width / $ratio ); + } else if ( $width == 0 ) { + $width = round( $height * $ratio ); + } - if ($dimensions[0] > ($width / $height) * $dimensions[1]){ - $dimY = $height; - $dimX = round($height * $dimensions[0] / $dimensions[1]); - } - if ($dimensions[0] < ($width / $height) * $dimensions[1]) { - $dimX = $width; - $dimY = round($width * $dimensions[1] / $dimensions[0]); - } - if ($dimensions[0] == ($width / $height) * $dimensions[1]) { - $dimX = $width; - $dimY = $height; - } + if ( $dimensions[0] > ( $width / $height ) * $dimensions[1] ) { + $dimY = $height; + $dimX = round( $height * $dimensions[0] / $dimensions[1] ); + } + if ( $dimensions[0] < ( $width / $height ) * $dimensions[1] ) { + $dimX = $width; + $dimY = round( $width * $dimensions[1] / $dimensions[0] ); + } + if ( $dimensions[0] == ( $width / $height ) * $dimensions[1] ) { + $dimX = $width; + $dimY = $height; + } - // Création de l'image avec la librairie GD - if ($useGD) { - $pattern = imagecreatetruecolor($width, $height); - $type = mime_content_type($img); - switch (substr($type, 6)) { - case 'jpeg': - $image = imagecreatefromjpeg($img); - break; - case 'gif': - $image = imagecreatefromgif($img); - break; - case 'png': - $image = imagecreatefrompng($img); - break; - } - imagecopyresampled($pattern, $image, 0, 0, 0, 0, $dimX, $dimY, $dimensions[0], $dimensions[1]); - imagedestroy($image); - imagejpeg($pattern, $to, $quality); + // Création de l'image avec la librairie GD + if ( $useGD ) { + $pattern = imagecreatetruecolor( $width, $height ); + $type = mime_content_type( $img ); + switch ( substr( $type, 6 ) ) { + case 'jpeg': + $image = imagecreatefromjpeg( $img ); + break; + case 'gif': + $image = imagecreatefromgif( $img ); + break; + case 'png': + $image = imagecreatefrompng( $img ); + break; + } + imagecopyresampled( $pattern, $image, 0, 0, 0, 0, $dimX, $dimY, $dimensions[0], $dimensions[1] ); + imagedestroy( $image ); + imagejpeg( $pattern, $to, $quality ); - return true; - } - return true; + return true; + } + + return true; } /** * Redéfini la gestion des erreurs + * * @param $errno * @param $errstr * @param $errfile * @param $errline + * * @return bool|void */ -function errorHandler($errno, $errstr, $errfile, $errline) { - if (!(error_reporting() & $errno)) { - // Ce code d'erreur n'est pas inclus dans error_reporting() - return ; - } +function errorHandler( $errno, $errstr, $errfile, $errline ) { + if ( ! ( error_reporting() & $errno ) ) { + // Ce code d'erreur n'est pas inclus dans error_reporting() + return; + } - // Insertion des logs - \base\Model\Logs::insert($errno, $errstr, $errfile, $errline, date('Y-m-d H:i:s')); - - ob_clean(); - new \base\Controller\Site\SiteError(500); + // Insertion des logs + \base\Model\Logs::insert( $errno, $errstr, $errfile, $errline, date( 'Y-m-d H:i:s' ) ); - /* Ne pas exécuter le gestionnaire interne de PHP */ - return ; + ob_clean(); + new \base\Controller\Site\SiteError( 500 ); + + /* Ne pas exécuter le gestionnaire interne de PHP */ + + return; } /** @@ -133,113 +141,115 @@ function errorHandler($errno, $errstr, $errfile, $errline) { */ function getBrowser() { - $u_agent = $_SERVER['HTTP_USER_AGENT']; - $bname = 'Unknown'; - $platform = 'Unknown'; - $ub = ""; + $u_agent = $_SERVER['HTTP_USER_AGENT']; + $bname = 'Unknown'; + $platform = 'Unknown'; + $ub = ""; - //First get the platform? - if (preg_match('/android/i', $u_agent) || preg_match('/Android/i', $u_agent)) { - $platform = 'android'; - } else if (preg_match('/linux/i', $u_agent)) { - $platform = 'linux'; - } else if (preg_match('/macintosh|mac os x/i', $u_agent)) { - $platform = 'mac'; - } else if (preg_match('/windows|win32/i', $u_agent)) { - $platform = 'windows'; - } + //First get the platform? + if ( preg_match( '/android/i', $u_agent ) || preg_match( '/Android/i', $u_agent ) ) { + $platform = 'android'; + } else if ( preg_match( '/linux/i', $u_agent ) ) { + $platform = 'linux'; + } else if ( preg_match( '/macintosh|mac os x/i', $u_agent ) ) { + $platform = 'mac'; + } else if ( preg_match( '/windows|win32/i', $u_agent ) ) { + $platform = 'windows'; + } - if (strstr($u_agent, 'mobile') || strstr($u_agent, 'Mobile')){ - $platform .= ' mobile'; - } + if ( strstr( $u_agent, 'mobile' ) || strstr( $u_agent, 'Mobile' ) ) { + $platform .= ' mobile'; + } - // Next get the name of the useragent yes seperately and for good reason - if (preg_match('/MSIE/i', $u_agent) && !preg_match('/Opera/i', $u_agent)) { - $bname = 'Internet Explorer'; - $ub = "MSIE"; - } else if (preg_match('/Edge/i', $u_agent)) { - $bname = 'Microsoft Edge'; - $ub = "Edge"; - } else if (preg_match('/Trident/i', $u_agent)) { - $bname = 'Internet Explorer'; - $ub = "rv"; - } else if (preg_match('/Firefox/i', $u_agent)) { - $bname = 'Mozilla Firefox'; - $ub = "Firefox"; - } else if (preg_match('/Chrome/i', $u_agent)) { - $bname = 'Google Chrome'; - $ub = "Chrome"; - } else if (preg_match('/Safari/i', $u_agent)) { - $bname = 'Apple Safari'; - $ub = "Safari"; - } else if (preg_match('/Opera/i', $u_agent)) { - $bname = 'Opera'; - $ub = "Opera"; - } else if (preg_match('/Netscape/i', $u_agent)) { - $bname = 'Netscape'; - $ub = "Netscape"; - } + // Next get the name of the useragent yes seperately and for good reason + if ( preg_match( '/MSIE/i', $u_agent ) && ! preg_match( '/Opera/i', $u_agent ) ) { + $bname = 'Internet Explorer'; + $ub = "MSIE"; + } else if ( preg_match( '/Edge/i', $u_agent ) ) { + $bname = 'Microsoft Edge'; + $ub = "Edge"; + } else if ( preg_match( '/Trident/i', $u_agent ) ) { + $bname = 'Internet Explorer'; + $ub = "rv"; + } else if ( preg_match( '/Firefox/i', $u_agent ) ) { + $bname = 'Mozilla Firefox'; + $ub = "Firefox"; + } else if ( preg_match( '/Chrome/i', $u_agent ) ) { + $bname = 'Google Chrome'; + $ub = "Chrome"; + } else if ( preg_match( '/Safari/i', $u_agent ) ) { + $bname = 'Apple Safari'; + $ub = "Safari"; + } else if ( preg_match( '/Opera/i', $u_agent ) ) { + $bname = 'Opera'; + $ub = "Opera"; + } else if ( preg_match( '/Netscape/i', $u_agent ) ) { + $bname = 'Netscape'; + $ub = "Netscape"; + } - // finally get the correct version number - // Added "|:" - $known = array('Version', $ub, 'other'); - $pattern = '#(?' . join('|', $known) . ')[/|: ]+(?[0-9.|a-zA-Z.]*)#'; - if (!preg_match_all($pattern, $u_agent, $matches)) { - // we have no matching number just continue - } + // finally get the correct version number + // Added "|:" + $known = array( 'Version', $ub, 'other' ); + $pattern = '#(?' . join( '|', $known ) . ')[/|: ]+(?[0-9.|a-zA-Z.]*)#'; + if ( ! preg_match_all( $pattern, $u_agent, $matches ) ) { + // we have no matching number just continue + } - // see how many we have - $i = count($matches['browser']); - if ($i != 1) { - //we will have two since we are not using 'other' argument yet - //see if version is before or after the name - if (strripos($u_agent,"Version") < strripos($u_agent,$ub)){ - $version= $matches['version'][0]; - } else { - $version= $matches['version'][1]; - } - } else { - $version= $matches['version'][0]; - } + // see how many we have + $i = count( $matches['browser'] ); + if ( $i != 1 ) { + //we will have two since we are not using 'other' argument yet + //see if version is before or after the name + if ( strripos( $u_agent, "Version" ) < strripos( $u_agent, $ub ) ) { + $version = $matches['version'][0]; + } else { + $version = $matches['version'][1]; + } + } else { + $version = $matches['version'][0]; + } - // check if we have a number - if ($version == null || $version == "") { - $version = "?"; - } + // check if we have a number + if ( $version == null || $version == "" ) { + $version = "?"; + } - return array ( - 'userAgent' => $u_agent, - 'platform' => $platform, - 'version' => $version, - 'pattern' => $pattern, - 'name' => $bname - ); + return array( + 'userAgent' => $u_agent, + 'platform' => $platform, + 'version' => $version, + 'pattern' => $pattern, + 'name' => $bname + ); } /** * @param $string * @param $limit + * * @return int */ -function getLimitWord($string, $limit) { - $i = $limit; - if (!isset($string) || empty($string)){ - return 0; - } - while ($i > 0 && $string[$i] != ' '){ - $i--; - } - return $i; +function getLimitWord( $string, $limit ) { + $i = $limit; + if ( ! isset( $string ) || empty( $string ) ) { + return 0; + } + while ( $i > 0 && $string[ $i ] != ' ' ) { + $i --; + } + + return $i; } /** - * @param array $destinataires [nom du destinataire => adresse du destinataire] On peut en ajouter autant que l'on veut - * @param string $subject Objet du mail - * @param string $body Corp du mail - * @param string|null $auteurMail L'auteur du mail Par défaut eldotravo@gmail.com - * @param array|null $files [nom du fichier => chemin du fichier] On peut en ajouter autant que l'on veut - * @param array|null $cci [nom du cci => adresse du cci] On peut en ajouter autant que l'on veut - * @param array|null $cc [nom du cc => adresse du cc] On peut en ajouter autant que l'on veut + * @param array $destinataires [nom du destinataire => adresse du destinataire] On peut en ajouter autant que l'on veut + * @param string $subject Objet du mail + * @param string $body Corp du mail + * @param string|null $auteurMail L'auteur du mail Par défaut eldotravo@gmail.com + * @param array|null $files [nom du fichier => chemin du fichier] On peut en ajouter autant que l'on veut + * @param array|null $cci [nom du cci => adresse du cci] On peut en ajouter autant que l'on veut + * @param array|null $cc [nom du cc => adresse du cc] On peut en ajouter autant que l'on veut */ /* function email(array $destinataires, string $subject, string $body, string $auteurMail = null, array $files = null, array $cci = null, $cc = null) { @@ -315,83 +325,89 @@ function email(array $destinataires, string $subject, string $body, string $aute * @param string $file * @param int $angle * @param string $newName + * * @return bool */ -function rotateImage(string $file, int $angle, string $newName) { - // Initialisation variable pou test futur - $image = null; - $type = mime_content_type($file); - // Création ressources en fonction de l'image - switch (substr($type, 6)) { - case 'jpeg': - $image = imagecreatefromjpeg($file); - break; - case 'png': - $image = imagecreatefrompng($file); - break; - } - // Si format image non prit en charge - if ($image == null){ - return false; - } - // Rotation de l'image - $rotate = imagerotate($image, $angle, 0); - // On recrée l'image au format de base - switch (substr($type, 6)) { - case 'jpeg': - imagejpeg($rotate, $file); - break; - case 'png': - imagepng($rotate, $file); - break; - } - imagedestroy($image); - imagedestroy($rotate); - rename($file, $newName); - return true; +function rotateImage( string $file, int $angle, string $newName ) { + // Initialisation variable pou test futur + $image = null; + $type = mime_content_type( $file ); + // Création ressources en fonction de l'image + switch ( substr( $type, 6 ) ) { + case 'jpeg': + $image = imagecreatefromjpeg( $file ); + break; + case 'png': + $image = imagecreatefrompng( $file ); + break; + } + // Si format image non prit en charge + if ( $image == null ) { + return false; + } + // Rotation de l'image + $rotate = imagerotate( $image, $angle, 0 ); + // On recrée l'image au format de base + switch ( substr( $type, 6 ) ) { + case 'jpeg': + imagejpeg( $rotate, $file ); + break; + case 'png': + imagepng( $rotate, $file ); + break; + } + imagedestroy( $image ); + imagedestroy( $rotate ); + rename( $file, $newName ); + + return true; } /** * @param array $data + * * @return array * Clean toutes les strings dans array en récursif, et filtre pour n'avoir qu'un espaces entre chaque mot */ -function cleanArray(array $data) { - if (!empty($data)){ - foreach ($data as $key => $donnée){ - switch (gettype($donnée)){ - case 'string': - if (!empty($donnée)){ - $new_string = ''; - foreach (explode(' ', trim($donnée)) as $str){ - if (!empty($str)){ - if ($new_string != ''){ - $new_string .= ' '; - } - $new_string .= $str; - } - } - $data[$key] = $new_string; - } - break; - case 'array': - if (!empty($donnée)){ - $data[$key] = cleanArray($donnée); - } - break; - } - } - } - return $data; +function cleanArray( array $data ) { + if ( ! empty( $data ) ) { + foreach ( $data as $key => $donnée ) { + switch ( gettype( $donnée ) ) { + case 'string': + if ( ! empty( $donnée ) ) { + $new_string = ''; + foreach ( explode( ' ', trim( $donnée ) ) as $str ) { + if ( ! empty( $str ) ) { + if ( $new_string != '' ) { + $new_string .= ' '; + } + $new_string .= $str; + } + } + $data[ $key ] = $new_string; + } + break; + case 'array': + if ( ! empty( $donnée ) ) { + $data[ $key ] = cleanArray( $donnée ); + } + break; + } + } + } + + return $data; } /** * @param $array + * * @return mixed */ -function endKey($array){ - end($array); - return key($array); +function endKey( $array ) { + end( $array ); + + return key( $array ); } ?> \ No newline at end of file diff --git a/src/lib/mail/PHPMailerAutoload.php b/src/lib/mail/PHPMailerAutoload.php index eaa2e30..2a82750 100644 --- a/src/lib/mail/PHPMailerAutoload.php +++ b/src/lib/mail/PHPMailerAutoload.php @@ -19,31 +19,31 @@ /** * PHPMailer SPL autoloader. + * * @param string $classname The name of the class to load */ -function PHPMailerAutoload($classname) -{ - //Can't use __DIR__ as it's only in PHP 5.3+ - $filename = dirname(__FILE__).DIRECTORY_SEPARATOR.'class.'.strtolower($classname).'.php'; - if (is_readable($filename)) { - require $filename; - } +function PHPMailerAutoload( $classname ) { + //Can't use __DIR__ as it's only in PHP 5.3+ + $filename = dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'class.' . strtolower( $classname ) . '.php'; + if ( is_readable( $filename ) ) { + require $filename; + } } -if (version_compare(PHP_VERSION, '5.1.2', '>=')) { - //SPL autoloading was introduced in PHP 5.1.2 - if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - spl_autoload_register('PHPMailerAutoload', true, true); - } else { - spl_autoload_register('PHPMailerAutoload'); - } +if ( version_compare( PHP_VERSION, '5.1.2', '>=' ) ) { + //SPL autoloading was introduced in PHP 5.1.2 + if ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ) { + spl_autoload_register( 'PHPMailerAutoload', true, true ); + } else { + spl_autoload_register( 'PHPMailerAutoload' ); + } } else { - /** - * Fall back to traditional autoload for old PHP versions - * @param string $classname The name of the class to load - */ - function __autoload($classname) - { - PHPMailerAutoload($classname); - } + /** + * Fall back to traditional autoload for old PHP versions + * + * @param string $classname The name of the class to load + */ + function __autoload( $classname ) { + PHPMailerAutoload( $classname ); + } } diff --git a/src/lib/mail/class.phpmailer.php b/src/lib/mail/class.phpmailer.php index d49626c..4278591 100644 --- a/src/lib/mail/class.phpmailer.php +++ b/src/lib/mail/class.phpmailer.php @@ -25,3864 +25,3872 @@ * @author Andy Prevost (codeworxtech) * @author Brent R. Matzelle (original founder) */ -class PHPMailer -{ - /** - * The PHPMailer Version number. - * @var string - */ - public $Version = '5.2.14'; - - /** - * Email priority. - * Options: null (default), 1 = High, 3 = Normal, 5 = low. - * When null, the header is not set at all. - * @var integer - */ - public $Priority = null; - - /** - * The character set of the message. - * @var string - */ - public $CharSet = 'utf-8'; - - /** - * The MIME Content-type of the message. - * @var string - */ - public $ContentType = 'text/plain'; - - /** - * The message encoding. - * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". - * @var string - */ - public $Encoding = '8bit'; - - /** - * Holds the most recent mailer error message. - * @var string - */ - public $ErrorInfo = ''; - - /** - * The From email address for the message. - * @var string - */ - public $From = 'root@localhost'; - - /** - * The From name of the message. - * @var string - */ - public $FromName = 'Root User'; - - /** - * The Sender email (Return-Path) of the message. - * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. - * @var string - */ - public $Sender = ''; - - /** - * The Return-Path of the message. - * If empty, it will be set to either From or Sender. - * @var string - * @deprecated Email senders should never set a return-path header; - * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything. - * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference - */ - public $ReturnPath = ''; - - /** - * The Subject of the message. - * @var string - */ - public $Subject = ''; - - /** - * An HTML or plain text message body. - * If HTML then call isHTML(true). - * @var string - */ - public $Body = ''; - - /** - * The plain-text message body. - * This body can be read by mail clients that do not have HTML email - * capability such as mutt & Eudora. - * Clients that can read HTML will view the normal Body. - * @var string - */ - public $AltBody = ''; - - /** - * An iCal message part body. - * Only supported in simple alt or alt_inline message types - * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator - * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ - * @link http://kigkonsult.se/iCalcreator/ - * @var string - */ - public $Ical = ''; - - /** - * The complete compiled MIME message body. - * @access protected - * @var string - */ - protected $MIMEBody = ''; - - /** - * The complete compiled MIME message headers. - * @var string - * @access protected - */ - protected $MIMEHeader = ''; - - /** - * Extra headers that createHeader() doesn't fold in. - * @var string - * @access protected - */ - protected $mailHeader = ''; - - /** - * Word-wrap the message body to this number of chars. - * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. - * @var integer - */ - public $WordWrap = 0; - - /** - * Which method to use to send mail. - * Options: "mail", "sendmail", or "smtp". - * @var string - */ - public $Mailer = 'mail'; - - /** - * The path to the sendmail program. - * @var string - */ - public $Sendmail = '/usr/sbin/sendmail'; - - /** - * Whether mail() uses a fully sendmail-compatible MTA. - * One which supports sendmail's "-oi -f" options. - * @var boolean - */ - public $UseSendmailOptions = true; - - /** - * Path to PHPMailer plugins. - * Useful if the SMTP class is not in the PHP include path. - * @var string - * @deprecated Should not be needed now there is an autoloader. - */ - public $PluginDir = ''; - - /** - * The email address that a reading confirmation should be sent to, also known as read receipt. - * @var string - */ - public $ConfirmReadingTo = ''; - - /** - * The hostname to use in the Message-ID header and as default HELO string. - * If empty, PHPMailer attempts to find one with, in order, - * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value - * 'localhost.localdomain'. - * @var string - */ - public $Hostname = ''; - - /** - * An ID to be used in the Message-ID header. - * If empty, a unique id will be generated. - * @var string - */ - public $MessageID = ''; - - /** - * The message Date to be used in the Date header. - * If empty, the current date will be added. - * @var string - */ - public $MessageDate = ''; - - /** - * SMTP hosts. - * Either a single hostname or multiple semicolon-delimited hostnames. - * You can also specify a different port - * for each host by using this format: [hostname:port] - * (e.g. "smtp1.example.com:25;smtp2.example.com"). - * You can also specify encryption type, for example: - * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). - * Hosts will be tried in order. - * @var string - */ - public $Host = 'localhost'; - - /** - * The default SMTP server port. - * @var integer - */ - public $Port = 25; - - /** - * The SMTP HELO of the message. - * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find - * one with the same method described above for $Hostname. - * @var string - * @see PHPMailer::$Hostname - */ - public $Helo = ''; - - /** - * What kind of encryption to use on the SMTP connection. - * Options: '', 'ssl' or 'tls' - * @var string - */ - public $SMTPSecure = ''; - - /** - * Whether to enable TLS encryption automatically if a server supports it, - * even if `SMTPSecure` is not set to 'tls'. - * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. - * @var boolean - */ - public $SMTPAutoTLS = true; - - /** - * Whether to use SMTP authentication. - * Uses the Username and Password properties. - * @var boolean - * @see PHPMailer::$Username - * @see PHPMailer::$Password - */ - public $SMTPAuth = false; - - /** - * Options array passed to stream_context_create when connecting via SMTP. - * @var array - */ - public $SMTPOptions = array(); - - /** - * SMTP username. - * @var string - */ - public $Username = ''; - - /** - * SMTP password. - * @var string - */ - public $Password = ''; - - /** - * SMTP auth type. - * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5 - * @var string - */ - public $AuthType = ''; - - /** - * SMTP realm. - * Used for NTLM auth - * @var string - */ - public $Realm = ''; - - /** - * SMTP workstation. - * Used for NTLM auth - * @var string - */ - public $Workstation = ''; - - /** - * The SMTP server timeout in seconds. - * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 - * @var integer - */ - public $Timeout = 300; - - /** - * SMTP class debug output mode. - * Debug output level. - * Options: - * * `0` No output - * * `1` Commands - * * `2` Data and commands - * * `3` As 2 plus connection status - * * `4` Low-level data output - * @var integer - * @see SMTP::$do_debug - */ - public $SMTPDebug = 0; - - /** - * How to handle debug output. - * Options: - * * `echo` Output plain-text as-is, appropriate for CLI - * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output - * * `error_log` Output to error log as configured in php.ini - * - * Alternatively, you can provide a callable expecting two params: a message string and the debug level: - * - * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; - * - * @var string|callable - * @see SMTP::$Debugoutput - */ - public $Debugoutput = 'echo'; - - /** - * Whether to keep SMTP connection open after each message. - * If this is set to true then to close the connection - * requires an explicit call to smtpClose(). - * @var boolean - */ - public $SMTPKeepAlive = false; - - /** - * Whether to split multiple to addresses into multiple messages - * or send them all in one message. - * Only supported in `mail` and `sendmail` transports, not in SMTP. - * @var boolean - */ - public $SingleTo = false; - - /** - * Storage for addresses when SingleTo is enabled. - * @var array - */ - public $SingleToArray = array(); - - /** - * Whether to generate VERP addresses on send. - * Only applicable when sending via SMTP. - * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path - * @link http://www.postfix.org/VERP_README.html Postfix VERP info - * @var boolean - */ - public $do_verp = false; - - /** - * Whether to allow sending messages with an empty body. - * @var boolean - */ - public $AllowEmpty = false; - - /** - * The default line ending. - * @note The default remains "\n". We force CRLF where we know - * it must be used via self::CRLF. - * @var string - */ - public $LE = "\n"; - - /** - * DKIM selector. - * @var string - */ - public $DKIM_selector = ''; - - /** - * DKIM Identity. - * Usually the email address used as the source of the email - * @var string - */ - public $DKIM_identity = ''; - - /** - * DKIM passphrase. - * Used if your key is encrypted. - * @var string - */ - public $DKIM_passphrase = ''; - - /** - * DKIM signing domain name. - * @example 'example.com' - * @var string - */ - public $DKIM_domain = ''; - - /** - * DKIM private key file path. - * @var string - */ - public $DKIM_private = ''; - - /** - * Callback Action function name. - * - * The function that handles the result of the send email action. - * It is called out by send() for each email sent. - * - * Value can be any php callable: http://www.php.net/is_callable - * - * Parameters: - * boolean $result result of the send action - * string $to email address of the recipient - * string $cc cc email addresses - * string $bcc bcc email addresses - * string $subject the subject - * string $body the email body - * string $from email address of sender - * @var string - */ - public $action_function = ''; - - /** - * What to put in the X-Mailer header. - * Options: An empty string for PHPMailer default, whitespace for none, or a string to use - * @var string - */ - public $XMailer = ''; - - /** - * An instance of the SMTP sender class. - * @var SMTP - * @access protected - */ - protected $smtp = null; - - /** - * The array of 'to' names and addresses. - * @var array - * @access protected - */ - protected $to = array(); - - /** - * The array of 'cc' names and addresses. - * @var array - * @access protected - */ - protected $cc = array(); - - /** - * The array of 'bcc' names and addresses. - * @var array - * @access protected - */ - protected $bcc = array(); - - /** - * The array of reply-to names and addresses. - * @var array - * @access protected - */ - protected $ReplyTo = array(); - - /** - * An array of all kinds of addresses. - * Includes all of $to, $cc, $bcc - * @var array - * @access protected - * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc - */ - protected $all_recipients = array(); - - /** - * An array of names and addresses queued for validation. - * In send(), valid and non duplicate entries are moved to $all_recipients - * and one of $to, $cc, or $bcc. - * This array is used only for addresses with IDN. - * @var array - * @access protected - * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc - * @see PHPMailer::$all_recipients - */ - protected $RecipientsQueue = array(); - - /** - * An array of reply-to names and addresses queued for validation. - * In send(), valid and non duplicate entries are moved to $ReplyTo. - * This array is used only for addresses with IDN. - * @var array - * @access protected - * @see PHPMailer::$ReplyTo - */ - protected $ReplyToQueue = array(); - - /** - * The array of attachments. - * @var array - * @access protected - */ - protected $attachment = array(); - - /** - * The array of custom headers. - * @var array - * @access protected - */ - protected $CustomHeader = array(); - - /** - * The most recent Message-ID (including angular brackets). - * @var string - * @access protected - */ - protected $lastMessageID = ''; - - /** - * The message's MIME type. - * @var string - * @access protected - */ - protected $message_type = ''; - - /** - * The array of MIME boundary strings. - * @var array - * @access protected - */ - protected $boundary = array(); - - /** - * The array of available languages. - * @var array - * @access protected - */ - protected $language = array(); - - /** - * The number of errors encountered. - * @var integer - * @access protected - */ - protected $error_count = 0; - - /** - * The S/MIME certificate file path. - * @var string - * @access protected - */ - protected $sign_cert_file = ''; - - /** - * The S/MIME key file path. - * @var string - * @access protected - */ - protected $sign_key_file = ''; - - /** - * The optional S/MIME extra certificates ("CA Chain") file path. - * @var string - * @access protected - */ - protected $sign_extracerts_file = ''; - - /** - * The S/MIME password for the key. - * Used only if the key is encrypted. - * @var string - * @access protected - */ - protected $sign_key_pass = ''; - - /** - * Whether to throw exceptions for errors. - * @var boolean - * @access protected - */ - protected $exceptions = false; - - /** - * Unique ID used for message ID and boundaries. - * @var string - * @access protected - */ - protected $uniqueid = ''; - - /** - * Error severity: message only, continue processing. - */ - const STOP_MESSAGE = 0; - - /** - * Error severity: message, likely ok to continue processing. - */ - const STOP_CONTINUE = 1; - - /** - * Error severity: message, plus full stop, critical error reached. - */ - const STOP_CRITICAL = 2; - - /** - * SMTP RFC standard line ending. - */ - const CRLF = "\r\n"; - - /** - * The maximum line length allowed by RFC 2822 section 2.1.1 - * @var integer - */ - const MAX_LINE_LENGTH = 998; - - /** - * Constructor. - * @param boolean $exceptions Should we throw external exceptions? - */ - public function __construct($exceptions = false) - { - $this->exceptions = (boolean)$exceptions; - } - - /** - * Destructor. - */ - public function __destruct() - { - //Close any open SMTP connection nicely - $this->smtpClose(); - } - - /** - * Call mail() in a safe_mode-aware fashion. - * Also, unless sendmail_path points to sendmail (or something that - * claims to be sendmail), don't pass params (not a perfect fix, - * but it will do) - * @param string $to To - * @param string $subject Subject - * @param string $body Message Body - * @param string $header Additional Header(s) - * @param string $params Params - * @access private - * @return boolean - */ - private function mailPassthru($to, $subject, $body, $header, $params) - { - //Check overloading of mail function to avoid double-encoding - if (ini_get('mbstring.func_overload') & 1) { - $subject = $this->secureHeader($subject); - } else { - $subject = $this->encodeHeader($this->secureHeader($subject)); - } - if (ini_get('safe_mode') || !($this->UseSendmailOptions)) { - $result = @mail($to, $subject, $body, $header); - } else { - $result = @mail($to, $subject, $body, $header, $params); - } - return $result; - } - - /** - * Output debugging info via user-defined method. - * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). - * @see PHPMailer::$Debugoutput - * @see PHPMailer::$SMTPDebug - * @param string $str - */ - protected function edebug($str) - { - if ($this->SMTPDebug <= 0) { - return; - } - //Avoid clash with built-in function names - if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { - call_user_func($this->Debugoutput, $str, $this->SMTPDebug); - return; - } - switch ($this->Debugoutput) { - case 'error_log': - //Don't output, just log - error_log($str); - break; - case 'html': - //Cleans up output a bit for a better looking, HTML-safe output - echo htmlentities( - preg_replace('/[\r\n]+/', '', $str), - ENT_QUOTES, - 'UTF-8' - ) - . "
\n"; - break; - case 'echo': - default: - //Normalize line breaks - $str = preg_replace('/\r\n?/ms', "\n", $str); - echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( - "\n", - "\n \t ", - trim($str) - ) . "\n"; - } - } - - /** - * Sets message type to HTML or plain. - * @param boolean $isHtml True for HTML mode. - * @return void - */ - public function isHTML($isHtml = true) - { - if ($isHtml) { - $this->ContentType = 'text/html'; - } else { - $this->ContentType = 'text/plain'; - } - } - - /** - * Send messages using SMTP. - * @return void - */ - public function isSMTP() - { - $this->Mailer = 'smtp'; - } - - /** - * Send messages using PHP's mail() function. - * @return void - */ - public function isMail() - { - $this->Mailer = 'mail'; - } - - /** - * Send messages using $Sendmail. - * @return void - */ - public function isSendmail() - { - $ini_sendmail_path = ini_get('sendmail_path'); - - if (!stristr($ini_sendmail_path, 'sendmail')) { - $this->Sendmail = '/usr/sbin/sendmail'; - } else { - $this->Sendmail = $ini_sendmail_path; - } - $this->Mailer = 'sendmail'; - } - - /** - * Send messages using qmail. - * @return void - */ - public function isQmail() - { - $ini_sendmail_path = ini_get('sendmail_path'); - - if (!stristr($ini_sendmail_path, 'qmail')) { - $this->Sendmail = '/var/qmail/bin/qmail-inject'; - } else { - $this->Sendmail = $ini_sendmail_path; - } - $this->Mailer = 'qmail'; - } - - /** - * Add a "To" address. - * @param string $address The email address to send to - * @param string $name - * @return boolean true on success, false if address already used or invalid in some way - */ - public function addAddress($address, $name = '') - { - return $this->addOrEnqueueAnAddress('to', $address, $name); - } - - /** - * Add a "CC" address. - * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. - * @param string $address The email address to send to - * @param string $name - * @return boolean true on success, false if address already used or invalid in some way - */ - public function addCC($address, $name = '') - { - return $this->addOrEnqueueAnAddress('cc', $address, $name); - } - - /** - * Add a "BCC" address. - * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. - * @param string $address The email address to send to - * @param string $name - * @return boolean true on success, false if address already used or invalid in some way - */ - public function addBCC($address, $name = '') - { - return $this->addOrEnqueueAnAddress('bcc', $address, $name); - } - - /** - * Add a "Reply-To" address. - * @param string $address The email address to reply to - * @param string $name - * @return boolean true on success, false if address already used or invalid in some way - */ - public function addReplyTo($address, $name = '') - { - return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); - } - - /** - * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer - * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still - * be modified after calling this function), addition of such addresses is delayed until send(). - * Addresses that have been added already return false, but do not throw exceptions. - * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' - * @param string $address The email address to send, resp. to reply to - * @param string $name - * @throws phpmailerException - * @return boolean true on success, false if address already used or invalid in some way - * @access protected - */ - protected function addOrEnqueueAnAddress($kind, $address, $name) - { - $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - if (($pos = strrpos($address, '@')) === false) { - // At-sign is misssing. - $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address"; - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new phpmailerException($error_message); - } - return false; - } - $params = array($kind, $address, $name); - // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. - if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) { - if ($kind != 'Reply-To') { - if (!array_key_exists($address, $this->RecipientsQueue)) { - $this->RecipientsQueue[$address] = $params; - return true; - } - } else { - if (!array_key_exists($address, $this->ReplyToQueue)) { - $this->ReplyToQueue[$address] = $params; - return true; - } - } - return false; - } - // Immediately add standard addresses without IDN. - return call_user_func_array(array($this, 'addAnAddress'), $params); - } - - /** - * Add an address to one of the recipient arrays or to the ReplyTo array. - * Addresses that have been added already return false, but do not throw exceptions. - * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' - * @param string $address The email address to send, resp. to reply to - * @param string $name - * @throws phpmailerException - * @return boolean true on success, false if address already used or invalid in some way - * @access protected - */ - protected function addAnAddress($kind, $address, $name = '') - { - if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) { - $error_message = $this->lang('Invalid recipient kind: ') . $kind; - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new phpmailerException($error_message); - } - return false; - } - if (!$this->validateAddress($address)) { - $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address"; - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new phpmailerException($error_message); - } - return false; - } - if ($kind != 'Reply-To') { - if (!array_key_exists(strtolower($address), $this->all_recipients)) { - array_push($this->$kind, array($address, $name)); - $this->all_recipients[strtolower($address)] = true; - return true; - } - } else { - if (!array_key_exists(strtolower($address), $this->ReplyTo)) { - $this->ReplyTo[strtolower($address)] = array($address, $name); - return true; - } - } - return false; - } - - /** - * Parse and validate a string containing one or more RFC822-style comma-separated email addresses - * of the form "display name
" into an array of name/address pairs. - * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. - * Note that quotes in the name part are removed. - * @param string $addrstr The address list string - * @param bool $useimap Whether to use the IMAP extension to parse the list - * @return array - * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation - */ - public function parseAddresses($addrstr, $useimap = true) - { - $addresses = array(); - if ($useimap and function_exists('imap_rfc822_parse_adrlist')) { - //Use this built-in parser if it's available - $list = imap_rfc822_parse_adrlist($addrstr, ''); - foreach ($list as $address) { - if ($address->host != '.SYNTAX-ERROR.') { - if ($this->validateAddress($address->mailbox . '@' . $address->host)) { - $addresses[] = array( - 'name' => (property_exists($address, 'personal') ? $address->personal : ''), - 'address' => $address->mailbox . '@' . $address->host - ); - } - } - } - } else { - //Use this simpler parser - $list = explode(',', $addrstr); - foreach ($list as $address) { - $address = trim($address); - //Is there a separate name part? - if (strpos($address, '<') === false) { - //No separate name, just use the whole thing - if ($this->validateAddress($address)) { - $addresses[] = array( - 'name' => '', - 'address' => $address - ); - } - } else { - list($name, $email) = explode('<', $address); - $email = trim(str_replace('>', '', $email)); - if ($this->validateAddress($email)) { - $addresses[] = array( - 'name' => trim(str_replace(array('"', "'"), '', $name)), - 'address' => $email - ); - } - } - } - } - return $addresses; - } - - /** - * Set the From and FromName properties. - * @param string $address - * @param string $name - * @param boolean $auto Whether to also set the Sender address, defaults to true - * @throws phpmailerException - * @return boolean - */ - public function setFrom($address, $name = '', $auto = true) - { - $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - // Don't validate now addresses with IDN. Will be done in send(). - if (($pos = strrpos($address, '@')) === false or - (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and - !$this->validateAddress($address)) { - $error_message = $this->lang('invalid_address') . " (setFrom) $address"; - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new phpmailerException($error_message); - } - return false; - } - $this->From = $address; - $this->FromName = $name; - if ($auto) { - if (empty($this->Sender)) { - $this->Sender = $address; - } - } - return true; - } - - /** - * Return the Message-ID header of the last email. - * Technically this is the value from the last time the headers were created, - * but it's also the message ID of the last sent message except in - * pathological cases. - * @return string - */ - public function getLastMessageID() - { - return $this->lastMessageID; - } - - /** - * Check that a string looks like an email address. - * @param string $address The email address to check - * @param string $patternselect A selector for the validation pattern to use : - * * `auto` Pick best pattern automatically; - * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14; - * * `pcre` Use old PCRE implementation; - * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; - * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. - * * `noregex` Don't use a regex: super fast, really dumb. - * @return boolean - * @static - * @access public - */ - public static function validateAddress($address, $patternselect = 'auto') - { - //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 - if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) { - return false; - } - if (!$patternselect or $patternselect == 'auto') { - //Check this constant first so it works when extension_loaded() is disabled by safe mode - //Constant was added in PHP 5.2.4 - if (defined('PCRE_VERSION')) { - //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2 - if (version_compare(PCRE_VERSION, '8.0.3') >= 0) { - $patternselect = 'pcre8'; - } else { - $patternselect = 'pcre'; - } - } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) { - //Fall back to older PCRE - $patternselect = 'pcre'; - } else { - //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension - if (version_compare(PHP_VERSION, '5.2.0') >= 0) { - $patternselect = 'php'; - } else { - $patternselect = 'noregex'; - } - } - } - switch ($patternselect) { - case 'pcre8': - /** - * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains. - * @link http://squiloople.com/2009/12/20/email-address-validation/ - * @copyright 2009-2010 Michael Rushton - * Feel free to use and redistribute this code. But please keep this copyright notice. - */ - return (boolean)preg_match( - '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . - '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . - '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . - '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . - '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . - '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . - '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . - '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . - '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', - $address - ); - case 'pcre': - //An older regex that doesn't need a recent PCRE - return (boolean)preg_match( - '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' . - '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' . - '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' . - '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' . - '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' . - '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' . - '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' . - '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' . - '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' . - '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', - $address - ); - case 'html5': - /** - * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. - * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) - */ - return (boolean)preg_match( - '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . - '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', - $address - ); - case 'noregex': - //No PCRE! Do something _very_ approximate! - //Check the address is 3 chars or longer and contains an @ that's not the first or last char - return (strlen($address) >= 3 - and strpos($address, '@') >= 1 - and strpos($address, '@') != strlen($address) - 1); - case 'php': - default: - return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL); - } - } - - /** - * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the - * "intl" and "mbstring" PHP extensions. - * @return bool "true" if required functions for IDN support are present - */ - public function idnSupported() - { - return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding'); - } - - /** - * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. - * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. - * This function silently returns unmodified address if: - * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) - * - Conversion to punycode is impossible (e.g. required PHP functions are not available) - * or fails for any reason (e.g. domain has characters not allowed in an IDN) - * @see PHPMailer::$CharSet - * @param string $address The email address to convert - * @return string The encoded address in ASCII form - */ - public function punyencodeAddress($address) - { - // Verify we have required functions, CharSet, and at-sign. - if ($this->idnSupported() and - !empty($this->CharSet) and - ($pos = strrpos($address, '@')) !== false) { - $domain = substr($address, ++$pos); - // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. - if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) { - $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); - if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ? - idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) : - idn_to_ascii($domain)) !== false) { - return substr($address, 0, $pos) . $punycode; - } - } - } - return $address; - } - - /** - * Create a message and send it. - * Uses the sending method specified by $Mailer. - * @throws phpmailerException - * @return boolean false on error - See the ErrorInfo property for details of the error. - */ - public function send() - { - try { - if (!$this->preSend()) { - return false; - } - return $this->postSend(); - } catch (phpmailerException $exc) { - $this->mailHeader = ''; - $this->setError($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - return false; - } - } - - /** - * Prepare a message for sending. - * @throws phpmailerException - * @return boolean - */ - public function preSend() - { - try { - $this->error_count = 0; // Reset errors - $this->mailHeader = ''; - - // Dequeue recipient and Reply-To addresses with IDN - foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { - $params[1] = $this->punyencodeAddress($params[1]); - call_user_func_array(array($this, 'addAnAddress'), $params); - } - if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) { - throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL); - } - - // Validate From, Sender, and ConfirmReadingTo addresses - foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) { - $this->$address_kind = trim($this->$address_kind); - if (empty($this->$address_kind)) { - continue; - } - $this->$address_kind = $this->punyencodeAddress($this->$address_kind); - if (!$this->validateAddress($this->$address_kind)) { - $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind; - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new phpmailerException($error_message); - } - return false; - } - } - - // Set whether the message is multipart/alternative - if ($this->alternativeExists()) { - $this->ContentType = 'multipart/alternative'; - } - - $this->setMessageType(); - // Refuse to send an empty message unless we are specifically allowing it - if (!$this->AllowEmpty and empty($this->Body)) { - throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL); - } - - // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) - $this->MIMEHeader = ''; - $this->MIMEBody = $this->createBody(); - // createBody may have added some headers, so retain them - $tempheaders = $this->MIMEHeader; - $this->MIMEHeader = $this->createHeader(); - $this->MIMEHeader .= $tempheaders; - - // To capture the complete message when using mail(), create - // an extra header list which createHeader() doesn't fold in - if ($this->Mailer == 'mail') { - if (count($this->to) > 0) { - $this->mailHeader .= $this->addrAppend('To', $this->to); - } else { - $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); - } - $this->mailHeader .= $this->headerLine( - 'Subject', - $this->encodeHeader($this->secureHeader(trim($this->Subject))) - ); - } - - // Sign with DKIM if enabled - if (!empty($this->DKIM_domain) - && !empty($this->DKIM_private) - && !empty($this->DKIM_selector) - && file_exists($this->DKIM_private)) { - $header_dkim = $this->DKIM_Add( - $this->MIMEHeader . $this->mailHeader, - $this->encodeHeader($this->secureHeader($this->Subject)), - $this->MIMEBody - ); - $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF . - str_replace("\r\n", "\n", $header_dkim) . self::CRLF; - } - return true; - } catch (phpmailerException $exc) { - $this->setError($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - return false; - } - } - - /** - * Actually send a message. - * Send the email via the selected mechanism - * @throws phpmailerException - * @return boolean - */ - public function postSend() - { - try { - // Choose the mailer and send through it - switch ($this->Mailer) { - case 'sendmail': - case 'qmail': - return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); - case 'smtp': - return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); - case 'mail': - return $this->mailSend($this->MIMEHeader, $this->MIMEBody); - default: - $sendMethod = $this->Mailer.'Send'; - if (method_exists($this, $sendMethod)) { - return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); - } - - return $this->mailSend($this->MIMEHeader, $this->MIMEBody); - } - } catch (phpmailerException $exc) { - $this->setError($exc->getMessage()); - $this->edebug($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - } - return false; - } - - /** - * Send mail using the $Sendmail program. - * @param string $header The message headers - * @param string $body The message body - * @see PHPMailer::$Sendmail - * @throws phpmailerException - * @access protected - * @return boolean - */ - protected function sendmailSend($header, $body) - { - if ($this->Sender != '') { - if ($this->Mailer == 'qmail') { - $sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); - } else { - $sendmail = sprintf('%s -oi -f%s -t', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender)); - } - } else { - if ($this->Mailer == 'qmail') { - $sendmail = sprintf('%s', escapeshellcmd($this->Sendmail)); - } else { - $sendmail = sprintf('%s -oi -t', escapeshellcmd($this->Sendmail)); - } - } - if ($this->SingleTo) { - foreach ($this->SingleToArray as $toAddr) { - if (!@$mail = popen($sendmail, 'w')) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - fputs($mail, 'To: ' . $toAddr . "\n"); - fputs($mail, $header); - fputs($mail, $body); - $result = pclose($mail); - $this->doCallback( - ($result == 0), - array($toAddr), - $this->cc, - $this->bcc, - $this->Subject, - $body, - $this->From - ); - if ($result != 0) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - } - } else { - if (!@$mail = popen($sendmail, 'w')) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - fputs($mail, $header); - fputs($mail, $body); - $result = pclose($mail); - $this->doCallback( - ($result == 0), - $this->to, - $this->cc, - $this->bcc, - $this->Subject, - $body, - $this->From - ); - if ($result != 0) { - throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - } - return true; - } - - /** - * Send mail using the PHP mail() function. - * @param string $header The message headers - * @param string $body The message body - * @link http://www.php.net/manual/en/book.mail.php - * @throws phpmailerException - * @access protected - * @return boolean - */ - protected function mailSend($header, $body) - { - $toArr = array(); - foreach ($this->to as $toaddr) { - $toArr[] = $this->addrFormat($toaddr); - } - $to = implode(', ', $toArr); - - if (empty($this->Sender)) { - $params = ' '; - } else { - $params = sprintf('-f%s', $this->Sender); - } - if ($this->Sender != '' and !ini_get('safe_mode')) { - $old_from = ini_get('sendmail_from'); - ini_set('sendmail_from', $this->Sender); - } - $result = false; - if ($this->SingleTo && count($toArr) > 1) { - foreach ($toArr as $toAddr) { - $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); - $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From); - } - } else { - $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); - $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); - } - if (isset($old_from)) { - ini_set('sendmail_from', $old_from); - } - if (!$result) { - throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL); - } - return true; - } - - /** - * Get an instance to use for SMTP operations. - * Override this function to load your own SMTP implementation - * @return SMTP - */ - public function getSMTPInstance() - { - if (!is_object($this->smtp)) { - $this->smtp = new SMTP; - } - return $this->smtp; - } - - /** - * Send mail via SMTP. - * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. - * Uses the PHPMailerSMTP class by default. - * @see PHPMailer::getSMTPInstance() to use a different class. - * @param string $header The message headers - * @param string $body The message body - * @throws phpmailerException - * @uses SMTP - * @access protected - * @return boolean - */ - protected function smtpSend($header, $body) - { - $bad_rcpt = array(); - if (!$this->smtpConnect($this->SMTPOptions)) { - throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); - } - if ('' == $this->Sender) { - $smtp_from = $this->From; - } else { - $smtp_from = $this->Sender; - } - if (!$this->smtp->mail($smtp_from)) { - $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); - throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL); - } - - // Attempt to send to all recipients - foreach (array($this->to, $this->cc, $this->bcc) as $togroup) { - foreach ($togroup as $to) { - if (!$this->smtp->recipient($to[0])) { - $error = $this->smtp->getError(); - $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']); - $isSent = false; - } else { - $isSent = true; - } - $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From); - } - } - - // Only send the DATA command if we have viable recipients - if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) { - throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL); - } - if ($this->SMTPKeepAlive) { - $this->smtp->reset(); - } else { - $this->smtp->quit(); - $this->smtp->close(); - } - //Create error message for any bad addresses - if (count($bad_rcpt) > 0) { - $errstr = ''; - foreach ($bad_rcpt as $bad) { - $errstr .= $bad['to'] . ': ' . $bad['error']; - } - throw new phpmailerException( - $this->lang('recipients_failed') . $errstr, - self::STOP_CONTINUE - ); - } - return true; - } - - /** - * Initiate a connection to an SMTP server. - * Returns false if the operation failed. - * @param array $options An array of options compatible with stream_context_create() - * @return bool - * @throws null - * @throws phpmailerException - * @uses SMTP - * @access public - */ - public function smtpConnect($options = array()) - { - if (is_null($this->smtp)) { - $this->smtp = $this->getSMTPInstance(); - } - - // Already connected? - if ($this->smtp->connected()) { - return true; - } - - $this->smtp->setTimeout($this->Timeout); - $this->smtp->setDebugLevel($this->SMTPDebug); - $this->smtp->setDebugOutput($this->Debugoutput); - $this->smtp->setVerp($this->do_verp); - $hosts = explode(';', $this->Host); - $lastexception = null; - - foreach ($hosts as $hostentry) { - $hostinfo = array(); - if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { - // Not a valid host entry - continue; - } - // $hostinfo[2]: optional ssl or tls prefix - // $hostinfo[3]: the hostname - // $hostinfo[4]: optional port number - // The host string prefix can temporarily override the current setting for SMTPSecure - // If it's not specified, the default value is used - $prefix = ''; - $secure = $this->SMTPSecure; - $tls = ($this->SMTPSecure == 'tls'); - if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) { - $prefix = 'ssl://'; - $tls = false; // Can't have SSL and TLS at the same time - $secure = 'ssl'; - } elseif ($hostinfo[2] == 'tls') { - $tls = true; - // tls doesn't use a prefix - $secure = 'tls'; - } - //Do we need the OpenSSL extension? - $sslext = defined('OPENSSL_ALGO_SHA1'); - if ('tls' === $secure or 'ssl' === $secure) { - //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled - if (!$sslext) { - throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL); - } - } - $host = $hostinfo[3]; - $port = $this->Port; - $tport = (integer)$hostinfo[4]; - if ($tport > 0 and $tport < 65536) { - $port = $tport; - } - if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { - try { - if ($this->Helo) { - $hello = $this->Helo; - } else { - $hello = $this->serverHostname(); - } - $this->smtp->hello($hello); - //Automatically enable TLS encryption if: - // * it's not disabled - // * we have openssl extension - // * we are not already using SSL - // * the server offers STARTTLS - if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) { - $tls = true; - } - if ($tls) { - if (!$this->smtp->startTLS()) { - throw new phpmailerException($this->lang('connect_host')); - } - // We must resend HELO after tls negotiation - $this->smtp->hello($hello); - } - if ($this->SMTPAuth) { - if (!$this->smtp->authenticate( - $this->Username, - $this->Password, - $this->AuthType, - $this->Realm, - $this->Workstation - ) - ) { - throw new phpmailerException($this->lang('authenticate')); - } - } - return true; - } catch (phpmailerException $exc) { - $lastexception = $exc; - $this->edebug($exc->getMessage()); - // We must have connected, but then failed TLS or Auth, so close connection nicely - $this->smtp->quit(); - } - } - } - // If we get here, all connection attempts have failed, so close connection hard - $this->smtp->close(); - // As we've caught all exceptions, just report whatever the last one was - if ($this->exceptions and !is_null($lastexception)) { - throw $lastexception; - } - return false; - } - - /** - * Close the active SMTP session if one exists. - * @return void - */ - public function smtpClose() - { - if (is_a($this->smtp, 'SMTP')) { - if ($this->smtp->connected()) { - $this->smtp->quit(); - $this->smtp->close(); - } - } - } - - /** - * Set the language for error messages. - * Returns false if it cannot load the language file. - * The default language is English. - * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") - * @param string $lang_path Path to the language file directory, with trailing separator (slash) - * @return boolean - * @access public - */ - public function setLanguage($langcode = 'en', $lang_path = '') - { - // Define full set of translatable strings in English - $PHPMAILER_LANG = array( - 'authenticate' => 'SMTP Error: Could not authenticate.', - 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', - 'data_not_accepted' => 'SMTP Error: data not accepted.', - 'empty_message' => 'Message body empty', - 'encoding' => 'Unknown encoding: ', - 'execute' => 'Could not execute: ', - 'file_access' => 'Could not access file: ', - 'file_open' => 'File Error: Could not open file: ', - 'from_failed' => 'The following From address failed: ', - 'instantiate' => 'Could not instantiate mail function.', - 'invalid_address' => 'Invalid address: ', - 'mailer_not_supported' => ' mailer is not supported.', - 'provide_address' => 'You must provide at least one recipient email address.', - 'recipients_failed' => 'SMTP Error: The following recipients failed: ', - 'signing' => 'Signing Error: ', - 'smtp_connect_failed' => 'SMTP connect() failed.', - 'smtp_error' => 'SMTP server error: ', - 'variable_set' => 'Cannot set or reset variable: ', - 'extension_missing' => 'Extension missing: ' - ); - if (empty($lang_path)) { - // Calculate an absolute path so it can work if CWD is not here - $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR; - } - $foundlang = true; - $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; - // There is no English translation file - if ($langcode != 'en') { - // Make sure language file path is readable - if (!is_readable($lang_file)) { - $foundlang = false; - } else { - // Overwrite language-specific strings. - // This way we'll never have missing translation keys. - $foundlang = include $lang_file; - } - } - $this->language = $PHPMAILER_LANG; - return (boolean)$foundlang; // Returns false if language not found - } - - /** - * Get the array of strings for the current language. - * @return array - */ - public function getTranslations() - { - return $this->language; - } - - /** - * Create recipient headers. - * @access public - * @param string $type - * @param array $addr An array of recipient, - * where each recipient is a 2-element indexed array with element 0 containing an address - * and element 1 containing a name, like: - * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User')) - * @return string - */ - public function addrAppend($type, $addr) - { - $addresses = array(); - foreach ($addr as $address) { - $addresses[] = $this->addrFormat($address); - } - return $type . ': ' . implode(', ', $addresses) . $this->LE; - } - - /** - * Format an address for use in a message header. - * @access public - * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name - * like array('joe@example.com', 'Joe User') - * @return string - */ - public function addrFormat($addr) - { - if (empty($addr[1])) { // No name provided - return $this->secureHeader($addr[0]); - } else { - return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader( - $addr[0] - ) . '>'; - } - } - - /** - * Word-wrap message. - * For use with mailers that do not automatically perform wrapping - * and for quoted-printable encoded messages. - * Original written by philippe. - * @param string $message The message to wrap - * @param integer $length The line length to wrap to - * @param boolean $qp_mode Whether to run in Quoted-Printable mode - * @access public - * @return string - */ - public function wrapText($message, $length, $qp_mode = false) - { - if ($qp_mode) { - $soft_break = sprintf(' =%s', $this->LE); - } else { - $soft_break = $this->LE; - } - // If utf-8 encoding is used, we will need to make sure we don't - // split multibyte characters when we wrap - $is_utf8 = (strtolower($this->CharSet) == 'utf-8'); - $lelen = strlen($this->LE); - $crlflen = strlen(self::CRLF); - - $message = $this->fixEOL($message); - //Remove a trailing line break - if (substr($message, -$lelen) == $this->LE) { - $message = substr($message, 0, -$lelen); - } - - //Split message into lines - $lines = explode($this->LE, $message); - //Message will be rebuilt in here - $message = ''; - foreach ($lines as $line) { - $words = explode(' ', $line); - $buf = ''; - $firstword = true; - foreach ($words as $word) { - if ($qp_mode and (strlen($word) > $length)) { - $space_left = $length - strlen($buf) - $crlflen; - if (!$firstword) { - if ($space_left > 20) { - $len = $space_left; - if ($is_utf8) { - $len = $this->utf8CharBoundary($word, $len); - } elseif (substr($word, $len - 1, 1) == '=') { - $len--; - } elseif (substr($word, $len - 2, 1) == '=') { - $len -= 2; - } - $part = substr($word, 0, $len); - $word = substr($word, $len); - $buf .= ' ' . $part; - $message .= $buf . sprintf('=%s', self::CRLF); - } else { - $message .= $buf . $soft_break; - } - $buf = ''; - } - while (strlen($word) > 0) { - if ($length <= 0) { - break; - } - $len = $length; - if ($is_utf8) { - $len = $this->utf8CharBoundary($word, $len); - } elseif (substr($word, $len - 1, 1) == '=') { - $len--; - } elseif (substr($word, $len - 2, 1) == '=') { - $len -= 2; - } - $part = substr($word, 0, $len); - $word = substr($word, $len); - - if (strlen($word) > 0) { - $message .= $part . sprintf('=%s', self::CRLF); - } else { - $buf = $part; - } - } - } else { - $buf_o = $buf; - if (!$firstword) { - $buf .= ' '; - } - $buf .= $word; - - if (strlen($buf) > $length and $buf_o != '') { - $message .= $buf_o . $soft_break; - $buf = $word; - } - } - $firstword = false; - } - $message .= $buf . self::CRLF; - } - - return $message; - } - - /** - * Find the last character boundary prior to $maxLength in a utf-8 - * quoted-printable encoded string. - * Original written by Colin Brown. - * @access public - * @param string $encodedText utf-8 QP text - * @param integer $maxLength Find the last character boundary prior to this length - * @return integer - */ - public function utf8CharBoundary($encodedText, $maxLength) - { - $foundSplitPos = false; - $lookBack = 3; - while (!$foundSplitPos) { - $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack); - $encodedCharPos = strpos($lastChunk, '='); - if (false !== $encodedCharPos) { - // Found start of encoded character byte within $lookBack block. - // Check the encoded byte value (the 2 chars after the '=') - $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2); - $dec = hexdec($hex); - if ($dec < 128) { - // Single byte character. - // If the encoded char was found at pos 0, it will fit - // otherwise reduce maxLength to start of the encoded char - if ($encodedCharPos > 0) { - $maxLength = $maxLength - ($lookBack - $encodedCharPos); - } - $foundSplitPos = true; - } elseif ($dec >= 192) { - // First byte of a multi byte character - // Reduce maxLength to split at start of character - $maxLength = $maxLength - ($lookBack - $encodedCharPos); - $foundSplitPos = true; - } elseif ($dec < 192) { - // Middle byte of a multi byte character, look further back - $lookBack += 3; - } - } else { - // No encoded character found - $foundSplitPos = true; - } - } - return $maxLength; - } - - /** - * Apply word wrapping to the message body. - * Wraps the message body to the number of chars set in the WordWrap property. - * You should only do this to plain-text bodies as wrapping HTML tags may break them. - * This is called automatically by createBody(), so you don't need to call it yourself. - * @access public - * @return void - */ - public function setWordWrap() - { - if ($this->WordWrap < 1) { - return; - } - - switch ($this->message_type) { - case 'alt': - case 'alt_inline': - case 'alt_attach': - case 'alt_inline_attach': - $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap); - break; - default: - $this->Body = $this->wrapText($this->Body, $this->WordWrap); - break; - } - } - - /** - * Assemble message headers. - * @access public - * @return string The assembled headers - */ - public function createHeader() - { - $result = ''; - - if ($this->MessageDate == '') { - $this->MessageDate = self::rfcDate(); - } - $result .= $this->headerLine('Date', $this->MessageDate); - - // To be created automatically by mail() - if ($this->SingleTo) { - if ($this->Mailer != 'mail') { - foreach ($this->to as $toaddr) { - $this->SingleToArray[] = $this->addrFormat($toaddr); - } - } - } else { - if (count($this->to) > 0) { - if ($this->Mailer != 'mail') { - $result .= $this->addrAppend('To', $this->to); - } - } elseif (count($this->cc) == 0) { - $result .= $this->headerLine('To', 'undisclosed-recipients:;'); - } - } - - $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName))); - - // sendmail and mail() extract Cc from the header before sending - if (count($this->cc) > 0) { - $result .= $this->addrAppend('Cc', $this->cc); - } - - // sendmail and mail() extract Bcc from the header before sending - if (( - $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail' - ) - and count($this->bcc) > 0 - ) { - $result .= $this->addrAppend('Bcc', $this->bcc); - } - - if (count($this->ReplyTo) > 0) { - $result .= $this->addrAppend('Reply-To', $this->ReplyTo); - } - - // mail() sets the subject itself - if ($this->Mailer != 'mail') { - $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject))); - } - - if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) { - $this->lastMessageID = $this->MessageID; - } else { - $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname()); - } - $result .= $this->headerLine('Message-ID', $this->lastMessageID); - if (!is_null($this->Priority)) { - $result .= $this->headerLine('X-Priority', $this->Priority); - } - if ($this->XMailer == '') { - $result .= $this->headerLine( - 'X-Mailer', - 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)' - ); - } else { - $myXmailer = trim($this->XMailer); - if ($myXmailer) { - $result .= $this->headerLine('X-Mailer', $myXmailer); - } - } - - if ($this->ConfirmReadingTo != '') { - $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>'); - } - - // Add custom headers - foreach ($this->CustomHeader as $header) { - $result .= $this->headerLine( - trim($header[0]), - $this->encodeHeader(trim($header[1])) - ); - } - if (!$this->sign_key_file) { - $result .= $this->headerLine('MIME-Version', '1.0'); - $result .= $this->getMailMIME(); - } - - return $result; - } - - /** - * Get the message MIME type headers. - * @access public - * @return string - */ - public function getMailMIME() - { - $result = ''; - $ismultipart = true; - switch ($this->message_type) { - case 'inline': - $result .= $this->headerLine('Content-Type', 'multipart/related;'); - $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); - break; - case 'attach': - case 'inline_attach': - case 'alt_attach': - case 'alt_inline_attach': - $result .= $this->headerLine('Content-Type', 'multipart/mixed;'); - $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); - break; - case 'alt': - case 'alt_inline': - $result .= $this->headerLine('Content-Type', 'multipart/alternative;'); - $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"'); - break; - default: - // Catches case 'plain': and case '': - $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); - $ismultipart = false; - break; - } - // RFC1341 part 5 says 7bit is assumed if not specified - if ($this->Encoding != '7bit') { - // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE - if ($ismultipart) { - if ($this->Encoding == '8bit') { - $result .= $this->headerLine('Content-Transfer-Encoding', '8bit'); - } - // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible - } else { - $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); - } - } - - if ($this->Mailer != 'mail') { - $result .= $this->LE; - } - - return $result; - } - - /** - * Returns the whole MIME message. - * Includes complete headers and body. - * Only valid post preSend(). - * @see PHPMailer::preSend() - * @access public - * @return string - */ - public function getSentMIMEMessage() - { - return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody; - } - - /** - * Assemble the message body. - * Returns an empty string on failure. - * @access public - * @throws phpmailerException - * @return string The assembled message body - */ - public function createBody() - { - $body = ''; - //Create unique IDs and preset boundaries - $this->uniqueid = md5(uniqid(time())); - $this->boundary[1] = 'b1_' . $this->uniqueid; - $this->boundary[2] = 'b2_' . $this->uniqueid; - $this->boundary[3] = 'b3_' . $this->uniqueid; - - if ($this->sign_key_file) { - $body .= $this->getMailMIME() . $this->LE; - } - - $this->setWordWrap(); - - $bodyEncoding = $this->Encoding; - $bodyCharSet = $this->CharSet; - //Can we do a 7-bit downgrade? - if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) { - $bodyEncoding = '7bit'; - $bodyCharSet = 'us-ascii'; - } - //If lines are too long, and we're not already using an encoding that will shorten them, - //change to quoted-printable transfer encoding - if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) { - $this->Encoding = 'quoted-printable'; - $bodyEncoding = 'quoted-printable'; - } - - $altBodyEncoding = $this->Encoding; - $altBodyCharSet = $this->CharSet; - //Can we do a 7-bit downgrade? - if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) { - $altBodyEncoding = '7bit'; - $altBodyCharSet = 'us-ascii'; - } - //If lines are too long, and we're not already using an encoding that will shorten them, - //change to quoted-printable transfer encoding - if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) { - $altBodyEncoding = 'quoted-printable'; - } - //Use this as a preamble in all multipart message types - $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE; - switch ($this->message_type) { - case 'inline': - $body .= $mimepre; - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[1]); - break; - case 'attach': - $body .= $mimepre; - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - case 'inline_attach': - $body .= $mimepre; - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/related;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[2]); - $body .= $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - case 'alt': - $body .= $mimepre; - $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - if (!empty($this->Ical)) { - $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', ''); - $body .= $this->encodeString($this->Ical, $this->Encoding); - $body .= $this->LE . $this->LE; - } - $body .= $this->endBoundary($this->boundary[1]); - break; - case 'alt_inline': - $body .= $mimepre; - $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/related;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[2]); - $body .= $this->LE; - $body .= $this->endBoundary($this->boundary[1]); - break; - case 'alt_attach': - $body .= $mimepre; - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->endBoundary($this->boundary[2]); - $body .= $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - case 'alt_inline_attach': - $body .= $mimepre; - $body .= $this->textLine('--' . $this->boundary[1]); - $body .= $this->headerLine('Content-Type', 'multipart/alternative;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding); - $body .= $this->encodeString($this->AltBody, $altBodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->textLine('--' . $this->boundary[2]); - $body .= $this->headerLine('Content-Type', 'multipart/related;'); - $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"'); - $body .= $this->LE; - $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding); - $body .= $this->encodeString($this->Body, $bodyEncoding); - $body .= $this->LE . $this->LE; - $body .= $this->attachAll('inline', $this->boundary[3]); - $body .= $this->LE; - $body .= $this->endBoundary($this->boundary[2]); - $body .= $this->LE; - $body .= $this->attachAll('attachment', $this->boundary[1]); - break; - default: - // catch case 'plain' and case '' - $body .= $this->encodeString($this->Body, $bodyEncoding); - break; - } - - if ($this->isError()) { - $body = ''; - } elseif ($this->sign_key_file) { - try { - if (!defined('PKCS7_TEXT')) { - throw new phpmailerException($this->lang('extension_missing') . 'openssl'); - } - $file = tempnam(sys_get_temp_dir(), 'mail'); - if (false === file_put_contents($file, $body)) { - throw new phpmailerException($this->lang('signing') . ' Could not write temp file'); - } - $signed = tempnam(sys_get_temp_dir(), 'signed'); - //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197 - if (empty($this->sign_extracerts_file)) { - $sign = @openssl_pkcs7_sign( - $file, - $signed, - 'file://' . realpath($this->sign_cert_file), - array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), - null - ); - } else { - $sign = @openssl_pkcs7_sign( - $file, - $signed, - 'file://' . realpath($this->sign_cert_file), - array('file://' . realpath($this->sign_key_file), $this->sign_key_pass), - null, - PKCS7_DETACHED, - $this->sign_extracerts_file - ); - } - if ($sign) { - @unlink($file); - $body = file_get_contents($signed); - @unlink($signed); - //The message returned by openssl contains both headers and body, so need to split them up - $parts = explode("\n\n", $body, 2); - $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE; - $body = $parts[1]; - } else { - @unlink($file); - @unlink($signed); - throw new phpmailerException($this->lang('signing') . openssl_error_string()); - } - } catch (phpmailerException $exc) { - $body = ''; - if ($this->exceptions) { - throw $exc; - } - } - } - return $body; - } - - /** - * Return the start of a message boundary. - * @access protected - * @param string $boundary - * @param string $charSet - * @param string $contentType - * @param string $encoding - * @return string - */ - protected function getBoundary($boundary, $charSet, $contentType, $encoding) - { - $result = ''; - if ($charSet == '') { - $charSet = $this->CharSet; - } - if ($contentType == '') { - $contentType = $this->ContentType; - } - if ($encoding == '') { - $encoding = $this->Encoding; - } - $result .= $this->textLine('--' . $boundary); - $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); - $result .= $this->LE; - // RFC1341 part 5 says 7bit is assumed if not specified - if ($encoding != '7bit') { - $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); - } - $result .= $this->LE; - - return $result; - } - - /** - * Return the end of a message boundary. - * @access protected - * @param string $boundary - * @return string - */ - protected function endBoundary($boundary) - { - return $this->LE . '--' . $boundary . '--' . $this->LE; - } - - /** - * Set the message type. - * PHPMailer only supports some preset message types, - * not arbitrary MIME structures. - * @access protected - * @return void - */ - protected function setMessageType() - { - $type = array(); - if ($this->alternativeExists()) { - $type[] = 'alt'; - } - if ($this->inlineImageExists()) { - $type[] = 'inline'; - } - if ($this->attachmentExists()) { - $type[] = 'attach'; - } - $this->message_type = implode('_', $type); - if ($this->message_type == '') { - $this->message_type = 'plain'; - } - } - - /** - * Format a header line. - * @access public - * @param string $name - * @param string $value - * @return string - */ - public function headerLine($name, $value) - { - return $name . ': ' . $value . $this->LE; - } - - /** - * Return a formatted mail line. - * @access public - * @param string $value - * @return string - */ - public function textLine($value) - { - return $value . $this->LE; - } - - /** - * Add an attachment from a path on the filesystem. - * Returns false if the file could not be found or read. - * @param string $path Path to the attachment. - * @param string $name Overrides the attachment name. - * @param string $encoding File encoding (see $Encoding). - * @param string $type File extension (MIME) type. - * @param string $disposition Disposition to use - * @throws phpmailerException - * @return boolean - */ - public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') - { - try { - if (!@is_file($path)) { - throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE); - } - - // If a MIME type is not specified, try to work it out from the file name - if ($type == '') { - $type = self::filenameToType($path); - } - - $filename = basename($path); - if ($name == '') { - $name = $filename; - } - - $this->attachment[] = array( - 0 => $path, - 1 => $filename, - 2 => $name, - 3 => $encoding, - 4 => $type, - 5 => false, // isStringAttachment - 6 => $disposition, - 7 => 0 - ); - - } catch (phpmailerException $exc) { - $this->setError($exc->getMessage()); - $this->edebug($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - return false; - } - return true; - } - - /** - * Return the array of attachments. - * @return array - */ - public function getAttachments() - { - return $this->attachment; - } - - /** - * Attach all file, string, and binary attachments to the message. - * Returns an empty string on failure. - * @access protected - * @param string $disposition_type - * @param string $boundary - * @return string - */ - protected function attachAll($disposition_type, $boundary) - { - // Return text of body - $mime = array(); - $cidUniq = array(); - $incl = array(); - - // Add all attachments - foreach ($this->attachment as $attachment) { - // Check if it is a valid disposition_filter - if ($attachment[6] == $disposition_type) { - // Check for string attachment - $string = ''; - $path = ''; - $bString = $attachment[5]; - if ($bString) { - $string = $attachment[0]; - } else { - $path = $attachment[0]; - } - - $inclhash = md5(serialize($attachment)); - if (in_array($inclhash, $incl)) { - continue; - } - $incl[] = $inclhash; - $name = $attachment[2]; - $encoding = $attachment[3]; - $type = $attachment[4]; - $disposition = $attachment[6]; - $cid = $attachment[7]; - if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) { - continue; - } - $cidUniq[$cid] = true; - - $mime[] = sprintf('--%s%s', $boundary, $this->LE); - //Only include a filename property if we have one - if (!empty($name)) { - $mime[] = sprintf( - 'Content-Type: %s; name="%s"%s', - $type, - $this->encodeHeader($this->secureHeader($name)), - $this->LE - ); - } else { - $mime[] = sprintf( - 'Content-Type: %s%s', - $type, - $this->LE - ); - } - // RFC1341 part 5 says 7bit is assumed if not specified - if ($encoding != '7bit') { - $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE); - } - - if ($disposition == 'inline') { - $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE); - } - - // If a filename contains any of these chars, it should be quoted, - // but not otherwise: RFC2183 & RFC2045 5.1 - // Fixes a warning in IETF's msglint MIME checker - // Allow for bypassing the Content-Disposition header totally - if (!(empty($disposition))) { - $encoded_name = $this->encodeHeader($this->secureHeader($name)); - if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) { - $mime[] = sprintf( - 'Content-Disposition: %s; filename="%s"%s', - $disposition, - $encoded_name, - $this->LE . $this->LE - ); - } else { - if (!empty($encoded_name)) { - $mime[] = sprintf( - 'Content-Disposition: %s; filename=%s%s', - $disposition, - $encoded_name, - $this->LE . $this->LE - ); - } else { - $mime[] = sprintf( - 'Content-Disposition: %s%s', - $disposition, - $this->LE . $this->LE - ); - } - } - } else { - $mime[] = $this->LE; - } - - // Encode as string attachment - if ($bString) { - $mime[] = $this->encodeString($string, $encoding); - if ($this->isError()) { - return ''; - } - $mime[] = $this->LE . $this->LE; - } else { - $mime[] = $this->encodeFile($path, $encoding); - if ($this->isError()) { - return ''; - } - $mime[] = $this->LE . $this->LE; - } - } - } - - $mime[] = sprintf('--%s--%s', $boundary, $this->LE); - - return implode('', $mime); - } - - /** - * Encode a file attachment in requested format. - * Returns an empty string on failure. - * @param string $path The full path to the file - * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' - * @throws phpmailerException - * @access protected - * @return string - */ - protected function encodeFile($path, $encoding = 'base64') - { - try { - if (!is_readable($path)) { - throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE); - } - $magic_quotes = get_magic_quotes_runtime(); - if ($magic_quotes) { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - set_magic_quotes_runtime(false); - } else { - //Doesn't exist in PHP 5.4, but we don't need to check because - //get_magic_quotes_runtime always returns false in 5.4+ - //so it will never get here - ini_set('magic_quotes_runtime', false); - } - } - $file_buffer = file_get_contents($path); - $file_buffer = $this->encodeString($file_buffer, $encoding); - if ($magic_quotes) { - if (version_compare(PHP_VERSION, '5.3.0', '<')) { - set_magic_quotes_runtime($magic_quotes); - } else { - ini_set('magic_quotes_runtime', $magic_quotes); - } - } - return $file_buffer; - } catch (Exception $exc) { - $this->setError($exc->getMessage()); - return ''; - } - } - - /** - * Encode a string in requested format. - * Returns an empty string on failure. - * @param string $str The text to encode - * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' - * @access public - * @return string - */ - public function encodeString($str, $encoding = 'base64') - { - $encoded = ''; - switch (strtolower($encoding)) { - case 'base64': - $encoded = chunk_split(base64_encode($str), 76, $this->LE); - break; - case '7bit': - case '8bit': - $encoded = $this->fixEOL($str); - // Make sure it ends with a line break - if (substr($encoded, -(strlen($this->LE))) != $this->LE) { - $encoded .= $this->LE; - } - break; - case 'binary': - $encoded = $str; - break; - case 'quoted-printable': - $encoded = $this->encodeQP($str); - break; - default: - $this->setError($this->lang('encoding') . $encoding); - break; - } - return $encoded; - } - - /** - * Encode a header string optimally. - * Picks shortest of Q, B, quoted-printable or none. - * @access public - * @param string $str - * @param string $position - * @return string - */ - public function encodeHeader($str, $position = 'text') - { - $matchcount = 0; - switch (strtolower($position)) { - case 'phrase': - if (!preg_match('/[\200-\377]/', $str)) { - // Can't use addslashes as we don't know the value of magic_quotes_sybase - $encoded = addcslashes($str, "\0..\37\177\\\""); - if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) { - return ($encoded); - } else { - return ("\"$encoded\""); - } - } - $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); - break; - /** @noinspection PhpMissingBreakStatementInspection */ - case 'comment': - $matchcount = preg_match_all('/[()"]/', $str, $matches); - // Intentional fall-through - case 'text': - default: - $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches); - break; - } - - //There are no chars that need encoding - if ($matchcount == 0) { - return ($str); - } - - $maxlen = 75 - 7 - strlen($this->CharSet); - // Try to select the encoding which should produce the shortest output - if ($matchcount > strlen($str) / 3) { - // More than a third of the content will need encoding, so B encoding will be most efficient - $encoding = 'B'; - if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) { - // Use a custom function which correctly encodes and wraps long - // multibyte strings without breaking lines within a character - $encoded = $this->base64EncodeWrapMB($str, "\n"); - } else { - $encoded = base64_encode($str); - $maxlen -= $maxlen % 4; - $encoded = trim(chunk_split($encoded, $maxlen, "\n")); - } - } else { - $encoding = 'Q'; - $encoded = $this->encodeQ($str, $position); - $encoded = $this->wrapText($encoded, $maxlen, true); - $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded)); - } - - $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); - $encoded = trim(str_replace("\n", $this->LE, $encoded)); - - return $encoded; - } - - /** - * Check if a string contains multi-byte characters. - * @access public - * @param string $str multi-byte text to wrap encode - * @return boolean - */ - public function hasMultiBytes($str) - { - if (function_exists('mb_strlen')) { - return (strlen($str) > mb_strlen($str, $this->CharSet)); - } else { // Assume no multibytes (we can't handle without mbstring functions anyway) - return false; - } - } - - /** - * Does a string contain any 8-bit chars (in any charset)? - * @param string $text - * @return boolean - */ - public function has8bitChars($text) - { - return (boolean)preg_match('/[\x80-\xFF]/', $text); - } - - /** - * Encode and wrap long multibyte strings for mail headers - * without breaking lines within a character. - * Adapted from a function by paravoid - * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 - * @access public - * @param string $str multi-byte text to wrap encode - * @param string $linebreak string to use as linefeed/end-of-line - * @return string - */ - public function base64EncodeWrapMB($str, $linebreak = null) - { - $start = '=?' . $this->CharSet . '?B?'; - $end = '?='; - $encoded = ''; - if ($linebreak === null) { - $linebreak = $this->LE; - } - - $mb_length = mb_strlen($str, $this->CharSet); - // Each line must have length <= 75, including $start and $end - $length = 75 - strlen($start) - strlen($end); - // Average multi-byte ratio - $ratio = $mb_length / strlen($str); - // Base64 has a 4:3 ratio - $avgLength = floor($length * $ratio * .75); - - for ($i = 0; $i < $mb_length; $i += $offset) { - $lookBack = 0; - do { - $offset = $avgLength - $lookBack; - $chunk = mb_substr($str, $i, $offset, $this->CharSet); - $chunk = base64_encode($chunk); - $lookBack++; - } while (strlen($chunk) > $length); - $encoded .= $chunk . $linebreak; - } - - // Chomp the last linefeed - $encoded = substr($encoded, 0, -strlen($linebreak)); - return $encoded; - } - - /** - * Encode a string in quoted-printable format. - * According to RFC2045 section 6.7. - * @access public - * @param string $string The text to encode - * @param integer $line_max Number of chars allowed on a line before wrapping - * @return string - * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment - */ - public function encodeQP($string, $line_max = 76) - { - // Use native function if it's available (>= PHP5.3) - if (function_exists('quoted_printable_encode')) { - return quoted_printable_encode($string); - } - // Fall back to a pure PHP implementation - $string = str_replace( - array('%20', '%0D%0A.', '%0D%0A', '%'), - array(' ', "\r\n=2E", "\r\n", '='), - rawurlencode($string) - ); - return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string); - } - - /** - * Backward compatibility wrapper for an old QP encoding function that was removed. - * @see PHPMailer::encodeQP() - * @access public - * @param string $string - * @param integer $line_max - * @param boolean $space_conv - * @return string - * @deprecated Use encodeQP instead. - */ - public function encodeQPphp( - $string, - $line_max = 76, - /** @noinspection PhpUnusedParameterInspection */ $space_conv = false - ) { - return $this->encodeQP($string, $line_max); - } - - /** - * Encode a string using Q encoding. - * @link http://tools.ietf.org/html/rfc2047 - * @param string $str the text to encode - * @param string $position Where the text is going to be used, see the RFC for what that means - * @access public - * @return string - */ - public function encodeQ($str, $position = 'text') - { - // There should not be any EOL in the string - $pattern = ''; - $encoded = str_replace(array("\r", "\n"), '', $str); - switch (strtolower($position)) { - case 'phrase': - // RFC 2047 section 5.3 - $pattern = '^A-Za-z0-9!*+\/ -'; - break; - /** @noinspection PhpMissingBreakStatementInspection */ - case 'comment': - // RFC 2047 section 5.2 - $pattern = '\(\)"'; - // intentional fall-through - // for this reason we build the $pattern without including delimiters and [] - case 'text': - default: - // RFC 2047 section 5.1 - // Replace every high ascii, control, =, ? and _ characters - $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; - break; - } - $matches = array(); - if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { - // If the string contains an '=', make sure it's the first thing we replace - // so as to avoid double-encoding - $eqkey = array_search('=', $matches[0]); - if (false !== $eqkey) { - unset($matches[0][$eqkey]); - array_unshift($matches[0], '='); - } - foreach (array_unique($matches[0]) as $char) { - $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); - } - } - // Replace every spaces to _ (more readable than =20) - return str_replace(' ', '_', $encoded); - } - - /** - * Add a string or binary attachment (non-filesystem). - * This method can be used to attach ascii or binary data, - * such as a BLOB record from a database. - * @param string $string String attachment data. - * @param string $filename Name of the attachment. - * @param string $encoding File encoding (see $Encoding). - * @param string $type File extension (MIME) type. - * @param string $disposition Disposition to use - * @return void - */ - public function addStringAttachment( - $string, - $filename, - $encoding = 'base64', - $type = '', - $disposition = 'attachment' - ) { - // If a MIME type is not specified, try to work it out from the file name - if ($type == '') { - $type = self::filenameToType($filename); - } - // Append to $attachment array - $this->attachment[] = array( - 0 => $string, - 1 => $filename, - 2 => basename($filename), - 3 => $encoding, - 4 => $type, - 5 => true, // isStringAttachment - 6 => $disposition, - 7 => 0 - ); - } - - /** - * Add an embedded (inline) attachment from a file. - * This can include images, sounds, and just about any other document type. - * These differ from 'regular' attachments in that they are intended to be - * displayed inline with the message, not just attached for download. - * This is used in HTML messages that embed the images - * the HTML refers to using the $cid value. - * @param string $path Path to the attachment. - * @param string $cid Content ID of the attachment; Use this to reference - * the content when using an embedded image in HTML. - * @param string $name Overrides the attachment name. - * @param string $encoding File encoding (see $Encoding). - * @param string $type File MIME type. - * @param string $disposition Disposition to use - * @return boolean True on successfully adding an attachment - */ - public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') - { - if (!@is_file($path)) { - $this->setError($this->lang('file_access') . $path); - return false; - } - - // If a MIME type is not specified, try to work it out from the file name - if ($type == '') { - $type = self::filenameToType($path); - } - - $filename = basename($path); - if ($name == '') { - $name = $filename; - } - - // Append to $attachment array - $this->attachment[] = array( - 0 => $path, - 1 => $filename, - 2 => $name, - 3 => $encoding, - 4 => $type, - 5 => false, // isStringAttachment - 6 => $disposition, - 7 => $cid - ); - return true; - } - - /** - * Add an embedded stringified attachment. - * This can include images, sounds, and just about any other document type. - * Be sure to set the $type to an image type for images: - * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. - * @param string $string The attachment binary data. - * @param string $cid Content ID of the attachment; Use this to reference - * the content when using an embedded image in HTML. - * @param string $name - * @param string $encoding File encoding (see $Encoding). - * @param string $type MIME type. - * @param string $disposition Disposition to use - * @return boolean True on successfully adding an attachment - */ - public function addStringEmbeddedImage( - $string, - $cid, - $name = '', - $encoding = 'base64', - $type = '', - $disposition = 'inline' - ) { - // If a MIME type is not specified, try to work it out from the name - if ($type == '' and !empty($name)) { - $type = self::filenameToType($name); - } - - // Append to $attachment array - $this->attachment[] = array( - 0 => $string, - 1 => $name, - 2 => $name, - 3 => $encoding, - 4 => $type, - 5 => true, // isStringAttachment - 6 => $disposition, - 7 => $cid - ); - return true; - } - - /** - * Check if an inline attachment is present. - * @access public - * @return boolean - */ - public function inlineImageExists() - { - foreach ($this->attachment as $attachment) { - if ($attachment[6] == 'inline') { - return true; - } - } - return false; - } - - /** - * Check if an attachment (non-inline) is present. - * @return boolean - */ - public function attachmentExists() - { - foreach ($this->attachment as $attachment) { - if ($attachment[6] == 'attachment') { - return true; - } - } - return false; - } - - /** - * Check if this message has an alternative body set. - * @return boolean - */ - public function alternativeExists() - { - return !empty($this->AltBody); - } - - /** - * Clear queued addresses of given kind. - * @access protected - * @param string $kind 'to', 'cc', or 'bcc' - * @return void - */ - public function clearQueuedAddresses($kind) - { - $RecipientsQueue = $this->RecipientsQueue; - foreach ($RecipientsQueue as $address => $params) { - if ($params[0] == $kind) { - unset($this->RecipientsQueue[$address]); - } - } - } - - /** - * Clear all To recipients. - * @return void - */ - public function clearAddresses() - { - foreach ($this->to as $to) { - unset($this->all_recipients[strtolower($to[0])]); - } - $this->to = array(); - $this->clearQueuedAddresses('to'); - } - - /** - * Clear all CC recipients. - * @return void - */ - public function clearCCs() - { - foreach ($this->cc as $cc) { - unset($this->all_recipients[strtolower($cc[0])]); - } - $this->cc = array(); - $this->clearQueuedAddresses('cc'); - } - - /** - * Clear all BCC recipients. - * @return void - */ - public function clearBCCs() - { - foreach ($this->bcc as $bcc) { - unset($this->all_recipients[strtolower($bcc[0])]); - } - $this->bcc = array(); - $this->clearQueuedAddresses('bcc'); - } - - /** - * Clear all ReplyTo recipients. - * @return void - */ - public function clearReplyTos() - { - $this->ReplyTo = array(); - $this->ReplyToQueue = array(); - } - - /** - * Clear all recipient types. - * @return void - */ - public function clearAllRecipients() - { - $this->to = array(); - $this->cc = array(); - $this->bcc = array(); - $this->all_recipients = array(); - $this->RecipientsQueue = array(); - } - - /** - * Clear all filesystem, string, and binary attachments. - * @return void - */ - public function clearAttachments() - { - $this->attachment = array(); - } - - /** - * Clear all custom headers. - * @return void - */ - public function clearCustomHeaders() - { - $this->CustomHeader = array(); - } - - /** - * Add an error message to the error container. - * @access protected - * @param string $msg - * @return void - */ - protected function setError($msg) - { - $this->error_count++; - if ($this->Mailer == 'smtp' and !is_null($this->smtp)) { - $lasterror = $this->smtp->getError(); - if (!empty($lasterror['error'])) { - $msg .= $this->lang('smtp_error') . $lasterror['error']; - if (!empty($lasterror['detail'])) { - $msg .= ' Detail: '. $lasterror['detail']; - } - if (!empty($lasterror['smtp_code'])) { - $msg .= ' SMTP code: ' . $lasterror['smtp_code']; - } - if (!empty($lasterror['smtp_code_ex'])) { - $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex']; - } - } - } - $this->ErrorInfo = $msg; - } - - /** - * Return an RFC 822 formatted date. - * @access public - * @return string - * @static - */ - public static function rfcDate() - { - // Set the time zone to whatever the default is to avoid 500 errors - // Will default to UTC if it's not set properly in php.ini - date_default_timezone_set(@date_default_timezone_get()); - return date('D, j M Y H:i:s O'); - } - - /** - * Get the server hostname. - * Returns 'localhost.localdomain' if unknown. - * @access protected - * @return string - */ - protected function serverHostname() - { - $result = 'localhost.localdomain'; - if (!empty($this->Hostname)) { - $result = $this->Hostname; - } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) { - $result = $_SERVER['SERVER_NAME']; - } elseif (function_exists('gethostname') && gethostname() !== false) { - $result = gethostname(); - } elseif (php_uname('n') !== false) { - $result = php_uname('n'); - } - return $result; - } - - /** - * Get an error message in the current language. - * @access protected - * @param string $key - * @return string - */ - protected function lang($key) - { - if (count($this->language) < 1) { - $this->setLanguage('en'); // set the default language - } - - if (array_key_exists($key, $this->language)) { - if ($key == 'smtp_connect_failed') { - //Include a link to troubleshooting docs on SMTP connection failure - //this is by far the biggest cause of support questions - //but it's usually not PHPMailer's fault. - return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; - } - return $this->language[$key]; - } else { - //Return the key as a fallback - return $key; - } - } - - /** - * Check if an error occurred. - * @access public - * @return boolean True if an error did occur. - */ - public function isError() - { - return ($this->error_count > 0); - } - - /** - * Ensure consistent line endings in a string. - * Changes every end of line from CRLF, CR or LF to $this->LE. - * @access public - * @param string $str String to fixEOL - * @return string - */ - public function fixEOL($str) - { - // Normalise to \n - $nstr = str_replace(array("\r\n", "\r"), "\n", $str); - // Now convert LE as needed - if ($this->LE !== "\n") { - $nstr = str_replace("\n", $this->LE, $nstr); - } - return $nstr; - } - - /** - * Add a custom header. - * $name value can be overloaded to contain - * both header name and value (name:value) - * @access public - * @param string $name Custom header name - * @param string $value Header value - * @return void - */ - public function addCustomHeader($name, $value = null) - { - if ($value === null) { - // Value passed in as name:value - $this->CustomHeader[] = explode(':', $name, 2); - } else { - $this->CustomHeader[] = array($name, $value); - } - } - - /** - * Returns all custom headers. - * @return array - */ - public function getCustomHeaders() - { - return $this->CustomHeader; - } - - /** - * Create a message from an HTML string. - * Automatically makes modifications for inline images and backgrounds - * and creates a plain-text version by converting the HTML. - * Overwrites any existing values in $this->Body and $this->AltBody - * @access public - * @param string $message HTML message string - * @param string $basedir baseline directory for path - * @param boolean|callable $advanced Whether to use the internal HTML to text converter - * or your own custom converter @see PHPMailer::html2text() - * @return string $message - */ - public function msgHTML($message, $basedir = '', $advanced = false) - { - preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); - if (array_key_exists(2, $images)) { - foreach ($images[2] as $imgindex => $url) { - // Convert data URIs into embedded images - if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) { - $data = substr($url, strpos($url, ',')); - if ($match[2]) { - $data = base64_decode($data); - } else { - $data = rawurldecode($data); - } - $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 - if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) { - $message = str_replace( - $images[0][$imgindex], - $images[1][$imgindex] . '="cid:' . $cid . '"', - $message - ); - } - } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) { - // Do not change urls for absolute images (thanks to corvuscorax) - // Do not change urls that are already inline images - $filename = basename($url); - $directory = dirname($url); - if ($directory == '.') { - $directory = ''; - } - $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 - if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { - $basedir .= '/'; - } - if (strlen($directory) > 1 && substr($directory, -1) != '/') { - $directory .= '/'; - } - if ($this->addEmbeddedImage( - $basedir . $directory . $filename, - $cid, - $filename, - 'base64', - self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION)) - ) - ) { - $message = preg_replace( - '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', - $images[1][$imgindex] . '="cid:' . $cid . '"', - $message - ); - } - } - } - } - $this->isHTML(true); - // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better - $this->Body = $this->normalizeBreaks($message); - $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced)); - if (!$this->alternativeExists()) { - $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . - self::CRLF . self::CRLF; - } - return $this->Body; - } - - /** - * Convert an HTML string into plain text. - * This is used by msgHTML(). - * Note - older versions of this function used a bundled advanced converter - * which was been removed for license reasons in #232 - * Example usage: - * - * // Use default conversion - * $plain = $mail->html2text($html); - * // Use your own custom converter - * $plain = $mail->html2text($html, function($html) { - * $converter = new MyHtml2text($html); - * return $converter->get_text(); - * }); - * - * @param string $html The HTML text to convert - * @param boolean|callable $advanced Any boolean value to use the internal converter, - * or provide your own callable for custom conversion. - * @return string - */ - public function html2text($html, $advanced = false) - { - if (is_callable($advanced)) { - return call_user_func($advanced, $html); - } - return html_entity_decode( - trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), - ENT_QUOTES, - $this->CharSet - ); - } - - /** - * Get the MIME type for a file extension. - * @param string $ext File extension - * @access public - * @return string MIME type of file. - * @static - */ - public static function _mime_types($ext = '') - { - $mimes = array( - 'xl' => 'application/excel', - 'js' => 'application/javascript', - 'hqx' => 'application/mac-binhex40', - 'cpt' => 'application/mac-compactpro', - 'bin' => 'application/macbinary', - 'doc' => 'application/msword', - 'word' => 'application/msword', - 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', - 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', - 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', - 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', - 'class' => 'application/octet-stream', - 'dll' => 'application/octet-stream', - 'dms' => 'application/octet-stream', - 'exe' => 'application/octet-stream', - 'lha' => 'application/octet-stream', - 'lzh' => 'application/octet-stream', - 'psd' => 'application/octet-stream', - 'sea' => 'application/octet-stream', - 'so' => 'application/octet-stream', - 'oda' => 'application/oda', - 'pdf' => 'application/pdf', - 'ai' => 'application/postscript', - 'eps' => 'application/postscript', - 'ps' => 'application/postscript', - 'smi' => 'application/smil', - 'smil' => 'application/smil', - 'mif' => 'application/vnd.mif', - 'xls' => 'application/vnd.ms-excel', - 'ppt' => 'application/vnd.ms-powerpoint', - 'wbxml' => 'application/vnd.wap.wbxml', - 'wmlc' => 'application/vnd.wap.wmlc', - 'dcr' => 'application/x-director', - 'dir' => 'application/x-director', - 'dxr' => 'application/x-director', - 'dvi' => 'application/x-dvi', - 'gtar' => 'application/x-gtar', - 'php3' => 'application/x-httpd-php', - 'php4' => 'application/x-httpd-php', - 'php' => 'application/x-httpd-php', - 'phtml' => 'application/x-httpd-php', - 'phps' => 'application/x-httpd-php-source', - 'swf' => 'application/x-shockwave-flash', - 'sit' => 'application/x-stuffit', - 'tar' => 'application/x-tar', - 'tgz' => 'application/x-tar', - 'xht' => 'application/xhtml+xml', - 'xhtml' => 'application/xhtml+xml', - 'zip' => 'application/zip', - 'mid' => 'audio/midi', - 'midi' => 'audio/midi', - 'mp2' => 'audio/mpeg', - 'mp3' => 'audio/mpeg', - 'mpga' => 'audio/mpeg', - 'aif' => 'audio/x-aiff', - 'aifc' => 'audio/x-aiff', - 'aiff' => 'audio/x-aiff', - 'ram' => 'audio/x-pn-realaudio', - 'rm' => 'audio/x-pn-realaudio', - 'rpm' => 'audio/x-pn-realaudio-plugin', - 'ra' => 'audio/x-realaudio', - 'wav' => 'audio/x-wav', - 'bmp' => 'image/bmp', - 'gif' => 'image/gif', - 'jpeg' => 'image/jpeg', - 'jpe' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'png' => 'image/png', - 'tiff' => 'image/tiff', - 'tif' => 'image/tiff', - 'eml' => 'message/rfc822', - 'css' => 'text/css', - 'html' => 'text/html', - 'htm' => 'text/html', - 'shtml' => 'text/html', - 'log' => 'text/plain', - 'text' => 'text/plain', - 'txt' => 'text/plain', - 'rtx' => 'text/richtext', - 'rtf' => 'text/rtf', - 'vcf' => 'text/vcard', - 'vcard' => 'text/vcard', - 'xml' => 'text/xml', - 'xsl' => 'text/xml', - 'mpeg' => 'video/mpeg', - 'mpe' => 'video/mpeg', - 'mpg' => 'video/mpeg', - 'mov' => 'video/quicktime', - 'qt' => 'video/quicktime', - 'rv' => 'video/vnd.rn-realvideo', - 'avi' => 'video/x-msvideo', - 'movie' => 'video/x-sgi-movie' - ); - if (array_key_exists(strtolower($ext), $mimes)) { - return $mimes[strtolower($ext)]; - } - return 'application/octet-stream'; - } - - /** - * Map a file name to a MIME type. - * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. - * @param string $filename A file name or full path, does not need to exist as a file - * @return string - * @static - */ - public static function filenameToType($filename) - { - // In case the path is a URL, strip any query string before getting extension - $qpos = strpos($filename, '?'); - if (false !== $qpos) { - $filename = substr($filename, 0, $qpos); - } - $pathinfo = self::mb_pathinfo($filename); - return self::_mime_types($pathinfo['extension']); - } - - /** - * Multi-byte-safe pathinfo replacement. - * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. - * Works similarly to the one in PHP >= 5.2.0 - * @link http://www.php.net/manual/en/function.pathinfo.php#107461 - * @param string $path A filename or path, does not need to exist as a file - * @param integer|string $options Either a PATHINFO_* constant, - * or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2 - * @return string|array - * @static - */ - public static function mb_pathinfo($path, $options = null) - { - $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''); - $pathinfo = array(); - if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) { - if (array_key_exists(1, $pathinfo)) { - $ret['dirname'] = $pathinfo[1]; - } - if (array_key_exists(2, $pathinfo)) { - $ret['basename'] = $pathinfo[2]; - } - if (array_key_exists(5, $pathinfo)) { - $ret['extension'] = $pathinfo[5]; - } - if (array_key_exists(3, $pathinfo)) { - $ret['filename'] = $pathinfo[3]; - } - } - switch ($options) { - case PATHINFO_DIRNAME: - case 'dirname': - return $ret['dirname']; - case PATHINFO_BASENAME: - case 'basename': - return $ret['basename']; - case PATHINFO_EXTENSION: - case 'extension': - return $ret['extension']; - case PATHINFO_FILENAME: - case 'filename': - return $ret['filename']; - default: - return $ret; - } - } - - /** - * Set or reset instance properties. - * You should avoid this function - it's more verbose, less efficient, more error-prone and - * harder to debug than setting properties directly. - * Usage Example: - * `$mail->set('SMTPSecure', 'tls');` - * is the same as: - * `$mail->SMTPSecure = 'tls';` - * @access public - * @param string $name The property name to set - * @param mixed $value The value to set the property to - * @return boolean - */ - public function set($name, $value = '') - { - if (property_exists($this, $name)) { - $this->$name = $value; - return true; - } else { - $this->setError($this->lang('variable_set') . $name); - return false; - } - } - - /** - * Strip newlines to prevent header injection. - * @access public - * @param string $str - * @return string - */ - public function secureHeader($str) - { - return trim(str_replace(array("\r", "\n"), '', $str)); - } - - /** - * Normalize line breaks in a string. - * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. - * Defaults to CRLF (for message bodies) and preserves consecutive breaks. - * @param string $text - * @param string $breaktype What kind of line break to use, defaults to CRLF - * @return string - * @access public - * @static - */ - public static function normalizeBreaks($text, $breaktype = "\r\n") - { - return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text); - } - - /** - * Set the public and private key files and password for S/MIME signing. - * @access public - * @param string $cert_filename - * @param string $key_filename - * @param string $key_pass Password for private key - * @param string $extracerts_filename Optional path to chain certificate - */ - public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '') - { - $this->sign_cert_file = $cert_filename; - $this->sign_key_file = $key_filename; - $this->sign_key_pass = $key_pass; - $this->sign_extracerts_file = $extracerts_filename; - } - - /** - * Quoted-Printable-encode a DKIM header. - * @access public - * @param string $txt - * @return string - */ - public function DKIM_QP($txt) - { - $line = ''; - for ($i = 0; $i < strlen($txt); $i++) { - $ord = ord($txt[$i]); - if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) { - $line .= $txt[$i]; - } else { - $line .= '=' . sprintf('%02X', $ord); - } - } - return $line; - } - - /** - * Generate a DKIM signature. - * @access public - * @param string $signHeader - * @throws phpmailerException - * @return string - */ - public function DKIM_Sign($signHeader) - { - if (!defined('PKCS7_TEXT')) { - if ($this->exceptions) { - throw new phpmailerException($this->lang('extension_missing') . 'openssl'); - } - return ''; - } - $privKeyStr = file_get_contents($this->DKIM_private); - if ($this->DKIM_passphrase != '') { - $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase); - } else { - $privKey = openssl_pkey_get_private($privKeyStr); - } - if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { //sha1WithRSAEncryption - openssl_pkey_free($privKey); - return base64_encode($signature); - } - openssl_pkey_free($privKey); - return ''; - } - - /** - * Generate a DKIM canonicalization header. - * @access public - * @param string $signHeader Header - * @return string - */ - public function DKIM_HeaderC($signHeader) - { - $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader); - $lines = explode("\r\n", $signHeader); - foreach ($lines as $key => $line) { - list($heading, $value) = explode(':', $line, 2); - $heading = strtolower($heading); - $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces - $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value - } - $signHeader = implode("\r\n", $lines); - return $signHeader; - } - - /** - * Generate a DKIM canonicalization body. - * @access public - * @param string $body Message Body - * @return string - */ - public function DKIM_BodyC($body) - { - if ($body == '') { - return "\r\n"; - } - // stabilize line endings - $body = str_replace("\r\n", "\n", $body); - $body = str_replace("\n", "\r\n", $body); - // END stabilize line endings - while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") { - $body = substr($body, 0, strlen($body) - 2); - } - return $body; - } - - /** - * Create the DKIM header and body in a new message header. - * @access public - * @param string $headers_line Header lines - * @param string $subject Subject - * @param string $body Body - * @return string - */ - public function DKIM_Add($headers_line, $subject, $body) - { - $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms - $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body - $DKIMquery = 'dns/txt'; // Query method - $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) - $subject_header = "Subject: $subject"; - $headers = explode($this->LE, $headers_line); - $from_header = ''; - $to_header = ''; - $date_header = ''; - $current = ''; - foreach ($headers as $header) { - if (strpos($header, 'From:') === 0) { - $from_header = $header; - $current = 'from_header'; - } elseif (strpos($header, 'To:') === 0) { - $to_header = $header; - $current = 'to_header'; - } elseif (strpos($header, 'Date:') === 0) { - $date_header = $header; - $current = 'date_header'; - } else { - if (!empty($$current) && strpos($header, ' =?') === 0) { - $$current .= $header; - } else { - $current = ''; - } - } - } - $from = str_replace('|', '=7C', $this->DKIM_QP($from_header)); - $to = str_replace('|', '=7C', $this->DKIM_QP($to_header)); - $date = str_replace('|', '=7C', $this->DKIM_QP($date_header)); - $subject = str_replace( - '|', - '=7C', - $this->DKIM_QP($subject_header) - ); // Copied header fields (dkim-quoted-printable) - $body = $this->DKIM_BodyC($body); - $DKIMlen = strlen($body); // Length of body - $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body - if ('' == $this->DKIM_identity) { - $ident = ''; - } else { - $ident = ' i=' . $this->DKIM_identity . ';'; - } - $dkimhdrs = 'DKIM-Signature: v=1; a=' . - $DKIMsignatureType . '; q=' . - $DKIMquery . '; l=' . - $DKIMlen . '; s=' . - $this->DKIM_selector . - ";\r\n" . - "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" . - "\th=From:To:Date:Subject;\r\n" . - "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" . - "\tz=$from\r\n" . - "\t|$to\r\n" . - "\t|$date\r\n" . - "\t|$subject;\r\n" . - "\tbh=" . $DKIMb64 . ";\r\n" . - "\tb="; - $toSign = $this->DKIM_HeaderC( - $from_header . "\r\n" . - $to_header . "\r\n" . - $date_header . "\r\n" . - $subject_header . "\r\n" . - $dkimhdrs - ); - $signed = $this->DKIM_Sign($toSign); - return $dkimhdrs . $signed . "\r\n"; - } - - /** - * Detect if a string contains a line longer than the maximum line length allowed. - * @param string $str - * @return boolean - * @static - */ - public static function hasLineLongerThanMax($str) - { - //+2 to include CRLF line break for a 1000 total - return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str); - } - - /** - * Allows for public read access to 'to' property. - * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. - * @access public - * @return array - */ - public function getToAddresses() - { - return $this->to; - } - - /** - * Allows for public read access to 'cc' property. - * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. - * @access public - * @return array - */ - public function getCcAddresses() - { - return $this->cc; - } - - /** - * Allows for public read access to 'bcc' property. - * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. - * @access public - * @return array - */ - public function getBccAddresses() - { - return $this->bcc; - } - - /** - * Allows for public read access to 'ReplyTo' property. - * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. - * @access public - * @return array - */ - public function getReplyToAddresses() - { - return $this->ReplyTo; - } - - /** - * Allows for public read access to 'all_recipients' property. - * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. - * @access public - * @return array - */ - public function getAllRecipientAddresses() - { - return $this->all_recipients; - } - - /** - * Perform a callback. - * @param boolean $isSent - * @param array $to - * @param array $cc - * @param array $bcc - * @param string $subject - * @param string $body - * @param string $from - */ - protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from) - { - if (!empty($this->action_function) && is_callable($this->action_function)) { - $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from); - call_user_func_array($this->action_function, $params); - } - } +class PHPMailer { + /** + * Error severity: message only, continue processing. + */ + const STOP_MESSAGE = 0; + /** + * Error severity: message, likely ok to continue processing. + */ + const STOP_CONTINUE = 1; + /** + * Error severity: message, plus full stop, critical error reached. + */ + const STOP_CRITICAL = 2; + /** + * SMTP RFC standard line ending. + */ + const CRLF = "\r\n"; + /** + * The maximum line length allowed by RFC 2822 section 2.1.1 + * @var integer + */ + const MAX_LINE_LENGTH = 998; + /** + * The PHPMailer Version number. + * @var string + */ + public $Version = '5.2.14'; + /** + * Email priority. + * Options: null (default), 1 = High, 3 = Normal, 5 = low. + * When null, the header is not set at all. + * @var integer + */ + public $Priority = null; + /** + * The character set of the message. + * @var string + */ + public $CharSet = 'utf-8'; + /** + * The MIME Content-type of the message. + * @var string + */ + public $ContentType = 'text/plain'; + /** + * The message encoding. + * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". + * @var string + */ + public $Encoding = '8bit'; + /** + * Holds the most recent mailer error message. + * @var string + */ + public $ErrorInfo = ''; + /** + * The From email address for the message. + * @var string + */ + public $From = 'root@localhost'; + /** + * The From name of the message. + * @var string + */ + public $FromName = 'Root User'; + /** + * The Sender email (Return-Path) of the message. + * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode. + * @var string + */ + public $Sender = ''; + /** + * The Return-Path of the message. + * If empty, it will be set to either From or Sender. + * @var string + * @deprecated Email senders should never set a return-path header; + * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything. + * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference + */ + public $ReturnPath = ''; + /** + * The Subject of the message. + * @var string + */ + public $Subject = ''; + /** + * An HTML or plain text message body. + * If HTML then call isHTML(true). + * @var string + */ + public $Body = ''; + /** + * The plain-text message body. + * This body can be read by mail clients that do not have HTML email + * capability such as mutt & Eudora. + * Clients that can read HTML will view the normal Body. + * @var string + */ + public $AltBody = ''; + /** + * An iCal message part body. + * Only supported in simple alt or alt_inline message types + * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator + * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ + * @link http://kigkonsult.se/iCalcreator/ + * @var string + */ + public $Ical = ''; + /** + * Word-wrap the message body to this number of chars. + * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. + * @var integer + */ + public $WordWrap = 0; + /** + * Which method to use to send mail. + * Options: "mail", "sendmail", or "smtp". + * @var string + */ + public $Mailer = 'mail'; + /** + * The path to the sendmail program. + * @var string + */ + public $Sendmail = '/usr/sbin/sendmail'; + /** + * Whether mail() uses a fully sendmail-compatible MTA. + * One which supports sendmail's "-oi -f" options. + * @var boolean + */ + public $UseSendmailOptions = true; + /** + * Path to PHPMailer plugins. + * Useful if the SMTP class is not in the PHP include path. + * @var string + * @deprecated Should not be needed now there is an autoloader. + */ + public $PluginDir = ''; + /** + * The email address that a reading confirmation should be sent to, also known as read receipt. + * @var string + */ + public $ConfirmReadingTo = ''; + /** + * The hostname to use in the Message-ID header and as default HELO string. + * If empty, PHPMailer attempts to find one with, in order, + * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value + * 'localhost.localdomain'. + * @var string + */ + public $Hostname = ''; + /** + * An ID to be used in the Message-ID header. + * If empty, a unique id will be generated. + * @var string + */ + public $MessageID = ''; + /** + * The message Date to be used in the Date header. + * If empty, the current date will be added. + * @var string + */ + public $MessageDate = ''; + /** + * SMTP hosts. + * Either a single hostname or multiple semicolon-delimited hostnames. + * You can also specify a different port + * for each host by using this format: [hostname:port] + * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). + * Hosts will be tried in order. + * @var string + */ + public $Host = 'localhost'; + /** + * The default SMTP server port. + * @var integer + */ + public $Port = 25; + /** + * The SMTP HELO of the message. + * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find + * one with the same method described above for $Hostname. + * @var string + * @see PHPMailer::$Hostname + */ + public $Helo = ''; + /** + * What kind of encryption to use on the SMTP connection. + * Options: '', 'ssl' or 'tls' + * @var string + */ + public $SMTPSecure = ''; + /** + * Whether to enable TLS encryption automatically if a server supports it, + * even if `SMTPSecure` is not set to 'tls'. + * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. + * @var boolean + */ + public $SMTPAutoTLS = true; + /** + * Whether to use SMTP authentication. + * Uses the Username and Password properties. + * @var boolean + * @see PHPMailer::$Username + * @see PHPMailer::$Password + */ + public $SMTPAuth = false; + /** + * Options array passed to stream_context_create when connecting via SMTP. + * @var array + */ + public $SMTPOptions = array(); + /** + * SMTP username. + * @var string + */ + public $Username = ''; + /** + * SMTP password. + * @var string + */ + public $Password = ''; + /** + * SMTP auth type. + * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5 + * @var string + */ + public $AuthType = ''; + /** + * SMTP realm. + * Used for NTLM auth + * @var string + */ + public $Realm = ''; + /** + * SMTP workstation. + * Used for NTLM auth + * @var string + */ + public $Workstation = ''; + /** + * The SMTP server timeout in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 + * @var integer + */ + public $Timeout = 300; + /** + * SMTP class debug output mode. + * Debug output level. + * Options: + * * `0` No output + * * `1` Commands + * * `2` Data and commands + * * `3` As 2 plus connection status + * * `4` Low-level data output + * @var integer + * @see SMTP::$do_debug + */ + public $SMTPDebug = 0; + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * + * @var string|callable + * @see SMTP::$Debugoutput + */ + public $Debugoutput = 'echo'; + /** + * Whether to keep SMTP connection open after each message. + * If this is set to true then to close the connection + * requires an explicit call to smtpClose(). + * @var boolean + */ + public $SMTPKeepAlive = false; + /** + * Whether to split multiple to addresses into multiple messages + * or send them all in one message. + * Only supported in `mail` and `sendmail` transports, not in SMTP. + * @var boolean + */ + public $SingleTo = false; + /** + * Storage for addresses when SingleTo is enabled. + * @var array + */ + public $SingleToArray = array(); + /** + * Whether to generate VERP addresses on send. + * Only applicable when sending via SMTP. + * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path + * @link http://www.postfix.org/VERP_README.html Postfix VERP info + * @var boolean + */ + public $do_verp = false; + /** + * Whether to allow sending messages with an empty body. + * @var boolean + */ + public $AllowEmpty = false; + /** + * The default line ending. + * @note The default remains "\n". We force CRLF where we know + * it must be used via self::CRLF. + * @var string + */ + public $LE = "\n"; + /** + * DKIM selector. + * @var string + */ + public $DKIM_selector = ''; + /** + * DKIM Identity. + * Usually the email address used as the source of the email + * @var string + */ + public $DKIM_identity = ''; + /** + * DKIM passphrase. + * Used if your key is encrypted. + * @var string + */ + public $DKIM_passphrase = ''; + /** + * DKIM signing domain name. + * @example 'example.com' + * @var string + */ + public $DKIM_domain = ''; + /** + * DKIM private key file path. + * @var string + */ + public $DKIM_private = ''; + /** + * Callback Action function name. + * + * The function that handles the result of the send email action. + * It is called out by send() for each email sent. + * + * Value can be any php callable: http://www.php.net/is_callable + * + * Parameters: + * boolean $result result of the send action + * string $to email address of the recipient + * string $cc cc email addresses + * string $bcc bcc email addresses + * string $subject the subject + * string $body the email body + * string $from email address of sender + * @var string + */ + public $action_function = ''; + /** + * What to put in the X-Mailer header. + * Options: An empty string for PHPMailer default, whitespace for none, or a string to use + * @var string + */ + public $XMailer = ''; + /** + * The complete compiled MIME message body. + * @access protected + * @var string + */ + protected $MIMEBody = ''; + /** + * The complete compiled MIME message headers. + * @var string + * @access protected + */ + protected $MIMEHeader = ''; + /** + * Extra headers that createHeader() doesn't fold in. + * @var string + * @access protected + */ + protected $mailHeader = ''; + /** + * An instance of the SMTP sender class. + * @var SMTP + * @access protected + */ + protected $smtp = null; + /** + * The array of 'to' names and addresses. + * @var array + * @access protected + */ + protected $to = array(); + /** + * The array of 'cc' names and addresses. + * @var array + * @access protected + */ + protected $cc = array(); + /** + * The array of 'bcc' names and addresses. + * @var array + * @access protected + */ + protected $bcc = array(); + /** + * The array of reply-to names and addresses. + * @var array + * @access protected + */ + protected $ReplyTo = array(); + /** + * An array of all kinds of addresses. + * Includes all of $to, $cc, $bcc + * @var array + * @access protected + * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc + */ + protected $all_recipients = array(); + /** + * An array of names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $all_recipients + * and one of $to, $cc, or $bcc. + * This array is used only for addresses with IDN. + * @var array + * @access protected + * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc + * @see PHPMailer::$all_recipients + */ + protected $RecipientsQueue = array(); + /** + * An array of reply-to names and addresses queued for validation. + * In send(), valid and non duplicate entries are moved to $ReplyTo. + * This array is used only for addresses with IDN. + * @var array + * @access protected + * @see PHPMailer::$ReplyTo + */ + protected $ReplyToQueue = array(); + /** + * The array of attachments. + * @var array + * @access protected + */ + protected $attachment = array(); + /** + * The array of custom headers. + * @var array + * @access protected + */ + protected $CustomHeader = array(); + /** + * The most recent Message-ID (including angular brackets). + * @var string + * @access protected + */ + protected $lastMessageID = ''; + /** + * The message's MIME type. + * @var string + * @access protected + */ + protected $message_type = ''; + /** + * The array of MIME boundary strings. + * @var array + * @access protected + */ + protected $boundary = array(); + /** + * The array of available languages. + * @var array + * @access protected + */ + protected $language = array(); + /** + * The number of errors encountered. + * @var integer + * @access protected + */ + protected $error_count = 0; + /** + * The S/MIME certificate file path. + * @var string + * @access protected + */ + protected $sign_cert_file = ''; + /** + * The S/MIME key file path. + * @var string + * @access protected + */ + protected $sign_key_file = ''; + /** + * The optional S/MIME extra certificates ("CA Chain") file path. + * @var string + * @access protected + */ + protected $sign_extracerts_file = ''; + /** + * The S/MIME password for the key. + * Used only if the key is encrypted. + * @var string + * @access protected + */ + protected $sign_key_pass = ''; + /** + * Whether to throw exceptions for errors. + * @var boolean + * @access protected + */ + protected $exceptions = false; + /** + * Unique ID used for message ID and boundaries. + * @var string + * @access protected + */ + protected $uniqueid = ''; + + /** + * Constructor. + * + * @param boolean $exceptions Should we throw external exceptions? + */ + public function __construct( $exceptions = false ) { + $this->exceptions = (boolean) $exceptions; + } + + /** + * Destructor. + */ + public function __destruct() { + //Close any open SMTP connection nicely + $this->smtpClose(); + } + + /** + * Close the active SMTP session if one exists. + * @return void + */ + public function smtpClose() { + if ( is_a( $this->smtp, 'SMTP' ) ) { + if ( $this->smtp->connected() ) { + $this->smtp->quit(); + $this->smtp->close(); + } + } + } + + /** + * Send messages using SMTP. + * @return void + */ + public function isSMTP() { + $this->Mailer = 'smtp'; + } + + /** + * Send messages using PHP's mail() function. + * @return void + */ + public function isMail() { + $this->Mailer = 'mail'; + } + + /** + * Send messages using $Sendmail. + * @return void + */ + public function isSendmail() { + $ini_sendmail_path = ini_get( 'sendmail_path' ); + + if ( ! stristr( $ini_sendmail_path, 'sendmail' ) ) { + $this->Sendmail = '/usr/sbin/sendmail'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'sendmail'; + } + + /** + * Send messages using qmail. + * @return void + */ + public function isQmail() { + $ini_sendmail_path = ini_get( 'sendmail_path' ); + + if ( ! stristr( $ini_sendmail_path, 'qmail' ) ) { + $this->Sendmail = '/var/qmail/bin/qmail-inject'; + } else { + $this->Sendmail = $ini_sendmail_path; + } + $this->Mailer = 'qmail'; + } + + /** + * Add a "To" address. + * + * @param string $address The email address to send to + * @param string $name + * + * @return boolean true on success, false if address already used or invalid in some way + */ + public function addAddress( $address, $name = '' ) { + return $this->addOrEnqueueAnAddress( 'to', $address, $name ); + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer + * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still + * be modified after calling this function), addition of such addresses is delayed until send(). + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws phpmailerException + * @return boolean true on success, false if address already used or invalid in some way + * @access protected + */ + protected function addOrEnqueueAnAddress( $kind, $address, $name ) { + $address = trim( $address ); + $name = trim( preg_replace( '/[\r\n]+/', '', $name ) ); //Strip breaks and trim + if ( ( $pos = strrpos( $address, '@' ) ) === false ) { + // At-sign is misssing. + $error_message = $this->lang( 'invalid_address' ) . " (addAnAddress $kind): $address"; + $this->setError( $error_message ); + $this->edebug( $error_message ); + if ( $this->exceptions ) { + throw new phpmailerException( $error_message ); + } + + return false; + } + $params = array( $kind, $address, $name ); + // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + if ( $this->has8bitChars( substr( $address, ++ $pos ) ) and $this->idnSupported() ) { + if ( $kind != 'Reply-To' ) { + if ( ! array_key_exists( $address, $this->RecipientsQueue ) ) { + $this->RecipientsQueue[ $address ] = $params; + + return true; + } + } else { + if ( ! array_key_exists( $address, $this->ReplyToQueue ) ) { + $this->ReplyToQueue[ $address ] = $params; + + return true; + } + } + + return false; + } + + // Immediately add standard addresses without IDN. + return call_user_func_array( array( $this, 'addAnAddress' ), $params ); + } + + /** + * Get an error message in the current language. + * @access protected + * + * @param string $key + * + * @return string + */ + protected function lang( $key ) { + if ( count( $this->language ) < 1 ) { + $this->setLanguage( 'en' ); // set the default language + } + + if ( array_key_exists( $key, $this->language ) ) { + if ( $key == 'smtp_connect_failed' ) { + //Include a link to troubleshooting docs on SMTP connection failure + //this is by far the biggest cause of support questions + //but it's usually not PHPMailer's fault. + return $this->language[ $key ] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting'; + } + + return $this->language[ $key ]; + } else { + //Return the key as a fallback + return $key; + } + } + + /** + * Set the language for error messages. + * Returns false if it cannot load the language file. + * The default language is English. + * + * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * + * @return boolean + * @access public + */ + public function setLanguage( $langcode = 'en', $lang_path = '' ) { + // Define full set of translatable strings in English + $PHPMAILER_LANG = array( + 'authenticate' => 'SMTP Error: Could not authenticate.', + 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', + 'data_not_accepted' => 'SMTP Error: data not accepted.', + 'empty_message' => 'Message body empty', + 'encoding' => 'Unknown encoding: ', + 'execute' => 'Could not execute: ', + 'file_access' => 'Could not access file: ', + 'file_open' => 'File Error: Could not open file: ', + 'from_failed' => 'The following From address failed: ', + 'instantiate' => 'Could not instantiate mail function.', + 'invalid_address' => 'Invalid address: ', + 'mailer_not_supported' => ' mailer is not supported.', + 'provide_address' => 'You must provide at least one recipient email address.', + 'recipients_failed' => 'SMTP Error: The following recipients failed: ', + 'signing' => 'Signing Error: ', + 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_error' => 'SMTP server error: ', + 'variable_set' => 'Cannot set or reset variable: ', + 'extension_missing' => 'Extension missing: ' + ); + if ( empty( $lang_path ) ) { + // Calculate an absolute path so it can work if CWD is not here + $lang_path = dirname( __FILE__ ) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; + } + $foundlang = true; + $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; + // There is no English translation file + if ( $langcode != 'en' ) { + // Make sure language file path is readable + if ( ! is_readable( $lang_file ) ) { + $foundlang = false; + } else { + // Overwrite language-specific strings. + // This way we'll never have missing translation keys. + $foundlang = include $lang_file; + } + } + $this->language = $PHPMAILER_LANG; + + return (boolean) $foundlang; // Returns false if language not found + } + + /** + * Add an error message to the error container. + * @access protected + * + * @param string $msg + * + * @return void + */ + protected function setError( $msg ) { + $this->error_count ++; + if ( $this->Mailer == 'smtp' and ! is_null( $this->smtp ) ) { + $lasterror = $this->smtp->getError(); + if ( ! empty( $lasterror['error'] ) ) { + $msg .= $this->lang( 'smtp_error' ) . $lasterror['error']; + if ( ! empty( $lasterror['detail'] ) ) { + $msg .= ' Detail: ' . $lasterror['detail']; + } + if ( ! empty( $lasterror['smtp_code'] ) ) { + $msg .= ' SMTP code: ' . $lasterror['smtp_code']; + } + if ( ! empty( $lasterror['smtp_code_ex'] ) ) { + $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex']; + } + } + } + $this->ErrorInfo = $msg; + } + + /** + * Output debugging info via user-defined method. + * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). + * @see PHPMailer::$Debugoutput + * @see PHPMailer::$SMTPDebug + * + * @param string $str + */ + protected function edebug( $str ) { + if ( $this->SMTPDebug <= 0 ) { + return; + } + //Avoid clash with built-in function names + if ( ! in_array( $this->Debugoutput, array( + 'error_log', + 'html', + 'echo' + ) ) and is_callable( $this->Debugoutput ) ) { + call_user_func( $this->Debugoutput, $str, $this->SMTPDebug ); + + return; + } + switch ( $this->Debugoutput ) { + case 'error_log': + //Don't output, just log + error_log( $str ); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace( '/[\r\n]+/', '', $str ), + ENT_QUOTES, + 'UTF-8' + ) + . "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace( '/\r\n?/ms', "\n", $str ); + echo gmdate( 'Y-m-d H:i:s' ) . "\t" . str_replace( + "\n", + "\n \t ", + trim( $str ) + ) . "\n"; + } + } + + /** + * Does a string contain any 8-bit chars (in any charset)? + * + * @param string $text + * + * @return boolean + */ + public function has8bitChars( $text ) { + return (boolean) preg_match( '/[\x80-\xFF]/', $text ); + } + + /** + * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the + * "intl" and "mbstring" PHP extensions. + * @return bool "true" if required functions for IDN support are present + */ + public function idnSupported() { + return function_exists( 'idn_to_ascii' ) and function_exists( 'mb_convert_encoding' ); + } + + /** + * Add a "CC" address. + * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. + * + * @param string $address The email address to send to + * @param string $name + * + * @return boolean true on success, false if address already used or invalid in some way + */ + public function addCC( $address, $name = '' ) { + return $this->addOrEnqueueAnAddress( 'cc', $address, $name ); + } + + /** + * Add a "BCC" address. + * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. + * + * @param string $address The email address to send to + * @param string $name + * + * @return boolean true on success, false if address already used or invalid in some way + */ + public function addBCC( $address, $name = '' ) { + return $this->addOrEnqueueAnAddress( 'bcc', $address, $name ); + } + + /** + * Add a "Reply-To" address. + * + * @param string $address The email address to reply to + * @param string $name + * + * @return boolean true on success, false if address already used or invalid in some way + */ + public function addReplyTo( $address, $name = '' ) { + return $this->addOrEnqueueAnAddress( 'Reply-To', $address, $name ); + } + + /** + * Parse and validate a string containing one or more RFC822-style comma-separated email addresses + * of the form "display name
" into an array of name/address pairs. + * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. + * Note that quotes in the name part are removed. + * + * @param string $addrstr The address list string + * @param bool $useimap Whether to use the IMAP extension to parse the list + * + * @return array + * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation + */ + public function parseAddresses( $addrstr, $useimap = true ) { + $addresses = array(); + if ( $useimap and function_exists( 'imap_rfc822_parse_adrlist' ) ) { + //Use this built-in parser if it's available + $list = imap_rfc822_parse_adrlist( $addrstr, '' ); + foreach ( $list as $address ) { + if ( $address->host != '.SYNTAX-ERROR.' ) { + if ( $this->validateAddress( $address->mailbox . '@' . $address->host ) ) { + $addresses[] = array( + 'name' => ( property_exists( $address, 'personal' ) ? $address->personal : '' ), + 'address' => $address->mailbox . '@' . $address->host + ); + } + } + } + } else { + //Use this simpler parser + $list = explode( ',', $addrstr ); + foreach ( $list as $address ) { + $address = trim( $address ); + //Is there a separate name part? + if ( strpos( $address, '<' ) === false ) { + //No separate name, just use the whole thing + if ( $this->validateAddress( $address ) ) { + $addresses[] = array( + 'name' => '', + 'address' => $address + ); + } + } else { + list( $name, $email ) = explode( '<', $address ); + $email = trim( str_replace( '>', '', $email ) ); + if ( $this->validateAddress( $email ) ) { + $addresses[] = array( + 'name' => trim( str_replace( array( '"', "'" ), '', $name ) ), + 'address' => $email + ); + } + } + } + } + + return $addresses; + } + + /** + * Set the From and FromName properties. + * + * @param string $address + * @param string $name + * @param boolean $auto Whether to also set the Sender address, defaults to true + * + * @throws phpmailerException + * @return boolean + */ + public function setFrom( $address, $name = '', $auto = true ) { + $address = trim( $address ); + $name = trim( preg_replace( '/[\r\n]+/', '', $name ) ); //Strip breaks and trim + // Don't validate now addresses with IDN. Will be done in send(). + if ( ( $pos = strrpos( $address, '@' ) ) === false or + ( ! $this->has8bitChars( substr( $address, ++ $pos ) ) or ! $this->idnSupported() ) and + ! $this->validateAddress( $address ) ) { + $error_message = $this->lang( 'invalid_address' ) . " (setFrom) $address"; + $this->setError( $error_message ); + $this->edebug( $error_message ); + if ( $this->exceptions ) { + throw new phpmailerException( $error_message ); + } + + return false; + } + $this->From = $address; + $this->FromName = $name; + if ( $auto ) { + if ( empty( $this->Sender ) ) { + $this->Sender = $address; + } + } + + return true; + } + + /** + * Return the Message-ID header of the last email. + * Technically this is the value from the last time the headers were created, + * but it's also the message ID of the last sent message except in + * pathological cases. + * @return string + */ + public function getLastMessageID() { + return $this->lastMessageID; + } + + /** + * Create a message and send it. + * Uses the sending method specified by $Mailer. + * @throws phpmailerException + * @return boolean false on error - See the ErrorInfo property for details of the error. + */ + public function send() { + try { + if ( ! $this->preSend() ) { + return false; + } + + return $this->postSend(); + } catch ( phpmailerException $exc ) { + $this->mailHeader = ''; + $this->setError( $exc->getMessage() ); + if ( $this->exceptions ) { + throw $exc; + } + + return false; + } + } + + /** + * Prepare a message for sending. + * @throws phpmailerException + * @return boolean + */ + public function preSend() { + try { + $this->error_count = 0; // Reset errors + $this->mailHeader = ''; + + // Dequeue recipient and Reply-To addresses with IDN + foreach ( array_merge( $this->RecipientsQueue, $this->ReplyToQueue ) as $params ) { + $params[1] = $this->punyencodeAddress( $params[1] ); + call_user_func_array( array( $this, 'addAnAddress' ), $params ); + } + if ( ( count( $this->to ) + count( $this->cc ) + count( $this->bcc ) ) < 1 ) { + throw new phpmailerException( $this->lang( 'provide_address' ), self::STOP_CRITICAL ); + } + + // Validate From, Sender, and ConfirmReadingTo addresses + foreach ( array( 'From', 'Sender', 'ConfirmReadingTo' ) as $address_kind ) { + $this->$address_kind = trim( $this->$address_kind ); + if ( empty( $this->$address_kind ) ) { + continue; + } + $this->$address_kind = $this->punyencodeAddress( $this->$address_kind ); + if ( ! $this->validateAddress( $this->$address_kind ) ) { + $error_message = $this->lang( 'invalid_address' ) . ' (punyEncode) ' . $this->$address_kind; + $this->setError( $error_message ); + $this->edebug( $error_message ); + if ( $this->exceptions ) { + throw new phpmailerException( $error_message ); + } + + return false; + } + } + + // Set whether the message is multipart/alternative + if ( $this->alternativeExists() ) { + $this->ContentType = 'multipart/alternative'; + } + + $this->setMessageType(); + // Refuse to send an empty message unless we are specifically allowing it + if ( ! $this->AllowEmpty and empty( $this->Body ) ) { + throw new phpmailerException( $this->lang( 'empty_message' ), self::STOP_CRITICAL ); + } + + // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + $this->MIMEHeader = ''; + $this->MIMEBody = $this->createBody(); + // createBody may have added some headers, so retain them + $tempheaders = $this->MIMEHeader; + $this->MIMEHeader = $this->createHeader(); + $this->MIMEHeader .= $tempheaders; + + // To capture the complete message when using mail(), create + // an extra header list which createHeader() doesn't fold in + if ( $this->Mailer == 'mail' ) { + if ( count( $this->to ) > 0 ) { + $this->mailHeader .= $this->addrAppend( 'To', $this->to ); + } else { + $this->mailHeader .= $this->headerLine( 'To', 'undisclosed-recipients:;' ); + } + $this->mailHeader .= $this->headerLine( + 'Subject', + $this->encodeHeader( $this->secureHeader( trim( $this->Subject ) ) ) + ); + } + + // Sign with DKIM if enabled + if ( ! empty( $this->DKIM_domain ) + && ! empty( $this->DKIM_private ) + && ! empty( $this->DKIM_selector ) + && file_exists( $this->DKIM_private ) ) { + $header_dkim = $this->DKIM_Add( + $this->MIMEHeader . $this->mailHeader, + $this->encodeHeader( $this->secureHeader( $this->Subject ) ), + $this->MIMEBody + ); + $this->MIMEHeader = rtrim( $this->MIMEHeader, "\r\n " ) . self::CRLF . + str_replace( "\r\n", "\n", $header_dkim ) . self::CRLF; + } + + return true; + } catch ( phpmailerException $exc ) { + $this->setError( $exc->getMessage() ); + if ( $this->exceptions ) { + throw $exc; + } + + return false; + } + } + + /** + * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. + * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. + * This function silently returns unmodified address if: + * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) + * - Conversion to punycode is impossible (e.g. required PHP functions are not available) + * or fails for any reason (e.g. domain has characters not allowed in an IDN) + * @see PHPMailer::$CharSet + * + * @param string $address The email address to convert + * + * @return string The encoded address in ASCII form + */ + public function punyencodeAddress( $address ) { + // Verify we have required functions, CharSet, and at-sign. + if ( $this->idnSupported() and + ! empty( $this->CharSet ) and + ( $pos = strrpos( $address, '@' ) ) !== false ) { + $domain = substr( $address, ++ $pos ); + // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + if ( $this->has8bitChars( $domain ) and @mb_check_encoding( $domain, $this->CharSet ) ) { + $domain = mb_convert_encoding( $domain, 'UTF-8', $this->CharSet ); + if ( ( $punycode = defined( 'INTL_IDNA_VARIANT_UTS46' ) ? + idn_to_ascii( $domain, 0, INTL_IDNA_VARIANT_UTS46 ) : + idn_to_ascii( $domain ) ) !== false ) { + return substr( $address, 0, $pos ) . $punycode; + } + } + } + + return $address; + } + + /** + * Check if this message has an alternative body set. + * @return boolean + */ + public function alternativeExists() { + return ! empty( $this->AltBody ); + } + + /** + * Set the message type. + * PHPMailer only supports some preset message types, + * not arbitrary MIME structures. + * @access protected + * @return void + */ + protected function setMessageType() { + $type = array(); + if ( $this->alternativeExists() ) { + $type[] = 'alt'; + } + if ( $this->inlineImageExists() ) { + $type[] = 'inline'; + } + if ( $this->attachmentExists() ) { + $type[] = 'attach'; + } + $this->message_type = implode( '_', $type ); + if ( $this->message_type == '' ) { + $this->message_type = 'plain'; + } + } + + /** + * Check if an inline attachment is present. + * @access public + * @return boolean + */ + public function inlineImageExists() { + foreach ( $this->attachment as $attachment ) { + if ( $attachment[6] == 'inline' ) { + return true; + } + } + + return false; + } + + /** + * Check if an attachment (non-inline) is present. + * @return boolean + */ + public function attachmentExists() { + foreach ( $this->attachment as $attachment ) { + if ( $attachment[6] == 'attachment' ) { + return true; + } + } + + return false; + } + + /** + * Assemble the message body. + * Returns an empty string on failure. + * @access public + * @throws phpmailerException + * @return string The assembled message body + */ + public function createBody() { + $body = ''; + //Create unique IDs and preset boundaries + $this->uniqueid = md5( uniqid( time() ) ); + $this->boundary[1] = 'b1_' . $this->uniqueid; + $this->boundary[2] = 'b2_' . $this->uniqueid; + $this->boundary[3] = 'b3_' . $this->uniqueid; + + if ( $this->sign_key_file ) { + $body .= $this->getMailMIME() . $this->LE; + } + + $this->setWordWrap(); + + $bodyEncoding = $this->Encoding; + $bodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if ( $bodyEncoding == '8bit' and ! $this->has8bitChars( $this->Body ) ) { + $bodyEncoding = '7bit'; + $bodyCharSet = 'us-ascii'; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding + if ( 'base64' != $this->Encoding and self::hasLineLongerThanMax( $this->Body ) ) { + $this->Encoding = 'quoted-printable'; + $bodyEncoding = 'quoted-printable'; + } + + $altBodyEncoding = $this->Encoding; + $altBodyCharSet = $this->CharSet; + //Can we do a 7-bit downgrade? + if ( $altBodyEncoding == '8bit' and ! $this->has8bitChars( $this->AltBody ) ) { + $altBodyEncoding = '7bit'; + $altBodyCharSet = 'us-ascii'; + } + //If lines are too long, and we're not already using an encoding that will shorten them, + //change to quoted-printable transfer encoding + if ( 'base64' != $altBodyEncoding and self::hasLineLongerThanMax( $this->AltBody ) ) { + $altBodyEncoding = 'quoted-printable'; + } + //Use this as a preamble in all multipart message types + $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE; + switch ( $this->message_type ) { + case 'inline': + $body .= $mimepre; + $body .= $this->getBoundary( $this->boundary[1], $bodyCharSet, '', $bodyEncoding ); + $body .= $this->encodeString( $this->Body, $bodyEncoding ); + $body .= $this->LE . $this->LE; + $body .= $this->attachAll( 'inline', $this->boundary[1] ); + break; + case 'attach': + $body .= $mimepre; + $body .= $this->getBoundary( $this->boundary[1], $bodyCharSet, '', $bodyEncoding ); + $body .= $this->encodeString( $this->Body, $bodyEncoding ); + $body .= $this->LE . $this->LE; + $body .= $this->attachAll( 'attachment', $this->boundary[1] ); + break; + case 'inline_attach': + $body .= $mimepre; + $body .= $this->textLine( '--' . $this->boundary[1] ); + $body .= $this->headerLine( 'Content-Type', 'multipart/related;' ); + $body .= $this->textLine( "\tboundary=\"" . $this->boundary[2] . '"' ); + $body .= $this->LE; + $body .= $this->getBoundary( $this->boundary[2], $bodyCharSet, '', $bodyEncoding ); + $body .= $this->encodeString( $this->Body, $bodyEncoding ); + $body .= $this->LE . $this->LE; + $body .= $this->attachAll( 'inline', $this->boundary[2] ); + $body .= $this->LE; + $body .= $this->attachAll( 'attachment', $this->boundary[1] ); + break; + case 'alt': + $body .= $mimepre; + $body .= $this->getBoundary( $this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding ); + $body .= $this->encodeString( $this->AltBody, $altBodyEncoding ); + $body .= $this->LE . $this->LE; + $body .= $this->getBoundary( $this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding ); + $body .= $this->encodeString( $this->Body, $bodyEncoding ); + $body .= $this->LE . $this->LE; + if ( ! empty( $this->Ical ) ) { + $body .= $this->getBoundary( $this->boundary[1], '', 'text/calendar; method=REQUEST', '' ); + $body .= $this->encodeString( $this->Ical, $this->Encoding ); + $body .= $this->LE . $this->LE; + } + $body .= $this->endBoundary( $this->boundary[1] ); + break; + case 'alt_inline': + $body .= $mimepre; + $body .= $this->getBoundary( $this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding ); + $body .= $this->encodeString( $this->AltBody, $altBodyEncoding ); + $body .= $this->LE . $this->LE; + $body .= $this->textLine( '--' . $this->boundary[1] ); + $body .= $this->headerLine( 'Content-Type', 'multipart/related;' ); + $body .= $this->textLine( "\tboundary=\"" . $this->boundary[2] . '"' ); + $body .= $this->LE; + $body .= $this->getBoundary( $this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding ); + $body .= $this->encodeString( $this->Body, $bodyEncoding ); + $body .= $this->LE . $this->LE; + $body .= $this->attachAll( 'inline', $this->boundary[2] ); + $body .= $this->LE; + $body .= $this->endBoundary( $this->boundary[1] ); + break; + case 'alt_attach': + $body .= $mimepre; + $body .= $this->textLine( '--' . $this->boundary[1] ); + $body .= $this->headerLine( 'Content-Type', 'multipart/alternative;' ); + $body .= $this->textLine( "\tboundary=\"" . $this->boundary[2] . '"' ); + $body .= $this->LE; + $body .= $this->getBoundary( $this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding ); + $body .= $this->encodeString( $this->AltBody, $altBodyEncoding ); + $body .= $this->LE . $this->LE; + $body .= $this->getBoundary( $this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding ); + $body .= $this->encodeString( $this->Body, $bodyEncoding ); + $body .= $this->LE . $this->LE; + $body .= $this->endBoundary( $this->boundary[2] ); + $body .= $this->LE; + $body .= $this->attachAll( 'attachment', $this->boundary[1] ); + break; + case 'alt_inline_attach': + $body .= $mimepre; + $body .= $this->textLine( '--' . $this->boundary[1] ); + $body .= $this->headerLine( 'Content-Type', 'multipart/alternative;' ); + $body .= $this->textLine( "\tboundary=\"" . $this->boundary[2] . '"' ); + $body .= $this->LE; + $body .= $this->getBoundary( $this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding ); + $body .= $this->encodeString( $this->AltBody, $altBodyEncoding ); + $body .= $this->LE . $this->LE; + $body .= $this->textLine( '--' . $this->boundary[2] ); + $body .= $this->headerLine( 'Content-Type', 'multipart/related;' ); + $body .= $this->textLine( "\tboundary=\"" . $this->boundary[3] . '"' ); + $body .= $this->LE; + $body .= $this->getBoundary( $this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding ); + $body .= $this->encodeString( $this->Body, $bodyEncoding ); + $body .= $this->LE . $this->LE; + $body .= $this->attachAll( 'inline', $this->boundary[3] ); + $body .= $this->LE; + $body .= $this->endBoundary( $this->boundary[2] ); + $body .= $this->LE; + $body .= $this->attachAll( 'attachment', $this->boundary[1] ); + break; + default: + // catch case 'plain' and case '' + $body .= $this->encodeString( $this->Body, $bodyEncoding ); + break; + } + + if ( $this->isError() ) { + $body = ''; + } elseif ( $this->sign_key_file ) { + try { + if ( ! defined( 'PKCS7_TEXT' ) ) { + throw new phpmailerException( $this->lang( 'extension_missing' ) . 'openssl' ); + } + $file = tempnam( sys_get_temp_dir(), 'mail' ); + if ( false === file_put_contents( $file, $body ) ) { + throw new phpmailerException( $this->lang( 'signing' ) . ' Could not write temp file' ); + } + $signed = tempnam( sys_get_temp_dir(), 'signed' ); + //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197 + if ( empty( $this->sign_extracerts_file ) ) { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath( $this->sign_cert_file ), + array( 'file://' . realpath( $this->sign_key_file ), $this->sign_key_pass ), + null + ); + } else { + $sign = @openssl_pkcs7_sign( + $file, + $signed, + 'file://' . realpath( $this->sign_cert_file ), + array( 'file://' . realpath( $this->sign_key_file ), $this->sign_key_pass ), + null, + PKCS7_DETACHED, + $this->sign_extracerts_file + ); + } + if ( $sign ) { + @unlink( $file ); + $body = file_get_contents( $signed ); + @unlink( $signed ); + //The message returned by openssl contains both headers and body, so need to split them up + $parts = explode( "\n\n", $body, 2 ); + $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE; + $body = $parts[1]; + } else { + @unlink( $file ); + @unlink( $signed ); + throw new phpmailerException( $this->lang( 'signing' ) . openssl_error_string() ); + } + } catch ( phpmailerException $exc ) { + $body = ''; + if ( $this->exceptions ) { + throw $exc; + } + } + } + + return $body; + } + + /** + * Get the message MIME type headers. + * @access public + * @return string + */ + public function getMailMIME() { + $result = ''; + $ismultipart = true; + switch ( $this->message_type ) { + case 'inline': + $result .= $this->headerLine( 'Content-Type', 'multipart/related;' ); + $result .= $this->textLine( "\tboundary=\"" . $this->boundary[1] . '"' ); + break; + case 'attach': + case 'inline_attach': + case 'alt_attach': + case 'alt_inline_attach': + $result .= $this->headerLine( 'Content-Type', 'multipart/mixed;' ); + $result .= $this->textLine( "\tboundary=\"" . $this->boundary[1] . '"' ); + break; + case 'alt': + case 'alt_inline': + $result .= $this->headerLine( 'Content-Type', 'multipart/alternative;' ); + $result .= $this->textLine( "\tboundary=\"" . $this->boundary[1] . '"' ); + break; + default: + // Catches case 'plain': and case '': + $result .= $this->textLine( 'Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet ); + $ismultipart = false; + break; + } + // RFC1341 part 5 says 7bit is assumed if not specified + if ( $this->Encoding != '7bit' ) { + // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE + if ( $ismultipart ) { + if ( $this->Encoding == '8bit' ) { + $result .= $this->headerLine( 'Content-Transfer-Encoding', '8bit' ); + } + // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible + } else { + $result .= $this->headerLine( 'Content-Transfer-Encoding', $this->Encoding ); + } + } + + if ( $this->Mailer != 'mail' ) { + $result .= $this->LE; + } + + return $result; + } + + /** + * Format a header line. + * @access public + * + * @param string $name + * @param string $value + * + * @return string + */ + public function headerLine( $name, $value ) { + return $name . ': ' . $value . $this->LE; + } + + /** + * Return a formatted mail line. + * @access public + * + * @param string $value + * + * @return string + */ + public function textLine( $value ) { + return $value . $this->LE; + } + + /** + * Apply word wrapping to the message body. + * Wraps the message body to the number of chars set in the WordWrap property. + * You should only do this to plain-text bodies as wrapping HTML tags may break them. + * This is called automatically by createBody(), so you don't need to call it yourself. + * @access public + * @return void + */ + public function setWordWrap() { + if ( $this->WordWrap < 1 ) { + return; + } + + switch ( $this->message_type ) { + case 'alt': + case 'alt_inline': + case 'alt_attach': + case 'alt_inline_attach': + $this->AltBody = $this->wrapText( $this->AltBody, $this->WordWrap ); + break; + default: + $this->Body = $this->wrapText( $this->Body, $this->WordWrap ); + break; + } + } + + /** + * Detect if a string contains a line longer than the maximum line length allowed. + * + * @param string $str + * + * @return boolean + * @static + */ + public static function hasLineLongerThanMax( $str ) { + //+2 to include CRLF line break for a 1000 total + return (boolean) preg_match( '/^(.{' . ( self::MAX_LINE_LENGTH + 2 ) . ',})/m', $str ); + } + + /** + * Return the start of a message boundary. + * @access protected + * + * @param string $boundary + * @param string $charSet + * @param string $contentType + * @param string $encoding + * + * @return string + */ + protected function getBoundary( $boundary, $charSet, $contentType, $encoding ) { + $result = ''; + if ( $charSet == '' ) { + $charSet = $this->CharSet; + } + if ( $contentType == '' ) { + $contentType = $this->ContentType; + } + if ( $encoding == '' ) { + $encoding = $this->Encoding; + } + $result .= $this->textLine( '--' . $boundary ); + $result .= sprintf( 'Content-Type: %s; charset=%s', $contentType, $charSet ); + $result .= $this->LE; + // RFC1341 part 5 says 7bit is assumed if not specified + if ( $encoding != '7bit' ) { + $result .= $this->headerLine( 'Content-Transfer-Encoding', $encoding ); + } + $result .= $this->LE; + + return $result; + } + + /** + * Encode a string in requested format. + * Returns an empty string on failure. + * + * @param string $str The text to encode + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * + * @access public + * @return string + */ + public function encodeString( $str, $encoding = 'base64' ) { + $encoded = ''; + switch ( strtolower( $encoding ) ) { + case 'base64': + $encoded = chunk_split( base64_encode( $str ), 76, $this->LE ); + break; + case '7bit': + case '8bit': + $encoded = $this->fixEOL( $str ); + // Make sure it ends with a line break + if ( substr( $encoded, - ( strlen( $this->LE ) ) ) != $this->LE ) { + $encoded .= $this->LE; + } + break; + case 'binary': + $encoded = $str; + break; + case 'quoted-printable': + $encoded = $this->encodeQP( $str ); + break; + default: + $this->setError( $this->lang( 'encoding' ) . $encoding ); + break; + } + + return $encoded; + } + + /** + * Encode a string in quoted-printable format. + * According to RFC2045 section 6.7. + * @access public + * + * @param string $string The text to encode + * @param integer $line_max Number of chars allowed on a line before wrapping + * + * @return string + * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment + */ + public function encodeQP( $string, $line_max = 76 ) { + // Use native function if it's available (>= PHP5.3) + if ( function_exists( 'quoted_printable_encode' ) ) { + return quoted_printable_encode( $string ); + } + // Fall back to a pure PHP implementation + $string = str_replace( + array( '%20', '%0D%0A.', '%0D%0A', '%' ), + array( ' ', "\r\n=2E", "\r\n", '=' ), + rawurlencode( $string ) + ); + + return preg_replace( '/[^\r\n]{' . ( $line_max - 3 ) . '}[^=\r\n]{2}/', "$0=\r\n", $string ); + } + + /** + * Attach all file, string, and binary attachments to the message. + * Returns an empty string on failure. + * @access protected + * + * @param string $disposition_type + * @param string $boundary + * + * @return string + */ + protected function attachAll( $disposition_type, $boundary ) { + // Return text of body + $mime = array(); + $cidUniq = array(); + $incl = array(); + + // Add all attachments + foreach ( $this->attachment as $attachment ) { + // Check if it is a valid disposition_filter + if ( $attachment[6] == $disposition_type ) { + // Check for string attachment + $string = ''; + $path = ''; + $bString = $attachment[5]; + if ( $bString ) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + } + + $inclhash = md5( serialize( $attachment ) ); + if ( in_array( $inclhash, $incl ) ) { + continue; + } + $incl[] = $inclhash; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + if ( $disposition == 'inline' && array_key_exists( $cid, $cidUniq ) ) { + continue; + } + $cidUniq[ $cid ] = true; + + $mime[] = sprintf( '--%s%s', $boundary, $this->LE ); + //Only include a filename property if we have one + if ( ! empty( $name ) ) { + $mime[] = sprintf( + 'Content-Type: %s; name="%s"%s', + $type, + $this->encodeHeader( $this->secureHeader( $name ) ), + $this->LE + ); + } else { + $mime[] = sprintf( + 'Content-Type: %s%s', + $type, + $this->LE + ); + } + // RFC1341 part 5 says 7bit is assumed if not specified + if ( $encoding != '7bit' ) { + $mime[] = sprintf( 'Content-Transfer-Encoding: %s%s', $encoding, $this->LE ); + } + + if ( $disposition == 'inline' ) { + $mime[] = sprintf( 'Content-ID: <%s>%s', $cid, $this->LE ); + } + + // If a filename contains any of these chars, it should be quoted, + // but not otherwise: RFC2183 & RFC2045 5.1 + // Fixes a warning in IETF's msglint MIME checker + // Allow for bypassing the Content-Disposition header totally + if ( ! ( empty( $disposition ) ) ) { + $encoded_name = $this->encodeHeader( $this->secureHeader( $name ) ); + if ( preg_match( '/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name ) ) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename="%s"%s', + $disposition, + $encoded_name, + $this->LE . $this->LE + ); + } else { + if ( ! empty( $encoded_name ) ) { + $mime[] = sprintf( + 'Content-Disposition: %s; filename=%s%s', + $disposition, + $encoded_name, + $this->LE . $this->LE + ); + } else { + $mime[] = sprintf( + 'Content-Disposition: %s%s', + $disposition, + $this->LE . $this->LE + ); + } + } + } else { + $mime[] = $this->LE; + } + + // Encode as string attachment + if ( $bString ) { + $mime[] = $this->encodeString( $string, $encoding ); + if ( $this->isError() ) { + return ''; + } + $mime[] = $this->LE . $this->LE; + } else { + $mime[] = $this->encodeFile( $path, $encoding ); + if ( $this->isError() ) { + return ''; + } + $mime[] = $this->LE . $this->LE; + } + } + } + + $mime[] = sprintf( '--%s--%s', $boundary, $this->LE ); + + return implode( '', $mime ); + } + + /** + * Check if an error occurred. + * @access public + * @return boolean True if an error did occur. + */ + public function isError() { + return ( $this->error_count > 0 ); + } + + /** + * Encode a file attachment in requested format. + * Returns an empty string on failure. + * + * @param string $path The full path to the file + * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable' + * + * @throws phpmailerException + * @access protected + * @return string + */ + protected function encodeFile( $path, $encoding = 'base64' ) { + try { + if ( ! is_readable( $path ) ) { + throw new phpmailerException( $this->lang( 'file_open' ) . $path, self::STOP_CONTINUE ); + } + $magic_quotes = get_magic_quotes_runtime(); + if ( $magic_quotes ) { + if ( version_compare( PHP_VERSION, '5.3.0', '<' ) ) { + set_magic_quotes_runtime( false ); + } else { + //Doesn't exist in PHP 5.4, but we don't need to check because + //get_magic_quotes_runtime always returns false in 5.4+ + //so it will never get here + ini_set( 'magic_quotes_runtime', false ); + } + } + $file_buffer = file_get_contents( $path ); + $file_buffer = $this->encodeString( $file_buffer, $encoding ); + if ( $magic_quotes ) { + if ( version_compare( PHP_VERSION, '5.3.0', '<' ) ) { + set_magic_quotes_runtime( $magic_quotes ); + } else { + ini_set( 'magic_quotes_runtime', $magic_quotes ); + } + } + + return $file_buffer; + } catch ( Exception $exc ) { + $this->setError( $exc->getMessage() ); + + return ''; + } + } + + /** + * Return the end of a message boundary. + * @access protected + * + * @param string $boundary + * + * @return string + */ + protected function endBoundary( $boundary ) { + return $this->LE . '--' . $boundary . '--' . $this->LE; + } + + /** + * Assemble message headers. + * @access public + * @return string The assembled headers + */ + public function createHeader() { + $result = ''; + + if ( $this->MessageDate == '' ) { + $this->MessageDate = self::rfcDate(); + } + $result .= $this->headerLine( 'Date', $this->MessageDate ); + + // To be created automatically by mail() + if ( $this->SingleTo ) { + if ( $this->Mailer != 'mail' ) { + foreach ( $this->to as $toaddr ) { + $this->SingleToArray[] = $this->addrFormat( $toaddr ); + } + } + } else { + if ( count( $this->to ) > 0 ) { + if ( $this->Mailer != 'mail' ) { + $result .= $this->addrAppend( 'To', $this->to ); + } + } elseif ( count( $this->cc ) == 0 ) { + $result .= $this->headerLine( 'To', 'undisclosed-recipients:;' ); + } + } + + $result .= $this->addrAppend( 'From', array( array( trim( $this->From ), $this->FromName ) ) ); + + // sendmail and mail() extract Cc from the header before sending + if ( count( $this->cc ) > 0 ) { + $result .= $this->addrAppend( 'Cc', $this->cc ); + } + + // sendmail and mail() extract Bcc from the header before sending + if ( ( + $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail' + ) + and count( $this->bcc ) > 0 + ) { + $result .= $this->addrAppend( 'Bcc', $this->bcc ); + } + + if ( count( $this->ReplyTo ) > 0 ) { + $result .= $this->addrAppend( 'Reply-To', $this->ReplyTo ); + } + + // mail() sets the subject itself + if ( $this->Mailer != 'mail' ) { + $result .= $this->headerLine( 'Subject', $this->encodeHeader( $this->secureHeader( $this->Subject ) ) ); + } + + if ( '' != $this->MessageID and preg_match( '/^<.*@.*>$/', $this->MessageID ) ) { + $this->lastMessageID = $this->MessageID; + } else { + $this->lastMessageID = sprintf( '<%s@%s>', $this->uniqueid, $this->serverHostname() ); + } + $result .= $this->headerLine( 'Message-ID', $this->lastMessageID ); + if ( ! is_null( $this->Priority ) ) { + $result .= $this->headerLine( 'X-Priority', $this->Priority ); + } + if ( $this->XMailer == '' ) { + $result .= $this->headerLine( + 'X-Mailer', + 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)' + ); + } else { + $myXmailer = trim( $this->XMailer ); + if ( $myXmailer ) { + $result .= $this->headerLine( 'X-Mailer', $myXmailer ); + } + } + + if ( $this->ConfirmReadingTo != '' ) { + $result .= $this->headerLine( 'Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>' ); + } + + // Add custom headers + foreach ( $this->CustomHeader as $header ) { + $result .= $this->headerLine( + trim( $header[0] ), + $this->encodeHeader( trim( $header[1] ) ) + ); + } + if ( ! $this->sign_key_file ) { + $result .= $this->headerLine( 'MIME-Version', '1.0' ); + $result .= $this->getMailMIME(); + } + + return $result; + } + + /** + * Return an RFC 822 formatted date. + * @access public + * @return string + * @static + */ + public static function rfcDate() { + // Set the time zone to whatever the default is to avoid 500 errors + // Will default to UTC if it's not set properly in php.ini + date_default_timezone_set( @date_default_timezone_get() ); + + return date( 'D, j M Y H:i:s O' ); + } + + /** + * Format an address for use in a message header. + * @access public + * + * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name + * like array('joe@example.com', 'Joe User') + * + * @return string + */ + public function addrFormat( $addr ) { + if ( empty( $addr[1] ) ) { // No name provided + return $this->secureHeader( $addr[0] ); + } else { + return $this->encodeHeader( $this->secureHeader( $addr[1] ), 'phrase' ) . ' <' . $this->secureHeader( + $addr[0] + ) . '>'; + } + } + + /** + * Create recipient headers. + * @access public + * + * @param string $type + * @param array $addr An array of recipient, + * where each recipient is a 2-element indexed array with element 0 containing an address + * and element 1 containing a name, like: + * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User')) + * + * @return string + */ + public function addrAppend( $type, $addr ) { + $addresses = array(); + foreach ( $addr as $address ) { + $addresses[] = $this->addrFormat( $address ); + } + + return $type . ': ' . implode( ', ', $addresses ) . $this->LE; + } + + /** + * Get the server hostname. + * Returns 'localhost.localdomain' if unknown. + * @access protected + * @return string + */ + protected function serverHostname() { + $result = 'localhost.localdomain'; + if ( ! empty( $this->Hostname ) ) { + $result = $this->Hostname; + } elseif ( isset( $_SERVER ) and array_key_exists( 'SERVER_NAME', $_SERVER ) and ! empty( $_SERVER['SERVER_NAME'] ) ) { + $result = $_SERVER['SERVER_NAME']; + } elseif ( function_exists( 'gethostname' ) && gethostname() !== false ) { + $result = gethostname(); + } elseif ( php_uname( 'n' ) !== false ) { + $result = php_uname( 'n' ); + } + + return $result; + } + + /** + * Create the DKIM header and body in a new message header. + * @access public + * + * @param string $headers_line Header lines + * @param string $subject Subject + * @param string $body Body + * + * @return string + */ + public function DKIM_Add( $headers_line, $subject, $body ) { + $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms + $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body + $DKIMquery = 'dns/txt'; // Query method + $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone) + $subject_header = "Subject: $subject"; + $headers = explode( $this->LE, $headers_line ); + $from_header = ''; + $to_header = ''; + $date_header = ''; + $current = ''; + foreach ( $headers as $header ) { + if ( strpos( $header, 'From:' ) === 0 ) { + $from_header = $header; + $current = 'from_header'; + } elseif ( strpos( $header, 'To:' ) === 0 ) { + $to_header = $header; + $current = 'to_header'; + } elseif ( strpos( $header, 'Date:' ) === 0 ) { + $date_header = $header; + $current = 'date_header'; + } else { + if ( ! empty( $$current ) && strpos( $header, ' =?' ) === 0 ) { + $$current .= $header; + } else { + $current = ''; + } + } + } + $from = str_replace( '|', '=7C', $this->DKIM_QP( $from_header ) ); + $to = str_replace( '|', '=7C', $this->DKIM_QP( $to_header ) ); + $date = str_replace( '|', '=7C', $this->DKIM_QP( $date_header ) ); + $subject = str_replace( + '|', + '=7C', + $this->DKIM_QP( $subject_header ) + ); // Copied header fields (dkim-quoted-printable) + $body = $this->DKIM_BodyC( $body ); + $DKIMlen = strlen( $body ); // Length of body + $DKIMb64 = base64_encode( pack( 'H*', hash( 'sha256', $body ) ) ); // Base64 of packed binary SHA-256 hash of body + if ( '' == $this->DKIM_identity ) { + $ident = ''; + } else { + $ident = ' i=' . $this->DKIM_identity . ';'; + } + $dkimhdrs = 'DKIM-Signature: v=1; a=' . + $DKIMsignatureType . '; q=' . + $DKIMquery . '; l=' . + $DKIMlen . '; s=' . + $this->DKIM_selector . + ";\r\n" . + "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" . + "\th=From:To:Date:Subject;\r\n" . + "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" . + "\tz=$from\r\n" . + "\t|$to\r\n" . + "\t|$date\r\n" . + "\t|$subject;\r\n" . + "\tbh=" . $DKIMb64 . ";\r\n" . + "\tb="; + $toSign = $this->DKIM_HeaderC( + $from_header . "\r\n" . + $to_header . "\r\n" . + $date_header . "\r\n" . + $subject_header . "\r\n" . + $dkimhdrs + ); + $signed = $this->DKIM_Sign( $toSign ); + + return $dkimhdrs . $signed . "\r\n"; + } + + /** + * Quoted-Printable-encode a DKIM header. + * @access public + * + * @param string $txt + * + * @return string + */ + public function DKIM_QP( $txt ) { + $line = ''; + for ( $i = 0; $i < strlen( $txt ); $i ++ ) { + $ord = ord( $txt[ $i ] ); + if ( ( ( 0x21 <= $ord ) && ( $ord <= 0x3A ) ) || $ord == 0x3C || ( ( 0x3E <= $ord ) && ( $ord <= 0x7E ) ) ) { + $line .= $txt[ $i ]; + } else { + $line .= '=' . sprintf( '%02X', $ord ); + } + } + + return $line; + } + + /** + * Generate a DKIM canonicalization body. + * @access public + * + * @param string $body Message Body + * + * @return string + */ + public function DKIM_BodyC( $body ) { + if ( $body == '' ) { + return "\r\n"; + } + // stabilize line endings + $body = str_replace( "\r\n", "\n", $body ); + $body = str_replace( "\n", "\r\n", $body ); + // END stabilize line endings + while ( substr( $body, strlen( $body ) - 4, 4 ) == "\r\n\r\n" ) { + $body = substr( $body, 0, strlen( $body ) - 2 ); + } + + return $body; + } + + /** + * Generate a DKIM canonicalization header. + * @access public + * + * @param string $signHeader Header + * + * @return string + */ + public function DKIM_HeaderC( $signHeader ) { + $signHeader = preg_replace( '/\r\n\s+/', ' ', $signHeader ); + $lines = explode( "\r\n", $signHeader ); + foreach ( $lines as $key => $line ) { + list( $heading, $value ) = explode( ':', $line, 2 ); + $heading = strtolower( $heading ); + $value = preg_replace( '/\s{2,}/', ' ', $value ); // Compress useless spaces + $lines[ $key ] = $heading . ':' . trim( $value ); // Don't forget to remove WSP around the value + } + $signHeader = implode( "\r\n", $lines ); + + return $signHeader; + } + + /** + * Generate a DKIM signature. + * @access public + * + * @param string $signHeader + * + * @throws phpmailerException + * @return string + */ + public function DKIM_Sign( $signHeader ) { + if ( ! defined( 'PKCS7_TEXT' ) ) { + if ( $this->exceptions ) { + throw new phpmailerException( $this->lang( 'extension_missing' ) . 'openssl' ); + } + + return ''; + } + $privKeyStr = file_get_contents( $this->DKIM_private ); + if ( $this->DKIM_passphrase != '' ) { + $privKey = openssl_pkey_get_private( $privKeyStr, $this->DKIM_passphrase ); + } else { + $privKey = openssl_pkey_get_private( $privKeyStr ); + } + if ( openssl_sign( $signHeader, $signature, $privKey, 'sha256WithRSAEncryption' ) ) { //sha1WithRSAEncryption + openssl_pkey_free( $privKey ); + + return base64_encode( $signature ); + } + openssl_pkey_free( $privKey ); + + return ''; + } + + /** + * Actually send a message. + * Send the email via the selected mechanism + * @throws phpmailerException + * @return boolean + */ + public function postSend() { + try { + // Choose the mailer and send through it + switch ( $this->Mailer ) { + case 'sendmail': + case 'qmail': + return $this->sendmailSend( $this->MIMEHeader, $this->MIMEBody ); + case 'smtp': + return $this->smtpSend( $this->MIMEHeader, $this->MIMEBody ); + case 'mail': + return $this->mailSend( $this->MIMEHeader, $this->MIMEBody ); + default: + $sendMethod = $this->Mailer . 'Send'; + if ( method_exists( $this, $sendMethod ) ) { + return $this->$sendMethod( $this->MIMEHeader, $this->MIMEBody ); + } + + return $this->mailSend( $this->MIMEHeader, $this->MIMEBody ); + } + } catch ( phpmailerException $exc ) { + $this->setError( $exc->getMessage() ); + $this->edebug( $exc->getMessage() ); + if ( $this->exceptions ) { + throw $exc; + } + } + + return false; + } + + /** + * Send mail using the $Sendmail program. + * + * @param string $header The message headers + * @param string $body The message body + * + * @see PHPMailer::$Sendmail + * @throws phpmailerException + * @access protected + * @return boolean + */ + protected function sendmailSend( $header, $body ) { + if ( $this->Sender != '' ) { + if ( $this->Mailer == 'qmail' ) { + $sendmail = sprintf( '%s -f%s', escapeshellcmd( $this->Sendmail ), escapeshellarg( $this->Sender ) ); + } else { + $sendmail = sprintf( '%s -oi -f%s -t', escapeshellcmd( $this->Sendmail ), escapeshellarg( $this->Sender ) ); + } + } else { + if ( $this->Mailer == 'qmail' ) { + $sendmail = sprintf( '%s', escapeshellcmd( $this->Sendmail ) ); + } else { + $sendmail = sprintf( '%s -oi -t', escapeshellcmd( $this->Sendmail ) ); + } + } + if ( $this->SingleTo ) { + foreach ( $this->SingleToArray as $toAddr ) { + if ( ! @$mail = popen( $sendmail, 'w' ) ) { + throw new phpmailerException( $this->lang( 'execute' ) . $this->Sendmail, self::STOP_CRITICAL ); + } + fputs( $mail, 'To: ' . $toAddr . "\n" ); + fputs( $mail, $header ); + fputs( $mail, $body ); + $result = pclose( $mail ); + $this->doCallback( + ( $result == 0 ), + array( $toAddr ), + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From + ); + if ( $result != 0 ) { + throw new phpmailerException( $this->lang( 'execute' ) . $this->Sendmail, self::STOP_CRITICAL ); + } + } + } else { + if ( ! @$mail = popen( $sendmail, 'w' ) ) { + throw new phpmailerException( $this->lang( 'execute' ) . $this->Sendmail, self::STOP_CRITICAL ); + } + fputs( $mail, $header ); + fputs( $mail, $body ); + $result = pclose( $mail ); + $this->doCallback( + ( $result == 0 ), + $this->to, + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From + ); + if ( $result != 0 ) { + throw new phpmailerException( $this->lang( 'execute' ) . $this->Sendmail, self::STOP_CRITICAL ); + } + } + + return true; + } + + /** + * Perform a callback. + * + * @param boolean $isSent + * @param array $to + * @param array $cc + * @param array $bcc + * @param string $subject + * @param string $body + * @param string $from + */ + protected function doCallback( $isSent, $to, $cc, $bcc, $subject, $body, $from ) { + if ( ! empty( $this->action_function ) && is_callable( $this->action_function ) ) { + $params = array( $isSent, $to, $cc, $bcc, $subject, $body, $from ); + call_user_func_array( $this->action_function, $params ); + } + } + + /** + * Send mail via SMTP. + * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. + * Uses the PHPMailerSMTP class by default. + * @see PHPMailer::getSMTPInstance() to use a different class. + * + * @param string $header The message headers + * @param string $body The message body + * + * @throws phpmailerException + * @uses SMTP + * @access protected + * @return boolean + */ + protected function smtpSend( $header, $body ) { + $bad_rcpt = array(); + if ( ! $this->smtpConnect( $this->SMTPOptions ) ) { + throw new phpmailerException( $this->lang( 'smtp_connect_failed' ), self::STOP_CRITICAL ); + } + if ( '' == $this->Sender ) { + $smtp_from = $this->From; + } else { + $smtp_from = $this->Sender; + } + if ( ! $this->smtp->mail( $smtp_from ) ) { + $this->setError( $this->lang( 'from_failed' ) . $smtp_from . ' : ' . implode( ',', $this->smtp->getError() ) ); + throw new phpmailerException( $this->ErrorInfo, self::STOP_CRITICAL ); + } + + // Attempt to send to all recipients + foreach ( array( $this->to, $this->cc, $this->bcc ) as $togroup ) { + foreach ( $togroup as $to ) { + if ( ! $this->smtp->recipient( $to[0] ) ) { + $error = $this->smtp->getError(); + $bad_rcpt[] = array( 'to' => $to[0], 'error' => $error['detail'] ); + $isSent = false; + } else { + $isSent = true; + } + $this->doCallback( $isSent, array( $to[0] ), array(), array(), $this->Subject, $body, $this->From ); + } + } + + // Only send the DATA command if we have viable recipients + if ( ( count( $this->all_recipients ) > count( $bad_rcpt ) ) and ! $this->smtp->data( $header . $body ) ) { + throw new phpmailerException( $this->lang( 'data_not_accepted' ), self::STOP_CRITICAL ); + } + if ( $this->SMTPKeepAlive ) { + $this->smtp->reset(); + } else { + $this->smtp->quit(); + $this->smtp->close(); + } + //Create error message for any bad addresses + if ( count( $bad_rcpt ) > 0 ) { + $errstr = ''; + foreach ( $bad_rcpt as $bad ) { + $errstr .= $bad['to'] . ': ' . $bad['error']; + } + throw new phpmailerException( + $this->lang( 'recipients_failed' ) . $errstr, + self::STOP_CONTINUE + ); + } + + return true; + } + + /** + * Initiate a connection to an SMTP server. + * Returns false if the operation failed. + * + * @param array $options An array of options compatible with stream_context_create() + * + * @return bool + * @throws null + * @throws phpmailerException + * @uses SMTP + * @access public + */ + public function smtpConnect( $options = array() ) { + if ( is_null( $this->smtp ) ) { + $this->smtp = $this->getSMTPInstance(); + } + + // Already connected? + if ( $this->smtp->connected() ) { + return true; + } + + $this->smtp->setTimeout( $this->Timeout ); + $this->smtp->setDebugLevel( $this->SMTPDebug ); + $this->smtp->setDebugOutput( $this->Debugoutput ); + $this->smtp->setVerp( $this->do_verp ); + $hosts = explode( ';', $this->Host ); + $lastexception = null; + + foreach ( $hosts as $hostentry ) { + $hostinfo = array(); + if ( ! preg_match( '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim( $hostentry ), $hostinfo ) ) { + // Not a valid host entry + continue; + } + // $hostinfo[2]: optional ssl or tls prefix + // $hostinfo[3]: the hostname + // $hostinfo[4]: optional port number + // The host string prefix can temporarily override the current setting for SMTPSecure + // If it's not specified, the default value is used + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = ( $this->SMTPSecure == 'tls' ); + if ( 'ssl' == $hostinfo[2] or ( '' == $hostinfo[2] and 'ssl' == $this->SMTPSecure ) ) { + $prefix = 'ssl://'; + $tls = false; // Can't have SSL and TLS at the same time + $secure = 'ssl'; + } elseif ( $hostinfo[2] == 'tls' ) { + $tls = true; + // tls doesn't use a prefix + $secure = 'tls'; + } + //Do we need the OpenSSL extension? + $sslext = defined( 'OPENSSL_ALGO_SHA1' ); + if ( 'tls' === $secure or 'ssl' === $secure ) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if ( ! $sslext ) { + throw new phpmailerException( $this->lang( 'extension_missing' ) . 'openssl', self::STOP_CRITICAL ); + } + } + $host = $hostinfo[3]; + $port = $this->Port; + $tport = (integer) $hostinfo[4]; + if ( $tport > 0 and $tport < 65536 ) { + $port = $tport; + } + if ( $this->smtp->connect( $prefix . $host, $port, $this->Timeout, $options ) ) { + try { + if ( $this->Helo ) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello( $hello ); + //Automatically enable TLS encryption if: + // * it's not disabled + // * we have openssl extension + // * we are not already using SSL + // * the server offers STARTTLS + if ( $this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt( 'STARTTLS' ) ) { + $tls = true; + } + if ( $tls ) { + if ( ! $this->smtp->startTLS() ) { + throw new phpmailerException( $this->lang( 'connect_host' ) ); + } + // We must resend HELO after tls negotiation + $this->smtp->hello( $hello ); + } + if ( $this->SMTPAuth ) { + if ( ! $this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->Realm, + $this->Workstation + ) + ) { + throw new phpmailerException( $this->lang( 'authenticate' ) ); + } + } + + return true; + } catch ( phpmailerException $exc ) { + $lastexception = $exc; + $this->edebug( $exc->getMessage() ); + // We must have connected, but then failed TLS or Auth, so close connection nicely + $this->smtp->quit(); + } + } + } + // If we get here, all connection attempts have failed, so close connection hard + $this->smtp->close(); + // As we've caught all exceptions, just report whatever the last one was + if ( $this->exceptions and ! is_null( $lastexception ) ) { + throw $lastexception; + } + + return false; + } + + /** + * Get an instance to use for SMTP operations. + * Override this function to load your own SMTP implementation + * @return SMTP + */ + public function getSMTPInstance() { + if ( ! is_object( $this->smtp ) ) { + $this->smtp = new SMTP; + } + + return $this->smtp; + } + + /** + * Send mail using the PHP mail() function. + * + * @param string $header The message headers + * @param string $body The message body + * + * @link http://www.php.net/manual/en/book.mail.php + * @throws phpmailerException + * @access protected + * @return boolean + */ + protected function mailSend( $header, $body ) { + $toArr = array(); + foreach ( $this->to as $toaddr ) { + $toArr[] = $this->addrFormat( $toaddr ); + } + $to = implode( ', ', $toArr ); + + if ( empty( $this->Sender ) ) { + $params = ' '; + } else { + $params = sprintf( '-f%s', $this->Sender ); + } + if ( $this->Sender != '' and ! ini_get( 'safe_mode' ) ) { + $old_from = ini_get( 'sendmail_from' ); + ini_set( 'sendmail_from', $this->Sender ); + } + $result = false; + if ( $this->SingleTo && count( $toArr ) > 1 ) { + foreach ( $toArr as $toAddr ) { + $result = $this->mailPassthru( $toAddr, $this->Subject, $body, $header, $params ); + $this->doCallback( $result, array( $toAddr ), $this->cc, $this->bcc, $this->Subject, $body, $this->From ); + } + } else { + $result = $this->mailPassthru( $to, $this->Subject, $body, $header, $params ); + $this->doCallback( $result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From ); + } + if ( isset( $old_from ) ) { + ini_set( 'sendmail_from', $old_from ); + } + if ( ! $result ) { + throw new phpmailerException( $this->lang( 'instantiate' ), self::STOP_CRITICAL ); + } + + return true; + } + + /** + * Call mail() in a safe_mode-aware fashion. + * Also, unless sendmail_path points to sendmail (or something that + * claims to be sendmail), don't pass params (not a perfect fix, + * but it will do) + * + * @param string $to To + * @param string $subject Subject + * @param string $body Message Body + * @param string $header Additional Header(s) + * @param string $params Params + * + * @access private + * @return boolean + */ + private function mailPassthru( $to, $subject, $body, $header, $params ) { + //Check overloading of mail function to avoid double-encoding + if ( ini_get( 'mbstring.func_overload' ) & 1 ) { + $subject = $this->secureHeader( $subject ); + } else { + $subject = $this->encodeHeader( $this->secureHeader( $subject ) ); + } + if ( ini_get( 'safe_mode' ) || ! ( $this->UseSendmailOptions ) ) { + $result = @mail( $to, $subject, $body, $header ); + } else { + $result = @mail( $to, $subject, $body, $header, $params ); + } + + return $result; + } + + /** + * Strip newlines to prevent header injection. + * @access public + * + * @param string $str + * + * @return string + */ + public function secureHeader( $str ) { + return trim( str_replace( array( "\r", "\n" ), '', $str ) ); + } + + /** + * Encode a header string optimally. + * Picks shortest of Q, B, quoted-printable or none. + * @access public + * + * @param string $str + * @param string $position + * + * @return string + */ + public function encodeHeader( $str, $position = 'text' ) { + $matchcount = 0; + switch ( strtolower( $position ) ) { + case 'phrase': + if ( ! preg_match( '/[\200-\377]/', $str ) ) { + // Can't use addslashes as we don't know the value of magic_quotes_sybase + $encoded = addcslashes( $str, "\0..\37\177\\\"" ); + if ( ( $str == $encoded ) && ! preg_match( '/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str ) ) { + return ( $encoded ); + } else { + return ( "\"$encoded\"" ); + } + } + $matchcount = preg_match_all( '/[^\040\041\043-\133\135-\176]/', $str, $matches ); + break; + /** @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + $matchcount = preg_match_all( '/[()"]/', $str, $matches ); + // Intentional fall-through + case 'text': + default: + $matchcount += preg_match_all( '/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches ); + break; + } + + //There are no chars that need encoding + if ( $matchcount == 0 ) { + return ( $str ); + } + + $maxlen = 75 - 7 - strlen( $this->CharSet ); + // Try to select the encoding which should produce the shortest output + if ( $matchcount > strlen( $str ) / 3 ) { + // More than a third of the content will need encoding, so B encoding will be most efficient + $encoding = 'B'; + if ( function_exists( 'mb_strlen' ) && $this->hasMultiBytes( $str ) ) { + // Use a custom function which correctly encodes and wraps long + // multibyte strings without breaking lines within a character + $encoded = $this->base64EncodeWrapMB( $str, "\n" ); + } else { + $encoded = base64_encode( $str ); + $maxlen -= $maxlen % 4; + $encoded = trim( chunk_split( $encoded, $maxlen, "\n" ) ); + } + } else { + $encoding = 'Q'; + $encoded = $this->encodeQ( $str, $position ); + $encoded = $this->wrapText( $encoded, $maxlen, true ); + $encoded = str_replace( '=' . self::CRLF, "\n", trim( $encoded ) ); + } + + $encoded = preg_replace( '/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded ); + $encoded = trim( str_replace( "\n", $this->LE, $encoded ) ); + + return $encoded; + } + + /** + * Check if a string contains multi-byte characters. + * @access public + * + * @param string $str multi-byte text to wrap encode + * + * @return boolean + */ + public function hasMultiBytes( $str ) { + if ( function_exists( 'mb_strlen' ) ) { + return ( strlen( $str ) > mb_strlen( $str, $this->CharSet ) ); + } else { // Assume no multibytes (we can't handle without mbstring functions anyway) + return false; + } + } + + /** + * Encode and wrap long multibyte strings for mail headers + * without breaking lines within a character. + * Adapted from a function by paravoid + * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283 + * @access public + * + * @param string $str multi-byte text to wrap encode + * @param string $linebreak string to use as linefeed/end-of-line + * + * @return string + */ + public function base64EncodeWrapMB( $str, $linebreak = null ) { + $start = '=?' . $this->CharSet . '?B?'; + $end = '?='; + $encoded = ''; + if ( $linebreak === null ) { + $linebreak = $this->LE; + } + + $mb_length = mb_strlen( $str, $this->CharSet ); + // Each line must have length <= 75, including $start and $end + $length = 75 - strlen( $start ) - strlen( $end ); + // Average multi-byte ratio + $ratio = $mb_length / strlen( $str ); + // Base64 has a 4:3 ratio + $avgLength = floor( $length * $ratio * .75 ); + + for ( $i = 0; $i < $mb_length; $i += $offset ) { + $lookBack = 0; + do { + $offset = $avgLength - $lookBack; + $chunk = mb_substr( $str, $i, $offset, $this->CharSet ); + $chunk = base64_encode( $chunk ); + $lookBack ++; + } while ( strlen( $chunk ) > $length ); + $encoded .= $chunk . $linebreak; + } + + // Chomp the last linefeed + $encoded = substr( $encoded, 0, - strlen( $linebreak ) ); + + return $encoded; + } + + /** + * Encode a string using Q encoding. + * @link http://tools.ietf.org/html/rfc2047 + * + * @param string $str the text to encode + * @param string $position Where the text is going to be used, see the RFC for what that means + * + * @access public + * @return string + */ + public function encodeQ( $str, $position = 'text' ) { + // There should not be any EOL in the string + $pattern = ''; + $encoded = str_replace( array( "\r", "\n" ), '', $str ); + switch ( strtolower( $position ) ) { + case 'phrase': + // RFC 2047 section 5.3 + $pattern = '^A-Za-z0-9!*+\/ -'; + break; + /** @noinspection PhpMissingBreakStatementInspection */ + case 'comment': + // RFC 2047 section 5.2 + $pattern = '\(\)"'; + // intentional fall-through + // for this reason we build the $pattern without including delimiters and [] + case 'text': + default: + // RFC 2047 section 5.1 + // Replace every high ascii, control, =, ? and _ characters + $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; + break; + } + $matches = array(); + if ( preg_match_all( "/[{$pattern}]/", $encoded, $matches ) ) { + // If the string contains an '=', make sure it's the first thing we replace + // so as to avoid double-encoding + $eqkey = array_search( '=', $matches[0] ); + if ( false !== $eqkey ) { + unset( $matches[0][ $eqkey ] ); + array_unshift( $matches[0], '=' ); + } + foreach ( array_unique( $matches[0] ) as $char ) { + $encoded = str_replace( $char, '=' . sprintf( '%02X', ord( $char ) ), $encoded ); + } + } + + // Replace every spaces to _ (more readable than =20) + return str_replace( ' ', '_', $encoded ); + } + + /** + * Word-wrap message. + * For use with mailers that do not automatically perform wrapping + * and for quoted-printable encoded messages. + * Original written by philippe. + * + * @param string $message The message to wrap + * @param integer $length The line length to wrap to + * @param boolean $qp_mode Whether to run in Quoted-Printable mode + * + * @access public + * @return string + */ + public function wrapText( $message, $length, $qp_mode = false ) { + if ( $qp_mode ) { + $soft_break = sprintf( ' =%s', $this->LE ); + } else { + $soft_break = $this->LE; + } + // If utf-8 encoding is used, we will need to make sure we don't + // split multibyte characters when we wrap + $is_utf8 = ( strtolower( $this->CharSet ) == 'utf-8' ); + $lelen = strlen( $this->LE ); + $crlflen = strlen( self::CRLF ); + + $message = $this->fixEOL( $message ); + //Remove a trailing line break + if ( substr( $message, - $lelen ) == $this->LE ) { + $message = substr( $message, 0, - $lelen ); + } + + //Split message into lines + $lines = explode( $this->LE, $message ); + //Message will be rebuilt in here + $message = ''; + foreach ( $lines as $line ) { + $words = explode( ' ', $line ); + $buf = ''; + $firstword = true; + foreach ( $words as $word ) { + if ( $qp_mode and ( strlen( $word ) > $length ) ) { + $space_left = $length - strlen( $buf ) - $crlflen; + if ( ! $firstword ) { + if ( $space_left > 20 ) { + $len = $space_left; + if ( $is_utf8 ) { + $len = $this->utf8CharBoundary( $word, $len ); + } elseif ( substr( $word, $len - 1, 1 ) == '=' ) { + $len --; + } elseif ( substr( $word, $len - 2, 1 ) == '=' ) { + $len -= 2; + } + $part = substr( $word, 0, $len ); + $word = substr( $word, $len ); + $buf .= ' ' . $part; + $message .= $buf . sprintf( '=%s', self::CRLF ); + } else { + $message .= $buf . $soft_break; + } + $buf = ''; + } + while ( strlen( $word ) > 0 ) { + if ( $length <= 0 ) { + break; + } + $len = $length; + if ( $is_utf8 ) { + $len = $this->utf8CharBoundary( $word, $len ); + } elseif ( substr( $word, $len - 1, 1 ) == '=' ) { + $len --; + } elseif ( substr( $word, $len - 2, 1 ) == '=' ) { + $len -= 2; + } + $part = substr( $word, 0, $len ); + $word = substr( $word, $len ); + + if ( strlen( $word ) > 0 ) { + $message .= $part . sprintf( '=%s', self::CRLF ); + } else { + $buf = $part; + } + } + } else { + $buf_o = $buf; + if ( ! $firstword ) { + $buf .= ' '; + } + $buf .= $word; + + if ( strlen( $buf ) > $length and $buf_o != '' ) { + $message .= $buf_o . $soft_break; + $buf = $word; + } + } + $firstword = false; + } + $message .= $buf . self::CRLF; + } + + return $message; + } + + /** + * Ensure consistent line endings in a string. + * Changes every end of line from CRLF, CR or LF to $this->LE. + * @access public + * + * @param string $str String to fixEOL + * + * @return string + */ + public function fixEOL( $str ) { + // Normalise to \n + $nstr = str_replace( array( "\r\n", "\r" ), "\n", $str ); + // Now convert LE as needed + if ( $this->LE !== "\n" ) { + $nstr = str_replace( "\n", $this->LE, $nstr ); + } + + return $nstr; + } + + /** + * Find the last character boundary prior to $maxLength in a utf-8 + * quoted-printable encoded string. + * Original written by Colin Brown. + * @access public + * + * @param string $encodedText utf-8 QP text + * @param integer $maxLength Find the last character boundary prior to this length + * + * @return integer + */ + public function utf8CharBoundary( $encodedText, $maxLength ) { + $foundSplitPos = false; + $lookBack = 3; + while ( ! $foundSplitPos ) { + $lastChunk = substr( $encodedText, $maxLength - $lookBack, $lookBack ); + $encodedCharPos = strpos( $lastChunk, '=' ); + if ( false !== $encodedCharPos ) { + // Found start of encoded character byte within $lookBack block. + // Check the encoded byte value (the 2 chars after the '=') + $hex = substr( $encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2 ); + $dec = hexdec( $hex ); + if ( $dec < 128 ) { + // Single byte character. + // If the encoded char was found at pos 0, it will fit + // otherwise reduce maxLength to start of the encoded char + if ( $encodedCharPos > 0 ) { + $maxLength = $maxLength - ( $lookBack - $encodedCharPos ); + } + $foundSplitPos = true; + } elseif ( $dec >= 192 ) { + // First byte of a multi byte character + // Reduce maxLength to split at start of character + $maxLength = $maxLength - ( $lookBack - $encodedCharPos ); + $foundSplitPos = true; + } elseif ( $dec < 192 ) { + // Middle byte of a multi byte character, look further back + $lookBack += 3; + } + } else { + // No encoded character found + $foundSplitPos = true; + } + } + + return $maxLength; + } + + /** + * Get the array of strings for the current language. + * @return array + */ + public function getTranslations() { + return $this->language; + } + + /** + * Returns the whole MIME message. + * Includes complete headers and body. + * Only valid post preSend(). + * @see PHPMailer::preSend() + * @access public + * @return string + */ + public function getSentMIMEMessage() { + return rtrim( $this->MIMEHeader . $this->mailHeader, "\n\r" ) . self::CRLF . self::CRLF . $this->MIMEBody; + } + + /** + * Add an attachment from a path on the filesystem. + * Returns false if the file could not be found or read. + * + * @param string $path Path to the attachment. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @param string $disposition Disposition to use + * + * @throws phpmailerException + * @return boolean + */ + public function addAttachment( $path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment' ) { + try { + if ( ! @is_file( $path ) ) { + throw new phpmailerException( $this->lang( 'file_access' ) . $path, self::STOP_CONTINUE ); + } + + // If a MIME type is not specified, try to work it out from the file name + if ( $type == '' ) { + $type = self::filenameToType( $path ); + } + + $filename = basename( $path ); + if ( $name == '' ) { + $name = $filename; + } + + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => $disposition, + 7 => 0 + ); + + } catch ( phpmailerException $exc ) { + $this->setError( $exc->getMessage() ); + $this->edebug( $exc->getMessage() ); + if ( $this->exceptions ) { + throw $exc; + } + + return false; + } + + return true; + } + + /** + * Map a file name to a MIME type. + * Defaults to 'application/octet-stream', i.e.. arbitrary binary data. + * + * @param string $filename A file name or full path, does not need to exist as a file + * + * @return string + * @static + */ + public static function filenameToType( $filename ) { + // In case the path is a URL, strip any query string before getting extension + $qpos = strpos( $filename, '?' ); + if ( false !== $qpos ) { + $filename = substr( $filename, 0, $qpos ); + } + $pathinfo = self::mb_pathinfo( $filename ); + + return self::_mime_types( $pathinfo['extension'] ); + } + + /** + * Multi-byte-safe pathinfo replacement. + * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe. + * Works similarly to the one in PHP >= 5.2.0 + * @link http://www.php.net/manual/en/function.pathinfo.php#107461 + * + * @param string $path A filename or path, does not need to exist as a file + * @param integer|string $options Either a PATHINFO_* constant, + * or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2 + * + * @return string|array + * @static + */ + public static function mb_pathinfo( $path, $options = null ) { + $ret = array( 'dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '' ); + $pathinfo = array(); + if ( preg_match( '%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo ) ) { + if ( array_key_exists( 1, $pathinfo ) ) { + $ret['dirname'] = $pathinfo[1]; + } + if ( array_key_exists( 2, $pathinfo ) ) { + $ret['basename'] = $pathinfo[2]; + } + if ( array_key_exists( 5, $pathinfo ) ) { + $ret['extension'] = $pathinfo[5]; + } + if ( array_key_exists( 3, $pathinfo ) ) { + $ret['filename'] = $pathinfo[3]; + } + } + switch ( $options ) { + case PATHINFO_DIRNAME: + case 'dirname': + return $ret['dirname']; + case PATHINFO_BASENAME: + case 'basename': + return $ret['basename']; + case PATHINFO_EXTENSION: + case 'extension': + return $ret['extension']; + case PATHINFO_FILENAME: + case 'filename': + return $ret['filename']; + default: + return $ret; + } + } + + /** + * Get the MIME type for a file extension. + * + * @param string $ext File extension + * + * @access public + * @return string MIME type of file. + * @static + */ + public static function _mime_types( $ext = '' ) { + $mimes = array( + 'xl' => 'application/excel', + 'js' => 'application/javascript', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'bin' => 'application/macbinary', + 'doc' => 'application/msword', + 'word' => 'application/msword', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'class' => 'application/octet-stream', + 'dll' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'exe' => 'application/octet-stream', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'psd' => 'application/octet-stream', + 'sea' => 'application/octet-stream', + 'so' => 'application/octet-stream', + 'oda' => 'application/oda', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'gtar' => 'application/x-gtar', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'php' => 'application/x-httpd-php', + 'phtml' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'tar' => 'application/x-tar', + 'tgz' => 'application/x-tar', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'zip' => 'application/zip', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mpga' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'ram' => 'audio/x-pn-realaudio', + 'rm' => 'audio/x-pn-realaudio', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'ra' => 'audio/x-realaudio', + 'wav' => 'audio/x-wav', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'jpeg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'png' => 'image/png', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'eml' => 'message/rfc822', + 'css' => 'text/css', + 'html' => 'text/html', + 'htm' => 'text/html', + 'shtml' => 'text/html', + 'log' => 'text/plain', + 'text' => 'text/plain', + 'txt' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'vcf' => 'text/vcard', + 'vcard' => 'text/vcard', + 'xml' => 'text/xml', + 'xsl' => 'text/xml', + 'mpeg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mov' => 'video/quicktime', + 'qt' => 'video/quicktime', + 'rv' => 'video/vnd.rn-realvideo', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie' + ); + if ( array_key_exists( strtolower( $ext ), $mimes ) ) { + return $mimes[ strtolower( $ext ) ]; + } + + return 'application/octet-stream'; + } + + /** + * Return the array of attachments. + * @return array + */ + public function getAttachments() { + return $this->attachment; + } + + /** + * Backward compatibility wrapper for an old QP encoding function that was removed. + * @see PHPMailer::encodeQP() + * @access public + * + * @param string $string + * @param integer $line_max + * @param boolean $space_conv + * + * @return string + * @deprecated Use encodeQP instead. + */ + public function encodeQPphp( + $string, + $line_max = 76, + /** @noinspection PhpUnusedParameterInspection */ + $space_conv = false + ) { + return $this->encodeQP( $string, $line_max ); + } + + /** + * Add a string or binary attachment (non-filesystem). + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * + * @param string $string String attachment data. + * @param string $filename Name of the attachment. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @param string $disposition Disposition to use + * + * @return void + */ + public function addStringAttachment( + $string, + $filename, + $encoding = 'base64', + $type = '', + $disposition = 'attachment' + ) { + // If a MIME type is not specified, try to work it out from the file name + if ( $type == '' ) { + $type = self::filenameToType( $filename ); + } + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $filename, + 2 => basename( $filename ), + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => $disposition, + 7 => 0 + ); + } + + /** + * Clear all To recipients. + * @return void + */ + public function clearAddresses() { + foreach ( $this->to as $to ) { + unset( $this->all_recipients[ strtolower( $to[0] ) ] ); + } + $this->to = array(); + $this->clearQueuedAddresses( 'to' ); + } + + /** + * Clear queued addresses of given kind. + * @access protected + * + * @param string $kind 'to', 'cc', or 'bcc' + * + * @return void + */ + public function clearQueuedAddresses( $kind ) { + $RecipientsQueue = $this->RecipientsQueue; + foreach ( $RecipientsQueue as $address => $params ) { + if ( $params[0] == $kind ) { + unset( $this->RecipientsQueue[ $address ] ); + } + } + } + + /** + * Clear all CC recipients. + * @return void + */ + public function clearCCs() { + foreach ( $this->cc as $cc ) { + unset( $this->all_recipients[ strtolower( $cc[0] ) ] ); + } + $this->cc = array(); + $this->clearQueuedAddresses( 'cc' ); + } + + /** + * Clear all BCC recipients. + * @return void + */ + public function clearBCCs() { + foreach ( $this->bcc as $bcc ) { + unset( $this->all_recipients[ strtolower( $bcc[0] ) ] ); + } + $this->bcc = array(); + $this->clearQueuedAddresses( 'bcc' ); + } + + /** + * Clear all ReplyTo recipients. + * @return void + */ + public function clearReplyTos() { + $this->ReplyTo = array(); + $this->ReplyToQueue = array(); + } + + /** + * Clear all recipient types. + * @return void + */ + public function clearAllRecipients() { + $this->to = array(); + $this->cc = array(); + $this->bcc = array(); + $this->all_recipients = array(); + $this->RecipientsQueue = array(); + } + + /** + * Clear all filesystem, string, and binary attachments. + * @return void + */ + public function clearAttachments() { + $this->attachment = array(); + } + + /** + * Clear all custom headers. + * @return void + */ + public function clearCustomHeaders() { + $this->CustomHeader = array(); + } + + /** + * Add a custom header. + * $name value can be overloaded to contain + * both header name and value (name:value) + * @access public + * + * @param string $name Custom header name + * @param string $value Header value + * + * @return void + */ + public function addCustomHeader( $name, $value = null ) { + if ( $value === null ) { + // Value passed in as name:value + $this->CustomHeader[] = explode( ':', $name, 2 ); + } else { + $this->CustomHeader[] = array( $name, $value ); + } + } + + /** + * Returns all custom headers. + * @return array + */ + public function getCustomHeaders() { + return $this->CustomHeader; + } + + /** + * Create a message from an HTML string. + * Automatically makes modifications for inline images and backgrounds + * and creates a plain-text version by converting the HTML. + * Overwrites any existing values in $this->Body and $this->AltBody + * @access public + * + * @param string $message HTML message string + * @param string $basedir baseline directory for path + * @param boolean|callable $advanced Whether to use the internal HTML to text converter + * or your own custom converter @see PHPMailer::html2text() + * + * @return string $message + */ + public function msgHTML( $message, $basedir = '', $advanced = false ) { + preg_match_all( '/(src|background)=["\'](.*)["\']/Ui', $message, $images ); + if ( array_key_exists( 2, $images ) ) { + foreach ( $images[2] as $imgindex => $url ) { + // Convert data URIs into embedded images + if ( preg_match( '#^data:(image[^;,]*)(;base64)?,#', $url, $match ) ) { + $data = substr( $url, strpos( $url, ',' ) ); + if ( $match[2] ) { + $data = base64_decode( $data ); + } else { + $data = rawurldecode( $data ); + } + $cid = md5( $url ) . '@phpmailer.0'; // RFC2392 S 2 + if ( $this->addStringEmbeddedImage( $data, $cid, 'embed' . $imgindex, 'base64', $match[1] ) ) { + $message = str_replace( + $images[0][ $imgindex ], + $images[1][ $imgindex ] . '="cid:' . $cid . '"', + $message + ); + } + } elseif ( substr( $url, 0, 4 ) !== 'cid:' && ! preg_match( '#^[a-z][a-z0-9+.-]*://#i', $url ) ) { + // Do not change urls for absolute images (thanks to corvuscorax) + // Do not change urls that are already inline images + $filename = basename( $url ); + $directory = dirname( $url ); + if ( $directory == '.' ) { + $directory = ''; + } + $cid = md5( $url ) . '@phpmailer.0'; // RFC2392 S 2 + if ( strlen( $basedir ) > 1 && substr( $basedir, - 1 ) != '/' ) { + $basedir .= '/'; + } + if ( strlen( $directory ) > 1 && substr( $directory, - 1 ) != '/' ) { + $directory .= '/'; + } + if ( $this->addEmbeddedImage( + $basedir . $directory . $filename, + $cid, + $filename, + 'base64', + self::_mime_types( (string) self::mb_pathinfo( $filename, PATHINFO_EXTENSION ) ) + ) + ) { + $message = preg_replace( + '/' . $images[1][ $imgindex ] . '=["\']' . preg_quote( $url, '/' ) . '["\']/Ui', + $images[1][ $imgindex ] . '="cid:' . $cid . '"', + $message + ); + } + } + } + } + $this->isHTML( true ); + // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better + $this->Body = $this->normalizeBreaks( $message ); + $this->AltBody = $this->normalizeBreaks( $this->html2text( $message, $advanced ) ); + if ( ! $this->alternativeExists() ) { + $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . + self::CRLF . self::CRLF; + } + + return $this->Body; + } + + /** + * Add an embedded stringified attachment. + * This can include images, sounds, and just about any other document type. + * Be sure to set the $type to an image type for images: + * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'. + * + * @param string $string The attachment binary data. + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML. + * @param string $name + * @param string $encoding File encoding (see $Encoding). + * @param string $type MIME type. + * @param string $disposition Disposition to use + * + * @return boolean True on successfully adding an attachment + */ + public function addStringEmbeddedImage( + $string, + $cid, + $name = '', + $encoding = 'base64', + $type = '', + $disposition = 'inline' + ) { + // If a MIME type is not specified, try to work it out from the name + if ( $type == '' and ! empty( $name ) ) { + $type = self::filenameToType( $name ); + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $string, + 1 => $name, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => true, // isStringAttachment + 6 => $disposition, + 7 => $cid + ); + + return true; + } + + /** + * Add an embedded (inline) attachment from a file. + * This can include images, sounds, and just about any other document type. + * These differ from 'regular' attachments in that they are intended to be + * displayed inline with the message, not just attached for download. + * This is used in HTML messages that embed the images + * the HTML refers to using the $cid value. + * + * @param string $path Path to the attachment. + * @param string $cid Content ID of the attachment; Use this to reference + * the content when using an embedded image in HTML. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File MIME type. + * @param string $disposition Disposition to use + * + * @return boolean True on successfully adding an attachment + */ + public function addEmbeddedImage( $path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline' ) { + if ( ! @is_file( $path ) ) { + $this->setError( $this->lang( 'file_access' ) . $path ); + + return false; + } + + // If a MIME type is not specified, try to work it out from the file name + if ( $type == '' ) { + $type = self::filenameToType( $path ); + } + + $filename = basename( $path ); + if ( $name == '' ) { + $name = $filename; + } + + // Append to $attachment array + $this->attachment[] = array( + 0 => $path, + 1 => $filename, + 2 => $name, + 3 => $encoding, + 4 => $type, + 5 => false, // isStringAttachment + 6 => $disposition, + 7 => $cid + ); + + return true; + } + + /** + * Sets message type to HTML or plain. + * + * @param boolean $isHtml True for HTML mode. + * + * @return void + */ + public function isHTML( $isHtml = true ) { + if ( $isHtml ) { + $this->ContentType = 'text/html'; + } else { + $this->ContentType = 'text/plain'; + } + } + + /** + * Normalize line breaks in a string. + * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format. + * Defaults to CRLF (for message bodies) and preserves consecutive breaks. + * + * @param string $text + * @param string $breaktype What kind of line break to use, defaults to CRLF + * + * @return string + * @access public + * @static + */ + public static function normalizeBreaks( $text, $breaktype = "\r\n" ) { + return preg_replace( '/(\r\n|\r|\n)/ms', $breaktype, $text ); + } + + /** + * Convert an HTML string into plain text. + * This is used by msgHTML(). + * Note - older versions of this function used a bundled advanced converter + * which was been removed for license reasons in #232 + * Example usage: + * + * // Use default conversion + * $plain = $mail->html2text($html); + * // Use your own custom converter + * $plain = $mail->html2text($html, function($html) { + * $converter = new MyHtml2text($html); + * return $converter->get_text(); + * }); + * + * + * @param string $html The HTML text to convert + * @param boolean|callable $advanced Any boolean value to use the internal converter, + * or provide your own callable for custom conversion. + * + * @return string + */ + public function html2text( $html, $advanced = false ) { + if ( is_callable( $advanced ) ) { + return call_user_func( $advanced, $html ); + } + + return html_entity_decode( + trim( strip_tags( preg_replace( '/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html ) ) ), + ENT_QUOTES, + $this->CharSet + ); + } + + /** + * Set or reset instance properties. + * You should avoid this function - it's more verbose, less efficient, more error-prone and + * harder to debug than setting properties directly. + * Usage Example: + * `$mail->set('SMTPSecure', 'tls');` + * is the same as: + * `$mail->SMTPSecure = 'tls';` + * @access public + * + * @param string $name The property name to set + * @param mixed $value The value to set the property to + * + * @return boolean + */ + public function set( $name, $value = '' ) { + if ( property_exists( $this, $name ) ) { + $this->$name = $value; + + return true; + } else { + $this->setError( $this->lang( 'variable_set' ) . $name ); + + return false; + } + } + + /** + * Set the public and private key files and password for S/MIME signing. + * @access public + * + * @param string $cert_filename + * @param string $key_filename + * @param string $key_pass Password for private key + * @param string $extracerts_filename Optional path to chain certificate + */ + public function sign( $cert_filename, $key_filename, $key_pass, $extracerts_filename = '' ) { + $this->sign_cert_file = $cert_filename; + $this->sign_key_file = $key_filename; + $this->sign_key_pass = $key_pass; + $this->sign_extracerts_file = $extracerts_filename; + } + + /** + * Allows for public read access to 'to' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * @access public + * @return array + */ + public function getToAddresses() { + return $this->to; + } + + /** + * Allows for public read access to 'cc' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * @access public + * @return array + */ + public function getCcAddresses() { + return $this->cc; + } + + /** + * Allows for public read access to 'bcc' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * @access public + * @return array + */ + public function getBccAddresses() { + return $this->bcc; + } + + /** + * Allows for public read access to 'ReplyTo' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * @access public + * @return array + */ + public function getReplyToAddresses() { + return $this->ReplyTo; + } + + /** + * Allows for public read access to 'all_recipients' property. + * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included. + * @access public + * @return array + */ + public function getAllRecipientAddresses() { + return $this->all_recipients; + } + + /** + * Add an address to one of the recipient arrays or to the ReplyTo array. + * Addresses that have been added already return false, but do not throw exceptions. + * + * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' + * @param string $address The email address to send, resp. to reply to + * @param string $name + * + * @throws phpmailerException + * @return boolean true on success, false if address already used or invalid in some way + * @access protected + */ + protected function addAnAddress( $kind, $address, $name = '' ) { + if ( ! in_array( $kind, array( 'to', 'cc', 'bcc', 'Reply-To' ) ) ) { + $error_message = $this->lang( 'Invalid recipient kind: ' ) . $kind; + $this->setError( $error_message ); + $this->edebug( $error_message ); + if ( $this->exceptions ) { + throw new phpmailerException( $error_message ); + } + + return false; + } + if ( ! $this->validateAddress( $address ) ) { + $error_message = $this->lang( 'invalid_address' ) . " (addAnAddress $kind): $address"; + $this->setError( $error_message ); + $this->edebug( $error_message ); + if ( $this->exceptions ) { + throw new phpmailerException( $error_message ); + } + + return false; + } + if ( $kind != 'Reply-To' ) { + if ( ! array_key_exists( strtolower( $address ), $this->all_recipients ) ) { + array_push( $this->$kind, array( $address, $name ) ); + $this->all_recipients[ strtolower( $address ) ] = true; + + return true; + } + } else { + if ( ! array_key_exists( strtolower( $address ), $this->ReplyTo ) ) { + $this->ReplyTo[ strtolower( $address ) ] = array( $address, $name ); + + return true; + } + } + + return false; + } + + /** + * Check that a string looks like an email address. + * + * @param string $address The email address to check + * @param string $patternselect A selector for the validation pattern to use : + * * `auto` Pick best pattern automatically; + * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14; + * * `pcre` Use old PCRE implementation; + * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; + * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. + * * `noregex` Don't use a regex: super fast, really dumb. + * + * @return boolean + * @static + * @access public + */ + public static function validateAddress( $address, $patternselect = 'auto' ) { + //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 + if ( strpos( $address, "\n" ) !== false or strpos( $address, "\r" ) !== false ) { + return false; + } + if ( ! $patternselect or $patternselect == 'auto' ) { + //Check this constant first so it works when extension_loaded() is disabled by safe mode + //Constant was added in PHP 5.2.4 + if ( defined( 'PCRE_VERSION' ) ) { + //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2 + if ( version_compare( PCRE_VERSION, '8.0.3' ) >= 0 ) { + $patternselect = 'pcre8'; + } else { + $patternselect = 'pcre'; + } + } elseif ( function_exists( 'extension_loaded' ) and extension_loaded( 'pcre' ) ) { + //Fall back to older PCRE + $patternselect = 'pcre'; + } else { + //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension + if ( version_compare( PHP_VERSION, '5.2.0' ) >= 0 ) { + $patternselect = 'php'; + } else { + $patternselect = 'noregex'; + } + } + } + switch ( $patternselect ) { + case 'pcre8': + /** + * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains. + * @link http://squiloople.com/2009/12/20/email-address-validation/ + * @copyright 2009-2010 Michael Rushton + * Feel free to use and redistribute this code. But please keep this copyright notice. + */ + return (boolean) preg_match( + '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . + '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . + '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . + '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . + '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . + '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . + '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . + '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', + $address + ); + case 'pcre': + //An older regex that doesn't need a recent PCRE + return (boolean) preg_match( + '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' . + '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' . + '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' . + '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' . + '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' . + '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' . + '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' . + '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' . + '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' . + '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', + $address + ); + case 'html5': + /** + * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. + * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) + */ + return (boolean) preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); + case 'noregex': + //No PCRE! Do something _very_ approximate! + //Check the address is 3 chars or longer and contains an @ that's not the first or last char + return ( strlen( $address ) >= 3 + and strpos( $address, '@' ) >= 1 + and strpos( $address, '@' ) != strlen( $address ) - 1 ); + case 'php': + default: + return (boolean) filter_var( $address, FILTER_VALIDATE_EMAIL ); + } + } } /** * PHPMailer exception handler * @package PHPMailer */ -class phpmailerException extends Exception -{ - /** - * Prettify error message output - * @return string - */ - public function errorMessage() - { - $errorMsg = '' . $this->getMessage() . "
\n"; - return $errorMsg; - } +class phpmailerException extends Exception { + /** + * Prettify error message output + * @return string + */ + public function errorMessage() { + $errorMsg = '' . $this->getMessage() . "
\n"; + + return $errorMsg; + } } diff --git a/src/lib/mail/class.smtp.php b/src/lib/mail/class.smtp.php index 2e32e2f..af41bf2 100644 --- a/src/lib/mail/class.smtp.php +++ b/src/lib/mail/class.smtp.php @@ -24,1158 +24,1191 @@ * @author Chris Ryan * @author Marcus Bointon */ -class SMTP -{ - /** - * The PHPMailer SMTP version number. - * @var string - */ - const VERSION = '5.2.14'; - - /** - * SMTP line break constant. - * @var string - */ - const CRLF = "\r\n"; - - /** - * The SMTP port to use if one is not specified. - * @var integer - */ - const DEFAULT_SMTP_PORT = 25; - - /** - * The maximum line length allowed by RFC 2822 section 2.1.1 - * @var integer - */ - const MAX_LINE_LENGTH = 998; - - /** - * Debug level for no output - */ - const DEBUG_OFF = 0; - - /** - * Debug level to show client -> server messages - */ - const DEBUG_CLIENT = 1; - - /** - * Debug level to show client -> server and server -> client messages - */ - const DEBUG_SERVER = 2; - - /** - * Debug level to show connection status, client -> server and server -> client messages - */ - const DEBUG_CONNECTION = 3; - - /** - * Debug level to show all messages - */ - const DEBUG_LOWLEVEL = 4; - - /** - * The PHPMailer SMTP Version number. - * @var string - * @deprecated Use the `VERSION` constant instead - * @see SMTP::VERSION - */ - public $Version = '5.2.14'; - - /** - * SMTP server port number. - * @var integer - * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead - * @see SMTP::DEFAULT_SMTP_PORT - */ - public $SMTP_PORT = 25; - - /** - * SMTP reply line ending. - * @var string - * @deprecated Use the `CRLF` constant instead - * @see SMTP::CRLF - */ - public $CRLF = "\r\n"; - - /** - * Debug output level. - * Options: - * * self::DEBUG_OFF (`0`) No debug output, default - * * self::DEBUG_CLIENT (`1`) Client commands - * * self::DEBUG_SERVER (`2`) Client commands and server responses - * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status - * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages - * @var integer - */ - public $do_debug = self::DEBUG_OFF; - - /** - * How to handle debug output. - * Options: - * * `echo` Output plain-text as-is, appropriate for CLI - * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output - * * `error_log` Output to error log as configured in php.ini - * - * Alternatively, you can provide a callable expecting two params: a message string and the debug level: - * - * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; - * - * @var string|callable - */ - public $Debugoutput = 'echo'; - - /** - * Whether to use VERP. - * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path - * @link http://www.postfix.org/VERP_README.html Info on VERP - * @var boolean - */ - public $do_verp = false; - - /** - * The timeout value for connection, in seconds. - * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 - * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. - * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 - * @var integer - */ - public $Timeout = 300; - - /** - * How long to wait for commands to complete, in seconds. - * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 - * @var integer - */ - public $Timelimit = 300; - - /** - * The socket for the server connection. - * @var resource - */ - protected $smtp_conn; - - /** - * Error information, if any, for the last SMTP command. - * @var array - */ - protected $error = array( - 'error' => '', - 'detail' => '', - 'smtp_code' => '', - 'smtp_code_ex' => '' - ); - - /** - * The reply the server sent to us for HELO. - * If null, no HELO string has yet been received. - * @var string|null - */ - protected $helo_rply = null; - - /** - * The set of SMTP extensions sent in reply to EHLO command. - * Indexes of the array are extension names. - * Value at index 'HELO' or 'EHLO' (according to command that was sent) - * represents the server name. In case of HELO it is the only element of the array. - * Other values can be boolean TRUE or an array containing extension options. - * If null, no HELO/EHLO string has yet been received. - * @var array|null - */ - protected $server_caps = null; - - /** - * The most recent reply received from the server. - * @var string - */ - protected $last_reply = ''; - - /** - * Output debugging info via a user-selected method. - * @see SMTP::$Debugoutput - * @see SMTP::$do_debug - * @param string $str Debug string to output - * @param integer $level The debug level of this message; see DEBUG_* constants - * @return void - */ - protected function edebug($str, $level = 0) - { - if ($level > $this->do_debug) { - return; - } - //Avoid clash with built-in function names - if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) { - call_user_func($this->Debugoutput, $str, $this->do_debug); - return; - } - switch ($this->Debugoutput) { - case 'error_log': - //Don't output, just log - error_log($str); - break; - case 'html': - //Cleans up output a bit for a better looking, HTML-safe output - echo htmlentities( - preg_replace('/[\r\n]+/', '', $str), - ENT_QUOTES, - 'UTF-8' - ) - . "
\n"; - break; - case 'echo': - default: - //Normalize line breaks - $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str); - echo gmdate('Y-m-d H:i:s') . "\t" . str_replace( - "\n", - "\n \t ", - trim($str) - )."\n"; - } - } - - /** - * Connect to an SMTP server. - * @param string $host SMTP server IP or host name - * @param integer $port The port number to connect to - * @param integer $timeout How long to wait for the connection to open - * @param array $options An array of options for stream_context_create() - * @access public - * @return boolean - */ - public function connect($host, $port = null, $timeout = 30, $options = array()) - { - static $streamok; - //This is enabled by default since 5.0.0 but some providers disable it - //Check this once and cache the result - if (is_null($streamok)) { - $streamok = function_exists('stream_socket_client'); - } - // Clear errors to avoid confusion - $this->setError(''); - // Make sure we are __not__ connected - if ($this->connected()) { - // Already connected, generate error - $this->setError('Already connected to a server'); - return false; - } - if (empty($port)) { - $port = self::DEFAULT_SMTP_PORT; - } - // Connect to the SMTP server - $this->edebug( - "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true), - self::DEBUG_CONNECTION - ); - $errno = 0; - $errstr = ''; - if ($streamok) { - $socket_context = stream_context_create($options); - //Suppress errors; connection failures are handled at a higher level - $this->smtp_conn = @stream_socket_client( - $host . ":" . $port, - $errno, - $errstr, - $timeout, - STREAM_CLIENT_CONNECT, - $socket_context - ); - } else { - //Fall back to fsockopen which should work in more places, but is missing some features - $this->edebug( - "Connection: stream_socket_client not available, falling back to fsockopen", - self::DEBUG_CONNECTION - ); - $this->smtp_conn = fsockopen( - $host, - $port, - $errno, - $errstr, - $timeout - ); - } - // Verify we connected properly - if (!is_resource($this->smtp_conn)) { - $this->setError( - 'Failed to connect to server', - $errno, - $errstr - ); - $this->edebug( - 'SMTP ERROR: ' . $this->error['error'] - . ": $errstr ($errno)", - self::DEBUG_CLIENT - ); - return false; - } - $this->edebug('Connection: opened', self::DEBUG_CONNECTION); - // SMTP server can take longer to respond, give longer timeout for first read - // Windows does not have support for this timeout function - if (substr(PHP_OS, 0, 3) != 'WIN') { - $max = ini_get('max_execution_time'); - // Don't bother if unlimited - if ($max != 0 && $timeout > $max) { - @set_time_limit($timeout); - } - stream_set_timeout($this->smtp_conn, $timeout, 0); - } - // Get any announcement - $announce = $this->get_lines(); - $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); - return true; - } - - /** - * Initiate a TLS (encrypted) session. - * @access public - * @return boolean - */ - public function startTLS() - { - if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { - return false; - } - // Begin encrypted connection - if (!stream_socket_enable_crypto( - $this->smtp_conn, - true, - STREAM_CRYPTO_METHOD_TLS_CLIENT - )) { - return false; - } - return true; - } - - /** - * Perform SMTP authentication. - * Must be run after hello(). - * @see hello() - * @param string $username The user name - * @param string $password The password - * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2) - * @param string $realm The auth realm for NTLM - * @param string $workstation The auth workstation for NTLM - * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) - * @return bool True if successfully authenticated.* @access public - */ - public function authenticate( - $username, - $password, - $authtype = null, - $realm = '', - $workstation = '', - $OAuth = null - ) { - if (!$this->server_caps) { - $this->setError('Authentication is not allowed before HELO/EHLO'); - return false; - } - - if (array_key_exists('EHLO', $this->server_caps)) { - // SMTP extensions are available. Let's try to find a proper authentication method - - if (!array_key_exists('AUTH', $this->server_caps)) { - $this->setError('Authentication is not allowed at this stage'); - // 'at this stage' means that auth may be allowed after the stage changes - // e.g. after STARTTLS - return false; - } - - self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); - self::edebug( - 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), - self::DEBUG_LOWLEVEL - ); - - if (empty($authtype)) { - foreach (array('LOGIN', 'CRAM-MD5', 'NTLM', 'PLAIN', 'XOAUTH2') as $method) { - if (in_array($method, $this->server_caps['AUTH'])) { - $authtype = $method; - break; - } - } - if (empty($authtype)) { - $this->setError('No supported authentication methods found'); - return false; - } - self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL); - } - - if (!in_array($authtype, $this->server_caps['AUTH'])) { - $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); - return false; - } - } elseif (empty($authtype)) { - $authtype = 'LOGIN'; - } - switch ($authtype) { - case 'PLAIN': - // Start authentication - if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { - return false; - } - // Send encoded username and password - if (!$this->sendCommand( - 'User & Password', - base64_encode("\0" . $username . "\0" . $password), - 235 - ) - ) { - return false; - } - break; - case 'LOGIN': - // Start authentication - if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { - return false; - } - if (!$this->sendCommand("Username", base64_encode($username), 334)) { - return false; - } - if (!$this->sendCommand("Password", base64_encode($password), 235)) { - return false; - } - break; - case 'XOAUTH2': - //If the OAuth Instance is not set. Can be a case when PHPMailer is used - //instead of PHPMailerOAuth - if (is_null($OAuth)) { - return false; - } - $oauth = $OAuth->getOauth64(); - - // Start authentication - if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) { - return false; - } - break; - case 'NTLM': - /* - * ntlm_sasl_client.php - * Bundled with Permission - * - * How to telnet in windows: - * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx - * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication - */ - require_once 'extras/ntlm_sasl_client.php'; - $temp = new stdClass; - $ntlm_client = new ntlm_sasl_client_class; - //Check that functions are available - if (!$ntlm_client->Initialize($temp)) { - $this->setError($temp->error); - $this->edebug( - 'You need to enable some modules in your php.ini file: ' - . $this->error['error'], - self::DEBUG_CLIENT - ); - return false; - } - //msg1 - $msg1 = $ntlm_client->TypeMsg1($realm, $workstation); //msg1 - - if (!$this->sendCommand( - 'AUTH NTLM', - 'AUTH NTLM ' . base64_encode($msg1), - 334 - ) - ) { - return false; - } - //Though 0 based, there is a white space after the 3 digit number - //msg2 - $challenge = substr($this->last_reply, 3); - $challenge = base64_decode($challenge); - $ntlm_res = $ntlm_client->NTLMResponse( - substr($challenge, 24, 8), - $password - ); - //msg3 - $msg3 = $ntlm_client->TypeMsg3( - $ntlm_res, - $username, - $realm, - $workstation - ); - // send encoded username - return $this->sendCommand('Username', base64_encode($msg3), 235); - case 'CRAM-MD5': - // Start authentication - if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { - return false; - } - // Get the challenge - $challenge = base64_decode(substr($this->last_reply, 4)); - - // Build the response - $response = $username . ' ' . $this->hmac($challenge, $password); - - // send encoded credentials - return $this->sendCommand('Username', base64_encode($response), 235); - default: - $this->setError("Authentication method \"$authtype\" is not supported"); - return false; - } - return true; - } - - /** - * Calculate an MD5 HMAC hash. - * Works like hash_hmac('md5', $data, $key) - * in case that function is not available - * @param string $data The data to hash - * @param string $key The key to hash with - * @access protected - * @return string - */ - protected function hmac($data, $key) - { - if (function_exists('hash_hmac')) { - return hash_hmac('md5', $data, $key); - } - - // The following borrowed from - // http://php.net/manual/en/function.mhash.php#27225 - - // RFC 2104 HMAC implementation for php. - // Creates an md5 HMAC. - // Eliminates the need to install mhash to compute a HMAC - // by Lance Rushing - - $bytelen = 64; // byte length for md5 - if (strlen($key) > $bytelen) { - $key = pack('H*', md5($key)); - } - $key = str_pad($key, $bytelen, chr(0x00)); - $ipad = str_pad('', $bytelen, chr(0x36)); - $opad = str_pad('', $bytelen, chr(0x5c)); - $k_ipad = $key ^ $ipad; - $k_opad = $key ^ $opad; - - return md5($k_opad . pack('H*', md5($k_ipad . $data))); - } - - /** - * Check connection state. - * @access public - * @return boolean True if connected. - */ - public function connected() - { - if (is_resource($this->smtp_conn)) { - $sock_status = stream_get_meta_data($this->smtp_conn); - if ($sock_status['eof']) { - // The socket is valid but we are not connected - $this->edebug( - 'SMTP NOTICE: EOF caught while checking if connected', - self::DEBUG_CLIENT - ); - $this->close(); - return false; - } - return true; // everything looks good - } - return false; - } - - /** - * Close the socket and clean up the state of the class. - * Don't use this function without first trying to use QUIT. - * @see quit() - * @access public - * @return void - */ - public function close() - { - $this->setError(''); - $this->server_caps = null; - $this->helo_rply = null; - if (is_resource($this->smtp_conn)) { - // close the connection and cleanup - fclose($this->smtp_conn); - $this->smtp_conn = null; //Makes for cleaner serialization - $this->edebug('Connection: closed', self::DEBUG_CONNECTION); - } - } - - /** - * Send an SMTP DATA command. - * Issues a data command and sends the msg_data to the server, - * finializing the mail transaction. $msg_data is the message - * that is to be send with the headers. Each header needs to be - * on a single line followed by a with the message headers - * and the message body being separated by and additional . - * Implements rfc 821: DATA - * @param string $msg_data Message data to send - * @access public - * @return boolean - */ - public function data($msg_data) - { - //This will use the standard timelimit - if (!$this->sendCommand('DATA', 'DATA', 354)) { - return false; - } - - /* The server is ready to accept data! - * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) - * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into - * smaller lines to fit within the limit. - * We will also look for lines that start with a '.' and prepend an additional '.'. - * NOTE: this does not count towards line-length limit. - */ - - // Normalize line breaks before exploding - $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); - - /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field - * of the first line (':' separated) does not contain a space then it _should_ be a header and we will - * process all lines before a blank line as headers. - */ - - $field = substr($lines[0], 0, strpos($lines[0], ':')); - $in_headers = false; - if (!empty($field) && strpos($field, ' ') === false) { - $in_headers = true; - } - - foreach ($lines as $line) { - $lines_out = array(); - if ($in_headers and $line == '') { - $in_headers = false; - } - //Break this line up into several smaller lines if it's too long - //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), - while (isset($line[self::MAX_LINE_LENGTH])) { - //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on - //so as to avoid breaking in the middle of a word - $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); - //Deliberately matches both false and 0 - if (!$pos) { - //No nice break found, add a hard break - $pos = self::MAX_LINE_LENGTH - 1; - $lines_out[] = substr($line, 0, $pos); - $line = substr($line, $pos); - } else { - //Break at the found point - $lines_out[] = substr($line, 0, $pos); - //Move along by the amount we dealt with - $line = substr($line, $pos + 1); - } - //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 - if ($in_headers) { - $line = "\t" . $line; - } - } - $lines_out[] = $line; - - //Send the lines to the server - foreach ($lines_out as $line_out) { - //RFC2821 section 4.5.2 - if (!empty($line_out) and $line_out[0] == '.') { - $line_out = '.' . $line_out; - } - $this->client_send($line_out . self::CRLF); - } - } - - //Message data has been sent, complete the command - //Increase timelimit for end of DATA command - $savetimelimit = $this->Timelimit; - $this->Timelimit = $this->Timelimit * 2; - $result = $this->sendCommand('DATA END', '.', 250); - //Restore timelimit - $this->Timelimit = $savetimelimit; - return $result; - } - - /** - * Send an SMTP HELO or EHLO command. - * Used to identify the sending server to the receiving server. - * This makes sure that client and server are in a known state. - * Implements RFC 821: HELO - * and RFC 2821 EHLO. - * @param string $host The host name or IP to connect to - * @access public - * @return boolean - */ - public function hello($host = '') - { - //Try extended hello first (RFC 2821) - return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); - } - - /** - * Send an SMTP HELO or EHLO command. - * Low-level implementation used by hello() - * @see hello() - * @param string $hello The HELO string - * @param string $host The hostname to say we are - * @access protected - * @return boolean - */ - protected function sendHello($hello, $host) - { - $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); - $this->helo_rply = $this->last_reply; - if ($noerror) { - $this->parseHelloFields($hello); - } else { - $this->server_caps = null; - } - return $noerror; - } - - /** - * Parse a reply to HELO/EHLO command to discover server extensions. - * In case of HELO, the only parameter that can be discovered is a server name. - * @access protected - * @param string $type - 'HELO' or 'EHLO' - */ - protected function parseHelloFields($type) - { - $this->server_caps = array(); - $lines = explode("\n", $this->last_reply); - - foreach ($lines as $n => $s) { - //First 4 chars contain response code followed by - or space - $s = trim(substr($s, 4)); - if (empty($s)) { - continue; - } - $fields = explode(' ', $s); - if (!empty($fields)) { - if (!$n) { - $name = $type; - $fields = $fields[0]; - } else { - $name = array_shift($fields); - switch ($name) { - case 'SIZE': - $fields = ($fields ? $fields[0] : 0); - break; - case 'AUTH': - if (!is_array($fields)) { - $fields = array(); - } - break; - default: - $fields = true; - } - } - $this->server_caps[$name] = $fields; - } - } - } - - /** - * Send an SMTP MAIL command. - * Starts a mail transaction from the email address specified in - * $from. Returns true if successful or false otherwise. If True - * the mail transaction is started and then one or more recipient - * commands may be called followed by a data command. - * Implements rfc 821: MAIL FROM: - * @param string $from Source address of this message - * @access public - * @return boolean - */ - public function mail($from) - { - $useVerp = ($this->do_verp ? ' XVERP' : ''); - return $this->sendCommand( - 'MAIL FROM', - 'MAIL FROM:<' . $from . '>' . $useVerp, - 250 - ); - } - - /** - * Send an SMTP QUIT command. - * Closes the socket if there is no error or the $close_on_error argument is true. - * Implements from rfc 821: QUIT - * @param boolean $close_on_error Should the connection close if an error occurs? - * @access public - * @return boolean - */ - public function quit($close_on_error = true) - { - $noerror = $this->sendCommand('QUIT', 'QUIT', 221); - $err = $this->error; //Save any error - if ($noerror or $close_on_error) { - $this->close(); - $this->error = $err; //Restore any error from the quit command - } - return $noerror; - } - - /** - * Send an SMTP RCPT command. - * Sets the TO argument to $toaddr. - * Returns true if the recipient was accepted false if it was rejected. - * Implements from rfc 821: RCPT TO: - * @param string $address The address the message is being sent to - * @access public - * @return boolean - */ - public function recipient($address) - { - return $this->sendCommand( - 'RCPT TO', - 'RCPT TO:<' . $address . '>', - array(250, 251) - ); - } - - /** - * Send an SMTP RSET command. - * Abort any transaction that is currently in progress. - * Implements rfc 821: RSET - * @access public - * @return boolean True on success. - */ - public function reset() - { - return $this->sendCommand('RSET', 'RSET', 250); - } - - /** - * Send a command to an SMTP server and check its return code. - * @param string $command The command name - not sent to the server - * @param string $commandstring The actual command to send - * @param integer|array $expect One or more expected integer success codes - * @access protected - * @return boolean True on success. - */ - protected function sendCommand($command, $commandstring, $expect) - { - if (!$this->connected()) { - $this->setError("Called $command without being connected"); - return false; - } - //Reject line breaks in all commands - if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { - $this->setError("Command '$command' contained line breaks"); - return false; - } - $this->client_send($commandstring . self::CRLF); - - $this->last_reply = $this->get_lines(); - // Fetch SMTP code and possible error code explanation - $matches = array(); - if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) { - $code = $matches[1]; - $code_ex = (count($matches) > 2 ? $matches[2] : null); - // Cut off error code from each response line - $detail = preg_replace( - "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m", - '', - $this->last_reply - ); - } else { - // Fall back to simple parsing if regex fails - $code = substr($this->last_reply, 0, 3); - $code_ex = null; - $detail = substr($this->last_reply, 4); - } - - $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); - - if (!in_array($code, (array)$expect)) { - $this->setError( - "$command command failed", - $detail, - $code, - $code_ex - ); - $this->edebug( - 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, - self::DEBUG_CLIENT - ); - return false; - } - - $this->setError(''); - return true; - } - - /** - * Send an SMTP SAML command. - * Starts a mail transaction from the email address specified in $from. - * Returns true if successful or false otherwise. If True - * the mail transaction is started and then one or more recipient - * commands may be called followed by a data command. This command - * will send the message to the users terminal if they are logged - * in and send them an email. - * Implements rfc 821: SAML FROM: - * @param string $from The address the message is from - * @access public - * @return boolean - */ - public function sendAndMail($from) - { - return $this->sendCommand('SAML', "SAML FROM:$from", 250); - } - - /** - * Send an SMTP VRFY command. - * @param string $name The name to verify - * @access public - * @return boolean - */ - public function verify($name) - { - return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); - } - - /** - * Send an SMTP NOOP command. - * Used to keep keep-alives alive, doesn't actually do anything - * @access public - * @return boolean - */ - public function noop() - { - return $this->sendCommand('NOOP', 'NOOP', 250); - } - - /** - * Send an SMTP TURN command. - * This is an optional command for SMTP that this class does not support. - * This method is here to make the RFC821 Definition complete for this class - * and _may_ be implemented in future - * Implements from rfc 821: TURN - * @access public - * @return boolean - */ - public function turn() - { - $this->setError('The SMTP TURN command is not implemented'); - $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); - return false; - } - - /** - * Send raw data to the server. - * @param string $data The data to send - * @access public - * @return integer|boolean The number of bytes sent to the server or false on error - */ - public function client_send($data) - { - $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); - return fwrite($this->smtp_conn, $data); - } - - /** - * Get the latest error. - * @access public - * @return array - */ - public function getError() - { - return $this->error; - } - - /** - * Get SMTP extensions available on the server - * @access public - * @return array|null - */ - public function getServerExtList() - { - return $this->server_caps; - } - - /** - * A multipurpose method - * The method works in three ways, dependent on argument value and current state - * 1. HELO/EHLO was not sent - returns null and set up $this->error - * 2. HELO was sent - * $name = 'HELO': returns server name - * $name = 'EHLO': returns boolean false - * $name = any string: returns null and set up $this->error - * 3. EHLO was sent - * $name = 'HELO'|'EHLO': returns server name - * $name = any string: if extension $name exists, returns boolean True - * or its options. Otherwise returns boolean False - * In other words, one can use this method to detect 3 conditions: - * - null returned: handshake was not or we don't know about ext (refer to $this->error) - * - false returned: the requested feature exactly not exists - * - positive value returned: the requested feature exists - * @param string $name Name of SMTP extension or 'HELO'|'EHLO' - * @return mixed - */ - public function getServerExt($name) - { - if (!$this->server_caps) { - $this->setError('No HELO/EHLO was sent'); - return null; - } - - // the tight logic knot ;) - if (!array_key_exists($name, $this->server_caps)) { - if ($name == 'HELO') { - return $this->server_caps['EHLO']; - } - if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) { - return false; - } - $this->setError('HELO handshake was used. Client knows nothing about server extensions'); - return null; - } - - return $this->server_caps[$name]; - } - - /** - * Get the last reply from the server. - * @access public - * @return string - */ - public function getLastReply() - { - return $this->last_reply; - } - - /** - * Read the SMTP server's response. - * Either before eof or socket timeout occurs on the operation. - * With SMTP we can tell if we have more lines to read if the - * 4th character is '-' symbol. If it is a space then we don't - * need to read anything else. - * @access protected - * @return string - */ - protected function get_lines() - { - // If the connection is bad, give up straight away - if (!is_resource($this->smtp_conn)) { - return ''; - } - $data = ''; - $endtime = 0; - stream_set_timeout($this->smtp_conn, $this->Timeout); - if ($this->Timelimit > 0) { - $endtime = time() + $this->Timelimit; - } - while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { - $str = @fgets($this->smtp_conn, 515); - $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); - $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); - $data .= $str; - // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen - if ((isset($str[3]) and $str[3] == ' ')) { - break; - } - // Timed-out? Log and break - $info = stream_get_meta_data($this->smtp_conn); - if ($info['timed_out']) { - $this->edebug( - 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', - self::DEBUG_LOWLEVEL - ); - break; - } - // Now check if reads took too long - if ($endtime and time() > $endtime) { - $this->edebug( - 'SMTP -> get_lines(): timelimit reached ('. - $this->Timelimit . ' sec)', - self::DEBUG_LOWLEVEL - ); - break; - } - } - return $data; - } - - /** - * Enable or disable VERP address generation. - * @param boolean $enabled - */ - public function setVerp($enabled = false) - { - $this->do_verp = $enabled; - } - - /** - * Get VERP address generation mode. - * @return boolean - */ - public function getVerp() - { - return $this->do_verp; - } - - /** - * Set error messages and codes. - * @param string $message The error message - * @param string $detail Further detail on the error - * @param string $smtp_code An associated SMTP error code - * @param string $smtp_code_ex Extended SMTP code - */ - protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') - { - $this->error = array( - 'error' => $message, - 'detail' => $detail, - 'smtp_code' => $smtp_code, - 'smtp_code_ex' => $smtp_code_ex - ); - } - - /** - * Set debug output method. - * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. - */ - public function setDebugOutput($method = 'echo') - { - $this->Debugoutput = $method; - } - - /** - * Get debug output method. - * @return string - */ - public function getDebugOutput() - { - return $this->Debugoutput; - } - - /** - * Set debug output level. - * @param integer $level - */ - public function setDebugLevel($level = 0) - { - $this->do_debug = $level; - } - - /** - * Get debug output level. - * @return integer - */ - public function getDebugLevel() - { - return $this->do_debug; - } - - /** - * Set SMTP timeout. - * @param integer $timeout - */ - public function setTimeout($timeout = 0) - { - $this->Timeout = $timeout; - } - - /** - * Get SMTP timeout. - * @return integer - */ - public function getTimeout() - { - return $this->Timeout; - } +class SMTP { + /** + * The PHPMailer SMTP version number. + * @var string + */ + const VERSION = '5.2.14'; + + /** + * SMTP line break constant. + * @var string + */ + const CRLF = "\r\n"; + + /** + * The SMTP port to use if one is not specified. + * @var integer + */ + const DEFAULT_SMTP_PORT = 25; + + /** + * The maximum line length allowed by RFC 2822 section 2.1.1 + * @var integer + */ + const MAX_LINE_LENGTH = 998; + + /** + * Debug level for no output + */ + const DEBUG_OFF = 0; + + /** + * Debug level to show client -> server messages + */ + const DEBUG_CLIENT = 1; + + /** + * Debug level to show client -> server and server -> client messages + */ + const DEBUG_SERVER = 2; + + /** + * Debug level to show connection status, client -> server and server -> client messages + */ + const DEBUG_CONNECTION = 3; + + /** + * Debug level to show all messages + */ + const DEBUG_LOWLEVEL = 4; + + /** + * The PHPMailer SMTP Version number. + * @var string + * @deprecated Use the `VERSION` constant instead + * @see SMTP::VERSION + */ + public $Version = '5.2.14'; + + /** + * SMTP server port number. + * @var integer + * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead + * @see SMTP::DEFAULT_SMTP_PORT + */ + public $SMTP_PORT = 25; + + /** + * SMTP reply line ending. + * @var string + * @deprecated Use the `CRLF` constant instead + * @see SMTP::CRLF + */ + public $CRLF = "\r\n"; + + /** + * Debug output level. + * Options: + * * self::DEBUG_OFF (`0`) No debug output, default + * * self::DEBUG_CLIENT (`1`) Client commands + * * self::DEBUG_SERVER (`2`) Client commands and server responses + * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status + * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages + * @var integer + */ + public $do_debug = self::DEBUG_OFF; + + /** + * How to handle debug output. + * Options: + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * + * @var string|callable + */ + public $Debugoutput = 'echo'; + + /** + * Whether to use VERP. + * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path + * @link http://www.postfix.org/VERP_README.html Info on VERP + * @var boolean + */ + public $do_verp = false; + + /** + * The timeout value for connection, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 + * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. + * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 + * @var integer + */ + public $Timeout = 300; + + /** + * How long to wait for commands to complete, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 + * @var integer + */ + public $Timelimit = 300; + + /** + * The socket for the server connection. + * @var resource + */ + protected $smtp_conn; + + /** + * Error information, if any, for the last SMTP command. + * @var array + */ + protected $error = array( + 'error' => '', + 'detail' => '', + 'smtp_code' => '', + 'smtp_code_ex' => '' + ); + + /** + * The reply the server sent to us for HELO. + * If null, no HELO string has yet been received. + * @var string|null + */ + protected $helo_rply = null; + + /** + * The set of SMTP extensions sent in reply to EHLO command. + * Indexes of the array are extension names. + * Value at index 'HELO' or 'EHLO' (according to command that was sent) + * represents the server name. In case of HELO it is the only element of the array. + * Other values can be boolean TRUE or an array containing extension options. + * If null, no HELO/EHLO string has yet been received. + * @var array|null + */ + protected $server_caps = null; + + /** + * The most recent reply received from the server. + * @var string + */ + protected $last_reply = ''; + + /** + * Connect to an SMTP server. + * + * @param string $host SMTP server IP or host name + * @param integer $port The port number to connect to + * @param integer $timeout How long to wait for the connection to open + * @param array $options An array of options for stream_context_create() + * + * @access public + * @return boolean + */ + public function connect( $host, $port = null, $timeout = 30, $options = array() ) { + static $streamok; + //This is enabled by default since 5.0.0 but some providers disable it + //Check this once and cache the result + if ( is_null( $streamok ) ) { + $streamok = function_exists( 'stream_socket_client' ); + } + // Clear errors to avoid confusion + $this->setError( '' ); + // Make sure we are __not__ connected + if ( $this->connected() ) { + // Already connected, generate error + $this->setError( 'Already connected to a server' ); + + return false; + } + if ( empty( $port ) ) { + $port = self::DEFAULT_SMTP_PORT; + } + // Connect to the SMTP server + $this->edebug( + "Connection: opening to $host:$port, timeout=$timeout, options=" . var_export( $options, true ), + self::DEBUG_CONNECTION + ); + $errno = 0; + $errstr = ''; + if ( $streamok ) { + $socket_context = stream_context_create( $options ); + //Suppress errors; connection failures are handled at a higher level + $this->smtp_conn = @stream_socket_client( + $host . ":" . $port, + $errno, + $errstr, + $timeout, + STREAM_CLIENT_CONNECT, + $socket_context + ); + } else { + //Fall back to fsockopen which should work in more places, but is missing some features + $this->edebug( + "Connection: stream_socket_client not available, falling back to fsockopen", + self::DEBUG_CONNECTION + ); + $this->smtp_conn = fsockopen( + $host, + $port, + $errno, + $errstr, + $timeout + ); + } + // Verify we connected properly + if ( ! is_resource( $this->smtp_conn ) ) { + $this->setError( + 'Failed to connect to server', + $errno, + $errstr + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] + . ": $errstr ($errno)", + self::DEBUG_CLIENT + ); + + return false; + } + $this->edebug( 'Connection: opened', self::DEBUG_CONNECTION ); + // SMTP server can take longer to respond, give longer timeout for first read + // Windows does not have support for this timeout function + if ( substr( PHP_OS, 0, 3 ) != 'WIN' ) { + $max = ini_get( 'max_execution_time' ); + // Don't bother if unlimited + if ( $max != 0 && $timeout > $max ) { + @set_time_limit( $timeout ); + } + stream_set_timeout( $this->smtp_conn, $timeout, 0 ); + } + // Get any announcement + $announce = $this->get_lines(); + $this->edebug( 'SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER ); + + return true; + } + + /** + * Check connection state. + * @access public + * @return boolean True if connected. + */ + public function connected() { + if ( is_resource( $this->smtp_conn ) ) { + $sock_status = stream_get_meta_data( $this->smtp_conn ); + if ( $sock_status['eof'] ) { + // The socket is valid but we are not connected + $this->edebug( + 'SMTP NOTICE: EOF caught while checking if connected', + self::DEBUG_CLIENT + ); + $this->close(); + + return false; + } + + return true; // everything looks good + } + + return false; + } + + /** + * Output debugging info via a user-selected method. + * @see SMTP::$Debugoutput + * @see SMTP::$do_debug + * + * @param string $str Debug string to output + * @param integer $level The debug level of this message; see DEBUG_* constants + * + * @return void + */ + protected function edebug( $str, $level = 0 ) { + if ( $level > $this->do_debug ) { + return; + } + //Avoid clash with built-in function names + if ( ! in_array( $this->Debugoutput, array( + 'error_log', + 'html', + 'echo' + ) ) and is_callable( $this->Debugoutput ) ) { + call_user_func( $this->Debugoutput, $str, $this->do_debug ); + + return; + } + switch ( $this->Debugoutput ) { + case 'error_log': + //Don't output, just log + error_log( $str ); + break; + case 'html': + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace( '/[\r\n]+/', '', $str ), + ENT_QUOTES, + 'UTF-8' + ) + . "
\n"; + break; + case 'echo': + default: + //Normalize line breaks + $str = preg_replace( '/(\r\n|\r|\n)/ms', "\n", $str ); + echo gmdate( 'Y-m-d H:i:s' ) . "\t" . str_replace( + "\n", + "\n \t ", + trim( $str ) + ) . "\n"; + } + } + + /** + * Close the socket and clean up the state of the class. + * Don't use this function without first trying to use QUIT. + * @see quit() + * @access public + * @return void + */ + public function close() { + $this->setError( '' ); + $this->server_caps = null; + $this->helo_rply = null; + if ( is_resource( $this->smtp_conn ) ) { + // close the connection and cleanup + fclose( $this->smtp_conn ); + $this->smtp_conn = null; //Makes for cleaner serialization + $this->edebug( 'Connection: closed', self::DEBUG_CONNECTION ); + } + } + + /** + * Read the SMTP server's response. + * Either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @access protected + * @return string + */ + protected function get_lines() { + // If the connection is bad, give up straight away + if ( ! is_resource( $this->smtp_conn ) ) { + return ''; + } + $data = ''; + $endtime = 0; + stream_set_timeout( $this->smtp_conn, $this->Timeout ); + if ( $this->Timelimit > 0 ) { + $endtime = time() + $this->Timelimit; + } + while ( is_resource( $this->smtp_conn ) && ! feof( $this->smtp_conn ) ) { + $str = @fgets( $this->smtp_conn, 515 ); + $this->edebug( "SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL ); + $this->edebug( "SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL ); + $data .= $str; + // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen + if ( ( isset( $str[3] ) and $str[3] == ' ' ) ) { + break; + } + // Timed-out? Log and break + $info = stream_get_meta_data( $this->smtp_conn ); + if ( $info['timed_out'] ) { + $this->edebug( + 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + // Now check if reads took too long + if ( $endtime and time() > $endtime ) { + $this->edebug( + 'SMTP -> get_lines(): timelimit reached (' . + $this->Timelimit . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } + } + + return $data; + } + + /** + * Initiate a TLS (encrypted) session. + * @access public + * @return boolean + */ + public function startTLS() { + if ( ! $this->sendCommand( 'STARTTLS', 'STARTTLS', 220 ) ) { + return false; + } + // Begin encrypted connection + if ( ! stream_socket_enable_crypto( + $this->smtp_conn, + true, + STREAM_CRYPTO_METHOD_TLS_CLIENT + ) ) { + return false; + } + + return true; + } + + /** + * Send a command to an SMTP server and check its return code. + * + * @param string $command The command name - not sent to the server + * @param string $commandstring The actual command to send + * @param integer|array $expect One or more expected integer success codes + * + * @access protected + * @return boolean True on success. + */ + protected function sendCommand( $command, $commandstring, $expect ) { + if ( ! $this->connected() ) { + $this->setError( "Called $command without being connected" ); + + return false; + } + //Reject line breaks in all commands + if ( strpos( $commandstring, "\n" ) !== false or strpos( $commandstring, "\r" ) !== false ) { + $this->setError( "Command '$command' contained line breaks" ); + + return false; + } + $this->client_send( $commandstring . self::CRLF ); + + $this->last_reply = $this->get_lines(); + // Fetch SMTP code and possible error code explanation + $matches = array(); + if ( preg_match( "/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches ) ) { + $code = $matches[1]; + $code_ex = ( count( $matches ) > 2 ? $matches[2] : null ); + // Cut off error code from each response line + $detail = preg_replace( + "/{$code}[ -]" . ( $code_ex ? str_replace( '.', '\\.', $code_ex ) . ' ' : '' ) . "/m", + '', + $this->last_reply + ); + } else { + // Fall back to simple parsing if regex fails + $code = substr( $this->last_reply, 0, 3 ); + $code_ex = null; + $detail = substr( $this->last_reply, 4 ); + } + + $this->edebug( 'SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER ); + + if ( ! in_array( $code, (array) $expect ) ) { + $this->setError( + "$command command failed", + $detail, + $code, + $code_ex + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, + self::DEBUG_CLIENT + ); + + return false; + } + + $this->setError( '' ); + + return true; + } + + /** + * Send raw data to the server. + * + * @param string $data The data to send + * + * @access public + * @return integer|boolean The number of bytes sent to the server or false on error + */ + public function client_send( $data ) { + $this->edebug( "CLIENT -> SERVER: $data", self::DEBUG_CLIENT ); + + return fwrite( $this->smtp_conn, $data ); + } + + /** + * Perform SMTP authentication. + * Must be run after hello(). + * @see hello() + * + * @param string $username The user name + * @param string $password The password + * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2) + * @param string $realm The auth realm for NTLM + * @param string $workstation The auth workstation for NTLM + * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) + * + * @return bool True if successfully authenticated.* @access public + */ + public function authenticate( + $username, + $password, + $authtype = null, + $realm = '', + $workstation = '', + $OAuth = null + ) { + if ( ! $this->server_caps ) { + $this->setError( 'Authentication is not allowed before HELO/EHLO' ); + + return false; + } + + if ( array_key_exists( 'EHLO', $this->server_caps ) ) { + // SMTP extensions are available. Let's try to find a proper authentication method + + if ( ! array_key_exists( 'AUTH', $this->server_caps ) ) { + $this->setError( 'Authentication is not allowed at this stage' ); + // 'at this stage' means that auth may be allowed after the stage changes + // e.g. after STARTTLS + return false; + } + + self::edebug( 'Auth method requested: ' . ( $authtype ? $authtype : 'UNKNOWN' ), self::DEBUG_LOWLEVEL ); + self::edebug( + 'Auth methods available on the server: ' . implode( ',', $this->server_caps['AUTH'] ), + self::DEBUG_LOWLEVEL + ); + + if ( empty( $authtype ) ) { + foreach ( array( 'LOGIN', 'CRAM-MD5', 'NTLM', 'PLAIN', 'XOAUTH2' ) as $method ) { + if ( in_array( $method, $this->server_caps['AUTH'] ) ) { + $authtype = $method; + break; + } + } + if ( empty( $authtype ) ) { + $this->setError( 'No supported authentication methods found' ); + + return false; + } + self::edebug( 'Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL ); + } + + if ( ! in_array( $authtype, $this->server_caps['AUTH'] ) ) { + $this->setError( "The requested authentication method \"$authtype\" is not supported by the server" ); + + return false; + } + } elseif ( empty( $authtype ) ) { + $authtype = 'LOGIN'; + } + switch ( $authtype ) { + case 'PLAIN': + // Start authentication + if ( ! $this->sendCommand( 'AUTH', 'AUTH PLAIN', 334 ) ) { + return false; + } + // Send encoded username and password + if ( ! $this->sendCommand( + 'User & Password', + base64_encode( "\0" . $username . "\0" . $password ), + 235 + ) + ) { + return false; + } + break; + case 'LOGIN': + // Start authentication + if ( ! $this->sendCommand( 'AUTH', 'AUTH LOGIN', 334 ) ) { + return false; + } + if ( ! $this->sendCommand( "Username", base64_encode( $username ), 334 ) ) { + return false; + } + if ( ! $this->sendCommand( "Password", base64_encode( $password ), 235 ) ) { + return false; + } + break; + case 'XOAUTH2': + //If the OAuth Instance is not set. Can be a case when PHPMailer is used + //instead of PHPMailerOAuth + if ( is_null( $OAuth ) ) { + return false; + } + $oauth = $OAuth->getOauth64(); + + // Start authentication + if ( ! $this->sendCommand( 'AUTH', 'AUTH XOAUTH2 ' . $oauth, 235 ) ) { + return false; + } + break; + case 'NTLM': + /* + * ntlm_sasl_client.php + * Bundled with Permission + * + * How to telnet in windows: + * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx + * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication + */ + require_once 'extras/ntlm_sasl_client.php'; + $temp = new stdClass; + $ntlm_client = new ntlm_sasl_client_class; + //Check that functions are available + if ( ! $ntlm_client->Initialize( $temp ) ) { + $this->setError( $temp->error ); + $this->edebug( + 'You need to enable some modules in your php.ini file: ' + . $this->error['error'], + self::DEBUG_CLIENT + ); + + return false; + } + //msg1 + $msg1 = $ntlm_client->TypeMsg1( $realm, $workstation ); //msg1 + + if ( ! $this->sendCommand( + 'AUTH NTLM', + 'AUTH NTLM ' . base64_encode( $msg1 ), + 334 + ) + ) { + return false; + } + //Though 0 based, there is a white space after the 3 digit number + //msg2 + $challenge = substr( $this->last_reply, 3 ); + $challenge = base64_decode( $challenge ); + $ntlm_res = $ntlm_client->NTLMResponse( + substr( $challenge, 24, 8 ), + $password + ); + //msg3 + $msg3 = $ntlm_client->TypeMsg3( + $ntlm_res, + $username, + $realm, + $workstation + ); + + // send encoded username + return $this->sendCommand( 'Username', base64_encode( $msg3 ), 235 ); + case 'CRAM-MD5': + // Start authentication + if ( ! $this->sendCommand( 'AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334 ) ) { + return false; + } + // Get the challenge + $challenge = base64_decode( substr( $this->last_reply, 4 ) ); + + // Build the response + $response = $username . ' ' . $this->hmac( $challenge, $password ); + + // send encoded credentials + return $this->sendCommand( 'Username', base64_encode( $response ), 235 ); + default: + $this->setError( "Authentication method \"$authtype\" is not supported" ); + + return false; + } + + return true; + } + + /** + * Calculate an MD5 HMAC hash. + * Works like hash_hmac('md5', $data, $key) + * in case that function is not available + * + * @param string $data The data to hash + * @param string $key The key to hash with + * + * @access protected + * @return string + */ + protected function hmac( $data, $key ) { + if ( function_exists( 'hash_hmac' ) ) { + return hash_hmac( 'md5', $data, $key ); + } + + // The following borrowed from + // http://php.net/manual/en/function.mhash.php#27225 + + // RFC 2104 HMAC implementation for php. + // Creates an md5 HMAC. + // Eliminates the need to install mhash to compute a HMAC + // by Lance Rushing + + $bytelen = 64; // byte length for md5 + if ( strlen( $key ) > $bytelen ) { + $key = pack( 'H*', md5( $key ) ); + } + $key = str_pad( $key, $bytelen, chr( 0x00 ) ); + $ipad = str_pad( '', $bytelen, chr( 0x36 ) ); + $opad = str_pad( '', $bytelen, chr( 0x5c ) ); + $k_ipad = $key ^ $ipad; + $k_opad = $key ^ $opad; + + return md5( $k_opad . pack( 'H*', md5( $k_ipad . $data ) ) ); + } + + /** + * Send an SMTP DATA command. + * Issues a data command and sends the msg_data to the server, + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being separated by and additional . + * Implements rfc 821: DATA + * + * @param string $msg_data Message data to send + * + * @access public + * @return boolean + */ + public function data( $msg_data ) { + //This will use the standard timelimit + if ( ! $this->sendCommand( 'DATA', 'DATA', 354 ) ) { + return false; + } + + /* The server is ready to accept data! + * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) + * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into + * smaller lines to fit within the limit. + * We will also look for lines that start with a '.' and prepend an additional '.'. + * NOTE: this does not count towards line-length limit. + */ + + // Normalize line breaks before exploding + $lines = explode( "\n", str_replace( array( "\r\n", "\r" ), "\n", $msg_data ) ); + + /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field + * of the first line (':' separated) does not contain a space then it _should_ be a header and we will + * process all lines before a blank line as headers. + */ + + $field = substr( $lines[0], 0, strpos( $lines[0], ':' ) ); + $in_headers = false; + if ( ! empty( $field ) && strpos( $field, ' ' ) === false ) { + $in_headers = true; + } + + foreach ( $lines as $line ) { + $lines_out = array(); + if ( $in_headers and $line == '' ) { + $in_headers = false; + } + //Break this line up into several smaller lines if it's too long + //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), + while ( isset( $line[ self::MAX_LINE_LENGTH ] ) ) { + //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on + //so as to avoid breaking in the middle of a word + $pos = strrpos( substr( $line, 0, self::MAX_LINE_LENGTH ), ' ' ); + //Deliberately matches both false and 0 + if ( ! $pos ) { + //No nice break found, add a hard break + $pos = self::MAX_LINE_LENGTH - 1; + $lines_out[] = substr( $line, 0, $pos ); + $line = substr( $line, $pos ); + } else { + //Break at the found point + $lines_out[] = substr( $line, 0, $pos ); + //Move along by the amount we dealt with + $line = substr( $line, $pos + 1 ); + } + //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 + if ( $in_headers ) { + $line = "\t" . $line; + } + } + $lines_out[] = $line; + + //Send the lines to the server + foreach ( $lines_out as $line_out ) { + //RFC2821 section 4.5.2 + if ( ! empty( $line_out ) and $line_out[0] == '.' ) { + $line_out = '.' . $line_out; + } + $this->client_send( $line_out . self::CRLF ); + } + } + + //Message data has been sent, complete the command + //Increase timelimit for end of DATA command + $savetimelimit = $this->Timelimit; + $this->Timelimit = $this->Timelimit * 2; + $result = $this->sendCommand( 'DATA END', '.', 250 ); + //Restore timelimit + $this->Timelimit = $savetimelimit; + + return $result; + } + + /** + * Send an SMTP HELO or EHLO command. + * Used to identify the sending server to the receiving server. + * This makes sure that client and server are in a known state. + * Implements RFC 821: HELO + * and RFC 2821 EHLO. + * + * @param string $host The host name or IP to connect to + * + * @access public + * @return boolean + */ + public function hello( $host = '' ) { + //Try extended hello first (RFC 2821) + return (boolean) ( $this->sendHello( 'EHLO', $host ) or $this->sendHello( 'HELO', $host ) ); + } + + /** + * Send an SMTP HELO or EHLO command. + * Low-level implementation used by hello() + * @see hello() + * + * @param string $hello The HELO string + * @param string $host The hostname to say we are + * + * @access protected + * @return boolean + */ + protected function sendHello( $hello, $host ) { + $noerror = $this->sendCommand( $hello, $hello . ' ' . $host, 250 ); + $this->helo_rply = $this->last_reply; + if ( $noerror ) { + $this->parseHelloFields( $hello ); + } else { + $this->server_caps = null; + } + + return $noerror; + } + + /** + * Parse a reply to HELO/EHLO command to discover server extensions. + * In case of HELO, the only parameter that can be discovered is a server name. + * @access protected + * + * @param string $type - 'HELO' or 'EHLO' + */ + protected function parseHelloFields( $type ) { + $this->server_caps = array(); + $lines = explode( "\n", $this->last_reply ); + + foreach ( $lines as $n => $s ) { + //First 4 chars contain response code followed by - or space + $s = trim( substr( $s, 4 ) ); + if ( empty( $s ) ) { + continue; + } + $fields = explode( ' ', $s ); + if ( ! empty( $fields ) ) { + if ( ! $n ) { + $name = $type; + $fields = $fields[0]; + } else { + $name = array_shift( $fields ); + switch ( $name ) { + case 'SIZE': + $fields = ( $fields ? $fields[0] : 0 ); + break; + case 'AUTH': + if ( ! is_array( $fields ) ) { + $fields = array(); + } + break; + default: + $fields = true; + } + } + $this->server_caps[ $name ] = $fields; + } + } + } + + /** + * Send an SMTP MAIL command. + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. + * Implements rfc 821: MAIL FROM: + * + * @param string $from Source address of this message + * + * @access public + * @return boolean + */ + public function mail( $from ) { + $useVerp = ( $this->do_verp ? ' XVERP' : '' ); + + return $this->sendCommand( + 'MAIL FROM', + 'MAIL FROM:<' . $from . '>' . $useVerp, + 250 + ); + } + + /** + * Send an SMTP QUIT command. + * Closes the socket if there is no error or the $close_on_error argument is true. + * Implements from rfc 821: QUIT + * + * @param boolean $close_on_error Should the connection close if an error occurs? + * + * @access public + * @return boolean + */ + public function quit( $close_on_error = true ) { + $noerror = $this->sendCommand( 'QUIT', 'QUIT', 221 ); + $err = $this->error; //Save any error + if ( $noerror or $close_on_error ) { + $this->close(); + $this->error = $err; //Restore any error from the quit command + } + + return $noerror; + } + + /** + * Send an SMTP RCPT command. + * Sets the TO argument to $toaddr. + * Returns true if the recipient was accepted false if it was rejected. + * Implements from rfc 821: RCPT TO: + * + * @param string $address The address the message is being sent to + * + * @access public + * @return boolean + */ + public function recipient( $address ) { + return $this->sendCommand( + 'RCPT TO', + 'RCPT TO:<' . $address . '>', + array( 250, 251 ) + ); + } + + /** + * Send an SMTP RSET command. + * Abort any transaction that is currently in progress. + * Implements rfc 821: RSET + * @access public + * @return boolean True on success. + */ + public function reset() { + return $this->sendCommand( 'RSET', 'RSET', 250 ); + } + + /** + * Send an SMTP SAML command. + * Starts a mail transaction from the email address specified in $from. + * Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * Implements rfc 821: SAML FROM: + * + * @param string $from The address the message is from + * + * @access public + * @return boolean + */ + public function sendAndMail( $from ) { + return $this->sendCommand( 'SAML', "SAML FROM:$from", 250 ); + } + + /** + * Send an SMTP VRFY command. + * + * @param string $name The name to verify + * + * @access public + * @return boolean + */ + public function verify( $name ) { + return $this->sendCommand( 'VRFY', "VRFY $name", array( 250, 251 ) ); + } + + /** + * Send an SMTP NOOP command. + * Used to keep keep-alives alive, doesn't actually do anything + * @access public + * @return boolean + */ + public function noop() { + return $this->sendCommand( 'NOOP', 'NOOP', 250 ); + } + + /** + * Send an SMTP TURN command. + * This is an optional command for SMTP that this class does not support. + * This method is here to make the RFC821 Definition complete for this class + * and _may_ be implemented in future + * Implements from rfc 821: TURN + * @access public + * @return boolean + */ + public function turn() { + $this->setError( 'The SMTP TURN command is not implemented' ); + $this->edebug( 'SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT ); + + return false; + } + + /** + * Get the latest error. + * @access public + * @return array + */ + public function getError() { + return $this->error; + } + + /** + * Set error messages and codes. + * + * @param string $message The error message + * @param string $detail Further detail on the error + * @param string $smtp_code An associated SMTP error code + * @param string $smtp_code_ex Extended SMTP code + */ + protected function setError( $message, $detail = '', $smtp_code = '', $smtp_code_ex = '' ) { + $this->error = array( + 'error' => $message, + 'detail' => $detail, + 'smtp_code' => $smtp_code, + 'smtp_code_ex' => $smtp_code_ex + ); + } + + /** + * Get SMTP extensions available on the server + * @access public + * @return array|null + */ + public function getServerExtList() { + return $this->server_caps; + } + + /** + * A multipurpose method + * The method works in three ways, dependent on argument value and current state + * 1. HELO/EHLO was not sent - returns null and set up $this->error + * 2. HELO was sent + * $name = 'HELO': returns server name + * $name = 'EHLO': returns boolean false + * $name = any string: returns null and set up $this->error + * 3. EHLO was sent + * $name = 'HELO'|'EHLO': returns server name + * $name = any string: if extension $name exists, returns boolean True + * or its options. Otherwise returns boolean False + * In other words, one can use this method to detect 3 conditions: + * - null returned: handshake was not or we don't know about ext (refer to $this->error) + * - false returned: the requested feature exactly not exists + * - positive value returned: the requested feature exists + * + * @param string $name Name of SMTP extension or 'HELO'|'EHLO' + * + * @return mixed + */ + public function getServerExt( $name ) { + if ( ! $this->server_caps ) { + $this->setError( 'No HELO/EHLO was sent' ); + + return null; + } + + // the tight logic knot ;) + if ( ! array_key_exists( $name, $this->server_caps ) ) { + if ( $name == 'HELO' ) { + return $this->server_caps['EHLO']; + } + if ( $name == 'EHLO' || array_key_exists( 'EHLO', $this->server_caps ) ) { + return false; + } + $this->setError( 'HELO handshake was used. Client knows nothing about server extensions' ); + + return null; + } + + return $this->server_caps[ $name ]; + } + + /** + * Get the last reply from the server. + * @access public + * @return string + */ + public function getLastReply() { + return $this->last_reply; + } + + /** + * Enable or disable VERP address generation. + * + * @param boolean $enabled + */ + public function setVerp( $enabled = false ) { + $this->do_verp = $enabled; + } + + /** + * Get VERP address generation mode. + * @return boolean + */ + public function getVerp() { + return $this->do_verp; + } + + /** + * Get debug output method. + * @return string + */ + public function getDebugOutput() { + return $this->Debugoutput; + } + + /** + * Set debug output method. + * + * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. + */ + public function setDebugOutput( $method = 'echo' ) { + $this->Debugoutput = $method; + } + + /** + * Set debug output level. + * + * @param integer $level + */ + public function setDebugLevel( $level = 0 ) { + $this->do_debug = $level; + } + + /** + * Get debug output level. + * @return integer + */ + public function getDebugLevel() { + return $this->do_debug; + } + + /** + * Get SMTP timeout. + * @return integer + */ + public function getTimeout() { + return $this->Timeout; + } + + /** + * Set SMTP timeout. + * + * @param integer $timeout + */ + public function setTimeout( $timeout = 0 ) { + $this->Timeout = $timeout; + } } diff --git a/src/lib/mail/mail_default.html b/src/lib/mail/mail_default.html index 5ae8cb1..1949812 100644 --- a/src/lib/mail/mail_default.html +++ b/src/lib/mail/mail_default.html @@ -4,7 +4,8 @@ -
+