diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 80ba16ff..06d20b93 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -734,13 +734,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } $active = intval($_data['active']); $quota_b = ($quota_m * 1048576); - $maildir = $domain . "/" . $local_part . "/"; $mailbox_attrs = json_encode( array( 'force_pw_update' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'])), 'tls_enforce_in' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in'])), 'tls_enforce_out' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'])), - 'sogo_access' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'])) + 'sogo_access' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'])), + 'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']) ) ); if (!is_valid_domain_name($domain)) { @@ -875,13 +875,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `attributes`, `active`) - VALUES (:username, :password_hashed, :name, :maildir, :quota_b, :local_part, :domain, :mailbox_attrs, :active)"); + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `active`) + VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :active)"); $stmt->execute(array( ':username' => $username, ':password_hashed' => $password_hashed, ':name' => $name, - ':maildir' => $maildir, ':quota_b' => $quota_b, ':local_part' => $local_part, ':domain' => $domain, @@ -1004,8 +1003,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `maildir`, `quota`, `local_part`, `domain`, `active`, `multiple_bookings`, `kind`) - VALUES (:name, 'RESOURCE', :description, 'RESOURCE', 0, :local_part, :domain, :active, :multiple_bookings, :kind)"); + $stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `active`, `multiple_bookings`, `kind`) + VALUES (:name, 'RESOURCE', :description, 0, :local_part, :domain, :active, :multiple_bookings, :kind)"); $stmt->execute(array( ':name' => $name, ':description' => $description, @@ -2847,7 +2846,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { `mailbox`.`active` AS `active_int`, CASE `mailbox`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`, `mailbox`.`domain`, - `mailbox`.`maildir`, + `mailbox`.`local_part`, `mailbox`.`quota`, `quota2`.`bytes`, `attributes`, @@ -2878,7 +2877,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $mailboxdata['active'] = $row['active']; $mailboxdata['active_int'] = $row['active_int']; $mailboxdata['domain'] = $row['domain']; - $mailboxdata['maildir'] = $row['maildir']; + $mailboxdata['local_part'] = $row['local_part']; $mailboxdata['quota'] = $row['quota']; $mailboxdata['attributes'] = json_decode($row['attributes'], true); $mailboxdata['quota_used'] = intval($row['bytes']); @@ -3193,7 +3192,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_SESSION['return'][] = array( 'type' => 'warning', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg'] + 'msg' => 'Could not move mail storage to garbage collector: ' . $maildir_gc['msg'] ); } $stmt = $pdo->prepare("DELETE FROM `domain` WHERE `domain` = :domain"); @@ -3369,14 +3368,24 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); continue; } - $maildir = mailbox('get', 'mailbox_details', $username)['maildir']; - $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $maildir); - $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); - if ($maildir_gc['type'] != 'success') { + $mailbox_details = mailbox('get', 'mailbox_details', $username); + if (!empty($mailbox_details['domain']) && !empty($mailbox_details['local_part'])) { + $maildir = $mailbox_details['domain'] . '/' . $mailbox_details['local_part']; + $exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $maildir); + $maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true); + if ($maildir_gc['type'] != 'success') { + $_SESSION['return'][] = array( + 'type' => 'warning', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg'] + ); + } + } + else { $_SESSION['return'][] = array( 'type' => 'warning', 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), - 'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg'] + 'msg' => 'Could not move maildir to garbage collector: variables local_part and/or domain empty' ); } $stmt = $pdo->prepare("DELETE FROM `alias` WHERE `goto` = :username"); diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 1b328d87..5baaa77b 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "22102018_1502"; + $db_version = "03112018_1117"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -78,7 +78,6 @@ function init_db_schema() { // TODO -> use TEXT and check if SOGo login breaks on empty aliases "aliases" => "TEXT NOT NULL", "ad_aliases" => "VARCHAR(6144) NOT NULL DEFAULT ''", - "home" => "VARCHAR(255)", "kind" => "VARCHAR(100) NOT NULL DEFAULT ''", "multiple_bookings" => "INT NOT NULL DEFAULT -1" ), @@ -230,7 +229,8 @@ function init_db_schema() { "username" => "VARCHAR(255) NOT NULL", "password" => "VARCHAR(255) NOT NULL", "name" => "VARCHAR(255)", - "maildir" => "VARCHAR(255) NOT NULL", + // mailbox_path_prefix is followed by domain/local_part/ + "mailbox_path_prefix" => "VARCHAR(150) DEFAULT '/var/vmail/'", "quota" => "BIGINT(20) NOT NULL DEFAULT '102400'", "local_part" => "VARCHAR(255) NOT NULL", "domain" => "VARCHAR(255) NOT NULL", @@ -962,16 +962,26 @@ DELIMITER ;'; // Insert new DB schema version $stmt = $pdo->query("REPLACE INTO `versions` (`application`, `version`) VALUES ('db_schema', '" . $db_version . "');"); - // Migrate tls_enforce_* options and add force_pw_update attribute - $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` IS NULL;"); + // Migrate attributes + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` = '' OR NULL;"); $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_EXTRACT(`attributes`, '$.force_pw_update') IS NULL;"); $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sogo_access', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.sogo_access') IS NULL;"); + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_EXTRACT(`attributes`, '$.mailbox_format') IS NULL;"); foreach($tls_options as $tls_user => $tls_options) { $stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in), `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out) WHERE `username` = :username"); $stmt->execute(array(':tls_enforce_in' => $tls_options['tls_enforce_in'], ':tls_enforce_out' => $tls_options['tls_enforce_out'], ':username' => $tls_user)); } + // Set tls_enforce_* if still missing (due to deleted attrs for example) + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.tls_enforce_out') IS NULL;"); + $stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.tls_enforce_in') IS NULL;"); + // Fix ACL + $stmt = $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);"); + $stmt = $pdo->query("INSERT INTO `da_acl` (`username`) SELECT DISTINCT `username` FROM `domain_admins` WHERE `username` != 'admin' AND NOT EXISTS (SELECT `username` FROM `da_acl`);"); + // Fix domain_admins + $stmt = $pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';"); + if (php_sapi_name() == "cli") { echo "DB initialization completed" . PHP_EOL; } else { @@ -981,11 +991,6 @@ DELIMITER ;'; 'msg' => 'db_init_complete' ); } - // Fix ACL - $stmt = $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);"); - $stmt = $pdo->query("INSERT INTO `da_acl` (`username`) SELECT DISTINCT `username` FROM `domain_admins` WHERE `username` != 'admin' AND NOT EXISTS (SELECT `username` FROM `da_acl`);"); - // Fix domain_admins - $stmt = $pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';"); } catch (PDOException $e) { if (php_sapi_name() == "cli") { @@ -1001,25 +1006,27 @@ DELIMITER ;'; } if (php_sapi_name() == "cli") { include '/web/inc/vars.inc.php'; - $now = new DateTime(); - $mins = $now->getOffset() / 60; - $sgn = ($mins < 0 ? -1 : 1); - $mins = abs($mins); - $hrs = floor($mins / 60); - $mins -= $hrs * 60; - $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); + // $now = new DateTime(); + // $mins = $now->getOffset() / 60; + // $sgn = ($mins < 0 ? -1 : 1); + // $mins = abs($mins); + // $hrs = floor($mins / 60); + // $mins -= $hrs * 60; + // $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, - PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", + //PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", ]; $pdo = new PDO($dsn, $database_user, $database_pass, $opt); $stmt = $pdo->query("SELECT COUNT('OK') AS OK_C FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view' OR TABLE_NAME = '_sogo_static_view';"); $res = $stmt->fetch(PDO::FETCH_ASSOC); if (intval($res['OK_C']) === 2) { - $stmt = $pdo->query("REPLACE INTO _sogo_static_view SELECT * from sogo_view"); + // Be more precise when replacing into _sogo_static_view, col orders may change + $stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `kind`, `multiple_bookings`) + SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `kind`, `multiple_bookings` from sogo_view"); $stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');"); echo "Fixed _sogo_static_view" . PHP_EOL; } diff --git a/data/web/inc/lib/composer.json b/data/web/inc/lib/composer.json index 6107bac7..dcd51fbe 100644 --- a/data/web/inc/lib/composer.json +++ b/data/web/inc/lib/composer.json @@ -4,6 +4,7 @@ "yubico/u2flib-server": "^1.0", "phpmailer/phpmailer": "^5.2", "php-mime-mail-parser/php-mime-mail-parser": "^2.9", - "soundasleep/html2text": "^0.5.0" + "soundasleep/html2text": "^0.5.0", + "ddeboer/imap": "^1.5" } } diff --git a/data/web/inc/lib/composer.lock b/data/web/inc/lib/composer.lock index 841f2e93..b99c212d 100644 --- a/data/web/inc/lib/composer.lock +++ b/data/web/inc/lib/composer.lock @@ -4,8 +4,67 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3edeec2e3fa875d4f9d5e7f22a8179be", + "content-hash": "baad410246ce54c06f9bbc7761e02a76", "packages": [ + { + "name": "ddeboer/imap", + "version": "1.5.5", + "source": { + "type": "git", + "url": "https://github.com/ddeboer/imap.git", + "reference": "acf56f54375babb27a245338a13f4e8246975268" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ddeboer/imap/zipball/acf56f54375babb27a245338a13f4e8246975268", + "reference": "acf56f54375babb27a245338a13f4e8246975268", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-imap": "*", + "ext-mbstring": "*", + "php": "^7.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.10", + "phpstan/phpstan": "^0.9.1", + "phpstan/phpstan-phpunit": "^0.9.3", + "phpunit/phpunit": "^6.5 || ^7.0", + "zendframework/zend-mail": "^2.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ddeboer\\Imap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + }, + { + "name": "Community contributors", + "homepage": "https://github.com/ddeboer/imap/graphs/contributors" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com" + } + ], + "description": "Object-oriented IMAP for PHP", + "keywords": [ + "email", + "imap", + "mail" + ], + "time": "2018-08-21T07:30:59+00:00" + }, { "name": "php-mime-mail-parser/php-mime-mail-parser", "version": "2.11.1", diff --git a/data/web/inc/lib/vendor/composer/ClassLoader.php b/data/web/inc/lib/vendor/composer/ClassLoader.php index dc02dfb1..fce8549f 100644 --- a/data/web/inc/lib/vendor/composer/ClassLoader.php +++ b/data/web/inc/lib/vendor/composer/ClassLoader.php @@ -279,7 +279,7 @@ class ClassLoader */ public function setApcuPrefix($apcuPrefix) { - $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** @@ -377,7 +377,7 @@ class ClassLoader $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); - $search = $subPath.'\\'; + $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { diff --git a/data/web/inc/lib/vendor/composer/autoload_psr4.php b/data/web/inc/lib/vendor/composer/autoload_psr4.php index cfa01d56..0a39ec29 100644 --- a/data/web/inc/lib/vendor/composer/autoload_psr4.php +++ b/data/web/inc/lib/vendor/composer/autoload_psr4.php @@ -9,4 +9,5 @@ return array( 'RobThree\\Auth\\' => array($vendorDir . '/robthree/twofactorauth/lib'), 'PhpMimeMailParser\\' => array($vendorDir . '/php-mime-mail-parser/php-mime-mail-parser/src'), 'Html2Text\\' => array($vendorDir . '/soundasleep/html2text/src'), + 'Ddeboer\\Imap\\' => array($vendorDir . '/ddeboer/imap/src'), ); diff --git a/data/web/inc/lib/vendor/composer/autoload_static.php b/data/web/inc/lib/vendor/composer/autoload_static.php index 9d9f1a85..9cfb9c58 100644 --- a/data/web/inc/lib/vendor/composer/autoload_static.php +++ b/data/web/inc/lib/vendor/composer/autoload_static.php @@ -19,6 +19,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b array ( 'Html2Text\\' => 10, ), + 'D' => + array ( + 'Ddeboer\\Imap\\' => 13, + ), ); public static $prefixDirsPsr4 = array ( @@ -34,6 +38,10 @@ class ComposerStaticInit873464e4bd965a3168f133248b1b218b array ( 0 => __DIR__ . '/..' . '/soundasleep/html2text/src', ), + 'Ddeboer\\Imap\\' => + array ( + 0 => __DIR__ . '/..' . '/ddeboer/imap/src', + ), ); public static $classMap = array ( diff --git a/data/web/inc/lib/vendor/composer/installed.json b/data/web/inc/lib/vendor/composer/installed.json index bd1deb85..4c4d0026 100644 --- a/data/web/inc/lib/vendor/composer/installed.json +++ b/data/web/inc/lib/vendor/composer/installed.json @@ -1,4 +1,65 @@ [ + { + "name": "ddeboer/imap", + "version": "1.5.5", + "version_normalized": "1.5.5.0", + "source": { + "type": "git", + "url": "https://github.com/ddeboer/imap.git", + "reference": "acf56f54375babb27a245338a13f4e8246975268" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ddeboer/imap/zipball/acf56f54375babb27a245338a13f4e8246975268", + "reference": "acf56f54375babb27a245338a13f4e8246975268", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-imap": "*", + "ext-mbstring": "*", + "php": "^7.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.10", + "phpstan/phpstan": "^0.9.1", + "phpstan/phpstan-phpunit": "^0.9.3", + "phpunit/phpunit": "^6.5 || ^7.0", + "zendframework/zend-mail": "^2.8" + }, + "time": "2018-08-21T07:30:59+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Ddeboer\\Imap\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + }, + { + "name": "Community contributors", + "homepage": "https://github.com/ddeboer/imap/graphs/contributors" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com" + } + ], + "description": "Object-oriented IMAP for PHP", + "keywords": [ + "email", + "imap", + "mail" + ] + }, { "name": "php-mime-mail-parser/php-mime-mail-parser", "version": "2.11.1", diff --git a/data/web/inc/lib/vendor/ddeboer/imap/CHANGELOG.md b/data/web/inc/lib/vendor/ddeboer/imap/CHANGELOG.md new file mode 100644 index 00000000..de55b8ae --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/CHANGELOG.md @@ -0,0 +1,486 @@ +# Change Log + +## [1.5.5](https://github.com/ddeboer/imap/tree/1.5.5) (2018-08-21) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.4...1.5.5) + +**Fixed bugs:** + +- Plain text attachments are not identified as Attachment parts [\#341](https://github.com/ddeboer/imap/issues/341) +- Handle plain/text attachments without Content-Type header [\#367](https://github.com/ddeboer/imap/pull/367) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.5.4](https://github.com/ddeboer/imap/tree/1.5.4) (2018-08-19) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.3...1.5.4) + +**Fixed bugs:** + +- Very long filename, result of getFilename\(\) = NULL? [\#365](https://github.com/ddeboer/imap/issues/365) +- Support RFC2231 attachment filenames [\#366](https://github.com/ddeboer/imap/pull/366) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.5.3](https://github.com/ddeboer/imap/tree/1.5.3) (2018-07-20) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.2...1.5.3) + +**Fixed bugs:** + +- Dates: handle UT timezone [\#361](https://github.com/ddeboer/imap/pull/361) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.5.2](https://github.com/ddeboer/imap/tree/1.5.2) (2018-07-10) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.1...1.5.2) + +**Fixed bugs:** + +- Fails to load Message Headers [\#358](https://github.com/ddeboer/imap/issues/358) +- Handle invalid headers [\#359](https://github.com/ddeboer/imap/pull/359) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.5.1](https://github.com/ddeboer/imap/tree/1.5.1) (2018-05-04) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.5.0...1.5.1) + +**Fixed bugs:** + +- getContent\(\) method returns wrong content part [\#342](https://github.com/ddeboer/imap/issues/342) +- Fix handle of attachment messages with attachments [\#343](https://github.com/ddeboer/imap/pull/343) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.5.0](https://github.com/ddeboer/imap/tree/1.5.0) (2018-03-26) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.4.1...1.5.0) + +**Implemented enhancements:** + +- ImapResource: cache last opened mailbox [\#328](https://github.com/ddeboer/imap/pull/328) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.4.1](https://github.com/ddeboer/imap/tree/1.4.1) (2018-03-22) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.4.0...1.4.1) + +**Fixed bugs:** + +- Return value of Ddeboer\\Imap\\Message\\AbstractPart::getDecodedContent\(\) must be of the type string, boolean returned [\#284](https://github.com/ddeboer/imap/issues/284) +- base64\_decode may return false in PHP \< 7.1 [\#324](https://github.com/ddeboer/imap/pull/324) ([Slamdunk](https://github.com/Slamdunk)) + +**Merged pull requests:** + +- Add entry in README about Mailbox::addMessage [\#325](https://github.com/ddeboer/imap/pull/325) ([soywod](https://github.com/soywod)) + +## [1.4.0](https://github.com/ddeboer/imap/tree/1.4.0) (2018-03-19) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.3.1...1.4.0) + +**Implemented enhancements:** + +- Lazy load Message [\#320](https://github.com/ddeboer/imap/pull/320) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- Invalid argument supplied for foreach\(\) in Parameters.php line 52 [\#317](https://github.com/ddeboer/imap/issues/317) +- Message "11964" does not exist: imap\_fetchstructure\(\): Bad message number [\#310](https://github.com/ddeboer/imap/issues/310) +- imap\_mime\_header\_decode may return false [\#322](https://github.com/ddeboer/imap/pull/322) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.3.1](https://github.com/ddeboer/imap/tree/1.3.1) (2018-03-09) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.3.0...1.3.1) + +**Implemented enhancements:** + +- Allow empty port [\#312](https://github.com/ddeboer/imap/pull/312) ([Slamdunk](https://github.com/Slamdunk)) + +**Closed issues:** + +- getServerString\(\) with no port [\#311](https://github.com/ddeboer/imap/issues/311) + +## [1.3.0](https://github.com/ddeboer/imap/tree/1.3.0) (2018-02-28) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.2.3...1.3.0) + +**Implemented enhancements:** + +- Implement bulk-move [\#306](https://github.com/ddeboer/imap/pull/306) ([particleflux](https://github.com/particleflux)) + +**Closed issues:** + +- feature: Bulk move [\#305](https://github.com/ddeboer/imap/issues/305) + +**Merged pull requests:** + +- README.md: add `Unknown search criterion: OR` note [\#304](https://github.com/ddeboer/imap/pull/304) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.2.3](https://github.com/ddeboer/imap/tree/1.2.3) (2018-02-09) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.2.2...1.2.3) + +**Fixed bugs:** + +- $part-\>type can be 9 [\#301](https://github.com/ddeboer/imap/issues/301) +- AbstractPart::isAttachment\(\) handle unknown part type [\#302](https://github.com/ddeboer/imap/pull/302) ([Slamdunk](https://github.com/Slamdunk)) + +**Merged pull requests:** + +- README.md: code-coverage has higher priority than Scrutinizer [\#300](https://github.com/ddeboer/imap/pull/300) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.2.2](https://github.com/ddeboer/imap/tree/1.2.2) (2018-02-05) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.2.1...1.2.2) + +**Implemented enhancements:** + +- Allow PHPUnit ^7.0 [\#296](https://github.com/ddeboer/imap/pull/296) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- Attachment-\>getFilename return null [\#297](https://github.com/ddeboer/imap/issues/297) +- Don't handle multiplart as an attachment [\#298](https://github.com/ddeboer/imap/pull/298) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.2.1](https://github.com/ddeboer/imap/tree/1.2.1) (2018-01-29) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.2.0...1.2.1) + +**Implemented enhancements:** + +- Introduce strict comparison [\#289](https://github.com/ddeboer/imap/pull/289) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- Invalid Date header found: "Thur, 04 Jan 2018 06:44:23 +0400" [\#293](https://github.com/ddeboer/imap/issues/293) +- MessageIterator::current\(\) fails when there are no messages [\#288](https://github.com/ddeboer/imap/issues/288) +- Remove weekday while parsing date header [\#294](https://github.com/ddeboer/imap/pull/294) ([Slamdunk](https://github.com/Slamdunk)) +- MessageIterator: forbid raw calls [\#290](https://github.com/ddeboer/imap/pull/290) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.2.0](https://github.com/ddeboer/imap/tree/1.2.0) (2018-01-15) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.1.2...1.2.0) + +**Implemented enhancements:** + +- Make imap\_append\(\) optional arguments reachable [\#280](https://github.com/ddeboer/imap/pull/280) ([Slamdunk](https://github.com/Slamdunk)) +- PHPStan: introduce static analysis [\#276](https://github.com/ddeboer/imap/pull/276) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- getAttachments\(\) problem when mixin inline and attachment [\#281](https://github.com/ddeboer/imap/issues/281) +- UnexpectedEncodingException: Cannot decode "5" [\#278](https://github.com/ddeboer/imap/issues/278) +- Handle correctly multiple nested attachments [\#283](https://github.com/ddeboer/imap/pull/283) ([Slamdunk](https://github.com/Slamdunk)) +- Manageable UnexpectedEncodingException [\#282](https://github.com/ddeboer/imap/pull/282) ([Slamdunk](https://github.com/Slamdunk)) + +**Closed issues:** + +- Appending mail with options [\#279](https://github.com/ddeboer/imap/issues/279) + +## [1.1.2](https://github.com/ddeboer/imap/tree/1.1.2) (2017-12-12) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.1.1...1.1.2) + +**Fixed bugs:** + +- Unsupported charset "134": mb\_convert\_encoding\(\): Illegal character encoding specified [\#270](https://github.com/ddeboer/imap/issues/270) +- Support Microsoft charset values [\#271](https://github.com/ddeboer/imap/pull/271) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.1.1](https://github.com/ddeboer/imap/tree/1.1.1) (2017-11-10) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.1.0...1.1.1) + +**Implemented enhancements:** + +- Transcoder: expand charset aliases list [\#267](https://github.com/ddeboer/imap/pull/267) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- Charset aliases: fix to lowercase search [\#266](https://github.com/ddeboer/imap/pull/266) ([Slamdunk](https://github.com/Slamdunk)) + +**Merged pull requests:** + +- README.md: add timeout note [\#263](https://github.com/ddeboer/imap/pull/263) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.1.0](https://github.com/ddeboer/imap/tree/1.1.0) (2017-11-06) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.8...1.1.0) + +**Implemented enhancements:** + +- Headers: no catchable exception [\#246](https://github.com/ddeboer/imap/issues/246) +- imap\_thread [\#113](https://github.com/ddeboer/imap/issues/113) +- Deprecate MessageInterface::maskAsSeen\(\) in favour of MessageInterface::markAsSeen\(\) [\#255](https://github.com/ddeboer/imap/pull/255) ([Slamdunk](https://github.com/Slamdunk)) +- Lazy load structured Headers [\#250](https://github.com/ddeboer/imap/pull/250) ([Slamdunk](https://github.com/Slamdunk)) +- Implement imap\_thread [\#249](https://github.com/ddeboer/imap/pull/249) ([Slamdunk](https://github.com/Slamdunk)) +- Require ext-iconv [\#248](https://github.com/ddeboer/imap/pull/248) ([Slamdunk](https://github.com/Slamdunk)) +- Message Part: expose $partNumber [\#244](https://github.com/ddeboer/imap/pull/244) ([wujku](https://github.com/wujku)) +- Add Mockability helpers and documentation [\#236](https://github.com/ddeboer/imap/pull/236) ([Slamdunk](https://github.com/Slamdunk)) +- Add missing interface change for \#225 [\#233](https://github.com/ddeboer/imap/pull/233) ([Slamdunk](https://github.com/Slamdunk)) +- Connection: check if the connection is still active with `imap\_ping` [\#232](https://github.com/ddeboer/imap/pull/232) ([wujku](https://github.com/wujku)) +- Message: add `References` and `In-Reply-To` headers shortcuts [\#230](https://github.com/ddeboer/imap/pull/230) ([wujku](https://github.com/wujku)) +- Added bulk set / clear flags functionality for mailbox messages [\#225](https://github.com/ddeboer/imap/pull/225) ([wujku](https://github.com/wujku)) + +**Merged pull requests:** + +- make docs more obvious [\#252](https://github.com/ddeboer/imap/pull/252) ([lgg](https://github.com/lgg)) +- README.md: add Table of Contents with Travis checker [\#234](https://github.com/ddeboer/imap/pull/234) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.8](https://github.com/ddeboer/imap/tree/1.0.8) (2017-10-27) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.7...1.0.8) + +**Fixed bugs:** + +- \[TypeError\] Return value of Ddeboer\Imap\Message\AbstractMessage::getId\(\) must be of the type string, null returned [\#253](https://github.com/ddeboer/imap/issues/253) +- BasicMessageInterface::getId\(\) can be null [\#254](https://github.com/ddeboer/imap/pull/254) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.7](https://github.com/ddeboer/imap/tree/1.0.7) (2017-10-16) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.6...1.0.7) + +**Fixed bugs:** + +- Problem with a IMAP resource stream [\#245](https://github.com/ddeboer/imap/issues/245) +- IMAP resource must be checked at every call for mailbox context [\#247](https://github.com/ddeboer/imap/pull/247) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.6](https://github.com/ddeboer/imap/tree/1.0.6) (2017-10-12) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.5...1.0.6) + +**Fixed bugs:** + +- \[TypeError\] Return value of AbstractMessage::getFrom\(\) must be an instance of EmailAddress, null returned [\#241](https://github.com/ddeboer/imap/issues/241) +- Message: Date header can be absent [\#243](https://github.com/ddeboer/imap/pull/243) ([Slamdunk](https://github.com/Slamdunk)) +- Message: From header can be absent [\#242](https://github.com/ddeboer/imap/pull/242) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.5](https://github.com/ddeboer/imap/tree/1.0.5) (2017-10-12) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.4...1.0.5) + +**Fixed bugs:** + +- Use set\_error\_handler with late exception [\#240](https://github.com/ddeboer/imap/pull/240) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.4](https://github.com/ddeboer/imap/tree/1.0.4) (2017-10-11) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.3...1.0.4) + +**Implemented enhancements:** + +- Avoid \(set|restor\)\_error\_handler [\#239](https://github.com/ddeboer/imap/pull/239) ([Slamdunk](https://github.com/Slamdunk)) + +**Fixed bugs:** + +- Current Transcoder class does not support all charsets. [\#237](https://github.com/ddeboer/imap/issues/237) +- Relay also iconv during decoding [\#238](https://github.com/ddeboer/imap/pull/238) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.3](https://github.com/ddeboer/imap/tree/1.0.3) (2017-10-11) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.2...1.0.3) + +**Fixed bugs:** + +- Attachment::getFilename\(\) may be null on inline-att, widen return type [\#235](https://github.com/ddeboer/imap/pull/235) ([wujku](https://github.com/wujku)) + +## [1.0.2](https://github.com/ddeboer/imap/tree/1.0.2) (2017-10-06) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.1...1.0.2) + +**Fixed bugs:** + +- Issue with saving XML attachments [\#228](https://github.com/ddeboer/imap/issues/228) +- Do not charset-decode attachments [\#231](https://github.com/ddeboer/imap/pull/231) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.1](https://github.com/ddeboer/imap/tree/1.0.1) (2017-10-05) +[Full Changelog](https://github.com/ddeboer/imap/compare/1.0.0...1.0.1) + +**Fixed bugs:** + +- Error with attachment charset [\#226](https://github.com/ddeboer/imap/issues/226) +- If charset is not specified defaults to "us-ascii" [\#227](https://github.com/ddeboer/imap/pull/227) ([Slamdunk](https://github.com/Slamdunk)) + +## [1.0.0](https://github.com/ddeboer/imap/tree/1.0.0) (2017-10-04) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.5.2...1.0.0) + +**Implemented enhancements:** + +- Need getAll for headers [\#200](https://github.com/ddeboer/imap/issues/200) +- Tests: implement @covers to avoid false positive on code-coverage [\#188](https://github.com/ddeboer/imap/issues/188) +- Remove commented code [\#174](https://github.com/ddeboer/imap/issues/174) +- Regex in SearchExpressions [\#157](https://github.com/ddeboer/imap/issues/157) +- How do I get unread messages count? [\#98](https://github.com/ddeboer/imap/issues/98) +- Add mocking ability through Interfaces [\#221](https://github.com/ddeboer/imap/pull/221) ([Slamdunk](https://github.com/Slamdunk)) +- Wrap imap resource to periodically check its status [\#220](https://github.com/ddeboer/imap/pull/220) ([Slamdunk](https://github.com/Slamdunk)) +- Add more coding-standard rules [\#218](https://github.com/ddeboer/imap/pull/218) ([Slamdunk](https://github.com/Slamdunk)) +- Always keep unseen: remove keepUnseen, add markAsSeen [\#217](https://github.com/ddeboer/imap/pull/217) ([Slamdunk](https://github.com/Slamdunk)) +- Embedded messages: refactor \#106 [\#216](https://github.com/ddeboer/imap/pull/216) ([Slamdunk](https://github.com/Slamdunk)) +- Headers now extends \ArrayIterator [\#215](https://github.com/ddeboer/imap/pull/215) ([Slamdunk](https://github.com/Slamdunk)) +- Implement imap\_mail\_copy [\#214](https://github.com/ddeboer/imap/pull/214) ([Slamdunk](https://github.com/Slamdunk)) +- Imap sort [\#213](https://github.com/ddeboer/imap/pull/213) ([Slamdunk](https://github.com/Slamdunk)) +- Increased code-coverage [\#211](https://github.com/ddeboer/imap/pull/211) ([Slamdunk](https://github.com/Slamdunk)) +- Update to PHPUnit ^6.2 [\#209](https://github.com/ddeboer/imap/pull/209) ([Slamdunk](https://github.com/Slamdunk)) +- Use specific exceptions to ease user catches [\#208](https://github.com/ddeboer/imap/pull/208) ([Slamdunk](https://github.com/Slamdunk)) +- Wrap Exception on invalid Date header [\#205](https://github.com/ddeboer/imap/pull/205) ([Slamdunk](https://github.com/Slamdunk)) +- Add tests for \#144 set flags functionalities [\#203](https://github.com/ddeboer/imap/pull/203) ([Slamdunk](https://github.com/Slamdunk)) +- Add imap\_fetchheader\(\) functionality to get raw headers [\#202](https://github.com/ddeboer/imap/pull/202) ([Slamdunk](https://github.com/Slamdunk)) +- Parse all email type headers [\#199](https://github.com/ddeboer/imap/pull/199) ([Slamdunk](https://github.com/Slamdunk)) +- Test search conditions [\#198](https://github.com/ddeboer/imap/pull/198) ([Slamdunk](https://github.com/Slamdunk)) +- Mailbox: get status [\#192](https://github.com/ddeboer/imap/pull/192) ([Slamdunk](https://github.com/Slamdunk)) +- SearchExpression is a Search\ConditionInterface [\#191](https://github.com/ddeboer/imap/pull/191) ([Slamdunk](https://github.com/Slamdunk)) +- SearchCondition: \_\_toString\(\) -\> toString\(\) [\#187](https://github.com/ddeboer/imap/pull/187) ([Slamdunk](https://github.com/Slamdunk)) +- Retain imap\_getmailboxes\(\) results [\#184](https://github.com/ddeboer/imap/pull/184) ([Slamdunk](https://github.com/Slamdunk)) +- Add type hints and return types [\#183](https://github.com/ddeboer/imap/pull/183) ([Slamdunk](https://github.com/Slamdunk)) +- Exception: increase verbosity with imap\_alerts\(\) and imap\_errors\(\) [\#182](https://github.com/ddeboer/imap/pull/182) ([Slamdunk](https://github.com/Slamdunk)) +- Add coding-standards [\#181](https://github.com/ddeboer/imap/pull/181) ([Slamdunk](https://github.com/Slamdunk)) +- Travis: re-enable code-coverage on scrutinizer [\#177](https://github.com/ddeboer/imap/pull/177) ([Slamdunk](https://github.com/Slamdunk)) +- Add .gitattributes to remove from releases unneded files [\#173](https://github.com/ddeboer/imap/pull/173) ([Slamdunk](https://github.com/Slamdunk)) +- Travis: use local Dovecot installation [\#170](https://github.com/ddeboer/imap/pull/170) ([Slamdunk](https://github.com/Slamdunk)) +- Need all Headers in string format [\#149](https://github.com/ddeboer/imap/pull/149) ([FlashWS](https://github.com/FlashWS)) +- Get raw mail [\#146](https://github.com/ddeboer/imap/pull/146) ([styxit](https://github.com/styxit)) +- add getBcc\(\), Set, Clear Flag\(\Seen, \Answered, \Flagged, \Deleted, and \Draft\), getHeadersRaw\(\) [\#144](https://github.com/ddeboer/imap/pull/144) ([trungpv93](https://github.com/trungpv93)) + +**Fixed bugs:** + +- Search\Condition needs charset escaping/indication [\#190](https://github.com/ddeboer/imap/issues/190) +- imap\_utf7\_\(encode|decode\) -\> mb\_convert\_encoding [\#185](https://github.com/ddeboer/imap/issues/185) +- España [\#176](https://github.com/ddeboer/imap/issues/176) +- getHeaders\(\) decode broke information [\#171](https://github.com/ddeboer/imap/issues/171) +- Date format for date search condition [\#168](https://github.com/ddeboer/imap/issues/168) +- Error when trying fetch messages from container [\#167](https://github.com/ddeboer/imap/issues/167) +- Attachment encoding error [\#158](https://github.com/ddeboer/imap/issues/158) +- getFilename\(\) is empty and no attachment, even when there is an attachment. [\#142](https://github.com/ddeboer/imap/issues/142) +- Encoding issues [\#136](https://github.com/ddeboer/imap/issues/136) +- URGENT: The timezone could not be found in the database [\#135](https://github.com/ddeboer/imap/issues/135) +- Incorrect transcoding of text attachments [\#132](https://github.com/ddeboer/imap/issues/132) +- Undefined offset [\#123](https://github.com/ddeboer/imap/issues/123) +- ICS file not supported as attachment [\#120](https://github.com/ddeboer/imap/issues/120) +- Should iconv be a requirement? [\#115](https://github.com/ddeboer/imap/issues/115) +- KeepUnseen doen't work [\#92](https://github.com/ddeboer/imap/issues/92) +- PHP Fatal error Failed to parse time string in ddeboer/imap/src/Message.php [\#89](https://github.com/ddeboer/imap/issues/89) +- encoding issue [\#85](https://github.com/ddeboer/imap/issues/85) +- keepUnseen not working correctly with Hotmail [\#84](https://github.com/ddeboer/imap/issues/84) +- Iconv Exception [\#78](https://github.com/ddeboer/imap/issues/78) +- $message-\>getAttachments\(\) doesn't recognize some attachments [\#74](https://github.com/ddeboer/imap/issues/74) +- Message::move\(\) doesn't work. [\#73](https://github.com/ddeboer/imap/issues/73) +- Message\Part: part number must distinguish original message [\#223](https://github.com/ddeboer/imap/pull/223) ([Slamdunk](https://github.com/Slamdunk)) +- Recursive Embedded email body bug [\#222](https://github.com/ddeboer/imap/pull/222) ([Slamdunk](https://github.com/Slamdunk)) +- Exclude HTML from allowed attachment subtype [\#212](https://github.com/ddeboer/imap/pull/212) ([Slamdunk](https://github.com/Slamdunk)) +- Fix imap\_mail\_move behaviour and test it [\#207](https://github.com/ddeboer/imap/pull/207) ([Slamdunk](https://github.com/Slamdunk)) +- Undefined encoding: throw exception [\#197](https://github.com/ddeboer/imap/pull/197) ([Slamdunk](https://github.com/Slamdunk)) +- Message charset: mb\_convert\_encoding + aliases [\#196](https://github.com/ddeboer/imap/pull/196) ([Slamdunk](https://github.com/Slamdunk)) +- Mailbox: only UTF-8 names [\#193](https://github.com/ddeboer/imap/pull/193) ([Slamdunk](https://github.com/Slamdunk)) +- Search\Date\AbstractDate: fix format to RFC-3501 [\#189](https://github.com/ddeboer/imap/pull/189) ([Slamdunk](https://github.com/Slamdunk)) +- Travis: fix failing tests [\#172](https://github.com/ddeboer/imap/pull/172) ([Slamdunk](https://github.com/Slamdunk)) +- Return body of single-part HTML message as HTML, not text [\#101](https://github.com/ddeboer/imap/pull/101) ([joker806](https://github.com/joker806)) +- Implement "undisclosed recipients" addresses [\#86](https://github.com/ddeboer/imap/pull/86) ([darit](https://github.com/darit)) + +**Closed issues:** + +- Potential memory issue with attachments [\#195](https://github.com/ddeboer/imap/issues/195) +- Explain Message::delete [\#175](https://github.com/ddeboer/imap/issues/175) +- Get raw message [\#161](https://github.com/ddeboer/imap/issues/161) +- Composer install problem [\#160](https://github.com/ddeboer/imap/issues/160) +- Transcoder not exist [\#154](https://github.com/ddeboer/imap/issues/154) +- The library doesn't support using sort by [\#151](https://github.com/ddeboer/imap/issues/151) +- Office 365 - Array to string conversion error [\#131](https://github.com/ddeboer/imap/issues/131) +- Is there a method to turn a seen message into an "unseen" one ? [\#130](https://github.com/ddeboer/imap/issues/130) +- Create mailbox [\#126](https://github.com/ddeboer/imap/issues/126) +- Move and Delete Message not working [\#112](https://github.com/ddeboer/imap/issues/112) +- Problem on production server [\#111](https://github.com/ddeboer/imap/issues/111) +- Authentication failed for a Gmail account [\#109](https://github.com/ddeboer/imap/issues/109) +- A method to run IMAP commands? [\#83](https://github.com/ddeboer/imap/issues/83) + +**Merged pull requests:** + +- Update README.md to latest develop changes [\#224](https://github.com/ddeboer/imap/pull/224) ([Slamdunk](https://github.com/Slamdunk)) +- Add Filippo Tessarotto as an author of the package [\#219](https://github.com/ddeboer/imap/pull/219) ([Slamdunk](https://github.com/Slamdunk)) +- README.md: call Connection::expunge after move and delete [\#210](https://github.com/ddeboer/imap/pull/210) ([Slamdunk](https://github.com/Slamdunk)) +- Remove misleading Mailbox::expunge\(\) [\#206](https://github.com/ddeboer/imap/pull/206) ([Slamdunk](https://github.com/Slamdunk)) +- Add CHANGELOG.md [\#194](https://github.com/ddeboer/imap/pull/194) ([Slamdunk](https://github.com/Slamdunk)) +- README.md updates [\#178](https://github.com/ddeboer/imap/pull/178) ([Slamdunk](https://github.com/Slamdunk)) + +## [0.5.2](https://github.com/ddeboer/imap/tree/0.5.2) (2015-12-03) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.5.1...0.5.2) + +**Closed issues:** + +- $message-\>getAttachments\(\) returns null if message has no attachments [\#80](https://github.com/ddeboer/imap/issues/80) +- Email objects visibility [\#76](https://github.com/ddeboer/imap/issues/76) + +**Merged pull requests:** + +- Fixed the keepUnseen method [\#95](https://github.com/ddeboer/imap/pull/95) ([aeyoll](https://github.com/aeyoll)) +- Mark Mailbox as countable, fix doc comments [\#91](https://github.com/ddeboer/imap/pull/91) ([krzysiekpiasecki](https://github.com/krzysiekpiasecki)) +- Message::getAttachments confirm to signature [\#82](https://github.com/ddeboer/imap/pull/82) ([boekkooi](https://github.com/boekkooi)) +- Added hasMailbox to Connection [\#81](https://github.com/ddeboer/imap/pull/81) ([boekkooi](https://github.com/boekkooi)) +- Make sure imap connection are reopened [\#79](https://github.com/ddeboer/imap/pull/79) ([joserobleda](https://github.com/joserobleda)) + +## [0.5.1](https://github.com/ddeboer/imap/tree/0.5.1) (2015-02-01) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.5.0...0.5.1) + +**Closed issues:** + +- imap\_open error [\#72](https://github.com/ddeboer/imap/issues/72) +- $message-\>getAttachments\(\) does not return anything, even though a message has at least one attachment [\#71](https://github.com/ddeboer/imap/issues/71) +- Prepare docs for 1.0 [\#69](https://github.com/ddeboer/imap/issues/69) +- "date" header is not reliable [\#63](https://github.com/ddeboer/imap/issues/63) +- File Attachments don't show up [\#55](https://github.com/ddeboer/imap/issues/55) + +**Merged pull requests:** + +- Add support for attachments without content disposition [\#70](https://github.com/ddeboer/imap/pull/70) ([ddeboer](https://github.com/ddeboer)) + +## [0.5.0](https://github.com/ddeboer/imap/tree/0.5.0) (2015-01-24) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.4.0...0.5.0) + +**Closed issues:** + +- Use utf8\_encode\(\) function to encode content [\#66](https://github.com/ddeboer/imap/issues/66) +- Please add function order by date [\#59](https://github.com/ddeboer/imap/issues/59) +- mb\_convert\_encoding breaks code [\#57](https://github.com/ddeboer/imap/issues/57) +- How get I getMessages but newest first ... [\#11](https://github.com/ddeboer/imap/issues/11) + +## [0.4.0](https://github.com/ddeboer/imap/tree/0.4.0) (2015-01-04) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.3.1...0.4.0) + +**Closed issues:** + +- Please add 6th parameter to imap\_open call [\#62](https://github.com/ddeboer/imap/issues/62) +- Should Message::delete\(\) use the Message UID? [\#46](https://github.com/ddeboer/imap/issues/46) +- mb\_convert\_encoding\(\): Illegal character encoding specified [\#35](https://github.com/ddeboer/imap/issues/35) +- Deleting a message isn't working [\#30](https://github.com/ddeboer/imap/issues/30) +- imap\_header doesn't work with message uid [\#26](https://github.com/ddeboer/imap/issues/26) + +**Merged pull requests:** + +- Added basic requirement [\#61](https://github.com/ddeboer/imap/pull/61) ([nikoskip](https://github.com/nikoskip)) +- FIX: PHP error: "Cannot declare class Ddeboer\Imap\Search\Text\Text ..." [\#58](https://github.com/ddeboer/imap/pull/58) ([racztiborzoltan](https://github.com/racztiborzoltan)) +- Message::delete sets the FT\_UID flag. Fixes \#30 Fixes \#46 [\#54](https://github.com/ddeboer/imap/pull/54) ([ctalbot](https://github.com/ctalbot)) +- Allow binary-encoded part content [\#48](https://github.com/ddeboer/imap/pull/48) ([joker806](https://github.com/joker806)) +- Fix CS [\#47](https://github.com/ddeboer/imap/pull/47) ([xelan](https://github.com/xelan)) +- fixed typo [\#45](https://github.com/ddeboer/imap/pull/45) ([xelan](https://github.com/xelan)) + +## [0.3.1](https://github.com/ddeboer/imap/tree/0.3.1) (2014-08-11) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.3.0...0.3.1) + +**Merged pull requests:** + +- \imap\_header dosen't work with UID [\#44](https://github.com/ddeboer/imap/pull/44) ([ysramirez](https://github.com/ysramirez)) + +## [0.3.0](https://github.com/ddeboer/imap/tree/0.3.0) (2014-08-10) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.2...0.3.0) + +**Closed issues:** + +- please remove useless wiki [\#42](https://github.com/ddeboer/imap/issues/42) +- Travis tests allways fail? [\#40](https://github.com/ddeboer/imap/issues/40) +- Garbled e-mail body encoding [\#27](https://github.com/ddeboer/imap/issues/27) +- Improve docs [\#25](https://github.com/ddeboer/imap/issues/25) +- "undisclosed-recipients" throws error [\#23](https://github.com/ddeboer/imap/issues/23) + +**Merged pull requests:** + +- correct minor typo [\#43](https://github.com/ddeboer/imap/pull/43) ([cordoval](https://github.com/cordoval)) +- Utf-8 encode body content. [\#39](https://github.com/ddeboer/imap/pull/39) ([cmoralesweb](https://github.com/cmoralesweb)) +- Fix regex parsing the date header \(allowing multiple brackets\) [\#38](https://github.com/ddeboer/imap/pull/38) ([joker806](https://github.com/joker806)) +- Allow empty connection flags [\#34](https://github.com/ddeboer/imap/pull/34) ([joker806](https://github.com/joker806)) +- Fixed typo [\#32](https://github.com/ddeboer/imap/pull/32) ([abhinavkumar940](https://github.com/abhinavkumar940)) + +## [0.2](https://github.com/ddeboer/imap/tree/0.2) (2013-11-24) +[Full Changelog](https://github.com/ddeboer/imap/compare/0.1...0.2) + +## [0.1](https://github.com/ddeboer/imap/tree/0.1) (2013-11-22) +**Closed issues:** + +- Prevent setting SEEN flag [\#20](https://github.com/ddeboer/imap/issues/20) +- Add tests [\#18](https://github.com/ddeboer/imap/issues/18) +- delete messages [\#9](https://github.com/ddeboer/imap/issues/9) +- README is missing basic usage [\#7](https://github.com/ddeboer/imap/issues/7) +- Subject and other texts are decoded incorrectly [\#3](https://github.com/ddeboer/imap/issues/3) + +**Merged pull requests:** + +- also fetch inline attachments [\#24](https://github.com/ddeboer/imap/pull/24) ([kaiserlos](https://github.com/kaiserlos)) +- since leading slash is always needed [\#22](https://github.com/ddeboer/imap/pull/22) ([huglester](https://github.com/huglester)) +- Added missed createMailbox\($name\) function [\#19](https://github.com/ddeboer/imap/pull/19) ([burci](https://github.com/burci)) +- Added move and delete function to message + expunge function [\#17](https://github.com/ddeboer/imap/pull/17) ([burci](https://github.com/burci)) +- Clean up some unused variable [\#16](https://github.com/ddeboer/imap/pull/16) ([burci](https://github.com/burci)) +- Fixed mailbox encoding [\#15](https://github.com/ddeboer/imap/pull/15) ([burci](https://github.com/burci)) +- Create new mailbox [\#14](https://github.com/ddeboer/imap/pull/14) ([burci](https://github.com/burci)) +- Fixed bug in getDecodedContent with 'format=flowed' email [\#13](https://github.com/ddeboer/imap/pull/13) ([burci](https://github.com/burci)) +- Fixed date parsing for some imap servers [\#12](https://github.com/ddeboer/imap/pull/12) ([thelfensdrfer](https://github.com/thelfensdrfer)) +- Add support for more complex search expressions. [\#10](https://github.com/ddeboer/imap/pull/10) ([jamesiarmes](https://github.com/jamesiarmes)) +- Allow user to change server connection flags [\#6](https://github.com/ddeboer/imap/pull/6) ([mvar](https://github.com/mvar)) +- Improvements in EmailAddress class [\#4](https://github.com/ddeboer/imap/pull/4) ([mvar](https://github.com/mvar)) + + + +\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/data/web/inc/lib/vendor/ddeboer/imap/LICENSE b/data/web/inc/lib/vendor/ddeboer/imap/LICENSE new file mode 100644 index 00000000..2c679e30 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2013 David de Boer + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/data/web/inc/lib/vendor/ddeboer/imap/README.md b/data/web/inc/lib/vendor/ddeboer/imap/README.md new file mode 100644 index 00000000..24319366 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/README.md @@ -0,0 +1,347 @@ +# IMAP library + +[![Build Status](https://travis-ci.org/ddeboer/imap.svg?branch=master)](https://travis-ci.org/ddeboer/imap) +[![Code Coverage](https://scrutinizer-ci.com/g/ddeboer/imap/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/ddeboer/imap/?branch=master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ddeboer/imap/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/ddeboer/imap/?branch=master) +[![Latest Stable Version](https://poser.pugx.org/ddeboer/imap/v/stable.svg)](https://packagist.org/packages/ddeboer/imap) +[![Total Downloads](https://poser.pugx.org/ddeboer/imap/downloads.png)](https://packagist.org/packages/ddeboer/imap) + +A PHP 7.0+ library to read and process e-mails over IMAP. + +This library requires [IMAP](https://secure.php.net/manual/en/book.imap.php), +[iconv](https://secure.php.net/manual/en/book.iconv.php) and +[Multibyte String](https://secure.php.net/manual/en/book.mbstring.php) extensions installed. + +## Table of Contents + +1. [Installation](#installation) +1. [Usage](#usage) + 1. [Connect and Authenticate](#connect-and-authenticate) + 1. [Mailboxes](#mailboxes) + 1. [Messages](#messages) + 1. [Searching for Messages](#searching-for-messages) + 1. [Unknown search criterion: OR](#unknown-search-criterion-or) + 1. [Message Properties and Operations](#message-properties-and-operations) + 1. [Message Attachments](#message-attachments) + 1. [Embedded Messages](#embedded-messages) + 1. [Timeouts](#timeouts) +1. [Mock the library](#mock-the-library) +1. [Running the Tests](#running-the-tests) + +## Installation + +The recommended way to install the IMAP library is through [Composer](https://getcomposer.org): + +```bash +$ composer require ddeboer/imap +``` + +This command requires you to have Composer installed globally, as explained +in the [installation chapter](https://getcomposer.org/doc/00-intro.md) +of the Composer documentation. + +## Usage + +### Connect and Authenticate + +```php +use Ddeboer\Imap\Server; + +$server = new Server('imap.gmail.com'); + +// $connection is instance of \Ddeboer\Imap\Connection +$connection = $server->authenticate('my_username', 'my_password'); +``` + +You can specify port, [flags and parameters](https://secure.php.net/manual/en/function.imap-open.php) +to the server: + +```php +$server = new Server( + $hostname, // required + $port, // defaults to '993' + $flags, // defaults to '/imap/ssl/validate-cert' + $parameters +); +``` + +### Mailboxes + +Retrieve mailboxes (also known as mail folders) from the mail server and iterate +over them: + +```php +$mailboxes = $connection->getMailboxes(); + +foreach ($mailboxes as $mailbox) { + // Skip container-only mailboxes + // @see https://secure.php.net/manual/en/function.imap-getmailboxes.php + if ($mailbox->getAttributes() & \LATT_NOSELECT) { + continue; + } + + // $mailbox is instance of \Ddeboer\Imap\Mailbox + printf('Mailbox "%s" has %s messages', $mailbox->getName(), $mailbox->count()); +} +``` + +Or retrieve a specific mailbox: + +```php +$mailbox = $connection->getMailbox('INBOX'); +``` + +Delete a mailbox: + +```php +$connection->deleteMailbox($mailbox); +``` + +You can bulk set, or clear, any [flag](https://secure.php.net/manual/en/function.imap-setflag-full.php) of mailbox messages (by UIDs): + +```php +$mailbox->setFlag('\\Seen \\Flagged', ['1:5', '7', '9']); +$mailbox->setFlag('\\Seen', '1,3,5,6:8'); + +$mailbox->clearFlag('\\Flagged', '1,3'); +``` + +**WARNING** You must retrieve new Message instances in case of bulk modify flags to refresh the single Messages flags. + +### Messages + +Retrieve messages (e-mails) from a mailbox and iterate over them: + +```php +$messages = $mailbox->getMessages(); + +foreach ($messages as $message) { + // $message is instance of \Ddeboer\Imap\Message +} +``` + +To insert a new message (that just has been sent) into the Sent mailbox and flag it as seen: + +```php +$mailbox = $connection->getMailbox('Sent'); +$mailbox->addMessage($messageMIME, '\\Seen'); +``` + +Note that the message should be a string at MIME format (as described in the [RFC2045](https://tools.ietf.org/html/rfc2045)). + +#### Searching for Messages + +```php +use Ddeboer\Imap\SearchExpression; +use Ddeboer\Imap\Search\Email\To; +use Ddeboer\Imap\Search\Text\Body; + +$search = new SearchExpression(); +$search->addCondition(new To('me@here.com')); +$search->addCondition(new Body('contents')); + +$messages = $mailbox->getMessages($search); +``` + +**WARNING** We are currently unable to have both spaces _and_ double-quotes +escaped together. Only spaces are currently escaped correctly. +You can use `Ddeboer\Imap\Search\RawExpression` to write the complete search +condition by yourself. + +Messages can also be retrieved sorted as per [imap_sort](https://secure.php.net/manual/en/function.imap-sort.php) +function: + +```php +$today = new DateTimeImmutable(); +$lastMonth = $today->sub(new DateInterval('P30D')); + +$messages = $mailbox->getMessages( + new Ddeboer\Imap\Search\Date\Since($lastMonth), + \SORTDATE, // Sort criteria + true // Descending order +); +``` + +#### Unknown search criterion: OR + +Note that PHP imap library relies on the `c-client` library available at https://www.washington.edu/imap/ +which doesn't fully support some IMAP4 search criteria like `OR`. If you want those unsupported criteria, +you need to manually patch the latest version (`imap-2007f` of 23-Jul-2011 at the time of this commit) +and recompile PHP onto your patched `c-client` library. + +By the way most of the common search criteria are available and functioning, browse them in `./src/Search`. + +References: + +1. https://stackoverflow.com/questions/36356715/imap-search-unknown-search-criterion-or +1. imap-2007f.tar.gz: `./src/c-client/mail.c` and `./docs/internal.txt` + +#### Message Properties and Operations + +Get message number and unique [message id](https://en.wikipedia.org/wiki/Message-ID) +in the form <...>: + +```php +$message->getNumber(); +$message->getId(); +``` + +Get other message properties: + +```php +$message->getSubject(); +$message->getFrom(); // Message\EmailAddress +$message->getTo(); // array of Message\EmailAddress +$message->getDate(); // DateTimeImmutable +$message->isAnswered(); +$message->isDeleted(); +$message->isDraft(); +$message->isSeen(); +``` + +Get message headers as a [\Ddeboer\Imap\Message\Headers](/src/Ddeboer/Imap/Message/Headers.php) object: + +```php +$message->getHeaders(); +``` + +Get message body as HTML or plain text: + +```php +$message->getBodyHtml(); // Content of text/html part, if present +$message->getBodyText(); // Content of text/plain part, if present +``` + +Reading the message body keeps the message as unseen. +If you want to mark the message as seen: + +```php +$message->markAsSeen(); +``` + +Or you can set, or clear, any [flag](https://secure.php.net/manual/en/function.imap-setflag-full.php): + +```php +$message->setFlag('\\Seen \\Flagged'); +$message->clearFlag('\\Flagged'); +``` + +Move a message to another mailbox: + +```php +$mailbox = $connection->getMailbox('another-mailbox'); +$message->move($mailbox); +$connection->expunge(); +``` + +Deleting messages: + +```php +$mailbox->getMessage(1)->delete(); +$mailbox->getMessage(2)->delete(); +$connection->expunge(); +``` + +### Message Attachments + +Get message attachments (both inline and attached) and iterate over them: + +```php +$attachments = $message->getAttachments(); + +foreach ($attachments as $attachment) { + // $attachment is instance of \Ddeboer\Imap\Message\Attachment +} +``` + +Download a message attachment to a local file: + +```php +// getDecodedContent() decodes the attachment’s contents automatically: +file_put_contents( + '/my/local/dir/' . $attachment->getFilename(), + $attachment->getDecodedContent() +); +``` + +### Embedded Messages + +Check if attachment is embedded message and get it: + +```php +$attachments = $message->getAttachments(); + +foreach ($attachments as $attachment) { + if ($attachment->isEmbeddedMessage()) { + $embeddedMessage = $attachment->getEmbeddedMessage(); + // $embeddedMessage is instance of \Ddeboer\Imap\Message\EmbeddedMessage + } +} +``` + +An EmbeddedMessage has the same API as a normal Message, apart from flags +and operations like copy, move or delete. + +### Timeouts + +The IMAP extension provides the [imap_timeout](https://secure.php.net/manual/en/function.imap-timeout.php) +function to adjust the timeout seconds for various operations. + +However the extension's implementation doesn't link the functionality to a +specific context or connection, instead they are global. So in order to not +affect functionalities outside this library, we had to choose whether wrap +every `imap_*` call around an optional user-provided timeout or leave this +task to the user. + +Because of the heterogeneous world of IMAP servers and the high complexity +burden cost for such a little gain of the former, we chose the latter. + +## Mock the library + +Mockability is granted by interfaces present for each API. +Dig into [MockabilityTest](tests/MockabilityTest.php) for an example of a +mocked workflow. + +## Running the Tests + +This library is functionally tested on [Travis CI](https://travis-ci.org/ddeboer/imap) +against a local Dovecot server. + +If you have your own IMAP (test) account, you can run the tests locally by +providing your IMAP credentials: + +```bash +$ composer install +$ IMAP_SERVER_NAME="my.imap.server.com" IMAP_SERVER_PORT="60993" IMAP_USERNAME="johndoe" IMAP_PASSWORD="p4ssword" vendor/bin/phpunit +``` + +You can also copy `phpunit.xml.dist` file to a custom `phpunit.xml` and put +these environment variables in it: + +```xml + + + + + ./tests/ + + + + + ./src + + + + + + + + + +``` + +**WARNING** Tests create new mailboxes without removing them. diff --git a/data/web/inc/lib/vendor/ddeboer/imap/composer.json b/data/web/inc/lib/vendor/ddeboer/imap/composer.json new file mode 100644 index 00000000..b7303c18 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/composer.json @@ -0,0 +1,47 @@ +{ + "name": "ddeboer/imap", + "description": "Object-oriented IMAP for PHP", + "keywords": [ + "email", + "mail", + "imap" + ], + "license": "MIT", + "authors": [ + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com" + }, + { + "name": "Community contributors", + "homepage": "https://github.com/ddeboer/imap/graphs/contributors" + } + ], + "require": { + "php": "^7.0", + "ext-iconv": "*", + "ext-imap": "*", + "ext-mbstring": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.10", + "phpstan/phpstan": "^0.9.1", + "phpstan/phpstan-phpunit": "^0.9.3", + "phpunit/phpunit": "^6.5 || ^7.0", + "zendframework/zend-mail": "^2.8" + }, + "autoload": { + "psr-4": { + "Ddeboer\\Imap\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Ddeboer\\Imap\\Tests\\": "tests/" + } + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php new file mode 100644 index 00000000..84963c3a --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Connection.php @@ -0,0 +1,210 @@ +resource = $resource; + $this->server = $server; + } + + /** + * Get IMAP resource. + * + * @return ImapResourceInterface + */ + public function getResource(): ImapResourceInterface + { + return $this->resource; + } + + /** + * Delete all messages marked for deletion. + * + * @return bool + */ + public function expunge(): bool + { + return \imap_expunge($this->resource->getStream()); + } + + /** + * Close connection. + * + * @param int $flag + * + * @return bool + */ + public function close(int $flag = 0): bool + { + return \imap_close($this->resource->getStream(), $flag); + } + + /** + * Get a list of mailboxes (also known as folders). + * + * @return MailboxInterface[] + */ + public function getMailboxes(): array + { + $this->initMailboxNames(); + + if (null === $this->mailboxes) { + $this->mailboxes = []; + foreach ($this->mailboxNames as $mailboxName => $mailboxInfo) { + $this->mailboxes[$mailboxName] = $this->getMailbox($mailboxName); + } + } + + return $this->mailboxes; + } + + /** + * Check that a mailbox with the given name exists. + * + * @param string $name Mailbox name + * + * @return bool + */ + public function hasMailbox(string $name): bool + { + $this->initMailboxNames(); + + return isset($this->mailboxNames[$name]); + } + + /** + * Get a mailbox by its name. + * + * @param string $name Mailbox name + * + * @throws MailboxDoesNotExistException If mailbox does not exist + * + * @return MailboxInterface + */ + public function getMailbox(string $name): MailboxInterface + { + if (false === $this->hasMailbox($name)) { + throw new MailboxDoesNotExistException(\sprintf('Mailbox name "%s" does not exist', $name)); + } + + return new Mailbox($this->resource, $name, $this->mailboxNames[$name]); + } + + /** + * Count number of messages not in any mailbox. + * + * @return int + */ + public function count() + { + return \imap_num_msg($this->resource->getStream()); + } + + /** + * Check if the connection is still active. + * + * @throws InvalidResourceException If connection was closed + * + * @return bool + */ + public function ping(): bool + { + return \imap_ping($this->resource->getStream()); + } + + /** + * Create mailbox. + * + * @param string $name + * + * @throws CreateMailboxException + * + * @return MailboxInterface + */ + public function createMailbox(string $name): MailboxInterface + { + if (false === \imap_createmailbox($this->resource->getStream(), $this->server . \mb_convert_encoding($name, 'UTF7-IMAP', 'UTF-8'))) { + throw new CreateMailboxException(\sprintf('Can not create "%s" mailbox at "%s"', $name, $this->server)); + } + + $this->mailboxNames = $this->mailboxes = null; + $this->resource->clearLastMailboxUsedCache(); + + return $this->getMailbox($name); + } + + /** + * Create mailbox. + * + * @param MailboxInterface $mailbox + * + * @throws DeleteMailboxException + */ + public function deleteMailbox(MailboxInterface $mailbox) + { + if (false === \imap_deletemailbox($this->resource->getStream(), $mailbox->getFullEncodedName())) { + throw new DeleteMailboxException(\sprintf('Mailbox "%s" could not be deleted', $mailbox->getName())); + } + + $this->mailboxes = $this->mailboxNames = null; + $this->resource->clearLastMailboxUsedCache(); + } + + /** + * Get mailbox names. + */ + private function initMailboxNames() + { + if (null !== $this->mailboxNames) { + return; + } + + $this->mailboxNames = []; + $mailboxesInfo = \imap_getmailboxes($this->resource->getStream(), $this->server, '*'); + foreach ($mailboxesInfo as $mailboxInfo) { + $name = \mb_convert_encoding(\str_replace($this->server, '', $mailboxInfo->name), 'UTF-8', 'UTF7-IMAP'); + $this->mailboxNames[$name] = $mailboxInfo; + } + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php new file mode 100644 index 00000000..b2b56ead --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ConnectionInterface.php @@ -0,0 +1,82 @@ + 'E_ERROR', + \E_WARNING => 'E_WARNING', + \E_PARSE => 'E_PARSE', + \E_NOTICE => 'E_NOTICE', + \E_CORE_ERROR => 'E_CORE_ERROR', + \E_CORE_WARNING => 'E_CORE_WARNING', + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', + \E_USER_ERROR => 'E_USER_ERROR', + \E_USER_WARNING => 'E_USER_WARNING', + \E_USER_NOTICE => 'E_USER_NOTICE', + \E_STRICT => 'E_STRICT', + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + \E_DEPRECATED => 'E_DEPRECATED', + \E_USER_DEPRECATED => 'E_USER_DEPRECATED', + ]; + + /** + * @param string $message The exception message + * @param int $code The exception code + * @param \Throwable $previous The previous exception + */ + final public function __construct(string $message, int $code = 0, \Throwable $previous = null) + { + $errorType = ''; + if (\is_int($code) && isset(self::$errorLabels[$code])) { + $errorType = \sprintf('[%s] ', self::$errorLabels[$code]); + } + + $joinString = "\n- "; + $alerts = \imap_alerts(); + $errors = \imap_errors(); + $completeMessage = \sprintf( + "%s%s\nimap_alerts (%s):%s\nimap_errors (%s):%s", + $errorType, + $message, + $alerts ? \count($alerts) : 0, + $alerts ? $joinString . \implode($joinString, $alerts) : '', + $errors ? \count($errors) : 0, + $errors ? $joinString . \implode($joinString, $errors) : '' + ); + + parent::__construct($completeMessage, $code, $previous); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AuthenticationFailedException.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AuthenticationFailedException.php new file mode 100644 index 00000000..c0e93d00 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Exception/AuthenticationFailedException.php @@ -0,0 +1,9 @@ +resource = $resource; + $this->mailbox = $mailbox; + } + + /** + * Get IMAP resource stream. + * + * @throws InvalidResourceException + * + * @return resource + */ + public function getStream() + { + if (false === \is_resource($this->resource) || 'imap' !== \get_resource_type($this->resource)) { + throw new InvalidResourceException('Supplied resource is not a valid imap resource'); + } + + $this->initMailbox(); + + return $this->resource; + } + + /** + * Clear last mailbox used cache. + */ + public function clearLastMailboxUsedCache() + { + self::$lastMailboxUsedCache = null; + } + + /** + * If connection is not currently in this mailbox, switch it to this mailbox. + */ + private function initMailbox() + { + if (null === $this->mailbox || $this->isMailboxOpen()) { + return; + } + + \imap_reopen($this->resource, $this->mailbox->getFullEncodedName()); + + if ($this->isMailboxOpen()) { + return; + } + + throw new ReopenMailboxException(\sprintf('Cannot reopen mailbox "%s"', $this->mailbox->getName())); + } + + /** + * Check whether the current mailbox is open. + * + * @return bool + */ + private function isMailboxOpen(): bool + { + $currentMailboxName = $this->mailbox->getFullEncodedName(); + if ($currentMailboxName === self::$lastMailboxUsedCache) { + return true; + } + + self::$lastMailboxUsedCache = null; + $check = \imap_check($this->resource); + $return = false !== $check && $check->Mailbox === $currentMailboxName; + + if (true === $return) { + self::$lastMailboxUsedCache = $currentMailboxName; + } + + return $return; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php new file mode 100644 index 00000000..5a3fead4 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ImapResourceInterface.php @@ -0,0 +1,20 @@ +resource = new ImapResource($resource->getStream(), $this); + $this->name = $name; + $this->info = $info; + } + + /** + * Get mailbox decoded name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Get mailbox encoded path. + * + * @return string + */ + public function getEncodedName(): string + { + return \preg_replace('/^{.+}/', '', $this->info->name); + } + + /** + * Get mailbox encoded full name. + * + * @return string + */ + public function getFullEncodedName(): string + { + return $this->info->name; + } + + /** + * Get mailbox attributes. + * + * @return int + */ + public function getAttributes(): int + { + return $this->info->attributes; + } + + /** + * Get mailbox delimiter. + * + * @return string + */ + public function getDelimiter(): string + { + return $this->info->delimiter; + } + + /** + * Get number of messages in this mailbox. + * + * @return int + */ + public function count() + { + return \imap_num_msg($this->resource->getStream()); + } + + /** + * Get Mailbox status. + * + * @param null|int $flags + * + * @return \stdClass + */ + public function getStatus(int $flags = null): \stdClass + { + return \imap_status($this->resource->getStream(), $this->getFullEncodedName(), $flags ?? \SA_ALL); + } + + /** + * Bulk Set Flag for Messages. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * @param array|MessageIterator|string $numbers Message numbers + * + * @return bool + */ + public function setFlag(string $flag, $numbers): bool + { + return \imap_setflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID); + } + + /** + * Bulk Clear Flag for Messages. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * @param array|MessageIterator|string $numbers Message numbers + * + * @return bool + */ + public function clearFlag(string $flag, $numbers): bool + { + return \imap_clearflag_full($this->resource->getStream(), $this->prepareMessageIds($numbers), $flag, \ST_UID); + } + + /** + * Get message ids. + * + * @param ConditionInterface $search Search expression (optional) + * + * @return MessageIteratorInterface + */ + public function getMessages(ConditionInterface $search = null, int $sortCriteria = null, bool $descending = false): MessageIteratorInterface + { + if (null === $search) { + $search = new All(); + } + $query = $search->toString(); + + // We need to clear the stack to know whether imap_last_error() + // is related to this imap_search + \imap_errors(); + + if (null !== $sortCriteria) { + $messageNumbers = \imap_sort($this->resource->getStream(), $sortCriteria, $descending ? 1 : 0, \SE_UID, $query); + } else { + $messageNumbers = \imap_search($this->resource->getStream(), $query, \SE_UID); + } + if (false === $messageNumbers) { + if (false !== \imap_last_error()) { + throw new InvalidSearchCriteriaException(\sprintf('Invalid search criteria [%s]', $query)); + } + + // imap_search can also return false + $messageNumbers = []; + } + + return new MessageIterator($this->resource, $messageNumbers); + } + + /** + * Get a message by message number. + * + * @param int $number Message number + * + * @return MessageInterface + */ + public function getMessage(int $number): MessageInterface + { + return new Message($this->resource, $number); + } + + /** + * Get messages in this mailbox. + * + * @return MessageIteratorInterface + */ + public function getIterator(): MessageIteratorInterface + { + return $this->getMessages(); + } + + /** + * Add a message to the mailbox. + * + * @param string $message + * @param null|string $options + * @param null|DateTimeInterface $internalDate + * + * @return bool + */ + public function addMessage(string $message, string $options = null, DateTimeInterface $internalDate = null): bool + { + $arguments = [ + $this->resource->getStream(), + $this->getFullEncodedName(), + $message, + ]; + if (null !== $options) { + $arguments[] = $options; + if (null !== $internalDate) { + $arguments[] = $internalDate->format('d-M-Y H:i:s O'); + } + } + + return \imap_append(...$arguments); + } + + /** + * Returns a tree of threaded message for the current Mailbox. + * + * @return array + */ + public function getThread(): array + { + \set_error_handler(function () {}); + + $tree = \imap_thread($this->resource->getStream()); + + \restore_error_handler(); + + return false !== $tree ? $tree : []; + } + + /** + * Bulk move messages. + * + * @param array|MessageIterator|string $numbers Message numbers + * @param MailboxInterface $mailbox Destination Mailbox to move the messages to + * + * @throws \Ddeboer\Imap\Exception\MessageMoveException + */ + public function move($numbers, MailboxInterface $mailbox) + { + if (!\imap_mail_move($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageMoveException(\sprintf('Messages cannot be moved to "%s"', $mailbox->getName())); + } + } + + /** + * Bulk copy messages. + * + * @param array|MessageIterator|string $numbers Message numbers + * @param MailboxInterface $mailbox Destination Mailbox to copy the messages to + * + * @throws \Ddeboer\Imap\Exception\MessageCopyException + */ + public function copy($numbers, MailboxInterface $mailbox) + { + if (!\imap_mail_copy($this->resource->getStream(), $this->prepareMessageIds($numbers), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageCopyException(\sprintf('Messages cannot be copied to "%s"', $mailbox->getName())); + } + } + + /** + * Prepare message ids for the use with bulk functions. + * + * @param array|MessageIterator|string $messageIds Message numbers + * + * @return string + */ + private function prepareMessageIds($messageIds): string + { + if ($messageIds instanceof MessageIterator) { + $messageIds = $messageIds->getArrayCopy(); + } + + if (\is_array($messageIds)) { + $messageIds = \implode(',', $messageIds); + } + + return (string) $messageIds; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php new file mode 100644 index 00000000..91474920 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MailboxInterface.php @@ -0,0 +1,141 @@ +structureLoaded) { + return; + } + $this->structureLoaded = true; + + $messageNumber = $this->getNumber(); + + $errorMessage = null; + $errorNumber = 0; + \set_error_handler(function ($nr, $message) use (&$errorMessage, &$errorNumber) { + $errorMessage = $message; + $errorNumber = $nr; + }); + + $structure = \imap_fetchstructure( + $this->resource->getStream(), + $messageNumber, + \FT_UID + ); + + \restore_error_handler(); + + if (!$structure instanceof \stdClass) { + throw new MessageStructureException(\sprintf( + 'Message "%s" structure is empty: %s', + $messageNumber, + $errorMessage + ), $errorNumber); + } + + $this->setStructure($structure); + } + + /** + * Ensure message exists. + * + * @param int $messageNumber + */ + protected function assertMessageExists(int $messageNumber) + { + if (true === $this->messageNumberVerified) { + return; + } + $this->messageNumberVerified = true; + + $msgno = \imap_msgno($this->resource->getStream(), $messageNumber); + if (\is_numeric($msgno) && $msgno > 0) { + return; + } + + throw new MessageDoesNotExistException(\sprintf( + 'Message "%s" does not exist', + $messageNumber + )); + } + + /** + * Get raw message headers. + * + * @return string + */ + public function getRawHeaders(): string + { + if (null === $this->rawHeaders) { + $this->rawHeaders = \imap_fetchheader($this->resource->getStream(), $this->getNumber(), \FT_UID); + } + + return $this->rawHeaders; + } + + /** + * Get the raw message, including all headers, parts, etc. unencoded and unparsed. + * + * @return string the raw message + */ + public function getRawMessage(): string + { + if (null === $this->rawMessage) { + $this->rawMessage = $this->doGetContent(''); + } + + return $this->rawMessage; + } + + /** + * Get message headers. + * + * @return Message\Headers + */ + public function getHeaders(): Message\Headers + { + if (null === $this->headers) { + // imap_headerinfo is much faster than imap_fetchheader + // imap_headerinfo returns only a subset of all mail headers, + // but it does include the message flags. + $headers = \imap_headerinfo($this->resource->getStream(), \imap_msgno($this->resource->getStream(), $this->getNumber())); + if (false === $headers) { + // @see https://github.com/ddeboer/imap/issues/358 + throw new InvalidHeadersException(\sprintf('Message "%s" has invalid headers', $this->getNumber())); + } + $this->headers = new Message\Headers($headers); + } + + return $this->headers; + } + + /** + * Clearmessage headers. + */ + private function clearHeaders() + { + $this->headers = null; + } + + /** + * Get message recent flag value (from headers). + * + * @return null|string + */ + public function isRecent() + { + return $this->getHeaders()->get('recent'); + } + + /** + * Get message unseen flag value (from headers). + * + * @return bool + */ + public function isUnseen(): bool + { + return 'U' === $this->getHeaders()->get('unseen'); + } + + /** + * Get message flagged flag value (from headers). + * + * @return bool + */ + public function isFlagged(): bool + { + return 'F' === $this->getHeaders()->get('flagged'); + } + + /** + * Get message answered flag value (from headers). + * + * @return bool + */ + public function isAnswered(): bool + { + return 'A' === $this->getHeaders()->get('answered'); + } + + /** + * Get message deleted flag value (from headers). + * + * @return bool + */ + public function isDeleted(): bool + { + return 'D' === $this->getHeaders()->get('deleted'); + } + + /** + * Get message draft flag value (from headers). + * + * @return bool + */ + public function isDraft(): bool + { + return 'X' === $this->getHeaders()->get('draft'); + } + + /** + * Has the message been marked as read? + * + * @return bool + */ + public function isSeen(): bool + { + return 'N' !== $this->getHeaders()->get('recent') && 'U' !== $this->getHeaders()->get('unseen'); + } + + /** + * Mark message as seen. + * + * @return bool + * + * @deprecated since version 1.1, to be removed in 2.0 + */ + public function maskAsSeen(): bool + { + \trigger_error(\sprintf('%s is deprecated and will be removed in 2.0. Use %s::markAsSeen instead.', __METHOD__, __CLASS__), \E_USER_DEPRECATED); + + return $this->markAsSeen(); + } + + /** + * Mark message as seen. + * + * @return bool + */ + public function markAsSeen(): bool + { + return $this->setFlag('\\Seen'); + } + + /** + * Move message to another mailbox. + * + * @param MailboxInterface $mailbox + * + * @throws MessageCopyException + */ + public function copy(MailboxInterface $mailbox) + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to true on header + $this->clearHeaders(); + + if (!\imap_mail_copy($this->resource->getStream(), (string) $this->getNumber(), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageCopyException(\sprintf('Message "%s" cannot be copied to "%s"', $this->getNumber(), $mailbox->getName())); + } + } + + /** + * Move message to another mailbox. + * + * @param MailboxInterface $mailbox + * + * @throws MessageMoveException + */ + public function move(MailboxInterface $mailbox) + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to true on header + $this->clearHeaders(); + + if (!\imap_mail_move($this->resource->getStream(), (string) $this->getNumber(), $mailbox->getEncodedName(), \CP_UID)) { + throw new MessageMoveException(\sprintf('Message "%s" cannot be moved to "%s"', $this->getNumber(), $mailbox->getName())); + } + } + + /** + * Delete message. + * + * @throws MessageDeleteException + */ + public function delete() + { + // 'deleted' header changed, force to reload headers, would be better to set deleted flag to true on header + $this->clearHeaders(); + + if (!\imap_delete($this->resource->getStream(), $this->getNumber(), \FT_UID)) { + throw new MessageDeleteException(\sprintf('Message "%s" cannot be deleted', $this->getNumber())); + } + } + + /** + * Set Flag Message. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * + * @return bool + */ + public function setFlag(string $flag): bool + { + $result = \imap_setflag_full($this->resource->getStream(), (string) $this->getNumber(), $flag, \ST_UID); + + $this->clearHeaders(); + + return $result; + } + + /** + * Clear Flag Message. + * + * @param string $flag \Seen, \Answered, \Flagged, \Deleted, and \Draft + * + * @return bool + */ + public function clearFlag(string $flag): bool + { + $result = \imap_clearflag_full($this->resource->getStream(), (string) $this->getNumber(), $flag, \ST_UID); + + $this->clearHeaders(); + + return $result; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php new file mode 100644 index 00000000..89962f96 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php @@ -0,0 +1,292 @@ + + * + * @return null|string + */ + final public function getId() + { + return $this->getHeaders()->get('message_id'); + } + + /** + * Get message sender (from headers). + * + * @return null|EmailAddress + */ + final public function getFrom() + { + $from = $this->getHeaders()->get('from'); + + return null !== $from ? $this->decodeEmailAddress($from[0]) : null; + } + + /** + * Get To recipients. + * + * @return EmailAddress[] Empty array in case message has no To: recipients + */ + final public function getTo(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('to') ?: []); + } + + /** + * Get Cc recipients. + * + * @return EmailAddress[] Empty array in case message has no CC: recipients + */ + final public function getCc(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('cc') ?: []); + } + + /** + * Get Bcc recipients. + * + * @return EmailAddress[] Empty array in case message has no BCC: recipients + */ + final public function getBcc(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('bcc') ?: []); + } + + /** + * Get Reply-To recipients. + * + * @return EmailAddress[] Empty array in case message has no Reply-To: recipients + */ + final public function getReplyTo(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('reply_to') ?: []); + } + + /** + * Get Sender. + * + * @return EmailAddress[] Empty array in case message has no Sender: recipients + */ + final public function getSender(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('sender') ?: []); + } + + /** + * Get Return-Path. + * + * @return EmailAddress[] Empty array in case message has no Return-Path: recipients + */ + final public function getReturnPath(): array + { + return $this->decodeEmailAddresses($this->getHeaders()->get('return_path') ?: []); + } + + /** + * Get date (from headers). + * + * @return null|\DateTimeImmutable + */ + final public function getDate() + { + $dateHeader = $this->getHeaders()->get('date'); + if (null === $dateHeader) { + return null; + } + + $alteredValue = $dateHeader; + $alteredValue = \str_replace(',', '', $alteredValue); + $alteredValue = \preg_replace('/^[a-zA-Z]+ ?/', '', $alteredValue); + $alteredValue = \preg_replace('/ +\(.*\)/', '', $alteredValue); + $alteredValue = \preg_replace('/\bUT\b/', 'UTC', $alteredValue); + if (0 === \preg_match('/\d\d:\d\d:\d\d.* [\+\-]\d\d:?\d\d/', $alteredValue)) { + $alteredValue .= ' +0000'; + } + + try { + $date = new \DateTimeImmutable($alteredValue); + } catch (\Throwable $ex) { + throw new InvalidDateHeaderException(\sprintf('Invalid Date header found: "%s"', $dateHeader), 0, $ex); + } + + return $date; + } + + /** + * Get message size (from headers). + * + * @return null|int|string + */ + final public function getSize() + { + return $this->getHeaders()->get('size'); + } + + /** + * Get message subject (from headers). + * + * @return null|string + */ + final public function getSubject() + { + return $this->getHeaders()->get('subject'); + } + + /** + * Get message In-Reply-To (from headers). + * + * @return array + */ + final public function getInReplyTo(): array + { + $inReplyTo = $this->getHeaders()->get('in_reply_to'); + + return null !== $inReplyTo ? \explode(' ', $inReplyTo) : []; + } + + /** + * Get message References (from headers). + * + * @return array + */ + final public function getReferences(): array + { + $references = $this->getHeaders()->get('references'); + + return null !== $references ? \explode(' ', $references) : []; + } + + /** + * Get body HTML. + * + * @return null|string + */ + final public function getBodyHtml() + { + $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $part) { + if (self::SUBTYPE_HTML === $part->getSubtype()) { + return $part->getDecodedContent(); + } + } + + // If message has no parts and is HTML, return content of message itself. + if (self::SUBTYPE_HTML === $this->getSubtype()) { + return $this->getDecodedContent(); + } + + return null; + } + + /** + * Get body text. + * + * @return null|string + */ + final public function getBodyText() + { + $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $part) { + if (self::SUBTYPE_PLAIN === $part->getSubtype()) { + return $part->getDecodedContent(); + } + } + + // If message has no parts, return content of message itself. + if (self::SUBTYPE_PLAIN === $this->getSubtype()) { + return $this->getDecodedContent(); + } + + return null; + } + + /** + * Get attachments (if any) linked to this e-mail. + * + * @return AttachmentInterface[] + */ + final public function getAttachments(): array + { + if (null === $this->attachments) { + static $gatherAttachments; + if (null === $gatherAttachments) { + $gatherAttachments = static function (PartInterface $part) use (&$gatherAttachments): array { + $attachments = []; + foreach ($part->getParts() as $childPart) { + if ($childPart instanceof Attachment) { + $attachments[] = $childPart; + } + if ($childPart->hasChildren()) { + $attachments = \array_merge($attachments, $gatherAttachments($childPart)); + } + } + + return $attachments; + }; + } + + $this->attachments = $gatherAttachments($this); + } + + return $this->attachments; + } + + /** + * Does this message have attachments? + * + * @return bool + */ + final public function hasAttachments(): bool + { + return \count($this->getAttachments()) > 0; + } + + /** + * @param array $addresses Addesses + * + * @return array + */ + private function decodeEmailAddresses(array $addresses): array + { + $return = []; + foreach ($addresses as $address) { + if (isset($address->mailbox)) { + $return[] = $this->decodeEmailAddress($address); + } + } + + return $return; + } + + /** + * @param \stdClass $value + * + * @return EmailAddress + */ + private function decodeEmailAddress(\stdClass $value): EmailAddress + { + return new EmailAddress($value->mailbox, $value->host, $value->personal); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php new file mode 100644 index 00000000..1182c0cb --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php @@ -0,0 +1,578 @@ + self::TYPE_TEXT, + \TYPEMULTIPART => self::TYPE_MULTIPART, + \TYPEMESSAGE => self::TYPE_MESSAGE, + \TYPEAPPLICATION => self::TYPE_APPLICATION, + \TYPEAUDIO => self::TYPE_AUDIO, + \TYPEIMAGE => self::TYPE_IMAGE, + \TYPEVIDEO => self::TYPE_VIDEO, + \TYPEMODEL => self::TYPE_MODEL, + \TYPEOTHER => self::TYPE_OTHER, + ]; + + /** + * @var array + */ + private static $encodingsMap = [ + \ENC7BIT => self::ENCODING_7BIT, + \ENC8BIT => self::ENCODING_8BIT, + \ENCBINARY => self::ENCODING_BINARY, + \ENCBASE64 => self::ENCODING_BASE64, + \ENCQUOTEDPRINTABLE => self::ENCODING_QUOTED_PRINTABLE, + ]; + + /** + * @var array + */ + private static $attachmentKeys = [ + 'name' => true, + 'filename' => true, + 'name*' => true, + 'filename*' => true, + ]; + + /** + * Constructor. + * + * @param ImapResourceInterface $resource IMAP resource + * @param int $messageNumber Message number + * @param string $partNumber Part number + * @param \stdClass $structure Part structure + */ + public function __construct( + ImapResourceInterface $resource, + int $messageNumber, + string $partNumber, + \stdClass $structure + ) { + $this->resource = $resource; + $this->messageNumber = $messageNumber; + $this->partNumber = $partNumber; + $this->setStructure($structure); + } + + /** + * Get message number (from headers). + * + * @return int + */ + final public function getNumber(): int + { + $this->assertMessageExists($this->messageNumber); + + return $this->messageNumber; + } + + /** + * Ensure message exists. + * + * @param int $messageNumber + */ + protected function assertMessageExists(int $messageNumber) + { + } + + /** + * @param \stdClass $structure Part structure + */ + final protected function setStructure(\stdClass $structure) + { + $this->structure = $structure; + } + + /** + * Part structure. + * + * @return \stdClass + */ + final public function getStructure(): \stdClass + { + $this->lazyLoadStructure(); + + return $this->structure; + } + + /** + * Lazy load structure. + */ + protected function lazyLoadStructure() + { + } + + /** + * Part parameters. + * + * @return Parameters + */ + final public function getParameters(): Parameters + { + $this->lazyParseStructure(); + + return $this->parameters; + } + + /** + * Part charset. + * + * @return null|string + */ + final public function getCharset() + { + $this->lazyParseStructure(); + + return $this->parameters->get('charset') ?: null; + } + + /** + * Part type. + * + * @return null|string + */ + final public function getType() + { + $this->lazyParseStructure(); + + return $this->type; + } + + /** + * Part subtype. + * + * @return null|string + */ + final public function getSubtype() + { + $this->lazyParseStructure(); + + return $this->subtype; + } + + /** + * Part encoding. + * + * @return null|string + */ + final public function getEncoding() + { + $this->lazyParseStructure(); + + return $this->encoding; + } + + /** + * Part disposition. + * + * @return null|string + */ + final public function getDisposition() + { + $this->lazyParseStructure(); + + return $this->disposition; + } + + /** + * Part bytes. + * + * @return null|string + */ + final public function getBytes() + { + $this->lazyParseStructure(); + + return $this->bytes; + } + + /** + * Part lines. + * + * @return null|string + */ + final public function getLines() + { + $this->lazyParseStructure(); + + return $this->lines; + } + + /** + * Get raw part content. + * + * @return string + */ + final public function getContent(): string + { + if (null === $this->content) { + $this->content = $this->doGetContent($this->getContentPartNumber()); + } + + return $this->content; + } + + /** + * Get content part number. + * + * @return string + */ + protected function getContentPartNumber(): string + { + return $this->partNumber; + } + + /** + * Get part number. + * + * @return string + */ + final public function getPartNumber(): string + { + return $this->partNumber; + } + + /** + * Get decoded part content. + * + * @return string + */ + final public function getDecodedContent(): string + { + if (null === $this->decodedContent) { + if (self::ENCODING_UNKNOWN === $this->getEncoding()) { + throw new UnexpectedEncodingException('Cannot decode a content with an uknown encoding'); + } + + $content = $this->getContent(); + if (self::ENCODING_BASE64 === $this->getEncoding()) { + $content = \base64_decode($content); + } elseif (self::ENCODING_QUOTED_PRINTABLE === $this->getEncoding()) { + $content = \quoted_printable_decode($content); + } + + if (false === $content) { + throw new UnexpectedEncodingException('Cannot decode content'); + } + + // If this part is a text part, convert its charset to UTF-8. + // We don't want to decode an attachment's charset. + if (!$this instanceof Attachment && null !== $this->getCharset() && self::TYPE_TEXT === $this->getType()) { + $content = Transcoder::decode($content, $this->getCharset()); + } + + $this->decodedContent = $content; + } + + return $this->decodedContent; + } + + /** + * Get raw message content. + * + * @param string $partNumber + * + * @return string + */ + final protected function doGetContent(string $partNumber): string + { + return \imap_fetchbody( + $this->resource->getStream(), + $this->getNumber(), + $partNumber, + \FT_UID | \FT_PEEK + ); + } + + /** + * Get an array of all parts for this message. + * + * @return PartInterface[] + */ + final public function getParts(): array + { + $this->lazyParseStructure(); + + return $this->parts; + } + + /** + * Get current child part. + * + * @return mixed + */ + final public function current() + { + $this->lazyParseStructure(); + + return $this->parts[$this->key]; + } + + /** + * Get current child part. + * + * @return mixed + */ + final public function getChildren() + { + return $this->current(); + } + + /** + * Get current child part. + * + * @return bool + */ + final public function hasChildren() + { + $this->lazyParseStructure(); + + return \count($this->parts) > 0; + } + + /** + * Get current part key. + * + * @return int + */ + final public function key() + { + return $this->key; + } + + /** + * Move to next part. + * + * @return int + */ + final public function next() + { + ++$this->key; + } + + /** + * Reset part key. + * + * @return int + */ + final public function rewind() + { + $this->key = 0; + } + + /** + * Check if current part is a valid one. + * + * @return bool + */ + final public function valid() + { + $this->lazyParseStructure(); + + return isset($this->parts[$this->key]); + } + + /** + * Parse part structure. + */ + private function lazyParseStructure() + { + if (true === $this->structureParsed) { + return; + } + $this->structureParsed = true; + + $this->lazyLoadStructure(); + + $this->type = self::$typesMap[$this->structure->type] ?? self::TYPE_UNKNOWN; + + // In our context, \ENCOTHER is as useful as an uknown encoding + $this->encoding = self::$encodingsMap[$this->structure->encoding] ?? self::ENCODING_UNKNOWN; + $this->subtype = $this->structure->subtype; + + foreach (['disposition', 'bytes', 'description'] as $optional) { + if (isset($this->structure->{$optional})) { + $this->{$optional} = $this->structure->{$optional}; + } + } + + $this->parameters = new Parameters(); + if ($this->structure->ifparameters) { + $this->parameters->add($this->structure->parameters); + } + + if ($this->structure->ifdparameters) { + $this->parameters->add($this->structure->dparameters); + } + + // When the message is not multipart and the body is the attachment content + // Prevents infinite recursion + if (self::isAttachment($this->structure) && !$this instanceof Attachment) { + $this->parts[] = new Attachment($this->resource, $this->getNumber(), '1', $this->structure); + } + + if (isset($this->structure->parts)) { + $parts = $this->structure->parts; + // https://secure.php.net/manual/en/function.imap-fetchbody.php#89002 + if ($this instanceof Attachment && $this->isEmbeddedMessage() && 1 === \count($parts) && \TYPEMULTIPART === $parts[0]->type) { + $parts = $parts[0]->parts; + } + foreach ($parts as $key => $partStructure) { + $partNumber = (!$this instanceof Message) ? $this->partNumber . '.' : ''; + $partNumber .= (string) ($key + 1); + + $newPartClass = self::isAttachment($partStructure) + ? Attachment::class + : SimplePart::class + ; + + $this->parts[] = new $newPartClass($this->resource, $this->getNumber(), $partNumber, $partStructure); + } + } + } + + /** + * Check if the given part is an attachment. + * + * @param \stdClass $part + * + * @return bool + */ + private static function isAttachment(\stdClass $part): bool + { + if (isset(self::$typesMap[$part->type]) && self::TYPE_MULTIPART === self::$typesMap[$part->type]) { + return false; + } + + // Attachment with correct Content-Disposition header + if ($part->ifdisposition) { + if ('attachment' === \strtolower($part->disposition)) { + return true; + } + + if ( + 'inline' === \strtolower($part->disposition) + && self::SUBTYPE_PLAIN !== \strtoupper($part->subtype) + && self::SUBTYPE_HTML !== \strtoupper($part->subtype) + ) { + return true; + } + } + + // Attachment without Content-Disposition header + if ($part->ifparameters) { + foreach ($part->parameters as $parameter) { + if (isset(self::$attachmentKeys[\strtolower($parameter->attribute)])) { + return true; + } + } + } + + /* + if ($part->ifdparameters) { + foreach ($part->dparameters as $parameter) { + if (isset(self::$attachmentKeys[\strtolower($parameter->attribute)])) { + return true; + } + } + } + */ + + return false; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php new file mode 100644 index 00000000..4c559b28 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php @@ -0,0 +1,64 @@ +getParameters()->get('filename') + ?: $this->getParameters()->get('name'); + } + + /** + * Get attachment file size. + * + * @return int Number of bytes + */ + public function getSize() + { + return $this->getParameters()->get('size'); + } + + /** + * Is this attachment also an Embedded Message? + * + * @return bool + */ + public function isEmbeddedMessage(): bool + { + return self::TYPE_MESSAGE === $this->getType(); + } + + /** + * Return embedded message. + * + * @throws NotEmbeddedMessageException + * + * @return EmbeddedMessageInterface + */ + public function getEmbeddedMessage(): EmbeddedMessageInterface + { + if (!$this->isEmbeddedMessage()) { + throw new NotEmbeddedMessageException(\sprintf( + 'Attachment "%s" in message "%s" is not embedded message', + $this->getPartNumber(), + $this->getNumber() + )); + } + + return new EmbeddedMessage($this->resource, $this->getNumber(), $this->getPartNumber(), $this->getStructure()->parts[0]); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php new file mode 100644 index 00000000..ae9a9d7d --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php @@ -0,0 +1,39 @@ + + * + * @return null|string + */ + public function getId(); + + /** + * Get message sender (from headers). + * + * @return null|EmailAddress + */ + public function getFrom(); + + /** + * Get To recipients. + * + * @return EmailAddress[] Empty array in case message has no To: recipients + */ + public function getTo(): array; + + /** + * Get Cc recipients. + * + * @return EmailAddress[] Empty array in case message has no CC: recipients + */ + public function getCc(): array; + + /** + * Get Bcc recipients. + * + * @return EmailAddress[] Empty array in case message has no BCC: recipients + */ + public function getBcc(): array; + + /** + * Get Reply-To recipients. + * + * @return EmailAddress[] Empty array in case message has no Reply-To: recipients + */ + public function getReplyTo(): array; + + /** + * Get Sender. + * + * @return EmailAddress[] Empty array in case message has no Sender: recipients + */ + public function getSender(): array; + + /** + * Get Return-Path. + * + * @return EmailAddress[] Empty array in case message has no Return-Path: recipients + */ + public function getReturnPath(): array; + + /** + * Get date (from headers). + * + * @return null|\DateTimeImmutable + */ + public function getDate(); + + /** + * Get message size (from headers). + * + * @return int + */ + public function getSize(); + + /** + * Get message subject (from headers). + * + * @return string + */ + public function getSubject(); + + /** + * Get message In-Reply-To (from headers). + * + * @return array + */ + public function getInReplyTo(): array; + + /** + * Get message References (from headers). + * + * @return array + */ + public function getReferences(): array; + + /** + * Get body HTML. + * + * @return string | null Null if message has no HTML message part + */ + public function getBodyHtml(); + + /** + * Get body text. + * + * @return string + */ + public function getBodyText(); + + /** + * Get attachments (if any) linked to this e-mail. + * + * @return AttachmentInterface[] + */ + public function getAttachments(): array; + + /** + * Does this message have attachments? + * + * @return bool + */ + public function hasAttachments(): bool; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php new file mode 100644 index 00000000..3f3788a4 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php @@ -0,0 +1,94 @@ +mailbox = $mailbox; + $this->hostname = $hostname; + $this->name = $name; + + if (null !== $hostname) { + $this->address = $mailbox . '@' . $hostname; + } + } + + /** + * @return null|string + */ + public function getAddress() + { + return $this->address; + } + + /** + * Returns address with person name. + * + * @return string + */ + public function getFullAddress(): string + { + $address = \sprintf('%s@%s', $this->mailbox, $this->hostname); + if (null !== $this->name) { + $address = \sprintf('"%s" <%s>', \addcslashes($this->name, '"'), $address); + } + + return $address; + } + + /** + * @return string + */ + public function getMailbox(): string + { + return $this->mailbox; + } + + /** + * @return null|string + */ + public function getHostname() + { + return $this->hostname; + } + + /** + * @return null|string + */ + public function getName() + { + return $this->name; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php new file mode 100644 index 00000000..1dfb8fd6 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php @@ -0,0 +1,81 @@ +headers) { + $this->headers = new Headers(\imap_rfc822_parse_headers($this->getRawHeaders())); + } + + return $this->headers; + } + + /** + * Get raw message headers. + * + * @return string + */ + public function getRawHeaders(): string + { + if (null === $this->rawHeaders) { + $rawHeaders = \explode("\r\n\r\n", $this->getRawMessage(), 2); + $this->rawHeaders = \current($rawHeaders); + } + + return $this->rawHeaders; + } + + /** + * Get the raw message, including all headers, parts, etc. unencoded and unparsed. + * + * @return string the raw message + */ + public function getRawMessage(): string + { + if (null === $this->rawMessage) { + $this->rawMessage = $this->doGetContent($this->getPartNumber()); + } + + return $this->rawMessage; + } + + /** + * Get content part number. + * + * @return string + */ + protected function getContentPartNumber(): string + { + $partNumber = $this->getPartNumber(); + if (0 === \count($this->getParts())) { + $partNumber .= '.1'; + } + + return $partNumber; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php new file mode 100644 index 00000000..c685edf9 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php @@ -0,0 +1,9 @@ + $value) { + $this[$key] = $this->parseHeader($key, $value); + } + } + + /** + * Get header. + * + * @param string $key + * + * @return null|string + */ + public function get(string $key) + { + return parent::get(\strtolower($key)); + } + + /** + * Parse header. + * + * @param string $key + * @param mixed $value + * + * @return mixed + */ + private function parseHeader(string $key, $value) + { + switch ($key) { + case 'msgno': + return (int) $value; + case 'from': + case 'to': + case 'cc': + case 'bcc': + case 'reply_to': + case 'sender': + case 'return_path': + foreach ($value as $address) { + if (isset($address->mailbox)) { + $address->host = $address->host ?? null; + $address->personal = isset($address->personal) ? $this->decode($address->personal) : null; + } + } + + return $value; + case 'date': + case 'subject': + return $this->decode($value); + } + + return $value; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php new file mode 100644 index 00000000..eb6ea136 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php @@ -0,0 +1,86 @@ + 'name', + 'filename*' => 'filename', + ]; + + /** + * @param array $parameters + */ + public function __construct(array $parameters = []) + { + parent::__construct(); + + $this->add($parameters); + } + + /** + * @param array $parameters + */ + public function add(array $parameters = []) + { + foreach ($parameters as $parameter) { + $key = \strtolower($parameter->attribute); + if (isset(self::$attachmentCustomKeys[$key])) { + $key = self::$attachmentCustomKeys[$key]; + } + $value = $this->decode($parameter->value); + $this[$key] = $value; + } + } + + /** + * @param string $key + * + * @return mixed + */ + public function get(string $key) + { + return $this[$key] ?? null; + } + + /** + * Decode value. + * + * @param string $value + * + * @return string + */ + final protected function decode(string $value): string + { + $parts = \imap_mime_header_decode($value); + if (!\is_array($parts)) { + return $value; + } + + $decoded = ''; + foreach ($parts as $part) { + $text = $part->text; + if ('default' !== $part->charset) { + $text = Transcoder::decode($text, $part->charset); + } + // RFC2231 + if (1 === \preg_match('/^(?[^\']+)\'[^\']*?\'(?.+)$/', $text, $matches)) { + $hasInvalidChars = \preg_match('#[^%a-zA-Z0-9\-_\.\+]#', $matches['urltext']); + $hasEscapedChars = \preg_match('#%[a-zA-Z0-9]{2}#', $matches['urltext']); + if (!$hasInvalidChars && $hasEscapedChars) { + $text = Transcoder::decode(\urldecode($matches['urltext']), $matches['encoding']); + } + } + + $decoded .= $text; + } + + return $decoded; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php new file mode 100644 index 00000000..3d76d85d --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php @@ -0,0 +1,130 @@ + 'Shift_JIS', + '129' => 'EUC-KR', + '134' => 'GB2312', + '136' => 'Big5', + '161' => 'windows-1253', + '162' => 'windows-1254', + '177' => 'windows-1255', + '178' => 'windows-1256', + '186' => 'windows-1257', + '204' => 'windows-1251', + '222' => 'windows-874', + '238' => 'windows-1250', + '5601' => 'EUC-KR', + '646' => 'us-ascii', + '850' => 'IBM850', + '852' => 'IBM852', + '855' => 'IBM855', + '857' => 'IBM857', + '862' => 'IBM862', + '864' => 'IBM864', + '864i' => 'IBM864i', + '866' => 'IBM866', + 'ansi-1251' => 'windows-1251', + 'ansi_x3.4-1968' => 'us-ascii', + 'arabic' => 'ISO-8859-6', + 'ascii' => 'us-ascii', + 'asmo-708' => 'ISO-8859-6', + 'big5-hkscs' => 'Big5', + 'chinese' => 'GB2312', + 'cn-big5' => 'Big5', + 'cns11643' => 'x-euc-tw', + 'cp-866' => 'IBM866', + 'cp1250' => 'windows-1250', + 'cp1251' => 'windows-1251', + 'cp1252' => 'windows-1252', + 'cp1253' => 'windows-1253', + 'cp1254' => 'windows-1254', + 'cp1255' => 'windows-1255', + 'cp1256' => 'windows-1256', + 'cp1257' => 'windows-1257', + 'cp1258' => 'windows-1258', + 'cp819' => 'ISO-8859-1', + 'cp850' => 'IBM850', + 'cp852' => 'IBM852', + 'cp855' => 'IBM855', + 'cp857' => 'IBM857', + 'cp862' => 'IBM862', + 'cp864' => 'IBM864', + 'cp864i' => 'IBM864i', + 'cp866' => 'IBM866', + 'cp932' => 'Shift_JIS', + 'csbig5' => 'Big5', + 'cseucjpkdfmtjapanese' => 'EUC-JP', + 'cseuckr' => 'EUC-KR', + 'cseucpkdfmtjapanese' => 'EUC-JP', + 'csgb2312' => 'GB2312', + 'csibm850' => 'IBM850', + 'csibm852' => 'IBM852', + 'csibm855' => 'IBM855', + 'csibm857' => 'IBM857', + 'csibm862' => 'IBM862', + 'csibm864' => 'IBM864', + 'csibm864i' => 'IBM864i', + 'csibm866' => 'IBM866', + 'csiso103t618bit' => 'T.61-8bit', + 'csiso111ecmacyrillic' => 'ISO-IR-111', + 'csiso2022jp' => 'ISO-2022-JP', + 'csiso2022jp2' => 'ISO-2022-JP', + 'csiso2022kr' => 'ISO-2022-KR', + 'csiso58gb231280' => 'GB2312', + 'csiso88596e' => 'ISO-8859-6-E', + 'csiso88596i' => 'ISO-8859-6-I', + 'csiso88598e' => 'ISO-8859-8-E', + 'csiso88598i' => 'ISO-8859-8-I', + 'csisolatin1' => 'ISO-8859-1', + 'csisolatin2' => 'ISO-8859-2', + 'csisolatin3' => 'ISO-8859-3', + 'csisolatin4' => 'ISO-8859-4', + 'csisolatin5' => 'ISO-8859-9', + 'csisolatin6' => 'ISO-8859-10', + 'csisolatin9' => 'ISO-8859-15', + 'csisolatinarabic' => 'ISO-8859-6', + 'csisolatincyrillic' => 'ISO-8859-5', + 'csisolatingreek' => 'ISO-8859-7', + 'csisolatinhebrew' => 'ISO-8859-8', + 'cskoi8r' => 'KOI8-R', + 'csksc56011987' => 'EUC-KR', + 'csmacintosh' => 'x-mac-roman', + 'csshiftjis' => 'Shift_JIS', + 'csueckr' => 'EUC-KR', + 'csunicode' => 'UTF-16BE', + 'csunicode11' => 'UTF-16BE', + 'csunicode11utf7' => 'UTF-7', + 'csunicodeascii' => 'UTF-16BE', + 'csunicodelatin1' => 'UTF-16BE', + 'csviqr' => 'VIQR', + 'csviscii' => 'VISCII', + 'cyrillic' => 'ISO-8859-5', + 'dos-874' => 'windows-874', + 'ecma-114' => 'ISO-8859-6', + 'ecma-118' => 'ISO-8859-7', + 'ecma-cyrillic' => 'ISO-IR-111', + 'elot_928' => 'ISO-8859-7', + 'gb_2312' => 'GB2312', + 'gb_2312-80' => 'GB2312', + 'gbk' => 'x-gbk', + 'greek' => 'ISO-8859-7', + 'greek8' => 'ISO-8859-7', + 'hebrew' => 'ISO-8859-8', + 'ibm-864' => 'IBM864', + 'ibm-864i' => 'IBM864i', + 'ibm819' => 'ISO-8859-1', + 'ibm874' => 'windows-874', + 'iso-10646' => 'UTF-16BE', + 'iso-10646-j-1' => 'UTF-16BE', + 'iso-10646-ucs-2' => 'UTF-16BE', + 'iso-10646-ucs-4' => 'UTF-32BE', + 'iso-10646-ucs-basic' => 'UTF-16BE', + 'iso-10646-unicode-latin1' => 'UTF-16BE', + 'iso-2022-cn-ext' => 'ISO-2022-CN', + 'iso-2022-jp-2' => 'ISO-2022-JP', + 'iso-8859-8i' => 'ISO-8859-8-I', + 'iso-ir-100' => 'ISO-8859-1', + 'iso-ir-101' => 'ISO-8859-2', + 'iso-ir-103' => 'T.61-8bit', + 'iso-ir-109' => 'ISO-8859-3', + 'iso-ir-110' => 'ISO-8859-4', + 'iso-ir-126' => 'ISO-8859-7', + 'iso-ir-127' => 'ISO-8859-6', + 'iso-ir-138' => 'ISO-8859-8', + 'iso-ir-144' => 'ISO-8859-5', + 'iso-ir-148' => 'ISO-8859-9', + 'iso-ir-149' => 'EUC-KR', + 'iso-ir-157' => 'ISO-8859-10', + 'iso-ir-58' => 'GB2312', + 'iso8859-1' => 'ISO-8859-1', + 'iso8859-10' => 'ISO-8859-10', + 'iso8859-11' => 'ISO-8859-11', + 'iso8859-13' => 'ISO-8859-13', + 'iso8859-14' => 'ISO-8859-14', + 'iso8859-15' => 'ISO-8859-15', + 'iso8859-2' => 'ISO-8859-2', + 'iso8859-3' => 'ISO-8859-3', + 'iso8859-4' => 'ISO-8859-4', + 'iso8859-5' => 'ISO-8859-5', + 'iso8859-6' => 'ISO-8859-6', + 'iso8859-7' => 'ISO-8859-7', + 'iso8859-8' => 'ISO-8859-8', + 'iso8859-9' => 'ISO-8859-9', + 'iso88591' => 'ISO-8859-1', + 'iso885910' => 'ISO-8859-10', + 'iso885911' => 'ISO-8859-11', + 'iso885912' => 'ISO-8859-12', + 'iso885913' => 'ISO-8859-13', + 'iso885914' => 'ISO-8859-14', + 'iso885915' => 'ISO-8859-15', + 'iso88592' => 'ISO-8859-2', + 'iso88593' => 'ISO-8859-3', + 'iso88594' => 'ISO-8859-4', + 'iso88595' => 'ISO-8859-5', + 'iso88596' => 'ISO-8859-6', + 'iso88597' => 'ISO-8859-7', + 'iso88598' => 'ISO-8859-8', + 'iso88599' => 'ISO-8859-9', + 'iso_8859-1' => 'ISO-8859-1', + 'iso_8859-15' => 'ISO-8859-15', + 'iso_8859-1:1987' => 'ISO-8859-1', + 'iso_8859-2' => 'ISO-8859-2', + 'iso_8859-2:1987' => 'ISO-8859-2', + 'iso_8859-3' => 'ISO-8859-3', + 'iso_8859-3:1988' => 'ISO-8859-3', + 'iso_8859-4' => 'ISO-8859-4', + 'iso_8859-4:1988' => 'ISO-8859-4', + 'iso_8859-5' => 'ISO-8859-5', + 'iso_8859-5:1988' => 'ISO-8859-5', + 'iso_8859-6' => 'ISO-8859-6', + 'iso_8859-6:1987' => 'ISO-8859-6', + 'iso_8859-7' => 'ISO-8859-7', + 'iso_8859-7:1987' => 'ISO-8859-7', + 'iso_8859-8' => 'ISO-8859-8', + 'iso_8859-8:1988' => 'ISO-8859-8', + 'iso_8859-9' => 'ISO-8859-9', + 'iso_8859-9:1989' => 'ISO-8859-9', + 'koi' => 'KOI8-R', + 'koi8' => 'KOI8-R', + 'koi8-ru' => 'KOI8-U', + 'koi8_r' => 'KOI8-R', + 'korean' => 'EUC-KR', + 'ks_c_5601-1987' => 'EUC-KR', + 'ks_c_5601-1989' => 'EUC-KR', + 'ksc5601' => 'EUC-KR', + 'ksc_5601' => 'EUC-KR', + 'l1' => 'ISO-8859-1', + 'l2' => 'ISO-8859-2', + 'l3' => 'ISO-8859-3', + 'l4' => 'ISO-8859-4', + 'l5' => 'ISO-8859-9', + 'l6' => 'ISO-8859-10', + 'l9' => 'ISO-8859-15', + 'latin1' => 'ISO-8859-1', + 'latin2' => 'ISO-8859-2', + 'latin3' => 'ISO-8859-3', + 'latin4' => 'ISO-8859-4', + 'latin5' => 'ISO-8859-9', + 'latin6' => 'ISO-8859-10', + 'logical' => 'ISO-8859-8-I', + 'mac' => 'x-mac-roman', + 'macintosh' => 'x-mac-roman', + 'ms932' => 'Shift_JIS', + 'ms_kanji' => 'Shift_JIS', + 'shift-jis' => 'Shift_JIS', + 'sjis' => 'Shift_JIS', + 'sun_eu_greek' => 'ISO-8859-7', + 't.61' => 'T.61-8bit', + 'tis620' => 'TIS-620', + 'unicode-1-1-utf-7' => 'UTF-7', + 'unicode-1-1-utf-8' => 'UTF-8', + 'unicode-2-0-utf-7' => 'UTF-7', + 'visual' => 'ISO-8859-8', + 'windows-31j' => 'Shift_JIS', + 'windows-949' => 'EUC-KR', + 'x-cp1250' => 'windows-1250', + 'x-cp1251' => 'windows-1251', + 'x-cp1252' => 'windows-1252', + 'x-cp1253' => 'windows-1253', + 'x-cp1254' => 'windows-1254', + 'x-cp1255' => 'windows-1255', + 'x-cp1256' => 'windows-1256', + 'x-cp1257' => 'windows-1257', + 'x-cp1258' => 'windows-1258', + 'x-euc-jp' => 'EUC-JP', + 'x-iso-10646-ucs-2-be' => 'UTF-16BE', + 'x-iso-10646-ucs-2-le' => 'UTF-16LE', + 'x-iso-10646-ucs-4-be' => 'UTF-32BE', + 'x-iso-10646-ucs-4-le' => 'UTF-32LE', + 'x-sjis' => 'Shift_JIS', + 'x-unicode-2-0-utf-7' => 'UTF-7', + 'x-x-big5' => 'Big5', + 'zh_cn.euc' => 'GB2312', + 'zh_tw-big5' => 'Big5', + 'zh_tw-euc' => 'x-euc-tw', + ]; + + /** + * Decode text to UTF-8. + * + * @param string $text Text to decode + * @param string $fromCharset Original charset + * + * @return string + */ + public static function decode(string $text, string $fromCharset): string + { + static $utf8Aliases = [ + 'unicode-1-1-utf-8' => true, + 'utf8' => true, + 'utf-8' => true, + 'UTF8' => true, + 'UTF-8' => true, + ]; + + if (isset($utf8Aliases[$fromCharset])) { + return $text; + } + + $originalFromCharset = $fromCharset; + $lowercaseFromCharset = \strtolower($fromCharset); + if (isset(self::$charsetAliases[$lowercaseFromCharset])) { + $fromCharset = self::$charsetAliases[$lowercaseFromCharset]; + } + + \set_error_handler(function () {}); + + $iconvDecodedText = \iconv($fromCharset, 'UTF-8', $text); + if (false === $iconvDecodedText) { + $iconvDecodedText = \iconv($originalFromCharset, 'UTF-8', $text); + } + + \restore_error_handler(); + + if (false !== $iconvDecodedText) { + return $iconvDecodedText; + } + + $errorMessage = null; + $errorNumber = 0; + \set_error_handler(function ($nr, $message) use (&$errorMessage, &$errorNumber) { + $errorMessage = $message; + $errorNumber = $nr; + }); + + $decodedText = \mb_convert_encoding($text, 'UTF-8', $fromCharset); + + \restore_error_handler(); + + if (null !== $errorMessage) { + throw new UnsupportedCharsetException(\sprintf( + 'Unsupported charset "%s"%s: %s', + $originalFromCharset, + ($fromCharset !== $originalFromCharset) ? \sprintf(' (alias found: "%s")', $fromCharset) : '', + $errorMessage + ), $errorNumber); + } + + return $decodedText; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php new file mode 100644 index 00000000..0900b668 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageInterface.php @@ -0,0 +1,120 @@ +resource = $resource; + + parent::__construct($messageNumbers); + } + + /** + * Get current message. + * + * @return MessageInterface + */ + public function current(): MessageInterface + { + $current = parent::current(); + if (!\is_int($current)) { + throw new Exception\OutOfBoundsException(\sprintf( + 'The current value "%s" isn\'t an integer and doesn\'t represent a message;' + . ' try to cycle this "%s" with a native php function like foreach or with the method getArrayCopy(),' + . ' or check it by calling the methods valid().', + \is_object($current) ? \get_class($current) : \gettype($current), + static::class + )); + } + + return new Message($this->resource, $current); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIteratorInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIteratorInterface.php new file mode 100644 index 00000000..f1b9fd92 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/MessageIteratorInterface.php @@ -0,0 +1,15 @@ +date = $date; + $this->dateFormat = $dateFormat; + } + + /** + * Converts the condition to a string that can be sent to the IMAP server. + * + * @return string + */ + final public function toString(): string + { + return \sprintf('%s "%s"', $this->getKeyword(), $this->date->format($this->dateFormat)); + } + + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + abstract protected function getKeyword(): string; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractText.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractText.php new file mode 100644 index 00000000..833ea3c3 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/AbstractText.php @@ -0,0 +1,46 @@ +text = $text; + } + + /** + * Converts the condition to a string that can be sent to the IMAP server. + * + * @return string + */ + final public function toString(): string + { + return \sprintf('%s "%s"', $this->getKeyword(), $this->text); + } + + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + abstract protected function getKeyword(): string; +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/ConditionInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/ConditionInterface.php new file mode 100644 index 00000000..e4c0a97d --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/ConditionInterface.php @@ -0,0 +1,18 @@ +addCondition($condition); + } + } + + /** + * Adds a new condition to the expression. + * + * @param ConditionInterface $condition the condition to be added + */ + private function addCondition(ConditionInterface $condition) + { + $this->conditions[] = $condition; + } + + /** + * Returns the keyword that the condition represents. + * + * @return string + */ + public function toString(): string + { + $conditions = \array_map(function (ConditionInterface $condition) { + return $condition->toString(); + }, $this->conditions); + + return \sprintf('( %s )', \implode(' OR ', $conditions)); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/RawExpression.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/RawExpression.php new file mode 100644 index 00000000..72da06db --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/RawExpression.php @@ -0,0 +1,34 @@ +expression = $expression; + } + + /** + * @return string + */ + public function toString(): string + { + return $this->expression; + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Deleted.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Deleted.php new file mode 100644 index 00000000..90ec5579 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Search/State/Deleted.php @@ -0,0 +1,24 @@ +conditions[] = $condition; + + return $this; + } + + /** + * Converts the expression to a string that can be sent to the IMAP server. + * + * @return string + */ + public function toString(): string + { + $conditions = \array_map(function (ConditionInterface $condition) { + return $condition->toString(); + }, $this->conditions); + + return \implode(' ', $conditions); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php b/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php new file mode 100644 index 00000000..4413ccba --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/Server.php @@ -0,0 +1,122 @@ +hostname = $hostname; + $this->port = $port; + $this->flags = $flags ? '/' . \ltrim($flags, '/') : ''; + $this->parameters = $parameters; + } + + /** + * Authenticate connection. + * + * @param string $username Username + * @param string $password Password + * + * @throws AuthenticationFailedException + * + * @return ConnectionInterface + */ + public function authenticate(string $username, string $password): ConnectionInterface + { + $errorMessage = null; + $errorNumber = 0; + \set_error_handler(function ($nr, $message) use (&$errorMessage, &$errorNumber) { + $errorMessage = $message; + $errorNumber = $nr; + }); + + $resource = \imap_open( + $this->getServerString(), + $username, + $password, + 0, + 1, + $this->parameters + ); + + \restore_error_handler(); + + if (false === $resource || null !== $errorMessage) { + throw new AuthenticationFailedException(\sprintf( + 'Authentication failed for user "%s"%s', + $username, + null !== $errorMessage ? ': ' . $errorMessage : '' + ), $errorNumber); + } + + $check = \imap_check($resource); + $mailbox = $check->Mailbox; + $connection = \substr($mailbox, 0, \strpos($mailbox, '}') + 1); + + // These are necessary to get rid of PHP throwing IMAP errors + \imap_errors(); + \imap_alerts(); + + return new Connection(new ImapResource($resource), $connection); + } + + /** + * Glues hostname, port and flags and returns result. + * + * @return string + */ + private function getServerString(): string + { + return \sprintf( + '{%s%s%s}', + $this->hostname, + '' !== $this->port ? ':' . $this->port : '', + $this->flags + ); + } +} diff --git a/data/web/inc/lib/vendor/ddeboer/imap/src/ServerInterface.php b/data/web/inc/lib/vendor/ddeboer/imap/src/ServerInterface.php new file mode 100644 index 00000000..f67dd6c8 --- /dev/null +++ b/data/web/inc/lib/vendor/ddeboer/imap/src/ServerInterface.php @@ -0,0 +1,21 @@ +connect('redis-mailcow', 6379); // PDO // Calculate offset -$now = new DateTime(); -$mins = $now->getOffset() / 60; -$sgn = ($mins < 0 ? -1 : 1); -$mins = abs($mins); -$hrs = floor($mins / 60); -$mins -= $hrs * 60; -$offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); +// $now = new DateTime(); +// $mins = $now->getOffset() / 60; +// $sgn = ($mins < 0 ? -1 : 1); +// $mins = abs($mins); +// $hrs = floor($mins / 60); +// $mins -= $hrs * 60; +// $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins); $dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name; $opt = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, - PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", + //PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;", ]; try { $pdo = new PDO($dsn, $database_user, $database_pass, $opt); @@ -60,8 +60,7 @@ if (fsockopen("tcp://dockerapi", 443, $errno, $errstr) === false) { exit; } -function pdo_exception_handler($e) { - print_r($e); +function exception_handler($e) { if ($e instanceof PDOException) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -74,12 +73,12 @@ function pdo_exception_handler($e) { $_SESSION['return'][] = array( 'type' => 'danger', 'log' => array(__FUNCTION__), - 'msg' => array('mysql_error', 'unknown error') + 'msg' => 'An unknown error occured: ' . print_r($e, true) ); return false; } } -set_exception_handler('pdo_exception_handler'); +set_exception_handler('exception_handler'); // TODO: Move function function get_remote_ip($anonymize = null) { @@ -109,6 +108,10 @@ function get_remote_ip($anonymize = null) { require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php'; +// IMAP lib +// use Ddeboer\Imap\Server; +// $imap_server = new Server('dovecot', 143, '/imap/tls/novalidate-cert'); + // Set language if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) { if ($DETECT_LANGUAGE && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { @@ -153,6 +156,11 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php'; init_db_schema(); if (isset($_SESSION['mailcow_cc_role'])) { + // if ($_SESSION['mailcow_cc_role'] == 'user') { + // list($master_user, $master_passwd) = explode(':', file_get_contents('/etc/sogo/sieve.creds')); + // $imap_connection = $imap_server->authenticate($_SESSION['mailcow_cc_username'] . '*' . trim($master_user), trim($master_passwd)); + // $master_user = $master_passwd = null; + // } acl('to_session'); } $UI_TEXTS = customize('get', 'ui_texts'); diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index c9cda357..5164333a 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -142,3 +142,6 @@ $MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false; // Force password change on next login (only allows login to mailcow UI) $MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true; + +// Default mailbox format, should not be changed, keep the trailing ":" +$MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format'] = 'mdbox:'; diff --git a/data/web/modals/user.php b/data/web/modals/user.php index 228670bd..cbc799d7 100644 --- a/data/web/modals/user.php +++ b/data/web/modals/user.php @@ -54,7 +54,7 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
- 10-3600 + 1-3600
@@ -77,44 +77,71 @@ if (!isset($_SESSION['mailcow_cc_role'])) { 0-125000000
+
+ +
+ + 1-32000 +
+
+
+ +
+ + 1-32000 +
+
+
+ +
+ +
+
- +
- +
- +
- +
- + +
+
+
+
+
+
+