diff --git a/.htaccess b/.htaccess index 55341e9..013cda8 100644 --- a/.htaccess +++ b/.htaccess @@ -10,12 +10,15 @@ RewriteRule ^ - [L] # RewriteCond %{HTTP_HOST} ^eldotravo.fr$ # RewriteRule ^(.*)$ http://www.eldotravo.fr/$1 [R=301,L,E=END:1] -# Redirection des fichiers static pour contact.sanchez-mathieu.dev -RewriteCond %{HTTP_HOST} ^contact.sanchez-mathieu.dev$ +# Redirection des fichiers static pour contact.sanchez-mathieu.test +RewriteCond %{HTTP_HOST} ^contact.sanchez-mathieu.test RewriteCond %{DOCUMENT_ROOT}/static%{REQUEST_URI} -f RewriteRule ^(.*)$ static/$1 [L,E=END:1] -# RewriteRule ^haute-garonne/toulouse/installation-entretien-climatisation /haute-garonne/toulouse/climatisation.php [L] +# Redirection des fichiers static pour contact.sanchez-mathieu.fr +RewriteCond %{HTTP_HOST} ^contact.sanchez-mathieu.fr +RewriteCond %{DOCUMENT_ROOT}/static%{REQUEST_URI} -f +RewriteRule ^(.*)$ static/$1 [L,E=END:1] # Redirection de toutes les requêtes vers Index.php RewriteRule ^ index.php [L,E=END:1] \ No newline at end of file diff --git a/index.php b/index.php index fd92929..f87b67c 100644 --- a/index.php +++ b/index.php @@ -35,7 +35,7 @@ if ( count( $pages ) > 1 && $pages[ count( $pages ) - 1 ] == '' ) { 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'] ) ) { +} else if ( preg_match( '#^contact.sanchez-mathieu\.test$#', $_SERVER[ 'SERVER_NAME' ] ) || preg_match( '#^contact.sanchez-mathieu\.fr#', $_SERVER[ 'SERVER_NAME' ] ) ) { new SiteRouter( $pages ); } else { new Error( 404 ); diff --git a/src/API/API.php b/src/API/API.php index 7f03283..69361d8 100644 --- a/src/API/API.php +++ b/src/API/API.php @@ -6,24 +6,24 @@ use CAUProject3Contact\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/APIContact.php b/src/API/APIContact.php new file mode 100644 index 0000000..4722bbd --- /dev/null +++ b/src/API/APIContact.php @@ -0,0 +1,201 @@ + [ + 'method' => 'POST', + 'params' => [ + 'firstName' => [ + 'required' => true, + 'type' => 'string' + ], + 'lastName' => [ + 'required' => true, + 'type' => 'string' + ], + 'surname' => [ + 'required' => false, + 'type' => 'string' + ], + 'email' => [ + 'required' => false, + 'type' => 'string' + ], + 'address' => [ + 'required' => false, + 'type' => 'string' + ], + 'phoneNumber' => [ + 'required' => false, + 'type' => 'string' + ], + 'birthday' => [ + 'required' => false, + 'type' => 'string' + ], + ] + ], + 'delete' => [ + 'method' => 'POST', + 'params' => [ + 'id' => [ + 'required' => true, + 'type' => 'int' + ] + ] + ], + 'get-contacts' => [ + 'method' => 'GET', + 'params' => [] + ], + 'update' => [ + 'method' => 'POST', + 'params' => [ + 'id' => [ + 'required' => true, + 'type' => 'int' + ], + 'firstName' => [ + 'required' => false, + 'type' => 'string' + ], + 'lastName' => [ + 'required' => false, + 'type' => 'string' + ], + 'surname' => [ + 'required' => false, + 'type' => 'string' + ], + 'email' => [ + 'required' => false, + 'type' => 'string' + ], + 'address' => [ + 'required' => false, + 'type' => 'string' + ], + 'phoneNumber' => [ + 'required' => false, + 'type' => 'string' + ], + 'birthday' => [ + 'required' => false, + 'type' => 'string' + ] + ] + ], + 'search' => [ + 'method' => 'POST', + 'params' => [ + 'query' => [ + 'required' => true, + 'type' => 'string' + ] + ] + ] + ]; + + /** + * APIContact constructor. + * + * @param array $declaredFunctions + */ + public function __construct() { + parent::__construct( $this->declaredFunctions ); + } + + /** + * @return array + */ + public function getDeclaredFunctions() { + return $this->declaredFunctions; + } + + public function insert( array $data ) { + + $id = Contact::insertNewContact( $data[ "firstName" ], $data[ "lastName" ], $data[ "surname" ], + $data[ "email" ], $data[ "address" ], $data[ "phoneNumber" ], $data[ "birthday" ] ); + + $this->returnJson( json_encode( [ + "status" => "success", + "data" => [ + "id" => $id + ] + ] ) ); + } + + public function delete( array $data ) { + Contact::deleteContact( $data[ "id" ] ); + } + + public function getContacts() { + $this->returnJson( json_encode( [ + "contacts" => Contact::getAllContact() + ] ) ); + } + + public function update( array $data ) { + $contact = Contact::getById( $data[ "id" ] ); + + $newData = []; + + if ( $data[ "firstName" ] !== null && $data[ "firstName" ] !== "" ) { + $newData[ "first_name" ] = $data[ "firstName" ]; + } + if ( $data[ "lastName" ] !== null && $data[ "lastName" ] !== "" ) { + $newData[ "last_name" ] = $data[ "lastName" ]; + } + if ( $data[ "surname" ] !== null && $data[ "surname" ] !== "" ) { + $newData[ "surname" ] = $data[ "surname" ]; + } + if ( $data[ "email" ] !== null && $data[ "email" ] !== "" ) { + $newData[ "email" ] = $data[ "email" ]; + } + if ( $data[ "address" ] !== null && $data[ "address" ] !== "" ) { + $newData[ "address" ] = $data[ "address" ]; + } + if ( $data[ "phoneNumber" ] !== null && $data[ "phoneNumber" ] !== "" ) { + $newData[ "phone_number" ] = $data[ "phoneNumber" ]; + } + if ( $data[ "birthday" ] !== null && $data[ "birthday" ] !== "" ) { + $newData[ "birthday" ] = date( "Y-m-d", strtotime( $data[ "birthday" ] ) ); + } + + $contact->updateContact( $newData ); + $this->returnJson( json_encode( $contact ) ); + } + + public function search( array $data ) { + if ( count_chars( $data[ "query" ] ) >= 3 ) { + $result = Contact::search( $data[ "query" ] ); + if ( $result !== null ) { + $this->returnJson( [ + "status" => "success", + "code" => 200, + "result" => $result, + ] ); + } else { + $this->returnJson( [ + "status" => "error", + "code" => 404, + "message" => "Nothing find", + ] ); + } + } else { + $this->returnJson( [ + "status" => "error", + "code" => 400, + "message" => "Need at least 3 chars", + ] ); + } + } + +} + +?> \ No newline at end of file diff --git a/src/API/APIError.php b/src/API/APIError.php index fbd88b1..891de31 100644 --- a/src/API/APIError.php +++ b/src/API/APIError.php @@ -6,87 +6,88 @@ use CAUProject3Contact\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 375dfb5..9b443cd 100644 --- a/src/API/APIRouter.php +++ b/src/API/APIRouter.php @@ -6,127 +6,127 @@ use CAUProject3Contact\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 659e755..4be7172 100644 --- a/src/Autoloader.php +++ b/src/Autoloader.php @@ -4,21 +4,21 @@ namespace CAUProject3Contact; 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 82430bc..a7137e7 100644 --- a/src/Config.php +++ b/src/Config.php @@ -3,12 +3,12 @@ namespace CAUProject3Contact; class Config { - const SITE_JS_VERSION = '1.00'; - const SITE_CSS_VERSION = '1.00'; + const SITE_JS_VERSION = '0.01'; + const SITE_CSS_VERSION = '0.01'; - const TITLE_HEADER = 'Your contact'; - const DESCRIPTION_HEADER = 'Manage your contact easly'; - const NAMESPACE = 'CAUProject3Contact'; + const TITLE_HEADER = 'Your contact'; + const DESCRIPTION_HEADER = 'Manage your contact easly'; + const NAMESPACE = 'CAUProject3Contact'; - const FAVICON_PATH = '/img/favicon.png'; + const FAVICON_PATH = '/img/favicon.ico'; } \ No newline at end of file diff --git a/src/Controller/Controller.php b/src/Controller/Controller.php index 0c7d2b6..c1a7984 100644 --- a/src/Controller/Controller.php +++ b/src/Controller/Controller.php @@ -6,35 +6,35 @@ use CAUProject3Contact\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 1a3f798..be653ce 100644 --- a/src/Controller/ControllerSite.php +++ b/src/Controller/ControllerSite.php @@ -6,67 +6,67 @@ use CAUProject3Contact\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 2ed3cbd..089d27a 100644 --- a/src/Controller/Error.php +++ b/src/Controller/Error.php @@ -4,96 +4,96 @@ namespace CAUProject3Contact\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 f56c0ff..5dd9ecd 100644 --- a/src/Controller/site/Index.php +++ b/src/Controller/site/Index.php @@ -3,25 +3,27 @@ namespace CAUProject3Contact\Controller\Site; use CAUProject3Contact\Controller\ControllerSite; +use CAUProject3Contact\Model\Contact; 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( [] ); - ] ); + $contacts = Contact::getAllContact(); - $this->addData( [] ); - $this->view(); - } + $this->addData( [ + "contacts" => $contacts + ] ); + $this->view(); + } } ?> \ No newline at end of file diff --git a/src/Controller/site/SiteError.php b/src/Controller/site/SiteError.php index b1b8130..7f53931 100644 --- a/src/Controller/site/SiteError.php +++ b/src/Controller/site/SiteError.php @@ -6,101 +6,101 @@ use CAUProject3Contact\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 f10df0b..8b1836a 100644 --- a/src/Controller/site/SiteRouter.php +++ b/src/Controller/site/SiteRouter.php @@ -4,21 +4,21 @@ namespace CAUProject3Contact\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 532830a..27fa205 100644 --- a/src/Model/BDD.php +++ b/src/Model/BDD.php @@ -6,35 +6,43 @@ use Exception; use PDO; class BDD { - const SQL_SERVER = 'sql.sanchez-mathieu.fr'; // BDD Server - const SQL_LOGIN = 'why7n0_contact'; // BDD Login - const SQL_PASSWORD = 'fC3c87Gy'; // BDD Password - const SQL_DB = 'why7n0_contact'; // BDD Name - private static $bdd; + // Server BDD +// const SQL_SERVER = 'sql.sanchez-mathieu.fr'; // BDD Server +// const SQL_LOGIN = 'why7n0_contact'; // BDD Login +// const SQL_PASSWORD = 'fC3c87Gy'; // BDD Password +// const SQL_DB = 'why7n0_contact'; // BDD Name - 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 - ]; + // Local BDD + const SQL_SERVER = 'localhost'; // BDD Server + const SQL_LOGIN = 'root'; // BDD Login + const SQL_PASSWORD = ''; // BDD Password + const SQL_DB = 'contact'; // BDD Name - 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() ); - } - } + private static $bdd; - public static function instance() { - return self::$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 static function lastInsertId() { - return self::$bdd->lastInsertId(); - } + 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 lastInsertId() { + return self::$bdd->lastInsertId(); + } } ?> \ No newline at end of file diff --git a/src/Model/BDTables.php b/src/Model/BDTables.php index aa46dc4..c3f66e8 100644 --- a/src/Model/BDTables.php +++ b/src/Model/BDTables.php @@ -4,10 +4,11 @@ namespace CAUProject3Contact\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"; + const CONTACT = "contact"; } ?> \ No newline at end of file diff --git a/src/Model/Contact.php b/src/Model/Contact.php new file mode 100644 index 0000000..3bffba0 --- /dev/null +++ b/src/Model/Contact.php @@ -0,0 +1,220 @@ +id = $id; + $this->firstName = $firstName; + $this->lastName = $lastName; + $this->surname = $surname; + $this->email = $email; + $this->address = $address; + $this->phoneNumber = $phoneNumber; + $this->birthday = $birthday; + } + + public static function getById( int $id ) { + $req = BDD::instance()->prepare( "SELECT * FROM " . BDTables::CONTACT . + " WHERE `id` = :id" ); + $req->execute( [ "id" => $id ] ); + $d = $req->fetch(); + return new Contact( $d[ "id" ], $d[ "first_name" ], $d[ "last_name" ], $d[ "surname" ], + $d[ "email" ], $d[ "address" ], $d[ "phone_number" ], $d[ "birthday" ] ); + } + + // Getters + + public static function insertNewContact( string $firstName, string $lastName, string $surname = null, + string $email = null, + string $address = null, string $phoneNumber = null, + string $birthday = null ) { + $data = [ + "first_name" => $firstName, + "last_name" => $lastName, + ]; + if ( $surname !== null && $surname !== "" ) { + $data[ "surname" ] = $surname; + } + if ( $email !== null && $email !== "" ) { + $data[ "email" ] = $email; + } + if ( $address !== null && $address !== "" ) { + $data[ "address" ] = $address; + } + if ( $phoneNumber !== null && $phoneNumber !== "" ) { + $data[ "phone_number" ] = $phoneNumber; + } + if ( $birthday !== null && $birthday !== "" ) { + $data[ "birthday" ] = date( "Y-m-d", strtotime( $birthday ) ); + } + return Model::insert( BDTables::CONTACT, $data ); + } + + public static function deleteContact( int $id ) { + Model::delete( BDTables::CONTACT, [ "id" => $id ] ); + } + + public static function getAllContact(): array { + $contacts = []; + $req = BDD::instance()->prepare( "SELECT * FROM " . BDTables::CONTACT ); + $req->execute(); + + foreach ( $req->fetchAll() as $c ) { + $contacts[] = new Contact( $c[ "id" ], $c[ "first_name" ], $c[ "last_name" ], $c[ "surname" ], + $c[ "email" ], $c[ "address" ], $c[ "phone_number" ], $c[ "birthday" ] ); + } + return ( count( $contacts ) > 0 ? $contacts : null ); + } + + public static function search( string $query ) { + $result = []; + $words = explode( " ", cleanString( $query ) ); + + $q1 = $q2 = $q3 = $q4 = "SELECT * FROM `" . BDTables::CONTACT . "` WHERE "; + + $lastKey = endKey( $words ); + foreach ( $words as $key => $word ) { + $normal = self::getQuerySearch( $word, [ "first_name", "last_name", "surname" ] );; + $hard = self::getQuerySearch( $word, [ "email", "address", "phone_number" ] ); + $q1 .= $normal; + $q2 .= $normal; + $q3 .= $hard; + $q4 .= $hard; + if ( $key != $lastKey ) { + $q1 .= " AND "; + $q2 .= " OR "; + $q3 .= " AND "; + $q4 .= " OR "; + } + } + + $req1 = BDD::instance()->prepare( $q1 ); + $req2 = BDD::instance()->prepare( $q2 ); + $req3 = BDD::instance()->prepare( $q3 ); + $req4 = BDD::instance()->prepare( $q4 ); + + $req1->execute(); + $req2->execute(); + $req3->execute(); + $req4->execute(); + + $tmp1 = $req1->fetchAll(); + $tmp2 = filterArrays( $tmp1, $req2->fetchAll() ); + $tmp3 = filterArrays( $tmp1, filterArrays( $tmp2, $req3->fetchAll() ) ); + $tmp4 = filterArrays( $tmp1, filterArrays( $tmp2, filterArrays( $tmp3, $req4->fetchAll() ) ) ); + + if ( count( $tmp1 ) > 0 || count( $tmp2 ) > 0 || count( $tmp3 ) > 0 || count( $tmp4 ) > 0 ) { + $result[ "1" ] = ( count( $tmp1 ) > 0 ? $tmp1 : null ); + $result[ "2" ] = ( count( $tmp2 ) > 0 ? $tmp2 : null ); + $result[ "3" ] = ( count( $tmp3 ) > 0 ? $tmp3 : null ); + $result[ "4" ] = ( count( $tmp4 ) > 0 ? $tmp4 : null ); + return $result; + } + return null; + } + + private static function getQuerySearch( string $word, array $fields ): string { + $str = ''; + $i = 0; + + foreach ( $fields as $field ) { + if ( $i === 0 ) { + $str .= "("; + } else { + $str .= " OR "; + } + $str .= "`" . $field . "` LIKE '%" . $word . "%'"; + $i++; + } + $str .= ')'; + return $str; + } + + /** + * @return string + */ + public function getFirstName(): string { + return $this->firstName; + } + + /** + * @return string + */ + public function getLastName(): string { + return $this->lastName; + } + + /** + * @return string + */ + public function getSurname() { + return $this->surname; + } + + // Method + + /** + * @return string + */ + public function getEmail() { + return $this->email; + } + + // Static functions + + /** + * @return string + */ + public function getAddress() { + return $this->address; + } + + /** + * @return string + */ + public function getPhoneNumber() { + return $this->phoneNumber; + } + + /** + * @return string + */ + public function getBirthday() { + return $this->birthday; + } + + public function updateContact( array $data ) { + foreach ( $data as $key => $value ) { + $this->{$key} = $value; + } + Model::update( BDTables::CONTACT, $data, "id", $this->getId() ); + } + + /** + * @return int + */ + public function getId(): int { + return $this->id; + } + +} + +?> \ No newline at end of file diff --git a/src/Model/FPDF.php b/src/Model/FPDF.php index 95c9a20..cef14e7 100644 --- a/src/Model/FPDF.php +++ b/src/Model/FPDF.php @@ -11,1903 +11,1903 @@ namespace CAUProject3Contact\Model; define( 'FPDF_VERSION', '1.81' ); class FPDF { - protected $page; // current page number - protected $n; // current object number - protected $offsets; // array of object offsets - protected $buffer; // buffer holding in-memory PDF - protected $pages; // array containing pages - protected $state; // current document state - protected $compress; // compression flag - protected $k; // scale factor (number of points in user unit) - protected $DefOrientation; // default orientation - protected $CurOrientation; // current orientation - protected $StdPageSizes; // standard page sizes - protected $DefPageSize; // default page size - protected $CurPageSize; // current page size - protected $CurRotation; // current page rotation - protected $PageInfo; // page-related data - protected $wPt, $hPt; // dimensions of current page in points - protected $w, $h; // dimensions of current page in user unit - protected $lMargin; // left margin - protected $tMargin; // top margin - protected $rMargin; // right margin - protected $bMargin; // page break margin - protected $cMargin; // cell margin - protected $x, $y; // current position in user unit - protected $lasth; // height of last printed cell - protected $LineWidth; // line width in user unit - protected $fontpath; // path containing fonts - protected $CoreFonts; // array of core font names - protected $fonts; // array of used fonts - protected $FontFiles; // array of font files - protected $encodings; // array of encodings - protected $cmaps; // array of ToUnicode CMaps - protected $FontFamily; // current font family - protected $FontStyle; // current font style - protected $underline; // underlining flag - protected $CurrentFont; // current font info - protected $FontSizePt; // current font size in points - protected $FontSize; // current font size in user unit - protected $DrawColor; // commands for drawing color - protected $FillColor; // commands for filling color - protected $TextColor; // commands for text color - protected $ColorFlag; // indicates whether fill and text colors are different - protected $WithAlpha; // indicates whether alpha channel is used - protected $ws; // word spacing - protected $images; // array of used images - protected $PageLinks; // array of links in pages - protected $links; // array of internal links - protected $AutoPageBreak; // automatic page breaking - protected $PageBreakTrigger; // threshold used to trigger page breaks - protected $InHeader; // flag set when processing header - protected $InFooter; // flag set when processing footer - protected $AliasNbPages; // alias for total number of pages - protected $ZoomMode; // zoom display mode - protected $LayoutMode; // layout display mode - protected $metadata; // document properties - protected $PDFVersion; // PDF version number - - /******************************************************************************* - * Public methods * - *******************************************************************************/ - - 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'; - } - - /******************************************************************************* - * 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 ); - } - } - - function Error( $msg ) { - // Fatal error - throw new Exception( 'FPDF error: ' . $msg ); - } - - 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; - } - } - } - - 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 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 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 SetTopMargin( $margin ) { - // Set top margin - $this->tMargin = $margin; - } - - function SetRightMargin( $margin ) { - // Set right margin - $this->rMargin = $margin; - } - - function SetTitle( $title, $isUTF8 = false ) { - // Title of document - $this->metadata['Title'] = $isUTF8 ? $title : utf8_encode( $title ); - } - - function SetAuthor( $author, $isUTF8 = false ) { - // Author of document - $this->metadata['Author'] = $isUTF8 ? $author : utf8_encode( $author ); - } - - function SetSubject( $subject, $isUTF8 = false ) { - // Subject of document - $this->metadata['Subject'] = $isUTF8 ? $subject : utf8_encode( $subject ); - } - - function SetKeywords( $keywords, $isUTF8 = false ) { - // Keywords of document - $this->metadata['Keywords'] = $isUTF8 ? $keywords : utf8_encode( $keywords ); - } - - function SetCreator( $creator, $isUTF8 = false ) { - // Creator of document - $this->metadata['Creator'] = $isUTF8 ? $creator : utf8_encode( $creator ); - } - - function AliasNbPages( $alias = '{nb}' ) { - // Define an alias for total number of pages - $this->AliasNbPages = $alias; - } - - 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(); - } - - 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; - } - - protected function _putheader() { - $this->_put( '%PDF-' . $this->PDFVersion ); - } - - 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 _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 _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; - } - } - // 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 ); - } - } - } - - 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 ++; - } - } - $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(); - } + protected $page; // current page number + protected $n; // current object number + protected $offsets; // array of object offsets + protected $buffer; // buffer holding in-memory PDF + protected $pages; // array containing pages + protected $state; // current document state + protected $compress; // compression flag + protected $k; // scale factor (number of points in user unit) + protected $DefOrientation; // default orientation + protected $CurOrientation; // current orientation + protected $StdPageSizes; // standard page sizes + protected $DefPageSize; // default page size + protected $CurPageSize; // current page size + protected $CurRotation; // current page rotation + protected $PageInfo; // page-related data + protected $wPt, $hPt; // dimensions of current page in points + protected $w, $h; // dimensions of current page in user unit + protected $lMargin; // left margin + protected $tMargin; // top margin + protected $rMargin; // right margin + protected $bMargin; // page break margin + protected $cMargin; // cell margin + protected $x, $y; // current position in user unit + protected $lasth; // height of last printed cell + protected $LineWidth; // line width in user unit + protected $fontpath; // path containing fonts + protected $CoreFonts; // array of core font names + protected $fonts; // array of used fonts + protected $FontFiles; // array of font files + protected $encodings; // array of encodings + protected $cmaps; // array of ToUnicode CMaps + protected $FontFamily; // current font family + protected $FontStyle; // current font style + protected $underline; // underlining flag + protected $CurrentFont; // current font info + protected $FontSizePt; // current font size in points + protected $FontSize; // current font size in user unit + protected $DrawColor; // commands for drawing color + protected $FillColor; // commands for filling color + protected $TextColor; // commands for text color + protected $ColorFlag; // indicates whether fill and text colors are different + protected $WithAlpha; // indicates whether alpha channel is used + protected $ws; // word spacing + protected $images; // array of used images + protected $PageLinks; // array of links in pages + protected $links; // array of internal links + protected $AutoPageBreak; // automatic page breaking + protected $PageBreakTrigger; // threshold used to trigger page breaks + protected $InHeader; // flag set when processing header + protected $InFooter; // flag set when processing footer + protected $AliasNbPages; // alias for total number of pages + protected $ZoomMode; // zoom display mode + protected $LayoutMode; // layout display mode + protected $metadata; // document properties + protected $PDFVersion; // PDF version number + + /******************************************************************************* + * Public methods * + *******************************************************************************/ + + 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'; + } + + /******************************************************************************* + * 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 ); + } + } + + function Error( $msg ) { + // Fatal error + throw new Exception( 'FPDF error: ' . $msg ); + } + + 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; + } + } + } + + 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 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 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 SetTopMargin( $margin ) { + // Set top margin + $this->tMargin = $margin; + } + + function SetRightMargin( $margin ) { + // Set right margin + $this->rMargin = $margin; + } + + function SetTitle( $title, $isUTF8 = false ) { + // Title of document + $this->metadata[ 'Title' ] = $isUTF8 ? $title : utf8_encode( $title ); + } + + function SetAuthor( $author, $isUTF8 = false ) { + // Author of document + $this->metadata[ 'Author' ] = $isUTF8 ? $author : utf8_encode( $author ); + } + + function SetSubject( $subject, $isUTF8 = false ) { + // Subject of document + $this->metadata[ 'Subject' ] = $isUTF8 ? $subject : utf8_encode( $subject ); + } + + function SetKeywords( $keywords, $isUTF8 = false ) { + // Keywords of document + $this->metadata[ 'Keywords' ] = $isUTF8 ? $keywords : utf8_encode( $keywords ); + } + + function SetCreator( $creator, $isUTF8 = false ) { + // Creator of document + $this->metadata[ 'Creator' ] = $isUTF8 ? $creator : utf8_encode( $creator ); + } + + function AliasNbPages( $alias = '{nb}' ) { + // Define an alias for total number of pages + $this->AliasNbPages = $alias; + } + + 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(); + } + + 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; + } + + protected function _putheader() { + $this->_put( '%PDF-' . $this->PDFVersion ); + } + + 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 _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 _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; + } + } + // 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 ); + } + } + } + + 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++; + } + } + $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 _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 _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 _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 ); + 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 - ); - } + 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 ); + 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; - } + 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 ); - } + 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; + // 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 ); - } 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 ); + // 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 ); + } 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; + 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; - } + 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' ); - } + 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; - } + return $res; + } - protected function _readint( $f ) { - // Read a 4-byte integer from stream - $a = unpack( 'Ni', $this->_readstream( $f, 4 ) ); + protected function _readint( $f ) { + // Read a 4-byte integer from stream + $a = unpack( 'Ni', $this->_readstream( $f, 4 ) ); - return $a['i']; - } + 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 ); + 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; - } + return $info; + } } ?> diff --git a/src/Model/Logs.php b/src/Model/Logs.php index 731ee89..5dc1495 100644 --- a/src/Model/Logs.php +++ b/src/Model/Logs.php @@ -6,179 +6,180 @@ use PDO; class Logs { - 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' + ]; + public $id; + public $level; + public $message; + public $file; + public $line; + public $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; - } + /** + * 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 = []; + $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 ad4937d..c7d10d7 100644 --- a/src/Model/Model.php +++ b/src/Model/Model.php @@ -4,47 +4,63 @@ namespace CAUProject3Contact\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 ) ) . ') + /** + * 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 ); + $req->execute( $data ); - return BDD::lastInsertId(); - } + return BDD::lastInsertId(); + } - /** - * 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; + /** + * 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; - //echo $reqStr; exit(); + //echo $reqStr; exit(); + + $req = BDD::instance()->prepare( $reqStr ); + $req->execute( $data ); + } + + public static function delete( string $tableName, array $conditions ) { + $reqStr = 'DELETE FROM ' . $tableName . ' WHERE '; + $lastKey = endKey( $conditions ); + + foreach ( $conditions as $key => $value ) { + $reqStr .= $key . ' = :' . $key; + if ( $key != $lastKey ) { + $reqStr .= ' AND '; + } + } + + $req = BDD::instance()->prepare( $reqStr ); + $req->execute( $conditions ); + } - $req = BDD::instance()->prepare( $reqStr ); - $req->execute( $data ); - } } ?> \ No newline at end of file diff --git a/src/View/Site/Index.php b/src/View/Site/Index.php index 0ccb255..891b5a4 100644 --- a/src/View/Site/Index.php +++ b/src/View/Site/Index.php @@ -1,3 +1,39 @@ -
- Bonjour 2 -
\ No newline at end of file +
+ +
+
+ + + + + + + + + + + + + + "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo " "; + echo ""; + } + ?> + +
First NameLast NameSurnameEmailAddressPhone NumberBirthday
" . $contact->getFirstName() . "" . $contact->getLastName() . "" . ( $contact->getSurname() ? $contact->getSurname() : "" ) . "" . ( $contact->getEmail() ? $contact->getEmail() : "" ) . "" . ( $contact->getAddress() ? $contact->getAddress() : "" ) . "" . ( $contact->getPhoneNumber() ? $contact->getPhoneNumber() : "" ) . "" . ( $contact->getBirthday() ? date( "Y-m-d", strtotime( $contact->getBirthday() ) ) : "" ) . "
+
+
+
+ + \ No newline at end of file diff --git a/src/View/Site/tpl/footer.php b/src/View/Site/tpl/footer.php index e9063ac..0fbdf1b 100644 --- a/src/View/Site/tpl/footer.php +++ b/src/View/Site/tpl/footer.php @@ -1,5 +1,32 @@ - - + + + + + + + + + + + + + diff --git a/src/View/Site/tpl/head.php b/src/View/Site/tpl/head.php index a7275a0..7a53de3 100644 --- a/src/View/Site/tpl/head.php +++ b/src/View/Site/tpl/head.php @@ -1,41 +1,66 @@ - + - - - + + + + + - + - <?= $this->head['title'] ?> - + <?= $this->head[ 'title' ] ?> + - - - - + + + + - - - - - + + - head['robotNoIndex'] ) && $this->head['robotNoIndex'] == true ) { ?> + head[ 'robotNoIndex' ] ) && $this->head[ 'robotNoIndex' ] == true ) { ?> - + + + + +
+
\ No newline at end of file diff --git a/src/lib/functions.php b/src/lib/functions.php index 8b4414e..fd84d40 100644 --- a/src/lib/functions.php +++ b/src/lib/functions.php @@ -12,36 +12,36 @@ include( 'src/lib/mail/PHPMailerAutoload.php' ); */ function formatURL( string $str, $encoding = 'utf-8' ) { - $str = str_replace( "+", "_plus_", $str ); - $str = str_replace( "%", "_pourcent_", $str ); - $str = str_replace( "&", "_et_", $str ); + $str = str_replace( "+", "_plus_", $str ); + $str = str_replace( "%", "_pourcent_", $str ); + $str = str_replace( "&", "_et_", $str ); - //on remplace les apotrophes et espaces par des underscore - $str = str_replace( array( "'", " ", "," ), "_", $str ); + //on remplace les apotrophes et espaces par des underscore + $str = str_replace( array( "'", " ", "," ), "_", $str ); - $str = str_replace( "__", "_", $str ); + $str = str_replace( "__", "_", $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 ); + // 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; } /** @@ -57,55 +57,55 @@ function formatURL( string $str, $encoding = 'utf-8' ) { */ 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; } /** @@ -119,20 +119,20 @@ function darkroom( $img, $to, $width = 0, $height = 0, $quality = 100, $useGD = * @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; - } + if ( !( error_reporting() & $errno ) ) { + // Ce code d'erreur n'est pas inclus dans error_reporting() + return; + } - // Insertion des logs - \CAUProject3Contact\Model\Logs::insert( $errno, $errstr, $errfile, $errline, date( 'Y-m-d H:i:s' ) ); + // Insertion des logs + \CAUProject3Contact\Model\Logs::insert( $errno, $errstr, $errfile, $errline, date( 'Y-m-d H:i:s' ) ); - ob_clean(); - new \CAUProject3Contact\Controller\Site\SiteError( 500 ); + ob_clean(); + new \CAUProject3Contact\Controller\Site\SiteError( 500 ); - /* Ne pas exécuter le gestionnaire interne de PHP */ + /* Ne pas exécuter le gestionnaire interne de PHP */ - return; + return; } /** @@ -141,87 +141,87 @@ 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 + ); } /** @@ -231,15 +231,15 @@ function getBrowser() { * @return int */ function getLimitWord( $string, $limit ) { - $i = $limit; - if ( ! isset( $string ) || empty( $string ) ) { - return 0; - } - while ( $i > 0 && $string[ $i ] != ' ' ) { - $i --; - } + $i = $limit; + if ( !isset( $string ) || empty( $string ) ) { + return 0; + } + while ( $i > 0 && $string[ $i ] != ' ' ) { + $i--; + } - return $i; + return $i; } /** @@ -329,38 +329,38 @@ function email(array $destinataires, string $subject, string $body, string $aute * @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 ); + // 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; + return true; } /** @@ -369,34 +369,38 @@ function rotateImage( string $file, int $angle, string $newName ) { * @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; - } - } - } +function cleanArray( array $data ): array { + if ( !empty( $data ) ) { + foreach ( $data as $key => $value ) { + switch ( gettype( $value ) ) { + case 'string': + if ( !empty( $str ) ) { + $data[ $key ] = cleanString( $value ); + } + break; + case 'array': + if ( !empty( $value ) ) { + $data[ $key ] = cleanArray( $value ); + } + break; + } + } + } - return $data; + return $data; +} + +function cleanString( string $str ): string { + $newStr = ''; + foreach ( explode( ' ', trim( $str ) ) as $word ) { + if ( !empty( $word ) && $word != '' ) { + if ( $newStr != '' ) { + $newStr .= ' '; + } + $newStr .= $word; + } + } + return $newStr; } /** @@ -405,9 +409,27 @@ function cleanArray( array $data ) { * @return mixed */ function endKey( $array ) { - end( $array ); + end( $array ); - return key( $array ); + return key( $array ); +} + +function filterArrays( $array1, $array2 ): array { + $newArray = []; + + foreach ( $array2 as $item2 ) { + $add = true; + foreach ( $array1 as $item1 ) { + if ( $item2[ "id" ] == $item1[ "id" ] ) { + $add = false; + } + } + if ( $add ) { + $newArray[] = $item2; + } + } + + return $newArray; } ?> \ No newline at end of file diff --git a/src/lib/mail/PHPMailerAutoload.php b/src/lib/mail/PHPMailerAutoload.php index 2a82750..d2bdae6 100644 --- a/src/lib/mail/PHPMailerAutoload.php +++ b/src/lib/mail/PHPMailerAutoload.php @@ -23,27 +23,27 @@ * @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; - } + //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' ); - } + //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 4278591..603b45d 100644 --- a/src/lib/mail/class.phpmailer.php +++ b/src/lib/mail/class.phpmailer.php @@ -26,3857 +26,3858 @@ * @author Brent R. Matzelle (original founder) */ 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 ); - } - } + /** + * 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; + } + + /** + * 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 ); + } + } + + /** + * 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; + } + } + + /** + * 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; + } + + /** + * 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 ); + } + + /** + * 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 ); + } + + /** + * 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 ) ); + } + + /** + * 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; + } + + /** + * 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; + } } /** @@ -3884,13 +3885,13 @@ class PHPMailer { * @package PHPMailer */ class phpmailerException extends Exception { - /** - * Prettify error message output - * @return string - */ - public function errorMessage() { - $errorMsg = '' . $this->getMessage() . "
\n"; + /** + * Prettify error message output + * @return string + */ + public function errorMessage() { + $errorMsg = '' . $this->getMessage() . "
\n"; - return $errorMsg; - } + return $errorMsg; + } } diff --git a/src/lib/mail/class.smtp.php b/src/lib/mail/class.smtp.php index af41bf2..c0cf857 100644 --- a/src/lib/mail/class.smtp.php +++ b/src/lib/mail/class.smtp.php @@ -25,1190 +25,1190 @@ * @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 = ''; - - /** - * 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; - } + /** + * 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 1949812..9c525bd 100644 --- a/src/lib/mail/mail_default.html +++ b/src/lib/mail/mail_default.html @@ -1,7 +1,7 @@ - +
li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.clearfix{clear:both}.z-depth-0{-webkit-box-shadow:none !important;box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-small,.btn-floating,.dropdown-content,.collapsible,.sidenav{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-small:hover,.btn-floating:hover{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2);box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{-webkit-box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3);box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{-webkit-box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2);box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2)}.z-depth-4{-webkit-box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2);box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2)}.z-depth-5,.modal{-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2);box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2)}.hoverable{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s}.hoverable:hover{-webkit-box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax-container .parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax-container .parallax img{opacity:0;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 1201px){.hide-on-extra-large-only{display:none !important}}@media only screen and (min-width: 1201px){.show-on-extra-large{display:block !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}.page-footer{padding-top:20px;color:#fff;background-color:#ee6e73}.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table;border-collapse:collapse;border-spacing:0}table.striped tr{border-bottom:none}table.striped>tbody>tr:nth-child(odd){background-color:rgba(242,242,242,0.5)}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:rgba(242,242,242,0.5)}table.centered thead tr th,table.centered tbody tr td{text-align:center}tr{border-bottom:1px solid rgba(0,0,0,0.12)}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{border-bottom:none;padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid rgba(0,0,0,0.12)}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar:not(.circle-clipper)>.circle,.collection .collection-item.avatar :not(.circle-clipper)>.circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;-webkit-transition:.25s;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;-webkit-transition:width .3s linear;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;-webkit-box-sizing:border-box;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px;-webkit-font-smoothing:auto}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-left:auto}.sidenav span.badge{margin-top:calc(24px - 11px)}table span.badge{display:inline-block;float:none;margin-left:auto}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.col .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}@media only screen and (min-width: 1201px){.row .col.xl1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.xl4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.xl7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.xl10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-xl1{margin-left:8.3333333333%}.row .col.pull-xl1{right:8.3333333333%}.row .col.push-xl1{left:8.3333333333%}.row .col.offset-xl2{margin-left:16.6666666667%}.row .col.pull-xl2{right:16.6666666667%}.row .col.push-xl2{left:16.6666666667%}.row .col.offset-xl3{margin-left:25%}.row .col.pull-xl3{right:25%}.row .col.push-xl3{left:25%}.row .col.offset-xl4{margin-left:33.3333333333%}.row .col.pull-xl4{right:33.3333333333%}.row .col.push-xl4{left:33.3333333333%}.row .col.offset-xl5{margin-left:41.6666666667%}.row .col.pull-xl5{right:41.6666666667%}.row .col.push-xl5{left:41.6666666667%}.row .col.offset-xl6{margin-left:50%}.row .col.pull-xl6{right:50%}.row .col.push-xl6{left:50%}.row .col.offset-xl7{margin-left:58.3333333333%}.row .col.pull-xl7{right:58.3333333333%}.row .col.push-xl7{left:58.3333333333%}.row .col.offset-xl8{margin-left:66.6666666667%}.row .col.pull-xl8{right:66.6666666667%}.row .col.push-xl8{left:66.6666666667%}.row .col.offset-xl9{margin-left:75%}.row .col.pull-xl9{right:75%}.row .col.push-xl9{left:75%}.row .col.offset-xl10{margin-left:83.3333333333%}.row .col.pull-xl10{right:83.3333333333%}.row .col.push-xl10{left:83.3333333333%}.row .col.offset-xl11{margin-left:91.6666666667%}.row .col.pull-xl11{right:91.6666666667%}.row .col.push-xl11{left:91.6666666667%}.row .col.offset-xl12{margin-left:100%}.row .col.pull-xl12{right:100%}.row .col.push-xl12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.sidenav-trigger{display:none}}nav .sidenav-trigger{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .sidenav-trigger i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{-webkit-transition:background-color .3s;transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{-webkit-transition:background-color .3s;transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-small,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-small>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;-webkit-box-shadow:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);-webkit-transition:color .3s;transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.sidenav-trigger,nav a.sidenav-trigger i{height:64px;line-height:64px}.navbar-fixed{height:64px}}a{text-decoration:none}html{line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.3}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.8rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:2.3733333333rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.9466666667rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.52rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:1.0933333333rem 0 .656rem 0}h6{font-size:1.15rem;line-height:110%;margin:.7666666667rem 0 .46rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light{font-weight:300}.thin{font-weight:200}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{-webkit-transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .2s !important;transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);position:relative;padding:16px 24px}.card .card-action:last-child{border-radius:0 0 2px 2px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;-webkit-transition:color .3s ease;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:default}.toast .toast-action{color:#eeff41;font-weight:500;margin-right:-25px;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;-webkit-transition:color .28s ease, background-color .28s ease;transition:color .28s ease, background-color .28s ease}.tabs .tab a:focus,.tabs .tab a:focus.active{background-color:rgba(246,178,181,0.2);outline:none}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.4);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden;background-color:#323232}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-small,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 16px;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.disabled.btn-small,.btn-floating.disabled,.btn-large.disabled,.btn-small.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-small:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-small:disabled,.btn-flat:disabled,.btn[disabled],.btn-large[disabled],.btn-small[disabled],.btn-floating[disabled],.btn-large[disabled],.btn-small[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;-webkit-box-shadow:none;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.disabled.btn-small:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-small.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-small,.btn-floating,.btn-large,.btn-small,.btn-flat{font-size:14px;outline:0}.btn i,.btn-large i,.btn-small i,.btn-floating i,.btn-large i,.btn-small i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-small:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large,.btn-small{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover,.btn-small:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;-webkit-transition:background-color .3s;transition:background-color .3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px;padding:0}.btn-floating.btn-large.halfway-fab{bottom:-28px}.btn-floating.btn-large i{line-height:56px}.btn-floating.btn-small{width:32.4px;height:32.4px}.btn-floating.btn-small.halfway-fab{bottom:-16.2px}.btn-floating.btn-small i{line-height:32.4px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:-20px}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:997}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.direction-left,.fixed-action-btn.direction-right{padding:0 0 0 15px}.fixed-action-btn.direction-left ul,.fixed-action-btn.direction-right ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.direction-left ul li,.fixed-action-btn.direction-right ul li{display:inline-block;margin:7.5px 15px 0 0}.fixed-action-btn.direction-right{padding:0 15px 0 0}.fixed-action-btn.direction-right ul{text-align:left;direction:rtl;left:64px;right:auto}.fixed-action-btn.direction-right ul li{margin:7.5px 0 0 15px}.fixed-action-btn.direction-bottom{padding:0 0 15px 0}.fixed-action-btn.direction-bottom ul{top:64px;bottom:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.fixed-action-btn.direction-bottom ul li{margin:15px 0 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0;z-index:1}.fixed-action-btn.toolbar ul li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;-webkit-transition:none;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{-webkit-box-shadow:none;box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;-webkit-transition:background-color .2s;transition:background-color .2s}.btn-flat:focus,.btn-flat:hover{-webkit-box-shadow:none;box-shadow:none}.btn-flat:focus{background-color:rgba(0,0,0,0.1)}.btn-flat.disabled,.btn-flat.btn-flat[disabled]{background-color:transparent !important;color:#b3b2b2 !important;cursor:default}.btn-large{height:54px;line-height:54px;font-size:15px;padding:0 28px}.btn-large i{font-size:1.6rem}.btn-small{height:32.4px;line-height:32.4px;font-size:13px}.btn-small i{font-size:1.2rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;overflow-y:auto;opacity:0;position:absolute;left:0;top:0;z-index:9999;-webkit-transform-origin:0 0;transform-origin:0 0}.dropdown-content:focus{outline:0}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left}.dropdown-content li:hover,.dropdown-content li.active{background-color:#eee}.dropdown-content li:focus{outline:none}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit;float:left;margin:0 24px 0 0;width:24px}body.keyboard-focused .dropdown-content li:focus{background-color:#dadada}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px;-webkit-transform:none;transform:none}.dropdown-trigger{cursor:pointer}/*! + * Waves v0.6.0 + * http://fian.my.id/Waves + * + * Copyright 2014 Alfiana E. Sibuea and other contributors + * Released under the MIT license + * https://github.com/fians/Waves/blob/master/LICENSE + */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;-webkit-transition:.3s ease-out;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);-webkit-transition:all 0.7s ease-out;transition:all 0.7s ease-out;-webkit-transition-property:opacity, -webkit-transform;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{-webkit-transition:none !important;transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;-webkit-box-shadow:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;-webkit-box-shadow:none;box-shadow:none}.collapsible.popout>li{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;-webkit-transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;-webkit-box-shadow:none;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;-webkit-transition:all .3s;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;-webkit-box-shadow:none !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;-webkit-transition:opacity .4s;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}::-ms-input-placeholder{color:#d1d1d1}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:border .3s, -webkit-box-shadow .3s;transition:border .3s, -webkit-box-shadow .3s;transition:box-shadow .3s, border .3s;transition:box-shadow .3s, border .3s, -webkit-box-shadow .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid ~ label,input[type=text]:not(.browser-default):focus.valid ~ label,input[type=password]:not(.browser-default):focus.valid ~ label,input[type=email]:not(.browser-default):focus.valid ~ label,input[type=url]:not(.browser-default):focus.valid ~ label,input[type=time]:not(.browser-default):focus.valid ~ label,input[type=date]:not(.browser-default):focus.valid ~ label,input[type=datetime]:not(.browser-default):focus.valid ~ label,input[type=datetime-local]:not(.browser-default):focus.valid ~ label,input[type=tel]:not(.browser-default):focus.valid ~ label,input[type=number]:not(.browser-default):focus.valid ~ label,input[type=search]:not(.browser-default):focus.valid ~ label,textarea.materialize-textarea:focus.valid ~ label{color:#4CAF50}input:not([type]):focus.invalid ~ label,input[type=text]:not(.browser-default):focus.invalid ~ label,input[type=password]:not(.browser-default):focus.invalid ~ label,input[type=email]:not(.browser-default):focus.invalid ~ label,input[type=url]:not(.browser-default):focus.invalid ~ label,input[type=time]:not(.browser-default):focus.invalid ~ label,input[type=date]:not(.browser-default):focus.invalid ~ label,input[type=datetime]:not(.browser-default):focus.invalid ~ label,input[type=datetime-local]:not(.browser-default):focus.invalid ~ label,input[type=tel]:not(.browser-default):focus.invalid ~ label,input[type=number]:not(.browser-default):focus.invalid ~ label,input[type=search]:not(.browser-default):focus.invalid ~ label,textarea.materialize-textarea:focus.invalid ~ label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}input.valid:not([type]),input.valid:not([type]):focus,input.valid[type=text]:not(.browser-default),input.valid[type=text]:not(.browser-default):focus,input.valid[type=password]:not(.browser-default),input.valid[type=password]:not(.browser-default):focus,input.valid[type=email]:not(.browser-default),input.valid[type=email]:not(.browser-default):focus,input.valid[type=url]:not(.browser-default),input.valid[type=url]:not(.browser-default):focus,input.valid[type=time]:not(.browser-default),input.valid[type=time]:not(.browser-default):focus,input.valid[type=date]:not(.browser-default),input.valid[type=date]:not(.browser-default):focus,input.valid[type=datetime]:not(.browser-default),input.valid[type=datetime]:not(.browser-default):focus,input.valid[type=datetime-local]:not(.browser-default),input.valid[type=datetime-local]:not(.browser-default):focus,input.valid[type=tel]:not(.browser-default),input.valid[type=tel]:not(.browser-default):focus,input.valid[type=number]:not(.browser-default),input.valid[type=number]:not(.browser-default):focus,input.valid[type=search]:not(.browser-default),input.valid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus,.select-wrapper.valid>input.select-dropdown{border-bottom:1px solid #4CAF50;-webkit-box-shadow:0 1px 0 0 #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input.invalid:not([type]),input.invalid:not([type]):focus,input.invalid[type=text]:not(.browser-default),input.invalid[type=text]:not(.browser-default):focus,input.invalid[type=password]:not(.browser-default),input.invalid[type=password]:not(.browser-default):focus,input.invalid[type=email]:not(.browser-default),input.invalid[type=email]:not(.browser-default):focus,input.invalid[type=url]:not(.browser-default),input.invalid[type=url]:not(.browser-default):focus,input.invalid[type=time]:not(.browser-default),input.invalid[type=time]:not(.browser-default):focus,input.invalid[type=date]:not(.browser-default),input.invalid[type=date]:not(.browser-default):focus,input.invalid[type=datetime]:not(.browser-default),input.invalid[type=datetime]:not(.browser-default):focus,input.invalid[type=datetime-local]:not(.browser-default),input.invalid[type=datetime-local]:not(.browser-default):focus,input.invalid[type=tel]:not(.browser-default),input.invalid[type=tel]:not(.browser-default):focus,input.invalid[type=number]:not(.browser-default),input.invalid[type=number]:not(.browser-default):focus,input.invalid[type=search]:not(.browser-default),input.invalid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus,.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus{border-bottom:1px solid #F44336;-webkit-box-shadow:0 1px 0 0 #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).valid ~ .helper-text[data-success],input:not([type]):focus.valid ~ .helper-text[data-success],input:not([type]).invalid ~ .helper-text[data-error],input:not([type]):focus.invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default).valid ~ .helper-text[data-success],input[type=text]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=text]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default).valid ~ .helper-text[data-success],input[type=password]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=password]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default).valid ~ .helper-text[data-success],input[type=email]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=email]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default).valid ~ .helper-text[data-success],input[type=url]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=url]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default).valid ~ .helper-text[data-success],input[type=time]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=time]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default).valid ~ .helper-text[data-success],input[type=date]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=date]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default).valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default).valid ~ .helper-text[data-success],input[type=number]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=number]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default).valid ~ .helper-text[data-success],input[type=search]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=search]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default):focus.invalid ~ .helper-text[data-error],textarea.materialize-textarea.valid ~ .helper-text[data-success],textarea.materialize-textarea:focus.valid ~ .helper-text[data-success],textarea.materialize-textarea.invalid ~ .helper-text[data-error],textarea.materialize-textarea:focus.invalid ~ .helper-text[data-error],.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid ~ .helper-text[data-error]{color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}input:not([type]).valid ~ .helper-text:after,input:not([type]):focus.valid ~ .helper-text:after,input[type=text]:not(.browser-default).valid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=password]:not(.browser-default).valid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=email]:not(.browser-default).valid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=url]:not(.browser-default).valid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=time]:not(.browser-default).valid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=date]:not(.browser-default).valid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=tel]:not(.browser-default).valid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=number]:not(.browser-default).valid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=search]:not(.browser-default).valid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.valid ~ .helper-text:after,textarea.materialize-textarea.valid ~ .helper-text:after,textarea.materialize-textarea:focus.valid ~ .helper-text:after,.select-wrapper.valid ~ .helper-text:after{content:attr(data-success);color:#4CAF50}input:not([type]).invalid ~ .helper-text:after,input:not([type]):focus.invalid ~ .helper-text:after,input[type=text]:not(.browser-default).invalid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=password]:not(.browser-default).invalid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=email]:not(.browser-default).invalid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=url]:not(.browser-default).invalid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=time]:not(.browser-default).invalid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=date]:not(.browser-default).invalid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=tel]:not(.browser-default).invalid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=number]:not(.browser-default).invalid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=search]:not(.browser-default).invalid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.invalid ~ .helper-text:after,textarea.materialize-textarea.invalid ~ .helper-text:after,textarea.materialize-textarea:focus.invalid ~ .helper-text:after,.select-wrapper.invalid ~ .helper-text:after{content:attr(data-error);color:#F44336}input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after,.select-wrapper+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;-webkit-transition:.2s opacity ease-out, .2s color ease-out;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#9e9e9e;position:absolute;top:0;left:0;font-size:1rem;cursor:text;-webkit-transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:transform .2s ease-out, color .2s ease-out;transition:transform .2s ease-out, color .2s ease-out, -webkit-transform .2s ease-out;-webkit-transform-origin:0% 100%;transform-origin:0% 100%;text-align:initial;-webkit-transform:translateY(12px);transform:translateY(12px)}.input-field>label:not(.label-icon).active{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;-webkit-transition:color .2s;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .helper-text,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;-webkit-transition:.3s background-color;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;-webkit-box-shadow:none;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;-webkit-box-shadow:none;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default) ~ .mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default) ~ .material-icons{color:#444}.input-field input[type=search]+.label-icon{-webkit-transform:none;transform:none;left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;-webkit-transition:.3s color;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;-webkit-box-sizing:border-box;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-transition:.28s ease;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;-webkit-transition:.28s ease;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;-webkit-transition:.2s;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;-webkit-transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;-webkit-transition:background 0.3s ease;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;-webkit-transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;-webkit-box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled) ~ .lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 10px rgba(38,166,154,0.26);box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-large:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.sidenav.sidenav-fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;-webkit-transition:visibility 0s .3s;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;-webkit-transition:visibility 0s;transition:visibility 0s}.tap-target-wrapper.open .tap-target{-webkit-transform:scale(1);transform:scale(1);opacity:.95;-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{-webkit-transform:scale(1);transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;-webkit-transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s 1s;transition:opacity .3s, transform .3s, visibility 0s 1s, -webkit-transform .3s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;-webkit-box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.tap-target-wave::after{visibility:hidden;-webkit-transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s;transition:opacity .3s, transform .3s, visibility 0s, -webkit-transform .3s;z-index:-1}.tap-target-origin{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;-webkit-transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, transform .3s;transition:opacity .3s, transform .3s, -webkit-transform .3s;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@-webkit-keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}@keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.datepicker-controls{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width: 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.datepicker-date-display{-webkit-box-flex:0;-webkit-flex:0 1 270px;-ms-flex:0 1 270px;flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{-webkit-transition:opacity 350ms, -webkit-transform 350ms;transition:opacity 350ms, -webkit-transform 350ms;transition:transform 350ms, opacity 350ms;transition:transform 350ms, opacity 350ms, -webkit-transform 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{-webkit-transform:scale(1.1, 1.1);transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.timepicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width: 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}} diff --git a/static/css/style.css b/static/css/style.css index 12c8ed7..221e301 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,55 +1,37 @@ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; +main { + background: #F1F1F1; } -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; +.nav-add { + padding-top: 12px; + height: 64px; } -body { - line-height: 1; +img.add-contacts { + height: 40px; + cursor: pointer; } -ol, ul { - list-style: none; +.row .col.s12.main-container { + background: #FFF; + padding: 10px; + border-radius: 5px; } -blockquote, q { - quotes: none; +#search { + background: white; + border: none; + border-radius: 7px; + height: 40px; + width: 200px; + transition: all 0.3s; + padding-left: 10px; } -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; +#search:hover { + width: 350px; } -table { - border-collapse: collapse; - border-spacing: 0; -} - -* { - padding: 0; - margin: 0; - box-sizing: border-box; -} +#search:focus { + width: 500px; +} \ No newline at end of file diff --git a/static/img/add-contacts.png b/static/img/add-contacts.png new file mode 100644 index 0000000..ea01cce Binary files /dev/null and b/static/img/add-contacts.png differ diff --git a/static/img/favicon.ico b/static/img/favicon.ico new file mode 100644 index 0000000..c6fa4af Binary files /dev/null and b/static/img/favicon.ico differ diff --git a/static/img/logo.png b/static/img/logo.png new file mode 100644 index 0000000..e513585 Binary files /dev/null and b/static/img/logo.png differ diff --git a/static/js/javascript.js b/static/js/javascript.js index e69de29..59fc7af 100644 --- a/static/js/javascript.js +++ b/static/js/javascript.js @@ -0,0 +1,74 @@ +let working = false; +let lastQuery = null; + +let displayContacts = (contacts) => { + let tbody = $("table tbody"); + + for (let contact of contacts) { + tbody.append(` + + ${(contact["first_name"] ? contact["first_name"] : (contact["firstName"] ? contact["firstName"] : ""))} + ${(contact["last_name"] ? contact["last_name"] : (contact["lastName"] ? contact["lastName"] : ""))} + ${(contact["surname"] ? contact["surname"] : "")} + ${(contact["email"] ? contact["email"] : "")} + ${(contact["address"] ? contact["address"] : "")} + ${(contact["phone_number"] ? contact["phone_number"] : (contact["phoneNumber"] ? contact["phoneNumber"] : ""))} + ${(contact["birthday"] ? new Date(contact["birthday"]).toLocaleDateString() : "")} + + `); + } +}; + +$(document).ready(() => { + + let modal = $('.modal'); + + modal.modal(); + + let search = (query) => { + if (query.length >= 3) { + working = true; + $.ajax({ + url: "/api/contact/search", + method: "POST", + data: { + query: query + }, + success: function (data) { + $("table tbody").empty(); + + for (let i = 0; i < 4; i++) { + if (data && data.result && data.result[i.toString()] && data.result[i.toString()].length > 0) { + displayContacts(data.result[i.toString()]); + } + } + + working = false; + }, + error: function (e) { + working = false; + } + }); + } else { + $("table tbody").empty(); + displayContacts(contacts); + } + }; + + $(document).on("input", "#search", () => { + let query = $("#search").val(); + + if (!working) { + search(query); + } else { + lastQuery = query; + } + }); + + $(document).on("click", ".add-contacts", () => { + modal.open(); + }); + + $("[data-tooltip]").tooltip(); + +}); diff --git a/static/js/jquery-3.2.1.min.js b/static/js/jquery-3.2.1.min.js deleted file mode 100644 index 644d35e..0000000 --- a/static/js/jquery-3.2.1.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S), -a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b), -null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("