Skip to content
Merged

6.x #741

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/php_code_coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: ['ubuntu-20.04']
php-versions: ['8.1']
operating-system: ['ubuntu-24.04']
php-versions: ['8.5']

steps:
- name: Setup PHP
Expand All @@ -22,18 +22,18 @@ jobs:
php-version: ${{ matrix.php-versions }}
coverage: xdebug

- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Validate composer.json and composer.lock
run: composer validate

- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"

- name: Cache Files
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
${{ steps.composer-cache.outputs.dir }}
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/php_static_analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: ['ubuntu-20.04']
php-versions: ['7.4', '8.0', '8.1']
operating-system: ['ubuntu-24.04']
php-versions: ['8.2', '8.3', '8.4', '8.5']

steps:
- name: Setup PHP
Expand All @@ -22,18 +22,18 @@ jobs:
php-version: ${{ matrix.php-versions }}
coverage: none

- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Validate composer.json and composer.lock
run: composer validate

- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"

- name: Cache Files
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
${{ steps.composer-cache.outputs.dir }}
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/php_unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: ['ubuntu-20.04']
php-versions: ['7.4', '8.0', '8.1']
operating-system: ['ubuntu-24.04']
php-versions: ['8.2', '8.3', '8.4', '8.5']

steps:
- name: Setup PHP
Expand All @@ -22,18 +22,18 @@ jobs:
php-version: ${{ matrix.php-versions }}
coverage: none

- uses: actions/checkout@v2
- uses: actions/checkout@v4

- name: Validate composer.json and composer.lock
run: composer validate

- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"

- name: Cache Files
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
${{ steps.composer-cache.outputs.dir }}
Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@ Initially released in December 2012, the PHP IMAP Mailbox is a powerful and open
| 7.1 | 3.x | End of life |
| 7.2 | 3.x, 4.x | End of life |
| 7.3 | 3.x, 4.x | End of life |
| 7.4 | >3.0.33, 4.x, 5.x | Active support |
| 8.0 | >3.0.33, 4.x, 5.x | Active support |
| 8.1 | >4.3.0, 5.x | Active support |
| 7.4 | >3.0.33, 4.x, 5.x | End of life |
| 8.0 | >3.0.33, 4.x, 5.x | End of life |
| 8.1 | >4.3.0, 5.x | End of life |
| 8.2 | 6.x | Active support |
| 8.3 | 6.x | Active support |
| 8.4 | 6.x | Active support |
| 8.5 | 6.x | Active support |

The next major release raises the minimum supported PHP version to PHP 8.2 and is tested on PHP 8.2 through PHP 8.5.

* PHP `fileinfo` extension must be present; so make sure this line is active in your php.ini: `extension=php_fileinfo.dll`
* PHP `iconv` extension must be present; so make sure this line is active in your php.ini: `extension=php_iconv.dll`
Expand Down Expand Up @@ -93,6 +99,11 @@ $mailbox->setConnectionArgs(
| OP_SECURE // don't do non-secure authentication
);

// Some providers require OAuth instead of a password.
// Obtain and refresh the access token outside of this library, then enable OAuth explicitly.
// Your ext-imap build must expose OP_XOAUTH2 for this to work.
$mailbox->enableOAuth($accessToken);

try {
// Get all emails (messages)
// PHP.net imap_search criteria: http://php.net/manual/en/function.imap-search.php
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"sort-packages": true
},
"require": {
"php": "^7.4 || ^8.0",
"php": "^8.2",
"ext-fileinfo": "*",
"ext-iconv": "*",
"ext-imap": "*",
Expand Down
120 changes: 117 additions & 3 deletions src/PhpImap/Mailbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ class Mailbox

public const PART_TYPE_TWO = 2;

public const AUTHENTICATION_TYPE_PASSWORD = 'password';

public const AUTHENTICATION_TYPE_OAUTH = 'oauth';

public const IMAP_OPTIONS_SUPPORTED_VALUES =
OP_READONLY // 2
| OP_ANONYMOUS // 4
Expand All @@ -113,6 +117,12 @@ class Mailbox
/** @var string */
protected $imapPassword;

/** @var string */
protected $authenticationType = self::AUTHENTICATION_TYPE_PASSWORD;

/** @var string|null */
protected $imapOAuthToken = null;

/** @var int */
protected $imapSearchOption = SE_UID;

Expand Down Expand Up @@ -371,6 +381,39 @@ public function getLogin(): string
return $this->imapLogin;
}

