Skip to content
Merged
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
12 changes: 0 additions & 12 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,6 @@ env:
matrix:
fast_finish: true
include:
- php: 5.6
env:
- DEPS=lowest
- php: 5.6
env:
- DEPS=latest
- php: 7.0
env:
- DEPS=lowest
- php: 7.0
env:
- DEPS=latest
- php: 7.1
env:
- DEPS=lowest
Expand Down
12 changes: 10 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@ All notable changes to this project will be documented in this file, in reverse

### Added

- [#95](https://github.com/laminas/laminas-mail/pull/95) adds a new interface, `Laminas\Mail\Header\HeaderLocatorInterface`. This defines classes that will locate header classes based on an email header name.

- [#95](https://github.com/laminas/laminas-mail/pull/95) adds a new class, `Laminas\Mail\Header\HeaderLocator`, implementing `Laminas\Mail\Header\HeaderLocatorInterface`. This is the default implementation of that new interface.

- [#95](https://github.com/laminas/laminas-mail/pull/95) adds the methods `setHeaderLocator(Laminas\Mail\Header\HeaderLocatorInterface $locator)` and `getHeaderLocator(): Laminas\Mail\Header\HeaderLocatorInterface` to the `Laminas\Mail\Headers` implementation. Users are encouraged to use these when providing custom header implementations in order to prepare for a 3.0 release. **The value of `getHeaderLocator()` will now be used as the default mechanism for resolving header names to header classes.**

- [#94](https://github.com/laminas/laminas-mail/pull/94) adds the "novalidatecert" option for POP3 and IMAP connections. When toggled true, you can connect to servers using self-signed certificates.

### Changed

- Nothing.
- [#95](https://github.com/laminas/laminas-mail/pull/95) bumps the minimum supported PHP version to 7.1.

### Deprecated

- Nothing.
- [#95](https://github.com/laminas/laminas-mail/pull/95) deprecates `Laminas\Mail\Header\HeaderLoader` in favor of the new `Laminas\Mail\Header\HeaderLocator` class. The class will be removed in version 3.0.0.

- [#95](https://github.com/laminas/laminas-mail/pull/95) deprecates the `Headers::getPluginClassLoader()` and `Headers::setPluginClassLoader()` methods, in favor of the new `setHeaderLocator()` and `getHeaderLocator()` methods. These methods will be removed in version 3.0.0, and laminas-loader `PluginClassLocator` instances will no longer be supported. Please update your code to use the new `HeaderLocatorInterface` instead.

### Removed

Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
}
},
"require": {
"php": "^5.6 || ^7.0",
"php": "^7.1",
"ext-iconv": "*",
"laminas/laminas-loader": "^2.5",
"laminas/laminas-mime": "^2.5",
Expand All @@ -42,8 +42,8 @@
"laminas/laminas-coding-standard": "~1.0.0",
"laminas/laminas-config": "^2.6",
"laminas/laminas-crypt": "^2.6 || ^3.0",
"laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1",
"phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4"
"laminas/laminas-servicemanager": "^3.2.1",
"phpunit/phpunit": "^7.5.20"
},
"suggest": {
"laminas/laminas-crypt": "Crammd5 support in SMTP Auth",
Expand Down
75 changes: 75 additions & 0 deletions src/Header/HeaderLocator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

/**
* @see https://github.com/laminas/laminas-mail for the canonical source repository
* @copyright https://github.com/laminas/laminas-mail/blob/master/COPYRIGHT.md
* @license https://github.com/laminas/laminas-mail/blob/master/LICENSE.md New BSD License
*/

declare(strict_types=1);

namespace Laminas\Mail\Header;

/**
* Plugin Class Loader implementation for HTTP headers
*/
final class HeaderLocator implements HeaderLocatorInterface
{
/**
* @var array Pre-aliased Header plugins
*/
private $plugins = [
'bcc' => Bcc::class,
'cc' => Cc::class,
'contentdisposition' => ContentDisposition::class,
'content_disposition' => ContentDisposition::class,
'content-disposition' => ContentDisposition::class,
'contenttype' => ContentType::class,
'content_type' => ContentType::class,
'content-type' => ContentType::class,
'contenttransferencoding' => ContentTransferEncoding::class,
'content_transfer_encoding' => ContentTransferEncoding::class,
'content-transfer-encoding' => ContentTransferEncoding::class,
'date' => Date::class,
'from' => From::class,
'in-reply-to' => InReplyTo::class,
'message-id' => MessageId::class,
'mimeversion' => MimeVersion::class,
'mime_version' => MimeVersion::class,
'mime-version' => MimeVersion::class,
'received' => Received::class,
'references' => References::class,
'replyto' => ReplyTo::class,
'reply_to' => ReplyTo::class,
'reply-to' => ReplyTo::class,
'sender' => Sender::class,
'subject' => Subject::class,
'to' => To::class,
];

public function get(string $name, ?string $default = null): ?string
{
$name = $this->normalizeName($name);
return isset($this->plugins[$name]) ? $this->plugins[$name] : $default;
}

public function has(string $name): bool
{
return isset($this->plugins[$this->normalizeName($name)]);
}

public function add(string $name, string $class): void
{
$this->plugins[$this->normalizeName($name)] = $class;
}

public function remove(string $name): void
{
unset($this->plugins[$this->normalizeName($name)]);
}

private function normalizeName(string $name): string
{
return strtolower($name);
}
}
25 changes: 25 additions & 0 deletions src/Header/HeaderLocatorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/**
* @see https://github.com/laminas/laminas-mail for the canonical source repository
* @copyright https://github.com/laminas/laminas-mail/blob/master/COPYRIGHT.md
* @license https://github.com/laminas/laminas-mail/blob/master/LICENSE.md New BSD License
*/

declare(strict_types=1);

namespace Laminas\Mail\Header;

/**
* Interface detailing how to resolve header names to classes.
*/
interface HeaderLocatorInterface
{
public function get(string $name, ?string $default = null): ?string;

public function has(string $name): bool;

public function add(string $name, string $class): void;

public function remove(string $name): void;
Comment on lines +18 to +24
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are why I lumped the bump in PHP version into the patch: scalar type hints, return type hints, nullable types, and void return types. Essentially it is this patch that necessitates the bump in minimum version, which is exactly what the TSC decision was about in that first meeting. 😄

}
95 changes: 85 additions & 10 deletions src/Headers.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
* @license https://github.com/laminas/laminas-mail/blob/master/LICENSE.md New BSD License
*/

declare(strict_types=1);

namespace Laminas\Mail;

use ArrayIterator;
use Countable;
use Iterator;
use Laminas\Loader\PluginClassLoader;
use Laminas\Loader\PluginClassLocator;
use Laminas\Mail\Header\GenericHeader;
use Laminas\Mail\Header\HeaderInterface;
Expand All @@ -30,9 +33,15 @@ class Headers implements Countable, Iterator
const FOLDING = "\r\n ";

/**
* @var \Laminas\Loader\PluginClassLoader
* @var null|Header\HeaderLocatorInterface
*/
protected $pluginClassLoader = null;
private $headerLocator;

/**
* @todo Remove for 3.0.0.
* @var null|PluginClassLocator
*/
protected $pluginClassLoader;

/**
* @var array key names for $headers array
Expand Down Expand Up @@ -119,30 +128,73 @@ public static function fromString($string, $EOL = self::EOL)
}

/**
* Set an alternate implementation for the PluginClassLoader
* Set an alternate PluginClassLocator implementation for loading header classes.
*
* @param PluginClassLocator $pluginClassLoader
* @return Headers
* @deprecated since 2.12.0
* @todo Remove for version 3.0.0
* @return $this
*/
public function setPluginClassLoader(PluginClassLocator $pluginClassLoader)
{
// Silenced; can be caught in custom error handlers.
@trigger_error(sprintf(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @glensc pointed out, this is how Symfony does deprecation notices. They'll get caught in error handlers, but not if unhandled. This prevents breakage in most use cases, but if you log errors, you'll catch them in your logs.

'Since laminas/laminas-mail 2.12.0: Usage of %s is deprecated; use %s::setHeaderLocator() instead',
__METHOD__,
__CLASS__
), E_USER_DEPRECATED);

$this->pluginClassLoader = $pluginClassLoader;
return $this;
}

/**
* Return an instance of a PluginClassLocator, lazyload and inject map if necessary
* Return a PluginClassLocator instance for customizing headers.
*
* Lazyloads a Header\HeaderLoader if necessary.
*
* @deprecated since 2.12.0
* @todo Remove for version 3.0.0
* @return PluginClassLocator
*/
public function getPluginClassLoader()
{
if ($this->pluginClassLoader === null) {
// Silenced; can be caught in custom error handlers.
@trigger_error(sprintf(
'Since laminas/laminas-mail 2.12.0: Usage of %s is deprecated; use %s::getHeaderLocator() instead',
__METHOD__,
__CLASS__
), E_USER_DEPRECATED);

if (! $this->pluginClassLoader) {
$this->pluginClassLoader = new Header\HeaderLoader();
}

return $this->pluginClassLoader;
}

/**
* Retrieve the header class locator for customizing headers.
*
* Lazyloads a Header\HeaderLocator instance if necessary.
*/
public function getHeaderLocator(): Header\HeaderLocatorInterface
{
if (! $this->headerLocator) {
$this->setHeaderLocator(new Header\HeaderLocator());
}
return $this->headerLocator;
}

/**
* @todo Return self when we update to 7.4 or later as minimum PHP version.
* @return $this
*/
public function setHeaderLocator(Header\HeaderLocatorInterface $headerLocator)
{
$this->headerLocator = $headerLocator;
return $this;
}

/**
* Set the header encoding
*
Expand Down Expand Up @@ -270,9 +322,20 @@ public function addHeader(Header\HeaderInterface $header)
*/
public function removeHeader($instanceOrFieldName)
{
if (! $instanceOrFieldName instanceof Header\HeaderInterface && ! is_string($instanceOrFieldName)) {
throw new Exception\InvalidArgumentException(sprintf(
'%s requires a string or %s instance; received %s',
__METHOD__,
Header\HeaderInterface::class,
is_object($instanceOrFieldName) ? get_class($instanceOrFieldName) : gettype($instanceOrFieldName)
));
}
Comment on lines +325 to +332
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit was interesting. Because I turned on strict types in this file, I discovered a test that was passing null to this method, which was incorrect. As such, I added this guard (and updated the test!).


if ($instanceOrFieldName instanceof Header\HeaderInterface) {
$indexes = array_keys($this->headers, $instanceOrFieldName, true);
} else {
}

if (is_string($instanceOrFieldName)) {
$key = $this->normalizeFieldName($instanceOrFieldName);
$indexes = array_keys($this->headersKeys, $key, true);
}
Expand Down Expand Up @@ -481,7 +544,7 @@ public function loadHeader($headerLine)
list($name, ) = Header\GenericHeader::splitHeaderLine($headerLine);

/** @var HeaderInterface $class */
$class = $this->getPluginClassLoader()->load($name) ?: Header\GenericHeader::class;
$class = $this->resolveHeaderClass($name);
return $class::fromString($headerLine);
}

Expand All @@ -496,7 +559,7 @@ protected function lazyLoadHeader($index)
$key = $this->headersKeys[$index];

/** @var GenericHeader $class */
$class = ($this->getPluginClassLoader()->load($key)) ?: Header\GenericHeader::class;
$class = $this->resolveHeaderClass($key);

$encoding = $current->getEncoding();
$headers = $class::fromString($current->toString());
Expand Down Expand Up @@ -528,4 +591,16 @@ protected function normalizeFieldName($fieldName)
{
return str_replace(['-', '_', ' ', '.'], '', strtolower($fieldName));
}

/**
* @param string $key
* @return string
*/
private function resolveHeaderClass($key)
{
if ($this->pluginClassLoader) {
return $this->pluginClassLoader->load($key) ?: Header\GenericHeader::class;
}
return $this->getHeaderLocator()->get($key, Header\GenericHeader::class);
}
}
Loading