/**
* Enables OAuth-based authentication for the IMAP connection.
*
* The provided access token will be passed to imap_open() instead of the password.
*
* @throws InvalidParameterException
*/
public function enableOAuth(string $accessToken): void
{
if ('' === \trim($accessToken)) {
throw new InvalidParameterException('enableOAuth() expects a non-empty OAuth access token.');
}

$this->imapOAuthToken = $accessToken;
$this->authenticationType = self::AUTHENTICATION_TYPE_OAUTH;
}

/**
* Disables OAuth-based authentication and switches back to password-based authentication.
*/
public function disableOAuth(): void
{
$this->authenticationType = self::AUTHENTICATION_TYPE_PASSWORD;
}

/**
* Returns whether OAuth-based authentication is enabled for the IMAP connection.
*/
public function isOAuthEnabled(): bool
{
return self::AUTHENTICATION_TYPE_OAUTH === $this->authenticationType;
}

/**
* Set custom connection arguments of imap_open method. See http://php.net/imap_open.
*
Expand All @@ -383,7 +426,7 @@ public function getLogin(): string
public function setConnectionArgs(int $options = 0, int $retriesNum = 0, array $params = null): void
{
if (0 !== $options) {
if (($options & self::IMAP_OPTIONS_SUPPORTED_VALUES) !== $options) {
if (($options & $this->getSupportedImapOptions()) !== $options) {
throw new InvalidParameterException('Please check your option for setConnectionArgs()! Unsupported option "'.$options.'". Available options: https://www.php.net/manual/de/function.imap-open.php');
}
$this->imapOptions = $options;
Expand Down Expand Up @@ -1709,15 +1752,86 @@ protected function initImapStream()
$imapStream = Imap::open(
$this->imapPath,
$this->imapLogin,
$this->imapPassword,
$this->imapOptions,
$this->getImapOpenSecret(),
$this->getImapOpenOptions(),
$this->imapRetriesNum,
$this->imapParams
);

return $imapStream;
}

/**
* Returns the supported imap_open() option bitmask for the current runtime.
*/
protected function getSupportedImapOptions(): int
{
$supportedOptions = self::IMAP_OPTIONS_SUPPORTED_VALUES;

if (\defined('OP_XOAUTH2')) {
$oauthOption = \constant('OP_XOAUTH2');
if (\is_int($oauthOption)) {
$supportedOptions |= $oauthOption;
}
}

return $supportedOptions;
}

/**
* Returns the credential that should be passed to imap_open().
*
* @throws ConnectionException
*/
protected function getImapOpenSecret(): string
{
if (!$this->isOAuthEnabled()) {
return $this->imapPassword;
}

if (!\is_string($this->imapOAuthToken) || '' === \trim($this->imapOAuthToken)) {
throw new ConnectionException(['OAuth authentication requires a non-empty access token.']);
}

return $this->imapOAuthToken;
}

/**
* Returns the option bitmask that should be passed to imap_open().
*
* @throws ConnectionException
*/
protected function getImapOpenOptions(): int
{
$options = $this->imapOptions;

if ($this->isOAuthEnabled()) {
$options |= $this->getOAuthImapOption();
}

return $options;
}

/**
* Returns the runtime-specific OP_XOAUTH2 flag.
*
* @throws ConnectionException
*/
protected function getOAuthImapOption(): int
{
if (!\defined('OP_XOAUTH2')) {
throw new ConnectionException(['OAuth authentication requires an ext-imap build with OP_XOAUTH2 support.']);
}

$oauthOption = \constant('OP_XOAUTH2');

if (!\is_int($oauthOption)) {
throw new ConnectionException(['OAuth authentication requires a valid OP_XOAUTH2 ext-imap constant.']);
}

return $oauthOption;
}

/**
* @param string|0 $partNum
*
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/Fixtures/Mailbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,23 @@ public function getImapPassword(): string
return $this->imapPassword;
}

public function getImapOAuthToken(): ?string
{
return $this->imapOAuthToken;
}

public function getImapOptions(): int
{
return $this->imapOptions;
}

public function getImapOpenSecretForTests(): string
{
return $this->getImapOpenSecret();
}

public function getImapOpenOptionsForTests(): int
{
return $this->getImapOpenOptions();
}
}
Loading
Loading